Functional Weave
Code in Python

health.waiting-time Unreviewed

NHS England referral-to-treatment (RTT) wait in days and weekly bands, with the 18-week breach flag.

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

Pinned by 18 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 clinician 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 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

How long a patient has waited on an NHS England consultant-led referral-to-treatment (RTT) pathway, the way NHS England counts it: days from the clock start to the clock stop (or to the measuring date while the patient is still waiting), the weekly reporting band, and whether the 18-week standard has been breached.

Patients in England have the right to start consultant-led non-emergency treatment within a maximum of 18 weeks from referral. 18 weeks is 126 days, and a patient who has waited exactly 126 days is still within it (they are reported in the 17-18 week band); 127 days is a breach. `eighteenWeekDate` is the clock start plus 126 days, the last day on which treatment still meets the standard.

For example

  • rtt_waiting_time(2026-01-05, —, 2026-01-05) → days 0, completed weeks 0, week band 1, breached18 weeks false, eighteen week date 2026-05-11, clock stopped false a clock that starts on the measuring date has waited zero days, in the 0-1 week band
  • rtt_waiting_time(2026-01-05, —, 2026-01-12) → days 7, completed weeks 1, week band 1, breached18 weeks false, eighteen week date 2026-05-11, clock stopped false 7 days is still the 0-1 week band, which holds eight days
  • rtt_waiting_time(2026-01-05, —, 2026-01-13) → days 8, completed weeks 1, week band 2, breached18 weeks false, eighteen week date 2026-05-11, clock stopped false 8 days is the first day of the 1-2 week band

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 rtt_waiting_time(clock_start: str, clock_stop: Optional[str], on_date: str) -> RttWait
clock_startdatethe date the RTT clock started: the provider received the referral
clock_stopdate?the date the clock stopped (first definitive treatment or a non-treatment stop); null while waiting
on_datedatethe date to measure a still-waiting pathway on; never today implicitly
returnsRttWait

The type it declares, generated into your project

@dataclass(frozen=True)
class RttWait:
    """The length of an RTT pathway as NHS England reports it."""

    #: clockStop (or onDate while waiting) minus clockStart
    days: int
    #: whole weeks waited, days divided by 7 rounded down
    completed_weeks: int
    #: n of the reporting time band ">n-1 to n weeks": 0-7 days is 1, 126 days is 18; above 104 is the ">104 weeks" band
    week_band: int
    #: more than 126 days
    breached18_weeks: bool
    #: clockStart + 126 days, the last day still within 18 weeks
    eighteen_week_date: str
    #: whether clockStop was given
    clock_stopped: bool

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

from fune.health.waiting_time import rtt_waiting_time  # health.waiting-time@^1
impl/python.py · 33 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, days_between, parse_iso_date  ← from dates.add-days ^1.0.0 · built alongside by fune
from .health_waiting_time_types import RttWait

#: 18 weeks: a wait of exactly 126 days is still within the standard.
_EIGHTEEN_WEEKS_DAYS = 126


def rtt_waiting_time(clock_start: str, clock_stop: Optional[str], on_date: str) -> RttWait:
    """The length of a referral-to-treatment pathway, counted from the clock
    start to the clock stop, or to ``on_date`` while the patient is still
    waiting. There are no pauses: NHS England removed them in 2015."""
    parse_iso_date(clock_start)
    parse_iso_date(on_date)
    if on_date < clock_start:
        raise ValueError("onDate %s is before clockStart %s" % (on_date, clock_start))
    if clock_stop is not None:
        parse_iso_date(clock_stop)
        if clock_stop < clock_start:
            raise ValueError("clockStop %s is before clockStart %s" % (clock_stop, clock_start))
        if clock_stop > on_date:
            raise ValueError("clockStop %s is after onDate %s" % (clock_stop, on_date))
    days = days_between(clock_start, clock_stop if clock_stop is not None else on_date)
    return RttWait(
        days=days,
        completed_weeks=days // 7,
        # The 0-1 week band holds eight days (0 to 7), every later band seven.
        week_band=1 if days <= 7 else (days + 6) // 7,
        breached18_weeks=days > _EIGHTEEN_WEEKS_DAYS,
        eighteen_week_date=add_days(clock_start, _EIGHTEEN_WEEKS_DAYS),
        clock_stopped=clock_stop is not None,
    )

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 1 dependency, 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 health.waiting-time
Download for Python health.waiting-time-1.0.1-python.fune · 13,619 bytes sha256 f4ac5244831538d6d9b212d6cc05aeef658d049731526b903bff2aade97ecd40

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

The whole function, every language, is one file too: health.waiting-time-1.0.1.fune, 17,626 bytes, sha256 07f74e0f2679def6b1c69bdf5b69be088b085710b9cb337bf662c9823b46f0b0. 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.waiting-time

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

# fune: after health.waiting-time

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 health.waiting-time

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.waiting-time --steps.

# fune: step health.waiting-time 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
a clock that starts on the measuring date has waited zero days, in the 0-1 week band 2026-01-05, —, 2026-01-05 → days 0, completed weeks 0, week band 1, breached18 weeks false, eighteen week date 2026-05-11, clock stopped false
7 days is still the 0-1 week band, which holds eight days 2026-01-05, —, 2026-01-12 → days 7, completed weeks 1, week band 1, breached18 weeks false, eighteen week date 2026-05-11, clock stopped false
8 days is the first day of the 1-2 week band 2026-01-05, —, 2026-01-13 → days 8, completed weeks 1, week band 2, breached18 weeks false, eighteen week date 2026-05-11, clock stopped false
exactly 18 weeks (126 days) is in the 17-18 week band and not a breach 2026-01-05, —, 2026-05-11 → days 126, completed weeks 18, week band 18, breached18 weeks false, eighteen week date 2026-05-11, clock stopped false
127 days breaches 18 weeks although only 18 whole weeks are completed: a naive completedWeeks > 18 check misses it 2026-01-05, —, 2026-05-12 → days 127, completed weeks 18, week band 19, breached18 weeks true, eighteen week date 2026-05-11, clock stopped false
125 days: 17 completed weeks, band 18 2026-01-05, —, 2026-05-10 → days 125, completed weeks 17, week band 18, breached18 weeks false, eighteen week date 2026-05-11, clock stopped false
728 days is the 103-104 week band 2026-01-05, —, 2028-01-03 → days 728, completed weeks 104, week band 104, breached18 weeks true, eighteen week date 2026-05-11, clock stopped false
729 days is beyond 104 weeks 2026-01-05, —, 2028-01-04 → days 729, completed weeks 104, week band 105, breached18 weeks true, eighteen week date 2026-05-11, clock stopped false
a stopped clock is measured to the stop, not to onDate 2026-01-05, 2026-04-20, 2026-09-23 → days 105, completed weeks 15, week band 15, breached18 weeks false, eighteen week date 2026-05-11, clock stopped true
started and stopped on the same day: zero days, still a pathway 2026-03-02, 2026-03-02, 2026-03-10 → days 0, completed weeks 0, week band 1, breached18 weeks false, eighteen week date 2026-07-06, clock stopped true
Show the other 8 tests
CaseArgumentsExpected
stopped on the measuring date itself 2026-01-05, 2026-05-12, 2026-05-12 → days 127, completed weeks 18, week band 19, breached18 weeks true, eighteen week date 2026-05-11, clock stopped true
the 18-week date crosses 29 February in a leap year 2027-11-01, —, 2028-03-06 → days 126, completed weeks 18, week band 18, breached18 weeks false, eighteen week date 2028-03-06, clock stopped false
the same span in a common year is a day shorter 2026-11-01, —, 2027-03-06 → days 125, completed weeks 17, week band 18, breached18 weeks false, eighteen week date 2027-03-07, clock stopped false
onDate before the clock start is an error 2026-01-05, —, 2026-01-04 → error: is before clockStart
a clock stop before the start is an error 2026-01-05, 2026-01-01, 2026-02-01 → error: clockStop 2026-01-01 is before clockStart
a clock stop after onDate is an error: the stop has not happened yet 2026-01-05, 2026-03-01, 2026-02-01 → error: is after onDate
an impossible date is an error 2026-02-30, —, 2026-04-01 → error: is not a real calendar date
a UK-style date is an error 05/01/2026, —, 2026-04-01 → error: is not an ISO date

More from the author

**Week bands.** RTT returns put each pathway in a weekly time band, ">n-1 to n weeks", and `weekBand` is that n. The first band, 0-1 weeks, holds eight days (0 to 7), every later band seven: 8-14 days is band 2, 120-126 days band 18, 127 days band 19. NHS England's last band is "104+ weeks", 729 days and more; `weekBand` keeps counting above 104 (729 days is 105) so a caller can still tell long waits apart, and a return should put anything above 104 in the >104 band. `completedWeeks` is simply whole weeks (days divided by 7, rounded down), for display. A clock that starts and stops on the same day is a real pathway of zero days.

**No clock pauses.** Earlier versions of the rules allowed "patient-initiated clock pauses". NHS England removed them in 2015 with the admitted pathway standard: "there is no longer any provision to report pauses or suspensions in RTT waiting time clocks ... under any circumstances". This capability therefore takes a clock start and an optional clock stop and nothing else, deliberately. Events that the rules do recognise are the caller's to apply:

- a clock stop (rules 4 and 5: first definitive treatment, a decision not to treat, active monitoring, a patient declining treatment, and so on) is the `clockStop` date; - a DNA of the first appointment nullifies the clock (rule 5e, footnote 10), and when the patient rebooks a new clock starts (rule 3e), so pass the rebooking date as a new `clockStart`; - other new clock starts (rule 3: a second bilateral procedure, a new treatment, the end of active monitoring) are likewise new pathways.

**Errors.** A measuring date before the clock start, a clock stop before the clock start, and a clock stop after the measuring date (a stop that has not happened yet) are errors. Dates are ISO `YYYY-MM-DD`, and the measuring date is always an argument, never the clock.

## Source

NHS England, "Recording and reporting referral to treatment (RTT) waiting times for consultant-led elective care", version 5.2, February 2025: section 2.2 reproduces the "Referral to treatment consultant-led waiting times rules suite" (clock starts, rules 1-3; clock stops, rules 4-5), the introduction records the removal of clock pauses, and section 10.1.7 "Measuring the length of RTT pathways" defines the time bands. https://www.england.nhs.uk/statistics/wp-content/uploads/sites/2/2025/10/Recording-and-reporting-RTT-waiting-times-guidance-v5.2-Feb25.pdf

## Before you rely on this

**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 above, 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.

**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 clinician 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.

1.0.1 marks it unreviewed. The code and the tests are unchanged.

Files

PathBytes
README.md4,514
impl/python.py1,529
impl/rust.rs2,400
impl/typescript.ts1,454
vectors.json4,143