ConceptMap terminology
Note
A whole tutorial referenced here is available in the following Postman collection: https://documenter.getpostman.com/view/1515623/2sBXcLex64
Note
If running against a public sandbox.open-fhir.com, don’t forget to include Authorization header of type Basic.
A more complex terminology mapping than described in Inline terminology can be provided with ConceptMaps.
openFHIR Engine has a full terminology service built-in. That terminology service provides a $translate operation as per the FHIR IG of the terminology service (https://build.fhir.org/terminology-service.html).
The ConceptMap below externalizes the same translation we defined inline in the previous step — between the openEHR
body_weight archetype’s local State of dress codes and the Vital Signs with Qualifying Elements IG’s Body Weight Associated Situation value set — but now covering
the whole value set in both directions (SNOMED CT plus the IG’s Solor temporary code system), including an
unmapped fallback.
Add ConceptMap
- POST http://localhost:8080/terminology/fhir/ConceptMap
POST http://localhost:8080/terminology/fhir/ConceptMap HTTP/1.1 Content-Type: application/json Body: { "resourceType": "ConceptMap", "id": "e1a90f56-4773-44a6-8004-0ef178ce85a8", "url": "http://open-fhir.com/DemoConceptmap", "name": "StateOfDressToAssociatedSituation", "status": "active", "description": "Maps the openEHR body_weight archetype's local 'State of dress' codes to the codes of the Body Weight Associated Situation value set of the HL7 'Vital Signs with Qualifying Elements' IG (SNOMED CT and the Solor temporary code system), and back.", "group": [ { "source": "local", "target": "http://snomed.info/sct", "element": [ { "code": "at0013", "display": "Naked", "target": [ { "code": "248160001", "display": "Undressed", "equivalence": "equivalent" } ] }, { "code": "at0011", "display": "Lightly clothed/underwear", "target": [ { "code": "248160001", "display": "Undressed", "equivalence": "inexact", "comment": "The value set has no 'underwear only' concept; 'Undressed' is the closest code for interpreting a weight measurement." } ] }, { "code": "at0017", "display": "Nappy/diaper", "target": [ { "code": "248160001", "display": "Undressed", "equivalence": "inexact", "comment": "The value set has no 'diaper only' concept; 'Undressed' is the closest code for interpreting a weight measurement." } ] } ], "unmapped": { "mode": "fixed", "code": "248160001", "display": "Undressed" } }, { "source": "local", "target": "http://hl7.org/fhir/us/vitals/CodeSystem/SolorTemporaryCodeSystem", "element": [ { "code": "at0010", "display": "Fully clothed, including shoes", "target": [ { "code": "971000205103", "display": "Wearing street clothes with shoes (finding)", "equivalence": "equivalent" } ] } ] }, { "source": "http://snomed.info/sct", "target": "local", "element": [ { "code": "248160001", "display": "Undressed", "target": [ { "code": "at0013", "display": "Naked", "equivalence": "narrower", "comment": "Infants are weighed naked, so the reverse translation defaults to at0013." } ] } ], "unmapped": { "mode": "fixed", "code": "at0013", "display": "Naked" } }, { "source": "http://hl7.org/fhir/us/vitals/CodeSystem/SolorTemporaryCodeSystem", "target": "local", "element": [ { "code": "971000205103", "display": "Wearing street clothes with shoes (finding)", "target": [ { "code": "at0010", "display": "Fully clothed, including shoes", "equivalence": "equivalent" } ] }, { "code": "961000205106", "display": "Wearing street clothes, no shoes (finding)", "target": [ { "code": "at0010", "display": "Fully clothed, including shoes", "equivalence": "inexact", "comment": "The archetype has no 'street clothes without shoes' concept; at0010 is the closest available state." } ] } ] } ] }
Reference ConceptMap in the model mapping
ConceptMap created above needs to be referenced from a specific model mapper so the engine picks it up when doing a translation from openEHR<>FHIR.
To do this, we replace the inline terminology tag from the previous step with a reference to the ConceptMap:
terminology:
type: "local" # local when using the embedded terminology service, could also be 'inline' and 'remote'
conceptmap: "http://open-fhir.com/DemoConceptmap" # concept map canonical URL as provided when creating it
Full payload when updating a model mapper should look like:
- PUT http://localhost:8080/fc/model/(id of the model mapper)
PUT http://localhost:8080/fc/model/(id) HTTP/1.1 Body: <yaml below>
grammar: FHIRConnect/v0.0.1
type: model
metadata:
name: "OBSERVATION.body_weight.v2"
version: 0.0.1a # version of this particular mapping
spec: # schema specific to the FHIRConnect v0.0.1 engine
system: FHIR
version: R4
openEhrConfig:
archetype: "openEHR-EHR-OBSERVATION.body_weight.v2"
fhirConfig:
structureDefinition: http://hl7.org/fhir/StructureDefinition/Observation
preprocessor:
hierarchy:
with:
fhir: "$resource"
openehr: "$archetype/data[at0002]/events[at0003]"
split:
fhir:
create: "resource"
fhirCondition:
targetRoot: "$resource"
targetAttribute: "code.coding.code"
operator: "one of"
criteria: "29463-7"
mappings:
- name: "weight"
with:
fhir: "$resource.value"
openehr: "$archetype/data[at0002]/events[at0003]/data[at0001]/items[at0004]"
- name: "category"
with:
fhir: "$resource.category.coding"
openehr: "$archetype"
manual:
- name: "vitalSignsCategory"
fhir:
- path: "code"
value: "vital-signs"
- path: "system"
value: "http://terminology.hl7.org/CodeSystem/observation-category"
- path: "display"
value: "Vital Signs"
- name: "time"
with:
fhir: "$resource.effective"
openehr: "$archetype/data[at0002]/events[at0003]/time"
- name: "status"
unidirectional: "openehr->fhir"
with:
fhir: "$resource"
openehr: "$archetype"
manual:
- name: "status"
fhir:
- path: "status"
value: "final"
- name: "profile"
unidirectional: "openehr->fhir"
with:
fhir: "$resource"
openehr: "$archetype"
manual:
- name: "profile"
fhir:
- path: "meta.profile"
value: "http://hl7.org/fhir/us/vitals/StructureDefinition/body-weight"
- name: "code"
unidirectional: "openehr->fhir"
with:
fhir: "$resource.code.coding"
openehr: "$archetype"
manual:
- name: "code"
fhir:
- path: "code"
value: "29463-7"
- path: "system"
value: "http://loinc.org"
- path: "display"
value: "Body weight"
- name: "comment"
with:
fhir: "$resource.note.text"
openehr: "$archetype/data[at0002]/events[at0003]/data[at0001]/items[at0024]"
- name: "state of dress"
with:
fhir: "$resource.extension"
openehr: "$archetype/data[at0002]/events[at0003]/state[at0008]"
fhirCondition:
targetRoot: "$resource.extension"
targetAttribute: "url"
operator: "one of"
criteria: "http://hl7.org/fhir/us/vitals/StructureDefinition/AssociatedSituationExt"
terminology:
type: "local"
conceptmap: "http://open-fhir.com/DemoConceptmap"
followedBy:
mappings:
- name: "value"
with:
fhir: "value"
openehr: "items[at0009]"
- name: "url"
with:
fhir: "$fhirRoot"
openehr: "$archetype"
manual:
- name: "url"
fhir:
- path: "url"
value: "http://hl7.org/fhir/us/vitals/StructureDefinition/AssociatedSituationExt"
Testing the embedded terminology service
Use the same example of openEHR as in previous steps:
- POST http://localhost:8080/$tofhir?templateId=Growth chart&patient=Patient/lina-weber
POST http://localhost:8080/$tofhir?templateId=Growth%20chart&patient=Patient/lina-weber HTTP/1.1 Content-Type: application/fhir+json Body: <Parameters resource wrapping the flat json below>
Within the result, you should see the AssociatedSituation extension translated through the ConceptMap:
"extension": [
{
"url": "http://hl7.org/fhir/us/vitals/StructureDefinition/AssociatedSituationExt",
"valueCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "248160001",
"display": "Undressed"
}
],
"text": "Naked"
}
}
]
Going in the other direction ($toopenehr), the reverse groups of the ConceptMap translate SNOMED CT 248160001
back to a properly openEHR-coded at0013 in the resulting Composition:
"growth_chart/body_weight/any_event:0/state_of_dress|code": "at0013",
"growth_chart/body_weight/any_event:0/state_of_dress|terminology": "local"
You can also test what happens when providing a code that’s not within the ConceptMap. A fallback would be used with the “unmapped” entry of the ConceptMap, as per the ConceptMap’s purpose.