Repository navigation
Route UK dated reforms through policyengine-uk's statutory pipeline - #575
Merged
Merged
Conversation
Simulation(policy=...) now passes a UK policy's parameter values to policyengine-uk as an explicit-interval reform dict at construction, instead of editing the already-annualised parameters through a modifier afterwards. With policyengine-uk#2305, that writes the changes onto the statutory timeline and reprocesses them as current law (uprating, threshold indexation, 30 April fiscal-year annualisation), before the UC rebalancing inputs and parameter-gated structural reforms read them. Custom simulation modifiers still run after construction. pe.uk.calculate_household(reform=...) gives date keys the same reading as Simulation(policy=...): a YYYY-MM-DD key is an effective date, so an April date is no longer a one-day override that changes nothing. Dict policies accept YYYY-MM-DD.YYYY-MM-DD inclusive intervals, and reject bare years, which policyengine-uk and policyengine-core read differently. Fixes #572. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes (GPT-6.1 Sol):
- A custom policy modifier followed by a parametric dynamic ran after the
dynamic, which joined the construction reform. The dynamic is now applied
after that modifier, as it was before.
- merge_reform_dicts replaced an existing period in place, so a later
policy period could override the dynamic where they overlapped. Override
periods now come after the base's.
- The dict compiler accepts any date strptime('%Y-%m-%d') accepts, as it
always has, including unpadded months and days.
Tests cover all four policy/dynamic combinations on the population route,
overlapping override precedence, and unpadded dates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review notes (GPT-6.1 Sol round 2): the merge docstring claimed the override always wins an overlap, which holds for policyengine-uk but not for policyengine-core's start-ordered Reform.from_dict on the US route; and the compiler's docs and error did not mention unpadded dates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… of them A Policy or Dynamic sum carrying both parameter values and a simulation modifier lost its values on the UK population route: the modifier ran in their place, on main as on this branch. The values now go in at construction and the modifier runs after them, policy before dynamic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 #572
Draft: GPT-6.1 Sol approved 7a1acc0 in round 2. One commit since then (0d4e9b8, below) is under review. The numerical behaviour needs PolicyEngine/policyengine-uk#2305.
What this changes
Simulation(policy=...)(UK). A policy's parameter values now reach policyengine-uk as an explicit-interval reform dict when theMicrosimulationis constructed. Before,simulation_modifier_from_parameter_valuesedited the already-annualised parameters afterwards. With policyengine-uk#2305, construction writes the changes onto the statutory timeline and reprocesses them as current law: uprating, threshold indexation and 30 April fiscal-year annualisation. That happens before the UC rebalancing inputs and the parameter-gated structural reforms read them. A policy or dynamic with a customsimulation_modifierstill runs it after construction, in the same order as before.PolicyorDynamiccarrying both (whatPolicy + Policygives when one side has a modifier) ran the modifier instead of the values, on main as well. The order is now: policy values, policy modifier, dynamic values, dynamic modifier.pe.uk.calculate_household(reform=...). Date keys now get the same reading asSimulation(policy=...):YYYY-MM-DDis an effective date, running until the parameter's next date or 2100-12-31. Before, the dict went straight to policyengine-uk, where a lone date is a one-day override, so{"2027-04-06": 0.21}changed nothing.YYYY-MM-DD.YYYY-MM-DDinclusive intervals, so a finite change can be written as a dict and the sim API can keep stop dates (Policy normalisation drops the stop date of start.stop keys, so bounded reforms never expire policyengine-sim-api#740). They reject bare years, with an explanation: policyengine-uk's dict reads"2027"as fiscal year 2027 only, while policyengine-core'sReform.from_dictreads it as onwards. The US household route keeps its country package's key handling.Invariants
Behaviour by installed policyengine-uk
{"2027-04-06": 0.21}: income tax change, £30k earner, 2027"2027-04-06.2028-04-05": 2027 / 2028Review
GPT-6.1 Sol round 1 (on 883cfca) asked for three changes. All are fixed in 7a1acc0, and round 2 is running.
merge_reform_dictsnow puts override periods after the base's, so a dynamic wins where it overlaps a policy period that has a different key."2027-4-6", whichstrptimealways accepted, are accepted again.Round 2 approved 7a1acc0 and noted that an object with both a modifier and parameter values lost the values. That is fixed in 0d4e9b8.
test_population_route_applies_policy_before_dynamicnow covers eight policy/dynamic combinations.Tests
tests/test_uk_dated_reforms.py,test_dict_reforms_on_simulation.py,test_household_impact.py,test_parametric_reforms.py,test_extra_variables.py. 86 passed, 3 skipped (needs policyengine-uk#2305); these figures are from before the review fixes.test_uk_dated_reforms.py,test_dict_reforms_on_simulation.py,test_parametric_reforms.py. 51 passed.test_uk_dated_reforms.py,test_household_impact.py,test_extra_variables.py,test_uk_analysis.py. 53 passed.test_uk_dated_reforms.py, 17 passed. CI passed at 19ba06b; it runs again on 0d4e9b8.The Budget reform contract runner that qualification imports is in a separate PR (W1 of the UK Autumn Budget 2026 CI plan).
🤖 Generated with Claude Code