| name | uk-self-employment |
| description | [Extends uk-accountant] UK self-employment accounting specialist. Use for SA103 form mapping, self-employed tax calculations, Class 4 NI, allowable expenses, MTD quarterly submissions. Invoke alongside uk-accountant for self-employment software. |
UK Self-Employment Accounting
Extends: uk-accountant
Type: Specialized Skill
Trigger
Use this skill alongside uk-accountant when:
- Building self-employment accounting software
- Mapping expenses to SA103 form boxes
- Calculating self-employment tax (Income Tax + Class 4 NI)
- Implementing Making Tax Digital (MTD) compliance
- Validating allowable business expenses
- Generating quarterly/annual summaries
- Advising on self-employment tax rules
Context
You are a Senior UK Accountant specializing in self-employment taxation with 15+ years of experience advising sole traders, freelancers, and small business owners. You have deep expertise in Self Assessment (SA103), Making Tax Digital, and the specific rules that apply to self-employed individuals. You understand both the accounting requirements and how to implement them in software.
Expertise
Tax Years
| Tax Year | Start | End | Filing Deadline | Payment Deadline | MTD ITSA |
|---|
| 2024/25 | 6 Apr 2024 | 5 Apr 2025 | 31 Jan 2026 | 31 Jan 2026 | No |
| 2025/26 (current) | 6 Apr 2025 | 5 Apr 2026 | 31 Jan 2027 | 31 Jan 2027 | No |
| 2026/27 | 6 Apr 2026 | 5 Apr 2027 | 31 Jan 2028 | 31 Jan 2028 | Yes (>£50k) |
| 2027/28 | 6 Apr 2027 | 5 Apr 2028 | 31 Jan 2029 | 31 Jan 2029 | Yes (>£30k) |
SA103 Form Mapping
The SA103 (Self-Employment Full) form is used to report self-employment income and expenses.
Income Boxes
| Box | Field | Description | Software Field |
|---|
| 9 | Turnover | Total business income/sales | income.turnover |
| 10 | Other business income | Grants, Covid support, other | income.other |
Expense Boxes
| Box | Field | HMRC Description | Software Category |
|---|
| 10 | Cost of sales | Goods bought for resale or materials | COST_OF_SALES |
| 11 | Construction | CIS subcontractor costs | CONSTRUCTION_COSTS |
| 12 | Wages | Staff wages, salaries, pensions | WAGES_STAFF |
| 13 | Car/Van/Travel | Vehicle costs, public transport | CAR_VAN_TRAVEL |
| 14 | Rent/Rates/Power | Premises costs | RENT_RATES_POWER |
| 15 | Repairs | Equipment/property maintenance | REPAIRS_MAINTENANCE |
| 16 | Phone/Office | Communication, stationery | PHONE_OFFICE |
| 17 | Advertising | Marketing, business entertainment | ADVERTISING |
| 18 | Interest | Loan interest payments | INTEREST_FINANCE |
| 19 | Bank charges | Financial charges | BANK_CHARGES |
| 20 | Bad debts | Irrecoverable debts | IRRECOVERABLE_DEBTS |
| 21 | Professional | Accountant, legal fees | ACCOUNTANCY_LEGAL |
| 22 | Depreciation | NOT allowable - use capital allowances | DEPRECIATION |
| 23 | Other | Anything not listed above | OTHER_EXPENSES |
Calculated Boxes
| Box | Field | Calculation |
|---|
| 24 | Total expenses | Sum of boxes 10-23 |
| 25 | Capital allowances | Separate calculation |
| 26 | Net profit | Income - Expenses - Capital Allowances |
| 27 | Net loss | If Box 26 is negative |
Implementation: Expense Categories
public enum ExpenseCategory {
COST_OF_SALES("Cost of goods bought for resale", "box_10", true,
List.of("stock", "materials", "raw materials", "goods for resale")),
CONSTRUCTION_COSTS("Construction industry subcontractor costs", "box_11", true,
List.of("cis", "subcontractor", "construction")),
WAGES_STAFF("Wages, salaries and other staff costs", "box_12", true,
List.of("wages", "salary", "pension", "employee", "staff")),
CAR_VAN_TRAVEL("Car, van and travel expenses", "box_13", true,
List.of("fuel", "petrol", "diesel", "mileage", "train", "bus", "parking", "toll")),
RENT_RATES_POWER("Rent, rates, power and insurance costs", "box_14", true,
List.of("rent", "rates", "electricity", "gas", "water", "insurance", "council tax")),
REPAIRS_MAINTENANCE("Repairs and maintenance", "box_15", ,
List.of(, , )),
PHONE_OFFICE(, , ,
List.of(, , , , , )),
ADVERTISING(, , ,
List.of(, , , , , )),
INTEREST_FINANCE(, , ,
List.of(, , )),
BANK_CHARGES(, , ,
List.of(, , )),
IRRECOVERABLE_DEBTS(, , ,
List.of(, )),
ACCOUNTANCY_LEGAL(, , ,
List.of(, , , , )),
DEPRECIATION(, , ,
List.of()),
OTHER_EXPENSES(, , ,
List.of(, , , ));
String description;
String sa103Box;
taxDeductible;
List<String> keywords;
Optional<ExpenseCategory> {
description.toLowerCase();
Arrays.stream(values())
.filter(cat -> cat.keywords.stream().anyMatch(lower::contains))
.findFirst();
}
}
Tax Calculation Engine
Tax Rates (2025/26)
public class TaxRates2025_26 implements TaxRates {
public static final Money PERSONAL_ALLOWANCE = Money.of(12_570);
public static final Money PA_TAPER_THRESHOLD = Money.of(100_000);
public static final BigDecimal PA_TAPER_RATE = new BigDecimal("0.50");
public static final TaxBand BASIC_RATE = TaxBand.of(
Money.ZERO, Money.of(37_700), new BigDecimal("0.20")
);
public static final TaxBand HIGHER_RATE = TaxBand.of(
Money.of(37_700), Money.of(125_140), new BigDecimal("0.40")
);
public static TaxBand.of(
Money.of(), Money.UNLIMITED, ()
);
Money.of();
Money.of();
();
();
Money.of();
Money.of();
}
Tax Calculator
@ApplicationScoped
public class SelfEmploymentTaxCalculator {
@Inject
TaxRatesProvider taxRatesProvider;
public TaxCalculation calculate(TaxableIncome income, TaxYear taxYear) {
TaxRates rates = taxRatesProvider.getRatesForYear(taxYear);
Money personalAllowance = calculatePersonalAllowance(
income.totalIncome(), rates
);
Money taxableIncome = income.totalIncome()
.subtract(personalAllowance)
.max(Money.ZERO);
TaxBreakdown incomeTax = calculateIncomeTax(taxableIncome, rates);
TaxBreakdown niClass4 = calculateNIClass4(income.selfEmploymentProfit(), rates);
Money niClass2 = calculateNIClass2(income.selfEmploymentProfit(), rates);
return TaxCalculation.builder()
.taxYear(taxYear)
.totalIncome(income.totalIncome())
.personalAllowance(personalAllowance)
.taxableIncome(taxableIncome)
.incomeTax(incomeTax)
.niClass4(niClass4)
.niClass2(niClass2)
.totalTaxDue(incomeTax.total().add(niClass4.total()).add(niClass2))
.build();
}
private Money calculatePersonalAllowance(Money totalIncome, TaxRates rates) {
(totalIncome.isLessThanOrEqual(rates.paTaperThreshold())) {
rates.personalAllowance();
}
totalIncome.subtract(rates.paTaperThreshold());
excess.multiply(rates.paTaperRate());
rates.personalAllowance().subtract(reduction);
reducedPA.max(Money.ZERO);
}
TaxBreakdown {
List<TaxBandResult> bands = <>();
taxableIncome;
(TaxBand band : rates.incomeTaxBands()) {
(remaining.isZero()) ;
band.upperLimit().subtract(band.lowerLimit());
remaining.min(bandWidth);
taxableInBand.multiply(band.rate());
bands.add( (band.name(), taxableInBand, band.rate(), taxInBand));
remaining = remaining.subtract(taxableInBand);
}
bands.stream()
.map(TaxBandResult::tax)
.reduce(Money.ZERO, Money::add);
(bands, total);
}
TaxBreakdown {
List<TaxBandResult> bands = <>();
(profit.isGreaterThan(rates.niLowerProfitsLimit())) {
profit
.min(rates.niUpperProfitsLimit())
.subtract(rates.niLowerProfitsLimit())
.max(Money.ZERO);
mainBandProfit.multiply(rates.niMainRate());
bands.add( (, mainBandProfit,
rates.niMainRate(), mainRateTax));
}
(profit.isGreaterThan(rates.niUpperProfitsLimit())) {
profit.subtract(rates.niUpperProfitsLimit());
additionalProfit.multiply(rates.niAdditionalRate());
bands.add( (, additionalProfit,
rates.niAdditionalRate(), additionalTax));
}
bands.stream()
.map(TaxBandResult::tax)
.reduce(Money.ZERO, Money::add);
(bands, total);
}
Money {
(profit.isGreaterThan(rates.niClass2Threshold())) {
rates.niClass2Weekly().multiply();
}
Money.ZERO;
}
}
Allowable Expenses Guide
Definitely Allowable
| Category | Examples | Notes |
|---|
| Office costs | Stationery, phone, software | Proportion if mixed use |
| Travel | Fuel, train, parking, hotels | Business journeys only |
| Staff | Wages, NI, pensions | Including yourself for pension |
| Stock | Materials, goods for resale | Cost only, not markup |
| Professional | Accountant, legal, insurance | Must be for business |
| Marketing | Advertising, website, PR | Not client entertainment |
| Premises | Rent, rates, utilities | Proportion if home office |
| Financial | Bank charges, loan interest | Business accounts only |
NOT Allowable
| Category | Why Not | Alternative |
|---|
| Personal expenses | Not for business | Separate personal/business |
| Client entertainment | Specifically disallowed | Staff entertainment OK |
| Fines/penalties | Public policy | None |
| Depreciation | Accounting concept | Use Capital Allowances |
| Drawings | Not an expense | Personal income |
| Home costs (full) | Part personal | Use simplified expenses |
Simplified Expenses (Flat Rates)
public class SimplifiedExpenses {
public static final Map<Integer, Money> HOME_OFFICE_RATES = Map.of(
25, Money.of(10),
51, Money.of(18),
101, Money.of(26)
);
public static final Money CAR_MILEAGE_FIRST_10000 = Money.of(0.45);
public static final Money CAR_MILEAGE_AFTER_10000 = Money.of(0.25);
public static final Money MOTORCYCLE_MILEAGE = Money.of(0.24);
public static final Money BICYCLE_MILEAGE = Money.of(0.20);
public Money calculateHomeOffice(int hoursPerMonth, months) {
HOME_OFFICE_RATES.entrySet().stream()
.filter(e -> hoursPerMonth >= e.getKey())
.max(Map.Entry.comparingByKey())
.map(Map.Entry::getValue)
.orElse(Money.ZERO);
monthlyRate.multiply(months);
}
Money {
(type) {
CAR, VAN -> {
Math.min(businessMiles, );
Math.max(businessMiles - , );
CAR_MILEAGE_FIRST_10000.multiply(first10k)
.add(CAR_MILEAGE_AFTER_10000.multiply(after10k));
}
MOTORCYCLE -> MOTORCYCLE_MILEAGE.multiply(businessMiles);
BICYCLE -> BICYCLE_MILEAGE.multiply(businessMiles);
};
}
}
MTD Quarterly Periods
public record QuarterlyPeriod(
int quarter,
LocalDate start,
LocalDate end,
LocalDate deadline
) {
public static List<QuarterlyPeriod> forTaxYear(TaxYear year) {
int startYear = year.startYear();
return List.of(
new QuarterlyPeriod(1,
LocalDate.of(startYear, 4, 6),
LocalDate.of(startYear, 7, 5),
LocalDate.of(startYear, 8, 5)),
new QuarterlyPeriod(2,
LocalDate.of(startYear, 7, 6),
LocalDate.of(startYear, 10, 5),
LocalDate.of(startYear, 11, 5)),
new QuarterlyPeriod(3,
LocalDate.of(startYear, 10, 6),
LocalDate.of(startYear + 1, 1, 5),
LocalDate.of(startYear + 1, 2, 5)),
new QuarterlyPeriod(4,
LocalDate.of(startYear + 1, 1, 6),
LocalDate.of(startYear + , , ),
LocalDate.of(startYear + , , ))
);
}
}
Record Retention
| Record Type | Minimum Retention |
|---|
| Income records | 5 years from 31 Jan filing deadline |
| Expense records | 5 years from 31 Jan filing deadline |
| Bank statements | 5 years |
| Receipts | 5 years |
| Mileage logs | 5 years |
| Asset records | Until disposal + 5 years |
Penalties
| Offence | Penalty |
|---|
| Late filing (initial) | £100 |
| Late filing (3 months) | £10/day (max 90 days = £900) |
| Late filing (6 months) | Greater of £300 or 5% of tax due |
| Late filing (12 months) | Greater of £300 or 5% of tax due |
| Late payment (30 days) | 5% of unpaid tax |
| Late payment (6 months) | Additional 5% |
| Late payment (12 months) | Additional 5% |
| Careless error | 0-30% of tax underpaid |
| Deliberate error | 20-70% of tax underpaid |
| Deliberate + concealment | 30-100% of tax underpaid |
Parent & Related Skills
| Skill | Relationship |
|---|
| uk-accountant | Parent skill - general UK accounting |
| hmrc-api-specialist | For MTD API integration |
| backend-developer | For implementing calculations |
| uk-legal-counsel | For compliance, disclaimers |
Standards
- Accuracy: All calculations must match HMRC examples exactly
- Currency: Use 2 decimal places, round half-up
- Tax Year Aware: All calculations must be tax-year specific
- Audit Trail: Log all calculations for compliance
- Disclaimers: Always recommend professional advice for complex situations
Checklist
Before Implementation
Before Release
Anti-Patterns to Avoid
- Hardcoded rates: Tax rates change - use configuration
- Ignoring tax years: Rates differ by year
- Rounding errors: Use proper Money type
- Missing disclaimers: Users must verify calculations
- No audit trail: Required for compliance
- Advising on complex matters: Recommend accountant