Repository navigation
UK landlords: bind the SPI net property amounts, leave property_wealth to the engine, add receipts and finance costs - #1145
Conversation
82c744c to
7b20ef7
Compare
|
@MaxGhenis, we need two rulings on the FRS property rule from #1081 and uk-data#509 before this merges. 1. Adding imputed finance costs back to FRS landlords' profit. ROYYR1 is the rent after the items on show card K6, which include mortgage payments and loan interest. So FRS landlords' profit sits below the SPI concept that
2. If the add-back stands, what about landlords who report a loss? Because K6 nets mortgage capital and interest, an FRS loss is often not a tax loss. Take £12,000 of rent, £2,000 of repairs and £11,000 of mortgage payments, £9,000 of them interest. That is reported as a £1,000 loss, but for tax the profit is £10,000, with £9,000 of finance costs. The stage draws finance costs only for FRS landlords reporting a profit, so landlords with a mortgage who report a loss stay at zero. The consistent extension would draw their interest the same way, add it to the reported loss, and keep the result if it is positive. That turns some reported losses into profits, against "a loss counts as zero". It affects at most 67 adults in the FRS 2024-25 (0.11m weighted). Should we extend the add-back to them, or keep their losses at zero? If you disagree with either, we'll change the stage before merge and re-measure. |
668d916 to
4cb36ac
Compare
- Band donors: the pools are cut on the income leaves alone, so a record's restricted finance costs never move it between total-income bands; the costs travel with the drawn record (review item 1, with a test). - property_components: each bounded band records its rounding allowance, the weight of the person straddling each of its boundaries; the top band records whether its multiplier sits at the floor of 1, the floor mean and the overshoot; the stage refuses a frame off the declared build period; the unused nonnegative-columns constant is gone (items 7, 8, 14). - uk_stage_property_components holds each band to its own allowance, fails any landlord banded below their profit and reports a multiplier at the floor; five gate tests join (item 8). The gate-battery and spine part digests move with gates.json. - The input-mass gate compares the release surface, the export frame, on both paths (item 5). - Wording: the top band is solved where the target mean is reachable and otherwise held at 1; the PRIS arms missed the 10% fit band, two breached the release bound, and uk_weight_ratio failed; the scratch H5's drop list is spelled out (items 6, 7, 9). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The 13 SPI Table 3.7 net property-income amount rows move from the flat per-capita GDP index to hmrc.itl_2026.total_income_growth_by_total_income_band, the index the paired count rows' growth belongs with and the employment, self-employment and private pension amount rows already use (María's ruling of 2026-10-11 on the #1145 review, item 4): GBP 33.60bn for 2025 instead of 31.78bn, with factors from 0.977 to 1.236 by band. The flat index implied per-band mean profit moves of -13% to +11% against the count rows. The generator's rationale, the docs and the receipts note say PRIS stays a diagnostic and describe the FRS-side add-back. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks for the review. Every item is addressed in the five commits above Must fix
Should fix
Minor
#1198 and #1167. Noted. Whichever lands first, the other rebases; if #1198 lands first, PRIS is re-vendored from 942a1fa and the West Midlands cell is measured again on the bridged head. |
Re-review (Claude Code) at
|
| # | Status | Note |
|---|---|---|
| 1 Band-donor pools | Code fixed; test doesn't catch it (M1) | spi_income.py:1155-1169 cuts the pools and the outside guard on income leaves only, and :1258 copies the costs at their own factor. The pools are again the pre-#1106 pools, so "earlier draws unchanged" holds. gift_aid and charitable_investment_gifts still count toward the pool total; that predates this PR. |
| 2 West Midlands | Fixed | The register is {}, and test_uk_terminal_gates.py:570 fails any re-added entry. |
| 3 Measured tree | Fixed | c7 ran on f478063d5; only receipts follow. Its base is still a469ebe3d; see CI below. |
| 4 Amount index | Fixed | All 13 rows use total_income_growth_by_total_income_band: £33.596bn, factors 0.977–1.236. Implied per-band means now run 0.993–1.025, as employment's do. The £1m+ band is +2.51%, a hair over the note's "within 2.5%". |
| 5 Signatures | Partly; see M4 and M5 | The input-mass property_wealth entry is fully signed, with an expiry, and the gate now compares uk_release_export_frame(...) on both paths (battery_bindings.py:1487). The spine-swap entry is signed. |
| 6 Scratch drops | Fixed | Only incapacity_benefit_reported and property_wealth are policyengine-uk variables in 2.122.2, and in 2.123.0 at its tag. The changelog says so. |
| 7 Top band | Fixed; text partly stale (M6) | The bisection and floor are recorded, and the gate reports the floor without failing. The floor test covers only a non-positive target, not a positive target with overshoot. |
| 8 Gate | Partly (M2) | Per-boundary allowances and rows_with_profit_above_their_band are now gated, with five new tests. |
| 9 Wording | Fixed | |
10 uv.lock |
Fixed | Revision 5. Against origin/main only the pe-uk 2.123.0 lines and the ptyprocess marker differ, and APPROVED_UV_LOCK_SHA256 matches the committed bytes. |
| 11 Stale text | Fixed, with new spots (M3, M6) | |
| 12 Figures | Fixed | They're c7's, and Part B states policyengine-uk 2.100.0. |
| 13 Receipts ≥ profit | Verified | 2.123.0's uprating_indices.yaml puts property_income and property_rental_income on obr.per_capita.gdp, and property_finance_costs on obr.mortgage_interest. |
| 14 Nits | Fixed | mapped_build_period is enforced and tested (property_components.py:912-918). |
New
Minor
- M1. The item-1 test passes on the old code.
test_resample_pools_ignore_restricted_finance_costs(test_uk_spi_band_donors.py:623-668) sets the finance-cost factor to 2.0. Under the old pool total, the sub-200k records with 300k of costs land in the 500k pool, not the 200k one. So the 200k pool count,carriers_outside_band, the drawn pay and the copied cost all still pass; only thepool_basisstring check would fail. Fix: use factor 1.0 with costs sized for the 200k band, or also assert that the 500k pool is unchanged. - M2. The empty-band gate case isn't shown through the stage.
- The gate test hand-sets the empty band's
boundary_allowance_weightto 7,000. But for an empty band both boundaries fall on the same person, soproperty_components.py:691-700would report twice that person's weight, while the walk can legitimately miss by at most once. - The gate trusts whatever allowance the stage reports, and never bounds it, though it reads
largest_landlord_weight. - Fix: count one boundary weight for an empty band, drive the test through
allocate_property_rental_income, and cap the allowance at twice the largest weight.
- The gate test hand-sets the empty band's
- M3. The parity reasons are stale.
_SPI_PROPERTY_AMOUNT_DRIFT_RATIONALE(tools/build_uk_ledger_compile_parity_signed_differences.py:72-80) still says the 13 rows use "the pinned engine's per-capita GDP index". It's regenerated into all 13 rows ofledger_compile_parity_incumbent_2025_signed_differences.json, whose values are now on the band index. - M4. The input-mass switch to the export frame is untested.
battery_bindings.py:1480-1487changed ind904b5dedwith no test, so reverting it would leave CI green. - M5. The engine-derived exclusion's new signature has no effect.
parity_reference._ledger_entries(:211-237) reads onlyreasonandtracking_note. Nothing requiresapproved_byorapproved_onthere, and there's no expiry. - M6. "k stays 1 on the licensed spine" is still in
changelog.d/1106-uk-property-components-stage.added.md:5and the body's C3 bullet. On c7 the multiplier is 1.166.
Nits
spine_swap_signed_differences.json:915and the input-massadjudicationcite c6x as "the final head". The band-donor fix then changed the landlord set (2,594 against 2,635 records), so the signed figures are pre-fix.d904b5ded,70f7bfdbaand824ce68adcarry main'suv.lock(pe-uk 2.122.2) against>=2.123.0in the pyprojects, untilf478063d5re-derives it. So a bisect through them won't build.docs/uk-income-anchors-280.md:57-63still says the employment, self-employment and pension amounts follow OBR indices; they use the band index.- The body labels c6x as
700e2e901; the receipts say7b20ef73c.
CI and main
- CI: the run for
adafe6b8cwas still queued. The last green run was at4cb36acf2on 2026-10-09. Main has since merged Make UK build outcomes honest in staging, telemetry and the Logbook #1147, Add country-neutral Orrery execution evidence and UK contracts #1144, Document per-country engine environments and test every workspace US install path (#1086) #1169, Cap every CI job's runtime #1172 and Bind state × AGI-band SOI targets and re-pin the US feed for TY2023 state bands (#940) #1102, so the PR as merged with current main hasn't been tested yet. - Main overlap: I found no semantic conflict with main's 68 newer commits. Its edits to
calibration_run.py,spine_build.py,test_uk_graph.pyand the docs don't touch this PR's logic. Main hasn't changedbattery_bindings,stage_health,gates.json,contract.pyoruv.locksince the base, so the digests and the lock pin should still hold. - Expiring exclusion: on main,
calibration_measure_exclusions.jsonhas an entry expiring 2026-10-15, which every UK build will hit.
Independent adversarial review (Claude Code) at
|
adafe6b to
59bf19a
Compare
- Band donors: the pools are cut on the income leaves alone, so a record's restricted finance costs never move it between total-income bands; the costs travel with the drawn record (review item 1, with a test). - property_components: each bounded band records its rounding allowance, the weight of the person straddling each of its boundaries; the top band records whether its multiplier sits at the floor of 1, the floor mean and the overshoot; the stage refuses a frame off the declared build period; the unused nonnegative-columns constant is gone (items 7, 8, 14). - uk_stage_property_components holds each band to its own allowance, fails any landlord banded below their profit and reports a multiplier at the floor; five gate tests join (item 8). The gate-battery and spine part digests move with gates.json. - The input-mass gate compares the release surface, the export frame, on both paths (item 5). - Wording: the top band is solved where the target mean is reachable and otherwise held at 1; the PRIS arms missed the 10% fit band, two breached the release bound, and uk_weight_ratio failed; the scratch H5's drop list is spelled out (items 6, 7, 9). - The lock is re-derived from main's with uv 0.13.0 (revision 5) and policyengine-uk 2.123.0, and the approved lock pin moves (item 10). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The 13 SPI Table 3.7 net property-income amount rows move from the flat per-capita GDP index to hmrc.itl_2026.total_income_growth_by_total_income_band, the index the paired count rows' growth belongs with and the employment, self-employment and private pension amount rows already use (María's ruling of 2026-10-11 on the #1145 review, item 4): GBP 33.60bn for 2025 instead of 31.78bn, with factors from 0.977 to 1.236 by band. The flat index implied per-band mean profit moves of -13% to +11% against the count rows. The generator's rationale, the docs and the receipts note say PRIS stays a diagnostic and describe the FRS-side add-back. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- M1: the band-donor test uses the stage's factor and costs that the old pools would have counted as income, and asserts the 200k and 500k pools both; it fails on the old code. - M2: an empty band records one straddler's weight as its allowance, since both boundaries fall between the same two people; the gate caps any reported allowance at twice the largest landlord weight; a stage-level empty-band test and a cap test join, with a positive-target floor test. - M3: the compile-parity rationale names the band index; the 2025 receipt is regenerated. - M4: a test holds the input-mass gate to the release surface: a frame carrying the spine's property_wealth against a reference that stores it passes with no stale exclusion. - M5: engine-derived exclusions carry approved_by and approved_on, which the loader requires for that section; a test covers it. - M6 and nits: the top band's multiplier is 1.166 on the licensed spine once the pools are cut on income leaves; the signed registers cite arm c7's figures (the input-mass digest moves); the docs list the four amount families on the band index; the notes read "within about 2.5%". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Taken in #1145 at María's direction, #1167 having merged. - Hardening: the evidence sanitiser drops donor_min and donor_max beside donor_floor and keeps only the spine file's name; the committed outputs and scripts carry no machine-local paths (the scripts read the inputs root from PE). - Minor 1: an ambiguous or duplicate vendored row is a data fault and fails closed as a plain ValueError; only a missing year is recorded as a report-only diagnostic being unavailable. - Minor 2: the recomposition receipt records each parent's uprating residue, the parent's factor against each netted part's, weighted; not corrected. - Nits: the consumption-basis gate holds each bridged column's recorded factor to after / base; the uncarried_spend and donor_uprating docstrings and the obr.fuel_duties_cars and 04.5 binding notes say what the rows are now (measured, not bound); the evidence README says the per-division movement between runs is redraw noise around the uniform bridge. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks for the re-review. All six minor items and the nits are addressed at Minor
Nits
Main. Rebased onto main Measurement on the rebased head ( #1167's re-review items, taken here at María's direction.
|
HMRC's Property Rental Income Statistics are receipts before allowable expenses; SPI Table 3.7 net income from property is landlords' profit after allowable expenses and before residential finance costs, the concept property_income holds (policyengine-uk#2172 now documents it so). The incumbent's x1.9 scaling (uk-data#311) set the first against the second, so the 13 amount rows were signed out pending a ruling. María ruled on 2026-10-05 to bind each concept on its own variable. - The signed exclusion property_income_undercount_adjustment_missing is retired, the hmrc_spi family outcome becomes active_strict_band_pins with a rationale for the net concept, and the contract note replaces the x1.9 sentence. The uprating index stays obr.per_capita.gdp. - Regenerated from feed 825406f: 1,244 active references on #1121's base (13 added, none changed), 15 signed out; membership candidates 1,253 -> 1,265. - Both national compile-parity receipts gain the 13 rows. A property- specific reason precedes the generic SPI drift reason, which blamed the fixture's one-year projection: the 2025 values are £31.78bn against the fixture's £55.78bn (1.9 x £29.36bn). - Tests: the reference counts, the inverted property test (13 rows on the per-capita GDP hold, no value operation), the membership outcome and signed-rationale lists; the income-anchors note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-uk defines property_wealth as residential_property_value (main_residence_value + other_residential_property_value) plus non_residential_property_value. The WAS stage saved the survey's own total, which also counts owned land and the overseas and other-property remainder; a persisted copy overrides the engine's sum, and the variable has no uprating index, so the published aa31bdf6 year files hold it flat from 2026 to 2030 while the sum grows 13% (the uk-data#543 defect). - UK_RELEASE_EXPORT_DROPPED_COLUMNS gains household.property_wealth, and the measure resolver's scratch H5 applies the same drops in #1115's _engine_scratch_frame, beside the alias codes it already leaves out; engine_scratch_dropped_columns in the receipt names them. The spine keeps the column for the WAS stage's identity gate and donor checks. - household.property_wealth is a reviewed export exclusion (constant and gates.json), and the coverage gate's two coverage halves read the frame the release writes, where the raw calibrated frame would report it as a stale exclusion; the build-state half keeps reading the spine (#1115). - The coverage ledger gets a declared engine_derived_exclusions section, separate from the evidence-derived known gaps; the builder refuses drift, overlap with the gaps, and any column that is not a formula- owned override the reference persists. Manifest: 144 required, 1 reviewed exclusion; the three components stay required. - Tests: the scratch H5 and receipt, the export columns, the export- frame coverage binding, the household spine read for nonnegativity, the ledger and manifest, and an engine test that the engine's total is the component sum in 2024 and 2025 and that a persisted total would override it. The gates.json change moves the release contract's UK gate-battery pins (the full-manifest policy, manifest and fingerprint digests and the release_cut part's two digests; microcosm-data contract.py and its test mirror), re-derived from the live spec as the pin tests do. The input-mass parity register entry (efrs-post-calibration) waits for María's signature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aggregates only. A: the 13 bound SPI amount rows compile to GBP 31.78bn for 2025 against the fixture's 55.78bn. B: on the A9 candidate at 2025 the engine-derived property_wealth is GBP 7,633.4bn against 7,469.5bn persisted (+2.2%), household land value 4,697.3bn against 4,595.0bn (target 4,559.8bn), land value 6,755.9bn against 6,653.6bn (target 7,117.8bn); the saved total sat below the uprated component sum for 45,232 of 63,941 households. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.123.0 carries PolicyEngine/policyengine-uk#2172: property_rental_income, property_finance_costs and property_finance_costs_brought_forward, the s. 274A finance-cost reduction, and a property allowance that no longer stacks on expenses already netted from profit. The lock's UK side moves exactly to 2.123.0 (uv lock --upgrade-package policyengine-uk==2.123.0) and nothing else moves: policyengine-core stays 3.32.19 on the UK side, which 2.123.0 accepts, and 3.32.5 on the US side (#1086's per-country pins). The UK extras of microcosm-build, microcosm-data and microcosm-frame require >=2.123.0. The step from #1121's 2.122.2 also brings #2160, #2096, #2097 and #2144. Re-derived with no data change: the uprating pin stamp (values unchanged), the concept coverage (288 inputs, the three property inputs uncovered) and the mapping reviewed against 2.123.0 (the property_income note quotes its new documentation, alignment still approximate; the liquid-asset reason holds unchanged), the spec's declared dependency and the H2 fixture's, the coverage manifest's source digests, and APPROVED_UV_LOCK_SHA256. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SPI tape's LLIR_RESTRICT_AMT_TOT is the "restricted land property finance cost (tax relief is included in TAX_CRED)" (SPI Public Use Tape 2022-23 documentation): the dwelling-related finance costs ITTOIA 2005 s. 272A no longer deducts and s. 274A relieves as a tax reduction. It becomes the first-stage output property_finance_costs, drawn last so it conditions on every income leaf and leaves every earlier draw where it was; the band donors copy it with the record. It is uprated from 2022 by the engine's own index for the variable, obr.mortgage_interest. The donor is refused unless the median TAX_CRED / LLIR_RESTRICT_AMT_TOT on rows with finance costs lies in [0.195, 0.205] (the 20% relief carried in TAX_CRED); the stage receipt records the check. FRS-channel rows start at zero until property_components imputes their landlords. Lockstep: SPI required columns and source columns in the spec and its projection, the stage-1 outputs, the stage outputs and non-negativity list, the graph cells (after the first-stage outputs, the legacy column order), the export allow-list and gates.json with the gate-battery digests, the H2 fixture (synthetic LLIR and TAX_CRED on every third landlord) and the coverage manifest's fixture digests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-uk 2.123.0 reads two landlord inputs beside property_income: property_rental_income (receipts before expenses, which decide between the property allowance and the expenses route) and property_finance_costs (the year's dwelling-related finance costs, relieved as the s. 274A tax reduction). A person-grain property_components stage after regional_property_uprating fills both. Finance costs on the FRS concept. ROYYR1 is rent after show card K6, which nets mortgage payments and loan interest. The stage prepares and rebases the SPI 2022-23 tape exactly as hmrc_spi_income_spine does and keeps its landlords with profit after restricted finance costs above zero, the analog of an FRS landlord reporting a profit. Incidence is a person-keyed uniform against the tape's share in the landlord's decile of profit after finance costs; the amount is the regime-gated QRF fitted on the analog landlords with costs, given that profit and age, drawn at a second person-keyed quantile. The costs are added back to the FRS landlord's property_income so the engine relieves the interest once; the mortgage capital the card also nets stays netted. The add-back is decision 6's recommendation, to be confirmed with Max before merge. The SPI channel keeps the costs the income stage drew. Receipts for every landlord, from PRIS 2024-25 vendored from the pinned feed (hmrc_property_rental_income_facts.json: Tables 1, 2, 6, 8 and 13, 44 rows, typed by uk_runtime.hmrc_property_rental). Landlords on both channels are ranked by profit and walked top band first into Table 13's eleven bands at their individuals-basis counts with the CGT Table 3 walk's rule; receipts are linear within a bounded band, never below profit, and the open top band takes max(100,000, k x profit) with k solved to the individuals' Table 2 receipts. Receipts less profit are the deductible expenses. Sub-letting rent keeps equal receipts and no expenses until Rent a Room is released; landlords without profit carry no receipts (a recorded residual). Gate uk_stage_property_components (stage health, release-blocking, in the spine scope): no receipts below profit, every band above the lowest within twice the largest landlord weight of its target, and the top band's multiplier below its cap. Lockstep: the sources manifest and its projection, two schema branches and the operation-kind allow-list; the graph roster, kernel registry and spine tool; the H2 fixture (40 stages, synthetic PRIS facts sized to the frame the stage reads) and the coverage manifest with its family coverage contract; the vendoring register and the country package; the export allow-list, gates.json, the stage-health parameter keys, the release contract's gate mirrors and its gate-battery digests; the stage-roster tests; the build-graph doc's stage count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1106) Three PRIS 2026 targets (tax year 2024-25), per María's ruling of 2026-10-05 that the receipts bind on their own variable: - hmrc.pris.rental_income.individuals: Table 2's individuals' rental income before expenses on property_rental_income, uprated by the engine's obr.per_capita.gdp (GBP 49.81bn -> 51.99bn for 2025). - hmrc.pris.landlord_count_by_rental_income_band: Table 13's eleven landlord counts by size of rental income, restated from all tax entities to individuals by Table 1's share (2.85m / 2.88m) through scaled_by_ratio and held at their 2024-25 levels; banded on property_rental_income. - hmrc.pris.residential_finance_costs: Table 8's residential finance costs on property_finance_costs, restated by Table 6's individuals' share of allowable expenses (30.03 / 34.75) and uprated by the engine's obr.mortgage_interest (GBP 12.82bn -> 12.13bn for 2025). Table 13's lowest band publishes only its upper edge (up to GBP 10,000), so the binding declares band_floor 0 and the shared band reader takes a declared floor for a band that states only an upper edge; a band without one is still skipped as unreadable. obr.mortgage_interest joins UK_ENGINE_INDEX_PARAMETERS and the pins are re-derived at 2.123.0. The contract records the three as new declarations with no incumbent registry row and adds the PRIS category. Regenerated: the national references and membership (1,244 -> 1,257 active; hmrc_pris family, 11 fan-out rows), the local references (the category only) and the 2025 compile-parity receipt (13 ledger-only rows; the 2023 receipt does not move). Pins moved: active reference counts, contract target counts (392 -> 395), registry scope (341 -> 344), unmapped declarations (149 -> 152), the engine index count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) The walk filled each Table 13 band to PRIS's individuals-basis count. Before calibration the spine carries 2.21m weighted landlords against PRIS's 2.85m, so the ten bands from GBP 10,000 took 71% of the spine's landlords where PRIS has 55%, and every profit rank landed in a higher receipts band than PRIS's distribution implies. The national calibration then scaled that up: on the C3 arm's calibrated weights, which bind no receipts, the stage's receipts came to GBP 55.39bn against PRIS's GBP 49.81bn, with 1.16m landlords at GBP 10,000 to 20,000 against 0.85m and 0.71m in the lowest band against 1.29m. Binding the PRIS targets (the C4 arm) then pulled weight across total-income bands: the SPI property amount at GBP 20,000 to 30,000 of total income went to -17.8% and the weight-ratio gate failed. Each band now takes its share of the individuals-basis landlords times the landlords' weight in the frame. The open top band's target mean is unchanged (the scale cancels), so k is still solved to the individuals' receipts per landlord. Evaluated offline on the same calibrated weights (scripts/walk_variants.py in the measurement folder): receipts GBP 46.11bn and band counts of 1.19m, 0.90m, 0.35m, 0.16m from the lowest up, against PRIS's 1.29m, 0.85m, 0.33m, 0.15m; the misallocation summed over the bands falls from about 1.12m landlords to 0.25m. The receipt records each band's published count beside its target mass and the share scale. Declared text moves with it (the operation's walk and the stage notes in both manifests, the gate notes, the stage's changelog), and so do the gate-battery digests, the H2 fixture's stage payload and the coverage manifest's digests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This reverts commit 82c744c. Receipts are a fixed function of profit rank, so every PRIS count or receipts target measured (all 13 rows, the 13 rows without the lowest band, and the two totals alone, under both walk rules) moved weight across the SPI's total-income bands: the SPI property amounts left their bound and `uk_weight_ratio` failed. The `property_components` stage still vendors PRIS 2024-25 and its receipt reports the spine against it; the stage gate keeps the band shares. Active national references return to 1,244. The arms are in experiments/1106-uk-property-income-receipts.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C3, C6, C7 and C8 of the receipts note: the stage on the licensed spine, the calibration arms (control, C3, C3 without the stale West Midlands deferral, C4, C4 without PRIS's lowest band, the share-filled walk with all 13 PRIS rows and with the two totals only), the engine quantities at 2025, and the decision to keep PRIS a diagnostic. The stage's changelog fragment says why PRIS binds no target. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Arm c6x on 7b20ef7: the share-filled walk with PRIS a diagnostic, spine and calibration built afresh. All 7 calibrated-seam gates and all 33 spine gates pass with #1121's stale West Midlands deferral dropped at gate time; receipts at the calibrated weights against PRIS; the engine's landlord quantities at 2025. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Band donors: the pools are cut on the income leaves alone, so a record's restricted finance costs never move it between total-income bands; the costs travel with the drawn record (review item 1, with a test). - property_components: each bounded band records its rounding allowance, the weight of the person straddling each of its boundaries; the top band records whether its multiplier sits at the floor of 1, the floor mean and the overshoot; the stage refuses a frame off the declared build period; the unused nonnegative-columns constant is gone (items 7, 8, 14). - uk_stage_property_components holds each band to its own allowance, fails any landlord banded below their profit and reports a multiplier at the floor; five gate tests join (item 8). The gate-battery and spine part digests move with gates.json. - The input-mass gate compares the release surface, the export frame, on both paths (item 5). - Wording: the top band is solved where the target mean is reachable and otherwise held at 1; the PRIS arms missed the 10% fit band, two breached the release bound, and uk_weight_ratio failed; the scratch H5's drop list is spelled out (items 6, 7, 9). - The lock is re-derived from main's with uv 0.13.0 (revision 5) and policyengine-uk 2.123.0, and the approved lock pin moves (item 10). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rs (#1106) María's rulings of 2026-10-11. The microcosm#1095 deferral of the West Midlands GBP 12,570 to 15,000 income-tax cell is retired: on this head the cell sits inside the 25% bound (+23.1% on C3, +23.4% on the final arm) and the gate failed it as stale. Signed: the property_wealth entry in the input-mass parity register (the release leaves the column to the engine; expires 2027-04-11), the two net-new landlord columns in the spine-swap register, and the engine-derived coverage exclusion's approval. The input-mass evidence digest and its mirrors move; the battery test clock moves to 2026-10-11. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The 13 SPI Table 3.7 net property-income amount rows move from the flat per-capita GDP index to hmrc.itl_2026.total_income_growth_by_total_income_band, the index the paired count rows' growth belongs with and the employment, self-employment and private pension amount rows already use (María's ruling of 2026-10-11 on the #1145 review, item 4): GBP 33.60bn for 2025 instead of 31.78bn, with factors from 0.977 to 1.236 by band. The flat index implied per-band mean profit moves of -13% to +11% against the count rows. The generator's rationale, the docs and the receipts note say PRIS stays a diagnostic and describe the FRS-side add-back. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Arm c7 on f478063, spine and calibration with no measurement-only edit: 7 of 7 calibrated-seam and 33 of 33 spine gates; the SPI property amounts on the band index fit to 0.4%; receipts against PRIS at the calibrated weights within 0.5%; the band-donor fix moves the SPI channel's finance costs and lifts the top band's multiplier off its floor; the engine's landlord quantities at 2025. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- M1: the band-donor test uses the stage's factor and costs that the old pools would have counted as income, and asserts the 200k and 500k pools both; it fails on the old code. - M2: an empty band records one straddler's weight as its allowance, since both boundaries fall between the same two people; the gate caps any reported allowance at twice the largest landlord weight; a stage-level empty-band test and a cap test join, with a positive-target floor test. - M3: the compile-parity rationale names the band index; the 2025 receipt is regenerated. - M4: a test holds the input-mass gate to the release surface: a frame carrying the spine's property_wealth against a reference that stores it passes with no stale exclusion. - M5: engine-derived exclusions carry approved_by and approved_on, which the loader requires for that section; a test covers it. - M6 and nits: the top band's multiplier is 1.166 on the licensed spine once the pools are cut on income leaves; the signed registers cite arm c7's figures (the input-mass digest moves); the docs list the four amount families on the band index; the notes read "within about 2.5%". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Taken in #1145 at María's direction, #1167 having merged. - Hardening: the evidence sanitiser drops donor_min and donor_max beside donor_floor and keeps only the spine file's name; the committed outputs and scripts carry no machine-local paths (the scripts read the inputs root from PE). - Minor 1: an ambiguous or duplicate vendored row is a data fault and fails closed as a plain ValueError; only a missing year is recorded as a report-only diagnostic being unavailable. - Minor 2: the recomposition receipt records each parent's uprating residue, the parent's factor against each netted part's, weighted; not corrected. - Nits: the consumption-basis gate holds each bridged column's recorded factor to after / base; the uncarried_spend and donor_uprating docstrings and the obr.fuel_duties_cars and 04.5 binding notes say what the rows are now (measured, not bound); the evidence README says the per-division movement between runs is redraw noise around the uniform bridge. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Main re-pinned the Chronicle feed to 1ee7dfe (352,549 rows) and merged #1167, #1087 and #1099 among 87 commits. On the merged tree: the PRIS resource is re-vendored from the new pin (44 rows unchanged), the target references, membership and both national compile-parity receipts are regenerated against it (1,245 active references: main's 1,232 plus the 13 SPI property amounts), the release input coverage manifest and the H2 fixture are re-derived, the gate-battery and certification part digests move with the merged gates.json, and the count pins follow. The commits below this one keep the surfaces they were measured with. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Arm c8 rebuilt the spine on the rebased head: 37 of 37 spine gates pass, and the national step then stopped on main's native-evidence reader defect (microcosm#1187). Arm c9 calibrated that spine with #1188's commit overlaid for measurement only: loss 0.00644, 98.5% of 1,177 targets within 10%, 7 of 7 calibrated-seam gates, the SPI property amounts within 0.3%, and the stage's design-weight receipt identical to arm c7's. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Re-review (Claude Code) at
|
| Item | Status |
|---|---|
| Rebase, #1145 logic | Faithful |
| Rebase, #1167 logic | Faithful |
| Surfaces at head | Fixed |
| M1 | Fixed: on the old pool formula the sub-200k records land in the 200k pool and the count assertion fails |
| M2 | Fixed: the stage counts one straddler for an empty band, the gate caps at twice the largest weight, and reverting fails the stage-driven test |
| M3 | Fixed: all 13 reasons name the band index |
| M4 | Fixed: on the raw frame the exclusion is stale and the gate fails, so reverting fails the test |
| M5 | Fixed: non-empty approved_by and an ISO approved_on are required (parity_reference.py:220-262) |
| M6 | Fixed |
| #1167 items | Fixed: the sanitiser drops donor_min and donor_max, scripts read PE with no machine paths, ambiguous rows fail closed, and the residue is recorded. The basis check is only partly fixed (item 6). |
Sequencing
- Bound native evidence by bytes, not by the schema item budget #1188 is open, so no national build or certifier can run on this head until it lands. Main's measure exclusion expiring 2026-10-15 also needs renewing before the next national build.
- One consistent UK target surface: enforced cross-grain reconciliation, declared uprating holds, nation household controls and the 942a1fa re-pin (#1123 PR A) #1198: after this lands, it must re-vendor
hmrc_property_rental_income_facts.jsonat942a1faand regenerate the 13 property references. - UK: port the #1095 fixes that need no new policyengine-uk release #1200: its base predates Set UK road-fuel and energy levels in the LCFS stage on a calendar-2024 basis (#1113) #1167 and it overlaps 33 of this PR's files, so whichever lands second re-derives the contract digests. There's also the register collision above.
59bf19a to
63943a7
Compare
|
Thanks María, reviewed at
What I checked
Good to merge from my side once Max has ruled, #1188 is in, and 1 and 2 are recorded. |
|
Thanks Vahid. Nothing here changes code, so no new CI round; items 1 and 2 are recorded in the description now and go into the receipts note with the follow-up PR that also carries the control arm on the current base.
The receipts note gets the same three items and the control arm on the current base in the follow-up. |
The branch now sits on 51a2637. main brought the landlord work of microcosm#1145: the 13 SPI Table 3.7 property-income amounts bound unscaled and uprated by the band index, the property components stage, the PRIS resource, and policyengine-uk 2.123.0; and #1188 and #1211. Where the two sides touched one hunk the rebase kept the branch's side; this commit puts both sides in and regenerates what depends on them, on the 942a1fa feed. - The SPI property-income amount target takes main's note and band index and keeps this branch's taxpayer filter (income_tax > 0), as every other SPI row does here (decision "taxpayer basis everywhere"). - National references: 1,281 active, the branch's 1,268 plus the 13 property amounts. - The vendor selections keep both sides' resources; hmrc_property_rental_income_facts.json is vendored again under the 942a1fa feed identity, and the UK spine parity fixture follows it. - The two national parity receipts and the reference membership are regenerated; the local references, the local census and the local parity receipt come out byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…note #1145 merged with these items in its description only; its author's reply said the receipts note would take them in a follow-up. They are: the HBAI size of the FRS add-back at 2025 with the engine follow-up (PolicyEngine/policyengine-uk#2390), furnished holiday lettings from 2025-26, and the effect of capital netting on the finance-cost draw. The figures are the description's. No code changes; the control arm on the current base is still owed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The branch is rebased again, on main after #1145, so both receipts are run again. Before is 51a2637 with only the receipt tool, on the 1ee7dfe feed. After is cd39d03 on the 942a1fa feed, on a clean tree. Bound national rows go from 1,177 to 1,213: 36 added, none removed, 552 moved. main's 13 SPI property-income amounts are on both sides at the same values, so both counts are 13 higher than before the rebase; the rows this branch adds and moves are the same. Local cells go from 20,885 to 21,804: 919 added, none removed, 18,811 moved. The before receipt's overlay is now in the tree as before-overlay.diff, and each implied cell carries its household share and share ratio. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes #1106. Rebased onto main
d1fda720bon 2026-10-12 (five commits since the previous base: #1202's NZ weight-quality gates, #1178 and #1186's PUF Social Security fixes, and two pool-checkpoint commits; none touches the UK build). The only overlap was the UK concept-coverage golden, regenerated on the merged mapping so main'ssource_household_identry sits beside the landlord inputs. The earlier rebase the same day, ontod648c976cafter #1167 merged, had picked up main's Chronicle feed re-pin to1ee7dfe: the PRIS resource is re-vendored from it and the references, membership and compile-parity receipts are regenerated against it in one commit near the head; the intermediate commits keep the surfaces they were measured with. Active national references: 1,245 (main's 1,232 plus the 13 SPI property amounts). The spec mirror, the coverage manifest, the H2 fixture and the gate-battery digests are re-derived on the merged spec. Marked ready for review on 2026-10-10; Max's ruling on the add-back is still open.What changes
A. The SPI net property-income amounts bind unscaled (
e34412b15). SPI Table 3.7 "net income from property" is landlords' profit after allowable expenses and before residential finance costs, the conceptproperty_incomeholds (PolicyEngine/policyengine-uk#2172 documents it). The incumbent's x1.9 scaling set the Property Rental Income Statistics (PRIS) receipts, which are before expenses, against it. The signed exclusion is retired and the 13 rows bind at £33.60bn for 2025 (the incumbent fixture holds £55.78bn), uprated by the band index the count rows' growth belongs with,hmrc.itl_2026.total_income_growth_by_total_income_band(María's ruling of 2026-10-11 on the review's item 4; the flat per-capita GDP index measured in the arms before the final one gave £31.78bn and implied per-band mean profit moves of −13% to +11% against the count rows).B.
property_wealthstays with the engine (0f86d6a54). The WAS stage saved the survey's own total, which overrides policyengine-uk's sum of the three property components and is never uprated (the uk-data#543 defect). The release export drops it, and so does the measure resolver's scratch H5, inside #1115's_engine_scratch_frame. It is a reviewed export exclusion and an engine-derived coverage exclusion; the three components stay required. On the A9 candidate at 2025, measured with policyengine-uk 2.100.0, this movesproperty_wealthfrom £7,469.5bn to £7,633.4bn and household land value from £4,595.0bn to £4,697.3bn (target £4,559.8bn). The scratch H5 applies the whole release drop list; of the other dropped columns onlyincapacity_benefit_reportedis an engine variable, and it is all zero on the spine.C0. policyengine-uk 2.123.0 (
e80030848), the release with PolicyEngine/policyengine-uk#2172's landlord inputs. Only policyengine-uk moves in the lock; policyengine-core stays 3.32.19 on the UK side and 3.32.5 on the US side.C1. Finance costs on the SPI channel (
dbbf83fe9). The tape'sLLIR_RESTRICT_AMT_TOT(restricted land property finance cost) becomesproperty_finance_costs, drawn last in the first-stage SPI model so every earlier draw is unchanged, and uprated byobr.mortgage_interest. The donor is refused unlessTAX_CREDcarries the 20% relief (median ratio between 0.195 and 0.205).C3. A
property_componentsstage (abb061af0, walk rule2efba5388) afterregional_property_uprating.property_income(see the question for Max below).property_rental_income(receipts) from PRIS 2024-25 Table 13. Landlords are ranked by profit and walked into its eleven bands with the CGT Table 3 walk's rule, each band filled to its PRIS share of the spine's own landlords. Receipts are linear within a band and never below profit. The open top band takesmax(100,000, k × profit)withk ≥ 1solved toward the individuals' receipts total, or held at 1 with the overshoot recorded where the floor at profit already exceeds that share; on this head's spinekis 1.166.uk_stage_property_components: no receipts below profit, no landlord banded below their profit, every bounded band within its recorded rounding allowance (the weight of the person straddling each boundary), and the top band's multiplier below its cap. The vendored PRIS resource has 44 rows (Tables 1, 2, 6, 8 and 13).PRIS stays a diagnostic.
bb900bba1bound three PRIS targets (individuals' receipts, the eleven landlord counts by size of receipts, residential finance costs);fc7a9484dreverts it, so active national references stay at 1,244. In every PRIS arm the SPI property amounts missed the 10% fit band, two arms breached the 25% release bound, anduk_weight_ratiofailed (below). The stage receipt reports the spine against PRIS instead.Measurement
Licensed arms with a national calibration and local staging only. The scripts and run outputs live outside the repo, in
data/ukds/acceptance/1106-property-income/(licensed inputs); the receipts are inexperiments/1106-uk-property-income-receipts.md. The arms up toc7ran on the pre-rebase commits, kept as local tags, from the 825406f feed artifact (rebuilt on 2026-10-08 with both digests matching the pin); armsc8andc9run on this head from the1ee7dfeartifact main now pins.uk_target_fitfails only because UK: port the remaining uk-data fixes tracked in #1095, engine items included #1121's West Midlands deferral is stale: that cell sits at +23.1%, inside the 25% bound. With the deferral dropped (c3x), 7 of 7 gates pass.c3x):property_finance_coststotal £11.71bn (PRIS: £12.13bn).income_taxtakes 0.33 s against 0.23 s to compute.uk_weight_ratiofails in all four.c6x, measured as7b20ef73c, now700e2e901): loss 0.00710, 98.1% of 1,180 within 10%; all gates pass once the stale West Midlands deferral is dropped. Receipts £45.72bn against PRIS's £49.81bn.c7,f478063d5, no measurement-only edit): loss 0.00700, 98.6% of 1,180 within 10%, the SPI property amounts within 0.4% of their £33.60bn and the counts within 0.3%. All 7 calibrated-seam gates and all 33 spine gates pass; the West Midlands cell sits at +20.6%.property_rental_income£52.11bn (PRIS uprated £51.99bn);property_finance_costs£10.25bn (PRIS £12.13bn); relief £1.76bn for 0.99m landlords, £1.46bn carried forward; the property allowance deduction £0.144bn for 0.32m (the control: £0.117bn for 0.22m); Housing Benefit −£38m, Council Tax Reduction −£69m, Pension Credit −£7m, Universal Credit +£82m against the control, which is calibrated separately.c8andc9,a33079775): the spine rebuilt afresh in 359 s; 37 of 37 spine gates pass (the four new ones are Set UK road-fuel and energy levels in the LCFS stage on a calendar-2024 basis (#1113) #1167's LCFS gates). The national step then stopped on main's native-evidence reader (microcosm#1187: run manifests are charged the schema's per-item budget), which fails every national build after a spine built on current main; this PR does not touch it, and Bound native evidence by bytes, not by the schema item budget #1188 (open) is the fix. Armc9calibrated thec8spine with Bound native evidence by bytes, not by the schema item budget #1188's commit overlaid for measurement only: loss 0.00644, 98.5% of 1,177 targets within 10% (the set moved with Set UK road-fuel and energy levels in the LCFS stage on a calendar-2024 basis (#1113) #1167's measure exclusions), ESS 5,203, 7 of 7 calibrated-seam gates.c7's: the rebase moved nothing in the stage's output.property_finance_costs£10.24bn; relief £1.77bn for 1.03m landlords, £1.38bn carried forward; the allowance deduction £0.143bn for 0.33m; income tax £326.01bn against the control's £327.05bn; Housing Benefit +£4m, Council Tax Reduction −£41m, Universal Credit +£44m.Signed and retired (2026-10-11)
target_fit_reviewed_exclusions.jsonis empty again.property_wealthentry ininput_mass_reviewed_exclusions.json(expires 2027-04-11) and thelandlord-inputs-person-net-new-columnsentry inspine_swap_signed_differences.json; the engine-derived coverage exclusion carries her signature in the ledger.Decisions held
property_income, so the engine relieves the interest once, as the s. 274A reduction; the capital part stays netted. This departs from the uk-data#509 rule of using FRS amounts as reported. The alternative is to leave FRS profit as reported and give FRS landlords no finance costs. We will change it before merge if Max disagrees.hbai_household_net_income,market_incomeandhousehold_market_incomeaddproperty_income, profit before finance costs, while HBAI's published basis is FRS property income after show card K6. On thec9candidate at its calibrated weights, reversing the add-back (FRS landlords' profit as reported, no finance costs) lowers HBAI household net income by £2.95bn (0.20% of £1,508.5bn) and household market income by £3.29bn, lowers income tax by £0.34bn, cuts the finance-cost relief from £1.77bn to £1.11bn, and raises the before-housing-costs poverty rate by 0.02 points and the after-housing-costs rate by 0.04 points; the Gini of equivalised HBAI income moves by 0.0002. The SPI channel sits on the same before-interest basis. A policyengine-uk follow-up for HBAI and market income to readproperty_income_after_finance_costs, as the means tests do, makes the add-back neutral for HBAI and fixes the SPI side too (issue draft held for María).#1167's re-review items, taken here
At María's direction the minor items of #1167's re-review (its comment of 2026-10-11) are addressed in this PR, since #1167 has merged.
donor_minanddonor_maxbesidedonor_floor, keeps only the spine file's name, and the committed outputs and scripts carry no machine-local paths (the scripts read the inputs root fromPE).ValueError; only a missing year is recorded as a report-only diagnostic being unavailable.after / base; theuncarried_spendanddonor_upratingdocstrings and theobr.fuel_duties_carsand 04.5 binding notes are current; the evidence README says the per-division movement between runs is redraw noise around the uniform bridge.Known residuals
LLIR_RESTRICT_AMT_TOTexcludes their costs. The FHL regime ended on 6 April 2025, so at 2025 their interest falls under s. 272A: theirproperty_incomeandproperty_finance_costsare both understated by it. Small, with the direction known.property_finance_costs_brought_forwardis unset.Testing
--check, and the vendoring and pin checks: run on C3.tools/ci_test_plan.py verify, and ruff on every changed file.a469ebe3d: the engine-UK lane with H2 parity (159 pass), 17 engine-free files across the touched surfaces (817 pass, 1 skipped), the coverage manifest--check, a fresh H2 fixture regeneration with no diff, the lock pin,tools/ci_test_plan.py verifyand ruff.--check, the uprating-pins check,tools/ci_test_plan.py verifyand ruff; the H2 fixture regenerated after the spec edit; armc7on the committed tree.d648c976c: the engine-UK lane with H2 parity (159 pass), 29 engine-free files across the touched surfaces (1,055 pass), the references test (30 pass), the LCFS, stage-health and donor-uprating tests (124 pass), the coverage manifest--check, the PRIS vendoring--check, the lock pin, the uprating-pins check,tools/ci_test_plan.py verifyand ruff; armsc8andc9on the committed tree.d1fda720b: the engine-UK lane with H2 parity (159 pass), 20 engine-free paths across the touched surfaces plus the frame and spec-loader tests (2,192 pass, 54 skipped), the references test against the pinned feed (30 pass), the concept-coverage--check, the coverage manifest--check, the PRIS vendoring--check, the lock pin, the uprating-pins check,tools/ci_test_plan.py verifyand ruff. The rebase is content-exact: the new head differs from the old one by main's own diff and the merged concept-coverage files alone.🤖 Generated with Claude Code