Send a lab result as v2 XML over HTTPS
Some results services take HL7 v2 in the XML encoding over HTTPS rather than the pipe-delimited form over MLLP, and answer each POST, on the same connection, with an XML acknowledgement. This page shows a channel that writes the document, presents a client certificate, and settles each delivery by the answer, with the v2 XML example as the project to copy, and says plainly what we have tested and what we have not. We have run nothing against the animal diagnostic lab network or any other real results service.
Write, send and settle
The example's channel, vet-results-to-xml, does four things. Its files are on
the example's page.
-
Take the result over MLLP. An MLLP source stores each message, then acknowledges it. The channel's
format: hl7v2hands the transform the message parsed by@cosyte/hl7. -
Write it as v2 XML. The transform filters out every message that is not an
ORU^R01and returnstoHl7Xml(msg, { dataTypes, groups })from@cosyte/hl7. Two things the writer cannot know from the message, so the transform gives them:- The data types. The XML encoding names each component for its field's data type (
PID.5is anXPN, so the family name isXPN.1), and a pipe-delimited message does not carry its types. The transform holds a table of the HL7 v2.5 type of each field the example's results fill that has components. A field with more than one component and no type in the table is refused withDATA_TYPE_UNKNOWN, so the message fails rather than go out in a shape the receiver cannot read. A field with one value and no type in the table is written as text; check the table against your receiver's schema, or read the types from a sample document withxmlDataTypes. - The groups. The encoding places the segments in the message structure's groups
(
ORU_R01.PATIENT_RESULT,ORU_R01.ORDER_OBSERVATIONand the rest), and their names come from each version's published schema.@cosyte/hl7ships the table forORU_R01in v2.5, the example's results, inXML_GROUP_TABLES. For a structure or version it has no table for, pass the table your receiver's schema gives asgroups, the way the example's stand-in passes theACKstructure it writes its answers in. The transform fails a result of a version with no table rather than guess.
The document goes in the
urn:hl7-org:v2xmlnamespace. A receiver that asks for none gets it withnamespace: null. - The data types. The XML encoding names each component for its field's data type (
-
POST it with a client certificate. An HTTP destination sends the document as
application/xmlto anhttps://URL, withtls: { ca, cert, key }: the CA the service's certificate is signed by, and the client certificate and key the channel presents. The service's certificate is always verified. -
Settle by the acknowledgement. With
ack: xml, the destination reads each 2xx answer'sMSA.1andMSA.2:The answer The delivery AAorCAdelivered AEorCEretried, coded ACK_AEorACK_CE; later results waitARorCRfailed for good, kept for replay; the destination moves on an MSA.2that is not theMSH.10sentretried, coded ACK_CONTROL_ID_MISMATCHnot one acknowledgement in the one shape that is read retried, coded ACK_UNREADABLEBefore its first POST, the destination reads the document it is about to send for its one
MSH.10. A document it cannot read (not well-formed, not UTF-8, or with a document type declaration or an entity) is not sent and fails at once with the reasonSENT_XML_UNREADABLE; one without exactly oneMSHholding oneMSH.10is not sent and fails at once with the reasonSENT_XML_NO_CONTROL_ID. The writer always gives oneMSHwith the result'sMSH.10, as long as the result has one. The full rules are in Settle by an HL7 v2 XML acknowledgement.
What we test
CI runs these on every change to Pathways, on synthetic messages only:
pathways teston the example: each of its four lab results gives the XML document in its expected file, byte for byte.- A real pass, as the example's page walks through it: a
running engine takes the four results over MLLP and acknowledges each, and POSTs them over HTTPS,
with the client certificate, to the stand-in, which answers
AA,AEthenAA,AR, and anAAto another control id thenAA. The test checks that the first is delivered, the second is retried withACK_AEand then delivered, the third fails with the reasonAR, and the fourth is retried withACK_CONTROL_ID_MISMATCHand then delivered; that the stand-in got six POSTs, each the expected document of its result; and thatpathways statuscounts three delivered and one failed. - The stand-in refuses a connection that presents no client certificate.
- Each document reads back with
parseHL7Xmlto the message it was written from. - The transform fails a result whose HL7 version has no group table, and one that fills a field with components whose data type its table does not hold.
- The test messages are the ones
@cosyte/synthgenerates for the seeds the example's README names, so they can be made again. - The
ack: xmlsetting itself, in the HTTP destination's own tests: every code, the control ID check, unreadable answers, the two kinds of document that are never sent, and mutual TLS.
What we do not test
- Nothing against the animal diagnostic lab network. We have not onboarded with the US National Animal Health Laboratory Network or sent anything to its test or production services, whose onboarding needs accounts, a lab OID and certificates only a lab holds. The stand-in answers with whatever codes it is given; it is not the network's service and does not check what the network checks.
- Not the network's own message. The network's public documents describe its results message as
OPU_R25in the HL7 v2.6 XML encoding, answered byACK_R25(see For the animal diagnostic lab network). This example writesORU_R01in v2.5, the structure@cosyte/hl7ships groups for. Building anOPU_R25, its groups and its data types is for your transform, from the network's own schema. - No schema validation. Nothing checks the document against a published XML schema. The data types in the transform's table are the v2.5 types of the fields these synthetic results fill, and a real lab's results will fill others.
- No real lab. The messages are made by
@cosyte/synth, with species and breed codes from its local list. A real lab system's messages will differ in fields, codes and quirks. - Development certificates only. The certificates come from
opensslon the same machine and expire in 30 days. A real service issues or names its own, and its client key belongs in a secret store. - POST only. The destination always POSTs. A service that wants another method, or credentials in headers, needs those confirmed with its operator.
Related
- Example: a veterinary lab result as v2 XML over HTTPS, the project to copy.
- Send and receive HTTP: the
ack: xmlsetting in full. - Route a veterinary lab result: the same kind of result sent on as FHIR.
- Secure endpoints: TLS and client certificates.