Repository navigation
Conversation
survey_consumption_coverage was derived in #2187 on Microcosm f9d1922c. The 6cba5d71 and a133f37a cuts record about 10% more standard-rated consumption, so with 0.67 total vat ran 7.5% to 10.3% above OBR receipts in 2024-25 to 2026-27. The doc's own calculation on a133f37a gives 0.739 for the latest outturn year; it is used as 0.74 (product 0.518), inside the 0.720 to 0.756 bound from receipts and the ready reckoner. Adds Hypothesis properties for the grossing factor and for vat being statutory liability over the shipped product. Totals at the new product and the reform-regression fixture wait on runs in the hub's next reserved window. Fixes #2633 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deprecated combined factor has one undated value, so every year, 2024 included, takes the two factors' product. A YAML case pins vat in 2024; adding a dated entry to the deprecated factor makes it fail. The doc's inputs now give the Hugging Face revisions of the a133f37a and 6cba5d71 tags, and the reproduction snippet pins a133f37a's revision. Refs #2633 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 49 (thresholds in Notes
Moves flagged for review
9 more flags are in the report artifact. Receipts by tax head
Spending by benefit and service
Caseloads
Government balance, each component counted once
Model totals
Standard reform costings (change in gov_balance)
Standard reforms: change in each reform's target
Household net income by decileNo household's net income changes in any year. No change in any year: household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles. Checks failing on both base and head (1)
Counted-once checkEach of 92 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.
Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
…p.isclose Review r1 of #2634 (Sol, REQUEST_CHANGES at b6d8af5) found: - The £9.94bn one-point figure was described as a reform run without the runs being cited. The doc now names them: reform minus baseline `vat` and `gov_balance` on 6cba5d71 (revision bb225848), policyengine-uk 2.135.7 and 2.152.6, £9.94bn in 2026-27 and £9.68bn in 2025-26. - The moved-legacy property classified closeness with pytest.approx, which takes the larger tolerance, while the helper uses np.isclose, which adds them. A legacy value of 0.518005185 failed the property. The band is now stated once (1e-8 + 1e-5 x the default), the moved property draws values outside it, a new property draws values inside it, and fixed cases sit at 0.99 and 1.01 bands either side. Changing the helper to exact equality fails the inside cases. - The helper properties kept Hypothesis's 200 ms deadline and flaked on a loaded host; they now have no deadline. - The doc attributed both cuts' rows to main and core 3.33.2; it now gives each cut's recorded versions. - The parameter said 6cba5d71 matches a133f37a to three decimal places; it now says the 2024-25 coverage (0.739) matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review r1 (GPT-6.1 Sol, REQUEST_CHANGES at b6d8af5): all five findings are fixed in e1ff493.
Local runs at e1ff493:
Still PENDING, and the PR stays a draft until they are done:
These run in the hub's next allowed host window. A delta review follows them. |
|
Plan change (24 Oct): this PR no longer lands on
So the value is re-derived on Monday's final cut, after its fit, with the formula on main. The 2pp fixture is updated from a run on the same code, and a delta review follows before this is marked ready. If that run puts VAT within ±2% of receipts at 0.67, this keeps 0.67. At #1383's head The figures run today are a reference only, on
|
Takes main's survey coverage (0.67) and derivation doc, and keeps this branch's property tests, the 2024 pin test and the record of the later Microcosm cuts. Test expectations go back to the 0.469 product. The coverage is re-derived with #2189's formula on the next certified cut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged main (with #2189) at The coverage is re-derived with #2189's formula on the next certified cut, after its fit. Title and body updated to match. |
docs/engineering/vat-coverage.md conflicted because both sides added a section after "Energy bills are not grossed up". Both are kept: main's private school split first, since it describes the current formula, then the later-cuts record. vat_grossing.yaml merged cleanly; the 2024 case is unaffected because the school-fee share is zero in model year 2024. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Picking this up for the UK hub. Reply here if you're mid-change and I'll stop. Generated by Claude Code |
vat takes private school fees' share of VAT receipts out of the grossed-up amount (from #2319, merged into this branch). The property's oracle divided the statutory liability by the shipped product without that step, so every example with more than about £0.56 of liability failed by the share (0.8353% in 2025). The oracle now reads the share from the parameters, asserts the households carry no energy-bill VAT, and always runs the minimal case as an explicit example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6J9S5H2Fv9MFjnDPCr
|
Ready for hub review Branch: Finding: the test's oracle is wrong, not the model
What the spend pair means: Energy-bill VAT (#2189): zero in these households. They record no energy bills, so Rounding and float32: Minimal case, by hand (one household, one spend line, PR head, 2025)
On the merge with I ran each of these through a one-household Reproduction under Fix (
|
| Run | Seed | Result |
|---|---|---|
| Fixed branch | 320551620663142353140068881523791551106 |
8 passed |
| Fixed branch | 769219029 (random) | 8 passed |
| Fixed branch | 12353493 (random) | 8 passed |
| Fixed branch | 2995432640 (random) | 8 passed |
Local merge of the fixed branch with main, after uv sync --frozen |
320551620663142353140068881523791551106 |
8 passed |
Other tests:
test_vat_grossing_properties.py,test_vat_grossing_legacy.pyandtest_private_school_vat_receipts.py: 55 passed.policyengine-core test policyengine_uk/tests/policy/baseline/gov/hmrc/vat: 10 passed.code_health: 2887 passed.ruff@0.17.2 format --check,check,check --select F811,F821andgit diff --check: all clean.
Skipped
I ran nothing that needs survey data. shard_durations.toml doesn't exist on this branch, and I renamed no tests.
Proposed PR body changes (not edited)
Invariant 3 is now untrue. I propose replacing it with:
- Each household's
vatwith no energy consumption equals (standard rate ×full_rate_vat_consumption+ reduced rate ×reduced_rate_vat_consumption) / shipped product × (1 −private_school_share_of_receipts). It is linear in each household's consumption, with no cross-household term.
In the paragraph after the invariants, I suggest adding: "Dropping the school share from vat, or the household share from vat_grossing_factor, fails invariant 3 on its fixed example."
Judgment calls
- I fixed the oracle, not the model, because the doc's allocation section and
vat.pyagree onG × (1 − s) + E. - I kept the test's name.
- I asserted that E is zero rather than adding
domestic_energy_vatto the oracle. - The fragment is typed
fixed, but the body'sReproducer:readsn/a. The Fix evidence check may wantReproducer: policyengine_uk/tests/test_vat_grossing_properties.py::test_vat_is_statutory_liability_over_the_shipped_product. I've left that, and the fragment type, to the PR owner.
Generated by Claude Code
|
Ready for hub review Verdict: REQUEST_CHANGES at The code delta is sound. The corrected oracle matches P1P1-1. Fix evidence fails with the current body, and would still fail on this branch
I ran
Proposed: against
That line passes under either type, so it stays valid if the coverage later moves and the fragment becomes P1-2. Invariant 3 in the body is false at this headBody, "Invariants", item 3: " In the paragraph after the invariants, replace "A mutation that drops the household share from Each clause rests on a mutant I ran (see "Mutants" below). P2None. P3P3-1. The fixed example covers only the reduced rate, so the standard-rate term depends on random drawsThe test is @example(spend=[(0.0, 15_438.0), (10_000.0, 0.0)])P3-2. The module docstring reads "less the share" as subtraction of an amountThe text is at P3-3. The hand-off's lint used a ruff version other than the lock's
Checked and sound
What I couldn't check
Generated by Claude Code |
|
Picking this up for the UK hub. Reply here if you're mid-change and I'll stop. Generated by Claude Code |
The fragment moves to 2633.added.md with its text unchanged: against main this branch adds tests and docs and changes no model output. The statutory property's fixed example gains a full-rate household, so a formula that charges standard-rated spending at another rate fails on every run, not only on random draws. The module and oracle docstrings say the school share multiplies the grossed-up VAT by one minus the share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N2tyHQ6oJEGs3igENBpYcM
|
Generated by Claude Code |
|
Generated by Claude Code |
|
Ready for hub review Branch: Findings and how I resolved themP1-1, Fix evidence. I renamed P1-2, invariant 3. P3-1, the fixed example. P3-2, the docstring. P3-3, ruff. I linted with the lock's Title. It had 99 characters. The new one has 66: Main. Mutation checksI ran these in a scratch copy of the property file with
On the real property file at Tests
I ran the property seeds against the working tree just before committing, and the committed content is identical. I started two more runs of the legacy pair, but my own 500 s timeout stopped them before they finished. They reported no failures, and I don't count them. Judgment calls
Skipped
Generated by Claude Code |
|
Ready for hub review Verdict: APPROVE WITH NITS at This commit and the live body close all four findings from the previous round. The delta is a rename, Previous findings
P1None. P2None. P3P3-1. The new docstring has two passivesThe text is at P3-2. The changelog fragment keeps "less private school fees' share"
P3-3. The body could claim the mutant that the new example now killsThe example was added to kill full-rate spending charged at the reduced rate (mutant F below), but the body's mutation sentence doesn't list that mutant. Optional replacement for the second mutation sentence: Checked and sound
What I couldn't check
Generated by Claude Code |
juaristi22
left a comment
There was a problem hiding this comment.
Reviewed exact head 36bbbf42f34a23968159406d1b6d8942ebcd6e1c. PARTIAL overall: no code findings, one nonblocking documentation finding and one precise source gap.
A1 — Refresh the current-default dataset provenance. The body calls a133f37a the current default and cites policyengine.py#715. That PR is closed without merging, and the actual wrapper main now certifies a different cut. Please identify the older cut as historical and refresh the current-default reference, while keeping the historical pre-energy-split comparisons clearly dated. This does not change this head's model outputs.
All 65 focused tests passed: 55 Python tests and all 10 serial YAML cases, with the repeated YAML IDs accounted for individually. Formatting, lint and fix-evidence checks also pass. No formulas, parameters or dependencies change.
The actual Microcosm #1383 merge, model pin and VAT diagnostic exclusion were checked, and HMRC's June 2025 ready reckoner corroborates the £8.8bn one-point benchmark for 2026–27. I could not independently read the exact March 2026 OBR table 3.8 receipt figures after two bounded access attempts. The available archived October 2026 workbook is a different edition and cannot substitute for that evidence; please provide an accessible source artifact for the three March rows.
The declared pre-merge requirement to re-derive coverage on the next certified cut remains outstanding. Approval is withheld while the PR is a draft, that measurement gate remains open, and A1/the source gap are unresolved.
|
Picking this up for the UK hub. Reply here if you're mid-change and I'll stop. Generated by Claude Code |
Re-derive gov.simulation.vat.survey_consumption_coverage on the certified Microcosm UK 2024-25 national cut a6299504, whose fit holds obr.vat out: 81.48 / (0.7 x (170.994 - 2.33)) = 0.690 in 2024-25. The deprecated combined factor moves to the product, 0.483, and the parameter's reference adds the OBR receipts workbook's own URL. Tests that pin the product move with it: the 2024 and 2025 cases in vat_grossing.yaml, the legacy default in test_vat_grossing_legacy.py, FACTOR in test_private_school_vat_receipts.py, whose published receipts check now uses a6299504's rounded inputs, and a stale illustrative figure in test_scenario_baseline_parameters.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WR8LHapmX36bpbh3SMixXg
…ance Add a derivation section for the certified Microcosm cut a6299504: the cut's provenance and certification, the obr.vat exclusion, the OBR receipts workbook (URL, SHA-256, archived copy, sheet, row and cells), the chunked method, the results at 0.67 and 0.69 on the cut and on Enhanced FRS 1.58.1, the bound and a reproducing snippet at revision 994cac89. Name policyengine.py's certified cuts correctly: a6299504 today, 6cba5d71 on 20 October, and a133f37a only proposed in policyengine.py#715. Retitle the October derivation as the earlier one on f9d1922c and date its statements of the value, and point the switch plan at the new cut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WR8LHapmX36bpbh3SMixXg
Use the proposed wording for the property module's docstring, take "times one minus" for the school share in the added fragment, and drop the fragment's claims that the cuts were certified since f9d1922c and that the value waits for re-derivation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WR8LHapmX36bpbh3SMixXg
The one-point check 4,337,453,646 x (1 - 0.008311) / 0.483 comes to 8,905,600,556, within 8,968 of the run, and the six-decimal 2026-27 coverage did not follow from the published inputs, so the doc keeps the three-decimal values. Date the 0.38 factor's and run 40116746457's figures, qualify the ready reckoner's 0.704 as leaving out the school share, say that baseline_vat keeps the two factors under a reform to the deprecated one, and attribute the pre-split division by receipts to the calculation rather than the model. Say that 0.69 matches 2024-25 receipts, show the 2025 YAML case's full computation, and name the cuts the added fragment's record covers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WR8LHapmX36bpbh3SMixXg
|
Generated by Claude Code |
|
Generated by Claude Code |
|
Ready for hub review Head The merge with Findings and how this head resolves themjuaristi22's review (PARTIAL at
The hub's review (APPROVE WITH NITS at
Errors in the run record. An independent review of this diff (four reviewers, each checked by a skeptic) found these two. I confirmed both by exact arithmetic.
Figures and their runsRun 7: the Microcosm bases, energy-bill VAT, pre-scaling VAT, coverages and totals at 0.67. Run 9: the Microcosm totals at 0.69, and the one-point rise in Grep for coverage-dependent figuresI ran it at
Tests at this headThese are clean runs of head
Revert checksThese ran in a scratch worktree of this head, with imports confirmed to load from it. Each check covered
Skipped
Judgment calls
Generated by Claude Code |
main's October 2026 EFO update moved private school fees' 2025-26 share of VAT receipts to 0.008368, and this branch moved the product to 0.483. The 2025 vat_grossing case keeps this branch's product and takes main's share: 2,050 / 0.483 x (1 - 0.008368) = 4,208.79. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main's October 2026 EFO gives 2025-26 outturn, so "the latest outturn year" no longer names 2024-25. The doc now gives the reason that holds under both EFOs, and says that the 2025-26 and 2026-27 rows come from policyengine-uk 2.157.1 on the March 2026 EFO. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The branch also merges |
juaristi22
left a comment
There was a problem hiding this comment.
Re-reviewed af0b602ca3170417bf306e162adcd130f94f60f6. COMPLETE within the reviewed scope: A1 and the prior OBR source gap are resolved. One nonblocking suggestion remains.
Fresh public wrapper manifests/certification confirm the current a6299504 pin, rather than the unmerged alternative. The official March 2026 OBR workbook's receipt cells and cash basis were checked directly. The published calibration inputs independently give coverage about 0.69012 and product 0.483; the empirical population bases/totals remain author-reported, not a new independent population reproduction.
S1 — Date the private-school accuracy claim. In survey_consumption_coverage.yaml:29, the retained present-tense “at current law” 0.06% bound belongs to the older 0.67/run 40116746457 result. Please date or qualify it as historical, matching the refreshed documentation. This does not establish that 0.69 or household outputs are wrong.
Validation: 92 completed checks passed before the 540-second limit, including all 55 material changed/direct Python checks and 36 VAT/integration YAML cases. The selected 187-item run did not finish; after running the previously unreached native diagnostic alone, 94 ancillary regression nodes remain uncompleted. No full-suite local pass is claimed. Three native configurations passed through final VAT, tax, government balance and household income: the new coverage lowers VAT/tax by £125.64 and raises household income by the same amount, preserving energy VAT and explicit legacy overrides. Hosted checks remain separate.
I am leaving a comment rather than approving while S1 remains open and current-head CI is pending.
…e-uk#2634 (microcosm#1359) The prior-weighted full-rate VAT base falls 1.7%. The coverage that matches receipts moves by about that ratio, from whatever value the cut it is re-derived on starts at: 0.69 under PolicyEngine/policyengine-uk#2634, derived on a6299504, which gives about 0.68. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-uk#2634 (microcosm#1359) The prior-weighted full-rate VAT base falls 1.7%. The coverage that matches receipts moves by about that ratio, from whatever value the cut it is re-derived on starts at: 0.69 under PolicyEngine/policyengine-uk#2634, derived on a6299504, which gives about 0.68. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-uk#2634 (microcosm#1359) The prior-weighted full-rate VAT base falls 1.7%. The coverage that matches receipts moves by about that ratio, from whatever value the cut it is re-derived on starts at: 0.69 under PolicyEngine/policyengine-uk#2634, derived on a6299504, which gives about 0.68. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The coverage description's 0.06% bound comes from the run at a coverage of 0.67 (Aggregate impact run 40116746457, 24 October 2026), so it now says so, as docs/engineering/vat-coverage.md does. The Aggregate impact report for this branch at af0b602, merged into main at 1d4d020 (run 41633358040, policyengine-uk 2.172.1), costs the 2pp VAT rise at £27.07bn in 2029-30 on Enhanced FRS 2024-25 (policyengine-uk-data 1.58.1), the change in gov_balance that test_reform_impacts.py measures. The fixture now expects 27.1 and keeps that costing in its receipts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22
left a comment
There was a problem hiding this comment.
Incremental review at ffe20fb9165ddcda48ac57531f87847ecc317b9e — COMPLETE; all findings resolved.
S1 resolved: the private-school receipt bound now explicitly names coverage 0.67, run 40116746457 and 24 October 2026. It is a dated result, distinct from the current 0.69 Microcosm derivation. A1’s dataset-provenance correction remains resolved.
The new reform fixture is supported by Aggregate run 41633358040: the 2029–30 gov_balance impact of the two-point VAT rise is £27.070598798bn on certified Enhanced FRS 1.58.1, correctly recorded as 27.071 and rounded to the 27.1 fixture. The verified run merged af0b602ca with main at 1d4d020aa; this costing is separate from the private Microcosm calibration.
Fixture schema, consumer year/metric/units and rounding pass static validation. Numerical parameters, model code, dependencies and existing focused VAT/final-income tests are unchanged; their valid prior proof and timeout limitations are retained. No new model or population run was performed. The PR remains draft, with current-head CI still in progress.
juaristi22
left a comment
There was a problem hiding this comment.
Approved at ffe20fb9165ddcda48ac57531f87847ecc317b9e after the readiness and CI follow-up. The completed model/source review remains valid at this unchanged head and description; A1 and S1 remain resolved, with no open findings or material evidence gaps.
All applicable checks now pass at this exact head, including the YAML/Python test result and Aggregate impact. The earlier focused test and public costing evidence is retained with its documented execution limits; this follow-up did not repeat model tests or a private population run.
Fixes #2633
Reproducer: policyengine_uk/tests/policy/baseline/gov/hmrc/vat/vat_grossing.yaml, policyengine_uk/tests/test_vat_grossing_legacy.py::test_legacy_default_is_the_product_of_the_two_factors
axiom: n/a: a microsimulation calibration factor for survey under-recording, not law
We move
gov.simulation.vat.survey_consumption_coveragefrom 0.67 to 0.69, the value we derive ona6299504, the Microcosm UK 2024-25 national cut that policyengine.py certifies. The deprecated combined factorgov.simulation.microdata_vat_coveragemoves to the new product, 0.483. The PR also adds VAT grossing property tests, a 2024 grossing case, and the cut's provenance and receipts source indocs/engineering/vat-coverage.md.Before the run, we set a rule. If a run with #2189's formula, on the cut that follows microcosm#1383's rebuild, put 2024-25 VAT within ±2% of receipts at 0.67, 0.67 would stay; otherwise the coverage would move to the value that run gave. On
a6299504, whose fit holdsobr.vatout, totalvatat 0.67 came to £176.06bn in 2024-25, exceeding OBR's £170.994bn by 3.0%, and the 2024-25 coverage came to 0.690. So the coverage moves to 0.69.What changed and why
We derived
gov.simulation.vat.survey_consumption_coveragein #2187 on the Microcosm UK 2024-25 cutf9d1922c(2 October 2026), and re-derived it in #2189 with energy bills split out. Its metadata tells maintainers to re-derive it whenever the dataset it should match changes.policyengine.py certified
6cba5d71on 20 October 2026 (commita66e883c4). A proposal to certifya133f37a, policyengine.py#715, closed unmerged on 27 October 2026, so no policyengine.py release certified that cut. policyengine.py now certifiesa6299504: commit324ecad65certified it on 26 October 2026, release 6.11.1 shipped it, and release 6.12.0 still certifies it.On 24 October 2026, before the energy split, we ran the two later cuts.
6cba5d71anda133f37arecord about 10% more standard-rated consumption thanf9d1922c(£435.60bn against £396.15bn in 2024-25 ona133f37a), so at 0.67 totalvatona133f37a(mainc0252415a) exceeded receipts by 7.5% to 10.3%:vatOn
6cba5d71, raising the standard rate from 20% to 21% raised totalvatandgov_balanceby £9.94bn in 2026-27 under policyengine-uk 2.135.7 and 2.152.6 alike, exceeding HMRC's £8.8bn ready reckoner by 12.9%. These pre-split runs gave the reason to re-derive the value, and none of them sets it.This branch changes the parameters, tests, docs and changelog:
survey_consumption_coveragemoves from 0.67 to 0.69. Its derivation now describesa6299504(81.48 / (0.7 x (170.994 - 2.33)) = 0.690 in 2024-25), and itsreferencenow lists the OBR receipts workbook's own URL beside the EFO page. Its 0.06% bound on the private school VAT split now names the run at 0.67 that measured it.microdata_vat_coveragemoves from 0.469 to 0.483, with "(0.69 x 0.7)" in its description.test_vat_grossing_properties.py.vat_grossing.yamlgains a 2024 case (2,050 / 0.483 = 4,244.31, the product later years use too). Its 2025 case moves to 2,050 / 0.483 x (1 - 0.008368) = 4,208.79, withmain's October 2026 EFO share of receipts.test_vat_grossing_legacy.pypins the deprecated default at 0.483.test_private_school_vat_receipts.pytakesFACTOR = 0.69 * 0.7, and its published receipts check usesa6299504's rounded inputs: 0.2 x £407.40bn / 0.483 + £2.33bn against £170.994bn. Intest_scenario_baseline_parameters.py, a comment's illustrative figure, 526.32 from the old 0.38 factor, becomes "about 411". TheRaise VAT standard rate by 2ppfixture inreforms_config.yamlmoves from 27.6 to 27.1.docs/engineering/vat-coverage.mdgains "Derivation ona6299504(November 2026)": the cut's provenance and certification, theobr.vatexclusion, the receipts workbook (URL, SHA-256, archived copy, sheet, row and cells), the chunked method, the results at 0.67 and 0.69, the bound and a reproducing snippet at revision994cac89. Its value section says why 2024-25 sets the coverage and dates the later-year rows to the March 2026 EFO. "Which datasets" now names the cut policyengine.py certifies and the two before it. The October derivation becomes the earlier derivation onf9d1922c, and the doc dates every statement of 0.67 as the value. The deprecated factor's note now says thatbaseline_vat, which reads the unreformed parameters, keeps the two factors under a reform to it, asbaseline_vat.pyandtest_private_school_vat_receipts.pyshow. "Later Microcosm cuts (24 October 2026)" stays as a dated record.docs/book/assumptions/mp30k-uk-switch-plan.mdnow namesa6299504.2633.added.md(the tests and the later-cut record) and2633.changed.md(the value).Impact
We ran each dataset on 3 November 2026 at
36bbbf42f, this PR's earlier head (policyengine-uk 2.157.1, policyengine-core 3.33.2), which ships 0.67; the runs at 0.69 set the coverage by reform. Totalvatcounts thevatvariable alone. It leaves outprivate_school_vat, which the receipts include: nothing in 2024-25, and about £1.5bn a year from 2025-26 on HMRC's costing.vatat 0.67vatat 0.69a6299504a6299504a6299504Since these runs,
mainhas adopted the October 2026 EFO (#2717). It gives 2025-26 outturn of £179.861bn and a 2026-27 forecast of £186.154bn, andmainuprates consumption with its forecasts, so the 2025-26 and 2026-27 rows record policyengine-uk 2.157.1 on the March 2026 EFO. The value rests on 2024-25, the cut's own year:apply_upratingleaves that year as the file gives it, and OBR's figure for it is outturn.Our Microcosm runs summed ten household chunks, since the whole cut needed more than 8 GiB in one process; the Enhanced FRS run used the whole file.
We also costed a one-point rise at 0.69, setting the standard rate to 21% for 2026-27 alone, on Microcosm
a6299504:vatrises from £179.862bn to £188.767bn, by £8.906bn, exceeding HMRC's £8.8bn ready reckoner by 1.2%. The formula's arithmetic, 1% of the standard-rated base times (1 - 0.008311) over 0.483, gives £8,905,600,556, within £8,968 of the run.gov_balancerises by £7.181bn,vatby £7.120bn andprivate_school_vatby £0.061bn. The last comes to 5.0% of those chunks' £1.214bn baselineprivate_school_vat, since that charge moves in proportion to the rate. In each of the eight chunks, the change ingov_balanceequals the change invatplus the change inprivate_school_vatto within £400 (float32 rounding on totals of about £0.9bn). Theattends_private_schoolformula ranks household net income within the simulated households, so each chunk ranks its own households.We estimate that a two-point rise in the VAT standard rate, to 22%, adds £27.07bn to
gov_balancein 2029-30 on the certified Enhanced FRS, withaf0b602camerged intomainat1d4d020aa. Onmainalone, it adds £27.87bn. TheRaise VAT standard rate by 2ppfixture inreforms_config.yamlnow expects 27.1 and records that costing in itsreceiptslist.Invariants
These hold for every input:
survey_consumption_coverage × household_share_of_receipts.isclosetolerance) overrides the two; a value inside the band counts as untouched.vatwith no electricity or gas bills equals (standard rate ×full_rate_vat_consumption+ reduced rate ×reduced_rate_vat_consumption) / shipped product × (1 −private_school_share_of_receipts). It scales linearly with each household's consumption, with no cross-household term.vat_grossing.yaml, 2024 case). Adding a dated entry to it, which would send earlier years down the legacy path, fails that case.Hypothesis tests in
policyengine_uk/tests/test_vat_grossing_properties.pycheck invariants 1–3, andtest_vat_grossing_legacy.py(main) checks invariant 4. The property oracles read the parameters, so they check the shipped product of 0.483. Dropping the household share fromvat_grossing_factorfails invariants 1 and 3. Dropping the school share fromvat, applying it twice, or charging either rate's spending at the other rate fails invariant 3 on its fixed example. For invariant 2, fixed cases sit at 0.99 and 1.01 bands either side of the declared value; changing the helper'snp.iscloseto exact equality fails the inside cases. The helper properties have no Hypothesis deadline, since they check algebra, not speed.Reverting the parameter change on a copy of
303e65852, the head before the merge withmain, shows which tests hold the value. With both parameters back at 0.67 and 0.469, the 2025 and 2024 cases invat_grossing.yamlfail (vatreturns to 4,334.49 and 4,371.00), and so dotest_legacy_default_is_the_product_of_the_two_factors,test_published_microcosm_receipts_check_is_unchangedand all sixtest_energy_bill_vat_bears_no_school_sharecases. Reverting the coverage alone fails the same tests. Reverting the deprecated factor alone to 0.469 leavesvaton the product, so the YAML cases pass, buttest_legacy_default_is_the_product_of_the_two_factorsfails (0.469 against 0.483). The clean head passes all of them.CI can't run the dataset-level check (total
vatwithin a few percent of receipts on the certified Microcosm cut), because the tests run on Enhanced FRS 1.58.1, their certified dataset. The doc's snippet reproduces it.Tests
At
303e65852, withHUGGING_FACE_TOKENandPOLICYENGINE_UK_DEFAULT_DATASETunset, in the locked environment (uv sync --frozen --extra dev):policyengine-core test policyengine_uk/tests/policy/baseline/gov/hmrc/vat -c policyengine_uk: 10 passed.private_school_vat.yaml: 15 passed.excise_integration.yaml: 2 passed.gambling_duty/integration.yaml: 7 passed.pytest -n 4ontest_vat_grossing_properties.py,test_vat_grossing_legacy.py,test_private_school_vat_receipts.py,test_private_school_vat_properties.py,test_domestic_electricity_vat.pyandtest_historic_vat_rates.py: 135 passed.pytest -n 4ontest_scenario_baseline_parameters.py,test_dated_reform_routes.pyandtest_no_economic_assumptions.py, which the coverage grep found: 132 passed, 1 skipped.test_before_load_spending_scenario_constructs_on_the_datasetneeds the dataset.pytest -n 4 test_fiscal_partition.py: 41 passed.pytest -n 4 aggregate_impact/: 306 passed, 9 skipped. All nine skips sit intest_aggregate_impact_certified.py, which needs the dataset.pytest policyengine_uk/tests/code_health -n 4: 2887 passed.uvx ruff@0.17.2 format --check .(1981 files already formatted),uvx ruff@0.17.2 check .anduvx ruff@0.17.2 check --select F811,F821 policyengine_uk: all checks passed.git diff --check 36bbbf42f..HEAD: clean.On a trial merge of
303e65852withmainat52b30809f, which settlesvat_grossing.yamlas this head does, thevatYAML directory gave 12 passed, andtest_vat_grossing_legacy.py,test_private_school_vat_receipts.pyandtest_vat_grossing_properties.pygave 55 passed (-n 4). CI runs the full suite at this head. The survey-data tests, including thereforms_config.yamlfixtures, run where CI has the dataset, which includes merge-queue andmainruns.Interaction with open work
PolicyEngine/microcosm#1383 (merged) locks policyengine-uk 2.151.0 and holds
obr.vatout of the fit as a diagnostic (commit94d4102fd). A Microcosm maintainer approved that exclusion on 24 October 2026 until 24 January 2027, on the terms that the row rebinds once policyengine-uk re-derives the factor on a file whose fit doesn't bindobr.vat. We re-derive it here ona6299504, which meets that condition, and juaristi22 decides whether and when the row rebinds, and any lock bump. Microcosm's calibration diagnostics for the cut report one warn-only VAT warning,uk_consumption_drift: the totalfull_rate_vat_basemoves by more than 2% of its design value between the design and the final weights. If a later cut bindsobr.vatunder 0.69, its fit would absorb the factor, so this value pairs only with a cut on which a run supports it.🤖 Generated with Claude Code