Functional Weave
Code in Rust

health.egfr Unreviewed

eGFR in mL/min/1.73 m² by the race-free CKD-EPI 2021 creatinine equation, from creatinine in µmol/L or mg/dL.

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

Pinned by 25 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 renal clinician or clinical scientist 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 renal clinician or clinical scientist 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.

Estimated glomerular filtration rate in mL/min/1.73 m² by the **CKD-EPI 2021 creatinine equation without race** (Inker et al., NEJM 2021):

For example

  • egfr_ckd_epi2021(0.7, mg-per-dl, 50, female) → value 105.3, reported 105 woman, 50, creatinine at the knot (0.7 mg/dL): 142 × 0.9938^50 × 1.012 = 105.3
  • egfr_ckd_epi2021(0.9, mg-per-dl, 50, male) → value 104, reported 104 man, 50, creatinine at the knot (0.9 mg/dL): 104.0
  • egfr_ckd_epi2021(1, mg-per-dl, 60, male) → value 86.2, reported 86 man, 60, 1.0 mg/dL: above the knot, exponent -1.200

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 egfr_ckd_epi2021(creatinine: f64, unit: &str, age_years: i64, sex: &str) -> Egfr
creatininefloatstandardised (IDMS-traceable) serum creatinine; 10 to 4000 µmol/L or 0.1 to 45 mg/dL
unitCreatinineUnitumol-per-l (UK) or mg-per-dl (US)
age_yearsintcompleted years, 18 to 120; the equation is for adults
sexSexthe sex the equation's coefficients use
returnsEgfr

The types it declares, generated into your project

// CreatinineUnit is a string in Rust, one of: "umol-per-l", "mg-per-dl".
// Parameters take it as &str and results hold it as String.

// Sex is a string in Rust, one of: "female", "male".
// Parameters take it as &str and results hold it as String.

/// The estimate to one decimal place, and the whole number a laboratory reports.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Egfr {
    /// mL/min/1.73 m², rounded half away from zero to 1 decimal place
    pub value: f64,
    /// mL/min/1.73 m², the unrounded estimate rounded half away from zero to a whole number
    pub reported: i64,
}

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

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

/// Inker et al. 2021, footnote to table 2: mg/dL × 88.4 = µmol/L.
const UMOL_PER_MG_DL: f64 = 88.4;

/// Estimated GFR by the CKD-EPI 2021 creatinine equation without race
/// (Inker et al., NEJM 2021;385:1737-49, table 2):
///
///   eGFR = 142 × min(Scr/κ, 1)^α × max(Scr/κ, 1)^-1.200 × 0.9938^age × 1.012 [if female]
///
/// with Scr in mg/dL, κ 0.7 (female) or 0.9 (male), α -0.241 (female) or
/// -0.302 (male). Powers go through math.pow so all three languages produce
/// the same double; the whole-number result is rounded from the unrounded
/// estimate, never from the one-decimal value (143.4993 is 143, not 144).
///
/// # Panics
/// Panics on an unknown unit or sex, or a creatinine or age out of range.
pub fn egfr_ckd_epi2021(creatinine: f64, unit: &str, age_years: i64, sex: &str) -> Egfr {
    let (low, high) = match unit {
        "umol-per-l" => (10.0, 4000.0),
        "mg-per-dl" => (0.1, 45.0),
        _ => panic!("unit must be umol-per-l or mg-per-dl, received {}", unit),
    };
    if !creatinine.is_finite() || creatinine < low || creatinine > high {
        panic!("creatinine must be a number from {} to {} {}, received {}", low, high, unit, creatinine);
    }
    if !(18..=120).contains(&age_years) {
        panic!("ageYears must be a whole number from 18 to 120, received {}", age_years);
    }
    if sex != "female" && sex != "male" {
        panic!("sex must be female or male, received {}", sex);
    }
    let female = sex == "female";
    let scr = if unit == "umol-per-l" { creatinine / UMOL_PER_MG_DL } else { creatinine };
    let kappa = if female { 0.7 } else { 0.9 };
    let alpha = if female { -0.241 } else { -0.302 };
    let ratio = scr / kappa;
    // Below the knot the max() term is 1, above it the min() term is 1.
    let creatinine_term = if ratio <= 1.0 { pow(ratio, alpha) } else { pow(ratio, -1.2) };
    let mut estimate = 142.0 * creatinine_term * pow(0.9938, age_years as f64);
    if female {
        estimate = estimate * 1.012;
    }
    Egfr { value: round_float(estimate, 1), reported: round_float(estimate, 0) as i64 }
}

pub fn egfr_to_value(result: &Egfr) -> Value {
    Value::obj(vec![("value", Value::Float(result.value)), ("reported", Value::Int(result.reported))])
}

pub fn fune_vector(args: &[Value]) -> Value {
    // Refuse what the typed signature cannot hold, with the wording TypeScript
    // and Python use, rather than let the conversion quietly change it.
    let unit = match &args[1] {
        Value::Str(s) => s.as_str(),
        other => panic!("unit must be umol-per-l or mg-per-dl, received {:?}", other),
    };
    let creatinine = match &args[0] {
        Value::Int(_) | Value::Float(_) => args[0].as_f64(),
        other => panic!("creatinine must be a number, received {:?}", other),
    };
    let age = match &args[2] {
        Value::Int(i) => *i,
        other => panic!("ageYears must be a whole number from 18 to 120, received {:?}", other),
    };
    let sex = match &args[3] {
        Value::Str(s) => s.as_str(),
        other => panic!("sex must be female or male, received {:?}", other),
    };
    egfr_to_value(&egfr_ckd_epi2021(creatinine, unit, age, sex))
}

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.egfr
Download for Rust health.egfr-1.0.1-rust.fune · 14,892 bytes sha256 37a15c7047771cbc996be37159ad4896c1dca470d9f64ed0ff555441bb2ed29b

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

The whole function, every language, is one file too: health.egfr-1.0.1.fune, 19,595 bytes, sha256 7568ccd75178204e88fa3b4010605a1598a6e4d83d65791523d0dad63d96c330. 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.egfr

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

// fune: after health.egfr

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

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

// fune: step health.egfr 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
woman, 50, creatinine at the knot (0.7 mg/dL): 142 × 0.9938^50 × 1.012 = 105.3 0.7, mg-per-dl, 50, female → value 105.3, reported 105
man, 50, creatinine at the knot (0.9 mg/dL): 104.0 0.9, mg-per-dl, 50, male → value 104, reported 104
man, 60, 1.0 mg/dL: above the knot, exponent -1.200 1, mg-per-dl, 60, male → value 86.2, reported 86
man, 60, 80 µmol/L (0.905 mg/dL) 80, umol-per-l, 60, male → value 97.1, reported 97
woman, 30, 62 µmol/L: 118.97 is 119.0 62, umol-per-l, 30, female → value 119, reported 119
woman, 75, 1.2 mg/dL: CKD stage G3a territory 1.2, mg-per-dl, 75, female → value 47.2, reported 47
woman, 70, 100 µmol/L 100, umol-per-l, 70, female → value 52.3, reported 52
man, 45, 150 µmol/L 150, umol-per-l, 45, male → value 50.1, reported 50
man, 80, 300 µmol/L: 17.56 reports as 18 300, umol-per-l, 80, male → value 17.6, reported 18
woman, 25, 0.5 mg/dL: below the knot, exponent -0.241 0.5, mg-per-dl, 25, female → value 133.4, reported 133
Show the other 15 tests
CaseArgumentsExpected
man, 18, 0.6 mg/dL: 143.4993 is 143.5 to one place but reports as 143, not 144 0.6, mg-per-dl, 18, male → value 143.5, reported 143
88.4 µmol/L is exactly 1 mg/dL 88.4, umol-per-l, 40, male → value 97.6, reported 98
61.88 µmol/L is the female knot, 0.7 mg/dL 61.88, umol-per-l, 40, female → value 112.1, reported 112
79.56 µmol/L is the male knot, 0.9 mg/dL 79.56, umol-per-l, 40, male → value 110.7, reported 111
man, 65, 3.0 mg/dL: stage G4 3, mg-per-dl, 65, male → value 22.3, reported 22
woman, 55, 700 µmol/L: kidney failure range 700, umol-per-l, 55, female → value 5.6, reported 6
the oldest accepted age 1.5, mg-per-dl, 120, male → value 36.5, reported 36
the youngest accepted age 0.8, mg-per-dl, 18, female → value 109.5, reported 109
a child is refused: the equation is for adults 0.5, mg-per-dl, 17, male → error: ageYears must be a whole number from 18 to 120
a fractional age is refused 0.9, mg-per-dl, 50.5, male → error: ageYears must be a whole number from 18 to 120
µmol/L entered with the mg/dL unit is refused, not read as 80 mg/dL 80, mg-per-dl, 60, male → error: creatinine must be a number from 0.1 to 45 mg-per-dl
mg/dL entered with the µmol/L unit is refused, not read as 0.9 µmol/L 0.9, umol-per-l, 60, male → error: creatinine must be a number from 10 to 4000 umol-per-l
zero creatinine is refused 0, umol-per-l, 60, male → error: creatinine must be a number
an unknown unit is refused 80, mmol/L, 60, male → error: unit must be umol-per-l or mg-per-dl
an unknown sex code is refused 80, umol-per-l, 60, F → error: sex must be female or male

More from the author

eGFR = 142 × min(Scr/κ, 1)^α × max(Scr/κ, 1)^-1.200 × 0.9938^age × 1.012 [if female]

| | female | male | |---|---|---| | κ | 0.7 | 0.9 | | α | -0.241 | -0.302 |

Scr is standardised (IDMS-traceable) serum creatinine in mg/dL; age is in years. Creatinine in µmol/L, as UK laboratories report it, is divided by 88.4 first (the paper's conversion: mg/dL × 88.4 = µmol/L).

## What comes back

- `value`: the estimate rounded half away from zero to one decimal place. - `reported`: the whole number a laboratory reports, rounded from the **unrounded** estimate. Rounding the one-decimal value again is a double rounding: a man of 18 with 0.6 mg/dL is 143.4993, which is 143.5 to one place and 143 as a whole number, not 144.

Laboratories often print values above 90 as "> 90"; that presentation is the caller's. Staging (G1 to G5) and the decision to act on a result are clinical, and not done here.

## Inputs

- **Unit is explicit.** `umol-per-l` or `mg-per-dl`. Each has its own range, 10 to 4000 µmol/L and 0.1 to 45 mg/dL, so a value in the wrong unit (80 given as mg/dL, 0.9 given as µmol/L) is refused instead of producing a plausible-looking wrong answer. The ranges are guard rails for a clinician to confirm; they are not from the paper. - **Adults only**, 18 to 120 whole years; the equation was developed and validated in adults. - **Sex** is `female` or `male`, as the coefficients need one of the two. - Nothing is clamped: an out-of-range input is an error.

## Precision

Every power goes through `math.pow` (never the platform `Math.pow` or `powf`), which returns the same double in TypeScript, Python and Rust, and the rounding is `math.round-float`, so the vectors compare exactly. The paper gives no worked example; the vectors were computed from the equation to 60 significant digits, independently of this implementation.

## Source

Inker LA, Eneanya ND, Coresh J, et al., for the Chronic Kidney Disease Epidemiology Collaboration. *New Creatinine- and Cystatin C-Based Equations to Estimate GFR without Race.* N Engl J Med 2021;385(19):1737-1749. doi:10.1056/NEJMoa2102953 (PMC8822996). Table 2, row "2021 CKD-EPI creatinine (2009 CKD-EPI creatinine fit without race); eGFRcr(AS), new": 142; F -0.241, M -0.302; -1.200; 0.9938; 1.012; with the footnote "κ is 0.7 for female participants and 0.9 for male participants". Read from the PubMed Central full text. The same equation and constants are published by the National Kidney Foundation (https://www.kidney.org/ckd-epi-creatinine-equation-2021) and the NIDDK, which also gives the µmol/L conversion (divide by 88.4).

## 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 renal clinician or clinical scientist 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 renal clinician or clinical scientist 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,602
impl/python.py2,265
impl/rust.rs3,281
impl/typescript.ts2,263
vectors.json3,760