Pathology Request Flow
Pathology request flow plan and execution summary.
Branch: claude/investigate-pathology-request-YL7KV
Commits: bbd885e5 … 231e472f (6 commits)
Status: All on branch, pushed; ready for review.
Context
The clinic ops team needed end-to-end coverage of the anatomical-pathology tissue request workflow on the new HIS, matching their Thai-HIS spec (form layout, ANATOMICAL PATHOLOGY confirmation, lab worklist columns, ประเภทงาน categories, 7-state colored flow). The pre-existing path was a generic order shell (OrderApril2025 filtered to the pathology tab) — usable, but it didn’t match the pathology-specific form fields, didn’t issue an ANATOMICAL PATHOLOGY confirmation, didn’t route nurse acknowledgement, and left the pathologist’s sign-out surface dormant.
This series of changes ships:
- A dedicated pathology request form — doctor side (
PathologyRequestminiapp) - A post-submit confirmation — ANATOMICAL PATHOLOGY layout with auto-print default + footer toggle, opt-out persisted to
localStorage - A nurse acknowledgement inbox with specimen barcode capture + ใบนำทาง / cover sheet printable
- Lab worklist parity with the spec (columns, per-day count chip strip, work-category filter)
- A central frontend flow contract — 7 user states, hospital-overridable status map, soft-gate transition table
- Work-category catalog (
(S) Surgical Pathology…(PM-I) Immunohistochemistry) with accession-prefix routing - Pathologist sign-out surface —
ApproveResulttab re-enabled with View / Send back / Reject / Approve, all gated by the flow contract
No backend code was changed. The frontend remains a soft gate; backend is still the write source of truth.
High-level flow
┌────────────────────────────────────┐
│ PathologyRequest (miniapp, doctor) │
│ • Header strip: Patho No / work │
│ type / surgery date / container │
│ • Routing strip: units / doctor / │
│ pathologist / payor │
│ • Tabs: General / Exam / History │
│ POST /v2/medication/orderRequests │
│ status='sent-specimens' │
└─────────────┬──────────────────────┘
│ (auto-opens)
▼
┌────────────────────────────────────┐
│ PathologyRequestConfirmation │
│ ANATOMICAL PATHOLOGY layout │
│ Auto-print toggle (default ON) │
│ ปิด / พิมพ์ / Export to PDF │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ PathologyRequestNurseAck (miniapp) │
│ • Lists pending pathology requests │
│ • Specimen barcode capture │
│ • พิมพ์ใบนำทาง (popup print) │
│ • รับทราบสิ่งส่งตรวจ │
│ POST .../updateAcknowledge/:id │
└─────────────┬──────────────────────┘
▼
┌────────────────────────────────────────────────────────────────┐
│ /pathology/laboratory (existing 6-tab Worklist.tsx) │
│ │
│ Register → SlidePrep → ReportIssue │
│ (sent-specimens) (accepted) (accepted) │
│ │
│ ↓ pathologist action │
│ │
│ ApproveResult → ViewResult CancelResult │
│ (resulted) (completed) (cancelled / rejected) │
│ │
│ Pathologist row actions on ApproveResult: │
│ • ดูผล │
│ • ตีกลับ Lab → SendBackToLabDialog → REGISTERED │
│ • ปฏิเสธ → RejectionFormDialog → SPECIMEN_REJECTED │
│ • อนุมัติ → REPORTABLE │
└────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ Frontend flow contract │
│ (single source of truth — colours, │
│ labels, soft-gate, hospital map) │
└────────────────────────────────────┘
Files created
New miniapp — web/packages/miniapps/pathology-request/
| File | Purpose |
|---|---|
PathologyRequest.tsx |
Doctor-side form. Header strip, routing strip, 3 tabs of free-text. Catalog picker over getLabItem. Submits via createOrderRequestApi (POST {VITE_APP_API}v2/medication/orderRequests) with status='sent-specimens'. |
PathologyRequestConfirmation.tsx |
Anatomical Pathology confirmation dialog opened on success. Auto-print toggle persisted to localStorage['pathology-request:auto-print'] (default ON). Buttons: ปิด / พิมพ์ / Export to PDF (html2canvas + jsPDF). |
PathologyRequestNurseAck.tsx |
Nurse inbox. Lists pending requests for the current patient via usePathologyRequest. Specimen-barcode capture, cover-sheet print, รับทราบสิ่งส่งตรวจ → POST .../orderRequests/updateAcknowledge/:id (existing endpoint). |
PathologyRequestCoverSheet.tsx |
Printable ใบนำทาง / A5 routing slip. Builds self-contained HTML and opens in a popup window with window.print(). Falls back to blob URL if popup blocked. |
index.tsx |
Barrel. |
Flow contract — web/packages/diagnostics-kit/src/pathology/flow-contract/
| File | Purpose |
|---|---|
states.ts |
PathologyUserState enum (7 states from spec) + PATHOLOGY_USER_STATE_META (Thai/EN labels, hex colours matching spec, MUI chip colour hint, terminal flag, order). |
status-map.ts |
DEFAULT_STATUS_TO_STATE + registerHospitalStatusMap() for tenant overrides + getBackendStatusesForState() reverse-lookup + resolveStateFromStatus(). |
transitions.ts |
PathologyAction enum (10 actions including SEND_BACK_TO_LAB) + TRANSITIONS table + canPerformAction() soft gate. |
resolve.ts |
resolvePathologyState(row) reads from multiple plausible status field locations; usePathologyFlowState() hook + canRowPerformAction() row-level helper. |
work-categories.ts |
8-entry catalog (S, R, ST, PM-S, PM-C, PM-F, PM-R, PM-I) with numericId, accessionPrefix, worklistSection, defaultSlaHours, isPremium. findWorkCategory() accepts code / numericId / prefix. Hospital-overridable. |
index.ts |
Barrel — single import point for consumers. |
Pathologist sign-out
| File | Purpose |
|---|---|
web/packages/diagnostics-kit/src/pathology/laboratory/dialogs/SendBackToLabDialog.tsx |
~140-line MUI dialog. Captures reason + optional follow-up tag (Restain / Recut / IHC / Other), serialises to a remark string. Mirrors RejectionFormDialog’s style. |
Files modified
| File | Change |
|---|---|
web/src/setup/dynamic/DynamicCoreApp.ts |
Two new enum values: PATHOLOGY_REQUEST, PATHOLOGY_REQUEST_NURSE_ACK. |
web/src/common/components/shared-engine/dynamic-core/engine/DynamicContentRenderer.tsx |
Imports + two new case branches rendering the new miniapps. Existing ORDER_PATHOLOGY case left untouched. |
web/src/locales/en/coreApps.json + web/src/locales/th/coreApps.json |
Labels for both new modules. |
web/packages/diagnostics-kit/src/pathology/laboratory/utils.tsx |
Register-tab columns: rename วันที่-เวลาออเดอร์ → วัน-เวลาส่งตรวจ, added อายุ (ปี), หน่วยงานที่ส่งตรวจ, หน่วยงานที่รับผล (with renderers reading from listitems[0].addons.*). All 6 tabs: เวลาที่ผ่าตัด → วัน-เวลาผ่าตัด (date+time). Re-enabled ApproveResult row-action block with 4 buttons. Added getUserStateFromTab() returning the flow-contract user state for each tab. |
web/packages/diagnostics-kit/src/pathology/laboratory/Worklist.tsx |
Added work-category Select filter (“ประเภทงาน”) on Register tab. Added filteredRows memo (client-side filter). Added dailyCounts memo + per-day chip strip under date pickers. Added handleSendBackToLab + submit handler, gated existing handleApproveResult via canRowPerformAction, added SEND_BACK_TO_LAB action route, un-commented bilingual snackbar copy. Mounted SendBackToLabDialog. |
API surface (unchanged, just consumed differently)
| Verb + path | Used by | Notes |
|---|---|---|
POST {VITE_APP_API}v2/medication/orderRequests |
PathologyRequest form |
Submits with status='sent-specimens' → row appears in Register tab. patient + encounter from usePatientProfileMongoIds (MongoDB ObjectIds) to avoid the Supabase-UUID 422. |
POST .../orderRequests/updateAcknowledge/:id |
PathologyRequestNurseAck |
updateAcknowledgeByOrderId — existing helper. |
GET .../medication/labItems |
Form catalog picker | getLabItem({ search, type:'pathology', isShowOrder:true }). |
mutateAsyncUpdatePathologyById |
All worklist mutations | Existing engine. Pathologist actions only change the status + remark payload values. |
Plan files
| Plan file | Scope |
|---|---|
/root/.claude/plans/ok-but-what-about-buzzing-quilt.md |
Pathologist sign-out plan (the final increment). Wrote in plan mode; user approved via ExitPlanMode. |
Earlier increments were proposed inline (with AskUserQuestion for scoping) and shipped immediately, not via a plan file.
Commits (oldest first)
| SHA | Title |
|---|---|
bbd885e5 |
feat(pathology-request): standalone doctor+nurse pathology tissue request |
ef5d740a |
feat(pathology-request): post-submit Anatomical Pathology confirmation + auto-print toggle |
dd20e6de |
feat(pathology-lab-worklist): wire PathologyRequest into Register tab + spec'd columns |
65e0af8e |
feat(pathology): central frontend flow contract (soft gate) for the 7-state lab workflow |
5c186404 |
feat(pathology): work-category catalog (ประเภทงาน) + Register-tab filter |
231e472f |
feat(pathology): pathologist sign-out surface in ApproveResult tab |
The flow contract — quick reference
7 user states
| State | Thai | Colour | Terminal |
|---|---|---|---|
AWAITING_SPECIMEN |
รอสิ่งส่งตรวจ | #E11D48 red |
no |
REGISTERED |
ลงทะเบียนรับแล้ว | #2563EB blue |
no |
ANALYZING |
กำลังตรวจวิเคราะห์ | #16A34A green |
no |
SENT_TO_PATHOLOGIST |
นำส่งพยาธิแพทย์ | #C026D3 magenta |
no |
REPORTABLE |
ตรวจสอบแล้วรายงานผลได้ | #EA580C orange |
no |
RECEPTION_CANCELLED |
ยกเลิกการรับสิ่งส่งตรวจ | #92400E brown |
yes |
SPECIMEN_REJECTED |
ปฏิเสธสิ่งส่งตรวจ | #991B1B dark red |
yes |
Soft-gate transition table
AWAITING_SPECIMEN → REGISTER_SPECIMEN → REGISTERED
→ REJECT_SPECIMEN → SPECIMEN_REJECTED (terminal)
→ CANCEL_RECEPTION → RECEPTION_CANCELLED (terminal)
→ PRINT_COVER_SHEET (inert)
REGISTERED → START_ANALYSIS → ANALYZING
→ REJECT_SPECIMEN → SPECIMEN_REJECTED (terminal)
→ PRINT_COVER_SHEET (inert)
ANALYZING → SEND_TO_PATHOLOGIST → SENT_TO_PATHOLOGIST
→ PRINT_COVER_SHEET (inert)
SENT_TO_PATHOLOGIST → APPROVE_RESULT → REPORTABLE
→ REJECT_SPECIMEN → SPECIMEN_REJECTED (terminal)
→ SEND_BACK_TO_LAB → REGISTERED
→ PRINT_RESULT (inert)
REPORTABLE → PRINT_RESULT (inert)
Default backend status → state mapping (medOS canonical)
sent-specimens / pending / draft → AWAITING_SPECIMEN
accepted / accepted-specimens /
registered / received / collected → REGISTERED
slide-prepared / slide-prep / analyzing /
in-process / gross-done / micro-done → ANALYZING
sent-pathologist / pathologist-review /
pending-signout → SENT_TO_PATHOLOGIST
resulted / completed / co-signed /
ready-to-report → REPORTABLE
cancelled / canceled /
cancel-reception → RECEPTION_CANCELLED
reject-specimens / rejected / reject → SPECIMEN_REJECTED
Hospitals override via registerHospitalStatusMap(key, overrides) at app boot.
8-entry work-category catalog
| Code | numericId | Section | SLA(h) | Premium |
|---|---|---|---|---|
S |
1 | surgical | 72 | no |
R |
4 | review | 120 | no |
ST |
14 | special-tissue | 168 | no |
PM-S |
9 | surgical | 24 | yes |
PM-C |
10 | cytology | 24 | yes |
PM-F |
11 | frozen | 1 | yes |
PM-R |
12 | review | 48 | yes |
PM-I |
18 | immuno | 48 | yes |
accessionPrefix drives global-sequence numbering (S25-…, PMI25-…).
Verification
Type-check
pnpm tsc --noEmit -p tsconfig.jsonfiltered to the touched files: zero new errors (pre-existing nuclear-medicine / workflow-editor errors are unrelated and predate this branch).
Manual UAT (sandbox route — see web/CLAUDE.md)
- Submit a request via
PathologyRequestfrom the/sandboxworkspace browser (modules.PathologyRequest) → confirms ANATOMICAL PATHOLOGY dialog opens, auto-print window pops, row getsstatus='sent-specimens'in Mongo. - Open
modules.PathologyRequestNurseAckfor the same patient → row appears, enter barcode → “พิมพ์ใบนำทาง” opens the A5 routing slip +window.print()→ “รับทราบสิ่งส่งตรวจ” flips status toaccepted. - Open
/pathology/laboratory→ Register tab shows row with new columns (วัน-เวลาส่งตรวจ / อายุ / หน่วยงานที่ส่งตรวจ / หน่วยงานที่รับผล). Work-category filter (“ประเภทงาน”) narrows rows; per-day chips reflect filtered counts. - Walk row through SlidePreparation / ReportIssue / ApproveResult.
- On ApproveResult tab: ดูผล opens
DialogViewResult; ตีกลับ Lab opensSendBackToLabDialog→ row hops back to SlidePreparation with statusaccepted+ the new remark; ปฏิเสธ reusesRejectionFormDialog; อนุมัติ writesstatus='completed'and the row moves to ViewResult.
Programmatic
import {
canRowPerformAction,
PathologyAction,
} from '@diagnostics-kit/pathology/flow-contract';
const ok = canRowPerformAction(row, PathologyAction.APPROVE_RESULT);
// { allowed: true, to: 'reportable' } for a 'resulted' row
// { allowed: false, reason: '...' } for a 'cancelled' row
Intentional non-goals
- No backend changes. All mutations reuse existing endpoints (
createOrderRequestApi,updateAcknowledgeByOrderId,mutateAsyncUpdatePathologyById). - No role-based gating. Pathology has zero RBAC today; adding it is a separate concern that should ride on the broader auth/role refactor.
- No standalone pathologist miniapp. Per the planning decision, the pathologist works inside the existing
/pathology/laboratorylab worklist on theApproveResulttab. Reduces UI duplication. - No new
DynamicCoreAppentries for the lab worklist. It’s already mounted at/pathology/laboratory. - Client-side work-category filter on the worklist. The data endpoint accepts a single
status=param today; swap to a server-side filter when the API gains aworkTypeCode/pathoCategoryquery param. - Per-day count chip strip is current-page only. Backend count endpoint groups by status, not date. Tooltip explicitly flags this as a lower-bound count.
Where to look next
web/packages/diagnostics-kit/src/pathology/flow-contract/index.ts— barrel; every UI surface that renders a pathology row should import from here.web/packages/miniapps/pathology-request/index.tsx— exports for the new doctor + nurse surfaces.web/packages/diagnostics-kit/src/pathology/laboratory/Worklist.tsx— the lab tech + pathologist UI lives here; tabs are switched via the existingselectedMainTab.
Recommended follow-ups (out of scope for this branch):
- Wire
usePathologyFlowStateinto the existingORDER_PATHOLOGY_STATUSbadge renderer indynamic-table/utils/badgeStatus.tsso the contract owns chip colours everywhere. - Add the per-day count to the backend count endpoint so the chip strip is full-range, not page-limited.
- Add RBAC at the route level so non-pathologists can’t open the ApproveResult tab.
- Stamp the resolved
accessionPrefixonto a real global-sequence call (S25-00001,PMI25-00001) when the pathology counter is added to the global-sequence service.