Repository navigation
UK #1334: compute the local metrics from their declarations, and measure the local HMRC rows over each person's year - #1347
Conversation
8db7e04 to
8b1e97f
Compare
|
@juaristi22 thanks, this is my review at Verdict. The design is right. One interpreter and one resolver now serve the national and local lines, so a rebind moves both. I checked commit 1's parity claim on a second file and it holds. Commit 2 points the five local HMRC rows at the right concept: HMRC's area tables count taxpayers over the tax year. I would not merge yet, for two reasons. First, commit 2 breaks two engine tests that pass on commit 1, and CI won't catch them until the PR retargets (item 1). Second, the branch sits on a superseded #1344 history and on a What I ran
Generated data versus code. GitHub shows 95 files and +74k lines, but nearly all of that is #1344 underneath. This PR's own two commits are 19 files, +3,943 / −3,447:
Findings1. must: commit 2 breaks two engine tests. At the head:
Both fail with Suggested fix: give the two fixtures the descriptor columns, as 2. must: restack onto the current #1344, and then onto
3. should: the incumbent extractor's high-value-homes figures change. Say so.
Please add a sentence to the body and to 4. should: the local rule-1 comparison has the same like-for-like gap as the national one. The extractor reads the incumbent's annual measures as its current values ( 5. should: the eFRS incumbent path now refuses. Prune it in this PR, or make it refuse clearly.
Either bring the prune into this PR, since the register entry already exists and runs to 2026-12-22, or catch the error in the extractor and name 6. should: the binding-key guard does not look inside filters. 7. should: the census fences still point at the deleted code. Three fences in
Please point the authorities at the five declarations and update the rule text. If the HMRC area rows are bound under a signed adjudication, record there that the proxy is now the annual one. 8. nit: the five selector notes swap HMRC's table numbers. They say "Tables 3.14 (constituency) and 3.15 (local authority)". HMRC's Table 3.14 is "Income and tax by borough and district or unitary authority" and Table 3.15 is "Income and tax by Parliamentary Constituency" (both 2023 to 2024). This is pre-existing text, but these are the declarations this PR edits. 9. nit: two small concept gaps against the HMRC tables, for the notes. Both are pre-existing and both apply to the national rows too.
A sentence in the selector notes would do. 10. nit: 11. nit: Disclosure (small areas)
Merge order and overlaps
What looks right
🤖 Generated with Claude Code |
8b1e97f to
ed095db
Compare
1c42b3d to
ab50a90
Compare
|
@juaristi22 thanks, this is my second look, at Verdict. Both musts from round 1 are resolved. The two engine tests pass, and the branch now sits on #1344 after #1333. Nothing in this PR blocks it once #1344 merges and it retargets to Status of round 1
A correction to my round 1Under "What looks right" I said commit 2 "matches HMRC's concept". That was too strong. The annual measures are each person's 12 months before their FRS interview, which is a proxy for the tax year, not the tax year itself. #1344's rebased changelog and this PR's body and changelog now say so plainly, and that wording is right. The direction of commit 2 still holds: it moves the local rows from a point-in-time read to a year of income, as HMRC counts. But it is a proxy, and I should have called it one. What I ran
Merge order and overlaps
Disclosure (small areas)No change. The PR adds no output surface and the body reports parity without area or record figures. My view from round 1 stands: keep "don't publish local diagnostics" in force until the local-line ruling lands, since commit 2 changes who counts in five local rows and no carrier check covers those rows yet. No new findingsThe new code in this round is the two fixture changes. They give a full year at today's income, so the annual measures equal the current ones and the tests still check what they did on commit 1. The comments say why. Nothing else is new. 🤖 Generated with Claude Code |
811f346 to
e399d21
Compare
compute_household_metrics reads each constituency, local-authority and BRMA metric's policyengine binding in uk_population_targets.json through the national rows' binding interpreter (prepared_binding_values, now public), on columns the measure resolver computes, and sums person and benefit-unit bindings onto their household with the engine's map_result. The local and national lines share one interpreter and one resolver, so a rebind moves both. A local declaration that carries a key the local line does not honour refuses. The full measure passes each block's resolver, and the incumbent extractor builds one over the incumbent file. Ten declarations change so they say what the code computed: the UC child bands count UC benefit units by uc_calibration_child_count, the FRS-role composition of the national UC-by-children rows; the private rents read benunit_rent summed to the household; the high-value homes read the owner_occupied_home_over_gbp_* measures; and the gas-connected households read the new domestic_gas_connected, which reads the stored spend at the dataset period as the kWh measures now do in the resolver. The interpreter gains in lists, lower-inclusive ranges and subtraction, and refuses a predicate that declares input_period or price_basis. On a national frame the 61 metric columns of the three grains match the old code bit for bit, except ons/equiv_housing_costs, which no active target reads: the difference of the two incomes is now taken in float64, not float32. The regenerated local references change only the entity they record. The local tests drive the real resolver over a stub engine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The five local hmrc declarations (the self-employment and employment amounts and counts and the higher rate taxpayers, by constituency and local authority) read the annual measures for their values, their taxpayer test and their rate band, as the national rows do (self_employment_income_after_loss_relief_annual, employment_income_annual, income_tax_annual and tax_band_annual). Target ids, facts, values and metric names do not change. Now that the local line computes its metrics from their declarations, the rebind is the declarations alone. The incumbent extractor reads an incumbent without annual income descriptors on its current values, as rule 1 scores it nationally (R1). An engine test counts a leaver who paid tax on the year's spell in their area, and the engine tests of the local energy and tax-band rows give their frames full-year descriptors, as production frames carry them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ab50a90 to
c0578bf
Compare
The changelog fragment's 2025 income tax and costing figures come from the e399d21 arm, signed with a throwaway key, against #1338's certified build. Say so, and say that current income lands below OBR's receipts on this build rather than by construction (Vahid's items 25 and 26 on #1344). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@juaristi22 thanks, this is my third look, at Verdict. The rebase is clean and changes no logic. Nothing in the code blocks this PR from our side. The two shoulds I asked for before merge in round 2 are still open, and both are wording only: item 3 (the body still says "with no change in output", and the changelog is silent on the extractor's three Status of earlier items
What changed since round 2
What I ran
Merge order and overlaps#1329, #1332, #1333, #1338 and #1344 are all merged, and this PR sits on Disclosure (small areas)No change. My view from round 1 stands: keep "don't publish local diagnostics" in force until the local-line ruling lands. No new findings🤖 Generated with Claude Code |
Vahid's round 3 on #1347. The census fences cite the declarations instead of the deleted in-code metrics: the HMRC area fence's rule is income_tax_annual > 0, and the UC grain fence and family name the FRS-role child count the bands read. The census JSON is regenerated, and María's signed SPI-frame adjudication is re-worded to the annual frame at both grains, its dates unchanged. A local filter may carry only a column test; one that names an entity, a concept or a reduction refuses. The incumbent extractor names benunit.uc_calibration_child_count and the incumbent-unresolvable register when an incumbent lacks the UC relationship inputs. The five local HMRC notes cite Table 3.15 for constituencies and 3.14 for local authorities, and say where HMRC's self-employment count and employment income differ. The energy cache is set in the resolver's init, and the bindings cache says how tests clear it. The changelog says the extractor's high-value homes columns move on a release incumbent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r like The fragment compared full-year UK-wide liabilities with HMRC's 2025-26 ready reckoner column, which differs on two counts. That column counts Self Assessment tax when it is paid, so it holds little of a change's Self Assessment effect; fitted to HMRC's later columns, a full year is 9% to 11% higher. And its scope is the UK government's exchequer impact: it leaves out the devolved Scottish and Welsh elements and adds the change in their block grants (note 1). On that scope and basis, in calendar 2025, the £1,000 allowance rise moves from 3.1% to 1.1% above HMRC, and 1p on the basic rate, with the rUK property income the engine's main-rate parameter misses, moves from 1.3% to 2.9% below. Moved to 2025-26, both gaps are about 1.9% (microcosm#1370). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vahid-ahmadi thanks. Round 3 is addressed in
Tests. The four engine-free files that the change touches, or that read the changed registers: 105 passed. #1370 (commit 5). The personal allowance gap is mostly not the model's. HMRC's 2025-26 column counts Self Assessment tax when it is paid, and it is on the UK government's scope (note 1). Like for like, the allowance rise is 1.1% above HMRC and 1p is 2.9% below, in calendar 2025; moved to 2025-26, both gaps are about 1.9%. The model's taxpayers by marginal rate are within 1% of HMRC's, so no calibration change follows. Commit 5 puts the changelog's comparison on that basis, and the split is on #1370. The 1p reform misses rUK property income, which is an engine issue, filed as PolicyEngine/policyengine-uk#2607. 🤖 Generated with Claude Code |
|
@juaristi22 thanks, this is my fourth look, at Verdict. Every round 3 should and nit is resolved at this head, and I checked each one in the code, not just the reply. Nothing in the code blocks this PR from our side. Item 4 stays open by design, for when the local scorer runs. Commit 5's HMRC comparison matches the January 2025 bulletin, and its arithmetic holds. I have two nits on how settled the full-year figures sound (items 12 and 13), both wording only. CI has not given a full result on any head since the PR moved to Status of earlier items
Commit 5 and #1370I checked the comparison against HMRC's "Direct effects of illustrative tax changes" bulletin (January 2025):
I could not check the model-side split (the Scottish and Welsh block-grant terms, the taxpayer counts, the taper zone) without a build, so I have not. 12. nit: the full-year uplift is a fit, and the changelog reads as if it were a published figure. HMRC's three columns don't pin a full year on their own. As a rough check, I took the 2027-28 column as a full year and deflated it to 2025-26 at your own 2.4% to 3.8% a year. That gives about +9% to +12% for the 1% allowance row, +7% to +10% for the 10% row, and +11% to +14% for 1p. Your 9.0%, 9.8% and 11% sit inside those bands or at their edge. But the bands are a few points wide, about as wide as the ±1.9% residuals. The £100 row is also out of line with the 1% row in the later years (£1,100m against £1,150m for about £126 in 2027-28), which is presumably why you left it out. Please either state the fit in #1370 (which columns, what Self Assessment share and growth), or give the changelog's "about £8.45bn" and "£7.28bn" as ranges. Then "like for like, about 1.9%" reads as within the fit's uncertainty, not as a measured gap. The fragment is unreleased, so this can go in any later commit. 13. nit: £0.09bn or £0.10bn. PolicyEngine/policyengine-uk#2607 says moving What I ran
Disclosure (small areas)No change. My view from round 1 stands: keep "don't publish local diagnostics" in force until the local-line ruling lands. 🤖 Generated with Claude Code |
On
mainat4cae4e426, after #1344 merged. Commits 1 and 2 are rebased from #1344's head with no code change; the national-rows changelog fragment keeps #1344's refreshed costing figures, and this PR's sentence on the local rows.e6ac8b157labels those figures as from the pre-Budget arm (#1344 review items 25 and 26). Commit 4 answers Vahid's round 3 on this PR. Commit 5 puts the changelog's costing comparison with HMRC on a like-for-like basis (#1370).Why
The local line computed its 43 metrics in hard-coded code (
local_targets.compute_household_metrics), beside the 59 local declarations inuk_population_targets.json. The national line reads its declarations. So a rebind moved the national rows but not the local ones. #1344 moves the national HMRC income rows to each person's year of income: the 12 months before their FRS interview, a proxy for the tax year. The five localhmrcrows could not follow it without a second, hand-written copy.What changes
Commit 1. The local metrics come from their declarations, with no change in the build's local output.
compute_household_metrics(resolver, area_type, ...)reads each metric'spolicyenginebinding through the national rows' interpreter (prepared_binding_values, now public). It reads the columns throughUKMeasureResolver. Person and benefit-unit bindings sum onto their household with the engine'smap_result, as before.high_value_homes/*columns do move on a Microcosm-release incumbent. The old extractor read the stored main residence value and tenure at 2025, where the engine uprates the value, against thresholds deflated to the dataset period. The resolver reads them at the frame's period, as the build's local line already did. So the local rule-1 comparison moves on those three metrics, and the build's local line does not.from_entity,map_tohousehold). Any other key refuses.uc_calibration_child_count. This is the FRS-role composition that the national UC-by-children rows read. The old code read the engine's storedis_uc_claimant, which the spine writes from the same roles.benunit_rentsummed to the household, filtered on the household's tenure.owner_occupied_home_over_gbp_*measures (stored value and tenure, deflated thresholds).domestic_gas_connected. Like the kWh measures (domestic_electricity_kwh,domestic_gas_kwh, also new in the resolver), it reads the stored spend at the dataset period.inlists, lower-inclusive ranges and subtraction in value expressions. It refuses a predicate that declaresinput_periodorprice_basis, which it cannot honour. No binding carries one now.local_target_references.jsonis regenerated. Only the recorded entity moves:benunitfor the 2,528 UC child-band references, andhouseholdfor the 334 private-rent references. The membership file does not change.Commit 2. The local
hmrcrows read each person's year of income.Commit 4. Review round 3.
income_tax_annual > 0, and its authority names the fivehmrcdeclarations. The UC grain fence names the fivedwp.uc.households_by_area*declarations, and it and the family's description name the child count the bands read (uc_calibration_child_count, the FRS-role count, not the old code'snum_children). Thebound_in_codestatus says the metric comes from its declaration.uk_local_target_census.jsonis regenerated: those six lines only. María's signedhmrc_spi_frame_model_proxyadjudication is re-worded to the annual frame at both grains, with its dates unchanged.variable,operator,value,equals,in,lowerandupper. A filter that names an entity, a concept or a reduction refuses.benunit.uc_calibration_child_countand the incumbent-unresolvable register when an incumbent lacks the UC relationship inputs.hmrcnotes cite Table 3.15 for constituencies and 3.14 for local authorities. The self-employment count and the two employment rows say where HMRC's concept differs.UKMeasureResolver.__init__.Commit 5. The costing comparison with HMRC, like for like (#1370).
Measurement
Parity of commit 1. One resolver over the annual arm's national frame (
uk-prebudget-annual-c1e0b099a, calibration year 2025, dataset period 2024). The script computed every local metric of the three grains through the declarations, and through the old function (verbatim from the base:local_targets.pyis unchanged onmainup to4cae4e426) on the same engine:ons/equiv_housing_costsdiffers within float32 precision. The old code subtracted the two equivalised incomes in float32; the new code does it in float64. No active local target reads this metric (the BHC/AHC fence).The UC child bands are identical, so on this frame the stored
is_uc_claimantand the recomputed FRS roles agree. The frame predates #1344's drop of fix 3; parity compares two versions of the code on one frame, so that does not matter. The rebase changes neither the local declarations, the references nor the measures they read (the resolver changes onmainsince are docstrings, bus-fare pricing and the national DfE spring-term measures).Commit 2 is not measured on a local build yet. On the local rows it does what #1344 does on the national ones: for example, a recent leaver whose year of income makes them liable to income tax counts in their area.
For review
dependent_childrencolumn, so it cannot computeuc_calibration_child_count. The constituency extraction now refuses at the UC child bands, and the error names the measure and the register. The old code computed them from the engine's fallback roles, which is a different composition from the candidate's. The national scorer prunes this measure under the signed incumbent-unresolvable register (benunit.uc_calibration_child_count, in force to 2026-12-22). The local comparison (score_uk_local_candidate.py) needs the same prune before it can run on the eFRS. It is opt-in since UK local-area release: publish a K=15, 150,000-household dense dataset in at most two builds #1301. See "Not in this PR".test_support/microcosm_build/uk_local_metric_fixtures.py). The fixture's third benefit unit gains a lone parent: the FRS roles need one or two claimants per unit, and the old fixture's lone 12-year-old had none. The expected values do not change. The two engine tests build a small frame and a real resolver.Tests
On the same code rebased onto #1344's
e399d21ef, with policyengine-uk 2.147.2 as the lock pins it. Since thenmainadds only #1344's changelog commit and #1362, which touches NZ files only.test_engine_is_reported_unavailable_without_the_uk_extraexpects no engine, as CI's engine-free job has none.ruff check .,tools/ci_test_plan.py verifyandtools/graph_acceptance_burndown.py --verify: ok.ruff checkandruff format --check: ok.main.Not in this PR
🤖 Generated with Claude Code