UK WAS stage: split private pension wealth out of corporate_wealth (#750) - #755
Draft
MaxGhenis wants to merge 6 commits into
Draft
UK WAS stage: split private pension wealth out of corporate_wealth (#750)#755MaxGhenis wants to merge 6 commits into
MaxGhenis wants to merge 6 commits into
Conversation
) The WAS wealth stage folded WAS private pension wealth less the current-employment defined-benefit component (totalpenr8_aggr - dvvaldbt_scaper8_aggr) into corporate_wealth, and policyengine-uk counts corporate_wealth as capital in every means test; the value of a right to receive a pension under an occupational or personal pension scheme is disregarded capital (UC Regs 2013 Sch 10 para 10 and the HB/JSA/ESA/IS/SPC parallels). On the incumbent artifact 46% of UC-reporting benefit units fail the GBP 16,000 test, 96% of them on corporate_wealth alone (uk-data#452, mechanism M2). The stage now emits private_pension_wealth as its own household output - drawn first in chain segment 2 (the slot the old folded corporate_wealth, 84.7% pension by donor mass, occupied), with the share-like holdings conditional on it and segment 3 conditional on both components - and keeps corporate_wealth to employee shares/options, UK shares, unit and investment trusts and the stocks-and-shares ISA fold. Row identity on the donor: old corporate_wealth == corporate_wealth + private_pension_wealth. The column is not DB-free (retained DB rights and DB pensions in payment stay inside it), hence the name and the documented current-DB exclusion. Lockstep: spec/sources.yaml + source_stages.json (derived, chain_order, outputs, nonnegative_outputs, notes), was_wealth_support_bounds.json regenerated from the pinned tab (new [0, 8000000] entry; corporate_wealth's committed bound unchanged at one significant figure), the export-surface allow-list (UK_ALLOWED_EXTRA_EXPORT_COLUMNS + gates.json, same slot as cash_isa / stocks_and_shares_isa), release_input_coverage_manifest.json regenerated (source_stages.json sha pins + the was_wealth outputs list; 145 required columns unchanged), UK spec_sha256 re-pinned e12a2cb8 -> d8a4ecff, gate-battery digests re-cut (policy 404968fb -> a0090bbf, gates manifest 59c7808d -> 12467bad, fingerprint bfb98736 -> e23ba388). Measurement receipt in experiments/452-uk-pension-wealth-split.md (aggregates only). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
impute_was_wealth reused one RegimeGatedQRF across its three chain segments; RegimeGatedQRF.start_chain respawns the fit and draw streams from the model seed on every call, so the k-th target of every segment consumed the same quantile and sign-gate uniforms per recipient - owned_land coupled with the first segment-2 target and property_wealth with the second, which the pension split makes the countable share-like holdings. On train/hold-out halves of the licensed donor the review measured P(shares > 0 | property_wealth = 0) at 0.011 against 0.055 observed, recovering to 0.045 with distinct per-segment seeds (coupling receipt committed alongside the measurement receipt, aggregates only). The stage now derives one child seed per segment from the declared stage seed (SeedSequence(seed).spawn(3)); the declared fit_weighted_qrf_chain seed stays the root, the result records the segment seeds, and a unit test holds the three roots distinct and deterministic. The spec note documents the behaviour (non-normative: the UK spec_sha256 is unchanged) and the coverage manifest's source_stages.json pins move with it. Re-measured: the UC caseload effect of the split is unchanged (6.268m vs 6.266m UK benefit units at 2025); the fix corrects who holds the countable assets. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Author
|
Local verification log (machine under heavy load from concurrent build lanes, so the full
|
…he union Main moved the same attested surfaces today (#744, #674, #754), so the merge re-cuts them over the union tree in one reviewed step, the d70ea39 pattern: release_input_coverage_manifest.json regenerated (145 required / 0 exclusions unchanged), the UK spec_sha256 re-pinned, and the three gate-battery digests recomputed into the microcosm-data contract and its test mirror. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the reformatted manifest and re-cut the digests over the union Main's #747 reformatted uk/source_stages.json and moved the attested surfaces again, so the merge takes main's file and re-applies the five WAS-stage edits (derived split, chain order, outputs, nonnegative outputs, notes incl. the per-segment seed sentence) in its format, regenerates release_input_coverage_manifest.json (145 required / 0 exclusions unchanged), re-pins the UK spec_sha256 and re-cuts the three gate-battery digests over the union - the d70ea39 pattern, second application. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pt, fixed-draw legal counterfactual, claim scoping Three evidence gaps from the independent sol review, fixed here (part applied by a parallel session on the same verdict, adopted): - The coupling hold-out had derived its child seeds with a modulo-2^31 truncation, so the quoted 0.045 recovery did not validate the shipped tuple. Re-run with was_wealth_segment_seeds itself: P(shares > 0 | no property) 0.011 as-is -> 0.039 with the production seeds (0.055 observed); the unit test now golden-pins the tuple (3757552657, 673228719, 3241444873) and the code comment quotes the validated number. - The +1.073m headline compared shared-seed OLD/NEW redraws, which is a whole-implementation comparison, not a fixed-draw effect, and no matched OLD' exists for the final per-segment-seed branch. The receipt now leads with the review's fixed-draw legal counterfactual: on the final code's own draws, folding the drawn private_pension_wealth back into corporate_wealth on the same rows moves the 2025 UC caseload 4.87m -> 6.27m (+1.40m benefit units; GB +1.33m; spend +11.7bn; eligible +6.9m), with the redraw pair relabelled as the secondary, machinery-inclusive figure. - Donor wording in the spec notes and code comment: DB-type components are SCAPE-valued, money-purchase components are reported fund values; the former-spouse component is "pensions expected from a former spouse or partner". Coverage manifest regenerated over the notes change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he union (third application) Main moved the attested surfaces again (#743 first calibrated UK candidate, #766 CI lane, #764 rename), so the merge re-pins the UK spec_sha256, re-cuts the three gate-battery digests into the microcosm-data contract and its test mirror, and regenerates the release-input coverage manifest over the union - the d70ea39 pattern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Part of #750 (tracking issue for PolicyEngine/policyengine-uk-data#452 mechanism M2). Branched from
origin/mainat 2aa9679; merged up to 2807e99 (#747). Draft — held for Max's go. Model-side companion: PolicyEngine/policyengine-uk#1824 (draft, lands first).What moved
Stage arithmetic (
uk_runtime/was_wealth.py): the WAS stage folded WAS private pension wealth less the current-employment defined-benefit component (totalpenr8_aggr − dvvaldbt_scaper8_aggr) intocorporate_wealth, which policyengine-uk counts as capital in every means test. It now emits that component as its own household output,private_pension_wealth, and keepscorporate_wealthto employee shares/options, UK shares, unit and investment trusts and the stocks-and-shares ISA fold. Row identity on the donor:old corporate_wealth == corporate_wealth + private_pension_wealth.Chain: segment 2 draws
(private_pension_wealth, corporate_wealth_excl_isa, stocks_and_shares_isa)— the pension component first, in the slot the old folded column (84.7% pension by donor mass) occupied, the share-like holdings conditional on it — and segment 3 conditions on both components, so the downstream targets keep the information the folded column carried. Segment 1 (owned_land,property_wealth) is untouched and its draws are identical at seed 0 (verified on the re-imputation receipt).Second commit — one child seed per chain segment (review finding):
impute_was_wealthreused oneRegimeGatedQRF, andstart_chainrespawns the fit/draw streams from the model seed on every call, so the k-th target of every segment consumed the same quantile and sign-gate uniforms per recipient. On train/hold-out halves of the donor the review measured P(shares > 0 | no property) at 0.011 against 0.055 observed; the production child seeds (was_wealth_segment_seeds, declared seed stays the root) recover 0.039 — the receipt was re-run with exactly the shipped derivation after cross-family review caught the first variant validating differently-truncated seeds, and the unit test golden-pins the tuple.Lockstep:
uk/spec/sources.yaml+uk/source_stages.json(derived,chain_order,outputs,nonnegative_outputs, notes);was_wealth_support_bounds.jsonregenerated from the pinned tab (private_pension_wealth[0, 8000000];corporate_wealth's committed bound is unchanged at one significant figure);UK_ALLOWED_EXTRA_EXPORT_COLUMNS+gates.jsonallow-list gainhousehold.private_pension_wealthin thecash_isa/stocks_and_shares_isaslot;release_input_coverage_manifest.jsonregenerated (source_stages.json sha pins and the was_wealth outputs list; 145 required / 0 exclusions unchanged); towncrier fragment; measurement receipts inexperiments/452-uk-pension-wealth-split*.{md,json}(aggregates only).Digests (stated, per the re-pin discipline; re-cut over the union on each merge of main — twice so far, past #744/#674/#754 and past #747, the d70ea39 pattern): currently UK
spec_sha256e3b171bd → e6594bd3; gate-battery policy → 61c1e22c, gates manifest → 409e2aa6, spec fingerprint → a47f0752 (microcosm-datacontract + test mirror). They move again mechanically on the next merge of main.Why this column and this name
Donor aggregates (WAS round 8, weighted £bn): total private pension wealth 4,807.7; current-employment DB (SCAPE) 1,148.7; remainder 3,659.0 = current occupational DC 313.9 + AVCs 12.2 + retained DC 550.0 + current personal pensions 203.7 + retained DB 863.6 + pensions in payment 1,692.4 + expected from a former spouse 23.3 (ten-component identity exact on 100% of rows; remainder never negative). Share-like holdings 659.4. So the folded component is not DB-free — retained DB rights and DB pensions in payment stay inside it — hence
private_pension_wealth(WAS/ONS private pension wealth less current-employment DB, stated first in its documentation), notnon_db_pension_wealth. Every part is "the value of a right to receive a pension under an occupational or personal pension scheme": disregarded capital under UC Regs 2013 Sch 10 ¶10, HB Regs 2006 Sch 6 ¶31–32 / HB(SPC) Sch 6 ¶24, JSA Regs 1996 Sch 8 ¶28–29, ESA Regs 2008 Sch 9 ¶28–29, IS Regs 1987 Sch 10 ¶23–23A, SPC Regs 2002 Sch V ¶22–23 (all fetched).Licensed acceptance (aggregates only; scripts outside the repo)
The fixed-draw legal effect (cross-family review design; the causal number for the disregard): on the final code's own draws (per-segment seeds, seed 0, 100 trees,
enhanced_frs_2024_25.h5v1.56.16, weights unchanged), folding the drawnprivate_pension_wealthback intocorporate_wealthon the same rows and recomputing UC at 2025 gives 4.87m benefit units counted vs 6.27m disregarded — +1.399m UK (+1.329m GB), spend +£11.7bn, eligible benefit units +6.90m, reporter records over £16k −1,453; GB elements +0.82 / +0.35 / +0.06 / +0.17 / +0.61m (housing / LCWRA / carer / childcare / child); anchored +1.456m. Every draw identical; only the capital-test classification differs.Whole-implementation comparison (secondary; the pre-fix shared-seed redraw pair — OLD and NEW independently fitted and drawn, so machinery and classification move together, and no matched OLD′ exists for the final branch):
With per-segment seeds (NEW′): 6.268m, eligible 15.57m, reporters over £16k 1,742. With FRS reporters anchored: OLD 5.22m → NEW 6.34m. The incumbent's own upper bound (counterfactual C,
corporate_wealth→ 0) is 6.88m. Defensible quotation for the redraw pair: at seed 0, with shared-seed microcosm redraws and unchanged incumbent weights, the old-stage-to-split-stage implementation moved the modelled 2025 UC caseload from 5.19m to 6.27m — not a fixed-draw legal effect (the table above is), not a recalibrated estimate, and not a national forecast.Allocation-key check on the NEW draw:
corporate_wealthalone puts 84–87% of its mass in the top 10% of households (27–29% in the top 1%);corporate_wealth + private_pension_wealth54% / 14% — why the model keeps the preserved key for shareholding, corporate land value and the employer-NI response.Caveats that travel with the numbers (full list in the receipt): weights not recalibrated (the UK surface now compiles a UC caseload target, #735); OLD is this package's draw, not the incumbent's (continuous-quantile full-leaf draws vs microimpute's 10-point grid; released-h5 predictor surface) — the OLD re-draw sits 1.15m below the incumbent artifact on the same households, a machinery difference registered on #750;
private_pension_wealthis engine-unknown at policyengine-uk 2.91.0.Gates
uk_input_mass_parity:corporate_wealthwill report about −0.73 to −0.85 against theefrs-post-calibrationreference (pension mass moved to a column the reference lacks); inside the armed tolerance 4.52, so no exclusion receipt (adding one would itself fail as stale).uk_export_surface: allow-list entry is the sanctioned mechanism; note the gate isevidence_absentin production builds at this pin (#686).uk_nonnegative_columns/uk_supportpick the new column up fromnonnegative_outputsand the regenerated bounds. Coverage manifest: required set unchanged (the new column is candidate-only).Sequencing and engine coupling
efrs-post-calibrationreference (re-pinning before step 2 would breach the input-mass gate oncorporate_wealth). Before any national build or calibration on a split dataset, bump theukextra lock to the policyengine-uk release carryingprivate_pension_wealth: the UK targetsons.land.corporate_land_value/land_valueare materialised through the engine's key, and an older engine drops the column and narrows it.Tests
Licensed round-trip (
test_was_support_bounds_round_trip_against_licensed_tab) run locally with the pinned tab.Follow-ups registered on #750 by the cross-family review
Raise the
ukextra floors (policyengine-uk>=) and re-lock once the #1824 release exists, before any split build or calibration; add a composite mass audit (old corporate wealth vs new corporate + pension) so the input-mass gate can distinguish the intended transfer from pension-mass loss; multi-seed / recalibrated results before any forecast-like use of the impact.Out of scope
Current-employment DB entitlements as their own column (
current_db_pension_wealth, would changetotal_wealth); the cash-ISA gap (counted nowhere as capital); the WAS-stage realisation variance and the OLD-vs-incumbent machinery gap; the same-seed-per-segment pattern in the E6 chains — all registered on #750.Authored by Claude (Fable) from the #452 diagnosis; adversarial verification by six refuters (statute, chain, model consumers, gates, measurement, completeness) whose blocking findings — coverage-manifest regeneration, gate-battery digest re-cut, the allow-list constant lockstep, the per-segment seed coupling — are all in this branch.
🤖 Generated with Claude Code