Repository navigation
Conversation
Savings interest, ISA interest and other investment income grew from the 2023-24 data year by GDP per head (10.3% to 2025-26 with population). The OBR's savings income forecast (October 2026 EFO, receipts Table 4.5) rises 35.8% over the same years. Dataset projection now uses the OBR series per person to 2025-26 and GDP per head after it, unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in Notes
Moves flagged for reviewNone. Spending by benefit and service
Caseloads
Standard reform costings (change in gov_balance)
Standard reforms: change in each reform's target
Household net income by decileNo household's net income changes in any year. No change in any year: receipts by tax head; government balance, each component counted once; model totals; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles. Checks failing on both base and head (1)
Counted-once checkEach of 91 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.
Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
The OBR savings income series starts in 2022-23, so growth_rate now hands earlier years to GDP per head too, rather than the parameter's backdated first value. test_projection_horizon's oracle reads the same rule. Docs and the changelog say the rule applies to dataset projection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Could you add the exact dataset build ID and H5 SHA256, runtime versions/model commit, forecast commit, and accessible scripts/aggregate results behind the yield table? The filename alone doesn’t distinguish builds. Our unchanged-data control reproduced £307.210m in 2029–30, using:
Binding this table’s £461.8m baseline to equivalent provenance would help reconcile the different run bases and support the matched model/data attribution. The existing distinction between the engine-only result, emulated rebuild and OII sensitivity is helpful. |
…for what it checks The changelog now says ISA interest grows only in datasets on the additive ISA contract; legacy gross datasets hold the ISA part flat. The window test checks that the rule starts at the first sourced year and ends at 2025-26, not that it covers every sourced year. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent re-review (GPT-6.1 Sol, Subfleet job
It raised three nits, fixed in f2220b3:
Not a merge signal. This stays a draft until d2142 is ruled, microcosm switches both legs and rebuilds, and #1963 lands. |
A dataset's savings income is fitted to targets its builder moved along some growth path, so projection must follow the same path. Datasets now declare it in the H5 root attribute policyengine_uk.savings_income_projection: gdp_per_head (the default, and every existing file) or obr_savings_income_2022_2025. growth_rate applies the OBR series only under the second rule, so existing data projects exactly as before and microcosm can switch its target legs and declare the rule in one rebuild. The declaration travels through single- and multi-year files, copies, filtered households, simulation clones and exports, and input frames. A value that contradicts the file, an unknown rule, or mixed rules across a multi-year dataset is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng-obr # Conflicts: # docs/book/assumptions/growthfactors.md
The review of the opt-in design (Sol, at fa17b34) approved with nits: - An export now refuses an empty, False or zero rule instead of falling back to the simulation's rule, as it already refused unknown names. - Input frames, filtered households, Core-format files and Core objects, and the full-variable export each get a round trip that checks the declared rule and the growth that follows it. - The growth_rate check covers every series uprating_indices.yaml maps, not only GDP and population. - The savings section of the dataset sources guide moves below the ISA contract paragraphs it had split. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2717 moved ons.population for 2022-2030 to the October 2026 EFO. The per-person series is the OBR's savings income over population, so each year is re-derived: 2025-26 is now 0.32%, not 0.35%. The docs table and the GDP-per-head comparison (10.2% from 2023-24 to 2025-26) follow. The file, copy, clone and export tests now also check that the data they return projects by the declared rule, as the review asked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GDP per head rises 4.0% in 2024-25 and 4.6% in 2025-26 since #2717, not 3.9% and 4.7%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hand-off to the UK hub (from the savings uprating session)
|
Fixes #2722
Series ruled: A (d2142, Max, 28 Oct 2026). UK savings income moves from 2023-24 to 2025-26 by the OBR's savings income forecast. The rule is revisited when SPI 2024-25 is published.
Datasets opt in. The OBR path applies only to datasets that declare it, so this PR changes nothing for any existing dataset and can be released before microcosm switches. The microcosm switch (both target legs, donors, the declaration and a rebuild) follows in a microcosm PR that needs this release first.
Landing: on Vahid's sign-off. No agent merges in Budget week, and a release can ride into the dashboard's next regeneration. With nothing declared, that regeneration's figures are unchanged (yields below).
What and why
In dataset projection, savings interest, ISA interest and other investment income can now grow by the OBR's savings income forecast per person from 2022-23 to 2025-26. In every other year they grow by GDP per head, as before.
Until now they grew by
obr.per_capita.gdpin every year. From 2023-24 to 2025-26, the OBR's "Savings income" rises 35.8% (34.96% in 2024-25, 0.65% in 2025-26), while GDP per head and population growth give 10.2%. The low 2025-26 level carries into every later year, so it lowers the yield of any savings tax change.obr.savings_income(new, inyoy_growth.yaml) is the "Savings income" row of the OBR receipts table "Self-employed income streams: growth rates", pre-measures.d115705716d77b11c190385d41cb1924f30080c9331a8a386c04d4e65a368e95.f65b6cb7f96931d4abd85940794eb60f6f65afca8bbf16c74a4548bf92766a28.16d69c0981f45e5b7fe3fa35d08bfe09e6170440231d546f374a9f4d48191991. 2022 is the index base year, since an index drops its first listed year's growth (Growth indices drop their first listed year's growth #2692).obr.per_capita.savings_income(new) is that series net ofons.populationgrowth: (1 + savings income) / (1 + population) − 1.uprating_indices.yamlmovessavings_interest_income,other_investment_incomeandnon_cash_isa_cash_interestto the new series.KNOWN_INDEX_OVERRIDESrecords why their declared index differs (rail_usage and savings_interest_income are still not uprated by the index they declare #1862, left as is).The declaration
A dataset's savings income is fitted to targets that its builder moved to the dataset year and beyond along some growth path. Projection must grow it along the same path, or projected totals miss the targets. So the dataset declares the path (
policyengine_uk/data/savings_income_projection.py), as it already declares its ISA income contract and energy price basis:policyengine_uk.savings_income_projection, a plain string.gdp_per_head: GDP per head in every year. Every dataset that declares nothing, which is every dataset today, gets this rule.obr_savings_income_2022_2025: the OBR series per person from 2022 to 2025, GDP per head in other years. A rule's name fixes its years, so a later window needs a new rule and data built under this one keeps its meaning.growth_rate()indata/economic_assumptions.pytakes the rule. It applies it to the YAML-mapped inputs and to the two code paths that grow interest outside the YAML: gross interest under thelegacy_grossISA contract, and ISA interest under theadditivecontract.UKSingleYearDataset(argument, file,save,copy,from_simulation),UKMultiYearDataset(one rule for every year, read from and written to the file), projection to later years, filtered households, simulation clones and exports, and input frames (frame.attrs).Why opt in rather than switch everyone: microcosm reads these indices from the released policyengine-uk it locks and pins (local versions are refused), so it cannot switch until this is released. Switching every dataset on release would have lowered 2025-26 savings income on today's data, by 6.1% for ordinary interest and 4.2% for other investment income (measured at 545129c), until the rebuild. Opting in leaves today's data exactly as it is and lets the rebuild declare the rule it was calibrated under.
What this does not change
gdp_per_head, under whichgrowth_rate()returnsobr.per_capita.gdpfor the savings inputs in every year, the index main uses. At fa17b34, undeclared data reproduced main's yields and savings totals on the 28 Oct build exactly (below). At this head, a differential reproduces main's projections and calculations exactly (test plan).uprating(direct calculator inputs and years after the dataset projection) is untouched (rail_usage and savings_interest_income are still not uprated by the index they declare #1862). There, interest follows ONS household interest income (D.41) and other investment income follows GDP per head.Coupling with microcosm
microcosm calibrates the UK dataset's savings interest to the SPI's 2023-24 amounts by band. It moves them to the calibration year in two legs, at microcosm
7bf1ed8e7,packages/microcosm-build/src/microcosm/build/uk_runtime/hmrc_uprating.py:UK_SPI_SAVINGS_INTEREST_HISTORY_PARAMETER=indices.ons.household_interest_income, 2023 → 2024 (the dataset year): ×1.142000.UK_SPI_SAVINGS_INTEREST_PROJECTION_PARAMETER=indices.obr.per_capita.gdp, 2024 → 2025: ×1.047402.After this is released, the microcosm PR should:
indices.obr.savings_income(2023 → 2024 ×1.349602) and the projection leg toindices.obr.per_capita.savings_income(2024 → 2025 ×1.003200). The factor becomes ×1.353920, which is K = 1.131914 times the 28 Oct build's 1.196133. Update the economic assumptions to the October 2026 EFO #2717 also moved both of today's legs (D.41 ×1.140500, GDP per head ×1.046199, so 1.193190 at this release), so the switch is what moves the targets, not the version bump;obr_savings_income_2022_2025on the H5 and on the datasets it calibrates with, and re-pin the engine values.That is the ratio this branch's indices imply under microcosm's convention: a per-person projection leg, with
weight_year.pydividing the solved weights by the engine's population growth. With aggregate growth on both legs the factor would be ×1.358373. microcosm has no matching uprating for other investment income.Yields
Savings rates 24/44/49% from 2027-28 (+2pp on each), October 2026 forecast scenario. Yield = change in
gov_balance.Each figure is a real policyengine-uk microsimulation run on the 28 Oct microcosm build (
microcosm_uk_2024_25.h5). The script and results are in~/reviews/savings-uprating-2024(scripts/run_yield.py,runs/*.json).When these ran: at fa17b34 against main at df239bc, before #2717 moved population and GDP per head to the October 2026 EFO. They show the size of the effect. At this head the per-person 2025-26 step is ×1.0032 rather than ×1.0035, and main's own GDP-per-head path has moved too, so a rerun would shift every row a little. The equivalence of undeclared data with main at this head is checked by the differential in the test plan.
runs/gated_undeclared.jsonagainstruns/main.json), and the declared emulated rebuild gives the yields of the same file run at 545129c, where the OBR rule applied to every dataset (runs/gated_rebuild_interest_declared.jsonagainstruns/branch_rebuild_interest.json).The Treasury's costing is +£520m in 2029-30.
--declare, it also writes theobr_savings_income_2022_2025declaration (scripts/emulate_rebuild.py;scripts/run_gated.shgives the command). A real microcosm rebuild recalibrates, so its figures will differ somewhat.CI
counted_once:state_pension: adding £1 to state pension raised Council Tax Benefit in fewer than 10 households. The certified dataset (uk-data 1.58.1) declares nothing, so at this head it projects as main does, and both pass (CI at fa17b34).Invariants
The tests check each of these:
obr.savings_incomeequals the OBR's published growth in each year from 2022 to 2031, to 4 decimals.obr.per_capita.savings_incomeequals (1 + savings income) / (1 + population) − 1, to 4 decimals.The aggregate index's 2025 / 2023 ratio equals the OBR's two-year product, 1.358379, within 1e-4.
growth_rate():obr_savings_income_2022_2025, returns the per-person savings series from 2022 to 2025 andobr.per_capita.gdpin every other year from 2015 to 2040;gdp_per_head, which is also its default, returnsobr.per_capita.gdpin every year from 2015 to 2040;uprating_indices.yamlmaps unchanged, and refuses an unknown rule;Projection under the OBR rule:
An undeclared dataset projects every savings input by GDP per head in every year from 2020-21 to 2027-28, under both ISA contracts.
For every singly-indexed input and every year to the projection horizon, one year's growth ratio is 1 + the index's growth (
test_projection_horizon, whose fixture declares the OBR rule and whose oracle applies it). Two exceptions are part of the test: residential stocks grow from the observed year by cumulative house prices, and the bereavement support split, stored complete in that fixture, stays flat.Property-based (Hypothesis), for any non-negative amounts, any year from 2015 to 2031, either ISA contract and any declaration (none,
gdp_per_head, the OBR rule):growth_rate()under that rule;The declaration round-trips, and data that comes back out projects by it, through each of these:
from_simulationand the full-variable export (calculate_all);filter_dataset);Datasetobjects.A file without the attribute reads as
gdp_per_head. Contradictions, unknown rules and mixed rules are refused, and an invalid rule given to an export (empty or false included) is refused rather than ignored.Test plan
pytest -n 4 policyengine_uk/tests/test_savings_income_uprating.py: 134 passed at 5ac2c7bscripts/differential_undeclared.py): undeclared datasets under both ISA contracts, five starting years and two random draws, projected to 2032, with seven calculated variables to 2035-36.ruff format --check,ruff checkchangelog.d/savings-income-obr-uprating.changed.mdReproducer:
policyengine_uk/tests/test_savings_income_uprating.py::test_projection_from_2023_24_reaches_the_obr_level_in_2025_26Review
Independent reviews by GPT-6.1 Sol, notes in
~/reviews/savings-uprating-2024/review/:#2717 (October EFO) merged at 1688f98 and moved
ons.populationfor 2022-2030 to the October 2026 EFO. This head merges it and re-derivesobr.per_capita.savings_incomefrom the new population (test_per_capita_savings_income_divides_by_populationchecks each year).axiom: n/a: dataset uprating and economic-assumption data, no policy rule
🤖 Generated with Claude Code