Repository navigation
Conversation
PolicyEngine UK's year N is tax year N to N+1: convert_to_fiscal_year_parameters gives every parameter, the uprating indices included, its value at 30 April N. hmrc_spi mapped the 2023-24 SPI to 2024 and hmrc_cgt followed, so both treated 2023-24 outturns as 2024-25 values and every projected year carried one year too little uprating. The FRS release (2024-25 -> 2024), OBR targets, the SPI microdata (2022-23 -> 2022) and #533's salary sacrifice tables already use the start year; the end-year mapping arrived without stated reasoning in the February 2026 registry refactor (de061fc). - _SPI_YEAR and _CGT_BASE_YEAR move to 2023; incomes_projection.csv is regenerated from the same ODS (its 2023 rows equal main's 2024 rows). - The CGT base year is now gated at the £6,000 AEA the outturn was realised under. - Tests tie _SPI_YEAR to the tax year in the configured ODS name, check the CGT base-year AEA against policyengine-uk, and (as in #541) check that the committed projection is the SPI table uprated with the committed factors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build_loss_matrix fetches targets without a year filter, and the registry keeps only the first target of each name. hmrc_spi emitted each band twice: the ODS outturn (base year only), then the projection (later years). The projected target was dropped, so _resolve_value carried the base-year outturn forward unuprated: the 2025 calibration has targeted raw 2023-24 SPI values since the February 2026 registry refactor. get_targets now merges the two into one target per band, and a test checks every projected band, variable and year through the calibration's own resolution path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Problem
The 2025 calibration has targeted the HMRC Survey of Personal Incomes (SPI) income bands at their raw 2023-24 values, with no uprating at all. Two bugs combine.
1. Projected SPI targets never reached calibration.
targets/build_loss_matrix.pyfetches targets withget_all_targets(geographic_level=...), without a year filter.targets/registry.pykeeps only the first target of each name.hmrc_spi.get_targets()emitted each band twice: the ODS outturn (base year only), then theincomes_projection.csvprojection (later years) under the same name._resolve_value(target, 2025)fell back to the nearest earlier year: the outturn, unuprated.A seeded build of main shows it. The 12,570–15,000 employment-income band is calibrated to £16.90bn, the raw ODS value, while
get_all_targets(year=2025)gives the projected £17.53bn. Year-filtered callers and the tests saw the projection; calibration did not. This has been the case since the February 2026 registry refactor (de061fc), which introduced both the unfiltered first-wins fetch and the split targets.2. The SPI and CGT outturns were mapped one year late.
convert_to_fiscal_year_parameters(policyengine_uk/utils/parameters.py) gives every parameter, including the uprating indices behindstorage/uprating_factors.csv, its value at 30 April N. Socgt.annual_exempt_amountis £6,000 in 2023 and £3,000 in 2024.hmrc_spi.pymapped the 2023-24 SPI to_SPI_YEAR = 2024("mapped to calendar 2024"), andhmrc_cgt.pyfollowed with_CGT_BASE_YEAR = 2024. The projection, the local-area income scaling (local_income.get_national_income_projections) and the CGT gains targets therefore carried one year too little growth.datasets/frs_release.py), the SPI 2022-23 microdata isSPI_FISCAL_YEAR = 2022, OBR FY 2025-26 is 2025, and Restore HMRC salary sacrifice relief calibration targets #533's salary sacrifice tables check that "2024 to 2025" is 2024.fiscal_year=2020.Fix
hmrc_spi.get_targets()returns one target per band holding the base year and every projected year (_merge_by_name, which_read_projection_csvalready used within the projection)._SPI_YEAR = 2023and_CGT_BASE_YEAR = 2023, with comments that state the convention.storage/incomes_projection.csvis regenerated from the same ODS.incomes.csvcomes out byte-identical, and the new 2023 rows equal main's 2024 rows.Target changes, as the calibration resolves them (2025)
These come from
build_loss_matrix's own path: the unfiltered registry plus_resolve_value. Of the 645 national targets that resolve for 2025, exactly 166 change. All of them are SPI band targets (13 bands × 6 incomes × amount and count) or CGT gains targets.hmrc/capital_gains_totaland the 9 CGT band gainsEach SPI change is the variable's uprating index for 2025 divided by that for 2023, which is two years of growth. The CGT change is one year of growth, because CGT targets already listed every year. The constituency and local authority employment and self-employment targets are scaled to the projection's 12,570-and-over row, so they rise by one year of growth (+4.65% and +4.78%).
Calibration fit
CALIBRATION_FIT_PENDING
Invariants
build_loss_matrix's path (unfiltered registry plus_resolve_value) equalsincomes_projection.csv, with the 1.9x scale on property income.hmrc_spiemits each name once. This istest_calibration_reads_the_projected_spi_targets; without the merge it fails (312 names, 156 unique).test_spi_year_is_the_start_of_the_configured_tax_yearreads the year from the configured ODS name (..._2324.ods).test_base_year_is_the_tax_year_the_outturn_was_realised_inreads the AEA from the real policyengine-uk parameter tree: £6,000 at_CGT_BASE_YEAR, £3,000 a year later.test_projection_is_the_spi_table_uprated_with_the_committed_tableis a differential test: it regenerates the projection fromincomes.csvanduprating_factors.csvand compares every cell. It is byte-for-byte the same test as in Regenerate the uprating factor table from policyengine-uk's load-time uprating #541, so the two PRs merge cleanly in either order; whichever lands second regeneratesincomes_projection.csv.Not changed
ons_demographicsalso emits three duplicate names. The secondons/uk_populationis in thousands. The two registry paths disagree only for 2023, which nothing calibrates to. This is reported to the UK hub separately.hmrc_salary_sacrifice.pywas handled by Restore HMRC salary sacrifice relief calibration targets #533.axiom: n/a: data (calibration target years)
🤖 Generated with Claude Code