medOS ultra

Pathology Scanner & QR Labels

Barcode/QR scanner integration and printable specimen-label generator for the pathology footer.

5 min read diagramsUpdated 2026-05-25docs/pathology/scanner-flow.md

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.tsCockpitAction.actionType now accepts 'scan' and 'print_label'
  • defaults/pathology-default-workflow.ts — Register node gets Scan Barcode and Print Label actions; Grossing gets Print Block Label
  • components/PathologyStateFooterContainer.tsx — handles 'scan' (opens CameraScannerDialog) and 'print_label' (opens label dialog) action types; falls back to the active store when no explicit specimenId prop 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 matches chip — 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:

  1. A barcode column on the lab-request collection + findByBarcode endpoint, or
  2. Standardise printed barcodes to use the lab-request _id directly (current default in PathologySpecimenLabel)

The label generator uses option 2 — print the _id so any scan re-focuses correctly without backend changes.

Demo Path

  1. Open /pathology/laboratory
  2. Top of page: scanner bar with “USB scanner ready” chip
  3. Connect a USB wedge (or use a phone with a barcode app over Bluetooth) and scan any specimen barcode containing the lab-request id
  4. Within ~50ms, scanner shows green success alert with the matched HN/specimen
  5. Within another ~300ms (Supabase realtime latency), the footer updates: shows the specimen’s canonical state and primary transition button
  6. Click Print Label (Register node default) → a label dialog opens → Print opens 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)
Ask Anything