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 bandrtt_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 daysrtt_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.
pub fn rtt_waiting_time(clock_start: &str, clock_stop: Option<&str>, on_date: &str) -> RttWait
| clock_start | date | the date the RTT clock started: the provider received the referral |
| clock_stop | date? | the date the clock stopped (first definitive treatment or a non-treatment stop); null while waiting |
| on_date | date | the date to measure a still-waiting pathway on; never today implicitly |
| returns | RttWait |
The type it declares, generated into your project
/// The length of an RTT pathway as NHS England reports it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RttWait {
/// clockStop (or onDate while waiting) minus clockStart
pub days: i64,
/// whole weeks waited, days divided by 7 rounded down
pub completed_weeks: i64,
/// 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
pub week_band: i64,
/// more than 126 days
pub breached18_weeks: bool,
/// clockStart + 126 days, the last day still within 18 weeks
pub eighteen_week_date: String,
/// whether clockStop was given
pub clock_stopped: bool,
}
Your code names it in one line, in the file that uses it
fune!(health.waiting-time@^1); // then call rtt_waiting_time(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::dates_add_days::{add_days, days_between, parse_iso_date}; ← from dates.add-days ^1.0.0 · built alongside by fune
/// 18 weeks: a wait of exactly 126 days is still within the standard.
const EIGHTEEN_WEEKS_DAYS: i64 = 126;
/// 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.
///
/// # Panics
/// Panics on a malformed date, `on_date` or `clock_stop` before `clock_start`,
/// or `clock_stop` after `on_date`.
pub fn rtt_waiting_time(clock_start: &str, clock_stop: Option<&str>, on_date: &str) -> RttWait {
parse_iso_date(clock_start);
parse_iso_date(on_date);
if on_date < clock_start {
panic!("onDate {} is before clockStart {}", on_date, clock_start);
}
if let Some(stop) = clock_stop {
parse_iso_date(stop);
if stop < clock_start {
panic!("clockStop {} is before clockStart {}", stop, clock_start);
}
if stop > on_date {
panic!("clockStop {} is after onDate {}", stop, on_date);
}
}
let days = days_between(clock_start, clock_stop.unwrap_or(on_date));
RttWait {
days,
completed_weeks: days / 7,
// The 0-1 week band holds eight days (0 to 7), every later band seven.
week_band: if days <= 7 { 1 } 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_some(),
}
}
pub fn rtt_wait_to_value(wait: &RttWait) -> Value {
Value::obj(vec![
("days", Value::Int(wait.days)),
("completedWeeks", Value::Int(wait.completed_weeks)),
("weekBand", Value::Int(wait.week_band)),
("breached18Weeks", Value::Bool(wait.breached18_weeks)),
("eighteenWeekDate", Value::str(&wait.eighteen_week_date)),
("clockStopped", Value::Bool(wait.clock_stopped)),
])
}
fn date_arg(value: &Value) -> &str {
match value {
Value::Str(s) => s.as_str(),
_ => panic!("{:?} is not an ISO date (YYYY-MM-DD)", value),
}
}
pub fn fune_vector(args: &[Value]) -> Value {
let stop = if args[1].is_null() { None } else { Some(date_arg(&args[1])) };
rtt_wait_to_value(&rtt_waiting_time(date_arg(&args[0]), stop, date_arg(&args[2])))
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Rust 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. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add health.waiting-time
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./health.waiting-time-1.0.1-rust.fune, or fetch it from a terminal with fune pull health.waiting-time@1.0.1:rust.
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.
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Path | Bytes |
|---|---|
| README.md | 4,514 |
| impl/python.py | 1,529 |
| impl/rust.rs | 2,400 |
| impl/typescript.ts | 1,454 |
| vectors.json | 4,143 |