Repository navigation
Conversation
UK year files were reused whenever they recorded a data year, so files cut from one release of a dataset were reused after the bundle re-pinned its name, after a local source was edited, and after a policyengine-uk or policyengine-core upgrade. create_datasets now stamps each UK year file, after its tables and data year, with the source identity, the installed policyengine-core and policyengine-uk versions and a year-file format revision. ensure_datasets cuts the files again and load_datasets refuses them unless the stamp equals the one a file cut now would carry, reading only the stamp and the data year's presence. The country-neutral pieces of the US stamp (JSON node I/O, installed versions, source and package reasons, the shared-stem refusal) move to policyengine.provenance.year_file_stamp, which both countries use. Fixes #600 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ensure/load Hypothesis and example tests in the style of test_us_year_file_stamp.py, with the data year as an extra dimension, plus a differential test that the UK and US checks name source and package differences alike. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Integration tests over policyengine-uk on the seven-person 2024 dataset: a re-pinned bundle dataset, an edited local source, a package upgrade, a source replaced mid-cut and year files from an earlier release are each cut again, then reused. Property tests for the shared stamp module, and the UK year-file section of docs/microsim.md now describes the stamp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the independent review of 4325d16: - create_datasets writes each UK year file to a temporary file beside its path, and stamps and moves it into place only after the source re-check, so two concurrent cuts can no longer leave one cut's tables under the other's stamp, and a failed cut leaves the existing files whole. - The UK stamp records the year it was cut for, so a copied or renamed year file is not taken for another year. - UK stamps live in their own H5 dataset (policyengine_uk_year_file_stamp), so a US stamp never passes as a UK one, nor the reverse; the US keeps its name and its read/write wrappers. - A stamped file without a data year gets its own reason. - Tests: two sources cut in one call, interleaved cuts, a failed cut over existing files, publish ordering, year mismatch, country isolation; the ensure-warning check no longer uses the checker as its oracle. - Docs: the new fields and behaviour, and the limits the review found (versions read from metadata, numpy draws, multi-year sources). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- temporary_year_file creates the file with os.open(..., 0o666), so the umask applies as it does when pandas creates a year file; mkstemp made every published year file readable by its owner alone. - create_datasets carries each result key in the cut list, so a year given as a float is published and returned as before. - Tests: the mode of temporary and published files, a save() failure mid-cut (no temporary left, existing files untouched), a float year, and a "cut for another year" state in the ensure/load property. - Docs and changelog: a source change aborts that dataset's year files only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mirror #593's c7bb48d: create_datasets with no years snapshots and re-checks nothing, so the UK runtime tests keep their original mocked paths. Docs say moving a year file into place needs write access to the data folder and gives the file the cutting user's mode and group. The mode assertions skip on Windows, and the float-year test runs ensure and load after the cut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
fix/uk-year-file-source-identity
branch
from
October 13, 2026 14:18
568c9cf to
9378b3c
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Fixes #600
Draft, stacked on #593 (
fix/us-year-file-source-identity, not merged yet; rebased onto its head c7bb48d; this PR's head is a2244c8). The base is that branch, so the diff shows only this PR; retarget tomainonce #593 merges (merge order: #596 → #594 → #593 → this).Problem
UK year files (
{data_folder}/{stem}_year_{year}.h5) are named after the dataset's logical name, which drops the revision.ensure_datasetsreused a year file whenever it recorded a data year, andload_datasetsrefused one only without it, so year files were reused after the bundle re-pinned a name (enhanced_frs_2024_25,populace_uk_2023, …), after a local source was edited, and after a policyengine-uk or policyengine-core upgrade.Change
uk.create_datasetsstamps each year file with{"packages": {"policyengine-core", "policyengine-uk"}, "revision": 1, "source": <dataset_source_identity>, "year": <int>}in its own H5 datasetpolicyengine_uk_year_file_stamp(uk.datasets.YEAR_FILE_STAMP_H5_DATASET). US stamps keeppolicyengine_year_file_stamp, so a year file of one country never passes as one of the other. The year means a copied or renamed year file is not taken for another year.uk.ensure_datasetscuts the files again, anduk.load_datasetsrefuses them, unless the file records its data year and its stamp equalsyear_file_stamp(requested_dataset_source_identity("uk", dataset), year).uk.datasets.year_file_stamp_problem(path, source, year)reads only the stamp and whether thedata_yearnode exists (h5py), never the tables or the source, and names what differs. Link cycles, dangling links and malformed stamps read as out of date. Permission, directory and lock errors are raised..{stem}_year_{year}.<hex>.partial, created0o666less umask). After the source re-check (snapshot after materializing; downloads re-hashed, as in Tie US year files to the source dataset and package versions they were cut from #593), each temporary is stamped and thenos.replaced onto its path. A path's tables and stamp therefore always come from one cut, even with concurrent cuts. A failed cut writes none of that dataset's year files, leaves the existing ones whole, and removes its temporaries.create_datasets(years=[])snapshots nothing (mirrors c7bb48d).policyengine.provenance.year_file_stampholds the country-neutral parts: JSON node I/O (read_json_dataset,read_stored_json,write_json_dataset), sentinels,installed_package_versions(names),source_and_package_differences,year_file_sources(country, …),temporary_year_file,publish_year_file.us/legacy_inputs.pyandus/datasets.pyuse them; US behaviour and messages are unchanged (Tie US year files to the source dataset and package versions they were cut from #593's US suites pass with one patch target moved:legacy_inputs.json.loads→json.loads).Invariants (each property-tested)
ensure_datasetsreuses it ⇔load_datasetsaccepts it ⇔ it records its data year ∧ its stamp ==year_file_stamp(requested source, year, installed packages)(Hypothesis, with an oracle that decodes stored nodes independently of the checker).None, never an error other than permission/directory/lock.Tests
tests/test_uk_year_file_stamp.py: unit + Hypothesis, no engine.tests/test_uk_year_file_stamp_integration.py: real policyengine-uk on the seven-person 2024 fixture. Covers a re-pinned bundle dataset, an edited local source, policyengine-uk / policyengine-core upgrades, a source replaced mid-cut, interleaved concurrent cuts, a failed cut over existing files,save()failing mid-cut, two sources in one call, pre-stamp files, a float year, and file mode.tests/test_year_file_stamp.py: the shared module (publish ordering, temporary mode under several umasks, collision refusal).Review and mutation testing
mkstemp; float-year key). Fixed. Round 3 approved 568c9cf; its nits are applied in 9378b3c. A full review of the final head a2244c8 is queued (--task review --tier hard: GPT-6.1 Sol when the Codex lanes reopen at about 23:05Z, they were open only 16:40–17:06Z; Opus if a Claude lane admits it first).create_datasets, load/ensure, shared module; unmutated baseline passes). At the final head b8bff75, the create-path mutants were re-run against the integration tests without-x:non_atomic_cut(restores the old write-then-stamp cut) fails 4 tests including the concurrency test;snapshot_neverandcreate_ignores_recheckfail the source-replaced tests.~/PolicyEngine/_reviews/pr601-uk-year-file-source-identity/.Documented limits
Versions are read from metadata when the files are cut, so an upgrade inside a running process after import is recorded at its new version. numpy's seeded draws for student loan plans aren't recorded. A multi-year source is read as stored. Moving a year file into place needs write access to the data folder and gives it the cutting user's mode and group. Saved UK outputs aren't yet tied to the year-file stamp (follow-up proposed).
Rebase notes
#594 adds code in
legacy_inputs.pythat uses_read_json_dataset,_write_json_dataset,_ABSENTand_UNREADABLE. These are now imported from the shared module under the same names; addUNREADABLE as _UNREADABLEback when rebasing onto #594. Its_year_file_reasonsshould call_source_and_package_differencesonce #593 adds source and packages to it.axiom: n/a: infra (year-file caching and provenance)
🤖 Generated with Claude Code