Repository navigation
Conversation
A UK dataset read from a year file, or returned by create_datasets, keeps the file's stamp in metadata. A UK run records an output stamp holding it and an output format revision, and save() writes that stamp after the tables. Simulation.load() refuses, and ensure() recalculates, an output whose stamp is not what a run of the simulation's dataset would record now under this release: one calculated from another year file or another cut of it, or from a year file cut with another year-file revision or other package versions. Fixes #606. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s, one cut per load Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, ensure agrees with load Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t a save records Pass the restored stamp through the output dataset's constructor, so the change adds no mypy errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… one file per load Review 1 (GPT-6.1 Sol) found that an output recorded the versions that cut its year file, not the ones that calculated it; that two saves to one path could leave one run's tables under another's stamp; that load validated one file and hydrated another; and that comparing the stamp before and after the tables missed a file replaced and put back. - The output stamp records the installed policyengine-core and policyengine-uk versions at the run, and load compares the whole stamp with the one a run of the simulation's dataset would record now. - save_output writes tables, data year and stamp to a temporary file and moves it onto the path, as create_datasets publishes a year file. - year_file_stamp.read_one_file holds the file open while it is read and checks the path still names it, unmodified; PolicyEngineUKDataset.load() and the output load both read through it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e, one file per load Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le per load Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_file misses The check never opens the tables, so the Hypothesis tests now write a 6 KB file per example rather than a 262 KB output or a 512 KB year file. A parametrized test checks that both kinds of file are decided alike. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 14, 2026
Contributor
|
A coordination note from #621, which records the capital gains realisation response each UK run applies (part of #602), as Vahid asked on #621.
|
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 #606
Stacked on #601 (UK year-file stamp), which is stacked on #593. The base is
fix/uk-year-file-source-identity; retarget tomainonce #601 merges. Merge order: #596, #594, #593, #601, then this. It mirrors #594 (the US output stamp) and will conflict textually with it incommon/model_version.py(save()/load()), where the UK branches sit beside the US ones.Status: reworked after the first independent review (REQUEST_CHANGES, four findings, all addressed below). Second review in progress; mutation testing queued behind a disk-space hold on the build host.
Problem
A saved UK output was checked only for a recorded data year. After #601 every UK year file carries a stamp (source identity, policyengine-core and policyengine-uk versions, year, format revision), but nothing tied a saved output to it. When a year file was cut again, because the bundle re-pinned the dataset, a local source was edited or policyengine-uk was upgraded,
Simulation.ensure()andload()still reused outputs calculated from the old cut.Change
PolicyEngineUKDataset.metadatais a new field, as on the US dataset. A dataset read from a year file, or returned bycreate_datasets, keeps the file's stamp under"year_file_stamp".uk.datasets.output_stamp(year_file)in the output'smetadata["output_stamp"]:{"packages": {policyengine-core, policyengine-uk versions installed at the run}, "revision": 1, "year_file": <the input dataset's year-file stamp, or null>}.load()checks the whole stamp.Simulation.load()refuses a saved UK output, andSimulation.ensure()runs it again, unless its stamp equals the one a run of the simulation's dataset would record now (uk.datasets.output_stamp_problem). The error names the years, sources or package versions that differ.uk.datasets.save_outputwrites the tables, the data year and the stamp to a temporary file beside the path and moves it onto the path, reusing Tie UK year files to the source dataset and package versions they were cut from #601'stemporary_year_file/publish_year_file. A save that fails leaves the previous output whole.provenance.year_file_stamp.read_one_file(new) holds the file open while it is read, then checks that the path still names that file with the same size and modification time, and reads again if not.PolicyEngineUKDataset.load()reads the year-file stamp and the tables through it, and the output load runs its check and reads the tables through it.PolicyEngineUKDataset.load()now raisesValueErrorfor a dataset with no filepath (it raised pandas'TypeErrorbefore).provenance.year_file_stamp.package_differences(stamped, packages, done="cut")is split out of Tie UK year files to the source dataset and package versions they were cut from #601'ssource_and_package_differences, so the output check words the same clauses as "calculated with". The year-file clauses are unchanged: I compared the two implementations over every pair of package maps on four names (196,608 stamp pairs).Design decisions
The output is compared with the simulation's dataset, not with the requested source. #594 compares a US output's year-file stamp with the one this release would write, which the release alone determined when #594 was written. A UK year-file stamp also depends on the source and the year, and an output cannot know which dataset was requested. So the check asks what a run of this simulation would record now: the stamp its dataset keeps of the tables it holds. That is what makes a re-pin or an edited source invalidate outputs:
ensure_datasetscuts the year file again with another stamp, the simulation's dataset is read from the new cut, and the old output's recorded stamp no longer equals it. It also refuses an output saved under the same id for another year's file in the same folder.The output records the versions that calculated it, separately from the year file's. The first review showed these can differ: a year file cut under one policyengine-uk and opened directly under another is run with the other. So the stamp has its own
packages, and an upgrade makes every saved UK output out of date at once, including outputs calculated from data built in memory.Whether a year file is itself current is left to
load_datasets. An output calculated from an out-of-date year file opened directly is what a run of that file gives now, so it is reused with that file. This differs from #594, which refuses outputs from year files that are not this release's.Invariants
output_stamp_problem(path, year_file, packages)isNoneexactly when the file records its data year, its stored stamp equalsoutput_stamp(year_file, packages)as JSON, andyear_fileisnullor a well-formed UK year-file stamp.None. It raises only for a missing file and for errors unrelated to the file's contents (no permission, a directory, a lock).ensure()runs the simulation exactly when the output is missing orload()would refuse it. The output it then saves is current exactly when the dataset keeps no year-file stamp or a well-formed one.Limits (documented in
docs/microsim.md)OUTPUT_STAMP_REVISION.ensure()returns the output it holds in memory for a simulation id without checking its stamp. That cache is keyed by id only, for both countries, and is not changed here.read_one_filedoes not notice a file rewritten in place to the same size within one tick of the file system's modification time, or a file replaced and put back by moving another hard link to it onto the path. Neithercreate_datasetsnorsave_outputwrites that way.Tests
tests/test_uk_output_stamp.py(162 tests, engine-free): pytest and Hypothesis properties for the invariants above, including a file replaced and put back during a load, a file rewritten in place, and one save interleaved inside another.tests/test_uk_output_stamp_integration.py(26 tests): real policyengine-uk runs on the seven-person fixture. Each replays one cause: a bundle re-pin, an edited local source, a policyengine-uk or policyengine-core upgrade, a year file run with other versions than cut it and those versions then restored, another year's file, a raised year-file revision, an output saved by an earlier release, a failed save, and another process's output moved onto the path between the check and the read. Each checks thatload()refuses the old output with the exact reason,ensure()runs once, the new output matches a direct policyengine-uk run of the new source, and it is then reused. A Hypothesis property checks that ensure agrees with load.🤖 Generated with Claude Code