Perioperative Status Kit
Auto-calc + severity-badge kit for the perioperative nursing record: NPO, duration, Apgar, sign-in completeness.
Status: Live Module:
web/packages/periops-kit/src/operating-room/components/list-or-patient/table-data/dialog/perioperative-status/Form: FM-OPR01-007 (แบบบันทึกการพยาบาลผู้ป่วยผ่าตัด · Perioperative Nursing Record) Surfaces: Sign-In dialog · Time-Out (Intraoperative) dialog · Time-Stamp dialog · FM-OPR01-007 print template
1. What it is
A shared hook + badge kit that auto-derives four clinical safety signals from the perioperative form state and surfaces them next to the relevant inputs (and on the printed record) with severity-coloured icon badges. It replaces eyeballing checklists / clock arithmetic with at-a-glance, role-readable status indicators.
Four signals, one tick clock, three surfaces (live form · time-stamp dialog · printed FM-OPR01-007 PDF).
2. The four signals
| Hook | Input | Output | Banding |
|---|---|---|---|
useNpoStatus(active, npoAt) |
NPO checkbox + start time | { hours, severity, label } |
ok ≤ 12 h · warn 12–24 h · danger > 24 h |
useDurationTicker(start, end?, warn=180m, danger=300m) |
Anaesthesia / Procedure / any time range | { minutes, label, running, severity } |
ok < 3 h · warn 3–5 h · danger > 5 h · ticks while end is null |
useApgarTotal(rawScore) |
Apgar field (string or number) | { score, severity, label } |
ok 7–10 · warn 4–6 · danger 0–3 · idle when blank |
useSignInCompleteness(preop) |
Whole Sign-In checklist | { doneCount, total, missingCritical[], severity, percent } |
derives 16 fields · escalates to danger when any critical is missing OR NPO > 24 h |
3. The shared clock
All four hooks share one module-level setInterval(30_000) via useNow.ts:
┌─────────────────────────────────────────────────────────────┐
│ useNow.ts │
│ │
│ const subscribers = new Set<() => void>() │
│ let timer: ReturnType<typeof setInterval> | null = null │
│ │
│ ensureTimer() ← starts the 30 s tick on first subscribe │
│ teardownIfIdle() ← stops the timer when last unmounts │
└─────────────────────────────────────────────────────────────┘
│ broadcasts now=Date.now() every 30s
▼
┌──────────────┬───────────────┬───────────────────────────────┐
│ useNpoStatus │ useDuration │ useSignInCompleteness │
│ │ Ticker │ (consumes useNpoStatus) │
└──────────────┴───────────────┴───────────────────────────────┘
│ all badges re-render on the same tick
▼
NPO clock · live procedure ticker · completeness ring
(lock-step; zero drift between them)
This is the sync guarantee — turning the OR clock 30 s forward updates every badge in the same React commit, so the NPO clock can’t disagree with the procedure ticker. No per-component intervals; no fanout drift.
4. The two visual components
``
Chip-style severity indicator. Icon auto-selected from severity (success / warning / error / hourglass) or override with icon="clock". Palette comes from MUI theme so it follows light/dark mode. Used inline in form rows and as endAdornment on text fields.
``
SVG circular-progress ring around a percentage. Stroke color follows status.severity. Hover tooltip lists every missing-critical field. Used in the FM-OPR01-007 header banner.
5. Where it’s wired
| Surface | Location | What it shows |
|---|---|---|
| Sign-In header banner | PreoperativeChecklistReview.tsx next to FM-OPR01-007 |
CompletenessRing with 12/16 (75%) + tooltip listing missing items |
| Sign-In NPO row | PreoperativeChecklistReview.tsx above the NPO checkbox |
StatusBadge showing NPO 14.2 h with clock icon |
| Intraoperative Apgar field | Implant.tsx (C/S section), Apgar Score endAdornment |
StatusBadge showing Apgar 7/10 in severity colour |
| Time-Stamp dialog | DialogTimeStamp.tsx, next to Anest Time + Procedure Time result fields |
StatusBadge showing live 1h 23m once start is set; disappears when finish is recorded |
| Print template (FM-OPR01-007) | PerioperativeNursingRecordPrint.tsx |
SIGN IN row prints Completeness 12/16 (75%) ⚠ · NPO chip · severity-coloured Apgar · “· 2h 15m” suffix on completed ranges |
| Print trigger | DialogSignIn.tsx footer |
พิมพ์ FM-OPR01-007 button between draft-save and final-save |
| Save guard | DialogSignIn.tsx save button |
useConfirmation modal lists every missing critical before allowing submit; [กลับไปแก้ไข] [บันทึกต่อ] |
The print template uses the same hooks, so what prints == what’s on screen.
6. The Sign-In save guard
Wraps handleSubmit in guardSignInSubmit:
const guardSignInSubmit = () => {
setIsError(true);
if (state.page === 'sign-in' && signInCompleteness.missingCritical.length > 0) {
confirm({
title: 'รายการ Sign-In ยังไม่ครบ',
description: `ก่อนยืนยันบันทึก กรุณาตรวจสอบรายการต่อไปนี้:\n• ${missing.join('\n• ')}`,
leftLabel: 'กลับไปแก้ไข',
rightLabel: 'บันทึกต่อ',
onSubmit: () => handleSubmit(),
});
return;
}
handleSubmit();
};
Critical fields (any one missing → guard fires):
- Patient ID confirmed
- Site verified
- Allergies verified
- Consent verified
- Procedure verified
- NPO time + npoAt date set
- Synthetic: NPO > 24 h (from
useNpoStatus)
Non-critical fields contribute to the X/Y count but don’t trigger the guard. The autosave path (onAutoUpdate) is unaffected so drafts keep saving silently.
7. Severity colours
Pulled from MUI palette so they follow theme mode:
| Severity | Light | Dark | Meaning |
|---|---|---|---|
ok |
palette.success.main #2e7d32 |
brighter green | within safe operating range |
warn |
palette.warning.main #ed6c02 |
amber | escalate / flag to anaesthetic team |
danger |
palette.error.main #d32f2f |
crimson | immediate clinical attention required |
idle |
palette.text.disabled #9e9e9e |
grey | input not present yet |
Print template uses a small inline SEVERITY_COLOR map (no ThemeProvider in the print window) — same hex values.
8. Verification — sandbox Playwright smoke
Target: web/sandbox/targets/PerioperativeStatusKitTarget.tsx
Spec: web/e2e/perioperative-status-kit.sandbox.spec.ts
pnpm e2e:sandbox -- perioperative-status-kit
# 5 tests, 5 passed (~4 s)
# ✓ all four hooks render without crashing
# ✓ NPO severity escalates by hours (6h ok · 18h warn · 30h danger)
# ✓ Apgar score banding 9 / 5 / 2 / null
# ✓ Duration ticker + running-vs-stopped
# ✓ Completeness ring lists missing criticals
The target mounts every hook + visual with deterministic synthetic data — useful both as a regression smoke and as a visual reference for designers / clinicians.
9. File inventory
| File | Purpose |
|---|---|
web/packages/periops-kit/src/operating-room/components/list-or-patient/table-data/dialog/perioperative-status/useNow.ts |
Shared 30 s ticker (module-level subscribe pattern) |
…/perioperative-status/useNpoStatus.ts |
NPO hours + severity |
…/perioperative-status/useDurationTicker.ts |
Live elapsed ticker for any start/end range |
…/perioperative-status/useApgarTotal.ts |
Apgar parse + severity |
…/perioperative-status/useSignInCompleteness.ts |
Sign-In done/total + missing-critical aggregator |
…/perioperative-status/StatusBadge.tsx |
Severity-coloured chip with icon + tooltip |
…/perioperative-status/CompletenessRing.tsx |
SVG progress ring with tooltip |
…/perioperative-status/index.ts |
Barrel |
…/sign-in/PreoperativeChecklistReview.tsx |
Wires CompletenessRing + NPO badge |
…/time-out/Implant.tsx |
Wires Apgar badge |
…/time-stamp/DialogTimeStamp.tsx |
Wires live duration badges |
…/print-perioperative-record/PerioperativeNursingRecordPrint.tsx |
Print template (consumes same hooks) |
…/DialogSignIn.tsx |
Save guard + Print FM-OPR01-007 button |
web/sandbox/targets/PerioperativeStatusKitTarget.tsx |
Sandbox harness |
web/e2e/perioperative-status-kit.sandbox.spec.ts |
Playwright smoke (5 cases) |
10. Extending to other modules
The kit is module-agnostic; only useSignInCompleteness is Sign-In-specific. To reuse elsewhere:
- Recovery (PACU) NPO post-op: drop
useNpoStatusinto PACU vitals form with a different threshold table - Labour-ward partogram: use
useDurationTickerfor first-stage / second-stage labour ranges with obstetric-specific thresholds - Imaging contrast wait time:
useDurationTicker(injectedAt, scanStart)with contrast-specific bands - Any checklist surface: write a thin
useXyzCompleteness(form)that picks fields + critical set, reuse+as-is
To add a new severity-banded scalar (e.g. blood loss, transfused units), the pattern is useMemo(() => severity-classify-the-number) + `` — about 30 lines.
11. Out of scope / future work
- CDS hook: feed NPO > 24 h or Apgar ≤ 3 into the
cds_rulesengine so they raise aCdsAlertSurfacetoast / inline badge (seecds-vital-signs-rules.md). Currently visual-only; no backend alert. - Backend persistence: the calculated values are derived on the client every render. If we ever need them queryable (analytics, FHIR write-back), persist
apgarSeverity,npoHours,procedureDurationMinutesas denormalised columns on the OR request. - Acknowledgement integration: when
useSignInCompletenessreturnsseverity === 'danger', optionally raise anAcknowledgementRequestto the anaesthesia team (seeacknowledgement-system.md). Currently the save guard is the only gate.