hl7apy

repository·develop·Indexed 43 days ago

https://github.com/crs4/hl7apy

A lightweight Python package for parsing, creating, and validating HL7 v2 messages. It supports versions 2.1 through 2.8.2, provides tools for MLLP communication via a built-in server, and allows data access by name, long name, or position. Key features include support for simple and complex datatypes, message profiles, Z-Elements, and serialization to ER7 format.

Tokens
7.1K
Snippets
21
Records
39
Agent score
53%

What's inside hl7apy

  1. Overview of hl7apy features

    develop

    hl7apy is a lightweight Python package designed to handle HL7 v2 messages according to official specifications.

    Key capabilities include:

    • Message Handling: Parsing and creation of HL7 messages.
    • Validation: Message validation following HL7 xsd specifications.
    • Data Access: Accessing elements by name, long name, or position.
    • Data Types: Support for all simple and complex datatypes.
    • Encoding: Customization of encoding characters and encoding messages in ER7 format (compliant with MLLP protocol).
    • Advanced Support: Support for message profiles and Z-Elements.
    • Networking: Simple MLLP server implementation.

    Supported HL7 Versions: 2.1, 2.2, 2.3, 2.3.1, 2.4, 2.5, 2.5.1, 2.6, 2.7, 2.8, 2.8.1, 2.8.2

  2. Work with Z Elements (Z Messages, Segments, and Fields)

    develop

    HL7apy supports custom Z elements:

    • Z Messages: Created by providing a name where both parts of the trigger event start with 'Z' (e.g., ZBE_Z01).
    • Z Segments: Segments with names starting with 'Z' (e.g., ZIN).
    • Z Fields: Fields belonging to a Z segment, named using the segment name and position (e.g., ZIN_1).

    By default, a Z field's datatype is ST. If a value assigned to a Z field contains components (e.g., abc^def), the datatype is automatically converted to None.

    When encoding to ER7, segments and groups are encoded in the order they were created.

    from hl7apy.core import Message, Segment, Field, Group
    
    # Create a Z Message
    m = Message('ZBE_Z01')
    
    # Add standard segments and Z segments
    m.pid = 'PID|1||566-554-3423^^^GHH^MR||EVERYMAN^ADAM^A|||M|||2222 HOME STREET^^ANN ARBOR^MI^^USA||555-555-2004~444-333-222|||M\r'
    m.zin = 'ZIN|aa|bb|cc'
    
    # Add a group
    m.add(Group('ADT_A01_INSURANCE'))
    
    # Encoding order follows creation order
    print(m.to_er7())
    
    # Z Segment and Field usage
    s = Segment('ZIN')
    s.zin_1 = 'abc'
    s.add_field('zin_2')
    z_field = Field('ZIN_3', datatype='CX')
    s.add(z_field)
  3. Use core HL7apy classes to manipulate messages

    develop

    HL7apy provides a hierarchical class structure to represent and manipulate HL7 v2 messages. You can work with the following core classes:

    • Message: The top-level container for an entire HL7 message.
    • Group: A logical grouping of segments or other groups within a message.
    • Segment: A single HL7 segment (e.g., MSH, PID).
    • Field: A specific field within a segment.
    • Component: A component within a field.
    • SubComponent: The smallest unit of data within a component.
    • Element: A generic base class for HL7 elements.

    Most of these classes support methods for adding new elements (add, add_segment, add_group, add_field, add_component, add_subcomponent), validating their structure (validate), and exporting to specific formats like ER7 (to_er7) or MLLP (to_mllp).

  4. Validate HL7 messages with STRICT and TOLERANT modes

    develop

    HL7apy supports two validation levels:

    • STRICT: The message must completely adhere to HL7 structures. It checks child names, cardinality (mandatory vs optional), and value constraints (e.g., date formats or string lengths). In this mode, instantiating unknown elements or violating structure (like adding a second MSH segment) will raise exceptions.
    • TOLERANT: The library does not perform strict structural or constraint checks during instantiation. To check compliance in this mode, you must explicitly call the .validate() method on an element.

    When a message is created using a message profile, validation is performed against that profile instead of the standard.

    from hl7apy.core import Message
    from hl7apy.consts import VALIDATION_LEVEL
    
    # STRICT mode example
    m = Message("ADT_A01", validation_level=VALIDATION_LEVEL.STRICT)
    # This would raise MaxChildLimitReached
    # m.add_segment('MSH') 
    
    # TOLERANT mode example
    m = Message("ADT_A01")
    # Manually trigger validation
    # m.validate() 
    
    # Validate and save a report
    m.validate(report_file='report')
  5. How datatypes and validation work

    develop

    HL7apy supports base and complex datatypes. Datatypes can be applied to Field, Component, and SubComponent (though SubComponent only supports base datatypes).

    • Naming Convention: If a name is specified, it must follow the pattern <complex_datatype>_<position>. If not specified, the name defaults to the datatype name.
    • Validation: The library provides base datatype classes (e.g., ST, NM, DTM) that validate values upon assignment. If a value violates the specification (e.g., exceeding max length or invalid format), an exception is raised.
    • SubComponents with Datatypes: A SubComponent's .value can be an instance of a base datatype.
    • WD Datatype: The WD (Withdrawn) identifier is used to mark fields withdrawn by the specification; presence of these fields causes strict validation to fail.
    from hl7apy.v2_4 import ST, NM, DTM
    
    # Validation examples
    s = ST('some information')
    n = NM(111)
    d = DTM('20131010')
    
    # SubComponent with a base datatype value
    s_sub = SubComponent(datatype="FT")
    s_sub.value = ST('some information')
  6. Implement an MLLP Server

    develop

    To handle incoming HL7 messages over MLLP, use the hl7apy.mllp module. You must implement handlers for different message types and register them in a handlers dictionary.

    • AbstractHandler: Subclass this to define how to process an incoming message. Implement the reply() method, which should return the response message (e.g., an RSP message) converted via .to_mllp().
    • AbstractErrorHandler: Subclass this to handle exceptions (like UnsupportedMessageType) using the .exc attribute.

    Handlers can also accept custom arguments passed via the handlers dictionary.

    from hl7apy.parser import parse_message
    from hl7apy.mllp import AbstractHandler, MLLPServer, AbstractErrorHandler, UnsupportedMessageType
    from hl7apy.core import Message
    
    # 1. Define a handler for a specific message type
    class PDQHandler(AbstractHandler):
        def __init__(self, msg, database_name):
            super().__init__(msg)
            self.database_name = database_name
    
        def reply(self):
            msg = parse_message(self.incoming_message)
            # ... process message using self.database_name ...
            res = Message('RSP_K21')
            return res.to_mllp()
    
    # 2. Define an error handler
    class ErrorHandler(AbstractErrorHandler):
        def reply(self):
            if isinstance(self.exc, UnsupportedMessageType):
                # Custom response for unsupported types
                pass
    
    # 3. Register handlers and start server
    handlers = {
        'QBP^Q22^QBP_Q21': (PDQHandler, 'demographic_db'),
        'ERR': (ErrorHandler,)
    }
    
    server = MLLPServer('localhost', 2575, handlers)
  7. Explore HL7apy usage examples

    develop

    To learn how to use HL7apy, you can follow two paths:

    1. Basic Usage: Refer to the official Tutorial for foundational concepts and getting started.
    2. Advanced Usage: For complex implementation patterns, inspect the examples/ subdirectory located within the HL7apy source code repository.
  8. Use Message Profiles with hl7apy

    develop

    You can use custom message profiles instead of standard HL7 structures.

    1. Generate the profile file: Use the hl7apy_profile_parser utility script with an XML static definition of your profile.
    2. Load the profile: Use load_message_profile to create a profile object.
    3. Apply the profile: Pass the profile object to the reference argument when instantiating a Message or to the message_profile argument when calling parse_message.

    Important: When using a profile, you must populate the message using traversal or specific Element methods (like add_group, add_segment, or add_field) rather than the generic add() method. This ensures the library uses the profile's specific structure instead of the official HL7 structure.

    # Create the profile file from an XML definition
    python hl7apy_profile_parser message_profile.xml -o $HOME/message_profile
    from hl7apy import load_message_profile, parse_message
    from hl7apy.core import Message
    
    # Load the profile
    mp = load_message_profile('$HOME/message_profile')
    
    # Use it for a new message
    m1 = Message('RSP_K21', reference=mp)
    
    # Use it for parsing
    m2 = parse_message(er7_str, message_profile=mp)
    
    # Correct way to populate to respect profile structure:
    m.pid.pid_3.cwe_1 = 'aaa'  # via traversal
    pid_3 = m.pid.add_field('PID_3')  # via specific method
  9. Handle HL7 v2.7 delimiter in MSH.2

    develop

    HL7 v2.7 introduced an optional delimiter # in the MSH.2 field. By default, hl7apy includes this delimiter when creating a version 2.7 (or newer) message.

    If you want to exclude the delimiter and only include the standard encoding characters, pass encoding_chars=DEFAULT_ENCODING_CHARS when instantiating the Message.

    # Default behavior for v2.7 includes the '#' delimiter
    >>> m = Message('ADT_A01', version='2.7')
    >>> print(m.to_er7())
    'MSH|^~\&#|||||20181024144452|||||2.7'
    
    # To exclude the delimiter
    >>> from hl7apy import DEFAULT_ENCODING_CHARS
    >>> m = Message('ADT_A01', version='2.7', encoding_chars=DEFAULT_ENCODING_CHARS)
    >>> print(m.to_er7())
    'MSH|^~\&|||||20181024144452|||||2.7'
  10. Manipulate HL7 elements and traverse trees

    develop

    You can navigate the HL7 hierarchy using several methods:

    • Traversal Styles: Access children by name (s.pid_5), long name (s.patient_name), or position (s.pid_5.pid_5_1). Traversal is case-insensitive.
    • Handling Repetitions: By default, accessing a child returns the first instance. To access subsequent instances, use index notation: s.pid_13[1].
    • Iterating:
      • Use .children to iterate over all direct children of an element.
      • Iterating over a named child (e.g., for spm in m.spm:) will yield all repetitions of that child.
    • Deletion: Use the del keyword to remove a child (e.g., del m.MSA or del m.spm[1].spm_1).
    • Missing Elements: During traversal, accessing a valid but uncreated child returns an empty list []. Accessing a child that does not exist in the specification raises an exception.
    # Accessing by name, long name, or position
    s.pid_5
    s.patient_name
    s.pid_5.pid_5_1
    
    # Accessing repetitions
    s.pid_13[1]
    
    # Iterating
    for child in m.children:
        pass
    
    for spm in m.spm:
        pass
    
    # Deleting
    del m.MSA
  11. Install hl7apy via pip or setup.py

    develop

    You can install hl7apy using pip from PyPI, or by downloading the latest release from GitHub and running the setup.py script. The package is platform-independent and supports Python 2.7 and Python 3.[4-12].

    # Using pip
    pip install hl7apy
    
    # Using setup.py
    python setup.py install