// ============================================================ // CALCULATORS — incremental React port. // Low-risk pure formulas run here; high-risk table-driven calculators // stay in the vanilla viewer until legacy vectors land. // // WHY this is gated on test vectors (from the migration checkpoint): // • AAP 2017 BP percentile uses Rosner quantile splines with long // hard-coded coefficient arrays. // • Fenton 2013 LMS preterm growth carries 210 validated cases. // • AAP 2022 bilirubin phototherapy + exchange: per-week risk // curves, 1190 validated cases. // • Bhutani nomogram risk zones. // • APLS + Best Guess weight-for-age. // // Per the checkpoint: "An LLM will sometimes 'simplify' a long array // of numbers and silently break it — don't let that happen." Every // calculator needs a JSON vector file (~20 known inputs + expected // outputs captured from public/js/calculators.js) before its React // port lands, and the port must match every vector byte-for-byte. // // Pill order + labels match public/components/calculators.html. // ============================================================ import { useState } from 'react'; import { calculateGcs, calculateMostellerBsa, calculateWeightBasedDose, } from '@shared/clinical/calculators'; import { classifyBhutani, classifyAapBili, type BiliRisk } from '@shared/clinical/bilirubin'; import { fentonWeightForAge, classifySizeForAge, type Sex } from '@shared/clinical/fenton'; const card = 'rounded-lg border border-border bg-card p-5 space-y-3'; const btnPrimary = 'rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium'; const btnGhost = 'rounded-md border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted'; const field = 'space-y-1'; const label = 'block text-xs font-medium text-muted-foreground'; const input = 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring'; const resultBox = 'rounded-lg border border-border bg-muted/40 p-4'; const errorBox = 'rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30 dark:text-red-200'; interface Pill { id: string; label: string; summary: string; source: string; // where the formulas live ported?: boolean; } const PILLS: Pill[] = [ { id: 'bp', label: 'BP Percentile', summary: 'AAP 2017 age/height/sex-adjusted BP percentiles (Rosner quantile splines).', source: 'AAP 2017 (Flynn) — Rosner splines' }, { id: 'bmi', label: 'BMI Percentile', summary: 'BMI-for-age (CDC 2000 z-score tables).', source: 'CDC 2000 LMS' }, { id: 'growth', label: 'Growth Charts', summary: 'Fenton 2013 preterm weight-for-GA with Z-score + percentile + SGA/AGA/LGA classification.', source: 'Fenton 2013 LMS', ported: true }, { id: 'bili', label: 'Bilirubin', summary: 'AAP 2022 phototherapy + exchange thresholds and Bhutani nomogram risk zones.', source: 'AAP 2022 (Kemper) + Bhutani 1999', ported: true }, { id: 'vitals', label: 'Vital Signs', summary: 'Normal HR / RR / BP ranges by age.', source: 'PALS + AHA reference' }, { id: 'bsa', label: 'Body Surface Area', summary: 'Mosteller body surface area formula.', source: 'Mosteller 1987', ported: true }, { id: 'dose', label: 'Weight-Based Dosing', summary: 'Generic mg/kg dosing with optional max-dose cap and concentration conversion.', source: 'Legacy calculator formula', ported: true }, { id: 'resus', label: 'Resus Meds', summary: 'Code-cart dosing (epinephrine, amiodarone, atropine, etc.).', source: 'PALS' }, { id: 'gcs', label: 'GCS', summary: 'Child/adult and infant Glasgow Coma Scale variants.', source: 'Teasdale + pediatric modification', ported: true }, { id: 'equipment', label: 'Equipment', summary: 'ETT size, blade, NG, Foley, suction by age/weight.', source: 'PALS + Broselow cross-reference' }, ]; function parseOptionalNumber(value: string): number | null { if (!value.trim()) return null; const parsed = Number(value); return Number.isFinite(parsed) ? parsed : null; } function FormField({ id, labelText, value, onChange, min, max, step = '0.1', placeholder, }: { id: string; labelText: string; value: string; onChange: (value: string) => void; min?: string; max?: string; step?: string; placeholder?: string; }) { return (
onChange(event.target.value)} placeholder={placeholder} className={input} />
); } function BsaPanel() { const [weight, setWeight] = useState(''); const [height, setHeight] = useState(''); const [result, setResult] = useState(null); const [error, setError] = useState(''); function calculate() { const next = calculateMostellerBsa(Number(weight), Number(height)); if (next == null) { setError('Enter a valid weight and height.'); setResult(null); return; } setError(''); setResult(next); } function clear() { setWeight(''); setHeight(''); setResult(null); setError(''); } return (

Body Surface Area

Mosteller formula: BSA (m2) = sqrt(height(cm) x weight(kg) / 3600).

{error ?
{error}
: null} {result == null ? null : (
Mosteller BSA
{result.toFixed(3)} m²
{weight} kg, {height} cm
)}
); } function DosePanel() { const [weight, setWeight] = useState(''); const [dosePerKg, setDosePerKg] = useState(''); const [frequency, setFrequency] = useState('1'); const [maxDose, setMaxDose] = useState(''); const [concentration, setConcentration] = useState(''); const [result, setResult] = useState>(null); const [error, setError] = useState(''); function calculate() { const next = calculateWeightBasedDose({ weightKg: Number(weight), dosePerKg: Number(dosePerKg), frequencyPerDay: Number(frequency), maxSingleDoseMg: parseOptionalNumber(maxDose), concentrationMgPerMl: parseOptionalNumber(concentration), }); if (next == null) { setError('Enter a valid weight, mg/kg dose, and frequency.'); setResult(null); return; } setError(''); setResult(next); } function clear() { setWeight(''); setDosePerKg(''); setFrequency('1'); setMaxDose(''); setConcentration(''); setResult(null); setError(''); } return (

Weight-Based Dosing

Generic mg/kg calculator. Always verify medication-specific dosing against formulary and local policy.

{error ?
{error}
: null} {result == null ? null : (
Single Dose
{result.singleDoseMg.toFixed(1)} mg
{result.capped ?
Capped at max dose
: null}
Daily Total
{result.dailyDoseMg.toFixed(1)} mg/day
x {result.frequencyPerDay}/day
Volume
{result.volumeMl == null ? 'n/a' : `${result.volumeMl.toFixed(1)} mL`}
per dose
)}
); } const GCS_OPTIONS = { child: { eye: [ ['4', '4 - Spontaneous'], ['3', '3 - To speech'], ['2', '2 - To pain'], ['1', '1 - None'], ], verbal: [ ['5', '5 - Oriented'], ['4', '4 - Confused'], ['3', '3 - Inappropriate words'], ['2', '2 - Incomprehensible sounds'], ['1', '1 - None'], ], motor: [ ['6', '6 - Obeys commands'], ['5', '5 - Localizes pain'], ['4', '4 - Withdraws to pain'], ['3', '3 - Abnormal flexion'], ['2', '2 - Abnormal extension'], ['1', '1 - None'], ], }, infant: { eye: [ ['4', '4 - Spontaneous'], ['3', '3 - To speech/sound'], ['2', '2 - To painful stimuli'], ['1', '1 - None'], ], verbal: [ ['5', '5 - Coos/babbles'], ['4', '4 - Irritable cry'], ['3', '3 - Cries to pain'], ['2', '2 - Moans to pain'], ['1', '1 - None'], ], motor: [ ['6', '6 - Normal spontaneous movement'], ['5', '5 - Withdraws to touch'], ['4', '4 - Withdraws to pain'], ['3', '3 - Abnormal flexion'], ['2', '2 - Abnormal extension'], ['1', '1 - None'], ], }, } as const; function GcsSelect({ id, labelText, value, options, onChange, }: { id: string; labelText: string; value: string; options: readonly (readonly [string, string])[]; onChange: (value: string) => void; }) { return (
); } function GcsPanel() { const [scale, setScale] = useState<'child' | 'infant'>('child'); const [eye, setEye] = useState('4'); const [verbal, setVerbal] = useState('5'); const [motor, setMotor] = useState('6'); const result = calculateGcs(Number(eye), Number(verbal), Number(motor)); const options = GCS_OPTIONS[scale]; function switchScale(next: 'child' | 'infant') { setScale(next); setEye('4'); setVerbal('5'); setMotor('6'); } return (

Glasgow Coma Scale

Select responses to calculate child/adult or infant-modified GCS. Total score 3-15.

{result == null ? null : (
{scale === 'infant' ? 'Infant-modified GCS' : 'Child / adult GCS'}
GCS: {result.total}/15
{result.severity}
Interpretation: 13-15 Mild, 9-12 Moderate, 3-8 Severe/Coma.
)}
); } function LegacyPanel({ pill }: { pill: Pill }) { return (

{pill.label}

{pill.summary}

Source of truth: {pill.source}.

This calculator runs in the legacy viewer. A React port is gated on capturing test vectors from the vanilla implementation so the numerical output can be verified byte-for-byte — the migration checkpoint specifically flags this class of data as the one an LLM is most likely to silently simplify.

Open in legacy viewer
); } function BiliPanel() { const [mode, setMode] = useState<'aap' | 'bhutani'>('aap'); const [ga, setGa] = useState('38'); const [hours, setHours] = useState(''); const [tsb, setTsb] = useState(''); const [risk, setRisk] = useState('low'); const [aapResult, setAapResult] = useState | null>(null); const [bhutResult, setBhutResult] = useState | null>(null); const [error, setError] = useState(''); function calc() { const hoursNum = Number(hours); const tsbNum = Number(tsb); if (!Number.isFinite(hoursNum) || !Number.isFinite(tsbNum) || hoursNum <= 0 || tsbNum <= 0) { setError('Enter hours of life and TSB (mg/dL).'); setAapResult(null); setBhutResult(null); return; } setError(''); if (mode === 'aap') { const gaNum = Number(ga); if (!Number.isFinite(gaNum) || gaNum < 35) { setError('AAP 2022 thresholds apply to GA ≥35 weeks.'); setAapResult(null); return; } setAapResult(classifyAapBili(gaNum, hoursNum, tsbNum, risk)); setBhutResult(null); } else { setBhutResult(classifyBhutani(hoursNum, tsbNum)); setAapResult(null); } } const statusColor = aapResult ? aapResult.status === 'Above Exchange' ? 'text-red-800 bg-red-100' : aapResult.status === 'Above Phototherapy' ? 'text-red-700 bg-red-50' : 'text-green-700 bg-green-50' : ''; const zoneColor = bhutResult ? bhutResult.zone === 'High-Risk' ? 'text-red-800 bg-red-100' : bhutResult.zone === 'High-Intermediate' ? 'text-orange-700 bg-orange-50' : bhutResult.zone === 'Low-Intermediate' ? 'text-amber-700 bg-amber-50' : 'text-green-700 bg-green-50' : ''; return (

Bilirubin

{mode === 'aap' && ( <>
)}
{error &&
{error}
} {aapResult && (
{aapResult.status}
TSB {tsb} mg/dL at {hours} hours of life (GA {ga}w {risk === 'medium' ? 'with' : 'without'} risk factors)
Phototherapy
{aapResult.photoThreshold.toFixed(1)} mg/dL
Exchange
{aapResult.exchangeThreshold.toFixed(1)} mg/dL
AAP 2022 CPG (Kemper et al.). Always use clinical judgment.
)} {bhutResult && (
{bhutResult.zone} Zone
TSB {tsb} mg/dL at {hours} hours of life
40th %ile
{bhutResult.p40.toFixed(1)}
75th %ile
{bhutResult.p75.toFixed(1)}
95th %ile
{bhutResult.p95.toFixed(1)}
Bhutani 1999 hour-specific risk nomogram for infants ≥35 weeks GA.
)}
); } function GrowthPanel() { const [sex, setSex] = useState('male'); const [ga, setGa] = useState(''); const [weight, setWeight] = useState(''); const [result, setResult] = useState | null>(null); const [error, setError] = useState(''); function calc() { const gaNum = Number(ga); const wtNum = Number(weight); if (!Number.isFinite(gaNum) || !Number.isFinite(wtNum) || gaNum < 22 || gaNum > 50 || wtNum <= 0) { setError('Enter GA (22-50 weeks) and weight (grams).'); setResult(null); return; } setError(''); setResult(fentonWeightForAge(gaNum, wtNum, sex)); } const classification = result ? classifySizeForAge(result.percentile) : null; const classColor = classification === 'SGA' ? 'text-orange-700 bg-orange-50' : classification === 'LGA' ? 'text-amber-700 bg-amber-50' : 'text-green-700 bg-green-50'; return (

Fenton 2013 Preterm Growth

Weight-for-gestational-age Z-score + percentile + SGA/AGA/LGA classification.

{error &&
{error}
} {result && classification && (
{classification}
Percentile
{result.percentile.toFixed(1)}%
Z-score
{result.z.toFixed(2)}
Median (M)
{Math.round(result.M)} g
L / S
{result.L.toFixed(3)} / {result.S.toFixed(3)}
Fenton TR, Kim JH. Systematic review — revised Fenton growth chart for preterm infants. BMC Pediatr 2013;13:59.
)}
); } function ActivePanel({ pill }: { pill: Pill }) { if (pill.id === 'bsa') return ; if (pill.id === 'dose') return ; if (pill.id === 'gcs') return ; if (pill.id === 'bili') return ; if (pill.id === 'growth') return ; return ; } export default function Calculators() { const [active, setActive] = useState(PILLS[0].id); const pill = PILLS.find((p) => p.id === active) ?? PILLS[0]; return (

Calculators

Pediatric calculators — BP percentiles, bilirubin thresholds, growth, dosing, equipment sizing. Simple pure-formula calculators run in React now; high-risk table-driven calculators remain legacy-gated until vectors are captured.

{PILLS.map((p) => ( ))}
); }