Port the WAS wealth imputation and regional property uprating as declarative source stages - #714
Conversation
9c24021 to
6fb284d
Compare
|
Adversarial review (Codex, 2026-08-19) dispositioned at 3b79074 — both findings fixed:
Full 🤖 Generated with Claude Code |
|
E5 licensed acceptance run COMPLETE (2026-08-19, evidence in Determinism & provenance — twin builds payload-identical, shares byte-identical; a third clean-tree build from the committed Parity vs the incumbent — parity or improvement, with donor-truth evidence:
🤖 Generated with Claude Code |
|
All four adjudications implemented at 274ae2b (María, 2026-08-19):
Post-adjudication state: twins rebuilt with the recode, all receipts green (seed-0 refit matches stored bitwise; identity receipt PASS; licensed bounds round-trip byte-identical), full suites + ruff green, evidence receipts updated. CI running on 274ae2b. 🤖 Generated with Claude Code |
…arative source stages E5 of the UK migration (#145, epic #665). Adds the was_wealth stage (13 outputs: the incumbent 11 plus the #180 ISA split) with a seeded weighted RegimeGatedQRF chain mirroring the incumbent's sequential conditioning, the fold_into corporate-wealth back-compat, donor-support clipping, the id-keyed student-loan waterfall (fixes the incumbent off-by-one), and the deterministic regional_property_uprating stage from the committed regional_land_values.json resource. New generic operation kinds: fit_weighted_qrf_chain, allocate_within_group_waterfall, uprate_to_regional_reference. New uk_support gate with a placeholder bounds resource pending licensed regeneration; export-surface ISA allowances; gate-battery fingerprints re-cut; coverage manifest families added; default-off QRF model cache in microcosm-fit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…udit WAS fits Addresses both adversarial-review findings. The uk_support bounds resource is now generated from the pinned licensed WAS household tab (sha-bound via source.tab_sha256; CI asserts the committed resource records the exact pin and carries no placeholder wording; a licensed-only round-trip test enforces byte-identity where the tab exists). The WAS QRF chain now emits one FitWeightRecord per fit, and the national build aggregates fit-weight evidence from every fitting stage — keyed by declared stage name or exposed records, so a hollow transform fails the weights audit rather than reporting a named absence, and one stage's records cannot mask another's missing evidence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rfall and stored columns The e5 permutation-identity receipt failed on the licensed spine: the regional uprating factor divided by a row-order-dependent float mean. The mean now sums sorted values, making the rewrite bitwise stable under row permutation. The receipt additionally re-runs the student-loan waterfall from reconstructed household balances (the allocation conserves mass), cross-checks recomputation against the stored columns, and fails the run unless both hold; arithmetic layers compare at one float rounding generation of tolerance, documented in the receipt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ds rationale Implements María's 2026-08-19 adjudications on the E5 licensed run. The licensed audit of all 33 used WAS columns found UKDS negative sentinels in exactly two nonnegative-domain columns — vcarnr8 (2 rows) and HBedRmR8 (95.8%: the bedrooms question is effectively unasked in the household tab) — now recoded to zero before fitting (signed difference; the incumbent trains on raw sentinels; measured post-recode deltas are within realization noise, the largest being student_loan_balance +12% toward the register). DVPriRntR8's -9 is structural not-applicable and stays. owned_land gains a reviewed input-mass-parity exclusion expiring 2026-09-19, evidenced by the seed-stability receipt; the end-of-workstream revisits (exclusion renewal-or-remedy, num_bedrooms predictor quality) are registered on #145. Support bounds regenerated from the recoded cleaning (num_vehicles floor now 0) with the accepted SDC rationale recorded in the resource; input-mass evidence pin re-cut in both mirrors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…es disambiguated Manifest order after the merge: frs_brma -> was_wealth -> regional_property_uprating -> the three SPI-channel stages -> the certified pair at the tail, honoring the donor-inheritance rule that the SPI stacking must see the wealth-imputed FRS rows at E10. The WAS donor artifact role becomes was_qrf_donor (the #717 input-pin collector keys by role and the SPI donor already claims qrf_donor); E7's pin test now checks both donor artifacts. Coverage manifest regenerated for the merged roster; stage-count and resource-tuple expectations updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The merged roster stacks 10k SPI-synthetic households after the E5 stages, with channel-imputed property values the E5 uprating never saw; the deterministic-layer identity claims apply to the household_is_spi_synthetic == False population the wealth and uprating stages actually transformed. The stacked rows are E7's receipt surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Rebased over merged #717 (tip
Full suites + ruff green locally; CI restarting on the rebased tip. 🤖 Generated with Claude Code |
274ae2b to
7185129
Compare
vahid-ahmadi
left a comment
There was a problem hiding this comment.
Reviewed the same way as E2–E4: every derivation against the incumbent's wealth.py/regional_property_uprating.py, plus the resources and both adjudication rounds. 258 tests pass for me with the uk extra.
Signed difference 2 is confirmed in the incumbent's code, and it should go upstream. At wealth.py:346 the incumbent passes output_df[column].clip(lower=0) to the allocator — output_df comes straight from model.predict and carries a 0-based RangeIndex — while _allocate_student_loan_balance_to_people iterates .items() treating that index as person_household_id and looks it up in a groupby keyed by real 1-based sernums. Every household receives the balance predicted for its positional neighbor, and the first household's prediction is dropped. This is live in production (and the incumbent's own test_student_loan_balance.py evidently doesn't catch it, since the allocation tiers behave plausibly on whichever balance arrives). Like the EDUCQUAL inversion, this deserves a policyengine-uk-data issue with your line citations — that's now two production defects this migration has found by porting carefully, and the upstream repo shouldn't have to rediscover them at its next release. The HBedRmR8 finding belongs in the same issue: 95.8% of the round-8 donor rows carry −8 in a QRF predictor the incumbent trains on raw, which means the incumbent's bedrooms predictor is effectively a corrupted constant.
Verified faithful against the incumbent: the corporate_wealth composition (your excl-ISA components + stocks_and_shares_isa fold reproduces the incumbent's five-component sum exactly, with the fold ordered before segment 3 so later outputs condition on the total, preserving the incumbent's chain semantics); the segmented chain state accumulating drawn targets within each segment (sequential conditioning intact); the recipient-side NORTHERN_IRELAND → WALES remap; the allocation tier cascade with repayment-proportional split at tier 0; and the uprating design (per-region HPI ratio on owner-occupier means, property_wealth moved proportionally, the unweighted means honestly signed as a port-as-is quirk). The exact-match cleaning replacing the r↔w fuzzy fallback — with DVCISAVR8_aggr resolved against the real header where #180's bare name didn't exist — is the coded-domain checklist rule doing its work at ingest.
The adjudication rounds are the strongest part. María's sentinel ruling is well-evidenced in both directions: the audit measured all 33 used columns rather than spot-checking, the recode's effect is bounded against the seed-realization noise band (owned_land −3.9% vs 35% noise), and the one material move (student_loan_balance +12%) goes toward the register. The post-rebase receipt — E5 stage draws byte-stable across the #717 rebase, adjudicated acceptance numbers reproducing exactly once the instruments scope to household_is_spi_synthetic == False and restore the declared grossing scale — is precisely the composition-aware re-verification the 16-stage roster needed. The was_qrf_donor role rename resolving the #717 pin collision, with E7's pin test verifying both, closes the coordination surface cleanly.
Two small things, neither blocking:
support_clip_to_donorsilentlycontinues when an output column is missing from the donor frame. The bounds resource and its round-trip test make that unreachable today, but everything around it fails loud on missing columns — oneraisewould keep the posture uniform.- In
run_segment,recipient_base = pd.concat([recipient_encoded.loc[:, list(base_predictors)]], axis=1)is a one-element concat — just a copy wearing a disguise;recipient_encoded.loc[:, list(base_predictors)].copy()says what it does.
Carried over from #717 (merged before my early review's items were addressed — all non-blocking, so no complaint about the merge, but they shouldn't evaporate): the or WeightKind.IMPORTANCE silent default in the support-channel transform, the last_result/resume invariant comment, the unexplained stage-2 seed + 1 convention, and the open question of whether the licensed run settled the incapacity_benefit_reported promotion or deferred it to E10. A one-commit sweep here or a note on #145 would close all four.
Ready from my side once the upstream issue is filed (or explicitly declined with a reason on the record).
|
The upstream condition from my review is satisfied — I filed it myself rather than leave it on your plate: policyengine-uk-data#463 covers both the |
…thout the package name Main's exclusion-register test pins the entry-name set (now includes owned_land), and its live-tree hygiene scan forbids the incumbent data package's full name anywhere outside the sha-locked launch references - the exclusion reason now cites the archived repo as uk-data#448. The reason text stays byte-identical between the committed resource and the contract mirror; the input-mass evidence digest re-cut to df745569. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same pattern as rounds 2-4: two new typed resources (regional_land_values.json, was_wealth_support_bounds.json), stage manifest regenerated (18 stages), closed variants for the six new kinds (fold_into, allocate_within_group_waterfall, fit_weighted_qrf_chain, uprate_to_regional_reference, private_microdata, public_aggregate_reference) plus derive's mapping-driven form, base_candidate's size_bytes/runtime flag made optional (the WAS candidate row omits them), UK pin refreshed (e3ffbb45 -> 6b7bdc39). Identity batch + lint green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…kept The country-package conflict is additive on both sides: main's WAS bounds + regional land values stay in main's ordering, this PR's uk_national_targets.json + target_references.json append after the census row, and the #717 resource-tuple test carries all four. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Workstream E5 of the UK migration (#145, master epic #665). Closes #681. Closes #180. Originally stacked on #709 (E4); rebased onto main after #709 and #706 merged, now based on main. Supersedes stranded draft #181 (its WAS column mappings and citations are salvaged here; its pre-Frame code is not).
What
Ports the incumbent WAS round-8 wealth layer as two declarative source stages appended after
frs_brma:was_wealth(household) — WAS round-8 household-tab donor (sha+size-pinned, UKDS SN 7215, DOI 10.5255/UKDA-SN-7215-20, caller-supplied local input), strict lower-case exact column matching (no fuzzy r↔w fallback), the incumbent's 11 imputed outputs plus the UK: surface cash ISA and stocks & shares ISA holdings in the wealth imputation #180 ISA split (cash_isa←DVCISAVR8_aggr,stocks_and_shares_isa←DVIISAVR8_aggr), a seeded weightedRegimeGatedQRFcheckpointed chain mirroring the incumbent's sequential conditioning (output i conditions on outputs 1..i−1;corporate_wealthdrawn ascorporate_wealth_excl_isa+stocks_and_shares_isaand folded before later outputs condition on it;cash_isaappended last so the 11-chain conditioning is untouched), donor-support clipping, integernum_vehicles, recipient-side NORTHERN_IRELAND→WALES region remap (prediction only), and the id-keyedallocate_within_group_waterfallforstudent_loan_balance→ person.regional_property_uprating(household, deterministic,rewritesonly) — per-region factoravg_house_price / unweighted_mean(main_residence_value | owner)applied tomain_residence_valueandproperty_wealthfrom the committedregional_land_values.jsonresource (MHCLG dwellings + ONS UK HPI Dec 2025; regeneratortools/build_uk_regional_land_values.pyfrom the public CSV fixture). NI has no reference row and is never scaled (port-as-is).Supporting: three new generic operation kinds (
fit_weighted_qrf_chain,allocate_within_group_waterfall,uprate_to_regional_reference) plus first semantics for the reservedfold_into; theuk_supportgate (release-blocking) against a committed disclosure-safe bounds resource;uk_export_surfaceallowances for the two ISA columns (no policyengine-uk model variables yet — policyengine-uk#1791 tracks); gate-battery fingerprints re-cut; coverage-manifest families for both stages (the 11 wealth columns were alreadyrequired); default-off metadata-keyed QRF model cache inmicrocosm-fit; acceptance instruments (e5_identity_receipt, household-grain weighted-integrity extension, support-bounds builder).UK-specificity note (required by #681)
Generic contributions: the three operation kinds,
fold_intosemantics, the QRF chain usage pattern, the model cache, and thesupportgate binding pattern live in shared modules/vocabularies. UK-specific: WAS column semantics and cleaning, the twobuild/uk/resources, the twouk_runtimetransforms, and the gate declarations. The incumbent package name appears nowhere in the tree.Signed differences
R8xshhwgt) RegimeGatedQRF chain replaces the incumbent's microimpute QRF (implicit seed 42, unweighted fit, per-output re-seeded RNG giving perfectly correlated quantile ranks across the 11 outputs). Parity is weighted-distributional only. The weighted fit is adjudicated in WAS wealth imputation with the ISA split, plus regional property uprating #681; incumbent seeds recorded as documentation.student_loan_balanceallocation off-by-one fixed (defect class): the incumbent looks up 1-based sernums in a 0-based RangeIndex (wealth.py:238–241), assigning each household its neighbor's balance and dropping household 1's. Ours allocates by entity id; a regression test pins non-positional alignment.corporate_wealthas component-sum: drawn as excl-ISA + S&S-ISA and folded, per the UK: surface cash ISA and stocks & shares ISA holdings in the wealth imputation #180/WAS wealth imputation with the ISA split, plus regional property uprating #681 split adjudication; later chain outputs condition on the folded total, preserving incumbent conditioning semantics.num_vehiclesinteger draw (rint + clip ≥0) replaces microimpute's incidental numeric-categorical RandomForestClassifier conditional-mode path.DVCISAVR8_aggrresolved against the real tab header (the bareDVCISAVR8named in UK: surface cash ISA and stocks & shares ISA holdings in the wealth imputation #180/UK: household-wealth imputation stage with cash / S&S ISA split #181 does not exist).fillna(0); GOR code 3 absent from the region map; unweighted uprating means; NI unscaled by the uprating; the WAS↔FRS predictor definitional mismatch (FRS-side predictors materialize through the rules-engine adapter with person/benunit values summed to household, the incumbent'smap_to="household"semantics).vcarnr8: 2 rows of −8;HBedRmR8: 95.8% −8 — the bedrooms question is effectively unasked in the WAS household file). The incumbent trains its QRF on the raw sentinel values.DVPriRntR8's −9 is structural not-applicable, so theis_rentingmapping is unchanged; genuinely negative domains (net financial wealth, losses) are never recoded. Post-recode deltas measured within realization noise (adjudicated in-PR, María 2026-08-19).Licensed acceptance — COMPLETE and adjudicated
Full run 2026-08-19 (evidence
data/ukds/acceptance/e5/, receiptacceptance_receipt.json; summary in the PR comments): twin builds + a clean-tree third build payload-identical; permutation-identity receipt PASS; out-of-build seed-0 refit reproduces all 13 stored columns bitwise; method-class parity-or-improvement vs the incumbent evidenced by the donor benchmark (the incumbent's correlated-rank draws inflate skewed columns 3.3–4.8× over WAS donor truth; ours 1.3–3.3×). Register gaps adjudicated as composition/calibration class (binds at E10).Adjudications (María, 2026-08-19): (1) support-bounds SDC treatment accepted — one-significant-figure outward rounding, rationale incl. the top-code caveat recorded in the resource; (2) WAS sentinel audit → recode-to-zero for the two nonnegative-domain columns carrying sentinels (
vcarnr82 rows;HBedRmR895.8% — bedrooms effectively unasked; predictor-quality revisit on #145), measured in-PR per the size rule (deltas within realization noise; signed difference 7); (3)owned_landreviewed input-mass-parity exclusion granted with a one-month expiry (2026-09-19) — the gate fails the expired entry until renewed — with the end-of-workstream revisit registered on #145; (4) the tail-attenuated columns (savings/other_residential/owned_land) signed as method-class differences: E5 is not required to reproduce the incumbent's inflated totals.Merge coordination with #706 — RESOLVED
#706 merged first, so this PR applied the agreed second-lander recipe on rebase: the gate entries are unioned (#706's re-baselined weight-ratio/input-mass/tail-concentration parameters kept intact; this PR adds
uk_supportand the two ISA export allowances) and the three_UK_GATE_BATTERY_*digests were re-cut from the producer payload (test_gate_battery_contract_pinsdriving), with both contract mirrors updated in lockstep.Implementation notes
Codex implemented under
/codex-implement-plan; one recorded deviation (the placeholder support bounds, covered above). Claude review added two fixes pre-commit, both in the class PR CI cannot see: recipient predictors are now aggregated to household grain by native entity (num_adults/num_childrenare benunit, the four income predictors person — the direct engine arrays would have crashed the licensed build; the fake-engine tests now serve native-grain arrays with entity metadata), and region is one-hot encoded jointly across donor and recipient (matching the incumbent's dummy encoding and thehmrc_spi_incomeprecedent) instead of an ordinal code.🤖 Generated with Claude Code