UK WAS stage: split private pension wealth out of corporate_wealth (#750) - #755
Draft
MaxGhenis wants to merge 3 commits into
Draft
UK WAS stage: split private pension wealth out of corporate_wealth (#750)#755MaxGhenis wants to merge 3 commits into
MaxGhenis wants to merge 3 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>
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 c7be819. 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; per-segment child seeds (SeedSequence(seed).spawn(3), declared seed stays the root) recover 0.045. Unit-tested; the UC caseload effect of the split is unchanged by it.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 after merging main past #744/#674/#754, the d70ea39 pattern): UK
spec_sha2568bf62b6e → d44cfeaa; gate-battery policy 5cb072a0 → 31a74493, gates manifest c5123517 → 932e039b, spec fingerprint 23cf63b6 → 538eae1a (microcosm-datacontract + test mirror).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)
WAS wealth block re-imputed on every household of
enhanced_frs_2024_25.h5(uk-data 1.56.16) with this stage code at seed 0 / 100 trees, OLD arithmetic vs NEW, swapped into the dataset before build, UC at 2025 with policyengine-uk 2.91.0, weights unchanged: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.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.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