Functional Weave
Code in Python

health.egfr@1.0.1

impl/rust.rs

3,281 bytes · the Rust implementation · view 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))
}