Repository navigation
Match ONS age targets to the FRS top code of 80 - #604
Conversation
|
Code hand-off at
The build job's push failed on GitHub TLS timeouts. The UK hub published the branch from the job's bundle (same commit) and opened this PR. The hub changed only the |
|
Addressed the independent review of
Commands (worktree locked venv on PATH; numerical threads <=4): UV_LINK_MODE=clone uv sync --frozen --all-extras
# Copied review offline_ons.py into .job/ so its fixture path resolves here.
PYTHONPATH="$PWD/.job:$PWD" pytest -n 0 -p offline_ons policyengine_uk_data/tests/test_national_age_bands.py
PYTHONPATH="$PWD/.job:$PWD" pytest -n 0 -p offline_ons \
policyengine_uk_data/tests/test_national_age_bands.py \
policyengine_uk_data/tests/test_ons_demographics_targets.py \
policyengine_uk_data/tests/test_age_demographics.py \
policyengine_uk_data/tests/test_matrix_builder.py \
policyengine_uk_data/tests/test_calibrate_validation_targets.py
uvx ruff format --check .
git diff --checkFive-file result: 46 passed (national 5; ONS targets 16; local-age 13; matrix builder 10; local-validation 2). Ruff: 266 files already formatted. Original-head reproduction and current-head logs/XML, progress and hand-off are under Code hand-off: #595 integration: whichever of #595 ( |
…test the matrix against main's Answers Sol's review of #595 (F2, F3, F5, F6, F7). - Duplicates (F2). build_target_matrix now reads every target the sources emit (registry.emitted_targets) and select_matrix_targets keeps the first of each name, national then region then country, exactly as before. Each target it drops gets the status `duplicate`, which fails the build unless known_gaps.yaml lists it. test_no_duplicate_names counts the raw emission, so it can fail. On the current registry 159 targets are dropped this way: 156 HMRC SPI projections (#557) and 3 ONS copies (#605). They are listed; a name may be listed under two statuses, since the ten SPI £1m+ bands are both without signal and duplicated. - Build kinds (F3). A known gap may set `builds: [testing]` or `[release]`; the build kind comes from TESTING, as in create_datasets. A testing-only gap may name fnmatch `patterns`, and one now covers every UC payment band in testing builds (#598), replacing the three SINGLE bands; release builds still fail on any band without signal but the three COUPLE_NO_CHILDREN ones. A pattern is stale only if it matches no target. Errors and non-finite values are never excused, whatever a gap says. Stale gaps and problems are also written, once each, to $GITHUB_STEP_SUMMARY. - Status order (F5). The status is computed after the column enters the matrix, and a column whose status fails leaves it, so each offered target ends with exactly one status. - Reasons (F6). The 85-90 age gaps are permanent while ages are top-coded at 80 (#604); no record can hold a postgraduate loan. - A Hypothesis differential test checks that require_coverage=False builds main's matrix from the same sources, duplicates included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With #557 each SPI income band is emitted once, so its 156 duplicate entries go; with #604 the ages are 80 and over, so the two ons/*_85_90 zero-signal entries go. The shipped-file counts move from 19 to 17 without signal and from 159 to 3 duplicates (the ONS three, #605). The ten SPI bands of £1m and over keep their zero-signal entries: #557 gives them no signal. On uk-data 1.58.1 at 2025 the coverage CLI reports 626 in the matrix, 3 duplicates, 15 with no 2025 value and 17 without signal, with no problems and no stale gaps, as a release build and as a testing build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #593
FRS ages are top-coded at 80. National calibration currently gives the entire 80+ sample an 80–84 target and also asks for two 85–90 columns with no records. This change emits
ons/{female,male}_80_plusandons/{region}_age_80_plus, with open-ended compute masks and complete ONS projection sums, including 90+ and the oldest grouped ages.The data still has no records over 80, and this change only stops calibration chasing targets the data can't represent. It does not assign older ages or establish the recalibrated fiscal or distributional effect.
2025 target values (people)
National targets preserve the existing 2024-based ONS UK principal projection. They now include every age group from 80 through 110 and over, including the grouped 105–109 row. The regional top targets use directly reproducible 2022-based ONS sums: the aligned five-year-migration English subnational projection and principal country projections. This also refreshes the projection vintage and precision; lower regional bands remain unchanged. The old regional values cannot safely be interpreted as omitting 90+: e.g. East 2022 was 350,000, close to published 80+ 350,364 rather than 80–89 284,842. Their original extraction is unidentified. See
tools/ons_age_targets/README.md.Signal on the released 1.58.1 file
Measured at calibration year 2025 on released uk-data 1.58.1 enhanced_frs_2024_25.h5, SHA-256
056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784, with locked policyengine-uk 2.122.2 / core 3.32.13. One Microsimulation was created; only demographic band compute functions and household weights were evaluated, through the same_compute_columndispatch ascreate_target_matrix. Person records are the sum of each household count column; nonzero households count contributing matrix rows; weighted totals are column dot household_weight for each stated year. Counts and weighted totals only were output. No dataset was built or recalibrated.Maximum age remains 80; 7,718 records are 80 and 0 are over 80. Both sex columns and the twelve regional columns separately partition those age-80 records. These are pre-recalibration column signals, not predicted recalibrated populations or fiscal effects.
Local calibration and integration
Local age CSVs contain single years 80–89 and 90+, but
local_age.pyemits only half-open age/0_10 … age/70_80. Age-80 people contribute zero to every local age-specific column. Their household weights still enter other local targets and the shared national demographic loss (dense local calibration uses summed local weights for that loss; the OA path stacks national targets). The new national 80+ constraint therefore applies during local calibration. Local age targets are unchanged; there is still no local 80+ age-specific constraint.The old literal names were checked across code, tests, docs and gap lists. Saved notebook outputs remain historical. #595 is a separate unmerged branch: when combining it with this fix, remove its two
ons/*_85_90known-gap entries and change its zero-signal gap-count assertion from 22 to 20 (release count 19 to 17).Validation
Validation at
29ca75420b885ba7cd0ee3819817a974841a5a99, on top of reviewed head61f8f7ef:UV_LINK_MODE=clone uv sync --frozen --all-extraswith Python 3.13 and threads capped at four; production lock unchanged. pytest-xdist installed ephemerally for serial-n 0runs.PYTHONPATH="$PWD/.job:$PWD" pytest -n 0 -p offline_ons policyengine_uk_data/tests/test_national_age_bands.pyat61f8f7ef: 2 failed, 3 passed (open-ended-band subtraction and 32-vs-34 emission count). All original file contents were used and the patch restored afterward.80_plus, validates the terminal(80, None)band and bounded five-year bands, and compares the complete 2025 population exactly with the fixture (69,502,341).pytest -n 0 -p offline_ons: 46 passed —test_national_age_bands.py5;test_ons_demographics_targets.py16;test_age_demographics.py13;test_matrix_builder.py10;test_calibrate_validation_targets.py2. The requested matching-test search found the first two files; the other three preserve round 1 coverage.uvx ruff format --check .: 266 files already formatted;git diff --checkpasses.The current continuation used only the committed public fixture and synthetic test data: no source/dataset downloads, dataset build or production calibration. Earlier regional extraction replay, released-file signals and main-versus-head checks remain documented in the round 1 hand-off and independent review; they were not repeated here. Independent review and merging remain with the hub and Max's d833 batch go. The rebuild-queue row was already installed by the hub; no build GO was requested or used.
Reproducer: policyengine_uk_data/tests/test_national_age_bands.py::test_national_gender_age_targets_are_emitted_once_per_band
axiom: n/a: calibration demographic targets only
🤖 Generated with Claude Code