Functional Weave
Code in TypeScript

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

  • statutoryRedundancyPay(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
  • statutoryRedundancyPay(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
  • statutoryRedundancyPay(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.

export function statutoryRedundancyPay(birthDate: string, startDate: string, relevantDate: string, weeklyPay: Money): RedundancyPay
birthDatedate
startDatedatefirst day of continuous employment
relevantDatedatethe relevant date under ERA 1996 s.145, usually the day employment ends; it picks the cap
weeklyPayMoneya week's pay before the cap
returnsRedundancyPay

The type it declares, generated into your project

/** The payment and how it was built. */
export interface RedundancyPay {
  /** at least two years' continuous service on the relevant date */
  readonly eligible: boolean;
  /** completed years of continuous service */
  readonly serviceYears: number;
  /** years reckoned: at most the last 20, 0 when not eligible */
  readonly yearsCounted: number;
  /** weeks of pay multiplied by 2 (1 per year under 22, 2 from 22, 3 from 41) */
  readonly halfWeeks: number;
  /** weeklyPay after the statutory cap */
  readonly weeklyPayUsed: Money;
  readonly amount: Money;
}

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

import { statutoryRedundancyPay } from "#fune/payroll.redundancy-pay@^1";
impl/typescript.ts · 47 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.

import { addDays } from "./dates_add_days.ts";  ← from dates.add-days ^1.0.0 · built alongside by fune
import { addMonths } from "./dates_add_months.ts";  ← from dates.add-months ^1.0.0 · built alongside by fune
import { age } from "./dates_age.ts";  ← from dates.age ^1.0.0 · built alongside by fune
import { lengthOfService } from "./hr_length_of_service.ts";  ← from hr.length-of-service ^1.0.0 · built alongside by fune
import { roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { WEEK_PAY_CAPS, WEEK_PAY_CAPS_HISTORY } from "./payroll_redundancy_pay_data.ts";  ← this capability’s own data, compiled from data/week-pay-caps.json into the same file by fune build
import { type RedundancyPay } from "./payroll_redundancy_pay_types.ts";

function weekPayCap(relevantDate: string): number {
  for (const row of WEEK_PAY_CAPS) {
    if (relevantDate >= row.validFrom && (row.validTo === null || relevantDate <= row.validTo)) return row.capMinor;
  }
  if (WEEK_PAY_CAPS_HISTORY !== "full") {
    let earliest: string | null = null;
    for (const row of WEEK_PAY_CAPS) if (earliest === null || row.validFrom < earliest) earliest = row.validFrom;
    if (earliest !== null && relevantDate < earliest) {
      throw new RangeError(
        `no statutory week's pay cap for ${relevantDate}: this build was installed with history=${WEEK_PAY_CAPS_HISTORY}, ` +
          `so it only carries caps from ${earliest}. Reinstall with history=full for older dismissals.`,
      );
    }
  }
  throw new RangeError(`no statutory week's pay cap for ${relevantDate}`);
}

/**
 * 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.
 */
export function statutoryRedundancyPay(birthDate: string, startDate: string, relevantDate: string, weeklyPay: Money): RedundancyPay {
  if (weeklyPay.minor < 0) throw new RangeError(`weeklyPay must not be negative, received ${weeklyPay.minor}`);
  const cap = weekPayCap(relevantDate);
  const weeklyPayUsed = money(Math.min(weeklyPay.minor, cap), weeklyPay.currency);
  const serviceYears = lengthOfService(startDate, relevantDate).years;
  const eligible = serviceYears >= 2;
  const yearsCounted = eligible ? Math.min(serviceYears, 20) : 0;
  let halfWeeks = 0;
  for (let k = 1; k <= yearsCounted; k++) {
    const firstDay = addDays(addMonths(relevantDate, -12 * k), 1);
    const ageThen = age(birthDate, firstDay);
    halfWeeks += ageThen >= 41 ? 3 : ageThen >= 22 ? 2 : 1;
  }
  const amount = money(roundDiv(halfWeeks * weeklyPayUsed.minor, 2, "half-up"), weeklyPay.currency);
  return { eligible, serviceYears, yearsCounted, halfWeeks, weeklyPayUsed, amount };
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 6 dependencies, pins them in fune.lock, downloads only the TypeScript 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 TypeScript payroll.redundancy-pay-1.0.1-typescript.fune · 16,018 bytes sha256 7a4a33f3c127562b22c73ad3f8f15735dfb25cdbfe76101ee19c899327ca13bb

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

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