13 KiB
title, description, date, order, tags, layout
| title | description | date | order | tags | layout | |||
|---|---|---|---|---|---|---|---|---|
| Edge Workstation: SQLite Database Schema | Database design for the offline-first smart workstation. | 2025-12-19 | 7 |
|
clqms-post.njk |
Overview
This document describes the SQLite database schema for the Edge Workstation — the local "brain" that enables 100% offline operation for lab technicians.
Stack: Node.js (Electron) + SQLite
Role: The "Cortex" — Immediate Processing
📊 Entity Relationship Diagram
┌─────────────┐ ┌──────────────┐
│ orders │────────<│ order_tests │
└─────────────┘ └──────────────┘
│
▼
┌─────────────┐ ┌──────────────┐
│ machines │────────<│ results │
└─────────────┘ └──────────────┘
│
▼
┌─────────────────┐
│ test_dictionary │ (The Translator)
└─────────────────┘
┌───────────────┐ ┌───────────────┐
│ outbox_queue │ │ inbox_queue │
└───────────────┘ └───────────────┘
(Push to Server) (Pull from Server)
┌───────────────┐ ┌───────────────┐
│ sync_log │ │ config │
└───────────────┘ └───────────────┘
🗂️ Table Definitions
1. orders — Cached Patient Orders
Orders downloaded from the Core Server. Keeps the last 7 days for offline processing.
| Column | Type | Description |
|---|---|---|
id |
INTEGER | Primary key (local) |
server_order_id |
TEXT | Original ID from Core Server |
patient_id |
TEXT | Patient identifier |
patient_name |
TEXT | Patient full name |
patient_dob |
DATE | Date of birth |
patient_gender |
TEXT | M, F, or O |
order_date |
DATETIME | When order was created |
priority |
TEXT | stat, routine, urgent |
status |
TEXT | pending, in_progress, completed, cancelled |
barcode |
TEXT | Sample barcode |
synced_at |
DATETIME | Last sync timestamp |
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
server_order_id TEXT UNIQUE NOT NULL,
patient_id TEXT NOT NULL,
patient_name TEXT NOT NULL,
patient_dob DATE,
patient_gender TEXT CHECK(patient_gender IN ('M', 'F', 'O')),
order_date DATETIME NOT NULL,
priority TEXT DEFAULT 'routine' CHECK(priority IN ('stat', 'routine', 'urgent')),
status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'completed', 'cancelled')),
barcode TEXT,
notes TEXT,
synced_at DATETIME DEFAULT CURRENT_TIMESTAMP,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
2. order_tests — Requested Tests per Order
Each order can have multiple tests (CBC, Urinalysis, etc.)
| Column | Type | Description |
|---|---|---|
id |
INTEGER | Primary key |
order_id |
INTEGER | FK to orders |
test_code |
TEXT | Standardized code (e.g., WBC_TOTAL) |
test_name |
TEXT | Display name |
status |
TEXT | pending, processing, completed, failed |
CREATE TABLE order_tests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
test_code TEXT NOT NULL,
test_name TEXT NOT NULL,
status TEXT DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
);
3. results — Machine Output (Normalized)
Results from lab machines, already translated to standard format by The Translator.
| Column | Type | Description |
|---|---|---|
id |
INTEGER | Primary key |
order_test_id |
INTEGER | FK to order_tests |
machine_id |
INTEGER | FK to machines |
test_code |
TEXT | Standardized test code |
value |
REAL | Numeric result |
unit |
TEXT | Standardized unit |
flag |
TEXT | L, N, H, LL, HH, A |
raw_value |
TEXT | Original value from machine |
raw_unit |
TEXT | Original unit from machine |
raw_test_code |
TEXT | Original code before translation |
validated |
BOOLEAN | Has been reviewed by tech? |
CREATE TABLE results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_test_id INTEGER,
machine_id INTEGER,
test_code TEXT NOT NULL,
value REAL NOT NULL,
unit TEXT NOT NULL,
reference_low REAL,
reference_high REAL,
flag TEXT CHECK(flag IN ('L', 'N', 'H', 'LL', 'HH', 'A')),
raw_value TEXT,
raw_unit TEXT,
raw_test_code TEXT,
validated BOOLEAN DEFAULT 0,
validated_by TEXT,
validated_at DATETIME,
machine_timestamp DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (order_test_id) REFERENCES order_tests(id),
FOREIGN KEY (machine_id) REFERENCES machines(id)
);
4. outbox_queue — The Registered Mail 📮
Data waits here until the Core Server sends an ACK (acknowledgment). This is the heart of our zero data loss guarantee.
| Column | Type | Description |
|---|---|---|
id |
INTEGER | Primary key |
event_type |
TEXT | result_created, result_validated, etc. |
payload |
TEXT | JSON data to sync |
target_entity |
TEXT | results, orders, etc. |
priority |
INTEGER | 1 = highest, 10 = lowest |
retry_count |
INTEGER | Number of failed attempts |
status |
TEXT | pending, processing, sent, acked, failed |
acked_at |
DATETIME | When server confirmed receipt |
CREATE TABLE outbox_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_type TEXT NOT NULL,
payload TEXT NOT NULL,
target_entity TEXT,
target_id INTEGER,
priority INTEGER DEFAULT 5,
retry_count INTEGER DEFAULT 0,
max_retries INTEGER DEFAULT 5,
last_error TEXT,
status TEXT DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
sent_at DATETIME,
acked_at DATETIME
);
Flow: Data enters as
pending→ moves tosentwhen transmitted → becomesackedwhen server confirms → deleted after cleanup.
5. inbox_queue — Messages from Server 📥
Incoming orders/updates from Core Server waiting to be processed locally.
CREATE TABLE inbox_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
server_message_id TEXT UNIQUE NOT NULL,
event_type TEXT NOT NULL,
payload TEXT NOT NULL,
status TEXT DEFAULT 'pending',
error_message TEXT,
received_at DATETIME DEFAULT CURRENT_TIMESTAMP,
processed_at DATETIME
);
6. machines — Connected Lab Equipment 🔌
Registry of all connected analyzers.
| Column | Type | Description |
|---|---|---|
id |
INTEGER | Primary key |
name |
TEXT | "Sysmex XN-1000" |
driver_file |
TEXT | "driver-sysmex-xn1000.js" |
connection_type |
TEXT | RS232, TCP, USB, FILE |
connection_config |
TEXT | JSON config |
CREATE TABLE machines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
manufacturer TEXT,
model TEXT,
serial_number TEXT,
driver_file TEXT NOT NULL,
connection_type TEXT CHECK(connection_type IN ('RS232', 'TCP', 'USB', 'FILE')),
connection_config TEXT,
is_active BOOLEAN DEFAULT 1,
last_communication DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Example config:
{
"port": "COM3",
"baudRate": 9600,
"dataBits": 8,
"parity": "none"
}
7. test_dictionary — The Translator 📖
This table solves the "WBC vs Leukocytes" problem. It maps machine-specific codes to our standard codes.
| Column | Type | Description |
|---|---|---|
machine_id |
INTEGER | FK to machines (NULL = universal) |
raw_code |
TEXT | What machine sends: W.B.C, Leukocytes |
standard_code |
TEXT | Our standard: WBC_TOTAL |
unit_conversion_factor |
REAL | Math conversion (e.g., 10 for g/dL → g/L) |
CREATE TABLE test_dictionary (
id INTEGER PRIMARY KEY AUTOINCREMENT,
machine_id INTEGER,
raw_code TEXT NOT NULL,
standard_code TEXT NOT NULL,
standard_name TEXT NOT NULL,
unit_conversion_factor REAL DEFAULT 1.0,
raw_unit TEXT,
standard_unit TEXT,
reference_low REAL,
reference_high REAL,
is_active BOOLEAN DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (machine_id) REFERENCES machines(id),
UNIQUE(machine_id, raw_code)
);
Translation Example:
| Machine | Raw Code | Standard Code | Conversion |
|---|---|---|---|
| Sysmex | WBC |
WBC_TOTAL |
× 1.0 |
| Mindray | Leukocytes |
WBC_TOTAL |
× 1.0 |
| Sysmex | HGB (g/dL) |
HGB (g/L) |
× 10 |
| Universal | W.B.C |
WBC_TOTAL |
× 1.0 |
8. sync_log — Audit Trail 📜
Track all sync activities for debugging and recovery.
CREATE TABLE sync_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
direction TEXT CHECK(direction IN ('push', 'pull')),
event_type TEXT NOT NULL,
entity_type TEXT,
entity_id INTEGER,
server_response_code INTEGER,
success BOOLEAN,
duration_ms INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
9. config — Local Settings ⚙️
Key-value store for workstation-specific settings.
CREATE TABLE config (
key TEXT PRIMARY KEY,
value TEXT,
description TEXT,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Default values:
| Key | Value | Description |
|---|---|---|
workstation_id |
LAB-WS-001 |
Unique identifier |
server_url |
https://api.clqms.com |
Core Server endpoint |
cache_days |
7 |
Days to keep cached orders |
auto_validate |
false |
Auto-validate normal results |
🔄 How the Sync Works
Outbox Pattern (Push)
┌─────────────────┐
│ Lab Result │
│ Generated │
└────────┬────────┘
▼
┌─────────────────┐
│ Save to SQLite │
│ + Outbox │
└────────┬────────┘
▼
┌─────────────────┐ ┌─────────────────┐
│ Send to Server │────>│ Core Server │
└────────┬────────┘ └────────┬────────┘
│ │
│ ◄──── ACK ─────────┘
▼
┌─────────────────┐
│ Mark as 'acked' │
│ in Outbox │
└─────────────────┘
Self-Healing Recovery
If the workstation was offline and missed Redis notifications:
// On startup, ask: "Did I miss anything?"
async function recoverMissedMessages() {
const lastSync = await db.get("SELECT value FROM config WHERE key = 'last_sync'");
const missed = await api.get(`/outbox/pending?since=${lastSync}`);
for (const message of missed) {
await inbox.insert(message);
}
}
📋 Sample Data
Sample Machine Registration
INSERT INTO machines (name, manufacturer, driver_file, connection_type, connection_config)
VALUES ('Sysmex XN-1000', 'Sysmex', 'driver-sysmex-xn1000.js', 'RS232',
'{"port": "COM3", "baudRate": 9600}');
Sample Dictionary Entry
-- Mindray calls WBC "Leukocytes" — we translate it!
INSERT INTO test_dictionary (machine_id, raw_code, standard_code, standard_name, raw_unit, standard_unit)
VALUES (2, 'Leukocytes', 'WBC_TOTAL', 'White Blood Cell Count', 'x10^9/L', '10^3/uL');
Sample Result with Translation
-- Machine sent: { code: "Leukocytes", value: 8.5, unit: "x10^9/L" }
-- After translation:
INSERT INTO results (test_code, value, unit, flag, raw_test_code, raw_value, raw_unit)
VALUES ('WBC_TOTAL', 8.5, '10^3/uL', 'N', 'Leukocytes', '8.5', 'x10^9/L');
🏆 Key Benefits
| Feature | Benefit |
|---|---|
| Offline-First | Lab never stops, even without internet |
| Outbox Queue | Zero data loss guarantee |
| Test Dictionary | Clean, standardized data from any machine |
| Inbox Queue | Never miss orders, even if offline |
| Sync Log | Full audit trail for debugging |
📁 Full SQL Migration
The complete SQL migration file is available at:
📄 docs/examples/edge_workstation.sql