Azure IoT Connector for FHIR (preview) mapping templates

This article details how to configure Azure IoT Connector for Fast Healthcare Interoperability Resources (FHIR®)* using mapping templates.

The Azure IoT Connector for FHIR requires two types of JSON-based mapping templates. The first type, Device mapping, is responsible for mapping the device payloads sent to the devicedata Azure Event Hub end point. It extracts types, device identifiers, measurement date time, and the measurement value(s). The second type, FHIR mapping, controls the mapping for FHIR resource. It allows configuration of the length of the observation period, FHIR data type used to store the values, and terminology code(s).

The mapping templates are composed into a JSON document based on their type. These JSON documents are then added to your Azure IoT Connector for FHIR through the Azure portal. The Device mapping document is added through the Configure Device mapping page and the FHIR mapping document through the Configure FHIR mapping page.

Note

Mapping templates are stored in an underlying blob storage and loaded from blob per compute execution. Once updated they should take effect immediately.

Device mapping

Device mapping provides mapping functionality to extract device content into a common format for further evaluation. Each message received is evaluated against all templates. This approach allows a single inbound message to be projected to multiple outbound messages, which are later mapped to different observations in FHIR. The result is a normalized data object representing the value or values parsed by the templates. The normalized data model has a few required properties that must be found and extracted:

Property Description
Type The name/type to classify the measurement. This value is used to bind to the required FHIR mapping template. Multiple templates can output to the same type allowing you to map different representations across multiple devices to a single common output.
OccurenceTimeUtc The time the measurement occurred.
DeviceId The identifier for the device. This value should match an identifier on the device resource that exists on the destination FHIR server.
Properties Extract at least one property so the value can be saved in the Observation resource created. Properties are a collection of key value pairs extracted during normalization.

Below is a conceptual example of what happens during normalization.

Normalization Example

The content payload itself is an Azure Event Hub message, which is composed of three parts: Body, Properties, and SystemProperties. The Body is a byte array representing an UTF-8 encoded string. During template evaluation, the byte array is automatically converted into the string value. Properties is a key value collection for use by the message creator. SystemProperties is also a key value collection reserved by the Azure Event Hub framework with entries automatically populated by it.

{
	"Body": {
		"content": "value"
	},
	"Properties": {
		"key1": "value1",
		"key2": "value2"
	},
	"SystemProperties": {
		"x-opt-sequence-number": 1,
		"x-opt-enqueued-time": "2019-02-01T22:46:01.8750000Z",
		"x-opt-offset": 1,
		"x-opt-partition-key": "1"
	}
}

Mapping with JSON path

The three device content template types supported today rely on JSON Path to both match the required template and extracted values. More information on JSON Path can be found here. All three template types use the JSON .NET implementation for resolving JSON Path expressions.

JsonPathContentTemplate

The JsonPathContentTemplate allows matching on and extracting values from an Event Hub message using JSON Path.

Property Description Example
TypeName The type to associate with measurements that match the template. heartrate
TypeMatchExpression The JSON Path expression that is evaluated against the Event Hub payload. If a matching JToken is found, the template is considered a match. All subsequent expressions are evaluated against the extracted JToken matched here. $..[?(@heartRate)]
TimestampExpression The JSON Path expression to extract the timestamp value for the measurement's OccurenceTimeUtc. $.endDate
DeviceIdExpression The JSON Path expression to extract the device identifier. $.deviceId
PatientIdExpression Optional: The JSON Path expression to extract the patient identifier. $.patientId
EncounterIdExpression Optional: The JSON Path expression to extract the encounter identifier. $.encounterId
Values[].ValueName The name to associate with the value extracted by the subsequent expression. Used to bind the required value/component in the FHIR mapping template. hr
Values[].ValueExpression The JSON Path expression to extract the required value. $.heartRate
Values[].Required Will require the value to be present in the payload. If not found, a measurement will not be generated and an InvalidOperationException will be thrown. true
Examples

Heart rate

Message

{
    "Body": {
        "heartRate": "78",
        "endDate": "2019-02-01T22:46:01.8750000Z",
        "deviceId": "device123"
    },
    "Properties": {},
    "SystemProperties": {}
}

Template

{
    "templateType": "JsonPathContent",
    "template": {
        "typeName": "heartrate",
        "typeMatchExpression": "$..[?(@heartRate)]",
        "deviceIdExpression": "$.deviceId",
        "timestampExpression": "$.endDate",
        "values": [
            {
                "required": "true",
                "valueExpression": "$.heartRate",
                "valueName": "hr"
            }
        ]
    }
}

Blood pressure

Message

{
    "Body": {
        "systolic": "123",
        "diastolic" : "87",
        "endDate": "2019-02-01T22:46:01.8750000Z",
        "deviceId": "device123"
    },
    "Properties": {},
    "SystemProperties": {}
}

Template

{
    "typeName": "bloodpressure",
    "typeMatchExpression": "$..[?(@systolic && @diastolic)]",
    "deviceIdExpression": "$.deviceId",
    "timestampExpression": "$.endDate",
    "values": [
        {
            "required": "true",
            "valueExpression": "$.systolic",
            "valueName": "systolic"
        },
        {
            "required": "true",
            "valueExpression": "$.diastolic",
            "valueName": "diastolic"
        }
    ]
}

Project multiple measurements from single message

Message

{
    "Body": {
        "heartRate": "78",
        "steps": "2",
        "endDate": "2019-02-01T22:46:01.8750000Z",
        "deviceId": "device123"
    },
    "Properties": {},
    "SystemProperties": {}
}

Template 1

{
    "templateType": "JsonPathContent",
    "template": {
        "typeName": "heartrate",
        "typeMatchExpression": "$..[?(@heartRate)]",
        "deviceIdExpression": "$.deviceId",
        "timestampExpression": "$.endDate",
        "values": [
            {
                "required": "true",
                "valueExpression": "$.heartRate",
                "valueName": "hr"
            }
        ]
    }
}

Template 2

{
    "templateType": "JsonPathContent",
    "template": {
        "typeName": "stepcount",
        "typeMatchExpression": "$..[?(@steps)]",
        "deviceIdExpression": "$.deviceId",
        "timestampExpression": "$.endDate",
        "values": [
            {
                "required": "true",
                "valueExpression": "$.steps",
                "valueName": "steps"
            }
        ]
    }
}

Project multiple measurements from array in message

Message

{
    "Body": [
        {
            "heartRate": "78",
            "endDate": "2019-02-01T22:46:01.8750000Z",
            "deviceId": "device123"
        },
        {
            "heartRate": "81",
            "endDate": "2019-02-01T23:46:01.8750000Z",
            "deviceId": "device123"
        },
        {
            "heartRate": "72",
            "endDate": "2019-02-01T24:46:01.8750000Z",
            "deviceId": "device123"
        }
    ],
    "Properties": {},
    "SystemProperties": {}
}

Template

{
    "templateType": "JsonPathContent",
    "template": {
        "typeName": "heartrate",
        "typeMatchExpression": "$..[?(@heartRate)]",
        "deviceIdExpression": "$.deviceId",
        "timestampExpression": "$.endDate",
        "values": [
            {
                "required": "true",
                "valueExpression": "$.heartRate",
                "valueName": "hr"
            }
        ]
    }
}

IotJsonPathContentTemplate

The IotJsonPathContentTemplate is similar to the JsonPathContentTemplate except the DeviceIdExpression and TimestampExpression aren't required.

The assumption when using this template is the messages being evaluated were sent using the Azure IoT Hub Device SDKs or Export Data (legacy) feature of Azure IoT Central. When using these SDKs, the device identity (assuming the device identifier from Azure Iot Hub/Central is registered as an identifer for a device resource on the destination FHIR server) and the timestamp of the message are known. If you're using Azure IoT Hub Device SDKs but are using custom properties in the message body for the device identity or measurement timestamp, you can still use the JsonPathContentTemplate.

Note: When using the IotJsonPathContentTemplate, the TypeMatchExpression should resolve to the entire message as a JToken. See the examples below.

Examples

Heart rate

Message

{
    "Body": {
        "heartRate": "78"        
    },
    "Properties": {
        "iothub-creation-time-utc" : "2019-02-01T22:46:01.8750000Z"
    },
    "SystemProperties": {
        "iothub-connection-device-id" : "device123"
    }
}

Template

{
    "templateType": "JsonPathContent",
    "template": {
        "typeName": "heartrate",
        "typeMatchExpression": "$..[?(@Body.heartRate)]",
        "deviceIdExpression": "$.deviceId",
        "timestampExpression": "$.endDate",
        "values": [
            {
                "required": "true",
                "valueExpression": "$.Body.heartRate",
                "valueName": "hr"
            }
        ]
    }
}

Blood pressure

Message

{
    "Body": {
        "systolic": "123",
        "diastolic" : "87"
    },
    "Properties": {
        "iothub-creation-time-utc" : "2019-02-01T22:46:01.8750000Z"
    },
    "SystemProperties": {
        "iothub-connection-device-id" : "device123"
    }
}

Template

{
    "typeName": "bloodpressure",
    "typeMatchExpression": "$..[?(@Body.systolic && @Body.diastolic)]",
    "values": [
        {
            "required": "true",
            "valueExpression": "$.Body.systolic",
            "valueName": "systolic"
        },
        {
            "required": "true",
            "valueExpression": "$.Body.diastolic",
            "valueName": "diastolic"
        }
    ]
}

IotCentralJsonPathContentTemplate

The IotCentralJsonPathContentTemplate also doesn't require DeviceIdExpression and TimestampExpression, and used when messages being evaluated are sent through the Export Data feature of Azure IoT Central. When using this feature, the device identity (assuming the device identifier from Azure Iot Central is registered as an identifer for a device resource on the destination FHIR server) and the timestamp of the message are known. If you're using Azure IoT Central's Data Export feature but are using custom properties in the message body for the device identity or measurement timestamp, you can still use the JsonPathContentTemplate.

Note: When using the IotCentralJsonPathContentTemplate, the TypeMatchExpression should resolve to the entire message as a JToken. See the examples below.

Examples

Heart rate

Message

{
    "applicationId": "1dffa667-9bee-4f16-b243-25ad4151475e",
    "messageSource": "telemetry",
    "deviceId": "1vzb5ghlsg1",
    "schema": "default@v1",
    "templateId": "urn:qugj6vbw5:___qbj_27r",
    "enqueuedTime": "2020-08-05T22:26:55.455Z",
    "telemetry": {
        "HeartRate": "88",
    },
    "enrichments": {
      "userSpecifiedKey": "sampleValue"
    },
    "messageProperties": {
      "messageProp": "value"
    }
}

Template

{
    "templateType": "IotCentralJsonPathContent",
    "template": {
        "typeName": "heartrate",
        "typeMatchExpression": "$..[?(@telemetry.HeartRate)]",
        "values": [
            {
                "required": "true",
                "valueExpression": "$.telemetry.HeartRate",
                "valueName": "hr"
            }
        ]
    }
}

Blood pressure

Message

{
    "applicationId": "1dffa667-9bee-4f16-b243-25ad4151475e",
    "messageSource": "telemetry",
    "deviceId": "1vzb5ghlsg1",
    "schema": "default@v1",
    "templateId": "urn:qugj6vbw5:___qbj_27r",
    "enqueuedTime": "2020-08-05T22:26:55.455Z",
    "telemetry": {
        "BloodPressure": {
            "Diastolic": "87",
            "Systolic": "123"
        }
    },
    "enrichments": {
      "userSpecifiedKey": "sampleValue"
    },
    "messageProperties": {
      "messageProp": "value"
    }
}

Template

{
    "templateType": "IotCentralJsonPathContent",
    "template": {
        "typeName": "bloodPressure",
        "typeMatchExpression": "$..[?(@telemetry.BloodPressure.Diastolic && @telemetry.BloodPressure.Systolic)]",
        "values": [
            {
                "required": "true",
                "valueExpression": "$.telemetry.BloodPressure.Diastolic",
                "valueName": "bp_diastolic"
            },
            {
                "required": "true",
                "valueExpression": "$.telemetry.BloodPressure.Systolic",
                "valueName": "bp_systolic"
            }
        ]
    }
}

FHIR mapping

Once the device content is extracted into a normalized model, the data is collected and grouped according to device identifier, measurement type, and time period. The output of this grouping is sent for conversion into a FHIR resource (Observation currently). Here the FHIR mapping template controls how the data is mapped into a FHIR Observation. Should an observation be created for a point in time or over a period of an hour? What codes should be added to the observation? Should value be represented as SampledData or a Quantity? These data types are all options the FHIR mapping configuration controls.

CodeValueFhirTemplate

The CodeValueFhirTemplate is currently the only template supported in FHIR mapping at this time. It allows you to define codes, the effective period, and the value of the observation. Multiple value types are supported: SampledData, CodeableConcept, and Quantity. Along with these configurable values, the identifier for the Observation resource and linking to the proper Device and Patient resources are handled automatically.

Property Description
TypeName The type of measurement this template should bind to. There should be at least one Device mapping template that outputs this type.
PeriodInterval The period of time the observation created should represent. Supported values are 0 (an instance), 60 (an hour), 1440 (a day).
Category Any number of CodeableConcepts to classify the type of observation created.
Codes One or more Codings to apply to the observation created.
Codes[].Code The code for the Coding.
Codes[].System The system for the Coding.
Codes[].Display The display for the Coding.
Value The value to extract and represent in the observation. For more information, see Value Type Templates.
Components Optional: One or more components to create on the observation.
Components[].Codes One or more Codings to apply to the component.
Components[].Value The value to extract and represent in the component. For more information, see Value Type Templates.

Value type templates

Below are the currently supported value type templates. In the future, further templates may be added.

SampledData

Represents the SampledData FHIR data type.Observation measurements are written to a value stream starting at a point in time and incrementing forward using the period defined. If no value is present, an E will be written into the data stream. If the period is such that two more values occupy the same position in the data stream, the latest value is used. The same logic is applied when an observation using the SampledData is updated.

Property Description
DefaultPeriod The default period in milliseconds to use.
Unit The unit to set on the origin of the SampledData.

Quantity

Represents the Quantity FHIR data type. If more than one value is present in the grouping, only the first value is used. When new value arrives that maps to the same observation it will overwrite the old value.

Property Description
Unit Unit representation.
Code Coded form of the unit.
System System that defines the coded unit form.

CodeableConcept

Represents the CodeableConcept FHIR data type. The actual value isn't used.

Property Description
Text Plain text representation.
Codes One or more Codings to apply to the observation created.
Codes[].Code The code for the Coding.
Codes[].System The system for the Coding.
Codes[].Display The display for the Coding.

Examples

Heart rate - SampledData

{
    "templateType": "CodeValueFhir",
    "template": {
        "codes": [
            {
                "code": "8867-4",
                "system": "http://loinc.org",
                "display": "Heart rate"
            }
        ],
        "periodInterval": 60,
        "typeName": "heartrate",
        "value": {
            "defaultPeriod": 5000,
            "unit": "count/min",
            "valueName": "hr",
            "valueType": "SampledData"
        }
    }
}

Steps - SampledData

{
    "templateType": "CodeValueFhir",
    "template": {
        "codes": [
            {
                "code": "55423-8",
                "system": "http://loinc.org",
                "display": "Number of steps"
            }
        ],        
        "periodInterval": 60,
        "typeName": "stepsCount",
        "value": {
            "defaultPeriod": 5000,
            "unit": "",
            "valueName": "steps",
            "valueType": "SampledData"
        }
    }
}

Blood pressure - SampledData

{
    "templateType": "CodeValueFhir",
    "template": {
        "codes": [
            {
                "code": "85354-9",
                "display": "Blood pressure panel with all children optional",
                "system": "http://loinc.org"
            }
        ],
        "periodInterval": 60,
        "typeName": "bloodpressure",
        "components": [
            {
                "codes": [
                    {
                        "code": "8867-4",
                        "display": "Diastolic blood pressure",
                        "system": "http://loinc.org"
                    }
                ],
                "value": {
                    "defaultPeriod": 5000,
                    "unit": "mmHg",
                    "valueName": "diastolic",
                    "valueType": "SampledData"
                }
            },
            {
                "codes": [
                    {
                        "code": "8480-6",
                        "display": "Systolic blood pressure",
                        "system": "http://loinc.org"
                    }
                ],
                "value": {
                    "defaultPeriod": 5000,
                    "unit": "mmHg",
                    "valueName": "systolic",
                    "valueType": "SampledData"
                }
            }
        ]
    }
}

Blood pressure - Quantity

{
    "templateType": "CodeValueFhir",
    "template": {
        "codes": [
            {
                "code": "85354-9",
                "display": "Blood pressure panel with all children optional",
                "system": "http://loinc.org"
            }
        ],
        "periodInterval": 0,
        "typeName": "bloodpressure",
        "components": [
            {
                "codes": [
                    {
                        "code": "8867-4",
                        "display": "Diastolic blood pressure",
                        "system": "http://loinc.org"
                    }
                ],
                "value": {
                    "unit": "mmHg",
                    "valueName": "diastolic",
                    "valueType": "Quantity"
                }
            },
            {
                "codes": [
                    {
                        "code": "8480-6",
                        "display": "Systolic blood pressure",
                        "system": "http://loinc.org"
                    }
                ],
                "value": {
                    "unit": "mmHg",
                    "valueName": "systolic",
                    "valueType": "Quantity"
                }
            }
        ]
    }
}

Device removed - CodeableConcept

{
    "templateType": "CodeValueFhir",
    "template": {
        "codes": [
            {
                "code": "deviceEvent",
                "system": "https://www.mydevice.com/v1",
                "display": "Device Event"
            }
        ],
        "periodInterval": 0,
        "typeName": "deviceRemoved",
        "value": {
            "text": "Device Removed",
            "codes": [
                {
                    "code": "deviceRemoved",
                    "system": "https://www.mydevice.com/v1",
                    "display": "Device Removed"
                }
            ],
            "valueName": "deviceRemoved",
            "valueType": "CodeableConcept"
        }
    }
}

Next steps

Check out frequently asked questions on Azure IoT Connector for FHIR (preview).

*In the Azure portal, Azure IoT Connector for FHIR is referred to as IoT Connector (preview). FHIR is a registered trademark of HL7 and is used with the permission of HL7.