Skip to content
1 change: 1 addition & 0 deletions changelog.d/2764.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `trading_allowance_claim_status`, a person input saying whether the input data's self-employment profit already deducts the trading allowance (claimant), deducts actual expenses above it (non-claimant), or is unknown (the default, which keeps the current treatment). A claimant's receipts are their profit plus the allowance legislated for the year the profit was measured (a dataset's first year, or in a situation the latest year, up to the one simulated, that the situation gave the profit for when it was built), which reforms do not move, so a raise to any level relieves them by its excess over that allowance, up to their profit; their capital allowances, which partial relief replaces, are not deducted. Their expenses are a share of that allowance, set by `gov.simulation.trading_allowance_claimant_expense_share` (between 0 and 1, by default 1), or their capital allowances if larger, never more than that allowance: like everyone else, they keep the allowance where it covers those expenses, unless deducting them, or a supplied current loss, is worth more, so a cut adds by default nothing. A non-claimant gets the allowance only through full relief, where supplied receipts, or casual income alone with no profit, are within it. Income tax and Class 4 both read the result, so datasets without turnover can cost changes to the allowance, such as the Budget 2026 raise to £3,000, on the traders who already claim it.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
description: Amount of trading allowance, which relieves trading receipts in place of deducting actual expenses.
description: Amount of trading allowance, which relieves trading receipts in place of deducting actual expenses. For people the input data mark as claimants (trading_allowance_claim_status), whose profit already deducts the allowance legislated for the data year, a cut below that allowance adds to their taxable profit only what gov.simulation.trading_allowance_claimant_expense_share sets, by default nothing.
values:
2005-04-01:
value: 0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
description: >-
Actual expenses and capital allowances of a trading allowance claimant, as a
share of the allowance their input profit already deducts; capital
allowances supplied for the claimant count where larger. It sets how much a
cut below that allowance adds to the taxable profit of people the input data
mark as claimants (trading_allowance_claim_status). A claimant keeps
claiming unless deducting these expenses instead, which is ordinary
treatment and lets a supplied current loss be claimed, is worth more, so
with no loss a cut adds the input allowance less the larger of the new
allowance and these expenses. The share is between 0 and 1, and a value
outside is taken as the nearer bound: expenses cannot be negative, and above
the allowance the claimant would have deducted them instead. An allowance at
or above the input allowance is unaffected for a claimant with no current
loss.
# A claimant's expenses and capital allowances are below the allowance they
# claim, or they would deduct them instead, but no survey records them. With
# a share of 1, the default, they equal it, so a cut leaves a claimant's
# taxable profit as reported: the lower bound of its yield. With 0 they have
# none beyond any capital allowances supplied for them, so a claimant with
# no capital allowances and no current loss keeps claiming and a cut adds its
# full amount: the upper bound. 1 is a convention, not an estimate, kept so
# that the model books no yield it cannot support
# (PolicyEngine/policyengine-uk#2764).
values:
2005-04-01: 1
metadata:
unit: /1
period: year
label: Trading allowance claimant expenses as a share of the input allowance
reference:
- title: Income Tax (Trading and Other Income) Act 2005 s. 783AI (partial relief)
href: https://www.legislation.gov.uk/ukpga/2005/5/section/783AI
- title: Income Tax (Trading and Other Income) Act 2005 s. 783AM (election for partial relief)
href: https://www.legislation.gov.uk/ukpga/2005/5/section/783AM
21 changes: 21 additions & 0 deletions policyengine_uk/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
forget_replaced_model_values,
keep_population_records,
model_value_input_records,
supplied_input_periods,
)
from policyengine_uk.reforms import create_structural_reforms_from_parameters
from policyengine_uk.parameters.gov.simulation.labour_supply_responses.aliases import (
Expand Down Expand Up @@ -629,6 +630,9 @@ def build_from_populations(self, populations) -> None:
# the values the new populations hold, and only those: an equal value
# a rebuild stores later (from a situation or a frame) is supplied.
keep_population_records(self, adopted)
# The new populations bring their own self-employment profits, so
# record the years those were supplied for (see build_from_situation).
self._situation_profit_years = self._supplied_profit_years()

def set_input(self, variable_name: str, period: Period, value) -> None:
# What is set here is supplied, even a value equal to the model's own,
Expand Down Expand Up @@ -686,6 +690,7 @@ def clone(self, *args, **kwargs) -> "Simulation":
clone._savings_income_projection = getattr(
self, "_savings_income_projection", None
)
clone._situation_profit_years = getattr(self, "_situation_profit_years", None)
return clone

def _isa_latest_input(self, name, period, *, earlier=False):
Expand Down Expand Up @@ -1067,6 +1072,15 @@ def apply_parameter_assignments(self, assignments: list) -> None:
# recompute them under the rebuilt parameters.
self._invalidate_all_caches()

def _supplied_profit_years(self) -> tuple:
"""The years self_employment_income is supplied for, earliest first."""
return tuple(
period.start.year
for period in supplied_input_periods(
self.populations["person"], "self_employment_income"
)
)

def build_from_situation(self, situation: Dict) -> None:
"""Build simulation from a situation dictionary.

Expand All @@ -1084,6 +1098,12 @@ def build_from_situation(self, situation: Dict) -> None:
builder.build_from_dict(self.tax_benefit_system, situation, self)
self.has_axes = builder.has_axes
self._capture_cgt_supplied_inputs()
# The years the situation gave a self-employment profit for, as
# supplied: the trading allowance reads the year a claimant's profit
# was measured from them (utils/trading_allowance.py, _data_year), so
# a later write of the profit, by a scenario or a derivative, must
# not move it.
self._situation_profit_years = self._supplied_profit_years()

self.dataset = UKSingleYearDataset.from_simulation(
self, fiscal_year=self.default_input_period
Expand Down Expand Up @@ -1474,6 +1494,7 @@ def _start_new_population(self, built_from_dataset: bool) -> None:
self._cgt_warned_unused = False
self._isa_income_contracts = {}
self._savings_income_projection = None
self._situation_profit_years = None

def build_from_ids(
self,
Expand Down
Loading
Loading