Repository navigation
UK #1095 A/B fixes: the SPI age band (uk-data#628), households' HRAD from Table 8a receipts, and the SPI pay floor (uk-data#629) - #1426
juaristi22 wants to merge 55 commits into
Conversation
…628) The SPI tape gives age only as a band (AGERANGE), so the age drawn for each donor inside its band carries no income pattern: the stage-1 forest learned none, and twins in one band faced conditionals that differed only by that noise. The tape and reference forests now read the band code instead: the tape's own code, composite records (-1) as a band of their own, and the band of a frame person's whole-year age. at_or_over_spa keeps the State Pension split inside a band. The cross-fit fold cells key on the band, since they key on the predictor values. The drawn ages stay on the donor for the band pools. Regenerated: source_stages.json (in step with sources.yaml), the release input coverage manifest and the H2 parity fixture. The SPI engine tests read the band and count the reference forest once per cross-fit fold in use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e 8a receipts The 2024-25 residential nil-rate band was GBP 250,000 all year, so a purchase at or below it owes no standard-rate SDLT and other buyers' Table 8a receipts there are surcharge. At Table 6b's gross HRAD receipts per transaction and per pound they imply about 48,100 HRAD transactions and GBP 7.05bn for other buyers, where individuals' share of all transactions, uk-data#573's rule, left them about 13,400. Households now take Table 6b's band less that; above it the shares stay (solving from receipts there goes negative above GBP 1m). The household bands become the price mix. Households' target: 151,928 transactions and GBP 63.71bn (was 186,589 and GBP 68.81bn), above uk-data#573's lower bounds; the lowest band carries 43% of households' purchases (was 54%). Published HMRC figures only. Regenerated: source_stages.json (in step with sources.yaml) and the release input coverage manifest. The H2 parity fixture is regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stage 1 of hmrc_spi_income_spine draws a recipient's annual tape pay on age, sex, region and the bridge predictors, not on hours, and the tape holds part-year workers. A copy whose FRS twin reports usual hours in an employee job could therefore draw a year's pay below what those hours earn at the minimum wage, and fail the 30 hours' and Tax-Free Childcare minimum-income tests more often than FRS rows. A new floor_spi_employment_pay_at_minimum_wage operation lifts the drawn hmrc_spi_pay leaf to weekly usual hours (hours_worked read back at 365.25/7) times 52 weeks times policyengine-uk's gov.hmrc.minimum_wage.non_apprentice rate for the twin's age, read at 30 April of the build year as the engine reads it (2024-25: 11.44 from 21, 8.60 from 18, 6.40 below). It runs after the stage-1 draw, the band-donor resample and the State Pension leaf guard, and before employment_income is derived from the leaves and stage 2 conditions on it; the transport receipt keeps the unfloored draws' uniforms. Only drawn recipients other than band carriers, whose twin has an employee main job, no trade and usual hours, and whose drawn pay is positive, are floored. Tape PAY is taxable pay; PAY alone is compared with the gross minimum because it is the leaf of the engine's gross employment_income input. The operation is declared in sources.yaml, its source_stages.json projection, the sources schema, the operation vocabulary and the HMRC income contract, and checked against a reviewed declaration. The stage evidence gains an employment_pay_floor receipt: source households rounded, weighted figures "<10" on one to nine source households, no row's value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The employment pay floor receipt gave the floored weight, the share floored and the amount added beside the eligible weight, each suppressed only on the floored side's support. Any of them with the eligible weight gives back the weight of the eligible rows the floor left alone, which could rest on one to nine source households. They are now "<10" whenever either side rests on one to nine, as the Carer's Allowance take-up receipt gives its share; the mean uplift alone does not give a side back and keeps the floored side's rule. The receipt declaration in sources.yaml, source_stages.json and the reviewed spine mapping says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, gate hourly pay uk-data#629: drawn SPI employees keep their FRS twin's hours while the tape redraws their pay, and the tape's pay is the tax year's, so part-year tape pay landed on current twins at very low hourly rates. On top of the floor from uk-cc-spipay: - The floor rate is the lower of the minimum wage for the twin's age and the twin's own hourly pay (its employment income before the draw over its annual hours), so no copy is held to a higher rate than the FRS person it copies; hours now read back at 52 a year (uk-data#531). - The floored pay is the row's current pay only: its annual share becomes the unfloored over the floored employment income, so the annual measures (the HMRC income rows) still read the tape's pay. - The receipt adds an hourly-pay check: the drawn employees' weighted share below the minimum wage before and after the floor, beside the FRS employees' share, and the eligible rows still below their floor; a share is withheld beside a side on one to nine source households. - A release-blocking stage gate, uk_stage_hmrc_spi_income_spine_hourly_pay, holds the floor invariant and the drawn share to the FRS share plus two points; it joins the spine gate scope, and the battery and contract digests move with it. Regenerated: source_stages.json (in step with sources.yaml), the sources schema, the release input coverage manifest and the H2 parity fixture. The floor's engine test gives each twin and its copy one age and pay, reads hours at 52 weeks, and checks the twin cap, the kept year of pay and the employer contribution that follows the floored pay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
María's ruling of 2026-10-29 (#1095). The SPI employment pay floor gives fewer low-paid SPI copies Universal Credit, so single people's caseload before the take-up redraw falls close to DWP's, and the redraw's usual downward move reads as about twice the 0.055 allowance; after the redraw single people end a little below the spine without the floor, all in the middle award range. The shortfall of middle and higher awards that the redraw cannot fill is the award fix's (#1095's UC awards below the top band), and the tolerance returns to 0.055 when it lands. The one tolerance also bounds the reachable pools, which realize DWP's count far more closely. The contract re-pins the gate battery's policy, manifest and fingerprint digests and the release certification's spine part; the stage-health test pins 0.12 and adds the floor's pattern. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@juaristi22 thanks, this is my review of Verdict: the three items look sound, with four should items and a nit. The age band is the right fix for #628. The nil-band arithmetic reproduces the body's numbers: £265m over £625m/113,500 is about 48,100 transactions, and the target falls by about 34,700. The pay floor runs where it should, after stage 1 and the uprating and before Findings
What I ran. I read the full diff at 🤖 Generated with Claude Code |
The SPI income stage now moves the 2022-23 tape's interest and other income to HMRC's published 2023-24 Table 3.7 amounts band by band, with the tape banded by its own total income, before any draw. The OBR's savings income growth then carries both columns to the frame's year, the path the Table 3.7 band targets take. Before, each column moved by its engine index from 2022 to 2024: other income by GDP per head (x1.09), interest by ONS household interest (x2.27). SPI's own amounts grew x1.65 and x2.69 in the single year to 2023-24. The frame carried about 70 percent of the savings rows at design weights, and the solve met them by stretching the few households that carry them. On the tape, the £1m-and-over band's other income outweighs its interest, as in the published table; the two indices had flipped that split. - uk/hmrc_spi_table_3_7_savings_rows.json vendors the 26 published rows from the feed pin, declared in the vendoring register and the country package. - spi_income.rebase_spi_donor_savings scales the prepared tape. A band the tape carries none of is left as it is and named in the receipt. The forest and the band-donor resample both read the rebased tape. - _spi_income_uprating_factors grows the two columns from 2023-24 on hmrc_uprating.uk_obr_savings_income_level. Both columns now run on the projection's GDP per head from the frame's year on (SPI_HISTORY_INDEX_DIFFERS_FROM_PROJECTION). - Tests cover the rebase, the published rows, the OBR factor and the stage wiring, and the engine lockstep tests are updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first spine built from 667fe53 stopped at uk_stage_uc_take_up. Single people's caseload ended 0.0568 of DWP's count further from it than before the redraw, against 0.0523 on PR C's spine and a tolerance of 0.055. The rebased SPI savings feed the UC reporter redraw's investment-income predictor. The few SPI benefit units whose report changes move the take-up stage's pool merging, which joins adjacent award bands until each pool rests on enough sources. Single people's £1,000 to £1,100 award band moved from a reachable pool into one whose DWP target exceeds the entitled units. The reachable pools still realize DWP's counts within 0.005. The tolerance goes to 0.06 (María's ruling of 2026-10-30), as PR A took it from 0.05 to 0.055 for the gilt reshuffle. Pool boundaries that move with small upstream changes are a follow-up on #1334. The gate's note, the committed-parameter test and the data contract's mirrored digests are updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The national built from f3cb3dd fitted every SPI Table 3.7 interest and other-income band but one, the £1m-and-over other-income row included. It missed the £1m-and-over interest amount row outside the uk_target_fit band. By María's ruling of 2026-10-30, the row is held out of the objective as a measured distortion, as the ten HMRC £1m-and-over cells are, and measured on every evaluation. The register entry carries no figures for the band. How the rebase sits in the top bands at design weights is a follow-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SPI Table 3.7 counts taxpayers only, but the rebase divided its amounts by the tape's amount over all records in each band. In the two lowest bands non-taxpayers hold much of the tape's savings income, so the rebased taxpayers reached only part of the published amounts there. The prepared tape now carries the tape's TAXPAYER flag. Each band's factor is computed over its taxpayers and applied to them; non-taxpayers take the all-bands factor, as donors below the first band already do. A tape without the flag counts every record, and the receipt says which basis it used. Option F of the #1428 other-income investigation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two FRS-row mappings disagreed with SPI Table 3.7, so the frame's other income below £100k carried the wrong concept. - NS&I Direct Saver and the other NS&I and bank products move from other_investment_income to savings_interest_income. SPI has counted all interest paid gross or net as bank interest since 2016-17 (note 4). Gilts stay in other income, as interest on securities (note 6). - Income from an annuity not bought with pension funds or from a trust or covenant (FRS PENSION.PENTYPE 4 and 5, the ADULT ANYPEN4 and ANYPEN5 sources) moves from private_pension_income to other_investment_income, the other income Table 3.7 counts (note 6). The FRS records the whole annuity payment, of which only the income element is taxable. Both columns are taxable savings income in policyengine-uk, so the savings tax on these rows does not move. The FRS spine test covers both routes. Options A1 and A2 of the #1428 other-income investigation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Other income was the one investment leaf of the SPI stage-1 transport with no FRS analogue. Its quantile came from an identity uniform keyed on the twin, which ignored the twin's holdings. Copies drew large amounts on modest incomes, and the draw was out of line with the rest of each copy's income. The analogue map now names the twin's own other_investment_income: gilts, trust income and purchased-annuity income, the FRS sources of SPI's other income after the previous commit. A copy's draw now ranks with its twin's holdings, as interest, dividends, pensions and property do. The stage spec's analogue_columns (sources.yaml and source_stages.json) and the release input coverage manifest that pins the spec follow, and the spine test's synthetic frame carries the column, as the FRS spine always writes it. Option C of the #1428 other-income investigation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings #1428 up to date with main, including PR B (#1421, the ISA subscriptions and policyengine-uk 2.164.2). Conflicts resolved: - frs_spine: main's ISA interest column, with this branch's NS&I-to- deposit routing. - The FRS spine test: ISA interest apart, plus NS&I as deposit interest (5.0 a week of non-ISA interest). - The SPI engine test: the savings columns on the OBR path. - The vendoring register: both new resources. - The coverage manifest: regenerated. - The data contract: gate digests recomputed for the merged gates.json (main's, plus the UC take-up tolerance of 0.06). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The band factors divide published amounts by the tape's FACT-weighted
amounts, so they assume the tape grosses to the taxpayer population.
The H2 spine fixture's synthetic tape doesn't: its factors ran to
millions, lifted its band donors' income out of their bands, and the
fixture's regeneration refused ("no record whose uprated total income
lies in the band from 200000"). CI's acceptance and staging smoke tests
would meet the same refusal.
Now a column whose banded tape amount, over the bands it carries, is
under a tenth of the published amount is left as it is, and the receipt
says so. The production tape holds about 0.37 of the published interest
and 0.6 of the other income. A test covers the skip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's engine-uk graph test and integration-uk smoke test refused the SPI income stage: the fixture's stage-spec snapshot still named no analogue for other_investment_income, against the reviewed map (C in b822474). tools/graph_uk_spine_fixture.py regenerates it on the merged tree: - fixture.json carries the new analogue entry; - the fixture's derived CGT and property fact sources follow the synthetic frame they're built from; - uk_spine.json's pin follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's engine test test_spi_stage2_does_not_require_frs_other_investment_income runs the SPI income stage on a frame whose FRS rows carry no other_investment_income. The stage fills that output with the stage-time zero, but only after the stage-1 draw. Since b822474 the transport reads the column as the twin's own analogue before the draw, so the stage refused the frame. Now a stage-1 output that is its own analogue and that the frame doesn't carry gets the same zero before the transport. Every twin locates as a non-carrier, and the final values are unchanged. Production FRS frames always write the column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
e5ac7a5 built the fixture in a local environment still on policyengine-uk 2.162.14. The lock has pinned 2.164.2 since the merge of main (9ee6fbd), so CI's engine-uk graph test found the stamped engine version differ from the graph it builds. Rebuilt with the environment synced to the lock: the graph's version stamp moves, and so do the fixture's synthetic property rental facts, which the engine uprates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
María's choice of 2026-10-30 (microcosm#1418, the trading allowance measures). With Table 3.10's self-employment counts alone, the solve can meet each GBP 1 to GBP 10,000 band with traders who have no pay. In the table, more than half of the traders below GBP 3,000 also have employment income, and an employee's side profit is taxed at their marginal rate. - hmrc.spi.employment_income.count_by_self_employment_income_band counts each person with pay over the year (employment_income_annual, which Table 3.6's employment rows read) in their own self-employment band, taxpayer or not, over the same eight bands as the self-employment counts. Table 3.10's column also holds miscellaneous income, benefits in kind and the contributory benefits, which the binding leaves out, as Table 3.6's does. The counts grow to 2025 with HMRC's projected growth of all taxpayers, as the self-employment counts do. - The zero band and the bands from GBP 10,000 are signed out row by row. The target is declared signature-incomplete beside the other Table 3.10 rows and is unmapped in the registry parity (no uk-data metric). - The national references and membership and the national compile-parity receipt are regenerated on the pinned feed (53815b2): eight new rows. The membership's 13 SPI dividend rows and 8 Table 3.10 self-employment amount rows also take the October EFO engine pins that main's policyengine-uk 2.164.2 lock brought (b89d3aa). The merge left those rows on the 2.162.14 pins. The parity receipt doesn't move for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vahid's review of #1422 (its second and third points). - Since the move to a floor of three, rounded_count printed three to nine exactly, while extreme_count and support_band print "3-9" and the module's own rule is that no exact count narrows a small group by difference. It now reads "3-9" there too. Two tests that read an incidental exact count follow. - The module docstring and the changelog say that the SPI Public Use Tape's two conditions (SN 9422) are unchanged by the move to three. - A guard test fails when a UK runtime module, the UK spec, the gate notes or a tool still writes the old "<10" label, so that a receipt left on the floor of ten cannot land unnoticed. #1426's SPI pay-floor receipt writes it today; whichever of the two lands second moves it onto UK_DISCLOSURE_FLOOR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
María's choice of 2026-10-31 (microcosm#1428). The three Income Tax liabilities statistics Table 2.6 rows bound HMRC's own 2024-25 and 2025-26 projections as a calendar-2025 window: GBP 8.40bn of tax on savings, x1.52 on the 2023-24 outturn, against x1.36 for savings income on the OBR's path. With SPI Table 3.7's savings income already on the OBR path, those rows pinned the tax on savings, and so the savings rates' static yield, about 12% above the base the Budget's costing grows. - The rows now read the 2023-24 outturn alone. They are observed facts, so hmrc_itl_savings_rate leaves the source-projection families. - They grow by the OBR's savings income growth (October 2026 EFO Table 4.5), x1.3562 to calendar 2025, the index Table 3.7's savings rows take: GBP 1.61bn, 2.24bn and 3.66bn, GBP 7.51bn in all. - Growing the tax on the income's path leaves out the leverage that HMRC's projection carries from the frozen Personal Savings Allowance and thresholds. - The national references, membership and compile-parity receipt are regenerated on the pinned feed, and only the three rows move. The engine test's comments now name calendar 2025's two tax years. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
María's ruling of 2026-10-31 (microcosm#1428): fix the take-up pools instead of widening the gate again. The spine from d563f4f stopped at uk_stage_uc_take_up. Single people's caseload ended 0.0674 of DWP's count further from it than before the redraw: 0.0568 on f3cb3dd's spine, against this branch's 0.06. The reachable pools met DWP's counts. The gap was the pools whose DWP count exceeds every entitled unit, which kept a clipped rate, and which bands land there moves with small upstream changes. - After the source-support pooling, the stage joins a pool that cannot meet DWP's count to an adjacent pool (_join_unmet_pools). Such a pool's undrawn units' expected claims exceed the count, or the count exceeds them plus every drawn unit. Lowest first, it joins the neighbour with more room for its gap, the lower on a tie. Joining stops when every pool can meet its count or the family type is one pool. A family type whose count exceeds all its entitled units stays one unreachable pool. - The stage spec restates the pooling rule (sources.yaml, source_stages.json, UC_TAKE_UP_POOLING), and the coverage manifest and the H2 fixture follow. - uk_stage_uc_take_up returns to main's 0.055, undoing this branch's 0.06. The gate note, the committed-parameter test and the data contract's mirrored digests follow. - New tests: an unmet pool joins the neighbour with more room, or the lower one on a tie; an overshooting pool joins one with drawn claims; a family type that cannot meet its count is one unreachable pool, flagged by the side it misses on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1426 now builds on #1422, which moves every UK disclosure floor to three (receipt_counts.UK_DISCLOSURE_FLOOR) and regenerates the national surfaces on Chronicle 53815b2 with policyengine-uk 2.165.1. - The SPI pay-floor receipt withholds through UK_DISCLOSURE_FLOOR and UK_WITHHELD: a figure on one or two source households reads "<3" where it read "<10" (Vahid's review of #1426). Its description in spi_spine.py, sources.yaml and source_stages.json, the hourly-pay gate's note and docstring, and its tests follow. Two test cases shrink their small side to two households, so they still test the withholding. - Conflicts: the SPI income imports (both sides kept), the stage-1 fold key (#1426's age band) beside #1422's savings-interest method text, and the UC take-up gate test. - Regenerated: the release input coverage manifest. The gate battery and spine-part digests are re-pinned. - The H2 parity fixture takes #1422's side here; a later commit regenerates it on the merged tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hours / 52 * weeks_in_year is a year's paid hours, not weeks (Vahid's review of #1426). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
uk_stage_uc_take_up's one tolerance bounded two checks: how far a family type's caseload may move from DWP's across the redraw, which the SPI pay floor needs at 0.12, and how closely the reachable pools realize DWP's count, which the draw meets within 0.0015. The pools now keep 0.055 as maximum_reachable_pool_deviation while maximum_caseload_deviation stays at 0.12 until the UC award fix of microcosm#1095 (Vahid's review of #1426). The gate battery and spine-part digests are re-pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
María's choice of 2026-10-31 (microcosm#1428). policyengine-uk applies
the trading allowance against self_employment_gross_receipts, which
neither the FRS nor the SPI tape records. Without receipts it relieved
profits up to the allowance in full and nothing above. So the Budget's
raise to GBP 3,000 missed its main base, the traders who already claim
the GBP 1,000 allowance against larger receipts.
- A new last spine stage, trading_allowance_receipts, flags each trader
with a positive profit as a claimant, drawn on their own person_id:
0.64 for profits up to GBP 4,500, 0.32 from GBP 4,500 to GBP 9,000,
none above.
- It stores receipts of profit plus GBP 1,000 for a claimant, exact
under current law, and profit plus GBP 3,000 for any other trader
(expenses at the raised allowance; the costing books switchers as
behaviour). A person with no positive profit keeps zero.
- The rates are calibrated to HMRC's policy paper (about 400,000
taxpayers with a lower bill, about GBP 380 on average) until HMRC
publishes claimant counts.
- Every trader with a positive profit is treated as filing, as the
bound Table 3.10 counts are of filers. So a profit of GBP 1,000 or
less is now taxed.
- Wiring:
- the operation kind in the stage schema and the allowed kinds;
- the stage in sources.yaml and source_stages.json, with its mass
receipt;
- the graph reads and cell, the kernel modules, the spine build and
fixture implementations, and the coverage manifest family.
- self_employment_gross_receipts leaves UK_ENGINE_DEFAULT_INPUTS and
joins the export surface's allowed columns (terminal_gates and
gates.json), so the data contract's gate digests follow.
- Tests: the stage's own, and the rosters (51 stages). The H2 fixture
follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
spi_age_band was an ordinal predictor, so any split that did not separate the composite records' code (-1) from 16-24 put the tape's high-income amalgams in the leaves 16-24 queries land in. The band is now one of the stage-1 categorical predictors, one-hot encoded with gender, region and main source, so composites are a category of their own (Vahid's review of #1426). The predictor rule, the spec's categorical list, the changelog and the coverage manifest follow; the cross-fit fold cells still key on the band's value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Table 8a's receipts do not split out the 2% non-resident surcharge, which a non-resident buyer pays on top at or below GBP 250,000. Read as HRAD surcharge, it raises other buyers' implied HRAD and lowers households', so households' nil-band purchases are, if anything, understated (Vahid's review of #1426). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This reverts commit 5c0137f (María's ruling of 2026-10-31, microcosm#1428). The national built from 6485af4 stopped at uk_target_fit. Under the engine's rules, tax on savings grows faster than savings income: the Personal Savings Allowance and the thresholds are frozen. So the tax rows grown on the income's path asked for less tax than SPI Table 3.7's savings income gives. To meet them, the solve cut the interest rows in the basic-rate bands: the GBP 30,000 band ended 30.7% under target and the GBP 20,000 band 12.0% under. HMRC's own 2024-25 and 2025-26 projections carry that leverage, and the national from b441bb5 met them beside the interest rows. So the savings rates' yield stays pinned to the projection. Its distance from the costing is a difference of basis (the projection's leverage) and of behaviour (ISA shifting), not a fault in the base. The three rows, their references, membership and parity receipt, and the two tests are back as at d563f4f. The changelog fragment goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spine from f9676fe stopped in trading_allowance_receipts, and so did CI's graph driver test and the staging smoke test. The stage counts its receipt's support by household_source_id, but its graph declaration read only self_employment_income, so the executor didn't hand it that cell. The stage now declares the read, as regional_home_values and property_purchases do, and the H2 fixture's graph follows. The fixture tool's legacy oracle runs every stage on the whole frame, so it didn't catch this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The receipts stage now runs after the ISA subscriptions, so the two tests that pinned the ISA subscriptions as the last spine stage name both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stage fits no model, but it declared an empty fit_weight_records tuple. The stage evidence then carried an empty list for it, and the release-cut weights audit reads an empty list as a fitting stage that recorded nothing, so every national certified after #1421 failed uk_weights_audit. A regression test reads the stage's evidence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gift Aid and charitable investment gifts are sparse leaves that the stage-1 chain draws after, and conditioned on, the income leaves. Rebasing the donor savings on the tape's taxpayers changed which few donors' gifts the copies drew, and the release-cut input-mass check found charitable_investment_gifts beyond its band. Each column's total over the drawn copies, at the stage's household weights, is now scaled to the prepared tape's FACT-weighted total (María's ruling of 2026-10-31). The stage evidence records each column's factor only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
María's ruling of 2026-10-31 (microcosm#1428). The release-cut input-mass check on the national from 0c2f531 found jsa_income_reported beyond its band. The national met OBR's total JSA by scaling income-based reporters together with contributory claimants, but income-based JSA closes with the move to Universal Credit: DWP's Spring Forecast 2026 puts it at GBP 91.0m in FY2024-25 and under GBP 1m from FY2025-26. - dwp.jsa.amount_income_based binds DWP's income-based expenditure line on jsa_income over Great Britain, at the calendar-2025 window of the two fiscal years, as the pension-age Housing Benefit row is. The solve now meets the rest of OBR's total on contributory claimants. The target is unmapped in the registry parity (no uk-data metric). - The national references and membership and the national compile-parity receipt are regenerated on the pinned feed (53815b2): one new row, resolved at GBP 23.4m. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No conflicts. The stricter floor guard passes on #1426's SPI and HRAD code, as do the ledger, reference, stage and graph tests. The gate digests and the coverage manifest are unchanged, and the H2 fixture regenerates byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The #1428 review's modelling findings 1, 6, 7 and 10. - Annuity and trust payments (FRS PENTYPE 4 and 5) stay in private_pension_income, where policyengine-uk's means tests, household income and tax read them. A calibration-only descriptor, annuity_and_trust_income, stores their part, and SPI Table 3.7's other-income rows read other_investment_income_spi_concept: the engine's other income plus that share of its private pension. Rows the SPI stage draws carry a zero descriptor. The FRS spine refuses a pension tab without PENTYPE. - The SPI savings rebase bands the tape on its income moved to 2023-24, repeating until no record crosses a band edge, so consumers that re-band on uprated income count the migration once. A taxpayer is a record the tape flags, or one whose income crosses the first band by 2023-24, and flagged taxpayers below the first band sit in it. A band under a tenth of its published amount takes the all-bands factor, a column the rebase skips grows on its engine index from 2022-23, and TAXPAYER codes other than 1 and 2 are refused. - The UC take-up stage holds a pool that can't meet DWP's count at its clip and passes its gap to neighbouring pools, offsetting opposite gaps first, so every band keeps its own rate. The export surface allows the descriptor, and the release-cut contract digests follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The #1428 review's findings 3, 4, 8, 11, 12 and 13. - A roster test pins the eight stage transforms that declare fit_weight_records, read from the source, so a stage that declares the hook and records nothing can't reach the weights audit unreviewed (3). - The receipts test builds its small-cell labels with rounded_count, so it holds whatever floor main sets (4). - The trading allowance receipts stage states its two limits precisely: the raise relieves £1,932 more in 2027-28, falling to £1,758 by 2031-32, and the other traders' stored £3,000 mis-scores allowances above £3,000. Both wait on policyengine-uk#2765's claim status (microcosm#1439) (8). - The support floor's counts scope takes Table 3.10's employment count by self-employment band (11). - Tests pin the income-based JSA and employment-count rows (12). - Texts: the £1m-and-over interest hold tracks microcosm#1418; the income anchors doc, the uprating test and the NS&I comments describe what ships; the employment count's note says SPI rows count benefits in kind; the changelog fragments describe the change, not the branch's history (13). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The synthetic H2 fixture's pension rows now carry PENTYPE, so the spine fixture exercises the annuity and trust routing; the spine and the coverage manifest's source digests follow the two commits before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No conflicts. The broader floor guard passes on #1426's SPI and HRAD code, as do the ledger, gate, reference, stage and graph tests. The gate digests and the coverage manifest are unchanged, and the H2 fixture regenerates byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rebase banded the tape on its income moved to 2023-24 with each record's savings at its own band's factor, repeating until no record changed band. That has no fixed point: the band sets the factor and the factor the band, and on the production tape records kept changing band from pass to pass, so the spine from 59ef8ec took the twentieth pass. The tape is now banded once: each income leaf at its 2022-23 to 2023-24 factor and each rebased column at its factor over all bands, on the tape's 2022-23 bands. Banded so, the rebased tape reproduces the published amounts band by band, and the band-edge migration the consumers re-band on uprated income is counted once. A record whose band's factor differs from all bands' can still sit across an edge on its rebased income. The receipt records each column's banding factor in place of the pass count. A column the tape carries none of takes a factor of 1, as the all-bands factor does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91775db added annuity_and_trust_income to the export surface in gates.json and moved the data contract's release-cut part digests, but not the full-manifest pins the release contract checks a certificate's spec against (policy, gates manifest and spec fingerprint), nor their mirror in the contract tests. The national from 9f70c71 certified, and its release directory then failed the contract on those three pins. They now match the committed spec, as f3540cb moved them for the receipts column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rates calibrated on b441bb5 (0.64 for 2024-25 profits up to £4,500, 0.32 to £9,000) give about 436,000 taxpayers with a lower bill in 2028-29 on the national from 9f70c71, by about £407 on average, against the policy paper's 400,000 and £380: the SPI copies' small traders moved with the savings rebase. Measured on policyengine-uk on that national, the rates that meet both figures are 0.72 and 0.14 (María's choice of 2026-11-01). The draw keeps each trader's uniform, so a lower rate keeps a subset of the claimants and the mean gain holds. The rates drift with each national; microcosm#1439 re-measures them after each build. The H2 fixture and the coverage manifest follow with the merge of main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main brings #1422 (policyengine-uk 2.165.1 and the small-cell floor of three) and #1430 (the company_cars stage and the Chronicle feed re-pin to 08962f1). Following #1430's merge order for this PR: - Both PRs add a stage in the same places, so the stage lists, kernels, transforms and coverage contracts keep both: company_cars after the SPI income stage, trading_allowance_receipts still last. The stage count is 52 in the spec tests, the graph test and the fixture tool. - The UC take-up test keeps this PR's gap passing with #1422's source households and floor: band 0's pool withholds its flag (<3), band 2's, on four sources, says the count exceeds every entitled unit. - The Table 3.10 employment count reads employment_income_with_benefits_in_kind_annual, the term every HMRC employment income row reads since #1430, with its notes, cross-grain reason, test and changelog. - The support floor's adjudication keeps #1422's floor of three and this PR's extension to the employment count. - Counts add both sides: 460 population targets and 398 in the registry scope, 45 held measure names, 185 mapped and 213 unmapped declarations, 1,418 active references and 37 signed exclusions. Regenerated on Chronicle 08962f1 (the artifact María supplied, digests verified): the Table 3.7 savings rows (feed identity only; the values are unchanged), the national references and membership and the national compile-parity receipt from the national cut, then the coverage manifest, the H2 fixture on policyengine-uk 2.165.1, and the gate-battery pins and certification part digests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1422 landed (11c1f4e), followed by #1430's company cars and #1432's default load. Only pinned and generated files conflicted. The gate battery and spine-part digests are re-pinned on the merged gates spec (five move), and the release input coverage manifest and the charter-H2 fixture are regenerated; the fixture checks byte-identical. policyengine-uk stays at 2.165.1, the lock main and #1428 share, and the feed pin moves to main's 08962f1. The H2 parity test and 86 related engine-free files pass (5,682 tests, 53 skipped). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vahid-ahmadi thanks for the review. All five points are addressed. #1422 has landed, and main is merged in at
The H2 fixture is regenerated on the merged tree ( |
María's national control for Phase B (cut f6e33b93) was built from #1428 at 329a99e, so #1426 takes #1428 at that commit: each arm is the control's tree plus one of #1426's items. #1428's later SPI banding change (2dbf7cf) comes with main. spi_income.py keeps both sides' additions: the employment pay floor beside the gift anchor and the annuity and trust clear. The UC take-up gate's note combines #1428's pool joining (María's ruling of 2026-10-31) with #1426's 0.12 from the pay floor (2026-10-29); its parameters stay at 0.12 for the caseload and 0.055 for the reachable pools, and the stage-health test's comment says the same. The gate digests are re-pinned (four move), and the coverage manifest and the charter-H2 fixture are regenerated; the fixture checks byte-identical. The H2 parity test, the SPI engine tests and 94 related engine-free files pass (5,802 tests, 54 skipped). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
María's ruling of 2026-11-02: with #1428's pool joining, every family type ends closer to DWP's count after the redraw than before it on the T1426 spine (#1426 at 97fd4d0), the SPI employment pay floor included, so maximum_caseload_deviation needs no wider allowance. The reachable pools keep their own maximum_reachable_pool_deviation, also 0.055 (Vahid's review). The gate's note says so, the test checks the 0.055 boundary and still tells the two tolerances apart, the changelog fragment is renamed and rewritten, and the gate battery and spine-part digests are re-pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vahid-ahmadi a follow-up on point 3: #1428 is now merged in at the control's commit, and on the T1426 spine every family type ends closer to DWP's count after the take-up redraw than before it. So the caseload check is back at 0.055 ( |
…ge (#1095 B5) The stage's earnings test now reads the pay salary_sacrifice converts into pension contributions. Running after salary_sacrifice puts it after the CGT stages' copies, so each draw is keyed on lineage: a member of a CGT support copy, incidence clone or residential arm draws as the member at the same rank of the household it copies (lineage_roots), and the balanced placement takes a source and its copies in one stratum as one record of their joint weight (lineage_balanced_positions), so they claim together. Original rows keep their draws, and the one-record check reads a record as the rows of one lineage. The spec block moves after salary_sacrifice with its notes and the reviewed rate derivation; source_stages.json keeps its key order (the HMRC contract reads the retained constituents in order). The roster pins, the coverage manifest and the H2 fixture follow. New tests cover the roots, the one position per lineage and the stage itself with every carer cloned at half weight, which claims as the uncloned frame does; keying the draws on person ids fails it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1095. This PR collects the #1095 items that pass their A/B measurement against the 28 October national build, and more land here as they pass. It holds three so far:
Every figure that rests on the SPI channel is given as a class only (SN 9422 and the disclosure rule).
How the items were measured
Every arm passes every spine gate and all seven calibration gates; this branch's passes with the take-up gate at 0.12 (item 3). Each arm that changes the SPI stage, the placebo included, loses one row to the signed zero-support floor.
This branch is rebased onto main at 945cb23. Since fddbc70, main has moved only in NZ code and in shared ledger compile modules; no UK target or compile-parity file changed.
1. The SPI stage-1 forests on the SPI age band (uk-data#628)
Why
The 2022-23 SPI public-use tape gives age only as a band (AGERANGE). The SPI income stage draws an age for each donor inside its band and trains the stage-1 tape forest on it, so the forest learns no real income pattern within a band. Twins of one band were then drawn from conditionals that differed only by that noise. uk-data#628 reports the same defect in uk-data and proposes training and querying the forests on the band, measured against a draw-seed placebo.
What changes
spi_age_bandinstead ofage. On the tape it is the AGERANGE code, with composite records (-1) as a band of their own. On a frame person it is the band of their whole-year age, through one helper shared by both frames.at_or_over_spastill carries the State Pension split inside the 65-74 band.stage1_age_predictornames the rule.sources.yamldeclares the predictor and the fold key.Measurement
dwp.uc.households_children_3, and three HMRC regional cells (income tax at £150,000 to £200,000 in Wales; taxpayers at £50,000 to £70,000 in the East Midlands and London). It takes three UC family rows inside.2. Households' HRAD at or below £250,000 from Table 8a receipts
Why
The property purchases stage (#1308, porting uk-data#573) fits households' HRAD purchases to Table 6b's 2024-25 higher-rates transactions and value. It allocates them to households by individuals' share of all Table 8a residential transactions (0.881) and value (0.882). #1308's review found that this probably overstates households' purchases (#1095, note of 17 October). María's ruling of 17 October left allocating from Table 8a's band receipts for later.
The residential nil-rate band was £250,000 for all of 2024-25. A purchase at or below it owes no standard-rate SDLT, so other buyers' receipts in that band are surcharge. At Table 6b's gross HRAD receipts per transaction in the band (£625m over 113,500 transactions), other buyers' £265m of receipts there is about 48,100 HRAD transactions and £7.05bn. The all-band share left them about 13,400.
What changes
uk_stage_property_purchases_fitneeds no change, since every*_sharekey stays in (0, 1].All figures in this section are HMRC's published Annual Stamp Tax Statistics 2024 to 2025 (corrected, 11 March 2026), Tables 6b and 8a, as the pinned Chronicle feed (272c987a) carries them.
Measurement
dwp.esa_claimantsand two UC family rows), and none move outside.obr.sdlt.3. Drawn SPI employees' current pay at their twin's hours (uk-data#629)
Why
The SPI income stage keeps each drawn copy's FRS twin's hours and job, while stage 1 redraws the copy's pay from the 2022-23 public-use tape. Stage 1 draws on age, sex, region and the bridge predictors, but not on hours. The tape's pay covers the tax year, and the tape holds part-year workers. So part-year tape pay can land on a current full-time twin, at an hourly rate far below the minimum wage.
Such a copy fails the minimum-income tests of the 30 hours' childcare offer, Tax-Free Childcare and the benefit cap more often than the FRS rows it copies. uk-data#629 reports the same defect in uk-data.
On the national spine, drawn employee copies sat below the minimum wage at their twin's hours far more often than FRS employees. Most of those copies had a normally paid twin working 16 hours or more. The tape's low-pay share was above FRS's in every working-age band, which points to part-year pay rather than survey noise. These are private figures, given here as classes only.
What changes
The floor. A new
floor_spi_employment_pay_at_minimum_wageoperation ofhmrc_spi_income_spinelifts a drawn copy'shmrc_spi_payleaf to:hours_workedread back at 52 weeks, uk-data#531),gov.hmrc.minimum_wage.non_apprenticerate for the twin's age (read as the engine reads the build year: £11.44 from 21, £8.60 from 18, £6.40 below) and the twin's own hourly pay.The cap at the twin's own rate means no copy is held to a higher rate than the FRS person it copies. The first two commits carry over María's floor from the childcare fixes work.
Who it touches. Only drawn recipients other than reserved band carriers, whose twin has an employee main job, no trade and usual hours, and whose drawn pay is positive. FRS rows are never modified.
When it runs. After the stage-1 draw, the band-donor resample and the State Pension leaf guard, and before
employment_incomeis derived. So stage 2, pension coherence (the twin's employer rate on the floored pay) and every later stage read the floored current pay. The transport receipt keeps the unfloored draws' uniforms.The tape's year of pay. The floored pay is current pay only. The row's
employment_income_annual_sharebecomes its unfloored over its floored employment income, so the annual measures that the HMRC income rows read still read the tape's pay.Receipt. The stage evidence gains an
employment_pay_floorreceipt. It also gains an hourly-pay check: the drawn employees' weighted shares below the minimum wage for their age before and after the floor, beside the FRS employees' share, and the eligible rows still below their floor. Any figure on one to nine source households, or beside a side on one to nine, is "<10".Gate. A new release-blocking stage gate,
uk_stage_hmrc_spi_income_spine_hourly_pay, refuses a spine in either case:Its details give verdicts only. It joins the spine gate scope. The gate battery's three digests and the spine certification part's two move with it, in
microcosm-data'scontract.pyand its mirror test.The UC take-up gate.
uk_stage_uc_take_upallows each family type 0.12 of DWP's count, up from 0.055, until UK: every open data item: uk-data parity, engine locks, the local surface, consumption and high-value homes #1095's UC award fix lands, and then returns to 0.055 (María's ruling of 29 October).Measurement
The floor was measured two ways:
dwp.esa_claimants, two UC family rows, and the HMRC regional taxpayers at £50,000 to £70,000 in London.Tests
Touched surface, JUnit counts on this branch:
Test files:
test_uk_spi_pension_age.py: the query predictors carry the band; the band and the State Pension flag still split State Pension inside 65-74.test_uk_spi_rank_transport.py: the fold cells on the band.tests/engine/uk/test_uk_spi_income.py:test_uk_property_purchases.py: the band allocation on synthetic rows, the refusal when receipts imply more HRAD than other buyers' transactions, and the packaged targets reproduced from the published table figures.test_uk_spi_income.py(engine-free): the floor on synthetic rows, the twin cap, the hourly-pay check and its small-cell withholding, the comparison with FRS rows, and the year-of-pay helper.test_uk_stage_health.py:Follow-up
Generated surfaces:
source_stages.json(in step withsources.yaml), the sources schema, the release input coverage manifest, the H2 parity fixture, and the gate digests mirrored inmicrocosm-data's contract. The age band moves the synthetic CGT, purchase and rental facts that the fixture sizes after the SPI stage.🤖 Generated with Claude Code