Functional Weave
Code in Python

finance.tax.vat-flat-rate Unreviewed

UK VAT Flat Rate Scheme: VAT due on gross turnover at the sector's dated rate, limited cost rate and first-year 1% off.

1.0.1 · published 2026-10-03 by charlie · Anterra

Pinned by 23 tests, run in TypeScript, Python and Rust.

Unreviewed. This capability’s implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified chartered accountant or tax adviser has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.

Not professional advice. This capability calculates tax figures from published rules. It is a software component for developers, not tax advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed in its README, and have a chartered accountant or tax adviser review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.

What it does

Under the VAT Flat Rate Scheme a small business pays HMRC a fixed percentage of its VAT-inclusive turnover instead of output tax less input tax. The percentage depends on the trade sector and changes over time, so it is dated data here, and the function takes the supply date.

**Which rate.** The sector's rate in force on the supply date (tax point), from `data/flat-rates.json`. A limited cost business uses 16.5% whatever its sector, from 1 April 2017 (VAT Notice 733, para 4.4); asking for it before that date is an error, since the rate did not exist. Whether a business is a limited cost business (relevant goods under 2% of turnover, or under GBP 1,000 a year, GBP 250 a quarter) is decided per return period by the caller and passed in; the sector is still checked so that a typo is caught.

For example

  • flat_rate_vat(£12,000.00, accountancy-or-book-keeping, false, —, 2026-05-01) → sector accountancy-or-book-keeping, limited cost business false, first year reduction false, rate 14.5%, vat due £1,740.00 accountancy at 14.5% on 12,000.00 gross turnover
  • flat_rate_vat(£12,000.00, accountancy-or-book-keeping, true, —, 2026-05-01) → sector accountancy-or-book-keeping, limited cost business true, first year reduction false, rate 16.5%, vat due £1,980.00 a limited cost business pays 16.5% whatever its sector
  • flat_rate_vat(£12,000.00, accountancy-or-book-keeping, false, 2026-01-15, 2026-05-01) → sector accountancy-or-book-keeping, limited cost business false, first year reduction true, rate 13.5%, vat due £1,620.00 first year of registration: 1% off

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def flat_rate_vat(turnover: Money, sector: str, limited_cost_business: bool, registration_date: Optional[str], supply_date: str) -> FlatRateVat
turnoverMoneyVAT-inclusive flat rate turnover (standard, reduced, zero-rated and exempt supplies), in GBP
sectorstringHMRC trade sector as a key, e.g. "accountancy-or-book-keeping"; see data/flat-rates.json
limited_cost_businessboolwhether the business is a limited cost business for the period (16.5% from 1 April 2017)
registration_datedate?date of VAT registration when entitled to the first-year 1% reduction; null otherwise
supply_datedatetax point of the supplies; the rate in force that day applies
returnsFlatRateVat

The type it declares, generated into your project

@dataclass(frozen=True)
class FlatRateVat:
    """The rate that applied and the VAT it gives."""

    sector: str
    limited_cost_business: bool
    #: whether the 1% first-year reduction was taken off
    first_year_reduction: bool
    #: the flat rate applied, after any reduction
    basis_points: int
    #: turnover at the flat rate, rounded half-up to the penny
    vat_due: Money

Your code names it in one line, in the file that uses it

from fune.finance.tax.vat_flat_rate import flat_rate_vat  # finance.tax.vat-flat-rate@^1
impl/python.py · 61 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from typing import Optional

from .dates_add_days import add_days  ← from dates.add-days ^1.0.0 · built alongside by fune
from .dates_add_months import add_months  ← from dates.add-months ^1.0.0 · built alongside by fune
from .finance_tax_vat_flat_rate_data import FLAT_RATES, FLAT_RATES_HISTORY  ← this capability’s own data, compiled from data/flat-rates.json into the same file by fune build
from .finance_tax_vat_flat_rate_types import FlatRateVat
from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune
from .money_apply_rate import apply_rate  ← from money.apply-rate ^1.0.0 · built alongside by fune

_LIMITED_COST = "limited-cost-business"


def _rate_on(sector: str, on_date: str) -> int:
    rows = [r for r in FLAT_RATES if r.sector == sector]
    for row in rows:
        if on_date >= row.valid_from and (row.valid_to is None or on_date <= row.valid_to):
            return row.basis_points
    if FLAT_RATES_HISTORY != "full" and len(rows) > 0:
        earliest = min(r.valid_from for r in rows)
        if on_date < earliest:
            raise ValueError(
                "no flat rate for %s on %s: this build was installed with history=%s, so it only carries rates from %s. "
                "Reinstall with history=full for older periods." % (sector, on_date, FLAT_RATES_HISTORY, earliest)
            )
    raise ValueError("no flat rate for %s on %s" % (sector, on_date))


def flat_rate_vat(
    turnover: Money,
    sector: str,
    limited_cost_business: bool,
    registration_date: Optional[str],
    supply_date: str,
) -> FlatRateVat:
    """VAT due under the Flat Rate Scheme on a slice of VAT-inclusive turnover.

    The rate is the one in force on the supply date; the first-year reduction
    runs to the day before the first anniversary of registration.
    """
    if turnover.currency != "GBP":
        raise ValueError("the flat rate scheme is for GBP turnover, received %s" % (turnover.currency,))
    add_days(supply_date, 0)
    if sector == _LIMITED_COST or not any(r.sector == sector for r in FLAT_RATES):
        raise ValueError('unknown flat rate sector "%s"' % (sector,))
    base = _rate_on(_LIMITED_COST if limited_cost_business else sector, supply_date)

    reduced = False
    if registration_date is not None:
        if supply_date < add_days(registration_date, 0):
            raise ValueError(
                "supply date %s is before the VAT registration date %s" % (supply_date, registration_date)
            )
        reduced = supply_date < add_months(registration_date, 12)
    basis_points = max(base - 100, 0) if reduced else base
    return FlatRateVat(
        sector=sector,
        limited_cost_business=limited_cost_business,
        first_year_reduction=reduced,
        basis_points=basis_points,
        vat_due=money(apply_rate(turnover, basis_points, "half-up").minor, "GBP"),
    )

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 4 dependencies, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add finance.tax.vat-flat-rate
Download for Python finance.tax.vat-flat-rate-1.0.1-python.fune · 32,914 bytes sha256 c40eaac101702b08e93aaa288321b743190792d41f40747e6b84e3f8031a4581

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./finance.tax.vat-flat-rate-1.0.1-python.fune, or fetch it from a terminal with fune pull finance.tax.vat-flat-rate@1.0.1:python.

The whole function, every language, is one file too: finance.tax.vat-flat-rate-1.0.1.fune, 39,155 bytes, sha256 8732114a4b9325f0257d3e3481a85f460a71b16a8c4f8260f2fb63c2ae0a7819. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before finance.tax.vat-flat-rate

after — your function gets the result and the arguments, and returns the final result.

# fune: after finance.tax.vat-flat-rate

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

# fune: replace dates.add-days in finance.tax.vat-flat-rate
# fune: replace dates.add-months in finance.tax.vat-flat-rate
# fune: replace money.amount in finance.tax.vat-flat-rate
# fune: replace money.apply-rate in finance.tax.vat-flat-rate

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show finance.tax.vat-flat-rate --steps.

# fune: step finance.tax.vat-flat-rate after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
accountancy at 14.5% on 12,000.00 gross turnover £12,000.00, accountancy-or-book-keeping, false, —, 2026-05-01 → sector accountancy-or-book-keeping, limited cost business false, first year reduction false, rate 14.5%, vat due £1,740.00
a limited cost business pays 16.5% whatever its sector £12,000.00, accountancy-or-book-keeping, true, —, 2026-05-01 → sector accountancy-or-book-keeping, limited cost business true, first year reduction false, rate 16.5%, vat due £1,980.00
first year of registration: 1% off £12,000.00, accountancy-or-book-keeping, false, 2026-01-15, 2026-05-01 → sector accountancy-or-book-keeping, limited cost business false, first year reduction true, rate 13.5%, vat due £1,620.00
first year off the limited cost rate: 15.5%, as in Notice 733 Example 1 £12,000.00, accountancy-or-book-keeping, true, 2017-04-01, 2018-03-31 → sector accountancy-or-book-keeping, limited cost business true, first year reduction true, rate 15.5%, vat due £1,860.00
the reduction runs to the day before the first anniversary £10,000.00, accountancy-or-book-keeping, false, 2025-06-01, 2026-05-31 → sector accountancy-or-book-keeping, limited cost business false, first year reduction true, rate 13.5%, vat due £1,350.00
and ends on the anniversary £10,000.00, accountancy-or-book-keeping, false, 2025-06-01, 2026-06-01 → sector accountancy-or-book-keeping, limited cost business false, first year reduction false, rate 14.5%, vat due £1,450.00
catering the day before the covid cut £10,000.00, catering-services-including-restaurants-and-takeaways, false, —, 2020-07-14 → sector catering-services-including-restaurants-and-takeaways, limited cost business false, first year reduction false, rate 12.5%, vat due £1,250.00
catering during the covid cut: 4.5% £10,000.00, catering-services-including-restaurants-and-takeaways, false, —, 2020-12-01 → sector catering-services-including-restaurants-and-takeaways, limited cost business false, first year reduction false, rate 4.5%, vat due £450.00
catering during the taper: 8.5% £10,000.00, catering-services-including-restaurants-and-takeaways, false, —, 2021-12-01 → sector catering-services-including-restaurants-and-takeaways, limited cost business false, first year reduction false, rate 8.5%, vat due £850.00
catering back at 12.5% from 1 April 2022 £10,000.00, catering-services-including-restaurants-and-takeaways, false, —, 2022-04-01 → sector catering-services-including-restaurants-and-takeaways, limited cost business false, first year reduction false, rate 12.5%, vat due £1,250.00
Show the other 13 tests
CaseArgumentsExpected
the first-year reduction cannot take the 0% hotel rate below zero £10,000.00, hotel-or-accommodation, false, 2021-01-01, 2021-03-01 → sector hotel-or-accommodation, limited cost business false, first year reduction true, rate 0%, vat due £0.00
pubs at 1% in 2021 £10,000.00, pubs, false, —, 2021-06-30 → sector pubs, limited cost business false, first year reduction false, rate 1%, vat due £100.00
the VAT is rounded half-up once: 14.5% of 1.00 is 14.5p, so 15p £1.00, accountancy-or-book-keeping, false, —, 2026-05-01 → sector accountancy-or-book-keeping, limited cost business false, first year reduction false, rate 14.5%, vat due £0.15
14.5% of 123.45 is 17.90 £123.45, accountancy-or-book-keeping, false, —, 2026-05-01 → sector accountancy-or-book-keeping, limited cost business false, first year reduction false, rate 14.5%, vat due £17.90
a credit note reduces the VAT due -£100.00, accountancy-or-book-keeping, false, —, 2026-05-01 → sector accountancy-or-book-keeping, limited cost business false, first year reduction false, rate 14.5%, vat due -£14.50
the first day of the 2011 table £10,000.00, retailing-food-confectionery-tobacco-newspapers-or-childrens-clothing, false, —, 2011-01-04 → sector retailing-food-confectionery-tobacco-newspapers-or-childrens-clothing, limited cost business false, first year reduction false, rate 4%, vat due £400.00
the limited cost rate did not exist before 1 April 2017 £10.00, accountancy-or-book-keeping, true, —, 2017-03-31 → error: no flat rate for limited-cost-business on 2017-03-31
dates before the 2011 table are refused £10.00, accountancy-or-book-keeping, false, —, 2011-01-03 → error: no flat rate for accountancy-or-book-keeping on 2011-01-03
an unknown sector is refused £10.00, accountancy, false, —, 2026-05-01 → error: unknown flat rate sector "accountancy"
the limited cost rate is not a sector £10.00, limited-cost-business, false, —, 2026-05-01 → error: unknown flat rate sector "limited-cost-business"
turnover must be in GBP €10.00, accountancy-or-book-keeping, false, —, 2026-05-01 → error: the flat rate scheme is for GBP turnover, received EUR
a supply before registration is refused £10.00, accountancy-or-book-keeping, false, 2026-06-01, 2026-05-31 → error: supply date 2026-05-31 is before the VAT registration date 2026-06-01
an impossible supply date is refused £10.00, accountancy-or-book-keeping, false, —, 2026-02-29 → error: not a real calendar date

More from the author

**First-year reduction.** In the first year of VAT registration 1 percentage point comes off the flat rate "until the day before your first anniversary of becoming VAT registered" (para 4.7), including off the limited cost rate (the Notice's Example 1: 16.5% becomes 15.5%). Pass the registration date when the business is entitled to it and null when it is not (registering late, or more than a year before joining the scheme, loses it). A supply date before the registration date is an error. The reduction cannot take a rate below 0%: the 0% hotel rate of 2020-21 stays 0%.

**Rate changes inside a period.** Notice 733 para 4.12 requires two calculations when a rate changes during a VAT period; call this once for each part of the turnover. The same goes for a period that straddles the end of the first year.

**Rounding.** The VAT due is the turnover times the rate, rounded half-up to the penny once. Notice 733 does not state a rounding rule; this should be confirmed by a VAT adviser. Negative turnover (credit notes) gives negative VAT.

## Data and sources

- Sector rates: HMRC, "VAT Flat Rate Scheme: how much you pay", https://www.gov.uk/vat-flat-rate-scheme/how-much-you-pay, read on 23 September 2026, including the temporary hospitality rates: catering 4.5% and hotels 0% and pubs 1% from 15 July 2020 to 30 September 2021, then 8.5%, 5.5% and 4% to 31 March 2022. - The current table dates from 4 January 2011, when it was substituted by SI 2010/2940 with the rise of VAT to 20% (VAT Regulations 1995, reg 55K, https://www.legislation.gov.uk/uksi/1995/2518/regulation/55K). Earlier rates are not included, so a supply date before 4 January 2011 is refused. - Limited cost business rate and first-year reduction: VAT Notice 733, "Flat Rate Scheme for small businesses", paras 4.4, 4.7 and 4.12, https://www.gov.uk/guidance/flat-rate-scheme-for-small-businesses-vat-notice-733--2 - Reg 55K on legislation.gov.uk also lists "Dealing in waste or scrap" at 10.5%, which gov.uk's table does not; it is left out until confirmed.

Sector keys are HMRC's sector names in lower case with punctuation turned into hyphens ("Retailing food, confectionery, tobacco, newspapers or children's clothing" is `retailing-food-confectionery-tobacco-newspapers-or-childrens-clothing`). When a rate changes, publish a new version with a new row. The data declares its effective dates, so a `history=current` build refuses older supply dates with an explanation instead of answering them at today's rate.

## Before you rely on this

**Not professional advice.** This capability calculates tax figures from published rules. It is a software component for developers, not tax advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed above, and have a chartered accountant or tax adviser review how you use it, before anyone relies on the output. Provided "as is" under its licence, without warranty.

**Unreviewed.** This capability's implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified chartered accountant or tax adviser has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.

## Notices

Contains public sector information licensed under the Open Government Licence v3.0 (https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).

Legislation: Crown copyright and database right.

1.0.1 marks it unreviewed and adds its attribution notices (NOTICE). The code and the tests are unchanged.

Files

PathBytes
NOTICE238
README.md4,732
data/flat-rates.json12,138
impl/python.py2,568
impl/rust.rs3,493
impl/typescript.ts2,504
vectors.json7,336