clinical_calc: cited clinical formulas in pure Dart, with explicit units - DEV Community

DEV Community

Cover image for clinical_calc: cited clinical formulas in pure Dart, with explicit units
Abdeldjalil Chougui
Abdeldjalil Chougui

Posted on Originally published at abdeldjalilchougui.biz

clinical_calc: cited clinical formulas in pure Dart, with explicit units

Clinical formulas are short. Cockcroft-Gault fits on one line. That is the trap: the arithmetic is easy, and the mistakes are somewhere else. A creatinine value typed in µmol/L goes into an equation that expects mg/dL. An eGFR indexed to 1.73 m² is used where an absolute clearance in mL/min was needed. An adult equation is applied to a child. None of these crashes. Each one returns a number that looks reasonable.

clinical_calc is a pure Dart package built around those mistakes. Quantities carry their unit, impossible inputs throw typed errors, formulas refuse to run outside the population they were validated in, and every formula cites its original publication.

One thing first, because the README puts it first: this package is for use by qualified professionals, it is not a medical device, and results must be verified. It provides formulas only. It contains no drug-dosing tables and makes no recommendations. Results can be wrong if inputs are wrong or if a formula is used outside the population it was developed in.

What it does

Kidney. CKD-EPI 2021 creatinine (the race-free equation), CKD-EPI 2021 creatinine-cystatin C, MDRD 4-variable IDMS (175), Cockcroft-Gault with actual, ideal or adjusted weight, bedside Schwartz, KDIGO categories G1 to G5, and de-indexing to mL/min.

Body size. BSA with five formulas (Mosteller, DuBois, Haycock, Gehan-George, Boyd), BMI with WHO categories, ideal body weight (Devine), adjusted body weight and lean body weight (Janmahasatian).

Units. Creatinine (mg/dL, µmol/L, mg/L, with the factor 88.4), glucose, urea and BUN, calcium, albumin, bilirubin, height (cm, m, in, ft+in) and weight (kg, lb, g).

Dosing arithmetic. Dose per kg and per m², volume to draw, vial count with leftover, infusion rate in mL/h, drip rate in drops/min, concentration, infusion concentration and stability window. This is arithmetic on numbers you provide. The package knows no drug.

Other. Corrected calcium, anion gap, albumin-corrected anion gap, corrected sodium (Katz and Hillier), QTc (Bazett, Fridericia, Framingham), Child-Pugh, CHA2DS2-VASc and HAS-BLED.

Three design choices hold it together:

  • Explicit units. Quantities are value types built with a named unit: Creatinine.mgPerDl(1.2), Creatinine.umolPerL(106), Height.cm(172), Weight.lb(180). Indexed eGFR (IndexedGfr, in mL/min/1.73 m²) is a separate type from absolute clearance in mL/min, so the compiler stops you from mixing them.
  • Typed errors. InvalidInputException for negative, non-finite or non-physiological values. OutOfValidatedRangeException when a formula is used outside its validated population, for example CKD-EPI below 18 years. Both extend ClinicalCalcException and carry parameter, value and message.
  • Pure Dart, no dependencies. It works in Flutter, on the server and on the web.

How to use it

Add it from GitHub:

dependencies:
  clinical_calc:
    git: https://github.com/AbdeldjalilChougui/clinical_calc
Enter fullscreen mode Exit fullscreen mode

Then, from the README:

import 'package:clinical_calc/clinical_calc.dart';

void main() {
  final scr = Creatinine.umolPerL(106);
  final height = Height.cm(172);
  final weight = Weight.kg(84);

  final egfr = Kidney.ckdEpi2021Creatinine(
    creatinine: scr,
    ageYears: 67,
    sex: Sex.male,
  );
  print(egfr.mlPerMinPer173m2); // mL/min/1.73 m2
  print(egfr.kdigoCategory.label); // G2

  final bsa = BodySize.bsaMosteller(height, weight);
  print(egfr.deindex(bsa)); // absolute GFR, mL/min

  final crcl = Kidney.cockcroftGault(
    creatinine: scr,
    ageYears: 67,
    sex: Sex.male,
    weight: weight,
    height: height,
    weightBasis: CockcroftGaultWeight.adjusted,
  );
  print(crcl); // mL/min

  final dose = Dosing.doseForBsa(mgPerM2: 75, bsaM2: bsa);
  final plan = Dosing.vials(
    doseMg: dose,
    vialContentMg: 80,
    concentrationMgPerMl: 20,
  );
  print(plan.vialCount);
  print(plan.leftoverMl);

  print(Qtc.fridericia(qtMs: 440, heartRateBpm: 88));

  try {
    Creatinine.mgPerDl(-1);
  } on InvalidInputException catch (e) {
    print(e.parameter); // creatinine
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice what the types do in this example. The creatinine is entered in µmol/L and the equation gets what it needs without a conversion in your code. The eGFR comes back as an IndexedGfr, and you have to call deindex(bsa) to get mL/min. The vial plan gives you both the count and the leftover.

The README has a table for every formula with the exact equation and its source, for example Kidney.ckdEpi2021Creatinine (Inker LA et al., N Engl J Med 2021) or BodySize.bsaMosteller (Mosteller RD, N Engl J Med 1987). The same citation is in each API doc comment, so it shows up in your editor.

How it was validated

The test suite has 160 tests. Each formula is checked against worked examples with explicit tolerances.

  • External reference values. MDCalc-derived worked examples published in MedCalc-Bench v1.0 (Khandekar N. et al., NeurIPS 2024 Datasets and Benchmarks) cover CKD-EPI 2021, MDRD, Cockcroft-Gault, Mosteller BSA, BMI, Devine IBW, adjusted body weight, corrected calcium, anion gap, albumin-corrected anion gap, Hillier sodium, the three QTc formulas, Child-Pugh, CHA2DS2-VASc and HAS-BLED. Each test names the dataset row, and each value was re-checked by hand against the original equation. Rows whose values were wrong because the dataset converted units inaccurately were not used.
  • Derived reference values. CKD-EPI creatinine-cystatin C, bedside Schwartz, DuBois, Haycock, Gehan-George, Boyd, Janmahasatian, Katz and the branch and boundary cases were checked against values computed independently from the published equations. No public worked example was available for these. The README is clear about what that means: the tests confirm the equation as written, but they do not prove it against a third-party calculator.
  • Tolerances are stated in each test group: typically 0.001 mL/min/1.73 m² for eGFR, 1e-4 m² for BSA, 0.001 to 0.01 ms for QTc, and exact equality for scores.

The package bundles no datasets. The tests quote a small number of numeric inputs and expected results from MedCalc-Bench v1.0 (released under CC BY-SA 4.0), with attribution by row number.

Things to know

  • Formulas only. No drug monographs, no renal dose-adjustment tables, no thresholds for action. Score results are numbers or classes. Interpreting them is up to the clinician and current guidelines.
  • Validated populations. CKD-EPI, MDRD and Cockcroft-Gault throw OutOfValidatedRangeException below 18 years. Bedside Schwartz was developed in children aged 1 to 16 with CKD. Devine IBW is undefined below 152.4 cm and throws. The eGFR equations assume steady-state, IDMS-traceable creatinine, and are unreliable in acute kidney injury, extremes of muscle mass, amputation, pregnancy and similar situations.
  • MDRD race coefficient. The published 1.212 coefficient is applied only when you pass black: true. Current KDIGO and NKF-ASN guidance recommends the race-free CKD-EPI 2021 equations.
  • Child-Pugh uses the widely adopted INR cut-offs (below 1.7, 1.7 to 2.3, above 2.3) instead of Pugh's prothrombin-time prolongation in seconds, and bilirubin cut-offs for non-cholestatic disease.
  • Accepted input ranges are not clinical reference ranges. Creatinine up to 40 mg/dL, height 20 to 275 cm, weight 0.2 to 700 kg and heart rate 20 to 300 bpm exist to reject impossible data entry.
  • Rounding. Results come back at full double precision. Round for display only.

Why I built it

The problem the README describes is not the math. It is everything around the math: units that get mixed, indexed and absolute values that share the same double, formulas applied outside their population, and coefficients with no source written next to them.

A double egfr(double creatinine, int age, bool male) function cannot protect you from any of that. A Creatinine type, a separate IndexedGfr type, two exception classes and a citation next to every coefficient can. I also wanted the validation to be honest about its limits, which is why the README separates formulas checked against external worked examples from formulas checked only against the published equation.

Get the code

The source, the formula and citation tables, a runnable example/main.dart and the tests are on GitHub: https://github.com/AbdeldjalilChougui/clinical_calc. It is MIT licensed, and it is provided "as is", without warranty of any kind. Verify results independently before any clinical decision.


Originally published at abdeldjalilchougui.biz.

Top comments (0)