// ============================================================ // CALCULATOR PANELS — BMI / Vitals / Resus / Equipment. // Data ported VERBATIM from public/js/calculators.js: // • VITALS_DATA lines 1703-1831 // • RESUS_MEDS lines 1873-2050 // • EQUIP_DATA lines 2173-2228 // BMI math + LMS table live in shared/clinical/bmi.ts, verified // byte-for-byte by calc-vectors.json (12 BMI cases). // ============================================================ import { useState } from 'react'; import { computeBmi } from '@shared/clinical/bmi'; import type { Sex } from '@shared/clinical/fenton'; import { computeBp, type BpClassification } from '@shared/clinical/bp'; const card = 'rounded-lg border border-border bg-card p-5 space-y-3'; 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 label = 'block text-xs font-medium text-muted-foreground'; 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 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'; // ── BP Percentile (AAP 2017 Rosner splines) ──────────────── const BP_CLASS_STYLE: Record = { normal: { label: 'Normal', color: '#10b981', bg: '#d1fae5' }, elevated: { label: 'Elevated', color: '#f59e0b', bg: '#fef3c7' }, stage1: { label: 'Stage 1 Hypertension', color: '#f97316', bg: '#ffedd5' }, stage2: { label: 'Stage 2 Hypertension', color: '#ef4444', bg: '#fee2e2' }, }; export function BpPanel() { const [ageYears, setAgeYears] = useState(''); const [sex, setSex] = useState<'female' | 'male'>('female'); const [heightCm, setHeightCm] = useState(''); const [sbp, setSbp] = useState(''); const [dbp, setDbp] = useState(''); const [error, setError] = useState(''); const [result, setResult] = useState | null>(null); function calc() { const a = Number.parseFloat(ageYears); const h = Number.parseFloat(heightCm); const s = Number.parseFloat(sbp); const d = Number.parseFloat(dbp); if (!Number.isFinite(a) || !Number.isFinite(h) || !Number.isFinite(s) || !Number.isFinite(d)) { setError('Fill in all fields.'); setResult(null); return; } if (a < 1 || a > 17) { setError('Age must be 1-17 years.'); setResult(null); return; } if (h < 50 || h > 200) { setError('Height must be 50-200 cm.'); setResult(null); return; } setError(''); setResult(computeBp(a, sex, h, s, d)); } const style = result ? BP_CLASS_STYLE[result.classification] : null; return (

BP Percentile (AAP 2017)

setAgeYears(e.target.value)} data-testid="bp-age" />
setHeightCm(e.target.value)} data-testid="bp-height" />
setSbp(e.target.value)} data-testid="bp-sbp" />
setDbp(e.target.value)} data-testid="bp-dbp" />
{error &&
{error}
} {result && style && (
{style.label}
Systolic
{result.sysPercentile}th %ile
{BP_CLASS_STYLE[result.sysClass].label}
Diastolic
{result.diaPercentile}th %ile
{BP_CLASS_STYLE[result.diaClass].label}
Height
{result.heightPercentile.toFixed(0)}th %ile
Overall
{style.label}
)}
Flynn JT et al. Clinical Practice Guideline for Screening and Management of High Blood Pressure in Children and Adolescents. Pediatrics 2017;140(3):e20171904.
); } // ── BMI ───────────────────────────────────────────────────── export function BmiPanel() { const [ageYr, setAgeYr] = useState(''); const [ageMo, setAgeMo] = useState(''); const [sex, setSex] = useState('male'); const [weight, setWeight] = useState(''); const [height, setHeight] = useState(''); const [error, setError] = useState(''); const [result, setResult] = useState | null>(null); function calc() { const yr = Number.parseFloat(ageYr) || 0; const mo = Number.parseInt(ageMo, 10) || 0; const age = yr + mo / 12; const w = Number.parseFloat(weight); const h = Number.parseFloat(height); if (!age || !Number.isFinite(w) || w <= 0 || !Number.isFinite(h) || h <= 0) { setError('Fill in all fields.'); setResult(null); return; } if (age < 2 || age > 20) { setError('Age must be 2-20 years.'); setResult(null); return; } setError(''); setResult(computeBmi(w, h, Math.round(age * 12), sex)); } return (

BMI Percentile (CDC 2000)

setAgeYr(e.target.value)} data-testid="bmi-age-yr" />
setAgeMo(e.target.value)} data-testid="bmi-age-mo" />
setWeight(e.target.value)} data-testid="bmi-weight" />
setHeight(e.target.value)} data-testid="bmi-height" />
{error &&
{error}
} {result && (
{result.classification.label}
BMI {result.bmi.toFixed(1)} kg/m² — {result.percentile}th percentile
BMI
{result.bmi.toFixed(1)}
Percentile
{result.percentile}th
Z-Score
{result.z.toFixed(2)}
{result.percentile >= 85 &&
% of 95th
{result.classification.pctOf95.toFixed(0)}%
}
)}
CDC 2000 LMS tables · Kuczmarski et al. Vital Health Stat 11. 2002;(246).
); } // ── Vitals ────────────────────────────────────────────────── // Data ported verbatim from calculators.js:1703-1831. interface VitalsEntry { label: string; hr: { awake: string; sleeping: string }; rr: string; sbp: string; dbp: string; temp: string; weight: string; spo2: string; notes: string[]; } const VITALS_DATA: Record = { premie: { label: 'Premie', hr: { awake: '120-170', sleeping: '100-150' }, rr: '40-70', sbp: '55-75', dbp: '35-45', temp: '36.5-37.5', weight: '0.5-2.5 kg', spo2: '88-95% (target)', notes: ['HR and RR are highly variable and depend on gestational age', 'BP increases with gestational age and postnatal age', 'Target SpO2 88-95% to reduce retinopathy of prematurity risk', 'Temperature instability is common — use servo-controlled warmers', 'Bradycardia (<100 bpm) and apnea are common in premature infants'] }, '0-3mo': { label: '0-3 Months', hr: { awake: '100-150', sleeping: '85-135' }, rr: '35-55', sbp: '65-85', dbp: '45-55', temp: '36.5-37.5', weight: '2.5-6 kg', spo2: '>95%', notes: ['HR normally increases with crying (up to 180-190 bpm) — this is physiologic', 'Periodic breathing (pauses <10 sec) is normal in neonates', 'Acrocyanosis (blue hands/feet) is normal; central cyanosis is not', 'BP is best measured in the right arm (pre-ductal) in neonates', 'Normal weight loss of 5-7% in first 3-5 days; regain by 10-14 days'] }, '3-6mo': { label: '3-6 Months', hr: { awake: '90-120', sleeping: '75-110' }, rr: '30-45', sbp: '70-90', dbp: '50-65', temp: '36.5-37.5', weight: '5-8 kg', spo2: '>95%', notes: ['Expected weight gain: 20-30 g/day (150-200 g/week)', 'HR gradually decreases as vagal tone matures', 'RR >60 at rest may indicate lower respiratory tract disease', 'BP should be measured with appropriate cuff size (width 40% of arm circumference)'] }, '6-12mo': { label: '6-12 Months', hr: { awake: '80-120', sleeping: '70-110' }, rr: '25-40', sbp: '80-100', dbp: '55-65', temp: '36.0-37.5', weight: '8-10 kg', spo2: '>95%', notes: ['Expected weight: triple birth weight by 12 months (~10 kg average)', 'Weight gain slows to ~10-15 g/day', 'Sinus arrhythmia (HR varies with breathing) is normal', 'Febrile tachycardia: HR increases ~10 bpm per 1 degree C above 37'] }, '1-3yr': { label: '1-3 Years', hr: { awake: '70-110', sleeping: '60-100' }, rr: '20-30', sbp: '90-105', dbp: '55-70', temp: '36.0-37.5', weight: '10-15 kg', spo2: '>95%', notes: ['Expected weight gain: ~200-250 g/month (2-2.5 kg/year)', 'Tachycardia: HR >110 at rest warrants evaluation', 'Tachypnea: RR >30 at rest may indicate respiratory distress', 'BP screening begins at age 3 per AAP 2017 guidelines', 'Estimated weight: 2 x (age in years) + 8'] }, '3-6yr': { label: '3-6 Years', hr: { awake: '65-110', sleeping: '55-100' }, rr: '20-25', sbp: '95-110', dbp: '60-75', temp: '36.0-37.5', weight: '14-20 kg', spo2: '>95%', notes: ['Annual BP screening recommended from age 3', 'Normal BP <90th percentile for age, sex, and height', 'Elevated BP: 90th to <95th percentile (or 120/80 if lower)', 'Estimated weight: 2 x (age in years) + 8', 'ETT size (uncuffed): (age/4) + 4'] }, '6-12yr': { label: '6-12 Years', hr: { awake: '60-95', sleeping: '50-85' }, rr: '14-22', sbp: '100-120', dbp: '60-75', temp: '36.0-37.5', weight: '20-40 kg', spo2: '>95%', notes: ['Resting HR >95 or <60 warrants evaluation', 'BP should be measured at every clinical encounter', 'Stage 1 HTN: >=95th percentile on 3 separate occasions', 'Estimated weight: 3 x (age in years) + 7', 'ETT size (cuffed): (age/4) + 3.5'] }, '>12yr': { label: '>12 Years', hr: { awake: '55-85', sleeping: '45-75' }, rr: '12-18', sbp: '110-135', dbp: '65-85', temp: '36.0-37.5', weight: '40-80 kg', spo2: '>95%', notes: ['Vital signs approach adult values', 'From age 13: use adult BP thresholds (AAP 2017)', 'Normal: <120/<80 mmHg; Elevated: 120-129/<80 mmHg', 'Stage 1 HTN: 130-139/80-89 mmHg; Stage 2 HTN: >=140/>=90 mmHg', 'Orthostatic vitals: measure lying, sitting, standing if dizzy', 'Athletic bradycardia (HR 45-60) may be normal in trained adolescents'] }, }; const VITALS_ORDER = ['premie', '0-3mo', '3-6mo', '6-12mo', '1-3yr', '3-6yr', '6-12yr', '>12yr']; export function VitalsPanel() { const [key, setKey] = useState('1-3yr'); const v = VITALS_DATA[key]; return (

Vital Signs by Age

Heart rate (awake)
{v.hr.awake} bpm
Heart rate (sleep)
{v.hr.sleeping} bpm
Respiratory rate
{v.rr} /min
SpO₂
{v.spo2}
SBP
{v.sbp} mmHg
DBP
{v.dbp} mmHg
Temperature
{v.temp} °C
Weight
{v.weight}
Clinical notes
    {v.notes.map((n, i) =>
  • {n}
  • )}
Harriet Lane Handbook 23rd ed · PALS · AAP 2017 BP guidelines.
); } // ── Resus Meds ────────────────────────────────────────────── // Data + math ported verbatim from calculators.js:1873-2050. interface ResusResult { dose: string; extra: string; max: string } interface ResusMed { name: string; indication: string; category: 'cardiac' | 'metabolic' | 'reversal'; route: string; calc: (w: number) => ResusResult } const RESUS_MEDS: ResusMed[] = [ { name: 'Adenosine', indication: 'SVT', category: 'cardiac', route: 'IV/IO rapid bolus', calc: (w) => { const d1 = +(w * 0.1).toFixed(2); const d2 = +(w * 0.2).toFixed(2); const d3 = +(w * 0.3).toFixed(2); return { dose: `${d1} mg (0.1 mg/kg)`, extra: `May repeat: ${Math.min(d2, 12)} mg (0.2 mg/kg), then ${Math.min(d3, 12)} mg (0.3 mg/kg)`, max: 'Max first dose 6 mg, max subsequent 12 mg' }; } }, { name: 'Amiodarone', indication: 'VT / VF', category: 'cardiac', route: 'IV/IO', calc: (w) => { const d = +(w * 5).toFixed(1); return { dose: `${Math.min(d, 300)} mg (5 mg/kg)`, extra: 'No pulse: push undiluted. Pulse: over 20-60 min. Subsequent max 150 mg.', max: 'Max first 300 mg, max total 15 mg/kg/24hr or 2200 mg' }; } }, { name: 'Atropine', indication: 'Bradycardia', category: 'cardiac', route: 'IV/IO/IM', calc: (w) => { const d = +(w * 0.02).toFixed(3); const ett = `${(w * 0.04).toFixed(3)}-${(w * 0.06).toFixed(3)}`; return { dose: `${Math.min(d, 0.5)} mg (0.02 mg/kg)`, extra: `ETT dose: ${ett} mg (0.04-0.06 mg/kg)`, max: 'Max single 0.5 mg, max total 1 mg' }; } }, { name: 'Calcium Chloride 10%', indication: 'Hypocalcemia / Hyperkalemia', category: 'metabolic', route: 'IV/IO', calc: (w) => { const d = +(w * 20).toFixed(0); return { dose: `${Math.min(d, 1000)} mg (20 mg/kg)`, extra: 'Give slowly. Central line preferred.', max: 'Max 1 g (1000 mg)' }; } }, { name: 'Calcium Gluconate 10%', indication: 'Hypocalcemia / Hyperkalemia', category: 'metabolic', route: 'IV/IO', calc: (w) => { const d = +(w * 60).toFixed(0); return { dose: `${Math.min(d, 3000)} mg (60 mg/kg)`, extra: 'Give slowly over 10-20 min with cardiac monitoring.', max: 'Max 3 g (3000 mg)' }; } }, { name: 'Dextrose', indication: 'Hypoglycemia', category: 'metabolic', route: 'IV', calc: (w) => { const grams = `${+(w * 0.5).toFixed(1)}-${+(w * 1).toFixed(1)}`; let detail = ''; if (w < 5) detail = `D10W: ${(w * 5).toFixed(1)}-${(w * 10).toFixed(1)} mL (5-10 mL/kg)`; else if (w < 45) detail = `D25W: ${(w * 2).toFixed(1)}-${(w * 4).toFixed(1)} mL (2-4 mL/kg)`; else detail = `D50W: ${(w * 1).toFixed(1)}-${(w * 2).toFixed(1)} mL (1-2 mL/kg)`; return { dose: `${grams} g (0.5-1 g/kg)`, extra: detail, max: 'Max 25 g' }; } }, { name: 'Epinephrine', indication: 'Pulseless arrest / Anaphylaxis', category: 'cardiac', route: 'IV/IO/IM/ETT', calc: (w) => { const iv = +(w * 0.01).toFixed(3); const ivVol = +(w * 0.1).toFixed(2); const ett = +(w * 0.1).toFixed(2); const im = +(w * 0.01).toFixed(3); return { dose: `${Math.min(iv, 1)} mg IV/IO (0.01 mg/kg of 0.1 mg/mL = ${Math.min(ivVol, 10)} mL) q3-5 min`, extra: `ETT: ${Math.min(ett, 2.5)} mg (0.1 mg/kg of 1 mg/mL). Anaphylaxis IM: ${Math.min(im, 0.5)} mg (0.01 mg/kg)`, max: 'Max IV 1 mg, max ETT 2.5 mg, max IM 0.5 mg' }; } }, { name: 'Hydrocortisone', indication: 'Adrenal crisis', category: 'metabolic', route: 'IV/IM/IO', calc: (w) => { const d = +(w * 2).toFixed(1); return { dose: `${Math.min(d, 100)} mg (2 mg/kg)`, extra: 'Stress dosing for adrenal insufficiency.', max: 'Max 100 mg' }; } }, { name: 'Insulin (Regular)', indication: 'Hyperkalemia', category: 'metabolic', route: 'IV', calc: (w) => { const d = +(w * 0.1).toFixed(2); const dex = +(w * 0.5).toFixed(1); return { dose: `${Math.min(d, 5)} units (0.1 units/kg)`, extra: `Give with ${dex} g/kg dextrose (0.5 g/kg). Monitor glucose closely.`, max: 'Max 5 units' }; } }, { name: 'Lidocaine', indication: 'Antiarrhythmic', category: 'cardiac', route: 'IV/IO', calc: (w) => { const d = +(w * 1).toFixed(1); const ett = `${(w * 2).toFixed(1)}-${(w * 3).toFixed(1)}`; return { dose: `${Math.min(d, 100)} mg (1 mg/kg)`, extra: `ETT: ${ett} mg (2-3 mg/kg). May repeat q5 min.`, max: 'Max 100 mg/dose, max total 3 mg/kg' }; } }, { name: 'Magnesium Sulfate', indication: 'Torsades de Pointes', category: 'cardiac', route: 'IV/IO', calc: (w) => { const d = +(w * 50).toFixed(0); return { dose: `${Math.min(d, 2000)} mg (50 mg/kg)`, extra: 'Give over 10-20 min (faster if pulseless).', max: 'Max 2 g (2000 mg)' }; } }, { name: 'Naloxone', indication: 'Opioid overdose', category: 'reversal', route: 'IV/IO/IM/IN/ETT', calc: (w) => { const partial = `${+(w * 0.001).toFixed(4)}-${+(w * 0.005).toFixed(4)}`; const full = +(w * 0.1).toFixed(3); return { dose: `Partial: ${partial} mg (0.001-0.005 mg/kg)`, extra: `Full reversal: ${Math.min(full, 2)} mg (0.1 mg/kg)`, max: 'Max partial first dose 0.1 mg, max full 2 mg' }; } }, { name: 'Sodium Bicarbonate', indication: 'Metabolic acidosis', category: 'metabolic', route: 'IV/IO', calc: (w) => { const d = +(w * 1).toFixed(1); return { dose: `${Math.min(d, 50)} mEq (1 mEq/kg)`, extra: w < 10 ? 'Dilute to 0.5 mEq/mL (use 4.2% solution) for neonates/small infants.' : 'Use 8.4% solution (1 mEq/mL).', max: 'Max 50 mEq' }; } }, ]; const catColor: Record = { cardiac: '#ef4444', metabolic: '#3b82f6', reversal: '#10b981' }; const catLabel: Record = { cardiac: 'Cardiac', metabolic: 'Metabolic', reversal: 'Reversal' }; export function ResusPanel() { const [weight, setWeight] = useState(''); const wt = Number.parseFloat(weight); const valid = Number.isFinite(wt) && wt > 0; return (

Resus Medications

setWeight(e.target.value)} data-testid="resus-weight" />
{!valid ?

Enter weight (kg) to see doses.

: ( <>
Doses for {wt} kg patient
{(['cardiac', 'metabolic', 'reversal'] as const).map((c) => ( {catLabel[c]} ))}
{RESUS_MEDS.map((med) => { const r = med.calc(wt); const color = catColor[med.category]; return (
{med.name}
{med.indication}
Dose: {r.dose}
{r.extra}
Max: {r.max}
Route: {med.route}
); })}
Disclaimer: Always verify doses against institutional protocols and current guidelines.
)}
); } // ── Equipment ─────────────────────────────────────────────── // Data ported verbatim from calculators.js:2173-2228. interface EquipEntry { label: string; bvm: string; nasal: string; oral: string; blade: string; ett: string; lma: string; glidescope: string; iv: string; cvl: string; ngt: string; chest: string; foley: string; } const EQUIP_DATA: Record = { premie: { label: 'Premie (1-3 kg)', bvm: 'Infant', nasal: '12 Fr', oral: 'Infant', blade: 'Miller 0', ett: '2.5-3.0', lma: '1', glidescope: '1', iv: '22-24 ga', cvl: '3 Fr', ngt: '5 Fr', chest: '10-12 Fr', foley: '6 Fr' }, newborn: { label: 'Newborn (2-4 kg)', bvm: 'Infant', nasal: '14-16 Fr', oral: 'Small 50 mm', blade: 'Miller 0', ett: '3.0-3.5', lma: '1', glidescope: '1', iv: '22-24 ga', cvl: '3-4 Fr', ngt: '5-8 Fr', chest: '10-12 Fr', foley: '6 Fr' }, '6mo': { label: '6 months (6-8 kg)', bvm: 'Infant', nasal: '14-16 Fr', oral: 'Small 60 mm', blade: 'Miller 1', ett: '3.5', lma: '1.5', glidescope: '2', iv: '20-24 ga', cvl: '4 Fr', ngt: '8 Fr', chest: '12-18 Fr', foley: '8 Fr' }, '1yr': { label: '1 year (10 kg)', bvm: 'Small child', nasal: '14-18 Fr', oral: 'Small 60 mm', blade: 'Miller 1 / MAC 2', ett: '4.0', lma: '2', glidescope: '2', iv: '20-24 ga', cvl: '4-5 Fr', ngt: '10 Fr', chest: '16-20 Fr', foley: '8 Fr' }, '2-3yr': { label: '2-3 years (12-16 kg)', bvm: 'Small child', nasal: '14-18 Fr', oral: 'Small 70 mm', blade: 'Miller 1 / MAC 2', ett: '4.0-4.5', lma: '2', glidescope: '2', iv: '18-22 ga', cvl: '4-5 Fr', ngt: '10-12 Fr', chest: '16-24 Fr', foley: '8 Fr' }, '4-6yr': { label: '4-6 years (20-25 kg)', bvm: 'Child', nasal: '16-20 Fr', oral: 'Small 70-80 mm', blade: 'Miller 2 / MAC 2', ett: '4.5-5.0', lma: '2.5', glidescope: '3', iv: '18-22 ga', cvl: '5 Fr', ngt: '12-14 Fr', chest: '20-28 Fr', foley: '8 Fr' }, '7-10yr': { label: '7-10 years (25-35 kg)', bvm: 'Child / Small adult', nasal: '18-22 Fr', oral: 'Medium 80-90 mm', blade: 'Miller 2 / MAC 2', ett: '5.5-6.0', lma: '2.5-3', glidescope: '3', iv: '18-22 ga', cvl: '5 Fr', ngt: '12-14 Fr', chest: '20-32 Fr', foley: '8 Fr' }, '11-15yr': { label: '11-15 years (40-50 kg)', bvm: 'Adult', nasal: '22-36 Fr', oral: 'Medium 90 mm', blade: 'Miller 2 / MAC 3', ett: '6.0-6.5', lma: '3', glidescope: '3 or 4', iv: '18-20 ga', cvl: '7 Fr', ngt: '14-18 Fr', chest: '28-38 Fr', foley: '10 Fr' }, '16yr': { label: '16+ years (>50 kg)', bvm: 'Adult', nasal: '22-36 Fr', oral: 'Medium 90 mm', blade: 'Miller 2 / MAC 3', ett: '7.0-8.0', lma: '4', glidescope: '3 or 4', iv: '18-20 ga', cvl: '7 Fr', ngt: '14-18 Fr', chest: '28-42 Fr', foley: '12 Fr' }, }; const EQUIP_ORDER = ['premie', 'newborn', '6mo', '1yr', '2-3yr', '4-6yr', '7-10yr', '11-15yr', '16yr']; export function EquipmentPanel() { const [key, setKey] = useState('1yr'); const e = EQUIP_DATA[key]; const rows: Array<[string, string]> = [ ['BVM', e.bvm], ['Nasopharyngeal', e.nasal], ['Oropharyngeal', e.oral], ['Laryngoscope', e.blade], ['ETT', e.ett], ['LMA', e.lma], ['Glidescope', e.glidescope], ['IV', e.iv], ['Central line', e.cvl], ['NG tube', e.ngt], ['Chest tube', e.chest], ['Foley', e.foley], ]; return (

Equipment Sizing

{rows.map(([lbl, val]) => (
{lbl}
{val}
))}
Harriet Lane Handbook · PALS · Broselow cross-reference.
); }