Skip to content

UK #882 repairs: the A16 legacy rows, the carer and childcare elements, the benefit-cap bands - #937

Merged
juaristi22 merged 19 commits into
mainfrom
uk-uc-repairs-882
Sep 17, 2026
Merged

juaristi22 merged 19 commits into
mainfrom
uk-uc-repairs-882

Conversation

@juaristi22

@juaristi22 juaristi22 commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator

Closes nothing on its own; work items of #882 (the A16 rows #867/#869, the carer and childcare elements, the benefit-cap bands). Plan: repos/uk-882-repairs-plan-2026-09-16.md (María's copy); receipts in experiments/882-uc-repairs-receipts.md.

Fix first

The three element rows signed in #921 were keyed on the register by their binding metric names (dwp/uc/elements/carer, /childcare, /housing); the applier matches reference names, so the next calibration run at main would have refused with "matched zero registry specs". Renamed to dwp.uc.households_{carer,childcare,housing}_element, with a test that every register name resolves to a committed reference.

A16 legacy rows (#867 Housing Benefit, #869 JSA) and the ESA rows

tools/diagnose_uk_legacy_benefits.py (spine-q, the Q50f candidate, FRS 2024-25) decomposes the misses. The source is not the problem: FRS 2024-25 reports 1.56m Housing Benefit units (99.9 % renters) near the DWP caseload, and 63k JSA units against DWP's 71,000. The SPI support channel re-imputes benefit receipt onto its synthetic households without tenure coherence, so 363k of its 451k Housing Benefit reporters sit in owner-occupied households, and 161k of the spine's 181k contributory JSA reporters are synthetic. The 55 % would_claim_uc draw marked 3.75m all-pension-age units as UC claimants, inert for UC but zeroing Housing Benefit for 377k reporters. Engine mechanisms recorded: claims_all_entitled_benefits is a scalar on microdata, rent is the household head's alone.

  • would_claim_uc is now drawn over benefit units with a working-age adult (population on the anchored-residual kernel; manifest aggregate any_adult_under_state_pension_age; the U8 rate 0.55 unchanged, its population re-signed; the signal gate measures the share on that population). The bounds are the engine's, read at the build instant by uk_take_up_population_policy (gov.dwp.state_pension.age, male and female asserted equal, and the is_adult threshold of 18), and assert_take_up_stage_population_declaration refuses a manifest whose would_claim_uc operation stops declaring that population; a requires_uk test checks the policy reproduces the engine's is_WA_adult for every age 0 to 100.
  • obr.housing_benefit and dwp.jsa_claimants re-adjudicated on the receipts to 2026-12-08 (tracking unchanged). Every adjudication of the tranche (those two, the three HB rows, the thirteen cap bands) cites the ruling that exists, the 2026-09-16 Align UK UC claim, child and period definitions #882 status comment and the approved plan; the shared 2026-12-08 expiry is the zero-band clock already on the register (UK dataset sizes: informed L0 and exact-count candidates (#355) #877), so the next spine re-adjudicates the UC repairs together rather than on the one-month or 10-to-15 regimes.
  • Three Housing Benefit caseload rows (dwp.hb.households, _social_rented, _private_rented) bind the Stat-Xplore client-type-by-tenure cube on the calendar-2025 window at benefit-unit grain, measured on the register (tracking UK spine: housing benefit £2.7bn against OBR £12.1bn — pensioner and supported-accommodation HB missing after the UC migration #867).
  • ESA: the three rows keep their bindings; the notes declare DWP's single-type counts, the credits-only claimants in the total and the SPI-channel loss (1.06m raw against 791k on the spine); JSA's note records the New Style basis and the August-2025 methodology break.

Childcare element

would_claim_uc_childcare is drawn in frs_take_up at one rate per family type (FRS couple flag), fitted by tools/fit_uk_uc_childcare_takeup.py so the expected paid-UC count equals DWP's calendar-2025 single and couple means at the Q50f calibrated weights (single 0.441, couple 0.2682); the mean award is unchanged by construction (£378 a month against £402.50). The contract citation declares the realization caveat: the SPI support channel clones benefit-unit rows after the draw, so synthetic units inherit the donor's flag and the realized count on a thin base sits below rate times base. The column rides the extra export surface as a net-new engine input (policyengine-uk 2.98.0 reads it).

Carer element (engine floor 2.98.0)

PolicyEngine/policyengine-uk#1857 (released as 2.98.0) adds would_claim_carers_allowance, would_claim_uc_childcare and the hours limb of is_carer_for_benefits. The root spine stage fills care_hours from FRS adult.hourtot at the band's lower edge (codes 5, 6, 7 and 10 reach the 35-hour line; 906k weighted 35-hour carers in UC-reporting units, 300k without an award) and sets would_claim_carers_allowance from reported receipt, so the allowance stays on reported claimants while the hours qualify carers for the element. The three net-new engine inputs ride the extra export surface (the coverage manifest measures the incumbent's input surface) and are signed as net-new columns. Floors, uv.lock and APPROVED_UV_LOCK_SHA256 move to 2.98.0. The hours limb counts reported hours alone, with no check that the person cared for receives a qualifying benefit; the carer target note and the changelog fragment say so.

Benefit-cap amount bands

Fourteen DWP Table 4 bands bound at benefit-unit grain (annualised monthly edges, half-open below and closed above). On Q50f the capped count fits exactly but the mean reduction is £502 a month against DWP's distribution, four bands are empty and 14.2 % of capped units sit above £1,300 a month against 0.6 % published, a tail of a few cloned large families outside London (4.45 children, full child elements, housing element £1,346). The up-to-£100 band, the one inside the 25 % bound, is in the objective; the other thirteen are on the register (tracking #882) with tools/diagnose_uk_benefit_cap.py as the tail diagnostic (its support join is positional on the engine's household order, since the engine's float32 id arrays cannot carry the spine's ids).

Surfaces

References 614 → 631 active (17 new, no existing row changed) on the pinned ec20085 feed; both national compile-parity receipts; register 53 → 67 (16 rows added, the carer and childcare element rows retired on the twin evidence); coverage manifest, gate-battery digests and H2 parity fixture regenerated; care_hours, would_claim_carers_allowance and would_claim_uc_childcare signed as net-new columns and declared on the extra export surface.

Evidence (R4–R6 in the receipts; licensed twins at design weights, engine as labelled)

Control (branch base 51c3143) and repaired (this branch) twins, 29 stages, 17 of 17 spine gates each, identical row counts. Payload diff: benunit gains would_claim_uc_childcare and would_claim_uc moves on 9,962 records; person moves only student_loan_balance (34 rows); household moves the QRF columns downstream of household_net_income. Housing Benefit paid units 401,489 → 644,183 (+60 %, of which +236,895 all-pension-age), £2.61bn → £4.11bn. Paid UC units 5,320,794 → 5,224,967 (−1.8 % on the 2.97.0 twin; 5,231,437, −1.7 %, on repaired-298b at 2.98.0): the re-signed draw over units with a working-age adult realizes a 0.559 share where the old all-units draw realized 0.582 among them (the anchors concentrate there); ruled 2026-09-16: keep the rate over the eligible population; the old share was an artefact of a count target taken over ineligible units. JSA, ESA and the benefit-cap figures are identical between twins. Benefit-cap support on the spine (positional join): 192 capped records from 93 source households, the tail 8 records from 4, so the Q50f tail is a selection-and-calibration effect on a thin base.

Repaired twin at 2.98.0 (R6, repaired-298b): paid-UC carer-element units 425,757 → 768,273 (+80 %; Q50f projection 1,075,100 against 1,078,514, −0.3 %); childcare element 116,324 units against 353,470 at rate one (projection 149,950 against 178,667, −16 %; SPI-synthetic units copy their donor's flag, so the realized count on a 579-record base sits below the expected count the rates control); Carer's Allowance is unchanged by construction, since the flag is reported receipt, and measured so: £3.625bn and 837,002 recipients, identical to the control, with 0 weighted mismatches between the flag and the refilled receipt once the flag is refreshed after the SPI fill (a first twin caught a +3.4 % move from a stale flag). Paid UC −1.7 %, the other elements within 1.8 %. Both element register rows are retired under the #921 rule on that design-weight projection onto Q50f, not on a calibrated run; María ruled 2026-09-16 that the projection retires them, and they go back on the register if the calibrated run fails either row.

Tests

Review round 1 (Vahid, 2026-09-16)

Adjudications point at the ruling that exists; the cap-band support join is positional; the take-up population is read from the engine and asserted against the manifest (working-age edge moves from 16 to the engine's 18, UC-inert since is_uc_eligible needs is_WA_adult); the fit receipt records the input's weights and a portable path and was re-fitted at 2.98.0 (rates unchanged); the SPI donor-copy is declared on the contract and the signed difference; the pre-rebase tag is on origin; the R1 to R3 receipts are under data/ukds/acceptance/882-uc-repairs/.

CI on f275613 failed on three things of this round, fixed in the follow-up commit: the H2 parity fixture's pinned graph JSON was stale after the rebase (regenerated), the identity-stability tool imported the engine eagerly for the population bounds (now injectable, read from the engine only in main), and the hermetic driver tests' synthetic spec lacked the population declaration the stage now asserts (declared, with the reader stubbed like the other deferred parameter readers).

Rebase

Rebased over main 4d832e2 (#927 transport and energy on the ec20085 feed) on 2026-09-17: the contract merged at the target level (298 targets, 247 in registry scope, 55 unmapped declarations), the national and local references and both national compile-parity receipts regenerated on the ec20085 pin (614 → 631 active, no existing row changed), the coverage manifest, H2 parity fixture and gate-battery digests regenerated on the merged sources, the vendored energy facts checked against the artifact. The lock carries main's engines plus policyengine-uk 2.98.0 with the approved lock digest re-pinned. Earlier bases: main d69a543 (#934, #936) on 2026-09-16 with the same treatment (296 targets, 613 → 630 active on df35af7). Pre-rebase heads kept as tags backup/uk-uc-repairs-882-pre-934 and backup/uk-uc-repairs-882-pre-927.

🤖 Generated with Claude Code

@vahid-ahmadi

Copy link
Copy Markdown
Contributor

Review at da2dd1a2 (Claude Code, high effort; worktree checkout of the thirteen commits on main d69a543e; without the engine, the tape or the pinned feed: 844 passed, 26 skipped, 0 failed across the 22 touched and neighbouring suites including the lock-digest and H2 parity checks; ruff check clean on all 29 changed files; the coverage manifest, the spec-engine coverage check and ci_test_groups --verify current; CI nine lanes green and fourteen running when I posted, none failed. Draft, so a comment rather than a review.)

First, the "fix first" item, because it is mine as much as yours. On main the applier matches register entries by spec.name (measure_simulation.py:561-571) and the three #921 entries are named by their binding metrics, dwp/uc/elements/{carer,childcare,housing}; loading the UK spec and the register on d69a543e leaves exactly those three unresolved, so #921 shipped a register the next calibration run would refuse. I approved #921 with the tranche unasserted and asked only for an assertion, which would not have caught the name. The rename is right, the new resolve-every-name test fails against main and passes here, and the 2026-09-15 tranche is now asserted too. Thank you for catching it.

The rest of the structure holds. The uniforms are identity-keyed, so restricting the would_claim_uc draw population does not reshuffle other units' draws, and the engine confirms the premise: UC is defined only for units with a working-age adult, and Housing Benefit eligibility is gated on ~would_claim_uc, so the 3.75m all-pension-age units were inert for UC and were zeroing Housing Benefit. The HB caseload rows bind the #260 cube at benefit-unit grain on the calendar-2025 window with the same tenure mapping as #921 and integral twelve-month means. The childcare draw is an unconditional identity-keyed Bernoulli by family type, so the mean-award claim holds in expectation; the rates are lockstepped between contract, manifest and runtime. The FRS hours bands in the spine match the receipts, unknown codes refuse, and the Carer's Allowance "identical to control" result is by construction: with would_claim set from reported receipt, the 2.98.0 formula collapses to reported receipt, and the SPI refresh keeps that on synthetic rows. The engine floor, lock and approved digest agree; references 613 → 630 with zero common rows changed; the three net-new columns are signed. Every body number except one matches receipts R4–R6.

Should-fix

1. Eighteen register entries cite a sign-off that has not happened. The re-adjudicated obr.housing_benefit and dwp.jsa_claimants, the three HB rows and the thirteen cap bands all carry "María's sign-off in review of the #882 repairs PR" as adjudication, approved 2026-09-16. The PR is a draft with no reviews. The #921 housing row beside them cites a real dated event. Approver-as-author is the convention; a prospective citation is not. Either point at the ruling that exists (the 2026-09-16 status comment on #882, if that is it) or date the entries when the sign-off lands.

2. The benefit-cap diagnostic's float32 join merges distinct households. diagnose_uk_benefit_cap.py:198-209 casts the spine's int64 household index to float32 and drops duplicates before reindexing. Spine ids for SPI, CGT and band-donor households sit at 10⁸ to 10¹⁰, where float32 spacing is 8 to 1,024; 100,000,001 and 100,000,003 both round to 100,000,000, which I checked. Colliding households inherit the first one's source id and no collision count is reported, so distinct-source counts can only be undercounted, and "192 capped from 176 source households, tail 8 from 8" may be understated. It does not move the register decision, which is measured on Q50f directly, but the tool should join positionally, since engine household order is spine table order, and say how many ids collided. The commit's own note that exact-integer matching failed also means the engine's float32 ids are lossy at these magnitudes, which deserves its own issue.

3. The manifest population: key is declaration-only. sources.yaml:749 declares population: uc_age_eligible with the aggregate any_adult_under_state_pension_age, mirrored in source_stages.json and admitted by the schema as a bare string, but no code reads either: frs_take_up.py:170 computes the mask directly with UK_STATE_PENSION_AGE = 66 hardcoded at line 56. So nothing binds manifest to runtime, the 66 has no build-year guard (the requires_uk test pins a 2025 situation only, and the comment admits SPA rises from 2026), and the stage's lower edge is 16 where the engine's working-age test is 18, so units whose only under-SPA adult is 16 or 17 are drawn and engine-inert. Lockstep-assert the declaration, and read the pension age from the engine parameter for the build year.

4. The childcare fit receipt contradicts its inputs. take_up_contract.json records the Q50f candidate as input_h5 while its basis and status strings say "at design weights"; the body's "at the Q50f calibrated weights" is the true one. The path is also an absolute path under your home directory.

5. The SPI donor-copy of take-up flags is undeclared in-tree. frs_take_up is stage 10 and the SPI channel at stage 20 clones benefit-unit rows wholesale, so synthetic units inherit the donor's would_claim_uc_childcare and would_claim_uc rather than drawing afresh. That is the mechanism behind 116k realised against 150k expected, and it is only in receipts R6 — not on the contract citation or the signed difference. It biases realised childcare low, so the −16% projection is conservative for retirement, but a calibrated run will under-realise the rate × base the fit assumed.

6. Twin provenance is dangling. backup/uk-uc-repairs-882-pre-934 is not on origin — the remote has no tags at all — and the four twin commits the receipts pin are unreachable from any branch. Push the tag. The R1–R3 receipt JSONs are marked "to be copied under data/ukds/acceptance/882-uc-repairs/", which does not exist.

7. Two expiry regimes on the register, unexplained. The re-adjudicated HB and JSA rows moved from the one-month window to 2026-12-08 alongside the sixteen new rows, while the other three A16 rows keep one month and #921's housing row keeps 10-15; the applier refuses outside a window. Nothing says why 12-08.

8. Body drift. "Surfaces" says 603 → 620 on 474a0ae; the tree is 613 → 630 on df35af7 (the membership header and the country-spec pin agree). "Evidence" mixes R5's paid-UC figure at 2.97.0 (5,224,967, −1.8%) with R6's at 2.98.0 (5,231,437, −1.7%) without saying which is which.

Questions

  • The retirement rule. Main's rule is register only when the row sits outside 25% at the calibrated baseline. Carer and childcare are retired here on a design-weight twin ratio times the Q50f count — a projection, and the commit that retired them predates the twin whose −0.3% and −16% the body quotes. That is a looser standard than the one you wrote on Bind the UC element targets on paid claims and an OBR UC total on the chronicle 474a0ae feed (#882) #921; María should say whether a projection is enough or a calibrated run is needed first.
  • Hours-only carers. 2.98.0's flag qualifies anyone with 35 reported hours with no check on the cared-for person, which #1857 documents; R0 shows about 30% of 35-hour carers are not caring for a household member on a qualifying benefit. That approximation is accepted upstream — where is it declared in microcosm, on the retirement note or the fragment?
  • The U8 citation says "without moving any Universal Credit outcome" and then "paid UC moves −1.8 percent".
  • The cap-band adjudication strings cite diagnose_uk_legacy_benefits.py, copied from the HB and JSA entries; the cap tool appears only in the reasons. The chronicle issue is EPIC: populace-be — Belgium as the first Axiom-engine country #259 in one place and Implement an Axiom-backed RulesEngine adapter #260 in two others.
  • The Carer's Allowance result should be stated as "by construction" in the body, citing R6's zero weighted mismatches, rather than as an empirical finding.

Nits

ruff format --check flags test_uk_measure_simulation.py:702; CI runs ruff check only. Stale strings: the frame adapter still says >=2.97.0, two test stubs pin __version__ = "2.97.0", and the childcare fragment says "until the policyengine-uk release that reads it lands" when 2.98.0 now does. The APPROVED_UV_LOCK_SHA256 re-pin is not called out in the body.

Could not verify

The FRS hourtot dictionary (only R0's list is in the repo), every twin figure, the requires_uk 66 test and the fit tool run, the feed-gated regeneration, and whether the 2026-09-16 status comment is the ruling the adjudications point at.


Nothing blocks on correctness; 1 and 2 before this leaves draft, 3 because a declaration no code reads is the kind of thing that drifts silently, and the retirement-rule question is María's.

@juaristi22

Copy link
Copy Markdown
Collaborator Author

Thanks Vahid. Round 1 addressed in 3b92516, a0cc8b1 and f275613 on top of 095a9af, plus 68f3155 for the CI fallout of the round (stale parity-fixture graph JSON after the rebase, an eager engine import in the identity tool, the hermetic driver spec missing the declaration the stage now asserts); the body carries a "Review round 1" section.

Should-fix

  1. Adjudications: the eighteen entries now cite the ruling that exists, the 2026-09-16 Align UK UC claim, child and period definitions #882 status comment (issuecomment-5694598278) and the plan María approved that day with her population ruling on U8, with the approver as author per the register convention. The register test pins that reference on every entry of the tranche. Cap bands cite tools/diagnose_uk_benefit_cap.py; the A16 and HB rows cite the legacy tool; chronicle references read "asked in UC element counts by Payment Indicator, housing-tenure relabel, childcare element amounts, earnings indicator and Housing Benefit caseload, January 2023 to the latest month, for the microcosm UC target surface chronicle#259, delivered by Add the UC element, employment, Housing Benefit and childcare packages issue 259 asks for chronicle#260".
  2. Cap-support join: positional on the engine's household order (populations["household"].members_entity_id through each unit's first member), no id cast, so nothing can collide; the tool refuses if the engine's household count or positions disagree with the spine table. Re-measured: the spine's 192 capped records rest on 93 source households and the tail's 8 records on 4 (the float32 join said 176 and 8); repaired-298b 182 records from 88, same tail. Receipts, the baseline doc and the body carry the corrected counts. The conclusion (thin base, selection-and-calibration effect) stands and is now stronger. An engine issue on the float32 id arrays is drafted for María's go.
  3. Population declaration: uk_take_up_population_policy(build_period) reads gov.dwp.state_pension.age.male and .female at {year}-01-01 from the engine parameters (asserted equal, otherwise the stage refuses until it consumes sex) and carries the engine's is_adult threshold, 18; the transform and the signal gate resolve it from the frame's period (tests inject it), and the gate records it in its details. assert_take_up_stage_population_declaration runs on every stage call and refuses a manifest whose would_claim_uc operation stops declaring population: uc_age_eligible or whose aggregate stops being any_adult_under_state_pension_age over age. The edge is now the engine's 18 rather than 16; is_uc_eligible needs is_WA_adult, so the units that drop out of the draw (a non-dependent 16 or 17 year old as the only adult) had no UC either way. A requires_uk test checks the policy reproduces is_WA_adult for ages 0 to 100.
  4. Fit receipt: basis and status now say the fit is at the weights the input carries; input_h5 is recorded relative to data/ukds/acceptance; re-fitted at 2.98.0 on Q50f and spine-q (rates unchanged, 0.441 and 0.2682; the receipts' engine_version and the pinned facts digest now match the branch).
  5. SPI donor-copy: declared on both childcare contract citations and on the signed net-new difference (synthetic units inherit the donor's flag; realized count on a thin base sits below rate times base; the projection is conservative).
  6. Twin provenance: backup/uk-uc-repairs-882-pre-934 is on origin, so the twin commits resolve; the R1 to R3 receipts are under data/ukds/acceptance/882-uc-repairs/ and the receipts say so.
  7. Expiry: 2026-12-08 is the zero-band clock already on the register (UK dataset sizes: informed L0 and exact-count candidates (#355) #877), chosen so the whole UC-repair tranche is re-adjudicated together at the next spine rather than on the one-month or 10-to-15 regimes; the adjudications now say this.
  8. Body: 613 → 630 on df35af7; the paid-UC figures are labelled by twin and engine (−1.8 % at 2.97.0, −1.7 % on repaired-298b at 2.98.0); APPROVED_UV_LOCK_SHA256 named.

Questions

  • Retirement on a projection: yes, both element rows are retired on the design-weight ratio projected onto Q50f, not on a calibrated run. María ruled today that the projection retires them, and they go back on the register if the calibrated run on this spine fails either row; the receipts and the body record it.
  • Hours-only carer approximation: declared on the carer target note (the hours limb does not check that the person cared for is on a qualifying benefit) and now in the carer changelog fragment and the body.
  • U8 citation: the "without moving any Universal Credit outcome" sentence is gone; the citation says the pension-age units have no UC either way while the working-age draw moves with the population definition, and points at the receipts.
  • Cap-band adjudications: they cite the cap diagnostic; the chronicle references are consistent (EPIC: populace-be — Belgium as the first Axiom-engine country #259 asked, Implement an Axiom-backed RulesEngine adapter #260 delivered).
  • Carer's Allowance: stated as by construction (the flag is reported receipt) and measured (0 weighted mismatches, spend and recipients identical to the control on repaired-298b).

Nits: ruff format --check is clean on the test file; the frame adapter guidance and the two resolver stubs say 2.98.0; the childcare fragment no longer waits for a release that has landed.

@juaristi22
juaristi22 marked this pull request as ready for review September 16, 2026 20:26

@vahid-ahmadi vahid-ahmadi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closing pass at 68f31555 (Claude Code, high effort; worktree checkout, the five response commits read against the dispositions; without the engine, the tape or the feed: 591 passed, 28 skipped, 0 failed across the touched, take-up, spine, parity, spec and lock-digest suites; ruff check and ruff format --check clean on all ten changed files; the coverage manifest, the spec-engine coverage check and ci_test_groups --verify current; CI 24 of 24 green; merge-tree clean against main).

All eight should-fixes closed, the questions answered, and the CI-fallout commit does what it says with no fence weakened.

1. Verified, with one thing to say plainly. All 18 tranche entries cite the 2026-09-16 status comment on #882 with the approver-as-author convention stated, the register test pins that reference, the zero-band clock and the tool on every entry, the cap bands cite the cap diagnostic, and the chronicle references read "asked in #259, delivered by #260"; the prospective sign-off text survives only on the #921 row, where it names a real event. The cited comment was created at 08:40 and edited at 14:27 to tick the #937 outcomes, so it is a ruling annotated onto the status comment after the fact rather than one that preceded the work. That is fine under the convention, but the closing review should say it. The other half of every citation, "the repairs plan María approved on 2026-09-16", resolves only to a local file; the U8 ruling text does exist in-tree on the take-up contract and in the receipts, so cite one of those, or post the plan on #882.

2. Verified in code; counts on your word. The join now takes members_entity_id as int64 positions through each unit's first member and indexes the spine's source ids positionally; no float32 cast remains. The refusal is a count check rather than an order check — the positions.max() clause is redundant once counts match — and the order claim rests on --dataset and --spine being the same file, which the help text requires. A cheap real guard would compare the spine's household ids at those positions to the engine's. The corrected 192 / 93 and 8 / 4 are in the receipts, the baseline doc and the body; I cannot re-measure them here. María's own comment still says "a base of eight source households".

3. Verified. UK_STATE_PENSION_AGE is gone. uk_take_up_population_policy reads the male and female pension ages at 1 January of the build year from the engine parameters and refuses if they differ; the adult threshold is 18 and documented as pinned by the lockstep test; without the engine there is no fallback, only an import error, and the unit tests inject the policy honestly. The declaration assertion runs first on every stage call and refuses a manifest that drops population: uc_age_eligible or the aggregate; the requires_uk test reproduces is_WA_adult for ages 0 to 100 and ran in both engine-uk lanes. One nit: the refusal test mutates only population, never the aggregate, so the second clause is untested.

4–8. Verified. The fit receipt states the input's weights, a portable path, engine 2.98.0 and the branch's facts digest with rates unchanged; the SPI donor-copy caveat is on both childcare citations and the signed difference; the tag resolves on origin at ca958d08 and the twin commits with it; every tranche entry explains the 12-08 clock as #877's zero-band clock, which is correct; the body carries 613 → 630 on df35af7, labels the two paid-UC figures by twin and engine, and names the lock digest, which equals the lock's sha. The retirement-on-projection ruling is recorded with the "back on the register if the calibrated run fails" condition; the hours-only carer approximation is on the target note, the fragment and the body; the U8 sentence is reworded; Carer's Allowance is stated as by construction and measured. In the fallout commit the spine-fixture pin moved because f2756136 edited the take-up contract, not the rebase — an attribution nit only.

Nits. R1–R3 receipt headers give bare data/ukds/acceptance/… paths where R4 says "licensed, this machine"; the directory is not in the tree and data/ is not gitignored. The status comment's "three zero bands at 12-08" is two at 12-08 and one at 11-26, pre-existing.

Ordering. Against #927 at 34a5c23a: twelve conflicting files — both parity receipts, membership, the coverage manifest, four test pin files, the data contract and its test, and the spine fixture's person table and graph. This branch pins df35af7 and #927 pins ec20085; whichever lands second re-pins the feed and regenerates.


Approving. The register now cites what exists, the population is read from the engine rather than a literal, and the diagnostic can no longer merge households. The plan citation is the one line I would still change.

juaristi22 and others added 19 commits September 17, 2026 11:26
… applier matches them (#882)

The three element rows signed in microcosm#921 were keyed by their binding
metric names (dwp/uc/elements/carer, /childcare, /housing). The measure-
exclusion applier matches spec.name, which is the reference name, so those
entries matched no compiled spec and every calibration run at main would have
refused with "matched zero registry specs". Rename them to
dwp.uc.households_{carer,childcare,housing}_element, move the test pins with
them (census marker, tranche tuple, the in-objective rows), and add a test that
every register name resolves to a committed reference name.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…age, bind the HB caseload and benefit-cap bands as diagnostics (#882)

Part A and Part D of the #882 repairs plan. The SPI support channel turns out
to carry reported Housing Benefit and JSA into owner-occupied synthetic
households (374k HB reporters, 161k of 181k JSA reporters on spine-q), and
the 55 percent would_claim_uc draw marked 3.75m all-pension-age units as UC
claimants, zeroing Housing Benefit for 377k reporters although they cannot
claim UC. The draw's population is now benefit units with an adult under
State Pension age (rate unchanged, U8 entry re-signed); the two A16 rows are
re-adjudicated to 2026-12-08 on the receipts; the DWP Housing Benefit
caseload by tenure and the fourteen benefit-cap amount bands bind at
benefit-unit grain, measured on the register except the up-to-GBP-100 band;
the ESA rows declare the single-type and credits-only reconciliation.
References regenerated on the pinned 474a0ae feed (603 -> 620 active, no
existing row changed) with both national compile-parity receipts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…882)

The engine pays the childcare element to every eligible family with
childcare costs (456k paid-UC units on Q50f against 179k published, with the
couple share at 28 percent against 19). The frs_take_up stage now draws
would_claim_uc_childcare at one contract rate per family type (the FRS
couple flag persisted as is_married), fitted by
tools/fit_uk_uc_childcare_takeup.py so the expected paid-UC count of each
family type equals DWP's calendar-2025 mean at the Q50f calibrated weights
(single 0.441, couple 0.2682; the spine-q design-weight fit is recorded in
the receipts). The column rides the extra export surface until the
policyengine-uk release that reads it; the signal gate measures its share on
the same family-type population, the H2 parity fixture and the coverage
manifest are regenerated, and the column is signed as net-new.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ids; record the repaired twin's receipts (#882)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…king-age adult (#882)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…identity-stability synthetic frame an is_married column (#882)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ngine floor to policyengine-uk 2.98.0 (#882)

Part B1 of the #882 repairs. The root spine stage maps the FRS adult HOURTOT
care-hours band to weekly hours at the band's lower edge (codes 5, 6, 7 and
10 reach the 35-hour Carer's Allowance line) and sets
would_claim_carers_allowance from positive reported receipt, so policyengine-uk
2.98.0 (PolicyEngine/policyengine-uk#1857) qualifies 35-hour carers for the
Universal Credit carer element while keeping the allowance on reported
claimants. The three net-new engine inputs (care_hours,
would_claim_carers_allowance, would_claim_uc_childcare) ride the extra export
surface, since the coverage manifest measures the incumbent's input surface;
each is signed as a net-new column. Engine floors and the lock move to
2.98.0 with the approved lock digest; the H2 parity fixture's adult table
gains a HOURTOT column and the fixture, the coverage manifest and the
gate-battery digests are regenerated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…receipt on SPI-redrawn rows (#882)

The root stage sets would_claim_carers_allowance from reported receipt, but
hmrc_spi_income_spine re-imputes carers_allowance_reported on the
SPI-redrawn rows; on the 2.98.0 twin 433k weighted redrawn persons kept a
true flag over a refilled zero (415k of them at 35 or more care hours, so
the engine paid them the allowance) and 388k kept a false flag over a
refilled positive receipt, moving Carer's Allowance spend by 3.4 percent. The
stage now refreshes the flag from the refilled amount beside its disability
refresh, and the graph declares the hidden rewrite cell.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…in evidence (#882)

Paid-UC carer-element units rise 51 percent at design weights on the
repaired twin (Q50f projection -17 percent of the calendar-2025 target) and
the childcare take-up draw reaches the element (projection -24 percent), both
inside the 25 percent bound the #921 rule sets for activation. The rows
leave the reviewed exclusion register; their binding notes record the
repairs and the realization caveat; the any-tenure housing row stays held
out. Register 69 -> 67.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…H2 fixture after the SPI carer-flag refresh (#882)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ement +80 percent, childcare -16 percent projected (#882)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… df35af7 feed after rebasing over #934 (#882)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…fit hierarchy category (#882)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Point the eighteen 2026-09-16 register entries (the two re-adjudicated A16
rows, the three Housing Benefit caseload rows and the thirteen benefit-cap
bands) at the 2026-09-16 #882 status comment and the approved repairs plan
instead of a prospective sign-off, say why the tranche shares the
2026-12-08 zero-band expiry, and cite the benefit-cap diagnostic on the cap
bands rather than the legacy-benefits one. Chronicle references name the
issue and the delivering PR. The register test pins the ruling reference and
the expiry rationale on every entry of the tranche, and the test file is
ruff-format clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The take-up stage no longer hardcodes State Pension age. A
UKTakeUpPopulationPolicy is read at the build instant from the engine's
gov.dwp.state_pension.age parameters (male and female asserted equal) with
the engine's is_adult threshold, so the population is exactly the units
with an is_WA_adult member (the lower edge moves from 16 to 18; is_uc_eligible
already needs a working-age adult). The stage transform asserts that the
manifest still declares the uc_age_eligible aggregate and population on the
would_claim_uc operation, so the declaration cannot drift from the code, and
the signal gate records the policy it measured against. Hermetic tests inject
the policy; a requires_uk test checks the reader reproduces is_WA_adult for
every age 0 to 100; a manifest that drops the declaration is refused.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Benefit-cap diagnostic joins support households positionally on the
engine's household order instead of float32-cast ids (the spine's 192 capped
records rest on 93 source households and the tail on 4, not 176 and 8);
receipts and the baseline doc carry the corrected counts. The childcare
fitter records the input's weights and a portable path and its receipt is
re-fitted at policyengine-uk 2.98.0 (rates unchanged). The contract citations
declare the SPI donor-copy of the take-up flags and drop the claim that the
population change moves no Universal Credit outcome; the signed net-new
difference says the same. The frame adapter guidance and the resolver test
stubs name 2.98.0; the childcare fragment no longer waits for a release that
has landed; the carer fragment declares the hours-only limb.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Regenerate the H2 parity fixture's pinned graph JSON, stale since the
rebase over #934. The identity-stability tool takes the take-up population
policy as an argument and reads it from the engine only in main, so the
hermetic E4 receipt test injects one instead of importing the engine. The
driver tests' synthetic spec declares the take-up population the stage now
asserts, with the population reader stubbed like the other deferred
parameter readers, and the refusal message lists the operations it saw.
The receipts record María's 2026-09-16 ruling that the design-weight
projection retires the two element rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#927 (#882)

The rebase merged the targets contract at the target level (298 targets,
247 in registry scope, 55 unmapped declarations) and kept main's energy
and transport surfaces. On the merged sources: national references
614 to 631 active with no existing row changed, national compile-parity
receipts, the release-input coverage manifest, the H2 parity fixture and
the gate-battery and certification digests regenerated; the vendored
energy facts check clean against the artifact; the count pins move to the
merged totals.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@juaristi22
juaristi22 merged commit d1196af into main Sep 17, 2026
24 checks passed
juaristi22 added a commit that referenced this pull request Sep 17, 2026
#927 and #937 (#725)

The rebase kept the branch's Chronicle pin (c5e5bf8 is main's ec20085 plus
chronicle#273) and merged the targets contract, the signed-exclusion
register, the gates manifest and the data-contract rosters at the entry
level; main's retirement of the carer and childcare exclusion rows stands,
so the branch's rename of those entries falls away. On the merged sources
every vendored resource is re-vendored from the c5e5bf8 artifact (rows
identical to main's, feed identity moved), the national references go
from 631 to 705 active with no existing value moved on either surface
(the 74 CGT rows are the only additions), the compile-parity receipts, the
release-input coverage manifest, the data-target census and the H2 parity
fixture regenerate, and the gate-battery and certification digests are
re-pinned. The gate roster, the export-column list and the contract's
target order follow the reviewed positions rather than the merge's
appends; the count pins move to the merged totals (705 / 13 / 307 / 256).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Sep 18, 2026
Should-fixes. The fan-out rationale no longer says Table 2.1a publishes no
tax column: Chronicle's package emits taxpayers and gains only while the
published sheet carries an amounts-of-tax column, so a liability-by-size-of-
gain family waits on Chronicle. The five CGT signed exclusions carry an
adjudication pointer to the rulings comment on the PR. Every measurement
receipt records its build commit, H5 digest, declared seeds, time period,
sampling receipt and Chronicle pin; the note maps each pre-rebase commit it
cites to its rebased counterpart and names the backup tag
uk-cgt-725-measured-pre-rebase; Part C credits #937 with the exclusion fix.

Questions and nits. The within_band_draws cell_means declaration says the
open band's means are ratios of rounded publisher numbers; the region
targets' notes state the error of one national trust share; the asset-type
gate's docstring and manifest notes bound the count by one carrier row's
weight and describe the Bernoulli sigma as a conservative envelope; the
policy_parameters artifacts declare policyengine-uk>=2.98.0; the membership's
signed_row value is decoded rather than JSON-quoted. References, membership,
coverage manifest, H2 fixture and the gate-battery and certification digests
regenerated; no compiled value moved.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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