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).
90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
// ============================================================
|
||
// 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);
|
||
});
|
||
});
|