Series by Stefan Spiska, Senior Backend Developer, PEN, vitagroup HIP
Part 3 of the series "Vendor neutral interoperability with openEHR and HL7 FHIR"
Previous examples handled the management of non medical data in HIP.
Let’s jump to the management of medical data with the example of creating and and in a later part searching a blood pressure in HIP.
Let’s assume that a hospital has developed an app on the HIP where patients in follow up care can document their blood pressure measurements. The app sends this data as an HL7 FHIR Observation to HIP:
{
"resourceType" : "Observation",
"id" : "blood-pressure",
"meta" : {
"profile" : ["http://hl7.org/fhir/StructureDefinition/vitalsigns"]
},
"identifier" : [{
"system" : "urn:ietf:rfc:3986",
"value" : "urn:uuid:187e0c12-8dd2-67e2-99b2-bf273c878281"
}],
"status" : "final",
"category" : [{
"coding" : [{
"system" : "http://terminology.hl7.org/CodeSystem/observation-category",
"code" : "vital-signs",
"display" : "Vital Signs"
}]
},
{
"coding" : [{
"system" : "http://loinc.org",
"code" : "85354-9",
"display" : "Blood pressure panel with all children optional"
}]
}],
"code" : {
"coding" : [{
"system" : "http://loinc.org",
"code" : "85354-9",
"display" : "Blood pressure panel with all children optional"
}],
"text" : "Blood pressure systolic & diastolic"
},
"subject" : {
"reference" : "https://hip.org/Patient/patient-example-01"
},
"effectiveDateTime" : "2012-09-17"
"component" : [{
"code" : {
"coding" : [{
"system" : "http://loinc.org",
"code" : "8480-6",
"display" : "Systolic blood pressure"
}
},
"valueQuantity" : {
"value" : 107,
"unit" : "mmHg",
"system" : "http://unitsofmeasure.org",
"code" : "mm[Hg]"
}
},
{
"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]"
},
}
openEHR, FHIR – Semantic binding
HIP uses EHRbase for data storage which implements the openEHR standard. The biggest difference between openEHR and FHIR is how the semantic bindings are done.
If one looks at the FHIR definition we get a syntactic structure in the form of an OBSERVATION which contains a list of components which in this case contains a Quantity. But only when we look at the coding and see a LOINC code of 8462-4 then we know from a semantic standpoint that value Quantity represent the diastolic blood pressure.
In openEHR on the other side we have archetypes which represent a specific medical domain object like a
blood pressure:
The important part here is that this is a maximal data set created by medical professionals, ensuring consistency and completeness of clinical data across systems and use-cases.
Creating an openEHR Template
To integrate FHIR Observation data into HIP, one can use the openEHR Archetype Designer to create a Template (demo-mapping.ehrbase.org.v0).
- The template acts as a structured clinical model, combining multiple openEHR Archetypes.
- One can disable elements based on what data are needed (e.g., just systolic and diastolic values).
- Since a template is a collection of constraints on archetypes, interoperability between different templates is ensured.
Mapping FHIR to openEHR Using PSPL
Similar to the HL7 v2 example in the previous part, one can use PSPL to define mappings between FHIR and openEHR.
For example now on can easily define the mappings for the component with code 8480-6 to the path /data[at0001]/events[at0006]/data[at0003]/items[at0004]/value in the archetype openEHR-EHR-OBSERVATION.blood_pressure.v2 which will contain a Dv_Quantity which represents the systolic blood pressure.
{
"storageClass": "DvQuantity",
"storagePath": "/content[openEHR-EHR-OBSERVATION.blood_pressure.v2]/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value",
"attributes": {
"magnitude": {
"type": "rw",
"dataType": "Double",
"sourcePath": "component[isList: true, where: 'code.coding[isList: true].code=\"8480-6\"'].valueQuantity.value"
},
"units": {
"type": "rw",
"dataType": "String",
"sourcePath": "component[isList: true, where: 'code.coding[isList: true].code=\"8480-6\"'].valueQuantity.code"
},
"unitsSystem": {
"type": "rw",
"dataType": "String",
"sourcePath": "component[isList: true, where: 'code.coding[isList: true].code=\"8480-6\"'].valueQuantity.system"
},
"unitsDisplayName": {
"type": "rw",
"dataType": "String",
"sourcePath": "component[isList: true, where: 'code.coding[isList: true].code=\"8480-6\"'].valueQuantity.unit"
}
}
}
By applying similar mappings for all components, the FHIR OBSERVATION can be transformed into an openEHR COMPOSITION (the root class of each document) based on the template.
{
"_type": "COMPOSITION",
"name": {
"_type": "DV_TEXT",
"value": "demo-mapping.ehrbase.org.v0"
},
"archetype_details": {
"archetype_id": {
"value": "openEHR-EHR-COMPOSITION.report.v1"
},
"template_id": {
"value": "demo-mapping.ehrbase.org.v0"
},
"rm_version": "1.0.4"
},
"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"
},
"code_string": "DE"
},
"category": {
"_type": "DV_CODED_TEXT",
"value": "event",
"defining_code": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "openehr"
},
"code_string": "433"
}
},
"composer": {
"_type": "PARTY_IDENTIFIED",
"name": "Max Mustermann"
},
"context": {
"_type": "EVENT_CONTEXT",
"start_time": {
"_type": "DV_DATE_TIME",
"value": "2012-09-17T00:00:00"
},
"setting": {
"_type": "DV_CODED_TEXT",
"value": "home",
"defining_code": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "openehr"
},
"code_string": "225"
}
}
},
"content": [
{
"_type": "OBSERVATION",
"name": {
"_type": "DV_CODED_TEXT",
"value": "Blood pressure panel with all children optional",
"defining_code": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "http://loinc.org"
},
"code_string": "8480-6"
}
},
"archetype_details": {
"archetype_id": {
"value": "openEHR-EHR-OBSERVATION.blood_pressure.v2"
},
"rm_version": "1.0.4"
},
"language": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "ISO_639-1"
},
"code_string": "en"
},
"encoding": {
"_type": "CODE_PHRASE",
"terminology_id": {
"_type": "TERMINOLOGY_ID",
"value": "IANA_character-sets"
},
"code_string": "ISO-10646-UTF-1"
},
"subject": {
"_type": "PARTY_SELF"
},
"data": {
"name": {
"_type": "DV_TEXT",
"value": "History"
},
"origin": {
"_type": "DV_DATE_TIME",
"value": "2012-09-17T00:00:00"
},
"events": [
{
"_type": "POINT_EVENT",
"name": {
"_type": "DV_TEXT",
"value": "Any event"
},
"time": {
"_type": "DV_DATE_TIME",
"value": "2012-09-17T00:00:00"
},
"data": {
"_type": "ITEM_TREE",
"name": {
"_type": "DV_TEXT",
"value": "blood pressure"
},
"items": [
{
"_type": "ELEMENT",
"name": {
"_type": "DV_TEXT",
"value": "Systolic"
},
"value": {
"_type": "DV_QUANTITY",
"units": "mm[Hg]",
"magnitude": 107
},
"archetype_node_id": "at0004"
},
{
"_type": "ELEMENT",
"name": {
"_type": "DV_TEXT",
"value": "Diastolic"
},
"value": {
"_type": "DV_QUANTITY",
"units": "mm[Hg]",
"magnitude": 60.0
},
"archetype_node_id": "at0005"
}
],
"archetype_node_id": "at0003"
},
"archetype_node_id": "at0006"
}
],
"archetype_node_id": "at0001"
},
"archetype_node_id": "openEHR-EHR-OBSERVATION.blood_pressure.v2"
}
],
"archetype_node_id": "openEHR-EHR-COMPOSITION.report.v1"
}
Thus similar to the case of the ADT message in the previous part whenever a FHIR Observation is sent from the App, a Composition with template demo-mapping.ehrbase.org.v0 is created in EHRbase for the patient which is using the app.
Dealing with encounter
There is a small thing which remains. In FHIR the encounter for an observation is represented by a reference
{
...
"encounter":{
"reference" : "https://hip.org//74aa52fc-6aff-46aa-9848-e37b01335411"
}
}
In openEhr, Compositions can be put into a virtual directory to represent logical ordering of things:
So the HIP-Bridge will for each encounter create a Folder and put the Composition in the folder representing the Encounter. We will see in the next part how to use this to quickly query for all Compositions in an Encounter.
Top comments (0)