OR Body-Site Field Map
Inventory of every OR case-modal field recording an anatomical location - surgical site, position, device/line/drain sites.
Inventory of every field in the operating-room case modal that records where on the body something happens — surgical site, patient position, device placement, line/drain insertion sites, specimen anatomy, laser sites, and retained-material locations.
The modal is DialogSignIn.tsx (the OR case dialog), which has five phases:
| # | Phase | Thai | WHO bucket |
|---|---|---|---|
| 1 | Waiting Room | ห้องรอ | pre-op (staging) |
| 2 | Sign In | ลงชื่อเข้า | pre-op (pre-induction) |
| 3 | Time Out | หยุดตรวจสอบ | intra-op (pre-incision + intra-op nursing) |
| 4 | Sign Out | ลงชื่อออก | post-op (before leaving OR) |
| 5 | Room Out | ออกจากห้อง | post-op (after) |
PHASE_STEPS is defined in web/packages/periops-kit/src/operating-room/components/list-or-patient/table-data/dialog/DialogSignIn.tsx:1523.
Pre-op tabs
Waiting Room
| Field | Component | Stored at | Purpose |
|---|---|---|---|
| Mark Site checkbox + photo upload | OperatingMarkSite.tsx |
markSite (bool) + uploaded files |
Surgeon photographs the marked anatomy (WHO Universal Protocol). Pure free-text/photo evidence, no anatomy picker. |
Mount points (rendered twice for two layout branches, same store key …:waiting:mark-site):
list-or-patient/table-data/dialog/WaitingRoom.tsx:622-628(split-column branch, gatedstate?.page === 'waiting-room')list-or-patient/table-data/dialog/WaitingRoom.tsx:753-759(full layout branch)- Source:
list-or-wait-requested/dialog/OperatingMarkSite.tsx
Sign In
| Field | Component / Line | Stored at | Notes |
|---|---|---|---|
| SITE per-procedure dropdown (ข้างซ้าย / ข้างขวา / ทั้ง2ข้าง / ไม่ระบุ) | miniapps/perform-surgery/DiagnosisIcd9Description.tsx:94-134 |
Operation.procedure[i].site.value |
Per-row (fixed by 50a5196b2 — used to be shared across all rows) |
| เพิ่มเติม / note per-procedure | same file :70-92 |
Operation.procedure[i].site.note |
free-text |
| เวลา / minute per-procedure | same file :136-159 |
Operation.procedure[i].site.minute |
|
Marked site radio (unnecessary / already_done / to_do) |
sign-in/SurgicalSafetyChecklist.tsx:355-382 |
signIn.safetyChecklist.markedSite |
WHO verification |
| Site verified checkbox + detail | sign-in/PreoperativeChecklistReview.tsx:336-358 |
verifySited + sitedDetails |
Yup-required (DialogSignIn.tsx:479). Inlines the photo from markSite (read-only) when present. |
verifyMarksite (declared, not rendered in OR) |
— | signIn.safetyChecklist.verifyMarksite |
Dead in OR. Only rendered in labour-room (labour-room/.../SurgicalSafetyChecklist.tsx:200-202). Initialised + autofilled in DialogSignIn.tsx:661, 1565 but no OR UI consumes it. |
Intra-op tab — Time Out
Mounted under time-out/IntraoperativeNursingData.tsx, section order:
ProcedureTeam → Implant → Antibiotics → Urinary Catheter → Drain → Packing → Hypothermia → Electrosurgical Risk → Injury Risk.
Surgical Safety Checklist
| Field | Line | Stored at |
|---|---|---|
| ตำแหน่งที่จะลงมีด / Incision Site checkbox + note | time-out/SurgicalSafetyChecklist.tsx:859-868 |
timeOut.safetyChecklist.surgeon.siteLocation + noteSiteLocation |
InjuryRisk card (time-out/InjuryRisk.tsx)
| Section | Options | Stored at |
|---|---|---|
| จัดท่า / Position for surgery | Supine, Prone, Lt. Lateral, Rt. Lateral, Lithotomy, Other (+ free-text) | injuryRisk.surg.* |
| อุปกรณ์จัดท่า / Position Devices | No, Shoulder Roll, Axillary Roll, Chest Roll, Pillow/wedge, Leg Holder | injuryRisk.devices.* |
| Skin / Safety strap — “Apply safety strap to” + “Position by” signature | text + signature | safetyStrap.{apply,note,positionBySignature} |
| Tourniquet body-region table | Left Arm / Right Arm / Left Thigh / Right Thigh / Body (per-region pressure + duration + signature) | injuryRisk.tourniquet.{leftArm,rightArm,leftThigh,rightThigh,body} |
ElectrosurgicalRisk card (time-out/ElectrosurgicalRisk.tsx)
| Section | Options | Stored at |
|---|---|---|
| Ground Pad / Return Electrode | Calf, Thigh, Arm, Back, Buttock, Megadyne, Other — each with left / right checkboxes (groundPadOptions, lines 31-38) |
injuryRisk.groundPad.<region>.{isValue,left,right} |
| Laser sites (multi-select Autocomplete) | ข้างซ้าย / ข้างขวา / ทั้งสองข้าง / ไม่ระบุ (siteOptions, lines 24-29) |
injuryRisk.laser.sites[] |
CatheterDrainPacking card (time-out/CatheterDrainPacking.tsx)
| Section | Body-site fields | Stored at |
|---|---|---|
Urinary Catheter (:14-82) |
catheterInsert (insertion location selector) + catheterSize |
urinaryCatheter, catheterInsert, catheterSize |
Drain (:94-208) — multi-row |
Per drain: drainType (Radivac / Jacobson / Lumbar / …), drainSite (text “ระบุ Site”), drainSize, drainNote |
columns[].{drainType,drainSite,drainSize,drainNote} |
Packing (:211-233) |
Button-launches DialogOperatingRoomAlert (see below) |
— |
Implant card (time-out/Implant.tsx)
| Field | Line | Stored at |
|---|---|---|
| C/S Specimen type + note (Pediatrician handoff) | :192-216 |
csSpecimen.value, csSpecimen.note |
Intra-op — three categories called out
1. Pathology / Specimens — time-out/Antibiotics.tsx:272-440
Nested inside the Antibiotics SignoffWrapper rather than its own card. Lives entirely under values.specimen.*.
- Top-level Specimen Yes/No →
specimen.value - Four specimen-type rows, each with a per-piece list of free-text notes (one note per “ชิ้น”):
| Specimen type | Checkbox flag | Pieces array | Per-piece field |
|---|---|---|---|
| C/S (culture) | specimen.cs |
specimen.csPieces[] |
.note (“ระบุ…”) |
| Frozen Section | specimen.frozen |
specimen.frozenPieces[] |
.note |
| Gram Stain | specimen.gramStain |
specimen.gramStainPieces[] |
.note |
| Other | specimen.other |
specimen.otherPieces[] |
.note |
- Pathology Orders sub-section (
:393-440):specimen.pathologyOrders[].note— free-text rows. - Running total
รวมชิ้นส่วน: N ชิ้นchip across all four piece arrays. - Mirror in Sign Out (
sign-out/SurgicalSafetyChecklist.tsx:477-549): re-readstimeOut.intraoperative.specimen.*, tags each piece withแล็บใน(in-house: Frozen, Gram Stain) orแล็บนอก(send-out: C/S, Other). - Separate
csSpecimenintime-out/Implant.tsx:192-216for C/S obstetric specimens — independent state fromspecimen.csabove.
⚠️ Every “site” here is free-text. No anatomic picker, no laterality, no SNOMED body-structure code. Pathology cannot auto-fill the specimen-source field from this data.
2. Laser — time-out/ElectrosurgicalRisk.tsx:231-319
Inside the “ความเสี่ยงบาดเจ็บจากเครื่องจี้ไฟฟ้า / Electrosurgical Injury Risk” card.
| Field | Stored at | Notes |
|---|---|---|
| Yes/No radio | injuryRisk.laser.no / injuryRisk.laser.yes |
|
| Type (text) | injuryRisk.laser.type |
free-text — no CO₂/Nd:YAG/KTP/Holmium picker |
| Setting (watts) | injuryRisk.laser.setting |
endAdornment: watt. |
| Site (multi-select) | injuryRisk.laser.sites[] |
siteOptions = ข้างซ้าย / ข้างขวา / ทั้งสองข้าง / ไม่ระบุ |
| Start / End time | injuryRisk.laser.startTime / endTime |
|
| Position by (signature) | injuryRisk.laser.positionBySignature |
declared + reset on No, but no visible form field for it in ElectrosurgicalRisk.tsx |
⚠️ “Site” is laterality only (4 options). Captures neither body region (eye, larynx, skin, prostate, cervix, etc.) nor anatomic detail required for a real laser register / radiation-safety log.
3. Gauzes / Packing — time-out/CatheterDrainPacking.tsx:211-233 → DialogOperatingRoomAlert
The Packing card itself is just a label + [Management] button that opens @ui-kit/components/dialog-operating-room-alert (mounted from time-out/TimeOut.tsx:656-665).
Dialog title: “เฝ้าระวังวัสดุคงค้างในบาดแผล / แผลผ่าตัด” (surveillance of retained materials in surgical wound).
Each row = one packed item left in the patient. Columns (packages/ui-kit/src/components/dialog-operating-room-alert/config.tsx):
| # | Column | Field | Type |
|---|---|---|---|
| 1 | วันที่/เวลาที่ บันทึก | recordDateTime |
timestamp |
| 2 | รายการวัสดุตกค้าง | pendingMaterialsList |
free-text (e.g. Gauze 4x4, Laparotomy sponge, Raytec sponge) |
| 3 | ตำแหน่งที่ PACK | locationPack |
free-text body location (e.g. RLQ abdomen, Pelvic cavity, Peritoneal) |
| 4 | จำนวน PACK | numPack |
number |
| 5 | ผู้ปฏิบัติ (Packing) | recorder |
user autocomplete |
| 6 | จำนวนที่ Remove | quantityToRemove |
number |
| 7 | ผู้ Remove | removedBy |
user |
| 8 | วันที่/เวลา Remove | removalDateTime |
timestamp |
| 9 | คงเหลือ | remaining |
numPack - quantityToRemove, computed |
| 10 | Acknowledged | acknowledged |
checkbox |
Dev autofill mocks (DialogOperatingRoomAlert.tsx:284-288):
- Gauze 4x4 →
RLQ abdomen× 5 - Laparotomy sponge →
Pelvic cavity× 3 - Raytec sponge →
Peritoneal× 2
Distinct from TableProceder.tsx which counts gauzes/needles/instruments on the field (in vs out reconciliation):
TableProceder= “did we lose anything?” (count audit)DialogOperatingRoomAlert= “what did we intentionally leave behind?” (retained-material tracking)
⚠️
locationPackis free-text. No anatomic picker, no link back to the procedure’s site / laterality, no timer or ward handoff for the implied “must remove before discharge” alert.
Post-op tabs
Sign Out
| Field | Source | Notes |
|---|---|---|
detail[].site.{value,note,minute} chips (“Site: |
sign-out/SurgicalSafetyChecklist.tsx:228-235, 395-405 |
Read-only mirror of the Sign-In / Time-Out site data |
| Specimen summary | sign-out/SurgicalSafetyChecklist.tsx:477-549 (reads timeOut.intraoperative.specimen) |
Read-only |
Room Out
| Field |
|---|
| None — status + discharge timestamps only. No body-position fields. |
Summary by intent
| Intent | Phase | Where |
|---|---|---|
| Surgical site / laterality (left vs right vs bilateral) | Sign In + Time Out + Sign Out mirror | per-procedure SITE dropdown + Incision Site checkbox |
| Marked-site evidence (photo of marking) | Waiting Room | OperatingMarkSite |
| Patient body position on table | Time Out | InjuryRisk → Position for surgery |
| Positioning devices (rolls, pillows, leg holders) | Time Out | InjuryRisk → Position Devices |
| Tourniquet body regions | Time Out | InjuryRisk → Tourniquet (4 limbs + body) |
| Grounding-pad anatomic placement (L/R per region) | Time Out | ElectrosurgicalRisk → Ground Pad |
| Laser energy body sites | Time Out | ElectrosurgicalRisk → Laser sites (laterality only) |
| Catheter insertion site | Time Out | CatheterDrainPacking → Urinary Catheter |
| Drain insertion sites (per drain row) | Time Out | CatheterDrainPacking → Drain table drainSite |
| Packing / retained gauze locations | Time Out | Packing → DialogOperatingRoomAlert locationPack |
| Specimen anatomy (notes per piece) | Time Out | Antibiotics → Specimen pieces .note |
| C/S Specimen + Pediatrician handoff | Time Out | Implant → csSpecimen |
| Read-only confirmation of all the above | Sign Out | SSC mirror chips + Specimen rows |
Gaps worth flagging
-
Every body-site field is free-text. No anatomic picker, no SNOMED body-structure code, no diagram pin. Impact:
- Pathology cannot auto-fill the pathology request form’s specimen-source field — lab re-keys
- Laser register cannot produce a body-region utilization report — only laterality
- Retained-material tracking cannot drive a ward alert (“Patient X has Raytec sponge in peritoneal cavity, due for removal in OR before discharge”)
-
No dedicated “Lines / Vascular Access” section anywhere in the modal. IV / arterial / central / epidural / spinal lines are only referenced as pre-prep prompts (
"พิจารณาเตรียม IV 2 เส้น / Central line"insign-in/SurgicalSafetyChecklist.tsx:610;"IV line patent"in thePreferenceCardChecklistmock list). Never recorded with body site, gauge, depth, or insertion-by signature the way drains or ground pads are. If WHO / TJC line-time-out documentation is needed, this is a missing section — most naturally a new “Lines / Access” card in Time Out next to CatheterDrainPacking. -
verifyMarksiteis declared in OR state but never rendered in the OR Sign-In UI. Only labour-room renders it as a checkbox (labour-room/.../SurgicalSafetyChecklist.tsx:200-202). In OR it’s initialised (DialogSignIn.tsx:661), reset, set by autofill (:1565), and typed (orRecord.types.ts:63) — but no SSC component reads or writes it via UI. Either restore the checkbox or drop the field from OR state shape. -
Lowest-friction unification: a shared
AnatomicSiteAutocomplete(SNOMED body-structure-backed) swapped in for the free-text inputs across Pathology pieces, Laser sites, PackinglocationPack, DraindrainSite, and CathetercatheterInsert. The data shape stays a string; entry just becomes constrained, codeable, and queryable.
File index
| File | Role |
|---|---|
web/packages/periops-kit/src/operating-room/components/list-or-patient/table-data/dialog/DialogSignIn.tsx |
Modal shell + PHASE_STEPS + initial values + autofill + Yup |
…/dialog/WaitingRoom.tsx |
Waiting Room tab; mounts OperatingMarkSite twice |
…/dialog/sign-in/SurgicalSafetyChecklist.tsx |
Sign In SSC card (markedSite radio, per-procedure SITE) |
…/dialog/sign-in/PreoperativeChecklistReview.tsx |
Sign In Preop Review (verifySited + sitedDetails, inlines mark-site photo) |
…/dialog/time-out/IntraoperativeNursingData.tsx |
Time Out intra-op container; section ordering |
…/dialog/time-out/SurgicalSafetyChecklist.tsx |
Time Out SSC (Incision Site) |
…/dialog/time-out/InjuryRisk.tsx |
Position + Position Devices + Skin/Strap + Tourniquet |
…/dialog/time-out/ElectrosurgicalRisk.tsx |
Ground Pad + Laser |
…/dialog/time-out/CatheterDrainPacking.tsx |
Urinary Catheter + Drain (table) + Packing button |
…/dialog/time-out/Antibiotics.tsx |
Specimen (4 types × N pieces) + Pathology orders |
…/dialog/time-out/Implant.tsx |
csSpecimen (C/S obstetric) |
…/dialog/sign-out/SurgicalSafetyChecklist.tsx |
Read-only mirror of site + specimen |
…/list-or-wait-requested/dialog/OperatingMarkSite.tsx |
markSite checkbox + image upload |
web/packages/miniapps/perform-surgery/DiagnosisIcd9Description.tsx |
Per-procedure SITE / note / minute table cells |
web/packages/ui-kit/src/components/dialog-operating-room-alert/DialogOperatingRoomAlert.tsx |
Retained-material surveillance dialog |
web/packages/ui-kit/src/components/dialog-operating-room-alert/config.tsx |
Column definitions for the dialog |
Connecting to the 3D Anesthesia Body Mapper
AnesthesiaBodyMapper (web/packages/miniapps/3d-health-anesthesia/AnesthesiaBodyMapper.tsx) is already wired through DynamicContentRenderer (web/src/common/components/shared-engine/dynamic-core/engine/DynamicContentRenderer.tsx:3568-3581) and exposed via DynamicCoreApp.ANESTHESIA_BODY_MAPPER = 'modules.AnesthesiaBodyMapper'. It is not yet 3D — currently a 100×100 SVG with CSS rotation simulating positions — but the semantic vocabulary is already correct, and DynamicCoreApp.ts:482 documents intent to evolve into a true 3D viewer. A 3DPainMapper.tsx sibling and a 3d-Health-Model/ subdir (DentalCharting, CameraControl, Annotation, Model, encounter-detail, hooks) suggest the Three.js work is underway.
The mapper’s onSave callback in DynamicContentRenderer currently just console.logs — nothing persists yet.
What the mapper already provides
| Construct | Coverage |
|---|---|
11 MarkerTypes |
injection_site, nerve_block, epidural, spinal, airway, iv_access, arterial_line, monitoring, incision, position_marker, annotation |
9 PATIENT_POSITIONS |
Supine / Prone / Left Lateral / Right Lateral / Lithotomy / Trendelenburg / Reverse Tren / Sitting / Beach Chair |
32 named BODY_PARTS_3D |
head, neck, throat, chest L/C/R, shoulder L/R, arm/forearm/hand L/R, abdomen, back upper/lower, lumbar spine, hip/groin L/R, thigh/knee/leg/ankle/foot L/R |
10 NERVE_BLOCK_SITES |
Interscalene, Supraclavicular, Infraclavicular, Axillary, Femoral, Sciatic, Popliteal, Ankle, TAP, Erector Spinae |
AnesthesiaMarker shape |
{id, type, position{x,y,z}, bodyPart, label, labelTh, details: Record<string,any>, color, timestamp, createdBy} |
| Output | onSave({markers, position}) |
OR-modal fields mapped to mapper markers
| OR-modal field | Mapper marker | Notes |
|---|---|---|
signIn.safetyChecklist.markedSite + Waiting-Room photo |
incision |
exact |
surgeon.siteLocation (Time-Out incision) |
incision |
exact |
Per-procedure Operation.procedure[i].site.value |
incision × N |
exact |
injuryRisk.surg.{supine,prone,LtLateral,RtLateral,lithotomy} |
position_marker + the modal’s position prop |
identical enum already |
injuryRisk.tourniquet.{leftArm,rightArm,leftThigh,rightThigh,body} |
new type tourniquet |
arm_left/right, thigh_left/right already exist |
injuryRisk.groundPad.{calf,thigh,arm,back,buttock}.{left,right} |
new type ground_pad |
all body parts already named |
injuryRisk.laser.sites (4-option laterality) |
new type laser |
upgrades from 4 → 32 body parts |
urinaryCatheter + catheterInsert |
new type urinary_catheter |
groin_left/right |
columns[].drainSite (multi-drain) |
new type drain |
any named part |
csSpecimen + specimen.csPieces[i].note etc. |
new type specimen |
per-piece body part |
DialogOperatingRoomAlert.locationPack (gauze packing) |
new type retained_material |
any named part |
| Missing entirely in OR modal | iv_access, arterial_line, airway |
already supported by mapper |
The mapper is therefore a superset of the modal’s vocabulary — it already covers vascular lines and airway, which the modal does not capture at all.
Three connection patterns (layered)
Pattern A — Read-only summary view (zero-risk, ~1 day)
Mount `` at the bottom of Sign Out and Room Out tabs (and on the printable OR record). Build a pure-function deriveMarkersFromOrRequest(orRequest) that walks every form path catalogued above and emits markers. Nothing else changes; you get one visual summary at end-of-case that previously required reading 8 separate cards.
Also expose via DynamicContentRenderer as a new case 'OrBodyMapSummary' so the same widget appears in patient profile, OR landing, PACU handoff, and the print report — single source of truth, no fork.
Pattern B — Inline picker upgrade (medium effort)
Every free-text site field in the modal gains a [📍 Pin on body] button. Click opens the mapper as a Dialog in single-marker-pick mode, returns {bodyPart, position{x,y,z}, label, labelTh} and writes into the existing field path PLUS into a parallel bodyMarker field. Old free-text still works; structured data accumulates alongside.
Six target fields ordered by ROI:
DialogOperatingRoomAlert.locationPack— biggest payoff (retained-material safety)specimen.csPieces[i].note(and frozen/gramStain/other Pieces) — feeds pathology requisitioncolumns[].drainSite(drain rows)injuryRisk.laser.sites— produces a real laser registercatheterInsertsignIn.preoperativeChecklist.sitedDetails
Pattern C — Mapper as source of truth (the actual unification)
A useOrBodyMarkers(orRequestId) store (same useSyncExternalStore + localStorage pattern as useOrTimesStore introduced on the claude/find-or-list-route-KxRbe branch). The mapper writes; every section form reads. Section forms become editors for marker.details — drain size, catheter size, lot/serial, pressure, duration, signatures all go into details: Record<string, any>.
This gives architectural cleanup:
- One canonical array
orRequest.bodyMarkers[]replaces ~15 scattered field paths - The marker IS the site marking —
verifyMarksitebecomes dead state to remove - Per-phase visibility filter: Sign-In shows
incision+airway+iv_access; Time-Out shows everything; Sign-Out shows everything read-only; Room-Out filters toretained_material+drain+iv_accessstill in patient - Sign-Out specimen mirror becomes a marker filter, not a hand-coded read of
timeOut.intraoperative.specimen
Four payoffs that justify the work
- Retained-material safety alert. Today:
locationPackis free-text, no link to discharge. After: theretained_materialmarker carries to PACU/ward handoff, gates discharge if unresolved, firesAcknowledgementInboxtask per the acknowledgement system contract. - Pathology requisition autopopulation. Today:
specimen.csPieces[i].noteis free-text the lab re-keys. After: each specimen marker hasbodyPart(SNOMED-mappable) → pathology request gets the source anatomy automatically. Plugs directly into the pathology module work on thepathology-lab-workbranch. - Real laser/radiation register. Today:
injuryRisk.laser.sitesis 4-option laterality. After: 32 body parts + duration + watts + type → device utilisation reports, radiation-safety logs, NM module integration (pernuclear-medicine-ris-pacs-integration.md). - The missing Lines/Vascular-Access section appears for free. The mapper already has
iv_access/arterial_line/airwaytypes. Adding a “Lines & Access” card in Time Out that is just a filtered marker list closes the gap flagged earlier — no new form components needed.
One caveat (forward-compatible)
The mapper today is SVG with CSS rotation. Calling it “3D” is aspirational (position.z exists in the schema but is unused). This does not block the connection above — the schema is forward-compatible. When the 3d-Health-Model/ Three.js work lands and the renderer swaps, the marker store, the OR modal wiring, the Supabase persistence, and the summary view all keep working unchanged. Do not wait for true 3D to start connecting; the data layer is the lasting contract, the renderer is replaceable.
Dual-View + BodyMapperSection (shipped)
The first piece of the integration above is now live. Two layers:
1. AnesthesiaBodyMapper — view-mode + face-aware markers
New props on the existing mapper (web/packages/miniapps/3d-health-anesthesia/AnesthesiaBodyMapper.tsx):
| Prop | Type | Notes |
|---|---|---|
viewMode? |
'single' | 'front' | 'back' | 'dual' |
Controlled mode. If omitted, mapper manages its own state. |
defaultViewMode? |
same union | Initial mode for uncontrolled usage. Defaults to 'single' so existing consumers (DynamicContentRenderer, AnesthesiaLandingPage, OpdSystemLanding) keep current behaviour. |
onViewModeChange? |
(mode: ViewMode) => void |
Fires whenever the user toggles the in-mapper view-mode group. |
New type fields:
AnesthesiaMarker.face?: 'anterior' \| 'posterior' \| 'lateral_left' \| 'lateral_right'— optional. Old markers without it are resolved byinferFace()(exported helper) using the body-part name.BODY_PARTS_3D[part].faces: Face[]— every body part declares which faces it is clickable from. New posterior-only parts:buttock_left/right,calf_left/right.
BodySVG now accepts a face? prop. When set, the panel is locked to that face (no rotation), markers are filtered to that face, and the click handler restricts body-part matching to parts whose faces include the current face. When omitted (legacy / single mode), behaviour is unchanged — the body rotates with the patient position.
The header has a 4-button toggle group: Default / Front / Back / Front+Back (TH tooltips). Center panel switches between 1 and 2 SVG panels via CSS grid based on viewMode.
2. BodyMapperSection — drop-in wrapper
web/packages/miniapps/3d-health-anesthesia/BodyMapperSection.tsx. Context-agnostic. Owns:
- Collapsible card chrome (per-context state in
localStorage[bodyMapper:collapsed:<type>:<id>]) - Marker persistence (per-context,
localStorage[bodyMapper:markers:<type>:<id>]) - View-mode preference (per-user,
localStorage[bodyMapper:viewMode]) - Header summary chips (marker count + active view-mode)
Props:
interface BodyMapperSectionProps {
contextId: string;
contextType: 'OR' | 'LR' | 'ER' | 'NURSING_HOME' | 'IPD' | 'ANESTHESIA_PRE' | 'CUSTOM';
patientId?: string;
patientName?: string;
gender?: 'male' | 'female';
initialMarkers?: AnesthesiaMarker[];
defaultCollapsed?: boolean;
defaultViewMode?: ViewMode; // wrapper default: 'dual'
readOnly?: boolean;
title?: string; // default: 'Body Map / แผนภาพร่างกาย'
onMarkersChange?: (markers: AnesthesiaMarker[]) => void;
}
Drop-in usage (one-liner per modal)
import { BodyMapperSection } from '@miniapps/3d-health-anesthesia';
<BodyMapperSection
contextId={record._id}
contextType="LR" // or "ER" / "NURSING_HOME" / "IPD" / "ANESTHESIA_PRE"
patientId={record.patientRef?._id}
patientName={record.patientRef?.fullName}
gender={record.patientRef?.gender === 'F' ? 'female' : 'male'}
defaultViewMode="dual" // optional; per-user pref overrides once set
/>
Currently mounted in
| Surface | File | Position |
|---|---|---|
| OR Time-Out (intra-op nursing) | web/packages/periops-kit/src/operating-room/.../time-out/IntraoperativeNursingData.tsx |
Between InjuryRisk and FocusList |
| LR Time-Out (intra-op nursing) | web/packages/periops-kit/src/labour-room/.../time-out/IntraoperativeNursingData.tsx |
Above FocusList |
| LR Time-Out (duplicated path) | web/packages/periops-kit/src/labour-room/labour-room/.../time-out/IntraoperativeNursingData.tsx |
Same |
Not yet wired (intentional follow-ups)
- ER encounter modal — same one-liner,
contextType="ER". Pending identification of the right mount file in the ER module. - Nursing home rounds —
contextType="NURSING_HOME". Natural home is the rounds note dialog. - Backend persistence — markers cache to localStorage today. When
bodyMarkers[]lands on Mongo + Supabase, swap the storage layer inBodyMapperSection.tsxwithout touching any mount site. - Pattern B/C field-level integration — the OR-modal free-text site fields (
drainSite,locationPack,specimen.csPieces[i].note, etc.) do not yet pre-populate the mapper or vice versa. TheonMarkersChangecallback is the integration seam when this work lands. facefield forNERVE_BLOCK_SITES— quick-add nerve blocks currently land on the inferred face. Adding an explicitfaceto each preset will auto-pin Sciatic on posterior, Femoral on anterior, etc.- Lateral profiles (
quadmode) — theViewModeunion is deliberately capped at 4 modes (single/front/back/dual) to avoid shipping a brokenquadtoggle. Lateral SVG outlines are a separate piece of work.