Functional Weave
Code in Python

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 weight
  • bmi(63.5, 165.1) → value 23.3, category healthy-weight 63.5 kg, 165.1 cm (10 st, 5 ft 5 in): 23.3
  • bmi(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.

def bmi(weight_kg: float, height_cm: float) -> Bmi
weight_kgfloatbody weight in kilograms, 10 to 650
height_cmfloatheight in centimetres, 50 to 272; not metres
returnsBmiadults only: children need BMI centiles, not these categories

The types it declares, generated into your project

BmiCategory = Literal["underweight", "healthy-weight", "overweight", "obesity-class-1", "obesity-class-2", "obesity-class-3"]

@dataclass(frozen=True)
class Bmi:
    """The index and the category read from it."""

    #: kg/m², rounded half away from zero to 1 decimal place
    value: float
    #: read from the rounded value, as the published bands are
    category: BmiCategory

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

from fune.health.bmi import bmi  # health.bmi@^1
impl/python.py · 36 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.

import math

from .health_bmi_types import Bmi, BmiCategory
from .math_round_float import round_float  ← from math.round-float ^1.0.0 · built alongside by fune


def _check_range(name: str, value: float, low: float, high: float, unit: str) -> None:
    ok = not isinstance(value, bool) and isinstance(value, (int, float)) and math.isfinite(value)
    if not ok or value < low or value > high:
        raise ValueError("%s must be a number from %s to %s %s, received %r" % (name, low, high, unit, value))


def bmi(weight_kg: float, height_cm: float) -> Bmi:
    """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."""
    _check_range("weightKg", weight_kg, 10, 650, "kg")
    # Height in metres (1.75) is the commonest unit slip; 50 cm is the floor.
    _check_range("heightCm", height_cm, 50, 272, "cm")
    metres = float(height_cm) / 100
    value = round_float(float(weight_kg) / (metres * metres), 1)
    category: BmiCategory
    if value < 18.5:
        category = "underweight"
    elif value <= 24.9:
        category = "healthy-weight"
    elif value <= 29.9:
        category = "overweight"
    elif value <= 34.9:
        category = "obesity-class-1"
    elif value <= 39.9:
        category = "obesity-class-2"
    else:
        category = "obesity-class-3"
    return Bmi(value=value, category=category)

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.bmi
Download for Python health.bmi-1.0.1-python.fune · 11,260 bytes sha256 3cdf1013081438d5356dcb5983640d4af189258b14749e1ebb5614e16ffca34a

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

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.

CaseArgumentsExpected
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
CaseArgumentsExpected
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

PathBytes
README.md4,078
impl/python.py1,507
impl/rust.rs2,039
impl/typescript.ts1,425
vectors.json3,028