Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
667fe53
Rebase SPI donor savings to SPI's 2023-24 bands, then the OBR path
juaristi22 Oct 30, 2026
f3cb3dd
Widen the UC take-up gate to 0.06 for the savings rebase
juaristi22 Oct 30, 2026
b441bb5
Hold the SPI £1m-and-over interest row on its measured distortion
juaristi22 Oct 30, 2026
4a6dc72
Rebase SPI donor savings on the tape's taxpayers
juaristi22 Oct 30, 2026
092de13
Book FRS savings income on SPI's definitions
juaristi22 Oct 30, 2026
b822474
Locate SPI copies' other-income draw at the twin's other income
juaristi22 Oct 30, 2026
9ee6fbd
Merge main into uk-savings-costing-anchor-1418
juaristi22 Oct 30, 2026
995969f
Leave the savings rebase off tapes whose weights don't gross
juaristi22 Oct 30, 2026
e5ac7a5
Regenerate the H2 spine fixture for the other-income analogue
juaristi22 Oct 30, 2026
53ad299
Zero a missing self-analogue output before the SPI transport
juaristi22 Oct 30, 2026
45c2888
Regenerate the H2 spine fixture on policyengine-uk 2.164.2
juaristi22 Oct 30, 2026
d563f4f
Bind SPI Table 3.10's employment-income counts by self-employment band
juaristi22 Oct 30, 2026
5c0137f
Anchor HMRC's tax on savings rows on the OBR savings path (option A)
juaristi22 Oct 31, 2026
6485af4
Join UC take-up pools that cannot meet DWP's count
juaristi22 Oct 31, 2026
f3540cb
Store the self-employed's gross receipts for the trading allowance
juaristi22 Oct 31, 2026
f9676fe
Revert the OBR-path anchor for HMRC's tax on savings rows (option A)
juaristi22 Oct 31, 2026
0c2f531
Declare the trading allowance receipts stage's source-household read
juaristi22 Oct 31, 2026
3702769
Pin the trading allowance receipts stage in the stage-order tests
juaristi22 Oct 31, 2026
b9bcd70
Drop the ISA subscriptions stage's empty fit-weight records
juaristi22 Oct 31, 2026
c5ef127
Anchor the SPI copies' gift leaves to the tape's weighted totals
juaristi22 Oct 31, 2026
a8e2587
Bind DWP's income-based Jobseeker's Allowance spending
juaristi22 Oct 31, 2026
91775db
Keep annuity and trust income in pensions and rebase savings once
juaristi22 Nov 1, 2026
24e2329
Pin the review's tests and correct the texts it found stale
juaristi22 Nov 1, 2026
59ef8ec
Regenerate the H2 fixture and the coverage manifest
juaristi22 Nov 1, 2026
9f70c71
Band the savings rebase once, moving savings by all bands' factor
juaristi22 Nov 1, 2026
4dd83e9
Move the gate battery pins with the descriptor's export surface
juaristi22 Nov 1, 2026
76f7dcc
Recalibrate the trading allowance claimant rates on policyengine-uk
juaristi22 Nov 2, 2026
329a99e
Merge main, with #1422 and #1430, into the savings follow-up
juaristi22 Nov 2, 2026
2dbf7cf
Band SPI records on total income net of expenses, without gifts
juaristi22 Nov 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1418-spi-donor-savings-rebase.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The SPI income stage now rebases the 2022-23 tape's interest and other income to HMRC's published 2023-24 Table 3.7 amounts band by band (the published rows are vendored as `uk/hmrc_spi_table_3_7_savings_rows.json`), then carries both to the frame's year on the OBR's savings income growth (October 2026 EFO, Table 4.5), the path the Table 3.7 band targets take (#1418 savings follow-up, #1428). Before, each column moved by its engine index from 2022 to 2024: other income by GDP per head (x1.09) where SPI's own other income grew x1.65 in the single year to 2023-24, and interest by ONS household interest (x2.27) where SPI's grew x2.69 in that year. The frame then carried about 70 percent of the Table 3.7 savings rows at design weights, and the solve met them by stretching the weights of the few households that carry them. Each band's factor is the published amount over the FACT-weighted amount of the tape's taxpayers in the band, as Table 3.7 counts taxpayers only. The tape is banded once on its income moved to 2023-24, each income leaf at its own factor and each rebased column at its factor over all bands, so the band-edge migration its consumers re-band on uprated income is counted once; the income is the published total plus the leaves' growth, net of employment expenses and without the gift leaves, which SPI's total income doesn't hold, and the factor over all bands counts the same taxpayers as the band factors; a record whose band's factor differs from that of all bands can still sit across an edge on its rebased income. A taxpayer is a record the tape flags (TAXPAYER 1) or one whose income crosses the Personal Allowance by 2023-24, so a saver who becomes a taxpayer counts once; a flagged taxpayer below the first band sits in it (note 8), and any other record takes the factor of all bands. A band too thin to gross (under a tenth of its published amount) takes the factor of all bands, a column whose tape does not gross to the published amounts is left as it is and grows on its engine index from 2022-23, and a tape whose TAXPAYER codes are not 1 or 2 is refused. On the tape, the £1m-and-over band's other income outweighs its interest, as in the published table, so the rebase also undoes the split the two indices had flipped.
1 change: 1 addition & 0 deletions changelog.d/1428-dwp-jsa-income-based.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The national calibration binds DWP's income-based Jobseeker's Allowance spending, `dwp.jsa.amount_income_based` (benefit expenditure and caseload tables, Spring Forecast 2026), at the calendar-2025 window of the fiscal years, as the pension-age Housing Benefit row is (#1428; María's ruling of 2026-10-31). Income-based JSA closes with the move to Universal Credit: £91.0m in FY2024-25 and under £1m from FY2025-26. Without the split, the calibration meets OBR's total JSA by scaling income-based reporters together with contributory claimants, far past DWP's income-based spending.
1 change: 1 addition & 0 deletions changelog.d/1428-frs-other-income-definitions.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FRS rows now book NS&I income on SPI's definitions (#1428). NS&I Direct Saver and the other NS&I and bank products go to `savings_interest_income`, because SPI has counted all interest paid gross or net as bank interest since 2016-17 (Table 3.7 note 4); gilts stay in `other_investment_income`. Income from an annuity not bought with pension funds or from a trust or covenant (FRS PENTYPE 4 and 5) stays in `private_pension_income`, where policyengine-uk's means tests (Pension Credit, Universal Credit, Housing Benefit, Council Tax Reduction, Income Support), household income and tax read it. Its part is stored in a new calibration-only descriptor, `annuity_and_trust_income`, and SPI Table 3.7's other-income rows read `other_investment_income_spi_concept`: the engine's other income plus that share of the engine's private pension, as the table counts both payments as other income (note 6). A row the SPI stage draws holds the tape's pension, which excludes them, so its descriptor is zero. Its other income is the tape's OTHERINV, Table 3.7's other income, which holds those payments, and policyengine-uk taxes it as savings income: on drawn rows they still take the Personal Savings Allowance, the starting rate and UK rates in Scotland. That limit predates this change; the tape doesn't split OTHERINV. The FRS spine now refuses a pension tab without PENTYPE.
1 change: 1 addition & 0 deletions changelog.d/1428-isa-fit-weight-records.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The ISA subscriptions stage no longer declares an empty list of fit-weight records (#1428). It fits no model, but the release-cut weights audit read its empty list as a fitting stage that recorded nothing, so every national certified after #1421 failed `uk_weights_audit`.
1 change: 1 addition & 0 deletions changelog.d/1428-spi-band-donor-total-income.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The SPI band donors place each tape record by the total income it realises on the frame as SPI's total income holds it: its income leaves at their factors net of employment expenses, without the gift leaves, which are reliefs, plus the rest of the published total, no longer floored at zero (#1428; Vahid's review). Before, a record's gifts and employment expenses added to its income, so a donor with large gifts of shares, or with expenses, sat in too high a band. With every factor at one the realised total is the published one. The savings rebase bands the tape the same way.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The national calibration binds SPI 2023-24 Table 3.10's individuals with employment income by self-employment band, `hmrc.spi.employment_income.count_by_self_employment_income_band`, over the eight bands from £1 to £10,000 that the self-employment counts and amounts already bind (#1418; María's choice of 2026-10-30). With the self-employment counts alone, the solve could meet each band with traders who have no pay, while in Table 3.10 more than half of the traders below £3,000 also have employment income; an employee's side profit is taxed at their marginal rate, so the mix of small traders decides what the Budget's trading allowance measures cost. Each person with pay or company car benefits over the year (`employment_income_with_benefits_in_kind_annual`, the term every HMRC employment income row reads since #1430) counts in their own self-employment band, taxpayer or not. Table 3.10's column is wider, income assessed under ITEPA 2003 plus other miscellaneous income, so like Table 3.6's employment rows the binding leaves out miscellaneous income and the contributory benefits; benefits in kind count on SPI rows, whose pay is the tape's PAY + EPB + TAXTERM, and the company car and car fuel benefits on every row. Counts grow to 2025 with HMRC's projected growth of all taxpayers (Income Tax liabilities statistics Table 2.5), as the self-employment counts do, and the zero band and the bands from £10,000 are signed out row by row.
1 change: 1 addition & 0 deletions changelog.d/1428-spi-gift-anchor.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The SPI income stage anchors the drawn copies' Gift Aid and charitable investment gifts to the SPI tape's own weighted totals (#1428; María's ruling of 2026-10-31). Both are sparse leaves that the stage-1 chain draws after, and conditioned on, the income leaves, so a change to the savings draws reshuffles which few donors' gifts the copies draw. Each column's total over the drawn copies, at the stage's household weights, is scaled to the tape's FACT-weighted total. A column that the tape or the copies carry none of is left as drawn. The scaling fixes the totals, not how concentrated each column is. The stage evidence records each column's factor, never a total or a count.
1 change: 1 addition & 0 deletions changelog.d/1428-spi-other-income-analogue.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The SPI stage-1 transport now locates each copy's other-income draw at its FRS twin's own other income (gilt interest), as it does for the other investment leaves, instead of an identity uniform that ignored the twin's holdings (#1428).
1 change: 1 addition & 0 deletions changelog.d/1428-trading-allowance-receipts.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A new last spine stage, `trading_allowance_receipts`, stores the self-employed's gross receipts (`self_employment_gross_receipts`), so policyengine-uk applies the trading allowance against receipts as the law does (#1428; María's choice of 2026-10-31). Neither the FRS nor the SPI tape records turnover. Without receipts the engine relieved profits up to the allowance in full and nothing above it, so a rise in the allowance missed its main base: traders who already claim the £1,000 allowance against larger receipts. Under current law a claimant's receipts are their profit plus £1,000 exactly. Each trader with a positive profit is flagged as a claimant with probability 0.72 for profits up to £4,500, 0.14 from £4,500 to £9,000 and none above, drawn on their own `person_id`. A claimant's receipts are their profit plus £1,000; any other trader's are their profit plus £3,000, expenses at the raised allowance, since the costing books traders who switch to the allowance as behaviour. The rates are calibrated to HMRC's policy paper (about 400,000 taxpayers with a lower bill, by about £380 on average), measured on policyengine-uk for the national from 9f70c71e7, until HMRC publishes claimant counts; they drift with each national, so #1439 re-measures them after each build. Every trader with a positive profit is treated as filing, as the Table 3.10 counts the calibration binds are of filers, so a profit of £1,000 or less is now taxed where the engine used to relieve it in full. Two limits remain until policyengine-uk#2765 lets the stage store the claim status itself (#1439). A claimant's £1,000 grows with the mixed income index while the allowance is nominal, so the raise relieves £1,932 more in 2027-28, falling to £1,758 by 2031-32. The other traders' stored £3,000 grows the same way, so allowances above £3,000 are mis-scored. `self_employment_gross_receipts` leaves the register of engine inputs left at their default and joins the export surface's allowed columns. The stage spec and schema, the graph and kernel wiring, the spine build, the coverage manifest, the H2 fixture and the data contract's gate digests follow.
1 change: 1 addition & 0 deletions changelog.d/1428-uc-take-up-passes-unmet-gaps.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The Universal Credit take-up stage now passes the gap of a pool that cannot meet DWP's count to its neighbouring pools, so each family type's caseload lands on DWP's wherever its entitled units allow (#1428; María's rulings of 2026-10-31 and 2026-11-01). The stage pools adjacent award bands within a family type until they rest on enough sources. A pool can't meet DWP's count when its reporters' and other undrawn units' expected claims already exceed it, or when the count exceeds every entitled unit. Such a pool used to keep its clipped rate, which left the family type's caseload off DWP's by the gap, and which bands fell short moved with small upstream changes. Now such a pool is held at its clip and its gap passes on. A shortfall and an excess elsewhere in the family type offset first. What remains goes to the other pools, nearest first, the one with more room first at equal distance and the lower band on a tie, each taking what its room allows. Every band keeps its own rate, so no band's drawn units claim against its own count to meet another's. The stage spec, the coverage manifest, the H2 fixture and the data contract's gate digests follow.
35 changes: 31 additions & 4 deletions docs/uk-income-anchors-280.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ membership carries the uprated value with its receipt.
others take `policyengine_uk_parameter:<path>`, the ratio of the pinned
engine's parameter between 1 January of the fact's opening year and 1
January of the calibration year, the instants the SPI donor rebasing already
uses: dividends `obr.per_capita.gdp`. Savings interest takes two legs
(microcosm#1095): `ons.household_interest_income`, the index of its
uses: dividends `obr.per_capita.gdp`. Savings interest outside SPI Table
3.7's rows takes two legs (microcosm#1095); Table 3.7's interest and other
income rows take the OBR's savings income path from 2023-24 instead, as the
next paragraph says (microcosm#1418): `ons.household_interest_income`, the index of its
history, to the dataset's year, then `obr.per_capita.gdp`, the index the
engine's dataset projection applies, to the calibration year (1.1420 x
1.0474 = 1.1961 on the pinned engine values; 1.0438 and 1.1920 before
Expand All @@ -96,6 +98,30 @@ membership carries the uprated value with its receipt.
by `tools/pin_uk_uprating_engine_values.py`, whose `--check` refuses drift), so
it runs and reproduces without policyengine-uk; a `requires_uk` test holds the
pins in lockstep with the installed engine, and an engine bump re-runs the tool.

The SPI donors reach the frame's year apart from these indices for interest
and other income (microcosm#1418 savings follow-up). The income stage first
scales each donor's two columns to HMRC's published 2023-24 Table 3.7 amounts
in its band of total income (`spi_income.rebase_spi_donor_savings`, the rows
vendored as `uk/hmrc_spi_table_3_7_savings_rows.json`), then grows them on the
OBR's savings income path the Table 3.7 targets take
(`hmrc_uprating.uk_obr_savings_income_level`). Each band's factor is the
published amount over the FACT-weighted amount of the tape's taxpayers in
the band, as Table 3.7 counts taxpayers only (microcosm#1428). The tape is
banded once on its income moved to 2023-24, each income leaf at its own
factor and each rebased column at its factor over all bands, so the
band-edge migration the consumers re-band on uprated income is counted
once. That income, like the band donors', is the published total plus the
leaves' growth, net of employment expenses and without the gift leaves,
which SPI's total income doesn't hold. A record whose band's factor differs from that of all bands can still
sit across an edge on its rebased income. A taxpayer is a record the tape flags (TAXPAYER 1) or one whose income
crosses the Personal Allowance by 2023-24; a flagged taxpayer below the first
band sits in it (note 8); any other record takes the factor of all bands. A
band the tape carries none of is left as it is, a band whose tape amount is
under a tenth of its published amount takes the factor of all bands, and a
column whose tape does not gross to the published amounts is left as it is
and grows on its engine index from 2022-23. From the frame's year on, the
engine projects the two columns on their own indices.
- Count rows: `hmrc.itl_2026.taxpayer_count_growth_by_total_income_band`, HMRC's
own projected growth in Income Tax payers for the Table 2.5 band that contains
the SPI band (Income Tax liabilities statistics, July 2026), as the
Expand Down Expand Up @@ -405,8 +431,9 @@ fold differs from its twin's, and a reference whose sign classes differ from
the tape forest's on any located target. One difference between the two
forests remains: the reference locates a leaf given the twin's observed
earlier analogue leaves, while the tape forest draws it given every earlier
output, the three identity-uniform leaves (`other_investment_income`,
`gift_aid`, `charitable_investment_gifts`) included, so a located quantile is
output, the two identity-uniform leaves (`gift_aid`,
`charitable_investment_gifts`) included, since `other_investment_income` has
located at the twin's own other income from microcosm#1428, so a located quantile is
read off a conditional those identity draws also shape; pay, which follows
them in the chain, has the weakest rank fidelity, and moving the analogue
targets ahead of the identity leaves is the follow-up to measure. Because the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"assign_student_loan_plan_cohorts",
"assign_main_asset_type",
"assign_trips_from_band_means",
# microcosm#1428: the self-employed's gross receipts.
"assign_trading_allowance_receipts",
"assign_uniform_draw",
"aggregate_person_to_benunit",
"allocate_per_capita_from_cell_table",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6612,6 +6612,75 @@
}
}
},
{
"type": "object",
"additionalProperties": false,
"required": [
"kind",
"output",
"profit",
"draw",
"seed",
"claimant_bands",
"claimant_expenses",
"other_expenses",
"calibration",
"rule"
],
"properties": {
"kind": {
"const": "assign_trading_allowance_receipts"
},
"output": {
"type": "string"
},
"profit": {
"type": "string"
},
"draw": {
"type": "string"
},
"seed": {
"type": "integer"
},
"claimant_bands": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"rate"
],
"properties": {
"upper_inclusive": {
"type": "number",
"exclusiveMinimum": 0
},
"rate": {
"type": "number",
"minimum": 0,
"maximum": 1
}
}
}
},
"claimant_expenses": {
"type": "number",
"minimum": 0
},
"other_expenses": {
"type": "number",
"minimum": 0
},
"calibration": {
"type": "string"
},
"rule": {
"type": "string"
}
}
},
{
"type": "object",
"additionalProperties": false,
Expand Down
Loading
Loading