End‑to‑end specification for a Mock HL7 Analyzer with a Vite + React + TypeScript frontend and a Node.js (Express) TypeScript backend. The system simulates a lab analyzer that accepts HL7 v2 orders (OML/ORM) over MLLP, manages a worklist, generates results, and emits ORU/OUL back to CARE. Includes REST APIs, WebSocket feeds, TCP endpoints, UI pages, data contracts, and operational considerations.
-
Frontend: Vite + React + TS, shadcn/ui, React Router, Zustand (or Redux Toolkit) for state, WebSocket for live logs, Axios for REST.
-
Backend: Node 20 + TypeScript; Express for REST; ws for WebSocket; custom MLLP server/client; Prisma ORM (SQLite dev, Postgres prod); Zod for schema validation; pino for logs; Prometheus metrics.
-
Services:
- MLLP IN: listens for OML/ORM; ACKs; persists work.
- Scheduler/Engine: transitions queued→processing→prelim→final; generates values.
- MLLP OUT: sends ORU/OUL to configured endpoints; handles ACK/Retry.
- REST & WS: configuration, worklist, scenarios, logs, stats.
| Purpose | Port | Protocol |
|---|---|---|
| Web UI + REST API | 8080 | HTTP/HTTPS (Express) |
| Live Logs / Events | 8080/ws | WebSocket (JSON messages) |
| MLLP IN (orders) | 2575 | TCP (MLLP) / optional TLS on 2576 |
| MLLP OUT (results) | 2577 | TCP (MLLP client to CARE) / optional TLS on 2578 |
MLLP framing: 0x0B … 0x1C 0x0D.
.env
NODE_ENV=development
PORT_HTTP=8080
PORT_WS=8080
PORT_MLLP_IN=2575
CARE_MLLP_HOST=care-middleware
CARE_MLLP_PORT=2577
MLLP_TLS=false
ACK_TIMEOUT_MS=5000
RETRY_MAX=5
RETRY_BACKOFF_MS=2000
PRELIM_ENABLED=true
DUR_QUEUE_TO_START_MS=3000
DUR_ANALYSIS_MS=5000
DUR_PRELIM_TO_FINAL_MS=4000
DB_URL=file:./dev.db
AUTH_MODE=apikey
API_KEY=change-me
Patient(id, mrn, name, birthDate, sex)
Specimen(id, accession, type, collectedAt, patientId)
WorkItem(id, specimenId, testCode, loinc, name, status, startedAt, finishedAt, channel, scenarioId)
Result(id, workItemId, value, unit, refLow, refHigh, flag, method, deviceId, status, effectiveAt)
Endpoint(id, name, host, port, tls, mode, ackRequired, lastAckLatencyMs)
CatalogTest(id, deviceCode, loinc, name, unit, refLow, refHigh, dtype, genMin, genMax, dist, abnormalProb)
MessageLog(id, direction, type, raw, parsedJson, sha256, status, correlationKey, createdAt)
Scenario(id, name, stepsJson, createdAt)
User(id, email, role)
status enums: WorkItem: queued|in_process|preliminary|final|corrected. Result: prelim|final|corrected.
-
GET /api/config → Current configuration
-
PUT /api/config → Update config
{ "resultsEndpoint": {"host":"care-mw","port":2577,"tls":false,"mode":"ORU"}, "ackTimeoutMs":5000, "retry": {"max":5,"backoffMs":2000}, "prelimEnabled": true, "durations": {"queueToStartMs":3000, "analysisMs":5000, "prelimToFinalMs":4000} }
- GET /api/catalog/tests → list
- PUT /api/catalog/tests → bulk upsert array of tests
- PUT /api/catalog/mappings/loinc → upsert device↔LOINC
- PUT /api/catalog/mappings/units → unit normalization map
-
POST /api/orders/hl7 → Ingest raw HL7 order (bypass MLLP)
{"hl7":"<raw HL7 string>"} -
GET /api/worklist?state=queued|in_process|final|*"
-
POST /api/worklist/{id}/start → move to in_process
-
POST /api/worklist/{id}/prelim → mark prelim (optional)
-
POST /api/worklist/{id}/finalize → finalize with optional value override
{"value":"6.8","unit":"mmol/L","flag":"H","note":"manual override"}
-
POST /api/results/emit → emit final results as ORU/OUL
{"workItemIds":["wi_123","wi_456"], "mode":"ORU", "splitPanels":true} -
POST /api/results/correct → send corrected result (OBX-11=C)
{"observationId":"obs_1","newValue":"7.1","reason":"delta check"}
-
POST /api/scenarios → define & run scenario
{ "name":"lost-ack-then-retry", "steps":[ {"action":"ingest-hl7","template":"oml_o21","vars":{ "SR":"SR-001", "ACC":"ACC-789", "TEST":"2345-7"}}, {"action":"set-failure","type":"drop-ack","count":1}, {"action":"advance","state":"final","afterMs":4000}, {"action":"emit-results"} ] } -
POST /api/failures → inject transient failures
{"type":"bad-unit"}
- GET /api/messages?direction=in|out&type=ORM,OML,ORU,OUL,ACK&limit=100
- GET /api/messages/{id} → raw + parsed + ack chain
- GET /api/stats → throughput, ack latency, errors, queue depth
- Event types:
mllp_in,mllp_out,ack,state_change,error,metric. - Payload example:
{"type":"mllp_out","ts":"2025-10-18T12:34:56Z","messageType":"ORU^R01","msgId":"MSG123","correlationKey":"MSG123.SR-001.ACC-789"}- Listens on
PORT_MLLP_IN(2575). Accepts OML^O21 / ORM^O01. - Parses
MSH, PID, ORC, OBR, [SPM]→ creates Patient (if needed), Specimen, WorkItems. - Returns ACK (
MSA|AA) orAE/ARon parse error. - Idempotency:
{MSH-10}.{ORC-2}.{ORC-3}.
- Cron/timers move items through states using
DUR_*config. - Deterministic generator by seed + test distribution; optional random noise.
- Composes ORU^R01 (default) or OUL^R22 (LAW mode).
- Handles ACK wait (ACK_TIMEOUT_MS), retry with backoff up to RETRY_MAX.
- Dead-letter queue on exhausted retries; surfaces via
/api/statsand WS.
MSH|^~\&|CARE|CAREHOSP|MOCKDEV|ANALYZER|{DTM}||OML^O21|{MSGID}|P|2.5.1
PID|1||{MRN}^^^CARE^MR||{LAST}^{FIRST}||{DOB}|{SEX}
ORC|NW|{PLACER}|{FILLER}
OBR|1|{PLACER}|{FILLER}|{TEST}^{NAME}^{LN}
SPM|1|{ACCESSION}|||{COLLDT}
MSH|^~\&|MOCKDEV|LAB|CARE|HOSP|{DTM}||ORU^R01|{MSGID}|P|2.5.1
PID|1||{MRN}^^^CARE^MR||{LAST}^{FIRST}||{DOB}|{SEX}
ORC|RE|{PLACER}|{FILLER}
OBR|1|{PLACER}|{FILLER}|{TEST}^{NAME}^{LN}||||||||||||||||||F
SPM|1|{ACCESSION}
OBX|1|NM|{TEST}^{NAME}^{LN}||{VALUE}|{UCUM}^{UCUM}^UCUM|{LOW}-{HIGH}|{FLAG}|F|||{EFFT}|||{METHOD}|{DEVICEID}
- / Dashboard: summary metrics, connection status, queue depth, ACK latency.
- /worklist Worklist: table (Accession, Patient, Test, Status, Value, Actions).
- /analyzer Analyzer view: channels, sliders (throughput, error prob), start/finalize.
- /mappings Test catalog & code↔LOINC mappings editor.
- /endpoints Configure outbound endpoints, ACK policy, TLS; test connection.
- /messages Message log: live stream + filters; detail drawer (raw HL7 | parsed JSON | ACK chain).
- /scenarios Scenario runner & failure injection.
- /settings App settings, API key management, PII redaction toggle.
StatusBar(MLLP lights, mode switch, metrics)WorklistTable(virtualized); row actions: Start, Prelim, Finalize, Emit, CorrectMessageViewer(syntax-highlight HL7, JSON pane)EndpointForm,MappingGrid,ScenarioBuilderMetricsCards(Prometheus snapshots via REST)
- Zustand slices:
config,worklist,messages,scenarios,metrics. - WebSocket listener → dispatches live events to stores.
- Axios instance with API key and retry; Zod schemas for responses.
- Happy path: Order arrives → appears in Worklist (queued) → auto start → prelim/final → Emit → ACK OK.
- Correction: Select final row → Correct → input new value → emit corrected ORU (OBX-11=C).
- Failure test: Run scenario → UI shows dropped ACK, retries, eventual success or DLQ.
- REST: API key or JWT; CORS locked to expected origins.
- Optional mTLS for MLLP.
- Roles:
operator,engineer,admin(feature gating in UI and REST guards). - PII masking in logs (configurable); raw HL7 accessible to
engineer/adminonly.
- /api/stats for UI cards; /metrics Prometheus:
mllp_in_total,mllp_out_total,ack_latency_seconds,order_queue_depth,retries_total,dlq_total. - Pino structured logs (JSON); request/response IDs; correlation keys included.
docker-compose.yml (excerpt)
services:
analyzer:
build: ./server
env_file: .env
ports:
- "8080:8080"
- "2575:2575"
depends_on: [db]
web:
build: ./web
environment:
- VITE_API_BASE=http://localhost:8080
ports:
- "5173:5173"
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD=dev
POSTGRES_DB=mock_analyzer
ports:
- "5432:5432"
- Unit: HL7 parser/serializer, value generator, MLLP framing.
- Integration: send OML via MLLP; assert ACK; transition to final; emit ORU; assert CARE ACK (use a mock MLLP server in CI).
- E2E: Cypress/Playwright for UI flows; record/replay HL7 samples; scenario runs.
- Conformance: validate messages with HL7 v2 profiles (z-schema/Zod for fields; optional NIST tooling offline).
export type WorkItemDTO = {
id: string;
accession: string;
patient: { mrn: string; name: string };
test: { deviceCode: string; loinc?: string; name: string };
status: 'queued'|'in_process'|'preliminary'|'final'|'corrected';
value?: string; unit?: string; flag?: 'L'|'N'|'H';
startedAt?: string; finishedAt?: string;
};export type ConfigDTO = {
resultsEndpoint: { host: string; port: number; tls: boolean; mode: 'ORU'|'OUL' };
ackTimeoutMs: number;
retry: { max: number; backoffMs: number };
prelimEnabled: boolean;
durations: { queueToStartMs: number; analysisMs: number; prelimToFinalMs: number };
};- Receive OML/ORM on 2575; respond with ACK; order visible in Worklist within 1s.
- Auto-process to final within configured durations; REST reflects state transitions.
- Emit ORU to configured CARE endpoint; receive ACK within
ACK_TIMEOUT_MS; retries on failure. - Message logs store raw HL7 + parsed JSON + correlation keys; searchable via REST & visible in UI.
- WebSocket broadcasts events for UI real-time updates.
- Security: API key enforced for all REST write ops; PII masking toggle works.