HL7v2 to FHIR - sending bundles to FHIR servers
Post converted bundles to FHIR servers through self.fhir - transaction and batch semantics, reading responses and handling server rejections.
A converted bundle is ready to post without any further work. An outgoing FHIR connection delivers it with the address and credentials configured in the Dashboard, and the server's reply names every resource it created.
Posting a bundle
Transaction and batch bundles are posted to the server's root URL. With an outgoing FHIR connection named FHIR.Sample:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
# ###########################################################################
# ###########################################################################
if 0:
from zato.hl7v2.base import HL7Message
# ###########################################################################
# ###########################################################################
class StoreInFHIR(Service):
name = 'hl7-api.store-in-fhir'
def handle(self) -> 'None':
msg:'HL7Message' = self.request.input
bundle = msg.to_fhir()
client = self.fhir['FHIR.Sample']
response = client.execute('', method='post', data=bundle.to_dict())
self.logger.info('Server response type -> %s', response['type'])
The empty path means the server's base URL - that is where FHIR servers accept bundles. Everything the connection is configured with, such as Basic Auth or OAuth, applies to the request automatically.
What the server sends back
For a transaction, the server replies with a transaction-response bundle - one entry per posted resource, in the same order, each with the HTTP status and the location of the created resource:
for entry in response['entry']:
self.logger.info('%s -> %s',
entry['response']['status'], entry['response']['location'])
The server assigned real IDs and rewrote all the urn:uuid: references between the resources - the stored Encounter's subject now points at Patient/8391.
Transaction or batch
Which bundle type to produce depends on how the receiving side should treat partial failures:
- A transaction is all or nothing. If any entry is invalid, the server rejects the whole bundle and stores nothing - the right choice when the resources only make sense together, a Patient with their Encounter and results.
- A batch processes each entry independently. The response includes a per-entry status, so some entries may succeed while others fail, and your service checks each one:
for entry in response['entry']:
status = entry['response']['status']
if not status.startswith('2'):
self.logger.warning('Entry rejected -> %s', entry['response'])
Handling rejections
When a server rejects a transaction, it responds with an error status and an OperationOutcome resource explaining why, and the FHIR client raises an exception that contains it. A service that must not lose messages catches the exception and routes the message to a retry path:
try:
response = client.execute('', method='post', data=bundle.to_dict())
except Exception as e:
self.logger.warning('FHIR server rejected the bundle -> %s', e)
raise
Raise the exception again - when the service runs behind an MLLP channel, the channel turns it into a negative acknowledgment and the sending system retransmits the message.
Two checks catch most rejections before they happen:
- Validate the bundle's resources against the R4 schema before posting
- Add your local values to the code mappings, so required coded elements carry your real codes instead of safe defaults
See also
| Page | What it covers |
|---|---|
| FHIR connections | Creating the outgoing connection in the Dashboard |
| FHIR security | Basic Auth, OAuth and TLS for the servers you post to |
| MLLP channels | The channel that receives the messages you convert |
| References and deduplication | Why transactions keep the bundle's graph intact on the server |
Learn more
Schedule a meaningful demo
Book a demo with an expert who will help you build meaningful systems that match your ambitions
"We evaluated 12 integration platforms and Zato was the only one to score 100%."
Philip Zuñiga, Assistant Professor, University of the Philippines