Menu

How to export Apple Health data to Azure API for FHIR?

Nowadays Azure has many healthcare specific services. This blog posts introduces you to FHIR converter and Azure API for FHIR service. I'll show you a simple example how to export Apple Health data to Azure API for FHIR. 

What is Azure API for FHIR?

The Azure API for FHIR is a managed (PaaS) data platform solution for healthcare organizations which supports FHIR – Fast Healthcare Interoperability Resources standard. FHIR is a next generation standards framework created by HL7. Managed Azure API for FHIR solution is built on Microsoft technologies like Azure AD and CosmosDB.  

What is FHIR converter?

FHIR converter is an open source project which enables to convert HL7 and CDA messages to FHIR via HTTP API endpoint. FHIR converter (handlebars engine) provides an Web UI which enables to create mapping templates (HL7 to FHIR, CDA to FHIR) and HTTP API endpoint. There is also Liquid Engine available which can be used via VS Code and from command line.

Apple Health data is in CDA format so data must be converted to FHIR before sending to Azure API for FHIR.

Architecture of the solution

This simple integration reads CDA file from blob storage, converts it to FHIR and uploads it to Azure API for FHIR.

undefined

How to export Apple Health data?

Apple Health mobile application provides an export functionality which exports data in CDA format (Clinical Document Architecture document). If you want to export health data, open Apple Health application and select your profile. In the profile view there is a button "Export All Health Data". This functionality creates export.zip file which contains route information in GPX,  export.xml and export_cda.xml files. Export_cda.xml file is interesting to us because data is already in healthcare standard format (CDA). 

CDA to FHIR conversion

Like said earlier CDA data must be converted to FHIR before sending to Azure API for FHIR. This blog post shows usage of Handlebars engine because currently Liquid Engine does not support C-CDA to FHIR conversions. This post is not a deep walkthrough to Handlebar engine details. More details about FHIR Converter (handlebars engine) in general can be found from here. Previously mentioned link contains a lot of information about how to use handlebar engine ja description about the API endpoints.

How to test CDA to FHIR conversion with FHIR converter (handlebar engine)?

FHIR converter (handlebars) allows you to input example HL7 or CDA data and create the FHIR mapping using handlebars. You're also free to create own handlebar templates. UI shows the FHIR conversion result in right section.

undefined

Raw CDA data exported from Apple Health looks like this:

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="CDA.xsl"?>
<ClinicalDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:hl7-org:v3 ../../../CDA%20R2/cda-schemas-and-samples/infrastructure/cda/CDA.xsd" xmlns="urn:hl7-org:v3" xmlns:cda="urn:hl7-org:v3" xmlns:sdtc="urn:l7-org:sdtc" xmlns:fhir="http://hl7.org/fhir/v3">
 <realmCode code="US"/>
 <typeId root="2.16.840.1.113883.1.3" extension="POCD_HD000040"/>
 <templateId root="2.16.840.1.113883.10.20.22.1.2"/>
 <id extension="Health Export CDA" root="1.1.1.1.1.1.1.1.1"/>
 <code codeSystem="2.16.840.1.113883.6.1" codeSystemName="LOINC" code="34109-9" displayName="Note"/>
 <title>Health Data Export</title>
 <effectiveTime value="20210313154208+0200"/>
 <confidentialityCode code="N" codeSystem="2.16.840.1.113883.5.25"/>
 <recordTarget>
  <patientRole>
   <id root="2.16.840.1.113883.4.6" nullFlavor="NA"/>
   <patient>
    <name use="CL">Patient Name</name>
    <administrativeGenderCode code="M" codeSystem="2.16.840.1.113883.5.1" displayName="Male"/>
    <birthTime value="19000101"/>
   </patient>
  </patientRole>
 </recordTarget>
 <entry typeCode="DRIV">
  <organizer classCode="CLUSTER" moodCode="EVN">
   <templateId root="2.16.840.1.113883.10.20.22.4.26"/>
   <id root="e39a8abd-09ee-414a-bac3-03309f6268f6"/>
   <code code="46680005" codeSystem="2.16.840.1.113883.6.96" codeSystemName="SNOMED CT" displayName="Vital signs"/>
   <statusCode code="completed"/>
   <effectiveTime>
    <low value="20201104162503+0200"/>
    <high value="20201104162503+0200"/>
   </effectiveTime>
   <component>
    <observation classCode="OBS" moodCode="EVN">
     <templateId root="2.16.840.1.113883.10.20.22.4.27"/>
     <id root="e39a8abd-09ee-414a-bac3-03309f6268f6"/>
     <code code="8302-2" codeSystem="2.16.840.1.113883.6.1" codeSystemName="LOINC" displayName="Height"/>
     <text>
      <sourceName>Health</sourceName>
      <sourceVersion>14.1</sourceVersion>
      <value>183</value>
      <type>HKQuantityTypeIdentifierHeight</type>
      <unit>cm</unit>
     </text>
     <statusCode code="completed"/>
     <effectiveTime>
      <low value="20201104162503+0200"/>
      <high value="20201104162503+0200"/>
     </effectiveTime>
     <value value="183" unit="cm"/>
     <interpretationCode code="N" codeSystem="2.16.840.1.113883.5.83"/>
    </observation>
   </component>
  </organizer>
 </entry>
 </ClinicalDocument>

Note! FHIR converter with handlebars does not offer ready made template which extracts observations from CDA data provided by Apple Health export. In this sample I'll use default CDA template (CCD.HBS) which converts data to the bundle resource. This basically means that observation data is added to the bundle resource as an attachment. If we open CCD.HBS template with FHIR converter Web UI we can see that DocumentRefence template uses gzip and base64 encoding for data handling. This is not an optimal approach. I'll investigate later how to modify templates to create multiple observation resource elements from the original data.

undefined

After conversion to FHIR data looks this:

{
  "resourceType": "Bundle",
  "type": "batch",
  "entry": [
    {
      "fullUrl": "urn:uuid:9c83837d-5acd-3831-b76f-46b6908e7cbd",
      "resource": {
        "resourceType": "Composition",
        "id": "9c83837d-5acd-3831-b76f-46b6908e7cbd",
        "identifier": {
          "use": "official",
          "value": "1.1.1.1.1.1.1.1.1"
        },
        "status": "final",
        "type": {
          "coding": [
            {
              "code": "34109-9",
              "display": "Note",
              "system": "http://loinc.org"
            }
          ]
        },
        "date": "2021-03-13T13:42:08.000Z",
        "title": "Health Data Export",
        "confidentiality": "N",
        "subject": {
          "reference": "Patient/ee80fe9e-15f2-3c90-bea4-9188eea3e031"
        }
      },
      "request": {
        "method": "PUT",
        "url": "Composition/9c83837d-5acd-3831-b76f-46b6908e7cbd"
      }
    },
    {
      "fullUrl": "urn:uuid:ee80fe9e-15f2-3c90-bea4-9188eea3e031",
      "resource": {
        "resourceType": "Patient",
        "meta": {
          "profile": [
            "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
          ]
        },
        "id": "ee80fe9e-15f2-3c90-bea4-9188eea3e031",
        "birthDate": "1900-01-01",
        "gender": "male"
      },
      "request": {
        "method": "PUT",
        "url": "Patient/ee80fe9e-15f2-3c90-bea4-9188eea3e031"
      }
    },
    {
      "fullUrl": "urn:uuid:5647bcb4-c3fa-31e4-8238-46d7b503f5a3",
      "resource": {
        "resourceType": "DocumentReference",
        "id": "5647bcb4-c3fa-31e4-8238-46d7b503f5a3",
        "type": {
          "coding": [
            {
              "code": "34109-9",
              "display": "Note",
              "system": "http://loinc.org"
            }
          ]
        },
        "date": "2021-03-21T11:00:24.138Z",
        "status": "current",
        "content": [
          {
            "attachment": {
              "contentType": "text/plain",
              "data": "H4sIAAAAAAAACp1WW2/iOBR+n19hRZqnVWI7CZmAAqNZ6G6roXSmMNW+rdzENJZyQbbDZX/9HucCgWmX6QKqHJ+Lv/OdzyeNPu/zDG25VKIsxhZ1iPV58iEyu7bSh4yrlHON9GHDx5bme433KrNQKvl6bE1nXxzzaCKmmShEzLJZGVc5LzSCDIUa7ZUYW6nWmxHGu93O2XlOKV+wSwjFf93Pl3HKc2aLQmlWxNxC4D9S9ea8jJmuQVWyGKXZJxsCR1sPOQ5ufnD8R5c8ujhOmN0EKZsVia1YvgHkWBRryZSWVawryY0bbiAnVgPvMne7PQLPt0wq0XFja01mozOuUyGP5UJsXavZxJBg8gFFkrMsn5YJRzH8GVs/lhY2+4bfuwTJstRjy3Vo4IQ+cahDqReGHizgfCCfF02bvj1MZ3/fzgh8fNJm4FAz0/+ZhTgu/MDguE2QSPpZbwGcTtHNflNKjYApq01FnYtvEx13dSwPCo5/7cgAfHsuC5ZD0fOHu8XUahnwfEqG9tBCiVBQwKFxWZSat3UJnfFJC23GNGvxRbixgAtfr3msxZavRM7RlmUVZHCJS4lHPTrwXRL+RkBxHepiLRJQqGCZ0IdeMxbWlWoGjjtokkgelzJZMfnCNTyjaANahZyPZQ0J1dS+1QffCSxUVFn2R8a2JQhm8aXOekpTP6CoAC5QpQDadG5NvjU2ZBiKsLG1bizJ4e6Bzpkh4U9eJFz2yrq/Xha9oP+eZbyFhKJnIXXap5YOQXeU0A407qE+PrRMRPiCqghs8lAPlGkNb/Z492TVoXBZWCH+4RLFGVOqMU/nP5arm0cL5WWZNFs3TwurOfk9ovcdN+gQH5vDvSEL2XNikyHntk99Zj+z2LOJ55HhOnCDcH0Mik+U+kEQAgeDa8wGzjD4Wf/LxcP9zQxNVxesPwnNMqTES6G6M2Es6kr1mhmXZrRpnnQeZ+JvO5aVu941IJQSnwbugHina2DcUvGSXveL8M9HRAZGWZyUWj4rLrf1vO437+H35auNe3/rPnWo/1/zztsXesS13evNe3t2nfXtlgOR+nSSeU22a+hgWcmYG8d2iEW4t3Xu9dS8hifUd2jn1u11nnW/JjT0ItwsO4O5UZPbr98rBqNNH1b1G8WMubXgsoEIM9M4dRFVIfQkziNcL1rwuIf+uvxeF+AvS/CXRfi6DDs6joMphNekKQaA5j3BFJrLjeS61uf7Bj5k7K4L7om8vRln1wAcugFWD7560NWry/+NJv8Cd2qLzXYJAAA=",
              "hash": "4I5hBk72qplf4duGUO1HoWYE2lM="
            }
          }
        ]
      },
      "request": {
        "method": "PUT",
        "url": "DocumentReference/5647bcb4-c3fa-31e4-8238-46d7b503f5a3"
      }
    }
  ]
}

Why I'm using FHIR converter from local machine?

I'm using FHIR converter from my local machine via NGROK because I didn't managed to get node.js based application working in Azure App Service. NGROK basically exposes my local FHIR conversation API endpoint behind NATs and firewalls to the public internet over secure tunnels.

Azure service configurations

Azure API for FHIR and Azure AD

First we need Azure API for FHIR service. You can configure this service via Azure Portal or CLI. 

Next we need to register an application to Azure AD which is used to get Azure AD token from Logic Apps. Azure AD token is required to have access to Azure API for FHIR endpoint. You can follow this guide to register application. After registration open Azure API for FHIR and add the ex. the following role assignment to your application.

undefined

Test access token fetch with Postman

Before continuing create a token endpoint request with your application credentials.

undefined

Orchestration of the integration

I'll use Logic App to orchestrate reading the CDA data, calling FHIR conversion API, fetch Azure AD token and send data to API for FHIR.

undefined

1. STEP: Recurrence

Logic app is triggered once in a month.

undefined

2. STEP: Get blob content

CDA export file content is fetched from the Blob Storage

undefined

3. STEP: Convert CDA to FHIR

This action sends blob file content to FHIR converter API which is in my local machine. In the URI is determined that datatype is "CDA" and template is "CCD.HBS". Body contains CDA data in XML format.

undefined

4. STEP: Skip root element

FHIR converter API endpoint returns converted FHIR data inside "fhirResource" element. This step skips the root element and returns only child elements because otherwise Azure API for FHIR endpoint does not work.

{
  "fhirResource": {
    "resourceType": "Bundle",
    "type": "batch",
    "entry": []
  }
}

undefined

5. STEP: Get Access Token

Integration needs Azure AD access token to communicate with Azure API for FHIR. This request gets access token from the Azure AD token endpoint.

undefined

6. STEP: Parse Access Token response

Parse JSON action parses access token response. After that data is more easily accessible in the next actions of the Logic App.

undefined

7. STEP: Send data to API for FHIR

Last step sends converted CDA data in the FHIR format to the Azure API for FHIR endpoint. Access token is added as a bearer token to the request.

undefined

Logic app template is available from my Github repository.

Create a query to API for FHIR endpoint

Create the following GET request to the API for FHIR to retrieve data which was sent earlier. Remember to add bearer token to the request.

undefined

Comments