HL7 v2 batch processing
Unpack and build BHS/BTS batches and FHS/FTS files with HL7Batch and HL7File.
High-volume clinical feeds often arrive wrapped - many messages in one envelope rather than one message per delivery, typically as files picked up from an SFTP server or attached to email. The parse_batch and parse_file functions unpack the envelopes into regular parsed messages, and create_batch and create_file build them, with the trailers and counts generated for you.
A batch wraps messages in BHS/BTS segments, and a file wraps batches in FHS/FTS segments:
FHS|... <- File header (optional)
BHS|... <- Batch header
MSH|... <- Message 1
...
MSH|... <- Message 2
...
BTS|2 <- Batch trailer with the message count
FTS|1 <- File trailer with the batch count
Parse a batch
A call to parse_batch reads BHS-wrapped content into an HL7Batch whose messages are regular parsed HL7 messages:
# Zato
from zato.hl7v2 import parse_batch
raw = (
'BHS|^~\\&|SENDING_APP|SENDING_FAC\r'
'MSH|^~\\&|S|F|R|D|20260315||ADT^A01^ADT_A01|M1|P|2.9\r'
'EVN|A01|20260315\r'
'PID|1||111^^^HOSP^MR||SMITH^JOHN\r'
'PV1|1|I\r'
'MSH|^~\\&|S|F|R|D|20260315||ADT^A01^ADT_A01|M2|P|2.9\r'
'EVN|A01|20260315\r'
'PID|1||222^^^HOSP^MR||JONES^MARY\r'
'PV1|1|I\r'
'BTS|2'
)
batch = parse_batch(raw, validate=False)
batch.message_count # 2
for message in batch:
print(message.msh.message_control_id) # 'M1', then 'M2'
first = batch[0] # batches support indexing and len()
The validate flag is passed through to the parsing of each contained message, with the same meaning as in parse_hl7 - see validation.
Parse a file
A call to parse_file reads FHS-wrapped content into an HL7File that contains one or more batches:
# Zato
from zato.hl7v2 import parse_file
hl7_file = parse_file(raw_file, validate=False)
hl7_file.batch_count # how many BHS/BTS batches the file contains
hl7_file.message_count # how many messages there are across all batches
for batch in hl7_file: # iterate batches ..
...
for message in hl7_file.messages: # .. or all messages directly
...
Auto-detection
When the input may be either a batch or a file, parse_batch_or_file reads the first segment and returns the matching type:
# Zato
from zato.hl7v2 import parse_batch_or_file
result = parse_batch_or_file(raw, validate=False) # HL7Batch or HL7File
Input that starts with neither FHS nor BHS raises ValueError - Content must start with FHS or BHS, found: MSH.
Create batches and files
Build a batch from parsed messages with create_batch. The optional dict fills BHS fields, keyed as bhs_3 through bhs_14 - e.g. BHS-3 is the sending application and BHS-4 the sending facility:
# Zato
from zato.hl7v2 import parse_hl7, create_batch, create_file
messages = [parse_hl7(raw1), parse_hl7(raw2)]
batch = create_batch(messages, {'bhs_3': 'MY_APP', 'bhs_4': 'MY_FACILITY'})
er7 = batch.serialize()
# BHS|^~\&|MY_APP|MY_FACILITY||||||||||
# MSH|...
# ...
# BTS|2
The create_file function does the same for files, with FHS fields keyed as fhs_3 through fhs_14:
hl7_file = create_file([batch], {'fhs_3': 'MY_APP'})
er7 = hl7_file.serialize() # FHS|... then the batches, then FTS|1
The BTS and FTS trailers are generated automatically with the correct counts. Messages can also be added to an existing batch with batch.append(message), and batches to a file with hl7_file.append(batch).
Unpack a batch received over MLLP
When a frame arriving on an MLLP channel starts with BHS| or FHS|, the channel routes on the first embedded MSH line and the service receives the whole envelope as one raw string - the details are in receiving over MLLP. One service unpacks either envelope:
# Zato
from zato.server.service import Service
from zato.hl7v2 import parse_batch_or_file
class BatchHandler(Service):
name = 'demo.hl7.batch-handler'
def handle(self) -> 'None':
# The channel delivers the whole envelope as one raw string ..
data:'str' = self.request.input
# .. unpack it into individual, fully parsed messages ..
result = parse_batch_or_file(data, validate=False)
# .. and handle each message separately - both HL7Batch
# .. and HL7File expose all their messages this way.
for message in result.messages:
control_id = message.msh.message_control_id
self.logger.info(f'Processing message: {control_id}')
With the batch above on the wire, the service logs:
See also
| Page | What it covers |
|---|---|
| Receiving over MLLP | How batch frames route and reach the service |
| Parsing and serialization | parse_hl7, serialize and the conversions to dicts and JSON |
| Validation | What the validate flag checks per message |
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