For years, integrating an instrument with SENAITE meant one protocol: ASTM. That covered most analyser families we ever saw on site. Roche, Sysmex, Abbott, Siemens, Horiba. But hospital labs kept asking the same question:
Does it speak HL7?
Now it does. senaite.astm 2.0 ships with a dedicated HL7 v2 server that listens on MLLP, maps every HL7 segment into the same envelope as ASTM, and feeds it into SENAITE through a single push consumer.
Same transport, same delivery, same audit. Two protocols, one path into the LIMS.
What changed
The 2.0 release is more than just HL7. The library was rewritten around a single message envelope so that ASTM and HL7 share the same downstream pipeline. Everything that used to read ASTM frames now reads either.
Concretely:
- HL7 v2 server. New
senaite-hl7-serverlistens on MLLP on the IANA-registered port 2575 by default. ORU^R01, OUL^R22, OML^O33 — the result-bearing messages — are parsed and forwarded to SENAITE. - ASTM server. Still there, still does what it did. New name:
senaite-astm-server. - Single push consumer. Both servers post to the same SENAITE endpoint. SENAITE does not care which protocol the data started out as.
- Capture-only mode. Run either server against a file destination instead of SENAITE. Useful for integration testing before you wire up production, and for forensics when an instrument starts behaving oddly.
- PHI scrubbing by allowlist. For HL7, you declare which segments and fields you want forwarded. Everything else is dropped before it leaves the listener. Helpful for environments where the LIMS does not need patient identifiers.
/statsendpoint. Each server exposes a Prometheus-friendly stats endpoint: messages received, parsed, forwarded, dropped, rejected. Hook it into your monitoring.- Production hardening. Graceful shutdown, structured logging, reconnect with backoff, configurable connection limits.
Why one envelope
The first version of senaite.astm shipped a parser that produced ASTM-shaped Python dictionaries. SENAITE’s import consumer knew how to read those dictionaries. When we added HL7, the cheap path was a second consumer that knew HL7. We did not take it.
The reason: every additional consumer is a place where the import behaviour can diverge. A SENAITE that imports HL7 ORU results differently from ASTM results is one more matrix of bugs.
So 2.0 normalises both protocols into a single envelope before the data leaves the listener. The envelope carries: sample identifier, analysis identifiers, result values with units, sender metadata, and the raw original message for audit. SENAITE imports the envelope. The protocol is metadata.
How it deploys
The shape is the same as a normal SENAITE-adjacent service: three small processes, low memory, behind a firewall that allows only the instrument network to reach the listener ports.
# When the 2.0 release is published it will be on PyPI; until
# then install from the senaite.astm GitHub repository.
pip install git+https://github.com/senaite/senaite.astm.git@2.x
# ASTM listener on port 4010
senaite-astm-server \
--port 4010 \
--url https://user:pass@your-senaite/
# HL7 v2 over MLLP on port 2575
senaite-hl7-server \
--port 2575 \
--url https://user:pass@your-senaite/
Each server is a long-running daemon. We deploy them under systemd on a dedicated interface, with the listening port firewalled to the instrument subnet.
Capture-only mode
Useful during a deployment when you do not yet trust the LIMS import to do the right thing.
senaite-hl7-server --port 2575 --capture-only ./hl7-capture/
Every received message lands as a timestamped file in
hl7-capture/. No forward, no transformation. Replay against a
staging SENAITE when you are ready.
PHI scrubbing
For HL7, the scrubber is allowlist-based. You declare which segments and which fields inside those segments you want forwarded. Everything else is dropped at the listener.
A representative configuration:
allow:
MSH: [3, 4, 5, 6, 9, 10, 11, 12]
OBR: [1, 2, 3, 4, 7, 22, 24, 25]
OBX: [1, 2, 3, 4, 5, 6, 7, 8, 11, 14, 17]
NTE: [3]
drop_unmatched_segments: true
PID, PV1, GT1, IN1 — the patient-demographic segments — never leave the listener. Useful for laboratories that route specimens without ever needing the patient identifier inside SENAITE itself.
Observability
Each server exposes /stats (and /healthz) on a separate
admin port:
# HELP senaite_astm_messages_total Messages by outcome
# TYPE senaite_astm_messages_total counter
senaite_astm_messages_total{outcome="forwarded"} 14823
senaite_astm_messages_total{outcome="rejected"} 11
senaite_astm_messages_total{outcome="dropped"} 97
senaite_astm_uptime_seconds 432119
Drop those into Prometheus or scrape them with HealthWatch (which ships with SENAITE Care) and you have a clear picture of instrument traffic in real time.
What this enables
Two cases we wrote 2.0 for, specifically.
Hospital labs already on HL7. A clinical chemistry lab with twenty analysers, all configured to emit ORU^R01 to the existing hospital HL7 broker, no longer needs a translation layer to bring them into SENAITE. The HL7 server is the layer.
Hybrid environments. A mining-services lab where the ICP-OES speaks ASTM and the new mass spectrometer speaks HL7. Both servers running on the same host, both feeding the same SENAITE. Operators see one inbox, not two.
What is next
senaite.astm 2.0 is the foundation for a couple of things on the roadmap we are not announcing yet, but the shape is visible: the common envelope makes it possible to add additional inputs (FHIR, custom JSON) without growing a third consumer. Same envelope, same import, same audit.
If you have an instrument integration project on the horizon and want help mapping protocols, talk to us. The HL7 work, like the ASTM transport before it, came out of real engagements with real labs.