Repository navigation
Conversation
…gives the least tax ITA 2007 s. 25(2) deducts Step 2 reliefs and Step 3 allowances in the way that gives the greatest reduction in liability, and from 2027-28 s. 25(3A) deducts them from income other than property, savings and dividend income first. The model set them against non-savings, savings and dividend income in that fixed order. utils/income_tax_allocation.py finds the least-tax share per person by evaluating the Step 5 tax at every crossing of the lines where its slope changes. Fixes #2106. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the least-tax share moves Base non-savings income on net_income plus trade loss relief, as the default order does, so a supplied adjusted net income or a reform that sets net income is followed. Add a 2027-28 case where s. 25(3A) keeps the allowance against pay. Update the gift aid and integration cases, and the gift aid snapshot, whose tax falls under s. 25(2), with the arithmetic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The search now runs only on people that three tests leave: one share exists (nothing to share, or no income left after the deductions); the old order sets every shared deduction against the first component and leaves no savings or dividends charged above the nil rates; or the rate on the first component just above where the old order leaves it covers the highest savings and dividend rates the moved deductions can reach. Each test gives the result the search would, and a property test checks that on synthetic people, with rising and with falling rates. The search evaluates its candidates only on the people left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fast path looked up each rate by the last threshold passed, which goes wrong once a Gift Aid gift lifts the UK basic and higher rate limits above the £10 million extra bracket: a person whose income sits below the lifted basic rate limit read as paying the additional rate, so the fast path could keep the old order where moving deductions saves tax. The fast path now sums the rate steps the income has passed, the slope of the tax the search evaluates, and requires every step to be non-negative. The property test gains that person. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ve it Case 12 sets £270 of the allowance against dividends above the basic rate limit, with £300 of savings between the pay and the dividends inside the savings allowance. The search finds that share only through the line where the pay reaches the basic rate limit. The changelog fragment now names the model as the actor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Property income of £40,000 and savings of £20,000 in 2026-27: under ITA 2007 s. 25(2) the model sets £10,270 of the Personal Allowance against savings, which keeps the property income at the basic rate limit and puts the £500 savings allowance against savings taxed at 40%. Savings tax falls from £5,846 to £3,692 and income tax from £11,332 to £11,232. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under ITA 2007 s. 25(2) the model can set allowances against savings or dividends while property income is left, so: - the accounting invariant subtracts the savings and dividends left after the allowances set against them; - the monotonic invariant tests income tax, since more property income can lower savings tax: property income of £30,000 to £40,000 with savings of £20,000 takes savings tax from £3,800 to £3,692 while income tax rises from £7,286 to £11,232, which a new test pins; - the higher-rate check pins savings tax of £3,692, income tax of £11,232 and non-savings taxable income of £37,700 for property income of £40,000 and savings of £20,000. The file now loads the model once through cloned_uk_tax_benefit_system, as other test files do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The notes now say the model sets the allowances under ITA 2007 s. 25(2), rather than the section or the allowance doing it. Comments and docstrings only; the code and the YAML data don't change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ITA 2007 s. 55B(1) gives the Marriage Allowance as a tax reduction, which Step 6 of s. 23 deducts (s. 26 lists Chapter 3A), not as a deduction at Step 3. The search now leaves marriage_allowance out of the earned income exclusions it reads, so the s. 55B(2)(b) rate test can read the share without a cycle. Case 13 of least_tax_allocation.yaml fails when the search reads the allowance again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments only: the module docstring, the IGNORED_EXCLUSIONS note and case 13's working. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace passive constructions in the branch's comments, two YAML case
names, a test module docstring and the documentation of
non_savings_income_after_allowances ("is taxed at 20%" becomes "bears
20%", "is left" becomes "remains"). No test input, expected value or
formula changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Scottish taxpayer's property income stays on the Scottish rates while the Budget 2026 savings rates rise, so the least-tax order can move an allowance onto savings. Property tax and savings tax then both move, so the identities in test_budget_2026_savings_property_rates_properties.py hold only for people whose allowance order the rates leave unchanged. Pin that person with a worked case, and add them to the fast-path test's fixed people. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…order Pension relief under net pay (FA 2004 s. 193(2)) and relief at source (s. 192(4)) is no Step 2 deduction the taxpayer can set against savings or dividends, so the least-tax search now holds min(pension relief, non-savings income) on non-savings income and shares only the rest. The income tax that Universal Credit deducts from earnings reads the old order's deductions, so more savings or dividends never raise it. Before 2027-28 the search adds the lines that an exclusion from earned income creates beside property income. non_savings_income_after_allowances comes from the allocation's own arithmetic, and earned_taxable_income never exceeds it, so float32 rounding in the relief added back leaves no residue. New tests: YAML cases for pension relief, the earned income add-back, a pension-exempting reform and the float32 residue; property tests for the UC slice, the search with exclusions and the Budget 2026 rate bound; tests that separate the fast path's supplied-net-income and Scottish starter rate checks. Docstrings name the actor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6JoK8gQvt3JZzVN18g
The branch renamed test_more_property_income_never_lowers_savings_or_dividend_tax to test_more_property_income_never_lowers_income_tax, and the checked-in durations still named the old test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6JoK8gQvt3JZzVN18g
Quote what FA 2004 s. 192(1) and (4) give for relief at source in the allocation's docstring and comment, YAML case 15 and the pension test. Give the first-threshold sample's counts, bound the float32 residue at under a penny, name the model's choice of PAYE coding for the Universal Credit slice, and note that non_savings_taxable_income reads non_savings_income_after_allowances through its two parts. No formula changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N2tyHPRRwcYXiPudqKR4bf
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 2 (thresholds in Notes
Moves flagged for review
Receipts by tax head
Spending by benefit and service
Caseloads
Government balance, each component counted once
Model totals
Household income totals
Inequality
Mean household net income in each side's own deciles
Standard reform costings (change in gov_balance)
The report suppresses a reform's costing and target change in these years, for the reasons below (
Standard reforms: change in each reform's target
Household net income by decileMean change in household net income per household, and as a share of the decile's base income; households stay in the base run's deciles.
No change in any year: population; poverty rates; people in poverty. Checks failing on both base and head (1)
Counted-once checkEach of 92 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.
Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
|
Ready for hub review Verdict: REQUEST_CHANGES at The text-only delta closes all nine P3 findings from the previous round. Every quotation I checked against the law in the brief matches word for word, and both of the author's judgment calls are right. The new 2027-28 case is right by hand and in a run. No formula changed. But CI on this head is red. Two existing tests, which pass on How I worked: detached worktrees of the head and of P1P1-1. CI is red: two tests still pin the old order. The CI run Neither file has changed since the merge base (a)
With the one-character assertion change, the file passes (on a scratch copy, then restored). (b)
Model runs give the same figures: on head, HIGH is
With that change the file passes (on a scratch copy, then restored). One caveat: this rests on the model's Body: add both tests to "Existing tests whose expected values move, and why", with the reasons above. Replace "Not run: the whole test suite, which CI runs on this PR, and any test that needs a survey dataset." with the CI result once it is green. P3P3-1. The pension paragraph's last sentence reads against its own claim (body, "Pension relief stays on non-savings income"). The paragraph says neither arrangement gives a Step 2 deduction. It then ends "since the taxpayer must otherwise deduct it from that income first (s. 25(3A))", but s. 25(3A) reaches only "the reliefs and allowances in Steps 2 and 3". Replace with: "…since the model otherwise deducts it from that income first, as s. 25(3A) requires of the reliefs and allowances in Steps 2 and 3." P3-2. Optional: Scottish limits ( P3-3. Axiom packet, the pending note on the 2027-28 case. The packet's own blocker section lists s. 8 among the sections already in the corpus at Previous round, finding by finding
The author's two judgment calls
The new 2027-28 caseBy hand, at the model's 2027-28 parameters (I read them: PA 12,570, UK thresholds [0, 37,700, 125,140], dividend rates [10.75%, 35.75%, 39.35%]). Pay £50,000 and dividends £20,000:
Run: QuotationsChecked word for word against the hub's fetched text in the brief:
Each cited subsection says what the text claims:
Checked and sound
What I couldn't check
Generated by Claude Code |
|
Picking this up for the UK hub. Reply here if you're mid-change and I'll stop. Generated by Claude Code |
The trading loss differential now bounds the tax with a loss by the tax on income cut in the old order, and keeps the equality where the person has no savings or dividend income. The paternity pay test expects the £100.20 rise that setting £1,500 of the Personal Allowance against savings gives. The allocation docs cite FA 2004 s. 192(4A) for the Scottish rate limits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6KkfvQBfSHTvh7rcen
|
Generated by Claude Code |
|
Generated by Claude Code |
|
Generated by Claude Code |
|
Ready for hub review Head: FindingsP1-1(a),
P1-1(b),
Body bullets (item 3): I added both bullets at the end of "Existing tests whose expected values move, and why", with figures from runs at head:
Test plan (item 4): I added a "Ran at 056a674…" list and labelled the older lists by commit. The checkbox now reads "Lint and format checks pass at 056a674…". The "Not run" line is unchanged. Merge notes: I reran them from head.
P3-1: I replaced the pension paragraph's last clause with the review's text. P3-2: P3-3 and case 11,
Where I departed from the review's or the brief's text
Tests at 056a674
Mutation checksEach ran on a scratch copy, on the renamed test without xdist, and was then restored.
Skipped
Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Ready for hub review Verdict: APPROVE WITH NITS at The delta closes P1-1(a), P1-1(b) and P3-1 to P3-3 from the review of How I worked: a detached worktree of P1None. P2None. P3P3-1.
Replace docstring lines 25 to 33, from "the payer keeps" to "shares only the rest.", with: In the comment at P3-2. Body, test plan and merge notes: production history, and SHAs that lag the head.
P3-3. Rules cited as actors in the new s. 12B text. The writing rules have Parliament act and cite the rule in brackets.
P3-4. Optional: widen the no-choice branch ( Earlier findings and this round's rulings
Checked and soundP1-1(a): the one-sided bound can fail. It fails wherever the model charges more than the old-order cut, for example when the relief comes off net income short of its full amount:
So the bound catches under-relief, and the kept equality catches over-relief. The author didn't rerun "survives with the branch disabled", and I didn't either. P1-1(a), the docstrings against the code.
Model runs, 2026-27, London, age 40.
The paternity rise is Hand checks.
The s. 12B reading, against the quoted law: right.
Merge notes,
Tests at
CI at the head. Every check run is Python shards from the job logs:
Live PR. The title and body match the hub's documents: the first lines are kept, both new "expected values move" bullets are there, and the s. 12B judgment call and the P3-1 text are in place. What I couldn't check
Generated by Claude Code |
FA 2004 s. 192(4A) raises the Scottish basic rate limit and the Scottish rate limits above it, where a Scottish rate above the Scottish basic rate is chargeable. The docstring and one comment said "the Scottish rate limits". Text only; the AST with docstrings blanked matches 3b54d53. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22
left a comment
There was a problem hiding this comment.
Reviewed 6602ccce488f1302972331d61ba94f02528d08e7. COMPLETE — requesting changes for one repository-completion requirement.
C1 — Finish the statutory evidence in the filed Axiom handoff. rulespec-uk#524 is open and labelled pe-parity, but companion case 4 depends on the 22% property rate in Finance Act 2026 s.7 while its completion boundary explicitly says that provision has not been quoted. It supplies no operative corpus/companion cross-reference for that premise. The repository's docs/engineering/skills/github-prs.md requires a dispatch-ready queued issue with verbatim law and externally sourced companion tests.
Append the operative s.7 rate text and relevant s.6(4), (8) hierarchy/commencement evidence, or identify the existing supported corpus/companion reference. This can be fixed in the filed handoff without running the encoder. The recorded signed-run/corpus-ingestion blockers are not themselves the finding.
This concerns the queue's 22% enacted-law companion, separately from this PR's 24% announced Budget 2026 model case. I found no source-backed wrong-money defect in the reviewed allocation change.
All 143 selected checks passed, including 96 YAML cases, 46 repository Python checks and a final income-tax/household-net-income diagnostic. The changed allocation rules and 2027 commencement were independently checked against primary sources; 33 source records/extracts passed integrity validation. CI is green at the reviewed head. Once the filed source premise is completed, that evidence update can be reviewed without repeating unchanged code tests.
|
For C1, we've quoted the law behind case 4 in rulespec-uk#524. Parliament set the 2027-28 property basic rate at 22% by FA 2026 s. 7. By s. 6(3) and (4) it inserted ITA 2007 ss. 11CA and 16A, with effect from 2027-28 (s. 6(8)). We quote s. 7, s. 11CA(1) and (4), and the s. 6(4) insertion beside the s. 6(8) commencement text. We now cite the law for each step of case 4, where we take a taxpayer who is neither a Scottish nor a Welsh taxpayer (s. 11CA(4)). The £19,000 of property income sits immediately below the dividends (s. 16A(4)), within the basic rate band, so Parliament charges it at 22% (s. 11CA(1) and s. 7(a)). We added the same steps to the At |
juaristi22
left a comment
There was a problem hiding this comment.
Re-reviewed 6602ccce488f1302972331d61ba94f02528d08e7 and the new filed evidence. COMPLETE — C1 RESOLVED; no open findings or material evidence gaps.
The actual Axiom #524 issue now supplies FA 2026 s.7's 22% rate, the operative s.11CA and s.16A insertions, commencement, the non-Scottish/non-Welsh case scope and the module/corpus ingestion boundary. These match the independently checked official legislation. Companion case 4's statutory 22% calculation remains separate from the PR's announced 24% scenario.
Code and description are unchanged. The prior 143 passing checks and native result were verified and retained, with current runtime/dependency pins; no tests or model calculations were rerun. Applicable checks pass at this exact head. Approved.
Fixes #2106
Reproducer:
policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/received_allowances/least_tax_allocation.yaml::Case 2, savings in the starting rate band leave the Personal Allowance to dividendsaxiom: TheAxiomFoundation/rulespec-uk#524 queued
What and why
Parliament set the rule in ITA 2007 s. 25(2): "At Steps 2 and 3, deduct the reliefs and allowances in the way which will result in the greatest reduction in the taxpayer's liability to income tax." The model always deducted them from non-savings income first, then savings, then dividends. Under that order it overcharged wherever an allowance set against savings or dividends would save more tax:
origin/mainBoth columns come from runs of the model at 6429896 (whose model code 70633f0 keeps) and
origin/main2da212a; the YAML cases pin the right-hand column.The model overcharged people with savings as well as dividends (second row), and people with only pay and savings (fourth row).
Parliament changed the rule from 2027-28:
gov.hmrc.income_tax.reliefs.other_income_first: false until 2027-04-06, true from then.The model now shares the deductions this way in every year it covers. Before 2027-28, who gains depends on the starting rate limit, the personal savings allowance, the dividend allowance and the rate limits. The model sets the dividend allowance at £5,000 in 2016-17 and 2017-18 and £2,000 from 2018-19 to 2022-23 (
dividend_allowance.yaml).In 2027-28, take property income of £20,000, dividends of £200,000 and a £1,000 trading loss. Setting the relief against property income saves 24% on property income in the basic rate band (22% under FA 2026 s. 7). It also moves £1,000 of dividends down into the 10.75% ordinary rate band, because the dividends start where the property income ends and still end at £219,000. Setting it against dividends leaves that boundary where it is. So the model sets the relief against property income and charges £74,710.21, as
origin/maindoes, against £74,842.71, worked by hand, with the relief against dividends. YAML case 4 works the arithmetic.Deductions the model shares out
At Steps 2 and 3 the model deducts:
trade_loss_relief_against_general_income(s. 64, a Step 2 relief). Parliament made s. 25(2) subject to the priority rule in s. 65(2) to (4) (s. 25(3)). Under that rule the taxpayer's claims follow a set order, but nothing ties the relief to a component, so the model sets it wherever it saves the most tax, after other income from 2027-28.allowances, the sum of:personal_allowanceandblind_persons_allowance, the Step 3 allowances (Part 3 Chapter 2);charitable_investment_gifts(Part 8 Chapter 3),covenanted_paymentsandother_deductions;pension_contributions_relief, which the model holds on non-savings income (below).Each part except pension relief can come off any component, and the tax depends only on how much comes off each. So the model shares out their sum, up to total income (s. 25(4) and (5)).
Pension relief stays on non-savings income. The model takes employee contributions as a net pay arrangement and personal contributions as relief at source (
pension_contributions_relief,net_pay_pension_relief). Under a net pay arrangement the employer deducts the contributions from employment income (FA 2004 s. 193(2)). Under relief at source the payer keeps basic rate tax on the contribution (s. 192(1)) and, on a claim where higher or additional rate tax is chargeable, Parliament raises the basic and higher rate limits by the contribution (s. 192(4)). Neither gives a Step 2 deduction that the taxpayer can set against savings or dividends; only a net pay excess relieved on a claim (s. 193(4) and (6)) or relief on a claim under s. 194(1) does. The model gives both kinds as a deduction from income, so the search holds min(pension relief, non-savings income) on non-savings income and shares only the rest. YAML cases 14 and 15 pin it: pay £40,000, £10,000 of pension contributions and savings £30,000 in 2026-27 give £11,332, the tax on pay of £30,000 and savings of £30,000. Worked by hand: 17,430 × 20% = 3,486 on the pay; savings from £17,430 to £47,430, £500 at the nil rate, 19,770 × 20% = 3,954 and 9,730 × 40% = 3,892. Every share keeps the savings allowance below the £37,700 limit, so no share lowers that tax. Sharing the pension relief too would charge £11,232. From 2027-28 the hold matters only where the relief exceeds income other than property, savings and dividend income, since the model otherwise deducts it from that income first, as s. 25(3A) requires of the reliefs and allowances in Steps 2 and 3.How the model finds the least tax
The model shares the deductions between three components: the first (non-savings income before 2027-28, property income after), savings and dividends. Write x for the first component left, w for x plus the savings left, and T for the Step 3 income of the three, which no share changes. Savings sit above non-savings income and dividends above savings (ss. 16 and 16A), so:
So the Step 5 tax runs continuous and linear on each piece of the feasible polygon that vertical (x constant), horizontal (w constant) and diagonal (w − x constant) lines cut. A linear function takes its least value on a polygon at a corner, so the least tax lies where two of the lines cross.
policyengine_uk/utils/income_tax_allocation.pylists every crossing, 173 a person in 2026-27 and 2027-28 from 11 vertical, 10 horizontal and 3 diagonal lines, projects each onto the feasible polygon and evaluates the Step 5 tax at all of them, for all the people searched at once.Before 2027-28 a reform can add an exclusion E from earned income, such as one that exempts pensions (
uc_earnings_deductions.yamlruns one). With property income P, the savings then sit on q = x up to x = P, on q = P up to x = P + E, and on q = x − E beyond. The search adds the lines of each piece: the two boundaries, the rate limits moved by P and E, and the diagonal lines that a fixed q = P gives. That raises the count to 1,034 a person, and only where someone has an exclusion. YAML case 17 pins the result: under that reform, pay £5,000, private pension £10,000 and savings £30,000 give £3,286, against £4,800 under the old order. With the pension and £5,000 of the allowance setting earned income at nil, £22,430 of savings start at nil: £5,000 at the starting rate, £1,000 at the savings nil rate and 16,430 × 20% = 3,286.The search relies on three things that hold in every modelled year: a starting rate limit at or below the basic rate limit, a starting rate band that shrinks by a pound for each pound of non-savings income (s. 12), and a first UK rate threshold at nil. A reform that sets that threshold above nil leaves savings below it untaxed and adds lines on which x + w stays constant, which the search doesn't list. On 6,520 synthetic people with that threshold at £2,000, a 101 × 101 grid found less tax than the search for 32 in 2026-27 (by up to £181.70) and 38 in 2027-28 (by up to £214.75). The fast path sends everyone to the search under such a reform, and the search still keeps the old order unless a share it lists charges less.
The search leaves the Marriage Allowance out. Parliament gives it as a tax reduction at Step 6 (ss. 26 and 55B(1)), not as an allowance at Step 3. The model still subtracts the allowance from earned income (#1947) and so moves a recipient's savings and dividends down by up to £1,260; for a recipient, the search's Step 5 tax can then differ from the model's. At 5d9ce13's model code we built 20,364 married couples a year by hand: 14,364 on a grid of pay, savings, dividends and property income in England and Scotland, and 6,000 drawn at random. 12,277 of them include a recipient. No recipient paid more income tax than under the old order, in 2026-27 or 2027-28, and 1,069 and 1,155 recipients paid less, by up to £645. That grid has no pension contributions and no exclusion beyond the Marriage Allowance, so the pension hold and the exclusion lines leave its people unchanged; nobody has rerun it at 6429896.
Until #1963 lands, a search that read the model's subtraction would charge 1,190 of those recipients less in 2026-27, by up to £252 (£1,260 at 20%), and none less in 2027-28 (5d9ce13). In YAML case 13, the model sets the Personal Allowance against all £5,000 of the recipient's pay, so it has no earned income left to subtract the Marriage Allowance from; with the Personal Allowance set against savings instead, it would subtract £1,260 from the pay. Once #1963 lands, the model gives the allowance as the s. 55B tax reduction, and the search's tax and the model's agree for recipients too.
Where several shares give the same tax, the model takes the one setting most against the first component, then most against savings: the nearest the least tax allows to the old order. It keeps the old order, to the float32 bit, unless another share saves more than £0.001.
Who skips the search
The search runs only on people three tests leave, and on nobody when the tests clear everyone. Each test gives the result the search would give.
Tests 2 and 3 apply only when every rate scale rises with income from nil; under a reform that makes a scale fall, or sets the first UK threshold above nil, the model sends everyone with something to share to the search.
For people the tests clear, the search would keep the old order too, since it moves the share only for a saving above £0.001. A property test checks the two paths give the same deductions, bit for bit, on synthetic people with rising and with falling rates, now with pension relief among them. Two new tests pin the checks that no earlier test separated: a supplied adjusted net income with trade loss relief, where the full search saves £200 and £60 over the old order, and a reform that lifts the Scottish starter band to £9,000, where it saves £30.
Cost
The search adds work only for people it can't skip. On 20,000 synthetic people of the "realistic" mix in
test_income_tax_allocation.py, most with pay alone and some with savings, dividends, property income or pension relief, the three tests send 0.925% of them (185 of 20,000) to the search in 2026-27 and 0.635% (127 of 20,000) in 2027-28, at 70633f0. Timings from earlier commits, on a hand-built mix of 20,000 people without a simulation: at 60de330 the deductions took 5.5 ms with the tests against 4.6 s with the search for everyone in 2026-27, and 3.4 ms against 2.5 s in 2027-28. At 5d9ce13, a coldincome_taxfor one household (pay £50,000 and savings £10,000, 2026-27) took 0.907 to 1.041 s of CPU time in three runs against 0.898 to 1.026 s onorigin/main, and the allocation variables took 2.6 to 4.6 ms against 0.35 to 0.45 ms. The pension hold adds one column to the search, and the exclusion lines run only for people with an exclusion; nobody has timed 6429896 or later.Aggregate impact
We estimate that income tax receipts fall, and household net income rises, by £18.7m in 2025-26, £19.5m in 2026-27, £9.5m in 2027-28, £10.7m in 2028-29, £20.4m in 2029-30, £20.6m in 2030-31 and £11.3m in 2031-32. Between 0.9% (2025-26) and 0.3% (2027-28, 2028-29 and 2031-32) of people gain over £1, and 0.0% lose over £1 in every year.
CI's Aggregate impact report (run 41535543658) compares main at
0080274d78with3b1d34afc6, CI's merge of 6602ccc into main, onenhanced_frs_2024_25.h5(policyengine-uk-data 1.58.1), a full microsimulation for each side and year. It passes, with 0 checks broken and 1 failing on both sides. It flags two moves, both in 2030-31 and on mean income in each side's own deciles: decile 5 rises £140 and decile 6 falls £114. By base decile, neither decile moves that year.Readers of the changed variables
Every output variable keeps its name.
git grepfinds these readers:received_allowances_savings_incomeandreceived_allowances_dividend_incomenow come from the search. Their readers (basic_rate_savings_income,higher_rate_savings_income,add_rate_savings_income,basic_rate_savings_income_pre_starter,savings_starter_rate_income,dividend_income_tax,taxed_dividend_income,received_allowances) don't change.received_allowances_earned_income(allowances against non-savings income beyond trade loss relief) andtrade_loss_relief_against_savings_and_dividendsnow follow from those two. They equal the old order's values wherever the share equals the old order.non_savings_income_after_allowancesnow comes from the allocation's own arithmetic: where the share keeps the old order, net income less savings, dividends and allowances, asorigin/maincomputes it; elsewhere, in float64, the non-savings income less the deductions the share sets against it.earned_taxable_incomeadds back the relief set against savings and dividends, becausenet_incomededucts all of it, and never exceedsnon_savings_income_after_allowanceswhile savings, dividends andreceived_allowances_earned_incomesit on its exclusions list. Without the add-back, the model would undercount non-savings income wherever the share set relief against savings or dividends while some non-savings income remained (YAML case 16). Without the float64 arithmetic, float32 rounding in net income plus the relief added back left a residue above nil (YAML case 18).taxable_property_income_after_allowances,property_income_tax,non_savings_taxable_income(throughearned_taxable_incomeandtaxable_property_income_after_allowances),earned_income_taxand the basic, higher and additional rate earned income variables read them as before.adjusted_net_incomeandnet_incomesum totals that no share changes, so neither moves. Nor do the Personal Allowance taper, the savings allowance band (savings_allowancereads net income less allowances),tax_bandor the Marriage Allowance conditions.utils/uc_income_tax.py(uc_income_tax_on_earnings,uc_income_tax_on_trade) takes earnings as the lowest slice of non-savings income, with trade loss relief off the other non-savings income first and allowances off earnings first. It reads the old order's deductions (default_order_deductions), not the share, so the slice equalsorigin/main's and adding savings or dividends never raises it. For self-employment income of £37,406 and savings of £14,407 in 2026-27, the share moves the whole Personal Allowance onto the savings and income tax falls from £8,057.20 to £8,016, whileuc_income_tax_on_tradestays at £4,967.20 (74fdacf gave £7,481.20). For pay £50,000 and savings £10,000 it stays at £7,486. See the Universal Credit judgment call for why the slice keeps the old order.housing_benefit/applicable_income/_net_earnings.pytakes income tax on earnings asincome_tax_before_winter_fuel_payment_chargetimes the earnings share of income, a pro-rata share rather than a slice.housing_benefit_applicable_income.pyandcouncil_tax_reduction_applicable_income.pydeduct that total. So all three deduct less income tax where the share lowers it.property_finance_costs_relievedcomputes adjusted total income (its reference: ITTOIA 2005 s. 274AA(6)) as net income less all the savings and dividend income,charitable_investment_gifts,other_deductionsand the personal and blind person's allowances. It doesn't read the share, so it doesn't change (see judgment calls).Invariants
For every person, on the Step 5 tax (
income_tax_pre_charges), the only tax the share changes:received_allowances_earned_incomeplus the relief not set against savings and dividends), savings and dividends add up to min(relief + allowances, income). None falls below zero or exceeds its component, and the relief set against savings and dividends never exceeds what they take.step_5_tax) equalsincome_tax_pre_chargesfor every share forced through the model.uc_income_tax_on_earningsoruc_income_tax_on_tradefor a person whose allowances it leaves unchanged.On invariant 8: pay £2,000, personal pension contributions £3,600 and dividends £30,000 in 2026-27 give
received_allowances_earned_incomeof £2,000 andreceived_allowances_dividend_incomeof £14,170, which includes the £1,600 of pension relief beyond the pay, and income tax of £1,647.975, at 70633f0 and onorigin/main6c6e229 alike.Test plan
Ran at 056a674 (
uv sync --frozen --extra dev; no survey dataset;HUGGING_FACE_TOKENandPOLICYENGINE_UK_DEFAULT_DATASETunset):test_trading_loss_properties.pyandtest_statutory_paternity_pay_income_tax_properties.py: all 20 pass with-n 4, and all 20 without xdist.test_income_tax_allocation.pyandtest_uc_income_tax_properties.py: all 24 pass.least_tax_allocation.yaml: all 18 pass.trade_loss_relief_against_general_income.yaml: all 22 pass.policyengine_uk/tests/code_healthwith-n 4: all 3,089 tests pass..github/check_fix_pr.pyon this title and body, with the branch's changes from its merge base withorigin/main6c6e229 (2da212a): "Fix evidence: PASS", with 15 changed executable tests.uvx ruff@0.17.2 format --check .("2167 files already formatted"),uvx ruff@0.17.2 check .anduvx ruff@0.17.2 check --select F811,F821 policyengine_ukpass, andgit diff --check 70633f089..HEADreports nothing.income_tax_allocation.pychanges only in a docstring and a comment: its AST, with docstrings blanked, matches 70633f0's.test_relief_never_taxes_more_than_income_cut_in_the_old_orderwithout xdist and then restored:net_incomesubtractingtrade_loss_relief_against_general_incometwice, with the relief itself unchanged: the one-sided bound passes for everyone, and the check for people with no savings or dividend income fails on pinned person 3 (pay £300,000, loss £200,000), with £53,703 against £87,453. With that check removed, the test passes under the mutant.At 3b54d53:
income_tax_allocation.pydiffers from 056a674 only in one comment, and its AST, with docstrings blanked, matches 056a674's, so the runs above hold there. At that head CI passes all seven Python shards, YAML, Lint, Fix evidence and Aggregate impact,uvx ruff@0.17.2 format --check .,check .andcheck --select F811,F821 policyengine_ukpass, and.github/check_fix_pr.pygives "Fix evidence: PASS" with 15 changed executable tests.At 6602ccc:
income_tax_allocation.pydiffers from 3b54d53 only in its module docstring and that comment, and its AST, with docstrings blanked, matches 3b54d53's, so the runs above hold for this head. At this head CI passes all seven Python shards, YAML, Lint, Fix evidence and Aggregate impact.Ran at 70633f0 (
uv sync --frozen --extra dev; no survey dataset):uvx ruff@0.17.2 format --check .,uvx ruff@0.17.2 check .anduvx ruff@0.17.2 check --select F811,F821 policyengine_ukpass, and so douvx ruff@0.17.0 formatandcheckon the changed Python files andgit diff --check. No line the branch adds to a Python file runs past 88 characters.policyengine_uk/tests/code_health: all 3,089 tests pass.least_tax_allocation.yamlamong them.test_income_tax_allocation.py: all 22 pass.test_uc_income_tax_properties.py: both pass..github/check_fix_pr.pyon this title and body, with the branch's changes againstorigin/main: "Fix evidence: PASS", with 13 changed executable tests.origin/main6c6e229:origin/main.synthetic_people, seeds 100 to 119, 326 each) withuk[0].thresholdat £2,000,vertex_search_and_gridfinds less tax on the grid than from the search for 32 people in 2026-27 (by up to £181.70) and 38 in 2027-28 (by up to £214.75), and for no one below the grid minimum less two steps. Seeds 0 to 3 give 6 of 1,304 by up to £91.86.Ran at def18d3, whose model code 70633f0 keeps (70633f0 changes only docstrings, comments and one variable's documentation):
policyengine_uk/tests/code_health: all 3,089 tests pass.test_income_tax_allocation.py(22) andtest_uc_income_tax_properties.py(2): all 24 pass.uc_earnings_deductions.yaml(15) andintegration/income.yaml(8): all 23 pass.Ran on the working tree as it stood at 6429896 or one step before it (
uv sync --extra dev, Python 3.13.18, 4 vCPU; no survey dataset). The earlier tree lacked only YAML case 18 and the cap that holds pension relief withinallowances, which can't bind in the model becauseallowancesincludes the relief; def18d3 keeps 6429896's code and renames one line ofshard_durations.toml. The runs at def18d3 above repeat the ones that touch case 18 or the allocation tests:policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax) passes 489 cases,integration/income.yaml8, the Universal Credit income directory (withuc_earnings_deductions.yaml) 174, anduc_trading_loss.yaml,uc_company_analogous_business.yamland the benefit cap directory 150.test_income_tax_allocation.py: 22 tests pass, including 7 new: the dense-grid search with exclusions in 2018-19, 2026-27 and 2027-28, two supplied adjusted net income cases, the Scottish starter band reform and the pension hold.test_uc_income_tax_properties.py(new): both tests pass, including the Hypothesis property with the self-employment witness as an explicit example in 2025-26 and 2026-27.test_budget_2026_savings_property_rates_properties.py: all 8 tests pass with-n 4and without, including the new bound (invariant 10).test_scottish_top_rate.py10,test_savings_starting_rate_indexation.py26 andtest_property_income_rate_reforms.py55 pass.test_gift_aid.py,test_property_income_band_base_properties.py,test_income_tax_step_6_step_7_properties.py,test_adjusted_net_income_pension_relief.py,test_income_concepts_properties.py,test_uc_company_owner_properties.pyandtest_pension_annual_allowance_properties.py: all 63 tests pass.origin/main2da212a on 3,000 random single adults a year in London and Scotland, 2025-26 to 2028-29 (12,000 rows), with pay, self-employment, property income, savings, dividends, employee and personal pension contributions, trading losses and Gift Aid:income_tax_pre_chargesfalls by more than 1p plus a millionth of the tax in 725 rows, by up to £645, and rises beyond that in none (float32 rounding explains the largest rise, £0.0078).uc_income_tax_on_earningsanduc_income_tax_on_trademove by more than 1p in no row. At 74fdacf the two slices rose 319 times over the 12,000 rows, counting each slice separately, by up to £4,930.98, and fell in none.adjusted_net_incomemoves in no row.origin/main's.origin/mainleaves with no non-savings income, 74fdacf left 607 above nil (up to £4.00) and 6429896 leaves 10 (up to £0.0078, rounding in the moved share);earned_taxable_incomewent from 63 above nil to none.tax_bandcompares a Scottish taxpayer's total income with the Scottish thresholds; the Scottish rates apply to non-savings, non-dividend income only #2350, checked for a dependency cycle (see Merge notes).Revert and mutant checks, each on a copy of the package as it stood at 6429896 or one step before it (the earlier copies lacked only the cap that holds pension relief within
allowances):received_allowances_earned_income2,300, not 22,570), and so doestest_pension_relief_stays_on_non_savings_incomeearned_taxable_incomeadd-back removedearned_taxable_income30,000, not 37,700)received_allowances_earned_income7,300, not 5,000), andtest_vertex_search_equals_dense_grid_with_exclusionsfails in 2018 and 2026test_uc_income_tax_properties.pyfail (the slice rises by £2,514)non_savings_income_after_allowancesfrom the add-back sum, with no cap onearned_taxable_incomeF6_old_order_check_droppedtest_fast_path_follows_a_supplied_net_incomecases failF11_all_used_threshold_widetest_fast_path_follows_a_supplied_net_income[all-savings-used]failsF8_uk_rates_for_scotstest_fast_path_reads_the_scottish_starter_ratefailsF10_all_used_without_old_order_checkF1_shared_within_first_droppedin_firstalready impliesshared <= first, so the check matters only for float32 residues and negative componentsF1andF6togethertest_fast_path_equals_full_searchfailThe 74fdacf model code gives £11,232 for cases 14 and 15 and fails both. Under the pension-exempting reform, the search at 74fdacf missed the least tax that a 101 × 101 grid finds for 40 of 5,216 synthetic people with exclusions, in eight years from 2016-17 to 2028-29, by up to £415.14; at 6429896 it misses it for none of 15,648 in the same years.
Ran at 74fdacf, before these changes:
test_budget_2026_savings_property_rates_properties.py: all 7 tests pass with-n 4and without, and the two failing tests pass with the seeds108342745077855236797573561117658719055and245902028163439803096709191647468822378.-n 4and without the rUK property test: 3 pass and 2 fail,test_savings_tax_rises_by_two_points_in_every_countryandtest_scottish_property_tax_ignores_the_property_rates, as at 5d9ce13. Both failing people pay Scottish income tax and have property income of about £54,000 and savings; see below.Earlier runs, at the commits named (each later commit to 74fdacf changed only tests, comments or the Marriage Allowance exclusion, as the commit messages say):
test_property_income_rate_reforms.py(55) andtest_savings_starting_rate_indexation.py(26) passed; 18 other YAML files passed, 271 cases, every other file in the income tax directory with a case of two or more people.A_rate_limit_lines_dropped,B_s25_3A_ignored,C_tie_break_reversedandG1_marriage_allowance_readeach failed one of the 13 cases then inleast_tax_allocation.yaml;F2,F3,F4,F5,F8b,F9andF12failedtest_fast_path_equals_full_search, andF7failedtest_fast_path_equals_full_search_when_rates_fall[2026].test_gift_aid.py(12),test_income_tax_step_6_step_7_properties.py(10),test_property_income_band_base_properties.py(5),test_adjusted_net_income_pension_relief.py(17) andtest_income_concepts_properties.py(2) passed.origin/main2da212a: cases 2, 3, 5, 7 and 8 of the first 11 cases ofleast_tax_allocation.yamlfailed and the other 6 passed.CI at 3b54d53 (
pull_requestrun 41520177402): Python shards 1 to 7 pass 11,579 tests (61 skipped, 2 xfailed), andTest / YAMLpasses 5,428. Not run: any test that needs a survey dataset.policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/received_allowances/least_tax_allocation.yaml: both rows of the issue's table; savings £4,000, dividends £200,000 and a £1,000 trading loss; property £20,000, dividends £200,000 and a £1,000 trading loss in 2027-28; two Scottish taxpayers; pay and savings, and pay and dividends, across the basic rate limit; pay, nil-rate savings and dividends across it; one income type (savings only; property only in 2027-28); a Marriage Allowance recipient whose share leaves the allowance out; net pay and relief at source pension relief kept on non-savings income; trading loss relief set against savings with the pay left in earned income; a reform that exempts pensions; trading loss relief past the non-savings income.changelog.d/2106.fixed.md.The Budget 2026 property tests and the share
Four tests in
test_budget_2026_savings_property_rates_properties.pycompare everyone under the Budget 2025 and Budget 2026 rates on the assumption that each person keeps the same allowance order. The least-tax search can move the allowance when the rates change, and at 5d9ce13 two of those tests failed on people it moved. Minimised to one person: a Scottish taxpayer with property income of £54,000 and savings of £13,000 in 2027-28. Under the Budget 2025 rates the allowance saves 42% against either income (Scottish higher rate on the property income, the UK higher savings rate on the savings), so the model keeps it on the property income. Under the Budget 2026 rates savings pay 44% while the property income still pays the Scottish 42%, so the model sets £12,500 of the allowance against savings, leaving £500 within the savings allowance, and £70 against the property income. Income tax stays at £15,904.89, against £16,154.89 had the allowance stayed on property income: the move saves 2% of £12,500. Property tax rises by 42% of £12,500, so the identity "Scottish property tax doesn't move" fails for this person, and so does "savings tax rises by 2% of banded savings". The search gives the least tax under s. 25(2); the tests' assumption was wrong.same_choiceslimits the identities to people whose election and allowance order stay the same, a pinned test (property £58,000, savings £24,000) checks the move, and invariant 10 checks every person, moved or not.Existing tests whose expected values move, and why
Each of these expected values moves because the model charges less tax under the least-tax share. Each case's comment works the arithmetic.
reliefs/trade_loss_relief_against_general_income.yaml, "Relief takes non-savings income first, then savings, then dividends", becomes "Relief and allowances leave savings that the starting rate and savings allowance cover". Savings take 4,000 (from 10,000) and dividends 23,570 (from 17,570), since savings up to £6,000 bear 0% (ss. 12 and 12A). Income tax falls from 1,282.48 to 637.475.integration/income.yaml, "Savings slice of basic rate band" (2020-21): £2,500 of the allowance against pay and £10,000 against savings keep the pay at the £37,500 limit.basic_rate_savings_income_pre_startergoes from 10,000 to 0,basic_rate_savings_incomefrom 9,500 to 0,higher_rate_savings_incomefrom 5,000 to 4,500 andtaxed_savings_incomefrom 14,500 to 4,500. The tax falls by £100, to £9,300. Setting £500 more against the savings leaves it at £9,300; setting £500 more against the pay charges £9,400.received_allowances_savings_income: 10_000joins the outputs.integration/income.yaml, "Personal allowance caps savings starter rate before dividends.", becomes "Personal allowance goes to dividends rather than savings at the starting rate." Savings take 0 (from 2,570) and dividends 2,570.basic_rate_savings_income_pre_starterandsavings_starter_rate_incomego from 2,430 to 5,000 andtaxed_dividend_incomefrom 4,500 to 1,930. Income tax falls from 483.75 to 207.475.gift_aid.yaml, "Gift Aid extends UK limits for dividends above the basic-rate limit":earned_taxable_incomegoes from [37,430, 37,430] to [37,700, 38,700],dividend_income_taxfrom [3,396.25, 3,203.75] to [3,299.725, 2,942.225] andincome_taxfrom [10,882.25, 10,689.75] to [10,839.725, 10,682.225].gift_aid.yaml, "Gift Aid extends UK limits across savings and dividend slices":savings_income_taxgoes from [460, 300] to [120, 0],dividend_income_taxfrom [1,608.75, 1,433.75] to [1,608.75, 1,001] andincome_taxfrom [9,368.75, 9,033.75] to [9,268.75, 8,941].gift_aid.yaml, "Gift Aid raises the basic rate limit for savings above property income":savings_income_taxgoes from [5,646, 5,846] to [3,292, 3,692]; the income tax of each falls by £100.test_gift_aid.py::test_zero_gift_preserves_baseline_outputs_bit_for_bit: the fourth rest-of-UK row (pay £30,000, savings £12,000, dividends £18,000, property £9,000) sets £1,300 against pay and £11,270 against savings.earned_taxable_incomeandbasic_rate_earned_incomego from 17,430 to 28,700,received_allowances_earned_incomefrom 12,570 to 1,300,received_allowances_savings_incomefrom 0 to 11,270,basic_rate_savings_incomefrom 10,770 to 0,higher_rate_savings_incomefrom 730 to 230,earned_income_taxfrom 3,486 to 5,740,savings_income_taxfrom 2,446 to 92 andincome_taxfrom 13,988.25 to 13,888.25. Its Scottish twin doesn't change, because pay above £31,092 would bear 42%.received_allowances_savings_income.yamlandreceived_allowances_dividend_income.yamlunit cases fedreceived_allowances_earned_incomeandreceived_allowances_savings_incomein as inputs, which the new formulas don't read. They now take the incomes as inputs. Every expected value stays the same except two dividend cases, which move from 0 to 500: the old order would leave £500 of dividends taxed at 8.75%, while £500 more allowance against dividends keeps the dividends left within the £500 nil rate (s. 13A). Onereceived_allowances_earned_income.yamlcase replaces an inconsistentnet_incomeinput withtaxable_employment_income; its expected value doesn't change.property_income_band_base.yaml, "Case 2, savings sit above property income" (2026-27, property income £40,000 and savings £20,000):savings_income_taxgoes from 5,846 to 3,692, andincome_tax(7,540 + 3,692) joins the outputs. The model sets £2,300 of the Personal Allowance against the property income, which leaves it at the £37,700 basic rate limit, and the other £10,270 against savings, so the £500 savings allowance (s. 12B(3)) covers savings that would bear 40%. Income tax falls by £100.test_property_income_band_base_properties.py:cloned_uk_tax_benefit_systemfixture, as 76 other test files do.test_budget_2026_savings_property_rates_properties.py:same_choicesreplacessame_election(above), and two tests join: the pinned Scottish move (property £58,000 and savings £24,000 in 2027-28: savings tax falls from £9,870 to £4,809.20, property tax rises by £5,279.40 and income tax rises by £218.60, against £470 had the allowance stayed on property income) and the bound for everyone (invariant 10).test_trading_loss_properties.py:test_relief_taxes_like_income_cut_in_section_25_orderbecomestest_relief_never_taxes_more_than_income_cut_in_the_old_order, and its key inshard_durations.tomlfollows. It asserted that a person's income tax before the winter fuel payment charge with a loss equals, within 50p and float32 rounding, the tax of a twin with no loss whose income the relief cuts in the order non-savings, savings, dividends. That cut, with the allowances set against it in the same order, gives the old order's share, which the model always weighs and keeps unless another share saves more than £0.001, and the Personal Allowance taper reads the same adjusted net income for both. So the tax with the loss can now only match the twin's or fall below it. Pinned person 1 (pay £5,000, savings £10,000, dividends £30,000, loss £20,000) pays £637.47 against the twin's £1,282.47, the figures of thetrade_loss_relief_against_general_income.yamlbullet above. The test now bounds the tax with the loss by the twin's for everyone, and keeps the equality for people with no savings or dividend income, for whom every share equals the cut; pinned person 3 (pay £300,000, loss £200,000) runs that check.test_statutory_paternity_pay_income_tax_properties.py::test_a_pound_of_statutory_paternity_pay_can_halve_the_savings_allowance: the rise in income tax before the winter fuel payment charge from a pound of statutory paternity pay on wages of £48,270 and savings of £2,000 (2026-27) goes from £100.40 to £100.20. With the pound, the model sets £1,500 of the Personal Allowance against savings and £11,070 against pay, so pay of £37,201 bears 20% and the £500 of savings left falls within the £500 savings allowance: income tax goes from £7,340 to £7,440.20, against £7,440.40 under the old order. The last pound of Step 3 income would bear 40% but for the savings nil rate, so it still counts as higher-rate income (ITA 2007 s. 12B(8)(b)(ii)), and the allowance stays at £500 (s. 12B(3)).Merge notes
git merge-tree --write-tree --name-onlywith explicit SHAs, from 6602ccc:tax_bandcompares a Scottish taxpayer's total income with the Scottish thresholds; the Scottish rates apply to non-savings, non-dividend income only #2350 (fix-2350-scottish-tax-bandat e5b16d1, draft Fix Scottish tax bands and test Marriage Allowance rates directly #2828): exit 1, one conflicted file,non_savings_income_after_allowances.py, in its documentation only;test_gift_aid.pymerges cleanly. Take this branch's file and end its documentation with "property_income_tax, taxable_property_income_after_allowances, earned_taxable_income, the Marriage Allowance rate test and, for Scottish taxpayers, tax_band read it, and non_savings_taxable_income reads it through taxable_property_income_after_allowances and earned_taxable_income."tax_bandcompares a Scottish taxpayer's total income with the Scottish thresholds; the Scottish rates apply to non-savings, non-dividend income only #2350 hasliable_only_at_marriage_allowance_ratesreadnon_savings_income_after_allowances,received_allowances_savings_incomeandreceived_allowances_dividend_income, and all three follow the share here. The search leaves the Marriage Allowance out, so no cycle runs through the allowance: on the resolved merge of def18d3 and e5b16d1, the income tax YAML directory andfinance/tax/income_tax/scottish_rates.yamlpass (551),test_tax_band_scottish_properties.py,test_gift_aid.py,test_marriage_allowance_cps_switch.py,test_removed_marriage_allowance_eligible_bands_parameter.pyandtest_income_tax_allocation.pypass (87), andincome_tax,marriage_allowanceandtax_bandcompute with noCycleErrorfor 24 married couples (6 profiles, including a recipient with pay of £50,270 and £1 of savings and a couple with net pay pension relief, a trading loss and savings, each in London and Scotland in 2026-27 and 2027-28). 70633f0 and 056a674 change no formula against def18d3, and nobody has rerun that merge at either. The hand-built couples above ran on a merge of 5d9ce13 and f0f576a: no couple paid more than under the old order in either year, 1,119 recipients paid less in 2026-27 and 1,200 in 2027-28, by up to £645, and in 2026-27 the model gave the allowance to 12 more couples than under the old order, and to none fewer.fix-2783-scottish-ratesat 4bae9d1, which carriestax_bandcompares a Scottish taxpayer's total income with the Scottish thresholds; the Scottish rates apply to non-savings, non-dividend income only #2350 at e5b16d1; no pull request yet): exit 1, the same single conflicted file, in the same documentation.origin/mainat 0080274: exit 0, no conflict.marriage_allowancefromearned_taxable_income_exclusionsand gives the allowance as a Step 6 reduction. Nobody has run a merge with it. A static read finds no cycle, because Model Marriage Allowance as the statutory s.55B transfer and Step 6 reduction #1963 computes the election in branch simulations, and the search already leaves the allowance out, so its share doesn't change when Model Marriage Allowance as the statutory s.55B transfer and Step 6 reduction #1963 lands. Its ownliable_only_at_marriage_allowance_ratesreadsnon_savings_taxable_incomeand both allocation variables. The search minimises the Step 5 tax, so check after both land that it still gives the least liability where the share changes whether the reduction applies.trade_loss_relief_against_general_incomeworks out the relief; the search reads it as a total. Git merged 74fdacf and Carry unused Income Tax trading losses forward once #2661 cleanly; not rerun at 6429896 or later.Judgment calls
origin/main, against £1,286 under the law (£2,286 less the £1,000 the payer keeps). Where the relief exceeds non-savings income, the model deducts the rest from savings and dividends, asorigin/maindoes. A net pay excess relieved on a claim (s. 193(4) and (6)) or relief on a claim under s. 194(1) could go against savings or dividends (s. 24(1)(a)); the model doesn't separate those, so it holds them too.property_finance_costs_relievedstill deducts all the savings and dividend income, whatever share the search takes. At Step 2 of ITTOIA 2005 s. 274AA(6), Parliament excludes only "so much of [net income] as is" savings or dividend income, so relief that the share sets against savings or dividends should raise adjusted total income under the law. The model's gap predates this PR, and this PR leaves it.capped_mcad,property_finance_cost_relief,pension_tax_allowanceandincome_tax_reductionsreadincome_tax_pre_chargesonly to cap the Step 6 reductions at the tax left.income_tax_before_winter_fuel_payment_chargecharges max(0, Step 5 tax − reductions) + the Step 7 charges, which never rises when the Step 5 tax falls, so the least Step 5 tax gives the least liability.savings_allowancecomputes from net income less allowances against the basic rate limit, which no share changes.🤖 Generated with Claude Code