Conversation
The second-stage QRF draws each SPI-donor person's benefit reports from age, gender, region and incomes, with no view of the benefit unit or of health. Those reports then act as existing claims in policyengine-uk. The take-up anchors, receives_benefits_in_own_right and ssmg_reported were also left as the FRS donor's. On SPI-donor rows, this zeroes the income-related awards (UC, Pension Credit, Housing Benefit, CTR, IS, tax credits, income-related ESA and JSA, SSMG), the out-of-work benefits (contributory ESA and JSA, incapacity benefit, SDA) and Child Benefit, whose only use is the take-up anchor. It then rebuilds the anchors and receives_benefits_in_own_right from the rows' own reports. The zeroed columns stay in the QRF chain, so the values of the reports kept do not change. Also adds hypothesis as a dev extra, for the property tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efit flag Review of 96672af (subfleet 20261002-011737-spi-514-review): - IIDB, AFCS and bereavement support follow an injury, service or a death, not income, and the QRF drew them at 6.2, 2.4 and 4.6 times the FRS rate by weight. SPI rows now take the donor's own values. - The Child Benefit take-up flag keeps the donor's value: the award does not depend on the replaced incomes and the claim is for the same children. Only the UC and Pension Credit flags are redrawn. - The comment no longer says the model's means test replaces the zeroed income-related reports: in policyengine-uk 2.93.0 housing benefit, CTR, IS, tax credits and income-related ESA and JSA need a report, so SPI rows no longer get them. Evidence is now cited by weight, and Child Benefit counts 16-19 qualifying young people. - One assign_reported_takeup helper serves create_frs and the SPI rows, so the rate and the anchoring rule have one source. The SPI draws no longer depend on which columns are present. - Tests pin the three rule sets, use shuffled, gapped ids and check the rebuilt flags against reports that are not zeroed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e year and the UC redraw Round-2 review of eb14fd0 (subfleet 20261002-041419-spi-514-review-r2, APPROVE): the FRS comparison for ESA (contributory) rested on under 10 records; the take-up rates' year and the stage-two UC redraw had no direct test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
25 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.
Draft. This changes the published dataset, so merging it is a data release and Max's call. No dataset was uploaded or released from this branch.
Problem
impute_incomestacks a 10,000-household copy of the FRS (household_is_spi_synthetic), replaces its incomes with SPI draws, and calibration gives those rows weight. On the main build (b45c373, policyengine-uk 2.93.0), they hold 8.62m of 31.09m households (27.7%).Their benefit columns come from two places:
impute_frs_only_variablesfills every*_reportedcolumn inFRS_ONLY_PERSON_VARIABLES, drawing each person's reports from their age, gender, region and six incomes. It sees nothing of their benefit unit (partner, children, rent, capital), their health or their history.create_frsbuildswould_claim_uc,would_claim_pcandwould_claim_child_benefit(anchored on the donor's reports),receives_benefits_in_own_rightandssmg_reportedbefore the SPI rows exist.policyengine-uk 2.93.0 treats a positive report as an existing claim, in three ways:
housing_benefit_eligible,income_support_eligible,is_WTC_eligible,is_CTC_eligible,esa_income_eligible,jsa_income_eligible,sdaandssmg.would_claim_housing_benefit,would_claim_IS,would_claim_WTC,would_claim_CTCandwould_claim_council_tax_reduction. Each isclaims_all_entitled_benefits | report > 0.claims_all_entitled_benefitssums reports over the whole simulation, so it is False in any dataset run.esa_incomeandjsa_income: the reported award less tariff income;council_tax_benefit, where there is no scheme;esa_contrib,jsa_contrib,incapacity_benefit,iidb,afcsandbsp, byadds.What that does on the main build (b45c373)
Data file (2024-25), calibrated weights:
receives_benefits_in_own_rightdisagrees with their own reports, weightedSPI report rates by weight, relative to FRS rows:
Benefits paid on SPI rows, policyengine-uk 2.93.0, 2025:
Change
apply_spi_donor_benefit_rules(imputations/frs_only.py) runs on the SPI rows after the QRF draw. It runs before the disability categories and flags are derived, because ESA, IIDB and AFCS feedis_disabled_for_benefits.Reports zeroed on SPI rows:
active.yaml). Pension-age HB on SPI rows was £0.03bn. CTR is the one that matters; see "Measured with real builds" and the follow-up. SSMG needs one of these awards and was a donor copy.Donor values restored: IIDB, AFCS and bereavement support. They follow from an injury, military service or a death, not income, and the QRF over-draws them (6.2x, 2.4x and 4.6x by weight). Nothing calibrates them.
Draws kept:
Flags rebuilt from the rows' own reports:
would_claim_ucandwould_claim_pcare redrawn withassign_reported_takeup, the helpercreate_frsnow uses too. With no reports, every SPI unit draws at the take-up rate, because whether a synthetic family claims a means-tested benefit at its imputed income is unobserved. The draws use their own seed (101;create_frsuses 100) and don't depend on which columns exist.would_claim_child_benefitkeeps the donor's flag. The award doesn't depend on the replaced incomes, and the donor's claim is for the same children.receives_benefits_in_own_rightis rebuilt from the rows' own UC, JSA and ESA reports. That makes it False on every SPI row, so SPI 16–19-year-olds can count as qualifying young people.The QRF chain is unchanged. Every column stays in it, so the kept draws are bit-identical, and a test checks this. The cost is that the kept values were drawn alongside values that are later zeroed or restored.
create_frskeeps its draw order, so the base FRS doesn't change; the builds below confirm it table by table.Other effects of zeroing that follow from this change:
Invariants (tested; Hypothesis for 2–6)
receives_benefits_in_own_rightmatches the person's own reports. This is checked with those reports left unzeroed, so the test can fail.impute_frs_only_variables:I broke the code in seven ways, and each failure was caught:
receives_benefits_in_own_rightnot rebuilttest_frs_only_reported_values_correlate_with_training_patternnow checks the QRF's income gradient on carer's allowance, because UC is zeroed on these rows.Measured with real builds
Two production-settings builds (512 epochs,
PE_UK_DATA_OA_CLONES=1), measured with policyengine-uk 2.93.0 (the lock):Both builds served every target download from the same frozen cache (0 misses). Scripts and outputs are in
analysis/of the evidence folder (local; the h5 files are FRS-derived).The base FRS is unchanged. All three tables are identical column by column (34,966 people, 18,850 benefit units, 16,288 households). The h5 checksums differ only in file metadata.
Coherence on SPI rows, after:
receives_benefits_in_own_rightdisagrees with their reports (before: 2.48m weighted).Benefits, 2025:
2026 moves the same way; for example, CTR is −£0.47bn and UC −£0.22bn.
Headline:
SPI rows' share of household weight fell from 8.63m to 8.20m: calibration moved weight onto FRS rows to carry what SPI rows no longer report.
What to read into these:
Interactions
would_claim_ucincreate_frs. Whichever of Calibrate pension-age Housing Benefit to DWP's GB figures #490 and Stop SPI-synthetic rows carrying benefit claims nobody observed #514 merges second must apply the same exclusion to the SPI redraw, or Calibrate pension-age Housing Benefit to DWP's GB figures #490'stest_built_dataset_has_no_pension_age_uc_claimantsfails. This is noted on Calibrate pension-age Housing Benefit to DWP's GB figures #490.would_claim_pcfor every unit after imputation, filling SPI rows like non-reporters. So it supersedes this PR's PC redraw, and itsspi_synthetic_benunitsexclusion becomes redundant.council_tax_benefit_reportedon SPI rows by taking it out of the QRF chain. This PR zeroes it after the draw instead. Build council_tax before council tax reduction; stop imputing reported CTR on SPI rows #499's test asserting that the zeroed and imputed lists don't overlap contradicts this design, so whichever merges second drops that assertion. Both addhypothesis>=6.168.3the same way.Not in this PR
employment_status,hours_workedand the ESA/JSA proxy flags built from them, which can contradict the imputed incomes.ssmg_reportedis annualised ×52 on a one-off grant; that's harmless, because only> 0is read.Reviews:
REQUEST_CHANGES, addressed in eb14fd0.axiom: n/a: dataset construction in uk-data; no policy rule encoded.
🤖 Generated with Claude Code