Repository navigation
Conversation
A signed-in session whose token could not be renewed ends without a reload, and saved work moves to the browser's unauthenticated user (a new session scope). The save's query key held that scope, so the household on screen was looked up and saved again as the browser's user, and the next sign-in merged that row into the account beside the one already there. The key now holds only the country and the v1 household, so a household saved on the page is not saved again. - The hook's comment says what it does: once saved, not looked up or saved again on the page; a failed save is tried again the next time the household's result shows. - A test that shows the same household again after six minutes, which fails without the session-long cache entry. - The fixture's summary is written as the Household tab writes it. Tests first: the session-end test failed with four requests instead of the signed-in lookup and save. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… of the retry - V3SavedWork.scope said a session that ends without a reload moves every query to new keys; the calculated household's save is not keyed by it, so the comment now says so. - A test that a failed save is tried again when the household's result shows again, as the hook's comment says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Estimates how much more income tax and NI a worker on the National Living Wage pays than if the Treasury had uprated both thresholds with inflation, across five paths for the NI threshold, with part-time and Universal Credit cases. Adds the reproducible analysis and its tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Narrow the headline figures to a single worker and state the Marriage Allowance couple separately. Replace the derivation of £16,070 and £16,632, which CPI compounding does not produce, with a stated sensitivity. Rewrite the row labels as actions with named actors, add the chart's three kinks to the model grid, and check the post's metadata, year-bound tables and two rows on the live model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Household tab stacked one card per household, each with its form and then its result under the form, so results sat between forms and the charts got the narrowest width on the page. - A row of tiles switches between the households (name, who they are, earnings and the change in net income) and "Compare all", one table of every household's baseline, reform and change. - One household shows at a time, with Results and Inputs. Results lead once it runs; a result for changed inputs says so and offers to calculate again. Households can be renamed in place. - The result has no frame or title of its own, so its tables and the net income, earnings and marginal tax rate charts take the panel's full width. - Inputs (v3 columns layout only): labels above inputs in the person cards, whole amounts without cents, add links that wrap, and "Household details" under the tab's own Household heading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The model calculates UC over a year, which matches equal monthly pay. A worker paid every four weeks would receive some UC in most months, so the post now states the assumption. The cover's "Uprated by inflation" label becomes an action with an actor. The section 57 property test now uses exact decimals, so it rejects an extra £10 at zero inflation or on an exact multiple. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two thresholds still stand equal, so "had held to its 2022 statement" and "kept the NI threshold equal" implied a break the sources don't show. The post now conditions on the Treasury raising the allowance, and its tables say "raised the NI threshold with the allowance". The UC example states no childcare costs, and the concept subjects give way to Parliament and the Treasury. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Treasury sets the threshold one year at a time, so it owns the path the threshold would have followed. Parliament set no path with or without the July 2022 rise. The part-time heading and chart legend now say what £242 a week measures, and the tests pin both strings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove the app's handling of x and y on UK area results
main's unit tests now refuse network requests nothing mocked (#1504), and its AbsoluteChangeByDistrict and RelativeChangeByDistrict tests answer the district map's GeoJSON request. This branch rewrote both tests around fakeStateDistrictApi, which replaces fetch, so they keep this branch's version, and the fake API now answers the boundary file with the map test's GeoJSON fixture instead of a 404. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ehold anew
From review:
- A failed save was dropped silently, so a Public API that refused
every save (a 422 on the body, a 403 for the browser's user) left
no trace. It is now logged with console.error, as the sign-in
upgrade logs its failure; the page is unchanged.
- runFlagshipHousehold's comment said the API dedupes identical
households. It does not: the Public API's POST /{country}/household
stores a new one on every call, so each run gets its own household
ID (and, with this PR, its own saved row).
- A test with two different households expecting two saves, which
fails if the household's ID is dropped from the save's query key.
- The hook's comment also says a failed save is tried again when the
session's user changes while the result shows.
Tests first: the logging test failed, as nothing was logged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd name each failure's own states - A state's result write started after its poll left the 4-request limit, so with several states finishing in one polling cycle a tab had more than 4 requests in flight (8 in the new test, with every state finishing at once). The write now waits for a place in the same limit, with no abort signal, so it is still sent when the view closes. - A result that arrived after the view closed was written, then dropped by React Query, and a reopened view polled the state again and wrote the same result a second time. After a successful write the state's cached records now say its base report is complete, so later polls do not write it again (the new test saw 55 writes for 51 states before). - The partial-failure message put every failed state's name in front of the first state's error, including states whose result had no districts. The hook now returns each calculation failure with its own error and the states without districts separately, and the panel gives one sentence per error and one for the states without districts. - The time limit's comment now says the room for a queue holds only for years with pre-built state files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v3 households: save each calculated household to its user, shown nowhere
…'s expiry The workflow keeps publishing directly with npm publish, so the README's setup now says to allow npm publish in the trusted publisher's allowed actions, which npm leaves off for new trusted publishers. Because npm expires a new trusted publisher that hasn't published within 2 days, the setup now merges the next version before adding it, then runs the workflow within 2 days. Also: - open npm-publish if a run has already created it, and set its branch rule; - enter the organization and repository as npm's separate fields; - say that the package's npm maintainers, not only scope owners, can still publish by hand; - list the manifest's repository.url among what npm checks, and say the admin bypass of the branch rule is undocumented; - note npm's 0.0.0-stage placeholder; - word the workflow comments as settings to make, not settings in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A package can have several npm trusted publishers, and a token matching any of them is accepted, so the setup now says to remove any that doesn't name npm-publish. It also says what to do if the 2-day window passes: delete the expired trusted publisher, add it again, and run the workflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nvironment @policyengine/api-client: publish only from main, through the npm-publish environment
The first version the publish workflow will release, so that npm's new trusted publisher completes a publish within its 2-day window. It differs from 0.1.0 only in the README's publishing setup. bun.lock's workspace entry is raised to match; bun install --frozen-lockfile passes with Bun 1.2.21. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unts In the post, the findings and the poverty sentence now say "Against that path", and we split five long sentences from round 10's fixes. We give the HMRC gaps as about 8%, say where a single renter's UC runs out on the comparison path and with thresholds frozen, and say that the other 18% of households have the same net income to within £1 a year. The levels test now runs on made-up tables like the national ones, each figure at its own rounding. They hold sums of changes with their totals, mean changes, counts, the shares with less and more, a count we never publish, and sums of amounts that never fall below zero. The programme in levels takes each figure's level, sign and denominator from the records, without disclosure.py. New tests cover a link remainder whose parts have opposite signs, a reader who keeps the sign of a figure that rounds to zero, a share whose group size we withhold too, and the £1 rule. Others cover each pay band's links with the row for all employees, and the decile margins, which the tests now write out. The meta records the change threshold the counts apply. Three Hypothesis tests kept the default 200 ms deadline, and now set none, as the others do, so a busy host can't fail them. Every mutant reviewer B wrote in round 10 now fails a test, and so does each of round 9's named mutants. The mutant that drops the share cap breaks only 34 of 1,000 made-up tables, so a fixed test now pins the cap. In the README, we narrow the invariants to what the tests check. The tests can't check the row for all employees in the top band's UC share and change in benefits, which we withhold in every scenario and year, or in the averages on and off UC. We list PolicyEngine UK 2.178.0, on which the household scripts give byte-identical results, and describe the Class 2 and Class 4 deductions from the minimum income floor. Round 10's rerun moved the README's share of gainers with no allowance from 60% to 61%; no figure the post cites moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@policyengine/api-client: release 0.1.1 through the publish workflow
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqCGPgxDukji8Bhs57c5rH
In the post, we tie the Pension Tax Allowance's cut of up to £94 to 2027-28, because the allowance, 20% of the full new State Pension above the Personal Allowance, follows the State Pension in 2028-29 and 2029-30. We apply reviewer A's five wording fixes. Against the path without the July 2022 rise, we now say our two estimates of people below the absolute line "fall short of" the table's. We set no deadline on any Hypothesis test in the four test files. In the levels test, we now publish some of the made-up tables' sums and counts in hundreds or thousands, as we publish the UK-wide tables in billions and millions. We also give each row and the whole table a net, the sum of changes less the amounts, whose link joins parts of opposite signs. Reviewer B's unit mutants, U2 and U3, now fail the levels test on 143 and 400 of 400 made-up tables. In the share cap test, we now weigh each record 2, so the mutant that caps a share at its number of records fails it, and we add a test that holds a remainder to the largest tolerance among its cells. We also correct the tolerance comment and a docstring, and add three blank lines. In the README's 1.6% sentence, we now count the households that have more net income with thresholds frozen than on the path without the July 2022 rise. In the invariants, we use reviewer C's wording on the Class 2 and Class 4 limits, the share cap, and how we weight the deciles and the bands. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ddress Review round 2 found no bugs. From its other points: - calculatorPublicApi's header no longer says the two routes keep one token cache. Vercel may build them as separate functions, each with its own in-memory cache; the Runtime Cache token store shares the token. - The US ask route's comment lists why fetchFromServer throws: a credential only partly configured, not a missing one, since with no credential configured at all the read goes out without one. It also names the non-2xx and unreadable-body failures that throw afterwards. - The forwarding package's README names the US ask route as a user. - Tests: the browser-identity case also sets x-real-ip and X-Forwarded-For and checks the metadata read carries neither; the failed-read case checks the route logs why. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'s file Review round 4 found no bugs in b8f743a and c611c41. From its other points: - The route's comment says request cancellation applies to every method: a write the browser has fully sent is dropped if the browser goes away before the route forwards it, at most for the token request. - deleteFailedSocietyWideCalculation's comment says an answer the route wrote itself throws with writtenByRoute, whatever its status. - The allow list's comment calls 800 s Vercel Pro's standard limit. - The environment example says the calculator's server code, not only the route, reads the POLICYENGINE_API_* variables. Once #1541 merges, the US ask route reads them too. - Tests: the DELETE cases also check writtenByRoute, and a new case checks that the route's module sits under the vercel.json glob, since Vercel doesn't check that a functions glob matches a file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read the signed-in user's type from /v2/me and add a minimum-user-type component
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts, both in tests: - v2UserMocks: main imports UNKNOWN_USER_TYPE where this branch imports the route URL helpers. Both are kept. - Account.page.test: main moved answerMe into v2UserMocks, so the page test's own copy goes. Main's shared answerMe and its new useMe test read request URLs with new URL(url), which throws on the same-origin path this branch sends (/api/policyengine/v2/me). They now use publicApiPathOf and requestUrlOf, as the rest of this branch's tests do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add blog post: Federal tax inflation adjustments for tax year 2027
…route Send the calculator's Public API calls through its own route
…ls-send-credential Send the calculator's server-side Public API call with its credential
The article page's schema.org JSON-LD listed each author by slug
("david-trimmer"), so search engines read a slug as the author's name on
every research post. It now takes the name from authors.json ("David
Trimmer"), through a new getAuthorName helper in website/src/lib/authors.ts.
The byline used the same slug, title-cased, which misspelled three authors
("Jason Debacker", "Maria Juaristi", "Chat Gpt"). It now reads the same
helper, so the byline and the JSON-LD name each author the same way. A slug
with no authors.json record falls back to its title-cased form in both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-ups: name the fast-check tests in the repo's given/then form, check the JSON-LD property against an independent oracle rather than the helper it calls, fail clearly if a post the tests name is renamed, and give the author card's headshot the same name its link shows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-names Name research post authors by name in JSON-LD and the byline
* Add a draft Veterans Day post on state income taxes on military retired pay The article is rendered from PolicyEngine/veterans-day-2026 (post/template.md and scripts/render_post.py), so its numbers and charts come from that repo's outputs. It is a draft: state checks and law citations are still in progress. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Explain each state's rule in the military retired pay post, with sources Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add the Veterans Day post's cover image A photographic still life generated from the prompt in PolicyEngine/veterans-day-2026 post/cover_prompt.md: 1800x840 WebP, teal only in the notebook and folder, no legible text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Replace the Veterans Day post's cover with a porch still life The first cover repeated the 2025 SPM covers: an oak desk by a window with a teal notebook, glasses and papers. The new one sets worn boots with blank dog tags, a folded olive blanket, a teal mug and a teal knit cap on a porch in late November light. PolicyEngine/veterans-day-2026 keeps the prompt in post/cover_prompt.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cite South Carolina's Revenue Ruling 22-11 in the military retired pay post The statehouse's code pages return 404, so the post links the Department of Revenue's ruling on S.C. Code § 12-6-1171. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix the P.L. 119-21 page anchors in the military retired pay post Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Rewrite the military retired pay post around the 28 and 13 state count Lead with how many states exempt the pay, add a table of the 13 states' rules and the retired couple's lost credits (Vermont, New Mexico and Oklahoma among them), and drop the draft label from the description. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> * Tighten three sentences in the military retired pay post Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> * Rerun the military retired pay post on PolicyEngine-US with ten pending fixes DC's 2026 senior deduction takes the retired couple's DC tax from $463 to $0, so that couple now owes more in ten states, up to $440 in Hawaii. California's 2026 indexing, Maine's 2026 factors and Montana's 2026 age-65 amount move four other figures by $3 to $15. The methods section now says the run used fixes not yet in the web calculator, and the description says how much more tax each couple owes because of the pay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Apply the final fact, law and voice gate to the military retired pay post Cite DC's permanent Fiscal Year 2027 Budget Support Act of 2026, say that Kentucky's $31,110 is its general pension exclusion, explain Utah's $390, Missouri's 15% and the Social Security Montana taxes, and say why four states that tax part of the pay add no tax for the retired couple. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Final review of the military retired pay post Name Maryland among the states that tax part of the retired couple's pay without adding tax, give Delaware's under-60 rule its age, and tighten the lead and the lead-in to the credit table. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> * Add David Trimmer as coauthor of the military retired pay post David checked each state's law, supplied the 28/13 count, hand-checked the chart values and found the New Mexico and Oklahoma credit-income bug. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Let blog bar charts run horizontally and drop the legend BlogChart's "bar" spec takes two optional fields: - "orientation": "horizontal" lists the categories down the left and runs the bars along a quantitative x-axis; value labels sit past each bar's end. - "legend": false leaves out the legend for a single series that the title or subtitle already names. Both default to the old behavior, so existing posts render as before. The military retired pay post's bar chart uses both, with full state names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Widen the category column so state names stay on one line "New Mexico" wrapped at 7 px per character; 8 px plus 16 fits it on one line on desktop and at 375 px. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restructure the military retired pay post and number its figures and tables Re-rendered from PolicyEngine/veterans-day-2026's template (its commit ce4580a, on the restructure-lead-with-couples branch): - A reworded lead that no longer repeats the description, followed by a four-row table grouping the states by how they treat the pay. - Each couple introduced at the top of its own section. - The table of each state's rule moved to an appendix, with a footnote pointing to it. - Figures 1 and 2 and Tables 1 to 4 numbered. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> * Stack federal and state tax in Figure 2 and give each couple's take-home Max asked for each couple's take-home after all taxes and a stacked federal and state bar. BlogChart's bar spec takes "stacked": true; the first series starts at zero so it stays comparable across rows, and each stack's label gives the row's total. Figure 2 now stacks the state income tax the pay adds (teal) under the federal tax it adds (gray), and each couple's section says how much of the pay it keeps after both. Also applies the voice gate's fixes to David's restructure: the 13 states "exempt all, part or none" of the pay; "the other spouse"; "owes more state income tax because of the pay"; Table 4's label; AGI defined once; the Kansas row in active voice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Put federal tax first in Figure 2's stacked bars, then state Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Shorten the military retired pay post's description and name New Mexico and Idaho - The description no longer repeats the post's first lines; it now says only what we calculate. - The article is re-rendered from PolicyEngine/veterans-day-2026 at 36a2cea, which adds New Mexico and Idaho to the states that tax part of the retired couple's pay but collect no income tax on it. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> * Show state tax only in Figure 2, add a conclusion, and move the other veteran rules to the appendix Re-rendered from PolicyEngine/veterans-day-2026 at a8c96dc: - Figure 2 shows state income tax only again; the text under it says the federal increase is the same in each state shown. - The methods cover federal and state income tax and note Alaska's Permanent Fund Dividend. - "Other rules for veterans" moves to one sentence in the methods, with its table in the appendix as Table 4; the state rules table is Table 3. - A conclusion restates the count and each couple's result. - Three wording changes in Table 1, the Utah, South Carolina and Maine sentence, and the appendix heading. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> * Split the take-home sentence, reorder the no-tax sentence, and fix Table 4's break Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Say the other veteran rules "depend on" service, not "turn on" it Re-rendered from PolicyEngine/veterans-day-2026 at 57f8976. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> * Apply the third voice gate: conclusion with each couple's take-home, Table 1 row, Alaska detail, description Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Apply the fourth voice gate: split the retired couple's take-home, clear three pronouns Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: David Trimmer <david@policyengine.org>
Preserve the round 13 work left in the Claude checkout: cover percent units, negative units, mean bounds and signed targets, tighten the independent bounds tolerance, and clarify four post sentences. Preserve current articles when resolving the index conflict. The 210 disclosure and national tests pass; no result file or calculation changes.
This branch was successfully deployed
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.
Round 13 at
c169b10aeapplies the findings that round 12’s reviewers recorded before their usage limit ended the run. We preserved the unfinished files and integrated current main. An independent peer approves this bounded continuation (tests, four wording clarifications, the index merge and the evidence record), with no findings or material gaps. This adds no new whole-PR policy or source audit. The PR remains a draft. We merge and publish when Max gives the go (d2303).We add a research post on what a single worker on the National Living Wage pays under frozen thresholds. We compare what they pay with what they would pay if the Treasury had raised the Personal Allowance under section 57, across five paths for the National Insurance threshold. We follow the difference through Universal Credit (UC) and Council Tax Reduction to net income, and we run the same comparison for all UK households.
Findings
For a single worker on the National Living Wage:
We chart net income by hours in 2026-27, against the government raising the allowance and NI threshold together. A worker with no benefits has £983 less from about 24.3 hours, and a lone parent on UC has £442 less. A single renter on UC has £442 less up to about 32 hours and £983 less from about 34 hours, once their UC runs out with thresholds frozen. A single renter in Kingston upon Thames has £354 less from about 24.3 to about 31 hours.
Across UK households, we estimate:
Model
We run the household scripts on PolicyEngine UK 2.175.5 and policyengine-core 3.34.7, both from PyPI. A rerun on PolicyEngine UK 2.178.2 and policyengine-core 3.34.10 finished at 00:35Z on 12 November:
results.csvandhours.csvmatch byte for byte, and the metadata differ only in model versions. PyPI still lists 2.178.2 when checked at 02:44Z on 12 November.We run the UK-wide figures on the certified pair that
policyengine6.16.1 pins: PolicyEngine UK 2.174.1 with policyengine-core 3.33.3, and the Microcosm UK filemicrocosm-uk-2024-25-national-20261107T020434Z-eeb6093e, with the OBR's October 2026 forecast. PolicyEngine UK 2.174.1 carries the Budget baseline and the Pension Tax Allowance. PolicyEngine releasedpolicyengine6.17.1 at 16:26Z on 10 November and 6.17.2 at 14:54Z on 11 November, and PyPI still listed 6.17.2 as the latest release when we checked at 02:44Z on 12 November. Both pin the same UK pair and change nothing in the disclosure layer.In round 8 we took the figures from the pair
policyengine6.11.1 pins, PolicyEngine UK 2.151.0 withmicrocosm-uk-2024-25-national-20261025T091414Z-a6299504, which predates the Budget. We drop round 8's sensitivity run on PolicyEngine UK 2.175.5, because the UK-wide figures now run on 2.174.1, which carries the Budget baseline. Nearly every figure in the post's UK-wide tables changes with the new pair. These figures in its text change too, with the new pair and with rounds 9 and 10, anddisclosure.pywithholds one of them:Two claims in round 8's description change too. On the new Microcosm UK file, 13% of employees aged 21 or over with pay and hours earn more than 5% below the £11.44 wage floor, against about 23% on round 8's file. Another 8.2% earn within half a penny of it, so round 8's "no spike at the wage floor" no longer holds. Our income tax and NI at the OBR's levels now exceed Box 3.3's by 15% in 2027-28, against 10% and 12% in round 8, and we withhold our 2026-27 figure.
Decisions
obr_thresholdsscenario, we also index that band, at the OBR's levels, for the comparison with its figures.policyengine6.16.1, whichdisclosure.pyrestates andtest_disclosure.pychecks against the layer itself. Our figures add up in ways a reader knows, so we record each identity among them and bound what a reader could work out with SciPy's HiGHS solver. We then withhold further figures until nothing a reader can work out through those identities fails the rules. We withhold the comparison scenarios' figures before the paths' and the last path's before the first's, so the path the post leads with keeps its figures. A reader could also subtract an OBR or HMRC figure from a path's. Some of those differences rest on few households, such as those with income between the OBR's £16,070 allowance and our £16,080, so we publish those two scenarios' figures to one decimal place fewer than the paths'. In 2026-27 we withhold 95 of 1,049 figures: 23 that fail the rules and 72 that would let a reader work one of those out. In 2027-28 we withhold 97: 28 and 69. Where fewer than 30 effective survey households stand behind a published figure, the layer marks it approximate (88 figures in 2026-27 and 80 in 2027-28), and we flag each one the post cites.hours_meta.jsonrecords a worked case on hypothetical inputs.results.csvand leave them out of the post.Cover
We drew the cover in the illustrated style of the Budget 2026 posts (#1457, #1465), with the Pension Tax Allowance cover as its template. We redrew that cover's wall, window light, table and letter, its badge on the letter and its pound coin on a coin stack, at the positions and sizes we measured on it. A desk clock and a work pass on a lanyard take the places of that cover's cup and glasses. The letter shows £12,570 on a flat teal line, with a grey step line above it that traces the section 57 path to scale, from £12,570 to £16,650. A snowflake on the badge marks the freeze. As in #1465, we name the file
cover-illustration.webp, and61d08e8a9deletes the old title card (cover.webp).make_cover.pydraws the cover as an SVG, rsvg-convert renders it at 1600×900, and the three commands in its README reproduce the committed webp byte for byte. We keep the script, its README and the comparison checks outside this repo, in~/reviews/budget-coordinator/social-evidence-hunt/drafts/fiscal-drag/cover/. Against the Pension Tax Allowance cover, the open wall differs by 0.36 green levels on average. Where only shadow falls below the badge and the coin, the largest step between rows 2 px apart comes to 7 and 4 levels, against 5 and 5 on that cover and 26 and 12 at89ee44286. Widening every filter region to 1100% changes no pixels, and all 13 fills we sample render at their exact palette values in the PNG.Invariants
test_freeze.py,test_hours.py,test_national.pyandtest_disclosure.pycheck these, with Hypothesis on the threshold paths, the Gini index, the sharing of benefits by person, the pay bands and the disclosure rules.Household results:
National results (
test_national.pyandtest_disclosure.pycheck these, andnational_freeze.pyrecords the first four innational_meta_certified.json):household_benefits, for every household, within 32-bit float rounding.disclosure.pycounts survey households, computes top shares and applies the 10% rule as PolicyEngine's layer does, and passes, withholds and marks approximate the same figures (a differential test).disclosure.pygives a reader match the bounds of a linear programme in levels over every figure at once. That programme takes each figure's level, sign and denominator, and each share's cap, from the records, withoutdisclosure.py(a differential test).Within the rounding of every figure involved, and wherever we publish all of them, the tests also check that the published tables add up:
Some households gain, and relative poverty falls:
We compare the tables, charts, selected prose figures and metadata with the results. The tests fail when we change a checked figure or swap the year headings. 47 disclosure, 28 household, 25 hours and 163 national tests passed on the exact round 13 files. The continuation checkout also passed all 210 disclosure and national tests together after integration with current main.
Review
497255259, job20261101-122342-nlw-post-r1: CHANGES, 12 blocking and 6 optional findings, no error in any table figure. Commits up tob10fa97faapply all 18. Where the wording differs from the reviewer's replacement: the title reads "What workers on the National Living Wage pay under frozen thresholds", the chart's three kinks sit at two decimal places, the part-time headings match the main tables' "If the government had" form, and the couple gets a sentence with model figures as well as the narrower scope. Record:~/reviews/budget-coordinator/social-evidence-hunt/drafts/fiscal-drag/post-review/review-r1.md.b10fa97fa, job20261101-143131-nlw-post-r2: CHANGES, 3 blocking and no optional findings. It confirmed all 18 r1 fixes, and its own recomputation matched every table and chart point.e7b6d70f8applies all three. We state the UC pay assumption in the post, the label on the old title-card cover named the Treasury across two lines, and the section 57 property test uses exact decimals. That test now fails five wrong rounding rules, including the reviewer's mutant. Record:review-r2.mdin the same folder.e7b6d70f8, which ran in session because no lane could take work and a disk hold kept all 35 pending jobs from starting: CHANGES, 3 blocking and 5 optional findings. It confirmed all three r2 fixes. The main fix removed "had held to its 2022 statement", which implied a broken commitment although both thresholds still stand equal.f99f06b81applies all eight, and a full pass for the same pattern changed "No statute indexes" to "Parliament has not indexed". Record:review-r3.mdin the same folder.f99f06b81: CHANGES, 2 blocking and 3 optional findings.5336efd47applies all five: the Treasury now owns the NI path, the part-time heading and legend say what £242 a week measures, and the tests pin both strings. Record: end ofreview-r3.md.5336efd47: APPROVE, no findings. It reread the whole post against the full voice file and reran the tests. Rounds 3 to 5 ran in session on Opus 5.5, the fallback for closed lanes.61d08e8a9, the first illustrated cover, which ran in session because no Subfleet lane could take work: APPROVE, with 5 optional findings.89ee44286applies the three findings on the image. It gives the letter, badge and coin the Budget briefing cover's drop shadow and matches the coin ring and stack bands to the Pension Tax Allowance cover. It also moves the work pass and lanyard clear of the letter and refits the wall to that cover in Chakra teal. With those changes, the badge, coin and stack match what the61d08e8a9commit message says (the third finding), and the Cover section above answers the fifth. The shadow filter r6 suggested reached only 20% past each object, so the badge's and the coin's shadows ended in hard edges.78184e5c4widens the drop-shadow filters' regions to 50% and fits the badge's shadow to the one on the Pension Tax Allowance cover. Record:review-r6-cover.mdin the same folder.78184e5c4, which ran in session because no Subfleet lane could take work: CHANGES, 3 blocking and 8 optional findings. It checked the cover against the Pension Tax Allowance cover and the other Budget covers, along with the r6 fixes and the rebuild recipe. It also read the post's changes since r2, the commit messages, this description and the full voice file. It found that the post's £980 sentence ran backwards, which rounds 1 to 5 had missed.0772a7baacorrects that sentence, pins it in a test, and applies r7's other wording on the post and the analysis README. We also state the comparison's direction before the tables.b2d09028dapplies r7's image findings. The soft filter under the clock, the stack and the pass still cut their contact shadows, so78184e5c4's message overstated its fix, and librsvg's linearRGB round trip left rings of 5 levels and moved fills by up to 2 levels, so89ee44286's "exact Chakra values" held only in the SVG source. Each shadow now blurs in sRGB from a copy of the object's outline, and the object draws itself unfiltered on top. This description applies the rest. Rounds 3 to 7 ran in session on Opus 5.5, the fallback for closed lanes, so GPT-6.1 Sol last read the post atb10fa97fabefore r8. Record:review-r7.mdin the same folder.83dd74f3a,ea54a84bb,7b033bac0andb2999039cadd the chart, the UK-wide section and their tests.01d1f8355reports our NI at the OBR's levels beside its employee NI figure, after7b033bac0set that figure beside only the path that lands nearest.01d1f8355, job20261105-105204-nlw-post-r8b: CHANGES, 4 blocking and 10 optional findings. Its household calculations, aggregates and cover checks reproduced.042e369caand41fc37d79apply them with the rerun on the 6.16.1 pair. The £55,000 example now excludes Scottish taxpayers (B1). In the employee section, we say how we share benefits and that we leave out other taxes (B2). We adjust HBAI income for household size in the poverty definition, and in the deciles and a methods note we say which income measure each figure uses (B3). In this description, we apply B4's three scope corrections. For O4, we replaced the per-row floor withdisclosure.py, which checks every published figure and every figure a reader could work out from them through the identities we record. We applied the optional voice edits to the post, the README and this description, apart from the Claude Code footer, which our tooling adds to every PR description. Round 9's reviewers found three README and description rows we had missed, and745c8c478applies them. For O9, we cut the alternative-allowance sentence, andtest_freeze.pynow checks that the post no longer cites the £16,632 allowance. Record:review-r8.mdin the same folder.41fc37d79, one each for the post, the code and the records, which ran in session because no Subfleet lane could take work: CHANGES, 17 blocking and 38 optional findings in all. No reviewer found a wrong figure in any table or a disclosure gap, and no reviewer found a reversed comparison in the post. Reviewer C found that this description had reversed the Gini comparison, and we corrected it.745c8c478applies the rest in full, except that we give the relative poverty reason in the README rather than the post (A-O11) and keep the July 2022 sentence as the reason for the five paths (A-O17). In the post, we turn the Marriage Allowance sentence the right way round, name both Table 4.17 lines, introduce the Pension Tax Allowance, and set out HMRC's scope and timing and both NI effects at the OBR's levels. In the code, we count a change of £1 or less as none, as the layer does, and rerun the tables, which moves no cited figure and withholds the 2027-28 minimum income floor share. We add tests that check the published tables add up within rounding and that a thin band takes its complements with it. Others check that the reader gets four families of identities and every link remainder, and that a reader's bounds match those of a programme in levels. Mutants I6 and I10, which passed before, now fail, and so do two mutants of the reader, M1 and M3. A third reader mutant, M2, failed our run only by raising an error, and passed reviewer B's rerun in round 10. We don't record the starting-rate limit in the meta, because that needs a national rerun, and we leave the round 9 commit messages as they stand, because the review records cite them. Record:review-r9.mdin the same folder.745c8c478, one each for the post, the code and the records, which ran in session because no Subfleet lane could take work: CHANGES, 11 blocking and 30 optional findings in all. No reviewer found a wrong figure in any table, a reversed sign in a table or a disclosure gap. Reviewer A found that "On that path", read plainly, reversed the comparison in two findings, the poverty sentence and the NI sentence, so we open each with "Against that path" or "Against the path without the July 2022 rise". Reviewer B found that the levels test ran on sums alone, although we claimed it in the README for every figure, and that four of the nine columns in the row for all employees never reach the identity check. Reviewer C found that this description claimed identity checks the tests can't run, creditednational_freeze.py's meta with every national check when it records the first four, and left nine moved figures and two changed claims out of the round 8 comparison. We correct all three above.81c1455d5applies all but three of the findings. The levels test now runs on made-up tables of sums, means, counts and shares, including figures we never publish, and takes each figure's level, sign and denominator, and each share's cap, from the records. New tests cover a remainder whose parts have opposite signs, the sign of a figure that rounds to zero, the £1 rule, each pay band's links and the decile margins, which the tests now write out. The post now states the £1 rule. We leave the meta's other change thresholds as they stand, because changing them needs a national rerun, and keep the on/off UC slack, which stays sound (B-O6, B-O10). For C-O12, we'll squash-merge with our own message, which leaves out every round's commit body. Every mutant reviewer B wrote in round 10 now fails a test, and so does each of round 9's named mutants. Reviewer B's D4, which drops the share cap, changes a bound or makes the reader raise an error on only 34 of 1,000 made-up tables, so a fixed test now pins the cap. Record:review-r10.mdin the same folder.81c1455d5, one each for the post, the code and the records, which ran in session because no Subfleet lane could take work. Reviewers A and C returned CHANGES and reviewer B APPROVE: 5 blocking and 24 optional findings in all. No reviewer found a wrong figure in any table. Reviewer A found that the post gave the Pension Tax Allowance's value of up to £94 in 2027-28 as a cap for every year. PolicyEngine UK computes the allowance each year as 20% of the full new State Pension above the Personal Allowance, rounded up to the pound. On the OBR's October 2026 forecast, that gives £182 in 2028-29 and £250 in 2029-30, so we now tie the £94 to 2027-28 in the post. Reviewer C found that this description still reversed the comparison that reviewer A's round 10 finding fixed in the post. It also found that our r10 line softened that finding, overcounted what round 11 applied and left out reviewer C's own round 10 corrections. We correct each above. Reviewer B wrote 26 mutants ofdisclosure.py: 22 failed a test, and reviewer B judged the four survivors equivalent or harmless. One of the four, R18, gives a remainder its cells' smallest tolerance, and reviewer B judged it harmless because no table builder then mixed tolerances in one link. Round 12 applies every finding:af791cae6in the post, the README and the tests, and the rest in this description, the review records and the squash message. We add a test that pins a remainder's largest tolerance. In the group-size test, we weigh each record 2, so the share cap differs from the number of records, and R4, which caps a share at that number, fails it. In the levels test, we now give some published sums and counts units of 100 and 1,000, as we publish the UK-wide tables in billions and millions. We also add to each row a net cell whose link has parts of both signs. Reviewer B's unit mutants, U2 and U3, now fail the levels test on 143 and 400 of 400 made-up tables. We set no deadline on any Hypothesis test in the four test files. Record:review-r11.mdin the same folder.af791cae6, in session: the account reached its weekly usage limit at 17:13Z on 11 November, ten minutes in, and the API ended all three before any reported. Their progress files held 15 distinct findings and notes, none on a figure. Reviewer A recorded four wording findings on the post, and found that "follows the State Pension" in our r11 line understated how the allowance grows. Reviewers B and C found that our reason for R18 no longer held, because round 12's net link mixes tolerances. Reviewer C also found that our r11 line creditedaf791cae6with fixes that landed in the records. We correct all three above. Reviewer B found that, since we added the net cell, a made-up table can reach a level of 336,000, where the levels test's tolerance would exceed the smallest half unit.c169b10aeapplies the four post findings and halves that tolerance, which now stays below the half unit for every table the generator can draw. It also adds a test that a reader knows no cap on a mean, which R26, one of round 11's four survivors, fails. The resumed probes cover all 12 further mutants. Fixed assertions catch U4, D3, S3, U7, U8, U5 and S2; the bounds probe disagrees with U6 on 157 of 200 tables and D1 on 190 of 200. S1 disagrees on 16 of 200 tables (39 of 400 in the earlier run); S4 fails the whole-file remainder test. S5 passed the first whole-file run, then fails the new fixed negative-unit interval test. These results come fromdev-r13/probes/mutants-r13.logandmutants-r13-light.log. Record:review-r12.mdin the same folder.Plan
policyengine6.16.1 pinsc169b10aesquash-message.mdfrom the review folder, not the round commit bodies🤖 Generated with Claude Code