Skip to content

Budget 2026 quick data fixes, PR A of #1418: savings base, Carer's Allowance pool, bus - #1419

Merged
juaristi22 merged 15 commits into
mainfrom
uk-budget-2026-static-fixes-1418
Oct 29, 2026
Merged

juaristi22 merged 15 commits into
mainfrom
uk-budget-2026-static-fixes-1418

Conversation

@juaristi22

@juaristi22 juaristi22 commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

PR A of microcosm#1418: the quick data fixes that let population runs score the Budget 2026 household measures closer to the official costings. PR B (ISA) and PR C (#1420, the slower items) follow separately; #1418 stays open. Main is merged in (#1411 has landed), so the diff is PR A's own.

Known gap: read before bundling this cut or moving the dashboard to it

The three Table 2.6 savings rows (commit 2) are met mostly through other_investment_income, the one savings column no target binds.

  • HMRC's savings tax grows ×1.52 from 2023-24 to the calendar-2025 window; the SPI Table 3.7 interest rows grow ×1.192 (OBR's savings path is ×1.358).
  • On the national cut built from this PR, both families fit (Table 2.6 within 0.11%, the Table 3.7 interest bands within 0.22%), and the share of band interest above £100k is unchanged (28.4% against 28.5% on 3c0d11b6).
  • But other_investment_income is £9.5bn in 2024, against £3.5bn on 3c0d11b6: £8.1bn on SPI-channel rows (from £3.5bn) and £1.4bn from commit 1's FRS NS&I and gilt routing. SPI 2023-24 Table 3.7 publishes £5.85bn of "other income".
  • What holds: the taxed savings base by rate matches HMRC's, which is the costing's base, and the engine taxes interest and other investment income alike, so savings-rate costings hold.
  • What does not hold: the split between bank interest and other investment income, and anything else that reads other_investment_income.
  • Distributional results: don't move the dashboard to this cut for distributional results before PR C (review round 2).
  • Fix (PR C, María's ruling of 29 Oct): re-uprate the Table 3.7 interest rows on the OBR savings path, bind Table 3.7's other income by band, and re-measure. Until then, any bundle of this cut, or any proposal to move the dashboard to it, should carry this note.

Commits, in order

  1. Book the FRS NS&I and gilt interest the spine dropped (Table 6.1 row 62, savings rates).
    • frs_spine._add_accounts counted interest only on FRS accounts 1, 3, 5, 27 and 28. It now routes the NS&I Direct Saver (2), National Savings and pensioners' bonds (10, 13, 16, 17, 18), guaranteed equity bonds (26) and gilts (6, which it booked as dividends) to other_investment_income, and fixed-rate and guaranteed bonds (25) to savings_interest_income. The tax-free products (11, 12, 14, 15, 19, 31) stay out. policyengine-uk taxes other_investment_income as savings income (taxable_savings_interest_income), so no engine change is needed.
    • On FRS 2024-25 only accounts 2 and 6 record interest in that group (16, 25 and 26 record none; 10, 13, 17 and 18 do not occur). Of the codes no list maps, only a share club (24) records interest; its returns are on shares, so it stays out of scope (commit 13).
    • FRS rows now carry the column, so the SPI income stage rewrites it on SPI rows (the tape's OTHERINV draw) instead of adding it. It stays out of the FRS-only stage-2 predictors and the stage-2 fill.
    • The ONS D.41 diagnostic (ons.savings_interest_income, held out of the objective) adds it.
  2. Bind HMRC's Income Tax on savings at each rate (ITL Table 2.6).
    • Three national rows, hmrc.itl.savings_tax_at_{basic,higher,additional}_rate: Table 2.6's tax on savings at each rate, all payers, at the calendar-2025 window of the 2024-25 and 2025-26 projections (£1.72bn, £2.59bn and £4.09bn), the costing's own base (costings p. 75).
    • Each row measures the rate times the engine's bracketized savings income on each person's year (basic_rate_savings_income_annual and the higher and additional rate measures, new annual measures beside income_tax_annual). The compiler had no "tax ÷ rate" step, so a value expression may now scale a name by a positive constant (0.2 * basic_rate_savings_income_annual); an engine test pins the three coefficients to gov.hmrc.income_tax.rates.savings in both tax years.
    • SPI Table 3.7's bank and building society interest rows stay. See the known gap above.
    • Regenerated: target_references.json, target_reference_membership.json and the national compile-parity receipt, where the three ledger-only rows get their own rationale.
  3. Draw the Carer's Allowance pool from carers of a severely disabled person (row 7).
    • The allowance is paid only for caring for someone policyengine-uk holds is_severely_disabled_for_carers_allowance (SSCBA 1992 s. 70(2)), which the FRS care hours do not record, so UK Carer's Allowance: would_claim follows reported receipt, so earnings-limit reforms reach 4,600 new carers against an official 61,000 #1319's draw reached every 35-hour carer over the earnings limit.
    • A carer whose largest in-household care band reaches 35 hours joins the pool when another household member is severely disabled at the survey year. frs_spine derives frs_care_hours_in_household, the largest of the HOURAB, HOURAH, HOURCB and HOURCH bands at its lower edge (stage-internal, dropped at release).
    • A carer whose largest in-household band does not reach the hours (care outside the household, or hours spread over bands, since s. 70 needs them for one person) joins on a second identity-keyed uniform below the household-weighted share of in-household carers reporting no allowance who have such a member (decision (a)).
    • The take-up rate stays 0.6 (María's ruling of 28 Oct). On 3c0d11b6 at fixed weights, the new pool at 0.6 gives about 72,000 gainers in England and Wales in 2031-32 under the 100% taper, against the Budget's 48,000. The 0.45 and 0.75 sensitivity counts stay private under disclosure control.
    • On the built cut (4374e0bf), the dashboard's taper measure gives about 80,000 gainers in 2030-31 and 2031-32.
    • Scaling the rate to the Budget's count gives about 0.40 on 3c0d11b6 (48,000 / 72,000 × 0.6) and 0.36 on the built cut, below UK: data fixes for the Budget 2026 household measures #1418's 0.45 to 0.75 range. María's ruling of 29 Oct is to keep 0.6 and measure first. PR C scores the taper on the costing's basis (Carer's Allowance plus UC, with no income tax) and recounts gainers against 48,000. It reports before any change to the rate or the pool.
    • care_hours and the UC carer element are unchanged. The receipt's realized_share is now drawn_share_of_over_limit_pool, beside the measured share and banded pool counts before and after the restriction.
  4. Price England outside London's bus support on BUS01c's older and disabled journeys (row 4; UK bus fares: outside London the stage makes only older-person eligibility and under-5s free, but its yield removes all concessionary journeys #1385's last selector, and the eligibility basis as its 24 Oct comment asks: youth-scheme journeys stay priced, disabled-pass holders are not yet flagged, and under-5s ride free though BUS01c counts no concessionary journey for them). On UK bus fares: outside London the stage makes only older-person eligibility and under-5s free, but its yield removes all concessionary journeys #1385's certified-build figures (model year 2024), the reimbursement per concessionary boarding outside London moves from £630.6m / 519.1m = £1.215 to £630.6m / 402.8m = £1.566. On the 420m eligible boardings, the priced reimbursement moves from about £510m (19% below BUS05bi) to about £658m (4% above). The 4% comes from under-5s and from unflagged disabled-pass holders, as the basis says.
  5. Give the NTS non-user band no trips (UK bus travel: the lowest frequency band gives about half the population trips, so incidence counts are about double #1386, option 1). The "less than once a year or never" band carries no trips, and each series' other bands in a residence group are scaled so the group's donor mean trips per person stay as the diary gives them. The receipt adds England residents' trips per person by household income quintile, to set beside NTS0705a. On the national cut, 34.2m people have local-bus trips in 2027 (26.9m outside London) and 14.9m households pay fares, against 69.9m and 20.8m on 3c0d11b6, where the non-user band carried trips. Fares total £4.60bn, against £4.50bn.
  6. Merge of Sign the draught shares' degenerate exclusions and build on policyengine-uk #2695 #1411's branch, then the pin pass: ruff format on the touched files, the release input coverage manifest (only source_manifest_sha256 moves) and the H2 spine fixture.
  7. The annual income engine test computes the three new annual measures too (its receipt check holds every annual measure).
  8. Widen the UC take-up stage gate from 0.05 (narrowed to 0.055 in commit 12). The first spine stopped at uk_stage_uc_take_up: commit 1 moves gilt interest out of dividend_income on a handful of FRS rows, and dividend_income is a predictor of the SPI income stage's twin and stage-2 forests, so their draws reshuffle the SPI channel. Gilt interest is savings income in law (ITTOIA 2005 Part 4), so the routing stays.
  9. Re-pin the release certification's spine part to that gate.
  10. Name the in-household care hours carrier among the release-dropped columns in the terminal-gates test.
  11. Merge main (Sign the draught shares' degenerate exclusions and build on policyengine-uk #2695 #1411 merged).
  12. Narrow the UC take-up gate to 0.055 (review, should fix 1). On the da944f1 spine, single people's caseload ends 0.0523 of DWP's count further from it than before the redraw (0.0486 on 3c0d11b6's spine). Couples without children, lone parents and couples with children end closer than before, by 0.53, 0.054 and 0.42 of their counts. Every family type's reachable pools realize DWP's count within 0.0015. So 0.055 is the smallest round band that holds, with about 8,000 to spare; the gate notes record these figures.
  13. Word the Carer's Allowance pool by the largest in-household band, and name the unmapped account codes (review, nice to have 3 and 5).
  14. Changelog: the Carer's Allowance gainers against the Budget's count, and the savings gap's other readers (review round 2).

Electricity stays a diagnostic in this cut: the ONS 04.5.1 household electricity row remains held out of the national calibration (decision D1). It moves to the ONS level in PR C, with its stage-level fix. Gas stays a diagnostic until PR C moves it too.

Fits on the national cut (2025)

Rate Table 2.6 tax, calendar-2025 window Relative error
Basic £1.720bn +0.07%
Higher £2.5875bn −0.09%
Additional £4.0875bn −0.11%
SPI 3.7 interest band Target (£m) This cut 3c0d11b6
£12,570–15,000 328 −0.00% +0.02%
£15,000–20,000 1,639 −0.04% −0.09%
£20,000–30,000 3,744 +0.09% −0.08%
£30,000–40,000 3,026 +0.08% −0.11%
£40,000–50,000 2,237 +0.15% +0.07%
£50,000–70,000 2,799 −0.04% −0.20%
£70,000–100,000 1,938 +0.02% −0.41%
£100,000–150,000 1,507 +0.01% +0.28%
£150,000–200,000 777 −0.02% +0.41%
£200,000–300,000 877 −0.10% −0.16%
£300,000–500,000 805 −0.22% +0.00%
£500,000–1m 781 +0.06% −0.01%
£1m and over 1,483 −0.02% +0.16%

Overall: final loss 0.00529 (3c0d11b6: 0.00553), 99% of targets within 10%, effective sample size 7,536 (7,527). Head-to-head on this register against 3c0d11b6: 0.0128 against 0.0155 full loss, 728 targets won against 571.

Before PR A, 3c0d11b6's taxed savings income was 74% of Table 2.6 tax ÷ rate (86/71/65% at the basic, higher and additional rates); commit 1's routing alone would bring it to 80%.

Build

A national-only cut is built from 7757add (on the spine built at da944f1, whose stages and gates are the same) on policyengine-uk 2.162.14. It is certified (release-cut gates 19/19) and published to the inspect lane (--no-latest --tag-only) as microcosm-uk-2024-25-national-20261028T214959Z-4374e0bf (H5 sha256 a912a704…); the local-area cut stays 6528c865. Carry the known gap above with this tag. Commits 10 to 14 came after the build: tests, the gate band (0.055 holds the built spine), wording and the changelog, so the cut is unchanged by them. Promotion is María's call.

Post-build scoring against the costings

Run on 4374e0bf and, for comparison, on 3c0d11b6. Both use the dashboard's measure code (uk-autumn-budget-dashboard-2026 main, 57095fd), its October 2026 forecast and policyengine-uk 2.162.14, so the difference between the two columns is this PR's data. Each figure is the weighted gov_balance change, in £bn.

Measure Year 3c0d11b6 4374e0bf Official
Savings income rates +2pp 2029-30 +0.462 +0.603 +0.520
2030-31 +0.490 +0.636 +0.555
2031-32 +0.515 +0.666 +0.590
Carer's Allowance taper 2030-31 −0.022 −0.017 −0.040
2031-32 −0.044 −0.032 −0.080
  • Savings: the yield rises by 30%, from 11–13% below the costing to 13–16% above it. The costing comes after savers shift into ISAs. At the calendar-2025 window, savings tax at each rate moves from 86%, 71% and 65% of Table 2.6 to 99%, 100% and 100%. The published dashboard's a6299504 gave about +0.29 for 2028-29; 3c0d11b6 already gives +0.43 on this engine, so most of that rise came before this PR.
  • Carer's Allowance: the net cost falls by about a quarter. Gainers in England and Wales fall from about 85,000 to about 80,000, against the Budget's 48,000. The dashboard's figure includes income tax; the costing counts Carer's Allowance and UC spending.
  • The other measures, which this PR does not target, move by less than 3%: the Household Energy Payment, zero VAT on electricity, fuel duty, the trading allowance and property income rates.
  • The State Pension measure: not scored on either cut, because the dashboard's template can't wrap income_tax on 2.162.14.

Each figure rests on ≥10 source households on both cuts. The full table, with the other measures, goes with the policyengine.py bundle PR (PolicyEngine/policyengine.py#728).

Testing

Touched tests only (CI runs the suites): FRS spine, SPI spine, income and support, source stages, graph, national frame, terminal gates, stage health, contract and gate battery pins, target materialization, population targets, target references, national calibration, measurement period, measure simulation, country spec, the Carer's Allowance stage (engine-free and engine), NTS bus travel, bus fare and support pricing, bus engine parity, and the new savings-rate lockstep test. The coverage manifest --check passes, the H2 fixture regenerates, and ci_test_plan.py verify is ok.

Disclosure

Aggregates only: weighted totals over many records, published HMRC, DfT and Treasury figures, and support bands with "<10".

🤖 Generated with Claude Code

juaristi22 and others added 8 commits October 28, 2026 20:23
…ment income

frs_spine._add_accounts counted interest only on FRS accounts 1, 3, 5, 27
and 28, dropped the NS&I Direct Saver, National Savings and pensioners'
bonds, fixed-rate and guaranteed bonds and guaranteed equity bonds, and booked
gilts as dividends (microcosm#1418). It now routes them as decision (c) of
the PR A plan rules: fixed-rate and guaranteed bonds join deposit interest,
the rest go to other_investment_income, which policyengine-uk taxes as
savings income beside savings_interest_income, and gilts leave
dividend_income. The tax-free products stay out. On FRS 2024-25 only the
NS&I Direct Saver and gilt rows record interest among them.

FRS rows now carry other_investment_income, so the SPI income stage
declares it a rewrite rather than a new output: SPI rows keep the tape's
OTHERINV draw and FRS rows keep their own. It stays out of the FRS-only
stage-2 predictors. The ONS D.41 diagnostic adds it to the interest it
measures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three UK national rows, hmrc.itl.savings_tax_at_{basic,higher,additional}_rate,
bind the Income Tax on savings at each rate that Table 2.6 of HMRC's Income
Tax liabilities statistics (July 2026) projects for all payers, at the
calendar-2025 window of the 2024-25 and 2025-26 projections (microcosm#1418).
It is the base the Budget 2026 savings income rates are costed on (costings
p. 75: taxable savings income above the PA, PSA and starting rate, ISAs
excluded, from HMRC's Personal Tax Model on SPI 2023-24).

Each row measures the rate times the engine's bracketized savings income on
each person's year, through three new annual measures beside
income_tax_annual. The compiler has no "tax over rate" step, so a value
expression may now scale a name by a positive constant
(0.2 * basic_rate_savings_income_annual); an engine test holds the three
coefficients to policyengine-uk's savings rates in both tax years of the
window. SPI Table 3.7's bank and building society interest rows stay.

Regenerated the national references and membership and the national
compile-parity receipt, whose new ledger-only rows get their own rationale.

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

The allowance is paid only for caring for a person policyengine-uk holds
is_severely_disabled_for_carers_allowance (SSCBA 1992 s. 70(2)), which the
FRS care hours do not record, so the #1319 draw opened the claim flag to
every 35-hour carer over the earnings limit (microcosm#1418). The pool now
needs one of:

- care inside the household alone reaching the hours line, with another
  household member severely disabled at the survey year. frs_spine derives
  frs_care_hours_in_household, the largest of the FRS HOURAB, HOURAH, HOURCB
  and HOURCH bands at its lower edge, a stage-internal carrier the release
  drops;
- for a carer whose hours are not all given inside the household, a second
  identity-keyed uniform (salt carers_allowance_outside_household_pool) below
  the household-weighted share of the in-household carers reporting no
  allowance who have such a member (decision (a)).

The stage materializes the disability test beside the earnings test, both at
the survey year. care_hours and the Universal Credit carer element are
unchanged. The receipt's realized_share is now
drawn_share_of_over_limit_pool, beside the measured share and banded pool
counts before and after the restriction, inside and outside the household.

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

bcad529 moved both fare pricings' England-outside-London concessionary
denominator to BUS01c's older and disabled journeys, as policyengine-uk prices
the yield; the support pricing kept the total. It now takes the same
category, so the concessionary travel reimbursement is spread over the
travel it pays for (microcosm#1385, the last of the three selectors).

The eligibility basis no longer says disabled passes and child half-fares
are absorbed by the yield (#1385's 24 Oct comment): youth-scheme journeys
stay priced and their discounts enter the average yield, disabled-pass
journeys leave the denominator while their holders are not yet flagged, and
under-5s ride free though BUS01c counts no concessionary journey for them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
assign_trips_from_band_means gave each person their interview band's diary
mean. The lowest band, "less than once a year or never", holds about half of
all persons, and its diary mean of a few trips a year gave every one of them
fares, so counts of riders and fare payers read about double
(microcosm#1386). Option 1: the band carries no trips, and each series' other
bands in a residence group take its trips, scaled by the group's
donor-weighted trips over the trips of its other bands, so the group's mean
trips per person on the donor stay as the diary gives them and the fare and
support pricing still meet BUS01. The published-shares fallback already
gives the band a zero midpoint.

The stage receipt records the scales and adds England residents' trips per
person by household income quintile, the frame's side of DfT's NTS0705a.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR A builds on 2.162.14 with #1411's draught-share sign-offs; once #1411
merges, main merges here and this merge's changes drop out of the diff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pin pass after merging #1411's branch: the release input coverage
manifest (only source_manifest_sha256 moves), the H2 spine fixture (the
spine's new other_investment_income and frs_care_hours_in_household columns
and the stage specs), and ruff format on the touched files. Both --check
runs pass; the fixture regenerates byte-identically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engine test holds the receipt's computed measures to every annual
measure, so it computes the three new ones too, on the same year: nobody in
its frame has savings income (microcosm#1418).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 and others added 3 commits October 28, 2026 21:24
The national spine at 0fc9975 stopped at uk_stage_uc_take_up: single
people's Universal Credit caseload ended 7.3k past the gate's band of 0.05
of DWP's count, which the build before held by 4.4k. Commit 1 routes gilt
interest out of dividend_income on a handful of FRS rows; dividend_income is
a predictor of the SPI income stage's twin and stage-2 forests, so their
draws reshuffle the SPI channel. Gilt interest is savings income in law, so
María kept the routing and widened the band to 0.06 (ruling of 2026-10-28,
microcosm#1418). The contract re-pins the gate battery's policy, manifest
and fingerprint digests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
da944f1 moved the gate battery's three pins but left the release
certification's spine part on the 0.05 gate: the spine scope holds
uk_stage_uc_take_up, so its scoped manifest and policy digests moved too.
The calibration-seam and release-cut parts are unchanged.

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

test_export_candidate_columns_strip_ids_and_carry_the_weight holds the
export boundary's dropped columns to an exact list; commit 3 drops
frs_care_hours_in_household there too (microcosm#1418).

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

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of #1419 at 27dc5da99.

Scope: PR A's own commits since #1411 (git log --first-parent --no-merges 55d5c9fd7..27dc5da99, 11 commits, 43 files). #1411 merged at 55d5c9fd7, so I did not review its content again. The GitHub diff still shows 70 files, because the branch has not merged main since #1411 merged. Please merge main now so that the GitHub diff shrinks to PR A's own changes. A trial merge (git merge-tree origin/main pr-1419) is clean.

Must fix

None found.

Should fix

  1. The UC take-up gate widens for every family type, and the PR does not record the size of the miss.

    • What: gates.json L619 moves maximum_caseload_deviation from 0.05 to 0.06. The value applies to every DWP family type and to both checks (the reachable pools against DWP, and "no further from DWP than before the redraw").
    • Evidence: commit da944f12 says that single people's caseload ended 7.3k past the 0.05 band, and that the build before held it by 4.4k. So the build-to-build swing from a small reshuffle of the SPI channel is about 11.7k. The gate was tuned closer than the noise of the stage. The new band adds 0.01 of DWP's count for every family type. For a large family type, that is far more headroom than the 7.3k miss needs.
    • Fix: state the realized deviation as a share of DWP's count for each family type on the da944f12c spine, in the commit or gate notes. Then use the smallest band that holds it with a stated margin (for example 0.055). Or keep 0.05 and add an override for single people only. If 0.06 stays, say in the gate notes why one value for all family types is right.
  2. The Table 2.6 rows and the SPI Table 3.7 interest rows imply different growth from the 2023-24 base. Please report both fits before merge.

    • What: both families come from SPI 2023-24, and both bind at 2025. The Table 3.7 interest rows grow by x1.192 to 2025 (ONS D.41, then GDP per head; Max's item 11 comment on #1418). Table 2.6 holds HMRC's own projection.
    • Evidence: Table 2.6 savings tax, all payers, is £1.19bn + £1.65bn + £2.70bn = £5.54bn in 2023-24. The calendar-2025 window is £1.72bn + £2.59bn + £4.09bn = £8.40bn, which is x1.52. Tax over rate is £16.08bn in 2023-24 and £24.15bn in 2025, which is x1.50. Frozen allowances lever taxable savings above gross interest, so part of this gap is expected. But the OBR savings path (x1.358, per Max's comment) is also well above x1.192. If the solve must meet both families, it can only push interest towards the higher and additional rates while it holds the band totals of interest. That can move the interest distribution without a basis in the data.
    • Fix: the PR body says that "the build is the test". Please put the national cut's post-calibration fit for the three Table 2.6 rows and the Table 3.7 interest rows (by band) in the PR before merge. Also add a short note on how the interest share above £100k moved. If the two families pull against each other, move the Table 2.6 rows to PR C as the body proposes, or hold them as a diagnostic.

Nice to have

  1. The "other carer" group is wider than the docstring says. carers_allowance_take_up.py L244 sets other = hours & ~in_household. A carer whose care is all inside the household but spread over two bands (for example 20-34 hours to a partner, HOURAB, and 20-34 hours to a child of another benefit unit, HOURCH) has no single band at 35. That carer goes into other and is drawn at the in-household share. The docstrings and CARERS_ALLOWANCE_POOL_RULE say "whose hours are not all given inside the household". The behaviour is a defensible approximation, because s.70 needs 35 hours for one person. Please make the wording match: "whose largest in-household band does not reach the hours".

  2. The 0.6 take-up check is private. #1418 item 2 asked for the 0.6 rate to be checked against the Treasury's 48,000 (sensitivity 0.45 to 0.75). The PR cites a private fixed-weight check. Please give the aggregate gainer count at 0.6 (and at 0.45 and 0.75 if you have them) in the PR body or in #1418. Then reviewers can see how close the pool restriction brings the count.

  3. Question: are there FRS 2024-25 account codes with interest that no list maps? frs_spine.py L761 lists the other-interest codes, and the deposit, dividend, ISA and tax-free lists cover the rest that you name. Codes such as 20, 22-24, 29, 30 and 32+ are not in any list. If any of them occur on FRS 2024-25 with a positive ACCINT, please name them in the comment as tax-free or out of scope. If none occur, a sentence that says so closes the question.

  4. Bus support: say the expected effect on the priced reimbursement. With #1385's certified-build figures (model year 2024), the reimbursement per concessionary boarding outside London moves from £630.6m / 519.1m = £1.215 to £630.6m / 402.8m = £1.566. On the 420m eligible boardings, the priced reimbursement moves from about £510m (19% below BUS05bi) to about £658m (4% above). The 4% comes from under-5s and from disabled-pass holders who are not flagged, as the new basis says. One sentence in the commit or in #1385 with these numbers helps the next reviewer.

Verified as correct

  • FRS account routing (commit 1). UKDS variable records for ACCOUNT give code 2 "NSB Direct Saver", code 6 "Government Gilt Edged Stock" and code 15 "Premium bonds" (most recent records I could reach are 2013-14 and 2014-15; I could not open the 2024-25 listing). Gilt interest is savings income (ITTOIA 2005 Part 4 Chapter 2). Before this PR, gilts were dividends when INVTAX was 1 and were dropped otherwise. Now all of code 6 goes to other_investment_income. Premium bonds (15), savings certificates (11, 12), SAYE (14), the Yearly Plan (19) and friendly society investments (31) stay out, which matches their tax-free status. ISAs (21) stay in tax_free_savings_income. In policyengine-uk 2.162.14, taxable_savings_interest_income = savings_interest_income + other_investment_income. That total feeds the starting rate, the PSA and the bracketized savings income, so no engine change is needed.
  • SPI rewrite against add. other_investment_income moves from UK_SPI_INCOME_SPINE_OUTPUT_COLUMNS to UK_SPI_INCOME_SPINE_REWRITE_COLUMNS. So SPI rows take OTHERINV and replace the FRS value, and FRS rows keep their own. It is left out of SPI_SPINE_STAGE2_OUTPUT_COLUMNS and of the FRS-only predictors. I found no path where both values add on one row.
  • Table 2.6 values. I downloaded HMRC's collated Tables 2.1 to 2.6 (July 2026). "All Income Tax payers", tax on savings, is £1,720m / £2,520m / £4,020m in 2024-25 and £1,720m / £2,610m / £4,110m in 2025-26. With 3/12 and 9/12: basic £1,720m, higher £2,587.5m, additional £4,087.5m. This matches £1.72bn / £2.59bn / £4.09bn. The "Basic rate" row includes savers-rate payers (£137m and £132m), which matches "all payers".
  • Pre-check arithmetic. 1.72 / 0.20 = 8.60; 2.5875 / 0.40 = 6.47; 4.0875 / 0.45 = 9.08; the total is 24.15. The table uses the unrounded window values, which is correct.
  • Rates and base. The 20 / 40 / 45% rates hold in 2024-25 and 2025-26. test_uk_itl_savings_tax_rates.py pins each coefficient to gov.hmrc.income_tax.rates.savings at 2024-04-30 and 2025-04-30, and checks that each row names its own rate's income. basic_rate_savings_income and the other two measures take savings after allowances, less the starting rate and the PSA, stacked on non-savings income. That is the taxable savings income that Table 2.6 taxes.
  • Compiler change. _parsed_expression takes <positive number> * <name> as one term. It refuses zero, a leading sign, name * k, k * k, k * max(...) and chained products. The new test covers each case, and _tree_names and _evaluate_tree handle the scaled node. measurement_period.py reads names through expression_names, so it accepts the new grammar.
  • Carer's Allowance pool (commit 3). is_severely_disabled_for_carers_allowance in 2.162.14 reads AA, DLA care at the middle or highest rate, PIP daily living and AFIP (SSCBA 1992 s.70(2)). frs_spine maps ADP and CDP into the PIP and DLA columns, so Scottish members count. other_member_severely_disabled excludes the carer and is keyed on household id. The pool uniform uses its own salt on the same person_id key as the take-up draw, so it is deterministic and independent of the take-up draw. The share is household-weighted over in-household carers who report no allowance. Unknown band codes still refuse the build, and band 9 and band 10 map to 20 and 35 at their lower edges. The engine-free tests cover the pool, the share, the "other member" rule, the receipt and the small-cell guards.
  • NTS non-user band (commit 5). With T = sum of persons x mean over all bands and R = T - non-user persons x non-user mean, each other band is scaled by T / R. The sum over the other bands is then R x T / R = T, so the donor-weighted mean trips per person in each series and residence group stay the same. The published-rate path already gives the band 0 trips (band_midpoints), so the two paths agree. The NTS0705a receipt states that its income measure differs (HBAI gross against NTS total household income), which is the right caveat.
  • BUS01c selector (commit 4). The support selector now matches the fare pricing (elderly_disabled_concessionary). The basis text does what #1385's 24 Oct comment asks.
  • Contract re-pins (commits 8 and 9). The gate battery's policy, manifest and fingerprint digests and the spine certification part's digests move together. test_contract.py passes, and the calibration-seam and release-cut digests are unchanged.

Open PRs on the same files. #1420 (PR C) is stacked on this branch and merges cleanly. It also touches gates.json and contract.py, so it will need a re-pin if finding 1 changes the gate. #1416 (target_materialization.py) merges cleanly with this branch. #1399, #1391 and #1410 conflict with this branch, but the conflicts come from their own older bases (for example derived_facts.py, worker_identity.py and the excise evidence files), not from this PR's changes.

Tests run locally

In a worktree at 27dc5da99, after uv sync --all-packages --locked --extra uk (policyengine-uk 2.162.14). I ran no build that needs private FRS or SPI data.

  • Engine-free, the touched files (test_uk_frs_spine, test_uk_carers_allowance_take_up, test_uk_nts_bus_travel, test_target_materialization, test_uk_population_targets, test_uk_target_references, test_uk_measure_simulation, test_uk_national_calibration, test_uk_stage_health, test_uk_terminal_gates, test_country_spec, microcosm-data test_contract): 985 passed, 5 skipped. All 5 skips are in test_uk_target_references.py and need the authenticated or pinned Chronicle feed.
  • Engine-free, the neighbouring stages (test_uk_spi_spine, test_uk_spi_support, test_uk_source_stages, test_uk_graph, test_uk_national_frame, test_uk_measurement_period, test_uk_bus_fare_pricing, test_uk_bus_support_pricing): 144 passed.
  • Engine (policyengine-uk 2.162.14): test_uk_itl_savings_tax_rates.py, test_uk_carers_allowance_take_up.py, test_uk_annual_income_measures.py, test_uk_bus_engine_parity.py: 13 passed, 0 skipped.
  • ruff check and ruff format --check on the 25 touched Python files: clean.
  • tools/build_uk_release_input_coverage_manifest.py --check: current (144 required, 1 reviewed exclusion). tools/graph_uk_spine_fixture.py --check: the H2 fixture regenerates byte-identically. tools/ci_test_plan.py verify: ok.
  • Not run: any spine or calibration build (it needs private FRS and SPI data).

🤖 Generated with Claude Code

Comment thread packages/microcosm-build/src/microcosm/build/uk/gates.json Outdated
"bindings": {
"policyengine": {
"metric_name": "hmrc/itl_savings_tax/basic_rate",
"value_expression": "0.2 * basic_rate_savings_income_annual",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The window values check out against HMRC's collated Table 2.6: 0.25 x 1,720 + 0.75 x 1,720 = 1,720; 0.25 x 2,520 + 0.75 x 2,610 = 2,587.5; 0.25 x 4,020 + 0.75 x 4,110 = 4,087.5. HMRC's projection grows savings tax x1.52 from 2023-24 (£5.54bn) to this window (£8.40bn), while the Table 3.7 interest rows grow by x1.192. Please report the post-calibration fit of both families on the national cut before merge. (Should fix 2.)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both fits from the national cut are in the PR body: Table 2.6 within 0.11%, the Table 3.7 interest bands within 0.22%. The pull lands in other_investment_income (£9.5bn in 2024 against £3.5bn); kept bound on María's ruling, with the gap at the top of the PR body and PR C fixing it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fits. Both families now fit, and the interest share above £100k holds. The cost is other_investment_income: £9.5bn in 2024 against £3.5bn before, and 62% above SPI Table 3.7's published £5.85bn. In policyengine-uk this column also feeds Housing Benefit income, the Welsh childcare offer test and gross_taxable_inputs, so it is not only a savings tax input. The gap is now documented. I leave it open in the follow-up review as a decision for the reviewers.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The body and changelog (c56fe96a1) now name the column's other readers, note that household_market_income leaves it out, and say not to move the dashboard to this cut for distributional results before PR C. The binding itself stays with PR C (María's ruling of 29 Oct). I've reopened this thread for the reviewers to close.

🤖 Addressed by Claude Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. The documentation now covers this, and the dashboard note is in the body. I confirmed the extra reader: HOUSEHOLD_MARKET_INCOME_VARIABLES on policyengine-uk main has no other_investment_income, so income tax charges the column but market and net income leave it out. That is an engine issue that predates this PR. Binding Table 3.7's other income stays with PR C, as you say. I leave this thread to the human reviewers to close.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened PolicyEngine/policyengine-uk#2733 for the engine gap.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Linked from the known gap in the PR body. The policyengine-uk fix will go against #2733. I'm leaving the thread for the human reviewers.

🤖 Addressed by Claude Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix for #2733 is up as PolicyEngine/policyengine-uk#2736. It adds other_investment_income to market_income, household_market_income and hbai_household_net_income.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. The known gap in the PR body already links #2733 and #2736. I'm leaving the thread for the human reviewers.

🤖 Addressed by Claude Code

juaristi22 and others added 3 commits October 29, 2026 00:27
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1419 (Pavel, should fix 1): 0.06 widened the band for every
family type and both checks, though only single people missed. On the spine
of 2026-10-28 (da944f1) single people's caseload ends 0.0523 of DWP's
count further from it than before the redraw (0.0486 on 3c0d11b6's spine);
couples without children, lone parents and couples with children end closer
than before by 0.53, 0.054 and 0.42 of their counts, and every family
type's reachable pools realize DWP's count within 0.0015. 0.055 holds single
people with about 8,000 to spare, and the gate notes now record these
figures. The contract re-pins the gate battery's policy, manifest and
fingerprint digests and the release certification's spine part.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e the unmapped account codes

Review of #1419 (Pavel, nice to have 3 and 5):
- The "other" carers are those whose largest in-household care band does
  not reach the hours: care outside the household, or hours spread over
  bands (the allowance needs the hours for one person). The docstrings, the
  pool rule in the eligibility text, the stage notes and the changelog now
  say so; the behaviour is unchanged.
- Of the FRS 2024-25 ACCOUNT codes no list maps, only a share club (24)
  records interest; its returns are on shares, so it stays out of scope as
  interest. Profit sharing (22), company share option plans (23), an
  endowment policy not linked (29) and informal assets (32) record none.

The Table 2.6 changelog fragment records the known gap the national cut
showed (Pavel's should fix 2): the solve meets the rows mostly through
other_investment_income, which PR C fixes. The coverage manifest and the H2
spine fixture follow the spec text.

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

Copy link
Copy Markdown
Collaborator Author

@PavelMakarchuk thanks. All of it is answered at ccf09d8cd.

Merge main. Done in eb2179e7f, so the diff is now PR A's own 43 files.

Should fix 1, the UC take-up gate. Narrowed to 0.055 in a094f5d56. Here is each family type on the da944f1 spine, as a share of DWP's count:

Family type Further from DWP than before the redraw Reachable pools against DWP
Single people +0.0523 (+0.0486 on 3c0d11b6's spine) 0.0005
Couples, no children −0.53 (ends closer) 0.0010
Lone parents −0.054 (ends closer) 0.0004
Couples with children −0.42 (ends closer) no reachable pool

Only single people use the band. 0.055 is the smallest round value that holds them, with about 8,000 to spare. The other family types sit far inside it, so a singles-only override would buy nothing today. The figures are in the gate notes and the changelog, and the contract re-pins the gate battery and spine-part digests. #1420 will need the same re-pin.

Should fix 2, Table 2.6 against Table 3.7. The fits from the national cut are in the PR body:

  • Table 2.6 is within 0.11% at all three rates.
  • Every Table 3.7 interest band is within 0.22% (3c0d11b6: 0.41%).
  • The share of band interest above £100k is unchanged, at 28.4% against 28.5%.

You were right that the two pull against each other. The pull lands in other_investment_income, the one savings column no target binds. It is £9.5bn in 2024, against £3.5bn on 3c0d11b6: £8.1bn on SPI rows and £1.4bn from the FRS routing. SPI Table 3.7 publishes £5.85bn of other income.

María's ruling: keep the rows bound in PR A. The taxed savings base by rate matches the costing's, and the engine taxes interest and other income alike. PR C then re-uprates the Table 3.7 interest rows on the OBR savings path and binds Table 3.7's other income by band. The gap is now the first section of the PR body and is in the changelog, so any bundle of this cut, or any move of the dashboard to it, carries it.

Nice to have:

  • 3. Reworded in ccf09d8cd to "whose largest in-household band does not reach the hours", in the code, the eligibility text, the notes and the changelog. The behaviour is unchanged.
  • 4. On 3c0d11b6 at fixed weights, the new pool at 0.6 gives about 72,000 gainers in England and Wales in 2031-32, against the Budget's 48,000. The 0.45 and 0.75 counts stay private: the nested pools differ by too few source households to publish side by side.
  • 5. Of the unmapped codes, only a share club (24) records interest on FRS 2024-25. Its returns are on shares, so it stays out of scope. 22, 23, 29 and 32 record none. The comment now says so.
  • 6. Your figures are in commit 4's line in the PR body.

🤖 Generated with Claude Code

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review of #1419 at ccf09d8cd (since 27dc5da99: the merge of main eb2179e, a094f5d and ccf09d8).

Must fix

None.

Should fix

  1. Fixed: the UC take-up gate. a094f5d sets 0.055. On the da944f1 spine only single people use the band (0.0523 of DWP's count). The other family types end closer to DWP than before the redraw. The gate notes give each figure.

  2. Still open, for the reviewers to decide: the Table 2.6 fit lands in an unbound column. The fits are good: Table 2.6 within 0.11% and every Table 3.7 interest band within 0.22%. But other_investment_income rises to £9.5bn in 2024, from £3.5bn on 3c0d11b6. SPI Table 3.7 publishes £5.85bn of other income, so the cut is 62% above it. In policyengine-uk 2.162.14 the column feeds more than savings tax: taxable_savings_interest_income, Housing Benefit applicable income (_net_earnings.py L71), the Welsh childcare offer's gross income, and gross_taxable_inputs. Most of the £8.1bn sits on SPI rows, so the effect on means tests is probably small, but household incomes at the top move. The PR body and changelog now state the gap, and PR C will bind Table 3.7's other income. One option is to bind Table 3.7's other income by band in this PR as well, so the solve can't put the pull there. Otherwise, the dashboard should not move to this cut for distributional results before PR C.

  3. New: the Carer's Allowance gainers are 50% above the Budget's count. At take-up 0.6, the new pool gives about 72,000 gainers in England and Wales in 2031-32, against the Budget's 48,000. With the pool fixed, 48,000 / 72,000 × 0.6 = 0.40 matches the Budget, which is below the 0.45 to 0.75 range in #1418. Please add the 72,000 to the PR body and the changelog. Then say if PR C revisits the take-up rate or the pool.

Nice to have

  • 3 (carer wording), 5 (unmapped codes) and 6 (bus effect): fixed.
  • 4 (take-up check): answered, see Should fix 3.

Verified as correct

  • Merge of main. The diff since 27dc5da99, without the merge, touches 14 files. These are the gate, its notes, the carer wording, the account-code comment, the contract re-pins and the fixtures.
  • Gate value. gates.json has 0.055, and the notes give the per-family figures from María's reply.

Tests run locally

At ccf09d8cd, after uv sync --all-packages --locked --extra uk:

  • test_uk_carers_allowance_take_up (engine-free and engine), test_uk_terminal_gates, test_uk_frs_spine, test_uk_stage_health and microcosm-data test_contract: 634 passed.
  • ruff check on the two touched runtime files: clean.

🤖 Generated with Claude Code

…savings gap's other readers

Review round 2 on PR A of microcosm#1418.

- The Carer's Allowance pool entry gives the restricted pool's gainers at
  the 0.6 take-up rate: about 72,000 in England and Wales in 2031-32 under
  the Budget's 100% taper (3c0d11b6 at fixed weights), against the
  Budget's 48,000.
- The savings known gap names the column's other readers in
  policyengine-uk 2.162.14: Housing Benefit's net earnings (the share of
  tax set against earnings), the Welsh childcare offer's income test and
  gross_taxable_inputs. It also notes that household_market_income leaves
  the column out, so household net income carries the tax on it without
  the income. Distributional results on cuts built from this change
  should wait for PR C.

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

Copy link
Copy Markdown
Collaborator Author

@PavelMakarchuk thanks. Round 2 is answered in c56fe96a1 (changelog only, so the cut is unchanged) and in the PR body.

Should fix 2: the unbound column. María ruled on 29 Oct to keep the three Table 2.6 rows bound here and to fix the level in PR C. Binding Table 3.7's other income here would need another spine and national build, so that stays with PR C. I've taken up the other half of your point:

  • Other readers named. The known gap, in the body and the changelog, now lists the column's other readers in policyengine-uk 2.162.14: Housing Benefit's net earnings, the Welsh childcare offer's income test and gross_taxable_inputs. In Housing Benefit the column only enters the share of tax set against earnings.
  • One more reader you didn't list. household_market_income leaves other_investment_income out, while income tax charges it, so household net income carries the tax on the column without the income.
    • On this cut, that understates household net income where the £8.1bn sits.
    • Commit 1's gilt interest also leaves market income; it was counted as dividends before.
    • The omission is the engine's and predates this PR. Costings read tax and benefits, so they are unaffected.
  • Dashboard. The body now says not to move the dashboard to this cut for distributional results before PR C.

Should fix 3: the Carer's Allowance gainers. The 72,000 was already in the body, under commit 3, and c56fe96a1 adds it to the changelog.

  • On the built cut (4374e0bf), the dashboard's taper measure gives about 80,000 gainers in 2030-31 and 2031-32. Your scaling gives about 0.36 there.
  • PR A keeps 0.6 (María's ruling of 28 Oct). Whether PR C revisits the rate or the pool is María's call; I've put the question and your arithmetic to her.

Post-build scoring. A new section in the body scores the dashboard's measures on 4374e0bf:

  • the savings rates measure: +£0.603bn, +£0.636bn and +£0.666bn in 2029-30 to 2031-32, against the costing's +£0.520bn, +£0.555bn and +£0.590bn (which come after savers shift into ISAs);
  • the Carer's Allowance taper: −£0.017bn and −£0.032bn, on the dashboard's net basis.

Each figure rests on ≥10 source households.

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review of #1419 at c56fe96a1 (one commit since ccf09d8cd, changelog only).

Must fix

None.

Should fix

  1. Documented. The decision is for the human reviewers. The body and changelog now list every reader of other_investment_income and say not to move the dashboard to this cut for distributional results before PR C. Binding Table 3.7's other income stays with PR C. I have no further ask on this PR.
  2. Documented. The 72,000 gainers are now in the changelog too. On the built cut the taper measure gives about 80,000, which implies a take-up of about 0.36 against the Budget's 48,000. PR A keeps 0.6, and PR C may revisit it.

Verified as correct

  • The extra reader. On policyengine-uk main, HOUSEHOLD_MARKET_INCOME_VARIABLES has no other_investment_income. Income tax charges the column, but market income and net income leave it out. This predates this PR, and it affects every dataset that fills the column. It needs its own policyengine-uk issue.
  • Post-build scoring. The savings rates measure gives +£0.603bn to +£0.666bn in 2029-30 to 2031-32. That is 14% to 16% above the costing's +£0.520bn to +£0.590bn, which already counts savers who move into ISAs. A static model has no such move, so a gap in this direction is expected.

Tests run locally

None for this commit, because it changes only two changelog fragments. The last run, at ccf09d8cd, passed 634 tests.

🤖 Generated with Claude Code

@juaristi22

Copy link
Copy Markdown
Collaborator Author

@PavelMakarchuk on the Carer's Allowance take-up rate (your should fix 3): María ruled today to keep 0.6 and measure first. PR C will:

  • score the taper on the costing's basis (Carer's Allowance plus UC, with no income tax);
  • recount gainers against the Budget's 48,000;
  • report before any change to the rate or the pool.

The PR body (commit 3) now says the same. For reference, on the same engine (2.162.14), gainers fall from about 85,000 on 3c0d11b6 to about 80,000 on 4374e0bf. The post-build section of the body has the before and after.

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