Functional Weave
Code in TypeScript

health.news2

NEWS2 early warning score from one set of observations: parameter scores, aggregate and RCP clinical risk band.

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

Pinned by 45 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 an acute-care 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

Status: needs review and sign-off by a qualified clinician before it is published. Not a medical device; for decision support only; always follow local clinical guidelines.

The National Early Warning Score 2 for one set of adult observations. Each of the six physiological parameters is scored from the Royal College of Physicians' chart 1, 2 is added when the patient needs supplemental oxygen, and the sum is the aggregate NEW score. The clinical risk band comes from chart 2:

For example

  • news2Score(respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37) → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 0, red score false, clinical risk low normal observations score 0, low risk
  • news2Score(respiration rate 8, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37) → respiration rate 3, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 3, red score true, clinical risk low-medium respiration 8 is a red 3: aggregate 3 but low-medium risk
  • news2Score(respiration rate 11, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37) → respiration rate 1, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 1, red score false, clinical risk low respiration 11 scores 1

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 news2Score(observations: News2Observations): News2Score
observationsNews2Observationsone set of adult observations taken together
returnsNews2Scoreeach parameter's score, the aggregate, whether any single parameter scored 3, and the risk band

The types it declares, generated into your project

/** One set of observations, as recorded on the NEWS2 chart. */
export interface News2Observations {
  /** breaths per minute, 1 to 80 */
  readonly respirationRate: number;
  /** oxygen saturation in percent, 1 to 100 */
  readonly spo2: number;
  /** 1, or 2 only for confirmed hypercapnic respiratory failure on a clinician's direction */
  readonly spo2Scale: number;
  /** true when supplemental oxygen is needed to keep the prescribed saturation */
  readonly onOxygen: boolean;
  /** mmHg, 20 to 300 */
  readonly systolicBp: number;
  /** beats per minute, 1 to 300 */
  readonly pulse: number;
  /** ACVPU: alert, new confusion, voice, pain or unresponsive */
  readonly consciousness: Acvpu;
  /** degrees Celsius, 25.0 to 45.0 */
  readonly temperature: number;
}

export type Acvpu = "alert" | "confusion" | "voice" | "pain" | "unresponsive";

export type News2Risk = "low" | "low-medium" | "medium" | "high";

/** The chart's scores for each parameter, their sum and the clinical risk it triggers. */
export interface News2Score {
  /** aggregate NEW score, 0 to 20 */
  readonly total: number;
  readonly respirationRate: number;
  readonly spo2: number;
  /** 2 on supplemental oxygen, else 0 */
  readonly airOrOxygen: number;
  readonly systolicBp: number;
  readonly pulse: number;
  readonly consciousness: number;
  readonly temperature: number;
  /** true when any single parameter scored 3 */
  readonly redScore: boolean;
  /** RCP chart 2: low, low-medium (a red score), medium or high */
  readonly clinicalRisk: News2Risk;
}

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

import { news2Score } from "#fune/health.news2@^1";
impl/typescript.ts · 107 lines · open · raw
import { type Acvpu, type News2Observations, type News2Risk, type News2Score } from "./health_news2_types.ts";

const CONSCIOUSNESS: readonly Acvpu[] = ["alert", "confusion", "voice", "pain", "unresponsive"];

/** Ranges are guard rails against unit and typing mistakes, not RCP rules; out of range is an error, never clamped. */
function checkInt(name: string, value: number, low: number, high: number): void {
  if (typeof value !== "number" || !Number.isInteger(value) || value < low || value > high) {
    throw new RangeError(`${name} must be a whole number from ${low} to ${high}, received ${value}`);
  }
}

/** RCP NEWS2 chart 1, respiration rate per minute. */
function respirationScore(rate: number): number {
  if (rate <= 8) return 3;
  if (rate <= 11) return 1;
  if (rate <= 20) return 0;
  if (rate <= 24) return 2;
  return 3;
}

/**
 * Chart 1, SpO2. Scale 2 targets 88-92%, so on oxygen a high saturation
 * scores (it signals over-oxygenation risk in hypercapnic failure); on air it
 * does not. Scale 1 ignores oxygen here: that is scored separately.
 */
function spo2Score(spo2: number, scale: number, onOxygen: boolean): number {
  if (scale === 1) {
    if (spo2 <= 91) return 3;
    if (spo2 <= 93) return 2;
    if (spo2 <= 95) return 1;
    return 0;
  }
  if (spo2 <= 83) return 3;
  if (spo2 <= 85) return 2;
  if (spo2 <= 87) return 1;
  if (spo2 <= 92 || !onOxygen) return 0;
  if (spo2 <= 94) return 1;
  if (spo2 <= 96) return 2;
  return 3;
}

function systolicScore(sbp: number): number {
  if (sbp <= 90) return 3;
  if (sbp <= 100) return 2;
  if (sbp <= 110) return 1;
  if (sbp <= 219) return 0;
  return 3;
}

function pulseScore(pulse: number): number {
  if (pulse <= 40) return 3;
  if (pulse <= 50) return 1;
  if (pulse <= 90) return 0;
  if (pulse <= 110) return 1;
  if (pulse <= 130) return 2;
  return 3;
}

/** Chart rows are to one decimal place; a value between rows scores with the higher row ("at or below"). */
function temperatureScore(t: number): number {
  if (t <= 35.0) return 3;
  if (t <= 36.0) return 1;
  if (t <= 38.0) return 0;
  if (t <= 39.0) return 1;
  return 2;
}

/**
 * The NEWS2 score (Royal College of Physicians, 2017) for one set of adult
 * observations: each parameter scored from chart 1, plus 2 for supplemental
 * oxygen, summed; and the clinical risk from chart 2, where a single
 * parameter scoring 3 raises a low aggregate to low-medium.
 */
export function news2Score(observations: News2Observations): News2Score {
  const o = observations;
  checkInt("respirationRate", o.respirationRate, 1, 80);
  checkInt("spo2", o.spo2, 1, 100);
  if (o.spo2Scale !== 1 && o.spo2Scale !== 2) {
    throw new RangeError(`spo2Scale must be 1 or 2, received ${o.spo2Scale}`);
  }
  if (typeof o.onOxygen !== "boolean") {
    throw new TypeError(`onOxygen must be true or false, received ${o.onOxygen}`);
  }
  checkInt("systolicBp", o.systolicBp, 20, 300);
  checkInt("pulse", o.pulse, 1, 300);
  if (!CONSCIOUSNESS.includes(o.consciousness)) {
    throw new RangeError(`consciousness must be one of alert, confusion, voice, pain, unresponsive, received ${o.consciousness}`);
  }
  if (typeof o.temperature !== "number" || !Number.isFinite(o.temperature) || o.temperature < 25 || o.temperature > 45) {
    throw new RangeError(`temperature must be from 25.0 to 45.0 degrees Celsius, received ${o.temperature}`);
  }

  const respirationRate = respirationScore(o.respirationRate);
  const spo2 = spo2Score(o.spo2, o.spo2Scale, o.onOxygen);
  const airOrOxygen = o.onOxygen ? 2 : 0;
  const systolicBp = systolicScore(o.systolicBp);
  const pulse = pulseScore(o.pulse);
  const consciousness = o.consciousness === "alert" ? 0 : 3;
  const temperature = temperatureScore(o.temperature);
  const total = respirationRate + spo2 + airOrOxygen + systolicBp + pulse + consciousness + temperature;
  const redScore = [respirationRate, spo2, systolicBp, pulse, consciousness, temperature].includes(3);
  let clinicalRisk: News2Risk = "low";
  if (total >= 7) clinicalRisk = "high";
  else if (total >= 5) clinicalRisk = "medium";
  else if (redScore) clinicalRisk = "low-medium";
  return { total, respirationRate, spo2, airOrOxygen, systolicBp, pulse, consciousness, temperature, redScore, clinicalRisk };
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and nothing else, 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.news2
Download for TypeScript health.news2-1.0.0-typescript.fune · 36,533 bytes sha256 c9285470a81cce4661df8e1b83e4b017e8f72ca75e76944d13c485b57718db2a

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

The whole function, every language, is one file too: health.news2-1.0.0.fune, 48,217 bytes, sha256 68949e2d83f623b010a7e675b0f81f366ce89b35ae219ce97c7d0b1cd951c512. 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.news2

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

// fune: after health.news2

replace — it requires no other capability, so there is no dependency to replace.

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.news2 --steps.

// fune: step health.news2 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
normal observations score 0, low risk respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 0, red score false, clinical risk low
respiration 8 is a red 3: aggregate 3 but low-medium risk respiration rate 8, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 3, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 3, red score true, clinical risk low-medium
respiration 11 scores 1 respiration rate 11, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 1, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 1, red score false, clinical risk low
respiration 21 scores 2, 24 still 2 respiration rate 24, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 2, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 2, red score false, clinical risk low
respiration 25 scores 3 respiration rate 25, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 3, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 3, red score true, clinical risk low-medium
the low edge of every 1-point row: 9/min, SpO2 94, SBP 101, pulse 41, 35.1C make 5, medium without any red respiration rate 9, spo2 94, spo2 scale 1, on oxygen false, systolic bp 101, pulse 41, consciousness alert, temperature 35.1 → respiration rate 1, spo2 1, air or oxygen 0, systolic bp 1, pulse 1, consciousness 0, temperature 1, total 5, red score false, clinical risk medium
the edges of the 0 rows: 12 and 20/min, SpO2 96, SBP 111, pulse 51, 36.1C all score 0 respiration rate 12, spo2 96, spo2 scale 1, on oxygen false, systolic bp 111, pulse 51, consciousness alert, temperature 36.1 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 0, red score false, clinical risk low
the other edges of the 0 rows: 20/min, SBP 219, pulse 90, 38.0C respiration rate 20, spo2 97, spo2 scale 1, on oxygen false, systolic bp 219, pulse 90, consciousness alert, temperature 38 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 0, red score false, clinical risk low
scale 1: SpO2 91 is a red 3 respiration rate 16, spo2 91, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 3, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 3, red score true, clinical risk low-medium
scale 1: SpO2 92-93 score 2 (93) respiration rate 16, spo2 93, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 2, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 2, red score false, clinical risk low
Show the other 35 tests
CaseArgumentsExpected
scale 1: SpO2 97 on oxygen scores 0 for saturation plus 2 for oxygen respiration rate 16, spo2 97, spo2 scale 1, on oxygen true, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 0, air or oxygen 2, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 2, red score false, clinical risk low
scale 2 on air: SpO2 97 scores 0 respiration rate 16, spo2 97, spo2 scale 2, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 0, red score false, clinical risk low
scale 2 on oxygen: SpO2 97 scores 3 plus 2 for oxygen, medium (scale 1 would give 2, low) respiration rate 16, spo2 97, spo2 scale 2, on oxygen true, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 3, air or oxygen 2, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 5, red score true, clinical risk medium
scale 2 on oxygen: SpO2 93-94 scores 1 (93) respiration rate 16, spo2 93, spo2 scale 2, on oxygen true, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 1, air or oxygen 2, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 3, red score false, clinical risk low
scale 2 on oxygen: SpO2 95-96 scores 2 (96) respiration rate 16, spo2 96, spo2 scale 2, on oxygen true, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 2, air or oxygen 2, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 4, red score false, clinical risk low
scale 2: SpO2 88-92 is the target range and scores 0 even on oxygen (88) respiration rate 16, spo2 88, spo2 scale 2, on oxygen true, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 0, air or oxygen 2, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 2, red score false, clinical risk low
scale 2: SpO2 86 scores 1 respiration rate 16, spo2 86, spo2 scale 2, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 1, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 1, red score false, clinical risk low
scale 2: SpO2 84 scores 2 respiration rate 16, spo2 84, spo2 scale 2, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 2, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 2, red score false, clinical risk low
scale 2: SpO2 83 is a red 3 respiration rate 16, spo2 83, spo2 scale 2, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 3, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 0, total 3, red score true, clinical risk low-medium
systolic 220 is a red 3 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 220, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 3, pulse 0, consciousness 0, temperature 0, total 3, red score true, clinical risk low-medium
systolic 90 is a red 3 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 90, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 3, pulse 0, consciousness 0, temperature 0, total 3, red score true, clinical risk low-medium
systolic 100 scores 2 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 100, pulse 70, consciousness alert, temperature 37 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 2, pulse 0, consciousness 0, temperature 0, total 2, red score false, clinical risk low
pulse 40 scores 3 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 40, consciousness alert, temperature 37 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 3, consciousness 0, temperature 0, total 3, red score true, clinical risk low-medium
pulse 91 scores 1 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 91, consciousness alert, temperature 37 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 1, consciousness 0, temperature 0, total 1, red score false, clinical risk low
pulse 111-130 scores 2 (130) respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 130, consciousness alert, temperature 37 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 2, consciousness 0, temperature 0, total 2, red score false, clinical risk low
pulse 131 scores 3 and with respiration 21 (2) the aggregate is 5: medium outranks the red score respiration rate 21, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 131, consciousness alert, temperature 37 → respiration rate 2, spo2 0, air or oxygen 0, systolic bp 0, pulse 3, consciousness 0, temperature 0, total 5, red score true, clinical risk medium
new confusion alone is a red 3 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness confusion, temperature 37 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 3, temperature 0, total 3, red score true, clinical risk low-medium
responds to voice scores 3 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness voice, temperature 37 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 3, temperature 0, total 3, red score true, clinical risk low-medium
temperature 35.0 scores 3 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 35 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 3, total 3, red score true, clinical risk low-medium
temperature 36.0 scores 1 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 36 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 1, total 1, red score false, clinical risk low
temperature 38.1 scores 1 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 38.1 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 1, total 1, red score false, clinical risk low
temperature 39.0 scores 1 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 39 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 1, total 1, red score false, clinical risk low
temperature 39.1 scores 2 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 39.1 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 2, total 2, red score false, clinical risk low
a temperature between chart rows, 35.05, scores with the higher row: 1 respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 35.05 → respiration rate 0, spo2 0, air or oxygen 0, systolic bp 0, pulse 0, consciousness 0, temperature 1, total 1, red score false, clinical risk low
aggregate 4 with no red score is low respiration rate 22, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 115, consciousness alert, temperature 37 → respiration rate 2, spo2 0, air or oxygen 0, systolic bp 0, pulse 2, consciousness 0, temperature 0, total 4, red score false, clinical risk low
aggregate 7 exactly is high respiration rate 22, spo2 97, spo2 scale 1, on oxygen false, systolic bp 95, pulse 115, consciousness alert, temperature 38.5 → respiration rate 2, spo2 0, air or oxygen 0, systolic bp 2, pulse 2, consciousness 0, temperature 1, total 7, red score false, clinical risk high
a septic picture: 25/min, SpO2 93 on oxygen, SBP 105, pulse 115, 38.5C, unresponsive respiration rate 25, spo2 93, spo2 scale 1, on oxygen true, systolic bp 105, pulse 115, consciousness unresponsive, temperature 38.5 → respiration rate 3, spo2 2, air or oxygen 2, systolic bp 1, pulse 2, consciousness 3, temperature 1, total 14, red score true, clinical risk high
a pulse above 300 is an error, not clamped respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 301, consciousness alert, temperature 37 → error: pulse must be a whole number from 1 to 300
a respiration rate of 0 is an error respiration rate 0, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → error: respirationRate must be a whole number from 1 to 80
a fractional respiration rate is an error respiration rate 16.5, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → error: respirationRate must be a whole number from 1 to 80
an SpO2 above 100 is an error respiration rate 16, spo2 101, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → error: spo2 must be a whole number from 1 to 100
there is no SpO2 scale 3 respiration rate 16, spo2 97, spo2 scale 3, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 37 → error: spo2Scale must be 1 or 2
a temperature in Fahrenheit is an error, not a fever respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness alert, temperature 98.6 → error: temperature must be from 25.0 to 45.0 degrees Celsius
consciousness outside ACVPU is an error respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 120, pulse 70, consciousness drowsy, temperature 37 → error: consciousness must be one of alert, confusion, voice, pain, unresponsive
a systolic pressure of 10 is an error respiration rate 16, spo2 97, spo2 scale 1, on oxygen false, systolic bp 10, pulse 70, consciousness alert, temperature 37 → error: systolicBp must be a whole number from 20 to 300

More from the author

| NEW score | Clinical risk | |---|---| | aggregate 0-4 | low | | a score of 3 in any single parameter (a "red score") | low-medium | | aggregate 5-6 | medium | | aggregate 7 or more | high |

The bands are checked in the order high, medium, red score, low: a red score raises a low aggregate to low-medium, but an aggregate of 5 or more already outranks it. `redScore` is returned separately because the RCP asks for an urgent ward-based review for any red score, whatever the band. The oxygen weighting (2) can never be a red score on its own.

## The scoring (RCP chart 1)

| Parameter | 3 | 2 | 1 | 0 | 1 | 2 | 3 | |---|---|---|---|---|---|---|---| | Respiration rate /min | ≤8 | | 9-11 | 12-20 | | 21-24 | ≥25 | | SpO2 scale 1 % | ≤91 | 92-93 | 94-95 | ≥96 | | | | | SpO2 scale 2 % | ≤83 | 84-85 | 86-87 | 88-92, or ≥93 on air | 93-94 on oxygen | 95-96 on oxygen | ≥97 on oxygen | | Air or oxygen | | oxygen | | air | | | | | Systolic BP mmHg | ≤90 | 91-100 | 101-110 | 111-219 | | | ≥220 | | Pulse /min | ≤40 | | 41-50 | 51-90 | 91-110 | 111-130 | ≥131 | | Consciousness | | | | alert | | | new confusion, voice, pain, unresponsive | | Temperature °C | ≤35.0 | | 35.1-36.0 | 36.1-38.0 | 38.1-39.0 | ≥39.1 | |

## Things a caller must get right

- **SpO2 scale 2 is only for patients with confirmed hypercapnic (type 2) respiratory failure, on the direction of a competent clinical decision maker** (RCP report, paragraphs 25-26). On scale 2 a high saturation while on oxygen scores up to 3, because over-oxygenation is the danger; on air it scores 0. Scoring such a patient on scale 1 under-scores them. - **NEWS2 is for adults aged 16 or over and not in pregnancy** (RCP report, paragraph 2). This function cannot see age or pregnancy; the caller must not use it for children or pregnant women. - **New confusion** (`confusion`, the C of ACVPU) means new-onset confusion, disorientation or delirium, and scores 3 like voice, pain or unresponsive. - **Temperature** is compared "at or below" each chart row, so a value between two one-decimal rows (35.05) scores with the higher row (35.1-36.0, score 1). Thermometers record one decimal place. - The other observations are whole numbers, as charted.

## Input guard rails (not from the RCP)

Out-of-range values are errors, never clamped, so a unit or typing mistake (98.6 for a Celsius temperature, a pulse of 700) cannot quietly produce a score. The ranges are the implementation's own and need clinical sign-off: respiration rate 1-80 /min, SpO2 1-100 %, systolic BP 20-300 mmHg, pulse 1-300 /min, temperature 25.0-45.0 °C, SpO2 scale 1 or 2, consciousness one of `alert`, `confusion`, `voice`, `pain`, `unresponsive`.

## Source

Royal College of Physicians. *National Early Warning Score (NEWS) 2: Standardising the assessment of acute-illness severity in the NHS.* Updated report of a working party. London: RCP, 2017. Chart 1 "The NEWS scoring system" and chart 2 "NEWS thresholds and triggers", https://www.rcp.ac.uk/improving-care/resources/national-early-warning-score-news-2/ . The vectors were worked out by hand from those two charts; the RCP publishes no numeric worked examples.

Files

PathBytes
README.md3,693
impl/python.py4,452
impl/rust.rs6,725
impl/typescript.ts4,270
vectors.json20,819