medOS ultra

Unit Clinical Scores to IPD Sheets

Additive wiring of unit clinical scores into IPD dynamic sheets.

6 min read diagramsUpdated 2026-06-02docs/architecture/unit-clinical-scores-ipd-checklist.md

Status: Shipped (additive) 2026-06-02. Companion to dynamic-sheet-platform-reconciliation.md and web/packages/miniapps/dynamic-sheet-template-store/DYNAMIC-SHEET-PRESETS.md.

The unit supplied a list of the clinical scores it uses and asked to “generate these as IPD Dynamic Sheet.” 9 of the requested scores already shipped as fully-wired openEHR presets — they render via DynamicSheetPresetRendererV2. The gap was discoverability (none of the DS_SCORE_* presets were in the “Add Tab” picker) and a curated bundle. This change closes both, additively, without touching any working renderer.

Requested items → status

# Requested Status Enum / preset Notes
1 Apache II Score ✅ exists + now surfaced DS_SCORE_APACHE_II · scoring/ipd-apache-ii.json ICU
2 NEWS Score ✅ exists + now surfaced DS_SCORE_NEWS · scoring/ipd-news.json
3 SOFA Score ✅ exists + now surfaced DS_SCORE_SOFA · scoring/ipd-sofa.json ICU
4 VNT Score deferred No preset; not a standard instrument we could identify. Open TODO below.
5 Pain Score ผู้ใหญ่ ✅ exists + now surfaced DS_SCORE_PAIN_ADULT · scoring/ipd-pain-adult.json exact label match
6 GCS Score ✅ exists + now surfaced DS_SCORE_GCS · scoring/ipd-gcs.json
7 NAS Score ✅ exists + now surfaced DS_SCORE_NAS · scoring/ipd-nas.json Nursing Activities Score
8 NIPS Score ✅ exists + now surfaced DS_SCORE_NIPS · scoring/ipd-nips.json neonatal
9 Modified Sarnat Score ✅ exists + now surfaced DS_SCORE_SARNAT · scoring/ipd-sarnat.json neonatal
10 PRISM Score ✅ exists + now surfaced DS_SCORE_PRISM · scoring/ipd-prism.json peds ICU
11 Score ต่างๆที่หน่วยใช้ ✅ realized Work Group unit-clinical-scores Interpreted as the umbrella title → one curated Work Group bundling items 1–10

What shipped (all additive)

  • [x] Icon/color for the 9 score enums — coreApp.tsx (reuses already-imported MUI icons + the registry’s hex colors).
  • [x] Picker section “Clinical Scores (openEHR)” in both Add-Tab pickers so each score is individually addable:
  • [x] Dialog-flow builder “Core App” menu (CustomDialogFlow.tsx initialCoreApps) — the 9 now appear as draggable Dynamic Core nodes in the worklist / dialog-flow builder sidebar (the “CoreAppMenu”), next to GRAPHIC_SHEET_IPD / ORDER_SYSTEM / the generic work-group entry. (Distinct surface from the patient-profile Add-Tab picker above — both are “Core App” lists.)
  • [x] Bilingual module labelspublic/locales/{en,th}/coreApps.json (modules.DsScore*), so every Core-App surface shows a real name instead of the raw modules.DsScore… key.
  • [x] Curated Work Group unit-clinical-scores (label_th = “Score ต่างๆที่หน่วยใช้”) bundling the 9 — 20260602_unit_clinical_scores_work_group.sql. Idempotent (ON CONFLICT (name) DO NOTHING).
  • [x] This checklist.

Not touched (reuse-only, per guardrails): the 9 preset JSONs, index.ts, registry.ts, the DynamicCoreApp enum, the DynamicContentRenderer switch, DynamicSheetPresetRendererV2, and the IPD Graphic Sheet. They were already complete for these 9.

Deploy

  1. Frontend (picker section + icons) — ships with the normal Vercel build on push.
  2. Work Group row — manual (Supabase migrations are not auto-applied):
    psql "$SUPABASE_DB_URL" -f web/supabase/migrations/20260602_unit_clinical_scores_work_group.sql
    
    Requires 20260514_dynamic_sheet_work_groups.sql (+ _extensions.sql) already applied.

Verify

  • Patient profile → Add Tab → Core Apps → scroll to “Clinical Scores (openEHR)” → the 9 cards appear; clicking one adds a tab that renders the scoring sheet.
  • After the SQL runs, the “Unit Clinical Scores / Score ต่างๆที่หน่วยใช้” Work Group shows in the work-group picker and at /admin/dynamic-sheet-work-groups.
  • Fast path (no clinic login): /sandbox/workspace?module=modules.DsScoreApacheIi (swap the enum for any of the 9).

Open TODO — VNT Score (deferred per unit)

“VNT Score” has no preset and is not a standard instrument we could identify. When the unit confirms its components, options, and total range, build it exactly like the other 9:

  1. presets/scoring/ipd-vnt.json — new UUID template_group_id, bilingual (TH+EN) ontology, ui_metadata, sheet_settings.
  2. Register in presets/index.ts (import + scoring category entry).
  3. Register in presets/registry.tsDS_SCORE_VNT / modules.DsScoreVnt / the UUID / category scoring.
  4. Add DS_SCORE_VNT = 'modules.DsScoreVnt' to src/setup/dynamic/DynamicCoreApp.ts.
  5. Add the case DynamicCoreApp.DS_SCORE_VNT: to the preset switch in DynamicContentRenderer.tsx.
  6. Add an icon/color entry in coreApp.tsx.
  7. Add DS_SCORE_VNT to scoringPresetApps (File 1) and the clinical-scores group (File 2).
  8. Append 'modules.DsScoreVnt' to sheet_enum_values in the Work Group seed.

Score calculation (computed totals) — 2026-06-02 follow-up

Problem reported: the rendered score sheets showed per-parameter sub-scores (e.g. SpO2 “92-93% (2)”) but no aggregate total — the gt0040 total was declared only as a graph line + ontology term, with no row and no formula, so nothing summed it.

Fix (preset-config only, no engine change): the V2 engine already auto-derives any row whose ui_metadata is computed/formula (applyComputedRows, wired at MainDataGrid.tsx:355, proven by ICU MAP). Select cells persist valueNum (CustomCellEditor.tsx:262), which readNumeric reads. So a computed gt0040 row summing the parameter rows produces the total + feeds the existing trend graph.

Added a gt0040 computed total row to 7 presets (sum of each score’s parameters):

Preset Inputs Formula Range
ipd-news.json 7 gt0003…gt0009 0-20
ipd-gcs.json 3 gt0003+gt0004+gt0005 3-15
ipd-sofa.json 6 gt0003…gt0008 0-24
ipd-nips.json 6 gt0003…gt0008 0-7
ipd-apache-ii.json 14 gt0003…gt0016 0-71
ipd-prism.json 17 gt0003…gt0019 0-76
ipd-nas.json 26 gt0003…gt0028 (step 0.1) 0-177

Excluded — ipd-sarnat.json: gt0040 is “Sarnat Stage (1-3)”, a clinical staging judgment (predominant pattern), not an arithmetic sum — summing the 6 categories (each 1-3) yields 6-18, a meaningless “stage”. Left without an auto-total to avoid charting a wrong stage; correct staging needs a CDS rule, not the linear sum engine.

Limitation (by design): applyComputedRows returns null if any input is blank, so a total only appears once all parameters are filled (prevents misleading partial scores). For many-input scores (APACHE II 14, PRISM 17, NAS 26) this means the total shows only when complete — clinically the safe behaviour.

Edit-lock (why cells looked un-clickable) — diagnosis, no code change

A reported “can’t click a cell to edit it” is the 24h edit-lock clinical-safety feature: editLockHours defaults to 24 (DynamicSheetSettingsContext.tsx:257); isCellLocked makes any cell older than that read-only. Preset sheet_settings.startTime is 2025-01-01 and the time axis anchors to the patient’s admit date — so for old/demo data every cell is >24h old → all locked. Kept as-is (deliberate safeguard). To edit historical/demo cells: open the View panel → set edit-lock hours to 0 / blank.

Ask Anything