Repository navigation
Carry unused Income Tax trading losses forward once - #2661
juaristi22 wants to merge 6 commits into
Conversation
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in Notes
Moves flagged for reviewNone. Spending by benefit and service
Caseloads
Standard reform costings (change in gov_balance)
Standard reforms: change in each reform's target
Household net income by decileNo household's net income changes in any year. No change in any year: receipts by tax head; government balance, each component counted once; model totals; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles. Checks failing on both base and head (1)
Counted-once checkEach of 91 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
|
|
Round 1 at Verdict: the law is right and "once" holds in every case I tried. Two shoulds before this leaves draft: one model choice in a year that has a new loss, and CI that has not finished at this head. No musts. Law checked on legislation.gov.uk
Findings
"Once" checks (household, local)
What I ran: a fresh clone at
Not read or not run: the full Python test file line by line, the rest of 🤖 Generated with Claude Code |
|
Round 2 at Status of round 1 (round 1). The new commit changes only the docs, one YAML case and the changelog fragment; the model code is the same as at
CI at
None of these is a test failure or a timeout (limit 75 min). All tests in the logs up to the shutdowns passed, including the PR's Class 4 property tests. Is it the branch? I cannot prove it, but the pattern points that way:
Suggestion: re-run the failed jobs once. If they lose their runners again, compare the memory lines in the logs with a main run, shard by shard, before marking ready. A green run is still needed before this leaves draft. New finding
What I ran, at
Not read or not run: no microsimulation and no full suite run. I did not re-read the model code, which is unchanged since round 1, and I did not find the cause of the higher CI memory. Verdict: should 1 is resolved by a clear, tested statement of the assumption. Should 2 still blocks: CI needs a green run. Still a draft, so this is a comment, not an approval. 🤖 Generated with Claude Code |
|
Round 3 at Status of round 2 (round 2). The new commit changes
CI and memory At this head, Python 2/5, 3/5 and 5/5 lost their runners ("The runner has received a shutdown signal"), 4/5 was cancelled (its log is no longer available), and 1/5 passed. Again, no test failed.
A correction to round 2: I said no other run in the window had lost a runner. That no longer holds. The merge-queue run for #2105 (run 40395541241, 26 Oct 21:27) lost Python 2/5 at 15,501 MB. So the suite on main is now at the 16 GB limit too. This branch still loses more shards (three or four of five on each head, against one of five there), and its peaks are 0.6 to 0.9 GB above passing runs, but I can no longer say the problem is only this branch. I also looked for a cause in the code and did not find one:
My view: this looks like the general worker memory growth the workflow already notes (#2330, and the comment above the New findings None. What I ran, at
Not run: no microsimulation and no full suite. I started a base-against-head memory run on Verdict: the code changes since round 2 are right and add no new findings. Two things remain: the one-sentence docs caveat (1), and a green CI run (2), which now looks like a CI capacity problem more than a PR problem. Still a draft, so this is a comment, not an approval. 🤖 Generated with Claude Code |
|
Round 4 at Status of round 3 (round 3). The only change since
CI at Code run 40424136853 failed: Python shards 2, 3, 4 and 5 failed, shard 1, YAML, lint and docs passed. Aggregate impact passed. All four shards ran to the end (67 to 71 minutes) and each failed on
The printed stack is only the last frames, but in shards 2 and 4 it runs The same tests pass without this PR: the #2690 run 40422010249, on recent main plus the shard change, passed all shards. So this looks caused by the PR's carry-forward chain once the dataset has supplied losses in earlier years. Two things would help: reproduce one of the At round 3 ( What I ran at
Verdict: not ready. Item 2 is a must: the PR's tests pass, but the full suite fails on this head merged with main, with recursion through the new carry-forward chain. It is still a draft, so no approval. 🤖 Generated with Claude Code |
Addresses #2087
axiom: needed
Track unused Income Tax trading losses as opening stock, consuming relief once. Only a loss explicitly supplied for its tax year enters the current-year claim. Explicit opening balances, including zero, reconcile external history. Class 4 keeps its separate balance.
The follow-up at
d3d2bbc55b42cee47fce9b9ce7993439ad257ca0clarifies the caller-assessed same-trade contract and adds one YAML case. With no other income or pensions, ordinary expense treatment and no capital allowances, an old £10,000 loss from trade A, followed by £8,000 profit from A and a new £4,000 loss from B, leaves £6,000 opening stock for 2027. If both losses belong to A while B earns the profit, identical aggregate inputs cannot identify that association. The docs exclude automatic reconstruction of that case and explain annual reconciliation, zero resets and later reseeding.The docs also warn conditionally that copying a loss into each later annual input supplies a new loss each year. This makes no claim about current certified-data columns or national effects. The follow-up changes only documentation, one YAML case and the existing Towncrier fragment; production code, dependencies and workflows are unchanged from
3bc00db9.Correction
16a12b93d0d5c962556cf7d0789ae66a99ffbb34marks the opening balance as a stock, preserving its annual amount in monthly reads, and removes the temporary YAML label. Annual relief formulas and existing oracles are unchanged. The normal correction push is published and independently reviewed.Reproducer: policyengine_uk/tests/test_income_tax_loss_carry_forward.py::test_monthly_reads_preserve_the_annual_opening_stock;
policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/reliefs/trade_loss_carry_forward.yaml::A loss already relieved against pay is not a new loss next yearValidation:
Earlier local stock, provenance/reset and same-trade checks passed on recorded heads and overlays. They do not validate the hosted merge checkout; the lifetime check was diagnostic only.
Current hosted Code fails. Two projection-test logs show
RecursionErrorduring simulation construction with prior-year loss-relief frames. Aggregate succeeded. The full cause and remedy remain unproved; no local reproduction is established.The docs follow-up ran no new model tests. The PR remains a draft.
This retains the current-year section 64 claim convention and caller-assessed same-trade eligibility. It does not implement previous-year elections, automatic unrelated-trade tracking, terminal/foreign loss rules or full pension claim optimisation, and does not claim full closure of #2087. Later-main dataset/ISA/locked-runtime/H5 qualification remains open in its original scope; this correction integrates no later main and makes no compatibility or population claim.
See
docs/engineering/income-tax-trading-losses.mdfor the contract and official references.