How to use CalculatedContent mappings

This article describes how to use CalculatedContent mappings with MedTech service device mappings in Azure Health Data Services.

Overview of CalculatedContent mappings

The MedTech service provides an expression-based content template to both match the wanted template and extract values. Either JSONPath or JMESPath can use expressions. Each expression within the template can use its own expression language.

Note

If you don't define an expression language, MedTech service device mappings use the default expression language that's configured for the template. The default is JSONPath, but you can overwrite it if necessary.

An expression is defined as:

<name of expression> : {
        "value" : <the expression>,
        "language": <the expression language>
    }

In the following example, typeMatchExpression is defined as:

"templateType": "CalculatedContent",
    "template": {
        "typeName": "heartrate",
        "typeMatchExpression": {
            "value" : "$..[?(@heartRate)]",
            "language": "JsonPath"
        },
        ...
    }

Tip

If you want to use JSON instead of the default JSONPath expression language, you can supply the expression alone.

"templateType": "CalculatedContent",
    "template": {
        "typeName": "heartrate",
        "typeMatchExpression": "$..[?(@heartRate)]",
        ...
    }

You can explicitly set the default expression language for MedTech service device mappings by using the defaultExpressionLanguage parameter:

"templateType": "CalculatedContent",
    "template": {
        "typeName": "heartrate",
        "defaultExpressionLanguage": "JsonPath",
        "typeMatchExpression": "$..[?(@heartRate)]",
        ...
    }

CalculatedContent mappings allow matching on, and extracting values from, an Azure Event Hubs message through the following expressions:

Property Description Example
TypeName The type to associate with measurements that match the template. heartrate
TypeMatchExpression The expression that the MedTech service evaluates against the EventData payload. If the service finds a matching JToken value, it considers the template a match. The service evaluates all later expressions against the extracted JToken value matched here. $..[?(@heartRate)]
TimestampExpression The expression to extract the timestamp value for the measurement's OccurrenceTimeUtc value. $.matchedToken.endDate
DeviceIdExpression The expression to extract the device identifier. $.matchedToken.deviceId
PatientIdExpression The expression to extract the patient identifier. Required when IdentityResolution is in Create mode, and optional when IdentityResolution is in Lookup mode. $.matchedToken.patientId
EncounterIdExpression Optional: The expression to extract the encounter identifier. $.matchedToken.encounterId
CorrelationIdExpression Optional: The expression to extract the correlation identifier. You can use this output to group values into a single observation in the FHIR destination mappings. $.matchedToken.correlationId
Values[].ValueName The name to associate with the value that the next expression extracts. Used to bind the wanted value or component in the FHIR destination-mapping template. hr
Values[].ValueExpression The expression to extract the wanted value. $.matchedToken.heartRate
Values[].Required Requires the value to be present in the payload. If the MedTech service doesn't find the value, it won't generate a measurement, and it will create an InvalidOperationException instance. true

Expression languages

When you're specifying the language to use for the expression, the following values are valid:

Expression language Value
JSONPath JsonPath
JMESPath JmesPath

Tip

For more information on JSONPath, see JSONPath - XPath for JSON. CalculatedContent mappings use the JSON .NET implementation for resolving JSONPath expressions.

For more information on JMESPath, see JMESPath Specification. CalculatedContent mappings use the JMESPath .NET implementation for resolving JMESPath expressions.

Custom functions

A set of custom functions for the MedTech service is also available. The MedTech service custom functions are outside the functions provided as part of the JMESPath specification. For more information on the MedTech service custom functions, see How to use custom functions with device mappings.

Matched token

The MedTech service evaluates TypeMatchExpression against the incoming EventData payload. If the service finds a matching JToken value, it considers the template a match.

The MedTech service evaluates all later expressions against a new JToken value. This new JToken value contains both the original EventData payload and the extracted JToken value matched here.

In this way, the original payload and the matched object are available to each later expression. The extracted JToken value will be available as the property matchedToken.

Here's an example message:

Message

{
  "Body": {
    "deviceId": "device123",
    "data": [
      {
        "systolic": "120", // Match
        "diastolic": "80", // Match 
        "date": "2021-07-13T17:29:01.061144Z"
      },
      {
        "systolic": "122", // Match
        "diastolic": "82", // Match
        "date": "2021-07-13T17:28:01.061122Z"
      }
    ]
  },
  "Properties": {},
  "SystemProperties": {}
}

Template

{
  "templateType": "CollectionContent",
  "template": [
    {
      "templateType": "CalculatedContent",
      "template": {
        "typeName": "heartrate",
        "typeMatchExpression": "$..[?(@systolic && @diastolic)]", // Expression
        "deviceIdExpression": "$.Body.deviceId", // This accesses the attribute 'deviceId' which belongs to the original event data
        "timestampExpression": "$.matchedToken.date", 
        "values": [
          {
            "required": "true",
            "valueExpression": "$.matchedToken.systolic",
            "valueName": "systolic"
          },
          {
            "required": "true",
            "valueExpression": "$.matchedToken.diastolic",
            "valueName": "diastolic"
          }
        ]
      }
    }
  ]
}

The MedTech service extracts two matches by using the preceding expression and uses them to create JToken values. The MedTech service will evaluate later expressions by using the following JToken values:

{
  "Body": {
    "deviceId": "device123",
    "data": [
      {
        "systolic": "120", 
        "diastolic": "80",
        "date": "2021-07-13T17:29:01.061144Z"
      },
      {
        "systolic": "122",
        "diastolic": "82",
        "date": "2021-07-13T17:28:01.061122Z"
      }
    ]
  },
  "Properties": {},
  "SystemProperties": {},
  "matchedToken" : {
      "systolic": "120",
      "diastolic": "80",
      "date": "2021-07-13T17:29:01.061144Z"
  }
}

And

{
  "Body": {
    "deviceId": "device123",
    "data": [
      {
        "systolic": "120",
        "diastolic": "80",
        "date": "2021-07-13T17:29:01.061144Z"
      },
      {
        "systolic": "122", 
        "diastolic": "82", 
        "date": "2021-07-13T17:28:01.061122Z"
      }
    ]
  },
  "Properties": {},
  "SystemProperties": {},
  "matchedToken" : {
      "systolic": "122",
      "diastolic": "82",
      "date": "2021-07-13T17:28:01.061122Z"
  }
}

Examples

Heart rate

Message

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

Template

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

Blood pressure

Message

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

Template

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

Projection of multiple measurements from a single message

Message

{
    "Body": {
        "heartRate": "78", // Match (Template 1)
        "steps": "2", // Match (Template 2)
        "endDate": "2019-02-01T22:46:01.8750000Z",
        "deviceId": "device123"
    },
    "Properties": {},
    "SystemProperties": {}
}

Template 1

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

Template 2

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

Projection of multiple measurements from an array in a message

Message

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

Template

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

Projection of data from a matched token and an original event

Message

{
  "Body": {
    "deviceId": "device123",
    "data": [
      {
        "systolic": "120", // Match
        "diastolic": "80", // Match 
        "date": "2021-07-13T17:29:01.061144Z"
      },
      {
        "systolic": "122", // Match
        "diastolic": "82", // Match
        "date": "2021-07-13T17:28:01.061122Z"
      }
    ]
  },
  "Properties": {},
  "SystemProperties": {}
}

Template

    {
      "templateType": "CalculatedContent",
      "template": {
        "typeName": "heartrate",
        "typeMatchExpression": "$..[?(@systolic && @diastolic)]", // Expression
        "deviceIdExpression": "$.Body.deviceId", // This accesses the attribute 'deviceId' which belongs to the original event data
        "timestampExpression": "$.matchedToken.date", 
        "values": [
          {
            "required": "true",
            "valueExpression": "$.matchedToken.systolic",
            "valueName": "systolic"
          },
          {
            "required": "true",
            "valueExpression": "$.matchedToken.diastolic",
            "valueName": "diastolic"
          }
        ]
      }
    }

Selection and transformation of incoming data

In the following example, height data arrives in either inches or meters. Assume that you want all normalized height data to be in meters. To achieve this outcome, you create a template that targets only height data in inches and transforms it into meters. Another template targets height data in meters and simply stores it as is.

Message

{
  "Body": [
    {
      "height": "78",
      "unit": "inches", // Match (Template 1)
      "endDate": "2019-02-01T22:46:01.8750000Z",
      "deviceId": "device123"
    },
    {
      "height": "1.9304",
      "unit": "meters", // Match (Template 2)
      "endDate": "2019-02-01T23:46:01.8750000Z",
      "deviceId": "device123"
    }
  ],
  "Properties": {},
  "SystemProperties": {}
}

Template 1

    {
      "templateType": "CalculatedContent",
      "template": {
        "typeName": "heightInMeters",
        "typeMatchExpression": "$..[?(@unit == 'inches')]",
        "deviceIdExpression": "$.matchedToken.deviceId",
        "timestampExpression": "$.matchedToken.endDate",
        "values": [
          {
            "required": "true",
            "valueExpression": {
              "value": "multiply(to_number(matchedToken.height), `0.0254`)", // Convert inches to meters. Notice we utilize JMESPath as that gives us access to transformation functions
              "language": "JmesPath"
            },
            "valueName": "height"
          }
        ]
      }
    }

Template 2

    {
      "templateType": "CalculatedContent",
      "template": {
        "typeName": "heightInMeters",
        "typeMatchExpression": "$..[?(@unit == 'meters')]",
        "deviceIdExpression": "$.matchedToken.deviceId",
        "timestampExpression": "$.matchedToken.endDate",
        "values": [
          {
            "required": "true",
            "valueExpression": "$.matchedToken.height", // Simply extract the height as it is already in meters
            "valueName": "height"
          }
        ]
      }
    }

Tip

For assistance in fixing MedTech service errors, see Troubleshoot MedTech service errors.

Next steps

In this article, you learned how to configure MedTech service device mappings by using CalculatedContent mappings.

To learn how to configure FHIR destination mappings, see:

FHIR® is a registered trademark of Health Level Seven International, registered in the U.S. Trademark Office, and is used with permission.