Skip to content

Align UK CGT calibration with FY2024-25 individual observations - #889

Merged
juaristi22 merged 8 commits into
mainfrom
uk-cgt-observed-year-875
Sep 10, 2026
Merged

Align UK CGT calibration with FY2024-25 individual observations#889
juaristi22 merged 8 commits into
mainfrom
uk-cgt-observed-year-875

Conversation

@juaristi22

@juaristi22 juaristi22 commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Measure CGT against FY2024–25 individual observations while retaining calibration index 2025 and exported time_period=2024.

The three fitted benchmarks are HMRC gains of £119.258bn, 551,000 taxpayers and £22.503bn liability. The previous gains/count benchmarks included trusts, and the fitted liability row used OBR cash receipts. Exact fact keys, tax-year selectors, dated aliases and the 2024 allowance prevent stored columns or a different observation period from bypassing this contract.

For this FY2024–25 deliverable, the decision and evidence note supersedes #875’s previous FY2025–26 fitting disposition. The scoped record on #875 preserves the original ruling and signed history while leaving its forward-year projection and general cash-lag questions open. Retire the obsolete OBR fit exemption; the HMRC liability target has no exemption. OBR’s unchanged FY2025–26 forecast remains optional diagnostic provenance. Missing, incompatible or duplicate diagnostic data produces an explicit unavailable result while the three strict HMRC targets still compile; malformed declarations and missing, incompatible or duplicate fitted observations remain errors.

Rebase onto actual main 45231f9e, retaining the 141bface feature baseline with merged #892 and #877; the later #899 change affects CI scheduling only. Compose the five UC/pension fit-exemption retirements with this PR’s OBR retirement, leaving the active fit-exemption register empty. Preserve the upstream boundary tests and HMRC-liability enforcement. Drop the duplicate graph-prerequisite commits while retaining the upstream inputs, canonical fixture and claimant-role regression. Preserve #891/#892 UC source-window semantics and #877’s size/checkpoint behavior, corrected local private-rent composition and attested calibration identities. National Chronicle remains ec7169b5, local remains separately pinned to 6fb700e, and PolicyEngine-UK 2.97.0 / Core 3.31.0 remain locked.

Historical numerical evidence: the aggregate receipt and 363-row non-CGT table remain unchanged. They record the matched control cc9c953c / numerical candidate 9b5ac7c5 comparison using identical authenticated source, priors and 1,500-epoch settings. All values below use FY2024–25 individual measures under those historical fitted weights:

Measure HMRC Control cc9 weights Candidate 9b5 weights
Gains £119.258bn £127.485bn £108.032bn
Taxpayer proxy 551,000 583,539 551,037
Liability £22.503bn £26.986bn £22.654bn

Count and liability errors fall to +0.007% and +0.67%; gains remain −9.41%, worsening in absolute error from +6.90%. The fit reduces represented gains alongside liability. The comparison combines measurement year, individual scope, liability benchmark and objective weights; it does not isolate their separate causal effects. All 363 non-CGT coefficient rows and targets match, with no new misses crossing 10% or 25%. Moving liability changes 22 automatic family weights within the original 366 rows/21 families. Taxpayer ESS falls from 490.70 to 384.59. Both original diagnostic exports preserve all 218 nonweight columns and base year 2024, and retain their original two empty UC bands and five stale-exemption blockers.

Applicability after rebase: the two UC payment-tail measure exclusions inherited from 141bface change the default fitting surface to 364 of 415 references. The later 45231f9e base advance leaves the verified runtime, target and dependency inputs unchanged. These pre-solve exclusions differ from the now-empty release-fit exemption register. The original 366-row objective, fitted weights and gate maps are historical; this PR does not claim a new-head fit, unchanged fitted weights or a current release-gate verdict. The old local rent diagnostic likewise does not establish behavior under #877’s corrected target composition.

Regenerate national compile-parity differences against unchanged historical fixtures. The 2023 comparison now includes five inherited UC child-count source-window rows; the 2025 update changes their explanations only. National references/membership and the separate local-feed artifacts retain their respective authenticated provenance. The documentation explains fixed observation dating versus #881’s release-relative years, deliberate provisional-revision adoption, and the local resolve/align/inject alias route.

Validation: 1,318 tests passed across 28 files with zero failures, errors or skips. Actual 2023/2025 parity gates (230/415 targets), canonical regeneration, Ruff, formatting, inventory, coverage and censuses passed. Local validation does not establish GitHub CI status. Exact-head GitHub CI is reported separately from historical fit results. No new source build, national fit or full local calibration was run for this rebase. Tail and age adjustments, liability-model changes and dataset certification remain separate work.

@vahid-ahmadi vahid-ahmadi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review pass at 92778398 (Claude Code, high effort; worktree checkout; the four commits read against #875 and against #892; without the engine or the pinned feed: 634 passed, 7 skipped across the 14 reference, ledger, calibration, gate, graph and contract suites; ruff check and ruff format clean on the 18 changed Python files; both census tools, the coverage manifest and ci_test_groups --verify current).

The CGT contract itself is sound

The three bindings now pin the individuals-only measures of chronicle's hmrc.cgt_statistics_2026.table1.ty2024 record set by aggregate fact key, period_type: tax_year and period_value: 2024, where the previous selectors resolved the trust-inclusive total_* concepts. I fetched the HMRC ODS from the fixture's URL: its sha256 matches the pinned source_sha256, and the 2024 to 2025 row reads 551 taxpayers / £119,258m gains / £22,503m liability for individuals against 584 / £127,316m / £24,169m for the old totals. measurement_period: 2024 with require_matching_fact_period: true disables the stored-column short-circuit and refuses a fact-period mismatch (target_materialization.py:490-519); the dated cgt_2024_* aliases refuse to be persisted and refuse a binding whose year differs from the alias (measure_simulation.py:216-244); the annual exempt amount reads the 2024 parameter (£3,000, numerically the same as 2025). Membership and references regenerate and the lockstep tools are current. Three things stop this being mergeable, one of them doctrine.

Findings

1. Blocking — the OBR target is demoted against #875's standing ruling, and the register entry that defers it is left dormant. uk_population_targets.json moves obr/capital_gains_tax from mapped to excluded ("retained only as diagnostic provenance … removed from fitting"), and target_references.json replaces the obr.capital_gains_tax row with hmrc.cgt.liability_total. But target_fit_reviewed_exclusions.json is unchanged: obr.capital_gains_tax@2025, adjudicated on #875 and in force to 2026-10-05, now defers a target that no longer exists, and the gate classifies it dormant rather than stale so CI will not object. #875's own text under "Not the deliverable" says "signing the gains total out of the solve (dropping a published fact) … considered and rejected in the #874 receipts"; this PR drops the OBR forecast from the solve. That may well be the right call after the individual-basis facts arrived, but it needs a #875 ruling and a signed register edit, not a body line saying "related to #875". One design coupling to note in the same breath: _cgt_cash_diagnostic_metadata (ledger_targets.py:294-340) makes the fitted liability target fail to compile if the OBR diagnostic fact is missing, and with min_matches=3 that blocks the whole national build on any feed without the OBR line.

2. Blocking — the compile-parity artifacts are stale and the parity gate enforces name-set drift. ledger_compile_parity_incumbent_2025_signed_differences.json:3093-3101 still signs hmrc.cgt.gains_total at £127,316m and taxpayers at 584,000 (its reason text still says "HMRC 2023-24 outturn value GBP 65,937,000,000"), and registry_parity_fixture_2025.json still carries obr.capital_gains_tax as a fitted row at £21.80bn. gates.py:262-292 emits fixture_only and ledger_absent kinds for exactly this, so on the feed the parity gate will see an unsigned fixture_only for the OBR row and value drift on both HMRC rows. The three tests that would catch it are the ones that skip without the pinned feed, and test_uk_target_references.py:601-616 still asserts the stale rationale text. The body's "authenticated reference regeneration" covers membership and references only.

3. Blocking (rebase) — the #883 prerequisite is duplicated with #892. The two consumes added to uc_capital_coherence in graph.py:195-196 and the uk_spine.json fixture are byte-identical to #892's, so those will merge cleanly, but the differently named tests inserted at the same point in test_uk_uc_capital_coherence.py conflict, and git merge-tree against #892 reports six conflicts including target_references.json, measure_simulation.py, the feed fixture and the generator. #892 is the PR named for this repair; #889 should drop ad2584a7 and a4980551 and rebase after #891 and #892 land.

4. Should-fix — the results table is unreceipted. No file under experiments/ or docs/evidence/ is in the diff; the baseline/candidate errors, "363 non-CGT rows match" and the ESS fall from 490.82 to 392.85 exist only in the body. Gains' absolute error worsening (+6.76% → −9.28%) is inside the 25% bound and just under the 10% line, and the body notes it without a mechanism; the plausible one is that the target fell 6.3% while the pull moved to liability, whose family coefficient the doc says jumped from 1/420 to 1/63.

Questions

  • This is a parallel dating path, not #881's rule. measurement_period is a generic binding field and the aliases are CGT-specific by regex; resolve_uk_year_rule is untouched. Two mechanisms for "which year does a measure evaluate in" is a maintenance cost worth a sentence, or a follow-up to fold them.
  • matched_fact_count_at_or_before_period: 1 means a future non-provisional HMRC revision fails compile loudly; is there a revision procedure, given the facts are labelled provisional?
  • UKFrameTargetAdapter.column raises for cgt_2024_gains; the local rowwise and diagnostic paths depend on the resolver having set the alias column first. I did not trace that end to end.

Needs the pinned feed or the engine

The three parity tests, the signed-differences regeneration, the engine-year-2024 test, the HDF export tests, and a national fit for the errors table.


Not mergeable as filed. The contract is right; the doctrine, the parity artifacts and the rebase are what need to land first.

@juaristi22
juaristi22 force-pushed the uk-cgt-observed-year-875 branch from 9277839 to 9b5ac7c Compare September 9, 2026 16:37
@juaristi22

juaristi22 commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks @vahid-ahmadi. I’ve addressed the review against 92778398. The functional fixes are in 1922f753; final f2964768 adds only CSV line-ending normalization and explicit export-hash provenance.

  1. UK CGT: translate the 2024-25 gains outturn to the 2025 calibration year (forestalling year; OBR CGT +44.2% signed on the stack register) #875 disposition and dating. The decision and evidence note records the scoped change: this FY2024–25 deliverable fits three individual observations while retaining calibration index 2025 and exported data year 2024. It does not amend/close UK CGT: translate the 2024-25 gains outturn to the 2025 calibration year (forestalling year; OBR CGT +44.2% signed on the stack register) #875 or settle its forward-year cash reconciliation. I retired only the obsolete OBR fit exemption and archived its exact signed history; HMRC liability has no exemption. measurement_period fixes an observation’s date; Declare survey- and calibration-year rules for UK FRS parameter reads (#862) #881’s year_rule resolves release-relative years for source construction, policy lookups and predictors. Automatically substituting the survey year would change the observation’s meaning.

  2. Optional cash diagnostic. Missing, incompatible or duplicate OBR data now records unavailable, its expected identity and a reason, without a zero or replacement forecast. All three valid HMRC observations still compile. Malformed diagnostic declarations and missing/mismatched/duplicate fitted HMRC observations remain errors. Valid cash provenance and value are unchanged.

  3. Compile parity. The historical national fixtures and separate local artifacts remain unchanged. Regenerated signed differences identify individual gains/count, added HMRC liability and the removed fitted cash row. The obsolete rationale is corrected. Inherited Align national UC targets with paid claims and explicit source windows #891 UC differences were separately reproduced against unchanged main, rather than attributed to CGT.

  4. Numerical evidence. The committed JSON receipt and 363-row CSV identify the historical 9b5ac7c5 candidate / cc9c953c control on identical source and priors. Candidate taxpayer-proxy/liability errors are +0.007%/+0.67%, but gains are £108.032bn: −9.41%, versus +6.90% under control weights, increasing the absolute error. All 363 non-CGT coefficients/targets match. Dating, individual scope, liability comparator and 22 family-weight changes form a combined treatment, not an isolated year effect. Later review changes preserve numerical inputs but change diagnostic registry identity; no new-head refit is claimed.

  5. Provisional revisions. A different-key revision alongside the pinned observation leaves the old key selected; removing or duplicating the pinned key fails. Same-key payload alteration is protected by artifact hashes, not the descriptive membership count. Adoption requires authenticated feed/key updates, regenerated references/fixtures/parity, strict-selection checks and refreshed numerical evidence when values change. New-key and duplicate regressions cover this.

  6. Local aliases. The rowwise path resolves each block, aligns entity IDs and injects aliases before materialization. National/local integration tests use the real resolver with distinct 2024/2025 outcomes and verify restored base columns plus fitted weights; a separate UK engine test checks dating. This does not certify a full local fit.

  7. Upstream integration. Align national UC targets with paid claims and explicit source windows #891 is merged. Repair UC capital graph inputs and document calibration support gaps #892 remains open: its exact graph regression replaces the duplicate, while required inputs remain. Two exemption-retirement/test conflicts still need composition when integrated; I have not imported its other retirements or claimed all peer conflicts resolved.

Validation: 1,039 tests passed, zero skips/failures, plus actual full-feed parity gates at 2023/2025 (225/415 compiled targets). UK 2.97.0/Core 3.31.0 and national ec7169b5/local 6fb700e remain pinned. Replacement CI is pending. Original national gate failures remain explicit; this is not dataset certification. Source-vintage, tail/age and liability-model fixes remain separate.

Ready for a second round of review.

@juaristi22
juaristi22 marked this pull request as ready for review September 9, 2026 20:48

@vahid-ahmadi vahid-ahmadi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second pass at f2964768 (Claude Code, high effort; worktree checkout; the response commits read against the round-one findings; without the engine or the pinned feed: 981 passed, 9 skipped, 0 failed across the round-one suites plus the new CGT, ledger, rowwise and feed suites; ruff check clean; both census tools, the coverage manifest and ci_test_groups --verify current).

The substance closed. Two things remain, one of them new since your reply.

1. Doctrine — substantively addressed; the record on #875 is still silent. The note (experiments/889-cgt-observation-year.md) says plainly that it "supersedes #875's previous FY2025-26 fit disposition only for that observed-year deliverable" and is "not a new human signature, an amendment to issue #875, or closure of its reconciliation work". The retired OBR exemption is archived verbatim in docs/evidence/uk-cgt-889/calibration-comparison.json under historical_fit_exclusions with its original approver, adjudication and dates and a retirement reason; no liability exemption exists and the gate tests assert that. Your distinction holds: #875's "not the deliverable" rejected dropping the gains fact, and this PR binds gains, taxpayers and liability. But #875's standing disposition says "the target stays bound in the solve; only the release fence is deferred", and this PR takes the OBR cash row out of the solve — the note concedes as much with "supersedes". #875 has zero comments, and the same person authored the ruling, approved the register entry and wrote the retirement. I am content to be the second reader on the substance; what is missing is one line on #875 itself recording the scoped supersession, so the issue's own text is not the last word on a ruling the code no longer follows.

2. Blocking (rebase) — the branch now conflicts with main. Your item 7 said #892 remains open; it merged yesterday evening. The merge-base is still the #891 merge, and git merge-tree against main conflicts in exactly the two files you predicted: target_fit_reviewed_exclusions.json (main removed the five UC and pension entries, this branch removed the OBR one — the resolution is the empty register) and test_uk_terminal_gates.py. The duplicate graph hunk is gone from the diff, so that part is done. Rebase, then let CI run on the merge ref.

3–6. Verified. The cash diagnostic is optional in the right way: a malformed declaration is a compile error, a missing, duplicate or mismatched OBR fact records unavailable with the expected identity and a reason and no substitute value, and the three HMRC observations still compile — eleven of the new tests fail on the reviewed head. The signed-differences file no longer carries the "2023-24 outturn" text or the £127,316m total; it signs gains 67.7bn → 119.258bn and taxpayers 378k → 551k as calibration drift, liability as ledger-only, and the OBR row as fixture-only with the #889 rationale; the national fixtures are unchanged. The evidence is committed: candidate 9b5ac7c5 and control cc9c953c on the same source, gains −9.41% versus +6.90%, taxpayers +0.007%, liability +0.67%, 363 non-CGT coefficients and targets exact, the CSV 363 rows, and "no new-head refit is claimed" preserved. The provisional-revision tests are regression guards (they pass on the old head too, as your wording implies). The rowwise path injects the aliases before materialisation and the integration tests run the real resolver at both years.


Not mergeable until the rebase; after that I would approve with the #875 line in place. The contract, the diagnostic and the parity artifacts are right.

@juaristi22

Copy link
Copy Markdown
Collaborator Author

Addressed the two remaining points in the second review at 240b272b52a6594e0ec62a7f72c2d9fbc51c7510.

The branch is rebased onto main 45231f9e. It retains the 141bface feature baseline with merged #892 and #877; the later #899 change affects CI scheduling only. The five UC/pension retirements and this PR’s cash retirement now compose to an empty fit-exemption register. The upstream 20 boundary cases and HMRC-liability rejection test remain; duplicate graph-prerequisite commits are dropped while upstream coverage stays. #877’s measure exclusions, corrected local rent composition and calibration changes are preserved.

The scoped record on #875 records the FY2024–25 supersession, preserving the original ruling and signed history and leaving forward-year reconciliation open.

Refreshed parity includes five inherited UC source-window rows in the 2023 comparison; the 2025 changes are explanation-only. Frozen fixtures and the public numerical JSON/CSV remain unchanged. The decision note explicitly labels the 9b5/cc9 fits and 366-row objective historical: the verified current fitting surface has 364 rows. The later base advance preserves the tested runtime and target inputs. No rebased-head fit, unchanged fitted weights or release certification is claimed.

Fresh validation and exact-head CI status: 1,318 tests passed across 28 files with zero failures, errors or skips. Actual 2023/2025 parity gates (230/415 targets), canonical regeneration, Ruff, formatting, inventory, coverage and censuses passed. Local validation does not establish GitHub CI status (CI run).

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.

2 participants