For AI agents: the documentation index is at /docs/aidbox/llms.txt. A Markdown version of this page is available at /docs/aidbox/notebooks.md or by requesting it with the Accept: text/markdown header.
Aidbox Docs
All notebooks

HL7v2 to FHIR conversion

Example HL7v2 messages parsing and transformation in Aidbox

18 cells · updated Mar 30, 2023

This is a read-only view. Responses shown were saved when the notebook was published — open it in your own Aidbox to run the cells against live data.

Documentation on HL7v2 Aidbox integration: https://docs.aidbox.app/modules-1/hl7-v2-integration

Input Data example

HL7v2

The incoming HL7v2 message:

MSH|^~\&||org-1|||20200101120000||ADT^A08|my-unique-message-id|P|2.9|||NE|AL
EVN|A08|20200101120000||Resident Diagnosis Update|1234^MyHealthCare^john.doe@myhealthcare.com^^^|20200101120000
PID|1|pt-1|org-1^^^^FI~pt-1^^^^PI||Doe^John^^^||19700101|M|||1 Lane^^Austin^TX^78000
PV1|1|I|1^1^A^ABC||||345^Adhoc^Test^^^||||||||||||1500|||||||||||||||||||||||||20200101120000

This message is parsed by HL7v2 Aidbox module like this:

  EVN:
    operator_id:
      - id: '1234'
        given: john.doe@myhealthcare.com
        family:
          surname: MyHealthCare
    event_occurred:
      time: '20200101120000'
    event_type_code: A08
    event_reason_code: Resident Diagnosis Update
    recorded_datetime:
      time: '20200101120000'
  PID:
    name:
      - given: John
        family:
          surname: Doe
    gender: M
    set_id: '1'
    address:
      - city: Austin
        state: TX
        street:
          text: 1 Lane
        postal_code: '78000'
    birth_date:
      time: '19990101'
    patient_id:
      value: pt-1
    identifiers:
      - value: org-1
        system: FI
      - value: pt-1
        system: PI

HL7 v2 Mapping

Mapping resources describe a transformation of ingested HL7v2 message to FHIR format.

HL7v2 messages can be transformed into multiple FHIR resources which then should be saved into Aidbox, to do this mapping describes a transaction Bundle that will be executed by Aidbox HL7v2 API to get these resources created or updated in Aidbox.

A mapping can be quite large since multiple resources can be created from a single message. To make developing mappings easier a mapping can be split into multiple Mapping resources referring to each other.

Following Mapping resource (Mapping/hl7v2-demo) describes a transaction Bunlde resource and refers to 3 other Mapping resources (Mapping/msh-segment, Mapping/pid-segment, Mapping/pv1-segment) describing different FHIR resources as entries of the Bundle:

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PUT /Mapping/hl7v2-demo
content-type: text/yaml
accept: text/yaml

body:
  $let:
  - {FACILITY_ID: $ msg.MSH.facility.ns}
  - {FAC_NAME: $ msg.MSH.facility.uid || "Care Center"}
  - {GEN_FAC_ID: $ str(abs(hash(str(FAC_ID) + "myhealthcare")))}
  - {PATIENT_ID: $ str(abs(hash(str(msg.PID.account_number.id) + GEN_FAC_ID + "myhealthcare")))}
  - meta:
      tag:
      - {code: myhealthcare, system: 'https://myhealthcare/resource/source'}
  - {EVENT_TYPE: $ msg.MSH.type.event}
  - {SOURCE_EMR: my-emr}
  - payload:
    - $if: $ FACILITY_ID && msg.MSH
      $then: {$include: msh-segment}
    - $if: $ msg.PID
      $then: {$include: pid-segment}
    - $if: $ msg.PV1.attending_doctor
      $then: {$include: pv1-segment}
  - mapped: {$as: p, $map: $ payload, $body: $ p.entry}
  $body: {type: transaction, entry: $ flatten(mapped), resourceType: Bundle}
Response: Body
Status: 200
body:
  $let:
  - {FACILITY_ID: $ msg.MSH.facility.ns}
  - {FAC_NAME: $ msg.MSH.facility.uid || "Care Center"}
  - {GEN_FAC_ID: $ str(abs(hash(str(FAC_ID) + "myhealthcare")))}
  - {PATIENT_ID: $ str(abs(hash(str(msg.PID.account_number.id) + GEN_FAC_ID + "myhealthcare")))}
  - meta:
      tag:
      - {code: myhealthcare, system: 'https://myhealthcare/resource/source'}
  - {EVENT_TYPE: $ msg.MSH.type.event}
  - {SOURCE_EMR: my-emr}
  - payload:
    - $if: $ FACILITY_ID && msg.MSH
      $then: {$include: msh-segment}
    - $if: $ msg.PID
      $then: {$include: pid-segment}
    - $if: $ msg.PV1.attending_doctor
      $then: {$include: pv1-segment}
  - mapped: {$as: p, $map: $ payload, $body: $ p.entry}
  $body: {type: transaction, entry: $ flatten(mapped), resourceType: Bundle}
id: hl7v2-demo
resourceType: Mapping
meta: {lastUpdated: '2023-03-27T14:01:03.876015Z', createdAt: '2023-03-27T13:56:36.739078Z', versionId: '363'}

Create dependent mappings

For each segment create a mapping translating this segment from HL7v2 to FHIR

MSH segment

This segment contains metadata. One of the fields contains facility id. We create an empty Organization resource if it doesn't exist yet.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PUT /Mapping/msh-segment
content-type: text/yaml
accept: text/yaml

body:
  type: transaction
  entry:
    - request:
        url: $ "/Organization/" + FACILITY_ID
        method: PUT
        IfNoneExist: $ FACILITY_ID
      resource:
        id: $ FACILITY_ID
        resourceType: Organization
  resourceType: Bundle
Response: Body
Status: 200
body:
  type: transaction
  entry:
  - request: {url: $ "/Organization/" + FACILITY_ID, method: PUT, IfNoneExist: $ FACILITY_ID}
    resource: {id: $ FACILITY_ID, resourceType: Organization}
  resourceType: Bundle
id: msh-segment
resourceType: Mapping
meta: {lastUpdated: '2023-03-27T14:02:57.561512Z', createdAt: '2023-03-27T13:56:48.382184Z', versionId: '367'}

EVN segment

We want to skip this segment of the HL7v2 message for keeping this example short. If parts of incoming data are not mentioned in a mapping then they will be ignored and not present in resulting FHIR resources

PID segment

This segment describes parts of Patient resource

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PUT /Mapping/pid-segment
content-type: text/yaml
accept: text/yaml

body:
  $let:
    - identifiers:
        $map: $ msg.PID.identifiers
        $as: i
        $body:
          $switch: $ i.system
          FI:
            type:
              text: Facility ID
              coding:
                - code: FI
                  system: http://terminology.hl7.org/CodeSystem/v2-0203
                  display: Facility ID
            value: $ i.id
            system: $ "https://" + SOURCE_EMR + ".com/patient/fi"
          MR:
            type:
              text: Medical record number
              coding:
                - code: MR
                  system: http://terminology.hl7.org/CodeSystem/v2-0203
                  display: Medical record number
            value: $ i.id
            system: $ "https://" + SOURCE_EMR + ".com/patient/mr"
  $body:
    type: transaction
    resourceType: Bundle
    entry:
      - request:
          url: $ "/Patient/" + PATIENT_ID
          method: PUT
        resource:
          address:
            $map: $ msg.PID.address
            $as: a
            $body:
              city: $ a.city
              line:
                - $ a.street.text
              state: $ a.state
              postalCode: $ a.postal_code
          managingOrganization:
            id: $ FACILITY_ID
            resourceType: Organization
          name:
            family: $ msg.PID.name.family.surname
            given: $ msg.PID.name.given
          birthDate:
            $if: $ msg.PID.birth_date.time
            $then: $ convertDate(msg.PID.birth_date.time, "YYYYMMdd", "YYYY-MM-dd")
          resourceType: Patient
          id: $ PATIENT_ID
          identifier: $ identifiers
          gender:
            $switch: $ toLowerCase(msg.PID.gender)
            m: male
            f: female
            $default: $ toLowerCase(msg.PID.gender)
Response: Body
Status: 201
body:
  $let:
  - identifiers:
      $as: i
      $map: $ msg.PID.identifiers
      $body:
        FI:
          type:
            text: Facility ID
            coding:
            - {code: FI, system: 'http://terminology.hl7.org/CodeSystem/v2-0203', display: Facility ID}
          value: $ i.id
          system: $ "https://" + SOURCE_EMR + ".com/patient/fi"
        MR:
          type:
            text: Medical record number
            coding:
            - {code: MR, system: 'http://terminology.hl7.org/CodeSystem/v2-0203', display: Medical record number}
          value: $ i.id
          system: $ "https://" + SOURCE_EMR + ".com/patient/mr"
        $switch: $ i.system
  $body:
    type: transaction
    entry:
    - request: {url: $ "/Patient/" + PATIENT_ID, method: PUT}
      resource:
        id: $ PATIENT_ID
        name: {given: $ msg.PID.name.given, family: $ msg.PID.name.family.surname}
        gender: {f: female, m: male, $switch: $ toLowerCase(msg.PID.gender), $default: $ toLowerCase(msg.PID.gender)}
        address:
          $as: a
          $map: $ msg.PID.address
          $body:
            city: $ a.city
            line: [$ a.street.text]
            state: $ a.state
            postalCode: $ a.postal_code
        birthDate: {$if: $ msg.PID.birth_date.time, $then: '$ convertDate(msg.PID.birth_date.time, "YYYYMMdd", "YYYY-MM-dd")'}
        identifier: $ identifiers
        resourceType: Patient
        managingOrganization: {id: $ FACILITY_ID, resourceType: Organization}
    resourceType: Bundle
id: pid-segment
resourceType: Mapping
meta: {lastUpdated: '2023-03-27T13:56:51.016953Z', createdAt: '2023-03-27T13:56:51.016953Z', versionId: '358'}

PV1

Using this segment we create an Encounter and a Practitioner resources

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PUT /Mapping/pv1-segment
content-type: text/yaml
accept: text/yaml

body:
  type: transaction
  entry:
    $let:
      - npi: >-
          $ replace(msg.PV1.attending_doctor.0.authority.ns ||
          msg.PV1.attending_doctor.0.authority.uid ||
          msg.PV1.attending_doctor.0.id, "\D", "")
      - practitioner_id: $ msg.PV1.attending_doctor.0.id
      - bed: $ msg.PV1.assigned_patient_location.bed
      - room: $ msg.PV1.assigned_patient_location.room
      - point_of_care: $ msg.PV1.assigned_patient_location.point_of_care
      - bed_display:
          $if: $ bed
          $then: $ "bed " + bed
      - room_display:
          $if: $ room
          $then: $ "room " + room
      - point_of_care_display:
          $if: $ point_of_care
          $then: $ "point of care " + point_of_care
      - loc_id:
          $if: $ bed || room || point_of_care
          $then: >-
            $ str(abs(hash(replace(bed + room + point_of_care, "\s",
            ""))))
    $body:
      - request:
          url: $ "/Practitioner/" + practitioner_id 
          method: PUT
          IfNoneExist: $ "_id=" + practitioner_id
        resource:
          id: $ practitioner_id
          name:
            - given:
                - $ msg.PV1.attending_doctor.0.given
              family: $ msg.PV1.attending_doctor.0.family.surname
      - request:
          url: $ "/Encounter"
          method: POST
        resource:
          subject:
            id: $ PATIENT_ID
            resourceType: Patient
          status: planned
          class:
            system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode'
            code: IMP
            display: inpatient encounter
          participant:
            - individual:
                resourceType: Practitioner
                id: $ practitioner_id
  resourceType: Bundle
Response: Body
Status: 201
body:
  type: transaction
  entry:
    $let:
    - {npi: '$ replace(msg.PV1.attending_doctor.0.authority.ns || msg.PV1.attending_doctor.0.authority.uid || msg.PV1.attending_doctor.0.id, "\D", "")'}
    - {practitioner_id: $ msg.PV1.attending_doctor.0.id}
    - {bed: $ msg.PV1.assigned_patient_location.bed}
    - {room: $ msg.PV1.assigned_patient_location.room}
    - {point_of_care: $ msg.PV1.assigned_patient_location.point_of_care}
    - bed_display: {$if: $ bed, $then: $ "bed " + bed}
    - room_display: {$if: $ room, $then: $ "room " + room}
    - point_of_care_display: {$if: $ point_of_care, $then: $ "point of care " + point_of_care}
    - loc_id: {$if: $ bed || room || point_of_care, $then: '$ str(abs(hash(replace(bed + room + point_of_care, "\s", ""))))'}
    $body:
    - request: {url: $ "/Practitioner/" + practitioner_id, method: PUT, IfNoneExist: $ "_id=" + practitioner_id}
      resource:
        id: $ practitioner_id
        name:
        - given: [$ msg.PV1.attending_doctor.0.given]
          family: $ msg.PV1.attending_doctor.0.family.surname
    - request: {url: $ "/Encounter", method: POST}
      resource:
        class: {code: IMP, system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode', display: inpatient encounter}
        status: planned
        subject: {id: $ PATIENT_ID, resourceType: Patient}
        participant:
        - individual: {id: $ practitioner_id, resourceType: Practitioner}
  resourceType: Bundle
id: pv1-segment
resourceType: Mapping
meta: {lastUpdated: '2023-03-27T13:56:56.298211Z', createdAt: '2023-03-27T13:56:56.298211Z', versionId: '359'}

Hl7v2Config resource

This resource is used to process incoming messages. The documentation on the Hl7v2Config can be found here

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PUT /Hl7v2Config/demo-config
content-type: text/yaml
accept: text/yaml

mapping:
  id: hl7v2-demo
  resourceType: Mapping
isStrict: false
Response: Body
Status: 201
mapping: {id: hl7v2-demo, resourceType: Mapping}
isStrict: false
id: demo-config
resourceType: Hl7v2Config
meta: {lastUpdated: '2023-03-27T13:57:00.930880Z', createdAt: '2023-03-27T13:57:00.930880Z', versionId: '360'}

Apply mapping

To transform incoming messages and execute resulting bundles use POST /Hl7v2Message specifying an Hl7v2Config and providing HL7v2 message in the body. Documentation

Response contains original message, internal HL7v2 parsed representation, transformation result and transaction Bundle apply response

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
POST /Hl7v2Message
content-type: text/yaml
accept: text/yaml

config:
  id: demo-config
  resourceType: Hl7v2Config 
src: |
  MSH|^~\&||org-1|||20200101120000||ADT^A08|my-unique-message-id|P|2.9|||NE|AL
  EVN|A08|20200101120000||Resident Diagnosis Update|1234^MyHealthCare^john.doe@myhealthcare.com^^^|20200101120000
  PID|1|pt-1|org-1^^^^FI~pt-1^^^^PI||Doe^John^^^||19990101|M|||1 Lane^^Austin^TX^78000
  PV1|1|I|1^1^A^ABC||||345^Adhoc^Test^^^||||||||||||1500|||||||||||||||||||||||||20200101120000
status: received
Response: Body
Status: 201
meta: {lastUpdated: '2023-03-27T13:57:04.826308Z', createdAt: '2023-03-27T13:57:04.826308Z', versionId: '362'}
config: {id: demo-config, resourceType: Hl7v2Config}
type: ADT
src: |
  MSH|^~\&||org-1|||20200101120000||ADT^A08|my-unique-message-id|P|2.9|||NE|AL
  EVN|A08|20200101120000||Resident Diagnosis Update|1234^MyHealthCare^john.doe@myhealthcare.com^^^|20200101120000
  PID|1|pt-1|org-1^^^^FI~pt-1^^^^PI||Doe^John^^^||19990101|M|||1 Lane^^Austin^TX^78000
  PV1|1|I|1^1^A^ABC||||345^Adhoc^Test^^^||||||||||||1500|||||||||||||||||||||||||20200101120000
outcome:
  mapping:
    type: transaction
    entry:
    - request: {url: /Patient/60178987, method: PUT}
      resource:
        id: '60178987'
        gender: male
        address:
        - city: Austin
          line: [1 Lane]
          state: TX
          postalCode: '78000'
        birthDate: '1999-01-01'
        identifier:
        - type:
            text: Facility ID
            coding:
            - {code: FI, system: 'http://terminology.hl7.org/CodeSystem/v2-0203', display: Facility ID}
          system: https://my-emr.com/patient/fi
        resourceType: Patient
        managingOrganization: {id: org-1, resourceType: Organization}
    - request: {url: /Practitioner/345, method: PUT, IfNoneExist: _id=345}
      resource:
        id: '345'
        name:
        - given: [Test]
          family: Adhoc
    - request: {url: /Encounter, method: POST}
      resource:
        class: {code: IMP, system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode', display: inpatient encounter}
        status: planned
        subject: {id: '60178987', resourceType: Patient}
        participant:
        - individual: {id: '345', resourceType: Practitioner}
    resourceType: Bundle
  response:
    body:
      id: processing
      text: {div: 'Transaction failed at entry[0]. Response status is 422. Response body is {"resourceType":"OperationOutcome","text":{"status":"generated","div":"Invalid resource"},"issue":[{"severity":"fatal","code":"invalid","expression":["Patient.managingOrganization"],"diagnostics":"Referenced resource Organization/org-1 does not exist"}]}.', status: generated}
      issue:
      - code: invalid
        severity: fatal
        expression: [Patient.managingOrganization]
        diagnostics: Referenced resource Organization/org-1 does not exist
      resourceType: OperationOutcome
    status: 400
resourceType: Hl7v2Message
event: A08
status: error
id: 832957b4-2267-42d4-9f1e-e717d1c44286
parsed:
  EVN:
    operator_id:
    - id: '1234'
      given: john.doe@myhealthcare.com
      family: {surname: MyHealthCare}
    event_occurred: {time: '20200101120000'}
    event_type_code: A08
    event_reason_code: Resident Diagnosis Update
    recorded_datetime: {time: '20200101120000'}
  MSH:
    encoding: ^~\&
    facility: {ns: org-1}
    proc_id: {proc_id: P}
    ack_type: NE
    type: {code: ADT, event: A08}
    app_ack_type: AL
    id: my-unique-message-id
    datetime: {time: '20200101120000'}
    version: {id: '2.9'}
    separator: '|'
  PID:
    name:
    - given: John
      family: {surname: Doe}
    gender: M
    set_id: '1'
    address:
    - city: Austin
      state: TX
      street: {text: 1 Lane}
      postal_code: '78000'
    birth_date: {time: '19990101'}
    patient_id: {value: pt-1}
    identifiers:
    - {value: org-1, system: FI}
    - {value: pt-1, system: PI}
  PV1:
    set_id: '1'
    visit_number: {value: '1500'}
    patient_class: I
    admit_datetime: {time: '20200101120000'}
    attending_doctor:
    - id: '345'
      given: Test
      family: {surname: Adhoc}
    assigned_patient_location:
      bed: A
      room: '1'
      facility: {ns: ABC}
      point_of_care: '1'

We got success response from the transaction Bundle apply and now can check if resources are present in Aidbox:

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
GET /fhir/Patient/60178987
accept: text/yaml
Response: Body
Status: 200
address:
- city: Austin
  line: [1 Lane]
  state: TX
  postalCode: '78000'
meta:
  lastUpdated: '2022-03-15T17:42:23.160883Z'
  versionId: '3762'
  extension:
  - {url: 'ex:createdAt', valueInstant: '2022-03-15T17:42:23.160883Z'}
managingOrganization: {reference: Organization/org-1}
birthDate: '1999-01-01'
resourceType: Patient
id: '60178987'
identifier:
- type:
    text: Facility ID
    coding:
    - {code: FI, system: 'http://terminology.hl7.org/CodeSystem/v2-0203', display: Facility ID}
  system: https://my-emr.com/patient/fi
gender: male