Repository navigation
Hold CGT realisations fixed in the Budget contract's rate-blending case, and check the response - #620
Conversation
The mid-year CGT case's static oracle relied on policyengine-uk's realisation response being off by default. policyengine-uk#2396 makes a retention-rate elasticity of 1.0 the default, which would fail the case on every in-process route and stop qualification of every later UK release. The case now pins the response off, and a new case, cgt_retention_response, checks the response itself at an elasticity of 1. Contract 1.1.0. Part of #602. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Good to merge once CI is green. The shoulds below are about the contract and the merge order once the default flips, not about this diff. On the main question: pinning the elasticity to 0 in
The new oracle checks out by hand: £10,000 × 0.80 / 0.82 = £9,756.10, and (£9,756.10 − £3,000) × 20% − £7,000 × 18% = £91.22. That matches the 2028-29 figure under the new default, which is a good sign the response is the same on every route. What I checked
🤖 Generated with Claude Code |
Vahid's review of #620: with both CGT cases setting the elasticity, the contract no longer saw the default policyengine-uk ships. A third case, cgt_default_response, runs the same reform with no response settings; its checks expect the retention-rate elasticity of 1 that policyengine-uk#2396 makes the default and carry pending until policyengine.py pins that release, so the report shows the default without gating. Each case now checks that all three response settings reached the built system, not only the elasticity. The prior-law wording no longer names contract 1.0.0 as if it were the current version. The golden rows are recorded again: the CGT cases add rows and no existing row changes. Part of #602. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks @vahid-ahmadi. Changes are in a5ca27c. I merged main first, so everything below ran on policyengine-uk 2.130.1.
The golden rows are recorded again: the CGT cases add 216 rows, and no existing row changes. 🤖 Generated with Claude Code |
CI's Test job hit its 60-minute limit on Python 3.12 and 3.13, seconds after pytest finished: the CGT integration test ran all three CGT cases on every in-process route, the slowest test in the file. The default case's checks are all pending, and the golden rows already cover its rows on every route under fake routes, so it now runs on country.reform_dict alone, with a data check that every check waits on policyengine-uk#2396 and that the case sets no response settings. The two gating cases still run on every route. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A correction to point 1 above, after CI. The Test job hit its 60-minute limit on Python 3.12 and 3.13, seconds after pytest finished; 3.11 and 3.14 passed. The CGT integration test was the slowest in the file. In 9372051 it runs the two gating cases on every in-process route as before, and the pending default case on For what it's worth, main's own Test jobs now take 24-51 minutes against the workflow's 60-minute limit, whose comment still says 28 minutes was the slowest observed in early October, so other PRs may hit it too. 🤖 Generated with Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #571 (f68b63a), uv.lock carries two dependency markers (cffi's pycparser and pexpect's ptyprocess) that uv 0.12.14, the version CI pins, doesn't write. uv lock --check accepts them, but when Versioning bumps the version, uv re-resolves and drops them, so release_lock.py --refresh sees a changed dependency graph and stops the release. Every merge since #571 has failed at Versioning, and none has published. Re-locked with uv 0.12.14 across a temporary version change, which writes the lock in that version's form. The diff is the two markers. Locally, bump_version.py followed by release_lock.py --refresh now passes with uv 0.12.14, and fails with the current lock with the same error as CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
I've added the release-lock fix to this PR, so that its merge publishes a release.
cc @MaxGhenis, since the markers came in with #571 and your merges since then haven't released. If you'd rather land the lock fix separately and first, it's one commit and I can split it out. 🤖 Generated with Claude Code |
Part of #602.
PR set: CGT retention-rate default (PolicyEngine/policyengine-uk#2396, #602)
Blocks step 2, as a release. The contract's consumer today is policyengine-sim-api's deploy gate (
test_budget_reform_contract_route.pyimportsCASES), which sees this change only once it pins a policyengine.py release that has it. So this PR has to be released, and that release taken by sim-api, before sim-api takes the policyengine-uk release that step 2 publishes. Merging it first is not enough on its own. #574's qualification is still a draft. Landing this first means it starts from a contract that holds under the new default.This PR also fixes policyengine.py's releases. Since #571,
uv.lockhas carried two dependency markers (cffi → pycparser,pexpect → ptyprocess) written by a different uv than the 0.12.14 CI pins.uv lock --checkaccepts them. But when Versioning bumps the version, uv re-resolves and drops them, sorelease_lock.py --refreshstops the release with "Versioning changed the reviewed dependency graph". No merge since #571 has published. d1bd9a5 re-locks with uv 0.12.14 (the diff is those two lines) and adds afixedfragment. Locally, the version bump followed byrelease_lock.py --refreshpasses on this branch and fails on main with CI's error. So merging this PR publishes a release carrying it and everything merged since #571, which is the release step 2 needs.Tracking checklist: checklist on #602
What
mid_year_blended_ratechecks how a CGT rate change part-way through a year blends into the fiscal-year rate. Its oracle charges £10,000 of gains at the blended rate, so it assumes realisations don't respond. policyengine-uk#2396 makes a retention-rate elasticity of 1.0 the default. On that release the case would fail on every in-process route, and qualification would stop:capital_gains_response(), so its expected values hold whatever the model's default. A parameter check confirms the setting reaches each route.cgt_retention_responsesets a 20% CGT basic rate from 6 April 2027 with an explicit retention-rate elasticity of 1. Realised gains are £10,000 × 0.80 / 0.82 = £9,756.10, and the CGT change is £91.22. The expected values come from_realised_gainsand_gains_response_uplift, which read current law as the other oracles do.cgt_default_responseruns the same reform with no response settings. Its checks expect the retention-rate elasticity of 1 that policyengine-uk#2396 makes the default. They carrypending(decisionpolicyengine-uk#2396) until policyengine.py pins that release, so the report shows what the default does on every route without failing qualification. Step 5 makes them gating with the pin.Checks
Main is merged in, so these ran on policyengine-uk 2.130.1, the current pin.
cgt_default_responsereports 19 pending rows on 2.130.1, whose default is still static. The 6 that hold are the MTR and switch settings. The 13 that don't yet hold are the elasticity rows and the response itself (£140 against the £91.22 expected).pytest tests/test_uk_budget_reform_contract.pygives 106 passed (about 7 minutes). The new tests cover:tests/fixtures/uk_contract/evaluate_case_rows.jsonis recorded again. The CGT cases add 216 rows, and no existing row changes.run_metrics_reportwith its pinned tool, thenmetrics_reviewandreform_responsiveness, for 2025-26 to 2031-32. It ran on one dataset for three installs: 2.130.1, #2419's merge base, and #2419's head. #2419 trips no review bound, and every baseline metric is unchanged. Reform responsiveness passes in all three.metrics_reviewfails identically in all three, from the tool pin rather than the model (see the reply below).test_budget_reform_contract_route.pypicks up the new cases. Its_normalise_policypassesgov.simulation.*paths and boolean values through unchanged, as it does forstructural_switch.🤖 Generated with Claude Code