Skip to content

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

Draft
juaristi22 wants to merge 54 commits into
mainfrom
uk-1095-spi-age-band-hrad
Draft

juaristi22 wants to merge 54 commits into
mainfrom
uk-1095-spi-age-band-hrad

Conversation

@juaristi22

@juaristi22 juaristi22 commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

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:

  1. The SPI stage-1 forests train and query on the SPI age band (uk-data#628).
  2. Households' additional-dwelling (HRAD) purchases at or below £250,000 are allocated from HMRC's Table 8a receipts.
  3. Drawn SPI employees' current pay is floored at what their twin's hours earn, and the tape's pay stays their year of pay (uk-data#629).

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

  • Control: the 28 October national build, attempt 4374e0bf at 7757add (staged on populace-uk-private). Its data path is main's at fddbc70, which adds only wording and the 0.055 UC take-up gate.
  • Arms: each item is one national arm (one spine and one calibration) built on fddbc70, with the control's review date (2026-10-28) and feed (272c987a).
  • Placebo: main with the SPI stage seed moved from 42 to 142 (local only) sizes the noise of a redraw.
  • Cross-machine check: the control's spine was built on another machine. Its 17 stages before the SPI income stage give receipts and records identical to the new spines'. So the arms differ only from the stages the items change.
Rows within 10% Final loss Rows moving more than 5 points against the control
Control 99.0% of 1,300 0.00529 —
Placebo 98.8% of 1,299 0.00560 23
1. SPI age band 98.9% of 1,299 0.00539 11
2. HRAD allocation 99.2% of 1,300 0.00531 0
This branch (1, 2 and 3) 98.8% of 1,299 0.00536 8

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

  • The stage-1 tape and reference forests read spi_age_band instead of age. 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_spa still carries the State Pension split inside the 65-74 band.
  • The cross-fit fold cells key on the band, since they key on the predictor values.
  • The drawn donor ages stay on the donor for their other readers: the band pools' State Pension side, and the property components' tape seed.
  • The stage receipt's stage1_age_predictor names the rule. sources.yaml declares the predictor and the fold key.

Measurement

  • Movement.
    • Against the control, this item takes four rows outside 10%: 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.
    • The placebo takes five rows outside and two inside.
    • The HMRC regional and income-band rows move no more than under the placebo, and the UC rows end closer to DWP's than in either other arm.
  • The age gradient (Chronicle: one append-only trace for every build attempt at every rung #628's symptom). This is the weighted rank correlation of age with pay within each SPI age band, on drawn SPI copies against FRS employees.
    • On main, the copies' pay barely rises with age inside the youngest bands, where FRS employees' clearly does.
    • With this change, the copies' pay rises with age inside 16-24 and 25-34 nearly as the FRS rows' does, and falls with age over 55 as theirs does.
    • The placebo changes none of this.

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

  • The nil-rate band from receipts. Households' HRAD at or below £250,000 is Table 6b's band less other buyers' HRAD implied by their Table 8a receipts, at Table 6b's gross receipts per transaction and per pound. The stage refuses an implied figure outside Table 6b's band or above other buyers' own transactions there.
  • Above the band, uk-data#573's shares stay. There the standard rates make up most receipts, and solving for other buyers' HRAD from them goes negative above £1m. So the shares remain, and probably still overstate households' purchases. The module docstring and the stage declaration say so.
  • The price mix follows the households' bands. The lowest band's share of households' purchases falls from 54% to 43%.
  • Targets. Households' 2024-25 HRAD target falls from 186,589 transactions and £68.81bn to 151,928 and £63.71bn. Both stay above uk-data#573's lower bounds, 87,200 and £33.28bn. The non-residential kind is unchanged.
  • Receipt. The stage's derivation adds the nil-band receipts, the implied HRAD and two shares: other buyers' HRAD over their nil-band transactions (0.625), and households' share of the band (0.576). uk_stage_property_purchases_fit needs no change, since every *_share key 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

  • The purchases stage.
    • Households' HRAD purchases fit at 152,849 against the new target of 151,928 (+0.6%), with the value at £63.71bn exactly and no shortfall. On main they fit 186,520 against 186,589.
    • The same value now spreads over fewer purchases, so their mean price rises. The share of purchasers held at their own stock rises from 37% to 48%.
    • Non-residential purchases are unchanged.
  • Movement. No row moves by more than 5 points. Three rows move inside 10% (dwp.esa_claimants and two UC family rows), and none move outside.
  • obr.sdlt.
    • Before calibration the model's receipts exceed OBR's £10.16bn by 49.2%, against 50.9% on main. That move is inside the placebo's, which gives 47.2%.
    • After calibration the row is within 0.25% of the OBR figure in every arm.
    • The rise in mean price offsets most of the drop in count, so the surcharge receipts fall less than the purchases.

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_wage operation of hmrc_spi_income_spine lifts a drawn copy's hmrc_spi_pay leaf to:

    • its twin's usual weekly hours (hours_worked read back at 52 weeks, uk-data#531),
    • times 52 weeks,
    • times the lower of policyengine-uk's gov.hmrc.minimum_wage.non_apprentice rate 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_income is 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_share becomes 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_floor receipt. 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:

    • an eligible drawn employee stays below its floor;
    • after the floor, drawn employees sit below the minimum wage more often than FRS employees by more than two points.

    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's contract.py and its mirror test.

  • The UC take-up gate. uk_stage_uc_take_up allows 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).

    • The floor gives fewer low-paid copies UC, so single people's caseload before the take-up redraw falls close to DWP's.
    • The redraw's usual downward move then reads as about twice the old 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.
    • The one tolerance also bounds the reachable pools, which realize DWP's count far more closely.
    • The gate's digests are re-pinned with it.

Measurement

The floor was measured two ways:

  • On this branch: one arm with all three items. It passes every spine gate, the take-up gate at 0.12 included, and all seven calibration gates. Against the SPI age band alone, it moves no row by more than 5 points. One HMRC regional cell (total income at £50,000 to £70,000 in the East Midlands) goes outside 10%.
  • On its own: with the HRAD allocation and the take-up gate widened on a local branch, against the HRAD allocation alone.
    • No row moves by more than 5 points, and the starting loss is lower.
    • Four rows already near 10% move outside it: dwp.esa_claimants, two UC family rows, and the HMRC regional taxpayers at £50,000 to £70,000 in London.
    • The HMRC employment-income bands end as without the floor, since their annual measures read the tape's pay.
    • Employee National Insurance ends further from OBR's figure, still within 10%: floored current pay raises the engine's NICs.
  • The floor itself:
    • It lifts a large share of eligible drawn employees.
    • After it, drawn employees sit below the minimum wage for their age less often than FRS employees do, within the gate's two-point margin.
    • No eligible row stays below its floor.
  • Across the award ranges: a diagnosis of UC awards against DWP's award bands finds the floor's single claimants leave only the middle range. Single people and lone parents were already short of DWP's middle and higher awards there before the floor; that is UK: every open data item: uk-data parity, engine locks, the local surface, consumption and high-value homes #1095's UC award item.

Tests

Touched surface, JUnit counts on this branch:

  • Engine-free (the SPI, purchases, gate, contract, manifest and spec files): 898, 0 failures, 1 skipped.
  • Engine tier (H2 parity and the SPI engine tests): 26, 0 failures.

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:
    • the stage-1 queries carry the band and no drawn age;
    • the reference forest is counted once per cross-fit fold in use;
    • the floor through the whole stage: each twin and its copy carry one age and pay, and the test checks the twin cap, the kept year of pay, and the employer contribution that follows the floored pay.
  • 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:
    • the hourly-pay gate holds drawn employees to their floor and to the FRS share plus two points;
    • the take-up gate pins 0.12 and passes the floor's pattern.

Follow-up

  • uk-data#573 still allocates every band by the shares; raising this there is María's call.

Generated surfaces: source_stages.json (in step with sources.yaml), the sources schema, the release input coverage manifest, the H2 parity fixture, and the gate digests mirrored in microcosm-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

juaristi22 and others added 6 commits October 29, 2026 19:35
…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 juaristi22 changed the title UK #1095 A/B fixes: the SPI age band (uk-data#628) and households' HRAD from Table 8a receipts 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) Oct 29, 2026
@vahid-ahmadi

Copy link
Copy Markdown
Contributor

@juaristi22 thanks, this is my review of 457af0274.

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 employment_income, stage 2 and pension coherence, with the twin cap and the kept year of pay. Nothing here blocks the A/B arms.

Findings

  1. should: composites (-1) sit next to 16-24 on an ordinal predictor. spi_age_band is numeric, not one of SPI_STAGE1_CATEGORICAL_PREDICTORS (spi_income.py:2252, 2623). So any split above -1 that doesn't separate -1 from 1 puts composite records in the leaves 16-24 queries land in. Before, composites drew an age across 16-70, so they spread over every band. Now they can only mix with the youngest one, and composites are the tape's high-income amalgams. Please either keep composites out of the stage-1 training set, one-hot the band, or check on the arm that 16-24 draws' top tail is not higher than on main. A line in the predictor rule saying which would do.
  2. should: the new floor receipt hardcodes ten. spi_income.py:936, 940, 951-952 and 961 use < 10 and "<10", and the gate docstring says "fewer than ten" (stage_health.py:1425). UK: take up #1095's actionable items on policyengine-uk 2.165.1 #1422 moves every UK floor onto receipt_counts.UK_DISCLOSURE_FLOOR. Whichever lands second should move these onto the constant and the helpers (small_cell_share, rounded_count), or this receipt will be the only one still on ten. The gate's "a withheld share is not judged" branch then follows too.
  3. should: one tolerance loosens two checks. uk_stage_uc_take_up goes from 0.055 to 0.12 (gates.json:633). That widens the "no further from DWP than before the redraw" leg, which the floor needs. But the same number bounds the reachable pools, which the notes say realize DWP's count within 0.0015. A second parameter for the pools, left at 0.055 or tighter, would keep that check meaningful while the other is wide. Please also name the issue that tracks the return to 0.055, so the revert isn't left to memory.
  4. should (a question): does Table 8a's nil-band receipts figure for other buyers include the 2% non-resident surcharge? If it does, part of those receipts is not HRAD surcharge. Then other buyers' implied HRAD is too high and households' too low (property_purchases.py:473). The direction is the safe one, and it may be small. One sentence in the module docstring saying which way it goes would do.
  5. nit: paid_weeks (spi_income.py:893) is annual paid hours (hours / 52 * 52), not weeks. paid_hours would read better.

What I ran. I read the full diff at 457af0274 and checked the HRAD figures by hand. I also checked that the SPI leaf reconciliation runs on the donor tape, before the floor, so the floor can't break it. CI is green at this head. I didn't run tests locally or build anything, and I didn't check the private A/B figures.

🤖 Generated with Claude Code

juaristi22 and others added 12 commits October 30, 2026 11:17
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>
juaristi22 added a commit that referenced this pull request Oct 31, 2026
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>
juaristi22 and others added 8 commits October 31, 2026 10:49
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>
juaristi22 and others added 14 commits October 31, 2026 11:51
The stage-1 queries carry the age band's one-hot columns, not the band
itself, and the pay floor's four floored households read "3-9".

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>
Only the gate digests conflict; they are re-pinned on the merged gates.
#1422's new floor-of-ten guard passes on #1426's SPI pay floor and age
band code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gate digests and the coverage manifest's source digest conflict; they
are re-pinned and regenerated on the merged gates and spec. #1422's
broader floor-of-ten guard passes on #1426's SPI code.

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>
The fixture conflicts, as #1426's SPI stages differ from #1422's; it is
regenerated on the merged tree, --check reproduces it byte-identically, and
the H2 parity test passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No conflicts. The exact-count floor guard passes on #1426's SPI and HRAD
code, as do the stage, ledger and reference tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 and others added 9 commits November 1, 2026 15:48
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>
@juaristi22

Copy link
Copy Markdown
Collaborator Author

@vahid-ahmadi thanks for the review. All five points are addressed. #1422 has landed, and main is merged in at bc7f59aac.

  1. Composites next to 16-24: the age band is now one of the stage-1 categorical predictors, one-hot encoded with gender, region and main source, so composite records are a category of their own (dae46ae94). The predictor rule, the spec's categorical list and the changelog say so. The next build re-measures the three items with it.
  2. The floor receipt's ten: the receipt withholds through UK_DISCLOSURE_FLOOR and UK_WITHHELD ("<3"). Its descriptions, the hourly-pay gate's note and docstring, and its tests follow (f1092f14c). Two test cases shrink their small side to two households, so they still test the withholding, and the engine test's floored households read "3-9" (8f0ec8fd7). UK: take up #1095's actionable items on policyengine-uk 2.165.1 #1422's floor guards pass on the merged tree.
  3. One tolerance, two checks: the reachable pools keep their own maximum_reachable_pool_deviation at 0.055, beside maximum_caseload_deviation at 0.12 (d68eb5cbb). The caseload check returns to 0.055 when UK: every open data item: uk-data parity, engine locks, the local surface, consumption and high-value homes #1095's UC award item ("UC awards below the top band are too low") lands, per María's ruling of 29 October.
  4. Table 8a and the non-resident surcharge: HMRC's commentary does not say whether Table 8a's receipts include it. HRAD makes up more than half of the residential receipts it reports, so they read as whole SDLT receipts. The module docstring now says which way the surcharge goes: read as HRAD surcharge, it raises other buyers' implied HRAD and lowers households', so households' nil-band purchases are, if anything, understated (f3d9c0af0).
  5. paid_weeks: renamed paid_hours (bc5adc97c).

The H2 fixture is regenerated on the merged tree (bc7f59aac). The full UK engine tier passed locally before main's merge, and CI runs it on the merge. #1095's Phase B joins this PR next (María's call of 31 October). Each item is measured against the national control built from #1428 at 329a99e71 (cut f6e33b93), on policyengine-uk 2.165.1, the lock main and #1428 share.

juaristi22 and others added 2 commits November 2, 2026 14:02
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>
@juaristi22

Copy link
Copy Markdown
Collaborator Author

@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 (0ddc23673, María's ruling of 2 November), and nothing is left to track. The pools keep their own maximum_reachable_pool_deviation, also at 0.055.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants