MapEHR Introduction
MapEHR is an engine that implements mappings between FHIR (or any other data source) and openEHR.
The MapEHR engine processes the incoming data into openEHR compositions for storage in an openEHR repository.
MapEHR Mapping Specification
MapEHR is an open, vendor-neutral specification for mapping FHIR (or any other data) to openEHR to FHIR. You can write mappings in a human friendly YAML format. The vendor-neutral approach ensures that any engine capable of running these mappings can execute them.
The specification is published to https://www.schemastore.org/json/. Search for MapEHR
or visit directly. Autocomplete is supported in most editors (IntelliJ IDE, Visual Studio Code,...).
The Basics
Mapping rules are stored in easy to read and write YAML files. Mapping engine receives a list of directories in which it searches for the rules. The directory structure is up to the users – engine searches all sub-folders of the received directories.
Each YAML file may contain one or more rule (under rules:
).
Map a start time in composition
A simple rule to map effectiveDateTime
found in FHIR data to context.start_time
attribute in openEHR composition:
templates:
openEHR-EHR-COMPOSITION.simple-encounter-blood-pressure.v0:
name: Blood pressure
uri: https://ckm.openehr.org/ckm/archetypes/1013.1.3574
map:
- path: /context[id9002]
element:
attribute: start_time
formats:
fhir:
- attribute: effectiveDateTime
In the above example /context[id9002]/start_time
will be set to the effectiveDateTime
found in the FHIR data.
Map simple attributes in an archetype
Another simple rule to set a /subject/name
attribute with a subject.reference
from the FHIR data:
archetypes:
openEHR-EHR-OBSERVATION.blood_pressure.v2:
name: Blood pressure
uri: https://ckm.openehr.org/ckm/archetypes/1013.1.3574
formats:
fhir:
name: Observationbp
uri: https://build.fhir.org/bp.html
resource: Observation
map:
- path: /subject
element:
attribute: name
formats:
fhir:
- attribute: subject
path: reference
The rules below set /data[id2|History|]/origin
and /data[id2|History|]/events[id7|Any event|]/time
using the effectiveDateTime
from the FHIR data:
- path: /data[id2|History|]
element:
attribute: origin
formats:
fhir:
- attribute: effectiveDateTime
- path: /data[id2|History|]/events[id7|Any event|]
element:
attribute: time
formats:
fhir:
- attribute: effectiveDateTime
Use a library method to set values
A value may get a value assigned by calling a library method. In the below example we use rm.CodePhrase('IANA', 'UTF-8')
to set /content/encoding
which is a CODE_PHRASE
type in openEHR:
- path: /encoding
element:
value: rm.CodePhrase('IANA', 'UTF-8')
The above example will result in the following data in the composition:
"encoding": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "IANA"
},
"code_string": "UTF-8"
},
Set element based on a terminology code
Mapping often uses terminology codes for matching the right properties in FHIR data:
- path: /protocol[id12|Tree|]/items
elements:
body_site:
value_type: DV_CODED_TEXT
formats:
fhir:
- attribute: bodySite
path: coding.code
elements:
body_site:
one_of:
- body_site_snomed
define:
body_site: id15|Location of measurement|
body_site_snomed: http://snomed.info/id/368209003
In the above example the id15|Location of measurement|
will be mapped to bodySite
with http://snomed.info/id/368209003
SNOMED CT code.
Map multiple values
We can map multiple values at the same time. In the below example systolic
, diastolic
and clinical_interpretation
elements are set for a blood pressure:
- path: /data[id2|History|]/events[id7|Any event|]/data[id4|Data|]/items
elements:
systolic:
value_type: DV_QUANTITY
diastolic:
value_type: DV_QUANTITY
clinical_interpretation:
value_type: DV_CODED_TEXT
formats:
fhir:
- attribute: component
path: code.coding.code
elements:
systolic:
one_of:
- systolic_loinc
- systolic_snomed
diastolic:
one_of:
- diastolic_loinc
- attribute: interpretation
path: coding.system
elements:
clinical_interpretation:
one_of:
- clinical_interpretation_hl7
define:
systolic: id5|Systolic|
diastolic: id6|Diastolic|
clinical_interpretation: id1060|Clinical interpretation|
systolic_loinc: http://loinc.org/8480-6
diastolic_loinc: http://loinc.org/8462-4
systolic_snomed: http://snomed.info/id/271649006
diastolic_snomed: http://snomed.info/id/271650006
clinical_interpretation_hl7: http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation
In the above example the id5|Systolic|
will be mapped to one of the FHIR components based on LOINC or SNOMED CT codes. Similarly the id6|Diastolic|
.
The element for id1060|Clinical interpretation|
will be matched based on the HL7 code system http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation
.
Use other elements to calculate a value
Sometimes we need to use other elements to calculate a value of another element. Calculating Body Mass Index (BMI) is such an example.
We use LOINC code 85353-1
to find the nearest common ancestor of all the elements we need. In this case the LOINC code is specified for a Composition
(see openEHR-EHR-COMPOSITION.simple-encounter-blood-pressure.v0
).
We use attribute: content
to select COMPOSITION.content
which is a list of OBSERVATION
s. For the BMI we need weight and height.
We use weight_observation
element (with LOINC code 29463-7
) to first select the OBSERVATION
for openEHR-EHR-OBSERVATION.t_vital_signs-weight.v1.0.0
. We need to go one level deeper to find the actual value for weight. This is achieved with using attribute: data
to search inside OBSERVATION.data
list. We use weight
element (with LOINC code 29463-7
) to find the weight ELEMENT
. Note that in this case the OBSERVATION
and the ELEMENT
we need, both use the same LOINC code. This is why we defined weight_observation
and weight
separately even if they use the same LOINC code. The difference is that the first one holds the OBSERVATION
element and the second one the ELEMENT
element. We will use the weight
of type ELEMENT
in the bmi
element.
Similarly we use height_observation
and height
to read the value for the height.
The BMI is in the OBSERVATION
for the LOINC code 59574-4
and inside its OBSERVATION.data
is an ELEMENT
for the LOINC code 59574-4
(note the same LOINC code is used here too).
The bmi
element uses a bmi()
function to calculate its value:
value: bmi($weight, $height, $default_bmi)
rules:
loinc:
85353-1:
uri: http://loinc.org/85353-1
name: Vital signs, weight, height, head circumference, oxygen saturation and BMI panel
set:
- attribute: content
elements:
weight_observation:
attribute: data
elements:
weight: # We only need the existing value for the bmi().
height_observation:
attribute: data
elements:
height: # We only need the existing value for the bmi().
bmi_observation:
attribute: data
elements:
bmi:
value: bmi($weight, $height, $default_bmi)
interpretation_interval:
low: 18.5
high: 24.9
vars:
# Normal Distribution values for BMI:
# Source: https://www.scirp.org/journal/paperinformation?paperid=117728
# Default mean in randomNormalDistribution() is an average of male and female means.
default_bmi: randomNormalDistribution((27.6863+25.4960)/2, sqrt(18.65))
define:
weight_observation: http://loinc.org/29463-7 # Same LOINC code is used for OBSERVATION and ELEMENT.
weight: http://loinc.org/29463-7
height_observation: http://loinc.org/8302-2 # Same LOINC code is used for OBSERVATION and ELEMENT.
height: http://loinc.org/8302-2
bmi_observation: http://loinc.org/59574-4
bmi: http://loinc.org/59574-4
Examples of using the bmi()
function:
-
Without defaults:
value: bmi($weight, $height)
-
Use variance directly in randomNormalDistribution():
value: 'bmi($weight, $height, randomNormalDistribution((27.6863+25.4960)/2, (4.4351+4.2031)/2))'
-
Using (unnecessary) long way to specify an expression:
value: |
var weight = $weight;
var height = $height;
if (weight == null || height == null) {
return randomNormalDistribution((27.6863+25.4960)/2, sqrt(18.65));
} else {
return bmi(weight, height);
}
Use programming language code
Notice the use of a full programming language in the last example. YAML is great but healthcare data is too complex for YAML. It is good to be able to use code when needed.
Example files
The above examples use the following FHIR source file and produce the openEHR composition.
FHIR source file
{
"resourceType": "Observation",
"id": "blood-pressure",
"meta": {
"profile": [
"http://hl7.org/fhir/StructureDefinition/vitalsigns"
]
},
"text": {
"status": "generated"
},
"identifier": [
{
"system": "urn:ietf:rfc:3986",
"value": "urn:uuid:187e0c12-8dd2-67e2-99b2-bf273c878281"
}
],
"basedOn": [
{
"identifier": {
"system": "https://acme.org/identifiers",
"value": "1234"
}
}
],
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}
]
}
],
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "85354-9",
"display": "Blood pressure panel with all children optional"
}
],
"text": "Blood pressure systolic & diastolic"
},
"subject": {
"reference": "Patient/example"
},
"effectiveDateTime": "2012-09-17",
"performer": [
{
"reference": "Practitioner/example"
}
],
"interpretation": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"code": "L",
"display": "low"
}
],
"text": "Below low normal"
}
],
"bodySite": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "368209003",
"display": "Right arm"
}
]
},
"component": [
{
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8480-6",
"display": "Systolic blood pressure"
},
{
"system": "http://snomed.info/sct",
"code": "271649006",
"display": "Systolic blood pressure"
},
{
"system": "http://acme.org/devices/clinical-codes",
"code": "bp-s",
"display": "Systolic Blood pressure"
}
]
},
"valueQuantity": {
"value": 107,
"unit": "mmHg",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]"
},
"interpretation": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"code": "N",
"display": "normal"
}
],
"text": "Normal"
}
]
},
{
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8462-4",
"display": "Diastolic blood pressure"
}
]
},
"valueQuantity": {
"value": 60,
"unit": "mmHg",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]"
},
"interpretation": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"code": "L",
"display": "low"
}
],
"text": "Below low normal"
}
]
}
]
}
openEHR composition
{
"_type": "COMPOSITION",
"uid": {
"_type": "OBJECT_VERSION_ID",
"value": "2e3718ca-daac-4836-82cd-4ee1067a7688"
},
"archetype_node_id": "id1",
"name": {
"_type": "DV_TEXT",
"value": "Encounter"
},
"archetype_details": {
"_type": "ARCHETYPED",
"archetype_id": {
"_type": "ARCHETYPE_ID",
"value": "openEHR-EHR-COMPOSITION.encounter.v1"
},
"template_id": {
"_type": "TEMPLATE_ID",
"value": "openEHR-EHR-COMPOSITION.simple-encounter-blood-pressure.v0.0.1"
},
"rm_version": "1.1.0"
},
"language": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "ISO_639-1"
},
"code_string": "en"
},
"territory": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "iso_3166-1-alpha2"
},
"code_string": "US"
},
"category": {
"_type": "DV_CODED_TEXT",
"value": "persistent",
"defining_code": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "iso_3166-1-alpha2"
},
"code_string": "US"
}
},
"composer": {
"_type": "PARTY_IDENTIFIED",
"name": "Lorena Grimes",
"identifiers": [
{
"_type": "DV_IDENTIFIER",
"id": "2e3718ca-daac-4836-82cd-4ee1067a7688"
}
]
},
"context": {
"_type": "EVENT_CONTEXT",
"start_time": {
"_type": "DV_DATE_TIME",
"value": "2012-09-17"
}
},
"content": [
{
"_type": "OBSERVATION",
"archetype_node_id": "openEHR-EHR-OBSERVATION.blood_pressure.v2",
"name": {
"_type": "DV_TEXT",
"value": "blood_pressure"
},
"archetype_details": {
"_type": "ARCHETYPED",
"archetype_id": {
"_type": "ARCHETYPE_ID",
"value": "openEHR-EHR-OBSERVATION.blood_pressure.v2"
},
"rm_version": "1.1.0"
},
"language": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "ISO_639-1"
},
"code_string": "en"
},
"subject": {
"_type": "PARTY_IDENTIFIED",
"name": "Lorena Grimes"
},
"protocol": {
"_type": "ITEM_TREE",
"archetype_node_id": "id12",
"name": {
"_type": "DV_TEXT",
"value": "Tree"
},
"items": [
{
"_type": "ELEMENT",
"archetype_node_id": "id15",
"name": {
"_type": "DV_TEXT",
"value": "Location of measurement"
},
"value": {
"_type": "DV_CODED_TEXT",
"value": "Right arm",
"defining_code": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "SNOMED-CT"
},
"code_string": "368209003"
}
}
}
]
},
"data": {
"_type": "HISTORY",
"archetype_node_id": "id2",
"name": {
"_type": "DV_TEXT",
"value": "History"
},
"origin": {
"_type": "DV_DATE_TIME",
"value": "2012-09-17"
},
"events": [
{
"_type": "POINT_EVENT",
"archetype_node_id": "id7",
"name": {
"_type": "DV_TEXT",
"value": "Any event"
},
"time": {
"_type": "DV_DATE_TIME",
"value": "2012-09-17"
},
"data": {
"_type": "ITEM_TREE",
"archetype_node_id": "id4",
"name": {
"_type": "DV_TEXT",
"value": "Data"
},
"items": [
{
"_type": "ELEMENT",
"archetype_node_id": "id5",
"name": {
"_type": "DV_TEXT",
"value": "Systolic"
},
"value": {
"_type": "DV_QUANTITY",
"magnitude": 107.0,
"property": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "SNOMED-CT"
},
"code_string": "http://snomed.info/id/259018001"
},
"units": "mm[Hg]",
"precision": 0
}
},
{
"_type": "ELEMENT",
"archetype_node_id": "id6",
"name": {
"_type": "DV_TEXT",
"value": "Diastolic"
},
"value": {
"_type": "DV_QUANTITY",
"magnitude": 60.0,
"property": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "SNOMED-CT"
},
"code_string": "http://snomed.info/id/259018001"
},
"units": "mm[Hg]",
"precision": 0
}
},
{
"_type": "ELEMENT",
"archetype_node_id": "id1060",
"name": {
"_type": "DV_TEXT",
"value": "Clinical interpretation"
},
"value": {
"_type": "DV_CODED_TEXT",
"value": "Below low normal",
"defining_code": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation"
},
"code_string": "L"
}
}
}
]
}
}
]
}
}
]
}