Skip to content

Add a trading allowance claim status, so allowance reforms reach claimants without turnover data - #2765

Draft
juaristi22 wants to merge 4 commits into
mainfrom
claude/trading-allowance-claimant-2764-1294f6
Draft

juaristi22 wants to merge 4 commits into
mainfrom
claude/trading-allowance-claimant-2764-1294f6

Conversation

@juaristi22

@juaristi22 juaristi22 commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2764

Survey data hold profit, not turnover, so a raise in the trading allowance missed its main base: traders who already claim £1,000 against larger receipts. The Budget 2026 raise to £3,000 (in current law since #2719) is costed on that base; HMRC's policy paper puts it at about 400,000 taxpayers with a lower bill, by about £380 on average. This PR lets a dataset say who claims, so the engine reaches that base at any allowance level.

What changes

  • Input trading_allowance_claim_status (person, enum, default UNKNOWN): whether the person claimed the allowance in the year their input profit was measured. It describes a positive profit; a claimant with no profit is read as unknown.
    • CLAIMANT: the profit is receipts less the allowance, as under partial relief (ITTOIA 2005 s. 783AI).
    • NON_CLAIMANT: the profit is receipts less actual expenses (capital allowances come off separately), and those deductions exceed the allowance.
  • Input allowance: the allowance legislated for the year the data were measured.
    • For microdata that is the dataset's first year, as the State Pension variables read it.
    • In a situation it is the latest year, up to the one simulated, for which the status was supplied, so inputs carried from an earlier year keep that year's allowance.
    • It is read from parameters.baseline, which reforms leave alone.
    • Data measured before April 2027 keep £1,000 when projected past the raise to £3,000; data measured from 2027-28 get £3,000.
  • Parameter gov.simulation.trading_allowance_claimant_expense_share (default 1, bounded to [0, 1]): claimants' expenses and capital allowances as a share of the input allowance. It only matters under a cut below the input allowance.
  • Formula in utils/trading_allowance.py, which trading_allowance_deduction, miscellaneous_income_trading_allowance_deduction and uses_trading_allowance share:
    • Claimant. Receipts are profit + input allowance, whatever receipts are supplied.
      • The deduction at allowance A is min(profit, max(A, share × input) − input), less their capital allowances. Partial relief replaces those, so the deduction gives them back, and it can be negative for a claimant.
      • So a raise relieves up to A − input (£2,000 at £3,000 on 2025 data), with full relief when profit + input is within A.
      • With the default share, a cut leaves their profit as reported.
      • Their claim was the election, so they take the allowance (uses_trading_allowance), giving up a current loss, while it is above nil and at least their expenses. Below their expenses they deduct those instead: ordinary treatment, which leaves a supplied loss claimable.
      • Their receipts take up the allowance before income not otherwise charged.
    • Non-claimant. No election for partial relief, so ordinary treatment, except full relief, which needs no election (s. 783AE). It applies where supplied receipts plus income not otherwise charged, or that income alone with no profit, are within the allowance. A larger current loss can still be claimed instead (s. 783AL).
    • Unknown. Unchanged: the receipts branch if self_employment_gross_receipts is supplied, otherwise the no-receipts rule. Existing datasets and situations give the same results.
    • Class 4 (ni_class_4_profits_before_losses) and relevant UK earnings read the same deduction, and their code is unchanged.

Design choices for review

An enum, not a bool with a known flag. One column holds three exclusive states. With a bool and a known flag, the two columns must agree: a dataset that sets the flag but not its partner is silently ignored, and one that marks everyone known turns every unflagged trader into a non-claimant. The default UNKNOWN is what leaves existing data alone. Datasets store the enum name (CLAIMANT), which Simulation encodes like other enum columns, and the value carries forward from the year supplied.

The input allowance is a fact about the data, not a simulation-year parameter. Reading it for the data year lets one model version serve data from before and after the April 2027 raise, with no separate copy of the allowance's history.

A situation records the periods its inputs were supplied for, so its data year is the latest year, up to the one simulated, for which the status was supplied.

  • A claimant whose status is supplied for 2027 is read as already net of £3,000.
  • One given 2025-26 inputs and simulated in 2027-28 deducted £1,000 and gains £2,000 from the raise, as the same inputs in a dataset measured in 2025 do.

Two simplifications remain, both documented in _data_year:

  • one year serves a whole situation;
  • a dataset can't declare the year its incomes were measured, so a file saved from a later projected year is read as measured in its first year.

A cut below the input allowance is set by an explicit share. Claimants' expenses are below the input allowance but unknown. Under a cut, a claimant keeps claiming while the new allowance covers their expenses. Otherwise they deduct those expenses, which is ordinary treatment and leaves a supplied current loss claimable. So their taxable profit rises by the input allowance less the larger of the two.

  • The default share of 1 takes the lower bound: a cut adds nothing.
  • A share of 0 gives the upper bound: every claimant keeps claiming.
  • A share outside [0, 1] is taken as the nearer bound, since expenses above the allowance would contradict the claim.

The trading allowance's own description points to the parameter.

Non-claimants keep full relief, but not partial relief. Partial relief needs an election (s. 783AM), and electing after a raise is behaviour, as HMRC's costings book it. Full relief needs none.

Without supplied receipts, a non-claimant's receipts are unknown but above their profit plus the input allowance. So the model shows full relief only from supplied receipts, or from casual income alone where there is no profit. Elsewhere, under a raise it gives none, which is a lower bound.

Chapter 8 miscellaneous income (#2725, #2726) is handled, not left as is. A claimant's receipts take up the allowance first (trades first, s. 783AK), and income not otherwise charged gets the rest.

  • At the input allowance that rest is nothing.
  • At £3,000 on 2026 data, a claimant with £500 of profit and £900 of casual income has both relieved in full.

A non-claimant gets it only through full relief.

For the data side (PolicyEngine/microcosm#1428)

  • Flag only profits net of the allowance. A profit at or below £1,000 flagged CLAIMANT is read as receipts above £1,000 and taxed at baseline, where UNKNOWN relieves it in full. Traders whose receipts were within £1,000 belong at UNKNOWN.
  • The stored receipts can go. With the flag, receipts of profit + £1,000 or + £3,000 are no longer needed: a claimant's status overrides self_employment_gross_receipts, and the input allowance is nominal, so the gap no longer grows with mixed-income uprating.
  • Data year. The input allowance comes from the dataset's first year, so a 2024-25 or 2025-26 base reads £1,000. A file saved from a later projected year would be read as measured then, so save releases from their base year.
  • Don't pin the deduction. A dataset that pins trading_allowance_deduction to zero, as policyengine-uk-data does on SPI records, bypasses the formula and blocks every allowance reform. Flagging claimants and non-claimants avoids that.

Tests

policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/trading_allowance_claim_status.yaml (41 cases):

  • Claimants:
    • at the input allowance, in 2026 and 2027;
    • under raises to £3,000 and £5,000 (partial and full relief), and £3,000 to £5,000 in 2027;
    • under a cut to £500 and abolition, each beside an unknown-status person so they can't pass without the reform;
    • under cuts at expense shares of 0 and 0.5, and with a share of 1.5 taken as 1;
    • with capital allowances, at £1,000 and under a raise;
    • a claimant's status overriding supplied receipts;
    • claimants with no profit.
  • Non-claimants:
    • with no receipts, under a raise, and with low-expense receipts above the allowance;
    • with receipts within it, alone and with casual income;
    • with relevant income exactly at it, and above it;
    • with no trade, including casual income exactly at the allowance.
  • Unknown status, with and without receipts.
  • Class 4 and income tax.
  • The current-loss election:
    • a claimant at £1,000, under a raise, under a cut below their expenses and under abolition;
    • a non-claimant, including electing out of full relief.
  • Chapter 8 income for claimants.

policyengine_uk/tests/test_trading_allowance_claim_status_properties.py (hypothesis, 30 people per draw, 15 scenarios of allowance and expense share, one at a share of 1.5):

  • Every status matches a reference written from the Act's options (full, partial, ordinary), with a claimant's choice taken by comparing the allowance with deducting their expenses. It covers taxable profit, Class 4 profits, the Chapter 8 deduction and uses_trading_allowance, with Chapter 8 and miscellaneous income drawn separately.
  • A claimant's deductions never exceed the allowance's excess over £1,000.
  • Taxable profit never rises with the allowance.
  • A smaller expense share never lowers a claimant's taxable profit.

policyengine_uk/tests/test_trading_allowance_claim_status_data_year.py (small datasets and situations):

  • data measured in 2025 get the 2027 raise;
  • data measured in 2027 gain nothing from it;
  • a reform of the data year leaves the input allowance;
  • a raise never withdraws a claimant's loss relief, on a dataset and in a situation;
  • the status carries forward;
  • a situation reads carried inputs as measured in the year supplied, and each year supplied as measured then.

Mutation checks. Each of these breaks fails both the YAML and the property tests:

  • dropping a non-claimant's full relief;
  • deducting a claimant's capital allowances;
  • inventing receipts for a claimant with no profit;
  • not counting claimants as taking the allowance.

Reading the input allowance at the simulation year fails the data-year tests.

Second-round mutation checks:

  • keeping the allowance for a claimant below their expenses fails the YAML and the property test;
  • a strict < for full relief fails the two exact-boundary YAML cases;
  • an unclamped expense share fails the YAML and the property test;
  • reading a situation's data year as the year simulated fails two data-year tests.

Commands run in the locked environment (uv run --frozen --extra dev):

  • policyengine-core test on policyengine_uk/tests/policy/baseline/gov/hmrc: 1,298 passed.
  • pytest on the three files above and on the related tests: 260 passed and 1 skipped (test_build_metadata.py's bundle-contract test, which needs policyengine_bundles and is on CI's allowlist). The related tests are trading allowance, Chapter 8, trading loss, Class 4, income concepts, parameter descriptions and metadata, Pension Credit Class 2, Welsh childcare income, director NI, variable units and build metadata.
  • ruff format . and ruff check .: clean. .github/check_changelog_fragments.py: OK.

Not run locally: the full suite, left to CI, and the microsimulation tests, which need the dataset token.

axiom: n/a: adds an input that records how the data's profits were measured and a gov.simulation convention parameter; the Part 6A rules (ss. 783AE, 783AF, 783AI) are unchanged

🤖 Generated with Claude Code

Add trading_allowance_claim_status, a person enum (UNKNOWN, CLAIMANT,
NON_CLAIMANT; default UNKNOWN), and
gov.simulation.trading_allowance_in_input_profits, the allowance a
claimant's input profit already deducts: £1,000 from 2017-18, not moved
by reforms to the trading allowance and not uprated.

A claimant's receipts are their profit plus that amount, so the deduction
at allowance A is min(profit, A - £1,000) less capital allowances, floored
at nil, and a cut below £1,000 leaves their profit as reported. A
non-claimant gets no allowance at any level, on trade receipts or on
income not otherwise charged. Unknown keeps the current rules, so
existing datasets are unchanged. Class 4 and the current-loss election
read the same deduction.

Issue #2764.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Aggregate impact on certified data: pass

Base 2da212abcf against head cd431aee26, both on enhanced_frs_2024_25.h5 (policyengine-uk-data 1.58.1, sha256 056e80fd8e91), fiscal years 2025-26 to 2031-32. Each year is a full microsimulation run on each side.

Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in report_spec.yaml; a flag does not fail the job).

Workflow run and full report

Notes

  • In the years a figure from fewer than 10 records is suppressed (spending.disabled_students_allowance), every total containing it, and the decile incomes, are rebuilt from their other components, so no published total includes it. Those totals are marked †. A change is withheld where the two sides leave out different figures and can't be lined up.

Moves flagged for review

None.

Spending by benefit and service

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Caseloads

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Standard reform costings (change in gov_balance)

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise Class 1 main employee NICs rate to 10% 0 0 0 0 0 suppressed suppressed
Raise additional rate by 3pp 0 0 0 0 0 suppressed suppressed

Standard reforms: change in each reform's target

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise Class 1 main employee NICs rate to 10%: change in national_insurance 0 0 0 0 0 suppressed suppressed
Raise additional rate by 3pp: change in income_tax 0 0 0 0 0 suppressed suppressed

Household net income by decile

No 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)
  • VAT excluding private school fees is never negative (sign:vat): 2025-26: 76 units below zero, a weighted £-53,888,684 in total; 2026-27: 76 units below zero, a weighted £-55,777,260 in total; 2027-28: 76 units below zero, a weighted £-57,727,917 in total
Counted-once check

Each 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.

  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; gov_spending; not a failure)
  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; household_benefits; not a failure)
Provenance
Side Commit Runtime Environment hash Partition hash Time
base main 2da212abcf uk 2.170.0 (files e654eb5f6f55), core 3.33.12, py 3.13.18 aarch64 77aac721ac5e 7cb61324a46b 1439.42s
head #2765 merged into main cd431aee26 uk 2.170.0 (files 475d70c93ac6), core 3.33.12, py 3.13.18 aarch64 77aac721ac5e 7cb61324a46b 1448.46s

Dataset: hf://policyengine/policyengine-uk-data-private/enhanced_frs_2024_25.h5@2526edbbb687a3b54dbb11272e5389e4b62d2ac2, sha256 056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784 (verified on both sides), policyengine-uk-data 1.58.1.

Receipts by tax head: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Final charge on cash interest in non-cash ISAs £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Lifetime ISA withdrawal charge £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Income tax £294.46bn £315.23bn £335.83bn £351.28bn £366.96bn £385.36bn £400.00bn
National Insurance (employee and self-employed) £51.70bn £54.14bn £56.13bn £57.73bn £59.76bn £61.64bn £63.51bn
National Insurance (employer) £148.71bn £155.37bn £160.88bn £165.09bn £172.06bn £177.36bn £182.57bn
Employer NI change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Employer NI change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Capital gains tax £12.60bn £13.15bn £13.60bn £14.11bn £14.67bn £15.24bn £15.80bn
Student loan repayments £7.82bn £8.13bn £8.39bn £8.58bn £8.79bn £9.04bn £9.29bn
VAT excluding private school fees £249.37bn £256.87bn £266.33bn £272.70bn £278.97bn £285.24bn £291.59bn
Change in VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
VAT on private school fees £1.95bn £2.01bn £2.09bn £2.14bn £2.20bn £2.26bn £2.31bn
Change in private school VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Fuel duties £24.71bn £24.13bn £26.00bn £26.83bn £26.51bn £25.78bn £24.63bn
Alcohol duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tobacco duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Vehicle excise duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Betting and gaming duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
TV licence fee £4.26bn £4.40bn £4.41bn £4.42bn £4.43bn £4.44bn £4.45bn
Council tax £49.21bn £52.16bn £54.89bn £57.95bn £61.16bn £64.47bn £67.91bn
High value council tax surcharge £0.00bn £0.00bn £0.00bn £0.48bn £0.49bn £0.50bn £0.51bn
Northern Ireland domestic rates £0.55bn £0.58bn £0.60bn £0.63bn £0.66bn £0.69bn £0.72bn
Business rates £34.68bn £36.20bn £38.00bn £39.46bn £42.27bn £43.25bn £44.29bn
Stamp duty land tax on the household's own purchases £6.88bn £7.32bn £7.66bn £8.06bn £8.47bn £8.91bn £9.35bn
Stamp duty land tax paid by companies £4.72bn £4.57bn £5.29bn £6.03bn £6.37bn £6.92bn £7.45bn
Land transaction tax (Wales) £0.07bn £0.08bn £0.08bn £0.09bn £0.10bn £0.10bn £0.11bn
Land and buildings transaction tax (Scotland) £0.31bn £0.32bn £0.33bn £0.35bn £0.36bn £0.38bn £0.40bn
Unspecified tax change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Unspecified tax change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax excluding the main residence £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Land value tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Carbon tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Spending by benefit and service: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Lifetime ISA bonus £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
State Pension £121.68bn £125.92bn £126.73bn £129.50bn £132.99bn £136.66bn £140.43bn
Pension Credit £6.66bn £7.03bn £7.29bn £7.48bn £7.60bn £7.84bn £8.03bn
Winter Fuel Payment £1.86bn £1.85bn £1.79bn £1.77bn £1.78bn £1.78bn £1.78bn
Pension Age Winter Heating Payment (Scotland) £0.19bn £0.20bn £0.20bn £0.21bn £0.21bn £0.22bn £0.22bn
Universal Credit £75.48bn £80.52bn £82.95bn £84.96bn £86.11bn £87.61bn £88.21bn
Housing Benefit £8.56bn £8.89bn £9.23bn £9.46bn £9.69bn £9.99bn £10.27bn
Council Tax Reduction £2.07bn £2.20bn £2.31bn £2.45bn £2.58bn £2.72bn £2.86bn
Employment and Support Allowance (income-related) £0.79bn £0.82bn £0.84bn £0.86bn £0.88bn £0.90bn £0.92bn
Employment and Support Allowance (contribution-based) £5.20bn £5.37bn £5.56bn £5.69bn £5.82bn £5.95bn £6.09bn
Jobseeker's Allowance (income-based) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Jobseeker's Allowance (contribution-based) £0.29bn £0.30bn £0.31bn £0.31bn £0.32bn £0.33bn £0.34bn
Income Support £0.11bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Working Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Incapacity Benefit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Personal Independence Payment £26.95bn £28.03bn £29.05bn £30.09bn £30.75bn £31.51bn £32.21bn
Disability Living Allowance £7.49bn £7.79bn £8.07bn £8.36bn £8.55bn £8.76bn £8.95bn
Attendance Allowance £8.68bn £9.03bn £9.36bn £9.69bn £9.90bn £10.15bn £10.38bn
Carer's Allowance £4.48bn £4.66bn £4.83bn £5.00bn £5.11bn £5.27bn £5.42bn
Carer Support Payment (Scotland) £0.31bn £0.32bn £0.33bn £0.34bn £0.35bn £0.36bn £0.37bn
Carer's Allowance Supplement (Scotland) £0.00bn £0.04bn £0.04bn £0.05bn £0.05bn £0.05bn £0.05bn
Severe Disablement Allowance £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn
Industrial Injuries Disablement Benefit £0.50bn £0.52bn £0.54bn £0.55bn £0.56bn £0.58bn £0.59bn
Armed Forces Compensation Scheme £0.61bn £0.63bn £0.65bn £0.66bn £0.68bn £0.69bn £0.71bn
War Widow's or Widower's Pension £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Benefit £16.19bn £16.82bn £17.39bn £18.00bn £18.39bn £18.84bn £19.25bn
Scottish Child Payment £0.49bn £0.52bn £0.53bn £0.55bn £0.56bn £0.57bn £0.56bn
Two Child Limit Payment (Scotland, counterfactual) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Healthy Start vouchers £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn
Maternity Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Maternity Pay £3.30bn £3.41bn £3.54bn £3.62bn £3.70bn £3.78bn £3.87bn
Statutory Paternity Pay £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Sick Pay £1.12bn £1.16bn £1.20bn £1.23bn £1.26bn £1.29bn £1.32bn
Sure Start Maternity Grant £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn
Bereavement Support Payment £0.67bn £0.69bn £0.71bn £0.73bn £0.75bn £0.76bn £0.78bn
Widowed Parent's Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tax-Free Childcare £0.56bn £0.57bn £0.57bn £0.58bn £0.60bn £0.61bn £0.59bn
Extended childcare entitlement £2.99bn £3.50bn £3.59bn £3.71bn £3.78bn £3.86bn £3.93bn
Universal childcare entitlement £2.06bn £2.22bn £2.29bn £2.37bn £2.43bn £2.48bn £2.53bn
Targeted childcare entitlement £0.50bn £0.51bn £0.52bn £0.54bn £0.55bn £0.57bn £0.56bn
Childcare Offer for Wales £0.26bn £0.28bn £0.28bn £0.29bn £0.30bn £0.31bn £0.32bn
Care to Learn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Childcare Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Parents' Learning Allowance £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn
Adult Dependants' Grant £0.06bn £0.06bn £0.04bn £0.04bn £0.04bn £0.04bn £0.04bn
Travel Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
NHS spending £203.89bn £204.30bn £204.56bn £204.91bn £205.34bn £205.85bn £206.35bn
State education spending £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn
Transport subsidy spending £25.05bn £25.10bn £25.14bn £25.18bn £25.23bn £25.30bn £25.36bn
Cost-of-living support payment £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Bills Rebate £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Household Energy Payment £0.00bn £2.11bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Price Guarantee subsidy £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disability basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Other public spending budget change £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Caseloads: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Lifetime ISA bonus 0k 0k 0k 0k 0k 0k 0k
State Pension 11,264k 11,145k 10,819k 10,700k 10,723k 10,750k 10,776k
Pension Credit 1,486k 1,480k 1,472k 1,452k 1,450k 1,455k 1,464k
Winter Fuel Payment 7,844k 7,783k 7,473k 7,375k 7,391k 7,409k 7,427k
Pension Age Winter Heating Payment (Scotland) 803k 802k 800k 797k 799k 801k 802k
Universal Credit 6,342k 6,347k 6,336k 6,297k 6,292k 6,271k 6,223k
Housing Benefit 1,249k 1,245k 1,244k 1,227k 1,227k 1,229k 1,232k
Council Tax Reduction 3,176k 3,195k 3,200k 3,214k 3,224k 3,235k 3,242k
Employment and Support Allowance (income-related) 71k 71k 71k 71k 71k 71k 71k
Employment and Support Allowance (contribution-based) 719k 720k 721k 722k 724k 726k 728k
Jobseeker's Allowance (income-based) 0k 0k 0k 0k 0k 0k 0k
Jobseeker's Allowance (contribution-based) 67k 67k 67k 67k 67k 68k 68k
Income Support 25k 0k 0k 0k 0k 0k 0k
Working Tax Credit 0k 0k 0k 0k 0k 0k 0k
Child Tax Credit 0k 0k 0k 0k 0k 0k 0k
Incapacity Benefit 0k 0k 0k 0k 0k 0k 0k
Personal Independence Payment 3,567k 3,575k 3,579k 3,585k 3,593k 3,602k 3,611k
Disability Living Allowance 1,291k 1,294k 1,296k 1,298k 1,300k 1,304k 1,307k
Attendance Allowance 1,695k 1,698k 1,700k 1,703k 1,707k 1,711k 1,715k
Carer's Allowance 1,034k 1,037k 1,038k 1,040k 1,042k 1,060k 1,062k
Carer Support Payment (Scotland) 71k 71k 71k 72k 72k 72k 72k
Carer's Allowance Supplement (Scotland) 0k 71k 71k 72k 72k 72k 72k
Severe Disablement Allowance 4k 4k 4k 4k 4k 4k 4k
Industrial Injuries Disablement Benefit 137k 137k 137k 138k 138k 138k 139k
Armed Forces Compensation Scheme 93k 93k 94k 94k 94k 94k 94k
War Widow's or Widower's Pension 0k 0k 0k 0k 0k 0k 0k
Child Benefit 8,123k 8,120k 8,120k 8,130k 8,145k 8,161k 8,178k
Scottish Child Payment 351k 352k 352k 353k 350k 348k 337k
Two Child Limit Payment (Scotland, counterfactual) 0k 0k 0k 0k 0k 0k 0k
Healthy Start vouchers 200k 200k 200k 201k 201k 201k 202k
Maternity Allowance 0k 0k 0k 0k 0k 0k 0k
Statutory Maternity Pay 333k 333k 334k 334k 335k 336k 337k
Statutory Paternity Pay 0k 0k 0k 0k 0k 0k 0k
Statutory Sick Pay 181k 182k 182k 182k 182k 183k 183k
Sure Start Maternity Grant 26k 26k 26k 26k 26k 26k 26k
Bereavement Support Payment 98k 98k 98k 98k 98k 99k 99k
Widowed Parent's Allowance 0k 0k 0k 0k 0k 0k 0k
Tax-Free Childcare 1,043k 1,027k 1,014k 1,009k 1,011k 1,009k 993k
Extended childcare entitlement 795k 800k 796k 797k 798k 798k 792k
Universal childcare entitlement 1,071k 1,072k 1,072k 1,073k 1,076k 1,079k 1,074k
Targeted childcare entitlement 165k 156k 154k 154k 155k 155k 150k
Childcare Offer for Wales 39k 39k 39k 39k 39k 39k 39k
Care to Learn 0k 0k 0k 0k 0k 0k 0k
Childcare Grant 0k 0k 0k 0k 0k 0k 0k
Parents' Learning Allowance 98k 98k 98k 99k 99k 99k 99k
Adult Dependants' Grant 18k 18k 12k 12k 12k 12k 12k
Travel Grant 0k 0k 0k 0k 0k 0k 0k
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund 0k 0k 0k 0k 0k 0k 0k
Cost-of-living support payment 0k 0k 0k 0k 0k 0k 0k
Energy Bills Rebate 0k 0k 0k 0k 0k 0k 0k
Household Energy Payment 0k 21,065k 0k 0k 0k 0k 0k
Energy Price Guarantee subsidy 0k 0k 0k 0k 0k 0k 0k
Basic income 0k 0k 0k 0k 0k 0k 0k
Disability basic income 0k 0k 0k 0k 0k 0k 0k
Government balance, each component counted once: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Government receipts, each component once £892.00bn £934.66bn £980.50bn £1,015.93bn £1,054.24bn £1,091.56bn £1,124.90bn
Government spending £529.38bn † £545.66bn † £550.77bn † £559.52bn † £567.20bn † £575.96bn † £583.61bn †
Government balance, each receipt once £362.62bn † £389.00bn † £429.73bn † £456.41bn † £487.04bn † £515.61bn † £541.29bn †
Benefits in household_benefits but not in gov_spending £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Model totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
gov_tax £892.00bn £934.66bn £980.50bn £1,015.93bn £1,054.24bn £1,091.56bn £1,124.90bn
household_tax £491.97bn £520.41bn £551.21bn £576.01bn £601.00bn £626.70bn £648.42bn
pre_budget_change_household_tax £484.15bn £512.28bn £542.82bn £566.95bn £591.72bn £617.16bn £638.62bn
gov_spending £529.38bn † £545.66bn † £550.77bn † £559.52bn † £567.20bn † £575.96bn † £583.61bn †
household_benefits £529.38bn † £545.66bn † £550.77bn † £559.52bn † £567.20bn † £575.96bn † £583.61bn †
gov_balance £362.62bn † £389.00bn † £429.73bn † £456.41bn † £487.04bn † £515.61bn † £541.29bn †
Household income totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Household market income £1,637.96bn £1,702.77bn £1,757.35bn £1,806.41bn £1,855.97bn £1,912.43bn £1,970.44bn
Household net income £1,636.69bn † £1,687.82bn † £1,715.46bn † £1,747.50bn † £1,778.70bn † £1,817.02bn † £1,859.69bn †
Household net income (HBAI definition) £1,449.57bn £1,499.83bn £1,530.87bn £1,565.18bn £1,597.89bn £1,635.60bn £1,676.62bn
Population: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
People 69,245k 69,383k 69,473k 69,592k 69,738k 69,912k 70,080k
Benefit units 35,110k 35,181k 35,226k 35,286k 35,360k 35,449k 35,534k
Households 31,046k 31,108k 31,149k 31,201k 31,267k 31,345k 31,420k
Poverty rates: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 16.3% 15.5% 16.4% 16.3% 16.5% 16.5% 16.6%
Absolute poverty, before housing costs: children 24.7% 22.1% 23.3% 22.9% 23.1% 23.2% 23.5%
Absolute poverty, before housing costs: working-age adults 14.1% 13.8% 14.7% 14.7% 14.8% 14.8% 15.0%
Absolute poverty, before housing costs: pensioners 13.7% 13.4% 14.0% 13.8% 13.9% 13.9% 13.9%
Absolute poverty, after housing costs: everyone 21.6% 21.0% 22.0% 22.6% 22.9% 23.1% 23.4%
Absolute poverty, after housing costs: children 33.3% 31.3% 32.3% 32.5% 32.9% 33.4% 34.2%
Absolute poverty, after housing costs: working-age adults 19.9% 19.8% 20.8% 21.6% 21.9% 22.0% 22.3%
Absolute poverty, after housing costs: pensioners 12.7% 12.4% 13.2% 13.2% 13.3% 13.4% 13.6%
Relative poverty, before housing costs: everyone 17.0% 16.0% 16.4% 16.3% 16.4% 16.3% 16.6%
Relative poverty, before housing costs: children 25.9% 22.9% 23.3% 22.9% 23.0% 22.9% 23.5%
Relative poverty, before housing costs: working-age adults 14.5% 14.1% 14.6% 14.7% 14.7% 14.7% 15.0%
Relative poverty, before housing costs: pensioners 14.5% 14.0% 13.9% 13.8% 13.8% 13.9% 13.9%
Relative poverty, after housing costs: everyone 21.4% 21.0% 21.3% 21.3% 21.3% 21.5% 21.7%
Relative poverty, after housing costs: children 33.1% 31.1% 31.2% 31.0% 31.1% 31.2% 31.8%
Relative poverty, after housing costs: working-age adults 19.9% 19.8% 20.2% 20.4% 20.5% 20.6% 20.8%
Relative poverty, after housing costs: pensioners 12.5% 12.4% 12.2% 11.7% 11.7% 11.8% 12.0%
People in poverty: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 11,278k 10,779k 11,402k 11,365k 11,475k 11,513k 11,651k
Absolute poverty, before housing costs: children 3,688k 3,314k 3,496k 3,445k 3,480k 3,502k 3,549k
Absolute poverty, before housing costs: working-age adults 5,965k 5,893k 6,308k 6,362k 6,420k 6,430k 6,521k
Absolute poverty, before housing costs: pensioners 1,624k 1,572k 1,598k 1,559k 1,575k 1,580k 1,581k
Absolute poverty, after housing costs: everyone 14,944k 14,600k 15,310k 15,721k 15,936k 16,141k 16,426k
Absolute poverty, after housing costs: children 4,975k 4,682k 4,837k 4,888k 4,945k 5,040k 5,179k
Absolute poverty, after housing costs: working-age adults 8,463k 8,461k 8,971k 9,344k 9,491k 9,584k 9,706k
Absolute poverty, after housing costs: pensioners 1,506k 1,457k 1,502k 1,490k 1,500k 1,517k 1,542k
Relative poverty, before housing costs: everyone 11,750k 11,084k 11,388k 11,364k 11,416k 11,415k 11,651k
Relative poverty, before housing costs: children 3,872k 3,423k 3,496k 3,445k 3,466k 3,454k 3,549k
Relative poverty, before housing costs: working-age adults 6,159k 6,013k 6,301k 6,362k 6,389k 6,389k 6,521k
Relative poverty, before housing costs: pensioners 1,720k 1,648k 1,590k 1,557k 1,562k 1,572k 1,581k
Relative poverty, after housing costs: everyone 14,849k 14,552k 14,775k 14,809k 14,874k 15,014k 15,234k
Relative poverty, after housing costs: children 4,942k 4,660k 4,673k 4,663k 4,679k 4,707k 4,806k
Relative poverty, after housing costs: working-age adults 8,426k 8,440k 8,711k 8,823k 8,873k 8,967k 9,062k
Relative poverty, after housing costs: pensioners 1,482k 1,452k 1,391k 1,323k 1,322k 1,339k 1,365k
Inequality: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Gini coefficient, equivalised HBAI income before housing costs 0.3080 0.3052 0.3058 0.3058 0.3063 0.3069 0.3085
Mean household net income in each side's own deciles: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1: mean household_net_income per household £17,681 † £17,997 † £17,915 † £18,152 † £18,307 † £18,531 † £18,717 †
Decile 2: mean household_net_income per household £27,939 † £29,023 † £28,711 † £29,013 † £29,276 † £29,691 † £30,151 †
Decile 3: mean household_net_income per household £34,320 † £35,485 † £36,073 † £36,611 † £37,197 † £37,737 † £38,187 †
Decile 4: mean household_net_income per household £38,933 † £40,687 † £41,724 † £42,727 † £43,334 † £44,132 † £44,434 †
Decile 5: mean household_net_income per household £43,239 † £45,567 † £46,012 † £46,960 † £47,714 † £48,584 † £50,279 †
Decile 6: mean household_net_income per household £47,927 † £49,210 † £50,893 † £51,939 † £52,942 † £53,942 † £54,680 †
Decile 7: mean household_net_income per household £55,662 † £56,571 † £57,442 † £57,670 † £58,730 † £59,651 † £61,082 †
Decile 8: mean household_net_income per household £68,789 † £70,803 † £71,858 † £73,466 † £73,880 † £75,317 † £77,178 †
Decile 9: mean household_net_income per household £78,890 † £81,013 † £80,861 † £82,646 † £85,464 † £86,946 † £89,509 †
Decile 10: mean household_net_income per household £117,005 † £120,339 † £124,742 † £126,632 † £125,971 † £128,600 † £130,938 †
Standard reform costings (change in gov_balance): head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp £6.41bn £6.66bn £6.63bn £6.85bn £7.03bn £7.25bn £7.49bn
Raise higher rate by 1pp £3.61bn £3.87bn £4.00bn £4.22bn £4.44bn £4.71bn £4.83bn
Raise personal allowance by ~800GBP/year £-3.80bn £-3.92bn £-3.98bn £-4.04bn £-4.10bn £-4.23bn £-1.95bn
Raise child benefit by 25GBP/week per additional child £-1.99bn £-1.75bn £-1.55bn £-1.38bn £-1.30bn £-1.18bn £-1.07bn
Reduce Universal Credit taper rate to 20% £-20.77bn £-21.32bn £-21.75bn £-21.99bn £-22.34bn £-22.75bn £-22.94bn
Raise Class 1 main employee NICs rate to 10% £10.89bn £11.30bn £11.71bn £12.01bn £12.31bn suppressed suppressed
Raise VAT standard rate by 2pp £24.89bn £25.71bn £26.63bn £27.24bn £27.87bn £28.50bn £29.14bn
Raise additional rate by 3pp £2.90bn £3.15bn £3.22bn £3.41bn £3.61bn suppressed suppressed
Raise the Universal Credit standard allowance by 10% £-3.06bn £-3.18bn £-3.26bn £-3.35bn £-3.44bn £-3.49bn £-3.50bn
Raise the State Pension by 10% £-7.78bn £-8.01bn £-8.24bn £-8.47bn £-8.82bn £-8.55bn £-8.89bn
Standard reforms: change in each reform's target: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp: change in income_tax £6.55bn £6.86bn £6.79bn £7.00bn £7.20bn £7.44bn £7.66bn
Raise higher rate by 1pp: change in income_tax £3.61bn £3.92bn £4.01bn £4.23bn £4.45bn £4.72bn £4.84bn
Raise personal allowance by ~800GBP/year: change in income_tax £-3.93bn £-4.07bn £-4.13bn £-4.18bn £-4.22bn £-4.40bn £-1.75bn
Raise child benefit by 25GBP/week per additional child: change in child_benefit £2.33bn £2.14bn £1.94bn £1.76bn £1.65bn £1.52bn £1.40bn
Reduce Universal Credit taper rate to 20%: change in universal_credit £20.74bn £21.31bn £21.78bn £22.04bn £22.37bn £22.78bn £22.99bn
Raise Class 1 main employee NICs rate to 10%: change in national_insurance £11.15bn £11.63bn £12.00bn £12.29bn £12.61bn suppressed suppressed
Raise VAT standard rate by 2pp: change in vat £24.69bn £25.51bn £26.42bn £27.03bn £27.65bn £28.28bn £28.91bn
Raise additional rate by 3pp: change in income_tax £2.90bn £3.15bn £3.22bn £3.42bn £3.62bn suppressed suppressed
Raise the Universal Credit standard allowance by 10%: change in universal_credit £3.06bn £3.19bn £3.28bn £3.37bn £3.43bn £3.49bn £3.51bn
Raise the State Pension by 10%: change in state_pension £10.68bn £11.17bn £11.28bn £11.60bn £12.04bn £12.47bn £12.97bn

@juaristi22

Copy link
Copy Markdown
Collaborator Author

Independent review of #2765 at 5520b59b5

This is an independent adversarial review. Three reviewers read the PR from different angles:

  • statute and a line-by-line pass;
  • callers and Python/numpy pitfalls;
  • cleanup and conventions.

A verifier then reproduced each finding with synthetic Simulation runs and re-read ITTOIA 2005 Part 6A, and a last pass looked for gaps. No dataset sets the claim status yet, so nothing below moves existing results.

Verdict: fix 1 before the data side adopts the flag. The property test can't catch it (3). The rest are edge cases, one parameter-design point, and texts.

Statute

1. A NON_CLAIMANT status blocks full relief, which needs no election.

  • utils/trading_allowance.py:117 and :131-135 zero both reliefs for every non-claimant. But full relief is automatic when relevant income is within the allowance (ss. 783AE(1), 783AF, 783AG). Only partial relief is elective (ss. 783AH(c), 783AM).
  • 2027 (allowance £3,000), with employment of £30,000:
    • A non-claimant with profit £600 and supplied receipts of £2,800 is taxed on the £600 (income tax £3,606). UNKNOWN with the same inputs gives nil (£3,486), as the Act does.
    • With receipts of £2,000 plus £900 of income not otherwise charged, both amounts are taxed instead of nil.
  • 2026, at today's £1,000: a non-claimant with no trade and £800 of income not otherwise charged is taxed on the £800 (£3,646 against £3,486), although £800 is within £1,000.
  • The "receipts unknown" reasoning doesn't apply when receipts are supplied. The YAML precedence case (line 192) uses receipts of £4,000, which is above the allowance.
  • Suggestion: let supplied receipts within the allowance, and a no-trade person's casual income, reach full relief whatever the status. Or say in the input's doc that NON_CLAIMANT is only for traders whose receipts exceed the allowance.

2. A claimant's capital allowances are still deducted (:113).

  • Partial relief replaces both expenses and capital allowances (s. 783AI; HMRC BIM86050).
  • A 2026 claimant with profit £2,500 and capital allowances of £500 is taxed on £2,000, where the Act gives £2,500. Under the raise the gain is then £1,500 rather than £2,000.
  • policyengine-uk-data sets capital_allowances from SPI CAPALL, so an enhanced FRS that adopts the flag would hit this. At least, the input's doc should tell datasets to zero capital allowances for claimants.

Tests

3. The property test's "reference written from the Act" copies the implementation (tests/test_trading_allowance_claim_status_properties.py:125-161, :212, :229-235).

  • It restates the supplied-receipts rule, profit + £1,000, the two non-claimant zeros and the claimant's capital allowances. So it asserts the results in 1 and 2: each kind of row turns up in 4-10% of drawn people. It would fail a statutory fix.
  • It always sets miscellaneous_income equal to income_not_otherwise_charged, so the min() cap is never exercised.
  • Nothing tests the status carrying forward from the data year.

Design

4. The input allowance is a fact about the data, read as a simulation-year parameter.

  • parameters/gov/simulation/trading_allowance_in_input_profits.yaml is read at utils/trading_allowance.py:90-92 and holds £1,000 for every year.
  • Take data measured under the £3,000 allowance (2027-28 onwards), flagged CLAIMANT with profit £2,500, run in 2028 at defaults. It gets a £2,000 deduction and is taxed on £500; with the parameter at £3,000 it would be taxed on £2,500. That is spurious relief in the baseline.
  • One model version can't serve datasets of both vintages without a reform per dataset.
  • policyengine-uk already has two ways to let a dataset declare its basis:
    • it reads dataset facts from the H5: energy_price_basis, savings_income_projection and isa_income's contract, read in simulation.py;
    • basic_state_pension reads the legislated value at min(simulation.dataset.years).

5. A cut's lower bound isn't visible where users reform the allowance.

  • Taking nil under a cut assumes each claimant's expenses equal £1,000, the extreme of the plausible range. So abolition yields £0 static from every flagged claimant.
  • That is stated in the PR body, in the YAML and in one clause of the deduction's doc, but not on gov.hmrc.income_tax.allowances.trading_allowance.
  • The repo's pattern for such assumptions is an explicit gov.simulation parameter, for example pensions/prior_year_input_ratio.yaml.

Edge cases

6. No-trade claimants get £1,000 of invented trade receipts (:101).

  • A claimant with no trade or zero profit is read as having exactly £1,000 of receipts. Those take the allowance ahead of Chapter 8 income:
    • in 2026, a no-trade claimant with £2,000 of casual income is taxed on £2,000 (UNKNOWN: £1,000);
    • in 2027, one with £2,800 is taxed on £800 (the Act: nil).
  • income_not_otherwise_charged.py:20-22 still says a trade with no profit has no receipts.
  • The data guidance excludes these people, so the input's doc should say so.

7. A float32 round trip flips uses_trading_allowance (:99-107).

  • Receipts are float32 profit + 1,000, and receipts − profit isn't exactly 1,000. A 2026 claimant with profit £1,800.40 gets a deduction of £0.0001 and uses_trading_allowance True. That happens for 14% of penny-valued profits under £4,500.
  • On datasets the money effect is nil, but counts of allowance users at £1,000 are inflated. In situation runs with a carried loss, loss relief can drop to 0.
  • Fix: expenses = where(claimant, input_allowance, ...).

8. uses_trading_allowance is False for every claimant at the input allowance (uses_trading_allowance.py:37).

  • The deduction now measures only relief beyond the input profit, but the variable's doc says it marks people who take the allowance, and the loss-relief rules gate on it.
  • A claimant with profit £2,500 and a supplied £1,500 loss gets the loss relieved in 2026, and the relief is withdrawn under the raise. The measured gain is £100 instead of £400.
  • The result matches UNKNOWN, so it isn't a regression. But anyone counting claimants with this variable gets zero.

9. A raise can withdraw a carried-forward loss and raise tax (:152-157).

  • Under any raise every claimant gets a positive deduction. That makes uses_trading_allowance True and withdraws a loss the engine carried forward. The current-loss election only weighs supplied losses.
  • A situation with 2025 inputs (employment £30,000, profit £5,000, loss £5,000, CLAIMANT) run in 2027 gives income tax of £4,558.85 under current law, against £3,895.89 if the allowance stayed at £1,000.
  • Datasets are unaffected, since projected losses count as supplied.

Texts and cleanup

10. miscellaneous_income_trading_allowance_deduction.py:27-30, the docstring this PR edits, still tells datasets to pin the deduction to zero, as policyengine-uk-data does on SPI records. The PR body says pinning blocks every allowance reform.

11. The NON_CLAIMANT definition (trading_allowance_claim_status.py:27-28) says the profit is net of capital allowances. But self_employment_income is profit before them, and they are deducted separately. A dataset that follows the doc would deduct them twice.

12. The non-claimant rule is applied by two where(non_claimant, 0, ...) masks plus a redundant claimant | term in receipts_known (:106, :116-117, :130-135, :157-161).

  • One ordinary_treatment = non_claimant | (current_loss > allowance_saving) at the return gives identical outputs: the maximum difference was 0.0 over 18,000 synthetic people.
  • It also matches the statute: not electing under s. 783AM is ordinary treatment.

Checked and found true

  • UNKNOWN is unchanged. Outputs and dtypes are identical to the merge base for 400 random people at five allowance levels, including supplied receipts, capital allowances, losses and Chapter 8 income.
  • Consistent claimants. The formula matches the Act at every allowance from £1,000 up, including the Chapter 8 trades-first split. The YAML cases at lines 36, 50, 61, 73, 304, 319 and 334 recompute by hand.
  • The status input. It encodes from enum names and carries forward from the data year, on both the situation and the dataset paths. An H5 without the column reads as UNKNOWN.
  • Tests and lint. The new YAML (24 cases) and the property tests pass, and so do ruff and the parameter and variable metadata tests.

Not covered

  • The full suite, and the microsimulation tests, which need the dataset token.

🤖 Generated with Claude Code

Non-claimants keep full relief, which needs no election (ITTOIA 2005
s. 783AE), where supplied receipts, or casual income alone with no
profit, are within the allowance; partial relief stays elective. One
ordinary-treatment mask replaces the two zeroing masks.

A claimant's input allowance is now the allowance legislated for the
year the data were measured (a dataset's first year, or the year
simulated), read from the unreformed baseline parameters, so data from
either side of the April 2027 raise read right. This replaces
gov.simulation.trading_allowance_in_input_profits.

Claimants: capital allowances are not deducted (partial relief replaces
them), the status applies only to a positive profit, the deduction is
computed from the allowances directly (no float32 round trip), and the
claim is their election, so they take the allowance and give up current
losses at any allowance above nil. A cut's effect is set by the new
gov.simulation.trading_allowance_claimant_expense_share (default 1, the
lower bound), which the trading allowance's description points to.

Tests: YAML cases for each review point, a property test whose reference
works from the Act's options, and dataset tests for the data year and
the status carrying forward.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@juaristi22

Copy link
Copy Markdown
Collaborator Author

Response to the review at 5520b59b5

All twelve points are addressed in 4d5b6c1. UNKNOWN is still unchanged, and every existing HMRC YAML test passes, as do the trading allowance, loss, Class 4 and income concept property tests.

Statute

1. Non-claimants keep full relief. Fixed. Partial relief stays elective (s. 783AM). A non-claimant now takes ordinary treatment only where full relief can't be shown; it needs no election (s. 783AE). It can be shown in two ways:

  • supplied receipts plus income not otherwise charged are within the allowance;
  • with no profit, that income alone is within it.

Your three cases now give income tax of £3,486, nil on both amounts, and £3,486. YAML cases cover each of them, plus the s. 783AL election out of full relief to claim a larger current loss.

2. A claimant's capital allowances are no longer deducted. Fixed in the engine, not only in the docs. Partial relief replaces them (s. 783AI), so for a claimant the deduction gives back any capital_allowances deducted separately. That means it can now be negative, which the deduction's documentation says. Your 2026 case is taxed on £2,500, and the raise gains the full £2,000. Class 4 and relevant UK earnings read the same sum, so they follow.

Tests

3. The property test. Rewritten.

  • The reference now works through the Act's options for each person instead of restating the formula: full relief without an election, partial relief only with one (never for a non-claimant), and ordinary treatment. A claimant's ordinary treatment deducts the expense share in 5.
  • It draws miscellaneous_income and income_not_otherwise_charged separately, so the min() cap is exercised.
  • It runs 14 scenarios: allowances from £0 to £10,000, and cuts at expense shares of 1, 0.5 and 0.
  • I re-applied four bugs from this review as mutations, and each fails both the property test and the YAML: the non-claimant zeros (1), the claimant's capital allowances (2), invented receipts for a claimant with no profit (6), and claimants not counted in uses_trading_allowance (8).
  • A new test_trading_allowance_claim_status_data_year.py covers the status carrying forward from the year supplied, and the data-year reading in 4. Reading the input allowance at the simulation year fails it.

Design

4. The input allowance is now read for the data year. Done the basic_state_pension way. gov.simulation.trading_allowance_in_input_profits is gone.

  • The engine reads the legislated allowance for the data year: the dataset's first year, or without a dataset the year simulated.
  • It reads it from parameters.baseline, which reforms leave alone (YAML inline ones included), sampled on 30 April as the fiscal-year conversion does. So there's no second copy of the allowance's history to keep in line.
  • On small datasets:
    • data measured in 2025 get the Budget raise in 2027 (£2,000);
    • data measured in 2027 get nothing from it in 2027 or 2028;
    • a reform that also changes the data year's allowance doesn't move the input allowance.

One consequence for situations: a claimant's 2027 profit is read as already net of £3,000. So the YAML raise cases now run in 2026 with reforms to £3,000 and £5,000, and a 2027 case shows £5,000 giving £2,000 over £3,000.

5. The cut assumption is now an explicit parameter. It is gov.simulation.trading_allowance_claimant_expense_share, a convention parameter like prior_year_input_ratio: claimants' expenses and capital allowances as a share of the input allowance.

  • Under a cut, a claimant keeps claiming while the new allowance covers those expenses, and otherwise deducts them. So a cut adds the input allowance less the larger of the two.
  • The default of 1 keeps the lower bound, so results don't change. 0 gives the upper bound.
  • The description of gov.hmrc.income_tax.allowances.trading_allowance now points to it.

Edge cases

6. Claimants with no profit. Fixed. The status describes a positive profit, and a claimant with none is read as unknown, so no receipts are invented. Your cases give £1,000 relieved (as UNKNOWN) and nil.

7. float32. Fixed. The claimant deduction is computed straight from the allowances, as min(profit, max(A, share × input) − input) − capital allowances. So it is exactly 0 at the input allowance (your £1,800.40 case included), and uses_trading_allowance no longer depends on its sign for claimants.

8. uses_trading_allowance for claimants. Fixed. Their claim was the election, so a claimant takes the allowance at any level above nil: they count, and a supplied current loss stays given up. Your example now gains £400. Under abolition there is nothing to take, so a supplied loss can be claimed.

9. Carried-forward losses. This follows from 8. A claimant gives up current losses at the input allowance and under a raise alike, so a raise can't withdraw one. It is tested on the dataset path (the raise lowers tax by exactly £400) and on the situation path (no change).

What remains predates this PR and also affects unknown-status traders: the current-loss election weighs only supplied losses, while Income Tax relieves inputs carried from earlier years. I've left that as it is.

Texts and cleanup

10. The Chapter 8 deduction's doc now says to flag claimants and non-claimants rather than pin trading_allowance_deduction, as policyengine-uk-data does on SPI records.

11. The NON_CLAIMANT definition now says the profit is net of actual expenses, with capital_allowances deducted from it separately.

12. Done. One ordinary mask, with the full-relief exception, replaces the two zeroing masks, and the redundant claimant | term is gone. Claimants skip the comparison.

Commands

Run in the locked environment (uv run --frozen --extra dev):

  • policyengine-core test on the new YAML (37 cases) and on policy/baseline/gov/hmrc: 1,294 passed.
  • pytest on the new property and data-year files and the existing related tests: 258 passed and 1 skipped (test_build_metadata.py's bundle-contract test, which needs policyengine_bundles and is on CI's allowlist).
  • ruff format and ruff check: clean.

Not run: the full suite, and the microsimulation tests, which need the dataset token.

🤖 Generated with Claude Code

Without a dataset the year simulated stands in for the year the inputs
were measured, even for inputs supplied for an earlier year and carried
forward, so a claimant given 2025-26 inputs and simulated in 2027-28 is
read as net of £3,000 and gets nothing from the raise. Keep that
convention, shared with the State Pension variables, and say so in
_data_year, the claim status input and the deduction's documentation,
with the workaround (build a dataset for the earlier year). A test pins
the behaviour so that a change of convention has to update the docs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@juaristi22

Copy link
Copy Markdown
Collaborator Author

Re-review of #2765 at 6b9a638d7

This re-reviews 4d5b6c1bd and 6b9a638d7 against my review at 5520b59b5. One reviewer re-ran each earlier case and re-applied each fixed bug as a mutation. A second read the new code for new defects, and I re-ran the main new case myself. Everything used synthetic situations and datasets.

Verdict: all twelve round-1 points are fixed. The property test and the YAML now each fail all four re-applied bugs.

  • Fix before the data side adopts the flag: a new defect in the cut logic (1), which the rewritten property test locks in (2).
  • Cheap fix available: the situation data year (3).
  • Small: 4 and 5.

New

1. Under a cut below a claimant's expenses, the claimant still counts as taking the allowance (utils/trading_allowance.py:212).

  • When the new allowance is below claimant_expenses (share × input allowance, :131), the claimant deducts their expenses instead. That is ordinary treatment, as the comment at :160 says.
  • But the third return value stays claimant & (allowance > 0). So uses_trading_allowance is True, and a supplied current loss stays given up. Only abolition releases it.
  • Example: 2026, employment of £30,000, profit of £2,500, a supplied trading_loss of £1,500, CLAIMANT, the default share of 1:
    • an allowance of £0 relieves the loss, for income tax of £3,686;
    • an allowance of £1, £500 or £999 doesn't, for £3,986;
    • the taxable trade profit is £2,500 in every case.
  • So moving the allowance from £0 to £1 raises this person's tax by £300, though not electing under s. 783AM would leave the loss claimable. A costing of a small cut books that £300 as yield, and abolition looks cheaper than a cut to £500.
  • Fix: claimant & (allowance > 0) & (allowance >= claimant_expenses). All 37 YAML cases still pass with it.

2. The property test copies the claimant rule, so it would fail that fix (tests/test_trading_allowance_claim_status_properties.py:189-196).

  • The reference's claims_loss = ~claimant & … and takes_allowance = where(claimant, allowance > 0, …) restate the code. With the fix above, it fails at an allowance of £300 with a share of 1.
  • The full-relief boundary is untested. s. 783AE gives full relief when relevant income "does not exceed" the allowance, and the code compares with <= (:150, :158, :203). But no YAML case sits exactly at the allowance, so nothing in the YAML pins <= against <.
  • One test can't fail. test_a_raise_leaves_a_situation_claimants_carried_loss (tests/test_trading_allowance_claim_status_data_year.py:138) runs on the situation path, where the raise gives nothing anyway (3). The dataset test at :84 is the real check.

3. A situation's data year can be read from its inputs.

  • 6b9a638d7 documents the limitation and says a situation "does not record when its inputs were measured". It does: after a 2027 calculation, the simulation's _user_input_keys holds only 2025 for both trading_allowance_claim_status and self_employment_income.
  • Suggested fix:
    • utils/supplied_inputs.py:133-168 (supplied_input_periods) already reads these keys. With the status added to SUPPLIED_INPUT_VARIABLES, _data_year could take the first year the status was supplied for.
    • Inputs carried from 2025 would then read as datasets do (£2,000 from the raise in 2027, not 0), and same-year inputs would stay as they are. Datasets could keep min(dataset.years).
  • Docs wording: a situation does have a dataset. build_from_situation stores a UKSingleYearDataset at default_input_period (simulation.py:1088-1090). _data_year falls back only because that class has no years, so the docs' "without a dataset" is not quite the mechanism.

4. The expense share has no upper bound (parameters/gov/simulation/trading_allowance_claimant_expense_share.yaml).

  • Its description says an allowance at or above the input allowance is unaffected. Above a share of 1 that is false: at 1.5, a claimant gets a £500 deduction under today's law, so the baseline moves.
  • A share above 1 also contradicts the claim itself, since a claimant whose expenses exceeded the allowance would have deducted them instead.
  • Suggestion: cap claimant_expenses at the input allowance, or bound the share to [0, 1].

5. A dataset can't declare the year it was measured (low). With the parameter gone, the input allowance follows min(dataset.years) alone.

  • A file whose first year is 2027 or later but whose incomes were measured under £1,000 would be read as net of £3,000. An example is a dataset projected forward and saved from a later year.
  • A dataset attribute, read the way energy_price_basis is, would let such a file say so.

Round-1 items

# Status
1. Non-claimants and full relief Fixed. The three cases give £3,486, nil on both amounts, and £3,486. Casual income above the allowance still gets nothing, as partial relief needs the election. The s. 783AL election out works in both directions.
2. Claimant capital allowances Fixed. Taxable profit is £2,500, and the raise gains the full £2,000. Class 4 and relevant UK earnings follow.
3. Property test Fixed. It works through the Act's options and exercises the min() cap. It fails each of the four re-applied bugs, as does the YAML. See 2 above for what it still copies.
4. Input allowance Fixed for datasets. Data measured in 2025 get £2,000 in 2027 and 2028, and data measured in 2027 get nothing. Five forms of reform all leave the input allowance alone. Situations: see 3 above.
5. Cut assumption Fixed. The new share parameter defaults to 1, and the allowance's description points to it. See 4 above.
6. No-profit claimants Fixed. They are read as unknown.
7. float32 Fixed. £1,800.40 gives exactly 0, and a sweep of 9,460 penny-valued profits found no non-zero deduction.
8. uses_trading_allowance Fixed at the input allowance and under a raise. Under a cut, see 1 above.
9. Carried-forward losses Fixed on datasets: the raise lowers tax by exactly £400, with the loss given up both ways.
10-12. Texts and the single mask Fixed.

Checked

  • The new YAML (37 cases), the data-year file (6 tests) and the property file pass, and ruff is clean.
  • On CI at 6b9a638d7, the YAML, lint and smoke-import jobs pass. The Python test shards were still running.
  • Not covered: the full suite, and the microsimulation tests, which need the dataset token.

🤖 Generated with Claude Code

A claimant takes the allowance only while it is above nil and at least
their expenses (the expense share of the input allowance); below them
they deduct those expenses, which is ordinary treatment, so a supplied
current loss is claimable under any cut, not only under abolition.

A situation's data year is now the latest year, up to the one simulated,
for which trading_allowance_claim_status was supplied (read through
supplied_input_periods; the status joins SUPPLIED_INPUT_VARIABLES), so
inputs carried from an earlier year keep that year's allowance. Microdata
keep their first year, chosen through built_from_data. This replaces the
limitation recorded in 6b9a638.

The expense share is clamped to [0, 1]. A dataset still cannot declare
the year its incomes were measured; that is now documented.

Tests: the property test's reference derives a claimant's choice by
comparing the allowance with deducting their expenses, and gains a
scenario at a share of 1.5; YAML cases cover a cut with a current loss,
full relief exactly at the allowance and a share above 1; the situation
data-year tests now check the supplied year.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@juaristi22

Copy link
Copy Markdown
Collaborator Author

Response to the re-review at 6b9a638d7

All five new points are addressed in 8944500. Unknown-status results are still identical to main, and every earlier case and mutation check still holds.

1. A cut below a claimant's expenses is ordinary treatment

Fixed as you suggested. The rule is now claimant & (allowance > 0) & (allowance >= claimant_expenses). Below their expenses, a claimant deducts those expenses (s. 783AM: no election), so a supplied current loss is claimable.

Your example now gives:

  • £3,686 at an allowance of £0, £1, £500 and £999, so there is no jump between £0 and £1;
  • £3,986 at £1,000, where their claim gives the loss up;
  • £3,586 at £3,000.

With the default share, the one jump left sits at the input allowance itself. A claimant with a supplied current loss gives it up there and claims it under any cut. Consistent data have no such person, since the claim makes the loss nil. A new YAML case covers the cut.

2. The property test

  • The claimant rule. The reference now compares the claimant's two options: their trade figure under the allowance, and under deducting the expense share. A claimant keeps the allowance while it does at least as well; a current loss doesn't enter. It passes with the fix and fails the old rule as a mutation. The near-boundary exclusions no longer apply to claimants, whose choice compares the allowances exactly, so the tie at the input allowance is checked rather than skipped.
  • The full-relief boundary. Two YAML cases now sit exactly at the allowance: a non-claimant with relevant income of 2,100 + 900 = 3,000, and one with £3,000 of casual income and no trade. Switching <= to < fails both.
  • The test that couldn't fail. The situation loss test can fail now: after 3, it expects the raise to lower tax by exactly £400.

3. A situation's data year comes from its inputs

Done as you suggested, with one refinement. _data_year now works like this:

  • Microdata: min(dataset.years), chosen through built_from_data.
  • A situation: the latest year, up to the one simulated, for which trading_allowance_claim_status was supplied. It reads that through supplied_input_periods, and the status is now in SUPPLIED_INPUT_VARIABLES.

The refinement: I take the latest supplied year rather than the first, so a situation with explicit inputs for 2025 and 2027 reads 2027's as measured in 2027. That case has a test.

Carried 2025 inputs now get £2,000 from the raise in 2027, while same-year inputs are unchanged, and so is every YAML case. The limitation text from 6b9a638d7 is gone, and its pinning test now checks the new rule.

The wording no longer says "without a dataset": the docs distinguish microdata from situations. One simplification is documented: one year serves a whole situation, so people whose status was supplied for different years all take the latest.

4. The expense share is bounded

The formula clamps it to [0, 1], and its description says so. A share of 1.5 now leaves today's law unchanged. A YAML case and a property-test scenario at 1.5 cover it, and the property test's bound on a claimant's deductions fails without the clamp.

5. A dataset can't declare its measurement year

Documented, not built. _data_year and the input's documentation now say a file saved from a later projected year is read as measured in its first year. A declared attribute like energy_price_basis would touch dataset_schema, H5 I/O and Simulation, so I've left it for a separate change.

Aggregate impact

The bot's FAIL comment is the run for 4d5b6c1bd. It was cancelled when 6b9a638d7 was pushed a minute later, so the report never ran; nothing crashed. The run for this commit will replace it.

Checks

Run in the locked environment (uv run --frozen --extra dev):

  • YAML: policyengine-core test on policy/baseline/gov/hmrc: 1,298 passed. That includes the 41 claim-status cases.
  • Python: pytest on the claim-status property and data-year files and the related trading allowance, Chapter 8, trading loss, Class 4, income concept, parameter and metadata tests: 260 passed and 1 skipped (test_build_metadata.py's bundle-contract test, which needs policyengine_bundles and is on CI's allowlist).
  • Mutations: Each fix in this round, re-applied as the old rule, fails the tests meant for it:
  • keeping the allowance for a claimant below their expenses fails the YAML and the property test;
  • a strict < for full relief fails the two exact-boundary YAML cases;
  • an unclamped share fails the YAML and the property test;
  • reading a situation's data year as the year simulated fails two data-year tests.

Round 1's four mutations still fail both files. Unknown status is still identical to main, checked over 400 random people at five allowances, dtypes included.

  • Lint: ruff format and ruff check are clean, and the changelog fragment check passes.

Not run: the full suite, and the microsimulation tests, which need the dataset token.

🤖 Generated with Claude Code

@juaristi22

Copy link
Copy Markdown
Collaborator Author

Third review of #2765 at 894450054

This re-reviews 894450054 against my second review at 6b9a638d7. As before:

  • one reviewer re-ran each earlier case and re-applied each fix's old rule as a mutation;
  • a second read the commit for new defects;
  • I reproduced the main new one myself.

Everything used synthetic situations and datasets.

Verdict: the five round-2 points are fixed as the reply describes. But the data-year change breaks an existing test (1), so that needs fixing before merge. Items 2 and 3 are the remaining design points, on situations and on claimants with losses. The rest are small.

Fix before merge

1. _data_year crashes a policyengine-core simulation built over UK data, so an existing test fails (utils/trading_allowance.py:41).

  • Why it crashes: getattr(getattr(simulation, "dataset", None), "years", None) swallows only AttributeError. A core Dataset answers unknown attributes through __getattr__, which calls self.load(name). That raises TypeError for Dataset.from_dataframe data and an h5py KeyError for files.
  • Who it hits: built_from_data is True for these simulations (is_over_dataset), and _data_year runs for everyone. So income tax fails whether or not anyone has a status.
  • The failing test: tests/test_data_built_imputations.py::test_core_simulation_over_data_gets_imputations (:291) fails at this head, and I reproduced it. Round 2's version caught TypeError, and with it swapped back in, the test passes. CI's Python jobs were still running when I checked.
  • Why catching alone isn't enough: such data would then fall into the situation rule. With the status stored for every year, "the latest supplied year up to the one simulated" is the year simulated, which zeroes the raise.
  • Fix: catch the loader's errors, or test for UKMultiYearDataset. For data without years, take the earliest year the status was supplied for.

Remaining design points

2. A situation's data year follows the year the status was supplied, not the profit (:44-51).

  • The input's definition (variables/input/trading_allowance_claim_status.py:17-18) ties the claim to "the tax year their input self-employment profit was measured". But the code reads the year the status was supplied for.
  • Status with no year key: it is stored for 2025. With self_employment_income of £5,000 given for 2027, a 2027 run deducts £2,000, reading a 2027 profit as net of £1,000. Round 2's head gave 0. Axes over that 2027 profit give £2,000 at every point.
  • The reverse: a status for 2027 with a profit for 2025 gives 0, reading the 2025 profit as net of £3,000.
  • Round trip: a situation exported with to_input_dataframe() and reloaded counts as microdata, so it gives a different 2027 answer from the situation itself (£2,000 against 0).
  • Suggestion: anchor on the latest year the profit was supplied for, up to the year simulated, or say in the input's docs that the status must carry the profit's year.

3. A claimant with a supplied current loss still jumps where the allowance crosses their expenses (:230).

  • The reply notes this jump at the input allowance, at the default share. But it moves with the share:
    • at 0.5 it sits at £499 to £500 (income tax £3,786 to £4,086);
    • at 0, the round-2 example comes back exactly (£3,886 at £0, £4,185.80 at £1).
  • It grows with the loss and the rate: +£2,400 at the higher rate (pay £60,000, profit £5,000, loss £6,000).
  • At the default share, every cut lowers such a claimant's tax by the loss's relief, so a costing books a cut as a cost for them.
  • The reply calls this inconsistent data, since a claim leaves the trade with no loss. If so, the PR's own rule for a claimant with no profit fits: read a claimant with a supplied current loss as unknown. Alternatively, compare the loss with the allowance's saving for claimants too, as for everyone else.

Tests and small items

4. The "up to the year simulated" filter is untested (:49).

  • Removing it passes all three touched test files.
  • The 2026 assertion (tests/test_trading_allowance_claim_status_data_year.py:149) can't fail: at £1,000, a default-share claimant gets 0 whichever year is read.
  • The filter matters once there's a loss. Take a status for 2025 and 2027, a 2026 loss of £1,500 and £30,000 of pay: 2026 income tax is £4,515.70 with the filter and £4,215.70 without.
  • Suggestion: assert uses_trading_allowance, or income tax with a loss, in 2026.

5. Small items:

  • Float tie: with a £3,000 input allowance and a share of 0.55, the expenses compute to 1,650.0000000000002. So a claimant at an allowance of £1,650 loses the allowance the docs say they keep: with a £1,500 loss, income tax is £3,956 instead of £4,256. Nine of 202 combinations of share and input allowance do this, all at £3,000. Rounding the expenses to pennies would fix it.
  • Capital allowances in the choice: below a share of 1, supplied capital allowances above the share's expenses don't count. With a share of 0.5, capital allowances of £800, an allowance of £600 and profit of £2,500, the claimant keeps the allowance (taxable £2,900), though ordinary treatment gives at most £2,700. Using the larger of share × input allowance and the capital allowances, capped at the input allowance, would cover it.
  • Texts:
    • _data_year says people whose status was supplied for different years "all take the latest". That can't happen as written: in any year one person's status is given, the builder gives everyone else the default, so the other claim reads UNKNOWN.
    • The changelog, trading_allowance_deduction.py:24-25 and the YAML header leave out "up to the year simulated".

Round-2 items

# Status
1. Cut below expenses Fixed at the default share. The round-2 case gives £3,686 at £0, £1, £500 and £999, £3,986 at £1,000, and £3,586 at £3,000. Over 175 allowance levels and 21 profiles, the only wrong-way steps are the loss jump in 3 above. Dataset and situation paths agree on all 2,856 values compared.
2. Property test Fixed. The reference compares the claimant's options rather than restating the rule, and it fails the old rule. Two YAML cases now sit exactly at the allowance, and < fails them. The situation loss test can now fail. Limit: it still shares the model's conventions (receipts as profit plus the input allowance, the clamp, a claimant's loss left out), so it can't test those.
3. Situation data year Fixed for situations whose status carries the profit's year. Every UK dataset path reads the dataset's first year: single- and multi-year datasets, h5 files, data frames, hf:// and gs:// cache hits, Microsimulation, and clones, branches, reforms and baselines. But see 1, 2 and 4 above.
4. Share bounds Fixed: 1.5 matches 1, and −0.5 matches 0. The lower clamp makes no difference for any allowance of £0 or more, which is harmless.
5. Dataset measurement year Documented, as the reply says.

The round-1 fixes all still hold. UNKNOWN status still gives the merge base's results, dtypes included: 400 random people × 7 scenarios × 10 variables.

Checked

  • Tests: the touched test files pass: the YAML (41 cases), the data-year file (8) and the property file (4).
  • Mutations: each mutation the reply lists fails its tests.
  • CI on 894450054: the YAML, lint, docs, smoke-import and aggregate impact jobs pass. The Python jobs were still running.
  • Not covered: the full suite, and the microsimulation tests, which need the dataset token.

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
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.

Let datasets mark trading allowance claimants, so allowance reforms reach their base without turnover data

1 participant