Repository navigation
Conversation
… simulable create_spi labels GORCODE 13 (address abroad), 14 (address unknown) and -1 (composite records) UNKNOWN. policyengine-uk 2.104.5 (PolicyEngine/policyengine-uk#1985) uprates their rent by the UK-wide index, so the dataset now simulates as built. Scottish taxpayer status comes from HMRC's SCOT_TXP flag rather than the region: the two disagree for 1,394 GORCODE-11 records and 606 Scottish taxpayers elsewhere on the 2022-23 tape. load_spi_dataset relabels UNKNOWN as SOUTH_EAST only on releases before 2.104.5, and ensure_spi_dataset rebuilds a cached H5 without the flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #540 round 1 (Sol 6.1, REQUEST CHANGES): - model_simulates_unknown_region now simulates one UNKNOWN household on the imported policyengine-uk instead of reading installed package metadata, which differs from the imported code under make data-local. Only a missing parameter for UNKNOWN reads as "cannot simulate"; any other error raises. - A differential test ties the probe to the release: where the imported model is the installed distribution, the probe agrees with version >= 2.104.5. - The unknown-region test xfails only on the missing private rent index for UNKNOWN; any other error fails it. - The region invariant test checks HMRC's codebook mapping written out in the test, not REGION_MAP itself. - New test: records whose SCOT_TXP agrees with their region keep exactly the income tax of the region-derived rule; the others change. - Income tax tests now cover 2030, the last year policyengine-uk carries dataset inputs to; the create_spi comment records that horizon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #540 round 2 (Sol 6.1, REQUEST CHANGES): - model_simulates_unknown_region no longer matches error text. A failure with region UNKNOWN reads as "cannot simulate" only if the same household labelled SOUTH_EAST simulates, which is exactly when the loader's relabel helps; otherwise the control's error is raised. The household mirrors the SPI-shaped fixture policyengine-uk's own test_rent_uprating.py simulates. (ParameterNotFoundError.name is reset to None by AttributeError.__init__, so the parameter name is not available to match.) - Probe tests cover unrelated parameters ending in .UNKNOWN, unrelated parameters and schema errors (all raised), and a non-parameter failure that relabelling cures (read as "no"). - The release differential test runs only for an unmodified final release installed from a package index and imported from there: it skips pre, dev and local versions, direct-URL or editable installs, and an economic_assumptions.py that does not match the release's RECORD. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aises Review of #540 round 3 (Sol 6.1, APPROVE WITH CHANGES, three nits): - The probe docstring says what it establishes: that the imported model can build a simulation of an SPI-shaped household in Region.UNKNOWN. - The release differential test describes its checks as they are: a final release not installed from a direct URL, whose imported economic_assumptions.py is the installed file and matches its RECORD. - The shared-failure test gives the UNKNOWN and SOUTH_EAST households distinct errors and requires the control's, with the UNKNOWN error as its context. A mutant that re-raised the UNKNOWN error had survived. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
Hand-off to the UK hub
|
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.
Fixes #505.
What and why
create_spilabels SPI records whoseGORCODEis not a UK region asUNKNOWN. HMRC's codebook (SN 9422, Annex A) gives these codes as 13 "Address abroad" and 14 "Address unknown or not available", and composite records carry -1. On the 2022-23 public use tape that is 18,859 of 836,850 records: 16,194 abroad, 353 unknown and 2,312 composite. Two things were wrong:Microsimulation(dataset=create_spi(tab, 2022))failed while the simulation was being built, withParameterNotFoundError: ... private_rental_prices.UNKNOWN. policyengine-uk 2.104.5 (released 2026-10-02) is the first release with Uprate rent for unknown-region households by the UK-wide index policyengine-uk#1985, which uprates rent forRegion.UNKNOWNby the UK-wide index. With it,UNKNOWNsimulates as built.UNKNOWNstays the right label: none of these records has a UK region, and relabelling them would distort regional totals.SCOT_TXP("Indicates whether the record is treated as Scottish, for tax purposes"), and its liabilities (TOTTAX_DEVO_TXP,TAX_ST_*_DEVO_TXP) are "based on SCOT_TXP or WELSH_TXP". The dataset didn't carry the flag, so policyengine-uk derivedpays_scottish_income_taxfrom the region. The two disagree for 2,000 records. HMRC notes that the region reflects residence at the end of the tax year, which can differ from Scottish taxpayer status. Records with an address abroad and composite records can also be Scottish taxpayers. HMRC built composites from Scottish, Welsh or other taxpayers only, never a mix.Changes:
datasets/spi.py:pays_scottish_income_taxfromSCOT_TXP. HMRC documents the flag as.or1, but the 2022-23 tape holds0or1; both parse.WELSH_TXPis not used, because policyengine-uk has no separate Welsh rates of income tax (no Welsh parameters undergov.hmrc.income_taxin 2.93.0 or 2.121.0).REGION_MAPcomment now gives the codes correctly.model_simulates_unknown_region(). It checks that the imported policyengine-uk can build a simulation of one SPI-shaped household labelledUNKNOWN, once per process (a few seconds). A failure means "cannot simulate" only if the same household labelledSOUTH_EASTbuilds, which is exactly when relabelling helps. Otherwise theSOUTH_EASThousehold's error is raised.test_rent_uprating.pysimulates withUNKNOWN, so the model keeps supporting that shape.make data-localputs a policyengine-uk checkout onPYTHONPATHahead of the installed release.utils/incomes_projection.py:load_spi_datasetrelabelsUNKNOWNasSOUTH_EASTonly when the imported model cannot simulateUNKNOWN. This replaces create_spi output cannot be simulated: unknown regions crash policyengine-uk, and Scottish taxpayer status ignores SCOT_TXP #505's to-do ("remove the relabelling once the lock reaches #1985"): the relabel switches itself off at the relock, with no follow-up PR.ensure_spi_datasetalso rebuilds a cachedspi_2022_23.h5that predates the flag. Without that, a stale cache would silently fall back to deriving the flag from the region.Horizon. policyengine-uk carries a single-year dataset's inputs forward to 2030 (
extend_single_year_dataset,end_year=2030, in 2.93.0 and 2.121.0). From 2031 it computespays_scottish_income_taxfrom the region again, as it does for any input variable that has a formula. A two-person synthetic check on both releases confirmed this: the input held in 2029 and 2030, and the region rule replaced it in 2031 and 2032. The tests cover 2022, 2026 and 2030.Evidence
Everything here is aggregate. No count under 10 records is shown. Each count is at least 10, and so is its complement within its group. A count of zero counts as under 10, so no figure here implies one by differencing.
HMRC tape documentation for 2022-23 (SN 9422) and 2020-21 (SN 9121) covers:
GORCODEcodes 1-14;SCOT_TXPandWELSH_TXPdefinitions;SCOT_TXPis on both tapes.Tape counts (2022-23):
GORCODE11 but not a Scottish taxpayer: 1,394 records (90,000 weighted).GORCODE11: 606 records (33,000 weighted).Historical income-tax evidence against HMRC's own
TOTTAX_DEVO_TXP, on the full 2022-23 tape, collected before this refresh. Both datasets ran under policyengine-uk 2.104.5, because the pre-batchcreate_spioutput could not be simulated under the then-locked 2.93.0. "main" in this historical table iscreate_spiat b45c373. Groups overlap. The within-£10 column is a weighted share (byFACT), not a share of records.GORCODE11, not a Scottish taxpayerGORCODEnot 11GORCODE13, 14 or -1git tag --containsputs it first in 2.104.5, and 2.104.4 does not contain it. PyPI shows 2.104.4 uploaded at 11:12 UTC and 2.104.5 at 11:15 UTC that day. The latest release when these tests ran (2026-10-05) was 2.122.0.Invariants
GORCODEalone. Codes 1-12 map to HMRC's regions, and every other code becomesUNKNOWN. Every label is aRegionmember, and Scottish taxpayers with codes 13, 14 or -1 are not relabelledSCOTLAND.pays_scottish_income_taxis true exactly whenSCOT_TXPparses to 1, whatever theGORCODEand however "no" is written (.,0or blank).SCOT_TXP. This holds in the data year, after uprating (2026), and in 2030, the last year policyengine-uk carries dataset inputs to.UNKNOWNor with the legacySOUTH_EASTgives equal income tax record for record, in 2022, 2026 and 2030.SCOT_TXPagrees with their region get exactly the income tax of the old region-derived rule, so only records where the two disagree can change. This is tested in 2022, 2026 and 2030.model_simulates_unknown_region():UNKNOWNhousehold fails and the same household labelledSOUTH_EASTsimulates;SOUTH_EASThousehold's error when both fail;economic_assumptions.pyis the installed file and matches itsRECORD, is true exactly when that release is 2.104.5 or later.load_spi_datasetchanges a region label only when the imported model cannot simulateUNKNOWN. A cached H5 is reused only if it has the release year and the flag.Tests
All in
tests/test_spi_build.py.SCOT_TXPis added to the fake-tape columns there and intests/test_spi_allowance_deductions.py.test_create_spi_region_and_scottish_taxpayer_invariants[0|.|blank](invariants 1-2): exhaustive overGORCODE∈ {-1, 1…14, 99} ×SCOT_TXP∈ {no, 1}, for each encoding of "no". It checks against HMRC's codebook mapping written out in the test, notREGION_MAP.test_create_spi_scottish_taxpayer_status_survives_h5_round_trip: the flag andUNKNOWNsurvivesaveand load.test_spi_income_tax_follows_scottish_taxpayer_flag(invariant 3): uses the legacy label, so it also runs under the historical 2.93.0 lock.test_spi_income_tax_changes_only_where_scottish_flag_and_region_disagree(invariant 5): compares against the same dataset with the flag column dropped, which is main's rule. It usesUNKNOWNwhere the model can simulate it, andSOUTH_EASTotherwise.test_create_spi_output_with_unknown_region_can_be_simulated(invariant 4): uses the defaultUNKNOWNlabel. Where the imported model cannot simulateUNKNOWN, the build must raiseParameterNotFoundErrorforprivate_rental_prices.UNKNOWNand nothing else, and the test reports an xfail. Otherwise it runs in full.test_unknown_region_probe_reads_the_simulation[…]: three cases. It simulates; the rent index is missing; or a non-parameter error that relabelling cures. In each case the test checks which households were simulated.test_unknown_region_probe_raises_failures_relabelling_would_not_cure[…]: an unrelated parameter ending in.UNKNOWN, an unrelated parameter, and a schema error, each also hitting theSOUTH_EASThousehold. Each must be raised as theSOUTH_EASThousehold's own error, with theUNKNOWNhousehold's distinct error as its context.test_unknown_region_probe_agrees_with_policyengine_uk_release. It skips:economic_assumptions.py(the module #1985 changed) that does not match the release'sRECORD.test_income_projection_loads_local_h5_dataset[False|True]test_income_projection_rebuilds_spi_dataset_without_scottish_flag, which uses real H5 files.Historical results from before this refresh (both files,
TESTING=1, old PR head4f86ceeeb844d6fcd76f8da7e68e5d2a8062525c; these were not rerun in this refresh session):PYTHONPATHPYTHONPATHThe last two rows are the version skew the first review found; the old gate read the metadata there and got the answer wrong both ways. The
RECORDcheck was also exercised directly on a 2.121.0 install. It reports a mismatch after a one-line edit toeconomic_assumptions.py, and a match once the edit is removed.Historical mutation checks from before this refresh. Each had to fail at least one test; all ran under the then-locked 2.93.0 unless noted. These mutation checks were not rerun in this refresh session.
GORCODE == 11(main's rule)UNKNOWNSCOTLANDSCOT_TXPas ScottishSCOT_TXPwithout parsing itREGION_MAP[8]set toLONDON(survived round 1)UNKNOWNhousehold's error instead of the control's (survived round 3)policyengine-uk lock
The 10/8 release batch landed on 10/7 as uk-data 1.58.0. Its main head
4cbedbecca352f52dffe47e849553c2759a00711pins policyengine-uk 2.122.2 and policyengine-core 3.32.13. This refresh inherits those lock and dependency changes by merging main. A separate one-line fix aligns the lock's local-project version withpyproject.tomlat 1.58.0; no dependency version changes.2.122.2 is newer than the first UNKNOWN-capable release, 2.104.5. The refreshed validation below confirmed that the unknown-region simulation test passes in full,
model_simulates_unknown_region()is true, andload_spi_datasetkeepsUNKNOWNwithout the legacySOUTH_EASTrelabel. The compatibility fallback remains for imported models that cannot simulateUNKNOWN.Impact: pending a real rebuild after the refresh
No dataset was rebuilt in this refresh. The other item owed after the batch remains a hub-scheduled rebuild of
storage/spi_2022_23.h5and the enhanced FRS, compared with the 1.58.0 batch outputs. From reading the code, no change to the enhanced FRS is expected:create_datasets.pydoes not callcreate_spi,ensure_spi_dataset,load_spi_datasetormodel_simulates_unknown_region.datasets/imputations/income.py. It importsAGE_RANGES,REGION_MAPand the release names fromspi.py, and none of them changes in this PR. The enhanced-FRS synthetic rows retain the FRS donor's geography.storage/spi_2022_23.h5. Onlyensure_spi_dataset(throughload_spi_dataset) andspi.py's__main__build it, and no step of the enhanced FRS build reads it.The real rebuild should confirm this expectation and quantify the refreshed standalone SPI impact. It remains pending on the hub's schedule, with one build at a time and more than 60 GB free on the host.
Post-batch refresh (10/7)
4fbd8be43b7dc550d9f6c2871ddc7270fec38c01, a fast-forward descendant of4f86ceeeb844d6fcd76f8da7e68e5d2a8062525c, with main4cbedbecca352f52dffe47e849553c2759a00711as the merge commit's other parent.tests/test_spi_build.pyby keeping both the PR's region/flag invariants and unknown-region simulation tests and Supply is_claimant_or_partner from the FRS adult table #524'stest_create_spi_marks_every_taxpayer_as_their_benefit_units_claimant. The shared fake-tape helper supplies bothSCOT_TXPand main'sSEINC_NUM.a22fb5daffa29d0ff28dc27d70775285202ff3f2: all 70 main-only paths matched main exactly, all three PR-only paths matched the old PR head, and both shared files preserved each branch's changes. No main or PR hunk was dropped; no conflict markers remain. The only follow-up changesuv.lock's local-project version from 1.57.4 to main's released 1.58.0, fixinguv lock --checkwithout changing any dependency pins.Ran the following under policyengine-uk 2.122.2 / policyengine-core 3.32.13, with
TESTING=1, sequentially in the foreground, one file per pytest process,-p no:cacheproviderand no xdist. All paths are underpolicyengine_uk_data/tests/.test_spi_build.pytest_spi_allowance_deductions.pytest_spi_donor_benefit_rules.pytest_spi_income_earnings_groups.pytest_claimant_or_partner.pyTotal: 73 passed, 3 skipped, 0 failed, 0 xfailed. The three skips need a built FRS/enhanced FRS. The unknown-region simulation test passed in full, rather than xfailing. A separate synthetic-H5 check used the real capability probe: it returned true,
load_spi_datasetkeptUNKNOWNwithout relabelling toSOUTH_EAST, and the Scottish flag was preserved.uv lock --checkpassed after the one-line metadata fix; the four PR Python files passruff format --check.The initial harness attempt had 15 passes and 18 setup errors because the configured temporary-directory parent was missing. Creating it fixed the harness; the final SPI run above has no errors. No source changes were needed for that rerun.
Review
Independent GPT-6.1 Sol reviews.
Round 1 (9fc0e29): REQUEST CHANGES.
private_rental_prices.UNKNOWNindex;Round 2 (3c37ef8): REQUEST CHANGES. Round 1 findings 2-4 resolved; 1 and 5 partly.
.UNKNOWN'in the message. It now uses a known-region control instead of matching text.error.name, but that cannot work:AttributeError.__init__resetsnameto None, so everyParameterNotFoundErrorcarriesname=None(checked on Python 3.13).UNKNOWN, and a failure that theSOUTH_EASThousehold shares is raised.Round 3 (c65d381): APPROVE WITH CHANGES. Round 2 findings 1, 2 and 4 resolved; 3 partly. All seven result rows were independently reproduced. The disclosure audit passed (93 assertions). Three nits remained, all fixed at 4f86cee:
economic_assumptions.pyinstalled and matchingRECORD.UNKNOWNerror) now fails 3 tests.Round 4 (
4f86ceeeb844d6fcd76f8da7e68e5d2a8062525c): APPROVE. This was the last independent Sol review before the batch refresh, as recorded by the hub; CI was green at that head. No new independent review is claimed for the refreshed head. The hub dispatches the delta review after the refresh push.Release
Held out of the 10/8 batch, which landed on 10/7 as uk-data 1.58.0. This PR is for the next uk-data batch, on Max's go (d833). The refresh does not merge the PR or request or dispatch reviews; the hub dispatches the delta review after the push.
This PR was rescued from an interrupted session's uncommitted patch and finished here. Changes from that patch:
.-encoding test is folded into the exhaustive invariant test.axiom: n/a: SPI dataset construction, no policy rule changes
🤖 Generated with Claude Code