diff --git a/changelog.d/1880.fixed.md b/changelog.d/1880.fixed.md
new file mode 100644
index 0000000000..3c510e8d4e
--- /dev/null
+++ b/changelog.d/1880.fixed.md
@@ -0,0 +1 @@
+- Stopped the trading allowance being deducted on top of expenses already netted out of self-employment profit (ITTOIA 2005 Part 6A). It now gives full relief only where profit is within the allowance, and partial relief when the new optional `self_employment_gross_receipts` input shows that expenses and capital allowances fall short of it. The allowance now starts in 2017-18.
diff --git a/docs/book/programs/gov/hmrc/income-tax.ipynb b/docs/book/programs/gov/hmrc/income-tax.ipynb
index 77f1fc99aa..f523dfb995 100644
--- a/docs/book/programs/gov/hmrc/income-tax.ipynb
+++ b/docs/book/programs/gov/hmrc/income-tax.ipynb
@@ -456,7 +456,7 @@
"
Trading Allowance | \n",
" Trading Allowance | \n",
" 1000.0 | \n",
- " 2005-04-01 | \n",
+ " 2017-04-06 | \n",
" currency-GBP | \n",
" \n",
" \n",
@@ -484,7 +484,7 @@
"16 Personal Savings Allowance Higher Threshold 500.0 2005-04-01 \n",
"17 Dividend Allowance Dividend Allowance 2000.0 2015-06-05 \n",
"18 Property Allowance Property Allowance 1000.0 2005-04-01 \n",
- "19 Trading Allowance Trading Allowance 1000.0 2005-04-01 \n",
+ "19 Trading Allowance Trading Allowance 1000.0 2017-04-06 \n",
"\n",
" Unit \n",
"0 currency-GBP \n",
@@ -648,8 +648,8 @@
"trading_allowance_data = {\n",
" \"Attribute\": [\"Trading Allowance\"],\n",
" \"Type\": [\"Trading Allowance\"],\n",
- " \"Value\": [trading_allowance.values_list[-1].value],\n",
- " \"Effective Date\": [trading_allowance.values_list[-1].instant_str],\n",
+ " \"Value\": [trading_allowance.values_list[0].value],\n",
+ " \"Effective Date\": [trading_allowance.values_list[0].instant_str],\n",
" \"Unit\": [trading_allowance.metadata[\"unit\"]],\n",
"}\n",
"\n",
diff --git a/policyengine_uk/parameters/gov/hmrc/income_tax/allowances/trading_allowance.yaml b/policyengine_uk/parameters/gov/hmrc/income_tax/allowances/trading_allowance.yaml
index 5e84fde99c..bbaa6d997b 100644
--- a/policyengine_uk/parameters/gov/hmrc/income_tax/allowances/trading_allowance.yaml
+++ b/policyengine_uk/parameters/gov/hmrc/income_tax/allowances/trading_allowance.yaml
@@ -1,8 +1,15 @@
-description: Amount of trading income untaxed per year.
+description: Amount of trading allowance, which relieves trading receipts in place of deducting actual expenses.
values:
2005-04-01:
+ value: 0
+ reference:
+ - title: Finance (No. 2) Act 2017 Sch. 3 para. 13 (allowance has effect from 2017-18)
+ href: https://www.legislation.gov.uk/ukpga/2017/32/schedule/3/paragraph/13
+ 2017-04-06:
value: 1_000
reference:
+ - title: Income Tax (Trading and Other Income) Act 2005 s. 783AD
+ href: https://www.legislation.gov.uk/ukpga/2005/5/section/783AD
- title: Finance (No. 2) Act 2017 Sch. 3
href: https://www.legislation.gov.uk/ukpga/2017/32/schedule/3
metadata:
diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances.yaml
index 2d774fffd5..5acbec23db 100644
--- a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances.yaml
+++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances.yaml
@@ -48,14 +48,23 @@
output:
trading_allowance_deduction: 500
taxable_self_employment_income: 0
-- name: Trading allowance partial deduction
+- name: Trading allowance partial deduction with no expenses
period: 2020
absolute_error_margin: 0
input:
self_employment_income: 5000
+ self_employment_gross_receipts: 5000
output:
trading_allowance_deduction: 1000
taxable_self_employment_income: 4000
+- name: Trading allowance does not stack on a profit already net of expenses
+ period: 2020
+ absolute_error_margin: 0
+ input:
+ self_employment_income: 5000
+ output:
+ trading_allowance_deduction: 0
+ taxable_self_employment_income: 5000
- name: Property allowance full deduction
period: 2020
absolute_error_margin: 0
diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/trading_allowance_deduction.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/trading_allowance_deduction.yaml
new file mode 100644
index 0000000000..bb1a8391de
--- /dev/null
+++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/trading_allowance_deduction.yaml
@@ -0,0 +1,127 @@
+# Trading allowance, ITTOIA 2005 Part 6A Chapter 1 (ss. 783A-783AR).
+# self_employment_income is profit after actual expenses. The allowance is an
+# alternative to those expenses (s. 783AI; HMRC BIM86050), so it may only add
+# the part of the £1,000 that actual deductions have not already used.
+
+- name: Sole trader with £25,000 profit gets no allowance on top of expenses
+ period: 2027
+ absolute_error_margin: 0.01
+ input:
+ self_employment_income: 25_000
+ output:
+ trading_allowance_deduction: 0
+ taxable_self_employment_income: 25_000
+ adjusted_net_income: 25_000
+ # (25,000 - 12,570) * 20%
+ income_tax: 2_486
+
+- name: Profit within the allowance, receipts unknown, is fully relieved
+ period: 2027
+ absolute_error_margin: 0
+ input:
+ self_employment_income: 600
+ output:
+ trading_allowance_deduction: 600
+ taxable_self_employment_income: 0
+
+- name: Profit equal to the allowance, receipts unknown, is fully relieved
+ period: 2027
+ absolute_error_margin: 0
+ input:
+ self_employment_income: 1_000
+ output:
+ trading_allowance_deduction: 1_000
+ taxable_self_employment_income: 0
+
+- name: Profit above the allowance, receipts unknown, is taxed as reported
+ period: 2027
+ absolute_error_margin: 0
+ input:
+ self_employment_income: 1_001
+ output:
+ trading_allowance_deduction: 0
+ taxable_self_employment_income: 1_001
+
+- name: Full relief when receipts are within the allowance (s. 783AE-783AF)
+ period: 2027
+ absolute_error_margin: 0
+ input:
+ self_employment_gross_receipts: 800
+ self_employment_income: 500
+ output:
+ trading_allowance_deduction: 500
+ taxable_self_employment_income: 0
+
+- name: Partial relief beats £100 of expenses (s. 783AI)
+ period: 2027
+ absolute_error_margin: 0
+ input:
+ self_employment_gross_receipts: 1_200
+ self_employment_income: 1_100
+ output:
+ trading_allowance_deduction: 900
+ # 1,200 receipts - 1,000 allowance
+ taxable_self_employment_income: 200
+
+- name: Partial relief beats £500 of expenses
+ period: 2027
+ absolute_error_margin: 0
+ input:
+ self_employment_gross_receipts: 1_200
+ self_employment_income: 700
+ output:
+ trading_allowance_deduction: 500
+ taxable_self_employment_income: 200
+
+- name: Actual expenses above the allowance leave no extra relief
+ period: 2027
+ absolute_error_margin: 0
+ input:
+ self_employment_gross_receipts: 25_000
+ self_employment_income: 20_000
+ output:
+ trading_allowance_deduction: 0
+ taxable_self_employment_income: 20_000
+
+- name: The allowance replaces capital allowances as well as expenses
+ period: 2027
+ absolute_error_margin: 0
+ input:
+ self_employment_gross_receipts: 1_500
+ self_employment_income: 1_500
+ capital_allowances: 300
+ output:
+ trading_allowance_deduction: 700
+ # 1,500 receipts - 1,000 allowance, with no capital allowances
+ taxable_self_employment_income: 500
+
+- name: Expenses plus capital allowances above the allowance use the profit method
+ period: 2027
+ absolute_error_margin: 0
+ input:
+ self_employment_gross_receipts: 1_500
+ self_employment_income: 1_200
+ capital_allowances: 900
+ output:
+ trading_allowance_deduction: 0
+ # 1,500 - 300 expenses - 900 capital allowances
+ taxable_self_employment_income: 300
+
+- name: No trading allowance before 2017-18
+ period: 2016
+ absolute_error_margin: 0
+ input:
+ self_employment_income: 600
+ output:
+ trading_allowance_deduction: 0
+ taxable_self_employment_income: 600
+
+- name: Receipts below profit are inconsistent and treated as unknown
+ period: 2027
+ absolute_error_margin: 0
+ input:
+ self_employment_gross_receipts: 500
+ self_employment_income: 1_500
+ output:
+ trading_allowance_deduction: 0
+ taxable_self_employment_income: 1_500
diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/bases/taxable_self_employment_income.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/bases/taxable_self_employment_income.yaml
index eb0a8fe786..61fd6bf0a9 100644
--- a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/bases/taxable_self_employment_income.yaml
+++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/bases/taxable_self_employment_income.yaml
@@ -49,7 +49,7 @@
period: 2024
input:
self_employment_income: 30000
- loss_relief: 10000
+ loss_relief: 15000
capital_allowances: 15000
gov.hmrc.income_tax.allowances.trading_allowance: 5000
output:
@@ -63,7 +63,7 @@
capital_allowances: 10000
gov.hmrc.income_tax.allowances.trading_allowance: 1000
output:
- taxable_self_employment_income: 34000
+ taxable_self_employment_income: 35000
- name: High self-employment income with some deductions
period: 2024
@@ -73,7 +73,7 @@
capital_allowances: 30000
gov.hmrc.income_tax.allowances.trading_allowance: 1000
output:
- taxable_self_employment_income: 149000
+ taxable_self_employment_income: 150000
- name: Low self-employment income with low deductions
period: 2024
@@ -83,7 +83,7 @@
capital_allowances: 200
gov.hmrc.income_tax.allowances.trading_allowance: 300
output:
- taxable_self_employment_income: 400
+ taxable_self_employment_income: 700
- name: Deductions exceed self-employment income
period: 2024
@@ -103,7 +103,7 @@
capital_allowances: 300000
gov.hmrc.income_tax.allowances.trading_allowance: 1000
output:
- taxable_self_employment_income: 499000
+ taxable_self_employment_income: 500000
- name: Decimal values
period: 2024
@@ -113,7 +113,7 @@
capital_allowances: 5000.75
gov.hmrc.income_tax.allowances.trading_allowance: 1000.00
output:
- taxable_self_employment_income: 33999.50
+ taxable_self_employment_income: 34999.50
- name: Pre-net self-employment income can override trading allowance deduction
period: 2024
diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/child_benefit_hitc.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/child_benefit_hitc.yaml
index 03739d3610..c20990a727 100644
--- a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/child_benefit_hitc.yaml
+++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/child_benefit_hitc.yaml
@@ -48,5 +48,5 @@
output:
child_benefit: 2_337.40
CB_HITC: [2_337.40, 0, 0, 0]
- income_tax_pre_charges: [35_142, 4_486, 0, 0]
- income_tax: [37_479.40, 4_486, 0, 0]
+ income_tax_pre_charges: [35_742, 4_486, 0, 0]
+ income_tax: [38_079.40, 4_486, 0, 0]
diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/pension_contributions_relief.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/pension_contributions_relief.yaml
index bf97289922..dc3dc27731 100644
--- a/policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/pension_contributions_relief.yaml
+++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/pension_contributions_relief.yaml
@@ -20,7 +20,7 @@
input:
self_employment_income: 30_000
output:
- income_tax: 3_286
+ income_tax: 3_486
- name: £30k self-employment income, £3k private pension contributions
period: 2023
@@ -30,7 +30,7 @@
personal_pension_contributions: 3_000
output:
pension_contributions_relief: 3_000
- income_tax: 3286 - 3000 * 0.2
+ income_tax: 3486 - 3000 * 0.2
- name: £60k self-employment income, no private pension contributions
period: 2023
@@ -38,7 +38,7 @@
input:
self_employment_income: 60_000
output:
- income_tax: 11_032
+ income_tax: 11_432
- name: £60k self-employment income, £6k private pension contributions
period: 2023
@@ -48,4 +48,4 @@
personal_pension_contributions: 6_000
output:
pension_contributions_relief: 6_000
- income_tax: 11032 - 6000 * 0.4
+ income_tax: 11432 - 6000 * 0.4
diff --git a/policyengine_uk/tests/test_trading_allowance_properties.py b/policyengine_uk/tests/test_trading_allowance_properties.py
new file mode 100644
index 0000000000..08ca9bfe31
--- /dev/null
+++ b/policyengine_uk/tests/test_trading_allowance_properties.py
@@ -0,0 +1,193 @@
+"""Property tests for the trading allowance (ITTOIA 2005 Part 6A, Chapter 1).
+
+self_employment_income is profit after actual expenses. The allowance is an
+alternative to those expenses, so the model may only add the part of the
+allowance that actual deductions have not already used. These tests check
+that invariant against an independent statutory reference, plus bounds and
+monotonicity, over many generated cases in one reusable simulation.
+"""
+
+import numpy as np
+import pytest
+from hypothesis import HealthCheck, given, settings
+from hypothesis import strategies as st
+
+from policyengine_uk import Simulation
+
+YEAR = 2027
+ALLOWANCE = 1_000
+OTHER_EMPLOYMENT_INCOME = [0, 8_000, 30_000, 60_000, 110_000, 200_000]
+CASES = 24 # Each case is simulated twice: as drawn, and with more profit.
+PEOPLE = 2 * CASES
+
+amount = st.one_of(
+ st.floats(min_value=0, max_value=2_500),
+ st.floats(min_value=0, max_value=300_000),
+ st.sampled_from([0.0, 999.99, 1_000.0, 1_000.01, 12_570.0, 50_270.0]),
+)
+case = st.tuples(
+ st.one_of(amount, st.floats(min_value=-5_000, max_value=0)), # profit
+ amount, # actual expenses
+ st.sampled_from([0.0, 0.0, 150.0, 900.0, 5_000.0]), # capital allowances
+ st.sampled_from([0.0, 0.0, 0.0, 300.0, 2_000.0]), # loss relief
+ # Receipts: unknown, consistent (profit + expenses), or inconsistently
+ # below profit, which the model must treat as unknown.
+ st.sampled_from(["unknown", "consistent", "below_profit"]),
+ st.floats(min_value=0.01, max_value=0.99), # receipts share if below profit
+ st.floats(min_value=0, max_value=20_000), # extra profit for monotonicity
+)
+cases = st.lists(case, min_size=CASES, max_size=CASES)
+
+
+@pytest.fixture(scope="module")
+def sim():
+ people = {
+ f"person_{i}": {
+ "age": {YEAR: 40},
+ "employment_income": {
+ YEAR: OTHER_EMPLOYMENT_INCOME[i % CASES % len(OTHER_EMPLOYMENT_INCOME)]
+ },
+ "self_employment_income": {YEAR: 0},
+ "self_employment_gross_receipts": {YEAR: 0},
+ "capital_allowances": {YEAR: 0},
+ "loss_relief": {YEAR: 0},
+ }
+ for i in range(PEOPLE)
+ }
+ return Simulation(
+ situation={
+ "people": people,
+ "benunits": {
+ f"benunit_{i}": {"members": [f"person_{i}"]} for i in range(PEOPLE)
+ },
+ "households": {
+ f"household_{i}": {"members": [f"person_{i}"]} for i in range(PEOPLE)
+ },
+ }
+ )
+
+
+def run(sim, drawn):
+ profit, expenses, capital_allowances, loss_relief, mode, share, extra = map(
+ np.array, zip(*drawn)
+ )
+ profit = np.concatenate([profit, profit + extra])
+ expenses, capital_allowances, loss_relief, mode, share = (
+ np.concatenate([x, x])
+ for x in (expenses, capital_allowances, loss_relief, mode, share)
+ )
+ receipts = np.select(
+ [mode == "consistent", mode == "below_profit"],
+ [profit + expenses, profit * share],
+ 0,
+ )
+ sim.set_input("self_employment_income", YEAR, profit)
+ sim.set_input("self_employment_gross_receipts", YEAR, receipts)
+ sim.set_input("capital_allowances", YEAR, capital_allowances)
+ sim.set_input("loss_relief", YEAR, loss_relief)
+ sim.reset_calculations()
+ # Compare against the stored (float32) inputs, not the float64 draws.
+ profit = sim.calculate("self_employment_income", YEAR).astype(float)
+ receipts = sim.calculate("self_employment_gross_receipts", YEAR).astype(float)
+ capital_allowances = sim.calculate("capital_allowances", YEAR).astype(float)
+ loss_relief = sim.calculate("loss_relief", YEAR).astype(float)
+ return dict(
+ profit=profit,
+ expenses=np.maximum(receipts - profit, 0),
+ capital_allowances=capital_allowances,
+ loss_relief=loss_relief,
+ receipts=receipts,
+ known=(receipts > 0) & (receipts >= profit),
+ relief=sim.calculate("trading_allowance_deduction", YEAR),
+ taxable=sim.calculate("taxable_self_employment_income", YEAR),
+ income_tax=sim.calculate("income_tax", YEAR),
+ )
+
+
+def statutory_taxable_profit(receipts, expenses, capital_allowances, loss_relief):
+ """Taxable trade profit under Part 6A when receipts are known.
+
+ Receipts within the allowance are fully relieved (profits or losses are
+ nil, s. 783AF). Above it, the person takes the better of actual
+ deductions (expenses and capital allowances) and the allowance (s. 783AI);
+ the two never stack. Loss relief then applies to what remains.
+ """
+ best_deduction = np.maximum(expenses + capital_allowances, ALLOWANCE)
+ return np.where(
+ receipts <= ALLOWANCE,
+ 0,
+ np.maximum(0, receipts - best_deduction - loss_relief),
+ )
+
+
+def unknown_receipts_taxable_profit(profit, capital_allowances, loss_relief):
+ """Fallback when receipts are unknown: full relief only for a profit
+ within the allowance; a larger profit is taxed as reported."""
+ return np.where(
+ profit <= ALLOWANCE,
+ 0,
+ np.maximum(0, profit - capital_allowances - loss_relief),
+ )
+
+
+def tolerance(*values):
+ """A penny plus float32 rounding on the amounts involved."""
+ return 0.01 + 1e-6 * sum(np.abs(v) for v in values)
+
+
+SETTINGS = settings(
+ max_examples=40,
+ deadline=None,
+ suppress_health_check=[HealthCheck.too_slow],
+)
+
+
+@SETTINGS
+@given(cases)
+def test_relief_is_bounded_by_allowance_and_profit(sim, drawn):
+ r = run(sim, drawn)
+ assert (r["relief"] >= 0).all()
+ assert (r["relief"] <= ALLOWANCE + tolerance(ALLOWANCE)).all()
+ positive_profit = np.maximum(r["profit"], 0)
+ assert (r["relief"] <= positive_profit + tolerance(r["profit"])).all()
+
+
+@SETTINGS
+@given(cases)
+def test_taxable_profit_matches_statutory_reference(sim, drawn):
+ r = run(sim, drawn)
+ expected = np.where(
+ r["known"],
+ statutory_taxable_profit(
+ r["receipts"], r["expenses"], r["capital_allowances"], r["loss_relief"]
+ ),
+ unknown_receipts_taxable_profit(
+ r["profit"], r["capital_allowances"], r["loss_relief"]
+ ),
+ )
+ assert (
+ np.abs(r["taxable"] - expected) <= tolerance(r["receipts"], r["profit"])
+ ).all()
+
+
+@SETTINGS
+@given(cases)
+def test_allowance_never_stacks_on_actual_deductions(sim, drawn):
+ r = run(sim, drawn)
+ known = r["known"] & (r["receipts"] > ALLOWANCE)
+ deducted_from_receipts = r["receipts"] - r["taxable"]
+ actual_deductions = r["expenses"] + r["capital_allowances"]
+ limit = (
+ np.maximum(actual_deductions, ALLOWANCE)
+ + r["loss_relief"]
+ + tolerance(r["receipts"])
+ )
+ assert (deducted_from_receipts[known] <= limit[known]).all()
+
+
+@SETTINGS
+@given(cases)
+def test_income_tax_is_non_decreasing_in_profit(sim, drawn):
+ r = run(sim, drawn)
+ base, more = r["income_tax"][:CASES], r["income_tax"][CASES:]
+ assert (more >= base - tolerance(base)).all()
diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/allowances/trading_allowance_deduction.py b/policyengine_uk/variables/gov/hmrc/income_tax/allowances/trading_allowance_deduction.py
index 8a7ea36ce1..6fe3203318 100644
--- a/policyengine_uk/variables/gov/hmrc/income_tax/allowances/trading_allowance_deduction.py
+++ b/policyengine_uk/variables/gov/hmrc/income_tax/allowances/trading_allowance_deduction.py
@@ -5,15 +5,52 @@ class trading_allowance_deduction(Variable):
value_type = float
entity = Person
label = "Deduction applied by the trading allowance"
+ documentation = (
+ "Relief from the trading allowance beyond the expenses already "
+ "deducted in self_employment_income. The allowance replaces actual "
+ "expenses and capital allowances rather than adding to them: receipts "
+ "within the allowance are fully relieved, and above it the person may "
+ "elect to deduct the allowance from receipts instead. With gross "
+ "receipts supplied, the deduction is the allowance less actual "
+ "expenses and capital allowances, capped at profit. Without them, a "
+ "profit within the allowance is treated as coming from receipts within "
+ "it, and a larger profit as already reflecting the better of actual "
+ "expenses and the allowance, so taxable profit jumps from nil to the "
+ "full profit just above the allowance. Receipts below profit are "
+ "inconsistent and treated as unknown."
+ )
definition_period = YEAR
- reference = "Income Tax (Trading and Other Income) Act 2005 s. 783AF"
+ reference = [
+ dict(
+ title="Income Tax (Trading and Other Income) Act 2005, s. 783AF (full relief)",
+ href="https://www.legislation.gov.uk/ukpga/2005/5/section/783AF",
+ ),
+ dict(
+ title="Income Tax (Trading and Other Income) Act 2005, s. 783AI (partial relief)",
+ href="https://www.legislation.gov.uk/ukpga/2005/5/section/783AI",
+ ),
+ dict(
+ title="HMRC Business Income Manual BIM86050",
+ href="https://www.gov.uk/hmrc-internal-manuals/business-income-manual/bim86050",
+ ),
+ ]
unit = GBP
def formula(person, period, parameters):
- trading_allowance = parameters(
- period
- ).gov.hmrc.income_tax.allowances.trading_allowance
- return min_(
- trading_allowance,
- person("self_employment_income", period),
+ allowance = parameters(period).gov.hmrc.income_tax.allowances.trading_allowance
+ profit = person("self_employment_income", period)
+ receipts = person("self_employment_gross_receipts", period)
+ capital_allowances = person("capital_allowances", period)
+ # Receipts below profit (including the default zero) mean unknown.
+ receipts_known = (receipts > 0) & (receipts >= profit)
+ expenses = max_(receipts - profit, 0)
+ # Full relief (receipts within the allowance, s. 783AE-783AF) makes
+ # the profit nil; partial relief (s. 783AI) replaces expenses and
+ # capital allowances with the allowance. Both come to the allowance's
+ # excess over actual deductions, capped at profit.
+ relief_with_receipts = min_(profit, allowance - expenses) - capital_allowances
+ relief_without_receipts = where(
+ profit <= allowance, profit - capital_allowances, 0
)
+ relief = where(receipts_known, relief_with_receipts, relief_without_receipts)
+ return max_(relief, 0)
diff --git a/policyengine_uk/variables/input/self_employment_gross_receipts.py b/policyengine_uk/variables/input/self_employment_gross_receipts.py
new file mode 100644
index 0000000000..c7ff04c188
--- /dev/null
+++ b/policyengine_uk/variables/input/self_employment_gross_receipts.py
@@ -0,0 +1,22 @@
+from policyengine_uk.model_api import *
+
+
+class self_employment_gross_receipts(Variable):
+ value_type = float
+ entity = Person
+ label = "self-employment gross receipts"
+ documentation = (
+ "Gross receipts (turnover) of the person's trades before expenses. "
+ "Optional: used only to apply the trading allowance, which is measured "
+ "against gross receipts and replaces actual expenses. Must be at least "
+ "self_employment_income; leave at zero when unknown. "
+ "self_employment_income remains the profit used everywhere else."
+ )
+ definition_period = YEAR
+ unit = GBP
+ reference = dict(
+ title="Income Tax (Trading and Other Income) Act 2005, s. 783AC",
+ href="https://www.legislation.gov.uk/ukpga/2005/5/section/783AC",
+ )
+ quantity_type = FLOW
+ uprating = "gov.economic_assumptions.indices.obr.per_capita.mixed_income"