Skip to content

Add post on the threshold freeze and the National Living Wage - #1510

Draft
MaxGhenis wants to merge 339 commits into
mainfrom
uk-nlw-threshold-freeze
Draft

MaxGhenis wants to merge 339 commits into
mainfrom
uk-nlw-threshold-freeze

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Round 13 at c169b10ae applies 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:

  • A worker on 35 hours a week pays £702 more income tax in 2026-27 and £816 more in 2027-28.
  • Across the five NI paths, the same worker pays £678 to £983 more income tax and NI in 2026-27, and £816 to £1,142 more in 2027-28.
  • A part-time worker in 2026-27 pays no income tax or NI at 19 hours a week or fewer, and pays the full extra income tax from about 24.3 hours.
  • While a worker receives UC under both sets of thresholds, they receive 55p more UC for each £1 of extra income tax and NI, so their net income falls by 45p. In a council that takes 20p of Council Tax Reduction for each £1 of income, their net income falls by 36p while they receive both. For example, a lone parent with two children has £305 to £442 less net income in 2026-27.
  • A one-earner couple who claim the Marriage Allowance pay £772 more income tax in 2026-27 and £898 more in 2027-28.

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:

  • Households pay £30 billion to £38 billion more income tax and NI in 2026-27 across the five paths, and £37 billion to £45 billion more in 2027-28. They receive about 3p more in benefits for each £1 of extra income tax and NI.
  • Households in the ninth income decile have 2.7% less net income in 2026-27 than if the government had raised the NI threshold with the allowance, and households in the lowest decile have 1.0% less.
  • Against that path, the 3.2 million employees aged 21 to State Pension age on £20,000 to £25,000 pay £1,031 more income tax and NI on average in 2026-27. After we share each family's benefit change among its members in proportion to their extra income tax and NI, they have £984 less net income.
  • Against that path, 401,000 more people fall below the absolute poverty line before housing costs in 2026-27, and 344,000 fewer fall below the relative line, which moves with median income.

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.csv and hours.csv match 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 policyengine 6.16.1 pins: PolicyEngine UK 2.174.1 with policyengine-core 3.33.3, and the Microcosm UK file microcosm-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 released policyengine 6.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 policyengine 6.11.1 pins, PolicyEngine UK 2.151.0 with microcosm-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, and disclosure.py withholds one of them:

Figure, 2026-27 unless stated Round 8 Now
Extra income tax and NI across the five paths £29.7 billion to £37.3 billion £30 billion to £38 billion
The same in 2027-28 £35.9 billion to £44.1 billion £37 billion to £45 billion
Average household's change in net income, first path −£1,245 about −£1,280
Households with less and more net income, first path 81%, 0.4% 82%, about 0.5%
Employees aged 21 to State Pension age in a family on UC 8% 7.5%
Lowest decile's extra income tax and NI, and extra benefits £270, £28 £281, £33
Ninth, lowest and top deciles, change in net income −2.6%, −0.9%, −1.3% −2.7%, −1.0%, −1.4%
Ninth and top deciles, in pounds −£2,303, −£2,079 −£2,315, −£2,148
Fifth, sixth and second deciles, change in net income −2.0%, −2.3%, −1.3% −2.2%, −2.4%, −1.4%
Employees on up to £12,570: extra income tax and NI, and the share with total income above £12,570 £203, 31% £232, 37%
Employees on £20,000 to £25,000 2.4 million 3.2 million
Their extra income tax and NI, and net income change £996, −£937 £1,031, −£984
Their share in a family on UC, and the net income change on and off UC 11%, −£462, −£996 9%, −£465, −£1,035
More people below the absolute line before housing costs, first path 481,000 401,000
The same on the path without the July 2022 rise the post gave the range across the paths, 432,000 to 481,000 (70,000 to 84,000 children) 320,000 (about 54,000 children)
Fewer people below the relative line 325,000 344,000
Households with more net income without the July 2022 rise 1.7% 1.6%
Gini index, frozen against raised with the allowance 0.282 against 0.281 0.284 against 0.283
Gini index, the OBR's levels against frozen 0.283 against 0.282 0.285 against 0.284
Top decile, the OBR's levels against frozen £3,277 £3,350
More people paying income tax against the OBR's levels 4.8 million and 5.5 million 4.7 million and 5.0 million
Our income tax yield against the OBR's levels £42.3 billion and £49.2 billion £43.5 billion and £50.5 billion
Our yield against the OBR's levels, over its Table 4.17 26% in each year about £10 billion and £11 billion, or 29% to 30%
Our baseline income tax against the OBR's forecast 6% and 8% lower 3% and 5% lower
Our estimate against HMRC's +10% row £15.2 billion and £15.9 billion (+6%, −10%) £15.5 billion and £16.2 billion (+8%, −8%)
Our NI against the OBR's levels −£5.1 billion in each year withheld in 2026-27, −£5.2 billion in 2027-28
NI on the path without the July 2022 rise, 2026-27 £0.6 billion less £630 million less
NI on the path without the July 2022 rise, 2027-28 £0.2 billion £250 million

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

  • Pay bands, with no count of National Living Wage workers. In the Microcosm UK file, 13% of employees aged 21 or over with pay and hours earn more than 5% below the data year's £11.44 wage floor when we divide their pay by their hours. Another 8.2% earn within half a penny of it, where Microcosm's build lifts pay it draws from HMRC's Survey of Personal Incomes when the draw falls short of the minimum wage for the record's usual hours. The Low Pay Commission projected that 5.9% of jobs for people aged 21 or over would pay the National Living Wage or up to 5p more in April 2026. We can't reliably identify National Living Wage workers from survey reports of hours and pay, so we group employees by annual pay.
  • What the five paths move. We move only the Personal Allowance and the Class 1 primary threshold. On the five paths, we keep the basic rate band at £37,700, so the higher rate threshold rises with the allowance, and we leave the upper earnings limit, Class 4 limits and employer thresholds where they stand. In the separate obr_thresholds scenario, we also index that band, at the OBR's levels, for the comparison with its figures.
  • Disclosure. We publish a national figure only when it passes the rules of PolicyEngine's disclosure layer in policyengine 6.16.1, which disclosure.py restates and test_disclosure.py checks 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.
  • More, less or the same. We count a household as having more or less net income when its net income changes by more than £1 a year. PolicyEngine's disclosure layer counts a change of £1 or less as none.
  • For Max before publishing: the gap with the OBR. Against the OBR's indexed thresholds, we estimate an income tax yield of £43.5 billion and £50.5 billion. Our yield exceeds the sum of its two Table 4.17 lines, £33.6 billion and £39.2 billion, by about £10 billion and £11 billion, or 29% to 30%. It exceeds its freeze line alone, £32.7 billion and £38.2 billion, by 33% and 32%. Our baseline income tax liabilities trail its receipts forecast by 3% and 5%. We estimate 4.7 million and 5.0 million more people pay income tax with thresholds frozen, close to the 4.8 million and 5.3 million in its Table 4.18. From the OBR's own counts, we work out about £46 billion by hand in 2026-27, and about £40 billion from HMRC's +10% row times 2.8 (analysis README). In Table 4.17 the OBR counts receipts, and we compute static liabilities. The OBR publishes the yield by measure with no split by income, so we can't trace the gap, and we say so in the post. We haven't run the OBR scenario one threshold at a time, which would show which threshold drives the gap. For NI, we estimate a larger fall than the OBR. Against the same levels, with the upper earnings and profits limits at the OBR's higher rate threshold, we estimate £5.2 billion less NI in 2027-28. In Box 3.3 of its November 2025 outlook, the OBR puts employee NI £1.3 billion and £0.5 billion lower in 2026-27 and 2027-28. Our income tax and NI together exceed Box 3.3's by 15% in 2027-28. We withhold our 2026-27 NI figure at those levels, because a reader could combine it with our other figures to work out too much about a few survey households.
  • HMRC's reckoner. HMRC includes behavioural responses and leaves out Scottish income tax and the Welsh rates of income tax. It also counts Self Assessment income tax when people pay it, so most of the 2026-27 change in that tax falls outside its 2026-27 figure. We include no behavioural response, and we leave out Scottish taxpayers' income tax but keep the Welsh rates.
  • Council Tax Reduction. PolicyEngine UK counts only the families who report it in the survey. For working-age families in English councils beyond the five it simulates, it generally keeps the reported amount, so we estimate no change for them, and we say so in the post. It can cap that amount where another family shares the bill and receives a simulated reduction. From 2.175.8, PolicyEngine UK gives working-age families in Scotland and Wales rules of their own (policyengine-uk#1966); 2.174.1 simulates the Scottish and Welsh schemes for families of every age.
  • UC's minimum income floor. On the first path in 2026-27, we estimate that more than 90% of the households that have more net income with thresholds frozen include someone on the floor, though that share rests on fewer than 30 effective survey households. We withhold the share for 2027-28. Under regulation 62, the Secretary of State decides how much income tax and NI to deduct from the floor. PolicyEngine UK 2.174.1 deducts the income tax a claimant would pay if the floor were their only income, after the standard Personal Allowance. It also deducts Class 2 and Class 4 NI on profits of that amount, and on all five paths we leave the Class 2 and Class 4 limits where they stand. So with the allowance frozen, we count lower earnings for the claimant and estimate more UC. hours_meta.json records a worked case on hypothetical inputs.
  • Section 57 allowance. We use £16,080 and £16,650, because under section 57 the Treasury rounds each increase up to a multiple of £10, and the government uses the same rule as its default in Annex A of its Budget 2026 costings. We keep rows on alternative allowances of £16,070 and £16,632 in results.csv and leave them out of the post.
  • Model runs. We ran the model for every row, including the one where the Treasury would have counted inflation from July 2022 to the September before each tax year and rounded the weekly threshold to the nearest £1, as in Annex A.
  • NI rate cut. We hold the main NI rate at 8% in every threshold comparison. Separately, we compare current NI with NI at 12% and a threshold of £12,272, the level it would reach in 2026-27 without the July 2022 rise; in that comparison we change both the rate and the threshold.
  • UC pay pattern. We calculate UC over a year, which matches equal monthly pay, and we state that assumption in the post. In 2026-27, the single renter's £902 maximum monthly award falls short of 55% of their £1,681 monthly net pay. If their employer paid them every four weeks, they would receive about £48 of UC in a month with one payday.
  • Scope. Our main household figures cover a single worker under State Pension age with no other income. We give figures for the one-earner couple with the Marriage Allowance separately.

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, and 61d08e8a9 deletes the old title card (cover.webp).

make_cover.py draws 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 at 89ee44286. 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.py and test_disclosure.py check 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:

  • Each section 57 step equals the CPI step rounded up to £10, for any CPI path of zero or more.
  • Each weekly NI step stays within 50p of the exact CPI step.
  • When both counterfactual thresholds stand at or above £12,570, extra tax and NI stay between zero and 20% of the allowance gap plus 8% of the threshold gap.
  • Extra income tax plus extra NI equals the total, and net loss equals the total plus the UC change within £1.
  • We compare every single-worker row with plain arithmetic within £1, and the run asserts the same check.
  • On the four paths whose NI thresholds stand at or above £12,570, the extra amount never falls as hours rise, and stays at zero up to 19 hours in 2026-27.
  • UC rises by 55% of the extra tax and NI wherever UC stays in payment on both sides.
  • Where a 20% scheme's reduction and UC both stay in payment on both sides, net income falls by 36p for each £1.
  • A worker with no benefits loses all of the extra tax and NI, and no one who pays more loses more than they pay.
  • A worker on 35, 37.5 or 40 hours, a worker in Scotland and a worker paying minimum auto-enrolment contributions all pay the same extra tax and NI.

National results (test_national.py and test_disclosure.py check these, and national_freeze.py records the first four in national_meta_certified.json):

  • Net income changes by the change in benefits less the change in tax, for every household, within 32-bit float rounding.
  • The benefit components add up to household_benefits, for every household, within 32-bit float rounding.
  • All five paths give each person the same income tax, and a higher primary threshold never gives anyone less extra NI.
  • For employees in the simple cases we identify, we compare the model's income tax and NI with plain arithmetic within £1.
  • Sharing a family's change in benefits among its members conserves the total.
  • The Gini index stays between 0 and 1, ignores scale, treats a weight as that many records, falls after a transfer from richer to poorer, and matches microdf (a differential test).
  • Each pay falls in one pay band.
  • disclosure.py counts 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).
  • Every identity we record between published figures holds record by record.
  • No published figure fails the rules, no figure a reader can work out from the published ones through those identities fails them, and withholding gives the same result each time.
  • On made-up tables of sums, means, counts and shares, including figures we never publish, the bounds disclosure.py gives 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, without disclosure.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:

  • In each column of average changes, the deciles, which we weight by their households, add up to the total.
  • In each average and share where every band shows a figure, the row for all employees averages the bands, which we weight by their employees. We withhold the top band's UC share and change in benefits in every scenario and year, so the tests can't check those two columns. Nor can they check the averages on and off UC, where we weight the bands by their employees on and off UC.
  • In each band, income tax plus NI gives the total, benefits less income tax and NI give the change in net income, and the averages on and off UC, which we weight by the UC share, give the band's average.

Some households gain, and relative poverty falls:

  • Extra NI turns negative in 2026-27 on the path without the July 2022 rise, because £12,570 exceeds the £12,272 that path reaches. Someone on more than £132,160 then has no allowance either way and pays up to £24 less NI. We estimate 1.6% of households have more net income with thresholds frozen than on that path, and, from fewer than 30 effective survey households, that 61% of them include such a person.
  • In our calculations, DWP pays more UC to a self-employed claimant on the minimum income floor under the freeze.
  • Relative poverty falls with thresholds frozen, because median income falls by a larger share than incomes near the line.
  • In Oxford, a fall in income can keep a lone parent in a higher band of support.
  • Against our five paths, the Gini index rises with thresholds frozen; against the OBR's indexed higher rate and additional rate thresholds, it 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

  • r1, GPT-6.1 Sol at 497255259, job 20261101-122342-nlw-post-r1: CHANGES, 12 blocking and 6 optional findings, no error in any table figure. Commits up to b10fa97fa apply 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.
  • r2, GPT-6.1 Sol at b10fa97fa, job 20261101-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. e7b6d70f8 applies 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.md in the same folder.
  • r3, an independent Opus 5.5 agent at 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. f99f06b81 applies all eight, and a full pass for the same pattern changed "No statute indexes" to "Parliament has not indexed". Record: review-r3.md in the same folder.
  • r4, the same agent at f99f06b81: CHANGES, 2 blocking and 3 optional findings. 5336efd47 applies 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 of review-r3.md.
  • r5, the same agent at 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.
  • r6, an independent Opus 5.5 agent at 61d08e8a9, the first illustrated cover, which ran in session because no Subfleet lane could take work: APPROVE, with 5 optional findings. 89ee44286 applies 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 the 61d08e8a9 commit 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. 78184e5c4 widens 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.md in the same folder.
  • r7, an independent Opus 5.5 agent at 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. 0772a7baa corrects 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. b2d09028d applies r7's image findings. The soft filter under the clock, the stack and the pass still cut their contact shadows, so 78184e5c4's message overstated its fix, and librsvg's linearRGB round trip left rings of 5 levels and moved fills by up to 2 levels, so 89ee44286'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 at b10fa97fa before r8. Record: review-r7.md in the same folder.
  • Max then asked why the post has no earnings chart of net income and no microsimulation. 83dd74f3a, ea54a84bb, 7b033bac0 and b2999039c add the chart, the UK-wide section and their tests. 01d1f8355 reports our NI at the OBR's levels beside its employee NI figure, after 7b033bac0 set that figure beside only the path that lands nearest.
  • r8, GPT-6.1 Sol at 01d1f8355, job 20261105-105204-nlw-post-r8b: CHANGES, 4 blocking and 10 optional findings. Its household calculations, aggregates and cover checks reproduced. 042e369ca and 41fc37d79 apply 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 with disclosure.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, and 745c8c478 applies them. For O9, we cut the alternative-allowance sentence, and test_freeze.py now checks that the post no longer cites the £16,632 allowance. Record: review-r8.md in the same folder.
  • r9, three independent Opus 5.5 agents at 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. 745c8c478 applies 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.md in the same folder.
  • r10, three independent Opus 5.5 agents at 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, credited national_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. 81c1455d5 applies 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.md in the same folder.
  • r11, three independent Opus 5.5 agents at 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 of disclosure.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: af791cae6 in 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.md in the same folder.
  • r12, three independent Opus 5.5 agents at 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 credited af791cae6 with 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. c169b10ae applies 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 from dev-r13/probes/mutants-r13.log and mutants-r13-light.log. Record: review-r12.md in the same folder.

Plan

  • Apply round 8 and rerun the UK-wide figures on the pair policyengine 6.16.1 pins
  • Apply round 9 and rerun the UK-wide tables with the layer's £1 change rule
  • Apply round 10's review
  • Apply round 11's review
  • Apply what round 12's reviewers recorded before a usage limit cut them off
  • Rerun the household scripts on PyPI's latest PolicyEngine UK release
  • Voice gate on the whole text after every edit
  • Independent peer approves the bounded round 13 continuation at the final head
  • Cover in the Budget posts' illustrated style (r6, r7)
  • Chart of net income by hours and UK-wide run
  • All 11 CI and preview checks green at c169b10ae
  • Squash-merge with squash-message.md from the review folder, not the round commit bodies
  • We queued the publish decision for Max (d2303)

🤖 Generated with Claude Code

anth-volk and others added 3 commits October 31, 2026 00:22
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>
@vercel

vercel Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
policyengine-calculator-next Ready Ready Preview Nov 12, 2026 2:52am UTC
policyengine-website Ready Ready Preview Nov 12, 2026 2:52am UTC

Request Review

MaxGhenis and others added 3 commits November 1, 2026 12:57
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>
PavelMakarchuk and others added 3 commits November 1, 2026 15:20
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>
anth-volk and others added 5 commits November 2, 2026 17:28
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
anth-volk and others added 5 commits November 11, 2026 18:12
…'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>
anth-volk and others added 4 commits November 11, 2026 19:00
@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>
anth-volk and others added 13 commits November 11, 2026 21:06
…'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

2 active deployments
Preview – policyengine-calculator-next — c169b10a Deployed Nov 12, 2026 by vercel[bot]
Preview – policyengine-website — c169b10a Deployed Nov 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants