Skip to content

Impute capital gains amounts from the published joint distribution - #560

Open
MaxGhenis wants to merge 1 commit into
uk-cgt-source-stagefrom
uk-cgt-imputation
Open

Impute capital gains amounts from the published joint distribution#560
MaxGhenis wants to merge 1 commit into
uk-cgt-source-stagefrom
uk-cgt-imputation

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

Part two of #552, stacked on #558 (the source surface). Review #558 first; this PR's own diff is the last commit.

What the stage does

Redraws existing gainers' capital_gains from HMRC table 3 — the published joint distribution of size of gain by taxable income — so the population can hold the gains above £2m that carry most of the tax. The measured gap this closes: the two top bands hold 54.6% of UK CGT and effectively none of the modelled tax, because the incumbent percentile source stops near £1m.

The stage owns amounts only. Incidence comes from the candidate, household weights pass through untouched (no mass change to record), and whether the published band facts also become calibration targets remains the fence adjudication recorded on the issue — nothing here assumes an answer.

Design

  • Conditioning. HMRC's rows condition on taxable income after reliefs and the Personal Allowance, which the raw-input population does not carry. The proxy sums the persisted components of the model's total_income concept (ITA 2007 s.23) and subtracts a tapered allowance, with the allowance amounts read from the policyengine-uk parameter tree at a stated instant — the uk extra's deferred-import pattern, per its own comment that the base package never imports policyengine-uk at import time. state_pension_reported stands in for social_security_income; reliefs are not deducted. Both divergences are documented where they can move a person one band.
  • Allocation. Rank-preserving within income band: the largest existing gainers absorb the published taxpayer mass, highest gain band first, scaled proportionally where the population holds less gainer mass than HMRC's taxpayers. No person splits across bands, so band mass is matched to one household weight's granularity.
  • Amounts. Within-band distributions matched to each cell's published mean — table 3 publishes a count and an amount per cell, so the mean is data, not assumption. Bounded bands use a truncated exponential (the one-parameter family whose mean can sit anywhere in the band, degenerating to uniform at the midpoint); the open £5m+ band uses a Pareto whose shape the published mean pins. A cell whose count is suppressed falls back to its band-total mean — dividing a published amount by an assumed count can put the implied "mean" outside the band, which is exactly the trap the band totals added in Read HMRC's joint distribution of capital gains by taxable income #558 exist to avoid.
  • Sub-AEA gainers. Table 3 covers only individuals with a CGT liability, so gainers beyond the published taxpayer mass keep their existing amounts capped at the annual exempt amount — not invented into the liability distribution, not deleted. Stated as scope in the module docstring.
  • Determinism. Draws are seeded on the build period; two runs are bit-identical, two periods differ.
  • Reporting. summarize_uk_cgt_imputation compares achieved band totals with the published surface. It is reporting, not a gate: where the population holds less gainer mass than HMRC's taxpayers the achieved totals sit below published by construction, and holding levels to the surface is the calibration question.

What it deliberately does not do

No family_coverage entry, no cgt_source_stages.json, no band-level UK_CGT_TARGET_SPECS. Promoting the stage to required_at_build and declaring targets follow the fence adjudication on #552, on the individuals-only basis (the two existing facts include trusts).

Tests

17 new tests: proxy arithmetic including the taper, mean-matching for both draw families (with the removable singularity at the uniform midpoint covered), determinism, rank preservation, the sub-AEA remainder, suppressed-cell fallback, artifact verification before read, and the parameter-tree read (runs when the uk extra is installed; importorskip otherwise — it is absent from the secrets-free PR gate by design). Full populace-build suite green locally with the extra installed.

🤖 Generated with Claude Code

The stage redraws existing gainers' amounts from HMRC table 3 so the
population can hold the gains above £2m that carry most of the tax. It owns
amounts only: incidence comes from the candidate, household weights pass
through untouched, and whether the published band facts also become
calibration targets stays a separate adjudication.

Taxable income is an arithmetic proxy — the persisted components of the
model's total_income concept less a tapered Personal Allowance, with the
allowance amounts read from the policyengine-uk parameter tree at a stated
instant through the uk extra's deferred import, following the US runtime's
pattern. Within each income band the allocation is rank-preserving: the top
of the existing ranking absorbs the published taxpayer mass, highest gain
band first, scaled to the available mass where the population holds less
than HMRC's taxpayers. Amounts then come from within-band distributions
matched to each cell's published mean — truncated exponential on bounded
bands, Pareto on the open top band — with a suppressed cell falling back to
its band-total mean. Gainers beyond the taxpayer mass keep their existing
amounts capped at the annual exempt amount rather than being invented into
the liability distribution or deleted.

Draws are seeded on the build period, so a build is reproducible and two
periods draw differently. A summary helper reports achieved band totals
against the published surface; it is reporting rather than a gate, since
holding levels to the surface is the calibration question.

Part two of #552, on top of the source surface in #558.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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