use super::funejson::Value; use super::math_pow::pow; use super::math_round_float::round_float; /// 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)) }