Repository navigation
Conversation
Calibration fits grouped targets; these checks measure what the groups hide, on the built dataset. Each PR build and its base build report person-weighted equivalised income medians, means and Gini, and relative low income for everyone, children, working-age adults and pensioners, against DWP's HBAI FYE 2025 estimates and 95% intervals (tables 8a and 8b, read from the published ODS files), at period 2024. They also report people by sex and single year of age against the ONS 2024-based projection at the calibration year. The comparison job adds them to the PR comment and flags a measure that leaves its interval; it fails only on a broken or mismatched report until the same-code spread is measured (#598). On uk-data 1.58.1, 5 of 14 HBAI measures are outside their intervals (median AHC, both Gini coefficients, child low income BHC and AHC), and single ages 0 to 79 are off by 19.5% on average. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 15, 2026
Sol review of #600: - Negative incomes before housing costs are reset to zero, and income after housing costs is derived from the adjusted figure (DWP HBAI methodology FYE 2025, "Negative incomes"). Weekly amounts use 365.25 / 7, as the FRS loader annualises them. - A report missing any reference measure, or with a missing or non-finite single-age summary, fails the comparison. - A missing distribution report fails the job. On uk-data 1.58.1, four of 14 measures are now outside their intervals (both Gini coefficients, child low income BHC and AHC); median AHC moves inside. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # .github/workflows/pull_request.yaml # policyengine_uk_data/tests/test_fit_report.py
hbai_measures divided by a zero group weight, which warned and gave NaN. It now returns None for an empty group, the report marks it outside its interval, and compare fails on it as missing or not finite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # .github/workflows/pull_request.yaml
…son (delta review of #600) The measurement downloads the ONS projection, so it now runs after the test suite with if: !cancelled(): an ONS outage no longer skips the tests and the fit summary. A crashed distribution comparison writes a FAIL line into the comment, and the final error names both checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…view 2 of #600) The head's distribution measurement now has continue-on-error: a missing report already fails the comparison, so an ONS outage no longer turns the test job red. A missing report gets its own FAIL heading in the comment. A test pins the step order and conditions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 15, 2026
…ggregates independent (review of #602) A crashed aggregate comparison now sets the failure status first and writes its own FAIL heading; a missing result says so under a heading too. The base build's aggregate steps run whatever its distribution measurement did. A measurement that writes no result warns instead of failing at cp, and the final error names every check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eview of #600) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 16, 2026
…iring (delta review of #602) - The tool pin moves to 5d5558ed, which refuses any non-index install and fingerprints the model before the simulation is built. - The head's tool checkout and aggregate measurement have continue-on-error: a missing result fails the comparison instead. - A test pins one tool commit across all three checkouts, !cancelled() on every aggregate step, and the crash fallback's status line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
59 of 80 tasks
This was referenced Oct 17, 2026
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.
Stacked on #599 (the base build and fit comparison), so its base is
w3-fit-comparison; retarget tomainonce #599 lands. Do not merge on its own. Once approved on its gates, this lands with the d833 uk-data batch on Max's go. It is part of workstream W3 of the Autumn Budget 2026 CI plan: the single-age and HBAI checks.Why
Calibration fits grouped targets: five-year national sex-age bands, ten-year regional age bands, income bands and totals. These checks measure what the groups hide, on the built dataset:
What changes
policyengine_uk_data/utils/distribution_validation.pycovers two areas.HBAI, at period 2024, which is FRS 2024/25 and HBAI FYE 2025. It measures:
Each is compared with DWP's estimate and 95% confidence interval. These are in
targets/reference_data/hbai_fye_2025.yaml, taken from the publisheduncertainty-hbai-2024-25-tables.ods, tables 8a and 8b. The independent review checked all 42 numbers against the tables; the tests spot-check three.This is not an exact reproduction of HBAI. DWP adjusts incomes within the year for when each household was interviewed ("in-year deflation"), and adjusts "very rich" households to HMRC's survey of personal incomes. The second affects means and the Gini coefficient, not the median-based statistics. Neither is applied here. The FRS loader also clips self-employment losses at zero (
datasets/frs.py,np.maximum(0, person.seincam2)), which HBAI keeps. So low-income households with losses can look richer here, which bears on the rates below 60% of the median.Single ages, at the calibration year. It measures weighted people by sex and age 0 to 79, plus 80 and over, against the ONS 2024-based projection the calibration targets come from. Ages from 80 up are one band because the dataset's oldest records fall in 80–84 (#593).
In CI (
.github/workflows/pull_request.yaml):testjob and Compare each pull request's calibration fit with its base build's #599'sbase-buildjob each measure their built dataset;distribution_report.json,base_distribution_report.json), which holds aggregates only. With Compare each pull request's calibration fit with its base build's #599, those reports and the fit summaries are the only artifacts. The raw calibration logs stay on the runner;fit-comparisonjob compares the two and adds the result to the PR comment;continue-on-error. It downloads the ONS projection, so an ONS outage can't skip the tests or the fit summary, or turn the test job red. The missing report fails the comparison instead, under its own FAIL heading;The comparison flags a measure that leaves its interval between base and head. It fails only on a broken, mismatched or missing report. That includes a report missing because the ONS projection couldn't be downloaded. The same-code spread of these measures is unmeasured (#598), so a no-worsening allowance can't yet be set on evidence. These runs measure it.
What it finds on uk-data 1.58.1
Measured on the released
enhanced_frs_2024_25.h5with policyengine-uk 2.122.2.Four of 14 HBAI measures are outside their 95% intervals:
The medians are inside their intervals: BHC 732.08 against 719.48, and AHC 636.65 against 623.14 (upper bound 637.07). The +8.4% BHC gap #487 found on 1.56.16 has narrowed. The lower Gini is partly expected, because the very-rich adjustment is not applied.
Single ages 0–79 differ from the ONS projection by 19.5% on average and by up to 89% for one age. Totals by sex are within 1–2% (men 33.7m against 34.0m, women 35.8m against 35.5m). Calibrating to five-year bands leaves single years lumpy across every age, not only 15–19.
Invariants (tested)
None, not a NaN), so the comparison fails on it.actions/cachestep (tested).Tests
pytest -W error::RuntimeWarning policyengine_uk_data/tests/test_distribution_validation.py policyengine_uk_data/tests/test_fit_report.py: 39 passed.axiom: n/a: data validation CI; no policy change
🤖 Generated with Claude Code