Skip to content

Adopt policyengine-uk's CGT retention-rate default - #633

Draft
juaristi22 wants to merge 1 commit into
mainfrom
cgt-602-adopt-retention-default
Draft

juaristi22 wants to merge 1 commit into
mainfrom
cgt-602-adopt-retention-default

Conversation

@juaristi22

Copy link
Copy Markdown
Contributor

Part of #602.

Blocked by: the policyengine-uk release from PolicyEngine/policyengine-uk#2419 (in the merge queue; expected 2.134.0), and the publisher's compatibility claim for that release in policyengine-uk-data 1.58.1's release manifest. certify-data refuses an unclaimed model version. This PR stays a draft until both exist.

PR set: CGT retention-rate default (PolicyEngine/policyengine-uk#2396, #602)

Step PR What Status
0 PolicyEngine/policyengine-uk#2277 Measurement branches merged, policyengine-uk 2.133.0
1 #620 Budget contract: static pin, response and default cases; release-lock fix merged, policyengine.py 6.5.0
2 PolicyEngine/policyengine-uk#2419 Retention-rate elasticity of 1.0 as the model default in the merge queue
3 #621 Record the response each UK run applies merged, policyengine.py 6.5.0
4 — Step 2's release, and its compatibility claim for policyengine-uk-data 1.58.1 after step 2
5 this PR Adopt the release: certify, pin, gating default cases, default tests, docs draft
6 not opened yet policyengine-app-v2 methodology page after this PR is released

Tracking checklist: checklist on #602

In this PR now

Everything adopting the default needs, apart from the pin. It passes on the current pin (policyengine-uk 2.130.1).

  • Contract 1.2.0. It adds cgt_default_response_relief_gains: 45% higher and additional rates for £50,000 of gains, £20,000 of them qualifying for Business Asset Disposal Relief. It checks both the realised gains and the relief gains' own response at the default elasticity of 1, against the share-weighted rate. Like Hold CGT realisations fixed in the Budget contract's rate-blending case, and check the response #620's cgt_default_response, its checks wait on policyengine-uk#2396. The golden rows add 48 rows for it and change none.

  • tests/test_uk_cgt_default.py.

    • No elasticity set gives the same result as an explicit retention-rate elasticity of 1. This holds through the population, household and managed routes, relief gains included.
    • An mtr_elasticity set alone applies on its own, with one CapitalGainsResponseWarning, and the record says so.
    • An unreformed run is unchanged by the default.
    • The pinned defaults are the central case.

    The module skips until the installed policyengine-uk has the default.

  • Docs.

    • The reforms guide gets a section on the response: who is affected, the static settings on each route, the sensitivities, the MTR convention, precedence, setting the same dynamic on both sides of a comparison, and the assumption's limits.
    • A new methodology page explains how the response is measured and when it is zero. It carries the caveats from policyengine-uk's review: the default is low for rate changes alone, it's a medium-term value, large rate rises can flip the sign, and household results count deferred gains as income lost.
    • The dynamic field's description says None keeps the model's defaults.
  • Changelog: 602-cgt-default.changed.md.

The pin commit, once step 4 is done

Following #617's recipe:

  1. scripts/bundle.py certify-data --country uk --data-producer populace --manifest-uri hf://model/policyengine/policyengine-uk-data-private@<claim commit>/releases/1.58.1/release_manifest.json --model-version <X>.
  2. Regenerate the bundle manifest and both TRO sidecars (bundle.py generate --include-tros), and check them with bundle.py check --published-spm --include-tros --strict-tros.
  3. Update the three policyengine-uk pins and uv.lock (uv 0.12.14, --upgrade-package policyengine-uk only), plus the test constants that pin the certified pair.
  4. Make the two default cases gating, run them on every in-process route in the CGT integration test, and remove the skip in test_uk_cgt_default.py.
  5. Fill in {X} (the policyengine-uk version) and {Y} (this release) in the docs and the fragment.
  6. Run make test with the HF token, refresh snapshots, and attribute each drift. The jump from 2.130.1 also brings in policyengine-uk 2.131–2.133: trading-loss relief, and #2277's labour supply measurement.

Evidence, aggregate-only:

  • the contract on the new pair;
  • runtime per route;
  • unreformed runs identical to static;
  • one CGT reform at 0, 0.5, 1 and 2 and the OBR comparison;
  • an income tax threshold reform's cross-effect;
  • the dashboard-against-Python parity check with uk-cgt-reform.

Checks so far

  • On the current pin: tests/test_uk_budget_reform_contract.py, tests/test_uk_cgt_responses.py and tests/test_uk_cgt_default.py give 142 passed. One module is skipped: the default tests, as intended. ruff format --check . and ruff check . are clean.
  • With Make a retention-rate elasticity of 1 the default CGT realisation response policyengine-uk#2419's code installed locally:
    • tests/test_uk_cgt_default.py gives 6 passed.
    • Every pending check in both default cases holds on all five in-process routes. So making them gating at the pin will pass.

🤖 Generated with Claude Code

Everything that adopting policyengine-uk's default capital gains
realisation response (policyengine-uk#2396) needs apart from the pin:

- Contract 1.2.0 adds cgt_default_response_relief_gains: 45% higher and
  additional rates for £50,000 of gains, £20,000 of them qualifying for
  Business Asset Disposal Relief, whose response is checked at the
  default elasticity of 1. Like cgt_default_response, its checks wait on
  policyengine-uk#2396 until the pin moves. The golden rows add 48 rows
  for it and change none.
- tests/test_uk_cgt_default.py checks the default through the
  population, household and managed routes, an MTR elasticity set alone,
  and that an unreformed run is unchanged. It skips until the installed
  policyengine-uk has the default.
- The reforms guide gains a section on the response, and a methodology
  page explains it, with the caveats from policyengine-uk's review: low
  for rate changes alone, a medium-term value, large rate rises, and
  household results counting deferred gains as income lost.
- The dynamic field's description says None keeps the model's defaults.

With policyengine-uk#2419's code installed, the default tests pass and
every pending check in both default cases holds on all five in-process
routes. The pin commit fills in {X} and {Y}, makes the default cases
gating, and removes the skip.

Part of #602.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant