CGT alignment: three clean parameters, and an elasticity that has to be cited (#97) - #102
Open
vahid-ahmadi wants to merge 1 commit into
Open
CGT alignment: three clean parameters, and an elasticity that has to be cited (#97)#102vahid-ahmadi wants to merge 1 commit into
vahid-ahmadi wants to merge 1 commit into
Conversation
…asticity (#97) Aligning capital gains tax rates with income tax rates is the Budget's flagship broad-based option with the "big three" ruled out, and PolicyEngine-UK expresses it cleanly: 18/24/24 today to 20/40/45, three parameters, all resolved against a real 2.89.2 with the baselines recorded from the engine rather than from reporting. The finding that reshaped the lane, verified rather than assumed: gov.simulation.capital_gains_responses.elasticity is ZERO from 2000-01-01, and capital_gains_behavioural_response short-circuits to zero when it is. So a default PolicyEngine-UK score of this measure is STATIC. The engine does not hand you a behavioural number — it hands you the machinery to compute one once you supply an elasticity. That makes the elasticity an assumption THIS REPO makes, and the single parameter the whole comparison turns on: published estimates of this measure disagree mostly about the realisations response, not the arithmetic. So the lane does not pick a value and publish a number. It sweeps, and the validator refuses to mark a sweep point publishable until it carries a named citation — an elasticity without one is an opinion with a decimal point. Today exactly one leg is publishable, the static one, because it asserts no behaviour; the central and high-response legs are held with a note saying what they need, including which elasticity DEFINITION the citation must state (semi-elasticity vs elasticity, and with respect to which rate — different numbers wearing the same word). Comparability is stated and enforced in the spec: an OBR costing of this measure will be post-behavioural, and the static leg must never be presented against it. That is #67's finding one level up, and the static-vs-behavioural difference is published as its own quantity on the behavioural_elasticity axis rather than folded into a residual. The validator also refuses a reform that restates the baseline (it would score zero and read as agreement), and warns loudly if the engine ever switches the response on by default, because that would invalidate the static/behavioural framing this lane rests on. Unexecuted, and says so: the compute needs the managed environment and the certified populace-uk bundle, exactly like #51 and #66 — a local Microsimulation() refuses without an explicit dataset. Everything below the engine boundary is verified and tested here. Suite 284 passed, ruff format clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016HuXJFVme8HRbnke2Ey3Me
Contributor
Author
|
Review request — @MaxGhenis @DTrim99. This one is part of a batch; the whole queue, with a suggested merge order and what is blocked on whom, is in #104 so you can triage in one place rather than PR by PR. |
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.
Closes #97. Branches from
main.Aligning capital gains tax rates with income tax rates is the Budget's flagship broad-based option with the "big three" ruled out by the manifesto pledge. PolicyEngine-UK expresses it cleanly — three parameters, all resolved against a real 2.89.2, with the baselines recorded from the engine rather than from reporting:
gov.hmrc.cgt.basic_rategov.hmrc.cgt.higher_rategov.hmrc.cgt.additional_rateThe finding that reshaped the lane
I opened #97 expecting to say "PE-UK has a CGT behavioural response, so this lane can score both legs". Checking the engine says something more specific:
So a default PolicyEngine-UK score of this measure is static. The engine does not hand you a behavioural number — it hands you the machinery to compute one once you supply an elasticity. (It also requires
simulation.baselineto be attached, so it exists only in a reform run.)That makes the elasticity an assumption this repo makes, and the single parameter the whole comparison turns on — published estimates of this measure disagree mostly about the realisations response, not the arithmetic.
So the lane sweeps, and citations are enforced
It does not pick a value and publish a number. The validator refuses to mark a sweep point publishable until it carries a named citation, because an elasticity without one is an opinion with a decimal point.
The static leg is publishable because it asserts no behaviour. The other two are held with a note saying what they need — including which elasticity definition the citation must state: semi-elasticity vs elasticity, and with respect to which rate. Those are different numbers wearing the same word, and picking the wrong one silently rescales the whole response.
Comparability, enforced not narrated
An OBR costing of this measure will be post-behavioural. The static leg must never be presented against it — that is #67's finding one level up, and the spec says so in a field the validator checks. The static-vs-behavioural difference is published as its own quantity on the
behavioural_elasticityaxis rather than folded into a residual.The validator also refuses a reform that restates the baseline (it would score zero and read as agreement), and warns loudly if the engine ever switches the response on by default — that would invalidate the static/behavioural framing this lane rests on.
Unexecuted, and says so
The compute needs the managed environment and the certified populace-uk bundle, exactly like #51 and #66; a local
Microsimulation()refuses without an explicit dataset. Everything below the engine boundary — reform values, paths, baselines, the sweep contract — is verified and tested here.Verification
Suite 273 passed,
ruff format --checkclean, validator green with and without the engine.Reviewers
@MaxGhenis @DTrim99 — the call worth testing: I've made the citation requirement a hard gate rather than a convention, which means this lane currently cannot publish a behavioural number at all. That is deliberate, but it does mean the lane ships less than it could. If you'd rather it carried a documented default elasticity with the caveat attached, that is a reasonable alternative and I'd take the argument.