Skip to content

Port the uk-data wave to policyengine-uk 2.134.4: stamp duty, Move to UC, national home values (sixth #1095 PR) - #1308

Open
juaristi22 wants to merge 18 commits into
mainfrom
uk-data-ports-1095-6
Open

juaristi22 wants to merge 18 commits into
mainfrom
uk-data-ports-1095-6

Conversation

@juaristi22

@juaristi22 juaristi22 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

The sixth #1095 ports PR. It renews the ONS savings-interest diagnostic and locks policyengine-uk 2.134.4, frozen for this PR (2.134.5 waits, see E1). It ports the stamp-duty purchases and the households' SDLT target (uk-data#573, uk-data#564). It gives the national line the regions' Land Registry home values (#1112) and draws the Move to Universal Credit claim flag at DWP's rates (uk-data#534). The rulings applied are María's of 16 and 17 October, and each commit names the one it applies.

Status: rebased on main after #1307 and #1306 merged. The commits from 8162e8dad to 4cc99b93a address the first review of this PR, and those from 2b03b7d1d on address the re-review; 6c011906d was amended for it. N2, the high-value home OBR-leg fix, is no longer here: #1306 carries it. R0 to S1b, the head before the re-review and the head now (on main with #1307 and #1306) all pass the 7 calibrated-seam gates on 2.134.4. The receipts follow in an update of this body.

Summary

Renewal (R0)

Engine (E1, and the freeze)

  • The UK lock moves from policyengine-uk 2.132.6 to 2.134.4. Only that package moves; policyengine-core stays at 3.33.3 (UK) and 3.32.5 (US).
    • 2.134.1 (pe-uk#2241) turns additional_residential_property_purchased and non_residential_property_purchased from formulas into inputs that default to nil. Until the data supplies them (S1 below), a purchasing household pays SDLT on its main home only.
    • 2.134.3 starts Scotland's top rate at the statutory £125,140 (pe-uk#2437).
    • 2.134.0 makes a realisation elasticity of 1 the default response to a CGT reform (pe-uk#2419). The CGT lockstep test's measurement reform therefore sets it to 0; no build path runs a parameter reform.
    • The uprating pins, concept coverage (300 to 302 inputs), cited texts, H2 fixture, coverage manifest digests and APPROVED_UV_LOCK_SHA256 re-derive.
  • The lock stops at 2.134.4 (María's ruling of 16 October). 2.134.5 (pe-uk#2240) limits pension relief to relevant earnings and charges the annual allowance, with a new unused_pension_annual_allowance input at nil. On the national spine that moves Universal Credit awards and income tax for households with pension contributions, and the solve fails the weight-ratio gate (see Measurement). 2.134.5 waits for a follow-up that supplies the unused allowance and checks the relief inputs.
  • The freeze is enforced at build time (6c011906d). An installed policyengine-uk other than the version the engine pins were taken from is refused in two places:
    • spine_build._rules_engine, which gives the spine build its engine, and which prepare_full_build and prepare_national_build call before they solve;
    • the start of the release-cut battery the certifier runs.
    • The match is exact, so a post-release or a local build of 2.134.4 is refused too. So is an importable policyengine-uk without distribution metadata. The tools that regenerate committed surfaces do not check: CI's --check jobs and the H2 parity run them on the lock.

Stamp duty (S1, S1b, S2)

  • S1. A new spine stage, property_purchases, imputes households' annual purchases of additional dwellings and of non-residential property (uk-data#573 at 07f6b460f, a draft; uk-data#556).
    • It runs at the spine's design weights, after the regional uprating and the high-value support copies.
    • Who can buy: a household whose stock of a kind is at least £40,000, the chargeable consideration HMRC's tables cover, once a year, at a hazard proportional to its stock.
    • The threshold: the event-score threshold fits HMRC's transactions in England and Northern Ireland and applies everywhere.
    • The prices: band means drawn at HMRC's band shares, times one scale fitted to HMRC's value, never below £40,000 and never above the stock.
    • The targets are HMRC's Annual Stamp Tax Statistics 2024 to 2025 (corrected), vendored from the pinned feed and pinned by a test. They are Table 6b's higher-rates (HRAD) transactions and value, allocated to households by individuals' shares of Table 8a, and Table 8b's individuals.
    • The stage fits transactions and value only. Households' SDLT is fitted at build level through obr.sdlt, and uk-data#573's 30% higher-rates SDLT test is not carried over.
    • Declared, as in uk-data#573 (María's ruling of 17 October): the HRAD allocation is an assumption, not an observed household series. uk-data#573's household lower bounds are 87,200 transactions and £33.28bn. The allocation probably overstates households' purchases, since nearly all receipts at or below the £250,000 nil-rate band are surcharge. UK: track the defects uk-data is fixing that microcosm still has #1095 has a note on it.
    • The receipt gives a weighted figure only where ten or more source households carry it, and "<10" otherwise (8162e8dad). The share at the stock cap is also withheld where the fit or either side of it rests on fewer than ten, and an empty cell reads "0" (0d4a58c42).
    • The stage refuses a frame at any period other than 2024, the period HMRC's 2024-25 tables are mapped to (8928b1e80).
  • S1b. Where the purchasers' stock cannot carry HMRC's value, every purchaser buys at its stock (María's ruling of 16 October). On the national spine the non-residential purchasers' stock cannot carry HMRC's Table 8b individuals' value. uk-data#573 refuses any shortfall, so the build stopped. The stage now records the fit (value_fit, value_shortfall_share) and refuses below 80% of the value. The selection, its stock exponent and HMRC's transactions are unchanged.
  • S2. obr.sdlt binds households' SDLT (uk-data#564).
    • Why: the OBR line covers every buyer, and HMRC's other buyers (companies, partnerships and collective investment schemes) pay about a third of the receipts. policyengine-uk charges households' stamp_duty_land_tax and adds corporate_sdlt beside it.
    • The target is the OBR line's calendar-2025 window times individuals' share of HMRC's 2024-25 SDLT receipts, residential (Table 8a) and non-residential (Table 8b) together.
    • A new value operation, calendar_year_window_scaled_by_ratio, carries the rule: the window of the reference's own series times the quotient of two operands. Each operand pins its own period and names its members in one list, and each member matches exactly one fact (4daa259f7).
    • The notes declare three gaps that are not corrected: non-resident individuals' SDLT stays inside the individuals' share, though no FRS household is non-resident; Table 6b counts the higher rates before replacement-home refunds, while Tables 8a and 8b and the OBR line are net; and the engine's main-home purchase rate also counts the higher-rates transactions.
    • uk-data#569 (a draft) subtracts policyengine-uk's corporate SDLT statistic instead, which needs the unreleased pe-uk#2322.
    • The target references, the membership report and the national compile parity move on obr.sdlt alone, from £15.03bn to £10.16bn.

Home values (N1)

  • N1. The national line carries the regions' Land Registry home values (UK national release: main_residence_value keeps the WAS-imputed tail, so the surcharge's top bands stay empty #1112).
    • What the stage does: a new spine stage, regional_home_values, runs after the high-value home support copies. It gives every England-and-Wales owner household with a value the one its region's uprated Land Registry distribution holds at the household's weighted mid-rank. It reads the region level of uk/high_value_home_value_knots.json and moves property_wealth by the change. Scotland, Northern Ireland, renters and owners without a value keep theirs, and the council-tax band is untouched.
    • The dense line does not change. The replaced value stays in main_residence_value_rank_basis, and the dense value node receives it through its slice and ranks on it. The node refuses a declared column that is missing, and a run_graph test pins this. The release boundary drops the column on both lines.
    • Three national rows move: obr.sdlt, through the main-home purchase value, and ons.land.land_value and ons.land.household_land_value, through property_wealth.
    • Declared (María's ruling of 17 October): the region knots' tails run up to each region's largest sale, as the dense line's do. They are to be fixed on both lines together in the UK national release: main_residence_value keeps the WAS-imputed tail, so the surcharge's top bands stay empty #1112 follow-up.
    • The high-value homes region rows (UK local-area release: publish a K=15, 150,000-household dense dataset in at most two builds #1301) still bind on the local line only (ca138083e). Main gave the reason that the national spine's value is the WAS donor's; N1 makes that untrue. The reason is now this: binding the rows on the national line would move the national registry and solve, while the knots' tails still place more homes in the top bands than the derived stock. That is left to the UK national release: main_residence_value keeps the WAS-imputed tail, so the surcharge's top bands stay empty #1112 follow-up.
    • The receipt counts its cap cell on distinct source households in a graph build too: the node declares household_source_id, and the stage refuses a frame without it (2b03b7d1d).

Move to Universal Credit (M1)

  • M1. would_claim_uc_at_legacy_closure is drawn at DWP's Move to UC claim rates (uk-data#534 at c67077a66, a draft; uk-data#492).
    • Why: from policyengine-uk 2.128.0 (pe-uk#2119), a family moved off a closed legacy benefit claims Universal Credit if would_claim_uc or this flag is true. The flag's default put every such family on Universal Credit.
    • A new stage, uc_legacy_closure_take_up, runs after uc_capital_coherence. It keeps the default for a unit reporting Universal Credit, a unit whose would_claim_uc is true and a unit reporting no legacy benefit.
    • Every other unit is drawn at (r − p) / (1 − p), clipped to [0, 1]. Here r is DWP's claimed / (claimed + did not claim) for the unit's combination of legacy benefits, or the all-households rate where DWP reports none. And p is the combination's share already claiming, among Great Britain's units, at the spine's design weights (María's rulings of 16 and 17 October; uk-data#534's share is unweighted). The probability applies in Northern Ireland too.
    • In expectation, the share claiming at the closure is DWP's rate wherever p is at most r. Where p exceeds r, the receipt flags the combination, never p itself, and only where ten or more source households carry it. In a graph build too, the node declares household_source_id, and the stage refuses a frame without it (2b03b7d1d). It also refuses a frame at any period other than 2024 (8928b1e80).
    • The limits carried over from uk-data#534 are declared: the cohort includes Pension Credit-route units that policyengine-uk does not close, and DWP's rates count households not entitled to Universal Credit.
    • The rates are DWP's Stat-Xplore counts of households sent a migration notice, July 2022 to June 2026, vendored from the pinned feed. The flag joins the export surface's reviewed extras, and the concept mapping binds it, so no take-up input is left at the engine's default.

Deviations and open points

  • S1b departs from uk-data#573, which refuses any shortfall. On the A1 and head spines, the non-residential fit reaches about 81% of HMRC's value (transactions within 1%), above the 80% floor by a small margin. Additional dwellings fit HMRC's value exactly.
  • M1's share already claiming is weighted and measured over Great Britain. uk-data#534's share is unweighted and UK-wide.
  • The engine is frozen at 2.134.4. 2.134.5 fails the weight-ratio gate (E1 above), and 2.134.6 (pe-uk#2242) changes the UC standard allowance from 2026-27 only.
  • For María: whether policyengine-uk's UKHPI-based land intensity should apply to Land Registry-basis values (N1); and a dense arm before the frozen build, to re-measure the dense line with the three new stages.

Measurement

Each arm builds its spine locally on feed abf0921 and runs the national role's calibration on it.

  • Control (Re-pin the UK Chronicle feed to abf0921 (#1300) #1302's head 4c51843, calibration only, on the ports-5 head spine): loss 0.26817 to 0.00584; 98.4% of 1,216 rows within 10%; all 7 calibrated-seam gates pass. obr.sdlt there is the whole OBR line.
  • On 2.134.5, E1 alone and A1 (R0 to S1b) both fail the weight-ratio gate while their other 6 gates pass. With the purchases and the households' target (A1), SDLT fits: £9.70bn at design weights against the £10.16bn target. So the cause is the engine bump, and the same R0 to S1b passes on 2.134.4 (next item).
  • A1 on 2.134.4 (R0 to S1b): loss 0.26739 to 0.00580; 98.4% of 1,216 rows within 10%; all 7 calibrated-seam gates pass; obr.sdlt within 0.2%. In the purchases stage, additional dwellings fit HMRC's value exactly, and non-residential purchases reach about 81% of HMRC's value at the stock cap, with transactions within 1%.
  • Head before the review (306bc11, 2.134.4): all 7 gates pass (loss 0.26808 to 0.00587). N1 raised households' SDLT at design weights from about £9.7bn to £13.5bn, and the solve still fit obr.sdlt to within 0.1%.
  • Head at the review (8739557, 2.134.4): loss 0.26808 to 0.00587; 98.4% of 1,216 rows within 10%; all 7 calibrated-seam gates pass. M1's Great Britain basis leaves the fit as it was. The rows N1 moves, at design weights and after the solve, compared with A1:
    • obr.sdlt: from £9.70bn to £13.51bn at design weights, against a £10.16bn target; within 0.1% after the solve.
    • ons.land.household_land_value: from £4.21tn to £5.09tn at design weights, against a £4.49tn target; within 0.02% after the solve on both arms.
    • ons.land.land_value: misses by about 4.7% after the solve on both arms, so N1 does not cause the gap. ons.land.corporate_land_value does not move.
  • Head at the re-review (ca138083e, on main with UK local release: publish a K=15, 150,000-household dense dataset in at most two builds (#1301) #1307 and UK local surface before the Budget build: high-value homes OBR leg, per-vehicle fuel flag, Northern Ireland energy share and housekeeping (#1278) #1306, 2.134.4): loss 0.26816 to 0.00565; 98.4% of 1,215 rows within 10%; all 7 calibrated-seam gates pass. Main now holds obr.ni_employer out as a diagnostic (pe-uk#2459), hence one row fewer.
    • UK local surface before the Budget build: high-value homes OBR leg, per-vehicle fuel flag, Northern Ireland energy share and housekeeping (#1278) #1306's stages run after property_purchases, so the purchases fit is as before: additional dwellings fit HMRC's value exactly, and non-residential purchases reach about 81% at the stock cap.
    • After the solve, obr.sdlt is within 0.4% and household land value within 0.1%. Total land value still misses by about 4.7%.
    • The weights behind N1's land moves, design weights against the solve's: England-and-Wales owner households' weight falls 4.4%, and their weighted home value 11.8%. So the solve meets the household land row mostly by down-weighting higher-value owners. Additional-dwelling purchasers' weight falls 4.2%, and non-residential purchasers' rises 8.1%.

Not in this PR

Disclosure

Checked against the UK Data Service rules before pushing: the commit messages, every added line, the fragments and this body. No record-level value, extreme, effective sample size, weight ratio or support count appears. The figures are published statistics or national weighted aggregates. The three stages' receipts band every cell on its distinct source households, in a graph build as well as in a direct call (2b03b7d1d).

Tests

  • CI: all 10 checks pass on ca138083e.
  • Locally, at ca138083e on 2.134.4: CI's engine-uk job, 217 tests, all pass. CI's engine-free job in full, 19,789 tests with 157 skips, all pass except 5 that this machine cannot run as CI does, and which pass in CI: two Orrery parser tests need an npm package that is not installed here, one reads the live release pointer, one refuses this Python 3.14.6, and one checks that the tier has no policyengine-uk.
  • The --check tools on 2.134.4 pass: the uprating pins, the concept coverage and the coverage manifest. The H2 fixture regenerates byte-identical at the head.
  • The feed-gated tests on feed abf0921: 180 with 2 skips, all pass. The committed national and local target surfaces, with the membership report, and the vendored resources regenerate byte-identically, and the national compile parity regenerates and passes on the union with UK local release: publish a K=15, 150,000-household dense dataset in at most two builds (#1301) #1307's receipt. The two skipped tests read the feed only from the repository root.

🤖 Generated with Claude Code

@juaristi22
juaristi22 force-pushed the uk-data-ports-1095-6 branch from 732131e to 306bc11 Compare October 17, 2026 00:01
@juaristi22 juaristi22 changed the title Port the uk-data wave to policyengine-uk 2.134.5: stamp duty, Move to UC, national home values (sixth #1095 PR) Port the uk-data wave to policyengine-uk 2.134.4: stamp duty, Move to UC, national home values (sixth #1095 PR) Oct 17, 2026
@juaristi22

Copy link
Copy Markdown
Collaborator Author

Independent adversarial review of this PR at 732131e

Run with Claude Code on 16–17 October 2026 at 732131e: seven independent review lenses (correctness, method, generated artifacts, tests, claims against the diff, disclosure, a second fresh-eyes pass), then one to three skeptics per finding who tried to refute it; (k/n) is how many agreed. No builds and no feed-gated tests were run. The branch was force-pushed to 306bc11 during the review (policyengine-uk relocked to 2.134.4; the stage code is unchanged), so every finding below applies to 306bc11 except the 2.134.5 lock finding and the E1 nit; no tests were run at 306bc11. The force-push rewrote every commit from E1 on: the reviewed-head commits b355aad, e5bee83 and 21cb5e0 cited below are 34a5526, a6c9758 and 908f6a3 on the live head.

Leave this PR in draft. The stages are faithful ports and the published-statistics arithmetic checks out exactly. What blocks it is in the code, not the method:

  • The property_purchases receipt ships a weighted total that rests on fewer than 10 FRS households. That would breach CD171's small-cell rule as soon as a national build stages or releases.
  • The dense value node never sees the rank-basis column, so the dense line changes, against the PR's claim, and nothing re-measures it before the frozen build.

Separately, the frozen build will be refused by the release-blocking local compile-parity gate unless someone regenerates that receipt on the final head (X-s01, see Cross-PR).

Blocking

  • The property_purchases receipt ships an unbanded weighted total on fewer than 10 households. CONFIRMED (3/3; two rate it high, one medium). packages/microcosm-build/src/microcosm/build/uk_runtime/property_purchases.py:658-660.

    • What goes wrong: purchases_outside_sdlt_regions_weight is the raw weighted sum of Scotland and Wales purchasers. purchaser_support (:661-663) bands only the England and NI purchasers.
    • On the head-arm spine, the non-residential outside-SDLT cell comes from '<10' distinct source households. Scotland alone is '<10' and Wales alone is '<10'. The additional-dwelling outside cell is '30+'.
    • The field travels through stage evidence into the spine's build sidecar, then spine_provenance, then the build block of calibration_diagnostics.json, then the release directory. The path is stage_evidence.py:45-62, spine_build.py:2211-2213, calibration_run.py:580, full_build_cli.py:2745,2765, and assemble_uk_release_dir.py:442. The repo's own rule in receipt_counts.py:3-10 forbids this.
    • The figure has not been uploaded or published: the arms ran with --no-staging.
    • Fix: count distinct household_source_id among the outside purchasers and print '<10' when 1-9 sources carry the cell, using receipt_counts.small_cell_share. Otherwise drop the field from the receipt. Add a test with a 1-9-source outside cell. The "receipts follow" update the body promises should not include this receipt as it stands.
  • The dense value node ranks on the new regional values, so "the dense line does not change" is false. CONFIRMED (3/3, all rate it medium; listed as blocking because the frozen build includes the dense line and the fix is small). packages/microcosm-build/src/microcosm/build/uk_runtime/graph_population.py:1131-1141.

    • What goes wrong: the uk.full.property.value Slice declares the lineage columns, _PROPERTY_VALUE_COLUMNS (:89-94) and constituency_code. It does not declare main_residence_value_rank_basis.
    • The executor hands a kernel only its declared columns (executor.py:741-771; kernel.py:330). So main_residence_rank_basis() falls back to main_residence_value with no warning (property_localisation.py:194-200). On an N1 spine that column holds the regional Land Registry value.
    • Two things move as a result. Tied WAS values take the regional stage's tie-break stream. High-value support copies no longer sit beside their roots, so every top-stratum root's u moves.
    • Three synthetic probes confirmed it through the real executor. In a toy dense graph the pool carries the basis column but the kernel's household view does not. On synthetic frames, 10-98% of owner rows' u moved, depending on ties. The moves are noise-level (mean |du| up to about 0.008), but they land in the top 5% that HVCTS measures.
    • The PR's test (tests/engine_free/uk/test_uk_regional_home_values.py:129-185) calls the helper on the whole staged frame, so it bypasses the Slice. The claim appears at regional_home_values.py:22-24, graph_population.py:476-478, source_stages.json:3151, the 1112 changelog fragment, and the body's N1 item.
    • Fix: add RANK_BASIS_COLUMN to the Slice when ('household', RANK_BASIS_COLUMN) is in cells, the same way UK_OPTIONAL_LINEAGE_COLUMNS are handled at :1012-1017. Make the helper refuse rather than fall back when the regional stage ran. Replace the unit test with a run_graph test asserting that u matches a pre-stage run. Re-measure a dense arm before the freeze.
  • X-s01: The local compile-parity receipt is stale on abf0921. This is cross-PR (high, 3/3) and blocks the frozen build, not this PR alone. See Cross-PR.

Should fix

  • The reviewed head locks 2.134.5, which fails the uk_weight_ratio seam gate. The live head has relocked 2.134.4. PLAUSIBLE (1/3). The other two agree on the facts but say the live head supersedes it. packages/microcosm-build/pyproject.toml:49.

    • At 732131e, both the E1-alone arm and the A1 arm (both on 2.134.5) are blocked by the uk_weight_ratio gate. A measure-only arm that changes only the pins to 2.134.4 passes all 7 gates, and so does the head arm at 306bc11. The 732131e head arm never finished.
    • 2.134.5 rewrites pension_contributions_relief and the AA charge. b355aad's message left that out; the live E1 (34a5526), the fragment and the body now say so.
    • What remains: the 20 engine-derived files at 306bc11 were not re-checked by this review. They are the pins, the concept golden, the coverage manifest, the H2 fixture and the fragments.
    • What would settle it: re-run in a 2.134.4 venv at 306bc11 the pin check, refresh_concept_coverage --check, build_uk_release_input_coverage_manifest.py --check, the H2 regen and the 71 engine tests.
  • N1's "no national target binds the value, so the national solve does not move" is false. CONFIRMED (2/2). changelog.d/1112-national-home-values.added.md:3, still present at 306bc11. Also in sources.yaml:2646 and the N1 commit.

    • N1 raises the E&W owners' weighted value by about 27%. In policyengine-uk the value reaches obr.sdlt through main_residential_property_purchased = main_residence_value × property_purchased. It reaches ons.land.household_land_value and land_value through property_wealth × intensity, because property_wealth is dropped at export and the engine rebuilds it.
    • Initial estimates, A1 against head (both on 2.134.4): obr.sdlt goes from £9.70bn to £13.51bn against a £10.16bn target, and household_land_value from £4.213tn to £5.085tn. corporate_land_value does not move. The effective sample size also falls slightly.
    • The live body now reports the SDLT move but not the land moves. Nobody verified the claimed purchaser and owner weight cuts.
    • Fix: remove the sentence everywhere, name the three rows that move, and judge S2's fit on the head arm. Decide whether policyengine-uk's UKHPI-based land intensity should apply to Land Registry-basis values.
  • "Receipts are checked against HMRC, never fitted" describes a check that does not exist. CONFIRMED (2/2). property_purchases.py:42, plus the S1 fragment:7, sources.yaml:2817, source_stages.json:3377, the H2 fixture.json:5001 and e5bee83.

    • No code, gate or test compares SDLT on the imputed purchases with Table 6b or 8b receipts. The stage reads only four transaction and value concepts (:387-464), and the receipt (:644-664) has no SDLT field. uk-data#573 does have such a check: test_saved_enhanced_frs_higher_rates_sdlt holds the result within 30%. The port dropped it without saying so.
    • "Never fitted" holds at stage level. But households' SDLT is fitted at build level through obr.sdlt, and that was already true before this PR.
    • Fix: add a report-only diagnostic of engine SDLT on the imputed purchases against Table 6b × household share and Table 8b, with a declared tolerance. Otherwise drop the sentence and say households' SDLT is fitted through obr.sdlt.
  • The household HRAD target probably overstates households' additional-dwelling purchases. CONFIRMED (2/2). property_purchases.py:419-431.

    • The target allocates Table 6b HRAD by individuals' share of all Table 8a residential transactions (0.881), which leaves other buyers about 25,100 HRAD transactions.
    • The residential nil-rate band was £250k for all of 2024-25, so in the ≤£250k band nearly all receipts are surcharge. Other buyers' £265m there implies about 48,000-51,000 non-individual HRAD transactions in that band alone.
    • So household HRAD looks overstated by at least about 24k transactions and likely 35-40k (20-25%), and by several £bn of value. The ≤£250k band also gets too much weight in the household price mix.
    • uk-data#573 calls this "an assumption" and gives lower bounds (87,200 transactions, £33.28bn). The port leaves out that caveat.
    • Fix: María's call. Raise it on uk-data#573 and allocate from Table 8a's band receipts, at least for the zero-standard-rate band. Otherwise declare the upward bias in the receipt text and the body.
  • N1's region knots interpolate up to the cap and down to the sample minimum. CONFIRMED (2/2). regional_home_values.py:199; the knots come from high_value_home_stock.py:564-579 with left=values[0], right=values[-1].

    • The top 0.1% of owners and 2% of support copies are spread linearly from q0.999 to the region's largest sale. That is £20m in six regions.
    • Integrated at the knots' own weights, they imply far more homes above £2m than the derived stock facts show. NE has 782 against 203, Yorkshire 1,804 against 907, East Midlands 1,654 against 962, and Wales 1,095 against 262. England has about 22.3k homes above £5m against OBR's 15,000 for all tenures.
    • The last segment supplies about 15.2k of the England £5m+ band and about £126m of an implied £482m surcharge.
    • The same interpolation already exists on the dense line. N1 brings it to the national line, where nothing corrects it.
    • Not verified: the method lens's larger estimate of the bottom-segment artefact; the skeptics' own estimate is smaller.
    • Fix: drop the extremes as knots and extrapolate the tails from interior quantiles, or clamp u to [grid[1], grid[-2]] on the national stage. At minimum, declare the artefact and keep the last segment out of HVCTS evidence.
  • S1b's 80% value floor has about 1.5 points of headroom. PLAUSIBLE (1/2). property_purchases.py:125, refusal at :593-600.

    • The non-residential fit is 81.47% on A1 and 81.49% on head. A rebuild of the stage input from the A1 spine reproduces the receipt to every digit.
    • Hard ValueError trips: a uniform 1.8% fall in stock, a 1.9% rise in the value target, or a fall of about 3.5% in the transactions target. 300 salt re-draws give SD of about 10 points, with 3% below 0.80.
    • The skeptic who refuted it found that N1, N2 and M1 left the ratio unchanged, the stage is deterministic, and no pending upstream change would move it. The floor and the thin margin are María's ruling, and the body states them.
    • What would settle it: whether any spine change can land before 20-21 October upstream of stage 23. Decide whether a floor breach should refuse or degrade to a recorded diagnostic, or add a warning band to the acceptance receipt.
  • The weighted fields in the property_purchases receipt are not gated on support. CONFIRMED (2/2). property_purchases.py:644-664.

    • fitted_value, value_shortfall_share, event_rate, price_scale and weighted_share_at_stock_cap all ship as floats even when purchaser_support is '<10'. A synthetic run showed this.
    • In stock_cap mode fitted_value is the purchasers' own weighted stock. The at-cap cell is never counted at all, and in a synthetic case it multiplied back to a 6-record weight.
    • Latent: every inside cell is '30+' on today's head spine.
    • Fix: blank or '<10' these fields when support is '<10'. Band the at-cap cell with extreme_count-style banding. Add a 1-9-source test. Do this together with the first Blocking item.
  • The uc_legacy_closure_take_up and regional_home_values receipts band rows, not sources. CONFIRMED (2/2). legacy_closure_take_up.py:228 and regional_home_values.py:225-231.

    • Support copies from high_value_home_support (6 per top household, stage 21) are already in the frame by stage 32.
    • Both skeptics rebuilt the head spine at stage 32, matched all 18 receipt bands, and found one combination that reads '10-29' by rows but '<10' by distinct sources. For rows_at_cap the problem is latent on the head spine.
    • No figure leaks: the cell carries only DWP's rate and the band. The disclosure label is still wrong.
    • Fix: map benunits to household_source_id and band distinct sources, as property_purchases.py:661-663 does. Add a test where copies push rows past 10 while sources stay below 10.

Minor

  • The follow-up list leaves out the 3 November and 14 November lapses. PLAUSIBLE (1/2), low. The slc.borrowers.plan_2_* and UC housing-element entries in calibration_measure_exclusions.json.

  • The window-ratio operand check counts total matches only. CONFIRMED (1/1), low. ledger_targets.py:3207-3219, membership matching at :4185-4194.

    • Two facts for one listed id and none for the other compile without error. A synthetic probe got ratio 1.0 instead of 0.75. A 3-id list declared with count 2 also passes when one id is missing.
    • Today each operand matches once in abf0921. linear_combination has the same design.
    • Fix: require each listed id to match exactly once and expected_member_count == len(list). Add a 2+0 test.
  • The R0 record says signed while the body says María's signature is owed. CONFIRMED (1/1), low. The ons.savings_interest_income entry in calibration_measure_exclusions.json; test at test_uk_measure_simulation.py:796-803.

    • The entry records approved_on 2026-10-16 and "renewed ... by juaristi22", and a test pins that. Body line 8 says María's confirmation and signature are owed.
    • Fix: update the body once María signs, or mark the record pending.
  • M1's flat claim "the share claiming at the closure ... is DWP's rate" overstates. CONFIRMED (1/1), low. legacy_closure_take_up.py:213-229 and :270.

    • The cohort includes Pension Credit-route units that policyengine-uk never closes, which biases q for mixed cohorts. Where p ≥ r the probability clips to 0 silently, and the receipt has no flag for it.
    • The four tax-credit combinations that close in 2025 each band '<10'. uk-data#534's limitation (rates include households that were not entitled) is not declared.
    • The function docstring hedges correctly, but the module docstring, the fragment and sources.yaml:4351 do not.
    • Fix: qualify the sentence, add a per-combination rate_unreachable flag (never p itself), and declare the limitation.
  • p pools GB and NI against GB-only DWP rates, and the comment says "Great Britain-wide". CONFIRMED (1/1), low. legacy_closure_take_up.py:276-277 and :296.

    • A toy run of the module's own function gives GB 78.4% against r = 80%. pension_credit_take_up masks to GB and extrapolates to NI; this stage does not.
    • Fix: compute p over GB units only, or at least correct the comment to "UK-wide" and declare the pooling.
  • S2 binds a net target to a gross, resident-only model quantity, and nothing declares it. CONFIRMED (1/1), low. uk_population_targets.json:2175.

    • Table 6b counts include later-refunded replacement homes, and policyengine-uk charges full higher rates with no refund. Tables 8a/8b and the OBR line are net. The 0.0385 main-home rate also counts HRAD transactions, so they are counted twice.
    • Fix: declare the gross/net gap and the attribution of non-resident individuals in the obr.sdlt notes.
  • The OBR-leg fix duplicates UK local surface before the Budget build: high-value homes OBR leg, per-vehicle fuel flag, Northern Ireland energy share and housekeeping (#1278) #1306's. CONFIRMED (1/1), low. high_value_home_gates.py:140-168. See Cross-PR.

  • The engine freeze is enforced only by uv.lock. CONFIRMED (1/1), low. packages/microcosm-build/pyproject.toml:49.

    • The extras say >=2.134.4 on the live head. Nothing at runtime compares metadata.version('policyengine-uk') with the pins: spine_build.py:709-714 and hmrc_uprating.py:318-338 only record it. A patched-version probe did not refuse.
    • CI uses --locked, so CI is safe.
    • Fix: cap the extras (<2.134.5) while the freeze holds, or add a stage-time version refusal like the US side has (us_annual_static_aging.py:67).
  • The property_purchases stage test passes with every purchase nil. CONFIRMED (1/1), low. tests/engine_free/uk/test_uk_property_purchases.py:281-303.

    • A mutation to the target-None branch kept both stage tests green.
    • The H2 fixture sets additional_residential to null and always lands in scaled_band_draws. Adding parity branches helps less than it seems, because both sides share the transform.
    • Fix: assert positive prices and fitted transactions and value in the stage test.
  • No test pins the real vendored Stamp Tax targets. CONFIRMED (1/1), low. test_uk_property_purchases.py:75.

    • Nothing calls load_property_purchase_targets. A probe that renamed a band id, keeping the row count, gets past every unit test and refuses mid-build.
    • Fix: add a test that pins uk-data#573's household figures: 186,588.86 transactions, £68.805bn, 25,000 and £7.64bn.
  • The feed-gated checks never run in CI and have not been run. CONFIRMED (1/1), low. test_uk_ledger_fact_vendoring.py:262 and test_uk_target_references.py:118 and :1496.

    • Nothing in CI re-derives the vendored values or obr.sdlt's £10.16bn. The body says these checks are "to run before review".
    • Fix: run them on abf0921 and put the counts in the body.
  • None of the three new stages has a stage-health gate. CONFIRMED (1/1), low, from the critic pass. gates.json:550 (pension_credit's gate, for comparison).

    • Nothing reads value_shortfall_share, weighted_share_claiming_at_closure or rows_at_cap, and uk_take_up_signal does not cover the new flag.
    • 19 of the 45 UK stages are ungated, including regional_property_uprating, so this is a pattern, not new. obr.sdlt is still held by uk_target_fit.
    • Fix: add checks before the freeze, or record in gates.json that the stages are deliberately ungated.
  • The legacy-closure manifest declares mapped_build_period 2025 on a 2024 spine. CONFIRMED, nit. legacy_closure_take_up.py:318.

    • Nothing enforces it; a demo ran fine at 2030. Declare 2024, or enforce the check as property_components.py:943-950 does.
  • Notice-window label conflict with uk-data#534. CONFIRMED, nit. legacy_closure_take_up.py:23.

  • E1 named no ruling. CONFIRMED, nit. Fixed at the live head: 34a5526 cites María's ruling of 16 October.

Cross-PR

Disclosure check

  • Breach on build: the purchases_outside_sdlt_regions_weight non-residential cell (property_purchases.py:658-660) rests on '<10' sources on the head-arm and A1 spines. The cell is in local build sidecars only, nothing has been uploaded, and the field should be dropped from any receipt before it is posted or committed. No calibration_diagnostics.json carrying it exists yet, and no committed file or body text holds the value.
  • Latent: the unbanded weighted fields in the same receipt (:644-664) and the at-cap cell.
  • Wrong label, no figure: one uc_legacy_closure_take_up combination reads '10-29' by rows but rests on '<10' sources (legacy_closure_take_up.py:228).
  • Comment fix: legacy_closure_take_up.py:296 calls the aggregates GB-wide. They are UK-wide, still above Region level, so this is not a breach.
  • Clean: the PR body, including the ~81% fit (E and NI cell, '30+' sources, which is why the support-check finding was refuted) and the national SDLT figures; all 8 commit messages; all 8 fragments; the vendored HMRC and DWP resources (published statistics); the obr.sdlt ledger value; all test and fixture values (synthetic); the R0 renewal entry and the new stage notes; the regional_home_values receipt (no differencing against the high_value_home_support receipt, and its at-cap band is latent, see the receipt-banding item under Should fix); the legacy-closure receipt (DWP rates and bands only); and N2's ratios (built on already-suppressed bands).

Tests and tools run

All runs were at 732131e unless noted. No tests were run at 306bc11.

  • Correctness and method:
    • test_uk_regional_home_values.py, test_uk_property_purchases.py and test_uk_legacy_closure_take_up.py (tests/engine_free/uk), run separately by each lens: 31 tests, 0 failures, each time.
    • Synthetic probes of the rank basis and of the window ratio.
  • Artifacts:
    • Gate pins, vendoring, high-value gates and ledger targets (test_gate_battery_contract_pins.py, test_uk_ledger_fact_vendoring.py, test_uk_high_value_home_gates.py, test_ledger_targets.py): 472 tests, 0 failures, 1 feed-gated skip.
    • tools/ci_test_plan.py verify: ok.
    • In a policyengine-uk 2.134.5 environment: tools/pin_uk_uprating_engine_values.py --check matches 2.134.5; tools/refresh_concept_coverage.py --check exits 0; tools/build_uk_release_input_coverage_manifest.py --check is current (144 required, 1 exclusion); tools/graph_uk_spine_fixture.py regenerates the committed bundle byte-identical.
    • tools/spec_engine_coverage.py --check: 42184/42184 fields, 41/41 inventory checks.
    • Re-vendoring from an extracted subset of the abf0921 feed gives hmrc_stamp_tax_facts.json (110 rows) and dwp_move_to_uc_facts.json (80 rows) byte-identical.
  • Tests lens:
    • New-unit files (property purchases, regional home values, legacy closure, high-value gates, ledger targets): 471 passed, 0 failed.
    • Tests of the modified files: 627, 5 feed-gated skips.
    • Adjacent surfaces: 678, 1 skip.
    • Engine tests on 2.134.5, in two batches: 72 passed, including H2 parity, and 9 passed.
    • In total, 1,776 engine-free tests (6 skipped, 0 failed) and 81 engine tests (0 failed). The body's count at review time (4,068 tests, 8 skips) was not reproduced in full.
  • Claims: 77 new-surface tests passed; 1 engine pin test passed.
  • Disclosure: 37 tests passed. A band-only check ran on the head-arm spine, built from 306bc11 with the same stage modules.
  • Fresh-eyes pass: 125 tests, 0 failures.
  • Critic pass: national calibration, rate bands, pension-age targets and stage health: 99 tests, 2 feed-gated skips.
  • Skeptics:
  • Feed greps: all four obr.sdlt operand ids match once each in abf0921.

Checked and found sound

  • S1's selection, band draw, floor and cap, and bisection are a faithful port of uk-data#573. The targets reproduce uk-data#573 exactly: 186,588.86 transactions and £68.805bn for additional dwellings; 25,000 and £7.64bn for non-residential.
  • S2's arithmetic: (9.145 + 0.245)/(10.38 + 3.505) = 0.6763, and £15.026bn × 0.6763 = £10.1617bn. The operands fail closed when a fact is missing, the ratio is bounded to (0,1], and there is no double uprating.
  • M1's (r − p)/(1 − p) is correct. All 20 DWP labels parse, In Progress is excluded, every writer of the inputs runs before stage 33, and tax_credits.active is false in 2025, so the flag acts.
  • N1: the knots' 2024 basis matches the spine, Wales maps to W92000004, the knots are validated, and the property_wealth floor never clips.
  • The vendored resources match abf0921 row for row and re-vendor byte-identical. The feed block is identical across all 28 resources.
  • The uv.lock sha equals APPROVED_UV_LOCK_SHA256 at both heads. The coverage manifest sha equals source_stages.json, and the new families' candidate sha is correct.
  • policyengine-uk 2.134.5 already uprates both purchase inputs, and sdlt_liable limits SDLT to England and NI.
  • N2 reads the vendored top-level fields correctly, and the leg stays report-only.
  • No removed test assertion loosened anything, and no new test reads the wall clock.

Refuted during verification

  • The body quotes the ~81% fit without a support check: refuted, because the E and NI non-residential purchasers band '30+'.
  • The control arm's spine predates the re-pin, confounding comparisons: true literally, and the body says so, but no confound was substantiated.
  • The fresh-eyes pass's claim that "UC reporters make the Move to UC rate overshoot": UC reporters are outside DWP's notified universe, and their flag is anchored true.
  • The same pass's doubt about the HRAD allocation (other buyers' total receipts cap them at about 23%) was overtaken by the band-level evidence behind the HRAD finding.
  • Engine materialisations at stages 27-29 seeing the closure flag at its default: the same accepted pattern as the other take-up redraws.

Not covered

  • No build of any kind was run. A1 and head outcomes come only from the PR's measurement arms.
  • The feed-gated tests on abf0921 (vendoring regeneration, target references, membership, compile parity) were not run.
  • The claim that policyengine-uk 2.134.6 (Apply the Universal Credit Act 2025 standard allowance uplifts to 2029-30 policyengine-uk#2242) would not move the 2025 build: 2.134.6 was not installed in any review environment.
  • A dense or local (K=25) build with the three new stages. The dense-line finding is shown by mechanism and probes, not by a run.
  • The live head 306bc11: no tests and no --check tools were run there, only file-level diffs.
  • The body's total at review time of 4,068 tests with 8 skips.
  • The size of the HRAD overstatement is a band-decomposition estimate, and DWP's March 2026 totals for the notice-window check could not be fetched.

🤖 Generated with Claude Code

juaristi22 added a commit that referenced this pull request Oct 17, 2026
On main after #1302: 25,327 compiled cells. The 18 Scottish BRMA rows are
ledger-only by construction, and no signed value moves. The UC drift
rationales now read their fixture/ours bands off the rows, and the support
floor's deferred cells carry their own signed rationale. The release-blocking
preflight gate uk_ledger_compile_parity_local_incumbent_2025 reads this
receipt, so whichever of #1305, #1306, #1307 and #1308 lands last
regenerates it on the combined tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22
juaristi22 force-pushed the uk-data-ports-1095-6 branch from 306bc11 to 8739557 Compare October 17, 2026 16:11
@juaristi22
juaristi22 changed the base branch from uk-chronicle-repin-1300 to main October 17, 2026 16:11
juaristi22 added a commit that referenced this pull request Oct 17, 2026
…gets (#1095)

Review of #1308: purchases_outside_sdlt_regions_weight was the raw
weighted total of Scotland's and Wales's purchasers, and on the national
spine the non-residential cell rests on fewer than ten source households;
the fitted value, the shortfall, the event rate, the scale and the
eligible owner weight were floats whatever their support. Every weighted
field now goes through receipt_counts.small_cell_share on the distinct
household_source_id values behind it, so one to nine sources read "<10";
the share at the stock cap appears only where neither the capped
purchasers nor the rest rest on fewer than ten, and the capped support
is an extreme_count band. Nothing was staged or uploaded with the old
field.

Tests: a cell of three outside sources and a fit region of five sources
read "<10"; a capped side of two sources hides the share; the stage test
now requires positive purchases and fitted transactions and value for
both kinds (a nil target branch passed it before); and the packaged HMRC
rows reproduce uk-data#573's household targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 17, 2026
Review of #1308: regional_home_values banded its capped cell on rows,
and support copies from high_value_home_support repeat their source
household, so a cell past ten rows could rest on fewer than ten sources.
The cell is now counted on distinct household_source_id values
(source_count, as the purchases stage counts them) and reported as
sources_at_cap; the row totals go through receipt_counts.rounded_count.
A test bands twelve copies of two households as "<10". No figure leaked:
the cell is latent on the head spine, and the receipt is build output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 17, 2026
…receipt (#1095)

Review of #1308, with María's ruling of 2026-10-17: p, the share of a
combination's drawn units that would claim anyway, pooled Great Britain
and Northern Ireland, while DWP's Move to UC rates cover Great Britain
only. p is now measured over Great Britain's units, and the probability
applies to the combination's units in Northern Ireland too, as the
Pension Credit take-up redraw does.

Where p exceeds DWP's rate the probability is 0, and the receipt now
flags the combination (rate_unreachable), never p itself, and only where
ten or more source households carry it. Each combination's support is
banded on distinct household_source_id values, not rows, since support
copies repeat their source; the aggregates cover Great Britain only and
go through receipt_counts.small_cell_share. The texts say that the share
reaches DWP's rate in expectation where p is at most r, and declare the
two limits uk-data#534 has: the drawn cohort includes units on the
Pension Credit route, whose legacy benefits policyengine-uk does not
close, and DWP's rates count every household sent a notice. The vendored
resource now maps to the spine's 2024 build period.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 17, 2026
Review of #1308: an operand that sums several cells was checked on its
total match count only, so two facts for one listed member and none for
another compiled, and a list of three members declared with a count of
two passed when one was missing. An operand that sums cells now names
them in exactly one list-valued selector field of expected_member_count
distinct members, and each member must match exactly one fact. obr.sdlt
compiles unchanged (each of its four HMRC record ids matches once in
abf0921). Two tests cover the 2+0 case and the mismatched list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 17, 2026
…1095)

Review of #1308, with María's ruling of 2026-10-17 to declare the HRAD
allocation as uk-data#573 does:

- "Receipts are checked against HMRC, never fitted" described a check
  that does not exist. The stage fits transactions and value only;
  households' SDLT is fitted at build level through obr.sdlt, and the
  texts now say so, and that uk-data#573's 30% higher-rates SDLT test is
  not carried over.
- Households' HRAD is not published, so the allocation by individuals'
  shares of all Table 8a residential transactions is an assumption, as
  uk-data#573 says; its household lower bounds are 87,200 transactions
  and GBP 33.28bn. The texts declare that it probably overstates
  households' purchases, since nearly all receipts at or below the
  GBP 250,000 nil-rate band are surcharge and other buyers' receipts
  there imply more higher-rates transactions than their overall share.
- obr.sdlt's notes declare two gaps it does not correct: Table 6b counts
  the higher rates before replacement-home refunds, which policyengine-uk
  never gives, while Tables 8a and 8b and the OBR line are net; and the
  engine's main-home purchase rate also counts the higher-rates
  transactions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 17, 2026
Review of #1308: the engine freeze (María's ruling of 2026-10-16) was
held by uv.lock alone; the UK extras accept any later release, and the
spine build and the calibration only recorded the installed version.
assert_installed_engine_is_pinned compares it with the version the
engine pins were taken from and refuses a mismatch, at the spine build's
engine and at the calibration's simulation factory. Tools that re-pin or
regenerate reports keep their own engines, and an environment without
policyengine-uk is left to the callers' own import errors. A test covers
the match, a later release and the absent engine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22 juaristi22 closed this Oct 17, 2026
@juaristi22 juaristi22 reopened this Oct 17, 2026
@juaristi22

Copy link
Copy Markdown
Collaborator Author

Reply to the review. Thanks for it. It is addressed at 87395578a, rebased onto main now that #1302 has merged. The rebase drops N2: #1306 owns the OBR-leg fix. The other commits replay with every generated surface regenerated, not picked. María's rulings of 17 October are applied where they are needed.

Blocking

  • Purchases receipt (a492afd7a). Every weighted field now goes through receipt_counts.small_cell_share on the distinct household_source_id values behind it. That covers the outside-region weight, the fitted transactions and value, the shortfall, the event rate, the scale and the eligible owner weight. One to nine sources read "<10". The stock-cap share appears only where neither side of it is small, and its support is an extreme_count band. New tests cover a 3-source outside cell, a 5-source fit region and a 2-source capped side. Nothing was staged or uploaded with the old field.
  • Dense rank basis (folded into N1, 9df36237e). The uk.full.property.value slice declares main_residence_value_rank_basis whenever the regional stage stored it, and passes rank_basis_declared. main_residence_rank_basis now refuses a declared-but-missing or a present-but-undeclared column instead of falling back. A run_graph test replaces the helper test: two London owners whose order the regional values swap give the same dense values and wealth as a run without the stage. Ranking on the regional values instead moves them, so the test is sensitive. A dense arm before the frozen build is still owed (see below).
  • X-s01, the local compile parity. Re-pin the UK Chronicle feed to abf0921 (#1300) #1302 regenerated the local receipt on abf0921 before it merged (d8493a321). This PR changes nothing the local compile reads. At the head, the feed-gated run regenerates the committed national and local target surfaces byte-identically from abf0921, and the national compile parity regenerates and passes.

Should fix

  • 2.134.5. Superseded: the lock is 2.134.4 (5b97c0ad3). In a 2.134.4 environment at the head, pin_uk_uprating_engine_values.py --check, refresh_concept_coverage.py --check and build_uk_release_input_coverage_manifest.py --check pass, and the H2 fixture regenerates byte-identical. CI's engine-uk job passes, 214 tests locally and green on CI.
  • N1's national rows (9df36237e). The sentence is gone from the fragment, the notes and the commit message. They now name the three rows N1 moves: obr.sdlt, ons.land.land_value and ons.land.household_land_value. The body reports the head arm's fits. Whether policyengine-uk's UKHPI-based land intensity should apply to Land Registry-basis values is María's call.
  • "Receipts are checked against HMRC" (5262b03f3). The texts now say the stage fits transactions and value only, households' SDLT is fitted through obr.sdlt, and uk-data#573's 30% higher-rates test is not carried over.
  • HRAD allocation (5262b03f3, María's ruling). Declared as uk-data#573 declares it: an assumption, with its household lower bounds of 87,200 transactions and £33.28bn. The texts add the likely upward bias, and UK: track the defects uk-data is fixing that microcosm still has #1095 has a note on it.
  • Knot tails (María's ruling). Declared in N1's texts. They are to be fixed on both lines together in the UK national release: main_residence_value keeps the WAS-imputed tail, so the surcharge's top bands stay empty #1112 follow-up, which keeps the last segment out of HVCTS evidence.
  • The 80% floor. María's ruling stands, and a breach still refuses. The stage is deterministic, and nothing upstream of stage 23 is due to change before the build.
  • The receipt's weighted fields. Fixed with the first blocking item.
  • Rows versus sources (0e481592b, 21f116d5f). The home-value receipt bands its capped cell on source_count and rounds its row totals. The Move to UC receipt bands each combination on distinct sources. Tests cover copies past ten rows on fewer than ten sources.

Minor

  • Follow-up dates. The body now lists them: the two Plan 2 entries lapse on 3 November, the UC housing element on 14 November, and 32 entries on 26 November.
  • Operand membership (28a2fb834). An operand that sums cells now names them in one list of expected_member_count distinct members, and each member must match exactly one fact. Tests cover 2+0 and a 3-member list declared as 2. obr.sdlt compiles unchanged.
  • R0 record. The body now says what is owed: María's confirmation of the 8 January 2027 expiry.
  • M1's claim (21f116d5f). Qualified to "in expectation, where p is at most r". A per-combination rate_unreachable flag is added, never p itself, and only on ten or more sources. uk-data#534's two limits are declared.
  • p pooling (21f116d5f, María's ruling). p is now measured over Great Britain's units and applied in Northern Ireland too. The receipt's aggregates cover Great Britain only.
  • Gross and net in S2 (5262b03f3). Declared in obr.sdlt's notes, as is the main-home rate's double count.
  • The OBR leg fixed twice. Dropped here, as above. UK local surface before the Budget build: high-value homes OBR leg, per-vehicle fuel flag, Northern Ireland energy share and housekeeping (#1278) #1306's stricter version has its own tests, which pin the vendored Table 1.1 values, so N2's test is not needed.
  • The engine freeze (87395578a). The spine build and the calibration's simulation factory now refuse an installed policyengine-uk other than the pinned version.
  • Stage test with nil purchases, and the packaged targets (a492afd7a).
  • Feed-gated checks. Run on abf0921 at the head: 103 with 2 skips, all pass. The two skips read the feed only from the repository root. CI: all 10 checks pass on 87395578a.
  • Stage-health gates (María's ruling). In a follow-up PR. The body lists the three stages as deliberately ungated for now.
  • mapped_build_period (21f116d5f). Now 2024.

Cross-PR

🤖 Generated with Claude Code

@juaristi22

Copy link
Copy Markdown
Collaborator Author

Re-review of this PR at 8739557

Run with Claude Code on 17–18 October 2026, after the rebase onto 1f1cfa6, the relock to policyengine-uk 2.134.4, the dropped N2, the six fix commits and the reply.

Verdict

Most fixes hold, in code:

  • the purchases receipt's banding on distinct source households;
  • the dense rank basis: the slice declares the column, the helper refuses a missing or undeclared one, and two mutations break the run_graph test;
  • operand members matching exactly once (obr.sdlt still compiles to £10.1617bn from the abf0921 rows);
  • the engine freeze;
  • the texts: HRAD, gross and net, N1's three moved rows, and the "checked against HMRC" sentence gone.

Tests: 1,244 engine-free and 214 engine tests pass. The engine run, on policyengine-uk 2.134.4, covers the whole engine-uk list including H2 parity. All the --check tools pass, and the H2 fixture regenerates byte-identical.

Two things block, and the savings-interest renewal is now time-critical.

Blocking

  • The Move to UC and home-value receipts still count rows in a graph build (medium; blocking under the UK Data Service rule before any receipt is staged or quoted).
    • _STAGE_CONSUMES in uk_runtime/graph.py declares ("household", "household_source_id") for property_purchases (:295-301), but not for regional_home_values (:283-291) or uc_legacy_closure_take_up (:412-423).
    • The graph executor hands a stage only its declared columns, so both stages fall back without a word:
      • legacy_closure_take_up.py:312-316 falls back to household ids, which count support and SPI copies separately;
      • regional_home_values.py:147-157 falls back to mask.sum().
    • Two reviewers independently ran the production node declarations through the real executor on synthetic copies of 3 source households. The Move to UC receipt printed rate_unreachable True and support "10-29". The direct function call gives "<10" for both.
    • The first review found a head-spine combination that reads "10-29" by rows but "<10" by sources. In a graph build it would still read "10-29", and now also carries a flag derived from p.
    • The home-value at-cap cell is latent: it always reads "0".
    • So the body's disclosure line ("The stage receipts band every cell on its distinct source households") and the reply's claims for 0e48159 and 21f116d hold at function level only. The H2 fixture is too small to show the difference, so parity can't catch it.
    • Fix:
      • declare the column for both stages;
      • make both refuse when it is missing, as property_purchases.py:823-827 does;
      • add a graph-level test (a node run through UKStageKernel on support copies);
      • regenerate uk_spine.json, the H2 fixture and the coverage manifest digests.
  • The head is behind main.

Timeline: the savings-interest renewal (R0)

Low

  • The purchases receipt's uncapped side (property_purchases.py:672).
    • Its size is taken as fitted_sources − at_cap_sources. That is the number of sources with no capped row, not the uncapped side's distinct sources.
    • A synthetic probe with 3 uncapped rows that copy capped sources shipped the at-cap share, and fitted_transactions × (1 − share) gave back a 3-source weight. Use sources(fitted & ~at_cap).
    • Related: when 1–9 sources carry the fit and every purchaser is at its stock (always so in stock_cap mode), the share prints 1.0 beside "<10". Band it when 0 < fitted_sources < 10.
    • Latent on the national spine.
  • The engine guard's placement doesn't match its description.
    • _default_simulation_factory (hmrc_calibration.py:458-470) serves only functions that no build path calls.
    • The real protection is _rules_engine() in prepare_national_build (full_build_cli.py:2388) and prepare_full_build (:999, and through spine_build.py:1528). The commit and body don't name those.
    • The release-cut certifier and the coverage, H2 and report tools never check the version, and no test drives a real call site with a mismatched version.
    • An importable policyengine-uk with no dist-info reads as "absent" and passes.
    • Strict string equality also refuses 2.134.4.post1 or +local builds. That's fine for a freeze, but worth saying in the docstring.
  • Tests that pass with the fix reverted.
    • All 14 Move to UC tests pass if the stage stops passing great_britain (so p is UK-wide again) or source_ids.
    • The regional source test calls source_count directly.
    • The stock-cap share test passes under a rows-not-sources mutation, because in its 3-source and 2-source scenarios the rows are also under 10. Put 10+ rows on fewer than 10 sources.
  • S2's gross and net, residual. The obr.sdlt note says "Non-resident individuals' surcharge stays inside the individuals' share". In fact their whole SDLT stays inside, and no FRS household is non-resident, so that deviation is still undeclared.
  • The stage test, residual. The H2 fixture still sets additional_residential to null and never reaches the stock_cap branch.
  • mapped_build_period: 2024 is declared for both stages but not enforced; a 2030 frame runs. property_components enforces it.

Nits

  • _support_band(0) prints "<10" for an empty cell, beside a 0.0 weight.
  • "Stage 23", in the first review and the reply, is property_components; property_purchases is manifest index 24.
  • The 80% floor has about 1.5 points of headroom and no warning band. María's ruling stands.
  • The owner and purchaser weight shifts behind N1's land moves are not reported.
  • Suggested rewording in the body's 2.134.5 bullet: "both fail the weight-ratio gate while their other 6 gates pass".

First-round dispositions

  • Fixed: the dense rank basis (the dense arm re-measure is still owed), the purchases receipt's outside-region weight, N1's "the national solve does not move", "checked against HMRC", the follow-up dates, the operand check, the R0 record, p's Great Britain basis, the engine freeze, the vendored-targets test, mapped_build_period, E1's ruling, and X-s01 for this PR.
  • Superseded by a ruling: the 2.134.5 lock (relocked to 2.134.4), the 80% floor, the HRAD allocation (declared), the knot tails (declared) and the stage-health gates (a follow-up).
  • Partly fixed:
    • rows versus sources (function level only; see Blocking);
    • the receipt's weighted fields (the uncapped-side proxy);
    • M1's claim (in a graph build the flag's ten-source gate counts rows);
    • S2's gross and net;
    • the stage test with nil purchases;
    • the feed-gated checks (the 103-test feed run wasn't re-run in this review);
    • X-m05 and X-s03.
  • No longer applies: the OBR-leg duplicate (N2 dropped), X-m06, X-s04 and the two refuted items.
  • Not needed: the notice-window label.
  • Now this PR's: X-s05 (above).

Cross-PR

Disclosure check

No figure in the 13 commit messages, the added lines, the body or the reply breaches the output rules. Every figure in the body is one of:

  • an HMRC, OBR or DWP statistic;
  • a statutory amount;
  • a register count or date;
  • a fit statistic;
  • a national weighted aggregate.

The "about 81%" non-residential fit rests on 30 or more source households. The body's disclosure line overstates the banding until the first Blocking item is fixed.

Tests and tools run

  • At 8739557:
    • engine-free: 1,244 tests, 0 failures, 6 skips, plus 1,258 more, 0 failures, 5 skips;
    • engine (policyengine-uk 2.134.4): 214 + 69 tests, 0 failures.
  • Tools:
    • tools/ci_test_plan.py verify ok;
    • the engine-pin, concept-coverage and coverage-manifest --checks pass;
    • the H2 fixture regenerates byte-identical.
  • Mutations: 10. Eight are caught; the two that survive, the Move to UC stage's great_britain and source_ids wiring, are listed under Low.
  • Feed: fixed-string searches only. obr.sdlt compiles from 11 extracted abf0921 rows to the committed value.

🤖 Generated with Claude Code

juaristi22 and others added 7 commits October 18, 2026 02:00
#866)

The ons.savings_interest_income measure exclusion lapses on 2026-10-22.
After that date no national calibration could run, since the window is
checked when the exclusions are applied. The row stays a measured
diagnostic and is never fitted, as ruling R3 of 2026-09-22 set: the ONS
D.41 row is a national-accounts concept, while the model carries cash
interest, and the SPI interest rows are the fitting anchor. Renewed by
juaristi22 on 2026-10-16, folded into the sixth #1095 ports PR. The
packaged-exclusions test pins the new window, the renewal note and the
entries approved on 2026-10-16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UK lock moves from 2.132.6 to 2.134.4, and the three packages' UK
extras require it. policyengine-core stays at 3.33.3 (UK) and 3.32.5
(US); 2.134.4 asks for core >=3.32.27. Nothing the build writes is
renamed, removed or re-typed.

Two inputs join the engine and stay uncovered here (coverage 300 to 302):
2.134.1 turns additional_residential_property_purchased and
non_residential_property_purchased into inputs that default to nil
(pe-uk#2241), which the stamp-duty commits of this PR supply.

2.134.3 starts Scotland's top rate at the statutory 125,140 of taxable
income (pe-uk#2437), so the rate-class docstring and the salary-sacrifice
fragment no longer say it files advanced-rate relief as additional.
2.134.0 makes a realisation elasticity of 1 the default response to a CGT
reform (pe-uk#2419): the CGT taxable-income lockstep test inverts the tax
under a measurement reform, so that reform now switches the response off.
No build path runs a parameter reform, so the build is unaffected.

The lock stops at 2.134.4 (María's ruling of 2026-10-16). 2.134.5
(pe-uk#2240) limits pension relief to relevant earnings and charges the
annual allowance with a new unused-allowance input at nil; on the national
spine it moves Universal Credit awards and income tax for households with
pension contributions, and the solve fails the calibrated-seam
weight-ratio gate. It waits for a follow-up that supplies the input.

Re-derived: the uprating pins (version stamp only), the concept golden
and report, the texts that cite the locked engine, the H2 fixture's
dependency, the coverage manifest's source digests and the lock hash.

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

From policyengine-uk 2.134.1 (pe-uk#2241) both purchases are inputs that
default to nil, so a purchasing household paid SDLT on its main home
only. A new spine stage, property_purchases, after the support copies,
ports uk-data#573 (a draft, at 07f6b460f) at the spine's design weights:
a household whose stock of a kind is at least GBP 40,000 (the
chargeable consideration HMRC's tables cover) can buy once a year, at a
hazard proportional to its stock; the event-score threshold is the one
whose weighted count of purchasers in England and Northern Ireland is
closest to HMRC's transactions, and it applies everywhere; prices are
band means at HMRC's band shares times one scale bisected to HMRC's
value, never below the floor or above the stock. Draws are
identity-keyed, so they differ from uk-data's positional streams.

The targets are vendored from the pinned feed (hmrc_stamp_tax_facts.json,
110 rows): Table 6b's 2024-25 HRAD transactions and value allocated to
households by individuals' shares of Table 8a, and Table 8b's 2024-25
individuals, which reproduce uk-data#573's figures exactly. The stage fits
transactions and value only; households' SDLT is fitted at build level
through obr.sdlt. The stage records support only as a band.

The roster, kernel tables, operation vocabulary, schema, export
allow-lists, coverage manifest, gate digests and H2 fixture move with it.
The fixture sizes its own targets to its frame and leaves a kind whose
synthetic stock is nil unimputed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…receipts (uk-data#564, #1095)

obr.sdlt bound the whole OBR Stamp Duty Land Tax line, which every buyer pays;
HMRC's other buyers (companies, partnerships and collective investment
schemes) pay about a third of the receipts, and policyengine-uk charges
households' stamp_duty_land_tax with corporate_sdlt beside it. The target is
now the line's calendar-2025 window times individuals' share of HMRC's
2024-25 SDLT receipts, residential (Table 8a) and non-residential (Table 8b)
together, from the pinned Chronicle feed. uk-data#569 (a draft, at
0544d692f) subtracts policyengine-uk's corporate SDLT statistic instead,
which needs the unreleased pe-uk#2322.

A new value operation, calendar_year_window_scaled_by_ratio, carries the
rule: the calendar-year window of the reference's own series times the
quotient of two operands that each pin their own period, may come from
another publisher, and may each sum a declared number of cells. The
regenerated target references, membership report and national compile
parity move on obr.sdlt alone; the parity row's reason now names the payer
basis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the national spine, the non-residential purchasers' stock falls short of
HMRC's Table 8b individuals' value, so the property purchases stage refused
to build (uk-data#573 refuses any shortfall). Per María's ruling of
2026-10-16, where the purchasers' stock cannot carry the value target every
purchaser buys at its stock, the most the cap allows, and the receipt
records the fit (value_fit, value_shortfall_share); the stage still refuses
below 80% of the value (minimum_value_share). The selection, its stock
exponent and HMRC's transactions are unchanged.

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

The national release solves the bound spine, which has no area, so its
main_residence_value stayed the WAS-imputed value after the regional
uprating; only the dense line read values off the assigned constituency's
Land Registry distribution (uk.full.property.value, microcosm#1240).

A new spine stage, regional_home_values, after the high-value home support
copies, gives every England-and-Wales owner household with a value the one
its region's uprated Land Registry distribution holds at the household's
weighted mid-rank among the owner households of its FRS region, at the
spine's weights. It ranks as the dense value node does (ties broken by an
identity-keyed draw; the support copies spread over the top stratum by their
own draws), reads the region level of uk/high_value_home_value_knots.json
(Wales is W92000004), and moves property_wealth by the value's change.
Scotland, Northern Ireland, renters and owners without a value keep theirs,
and the council-tax band is untouched.

The stage keeps the value it replaced in main_residence_value_rank_basis.
The dense value node receives that column through its slice and ranks on
it, so the dense line's values, wealth and bands do not change; it refuses
if a declared column is missing, and a run_graph test pins both. The
release boundary drops the column on both lines. The value reaches three
national rows: obr.sdlt, through the main-home purchase value, and
ons.land.land_value and ons.land.household_land_value, through
property_wealth; corporate land value does not move. The region knots'
tails run up to each region's largest sale, as the dense line's do; they
are to be fixed on both lines together (María's ruling of 2026-10-17).
The roster pins move to 44
stages; the coverage manifest, the H2 fixture and the stage JSON are
regenerated, and no gate digest moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-uk 2.128.0 (pe-uk#2119) moves a family off a legacy benefit
DWP has closed and takes its other legacy awards away; the family then
claims Universal Credit if would_claim_uc or would_claim_uc_at_legacy_closure
is true, and the engine's default for the second, true, put every such
family on Universal Credit.

A new spine stage, uc_legacy_closure_take_up, after uc_capital_coherence and
before pension_credit_take_up, ports uk-data#534 (a draft, at c67077a66;
uk-data#492). A unit reporting Universal Credit, a unit whose would_claim_uc
is true and a unit reporting none of the six legacy means-tested benefits
keep the default. Every other unit is drawn at (r - p) / (1 - p), clipped
to [0, 1]: r is DWP's claimed / (claimed + did not claim) for the unit's
combination of legacy benefits (the all-households rate where DWP reports
none), and p is the combination's share already claiming, at the spine's
design weights (María's ruling of 2026-10-16; uk-data#534's share is
unweighted). The rates are DWP's Stat-Xplore households sent a migration
notice, notices July 2022 to June 2026, vendored from the pinned feed
(dwp_move_to_uc_facts.json). Each unit reads an identity-keyed uniform.

The flag joins the export surface's reviewed extra columns, so 5 of the 12
mirrored gate digests move; the policyengine-uk concept mapping binds it as
a take-up input, so no take-up input is left at the engine's default. The
roster pins move to 45 stages, and the coverage manifest, the H2 fixture,
the stage JSON and the concept coverage report are regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 and others added 4 commits October 18, 2026 02:24
…gets (#1095)

Review of #1308: purchases_outside_sdlt_regions_weight was the raw
weighted total of Scotland's and Wales's purchasers, and on the national
spine the non-residential cell rests on fewer than ten source households;
the fitted value, the shortfall, the event rate, the scale and the
eligible owner weight were floats whatever their support. Every weighted
field now goes through receipt_counts.small_cell_share on the distinct
household_source_id values behind it, so one to nine sources read "<10";
the share at the stock cap appears only where neither the capped
purchasers nor the rest rest on fewer than ten, and the capped support
is an extreme_count band. Nothing was staged or uploaded with the old
field.

Tests: a cell of three outside sources and a fit region of five sources
read "<10"; a capped side of two sources hides the share; the stage test
now requires positive purchases and fitted transactions and value for
both kinds (a nil target branch passed it before); and the packaged HMRC
rows reproduce uk-data#573's household targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1308: regional_home_values banded its capped cell on rows,
and support copies from high_value_home_support repeat their source
household, so a cell past ten rows could rest on fewer than ten sources.
The cell is now counted on distinct household_source_id values
(source_count, as the purchases stage counts them) and reported as
sources_at_cap; the row totals go through receipt_counts.rounded_count.
A test bands twelve copies of two households as "<10". No figure leaked:
the cell is latent on the head spine, and the receipt is build output.

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

Review of #1308, with María's ruling of 2026-10-17: p, the share of a
combination's drawn units that would claim anyway, pooled Great Britain
and Northern Ireland, while DWP's Move to UC rates cover Great Britain
only. p is now measured over Great Britain's units, and the probability
applies to the combination's units in Northern Ireland too, as the
Pension Credit take-up redraw does.

Where p exceeds DWP's rate the probability is 0, and the receipt now
flags the combination (rate_unreachable), never p itself, and only where
ten or more source households carry it. Each combination's support is
banded on distinct household_source_id values, not rows, since support
copies repeat their source; the aggregates cover Great Britain only and
go through receipt_counts.small_cell_share. The texts say that the share
reaches DWP's rate in expectation where p is at most r, and declare the
two limits uk-data#534 has: the drawn cohort includes units on the
Pension Credit route, whose legacy benefits policyengine-uk does not
close, and DWP's rates count every household sent a notice. The vendored
resource now maps to the spine's 2024 build period.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1308: an operand that sums several cells was checked on its
total match count only, so two facts for one listed member and none for
another compiled, and a list of three members declared with a count of
two passed when one was missing. An operand that sums cells now names
them in exactly one list-valued selector field of expected_member_count
distinct members, and each member must match exactly one fact. obr.sdlt
compiles unchanged (each of its four HMRC record ids matches once in
abf0921). Two tests cover the 2+0 case and the mismatched list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 and others added 2 commits October 18, 2026 02:44
…1095)

Review of #1308, with María's ruling of 2026-10-17 to declare the HRAD
allocation as uk-data#573 does:

- "Receipts are checked against HMRC, never fitted" described a check
  that does not exist. The stage fits transactions and value only;
  households' SDLT is fitted at build level through obr.sdlt, and the
  texts now say so, and that uk-data#573's 30% higher-rates SDLT test is
  not carried over.
- Households' HRAD is not published, so the allocation by individuals'
  shares of all Table 8a residential transactions is an assumption, as
  uk-data#573 says; its household lower bounds are 87,200 transactions
  and GBP 33.28bn. The texts declare that it probably overstates
  households' purchases, since nearly all receipts at or below the
  GBP 250,000 nil-rate band are surcharge and other buyers' receipts
  there imply more higher-rates transactions than their overall share.
- obr.sdlt's notes declare two gaps it does not correct: Table 6b counts
  the higher rates before replacement-home refunds, which policyengine-uk
  never gives, while Tables 8a and 8b and the OBR line are net; and the
  engine's main-home purchase rate also counts the higher-rates
  transactions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1308: the engine freeze (María's ruling of 2026-10-16) was
held by uv.lock alone; the UK extras accept any later release, and the
builds only recorded the installed version. assert_installed_engine_is_pinned
compares it with the version the engine pins were taken from and refuses
a mismatch. It runs in spine_build._rules_engine, which gives the spine
build its engine and which prepare_full_build and prepare_national_build
call before they solve, and at the start of the release-cut battery the
certifier runs. The HMRC calibration helpers' default simulation factory,
which no build path calls, checks too.

The match is exact, so a post-release or a local build of the pinned
version is refused as well. An importable policyengine-uk without
distribution metadata is refused, since its version cannot be read; an
environment without policyengine-uk is left to the callers' own import
errors. The tools that regenerate committed surfaces do not check: CI's
--check jobs and the H2 parity run them on the lock. Tests drive
_rules_engine and the release-cut battery with a later release, and
cover the match, a post-release, the absent engine and the missing
metadata.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 18, 2026
On main after #1307 and #1306: 27,267 rows, 23,004 of them compiled. The 18
Scottish BRMA rows are ledger-only by construction, and no signed value
moves. The 40 cells of the support floor's two authorities, E06000053 and
E09000001, carry the floor's own signed rationale; nothing else changes.
The release-blocking preflight gate
uk_ledger_compile_parity_local_incumbent_2025 reads this receipt, so
whichever of #1305 and #1308 lands second regenerates it on the combined
tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 and others added 5 commits October 18, 2026 03:09
…count (#1095)

Re-review of #1308: the regional_home_values and
uc_legacy_closure_take_up nodes did not declare household_source_id, and
the graph executor hands a stage only its declared cells. In a graph
build both receipts fell back without a word, the Move to UC one to
household ids and the home-value one to row counts, so support copies
counted once each. Both nodes now declare the column, and both stages
refuse a frame without it, as property_purchases does.

New tests run each production node through the executor's input
projection and UKStageKernel on twelve support copies of three source
households: the Move to UC combination reads "<10" for the flag and the
support, and the home-value cap cell reads "<10" (the test lowers the
cap, which on the packaged knots no rank below 1 reaches). Before this
change the same tests read True and "10-29". Stage-level tests now also
fail if the stage stops passing Great Britain's units (a UK-wide p
crosses the rate) or the source ids. The H2 graph fixture moves on the
two nodes' inputs only.

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

Re-review of #1308: the purchases receipt sized the uncapped side as the
fit's sources less the capped side's, which is the number of sources
with no capped row. Where uncapped rows copy capped sources, a share on
a three-source side shipped, and the fitted weight times one less the
share gave that side's weight back. Each side is now counted on its own
distinct sources, and the share is also withheld when one to nine
sources carry the fit, since at the stock cap every purchaser buys at
its stock and a share of 1.0 beside "<10" would still describe it.
Tests put twelve capped rows on two sources, three uncapped rows on
copies of capped sources, and a stock-cap fit on five sources; the old
logic and a rows-not-sources count each fail them.

The three stages shared a copy of the same support band, which read
"<10" for an empty cell beside a zero weight. receipt_counts.support_band
now serves all three and reads "0" there.

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

Re-review of #1308: both stages declare mapped_build_period 2024 for
their vendored facts, HMRC's 2024-25 Stamp Tax tables and DWP's
cumulative Move to UC rates, but neither checked it, so a 2030 frame
ran on them. Each now refuses a frame at any other period, as
property_components does, and the declared period is the constant the
check reads. A test per stage covers a 2030 frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-review of #1308: the H2 fixture never reached the stock-cap fit, the
branch the national spine's non-residential purchases take. Its
synthetic non-residential target now asks a ninth more than the
purchasers' stock, so the stage buys at the stock in both executors;
the synthetic frame holds no other-residential stock, so that kind is
still not imputed there. A stage test now fits one kind at the stock
beside one it scales, in one run.

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

Re-review of #1308: main gave the region rows' local-only reason as the
national spine's main_residence_value being the WAS donor's, not a Land
Registry placement (microcosm#1301). N1 makes that untrue: the national
spine now reads the value off the regions' Land Registry knots. The rows
stay local-only for now, and the reason is restated in the three notes,
#1301's fragment, the calibration-lines docstring, the generator's
signed rationale with the membership file it writes, and its pin:
binding them on the national line would move the national registry and
solve while the knots' tails still place more homes in the top bands
than the derived stock, which is left to microcosm#1112's follow-up.

obr.sdlt's note said non-resident individuals' surcharge stays inside
the individuals' share. Their whole SDLT does, and no FRS household is
non-resident, so that is now the first of three declared gaps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22
juaristi22 force-pushed the uk-data-ports-1095-6 branch from 8739557 to ca13808 Compare October 18, 2026 03:22
@juaristi22

Copy link
Copy Markdown
Collaborator Author

Reply to the re-review. Thanks for it. It is addressed at ca138083e, rebased onto main 58b0f242f (#1307 and #1306). Every commit after R0 was replayed with its generated surfaces regenerated under policyengine-uk 2.134.4, not picked from either side; R0 changes none.

Blocking

  • The graph receipts (2b03b7d1d).
    • _STAGE_CONSUMES now declares ("household", "household_source_id") for regional_home_values and uc_legacy_closure_take_up.
    • Both stages now refuse a frame without it, as property_purchases does. The silent fallbacks to household ids and to row counts are gone.
    • New tests run each production node through the executor's input projection and UKStageKernel, on twelve support copies of three source households:
      • the Move to UC combination reads "<10" for both the flag and the support;
      • the home-value cap cell reads "<10" (the test lowers the cap, because on real knots the cell cannot fill).
    • Before this fix, the same tests read True for the Move to UC flag and "10-29" for the home-value cell. With the old declarations, the stages would now refuse.
    • Every derived surface was regenerated: uk_spine.json moves on the two nodes' inputs only, and the H2 fixture and the coverage manifest regenerate unchanged.
    • The body's disclosure line now holds in a graph build too.
  • Behind main.

Timeline (R0)

  • R0 is cc21887c0 on main, with the one-line test update. It merges alone as well. Whether to ship it alone now is María's call.

Low

  • The uncapped side (0d4a58c42). Its size is now sources(fitted & ~at_cap). The share is also withheld when one to nine sources carry the fit. Tests cover three probes:
    • twelve capped rows on two sources;
    • three uncapped rows that copy capped sources;
    • a stock-cap fit on five sources.
      A rows-not-sources mutation and the old proxy each fail them.
  • The engine guard (6c011906d, amended).
    • The docstring, the commit and the body now name the real call sites: spine_build._rules_engine, which prepare_full_build and prepare_national_build call before they solve, and which gives the spine build its engine.
    • The release-cut battery now checks too, so the certifier refuses as well.
    • The factory check stays, and is described as covering helpers that no build path calls.
    • An importable policyengine-uk without distribution metadata is now refused.
    • The docstring says the match is exact, so a post-release or a local build of the pinned version is refused.
    • The tools that regenerate committed surfaces still do not check. CI's --check jobs and the H2 parity run them on the lock.
    • Tests drive _rules_engine and the battery with a later release.
  • Tests that passed with the fix reverted (2b03b7d1d).
    • New stage-level Move to UC tests fail if the stage stops passing great_britain (a UK-wide p crosses the rate) or source_ids (copies read "10-29").
    • The regional test now goes through the stage and the node, not source_count.
    • The stock-cap tests put twelve rows on two sources.
  • S2's gross and net (ca138083e). The note now says that non-resident individuals' whole SDLT stays inside the individuals' share, and that no FRS household is non-resident. That makes three declared gaps.
  • The stage test (f6e9d619f). The H2 fixture's non-residential target now asks a ninth more than its purchasers' stock, so the fixture runs the stock-cap fit, as the national spine does. The fixture's frame holds no other-residential stock, so that kind stays unimputed there. A new stage test fits one kind at the stock beside one it scales.
  • mapped_build_period (8928b1e80). Both stages now refuse a frame at any period other than 2024, as property_components does.

Nits

  • Empty cells (0d4a58c42). One support_band in receipt_counts now serves all three stages, and reads "0" for an empty cell.
  • Stage index. Right: property_purchases is manifest index 24, and stage 23 is property_components.
  • The 80% floor. María's ruling stands. The head arm's fit is in the body.
  • Weight shifts behind N1's land moves. Now in the body, from the head arm, design weights against the solve's: England-and-Wales owner households' weight falls 4.4% and their weighted home value 11.8%, so the solve meets the household land row mostly by down-weighting higher-value owners. Additional-dwelling purchasers' weight falls 4.2%, and non-residential purchasers' rises 8.1%. Each group rests on 30 or more source households.
  • The 2.134.5 bullet. Reworded as suggested.

Cross-PR

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
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.

1 participant