Skip to content

Bind APS employment by SOC 2020 major group by region and sub-major group by sex, on the chronicle#411 feed (#1490) - #1520

Merged
juaristi22 merged 5 commits into
mainfrom
uk-occupation-targets-1490
Nov 6, 2026
Merged

juaristi22 merged 5 commits into
mainfrom
uk-occupation-targets-1490

Conversation

@juaristi22

@juaristi22 juaristi22 commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Follows #1511 (merged), which carried the occupation stages of #1490 (from #1512). This PR adds the calibration targets those stages are fitted to. It now targets main, which is merged in; the only conflict was the v5 row declaration #1510 landed first (see section 1).

Summary

The UK contract binds the ONS Annual Population Survey's people in employment by SOC 2020 occupation, from Nomis APS218 for January to December 2025, which reaches Microcosm through PolicyEngine/chronicle#411. There are 158 references:

  • the nine major groups, each over the twelve region-tier areas: 108 references (María's ruling of 4 November 2026);
  • 25 of the 26 sub-major groups for women and for men, at UK level: 50 references (her ruling of 5 November 2026).

Community and civil enforcement (63) has no rows of its own and binds only through its major group's regional rows. The sub-major groups are not bound by region. Both points are covered in section 4.

The APS facts are only in Chronicle after PolicyEngine/chronicle#411, so the PR first re-pins the UK feed. It has four commits: the re-pin, the major groups, the sub-major groups by sex, and the fixes from the review. main is merged in between the third and the fourth.

1. Re-pin the UK feed onto the merge of chronicle#411

The feed moves from 08962f1 to 6475380 (PolicyEngine/chronicle#411 on top of 08962f1), 1,585,295 rows. The merge's tree differs from the PR head's, so the artifact was built at the merge commit itself.

The runbook entry is in docs/uk-chronicle-feed-repin.md, and the fragment is changelog.d/1490-uk-feed-repin-6475380.changed.md.

2. Bind the APS's people in employment by SOC 2020 major group

  • Targets: ons.occupation.<group>_by_region, family ons_occupation, category ons.employment. Each is a two-level target that fans out over the region tier.
  • What each target counts: people whose soc2020_major_group is the group and whose employment_status is full-time or part-time employed or self-employed, summed to households, as ons.public_sector_employment does.
    • Out-of-work adults keep their last job's group and are not counted.
    • The APS also counts unpaid family workers, which the FRS statuses don't name.
  • Selector: the APS estimate concept, all persons, all employment types, calendar year 2025. The confidence intervals are separate concepts and are not read.
  • What moves: the national references gain exactly the 108 rows, and the national compile-parity receipt adds 108 ledger-only rows. Every other entry is unchanged.
  • Local surface: it cannot move. The local generator keeps only targets with local geography levels, and these are country and region only.
  • employment_status and soc2020_major_group join the reviewed plain-column filters.

3. Bind the APS's people in employment by SOC 2020 sub-major group and sex

  • Why: with only the major groups bound, the sub-major draw left 16 of the 26 UK sub-major groups, and the mix of women and men inside many of them, outside the APS's confidence intervals on the calibrated frame. Occupation in this study, and whether Microcosm can carry an imputed SOC major group uk-ai-study#16 reads the sub-major groups.
  • Targets: ons.occupation.<female|male>_<sub-major group>, family ons_occupation, category ons.employment. Each is a UK-level row, like the ONS population rows by sex.
  • What each target counts: people whose soc2020_sub_major_group is the group, whose gender is the sex and whose employment_status is in work, summed to households.
    • Women and men together fix each sub-major group's UK total, which nests in its major group's regional rows. The totals are not bound separately: that would count the same constraint twice.
    • Nomis rounds each cell to the nearest hundred, so each pair adds to the all-persons figure within 100.
  • Selector: the APS estimate for the UK, one sub-major group and one sex, all employment types, calendar year 2025.
  • Not bound: the sub-major groups by region (section 4), and the employee and self-employed splits.
  • What moves: the national references gain exactly the 52 rows, and the national compile-parity receipt adds 52 ledger-only rows. Every other entry is unchanged, and the local surface cannot move.
  • soc2020_sub_major_group joins the reviewed plain-column filters.

4. Fixes from the review

  • The thinnest UK cells by sex: group 63's two rows are dropped, leaving 63 to its major group's regional rows.
    • Their APS intervals are 60% (women) and 29% (men) of the estimates, and few households on the frame carry the group.
    • An all-persons UK row for 63 would not help: its interval is 26%.
  • Two cells above the review's 20% line stay: women in skilled metal and electrical trades (52, 21%) and in skilled construction and building trades (53, 26%).
    • Each rests on at least 45 distinct FRS households, so binding them does not stretch a few weights.
    • The thinnest kept row rests on 40.
  • The sub-major groups by region stay unbound. Binding them was examined (María, 5 November 2026), and too many area cells are thin: 61 of the 300 rest on fewer than 30 distinct FRS households, and 48 have APS intervals over 20%. Binding them would stretch a few households' weights to fit noise. They can be revisited once the spine pools several FRS years and carries more clones.
  • A test of what the rows select: test_uk_occupation_targets.py pins it:
    • the APS estimate concept (never the interval), all employment types, calendar 2025 and the four in-work statuses;
    • one sex per UK row, never an all-persons UK sub-major total;
    • only major groups over the region tier, all persons;
    • the groups bound.
    • It also checks on the committed receipt that each major group's rows by sex fit inside its regional rows. Where every sub-major group binds, they make up all of it, within Nomis's rounding.
  • What moves: the national references lose the 2 rows, and the national compile-parity receipt loses their 2 ledger-only rows. Every other entry is unchanged.

The fragment changelog.d/1490-uk-occupation-targets.added.md covers sections 2 to 4.

Testing

  • Re-pin commit: the touched surface's engine-free tests, run without the local feed links (as CI runs them): 424 passed, 6 skipped. The skips are the regeneration checks that need the feed; the regeneration ran them for real.
    • test_h2_uk_spine_parity passes.
    • The high-value home stock's feed check passes against the new feed.
  • Major-group commit: the contract, reference, gate and spec tests: 467, 435 and 1,051 tests in three chunks, all passing.
  • Sub-major commit: the same three chunks: 467, 435 and 1,051 tests, all passing. The 52 rows move the target count (476 → 528, pinned in three tests), the registry scope (414 → 466), the unmapped declarations (229 → 281) and the active reference count (1,533 → 1,585).
  • After merging main: the 15 UK and shared test files this PR or main touches, Declare Chronicle consumer_fact.v5 and refuse feeds on undeclared row contracts #1510's row-contract tests among them: 1,093 tests, all passing; the 7 skips need the feed.
  • Review fixes:
    • The national surfaces were regenerated from the pinned feed: references, census, the national compile-parity receipt and the coverage manifest. The references and the receipt lose the 2 rows, and nothing else moves.
    • The 26 UK and shared test files of the touched surface, run without the local feed links (as CI runs them): 1,757 passed, 7 skipped. The skips need the feed.
  • Arms:
    • The re-pin against the major-group commit is in the comment above: the 108 cells fit, and the other rows hold.
    • The sub-major commit (df0323743, the 52 rows by sex) against the major-group arm, both with review date 2026-11-04:
      • the terminal gates: 7 of 7 in both;
      • final loss 0.00521 → 0.00511;
      • all 52 new rows within 1%, and the 108 major cells still within 1%;
      • one of the 1,452 shared rows moved by more than 2 points, and none crossed 10%.
    • This head: the national role's dry run (--dry-run, review date 2026-11-05) passes. It compiles uk.full.national_targets from the pinned feed with the build's own loader, including the reconciliation, cross-grain coverage and subset checks.
      • It gives 1,555 compiled targets, 47 excluded by the reviewed registers and 1,508 active. That is the sub-major build's register less group 63's two rows.
      • That build also left out 6 rows that no household carries, a floor only a solve applies.
      • No solve has run at this head. It differs from the sub-major commit by group 63's two rows, and by main's merge, which adds checks only. The sub-major commit's build is published for inspection (comment below).

🤖 Generated with Claude Code

juaristi22 and others added 2 commits November 5, 2026 01:40
Chronicle main 6475380 (PolicyEngine/chronicle#411 on top of 08962f1) brings
the UK facts of PolicyEngine/chronicle#407, which microcosm#1490's occupation
targets read: the ONS Annual Population Survey's people in employment by SOC
2020 major and sub-major group (Nomis APS218, three twelve-month periods from
April 2024 to March 2026) and ASHE Table 2's gross weekly and annual pay by
occupation (73,039 rows; 1,585,295 in all). The merge tree differs from the
pull request head's, so the artifact was built from the merge commit itself.

The rows move to chronicle.consumer_fact.v5 (PolicyEngine/chronicle#377, one
label per dimension value). Microcosm declares the v5 row contract beside v1
to v4 in chronicle_epoch.py: the per-row id is carried, never gated on, and
labels never enter a fact key. A row-by-row diff of the two artifacts finds no
row dropped and no value moved; 183,848 rows change display labels only.

No compiled value moves on either surface: both reference files and both
compile-parity receipts are byte-identical, which, since a receipt lists every
compiled target unless it equals the frozen fixture exactly, means every
resolved target value is unchanged. The national membership moves its feed
label, the local membership its feed label and the 1,263 DESNZ constituency
keys (their derived keys hash the feed identity), the vendored resources and
the DESNZ constituency resource carry the same rows under the new feed
identity, the census restates the pin, and the H2 fixture moves the
parameters of the 19 stages that bind vendored resources. The high-value home
stock's feed inputs are unchanged, so it is not rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UK contract binds nine calibration targets from the ONS Annual Population
Survey (Nomis APS218, January to December 2025, through
PolicyEngine/chronicle#411): people in employment in each SOC 2020 major
group, all persons and all employment types, each over the twelve
region-tier areas (108 references, family ons_occupation, category
ons.employment). María's ruling of 4 November 2026 chose the majors by region;
sub-major groups are not targets, since the sub-major draw takes them from the
same APS records.

Each target counts the people whose soc2020_major_group is that group and
whose employment_status is full-time or part-time employed or self-employed,
summed to households, as ons.public_sector_employment does. Out-of-work adults
keep their last job's group and are not counted; the APS also counts unpaid
family workers. The selector picks the estimate concept by its dimensions; the
confidence intervals are concepts of their own and are not read. The twelve
areas of each group sum to the APS's UK figure within its rounding to the
nearest hundred.

The national references gain the 108 rows and nothing else moves: the national
compile-parity receipt adds 108 ledger-only rows, every other entry unchanged,
and the local surface cannot move (the local generator keeps only targets with
local geography levels). employment_status and soc2020_major_group join the
reviewed plain-column filters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22

Copy link
Copy Markdown
Collaborator Author

Arm results for this PR. Both arms ran locally on 5 November, one after the other on a quiet machine. Each built a full spine and ran a national calibration, with the same review date (2026-11-04) and the re-pinned ledger (64753803). Nothing was uploaded.

  • Control ctl-141b: the re-pin, 141b05b03.
  • Head occt-0644f: this PR's head, 0644fe2f4, which adds the 108 occupation references.

The two spines hold the same data, and every existing row's support in the calibration matrix is identical. So the 108 references are the only difference between the arms.

The bound cells

Each of the 108 cells rests on at least 30 survey households. The gaps to the APS, control → head:

  • Cells within 10%: 46 → 108. On the head, all 108 are within 1%. The median gap falls from 12% to under 0.1%, and the largest on the head is 0.55%.
  • Cells outside the APS's published confidence interval: 77 → 0.
  • By area, the mean gap falls from 11–30% to 0.1–0.2%. London's falls from 30% to 0.1%.
  • Summed over the twelve areas, the control has too many people in manual and service work and too few in professional and managerial work. The head corrects every group:
    • process, plant and machine operatives +23.6% → −0.09%; skilled trades +18.2% → −0.14%; caring, leisure and other service +18.0% → −0.02%; sales and customer service +11.4% → −0.10%; elementary +10.4% → −0.13%;
    • professional −9.2% → −0.05%; associate professional −7.7% → −0.11%; managers, directors and senior officials −6.9% → −0.10%; administrative and secretarial −5.0% → −0.10%.
  • The design weights, before calibration, leave a median gap of 11%. So without the targets, the calibration widened the gaps rather than closing them.

Occupation cells the targets don't bind

The same APS release gives the 26 sub-major groups, and splits every major group by sex and by employee or self-employed. None of these cells is bound, so they show whether the targets improve occupation beyond the cells they fix. The median gap, control → head, with the design weights in brackets:

  • UK sub-major groups (26 cells): 10.3% → 4.7% (10.2%). Within 10%: 13 → 19.
  • UK major group by sex (18): 12.6% → 4.8% (6.3%). Within 10%: 8 → 16.
  • UK major group by employee or self-employed (18): 11.3% → 3.9% (8.3%). Within 10%: 7 → 13.
  • By region-tier area:
    • major group by sex (216 cells): 18.1% → 7.3%;
    • major group by employee or self-employed (215): 20.9% → 11.3%;
    • sub-major groups (301): 18.8% → 12.2%.
    • The ten cells resting on fewer than 3 survey households are left out.
  • Most of these cells still sit outside the APS's confidence interval on the head, though fewer than on the control. At UK level, that is 16 of the 26 sub-majors (21 on the control), and 8 of the 18 employee and self-employed cells (15).

What the targets don't fix is the mix inside a group. They set each group's total in each area, and the split below that comes from the frame's records and the sub-major draw. Where the split is off, moving the total can push a part further from the APS. On the head, these UK cells sit outside the APS's confidence interval (control in brackets):

  • sub-major groups: culture, media and sports +25.6% (+7.8%); secretarial and related +20.8% (+13.0%); protective service −20.2% (−26.9%); leisure, travel and related personal service −19.7% (−6.5%); skilled agricultural and related trades −14.4% (−5.0%);
  • by sex: women in skilled trades −22.6% (+3.7%); men in administrative and secretarial work +18.5% (+7.3%);
  • by status: self-employed managers −21.7% (−27.5%); self-employed associate professionals +19.5% (+4.0%); self-employed skilled trades −17.4% (−7.4%), while employees in skilled trades are +10.3% (+33.5%).

The other rows

  • The terminal gates pass in both arms, 7 of 7, the weight gates included.
  • On the 1,344 rows the arms share, weighted as the control weights them, the loss is 0.00532 on the control and 0.00531 on the head. The head's own final loss, over its 1,452 rows, is 0.00521.
  • Rows within 10% of target: 1,325 of the 1,344 in both arms, and they are the same rows. Within 5%: 1,272 and 1,269. Within 1%: 1,115 and 1,100.
  • 39 rows move by more than 1 point, 4 by more than 2, and none by more than 5. 28 of the 39 are SPI regional income-band rows. The four largest moves:
    • ons.population.age_30_39_by_region@E12000002@2025 moves from −0.05% to −2.46%;
    • hmrc.spi_region.taxpayers_by_region_12570_15000@E12000006@2025 moves away from target, and hmrc.spi_region.total_income_by_region_12570_15000@E12000006@2025 moves closer. Both rest on the SPI channel, so I give no figures for them;
    • hmrc.cgt.tax_by_age_band.age_16_to_24@2025 moves closer to target. It covers a small group, so I give no figure for it.
  • ons.public_sector_employment@2025 moves from +0.18% to +0.02%.
  • The SPI rows for employment income in the two total-income bands from £12,570 to £20,000, counts and amounts, were already more than 10% below target on the control. They move less than a point further below. They rest on the SPI channel, so I give no figures.

Time

  • No stage changed, so the spines' times are not compared.
  • The calibration takes 27 min 15 s on the control and 27 min 32 s on the head. Measuring the targets takes 1,145 s against 1,162 s, and the solve 206 s against 211 s.

🤖 Generated with Claude Code

juaristi22 and others added 2 commits November 5, 2026 13:05
…ex (#1490)

The UK contract binds 52 more calibration targets from the ONS Annual
Population Survey (Nomis APS218, January to December 2025, through
PolicyEngine/chronicle#411): people in employment in each of the 26 SOC 2020
sub-major groups, for women and for men, at UK level (family ons_occupation,
category ons.employment). María's ruling of 5 November 2026: with only the
major groups bound, the sub-major draw left 16 of the 26 UK sub-major groups,
and the mix of women and men inside many of them, outside the APS's
confidence intervals on the calibrated frame. The sub-major groups by region
are not bound: their intervals are wide, and in an offline cross-validation of
the national solve, binding them did not improve the cells held out.

Each target counts the people whose soc2020_sub_major_group is that group,
whose gender is that sex and whose employment_status is full-time or
part-time employed or self-employed, summed to households. Women and men
together fix each sub-major group's UK total, which nests in its major group's
regional rows, so the totals are not bound separately. Nomis rounds each cell
to the nearest hundred, so each pair adds to the all-persons figure within
100. soc2020_sub_major_group joins the reviewed plain-column filters.

The national references gain the 52 rows and nothing else moves: the national
compile-parity receipt adds 52 ledger-only rows, every other entry unchanged,
and the local surface cannot move. The changelog fragment now covers the
major and the sub-major rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1510 merged into main first with the same
chronicle.consumer_fact.v5 declaration this branch's re-pin made in
chronicle_epoch.py, and with checks that refuse a feed whose pin or rows name
an undeclared row contract. The merge keeps #1510's chronicle_epoch.py and
test_chronicle_epoch.py. This branch's pin and rows both name v5, so those
checks pass on it; main's other changes leave the regenerated references and
receipts as they are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22 juaristi22 changed the title Bind APS employment by SOC 2020 major group by region, on the chronicle#411 feed (#1490) Bind APS employment by SOC 2020 major group by region and sub-major group by sex, on the chronicle#411 feed (#1490) Nov 5, 2026
@vahid-ahmadi

Copy link
Copy Markdown
Contributor

Review of bde6d3f. I read the 61 new contract entries in uk_population_targets.json, the 160 references and their resolved values in the compile-parity receipt, the aps_occupation draw, the test changes and your arm comment. I also checked the values against the Jan-Dec 2025 APS218 CSV in chronicle#411 (aggregates only). I did not run a build or the solve; I relied on CI for the tests.

Should

  1. The thinnest UK-by-sex cells are bound as exact points, on a drawn label. uk_population_targets.json:35410 and its neighbours. The sub-major group is imputed within the major group (aps_occupation.py:1-25), so these 52 rows reweight households to fit a draw. Under the national doctrine's family_equal rule (national_doctrine.py:196-229) and relative scales, all 160 rows share the ons_occupation family's weight equally. So a 4,300-person cell counts as much as a 3.8m one, and a 60% miss on it costs as much as a 60% miss on the professional group in London. The APS intervals on some of the 52 are wide:

    • women in community and civil enforcement (63): 4,300 ± 2,600 (60%);
    • men in 63: 18,900 ± 5,400 (29%);
    • women in skilled construction (53): 23,400 ± 6,000 (26%);
    • women in skilled metal and electrical trades (52): 35,300 ± 7,400 (21%);
    • men in secretarial (42): 44,600 ± 8,300 (19%);
    • women in skilled agricultural trades (51): 60,200 ± 9,600 (16%).

    Failure case: if only a few frame households carry a female-63 draw, fitting 4,300 exactly scales up those few weights. The point being fitted could be anywhere from about 1,700 to 6,900. Your arm comment gives the support for the 108 major cells (at least 30 households each), but not yet for these 52. Suggested fix: report each cell's household support in the sub-major arm. Then drop or pool the cells whose interval is wider than about 20% of the estimate: for example, bind 63 for all persons only (23,200 ± 6,000), or leave 63 to its major group's regional rows. Keep the rest of the 52, where the intervals are tight. This is the same thin-63 problem we flagged on chronicle#411. There the regional 63 rows had no interval at all, and this PR avoids those by not binding sub-majors by region.

  2. The arm for this head is still pending. Your comment covers 0644fe2f4, the 108 major cells only. Before this leaves draft, it would help to see the 52-row arm: the weight gates, how many of the 16 sub-majors outside the interval come inside, and the drift on the 1,344 shared rows. Item 1 depends on those numbers.

Nit

  1. No test pins what the occupation rows select. The test changes only bump counts (test_uk_population_targets.py:276, test_uk_target_references.py:137), and no test mentions ons.occupation. The selectors are right at head: all 61 carry ons.aps_people_in_employment with statistic: estimate, employment_status: all, calendar 2025 and the four in-work statuses, and no sub-major row carries sex: all. A short engine-free test over every ons.occupation.* target asserting those properties would stop a later edit from binding an all-persons sub-major total on top of the sex rows, or an employee-only split, without anyone noticing.

Checked and fine

  • Interval facts: Chronicle emits them under their own concept (ons.aps_people_in_employment_confidence_interval_half_width), and the selectors also filter statistic: estimate, so no interval can resolve as a target.
  • In-work restriction: every row filters employment_status to FT/PT employed or self-employed, so adults out of work who keep their last job's group are not counted.
  • No double counting. The UK sub-major rows bind women and men only, never the all-persons total. They nest exactly in the regional major rows: for each major group, the sum of the women and men sub-major cells is within 100 of the sum of its 12 regional cells (at most 0.004%), so the two sets agree rather than pulling against each other.
  • Levels, not shares: the rows bind counts, and the 0.14% of Nomis's UK total in no major group (33,263,700 against a 33,216,900 sum of the nine) is unbound. So whether shares use the Nomis total or the nine-group sum does not arise here.
  • Geography: the nine English regions plus Wales, Scotland and Northern Ireland at country level (12 areas × 9 = 108), and K02000001 for the 52.
  • Period: calendar 2025 at the 2025 calibration year, as the other ONS stock rows use (for example ons.public_sector_employment's 2025 quarter-end mean). No uprating is applied.
  • All 160 rows are active references that bind, not diagnostics. The local surface cannot move, because the rows have no local levels.

CI at bde6d3f: all 11 checks pass (lint, engine-free 3.13/3.14, engine-uk and engine-us 3.13/3.14, integration-uk, wheels, installed-emitter, select-countries).

The major-group rows look ready. The sub-major-by-sex rows need the support numbers and probably a trim of the thinnest cells before this leaves draft.

🤖 Generated with Claude Code

…e occupation rows select (#1490)

Vahid's review of bde6d3f. The two UK rows by sex for community and civil
enforcement (63) are the thinnest of the 52: their APS intervals are 60%
(women) and 29% (men) of the estimates, and few households on the frame carry
the group. So 63 now binds only through its major group's regional rows. Every
other row rests on at least 40 distinct FRS households; women in skilled metal
and electrical trades (52) and in skilled construction (53), whose intervals
sit above 20%, rest on at least 45 each and stay.

A new engine-free test pins what every ons.occupation row selects: the APS
estimate concept, all employment types, calendar 2025, the four in-work
statuses, one sex per UK row, only major groups over the region tier, and the
groups bound. It also checks on the committed receipt that each major group's
rows by sex fit inside its regional rows. National references -2 and the
national compile-parity receipt -2 ledger-only rows; nothing else moves. The
sub-major groups stay unbound by region: too many of their area cells rest on
few households (María, 5 November 2026).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22

Copy link
Copy Markdown
Collaborator Author

Thanks, Vahid. Pushed bde6d3f9d..9de8ba6ab, one commit with the changes below. The description has the details.

1. Thin UK cells by sex. The household support of the 52 rows by sex, on the sub-major arm's frame (distinct FRS households):

  • Community and civil enforcement (63): both rows rest on fewer than 10. Both are dropped, so 63 binds only through its major group's regional rows. An all-persons UK row for 63 would not fix it: its interval is 26% of the estimate.
  • Every other row: at least 40 households.
  • Women in skilled metal and electrical trades (52, interval 21%) and in skilled construction (53, 26%): these two sit above your 20% line. Each rests on at least 45 households, so binding them doesn't stretch a few weights, and we keep them.

Sub-major groups by region. We also looked into binding the sub-major groups over the region tier, but those cells are too thin: 61 of the 300 area cells rest on fewer than 30 distinct FRS households, and 48 have APS intervals over 20% of the estimate. Binding them would stretch a few households' weights to fit noise, so they stay unbound. We can look at this again once the spine pools several FRS years and, if it does, carries more clones.

2. The arm for the sub-major head (df0323743, the 52 rows by sex), against the 108-cell arm:

  • the terminal gates: 7 of 7 in both;
  • final loss 0.00521 → 0.00511;
  • all 52 new rows within 1% (median 0.05%), and the 108 major cells still within 1%;
  • the 26 UK sub-major totals: 26 inside the APS interval, against 10 with the major groups alone (largest gap 0.16%);
  • of the 1,452 rows the two arms share, one moved by more than 2 points (ONS North West age 30–39, −2.5% → −4.6%), and none crossed 10%.

3. Test. test_uk_occupation_targets.py pins what every ons.occupation.* row selects:

  • the APS estimate concept, never the interval;
  • employment_status: all, calendar 2025 and the four in-work statuses;
  • one sex per UK row, never an all-persons UK sub-major total;
  • only major groups over the region tier;
  • the groups bound.

It also makes your nesting check permanent: on the committed receipt, each major group's rows by sex must fit inside its regional rows, and must make up all of it, within Nomis's rounding, wherever every sub-major group binds.

🤖 Generated with Claude Code

@juaristi22
juaristi22 marked this pull request as ready for review November 5, 2026 23:29
@juaristi22

juaristi22 commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator Author

Published for inspection: the sex-split build is on policyengine/populace-uk-private as cut microcosm-uk-2024-25-national-20261105T133052Z-51f60255. It is inspect-only: latest-national.json stays on microcosm-uk-2024-25-national-20261025T091414Z-a6299504.

  • What it is: the full build of this PR's sub-major commit df0323743, with the 108 major cells and the 52 rows by sex. Its spine and calibration both come from that commit. Review date 2026-11-04, Chronicle feed 6475380, policyengine-uk 2.174.1.
  • How it differs from this PR's head (9de8ba6ab):
    • it still binds group 63's two UK rows by sex, which the head drops after the review;
    • it predates the merge of main, which adds checks only.
  • Release checks (docs/uk-national-release-assembly-runbook-806.md):
  • Published with --allow-missing-staging (María's call). The build ran as a measurement arm with staging kept local, so it never appeared on the staging dashboard. The checks above stood in for that review. The cut can be inspected in the dashboard's release view (?country=uk&release=microcosm-uk-2024-25-national).
  • No build covers this PR's head yet. A compile of the national targets at the head is owed before merge.

🤖 Generated with Claude Code

@vahid-ahmadi

Copy link
Copy Markdown
Contributor

Round 2 at 9de8ba6. I read the one new commit (bde6d3f9d..9de8ba6ab): the two dropped rows in the contract, references, membership and receipt, the new test_uk_occupation_targets.py, the count bumps, and your two comments. I recomputed the nesting check from the committed receipt. I did not run a build or the solve.

Earlier items

  1. Should, thin UK cells by sex bound as points: fixed. Group 63's two rows are gone from all four resources, and nothing else moves (158 occupation rows on the receipt: 108 regional, 50 by sex). Keeping women in 52 (21%) and 53 (26%) on at least 45 households each is a fair reading of the line I suggested.
  2. Should, the arm for the 52 rows: fixed for df0323743. The gates, the loss, the 26 sub-major totals inside the interval and the shared-row drift answer what I asked. What remains is that no solve covers this head, so group 63's UK cells are unmeasured now that only its major group's regional rows hold them. When the compile or solve at head that you list as owed before merge runs, it would help to give 63's UK gap for women, men and all persons next to the df0323743 figures.
  3. Nit, no test of what the rows select: fixed. The selector test covers every point I listed, and the by-sex test fails if anyone adds an all-persons UK sub-major row or a regional sub-major row.

New

Nit

  1. The nesting check's allowance is about 100 times looser than the rounding it allows for. test_uk_occupation_targets.py:28 sets ROUNDING_ALLOWANCE = 0.005, a relative 0.5%. On the committed receipt, the gap between each major group's 12 regional cells and its rows by sex is at most 100 people (0.004%, groups 2 to 5 and 9), while 0.5% lets through 9,200 to 44,800 people depending on the group. Failure case: if one row by sex later read a different twelve-month period and moved by 10,000 (for example women in secretarial, 42), the professional or administrative group would still pass both bounds, which is the drift this test exists to catch. Suggested fix: use an absolute bound from the rounding itself, 50 times the number of cells summed (50 * (12 + 2 * len(bound_subs)), so 1,000 to 1,400 people). Major group 6 then still gets only the upper bound, which is right while 63 is unbound (its gap is 23,200, the all-persons 63 figure).

Checked and fine

  • The receipt's 158 values: for every major group except 6, the rows by sex sum to the 12 regional cells within 100. For group 6 they fall short by exactly 23,200, the all-persons UK figure for 63, so the drop removed nothing else.
  • The test reads 63 as unbound in one place (UNBOUND_SUB_MAJOR_GROUPS), and both the by-sex set check and the nesting check use it, so re-adding 63 later means a one-line change that both tests see.
  • The counts move consistently: targets 528 → 526, registry scope 466 → 464, unmapped 281 → 279, active references 1,585 → 1,583, ledger-only 990 → 988.
  • The changelog now says 25 of 26 and 158 references, and gives the reason for 63.

CI at 9de8ba6: all 11 checks pass.

Verdict: all three round 1 items are done. Item 4 is a small tightening, and the head's own measurement of group 63 is the only open point before this leaves draft.

🤖 Generated with Claude Code

@juaristi22
juaristi22 merged commit 9d080e7 into main Nov 6, 2026
11 checks passed
@vahid-ahmadi

Copy link
Copy Markdown
Contributor

Round 3 (post-merge), on the published cut microcosm-uk-2024-25-national-20261105T133052Z-51f60255. I read the person and household tables of microcosm_uk_2024_25.h5 (weights, region, status, sex, age and the two SOC columns only) and compared them with the 160 occupation values on df0323743's compile-parity receipt and with the Jan-Dec 2025 APS218 CSV in chronicle#411. Aggregates only. This cut is the build of df0323743, so it still binds group 63's two UK rows by sex, which the merged head drops.

Earlier items, on main at b563fbb6f

  • 4, Nit, the nesting check's 0.5% allowance (test_uk_occupation_targets.py:28): open. No commit since the merge touches the file.
  • Group 63's UK cells at the head: open. No build covers 9de8ba6ab yet, so the figures below are for the cut that still binds them.

Measurement on this cut

The stored weights sit at the dataset year: the release divides the solved weights by the engine's 2025 population growth (0.33%, weight_year.py). Read raw, every one of the 160 cells is about 0.33% below its target. The figures below lift the weights to 2025 as policyengine-uk does.

Set Cells Median abs gap Max abs gap Fewest distinct FRS households
Major group x region 108 0.09% 0.40% 62
Sub-major group x sex, UK 52 0.05% 0.41% 4 (women in 63); 40 without 63
  • Worst major cells: skilled trades in the East of England −0.40%, process, plant and machine operatives in the East −0.35%, elementary in the East −0.33%.
  • Worst cells by sex: women in skilled metal and electrical trades (52) −0.41%, men in secretarial (42) +0.32%, men in health and social care associate professionals (32) +0.28%.
  • Group 63 at UK level, bound in this cut: women +0.16%, men +0.02%, all persons +0.05%, on 4, 7 and 11 distinct FRS households. These are the figures to set the head's against once a build covers it.

So the cut reproduces your arm for df0323743 (52 rows within 1%, median 0.05%).

Sub-major groups by region (not bound)

APS218 gives a numeric estimate and interval for 301 of the 312 cells. The 11 without are all group 63. Against those 301:

Cells n Outside the APS interval Median abs gap
All 301 135 12.1%
Fewer than 30 distinct FRS households 62 35 25.5%
Interval over 20% of the estimate 49 28 29.2%
At least 30 households and interval up to 20% 225 91 9.5%
  • Support across the 301 cells: median 61 distinct FRS households, 10th percentile 21. Six of the 312 cells have none, all group 63.
  • This agrees with your thin-cell count to within one cell (62 against 61 under 30 households, 49 against 48 over 20%), which fits 301 cells against your 300.
  • It also qualifies it. The thin cells are the worst fitted, but 91 of the 225 well-supported cells also sit outside the interval, with a median gap of 9.5%. The all-cell median, 12.1%, is close to the 12.2% your first arm comment gives for the 108-cell head, so the rows by sex did not move the regional mix. When the spine reorganisation adds records, these 225 cells are the set to look at first.

Checked and fine

  • Every person's code is valid SOC 2020: nine major groups plus NONE, and all 26 sub-major groups. Every sub-major group nests in the person's major group, and NONE coincides in the two columns.
  • In-work people with no code: 0.39% of weighted in-work people. The APS's own residual outside the nine groups is 0.14% (33,263,700 against 33,216,900), and nothing binds the in-work total, so this does not affect a bound cell.
  • No one under 16 is in work or carries a code.
  • Out-of-work adults with a last-job code: 75.0% of weighted adults not in work, by design. The in-work filter on every row keeps them out of the targets.
  • CI on the merge commit 9d080e793: all 11 checks pass.

Verdict: the published cut fits the 160 rows it binds, within 0.41%. The two round 2 points stay open on main and need the head's build.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants