Repository navigation
Budget 2026 quick data fixes, PR A of #1418: savings base, Carer's Allowance pool, bus - #1419
Conversation
…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>
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>
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
left a comment
There was a problem hiding this comment.
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
-
The UC take-up gate widens for every family type, and the PR does not record the size of the miss.
- What:
gates.jsonL619 movesmaximum_caseload_deviationfrom 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
da944f12says 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
da944f12cspine, 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.
- What:
-
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
-
The "other carer" group is wider than the docstring says.
carers_allowance_take_up.pyL244 setsother = 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 intootherand is drawn at the in-household share. The docstrings andCARERS_ALLOWANCE_POOL_RULEsay "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". -
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.
-
Question: are there FRS 2024-25 account codes with interest that no list maps?
frs_spine.pyL761 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. -
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 intax_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_incomemoves fromUK_SPI_INCOME_SPINE_OUTPUT_COLUMNStoUK_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 ofSPI_SPINE_STAGE2_OUTPUT_COLUMNSand 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.pypins each coefficient togov.hmrc.income_tax.rates.savingsat 2024-04-30 and 2025-04-30, and checks that each row names its own rate's income.basic_rate_savings_incomeand 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_expressiontakes<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_namesand_evaluate_treehandle thescalednode.measurement_period.pyreads names throughexpression_names, so it accepts the new grammar. - Carer's Allowance pool (commit 3).
is_severely_disabled_for_carers_allowancein 2.162.14 reads AA, DLA care at the middle or highest rate, PIP daily living and AFIP (SSCBA 1992 s.70(2)).frs_spinemaps ADP and CDP into the PIP and DLA columns, so Scottish members count.other_member_severely_disabledexcludes the carer and is keyed on household id. The pool uniform uses its own salt on the sameperson_idkey 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.pypasses, 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-datatest_contract): 985 passed, 5 skipped. All 5 skips are intest_uk_target_references.pyand 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 checkandruff format --checkon 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
| "bindings": { | ||
| "policyengine": { | ||
| "metric_name": "hmrc/itl_savings_tax/basic_rate", | ||
| "value_expression": "0.2 * basic_rate_savings_income_annual", |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Opened PolicyEngine/policyengine-uk#2733 for the engine gap.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
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>
|
@PavelMakarchuk thanks. All of it is answered at Merge main. Done in Should fix 1, the UC take-up gate. Narrowed to 0.055 in
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:
You were right that the two pull against each other. The pull lands in 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:
🤖 Generated with Claude Code |
PavelMakarchuk
left a comment
There was a problem hiding this comment.
Follow-up review of #1419 at ccf09d8cd (since 27dc5da99: the merge of main eb2179e, a094f5d and ccf09d8).
Must fix
None.
Should fix
-
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.
-
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_incomerises 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.pyL71), the Welsh childcare offer's gross income, andgross_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. -
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.jsonhas 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_healthand microcosm-datatest_contract: 634 passed.ruff checkon 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>
|
@PavelMakarchuk thanks. Round 2 is answered in 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:
Should fix 3: the Carer's Allowance gainers. The 72,000 was already in the body, under commit 3, and
Post-build scoring. A new section in the body scores the dashboard's measures on 4374e0bf:
Each figure rests on ≥10 source households. |
PavelMakarchuk
left a comment
There was a problem hiding this comment.
Follow-up review of #1419 at c56fe96a1 (one commit since ccf09d8cd, changelog only).
Must fix
None.
Should fix
- Documented. The decision is for the human reviewers. The body and changelog now list every reader of
other_investment_incomeand 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. - 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_VARIABLEShas noother_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
|
@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:
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. |
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.other_investment_incomeis £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".other_investment_income.gross_taxable_inputs.household_market_incomeleaves the column out, so household net income carries the tax on it without the income. Most of the £8.1bn sits on SPI-channel rows, whose household net income is understated by it.Commits, in order
frs_spine._add_accountscounted 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) toother_investment_income, and fixed-rate and guaranteed bonds (25) tosavings_interest_income. The tax-free products (11, 12, 14, 15, 19, 31) stay out. policyengine-uk taxesother_investment_incomeas savings income (taxable_savings_interest_income), so no engine change is needed.ons.savings_interest_income, held out of the objective) adds it.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).basic_rate_savings_income_annualand the higher and additional rate measures, new annual measures besideincome_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 togov.hmrc.income_tax.rates.savingsin both tax years.target_references.json,target_reference_membership.jsonand the national compile-parity receipt, where the three ledger-only rows get their own rationale.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.frs_spinederivesfrs_care_hours_in_household, the largest of the HOURAB, HOURAH, HOURCB and HOURCH bands at its lower edge (stage-internal, dropped at release).care_hoursand the UC carer element are unchanged. The receipt'srealized_shareis nowdrawn_share_of_over_limit_pool, beside the measured share and banded pool counts before and after the restriction.ruff formaton the touched files, the release input coverage manifest (onlysource_manifest_sha256moves) and the H2 spine fixture.uk_stage_uc_take_up: commit 1 moves gilt interest out ofdividend_incomeon a handful of FRS rows, anddividend_incomeis 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.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)
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) asmicrocosm-uk-2024-25-national-20261028T214959Z-4374e0bf(H5 sha256a912a704…); 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_balancechange, in £bn.income_taxon 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
--checkpasses, the H2 fixture regenerates, andci_test_plan.py verifyis ok.Disclosure
Aggregates only: weighted totals over many records, published HMRC, DfT and Treasury figures, and support bands with "<10".
🤖 Generated with Claude Code