Unit Clinical Scores to IPD Sheets
Additive wiring of unit clinical scores into IPD dynamic sheets.
Status: Shipped (additive) 2026-06-02. Companion to
dynamic-sheet-platform-reconciliation.mdandweb/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:
renderer/app-surface/CoreAppList.tsx(scoringPresetAppsarray + section).display-profile/app-surface/CoreAppList.tsx(clinical-scoresconfig group).
- [x] Dialog-flow builder “Core App” menu (
CustomDialogFlow.tsxinitialCoreApps) — the 9 now appear as draggable Dynamic Core nodes in the worklist / dialog-flow builder sidebar (the “CoreAppMenu”), next toGRAPHIC_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 labels —
public/locales/{en,th}/coreApps.json(modules.DsScore*), so every Core-App surface shows a real name instead of the rawmodules.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
- Frontend (picker section + icons) — ships with the normal Vercel build on push.
- Work Group row — manual (Supabase migrations are not auto-applied):
Requirespsql "$SUPABASE_DB_URL" -f web/supabase/migrations/20260602_unit_clinical_scores_work_group.sql20260514_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:
presets/scoring/ipd-vnt.json— new UUIDtemplate_group_id, bilingual (TH+EN)ontology,ui_metadata,sheet_settings.- Register in
presets/index.ts(import +scoringcategory entry). - Register in
presets/registry.ts—DS_SCORE_VNT/modules.DsScoreVnt/ the UUID / categoryscoring. - Add
DS_SCORE_VNT = 'modules.DsScoreVnt'tosrc/setup/dynamic/DynamicCoreApp.ts. - Add the
case DynamicCoreApp.DS_SCORE_VNT:to the preset switch inDynamicContentRenderer.tsx. - Add an icon/color entry in
coreApp.tsx. - Add
DS_SCORE_VNTtoscoringPresetApps(File 1) and theclinical-scoresgroup (File 2). - Append
'modules.DsScoreVnt'tosheet_enum_valuesin 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.