.. _hl7: ============== Health Level 7 ============== HL7 stands for Health Level Seven, which is a standard for exchanging health information between medical applications. This standard defines a format for the transmission of health-related information, such as patient records, lab results, billing data, etc. Information sent using the HL7 standard is sent as a collection of one or more messages, each containing segments, fields, and sub-fields that have predefined meanings and formats. .. glossary:: HL7 Health Level Seven, which is a standard for exchanging health information between medical applications. .. seealso:: `Hl7 Summary `_ ADT ADT stands for Admission, Discharge and Transfer, which are some of the common events that trigger HL7 messages. ADT messages are used to communicate basic patient information, visit information and patient state at a healthcare facility. ADT-A01 Patient Admit/Visit Notification ADT-A02 Patient Transfer Notification ADT-A03 Patient Discharge/End Visit Notification ADT-A04 Patient Registration ADT-A05 Patient Pre-Admission ADT-A06 Change an outpatient to an inpatient ADT-A07 Change an inpatient to an outpatient ADT-A08 Patient Information Update ADT-A09 Tracks a patient's movement within a facility. ADT-A10 Tracks the arrival of a patient at a facility. ADT-A11 Cancel Patient Admit Notification ADT-A12 Cancel Patient Transfer Notification ADT-A13 Cancel Patient Discharge/End Visit Notification ADT-A14 Pending admission of a patient to a facility. ADT-A15 Pending transfer of a patient. ADT-A16 Pending discharge of a patient. ADT-A40 Merge patient record. ADT-A42 Merge patient visit. MRN Medical reference number HL7 Integration ---------------- MEG provides an API for integrating with HL7 data sources. :class:`~hl7_integration.models.HL7Config` defines an hl7 interface for an :class:`~megforms.models.AuditForm`. HL7 messages can be POSTed to ``/hl7/api/v1/message`` .. code-block:: shell curl -X POST localhost:8000/hl7/api/v1/message -H "Content-Type: application/json" -H "Authorization: Token 432079ad-4c89-478e-bccc-50500d8b1919" -d '{"message": "MSH|^~\\\\&|Company EHR|Company ABC|HIE Application|State HIE|20230704102554||ADT^A01^ADT_A01|1234567890|P|2.8.2\\rPID|1||123456789^^^MRN||Smith^John^A||19800101|M||2106-3^White^HL70005|123 Main St^^Anytown^NY^12345||(555)555-5555\\rPV1|1|I|ICU^A1^B1||||1111111111^Jones^Bob^M^^Dr.^MD^^NPI\\rZxx|1||Some custom data"}' If the authentication token ``432079ad-4c89-478e-bccc-50500d8b1919`` matches a :class:`~hl7_integration.models.HL7Config` the message data will be parsed and saved as an observation. .. note:: The HL7 API acquires a lock on the :class:`~megforms.models.AuditForm`. This is to prevent race conditions. So no other request can modify the form while the request is being processed. Complex integrations ^^^^^^^^^^^^^^^^^^^^^ If an integration involves more than one form, or message type, it is advisable to use :class:`~hl7_integration.models.HL7EntryPoint`. It can route messages to many :class:`~hl7_integration.models.HL7Config` from a single url endpoint. This means the mirth connect channel can be much simpler while all the complex message routing can be configured in MEG. The url contains the slug of the entrypoint model, providing improved traceability. .. code-block:: shell curl -X POST localhost:8000/hl7/api/v1/entrypoint/test-integration -H "Content-Type: application/json" -H "Authorization: Token 432079ad-4c89-478e-bccc-50500d8b1919" -d '{"message": "MSH|^~\\\\&|Company EHR|Company ABC|HIE Application|State HIE|20230704102554||ADT^A01^ADT_A01|1234567890|P|2.8.2\\rPID|1||123456789^^^MRN||Smith^John^A||19800101|M||2106-3^White^HL70005|123 Main St^^Anytown^NY^12345||(555)555-5555\\rPV1|1|I|ICU^A1^B1||||1111111111^Jones^Bob^M^^Dr.^MD^^NPI\\rZxx|1||Some custom data"}' Message Parsing ^^^^^^^^^^^^^^^ The :class:`~hl7_integration.models.HL7FieldMapping` model maps a :class:`~audit_builder.models.CustomField` to a :term:`HL7` message segment accessor. Using this field mapping logic observation data is extracted from the message. If the message doesn't contain the accessor path defined in :class:`~hl7_integration.models.HL7FieldMapping` then an exception is raised to sentry, but it doesn't crash. This allows messages of varying types to be parsed by the same :class:`~hl7_integration.models.HL7Config`. Updating Observations ^^^^^^^^^^^^^^^^^^^^^ The default behavior is to create an observation for every message that's POSTed to the endpoint. If you would like to update observations, :class:`~hl7_integration.models.HL7FieldMapping` allows you to specify a field as unique in Django HL7 config Admin as a checkbox, which can be used to look up observations for updating. .. note:: Observations are updated in a celery task. While updating, a lock is acquired on the observation to prevent race conditions in updating the observation. If there are multiple observations with a value for the unique field, the first one ``created`` will be updated. The ``config`` field of :class:`~hl7_integration.models.HL7Config` provides a way to filter for unique matches if there are multiple observations that may match the value of your unique field. .. code-block:: json :caption: Example of filtering records for update when ``admission_date`` is ``null`` { "observation_filters": { "field": "admission_date", "answer_operator": "is_null", "answer": true } } Nested filtering is also supported .. code-block:: json { "observation_filters": { "operator": "and", "field": [ { "answer_operator": "equal", "field": "field_1", "answer": "1" }, { "operator": "or", "field": [ { "answer_operator": "equal", "field": "field_2", "answer": "2" }, { "answer_operator": "equal", "field": "field_3", "answer": "3" } ] } ] } } You can configure the HL7 integration to only update existing records and never create new ones. .. code-block:: json :caption: Set ``update_only`` to ``true`` in the ``config`` field of :class:`~hl7_integration.models.HL7Config` to prevent creating new observations { "update_only": true } When ``update_only`` is enabled, the system will only update existing observations that match the unique field. If no matching observation is found, no new observation will be created and the system will return a 200 response. You can configure the HL7 integration to set hardcoded values for specific message types. .. code-block:: json :caption: An example of ``set_value`` where a patient discharge event is cancelled (:term:`ADT-A13`). { "set_value": { "discharge_date": null, "status": "Inpatient", } } Merging a patient record ^^^^^^^^^^^^^^^^^^^^^^^^ The :term:`ADT-A40` message type represents a merged patient record. It identifies the old record id and the new one. Assuming a prior :term:`ADT-A01` or other demographic message was dispatched to record the new patient record, we can use :term:`ADT-A40` to unpublish the old one. We do this by creating a :class:`~hl7_integration.models.HL7Config` to listen for the :term:`ADT-A40` and unpublish the record that matches the old record id. .. code-block:: json :caption: Add the below condition to the Configuration JSON field in Django HL7 Admin config to unpublish existing records. { "unpublish": true } Merging a patient visit via A40 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Some systems don't dispatch an :term:`ADT-A42` message to signify that a patient visit record should be merged. Instead they merge the patient record, the patient visits and then dispatch an :term:`ADT-A40`. To handle this in MEG we need to merge all the patient visit records of the merged patient record. This requires some specific configuration on the :class:`~hl7_integration.models.HL7Config`. .. code-block:: json :caption: Add the below condition to the Configuration JSON field in Django HL7 Admin config to signify that many records must be updated by this configuration. { "update_many": true, "unpublish": true } This :class:`~hl7_integration.models.HL7Config` will listen for the :term:`ADT-A40` and unpublish all records that have been found that match the old record id. If you need to move the patient visits to the merged :term:`MRN`, this requires a different setup. In this case you need to query the patient visit records by one :term:`MRN` and then update the :term:`MRN` from another accessor: .. code-block:: json :caption: A :class:`~hl7_integration.models.HL7FieldMapping` queries by the mrn in ``MRG.1.1`` and all patient records found will be moved to the :term:`MRN` in ``PID.3.1``. { "update_many": true, "overwrite": { "mrn": "PID.3.1" } } Dynamically updating the ward ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ By default every form entry created or updated by HL7 is assigned the ward of the :class:`~hl7_integration.models.HL7Config`. If you need this to be dynamically set based on content in the message, you need to configure the :class:`~hl7_integration.models.HL7Config` to specify the field where the ward code is stored. .. code-block:: json :caption: The HL7 API will look for a ward based on the value stored in `PV1.3.4`. { "ward_accessor": "PV1.3.4", } If you have a :class:`~megforms.models.Ward` where the `ward_number` (code) field matches the value in the HL7 message located at the `ward_accessor`, then that :class:`~megforms.models.Ward` will be assigned to the form entry. This will also update the :class:`~megforms.models.Ward` of the :class:`~megforms.models.AuditSession` (provided there is only one form entry in the session, otherwise only the :class:`~megforms.models.Ward` of the form entry will be updated). Testing HL7 Integration ^^^^^^^^^^^^^^^^^^^^^^^ :file:`mirth_connect_channels/meg-message-forwarder.xml` is an example channel which is configured to POST :term:`HL7` messages to the aforementioned endpoint. This channel is configured to point at the :class:`~hl7_integration.models.HL7Config` configuration generated by :file:`setupdatabase.sh`.