Pathology Scanner & QR Labels
Barcode/QR scanner integration and printable specimen-label generator for the pathology footer.
Barcode/QR scanner integration for the pathology footer + a printable specimen label generator. Both ride on existing infrastructure (
react-barcode-reader,CameraScannerDialog,qrcode.react,react-barcode) — no new dependencies.
Overview
USB wedge scanner ─┐
│
Camera (BarcodeDetector) ─┐
├──▶ useLabRequestByBarcode ──▶ activePathologySpecimenStore
Manual TextField ─────────┘ (Mongo lookup via │
getListSpecimenCollection- │
WithSearch) ▼
PathologyStateFooterContainer
│
reads activeStore as fallback
│
▼
policy_gates evaluation +
state machine + transitions
What’s New
| File | Purpose |
|---|---|
state/activePathologySpecimenStore.ts |
zustand store — the scanner writes; the footer reads |
hooks/useLabRequestByBarcode.ts |
Wraps getListSpecimenCollectionWithSearch for barcode/HN/order-id lookup |
components/PathologySpecimenScanner.tsx |
USB wedge + camera + manual entry combo, with success/error feedback |
components/PathologySpecimenLabel.tsx |
Printable 50×30 mm QR + 1D barcode sticker with qrcode.react + react-barcode |
Plus extensions:
types/index.ts—CockpitAction.actionTypenow accepts'scan'and'print_label'defaults/pathology-default-workflow.ts— Register node getsScan BarcodeandPrint Labelactions; Grossing getsPrint Block Labelcomponents/PathologyStateFooterContainer.tsx— handles'scan'(opensCameraScannerDialog) and'print_label'(opens label dialog) action types; falls back to the active store when no explicitspecimenIdprop is given
Mount Pattern
web/src/containers/pathology/laboratory/page.tsx is the reference mount:
<PathologyLaboratoryProvider>
<Stack spacing={2} sx={{ pb: 10 }}>
<Box sx={{ px: 2, pt: 2 }}>
<PathologySpecimenScanner />
</Box>
<PathologyLaboratoryWorklist />
</Stack>
<PathologyStateFooterContainer
nodes={DEFAULT_PATHOLOGY_NODES}
edges={DEFAULT_PATHOLOGY_EDGES}
/>
</PathologyLaboratoryProvider>
The same pattern works in collect-specimens/page.tsx and any other pathology container page. The footer + scanner share the active store, so they coordinate without prop drilling.
Scanner Behaviour
| Source | UX | Default state |
|---|---|---|
| USB wedge | Always listening (mounted ``). Scan with a Honeywell/Datalogic-style scanner — keystrokes are captured globally on the page. | 'USB scanner ready' chip green |
| Camera | Click Scan with camera button → opens CameraScannerDialog (browser BarcodeDetector API; supports QR + Code 128 + Code 39 + EAN-13 + Data Matrix). Falls back to “unsupported” alert if the browser lacks the API. |
Closed by default |
| Manual | Inline TextField + Look up button. Enter key submits. |
Empty |
Cockpit-action scan |
A node’s cockpit action with actionType: 'scan' opens the same camera dialog from inside the footer. |
Disabled until the workflow places the specimen at a node with a scan action |
After a barcode is captured, the lookup runs through the existing search endpoint — no schema changes required. The endpoint already accepts HN, lab number, patient name, or order id (see web/src/services/ever-medication/labRequest.service.ts → getListSpecimenCollectionWithSearch). When the result resolves, setActive() writes the focused specimen into the zustand store.
Lookup Result Behaviour
- Single match: active store updated, footer re-evaluates with new
specimenId+encounterId - Multiple matches: first row is focused; UI shows
+N more matcheschip — host can implement disambiguation - No match: warning alert; no store mutation
- Lookup error: error alert; no store mutation
QR Label
`` is the printable component. Default size targets a 50 × 30 mm thermal label (Brother QL/Zebra). The label’s QR payload is the lab-request _id, so any other scanner in the lab (microscope-side, sign-out station) can re-focus the same specimen by scanning that label.
Print path:
PathologySpecimenLabel.handlePrint()
↓
hidden iframe with @page 50mm 30mm
↓
window.print() → CUPS / IPP queue → thermal printer
For lab-internal label printers connected over a network, the iframe approach is sufficient. For direct serial printers (rare today), wire to a vite-plugin-pwa background worker that POSTs to a local print bridge — out of scope for this commit.
Cockpit Actions
Two new CockpitAction.actionType values:
actionType |
Behaviour | Required fields |
|---|---|---|
scan |
Opens CameraScannerDialog from the footer; result flows through the active store |
none |
print_label |
Opens a rendering with print button |
requires specimenId to be focused |
Both action types are seeded in DEFAULT_PATHOLOGY_NODES (Register, Grossing). To add to a new node type at runtime: edit the node in /admin/pathology-workflow, click Add cockpit action, choose action type. The change persists via workflow_templates and propagates over Supabase realtime to all running tabs.
Backend Schema Caveat
The lab-request schema currently uses _id (Mongo ObjectId) as the canonical identifier. There is no dedicated barcode field — the search endpoint matches against HN, lab number, patient name, or order id. If the lab issues physical barcodes that differ from _id (e.g., a printed accession number prefix), production will need either:
- A
barcodecolumn on the lab-request collection +findByBarcodeendpoint, or - Standardise printed barcodes to use the lab-request
_iddirectly (current default inPathologySpecimenLabel)
The label generator uses option 2 — print the _id so any scan re-focuses correctly without backend changes.
Demo Path
- Open
/pathology/laboratory - Top of page: scanner bar with “USB scanner ready” chip
- Connect a USB wedge (or use a phone with a barcode app over Bluetooth) and scan any specimen barcode containing the lab-request id
- Within ~50ms, scanner shows green success alert with the matched HN/specimen
- Within another ~300ms (Supabase realtime latency), the footer updates: shows the specimen’s canonical state and primary transition button
- Click
Print Label(Register node default) → a label dialog opens →Printopens the print preview
Files
| File | Purpose |
|---|---|
web/src/common/components/medical/builder/pathology-workflow-editor/state/activePathologySpecimenStore.ts |
Active specimen zustand store |
web/src/common/components/medical/builder/pathology-workflow-editor/hooks/useLabRequestByBarcode.ts |
Search-endpoint lookup wrapper |
web/src/common/components/medical/builder/pathology-workflow-editor/components/PathologySpecimenScanner.tsx |
USB+camera+manual scanner bar |
web/src/common/components/medical/builder/pathology-workflow-editor/components/PathologySpecimenLabel.tsx |
Printable QR + barcode label |
web/src/common/components/medical/builder/pathology-workflow-editor/components/PathologyStateFooterContainer.tsx |
Footer container; handles scan + print_label action types |
web/src/containers/pathology/laboratory/page.tsx |
Reference mount point |
web/src/containers/pathology/scanner-test/page.tsx |
Pre-existing scanner test page (unchanged) |
web/packages/diagnostics-kit/src/pathology/laboratory/dialogs/CameraScannerDialog.tsx |
Pre-existing camera scanner dialog (reused) |