lumnik. / scénarios Tous les scénarios · English

HL7 v2: le résultat d'analyseur hors norme, signalé ou non

Objectif Lire les messages HL7 v2 que deux analyseurs déposent pour le LIMS, et être prévenu quand un résultat dépasse sa norme.

Deux analyseurs de chimie, HL7 v2.5.1 ORU^R01: des segments terminés par un retour chariot seul.

segment
MSH|^~\&|CHEM-01|QC-LAB|LIMS|QC|20261105090700||ORU^R01|CHEM-01-1001|P|2.5.1\r
segment
PID|1||S-2026-1111^^^QC||LOT 26-0451\r
segment
OBR|1||S-2026-1111|CHEM^Chemistry panel|||20261105090700\r
segment
OBX|1|NM|GLU^Glucose||5.25|mmol/L|3.9-6.1|N|||F|||20261105090700\r
segment
OBX|2|NM|LAC^Lactate||1.43|mmol/L|0.5-2.2|N|||F|||20261105090700\r
segment
OBX|3|NM|CREA^Creatinine||82.4|umol/L|45-110|N|||F|||20261105090700\r
segment
OBX|4|NM|K^Potassium||4.17|mmol/L|3.5-5.1|N|||F|||20261105090700\r

preuves: replay.log, message-1.txt, message-rows.txt, count-read.txt, out-of-range.txt, inbox-batch.json · transform v1

manifests/10-analyser-results.yaml
apiVersion: connectors.lumnik.io/v1kind: Connectormetadata:  name: scenario-hl7-a  connector_type: csv-file  scopes: [qc-chem-a]spec:  transport:    kind: local    path: /tmp/lumnik-scenario/hl7-analyser-results/a/*.hl7   # the folder the analysers drop their batches in  parser:    lines: true                    # one segment per row; HL7's lone carriage return ends a segment  after_process: none  mapping:    cell:      - regex-extract: { column: line, pattern: "^([A-Z][A-Z0-9]{2})\\|", groups: { 1: segment } }      # MSH: the analyser (MSH-3) and the message's control id (MSH-10)      - regex-extract: { column: line, pattern: "^MSH\\|[^|]*\\|([^|]*)\\|(?:[^|]*\\|){6}([^|]*)\\|", groups: { 1: analyser, 2: message_id } }      # PID: the sample (PID-3, first component) and the lot (PID-5)      - regex-extract: { column: line, pattern: "^PID\\|[^|]*\\|\\|([^^|]*)[^|]*\\|\\|([^|]*)", groups: { 1: sample, 2: lot } }      # OBX: the test, the value, the unit, the reference range, the flag, the time of observation      - regex-extract:          column: line          pattern: "^OBX\\|(\\d+)\\|NM\\|([^^|]*)\\^([^|]*)\\|\\|([^|]*)\\|([^|]*)\\|([^-|]*)-([^|]*)\\|([^|]*)\\|(?:[^|]*\\|){5}(\\d{14})"          groups: { 1: obx_seq, 2: test_code, 3: test_name, 4: value, 5: unit, 6: range_low, 7: range_high, 8: flag, 9: observed_at }      # the message's analyser and sample, on every segment of the message      - fill-down: { columns: [analyser, message_id, sample, lot], reset-when: { column: segment, in: [MSH] } }      - parse-number: { column: value }      - parse-number: { column: range_low }      - parse-number: { column: range_high }      - parse-date: { column: observed_at, formats: ["yyyyMMddHHmmss"] }
manifests/20-above-range.yaml
apiVersion: apps.lumnik.io/v1kind: Processmetadata:  name: scenario-hl7-above-range-aspec:  scope: qc-chem-a  on: { event: data.row.changed, table: connector.t_scenario_hl7_a }  when:    - segment=OBX    - "value>{range_high}"           # above the range the analyser sent with the result, whatever its flag  emit:    type: decision.above-range-a    message: "{sample} ({lot}) {test_name}: {value} {unit}, above {range_low}-{range_high} on {analyser}, flag '{flag}'"    key: sample
$ lm connector run scenario-hl7-a
run scheduled: 2b726035-44f7-4408-87a8-b0f7e0a3b8c9

$ lm run get 2b726035-44f7-4408-87a8-b0f7e0a3b8c9
ID        CONNECTOR       ENDPOINT  STATUS     IN  OUT  SKIPPED  DEAD-LETTERS  STARTED           ENDED             EVALUATED
2b726035  scenario-hl7-a  default   Completed  56  56   0        0             2026-11-06 16:57  2026-11-06 16:57  yes
• 1 file(s) unchanged since they were last read — not read again

$ lm inbox --process scenario-hl7-above-range-a
ID     WHEN      PROCESS                     MESSAGE                                                                             ENTITY_KEY
24534  just now  scenario-hl7-above-range-a  S-2026-1126 (LOT 26-0456) Creatinine: 162 umol/L, above 45-110 on CHEM-02, flag ''  S-2026-1126
24533  just now  scenario-hl7-above-range-a  S-2026-1123 (LOT 26-0453) Lactate: 3.1 mmol/L, above 0.5-2.2 on CHEM-01, flag 'H'   S-2026-1123

Limites

  • La table garde chaque segment: une View ou une règle lit segment=OBX. Aucun hook ne retire une ligne après fill-down.
  • Les conditions se combinent en ET: sous la norme, c'est un second Process.
  • Seuls les résultats NM sont lus; un OBX texte (ST) ou codé (CE) garde sa ligne sans colonnes.
  • Des fichiers, pas MLLP: les analyseurs écrivent dans un dossier, le hub n'écoute pas de socket.
  • Échantillons, lots et résultats synthétiques.

Laboratoire · HL7 v2

Les analyseurs parlent HL7 v2.

Un message par échantillon, un segment par ligne.

ORU^R01, MSH · PID · OBR · OBX

Le problème

Un analyseur n'envoie aucun drapeau.

  • Sa créatinine à 162 µmol/L passe inaperçue.
  • La norme était pourtant dans le message.

Ce qui n'est pas modifié

Les analyseurs restent tels quels.

  • Leurs fichiers HL7, dans leur dossier.
  • Aucun moteur d'interface, aucun code.

Ce qu'il a fallu écrire

fill-down: { columns: [sample], reset-when: MSH }
when: [segment=OBX, "value>{range_high}"]

Un regex par segment, une condition.

  • csv-file: les segments en lignes.
  • Un Process: la norme, pas le drapeau.

Le résultat

Deux décisions, dont une sans drapeau.

  • Lactate 3,1, signalé H.
  • Créatinine 162, drapeau vide.

Essayer

Le scénario se rejoue en local.

Chaque étape est publiée avec sa sortie.

Ce scénario tourne sur lumnik Open, gratuit et open source.

Essayer lumnik Open →