UK cut: the personal allowance, from statute to a Manchester family to the whole UK - #2
Merged
Merged
Conversation
…mily
?country=uk runs the same timeline on UK law, from data/uk/video.json.
The US film is unchanged pixel for pixel.
Real, and asserted by tools/uk/build_uk_video.py and tests/uk:
- The statute wall is Income Tax Act 2007 Part 3, Chapters 1-3, from
legislation.gov.uk. The dive lands on s. 35(1), £12,570.
- The quote is Edward VI, 1551 ("…made more plaine and short…"), verbatim
from Nichols (1857).
- The code panel shows policyengine-uk's personal_allowance/amount.yaml at
412b25aa, byte-identical to the file the model ran.
- The reform raises the personal allowance to £15,000 in 2026-27.
- The reform card shows the exact dict the model received, and each
run asserts that the parameter moved.
- The family is a single parent in Manchester, two kids, housing
association rent of £500 a month.
- Gains: +£219 on Universal Credit from £15,000 to £48,000, and +£486
off UC from £48,700 to £50,270. The pin shows +£972 at £60,000.
- Every sentence holds at all 80,001 £1 points.
- The results agree with policyengine-uk 2.102.0 and with
policyengine.py's household calculator.
- tools/uk/rent_sensitivity.py reruns the family at £400 and £600 rent.
- The map shows 650 July 2024 Westminster constituencies (ONS, OGL v3.0).
The ONS and OS statements appear, verbatim, in the map's corner tag, as the boundary licence requires.
MOCK, bannered on every frame: the national figures, dots and deciles.
They stay MOCK until PolicyEngine has permission to process the UK survey
data with AI tools. No survey microdata is loaded:
uk_family.no_microdata() fails any run that tries.
Renderer changes:
- The country, currency and captions now come from the data file.
- An evenly spaced family layout is used for households other than the
US one.
- Fonts in the code panel, reform card and captions shrink only when
their text would overflow.
- In portrait, the chart moves down when the family description runs
long.
- The UK map box ends higher in landscape so its source tag clears the
south coast. The MOCK decile caption is shortened so it clears the chip.
- statText takes its currency as an option, so stats.map(statText) is
still "$".
Tests:
- The UK tests run in `bun run test` and in CI.
- UK determinism is part of `bun run determinism`.
- The UK build report no longer carries a timestamp, and the geography
build no longer records the Python patch version, so test runs leave the
tree clean under any interpreter. (Recording the version made the
round-trip test fail once uv moved from 3.14.4 to 3.14.7.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Max ruled on d404 (9/27): "we can do it; no UKDS permission needed. Proceed with the national figures." tools/uk/national.py runs policyengine.py 6.1.1's UK flow on its certified dataset, the enhanced FRS 2024-25 (policyengine-uk-data 1.56.16; the sha256 matches the release manifest), uprated to 2026. The reform sets a £15,000 personal allowance from 2026-01-01. A 6 April start silently leaves 2026-27 on current law; that was checked. The run asserts every allowance moved from £12,570 to £15,000 and none fell. On screen: - £22 billion net cost in 2026-27: £22.82bn of income tax forgone, less £0.60bn of Universal Credit and £0.08bn of Pension Credit withdrawn. - 75.9% of households gain. - 66,800 fewer children in poverty, on the absolute after-housing-costs measure. That is a fixed line, like the US SPM thresholds. The relative measures rise by 120,367 (AHC) and 136,071 (BHC) children, because policyengine-uk draws the relative line at 60% of each run's own median, which the reform raises. All four measures are recorded and documented. - Decile averages: £168 in the lowest decile to £1,635 in the highest. - Tags: the map's tag names the run, the poverty measure and the ONS licence; the decile tag names the run. Checks (asserted in the run, then tested): - Households' total gain equals the net Exchequer cost within 0.5%. The gap is £11m, which matches Housing Benefit falling outside the programme list. - policyengine.py's programme totals agree with the script's own weighted sums. - Decile averages recompute from the household rows to the penny. - 12 records (0.06% of households) lose, through Pension Credit and Housing Benefit falling by more than their tax cut. This is recorded, not hidden. The map's dots: - 12,000 draws by household weight. Great Britain households carry constituency_code_oa in the dataset. Northern Ireland's (2.5%) carry none, so their dots fall anywhere in Northern Ireland, and the legend says so. - The draws are record-level survey derivatives, so they stay in data/uk/private/ (git-ignored, with the microdata). video.json only names the file. - A clone without the file renders placeholder dots under the MOCK DATA banner, which is what CI sees. A test asserts nothing under data/uk/private/ is tracked and that national.json holds only aggregates. - The UK landscape map box ends higher, so the four-line tag clears the south coast. The US film is pixel-identical at every poster frame in both layouts. Determinism passes for both countries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a mismatch, tools/determinism.mjs writes both the played-through and the fresh frame to frames/determinism/, and CI uploads them as an artifact. The UK portrait frame at t=12.4 s differs on the Linux runner but matches locally, with or without the private sample, so the frames are needed to see what differs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ounds On the Linux runner, the UK landscape frame at t=12.4 s differed between the played-through and the fresh page. The difference was 56 pixels (0.011% of the frame), all in the blurred glow beside the curve's steep segment, at most 4 levels in any channel. Chromium's software rasterizer rounds a blur slightly differently when it repaints only the damaged part of a page than when it paints the page whole. No state carries between frames in our code, and the frames match byte for byte locally. A frame now passes as "within noise" when no channel of any pixel differs by more than 8 levels and under 0.05% of pixels differ; it is reported as such, so it stays visible. Everything else is still exact. Checked both ways: - The CI frames come out at 56 px and 4 levels, so they pass. - An injected leftover state, where the chart shifts 1 px after the third render, fails at 4,140-7,484 px and up to 115 levels. The comparison decodes both screenshots in a blank Chromium page, so the check needs no new dependency. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…est harder Two independent reviews (Astra, Opus) of PR 2 requested changes. The US portrait film had changed. The flying "$2,200" landed at the measured code font (21 px in portrait) instead of its original 22 px, so frames at 4.1-4.8 s differed. The US now keeps 22 px, and other countries follow the panel. New: tools/compare_revisions.mjs renders a film from this checkout and from another every 0.1-0.2 s in both layouts and compares them. - With the regression reinstated, it flags exactly those 8 portrait frames, at up to 204 levels. - With the fix, all 602 US frames are identical to main. - CI now runs it against the PR's base on every pull request, unless the PR carries the us-film-changes label. The determinism check compares a revision with itself and cannot catch this. - tools/frame_compare.mjs holds the shared comparator (exact, or bounded rasterizer noise), and serve() can serve another checkout. FRS citation (UK Data Service End User Licence, clause 11, which covers any "printed, electronic or broadcast" publication): - The film now carries the citation in exactly the form the catalogue entry for study 9563 specifies, under the decile chart, with "© Crown copyright". - The READMEs carry it too, name the other surveys the enhancement imputes from, and note clause 12 (report published work to the UK Data Service). - In landscape the UK decile chart ends 40 px higher so the citation fits. Tests: - The child poverty count and its direction now come from the baseline and reform headcounts; a flipped stored difference fails. - Privacy: every tracked file under data/uk/ and audio/uk/ must be on a reviewed list, and no tracked JSON may hold survey-like rows (area code plus numbers, 100 or more). A companion test shows the scan catches a smuggled sample under any key and passes the map geometry. - CI's placeholder dots now include Northern Ireland rows, so the region fallback runs in CI too. README fixes: - Allowances rose to at most £15,000 and fell for nobody. The 1,580 records with none in either run all have adjusted net income above £130,000 (checked). - Housing Benefit explains £10.1m of the £11.4m identity gap, not all of it. - Deciles are ranked by baseline household net income (not equivalised) into groups of equal numbers of people, then household-weighted. Read from policyengine.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Max's ruling on d509 (2026-09-28): PolicyEngine defaults to absolute poverty before housing costs. Relative poverty measures inequality rather than poverty. After-housing-costs measures subtract discretionary spending (a flat or a mansion), which international poverty measurement does not do. The UK stat now reads "59,900 fewer children in poverty": children in absolute poverty before housing costs fall from 11.78% to 11.39% in 2026-27 (59,863, shown to the hundred). The map tag reads "poverty: absolute, before housing costs". The UK README states the default and its reasons, and records all four measures. It also documents the line itself: policyengine-uk uses HBAI's FYE 2011 absolute line (£251.40 a week before housing costs, uprated by CPI). DWP moved its absolute reference year to FYE 2025 in March 2026 (HBAI FYE 2025 report), so rates on the model's line sit below DWP's current figures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lute line policyengine.py 6.2.0 pins policyengine-uk 2.102.3, which draws the absolute poverty line as HBAI has since March 2026: 60% of the FYE 2025 median, £431.69 a week before housing costs, uprated by CPI to £456.63 in 2026-27. On that line, children in absolute poverty before housing costs are 16.32% today and 16.06% under the reform: 39,996 fewer, shown as 40,000 (was 59,900 on the old FYE 2011 line). Net cost (£22bn) and the share gaining (75.9%) are unchanged to the pound; decile averages move by up to £8 with the model's baseline incomes. - Every UK compute script now runs on requirements.pe-6.2.0.lock.txt. - The family curve on 6.1.1 (policyengine-uk 2.90.2) becomes the differential, replacing the 2.102.0 run; it matches 6.2.0 to £0.00. - national.py stamps the uprated 2026 file with the policyengine and policyengine-uk versions that built it and rebuilds it on a mismatch: ensure_datasets otherwise reuses a file uprated by another install. - national.json records Housing Benefit's change, which explains most of the accounting-identity gap the README cites. - The decile notes' velocities follow the new bar heights (events.json). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d from scratch An adversarial review of 693efa3 (four lenses, each finding put to a skeptic) confirmed these; all are fixed here. - Citation chip (UK only, both layouts): the "Income Tax Act 2007, s. 35" chip sat on "(1) An individual who..." in the statute scene and over line 19 of the YAML in the code scene. placeChips() now keeps each chip at its designed spot unless text overlaps it there, else moves it to the nearest spot clear of text in every frame it shows (smaller only if no full-size spot exists: 75% in the vertical statute scene). Solved once in measure(), so frames stay history-free. The US film is unchanged: 602 of 602 frames identical to main in both layouts. clearOffset() lives in site/util.js with fast-check properties (design spot kept when clear; a move clears every obstacle in every frame; the move is the shortest the grid allows at the largest scale that allows one; stuck iff nothing clears), each shown to catch a planted bug. render.mjs refuses to render a chip with no clear spot. - Deciles: national.py now rebuilds the groups from their definition (people-weighted rank of baseline household net income, ties sharing a rank, negatives left out) instead of reusing policyengine.py's; all 52,846 households match. The README now says the 460 negative-income records are dropped after ranking, so the bottom decile holds 9.5% of people, not "equal numbers". - Allowance: national.json records who has no allowance (1,580 records, lowest adjusted net income £130,100) and who gains the full £2,430 (111,917) or part of it (120). The docstring no longer claims every person's allowance moved from £12,570 to £15,000. - The uprated-dataset stamp now carries the file's sha256 and goes with the file, so a file rewritten by anything else is rebuilt. - README: policyengine-uk 2.90.2's old line was £251.40 through 2019 and £305.70 from 2020, CPI-uprated to £403.42 in 2026-27. Correction to 693efa3's message: net cost is not "unchanged to the pound". It rises £360,042.55 (Universal Credit withdrawn) and still reads £22.14bn; income tax forgone and the share gaining are identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l.json A second review round (of 163c869) found every first-round finding resolved and raised four new ones, fixed here. - Chip placement now checks every frame the chip shows, with the slide it enters and leaves on (up to 12 px statute, 10 px code), not only the frames to flyStart + 0.15 at rest. Wall text counts while the wall is legible (at least half opaque, at most 2 px of blur: to about 4.35 s). The design position is judged at rest, since its slide is part of the design (clearOffset's new designFrames), so the US film is unchanged: 602 of 602 frames identical to main. New property: a design position clear at rest is kept; any move clears every sliding frame. - national.json dropped two single-record values: the lowest adjusted net income among the 1,580 records with no allowance (now the asserted bound, £130,000) and the largest loss among the 12 losing records. A minimum or maximum over survey records is one record's value; the file holds aggregates only, and a new test fails on any key naming an extreme. Every published figure is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: README.md keeps main's soundtrack description and adds the compare_revisions line; tests/test_soundtrack.py takes main's limiter tests. The UK score now shares the new mix, so the tests that bound the master (platform targets, the limiter only catching stray peaks, no story cue in a limiter dip, heavy cues on downbeats) run on both scores through an each_score fixture, and the UK sweeps meet the same 6 ± 0.3 LU bound after the master as the US ones (their 4.3 LU swell floor was the pumping). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… key style From the independent review of 26bdd97 (approved; these were its two non-blocking notes): - national.py publishes the losers' programme changes only when there are at least 10 losing records; below that a sum is close to one record's values, so only the count and weight go out. With 12 records today nothing published changes (test_small_groups_publish_counts_not_sums). - The extremes test now splits keys in any style (snake, kebab, camelCase) and knows more words for an extreme (worst, best, peak, extreme, biggest), with a self-check that 'maximise_share' is not flagged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er loses The independent review of 62e2130 noted that a 10-record floor on the losing group does not stop one of its sums resting on one record. It did: the Universal Credit change among the 12 losers came from 1 household and Housing Benefit's from 7. tools/uk/disclosure.py now publishes a sum only when at least 10 records contribute a nonzero change (property-tested, off-by-one mutation caught). Income tax (12) and Pension Credit (11) stay; UC and HB are withheld. national.json records the contributor counts. The README said the losers lose because their Pension Credit and Housing Benefit fall by more than their tax cut. Checked household by household, that held for only 6 of 12 on those two alone. The run now records, and asserts for every loser, that benefits fall by more than taxes, with the count of losers each benefit falls for: Pension Credit 11, Housing Benefit 7, Winter Fuel Allowance 4, Council Tax Benefit 3, Universal Credit and Scottish Child Payment 1 each (policyengine-uk's own benefit list and labels). Also from the review: the extremes key scan splits acronym boundaries (UCPeak) and letter-digit boundaries (loss_max2), and the UK sweep test reuses the uk_score fixture. No published figure moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Merged on Max's approval after watching the UK cut (d498, 2026-09-29). Gates: CI green on 0394507 (invariants, determinism for both countries and layouts, US film unchanged), MERGEABLE, three adversarial review rounds plus independent Opus 5.5 reviews of 26bdd97, 62e2130 and 0394507 (all APPROVE). Squash-merged so main's history carries neither the retracted 'unchanged to the pound' line nor the withdrawn single-household sum. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A UK cut of the 30-second video, with
?country=ukandtools/render_all.sh uk. It follows one real rule from statute to code to one family to the whole UK: the personal allowance, raised from £12,570 to £15,000 in 2026-27. The US film is unchanged: every poster frame is pixel-identical in both layouts.What's on screen, and where it comes from
policyengine_uk/…/personal_allowance/amount.yamlat 412b25aatools/uk/national.py: policyengine.py 6.2.0 (policyengine-uk 2.102.3) on the enhanced FRS 2024-25 (policyengine-uk-data 1.56.16), uprated to 2026constituency_code_oa; ONS July 2024 boundariesEach chart carries its own small print, as in v1.1.0. The map's tag also carries the ONS/OS licence statements verbatim, since the licence requires them wherever the boundaries are used.
Sanity checks against official figures (2026-09-27)
Sourced reports are in
~/reviews/pe30-sanity-2026-09-27/.The poverty measure (ruled: d509)
The stat uses PolicyEngine's default measure, absolute poverty before housing costs (Max, 2026-09-28). Relative poverty measures inequality: the line is 60% of each run's own median, so this reform raises the line. After-housing-costs measures subtract discretionary housing spending. The default is now documented in PolicyEngine/policyengine.py#534.
The absolute line is DWP's since March 2026 (ruled: d516): 60% of the FYE 2025 median, £431.69 a week before housing costs, uprated by CPI to £456.63 in 2026-27. policyengine-uk 2.102.3 carries it (PolicyEngine/policyengine-uk#1889), and policyengine.py 6.2.0 pins that (PolicyEngine/policyengine.py#536). On the old FYE 2011 line the stat was 59,900. The share gaining, income tax forgone and the family curve don't move at all; net cost moves by £0.36m (Universal Credit withdrawn), so it still reads £22.14bn; decile averages move by up to £8.
Survey data
The FRS is UK Data Service data under the End User Licence. The microdata and the map's record-level draws stay in
data/uk/private/(git-ignored); only aggregates (data/uk/national.json) are committed.video.jsonnames the sample file but doesn't contain it.data/uk/private/is tracked, that no tracked JSON carries sample-shaped rows, and thatnational.jsonnames no minimum or maximum (over survey records, an extreme is one record's value).Invariants (tested)
tests/util.test.js; a render fails if no spot exists).Determinism check
On the Linux runner, one UK frame (landscape, t = 12.4 s) differed between the played-through and the fresh page. The difference was 56 pixels (0.011% of the frame), all in the blurred glow beside the curve's steep segment, at most 4 levels in any channel. Chromium's software rasterizer rounds a blur slightly differently on a partial repaint. The frames match byte for byte locally.
tools/determinism.mjsnow reports a frame as "within noise" when no channel of any pixel differs by more than 8 levels and fewer than 0.05% of pixels differ; everything else must still match exactly. An injected one-pixel leftover-state bug still fails, at 4,140 to 7,484 pixels and up to 115 levels. On failure CI uploads both frames as an artifact.The limiter fix (#3) is merged in
The branch merges main after #3 (Max's ruling, d518), so the UK score takes the same mix as the US one. The soundtrack tests that bound the master (platform targets, the limiter only catching stray peaks, no story cue in a limiter dip, heavy cues on downbeats) now run on both scores, and the UK sweeps meet the US bound of 6 ± 0.3 LU after the master.
Layout fix: the citation chip (UK only)
The UK statute and parameter file are denser than the US ones, so the "Income Tax Act 2007, s. 35" chip sat on text in both scenes: across "(1) An individual who…" above the boxed £12,570, and over line 19 of the YAML. It now takes the nearest spot clear of text in every frame it shows: beside the "35 Personal allowance" heading, and right of lines 17–18 (70% size in the vertical statute scene, where no full-size spot exists). The US film doesn't change.
Known and recorded
national.jsonpublishes a sum over survey records only when at least 10 records contribute to it (tools/uk/disclosure.py), and never a minimum or maximum; the losers' Universal Credit and Housing Benefit sums are withheld on that rule.national.pystamps the uprated 2026 file with the versions that built it and rebuilds it when they change;ensure_datasetswould otherwise reuse a file uprated by an older install.tools/uk/rent_sensitivity.pyshows how it moves where UC ends.Details for every string:
data/uk/README.md.🤖 Generated with Claude Code