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: 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).

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 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:

POST /terminology/fhir/ConceptMap HTTP/1.1
Content-Type: application/json
Body: <FHIR ConceptMap json>
PUT /terminology/fhir/ConceptMap/(id)

Update an existing ConceptMap. Responds with 200 and the stored ConceptMap as application/fhir+json.

Example request:

PUT /terminology/fhir/ConceptMap/123 HTTP/1.1
Content-Type: application/json
Body: <FHIR ConceptMap json>
GET /terminology/fhir/ConceptMap/(id)

Read an existing ConceptMap.

Example request:

GET /terminology/fhir/ConceptMap/123 HTTP/1.1
Accept: application/json
GET /terminology/fhir/ConceptMap?url=

Search an existing ConceptMap by ConceptMap.url.

Example request:

GET /terminology/fhir/ConceptMap?url=http123 HTTP/1.1
Accept: application/json
POST /terminology/fhir/ConceptMap/(id)/$translate

Translate coding using a specific ConceptMap

Example request:

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"
      }
    ]
  }
POST /terminology/fhir/ConceptMap/$translate

Translate coding using ConceptMap

Example request:

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.

{
  "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"
            }
          ]
        }
      ]
    }
  ]
}