Skip to content

UK: port the #1095 fixes that need no new policyengine-uk release - #1200

Merged
juaristi22 merged 34 commits into
mainfrom
uk-data-ports-1095-3
Oct 13, 2026
Merged

juaristi22 merged 34 commits into
mainfrom
uk-data-ports-1095-3

Conversation

@juaristi22

@juaristi22 juaristi22 commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

This is the third #1095 ports PR. It covers every actionable item on #1095 that waits for no policyengine-uk release, on the locked policyengine-uk 2.123.0.

The branch is rebased onto main 977ac983a (after #1167, #1188, #1145 and #1198). A calibration of this head passes all 7 calibrated-seam gates, as main does. Its loss is 0.00586 against main's 0.00611.

Before any national build: the renewals of the measure exclusions that expire from 15 October. After merge, as for #1100 and #1121: the signed-difference entries for the new and changed columns (D1, D5, D3, D4), whose drafts stay outside the repository until signed, then the national build and the certifier.

Summary

Root columns (D1, D5, D6)

Take-up and capital (D2, D3, D4)

  • D2. The UC take-up population is benefit units with a UC claimant or partner aged 18 or over and under Pension Credit qualifying age, the age half of the engine's is_uc_eligible (Supply is_hbai_dependent_child from the FRS and move targets and imputations off the deprecated is_child/is_adult policyengine-uk-data#486, Supply is_claimant_or_partner from the FRS adult table policyengine-uk-data#524). An engine lockstep test names the two cases it doesn't mirror: reg 8 claimants aged 16–17, and mixed-age couples on the Pension Credit route. The contract's population text is re-signed with the rate unchanged.
  • D3. Property in recorded capital. UC and Pension Credit recorded capital add the household's countable property to the financial carrier. The engine replaces every capital source with the recorded figure, and the FRS capital question holds no property.
    • Who holds it. The household reference person's benefit unit holds all of the household's property, and no other unit records any (María's ruling of 2026-10-12). Each means test counts the claimant's and partner's own capital, and WAS records property for the household. The engine's proxies share it over every claimant or partner, which puts a parent's property on an adult child's claim; they apply to fewer than 10 units.
    • At what value. 90% of its value: market value less 10% for the costs of sale. Secured debt is not on the frame and goes with the WAS property draw (Value means-tested capital less 10% sale expenses and secured debt (UC reg 49 and legacy equivalents) policyengine-uk#1969 is the engine side).
    • A consequence. The reference person is the householder with the highest income, who is not always the owner. Where that person is of working age, the property of a pension-age unit in the household is recorded on the reference person's unit, and no Pension Credit assessment counts it. On the head's spine, 0.04m pension-age benefit units are in such a household.
    • A unit that reports Universal Credit keeps its receipt and records no UC property. DWP assessed its capital to pay it, and the property is imputed without that receipt.
    • For Universal Credit, D3 is complete only after the next engine bump. policyengine-uk 2.123.0 holds uc_reported_capital flat in its dataset projection, while Pension Credit recorded capital and the property columns project by GDP per head. So in 2025 and later the same property counts at a lower value for UC than for Pension Credit. policyengine-uk 2.124.0 projects it too (Record benefit-unit capital once; derive UC and Pension Credit reported capital from it policyengine-uk#2201).
    • The reporter redraw screens an FRS unit that reports UC on its carrier alone, so that unit stays in the training set.
    • The uk_uc_capital_coherence gate re-derives both capitals with the same rule, and now checks Pension Credit capital.
  • D4. Pension Credit take-up (Solve Pension Credit take-up over entitled benefit units policyengine-uk-data#510) is solved over Great Britain, where DWP's rates apply, and drawn UK-wide. Units not entitled are drawn at the 37% rate, so a reform that entitles them pays them. Entitlement is read at the calibration year, on the frame policyengine-uk projects there from its survey year.

Targets (T1–T6)

What the measurement changed

The first head bound the top UC band and fitted the two PIP rows. It failed two gates, and calibrate-only runs on its spine attributed each failure to one change.

  • The PIP rows fail the weight-ratio gate. They fit within 0.1%, but on that spine uk_weight_ratio failed with them and passed without them. So T6b holds them as diagnostics.
  • The top UC band costs the UC total. Unbound, the calibration holds 2.57 times DWP's count of lone parents in it and 1.99 times for couples with children, £13.1bn of UC, and reaches OBR's £76.1bn. Bound to DWP's counts, obr.universal_credit falls to −5.6%. Couples without children have fewer than 10 households in the band. So the band stays unbound, as on main. The awards below it are too low for DWP's band counts and OBR's total to fit together, which needs its own issue.
  • D3 for every unit put UC reporters over the capital limit. On the Arm 2 spine, 30+ FRS-channel rows that report UC went above £16,000 through property alone. About three quarters were in households with no property income, and half in households with two or more benefit units. Hence the reporter rule.
  • D1 from the donor's status flagged SPI rows with no trade. 1.67m weighted SPI-channel people were flagged with no self-employment income. Hence the earnings route for SPI rows.

María ruled on all four on 2026-10-10. The sparse-band support floor the plan added moves to its own PR.

Review round 1 (11 October)

An adversarial review of cf42d8a7a is in the comments. Five commits answer it.

  • D4 read 2024 values against 2025 rules, because the engine adapter stamped the frame with the entitlement year. The frame is now stamped with its survey year and projected. Units entitled to Guarantee Credit fall from 1,354k to 1,247k.
  • T5 rebased the 2022-23 SPI donors to 2024 by GDP per head, and so dropped the rise in savings interest of those years (ONS household interest income is ×1.99 from 2022 to 2023). The donor rebase is main's again, and only the projection leg of the band targets uses GDP per head. This was the cause of the pull on the top income cells that the first write-up assigned to T5 as a side effect.
  • D3's training screen dropped FRS units that report UC and hold imputed property. It now screens them on their carrier.
  • D6 let a redrawn SPI row keep its FRS twin's landlord flag, which turned the CGT residential signal on for 695k weighted SPI-channel people. Redrawn rows now drop it.
  • T2 now checks its classes against the engine's earned_income_tax.
  • W. The branch retired the West Midlands deferral and then restored it. Both commits are overtaken: UK landlords: bind the SPI net property amounts, leave property_wealth to the engine, add receipts and finance costs #1145 retired the deferral on main, and One consistent UK target surface: enforced cross-grain reconciliation, declared uprating holds, nation household controls and the 942a1fa re-pin (#1123 PR A) #1198 reconciles the cell, which is at +0.2% on this head.

María's rulings of 12 October, from the review:

  • D3, who holds the property. Shared pro rata, £104bn of property sat on 0.99m benefit units that are not the household reference person's. Now the reference person's unit holds it, at 90% of its value. Measured before the rebase, on the recalibrated head:
    • units over the UC capital limit through property fall from 2.65m to 1.50m, and almost none of them is another unit than the reference person's (1.16m before);
    • in households with other property, units that are not the reference person's are paid £1.16bn of UC across 216k units (£0.60bn across 85k before). The UC total is on target both times.
  • Secured debt waits for the WAS property draw. On the WAS donor file, mortgages on other property are 26% of its value.

Re-review and rebase (12 October)

A re-review of bcbe274b2 is in the comments. It asked for the rebase, a new measurement, and five smaller changes.

  • The rebase. The 24 commits are replayed onto main 977ac983a. One commit, 4dac8a31d, then sets the tree to a merge that was regenerated and tested as a whole: hand-written files merged by hand, generated files regenerated on Chronicle 942a1fa. So the branch does not bisect across that commit.
  • The merged surface: 1,279 active references (main's 1,281 less obr.ni and the all-rates salary-sacrifice relief), 396 contract targets and 65 measure exclusions.
  • One consistent UK target surface: enforced cross-grain reconciliation, declared uprating holds, nation household controls and the 942a1fa re-pin (#1123 PR A) #1198's salary-sacrifice hold named hmrc.salary_sacrifice.it_relief_total. That row is a diagnostic here, so its id leaves the signed hold. The hold keeps the three rate bands and the employee NICs relief.
  • D3's reference-person rule (N3, N4): two engine tests and the module text now state what the rule leaves. They cover the pension-age case above, and a unit on the unavailable sentinel, which stays on the engine's proxies beside a recording unit.
  • T2's refusal (N5) allows £1 plus four float32 steps at the person's own amounts, since the engine computes in float32.
  • The Pension Credit stage resolves its declared survey_year rules and refuses a frame from another year. The adapter's dataset_period argument has a named protocol.
  • Main's new pins caught two things on the rebased branch, both fixed: a pinned kernel hash that a typing-only addition moved, and an import that One consistent UK target surface: enforced cross-grain reconciliation, declared uprating holds, nation household controls and the 942a1fa re-pin (#1123 PR A) #1198's coverage test wants declared.

Vahid's review (13 October)

Still open: the signed differences. The committed uc-reported-capital-net-new-column text predates D3, and its redraft waits for María's signature.

Where this departs from the plan

  • D5 reads the foster relationship code alone. The FRS records few looked-after children (10–29 child records), and the carer's fostering allowance covers fewer still.
  • D2 doesn't use Max's test scaffolding, which isn't on main. His uk-take-up-per-person-spa branch solves the State Pension age by date of birth and will need a rebase onto D2.
  • D3 gives the property to the household reference person's unit at 90% of its value, where the plan mirrored each engine proxy (María's ruling of 12 October).
  • T3 adds an exact-id geography pin: the substring rule would read "scotland" in the row's concept.
  • T3 and T4 give their compile-parity rows their own rationales.
  • T5 changes the projection leg of the targets only (review round 1).
  • T6b lets a diagnostic reference carry a fact dated by month.
  • T1 leaves the top band unbound, and the support floor moves out (above).

Measurement

Each arm was built and calibrated locally on Chronicle 942a1fa with policyengine-uk 2.123.0. The control is this branch's base, main 977ac983a. The head arm ran on the tree of e87e2ce7d; the commits after it change no stage and no target.

Full numbers are in experiments/1095-uk-data-ports-3-receipts.md.

Testing

  • Each commit of the earlier rounds: touched-surface tests. The rebased commits before 4dac8a31d were not re-tested one by one.
  • This head (lanes run at 30ef5546f; the commits after it change the receipts, the version strings and the stage text regenerated from them, with 1,545 touched-surface tests passing):
    • the engine-free lane's 558 test files, with policyengine-uk blocked as in CI: 16,708 passed and 148 skipped. The three failures are environmental: the live Hugging Face pointer test fails on main too, the telemetry token exchange needs the hosted service, and main's US head-to-head scorer test needs Python 3.14.7;
    • the engine-uk lane's 43 test files, run in full: 191 passed;
    • against the pinned feed 942a1fa: the national compile-parity receipts regenerate identically, the committed references regenerate, and the diagnostics resolve their exact facts (59 passed, 2 skipped). One feed-gated test fails, and it fails on main 977ac983a in the same way: its gate call reads three 2023 land rows as fixture_only where the receipt signs them ledger_absent. CI skips that test.
  • Lint and CI plan: ruff check and ruff format --check pass on the 67 changed Python files, and so do tools/ci_test_plan.py verify and tools/graph_acceptance_burndown.py --verify.

Overlaps and what follows

Receipts: experiments/1095-uk-data-ports-3-receipts.md.

🤖 Generated with Claude Code

@juaristi22

Copy link
Copy Markdown
Collaborator Author

Independent adversarial review (Claude Code) at cf42d8a7a

Static review only. I read the code, the committed registers and experiments/1095-uk-data-ports-3-receipts.md against main 7639ef8b0, and the installed policyengine-uk 2.122.2 source for engine semantics. I ran nothing. CI is green.

Verdict. Most of the ports are sound:

  • D1 and D2: the rules are as stated.
  • D2 direction: mixed-age couples are correctly inside the UC population.
  • Determinism: no new RNG streams.
  • D6: the flag is dropped at export.
  • T2: the rate-class arithmetic reproduces uk-data's examples by hand.
  • T1, T3, T4 and T6: they do what the body says.
  • Fixture: synthetic.

Three things should change before this leaves draft: D4 reads survey-year values against calibration-year rules, D3 adds gross property to recorded capital, and T5 changes the donor rebase in a way the targets can't absorb. One cross-PR collision with #1198 breaks the national compile whichever PR merges second.

Should fix

  1. D4 reads 2024 values against 2025 rules.

    • Where: pension_credit_take_up.py:232 calls engine.materialize(with_saving, requested, entitlement_year). with_saving is the survey-year frame (time_period=period), and the adapter builds UKSingleYearDataset(fiscal_year=int(period)) from the requested period (microcosm-frame/.../policyengine_uk.py:172).
    • Effect: the frame's 2024 incomes, capital and State Pension are relabelled as 2025 inputs and never projected. On main, both the frame and the call were at the survey year.
    • Example: a single pensioner on the full new State Pension and nothing else is read at about £221.20 a week against the 2025 guarantee of £221.85, so is entitled. On the released file at 2025 the pension is £230.25 and the unit is not.
    • Consequences: the entitled set E is inflated. The residual rates, and the stage-gate receipt, describe a population the released file doesn't reproduce.
    • Fix: materialize at the survey year with the engine computing period 2025, so policyengine-uk projects the frame forward. If that's what uk-data does, cite it.
  2. D3 adds gross property, not countable capital. It does so by mirroring the engine proxy, but the engine treats a recorded figure as already countable.

    • Gross value: UC_PROPERTY_CAPITAL_SOURCES sums other_residential_property_value and non_residential_property_value (Pension Credit adds owned_land) at market value (uc_capital_coherence.py:89-93).
      • UC Regs 2013 reg 49 and SPC Regs 2002 reg 19 value capital at market value less 10% for sale costs and less any debt secured on it.
      • A £120k buy-to-let with a £110k mortgage counts as about £0; this records £120k, so the unit fails the UC limit and Pension Credit deems tariff income on it.
      • was_wealth already draws other_mortgage, but only mortgage_debt reaches the frame.
    • Owners: _household_property_share splits a household's property over every claimant and partner in it (UC), or every member at or over qualifying age (Pension Credit). So an adult child's unit takes a third of the parents' buy-to-let; tests/engine/uk/test_uk_uc_capital_coherence.py:136 asserts the £66,667/£33,333 split. UC counts only the claimants' own capital (reg 18); Pension Credit counts the claimant's and partner's (SPCA 2002 s.5).
    • Size (receipts:108-112): £1,247bn of UC capital over 5.18m units. With the reporter rule, UC falls £0.80bn and 179k non-reporters lose UC.
    • What the reporter rule tells us: about 157k weighted UC reporters would have crossed £16,000 on imputed property, although DWP assessed their capital and paid them. That is evidence the imputed figure overstates countable capital. The rule protects reporters, but the same overstatement still applies to non-reporters.
    • Fix: net secured debt and the 10%, and assign property to its owning unit (the household reference person's unit, or WAS ownership where available) rather than pro rata.
  3. T5's donor rebase drops a known surge that the targets already contain.

    • Where: SPI_INCOME_UPRATING_INDEX_OVERRIDES (spi_income.py:93) rebases the 2022-23 SPI donors' savings interest to 2024 by GDP per head (×1.09) instead of household interest income (×2.27). The commit says donors fall about 52% and the band targets 8.4%.
    • The problem: the engine-alignment argument (uk-data#541, uprating_indices.yaml) is about how policyengine-uk projects a dataset forward from its own year. The 2022 → 2024 donor rebase is history, and savings interest roughly doubled over it as rates rose. The 2023-24 SPI target base contains that rise; the rebased donors don't. So the solve has to make up about half of SPI savings interest through the weights, concentrated where savings sit.
    • What it explains: T5 alone moves the £500k–1m employment band from −2.5% to −13.3%, which an 8.4% cut to savings targets can't do directly. The head still has top income cells at −11.5% and −15.2%.
    • Fix: keep the household-interest index for the historical rebase to the base year and use GDP per head only for the projection beyond it. Or record why not.
    • Index pairing: the amount rows also stay on a flat index while their count rows grow by band. The implied mean per saver falls 4–13% above £30k, against about 1.00 for employment under the UK spine follow-ups after #1045: SPI band donors as a mass-conserving support channel, WAS tenure coherence, the residential draw's bound and Table 7 composition, then a national build #1063 rule. Dividends and property (UK landlords: bind the SPI net property amounts, leave property_wealth to the engine, add receipts and finance costs #1145) have the same pattern.
  4. Cross-PR: One consistent UK target surface: enforced cross-grain reconciliation, declared uprating holds, nation household controls and the 942a1fa re-pin (#1123 PR A) #1198's holds register refuses this PR's T2 in either merge order.

  5. The after-merge sequence needs the exclusion renewals first.

    • dwp.uc.households_housing_element expires 2026-10-15, ons.savings_interest_income 2026-10-22, and the two slc.borrowers.plan_2_* entries 2026-11-03.
    • apply_uk_calibration_measure_exclusions raises outside the window (measure_simulation.py:919-931), so any national build after 15 October fails until they're renewed.
    • This PR's own measurement relied on both near-term entries. T5 moves the model further from the D.41 row that ons.savings_interest_income excludes, so that entry needs renewing, not dropping.
    • Treat the renewal PR as a prerequisite, not a follow-up.

Minor

  1. The measurement isn't of this tree, and the control is confounded.
  2. W's 1.1-point margin across the open PRs.
  3. The signed-difference step is not enforced, and one committed signature is now false.
    • Not enforced: spine_swap_signed_differences.json is read only by manual tools (verify_uk_spine_parity.py, compare_uk_h5_payload.py). The release cut already lists uc_is_in_gainful_self_employment and is_looked_after_by_local_authority as reviewed candidate-only fields.
    • False signature: the uc-reported-capital-net-new-column entry (:525) says uc_reported_capital comes "from the same frs_benunit_capital carrier". After D3 it doesn't, and its only check (that the column exists) still passes.
    • Question: why aren't D4 (would_claim_pc now drawn for 37% of non-entitled units) and T5 (SPI donor savings −52%) on the D1/D5/D3 signature list?
  4. D3 reporter rule.
    • The share doesn't stay put: a reporter records the carrier alone, but policyengine-uk's household residual sums only recorded figures. So a co-resident unit with the −1 sentinel absorbs the reporter's withheld share through the proxy, which contradicts "the other units of its household keep their own shares".
    • Training screen: uc_reporter_redraw.py:280/381 applies carrier plus property to every training unit, including the reporters the stage protects, so real UC reporters drop out of the QRF training set.
  5. D4's 37% draw has an Income Support side effect. policyengine-uk's income_support_eligible bars Income Support where meets_pension_credit_age_conditions & would_claim_pc, with no means test. So mixed-age couples on the Pension Credit route who report Income Support and aren't entitled to Pension Credit now lose Income Support 37% of the time. The population is small, but it's undeclared.
  6. D5's under-16 cutoff (frs_relationships.py:193, below_age: 16) can only drop flags. It misses 16–19-year-old foster children who are qualifying young persons in the carer's unit, who are still looked after (Children Act 1989 ss.22, 105; UC Regs reg 4(6)(a)). test_uk_frs_relationships.py:437 pins a 16-year-old as not looked after.
  7. D6 on SPI rows: the landlord flag is written from the FRS twin (frs_spine.py:661), and no SPI refresh rewrites it. So SPI rows whose drawn property income is zero keep the twin's flag and turn on the residential CGT signal. That's the "donor status says nothing about drawn incomes" problem D1 rules against.
  8. T2.
    • Wrong comparison: the receipt compares the class sum with Δincome_tax, which differs legitimately through savings, dividends and HICBC. The engine variable the allocation re-implements is earned_income_tax, and comparing against that would catch schedule drift.
    • Untested diagnostic: no test checks that the it_relief_total diagnostic resolves. _diagnostic_reference_metadata turns a mismatch into unavailable without failing.
    • Question: does HMRC Table 6.1 classify relief bracket by bracket or by marginal rate? At design weights, basic reads 0.66×, higher 0.53× and additional 1.37×, which takes strong opposite reweighting to fit.
  9. Stale text.
    • tools/build_uk_ledger_compile_parity_signed_differences.py:76 still says the savings rows are "uprated by the household interest index", and that's regenerated into the 13 rows' signed reason, whose values moved ×0.916. docs/uk-income-anchors-280.md says the same.
    • The changelog fragments carry intermediate counts (391/1,232, 390/1,231, "stays at 1,231") where the head is 388/1,229.
    • The W fragment's "+28.9% on main" is pre-Weight UK BRMA assignment by census private-rented households #1087.
  10. Nits.
    • The D1 earnings route silently skips SPI rows if the column is missing (spi_income.py:2082), falling back to the engine default.
    • The D2 lockstep test narrowed from ages 0–100 to 14–90 without saying why.
    • T1 compares annual pence, not DWP's monthly pence (negligible).
    • income_tax_rate_classes.py:65-71 files a schedule's top rate as "additional" even at 40%.
    • The UC skipped-fact count changed from 12 to 8 on the same feed pin with no note.

Merge order with #1145, #1198 and #1167

Checked and fine:

  • Counts: 1,168 targets = 1,229 active − 61 exclusions.
  • Values: T3's £1.34bn (50.116 − 48.774), and T4's total at 1.0182× the class rows.
  • T5's ratios: 0.916 on all 13 rows.
  • T6: a month-dated fact reaches only diagnostics.
  • Exclusions: the three salary-sacrifice exclusions were genuinely redundant.
  • Mirrors: the coverage-manifest and gate digests move consistently.
  • Take-up contract: its rate is unchanged at 0.55.

@juaristi22

Copy link
Copy Markdown
Collaborator Author

Re-review (Claude Code) at bcbe274b2

Static review of cf42d8a7a..bcbe274b2 against the review and the reply. I read the code and policyengine-uk 2.122.2's source, and ran nothing.

Verdict.

  • Fixed: the three main items. D4 now projects the survey-year frame, T5's donor rebase is back on history, and D3 follows the ruling.
  • Answered with reasons that hold up: item 10 (Income Support) and item 11 (D5 age).
  • No regression, and no test edited to match a count.
  • Not ready to leave draft: CI hasn't run on the D3 rewrite, the branch conflicts with main, and every figure is on the pre-Set UK road-fuel and energy levels in the LCFS stage on a calendar-2024 basis (#1113) #1167 base, while the head fails uk_weight_ratio and W sits just over the bound.
  • Disclosure: a few disclosure-control items went to the author separately.

Previous items

# Status Note
1 D4 Fixed materialize(..., dataset_period=) is keyword-only with default None, so other callers are unchanged, and a dataset year after the period is refused (policyengine_uk.py:85-109). The stage and the calibration resolver both load the survey year and compute at 2025. The engine test pins the pensioner case.
2 D3 Changed by ruling The owner is FRS hrpid == 1, checked as exactly one per household (uc_capital_coherence.py:426-467), and property counts at 0.9 (:104). Pension Credit follows the same rule. The gate re-derives with the same functions and refuses a drift in the declared rule. See N3 and N4.
3 T5 Fixed A declared composed index (hmrc_uprating.py:455-525): household interest to the dataset year, then GDP per head. The 13 amount rows move ×1.0086 against main, and count rows don't move.
4 #1198 hold Fixed (text) #1198 holds all four it_relief_* rows, so whichever lands second removes it_relief_total from it.
5 Renewals Fixed The description lists them first. Nit: main's W deferral (expiring 2026-11-03) isn't on the list.
6 Measurement Partly The control is now on the base, and the #1087 attribution holds: between d50220d1f and a469ebe3d the only first-parent change is #1087. But see N1 and N2.
7 W Reverted The register and its test are byte-identical to main.
8 Signed differences Open, acknowledged spine_swap_signed_differences.json:513-525 is now stale twice over (carrier and owner rule), and still nothing enforces it.
9 Reporter rule Fixed The redraw screens a UC-reporting FRS unit on its carrier alone (uc_reporter_redraw.py:386-399). The only remaining leak is to sentinel units, which is documented.
10 D4 and Income Support Not changed, justified In 2.122.2, meets_pension_credit_age_conditions gives a mixed-age couple the Pension Credit route only with the SI 2019/37 saving. That saving needs reported Pension Credit or pension-age Housing Benefit without an income-related benefit, so an Income Support reporter who isn't a Pension Credit reporter can't have it. The engine test pins it.
11 D5 age Not changed, explained frs_relationships.py:186-192.
12 D6 on SPI rows Fixed spi_income.py:2106-2128, graph.py:1023-1026.
13 T2 Fixed / partly The per-person earned_income_tax check refuses beyond £1 (measure_simulation.py:741-763), and the top rate is fixed. The it_relief_total test is feed-gated and skips in CI, where the compile still turns a mismatch into unavailable.
14 Stale text Fixed
15 Nits Partly D2 is back to 0–100, the top rate and 12 → 8 are explained, and D1's skip and T1's pence are kept with reasons.

New

Before leaving draft

  • N1. CI has not run on the fix round.
  • N2. The merge gate picture is on a stale base.
    • The head fails uk_weight_ratio, and W is just over the bound, both measured on 7639ef8b0.
    • Set UK road-fuel and energy levels in the LCFS stage on a calendar-2024 basis (#1113) #1167 changes the solve: four new measure exclusions including obr.vat, the LCFS stage, and feed 1ee7dfe. So after the rebase W may go stale (the gate then fails the deferral) or stay over, and the weight ratio may move either way.
    • The ruling that this PR proceeds while main's breach is resolved separately exists only as text. No issue tracks main's uk_weight_ratio breach (I searched).
    • Open one and link it, and re-measure on the rebased tree before leaving draft.
  • Conflicts with main. These 13 files conflict:
    • the two compile-parity receipts;
    • release_input_coverage_manifest.json;
    • target_reference_membership.json;
    • contract.py and its test;
    • uk_spine.json;
    • test_country_spec.py, test_uk_measure_simulation.py, test_uk_national_calibration.py, test_uk_population_targets.py and test_uk_target_references.py.
  • Merged without conflict but inconsistent:

Minor

  • N3. The reference-person rule moves pensioners' property away from Pension Credit.
    • The FRS reference person is the householder with the highest income, not necessarily the owner of the other property. Pension-age parents living in a household headed by their adult child now record none; it lands on a working-age unit, where Pension Credit never reads it and UC may.
    • The receipts show Pension Credit property on pension-age units falling by roughly £53bn, while design Pension Credit moves only +£0.03bn.
    • It's a consequence of the ruling, not a bug, but it belongs in the D3 signed-difference draft.
  • N4. Sentinel units no longer match recorded units.
    • Units on the −1 sentinel stay on the engine's pro-rata proxies at 100% of gross.
    • The Pension Credit proxy doesn't subtract recorded figures, so a pension-age sentinel unit living with a recording reference person counts the property twice.
    • The engine test's mixed recorded/sentinel case was deleted rather than replaced, and the docstring covers UC only.
    • The population is tiny, but the test is worth keeping.
  • N5. The £1 refusal in T2 is absolute, while the engine computes in float32 (measure_simulation.py:755-756). For very high-pay salary-sacrifice earners, float32 rounding approaches that size, so a correct schedule could stop the build. It fails closed and passes on the current arms. Add a tolerance of a few float32 steps on top of the £1.
  • N6. Merge order with UK landlords: bind the SPI net property amounts, leave property_wealth to the engine, add receipts and finance costs #1145 on W is a semantic collision git won't flag. UK landlords: bind the SPI net property amounts, leave property_wealth to the engine, add receipts and finance costs #1145 empties the register and pins it empty. If UK landlords: bind the SPI net property amounts, leave property_wealth to the engine, add receipts and finance costs #1145 lands first, this PR inherits an empty register with its cell over the bound, and uk_target_fit fails. The description notes the deletion but not that consequence.
  • N7. Recorded property still overstates countable capital. By the receipts' own WAS figures it's about 64% of gross after debt, against the 90% recorded. The only tracking is pe-uk#1969, which is the engine side, so a microcosm issue for the WAS mortgage draw would keep it visible.
  • Nits.
    • dataset_period sits outside the RulesEngine Protocol (microcosm-frame/rules.py:80-92).
    • The declared dataset_year_rule: survey_year is never resolved; the code uses uk_time_period(frame).
    • _refresh_landlord_carrier silently skips a missing column (spi_income.py:2118-2119), the same pattern as D1.

juaristi22 and others added 27 commits October 12, 2026 23:33
The calibration of #1121's head ran on 2026-10-08 (arm head-r3, tree
c7fe76e8a, the tree of the merged head): loss 0.00712, 98.3% of 1,167
targets within 10%, 7 of 7 seam gates and the certifier's input-mass gate
passing with the West Midlands deferral and the two UC band exclusions in
force. The results were posted on #1121 rather than pushed, to avoid
another CI run; this records them in the receipts.

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

Nothing set policyengine-uk's uc_is_in_gainful_self_employment, so the
engine's rule applied: any nonzero self_employment_income was gainful.
The spine floors losses at zero, so a trader at a loss or breaking even
never met the Universal Credit minimum income floor (uc_mif_applies, the
input's only reader), while an employee with a small side trade did.

Port uk-data#525's rule (UC Regs 2013 reg 64(a); ADM H4031, H4034): a
self-employed main job (EMPSTATI 3 or 4) whatever its profit, or a side
trade whose SEINCAM2 profit is above zero and above INEARNS pay. The root
stage sets it. SPI-channel rows keep their donor's status but draw new
incomes that do not read it, so after the redraw they take the earnings
route alone, until the SPI draw follows employment status (uk-data#529,
microcosm#840). A net-new export column; its signed difference waits for
María's signature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1095)

The FRS 2024-25 question instructions put foster children under 16 in
the carer's benefit unit, and nothing set policyengine-uk's
is_looked_after_by_local_authority, so the engine counted them as the
carer's own children in its child tests (Universal Credit, Child Tax
Credit, Pension Credit, Tax-Free Childcare, extended childcare, the WTC
childcare element and the legacy benefits).

frs_relationships now sets the flag for a child-table member under 16
whose household-grid code toward an adult in their own benefit unit is
foster child (5), or toward whom that adult records foster parent (9).
The rule is declared in the stage (looked_after_child) and the spec
schema. It follows the relationship code alone: few such children are
recorded, and the carer's local-authority fostering allowance (ALLOW3)
covers fewer still. A net-new export column; its signed difference waits
for María's signature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CGT residential split read property_income > 0 as one of its two signs of
residential property, so loss-making landlords lost the sign and its
log-odds shift.

The spine now carries an internal landlord flag,
reports_rent_from_other_property (a positive ROYYR1, at a profit or a
loss), which the CGT asset-type and residential-split stages read beside
household residential property value and property income. The declared
stock signal text says so. The flag is no engine input: the release
boundary drops it, beside the other internal carriers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Universal Credit take-up population becomes benefit units with a UC
claimant or partner (is_uc_claimant) aged 18 or over and under Pension
Credit qualifying age, the age half of policyengine-uk 2.122.2's
is_uc_eligible. The draw, the SPI-channel redraw and the take-up signal
gate share the helper; both stage manifests declare the role column and
the code refuses one that does not. The contract's UC population text is
re-signed with the rate unchanged.

The engine lockstep test now compares the population with is_uc_eligible
and names the two cases it does not mirror: reg 8 claimants aged 16-17
and mixed-age couples on the Pension Credit route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-uk replaces every capital source with a recorded figure of 0
or more, but TOTCAPB4 holds no property, so no unit was assessed on its
household's second homes, buy-to-let, buildings or land. The coherence
stage now records each capital as the financial carrier plus the unit's
share of the household's countable property, mirroring each engine
proxy: UC shares other residential and non-residential property by
claimants and partners; Pension Credit adds owned land and shares by
members at or over its qualifying age. The -1 sentinel passes through.

A unit that reports Universal Credit keeps its receipt and records the
carrier alone for UC (María's ruling of 2026-10-10). DWP assessed its
capital to pay it, and the property is imputed without that receipt and
shared over every claimant or partner in the household.

The reporter redraw screens SPI reporters on the carrier plus the share.
The capital gate re-derives both capitals from the carrier, the declared
shares and the reporter rule, and now checks Pension Credit capital. An
engine lockstep test shows the shares equal the engine's proxies,
recorded or mixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed units (#1095)

DWP's take-up rates cover Great Britain, so each component band's
residual is now solved there and drawn for every entitled non-reporter,
Northern Ireland's included. A unit with no entitlement that does not
report claims at the Savings Credit-only take-up (37%, new contract key
pension_credit_newly_entitled), so a reform or a later year that entitles
it pays it. Entitlement is read at the release's calibration year, as
uk-data reads it, with the mixed-age saving still stored at the survey
year. The stage gate requires Great Britain receipts, and the receipt
records Pension Credit capital and deemed income by channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-uk projects a loaded dataset's savings_interest_income by
GDP per head (data/uprating_indices.yaml); the household-interest index
its Variable.uprating attribute names only carries a situation input
forward (uk-data#541). The SPI donor rebase and the 13 HMRC SPI
savings-interest band targets now follow the projection: donors fall
about 52%, the band targets 8.4%. Engine lockstep tests hold every
indexed SPI component and every HMRC SPI amount family to the
projection table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DWP reports PIP for England and Wales only since devolution, as obr.pip's
binding records. The two daily-living caseload rows take obr.pip's
country filter and household condition, and a dwp.pip. prefix pin
selects K04000001, the geography Chronicle stamps DWP's January 2025
caseload with. Under the Great Britain default they matched no fact; they
now resolve and add two calibration targets (1,233 active references).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OBR table 4.1 row 15 sums the England, Scotland and Wales council tax
receipts; row 19, bound before, adds Northern Ireland domestic rates,
the accruals adjustment and the high-value surcharge, which no
household's council tax carries (GBP 1.34bn, 2.7%). The row now binds
row 15 over Great Britain's households, pinned to the UK stamp Chronicle
gives OBR's table through a new exact-id pin, since the substring rule
would read Scotland in the concept.

Its compile-parity row gets its own rationale, since the series and the
period basis both differ from the incumbent's row 19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OBR's table 3.8 total is a cash figure against the accrued class rows,
and it carries statutory-payment recoveries, Class 1A, 1B and 3 and
settlements no household carries; at the calendar-2025 window it sits
about 1.8% above the class rows. It leaves the fit as a diagnostic on
obr.ni_employee with its exact FY2025-26 fact (María's ruling of
2026-10-09; uk-data#537 drops it). The contract holds 391 targets and
1,232 active references; obr/ni moves to the excluded incumbent rows.

Its fixture-only compile-parity row carries the ruling, as
obr.state_pension's did (microcosm#1069 R4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The three HMRC Table 6.1 relief bands were held out since #1069 because
their band test compared adjusted net income with after-allowance
thresholds and ignored the Scottish bands. The measure resolver gains a
declared rate-class allocation: one cached counterfactual returns the
sacrifice to pay, and each person's relief is the rise in tax on earned
taxable income, bracket by bracket, under their rest-of-UK or Scottish
schedule from the simulation's own fiscal-year parameters (below 30%
basic, the top rate additional, the rest higher), as uk-data#533 does.
uk-data's worked examples reproduce exactly. The bands bind, the Table
6.2 total becomes a diagnostic on the higher-rate band, and the three
exclusions retire.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DWP publishes the UC monthly award bands in whole pence, each starting a
penny above its predecessor's top. The four payment-distribution bindings
opt in to band_semantics whole_pence_upper_closed: a band is (label lower
- GBP 0.01, next label lower - GBP 0.01] x 12 compared in whole pence, so
a penny-a-month award lands in the bottom band and GBP 100.01 a month no
longer rounds into the band below. The reading is opt-in and refuses an
unknown value; the other 28 banded bindings keep half-open bands.

The GBP 2,500.01 or over band stays unbound (María's ruling of
2026-10-10), and band_upper_bound closes the last bound band at GBP
2,500.00 a month. Bound to DWP's counts, it left OBR's Universal Credit
total 5.6% short on the measured head; unbound, the calibration holds 2.6
times DWP's count of lone parents in it. The generator counts the fan-out
facts it skips instead of declaring 12 (the feed leaves eight). 1,231
active references.

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

Scoped to England and Wales, the two DWP caseload rows resolve, and a
calibration fits both within 0.1%. But fitting them pushes the heaviest
support family's weight from 1,061 to 1,241 times the positive median,
over the uk_weight_ratio fence of 1,151, which holds at the June
measurement. So the rows leave the fit (María's ruling of 2026-10-10):
each is a diagnostic reference on obr.pip, pinned to its exact January
2025 fact, and the dwp.pip prefix pin goes with the targets. Diagnostic
references now accept a fact dated by month. The contract holds 388
targets and 1,229 active references.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On this head the calibration brings the West Midlands GBP 12,570-15,000
income-tax cell to +23.9%, back inside the 25% bound, and uk_target_fit
fails the deferral as stale. The cell sat at +28.9% on main and +26.8% on
this branch's first head; without the PIP caseload rows and the top UC
band in the fit, and with UC reporters keeping their receipt, it comes
inside. The target-fit register is empty again.

The defect stands upstream (pe-uk#2174): the cell's design value is
still 6.2 times its target, so a later change can push it back over the
bound, and it would then need a new deferral.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The arms before and after María's rulings of 2026-10-10, the attribution
of the first head's two gate failures, and the final head's calibration,
gates and test runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pension Credit take-up read calibration-year entitlement on survey-year
values: the adapter stamped the frame with the entitlement year, so a
2024-25 State Pension sat against the 2025-26 guarantee. The UK adapter
takes a dataset_period, the stage stamps the frame with its survey year,
and policyengine-uk projects it forward as it does the released file and
as uk-data#510 reads it.

The reporter redraw screens an FRS unit that reports Universal Credit on
its carrier alone, as uc_capital_coherence records it, so its imputed
property no longer removes it from the training set. The reporter rule's
text says what a co-resident unit on the -1 sentinel takes.

A redrawn SPI row drops its FRS twin's landlord carrier: its property
income is an SPI donor's, and the twin's flag turned the residential CGT
signal on for rows with no drawn letting.

The looked-after rule states why it stops at 16: the FRS makes an older
foster child with a local authority allowance a separate benefit unit.

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

The port of uk-data#541 moved both the SPI donor rebase (2022 to 2024)
and the 13 band targets (2023-24 to 2025) to GDP per head, the index
policyengine-uk's dataset projection applies. The engine projects from
the dataset's year on. The years before it are history, and savings
interest doubled over them as rates rose: ONS household interest income
is x1.99 from 2022 to 2023, GDP per head x1.05. The rebased donors
therefore fell about 52% while the targets, whose SPI 2023-24 base holds
the rise, fell 8.4%, and the solve made up the difference through the
weights of the top bands.

The donor rebase returns to the index the variable names for its own
history. The band targets take two legs: household interest income to
the dataset's year, then GDP per head to the calibration year, a factor
of 1.192 where main has 1.182. An engine lockstep test holds every SPI
component's rebasing index and every HMRC SPI amount family's projection
index to the engine's projection table, and names savings interest's
history index apart. Compile-parity receipts follow in an amend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The salary-sacrifice allocation re-implements policyengine-uk's
earned_income_tax by rate class. Its receipt compared the classes with
the change in income_tax, which differs for good reasons (savings,
dividends, HICBC), so a drifted schedule would not show. The measure now
compares each person's classes with the change in earned_income_tax and
refuses beyond GBP 1. A schedule with one rate above the basic class
files it as higher, not additional.

Tests hold the all-rates total as a diagnostic on the higher-rate band
and, against the pinned feed, that it resolves its exact fact: a
mismatch reads as unavailable and does not fail the compile. The UC
take-up lockstep covers ages 0 to 100 again. The changelog fragments
drop the intermediate target counts and say why the UC fan-out skips 8
facts where 12 were declared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts (#1095)

The 13 SPI savings-interest band rows move with the two-leg index, and
their signed reason says so. Regenerated against the pinned 825406f
facts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This reverts a7e5bc5. With the review's fixes the calibration puts the
West Midlands GBP 12,570-15,000 income-tax cell at +26.9%, above the 25%
bound, so María's signed deferral is needed and is not stale. The cell
was inside the bound (+23.9%) only on the head that rebased the SPI
savings donors by GDP per head, an error the review found. Across this
PR's arms it has ranged from +23.9% to +29.7%; main, this branch's base,
has it at +26.5%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What the adversarial review changed, the control on the branch's base,
the head's calibration (six of seven gates; uk_weight_ratio at 1,155
where main is at 1,213 since #1087), the D3 owner and value figures left
for María's ruling, and the test runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1095)

Recorded UC and Pension Credit capital shared a household's property
pro rata over every claimant or partner, as policyengine-uk's proxies
do. That put GBP 104bn of property on 0.99m benefit units that are not
the household reference person's, such as an adult child's unit beside
the parents who own a second property. Each means test counts the
claimant's and partner's own capital (UC Regs 2013 reg 18; SPCA 2002
s.5), and fewer than 10 units take the engine's proxy, so the recorded
figure decides alone.

By María's ruling of 2026-10-12 the household reference person's unit
holds all of the household's property and no other unit records any,
at 90% of its value: market value less 10% for the costs of sale (UC
Regs 2013 reg 49; SPC Regs 2002 reg 19). Secured debt is not on the
frame and waits for the WAS property draw (pe-uk#1969). The reporter
rule stands. The Pension Credit share no longer reads the qualifying
age, so the stage and its gate take no population policy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The D3 owner and valuation rule and what it changes: who holds the
property, who is over the capital limit, who is paid UC, the stage
receipt and the recalibration (six of seven gates; uk_weight_ratio at
1,176, main at 1,213). Also sorts one import block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The branch is rebased onto main 977ac98, which gained #1167
(consumption-tax levels), #1188, #1145 (property income,
policyengine-uk 2.123.0) and #1198 (target surface, feed 942a1fa). This
commit sets the tree to the merge that was tested: hand-written files
merged by hand, generated files regenerated on the merged tree (rendered
stages, national references and membership, the coverage manifest, the
H2 fixture, the gate digests and the two national compile-parity
receipts).

The merged surface: 1,279 references (main's 1,281 less obr.ni and the
all-rates salary-sacrifice relief, now diagnostics), 396 contract
targets and 65 measure exclusions (main's four new ones, less the three
this branch retires). hmrc.salary_sacrifice.it_relief_total leaves the
signed salary-sacrifice hold in uk/uprating_holds.json: the row is a
diagnostic here, and the compile refuses a hold that matches no held
row. main's empty target-fit register stands: the West Midlands
deferral this branch had restored is not carried over.

The commits before this one do not each regenerate on the new base, so
the branch does not bisect across it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The relief bands refuse where a person's classes leave the engine's
earned_income_tax by more than GBP 1. policyengine-uk computes in
float32, whose step is GBP 1 at GBP 10 million, so rounding alone could
refuse a schedule that still matches for a very high earner. The
tolerance is now GBP 1 plus four float32 steps at the person's own
largest amount. Below about GBP 2 million the steps add less than GBP 1.

Re-review of 12 October on #1200, item N5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A benefit unit on the unavailable-capital sentinel stays on
policyengine-uk's proxies, which share the household's gross property.
Two engine tests hold the cases the reference-person rule leaves:

- beside a recording reference person's unit, the Universal Credit proxy
  leaves a sentinel unit the 10% of sale costs that unit did not record,
  and the Pension Credit proxy, which subtracts nothing, assesses a
  pension-age sentinel unit the gross property a second time;
- where the reference person is of working age, the property of a
  pension-age unit in the household is recorded on the reference
  person's unit, and no Pension Credit assessment counts it.

The module text now says both. Re-review of 12 October on #1200, items
N3 and N4.

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

Copy link
Copy Markdown
Contributor

Thanks María, reviewed at a50ffba5e. Verdict: the "no new release" claim holds on the locked policyengine-uk 2.123.0, and the ports do what #1095 asked. Nothing in the code blocks this from my side once CI is green and 1 is settled. 2 is a record fix, and 3 and 4 are small.

  1. Disclosure: exact unweighted counts for small groups in the receipts (experiments/1095-uk-data-ports-3-receipts.md).

  2. D3 is complete for Universal Credit only after a bump, and the description doesn't say so.

    • On 2.123.0, uc_reported_capital is not in policyengine_uk/data/uprating_indices.yaml, so the dataset projection holds it flat. D3 now puts property into it, so UC assesses property at survey-year values in 2025 and every later year. pension_credit_reported_capital, other_residential_property_value, non_residential_property_value and owned_land all project by GDP per head. So in any year after the survey year, the same property counts at a lower value for UC than for Pension Credit.
    • Your changelog fragment says this (changelog.d/1095-recorded-capital-property.fixed.md:15). But the title and the "Left for later" list don't, and the fix needs an engine release: pe-uk#2201, released in 2.124.0, adds uc_reported_capital to the projection map ("where it used to be held flat").
    • Suggest: add it to "Left for later" and to UK: track the defects uk-data is fixing that microcosm still has #1095's next-bump item, with a note that the D3 receipts at 2025 (units over the UC limit through property) will move at that bump. 2.124.0 also adds benunit_reported_capital, but D3 writes two different figures (land for Pension Credit only, and the UC reporter rule), so microcosm should keep writing both columns directly.
  3. One "fix in microcosm now" item from UK: track the defects uk-data is fixing that microcosm still has #1095 isn't in the description. uk-data#547 (no child aged 9-11 months reaches the working-parent entitlement) is in neither the covered list nor "Left for later", and I couldn't find an issue tracking it. It needs a ruling first, so it's fine to leave out, but one line saying so would stop the next UK: track the defects uk-data is fixing that microcosm still has #1095 audit from reading "every actionable item" as covering it. Everything else in that section is covered, listed as later, or in its own PR (Weight UK BRMA assignment by census private-rented households #1087, Map FRS EMPSTATI 11 to OTHER_INACTIVE and refuse unknown adult codes (uk-data#526 parity) #1097, UK: source the extended-hours usage draw from DfE additional-hours PTEs (#1126) #1127, UK scorecard: populace_uk_2023 vs enhanced_frs_2024_25 vs admin benchmarks (2026) #731, and UK landlords: bind the SPI net property amounts, leave property_wealth to the engine, add receipts and finance costs #1145 for property_wealth).

  4. Minor: added text cites policyengine-uk 2.122.2, but the lock is 2.123.0. There are 12 added lines across 9 files, including the signed registers: uc_capital_coherence.py:77, frs_take_up.py:52, the D3 changelog fragment, source_stages.json, spec/sources.yaml, take_up_contract.json, test_uk_frs_stochastic.py and the parity fixture. I checked what they describe against 2.123.0 and it's the same, so this is wording only. The next UK: track the defects uk-data is fixing that microcosm still has #1095 audit reads these strings, though, so it's worth making them match the lock.

What I checked

  • The no-release claim. uv.lock is unchanged at policyengine-uk 2.123.0. I downloaded the 2.123.0 wheel from PyPI and checked each engine variable and parameter path named in the added code: all 32 variables and all 7 parameter paths exist there. I also read the semantics each port relies on there:
    • is_uc_eligible (D2);
    • the uc_is_in_gainful_self_employment default formula (D1);
    • is_looked_after_by_local_authority and its readers (D5);
    • the UC and Pension Credit capital sources lists and both reported-capital docstrings (D3);
    • earned_income_tax, the schedule on earned_taxable_income by pays_scottish_income_tax that the T2 allocation re-implements;
    • uprating_indices.yaml, which projects savings_interest_income by GDP per head, as T5 assumes, even though the variable's own uprating attribute names household interest.
  • UK: track the defects uk-data is fixing that microcosm still has #1095 against the diff. Each port matches its item, including the latent PIP daily-living rows (T6) and the CGT landlord signal (D6). The departures are the ones the description names.
  • Tests. Fresh uv venvs, with Hugging Face offline and no private data read.
    • Engine-free tests in the 28 touched files: 1,135 passed, 10 skipped (pinned feed or SPI donor not staged).
    • The same test files on base 977ac983a: 37 fail and 8 files don't import (the D3 and reporter-redraw tests among them), so the new tests pin the changes.
    • The touched engine/uk tests on 2.123.0: 31 passed.
    • ruff check and ruff format --check are clean on the 67 Python files, and tools/ci_test_plan.py verify passes.
  • CI and status. The PR left draft this morning, and CI was still pending on this head when I looked.

juaristi22 and others added 2 commits October 13, 2026 11:24
Vahid's review of a50ffba, item 4: twelve lines this branch adds named
policyengine-uk 2.122.2, the lock when they were written. The lock is
2.123.0 since #1145, and what the lines describe is the same there: the
age conditions of is_uc_eligible, and the recorded-capital rule. The
stage notes, the two code comments, a test docstring and the D3
changelog fragment now name 2.123.0; the rendered stages and the H2
fixture are regenerated.

The Universal Credit take-up citation was re-signed on 2026-10-09
against 2.122.2. It keeps that fact and adds that the locked 2.123.0 is
the same.

The D3 fragment also states item 2 of the review: 2.123.0 holds
uc_reported_capital flat in its dataset projection while Pension Credit
recorded capital and the property columns project by GDP per head, and
policyengine-uk 2.124.0 projects it too (pe-uk#2201).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vahid's review of a50ffba, item 1. The receipts gave exact counts of
survey records and benefit units for small groups. Each passed the
file's rule of 10, but some pairs narrowed a remainder, and one paired a
count with its weighted total. They are now bands (<10, 10-29, 30+), as
microcosm#1241 publishes support counts. The looked-after children rest
on fewer than 10 survey households, so their weighted totals are gone.

Items 2 and 3, under "Left for later": Universal Credit's recorded
property stays at survey-year values until the engine bump that brings
pe-uk#2201, and uk-data#547 is named as not covered.

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

Copy link
Copy Markdown
Collaborator Author

Thanks Vahid. All four are done at a23ddcd64.

  1. Disclosure: banded. The receipts and the description now give counts of survey records for small groups only as <10, 10–29 or 30+, as UK local: rerun the 2025 Autumn Budget constituency analysis on the local candidate to find data gaps and per-seat support before Budget day #1241 does.
    • The pairs you named are gone: the total beside "fewer than 10 of them", and the unit count beside its weighted total.
    • One thing more than you asked: the looked-after children are 10–29 child records, but they sit in fewer than 10 survey households. So their weighted totals are removed too.
    • The edit history and the earlier commits are with María.
  2. D3 and the engine projection: stated. The D3 summary, "Left for later" and the D3 changelog fragment now say that uc_reported_capital stays at survey-year values on 2.123.0, that 2.124.0 projects it (Record benefit-unit capital once; derive UC and Pension Credit reported capital from it policyengine-uk#2201), and that the D3 figures at 2025 will move at that bump. They also say that the spine keeps writing both columns, for the reason you give.
  3. Give children recorded as aged 0 an age in months policyengine-uk-data#547: named as not covered. María ruled on 9 October that the fix belongs in policyengine-uk, whose childcare test should read age in months. No issue is filed for it yet.
  4. Engine version: fixed. The lines this PR adds cite the locked 2.123.0, and the rendered stages and the H2 fixture are regenerated. One exception by design: the UC take-up citation was re-signed on 9 October against 2.122.2, so it keeps that fact and adds that 2.123.0 is the same.

Separately, the rebase turned up a defect on main, outside this PR: the release-blocking gate uk_ledger_compile_parity_production_2023 would fail at the next release cut, because the certifier drops the compile's list of unresolved references. The fix is draft #1257.

juaristi22 pushed a commit that referenced this pull request Oct 13, 2026
#840)

Rebased onto #1200's branch (uk-data-ports-1095-3, which carries #1145 and

- The restricted finance costs leaf (property_finance_costs, the last
  stage-1 target since #1145) reaches the FRS rows only at
  property_components, after this stage, so the twin has no analogue and
  the leaf takes identity-keyed uniforms; the analogue map and the
  manifest name it, and the drift test's example of an unmapped leaf
  changes.
- property_components told tape-concept rows from FRS-concept ones by
  re-deriving the recipient rule (channel, age, role), which would have
  read an undrawn copy's twin-valued property income as a tape draw. The
  stage now writes person_is_spi_income_recipient, True exactly for the
  rows stage 1 drew, and property_components reads it, refusing a frame
  without it or with the flag set outside the SPI channel. The column is
  a declared stage output, a typed graph cell, allow-listed beside the
  band-carrier flag in the terminal gates, and backed in the lockstep
  test.
- The UC gainful self-employment refresh from #1200 flagged drawn rows by
  the earnings route alone because the draw did not read employment
  status; has_trade now conditions the draw, so drawn rows take the
  root's whole rule (the self-employed main job as well as the earnings
  route) and undrawn copies keep their twin's flag. The stage evidence
  and the replay carry a receipt of the rows each route flags.
- Regenerated: the source_stages mirror (stage 17, authored key order),
  the release input coverage manifest, the UK spine parity fixture; the
  spec loader's golden hash re-pinned; the end-to-end tests moved earlier
  sit after #1200's in the engine-side file.

Tests: targeted engine-free files (437 passed, 6 skipped), engine-side
SPI and release-coverage files (72 passed), microcosm-graph (756 passed,
7 skipped), microcosm-fit QRF (36 passed); ci_test_plan verify, ruff and
git diff --check clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juaristi22 pushed a commit that referenced this pull request Oct 13, 2026
#840)

R1f of the receipts note: control at uk-data-ports-1095-3 (a50ffba),
treatment the branch on that commit with the integration items; all 37
spine gates pass on both arms. The R1b to R1e receipts reproduce within
a point or two (lone parents' award screen 0.634 FRS / 0.532 / 0.636;
copies earn when their twins earn, 0.587 against 0.583; SPI Universal
Credit at design weights £17.9bn to £36.4bn against £35.9bn on FRS rows).
One FRS-row mechanism is new on this base: property_components ranks
both channels' landlords by profit against the PRIS bands, so the SPI
rows' redrawn property incomes move the FRS landlords' places
(property_rental_income on 3,341 FRS persons, −6.2 %). Aggregates only.

The release input coverage manifest is regenerated on the current
#1200 head (a23ddcd), after the rebase onto it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 13, 2026
docs/evidence/uk-lcfs-2024-25-1164 gains a stage-only run of etb_vat on
the K25 spine for #1200's head and this branch (etb_vat_dev_run.py),
with the stage's support gate. The weighted mean full-rate VAT
expenditure rate moves from 0.401 to 0.398, and by quintile of HBAI net
income it now rises with income where it dipped in the top quintile;
no recipient keeps a negative or zero rate. The README records the
income-concept comparison behind the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22
juaristi22 merged commit 48c0799 into main Oct 13, 2026
10 checks passed
juaristi22 added a commit that referenced this pull request Oct 13, 2026
The branch now stacks on #1200 (which carries #1145 and #1198).
Regenerated from the stacked tree: the release input coverage manifest,
the H2 parity fixture and the gate digests. source_stages.json projects
unchanged from the merged sources.yaml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 13, 2026
The stage-only runs and the NTS-then-LCFS chain re-run with the base
tree at #1200's head (which carries #1145 and #1198) and this branch
with the gas connect leg and the HBAI-gross bands. Every LCFS and NTS
stage-health gate passes on both arms; the README restates the results
on that base, the decisions behind the energy changes and the
prepayment-meter benchmark for #1110. The runner takes a WAIT override
for its quiet-machine wait, and the run script no longer buckets the
support clip's boolean exempt flag as a count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 13, 2026
docs/evidence/uk-lcfs-2024-25-1164 gains a stage-only run of etb_vat on
the K25 spine for #1200's head and this branch (etb_vat_dev_run.py),
with the stage's support gate. The weighted mean full-rate VAT
expenditure rate moves from 0.401 to 0.398, and by quintile of HBAI net
income it now rises with income where it dipped in the top quintile;
no recipient keeps a negative or zero rate. The README records the
income-concept comparison behind the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 13, 2026
docs/evidence/uk-lcfs-2024-25-1164 gains a stage-only run of etb_vat on
the K25 spine for #1200's head and this branch (etb_vat_dev_run.py),
with the stage's support gate. The weighted mean full-rate VAT
expenditure rate moves from 0.401 to 0.398, and by quintile of HBAI net
income it now rises with income where it dipped in the top quintile;
no recipient keeps a negative or zero rate. The README records the
income-concept comparison behind the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 pushed a commit that referenced this pull request Oct 13, 2026
#840)

Rebased onto #1200's branch (uk-data-ports-1095-3, which carries #1145 and

- The restricted finance costs leaf (property_finance_costs, the last
  stage-1 target since #1145) reaches the FRS rows only at
  property_components, after this stage, so the twin has no analogue and
  the leaf takes identity-keyed uniforms; the analogue map and the
  manifest name it, and the drift test's example of an unmapped leaf
  changes.
- property_components told tape-concept rows from FRS-concept ones by
  re-deriving the recipient rule (channel, age, role), which would have
  read an undrawn copy's twin-valued property income as a tape draw. The
  stage now writes person_is_spi_income_recipient, True exactly for the
  rows stage 1 drew, and property_components reads it, refusing a frame
  without it or with the flag set outside the SPI channel. The column is
  a declared stage output, a typed graph cell, allow-listed beside the
  band-carrier flag in the terminal gates, and backed in the lockstep
  test.
- The UC gainful self-employment refresh from #1200 flagged drawn rows by
  the earnings route alone because the draw did not read employment
  status; has_trade now conditions the draw, so drawn rows take the
  root's whole rule (the self-employed main job as well as the earnings
  route) and undrawn copies keep their twin's flag. The stage evidence
  and the replay carry a receipt of the rows each route flags.
- Regenerated: the source_stages mirror (stage 17, authored key order),
  the release input coverage manifest, the UK spine parity fixture; the
  spec loader's golden hash re-pinned; the end-to-end tests moved earlier
  sit after #1200's in the engine-side file.

Tests: targeted engine-free files (437 passed, 6 skipped), engine-side
SPI and release-coverage files (72 passed), microcosm-graph (756 passed,
7 skipped), microcosm-fit QRF (36 passed); ci_test_plan verify, ruff and
git diff --check clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juaristi22 pushed a commit that referenced this pull request Oct 13, 2026
#840)

R1f of the receipts note: control at uk-data-ports-1095-3 (a50ffba),
treatment the branch on that commit with the integration items; all 37
spine gates pass on both arms. The R1b to R1e receipts reproduce within
a point or two (lone parents' award screen 0.634 FRS / 0.532 / 0.636;
copies earn when their twins earn, 0.587 against 0.583; SPI Universal
Credit at design weights £17.9bn to £36.4bn against £35.9bn on FRS rows).
One FRS-row mechanism is new on this base: property_components ranks
both channels' landlords by profit against the PRIS bands, so the SPI
rows' redrawn property incomes move the FRS landlords' places
(property_rental_income on 3,341 FRS persons, −6.2 %). Aggregates only.

The release input coverage manifest is regenerated on the current
#1200 head (a23ddcd), after the rebase onto it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 13, 2026
The branch now stacks on #1200 (which carries #1145 and #1198).
Regenerated from the stacked tree: the release input coverage manifest,
the H2 parity fixture and the gate digests. source_stages.json projects
unchanged from the merged sources.yaml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 13, 2026
The stage-only runs and the NTS-then-LCFS chain re-run with the base
tree at #1200's head (which carries #1145 and #1198) and this branch
with the gas connect leg and the HBAI-gross bands. Every LCFS and NTS
stage-health gate passes on both arms; the README restates the results
on that base, the decisions behind the energy changes and the
prepayment-meter benchmark for #1110. The runner takes a WAIT override
for its quiet-machine wait, and the run script no longer buckets the
support clip's boolean exempt flag as a count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 13, 2026
docs/evidence/uk-lcfs-2024-25-1164 gains a stage-only run of etb_vat on
the K25 spine for #1200's head and this branch (etb_vat_dev_run.py),
with the stage's support gate. The weighted mean full-rate VAT
expenditure rate moves from 0.401 to 0.398, and by quintile of HBAI net
income it now rises with income where it dipped in the top quintile;
no recipient keeps a negative or zero rate. The README records the
income-concept comparison behind the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 pushed a commit that referenced this pull request Oct 14, 2026
Stage 17 (hmrc_spi_income_spine) drew every SPI-channel claimant's and
partner's income leaves from the SPI tape conditioned on age, gender and
region only, at the model's random numbers. The FRS twin's own incomes,
and the relation between family composition and income they carry, were
discarded: half the lone-parent UC mass sat on SPI copies drawn as
taxpayers, SPI lone parents passed the award screen at about half the
FRS rate, and SPI employment status disagreed with the drawn income
(uk-data#529).

Bridge predictors. The tape forest now conditions on four facts about a
record's income that the tape and the twin each carry: main_source (the
largest of pay, pension and trade) and has_pay from the same leaf
formulas on both sides; has_trade and at_or_over_spa from the tape's
published indicator codes (SEINC_NUM, SPA; the leaf formulas serve a
composite record without codes) and, on the twin, its trade leaf or
self-employed status and its age against State Pension age. The read
requires the three code columns and refuses unknown codes by name; the
published MAINSRCE code validates the leaf-defined main source (0.969 of
the classified weight on the full 2022-23 tape with the stage's
preparation; floor 0.95) and the receipt reports each indicator code
against its leaf both ways.

Twin-conditional draw. Reference forests fitted on the FRS base claimants
and partners with the same predictors and chain order locate each twin's
observed leaves (FittedRegimeGatedQRF.conditional_uniforms, a Rosenblatt
transform: the sign uniform in the gate's cumulative class probabilities,
the quantile on the sign's magnitude forest through the new
_Forest.quantile_positions, the inverse of its draw), and the tape forest
draws at those uniforms through predict_from_uniforms. The reference is
cross-fitted in five folds keyed on the predictor cell, so the forest
locating a copy never saw its twin (an in-sample forest holds the row's
own value as an atom that took a median 0.23 to 0.74 of the conditional's
mass under the weighted bootstrap and kept under half of the twin's
rank). Leaves without an FRS analogue, and the placements inside a class
or a flat stretch, take uniforms keyed on person_source_id, so stage 1
consumes no model random stream. A full build refuses a reference the
cells cannot cross-fit (only a sampled rung or a synthetic fixture may
locate in-sample, and its receipt says so), a recipient whose fold
differs from its twin's, a frame without person_source_id, and a located
target whose reference sign classes differ from the tape forest's
(FittedRegimeGatedQRF.sign_classes).

No-record rule. A twin with no taxable income (no pay, trade or pension,
and at most £1,000 of taxable interest, dividends, property and
miscellaneous income together) has no tax record for the tape to
condition on; its copy keeps the twin's values at stage 1 and stage 2.
Reserved band carriers are always drawn. Stage 2, the band-donor
resample, the uprating and the SPA guard are unchanged.

Integration with #1145 and #1200. The restricted finance costs leaf
reaches the FRS rows only at property_components, so it takes
identity-keyed uniforms. The stage writes person_is_spi_income_recipient,
True exactly for the rows it drew, as its last new column; the landlord
stage reads it to tell tape-concept rows from FRS-concept ones (its graph
node declares the read, with a test that the declaration covers the
stage's inputs), so an undrawn copy keeps its twin's property income on
the FRS concept. The UC gainful self-employment refresh on drawn rows
takes the root's whole rule, undrawn copies keep their twin's flag, and
the evidence counts the rows each route flags.

Surfaces. The manifest declares the bridge, the recipient income rule,
the draw, the reference model and the identity uniforms; spi_spine pins
them with drift asserts; the stage evidence carries the bridge-validation
receipt and the per-target transport receipt (analogue, FRS regime and
sign classes, uniform source, Kolmogorov distance from uniform overall
and by main source; a positive-row count under the group floor is
withheld, and the counts stay with the build). Regenerated: the schema,
the source_stages mirror, the coverage manifest, the UK spine parity
fixture, the spec loader's golden hash, the fit.qrf parity pins, the US
spec-engine inventory digests and coverage report (the fit kernel's
source hash moved), and the gate-battery vintage and release_cut pins in
the data contract (the terminal-gate allow-list gained the flag).

Tests: fit (round trip, uniformity, two-population transport, zero-class
carry, chunked equality, sign classes, refusals); engine-free UK (bridge
and code refusal with disagreeing codes, no-record rule, analogue map,
transport, cell folds, cross-fit hold-out, in-sample and twin-fold and
sign-class refusals, spine drift pins, graph reads); engine-side UK
(identity-keyed rewrites, fit records, the recipient flag and the
refresh receipt). The full engine-free, engine-uk and engine-us suites
pass locally.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juaristi22 pushed a commit that referenced this pull request Oct 14, 2026
experiments/840-spi-draw-receipts.md: the tape receipts (R0), the spine
twins at design weights on the pre-rebase base (R1a to R1e: stage
evidence, the award screen and reporters by channel, rank fidelity and
the in-sample atom behind the cross-fit, the rebuilt treatment, and the
downstream moves by mechanism) and the twins on #1200's base (R1f).
Shares and weighted totals only; record counts stay with the raw
receipts beside the builds (CD137 cl. 8 / CD171 5.2.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juaristi22 pushed a commit that referenced this pull request Oct 14, 2026
Stage 17 (hmrc_spi_income_spine) drew every SPI-channel claimant's and
partner's income leaves from the SPI tape conditioned on age, gender and
region only, at the model's random numbers. The FRS twin's own incomes,
and the relation between family composition and income they carry, were
discarded: half the lone-parent UC mass sat on SPI copies drawn as
taxpayers, SPI lone parents passed the award screen at about half the
FRS rate, and SPI employment status disagreed with the drawn income
(uk-data#529).

Bridge predictors. The tape forest now conditions on four facts about a
record's income that the tape and the twin each carry: main_source (the
largest of pay, pension and trade) and has_pay from the same leaf
formulas on both sides; has_trade and at_or_over_spa from the tape's
published indicator codes (SEINC_NUM, SPA; the leaf formulas serve a
composite record without codes) and, on the twin, its trade leaf or
self-employed status and its age against State Pension age. The read
requires the three code columns and refuses unknown codes by name; the
published MAINSRCE code validates the leaf-defined main source (0.969 of
the classified weight on the full 2022-23 tape with the stage's
preparation; floor 0.95) and the receipt reports each indicator code
against its leaf both ways.

Twin-conditional draw. Reference forests fitted on the FRS base claimants
and partners with the same predictors and chain order locate each twin's
observed leaves (FittedRegimeGatedQRF.conditional_uniforms, a Rosenblatt
transform: the sign uniform in the gate's cumulative class probabilities,
the quantile on the sign's magnitude forest through the new
_Forest.quantile_positions, the inverse of its draw), and the tape forest
draws at those uniforms through predict_from_uniforms. The reference is
cross-fitted in five folds keyed on the predictor cell, so the forest
locating a copy never saw its twin (an in-sample forest holds the row's
own value as an atom that took a median 0.23 to 0.74 of the conditional's
mass under the weighted bootstrap and kept under half of the twin's
rank). Leaves without an FRS analogue, and the placements inside a class
or a flat stretch, take uniforms keyed on person_source_id, so stage 1
consumes no model random stream. A full build refuses a reference the
cells cannot cross-fit (only a sampled rung or a synthetic fixture may
locate in-sample, and its receipt says so), a recipient whose fold
differs from its twin's, a frame without person_source_id, and a located
target whose reference sign classes differ from the tape forest's
(FittedRegimeGatedQRF.sign_classes).

No-record rule. A twin with no taxable income (no pay, trade or pension,
and at most £1,000 of taxable interest, dividends, property and
miscellaneous income together) has no tax record for the tape to
condition on; its copy keeps the twin's values at stage 1 and stage 2.
Reserved band carriers are always drawn. Stage 2, the band-donor
resample, the uprating and the SPA guard are unchanged.

Integration with #1145 and #1200. The restricted finance costs leaf
reaches the FRS rows only at property_components, so it takes
identity-keyed uniforms. The stage writes person_is_spi_income_recipient,
True exactly for the rows it drew, as its last new column; the landlord
stage reads it to tell tape-concept rows from FRS-concept ones (its graph
node declares the read, with a test that the declaration covers the
stage's inputs), so an undrawn copy keeps its twin's property income on
the FRS concept. The UC gainful self-employment refresh on drawn rows
takes the root's whole rule, undrawn copies keep their twin's flag, and
the evidence counts the rows each route flags.

Surfaces. The manifest declares the bridge, the recipient income rule,
the draw, the reference model and the identity uniforms; spi_spine pins
them with drift asserts; the stage evidence carries the bridge-validation
receipt and the per-target transport receipt (analogue, FRS regime and
sign classes, uniform source, Kolmogorov distance from uniform overall
and by main source; a positive-row count under the group floor is
withheld, and the counts stay with the build). Regenerated: the schema,
the source_stages mirror, the coverage manifest, the UK spine parity
fixture, the spec loader's golden hash, the fit.qrf parity pins, the US
spec-engine inventory digests, coverage report and the multispine pool
tool's spec-hash pin (the fit kernel's source hash moved), and the
gate-battery vintage and release_cut pins in the data contract (the
terminal-gate allow-list gained the flag).

Tests: fit (round trip, uniformity, two-population transport, zero-class
carry, chunked equality, sign classes, refusals); engine-free UK (bridge
and code refusal with disagreeing codes, no-record rule, analogue map,
transport, cell folds, cross-fit hold-out, in-sample and twin-fold and
sign-class refusals, spine drift pins, graph reads); engine-side UK
(identity-keyed rewrites, fit records, the recipient flag and the
refresh receipt). The engine-free, engine-uk and engine-us job lists
pass locally.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juaristi22 pushed a commit that referenced this pull request Oct 14, 2026
experiments/840-spi-draw-receipts.md: the tape receipts (R0), the spine
twins at design weights on the pre-rebase base (R1a to R1e: stage
evidence, the award screen and reporters by channel, rank fidelity and
the in-sample atom behind the cross-fit, the rebuilt treatment, and the
downstream moves by mechanism) and the twins on #1200's base (R1f).
Shares and weighted totals only; record counts stay with the raw
receipts beside the builds (CD137 cl. 8 / CD171 5.2.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juaristi22 pushed a commit that referenced this pull request Oct 14, 2026
Stage 17 (hmrc_spi_income_spine) drew every SPI-channel claimant's and
partner's income leaves from the SPI tape conditioned on age, gender and
region only, at the model's random numbers. The FRS twin's own incomes,
and the relation between family composition and income they carry, were
discarded: half the lone-parent UC mass sat on SPI copies drawn as
taxpayers, SPI lone parents passed the award screen at about half the
FRS rate, and SPI employment status disagreed with the drawn income
(uk-data#529).

Bridge predictors. The tape forest now conditions on four facts about a
record's income that the tape and the twin each carry: main_source (the
largest of pay, pension and trade) and has_pay from the same leaf
formulas on both sides; has_trade and at_or_over_spa from the tape's
published indicator codes (SEINC_NUM, SPA; the leaf formulas serve a
composite record without codes) and, on the twin, its trade leaf or
self-employed status and its age against State Pension age. The read
requires the three code columns and refuses unknown codes by name; the
published MAINSRCE code validates the leaf-defined main source (0.969 of
the classified weight on the full 2022-23 tape with the stage's
preparation; floor 0.95) and the receipt reports each indicator code
against its leaf both ways.

Twin-conditional draw. Reference forests fitted on the FRS base claimants
and partners with the same predictors and chain order locate each twin's
observed leaves (FittedRegimeGatedQRF.conditional_uniforms, a Rosenblatt
transform: the sign uniform in the gate's cumulative class probabilities,
the quantile on the sign's magnitude forest through the new
_Forest.quantile_positions, the inverse of its draw), and the tape forest
draws at those uniforms through predict_from_uniforms. The reference is
cross-fitted in five folds keyed on the predictor cell, so the forest
locating a copy never saw its twin (an in-sample forest holds the row's
own value as an atom that took a median 0.23 to 0.74 of the conditional's
mass under the weighted bootstrap and kept under half of the twin's
rank). Leaves without an FRS analogue, and the placements inside a class
or a flat stretch, take uniforms keyed on person_source_id, so stage 1
consumes no model random stream. A full build refuses a reference the
cells cannot cross-fit (only a sampled rung or a synthetic fixture may
locate in-sample, and its receipt says so), a recipient whose fold
differs from its twin's, a frame without person_source_id, and a located
target whose reference sign classes differ from the tape forest's
(FittedRegimeGatedQRF.sign_classes).

No-record rule. A twin with no taxable income (no pay, trade or pension,
and at most £1,000 of taxable interest, dividends, property and
miscellaneous income together) has no tax record for the tape to
condition on; its copy keeps the twin's values at stage 1 and stage 2.
Reserved band carriers are always drawn. Stage 2, the band-donor
resample, the uprating and the SPA guard are unchanged.

Integration with #1145 and #1200. The restricted finance costs leaf
reaches the FRS rows only at property_components, so it takes
identity-keyed uniforms. The stage writes person_is_spi_income_recipient,
True exactly for the rows it drew, as its last new column; the landlord
stage reads it to tell tape-concept rows from FRS-concept ones (its graph
node declares the read, with a test that the declaration covers the
stage's inputs), so an undrawn copy keeps its twin's property income on
the FRS concept. The UC gainful self-employment refresh on drawn rows
takes the root's whole rule, undrawn copies keep their twin's flag, and
the evidence counts the rows each route flags.

Surfaces. The manifest declares the bridge, the recipient income rule,
the draw, the reference model and the identity uniforms; spi_spine pins
them with drift asserts; the stage evidence carries the bridge-validation
receipt and the per-target transport receipt (analogue, FRS regime and
sign classes, uniform source, Kolmogorov distance from uniform overall
and by main source; a positive-row count under the group floor is
withheld, and the counts stay with the build). Regenerated: the schema,
the source_stages mirror, the coverage manifest, the UK spine parity
fixture, the spec loader's golden hash, the fit.qrf parity pins, the US
spec-engine inventory digests, coverage report and the multispine pool
tool's spec-hash pin (the fit kernel's source hash moved), and the
gate-battery vintage and release_cut pins in the data contract (the
terminal-gate allow-list gained the flag).

Tests: fit (round trip, uniformity, two-population transport, zero-class
carry, chunked equality, sign classes, refusals); engine-free UK (bridge
and code refusal with disagreeing codes, no-record rule, analogue map,
transport, cell folds, cross-fit hold-out, in-sample and twin-fold and
sign-class refusals, spine drift pins, graph reads); engine-side UK
(identity-keyed rewrites, fit records, the recipient flag and the
refresh receipt). The engine-free, engine-uk and engine-us job lists
passed locally before the final rebase onto main; the parity, pin and
targeted files after it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juaristi22 pushed a commit that referenced this pull request Oct 14, 2026
experiments/840-spi-draw-receipts.md: the tape receipts (R0), the spine
twins at design weights on the pre-rebase base (R1a to R1e: stage
evidence, the award screen and reporters by channel, rank fidelity and
the in-sample atom behind the cross-fit, the rebuilt treatment, and the
downstream moves by mechanism) and the twins on #1200's base (R1f).
Shares and weighted totals only; record counts stay with the raw
receipts beside the builds (CD137 cl. 8 / CD171 5.2.1).

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