.. _terminology: Terminology =========== .. note:: Access to a terminology service within openFHIR Engine is not included in the standard license. To check if you have access to it, you can check if you license.json includes "terminology" within "options". Alternatively, you can do an ``HTTP GET /terminology/fhir/ConceptMap/123`` to verify it. An important aspect to successful mappings between FHIR and openEHR is a service able to translate codings, enums, terminologies from one to the other. In many cases, something coded in openEHR is entirely different than what's coded in FHIR. Similarly and perhaps even more problematic is when something coded in openEHR is of value not allowed in FHIR (i.e. Observation.status, where in FHIR is has fixed pre-defined values). For this purpose, openFHIR Engine has a built-in terminology service according to https://build.fhir.org/terminology-service.html Implementation Guide. It works with FHIR ConceptMaps to translate from one coding to another. Simplified inline translations ------------------------------ For simple inline translations (i.e. of enums), alternative to ConceptMap is providing a translation inline within model mappers. .. note:: This is outside the scope of standard FHIR Connect specification (.model json schema). Additional properties (terminology section) is an extension to the standard json schema. Example of an inline terminology mapping: :: - name: "status" with: fhir: "$resource.status" openehr: "$archetype/data[at0001]/items[at0004]" type: "TEXT" terminology: type: "inline" mappings: - openehr: # openEhrFinal value from an openEHR Composition will be translated to "final" when doing a mapping to FHIR code: "openEhrFinal" fhir: code: "final" # final value from an FHIR Resource will be translated to "openEhrFinal" when doing a mapping to an openEHR Composition - openehr: code: "openEhrPreliminary" fhir: code: "preliminary" - openehr: code: "openEhrActive" fhir: code: "active" Terminology service ------------------- For more complex translations of Codings, CodeableConcepts, .. embedded (or remote, see: :ref:`integrations`) terminology service can be used. .. note:: This is outside the scope of standard FHIR Connect specification (.model json schema). Additional properties (terminology section) is an extension to the standard json schema. Example of an terminology mapping: :: - name: "type of directive" with: fhir: "$resource.category.coding" openehr: "$archetype/data[at0001]/items[at0005]" type: "CODING" condition: targetRoot: "$resource.category.coding" targetAttribute: "system" operator: "one of" criteria: "urn:oid:2.16.840.1.113883.2.4.3.11.60.40.4.14.1" terminology: # can be here within a specific mapping or even above in the header of the model.yml, in which case all CODEABLECONCEPT and CODING will try to be translated type: "local" # local means that the embedded openFHIR terminology service will be used; alternatively, 'remote' is possible, see Integrations conceptmap: "http://url-of-the-conceptmap" # ConceptMap.url How a ConceptMap is resolved ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A translation is looked up in the ConceptMap's ``group`` elements as follows: 1. Groups whose ``source`` equals the coding's system (and whose ``target`` equals the requested target system, when one is given) are used, and only those. This is why a bidirectional ConceptMap — one group per direction — translates correctly even when a code is spelled the same on both sides and only the system differs: the direction is decided by the system, never by which group happens to contain the code. 2. Only when no group matches by system is the code looked up in every group. This is the fallback for callers that have no system at all (a plain string or an enum), so keep ``group.source`` / ``group.target`` filled in with the exact system URIs (for openEHR, the ``terminology_id`` as written in the template) whenever you can. 3. Within the selected groups the first ``element`` whose ``code`` equals the coding's code — or is ``"*"`` — provides the target. If nothing matches, the group's ``unmapped`` is honoured for the modes ``other-map`` (the translation continues in the referenced ConceptMap) and ``fixed``. An element with ``code: "*"`` and a target with ``code: "*"`` means *keep the code, change only the system* — the idiom for open code sets such as ATC or ICD-10, where the openEHR ``terminology_id`` and the FHIR ``system`` differ but the codes are the same. The translated coding then carries the source coding's display, when the caller passed one (``$translate`` with a ``sourceCoding`` does; a ``sourceCode`` + ``system`` pair has no display to carry). Translating into a FHIR enumeration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A FHIR *enumeration* — a ``code`` element such as ``MedicationAdministration.status`` or ``Consent.provision.type`` — carries no display of its own, so on the ``$toopenehr`` leg the openEHR ``|value`` is taken from the translation, in this order: 1. the **display** of the matched mapping or ConceptMap target, when it has one; 2. otherwise the **translated code**; 3. otherwise the enumeration's own value, when nothing was translated at all. So give the openEHR side of such a translation a ``display`` whenever the element has a rubric: a ConceptMap target of ``{"code": "532", "display": "completed"}`` yields ``|code: 532`` and ``|value: completed``, which is what an operational template expects. Without a display the code is written to both, which is still a valid coded text — what never happens is the untranslated FHIR token leaking into ``|value``. RESTful API ^^^^^^^^^^^ .. note:: Main purpose of the RESTful API of the embedded terminology service is to validate it's state and how it does the translations. When you want to use this terminology as part of the mappings, you don't need to use this RESTful API (apart from state configuration). All you need to do is reference ConceptMap from mappings (following the 'terminology' section syntax described above). .. http:post:: /terminology/fhir/ConceptMap Create a new ConceptMap that will be available for mappings. Alternative to POSTing it like this is to create it as part of the Bootstrapping, see :ref:`state-configuration` Responds with ``201`` and the stored ConceptMap (with its assigned ``id``) as ``application/fhir+json``; the ``Location`` header points at it. ``ConceptMap.url`` must be unique within the tenant — posting one that already exists returns ``400``, update it with ``PUT`` instead. There is no size limit on the ConceptMap beyond the request size the engine accepts. **Example request**: .. sourcecode:: http POST /terminology/fhir/ConceptMap HTTP/1.1 Content-Type: application/json Body: .. http:put:: /terminology/fhir/ConceptMap/(id) Update an existing ConceptMap. Responds with ``200`` and the stored ConceptMap as ``application/fhir+json``. **Example request**: .. sourcecode:: http PUT /terminology/fhir/ConceptMap/123 HTTP/1.1 Content-Type: application/json Body: .. http:get:: /terminology/fhir/ConceptMap/(id) Read an existing ConceptMap. **Example request**: .. sourcecode:: http GET /terminology/fhir/ConceptMap/123 HTTP/1.1 Accept: application/json .. http:get:: /terminology/fhir/ConceptMap?url= Search an existing ConceptMap by ConceptMap.url. **Example request**: .. sourcecode:: http GET /terminology/fhir/ConceptMap?url=http123 HTTP/1.1 Accept: application/json .. http:post:: /terminology/fhir/ConceptMap/(id)/$translate Translate coding using a specific ConceptMap **Example request**: .. sourcecode:: http POST /terminology/fhir/ConceptMap/123/$translate HTTP/1.1 Content-Type: application/json Body: { "resourceType": "Parameters", "parameter": [ { "name": "sourceCode", #can also be sourceCoding or sourceCodeableConcept with the corresponding value type underneath "valueCode": "female" }, { "name": "system", "valueUri": "http://123.com" }, { "name": "targetSystem", "valueUri": "http://456.com" } ] } .. http:post:: /terminology/fhir/ConceptMap/$translate Translate coding using ConceptMap **Example request**: .. sourcecode:: http POST /terminology/fhir/ConceptMap/123/$translate HTTP/1.1 Content-Type: application/json Body: { "resourceType": "Parameters", "parameter": [ { "name": "sourceCode", #can also be sourceCoding or sourceCodeableConcept with the corresponding value type underneath "valueCode": "female" }, { "name": "url", "valueUri": "http://123.com" # ConceptMap.url that should be used }, { "name": "system", "valueUri": "http://123.com" }, { "name": "targetSystem", "valueUri": "http://456.com" } ] } ConceptMap ^^^^^^^^^^ Example ConceptMap Note: when element.code is * and element.target.code is *, it means it will only translate systems. .. code-block:: json { "resourceType": "ConceptMap", "url": "http://hl7.org/fhir/ConceptMap/101", "group": [ { "source": "OID: 2.16.840.1.113883.2.4.3.11.60.40.2.7.15.1", "target": "urn:oid:2.16.840.1.113883.2.4.3.11.60.40.4.14.1", "element": [ { "code": "*", "target": [ { "code": "*" } ] } ] }, { "source": "urn:oid:2.16.840.1.113883.2.4.3.11.60.40.4.14.1", "target": "OID: 2.16.840.1.113883.2.4.3.11.60.40.2.7.15.1", "element": [ { "code": "*", "target": [ { "code": "*", "equivalence": "equivalent" } ] } ] }, { "source": "http://snomed.info/sct", "target": "http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.40.2.2.1.1--20171231000000", "element": [ { "code": "*", "target": [ { "code": "*", "equivalence": "equivalent" } ] } ] }, { "source": "http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.40.2.2.1.1--20171231000000", "target": "http://snomed.info/sct", "element": [ { "code": "*", "target": [ { "code": "*", "equivalence": "equivalent" } ] } ] }, { "source": "http://clinical.vitaly.parsek.com/codeable-concept/questionnaire-question-answer-code-simple", "target": "", "element": [ { "code": "0", "target": [ { "code": "at0005", "equivalence": "equivalent" } ] }, { "code": "10", "target": [ { "code": "at0006", "equivalence": "equivalent" } ] }, { "code": "20", "target": [ { "code": "at0007", "equivalence": "equivalent" } ] }, { "code": "30", "target": [ { "code": "at0008", "equivalence": "equivalent" } ] }, { "code": "40", "target": [ { "code": "at0009", "equivalence": "equivalent" } ] }, { "code": "50", "target": [ { "code": "at0010", "equivalence": "equivalent" } ] }, { "code": "60", "target": [ { "code": "at0011", "equivalence": "equivalent" } ] }, { "code": "70", "target": [ { "code": "at0012", "equivalence": "equivalent" } ] }, { "code": "80", "target": [ { "code": "at0013", "equivalence": "equivalent" } ] }, { "code": "90", "target": [ { "code": "at0014", "equivalence": "equivalent" } ] }, { "code": "100", "target": [ { "code": "at0015", "equivalence": "equivalent" } ] } ] } ] }