Functional Weave
Code in Python

payroll.redundancy-pay Unreviewed

Statutory redundancy pay from age, completed years of service (at most 20) and a week's pay capped by date.

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

Pinned by 13 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 payroll specialist 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 payroll figures from published rules. It is a software component for developers, not tax or legal 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 payroll specialist review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.

What it does

Statutory redundancy pay in Great Britain (Employment Rights Act 1996 s.162):

- Count the completed years of continuous employment ending with the relevant date (hr.length-of-service, both ends inclusive). Under two years there is no entitlement (s.155). - Reckon **backwards** from the relevant date, at most 20 years (s.162(3)). - Each year earns half a week's pay if the employee was under 22 during it, a week's pay if 22 to 40, and a week and a half if 41 or over (s.162(2)). - A week's pay is capped (s.227(1)) at the figure in force on the relevant date.

For example

  • statutory_redundancy_pay(1980-05-10, 2016-09-01, 2026-09-23, £600.00) → eligible true, service years 10, years counted 10, half weeks 25, weekly pay used £600.00, amount £7,500.00 ten years straddling age 41: five years at 1.5 weeks, five at 1
  • statutory_redundancy_pay(1980-05-10, 2016-09-01, 2026-09-23, £900.00) → eligible true, service years 10, years counted 10, half weeks 25, weekly pay used £751.00, amount £9,387.50 the same employee on higher pay is capped at 751 pounds from April 2026
  • statutory_redundancy_pay(1980-05-10, 2016-09-01, 2026-04-05, £900.00) → eligible true, service years 9, years counted 9, half weeks 22, weekly pay used £719.00, amount £7,909.00 dismissed on 5 April 2026: the 2025 cap of 719 pounds and one year less

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 statutory_redundancy_pay(birth_date: str, start_date: str, relevant_date: str, weekly_pay: Money) -> RedundancyPay
birth_datedate
start_datedatefirst day of continuous employment
relevant_datedatethe relevant date under ERA 1996 s.145, usually the day employment ends; it picks the cap
weekly_payMoneya week's pay before the cap
returnsRedundancyPay

The type it declares, generated into your project

@dataclass(frozen=True)
class RedundancyPay:
    """The payment and how it was built."""

    #: at least two years' continuous service on the relevant date
    eligible: bool
    #: completed years of continuous service
    service_years: int
    #: years reckoned: at most the last 20, 0 when not eligible
    years_counted: int
    #: weeks of pay multiplied by 2 (1 per year under 22, 2 from 22, 3 from 41)
    half_weeks: int
    #: weeklyPay after the statutory cap
    weekly_pay_used: Money
    amount: Money

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

from fune.payroll.redundancy_pay import statutory_redundancy_pay  # payroll.redundancy-pay@^1
impl/python.py · 51 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 .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 .dates_age import age  ← from dates.age ^1.0.0 · built alongside by fune
from .hr_length_of_service import length_of_service  ← from hr.length-of-service ^1.0.0 · built alongside by fune
from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune
from .payroll_redundancy_pay_data import WEEK_PAY_CAPS, WEEK_PAY_CAPS_HISTORY  ← this capability’s own data, compiled from data/week-pay-caps.json into the same file by fune build
from .payroll_redundancy_pay_types import RedundancyPay


def _week_pay_cap(relevant_date: str) -> int:
    for row in WEEK_PAY_CAPS:
        if relevant_date >= row.valid_from and (row.valid_to is None or relevant_date <= row.valid_to):
            return row.cap_minor
    if WEEK_PAY_CAPS_HISTORY != "full":
        earliest = min((row.valid_from for row in WEEK_PAY_CAPS), default=None)
        if earliest is not None and relevant_date < earliest:
            raise ValueError(
                "no statutory week's pay cap for %s: this build was installed with history=%s, "
                "so it only carries caps from %s. Reinstall with history=full for older dismissals."
                % (relevant_date, WEEK_PAY_CAPS_HISTORY, earliest)
            )
    raise ValueError("no statutory week's pay cap for %s" % (relevant_date,))


def statutory_redundancy_pay(birth_date: str, start_date: str, relevant_date: str, weekly_pay: Money) -> RedundancyPay:
    """ERA 1996 s.162 redundancy pay. Years are reckoned backwards from the
    relevant date and each is priced by the age on its first day, so the rate
    steps up only for years spent wholly at 22 or 41 and over.
    """
    if weekly_pay.minor < 0:
        raise ValueError("weeklyPay must not be negative, received %s" % (weekly_pay.minor,))
    cap = _week_pay_cap(relevant_date)
    weekly_pay_used = money(min(weekly_pay.minor, cap), weekly_pay.currency)
    service_years = length_of_service(start_date, relevant_date).years
    eligible = service_years >= 2
    years_counted = min(service_years, 20) if eligible else 0
    half_weeks = 0
    for k in range(1, years_counted + 1):
        first_day = add_days(add_months(relevant_date, -12 * k), 1)
        age_then = age(birth_date, first_day)
        half_weeks += 3 if age_then >= 41 else 2 if age_then >= 22 else 1
    amount = money(round_div(half_weeks * weekly_pay_used.minor, 2, "half-up"), weekly_pay.currency)
    return RedundancyPay(
        eligible=eligible,
        service_years=service_years,
        years_counted=years_counted,
        half_weeks=half_weeks,
        weekly_pay_used=weekly_pay_used,
        amount=amount,
    )

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 6 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 payroll.redundancy-pay
Download for Python payroll.redundancy-pay-1.0.1-python.fune · 16,022 bytes sha256 8a84f3ff0d66f7dba0d2b2be84bb40b08ab020499462c4e5c4eec00b314d0a25

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./payroll.redundancy-pay-1.0.1-python.fune, or fetch it from a terminal with fune pull payroll.redundancy-pay@1.0.1:python.

The whole function, every language, is one file too: payroll.redundancy-pay-1.0.1.fune, 21,743 bytes, sha256 0959e1fee050e56472f1d7d7bb9e96fc665845510c0231d65be3aab98a03b682. 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 payroll.redundancy-pay

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

# fune: after payroll.redundancy-pay

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 payroll.redundancy-pay
# fune: replace dates.add-months in payroll.redundancy-pay
# fune: replace dates.age in payroll.redundancy-pay
# fune: replace hr.length-of-service in payroll.redundancy-pay
# fune: replace math.round-div in payroll.redundancy-pay
# fune: replace money.amount in payroll.redundancy-pay

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 payroll.redundancy-pay --steps.

# fune: step payroll.redundancy-pay 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
ten years straddling age 41: five years at 1.5 weeks, five at 1 1980-05-10, 2016-09-01, 2026-09-23, £600.00 → eligible true, service years 10, years counted 10, half weeks 25, weekly pay used £600.00, amount £7,500.00
the same employee on higher pay is capped at 751 pounds from April 2026 1980-05-10, 2016-09-01, 2026-09-23, £900.00 → eligible true, service years 10, years counted 10, half weeks 25, weekly pay used £751.00, amount £9,387.50
dismissed on 5 April 2026: the 2025 cap of 719 pounds and one year less 1980-05-10, 2016-09-01, 2026-04-05, £900.00 → eligible true, service years 9, years counted 9, half weeks 22, weekly pay used £719.00, amount £7,909.00
a day short of two years: nothing 1990-01-01, 2024-10-01, 2026-09-23, £600.00 → eligible false, service years 1, years counted 0, half weeks 0, weekly pay used £600.00, amount £0.00
exactly two years under 22: half a week each 2004-01-15, 2024-09-24, 2026-09-23, £450.00 → eligible true, service years 2, years counted 2, half weeks 2, weekly pay used £450.00, amount £450.00
36 years' service counts only the last 20: the 22,530 pound maximum 1960-01-01, 1990-01-01, 2026-09-23, £800.00 → eligible true, service years 36, years counted 20, half weeks 60, weekly pay used £751.00, amount £22,530.00
age is taken at the start of each year, not on the relevant date 2000-09-24, 2020-01-01, 2026-09-23, £500.00 → eligible true, service years 6, years counted 6, half weeks 10, weekly pay used £500.00, amount £2,500.00
one year at 41 and a half penny rounded up 1984-09-24, 2016-09-24, 2026-09-23, £500.01 → eligible true, service years 10, years counted 10, half weeks 21, weekly pay used £500.01, amount £5,250.11
a 2023 dismissal uses the 643 pound cap 1970-01-01, 2013-10-02, 2023-10-02, £700.00 → eligible true, service years 10, years counted 10, half weeks 30, weekly pay used £643.00, amount £9,645.00
the day the 2024 cap came in 1970-01-01, 2014-04-06, 2024-04-06, £700.00 → eligible true, service years 10, years counted 10, half weeks 30, weekly pay used £700.00, amount £10,500.00
Show the other 3 tests
CaseArgumentsExpected
a relevant date before the caps this version carries is an error 1970-01-01, 2010-01-01, 2023-04-05, £600.00 → error: no statutory week's pay cap
negative weekly pay is an error 1970-01-01, 2010-01-01, 2026-09-23, -£0.01 → error: must not be negative
a relevant date before the start is an error 1970-01-01, 2026-09-24, 2026-09-23, £600.00 → error: is before startDate

More from the author

**Age is taken year by year, not at the relevant date.** A year counts at a rate only if the employee was at least that age throughout it, so the age that matters is the age on the first day of each counted year. Someone who turns 22 on the relevant date has two years at the under-22 rate, not two at 22+. This is the reading the published ready reckoners use (age 22 with two years' service is one week; age 23 is one and a half) and the one a naive "age today x years" calculation gets wrong.

The amount is `halfWeeks x weeklyPayUsed / 2`, rounded half-up to the penny. The statute does not specify rounding; the only fraction possible is a half penny, which is paid up.

**Caps** (data/week-pay-caps.json, by relevant date):

| From | Cap | |---|---| | 6 April 2023 | £643 | | 6 April 2024 | £700 | | 6 April 2025 | £719 | | 6 April 2026 | £751 |

The newest cap has no end date: when the next Order is made, publish a new version. A build installed with `history=current` refuses relevant dates before the caps it still carries.

Not covered: working out a week's pay (s.221-229, the 12-week average for variable hours), the relevant date itself (s.145, including the notice extension), Northern Ireland's separate limits, exclusions (s.140-144) and enhanced contractual schemes.

Sources: - Employment Rights Act 1996 s.162, "Amount of a redundancy payment", https://www.legislation.gov.uk/ukpga/1996/18/section/162 - Employment Rights Act 1996 s.155, "Qualifying period of employment", https://www.legislation.gov.uk/ukpga/1996/18/section/155 - The Employment Rights (Increase of Limits) Order 2023 (SI 2023/318), https://www.legislation.gov.uk/uksi/2023/318/made (£571 to £643 from 6 April 2023) - The Employment Rights (Increase of Limits) Order 2024 (SI 2024/213), https://www.legislation.gov.uk/uksi/2024/213/made (£643 to £700 from 6 April 2024) - The Employment Rights (Increase of Limits) Order 2025 (SI 2025/348), https://www.legislation.gov.uk/uksi/2025/348/made (£700 to £719 from 6 April 2025) - The Employment Rights (Increase of Limits) Order 2026 (SI 2026/310), https://www.legislation.gov.uk/uksi/2026/310/made (£719 to £751 from 6 April 2026) - GOV.UK, "Redundancy: your rights - statutory redundancy pay", https://www.gov.uk/redundancy-your-rights/redundancy-pay (£751 cap and £22,530 maximum from 6 April 2026)

## Before you rely on this

**Not professional advice.** This capability calculates payroll figures from published rules. It is a software component for developers, not tax or legal 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 payroll specialist 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 payroll specialist 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
NOTICE235
README.md4,324
data/week-pay-caps.json723
impl/python.py2,483
impl/rust.rs3,064
impl/typescript.ts2,473
vectors.json4,138