health.bmi Unreviewed
Adult body mass index (kg/m², 1 decimal place) and its NICE/WHO weight category.
1.0.1 · published 2026-10-03 by charlie · Anterra
Pinned by 22 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
Body mass index for an **adult**: weight in kilograms divided by the square of height in metres, rounded half away from zero to one decimal place, with the weight category it falls in.
**Adults only.** These bands do not apply to children and young people, whose BMI is read against age- and sex-specific centile charts (UK-WHO / UK90). Do not call this for anyone under 18.
For example
bmi(70, 175)→ value 22.9, category healthy-weight 70 kg, 175 cm: 22.857 is 22.9, healthy weightbmi(63.5, 165.1)→ value 23.3, category healthy-weight 63.5 kg, 165.1 cm (10 st, 5 ft 5 in): 23.3bmi(50, 180)→ value 15.4, category underweight 50 kg, 180 cm: 15.4, underweight
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 bmi(weight_kg: f64, height_cm: f64) -> Bmi
| weight_kg | float | body weight in kilograms, 10 to 650 |
| height_cm | float | height in centimetres, 50 to 272; not metres |
| returns | Bmi | adults only: children need BMI centiles, not these categories |
The types it declares, generated into your project
// BmiCategory is a string in Rust, one of: "underweight", "healthy-weight", "overweight", "obesity-class-1", "obesity-class-2", "obesity-class-3".
// Parameters take it as &str and results hold it as String.
/// The index and the category read from it.
#[derive(Debug, Clone, PartialEq)]
pub struct Bmi {
/// kg/m², rounded half away from zero to 1 decimal place
pub value: f64,
/// read from the rounded value, as the published bands are
pub category: String,
}
Your code names it in one line, in the file that uses it
fune!(health.bmi@^1); // then call bmi(…)
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::math_round_float::round_float; ← from math.round-float ^1.0.0 · built alongside by fune
fn check_range(name: &str, value: f64, low: f64, high: f64, unit: &str) {
if !value.is_finite() || value < low || value > high {
panic!("{} must be a number from {} to {} {}, received {}", name, low, high, unit, value);
}
}
/// Body mass index for an adult: weight over height squared, to one decimal
/// place, and the band it falls in. The bands are published to one decimal
/// place (18.5-24.9, 25-29.9, ...), so the category is read from the rounded
/// index: 24.97 is reported as 25.0 and is overweight, not healthy.
///
/// # Panics
/// Panics when the weight or height is outside its range.
pub fn bmi(weight_kg: f64, height_cm: f64) -> Bmi {
check_range("weightKg", weight_kg, 10.0, 650.0, "kg");
// Height in metres (1.75) is the commonest unit slip; 50 cm is the floor.
check_range("heightCm", height_cm, 50.0, 272.0, "cm");
let metres = height_cm / 100.0;
let value = round_float(weight_kg / (metres * metres), 1);
let category = if value < 18.5 {
"underweight"
} else if value <= 24.9 {
"healthy-weight"
} else if value <= 29.9 {
"overweight"
} else if value <= 34.9 {
"obesity-class-1"
} else if value <= 39.9 {
"obesity-class-2"
} else {
"obesity-class-3"
};
Bmi { value, category: category.to_string() }
}
pub fn bmi_to_value(result: &Bmi) -> Value {
Value::obj(vec![("value", Value::Float(result.value)), ("category", Value::str(&result.category))])
}
fn number_arg(value: &Value, name: &str, low: f64, high: f64, unit: &str) -> f64 {
match value {
Value::Int(_) | Value::Float(_) => value.as_f64(),
_ => panic!("{} must be a number from {} to {} {}, received {:?}", name, low, high, unit, value),
}
}
pub fn fune_vector(args: &[Value]) -> Value {
bmi_to_value(&bmi(
number_arg(&args[0], "weightKg", 10.0, 650.0, "kg"),
number_arg(&args[1], "heightCm", 50.0, 272.0, "cm"),
))
}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.bmi
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./health.bmi-1.0.1-rust.fune, or fetch it from a terminal with fune pull health.bmi@1.0.1:rust.
The whole function, every language, is one file too: health.bmi-1.0.1.fune, 14,901 bytes, sha256 2288b649366d888521b2232654f5e07174beeac4280a8d070cc3bdb001bcb593. 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.bmi
after — your function gets the result and the arguments, and returns the final result.
// fune: after health.bmi
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 math.round-float in health.bmi
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.bmi --steps.
// fune: step health.bmi 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 | |
|---|---|---|---|
| 70 kg, 175 cm: 22.857 is 22.9, healthy weight | 70, 175 | → | value 22.9, category healthy-weight |
| 63.5 kg, 165.1 cm (10 st, 5 ft 5 in): 23.3 | 63.5, 165.1 | → | value 23.3, category healthy-weight |
| 50 kg, 180 cm: 15.4, underweight | 50, 180 | → | value 15.4, category underweight |
| exactly 18.5 is the bottom of healthy weight | 74, 200 | → | value 18.5, category healthy-weight |
| 18.425 rounds to 18.4, underweight | 73.7, 200 | → | value 18.4, category underweight |
| exactly 24.9 is still healthy weight | 99.6, 200 | → | value 24.9, category healthy-weight |
| 24.975 is reported 25.0 and is overweight: a naive < 25 on the raw value says healthy | 99.9, 200 | → | value 25, category overweight |
| 29.925 rounds to 29.9, overweight | 119.7, 200 | → | value 29.9, category overweight |
| exactly 30.0 is obesity class 1 | 120, 200 | → | value 30, category obesity-class-1 |
| 34.975 is reported 35.0, obesity class 2 | 139.9, 200 | → | value 35, category obesity-class-2 |
Show the other 12 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| 92 kg, 160 cm: 35.9375 is 35.9, obesity class 2 | 92, 160 | → | value 35.9, category obesity-class-2 |
| 39.925 rounds to 39.9, obesity class 2 | 159.7, 200 | → | value 39.9, category obesity-class-2 |
| 39.975 is reported 40.0, obesity class 3 | 159.9, 200 | → | value 40, category obesity-class-3 |
| 110 kg, 160 cm: 42.97 is 43.0, obesity class 3 | 110, 160 | → | value 43, category obesity-class-3 |
| the lowest accepted weight and height | 10, 50 | → | value 40, category obesity-class-3 |
| the highest accepted weight and height | 650, 272 | → | value 87.9, category obesity-class-3 |
| height in metres is refused, not read as 1.75 cm | 70, 1.75 | → | error: heightCm must be a number from 50 to 272 cm |
| zero weight is refused | 0, 175 | → | error: weightKg must be a number from 10 to 650 kg |
| negative weight is refused | -70, 175 | → | error: weightKg must be a number from 10 to 650 kg |
| weight in pounds past the maximum is refused | 700, 175 | → | error: weightKg must be a number from 10 to 650 kg |
| a height over 272 cm is refused | 70, 1,750 | → | error: heightCm must be a number from 50 to 272 cm |
| a weight given as text is refused | 70, 175 | → | error: weightKg must be a number from 10 to 650 kg |
More from the author
## Categories
| category | BMI (kg/m²) | |-------------------|--------------| | `underweight` | below 18.5 | | `healthy-weight` | 18.5 to 24.9 | | `overweight` | 25 to 29.9 | | `obesity-class-1` | 30 to 34.9 | | `obesity-class-2` | 35 to 39.9 | | `obesity-class-3` | 40 or more |
The bands are published to one decimal place, so the category is read from the rounded index. 99.9 kg at 200 cm is 24.975, reported as 25.0, and is overweight; a classifier that compares the raw 24.975 with `< 25` says healthy weight, which disagrees with the number it prints.
## What it deliberately does not do
- **Lower thresholds for some ethnic backgrounds.** NICE recommends lower thresholds (overweight 23 to 27.4, obesity 27.5 or more) for people with a South Asian, Chinese, other Asian, Middle Eastern, Black African or African-Caribbean family background. This function returns the general bands only; apply the lower thresholds to `value` where they are relevant. - **Interpretation.** BMI does not measure body fat or central adiposity; NICE asks for clinical judgement, particularly in the healthy-weight band, and a measure of central adiposity alongside it.
## Inputs
Weight is 10 to 650 kg and height 50 to 272 cm. Anything outside is refused, not clamped: in practice it is a unit slip, such as height in metres (1.75) or weight in pounds. The ranges are wide enough to include every recorded adult. The arithmetic is plain division and multiplication, which is correctly rounded in every language, and the rounding is `math.round-float`, so the three implementations return the same double (`floats exact`).
## Sources
- NICE guideline NG246, *Overweight and obesity management*, recommendations 1.9.10 (the bands above, adults) and 1.9.11 (lower thresholds for some ethnic backgrounds): https://www.nice.org.uk/guidance/ng246/chapter/Identifying-and-assessing-overweight-obesity-and-central-adiposity - The cut-offs are those of the World Health Organization's adult classification (WHO, *Obesity: preventing and managing the global epidemic*, Technical Report Series 894, 2000). `underweight` (below 18.5) is WHO's; NICE's list starts at healthy weight, 18.5. (The WHO report itself was not retrieved when this was written; every cut-off here was checked against NICE NG246.)
## 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,078 |
| impl/python.py | 1,507 |
| impl/rust.rs | 2,039 |
| impl/typescript.ts | 1,425 |
| vectors.json | 3,028 |