# 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:

```hl7
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 {#parse-a-batch}

A call to `parse_batch` reads BHS-wrapped content into an `HL7Batch` whose messages are regular parsed HL7 messages:

```python
# 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](https://zato.io/docs/dev/healthcare/hl7/v2/validation/index.html).

## Parse a file {#parse-a-file}

A call to `parse_file` reads FHS-wrapped content into an `HL7File` that contains one or more batches:

```python
# 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 {#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:

```python
# 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 {#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:

```python
# 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`:

```python
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 {#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](https://zato.io/docs/dev/healthcare/hl7v2/mllp/index.html#batches-over-mllp). One service unpacks either envelope:

```python
# 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:

```text
INFO - Processing message: M1
INFO - Processing message: M2
```

## See also {#see-also}

- [Receiving over MLLP](https://zato.io/docs/dev/healthcare/hl7v2/mllp/index.html) - How batch frames route and reach the service
- [Parsing and serialization](https://zato.io/docs/dev/healthcare/hl7/v2/parsing/index.html) - parse\_hl7, serialize and the conversions to dicts and JSON
- [Validation](https://zato.io/docs/dev/healthcare/hl7/v2/validation/index.html) - What the validate flag checks per message

## Learn more {#learn-more}

- [Healthcare interface engine](https://zato.io/docs/dev/healthcare/) - Clinical messages, FHIR, conversion and operations
- [HL7 v2 parsing](https://zato.io/docs/dev/healthcare/hl7/v2/parsing/) - Messages as typed objects, with field access and validation
- [MLLP channels](https://zato.io/docs/dev/healthcare/hl7v2/mllp/) - Receiving and sending HL7 v2 over MLLP sockets
- [FHIR integrations](https://zato.io/docs/dev/healthcare/hl7/fhir/) - Read and write FHIR resources, with paths, bundles and extensions
- [HL7 v2 to FHIR](https://zato.io/docs/dev/healthcare/hl7/to-fhir/) - Converting v2 messages into FHIR bundles, codes and references included
- [EDIFACT](https://zato.io/docs/dev/healthcare/edifact/) - Parsing interchanges, dialects and the transports they arrive on
- [Transformation](https://zato.io/docs/dev/healthcare/transformation/) - One message in, another standard out, in ordinary Python
- [Audit log](https://zato.io/docs/dev/healthcare/audit-log.html) - Every message with its acknowledgment, searchable by patient identifier
- [AI in clinical interfaces](https://zato.io/docs/dev/healthcare/ai/) - Services calling LLMs and AI agents calling clinical services as tools
