Repository navigation
Carry unused Income Tax trading losses forward once - #2661
juaristi22 wants to merge 10 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)
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 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 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
|
|
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 |
|
Round 5 at I checked the commits since round 4 (
New finding
Checked and fine
CI at What I ran, at
Not read line by line: the cache-cleanup changes in Verdict: one must left, the #2765 test in item 7. It is a test update, not a model change. Everything from earlier rounds is fixed. 🤖 Generated with Claude Code |
Addresses #2087
axiom: needed
Track unused Income Tax trading losses as an opening stock and consume each loss once. Only losses explicitly supplied for their tax year enter the current-year claim. Carry forward the remainder after actual general-income and trading-profit deductions, account for ordinary net-pay pension relief, and preserve explicit opening-balance overrides, including zero. Monthly reads retain the annual stock. Class 4 keeps its separate balance.
Carry-forward eligibility uses the shared trading allowance election. Full relief makes a qualifying trade's loss nil even when the allowance deduction arises only on miscellaneous income (ITTOIA 2005 s783AF). The £400 receipts / £200 loss / £600 miscellaneous-income example carries £0 into 2026. Under ordinary treatment, an £800 loss with £600 general-income relief retains £200.
General-income relief returns zero before resolving the allowance election when no new loss was supplied or every supplied raw loss clamps to zero. If the election suppresses every current loss, it returns zero before resolving net income, net-pay pensions and the cap. Mixed and positive effective losses retain the existing vector calculation and limits; earlier positive losses continue through the carry-forward recurrence. Explicit relief inputs retain their override behavior.
UK cleanup delegates to Core's public array-cleanup API, preserving supplied and model-owned input arrays by period and branch while clearing derived periods. The existing
reset_calculationsmethod uses the same helper. ItsNonereturn,changed_from_baselineflag and input-name selectors are preserved. This cleanup can recompute more values after input changes and resets.For example, with a supplied £7,000 opening balance, a warmed next-year balance must refresh when a public input change replaces a zero current loss with £50,000. After £40,000 actual general-income relief, the next opening is £17,000. The regression also checks branches, reforms and explicit overrides without inserting a manual reset.
Integrates official
mainthrough52b30809. The shared supplied-input helper retains the official implementation plus theloss_reliefregistration required by opening-stock overrides.The model retains its current-year section 64 claim convention and requires callers to assess same-trade eligibility. Aggregate inputs cannot reconstruct unrelated trades. Previous-year elections, historical, terminal and foreign loss rules, and full pension claim optimisation remain outside scope. Explicit zero resets and later reseeding reconcile external history. Copying a loss input into another year supplies a new loss. Public-fixture validation makes no population or private H5/dataset compatibility claim. This PR does not claim full closure of #2087. See
docs/engineering/income-tax-trading-losses.mdfor the contract and limitations.Validation: 221 distinct public-fixture cases pass on Python 3.13.15, locked Core 3.34.1 and PolicyEngine UK 2.172.0: 150 Python and 71 native-Core YAML cases. These include loss/carry/net-pay controls, all three cleanup routes, the warmed £17,000 stock mutation, state-pension and positive-loss horizon constructors, cache callers and stored-household-total controls. The positive horizon fixture verifies £2,000 supplied, effective and general-income relief for each of five people in 2024, with opening stock zero by 2032. Formatting leaves all files unchanged; Ruff lint, frozen lock consistency and staged/unstaged whitespace checks pass. Required hosted checks for the updated head remain separate from local focused validation.
Companion Axiom parity remains needed. rulespec-uk #429 tracks the related loss-pool, carry-forward, reset and net-pay companions, with #348 as related context. Their queue labels do not establish encoded parity.
Reproducer:
policyengine_uk/tests/test_trade_loss_relief_short_circuits.py::test_zero_and_positive_relief_refresh_after_public_changes;policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/reliefs/trade_loss_carry_forward.yaml::Shared trading allowance carry-forward under full relief on miscellaneous income;policyengine_uk/tests/test_projection_horizon.py::test_positive_effective_losses_preserve_the_projection_horizon