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:

INFO - Processing message: M1
INFO - Processing message: M2

See also

PageWhat it covers
Receiving over MLLPHow batch frames route and reach the service
Parsing and serializationparse_hl7, serialize and the conversions to dicts and JSON
ValidationWhat 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