pediatric-ai-scribe-v3/shared/clinical/fenton.test.ts
Daniel 941b73f9e5 feat(client): port final 5 Bedside panels — all 15 now run in React
Finishes Bedside parity. Neonatal, Respiratory, Ventilation, Sepsis,
and Burns replace their LegacyPanel fallbacks. All 15 vanilla
Bedside sub-modules are now real React components.

shared/clinical/fenton.ts — second, higher-accuracy Fenton table
  fentonLmsPeditools: 21 GA weeks × 2 sexes (22-42 in 1-week steps)
  ported verbatim from public/js/bedside/neonatal.js:20-37. This is
  the peditools-derived table that superseded the older hand-rounded
  version (still used by the Growth Charts calculator). Adds
  calcZNeonatal (|L|<0.001 threshold) + zToPercentileNeonatal
  (Abramowitz & Stegun erf form) so neonatal output matches the
  vanilla Bedside numbers to 3 decimal places.
  neonatalAssess() returns { gaDecimal, gaClass, bwClass,
  weightClass, expectedWeight, z, percentile, L, M, S }.

scripts/capture-calc-vectors.js + e2e/fixtures/calc-vectors.json
  Adds 8 neonatal vectors (including the 40w5d male 3070g validated
  case from the vanilla file's own comment: z = -1.42).

shared/clinical/fenton.test.ts — now covers neonatal too (110 total
vitest cases pass: 19 calculators, 21 fenton, 70 bilirubin).

client/src/pages/BedsidePanels2.tsx — five new panels
  • NeonatalPanel — GA+weight+sex inputs drive the Fenton assessment
    (gestational-age class, weight-for-GA class, birth-weight
    category, Z-score, percentile, expected M). Full NRP pathway
    cards (birth→HR<100→HR<60 escalation), NRP drug table scaled
    by weight (epi IV/IO/ETT, NS bolus, D10), and 5-element Apgar
    scorer with reassuring/moderately-depressed/severely-depressed
    guidance.
  • RespiratoryPanel — four sub-modes: Asthma (mild/moderate/
    severe with full drug tables + "when to intubate" / ABG /
    heliox clinical decision boxes), PRAM scorer (0-12),
    Westley croup scorer (0-17 with severity-tiered treatment),
    Bronchiolitis admission decision tree (age / SpO₂ / hydration
    / distress) with AAP "NOT recommended" list.
  • VentilationPanel — target SpO₂ table by population, 6-step
    escalation ladder (NC → FM → NRB → HFNC → NIV → intubation)
    with live-scaled HFNC flow (1-2 L/kg/min), BVM how-to,
    mechanical vent starting settings (TV, rate by age, PEEP,
    FiO₂, I:E), gas-exchange adjustment table, and the
    oxygenation-vs-ventilation mental model.
  • SepsisPanel — Phoenix Sepsis Criteria (JAMA 2024), red-flag
    list, age-banded workup + empirical therapy (neonate /
    infant / child abx drugs keyed to weight), SSC 2020 first-hour
    bundle (0-5 min recognize → >60 min refractory-shock
    vasoactive), and resuscitation targets.
  • BurnsPanel — full 19-region Lund-Browder age-adjusted table
    (ported verbatim), with per-region % input + live TBSA
    computation + override. Parkland formula (4 × kg × TBSA,
    8h/16h split + per-hr rates), 4-2-1 maintenance, UOP targets,
    pearl list (palm rule, no first-degree, analgesia, tetanus),
    and ABA burn-center referral criteria.

client/src/pages/BedsidePanels.tsx — dispatch extended to all 15
pills. REAL_BEDSIDE_PANELS now contains every Bedside pill id, so
the legacy-link fallback is dead code that can be pruned later.

Client tsc + vite build + 110/110 vitest tests all green.
2026-04-24 02:00:50 +02:00

49 lines
2.2 KiB
TypeScript

// ============================================================
// FENTON 2013 parity tests — drives the TS port against vectors
// captured from the authoritative vanilla implementation.
// ============================================================
import { describe, expect, it } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fentonWeightForAge, neonatalAssess, type Sex } from './fenton';
interface FentonCase {
name: string;
inputs: { sex: Sex; gaWeeks: number; weightGrams: number };
output: { L: number; M: number; S: number; z: number; percentile: number };
}
interface NeonatalCase {
name: string;
inputs: { weeks: number; days: number; weightGrams: number; sex: Sex };
output: { gaDecimal: number; L: number; M: number; S: number; z: number; percentile: number; expectedWeight: number };
}
interface Vectors { fenton: FentonCase[]; neonatal: NeonatalCase[] }
const vectors: Vectors = JSON.parse(
fs.readFileSync(path.join(__dirname, '..', '..', 'e2e', 'fixtures', 'calc-vectors.json'), 'utf8'),
);
describe('Fenton 2013 weight-for-GA — parity with vanilla calculators.js', () => {
it.each(vectors.fenton)('$name', ({ inputs, output }) => {
const actual = fentonWeightForAge(inputs.gaWeeks, inputs.weightGrams, inputs.sex);
expect(actual.L).toBeCloseTo(output.L, 10);
expect(actual.M).toBeCloseTo(output.M, 6);
expect(actual.S).toBeCloseTo(output.S, 10);
expect(actual.z).toBeCloseTo(output.z, 10);
expect(actual.percentile).toBeCloseTo(output.percentile, 10);
});
});
describe('Neonatal peditools Fenton — parity with vanilla bedside/neonatal.js', () => {
it.each(vectors.neonatal)('$name', ({ inputs, output }) => {
const actual = neonatalAssess(inputs.weeks, inputs.days, inputs.weightGrams, inputs.sex);
expect(actual.gaDecimal).toBeCloseTo(output.gaDecimal, 10);
expect(actual.L).toBeCloseTo(output.L, 10);
expect(actual.M).toBeCloseTo(output.M, 6);
expect(actual.S).toBeCloseTo(output.S, 10);
expect(actual.z).toBeCloseTo(output.z, 10);
expect(actual.percentile).toBeCloseTo(output.percentile, 10);
expect(actual.expectedWeight).toBe(output.expectedWeight);
});
});