Repository navigation
Roll back failed override batches and keep structural-reform nodes across rebuilds - #2375
Conversation
…ross rebuilds Fixes #2374, the low-severity notes from GPT-6.1 Sol's round-3 review of #2305: - add_legacy_overrides applies a batch to a copy and swaps it in, so a batch that fails applies none of its overrides. - Rebuilds (apply_parameter_assignments) and reprocessing (reset_parameters then process_parameters) add back the nodes a structural reform added to the processed parameters (graft_missing_nodes), so their overrides replay; process_parameters rolls back on failure. - The shared projected timelines are documented as shared. - The scenario docs say Scenario.apply edits the annualised values, and which steps run after the UC rebalancing and structural reforms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…raft scale bands Review of #2375 (GPT-6.1 Sol, review-hardening-out.md): - process_parameters processes a copy of the reloaded tree, so its rollback returns that tree unprocessed, not an object processing had already changed; the test now checks the contents and a retry. - add_legacy_overrides undoes a failed batch by restoring each target's values_list (update replaces it), keeping the tree's identity and costing O(batch) rather than a whole-tree clone. - graft_missing_nodes also appends scale bands a structural reform added and recurses into existing bands. - reset_parameters keeps only a processed tree, and keeps the first one across a second reset before processing. - Docstrings say grafted nodes are copies and come back even if a modifier deleted them after the reset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ilds keep Review round 2 of #2375 (GPT-6.1 Sol, APPROVE with two notes): - a failed override batch now restores the modified flags of its targets and their ancestors as well as their values, so core's cache eligibility is as it was; - the dated_reforms module docstring says rebuilds add back nodes a structural reform added and keep no other edits to existing processed parameters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An unknown path is refused when targets are looked up, before anything changes, so the existing test never reached the rollback. A Reform.from_dict key reaches the overrides unparsed and fails after the first override has updated the rate; the new test checks the rate's values_list, its annual value, the recorded overrides and the modified flags on its path are all as before. It fails at 7819755 (flags left set) and passes here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A spy on apply_legacy_overrides records that the batch raised inside the overrides after the rate was updated, so the test cannot pass without running the rollback if Reform.from_dict keys are ever checked earlier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hand-off to the UK hub (W1, session local_37646626-ec63-43ad-a47b-7752ce411a5c)
|
|
UK hub (2026-10-12): this PR's head |
|
Note for the UK hub (W1): the red checks on d594634 are cancelled runs, not failures.
One re-run of the cancelled workflows, when the queue allows, should be all it needs. I won't push or re-run anything myself; the hub owns this PR. |
Fixes #2374
Review: GPT-6.1 Sol requested changes on 175ac4b, then approved 7819755 with two notes. Both notes are fixed in 63d2e7e: a failed batch now also restores modified flags, and the module docstring is updated. An independent Opus 5.5 review of the final delta found the rollback test never reached the rollback. fb5ee57 adds a test that fails after a real update, and d594634 adds a spy so that test can't go vacuous. The Opus review approves d594634.
Reproducer: policyengine_uk/tests/test_dated_reform_routes.py::test_a_batch_failing_after_an_update_restores_values_and_flags
Reproducer: policyengine_uk/tests/test_dated_reform_routes.py::test_a_rebuild_keeps_a_structural_reforms_parameter
Reproducer: policyengine_uk/tests/test_dated_reform_routes.py::test_reprocessing_keeps_a_structural_reforms_parameter
Reproducer: policyengine_uk/tests/test_dated_reform_routes.py::test_failed_reprocessing_leaves_the_system_as_it_was
Reproducer: policyengine_uk/tests/test_dated_reform_routes.py::test_a_rebuild_keeps_a_band_a_structural_reform_appended
These are GPT-6.1 Sol's four low-severity notes from its round-3 approval of #2305 (
~/reviews/w1-budget-reform-contract-2026-10-11/review-uk-r4-out.md). Each was reproduced on #2305's head before it was fixed.What this changes
CountryTaxBenefitSystem.add_legacy_overridesfinds every target first, then keeps each target'svalues_listand themodifiedflags on its path. If any override fails, it restores them. That's O(batch), and the parameters tree stays the same object. Before,Scenario.from_reform({rate: {"2027": 0.3}, "gov.not_a_real_parameter": {"2027": 1}}).apply(sim)raised but left the rate at 0.3, unrecorded, and the next rebuild reverted it.graft_missing_nodes(utils/dated_reforms.py) adds back to a rebuilt tree any node the previous processed tree had and the rebuild lacks. Existing parameters keep their rebuilt values, and the root'sbaselinechild is skipped. It runs afterapply_parameter_assignmentsrebuilds, and afterprocess_parametersreprocesses a treereset_parametersreloaded (reset_parameterskeeps the tree it replaced until then). Before, afterSimulation(reform=(add_bonus, {"test_budget.bonus": 7})), bothsim.apply_parameter_changes(...)andno_economic_assumptions.apply(sim)raisedCould not find the parameter test_budget.bonus.process_parametersprocesses a copy of the reloaded tree. If processing or the override replay fails, it restores the reloaded tree unprocessed, along with the projected timelines and rebuild base.reset_parameterskeeps the processed tree it replaces only once, and only if that tree is processed.graft_missing_nodesalso appends bands a structural reform added to a scale, and recurses into the bands both trees have.projected_timelines(). The system property still returns a copy.docs/book/usage/scenarios.mdnow says:Scenario.applyon a built simulation edits the annualised values, so a dated key there does not set its fiscal year;Invariants
add_legacy_overrides,apply_parameter_assignmentsorprocess_parametersleaves the system's parameters, timelines, rebuild base and recorded changes as they were.Tests
New tests in
policyengine_uk/tests/test_dated_reform_routes.py. The first five fail on #2305's head (603fecb) and pass here:Added after the review:
The reprocessing test now also checks that the restored tree is unprocessed and that a retry works.
test_a_batch_failing_after_an_update_restores_values_and_flagsfails a batch after its first override has updated the rate, using aReform.from_dictkey that is parsed only when applied. It checks the rate's list, value, recorded overrides and flags, and a spy confirms the rollback ran. It fails at 7819755 and passes here.The 14 reform and parameter-processing test files pass: 441 passed on fb5ee57. The two rollback tests pass on d594634, which changes only the test file.
axiom: n/a: reform-route and parameter-processing infrastructure; no policy provision changes.
🤖 Generated with Claude Code