Align UK CGT calibration with FY2024-25 individual observations - #889
Conversation
vahid-ahmadi
left a comment
There was a problem hiding this comment.
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_periodis a generic binding field and the aliases are CGT-specific by regex;resolve_uk_year_ruleis 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: 1means a future non-provisional HMRC revision fails compile loudly; is there a revision procedure, given the facts are labelled provisional?UKFrameTargetAdapter.columnraises forcgt_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.
9277839 to
9b5ac7c
Compare
|
Thanks @vahid-ahmadi. I’ve addressed the review against
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 Ready for a second round of review. |
vahid-ahmadi
left a comment
There was a problem hiding this comment.
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.
f296476 to
240b272
Compare
|
Addressed the two remaining points in the second review at The branch is rebased onto main 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 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). |
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 the141bfacefeature 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 remainsec7169b5, local remains separately pinned to6fb700e, 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 candidate9b5ac7c5comparison using identical authenticated source, priors and 1,500-epoch settings. All values below use FY2024–25 individual measures under those historical fitted weights:cc9weights9b5weightsCount 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
141bfacechange the default fitting surface to 364 of 415 references. The later45231f9ebase 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.