health.news2 Unreviewed
NEWS2 early warning score from one set of observations: parameter scores, aggregate and RCP clinical risk band.
1.0.1 · published 2026-10-03 by charlie · Anterra
Pinned by 45 tests, run in TypeScript, Python and Rust.
Unreviewed. Not for clinical use; regulatory review pending. 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 acute-care 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 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
news2_score(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 risknews2_score(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 risknews2_score(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.
def news2_score(observations: News2Observations) -> News2Score
| observations | News2Observations | one set of adult observations taken together |
| returns | News2Score | each parameter's score, the aggregate, whether any single parameter scored 3, and the risk band |
The types it declares, generated into your project
@dataclass(frozen=True)
class News2Observations:
"""One set of observations, as recorded on the NEWS2 chart."""
#: breaths per minute, 1 to 80
respiration_rate: int
#: oxygen saturation in percent, 1 to 100
spo2: int
#: 1, or 2 only for confirmed hypercapnic respiratory failure on a clinician's direction
spo2_scale: int
#: true when supplemental oxygen is needed to keep the prescribed saturation
on_oxygen: bool
#: mmHg, 20 to 300
systolic_bp: int
#: beats per minute, 1 to 300
pulse: int
#: ACVPU: alert, new confusion, voice, pain or unresponsive
consciousness: Acvpu
#: degrees Celsius, 25.0 to 45.0
temperature: float
Acvpu = Literal["alert", "confusion", "voice", "pain", "unresponsive"]
News2Risk = Literal["low", "low-medium", "medium", "high"]
@dataclass(frozen=True)
class News2Score:
"""The chart's scores for each parameter, their sum and the clinical risk it triggers."""
#: aggregate NEW score, 0 to 20
total: int
respiration_rate: int
spo2: int
#: 2 on supplemental oxygen, else 0
air_or_oxygen: int
systolic_bp: int
pulse: int
consciousness: int
temperature: int
#: true when any single parameter scored 3
red_score: bool
#: RCP chart 2: low, low-medium (a red score), medium or high
clinical_risk: News2Risk
Your code names it in one line, in the file that uses it
from fune.health.news2 import news2_score # health.news2@^1
import math
from .health_news2_types import News2Observations, News2Risk, News2Score
_CONSCIOUSNESS = ("alert", "confusion", "voice", "pain", "unresponsive")
def _check_int(name: str, value: object, low: int, high: int) -> None:
# Ranges are guard rails against unit and typing mistakes, not RCP rules;
# out of range is an error, never clamped.
if isinstance(value, bool) or not isinstance(value, int) or value < low or value > high:
raise ValueError("%s must be a whole number from %d to %d, received %r" % (name, low, high, value))
def _respiration_score(rate: int) -> int:
if rate <= 8:
return 3
if rate <= 11:
return 1
if rate <= 20:
return 0
if rate <= 24:
return 2
return 3
def _spo2_score(spo2: int, scale: int, on_oxygen: bool) -> int:
# Scale 2 targets 88-92%, so on oxygen a high saturation scores; on air it
# does not. Scale 1 ignores oxygen here: that is scored separately.
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 or not on_oxygen:
return 0
if spo2 <= 94:
return 1
if spo2 <= 96:
return 2
return 3
def _systolic_score(sbp: int) -> int:
if sbp <= 90:
return 3
if sbp <= 100:
return 2
if sbp <= 110:
return 1
if sbp <= 219:
return 0
return 3
def _pulse_score(pulse: int) -> int:
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
def _temperature_score(t: float) -> int:
# Chart rows are to one decimal place; a value between rows scores with
# the higher row ("at or below").
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
def news2_score(observations: News2Observations) -> News2Score:
"""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."""
o = observations
_check_int("respirationRate", o.respiration_rate, 1, 80)
_check_int("spo2", o.spo2, 1, 100)
if isinstance(o.spo2_scale, bool) or o.spo2_scale not in (1, 2) or not isinstance(o.spo2_scale, int):
raise ValueError("spo2Scale must be 1 or 2, received %r" % (o.spo2_scale,))
if not isinstance(o.on_oxygen, bool):
raise ValueError("onOxygen must be true or false, received %r" % (o.on_oxygen,))
_check_int("systolicBp", o.systolic_bp, 20, 300)
_check_int("pulse", o.pulse, 1, 300)
if o.consciousness not in _CONSCIOUSNESS:
raise ValueError(
"consciousness must be one of alert, confusion, voice, pain, unresponsive, received %s" % (o.consciousness,)
)
t = o.temperature
if isinstance(t, bool) or not isinstance(t, (int, float)) or not math.isfinite(t) or t < 25 or t > 45:
raise ValueError("temperature must be from 25.0 to 45.0 degrees Celsius, received %r" % (t,))
respiration = _respiration_score(o.respiration_rate)
spo2 = _spo2_score(o.spo2, o.spo2_scale, o.on_oxygen)
air_or_oxygen = 2 if o.on_oxygen else 0
systolic = _systolic_score(o.systolic_bp)
pulse = _pulse_score(o.pulse)
consciousness = 0 if o.consciousness == "alert" else 3
temperature = _temperature_score(t)
total = respiration + spo2 + air_or_oxygen + systolic + pulse + consciousness + temperature
red = 3 in (respiration, spo2, systolic, pulse, consciousness, temperature)
risk: News2Risk = "low"
if total >= 7:
risk = "high"
elif total >= 5:
risk = "medium"
elif red:
risk = "low-medium"
return News2Score(
total=total,
respiration_rate=respiration,
spo2=spo2,
air_or_oxygen=air_or_oxygen,
systolic_bp=systolic,
pulse=pulse,
consciousness=consciousness,
temperature=temperature,
red_score=red,
clinical_risk=risk,
)Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and nothing else, 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.news2
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./health.news2-1.0.1-python.fune, or fetch it from a terminal with fune pull health.news2@1.0.1:python.
The whole function, every language, is one file too: health.news2-1.0.1.fune, 50,450 bytes, sha256 ff104d3b779d004108990f2752a5f6c606cac001472954b3ed60b0cdd256246f. 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.
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Case | Arguments | Expected | |
|---|---|---|---|
| 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.
## 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 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.
**Not for clinical use; regulatory review pending.** Whether publishing this capability makes it a medical device is under regulatory review. Until that is settled it is a developer library only.
**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 acute-care 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.
## Notices
Reproduced from: 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.
1.0.1 marks it unreviewed (not for clinical use; regulatory review pending) and adds its attribution notices (NOTICE). The code and the tests are unchanged.
Files
| Path | Bytes |
|---|---|
| NOTICE | 220 |
| README.md | 5,566 |
| impl/python.py | 4,452 |
| impl/rust.rs | 6,725 |
| impl/typescript.ts | 4,270 |
| vectors.json | 20,819 |