Functional Weave
Code in TypeScript

health.nhs-number-format

Display an NHS number in the 3-3-4 format ("943 476 5919") after checking its modulus 11 check digit.

1.0.0 (not the latest) · published 2026-10-03 by charlie · Anterra

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

Not professional advice. This capability calculates health figures from published rules. It is a software component for developers, not medical 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 clinician review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.

Not a medical device. It is not intended to diagnose, treat or support clinical decisions about any individual. Anyone building it into clinical software is responsible for that software’s regulatory status, and must validate it under their own clinical governance.

What it does

An NHS number in the 3-3-4 display format, `943 476 5919`, after a full modulus 11 check by `validation.nhs-number` (`validateNhsNumber`, ^2.0.0).

NHS numbers are stored as ten digits and conventionally shown to people as three groups separated by spaces, which makes a transcription error easier to spot on a screen, a letter or a wristband. Input may already be spaced or hyphenated; the digits are what count.

For example

  • formatNhsNumber(9434765919) → 943 476 5919 ten plain digits are spaced 3-3-4
  • formatNhsNumber(943 476 5919) → 943 476 5919 an already formatted number comes back unchanged
  • formatNhsNumber(943-476-5919) → 943 476 5919 hyphens are replaced by spaces

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 formatNhsNumber(value: string): string
valuestringten digits, optionally already spaced or hyphenated
returnsstringthree digits, a space, three digits, a space, four digits

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

import { formatNhsNumber } from "#fune/health.nhs-number-format@^1";
impl/typescript.ts · 16 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 { validateNhsNumber } from "./validation_nhs_number.ts";  ← from validation.nhs-number ^2.0.0 · built alongside by fune

/**
 * The NHS number in the 3-3-4 display format NHS systems use, after
 * a full check with validation.nhs-number. An invalid number is an error, not
 * a formatted string: a well-spaced wrong number looks more trustworthy than
 * a badly spaced right one. The error names the reason but never echoes the
 * number, which is a patient identifier and has no place in a log.
 */
export function formatNhsNumber(value: string): string {
  const result = validateNhsNumber(value);
  if (!result.valid || result.formatted === null) {
    throw new RangeError(`not a valid NHS number (${result.reason})`);
  }
  return result.formatted;
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 1 dependency, 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 health.nhs-number-format
Download for TypeScript health.nhs-number-format-1.0.0-typescript.fune · 5,645 bytes sha256 fa27c132c6fd332177c1f07242e07415a6d9d267ff2b37f3e389a0ab2f0743ae

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./health.nhs-number-format-1.0.0-typescript.fune, or fetch it from a terminal with fune pull health.nhs-number-format@1.0.0:typescript.

The whole function, every language, is one file too: health.nhs-number-format-1.0.0.fune, 7,454 bytes, sha256 954512d1a1fc983bea7f92b75a8b24baa80192dfb610d5c18222e474c892fb80. 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 health.nhs-number-format

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

// fune: after health.nhs-number-format

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 validation.nhs-number in health.nhs-number-format

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 health.nhs-number-format --steps.

// fune: step health.nhs-number-format 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 plain digits are spaced 3-3-4 9434765919 → 943 476 5919
an already formatted number comes back unchanged 943 476 5919 → 943 476 5919
hyphens are replaced by spaces 943-476-5919 → 943 476 5919
stray and doubled spaces are normalised 94 34 76 5919 → 943 476 5919
another valid number 4010232137 → 401 023 2137
a check digit of 0: remainder 0 gives 11, which means 0 4000000020 → 400 000 0020
a wrong check digit is refused, not formatted 9434765918 → error: not a valid NHS number (bad-check-digit)
nine digits are refused 943476591 → error: not a valid NHS number (bad-length)
eleven digits are refused 94347659190 → error: not a valid NHS number (bad-length)
a letter is refused 943476591A → error: not a valid NHS number (bad-character)
Show the other 3 tests
CaseArgumentsExpected
an empty string is refused → error: not a valid NHS number (empty)
0000000000 passes the arithmetic but is a placeholder 0000000000 → error: not a valid NHS number (placeholder)
a number with no possible check digit (remainder 1) is refused 1000000010 → error: not a valid NHS number (bad-check-digit)

More from the author

## Why an invalid number is an error

A formatter that spaces out whatever it is given turns a mistyped number into something that looks official. So the number is checked first, and an invalid one raises `not a valid NHS number (<reason>)`, where the reason is `validation.nhs-number`'s: `empty`, `bad-character`, `bad-length`, `bad-check-digit` or `placeholder` (0000000000 and the like, which pass the arithmetic). The message deliberately does not repeat the number: it is a patient identifier, and error messages end up in logs.

A valid check digit means the number is well formed, not that it was issued or belongs to this patient; that needs a Personal Demographics Service trace.

## Level

The catalogue lists this at level 0, but it requires `validation.nhs-number`, which is level 1, and a capability may not depend on a higher level; so it is level 1.

## Source

NHS Data Model and Dictionary, *NHS NUMBER*: ten numeric digits, the tenth a check digit validated by the modulus 11 algorithm (https://www.datadictionary.nhs.uk/attributes/nhs_number.html). The check itself, and the 3-3-4 `formatted` form returned here, are `validation.nhs-number`'s; see its README. The Data Dictionary entry does not itself define the display grouping; 3-3-4 is the grouping NHS systems and correspondence conventionally use.

Files

PathBytes
README.md1,766
impl/python.py681
impl/rust.rs1,058
impl/typescript.ts700
vectors.json1,570