Repository navigation
Conversation
… uprating storage/uprating_factors.csv was last built on 2026-05-20 from each variable's `uprating` attribute. policyengine-uk 2.93.0 projects a saved dataset with the year-on-year growth parameters in data/uprating_indices.yaml instead, so the build calibrated 2025 values the model does not run on: earnings were uprated 3.7% for 2024-25 against the engine's 5.3%, self-employment income 4.7% against 0.7%, and GDP-indexed incomes and wealth 2.8% against 4.4%. Regenerating with the old generator would not fix it: on 2.93.0 it emits no employment_income row (an `adds` formula with no attribute) and indexes savings interest by household interest income where the engine uses GDP per head. The generator now compounds the growth parameter each variable is listed under in uprating_indices.yaml, keeps the household-weight and road-fuel overrides, and stores six decimal places. The table loses four rows the engine does not uprate at load (benunit_rent, housing_service_charges, pension_contributions_via_salary_sacrifice, water_and_sewerage_charges) and gains bus_fare_spending and private_pension_wealth. incomes_projection.csv is regenerated: the table is its only input that changed (rebuilding it from main's table reproduced main's file exactly). compute_ss_headcount deflated contributions through the salary sacrifice row, which no longer exists; the loss matrix would have skipped all three headcount targets. It now classifies contributions as simulated in the calibration year, the values test_salary_sacrifice_headcount checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r1 of #541: policyengine-uk also reassigns student loan plans at load and zeroes the repayments of loans it writes off, which the table's single average-earnings index cannot represent. The generator docstring and the test module now name it beside council tax and rent, and a test projects a kept Plan 2 loan and a written-off Plan 1 loan through both paths: repayments agree for the first, and only the engine zeroes the second. The docstring also states when policyengine-core applies a variable's `uprating` attribute: only to periods with no stored value. A second test fails if the table gains a salary sacrifice row, as policyengine-uk#1863 would add. The headcount targets then move from survey-year to nominal calibration-year contributions, a methodology choice to make at that point. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r2 of #541: policyengine-uk carries ages unchanged at load, so ages taken from the base year put the engine's university starts at 2013 and 1983 rather than the 2012 and 1982 the comment names. Deriving them from the projected year makes the cohorts exact; 2012 is the first Plan 2 year. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge uk-data 1.58.0 (4cbedbe) into uprating-factors-v2. Resolve both uprating CSV conflicts with the PR generator under policyengine-uk 2.122.2 and core 3.32.13, retaining pension_credit_reported_capital. Regenerate the HMRC income projection and retire the obsolete attribute-based three-decimal pension-capital assertion in favour of all-row engine checks. Preserve restored salary-sacrifice relief, the direct 2,000 pound headcount cutoff and d979 guard, and the batch pension-age mask pipeline wiring.
The release updated pyproject.toml but left the editable package version at 1.57.4 in uv.lock, making uv lock --check fail. Update only that metadata entry; dependency versions and hashes, including UK 2.122.2 and core 3.32.13, remain unchanged.
21 of 52 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Post-batch refresh (10/7)
New head:
31888a2d52aaf49bd860941a9d5e1ad111275d1d, a fast-forward descendant ofc4c24a261e123d39f953201718551fcb0d17fa18. The merge commit has post-batch main4cbedbecca352f52dffe47e849553c2759a00711as a parent. The planned 10/8 batch landed on 10/7 as 1.58.0; this PR remains for the next batch, on Max's go (d833).uprating_factors.csvanduprating_growth_factors.csv, were resolved by running this PR's generator under policyengine-uk 2.122.2 / core 3.32.13, never by hand-merging. There are 84 rows; Use the FRS benefit-unit capital for Pension Credit's capital test #513'spension_credit_reported_capitalsurvives under GDP per head. Its obsolete attribute-based three-decimal row test is retired in favour of the tests of every row against the engine; all six other capital tests remain.incomes_projection.csvwas regenerated from the live HMRC SPI ODS. Main's, the old PR's and the refreshed tables reproduce their corresponding projection byte for byte. Against main, all 588 amount cells follow their factor ratio within £1 rounding (maximum error £0.95419312), with zero changed count cells or 2024 cells. The refreshed projection is byte-identical to the old PR's. Re-running both generator entrypoints at the new committed head reproduces both factor tables, the projection and the unchangedincomes.csvbyte for byte.pyproject.toml1.58.0). A separate one-line fix aligns it to 1.58.0; every dependency version and hash is unchanged.uv lock --checkpasses. Python is 3.13.9. Ruff checks and formatting pass for all eight changed Python files; conflict-marker and whitespace checks pass.Each requested test file ran sequentially in its own foreground pytest process, with
-p no:cacheproviderand no xdist:test_uprating_factors_table.pytest_income_projection.pytest_road_fuel_volume_uprating.pytest_salary_sacrifice_headcount.pytest_uprating_range.pytest_pension_credit_reported_capital.pytest_hmrc_salary_sacrifice_targets.pyTotal: 88 passed, 4 skipped, 0 failed, 0 errors. The four skips require built enhanced FRS. The table/headcount pair remains 32 passed, 3 skipped, matching the recorded r3 run at the old head. The runbook's #532 SPI-rebasing and #506 boarder/lodger test files are absent because those drafts are not on main.
Impact remains pending a real rebuild after this refresh. No dataset build was run. The hub schedules that rebuild and the delta review. Evidence is untracked under
_build/hub-evidence/in the assigned workspace.What was wrong
The diagnosis and numerical comparisons below record the original pre-batch evidence at policyengine-uk 2.93.0. The refreshed branch uses the post-batch lock (policyengine-uk 2.122.2 / core 3.32.13); current results are recorded in the "Post-batch refresh (10/7)" section.
storage/uprating_factors.csvwas last built on 2026-05-20, from each variable'supratingattribute.uprate_datasetuses it to move the FRS 2024 build to the 2025 calibration year and back.uprate_valuesuses it to project the HMRC SPI income targets (incomes_projection.csv) and the CGT targets.The original locked policyengine-uk (2.93.0) moves a saved dataset differently.
Simulationcallsextend_single_year_dataset, which projects the file to 2030. Each year,apply_single_year_upratingmultiplies every variable listed indata/uprating_indices.yamlby one plus the year-on-year growth parameter it is listed under. Variables the file doesn't list are carried forward unchanged. Three load-time changes are not single indices:policyengine-core's attribute-based uprating (
simulation.py, thevariable.upratingbranch) runs only for periods with no stored value. For a dataset column, that means after 2030.So calibration fitted 2025 values the model does not run on. Growth from 2024 to 2025 (
check/table_evidence.out):obr.average_earnings)obr.per_capita.mixed_income)obr.per_capita.gdp)obr.per_capita.gdp)obr.private_pension_index)obr.consumer_price_index)Rerunning the old generator wouldn't fix it. On 2.93.0 it emits no row for
employment_income(anaddsformula with no attribute),capital_gains,state_pension,state_pension_reported,employee_pension_contributionsorstudent_loan_repayments. A missingcapital_gainsrow silently truncates the CGT target projection. It also indexes savings interest by household interest income (×2.27 for 2022→2024), where the engine uses GDP per head (×1.092). policyengine-uk#1862 tracks that attribute mismatch and the four declared-but-unlisted variables.Over the 75 rows main and this PR share, the 2024→2025 factor moves by between −3.8% and +1.6%. Only 3 rows are unchanged to 0.1%.
The change
Generator (
utils/uprating.py).policyengine_uk_load_time_index()compounds the growth parameter each variable is listed under in the locked policyengine-uk'suprating_indices.yaml. It raises if a variable is listed twice.build_uprating_factors_table()then applies the existing household-weight and road-fuel litre-proxy overrides. Levels are stored to six decimal places: at three, rounding alone moved a year-on-year factor by up to 0.07%.Table. On the refreshed 2.122.2 lock, it drops five rows from post-batch main that the engine does not list for load-time uprating:
benunit_rent: anaddsaggregate, so no stored column changes;domestic_energy_consumption: absent from the locked engine's load-time index list;housing_service_charges,pension_contributions_via_salary_sacrificeandwater_and_sewerage_charges: policyengine-uk carries these unchanged, so calibration now does too.Relative to post-batch main it gains
bus_fare_spendingandbus_fare_spending_reported(CPI),private_pension_wealth,capital_gains_badr,capital_gains_carried_interest,capital_gains_residential_property,household_lifetime_isa_balanceandlifetime_isa_balance(GDP per head), anddomestic_rates(Northern Ireland domestic rates). Use the FRS benefit-unit capital for Pension Credit's capital test #513'spension_credit_reported_capitalrow is retained (GDP per head). The table has 84 rows.incomes_projection.csvis regenerated (see the check below).Salary sacrifice headcount targets (
targets/compute/income.py).compute_ss_headcountused to deflate contributions to 2023 prices through the salary sacrifice row before applying the £2,000 cap. That row no longer exists. The loss matrix catches the resultingKeyErrorand skips the target (build_loss_matrix.py, "Skipping target"), so all three headcount targets would have dropped out.It now classifies the contributions the calibration-year simulation holds, which on the refreshed 2.122.2 lock are still the 2024-25 survey amounts. These are the values
test_salary_sacrifice_headcount's built-data tests already classify. The cutoff moves. Main's uprating and deflation together compared survey amounts with an effective £2,092.95 (2,000 × 1.261 / 1.205, main's 2024 and 2023 rows), a leftover of the 2023-24 base year. This PR compares them with £2,000.There are two readings: survey-year contribution groups, or nominal calibration-year contributions. Today they coincide, because policyengine-uk carries this variable unchanged at load. They separate by a year of earnings growth once policyengine-uk#1863 makes the engine uprate it. A new test (
test_calibration_year_contributions_are_the_survey_amounts) fails when the table gains that row, which forces the choice at that point. The choice is queued for Max as d979, recommending survey-year groups because the targets grow both groups at the same rate.create_datasets.py: a comment only.rail_usageis not re-uprated at load, andbus_fare_spendingnow moves by the same CPI factor both ways.incomes_projection.csv: the table is the only cause of the change
check/incomes_projection_check.pyand its.outfile record this:project_income_tableon the committedincomes.csv, rebuilds main'sincomes_projection.csvbyte for byte.incomes_projection.csvbyte for byte. As a control, main's table does not reproduce this PR's file.incomes.csvequals the live HMRC SPI table thatcreate_income_projections()downloads.incomes_projection.py,hmrc_spi.py,spi.pynorincomes.csv.All-band totals, this PR / main, 2025 and 2029:
Invariants, tested
tests/test_uprating_factors_table.pychecks these for every row, and for every year or pair of years:extend_single_year_datasetprojects a dataset of ones from 2020 to 2034 and agrees with the table for every row and year. Fuel spending × household weight agrees with the engine's.uprate_datasetfrom the FRS base year gives what policyengine-uk's scalar YAML growth gives in each year from 2024 to 2030. That includes the four columns both paths carry unchanged.test_student_loan_plan_reassignment_is_outside_the_table).uprate_datasetthere and back between any two of the 15 years is the identity (225 pairs).household_weight,capital_gains) has a row.test_income_projection.pyadds a check thatincomes_projection.csvis the committed SPI table projected with the committed table.test_salary_sacrifice_headcount.pyadds two tests:Tests before the refresh
The pre-refresh author and reviewers recorded the following results. On Python 3.13 with the then-locked environment (
uv sync --frozen --extra dev; no lock change):test_uprating_factors_table,test_income_projection,test_road_fuel_volume_uprating,test_salary_sacrifice_headcount,test_uprating_range,test_spi_build,test_property_income_targets,test_frs_prerequisites,test_local_la_extras,test_la_loss_missing_sourcesandtest_hmrc_cgt_targetsgive 116 passed, 4 skipped at cc5b4c1. The skips are the four tests that need the built enhanced FRS. At 8d8b608, before the two tests review r1 prompted, the same set gave 114 passed, 4 skipped. c4c24a2 changes only that fixture's ages, and its two test files give 32 passed, 3 skipped.test_spi_income_rebasing(Hypothesis) andtest_cgt_band_donors, plus this PR'stest_uprating_factors_tableandtest_income_projection, give 56 passed, 5 skipped (built-data gated).ruff format --checkandruff checkon the changed files are clean.How this interacts with #532 (SPI income rebase)
The pre-batch trial merge with #532 was clean. #532 did not enter the batch: it remains an open, conflicting draft. The intended interaction is by design. #532's
rebase_spi_drawsreads this same table throughuprate_values, so once both are on main the SPI draws follow the regenerated table. That is the follow-up #532 lists. The pre-batch 2022→2024 factor comparison was:These are the "policyengine-uk at load" column of #532's own table. Gift Aid and qualifying-investment gifts stay nominal, as #532's
SPI_NOMINAL_IMPUTATIONSexpects.Order: whichever of #532 and this PR lands second must regenerate both uprating tables and
incomes_projection.csvwith the locked engine. #532 is still an open draft and was excluded from the batch. Its earlier measured impact used the old table; this PR's pending rebuild will measure the refreshed branch against post-batch main, which does not include #532.Batch and later interactions
The planned 10/8 batch landed on 10/7 as uk-data 1.58.0 (
4cbedbecca). #513 landed and introduced the pension-capital row conflict in both uprating CSVs. #501, #506 and #528 remain open drafts; their table-row interactions below are future dependencies. #533 and #529 landed, and their source/test changes are preserved in the refresh.pension_credit_reported_capitalobr.per_capita.gdp(2.107.0 and main)rent_paid_as_boarder,rent_paid_as_lodgerprivate_pension_wealthobr.per_capita.gdp(already locked 2.93.0; this PR adds the row)cash_isa,directly_held_shares,stocks_and_shares_isa,unit_and_investment_truststrading_lossThe refresh resolves the two table conflicts by regenerating both files, never by hand-merging them. Each row then follows the engine the lock points to: rows whose variables the locked engine lists are kept, and the rest are carried unchanged, as the engine carries them.
The refresh retires #513's
test_uprating_rows_match_the_model, which expected the attribute-based 3-decimal row, in favour of this PR's tests of every row against the engine. All other pension-capital tests are retained. #506's futuretest_rent_paid_is_uprated_*tests require the PE-UK #2002 rows in the lock; #506 is not on main.Impact
Impact: pending a real rebuild after this refresh on post-batch main (disk rule). No dataset was built for this PR or during the refresh. Runbook:
~/reviews/uk-hub/jobs/rescue-uprating-factors/rebuild/README.md. With the refreshed branch and regenerated tables:run_build.shholds the host-wide build lock and runs one build at a time. It needs more than 60 GB free.Known gaps, unchanged here
These three load-time changes are not single indices, and neither this table nor main's covers them:
council_taxby country andrentby region and tenure at load. Calibration therefore sees their 2024 amounts in 2025 (NOT_SINGLE_INDICESin the test module).test_student_loan_plan_reassignment_is_outside_the_tablepins exactly where the two paths diverge.Review
~/reviews/uk-hub/jobs/rescue-uprating-factors/review/review-r1.md. It reproduced the byte-for-byte projection check, every evidence number and the Rebase SPI income draws to the FRS survey year #532 merge (56 passed), and killed two generator mutants.private_pension_wealthrow is corrected.review/review-r2.md. All four r1 findings resolved. One NIT: the student loan fixture's ages gave 2013/1983 starters rather than 2012/1982. Fixed in c4c24a2.review/review-r3.md).This PR was excluded from the batch that landed on 10/7 as 1.58.0. It remains for the next uk-data batch, on Max's go (d833).
axiom: n/a: uk-data input construction, no policy rule encoded.
🤖 Generated with Claude Code