pediatric-ai-scribe-v3/client/src/data/pe-data.test.ts
Daniel aafe7981dc feat(client): port full PE_DATA checklist + Generate Exam Report flow
Biggest data port of the migration so far. PE_DATA is the 1000+ line
age-group × system × component × step hierarchy driving the pediatric
physical-exam checklist; every entry, pearl, significance note, and
abnormal-hint array is now available in the React tree.

client/src/data/pe-data.ts — verbatim port
  Extracted lines 316-1334 of public/js/peGuide.js with awk/sed, then
  wrapped in TS types. Every byte of the data body is byte-identical
  to the vanilla source. Added interfaces:
    PeStep { label, method, normal }
    PeComponent { name, steps[], abnormalHints[], pearl?, significance? }
    PeSystem { overview, components[] }
    PeAgeGroup { label, msk, neuro, resp, cv }
  …plus AGE_GROUP_ORDER / SYSTEM_ORDER / SYSTEM_LABELS canonical
  orderings for the UI.

client/src/data/pe-data.test.ts — parity lock
  Vitest suite that asserts every count captured from the vanilla
  source so any accidental drop surfaces as a red test:
    • 6 age groups × 4 systems
    • 103 components total
    • 27 pearl entries
    • 23 significance entries
    • per-cell component counts (e.g. toddler.neuro = 7, adolescent.cv = 5)
  Counts captured 2026-04-24 against peGuide.js commit 313ba7f.

client/src/pages/PeGuide.tsx — full viewer (replaces legacy-link stub)
  • Age-group pills (6) + system pills (4) drive the visible section
  • Overview banner per combination
  • CV system shows APTM legend + cardiac sounds library + innocent
    murmurs reference (unchanged clinical content from the earlier
    commit that added the scales/sounds file)
  • Resp system shows the respiratory sounds library
  • Collapsible grading-scales reference pulls from SYSTEM_SCALES
  • Component checklist: per-step Normal / Abnormal toggle, abnormal-
    hint list, pearl + significance callouts
  • Mark-all-normal + Reset shortcuts
  • Generate Exam Report posts the full step payload to
    /api/generate-pe-narrative, renders the returned narrative inline
  • No more "Open checklist in legacy viewer" amber banner — the
    React port now does the whole thing

e2e/tests/peguide-react.spec.js
  Age-group pills, system pills, overview rewrite on age change,
  CV/resp system-specific reference panels, mark-all-normal + summary,
  and a mocked /api/generate-pe-narrative round-trip.

Client tsc -b + vite build clean. Bundle 580.30 kB / 166.08 kB gz
(up ~100 kB from the shell-only port — the 1000-line PE_DATA is the
bulk; acceptable for the clinical reference data it surfaces).
2026-04-24 01:21:44 +02:00

90 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// PE_DATA parity counts — catches the class of bug where an LLM
// silently drops entries from a long clinical array during a port.
// Numbers captured 2026-04-24 against public/js/peGuide.js commit
// 313ba7f. If the vanilla source changes, update both files in the
// same commit.
// ============================================================
import { describe, expect, it } from 'vitest';
import { PE_DATA, AGE_GROUP_ORDER, SYSTEM_ORDER } from './pe-data';
describe('PE_DATA shape', () => {
it('has the six expected age groups', () => {
expect(Object.keys(PE_DATA).sort()).toEqual([...AGE_GROUP_ORDER].sort());
});
it.each([...AGE_GROUP_ORDER])('%s has all four systems', (age) => {
const group = PE_DATA[age];
for (const s of SYSTEM_ORDER) {
expect(group[s]).toBeDefined();
expect(group[s].overview.length).toBeGreaterThan(0);
expect(Array.isArray(group[s].components)).toBe(true);
expect(group[s].components.length).toBeGreaterThan(0);
}
});
it('component + abnormalHints + pearl + significance counts match vanilla', () => {
let componentCount = 0;
let pearlCount = 0;
let significanceCount = 0;
for (const age of AGE_GROUP_ORDER) {
for (const sys of SYSTEM_ORDER) {
const comps = PE_DATA[age][sys].components;
for (const c of comps) {
componentCount++;
if (c.pearl) pearlCount++;
if (c.significance) significanceCount++;
expect(Array.isArray(c.steps)).toBe(true);
expect(c.steps.length).toBeGreaterThan(0);
expect(Array.isArray(c.abnormalHints)).toBe(true);
}
}
}
// Locked against vanilla peGuide.js (2026-04-24):
// 103 components, 27 pearl, 23 significance.
expect(componentCount).toBe(103);
expect(pearlCount).toBe(27);
expect(significanceCount).toBe(23);
});
});
// Per-age-group × per-system component counts — captured from the
// legacy file with:
// awk 'NR>=316 && NR<=1334' public/js/peGuide.js |
// awk '/^ [a-z]+: \{$/{age=$1} /^ [a-z]+: \{$/{sys=$1}
// /^ { name:/{c[age" "sys]++} END{for(k in c) print k" "c[k]}' | sort
// so any drift in the TS port surfaces as a failing test here.
describe('PE_DATA per-cell component counts', () => {
const EXPECTED: Record<string, number> = {
'newborn msk': 6,
'newborn neuro': 6,
'newborn resp': 2,
'newborn cv': 2,
'infant msk': 4,
'infant neuro': 5,
'infant resp': 2,
'infant cv': 2,
'toddler msk': 5,
'toddler neuro': 7,
'toddler resp': 2,
'toddler cv': 2,
'preschool msk': 5,
'preschool neuro': 7,
'preschool resp': 2,
'preschool cv': 2,
'school msk': 5,
'school neuro': 7,
'school resp': 3,
'school cv': 2,
'adolescent msk': 6,
'adolescent neuro': 8,
'adolescent resp': 6,
'adolescent cv': 5,
};
it.each(Object.entries(EXPECTED))('%s matches', (key, expected) => {
const [age, sys] = key.split(' ') as ['newborn', 'msk'];
expect(PE_DATA[age][sys].components.length).toBe(expected);
});
});