Functional Weave
Code in Rust

health.bsa Unreviewed

Body surface area in m² by the Mosteller and the DuBois & DuBois formulas, to 2 decimal places.

1.0.1 · published 2026-10-03 by charlie · Anterra

Pinned by 16 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 clinician or pharmacist 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 or pharmacist 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.

Body surface area (BSA) in square metres from weight and height, by the two formulas in common clinical use, each rounded half away from zero to two decimal places:

For example

  • body_surface_area(60, 150) → mosteller 1.58, du bois 1.55 DuBois & DuBois's own worked example: 150 cm, 60 kg is 1.55 m² (Mosteller 1.58)
  • body_surface_area(80, 180) → mosteller 2, du bois 2 180 cm, 80 kg: Mosteller is exactly √4 = 2.00; DuBois 1.9964 rounds to 2.00
  • body_surface_area(70, 170) → mosteller 1.82, du bois 1.81 170 cm, 70 kg: the formulas disagree in the second place

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 body_surface_area(weight_kg: f64, height_cm: f64) -> BodySurfaceArea
weight_kgfloatbody weight in kilograms, 1 to 650
height_cmfloatheight in centimetres, 30 to 272; not metres
returnsBodySurfaceArea

The type it declares, generated into your project

/// Both formulas, so a caller uses the one its protocol names and never mixes them.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BodySurfaceArea {
    /// m², √(height cm × weight kg ÷ 3600), rounded half away from zero to 2 places
    pub mosteller: f64,
    /// m², 71.84 × W^0.425 × H^0.725 cm² ÷ 10000, rounded half away from zero to 2 places
    pub du_bois: f64,
}

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

fune!(health.bsa@^1);  // then call body_surface_area(…)
impl/rust.rs · 45 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.

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_pow::pow;  ← from math.pow ^1.0.0 · built alongside by fune
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 surface area by both published formulas. They differ by up to a few
/// per cent, and a dose protocol names one, so both are returned rather than a
/// silent default. Powers go through math.pow, never `powf` or `sqrt`, so the
/// three languages agree to the bit before rounding.
///
/// # Panics
/// Panics when the weight or height is outside its range.
pub fn body_surface_area(weight_kg: f64, height_cm: f64) -> BodySurfaceArea {
    check_range("weightKg", weight_kg, 1.0, 650.0, "kg");
    // Height in metres (1.75) is the commonest unit slip.
    check_range("heightCm", height_cm, 30.0, 272.0, "cm");
    // Mosteller (1987): BSA (m²) = ([height (cm) × weight (kg)] / 3600)^½.
    let mosteller = pow((height_cm * weight_kg) / 3600.0, 0.5);
    // DuBois & DuBois (1916): A = W^0.425 × H^0.725 × 71.84, A in cm².
    let du_bois = (pow(weight_kg, 0.425) * pow(height_cm, 0.725) * 71.84) / 10000.0;
    BodySurfaceArea { mosteller: round_float(mosteller, 2), du_bois: round_float(du_bois, 2) }
}

pub fn body_surface_area_to_value(result: &BodySurfaceArea) -> Value {
    Value::obj(vec![("mosteller", Value::Float(result.mosteller)), ("duBois", Value::Float(result.du_bois))])
}

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 {
    body_surface_area_to_value(&body_surface_area(
        number_arg(&args[0], "weightKg", 1.0, 650.0, "kg"),
        number_arg(&args[1], "heightCm", 30.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 2 dependencies, 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.bsa
Download for Rust health.bsa-1.0.1-rust.fune · 10,861 bytes sha256 ba12b751fcd094cef429ffe69ecd6261f3a3db9c3a567f43f7a7bd18e0ad00ba

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

The whole function, every language, is one file too: health.bsa-1.0.1.fune, 13,763 bytes, sha256 fe964241429628530d99653abd5ac16c3aa6ebad194a08439b4b61137669358c. 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.bsa

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

// fune: after health.bsa

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.pow in health.bsa
// fune: replace math.round-float in health.bsa

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

// fune: step health.bsa 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
DuBois & DuBois's own worked example: 150 cm, 60 kg is 1.55 m² (Mosteller 1.58) 60, 150 → mosteller 1.58, du bois 1.55
180 cm, 80 kg: Mosteller is exactly √4 = 2.00; DuBois 1.9964 rounds to 2.00 80, 180 → mosteller 2, du bois 2
170 cm, 70 kg: the formulas disagree in the second place 70, 170 → mosteller 1.82, du bois 1.81
175 cm, 70 kg: here DuBois is the larger 70, 175 → mosteller 1.84, du bois 1.85
152 cm, 45 kg 45, 152 → mosteller 1.38, du bois 1.38
190 cm, 100 kg 100, 190 → mosteller 2.3, du bois 2.28
165 cm, 150 kg: in obesity DuBois is 0.17 m² lower 150, 165 → mosteller 2.62, du bois 2.45
a child: 110 cm, 20 kg; DuBois 0.77502 is just above the 0.775 tie 20, 110 → mosteller 0.78, du bois 0.78
a newborn: 50 cm, 3.5 kg 3.5, 50 → mosteller 0.22, du bois 0.21
the lowest accepted weight and height 1, 30 → mosteller 0.09, du bois 0.08
Show the other 6 tests
CaseArgumentsExpected
the highest accepted weight and height 650, 272 → mosteller 7.01, du bois 6.56
height in metres is refused 70, 1.75 → error: heightCm must be a number from 30 to 272 cm
zero weight is refused 0, 170 → error: weightKg must be a number from 1 to 650 kg
negative height is refused 70, -170 → error: heightCm must be a number from 30 to 272 cm
a weight in grams is refused 70,000, 170 → error: weightKg must be a number from 1 to 650 kg
a missing weight is refused —, 170 → error: weightKg must be a number from 1 to 650 kg

More from the author

- **Mosteller**: BSA (m²) = √( height (cm) × weight (kg) ÷ 3600 ) - **DuBois & DuBois**: A = W^0.425 × H^0.725 × 71.84, with A in cm², W in kg and H in cm; divided by 10 000 for m² (the familiar 0.007184 × W^0.425 × H^0.725)

## Why both

The two disagree by up to a few per cent, more in obesity (150 kg at 165 cm is 2.62 m² by Mosteller and 2.45 m² by DuBois). Chemotherapy verification standards accept either but insist on consistency: a protocol, a prescriber and a pharmacist checking the prescription must all use the same formula. So both are returned, named, and nothing picks one silently.

## Edge cases

- Weight 1 to 650 kg, height 30 to 272 cm; anything else is refused, never clamped. Height in metres (1.75) is the usual slip. These ranges are guard rails for a clinician to confirm, not part of either formula. - DuBois & DuBois derived their formula from nine subjects, one a child; paediatric protocols often specify Mosteller (or another formula) instead. - Two decimal places is the precision BSA is prescribed at. Some units round further (to 0.1 m²) for dose banding; that is the caller's step. - Powers use `math.pow` (and the square root is `math.pow(x, 0.5)`), not the platform library, so the three languages agree to the bit (`floats exact`).

## Sources

- Du Bois D, Du Bois EF. *Clinical calorimetry, tenth paper: A formula to estimate the approximate surface area if height and weight be known.* Arch Intern Med 1916;17(6):863-871. The equation, the constant 71.84 and the worked example (150 cm, 60 kg, 1.55 m²) were read from the scanned original: https://archive.org/details/sim_jama-internal-medicine_1916-06-15_17_6 - Mosteller RD. *Simplified calculation of body-surface area.* N Engl J Med 1987;317(17):1098. doi:10.1056/NEJM198710223171717. The letter itself is behind a paywall and was not retrieved; the equation and constant 3600 were confirmed from the NHS Thames Valley Cancer Network, *Verification standards SOP* (2015), which states "BSA (m²) = ([Height(cm) x Weight(kg)]/3600)½" citing the letter, and that "the DuBois or Mosteller formulae are accepted as the standard BSA nomogram for adults": https://thamesvalleycanceralliance.nhs.uk/wp-content/uploads/2022/03/TVCN-Verification-SOP-November-2015.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 or pharmacist 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 clinician or pharmacist 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 (not for clinical use; regulatory review pending). The code and the tests are unchanged.

Files

PathBytes
README.md4,278
impl/python.py1,411
impl/rust.rs2,045
impl/typescript.ts1,371
vectors.json2,071