.. _fhirconnect: FHIR Connect ============ .. note:: Before creating or modifying mappings in OpenFHIR, users must familiarize themselves with the `FHIR Connect specification `_. Understanding OpenFHIR Mappings _______________________________ Importance of Proper Mapping in OpenFHIR ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ OpenFHIR is a powerful engine designed to process and transform healthcare data using FHIR (Fast Healthcare Interoperability Resources). However, for OpenFHIR to function correctly, it is essential that all mappings are defined according to the `FHIR Connect specification `_. Properly structured mappings ensure interoperability, consistency, and reliable data transformation within the OpenFHIR ecosystem. Why Adhering to FHIR Connect Specification Matters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The FHIR Connect specification establishes clear guidelines on how mappings should be structured to enable seamless data exchange. If mappings are not correctly expressed according to this specification, OpenFHIR may not operate as expected, leading to errors, incomplete transformations, or failed interoperability between systems. By following the FHIR Connect specification: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Data transformations are predictable and reliable. - Integration between different healthcare systems remains seamless. - The engine can properly interpret and execute mappings without errors. Engine-specific behaviour _________________________ Selecting a coding with ``fhirCondition`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A ``DV_CODED_TEXT`` can carry more than one code: its own ``defining_code`` and any number of TERM_MAPPINGs (``_mapping:N/target`` in the flat format). ``$tofhir`` parses all of them into one CodeableConcept, the element's own code first and the mapping targets after it. ``$toopenehr`` does the reverse, writing the first coding as the element's code and the rest as TERM_MAPPINGs. A ``fhirCondition`` whose ``targetRoot`` addresses those codings selects which of them cross to FHIR. In the ``$toopenehr`` direction it filters the codings the FHIR path yields, as any condition does; in the ``$tofhir`` direction it keeps only the codings that satisfy it. The same condition therefore reads the same way in both directions. The condition is coding-level when its ``targetRoot`` is * the mapped path's ``coding`` for a CodeableConcept target (``with.fhir: $resource.route`` and ``targetRoot: $resource.route.coding``), or * the mapped path itself for a Coding target (``with.fhir: $resource.route.coding`` and ``targetRoot: $resource.route.coding``). A condition anchored anywhere else (a sibling, a parent, an extension a child mapping writes) leaves the codings alone. The operators are ``one of`` and ``not of``; ``empty`` / ``not empty`` are presence gates and never select. Inside a ``followedBy`` block spell the ``targetRoot`` the way the child's own path is spelled, relative to the parent: a child ``with.fhir: route`` under a ``$resource.dosageInstruction`` parent takes ``targetRoot: route.coding``. A ``$resource.``-anchored targetRoot is resolved against the parent's path there, exactly like a ``$resource.``-anchored child path, so it would address a different element. The typical use is a source system that sends its local code with the standard equivalent as a TERM_MAPPING, while the target profile allows exactly one coding of the standard system. The flat composition carries .. code-block:: json { "ordination/medication_order/order:0/route|code": "3.0000", "ordination/medication_order/order:0/route|value": "Intravenös", "ordination/medication_order/order:0/route|terminology": "Cytodos", "ordination/medication_order/order:0/route/_mapping:0|match": "=", "ordination/medication_order/order:0/route/_mapping:0/target|code": "47625008", "ordination/medication_order/order:0/route/_mapping:0/target|terminology": "SNOMED-CT" } and the mapping picks the SNOMED coding: .. code-block:: yaml - name: "route" with: fhir: "$resource.dosageInstruction.route.coding" openehr: "$archetype/activities[at0001]/description[at0002]/items[at0091]" fhirCondition: targetRoot: "$resource.dosageInstruction.route.coding" targetAttribute: "system" operator: "one of" criteria: "http://snomed.info/sct" terminology: type: "local" conceptmap: "http://openfhir.com/ConceptMap/snomed-passthrough" The condition is evaluated **after** terminology translation, so its criteria are the FHIR systems that will actually be written. Here the ``"*"`` passthrough ConceptMap rewrites the openEHR terminology id ``SNOMED-CT`` to ``http://snomed.info/sct`` (see :doc:`terminology`); without a ConceptMap the coding keeps the openEHR terminology id as its ``system``, and the criteria has to name that instead. Selection is not a gate. When no coding satisfies the condition the codings are written unchanged and a debug line is logged — a mapping whose condition describes a value a child ``manual`` writes later (the usual bidirectional idiom for ``clinicalStatus`` and the like) keeps working as before. On the way back, ``$toopenehr`` sees only the codings that were written, so a many-to-one selection is lossy: the Cytodos code above is not recovered from the SNOMED coding unless a ConceptMap maps it back. Casts in ``with.fhir`` ^^^^^^^^^^^^^^^^^^^^^^ A cast to a FHIR type (``value.as(string)``) can be spelled any of these ways, and each one maps the same in both directions: * the FHIR spelling, ``as(string)``, ``as(dateTime)``, ``as(Quantity)``, or any other capitalisation (``as(String)``, ``as(quantity)``) * the ``FHIR`` namespace, ``as(FHIR.string)`` * the HAPI class name, ``as(StringType)`` * a FHIRPath System type, ``as(System.String)``, read as the FHIR type of the same name. A ``WARN`` is logged once, because elements of a more specific FHIR type (``code``, ``uri``, ``positiveInt``, ``instant`` …) are not selected by it ``ofType(...)`` is read the same way by ``$toopenehr``; ``$tofhir`` only creates elements through ``as(...)``. A type that does not exist in the mapper's FHIR version (``as(foo)``, ``as(integer64)`` in R4) builds nothing in ``$tofhir`` and logs a ``WARN`` naming the mapping and the path. Attributes of a data value in ``with.openehr`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ An openEHR path may end in one attribute of an element's data value instead of the element itself. The mapping then writes and reads only that attribute, as its flat pipe attribute (``…|magnitude``; on an element allowing several types, below its type, ``…/quantity_value|magnitude``), and ``$toaql`` compares that attribute. The attribute can be spelled any of these ways, and each one maps the same: * strict AQL through the element's ``value``: ``items[at0004]/value/magnitude``, ``items[at0130]/value/defining_code/code_string`` * without the element's ``value``: ``items[at0004]/units``, ``items[at0130]/defining_code/code_string``. A bare ``items[at0004]/value`` is the whole data value, not an attribute * an alias: ``defining_code`` or ``code_string`` for ``defining_code/code_string``, ``terminology_id`` for ``defining_code/terminology_id``, ``ordinal`` for a ``DV_ORDINAL``'s ``value``, ``media_type`` and ``uri`` for a ``DV_MULTIMEDIA``'s * the flat pipe attribute: ``items[at0130]/|code`` or ``items[at0130]|code`` * on a node that is a data value itself: ``null_flavour/defining_code/code_string``, ``ism_transition/current_state/value`` A ``DV_TEXT`` element also takes the attributes of a ``DV_CODED_TEXT``, which it may hold. The same spellings are accepted as an ``openehrCondition``'s ``targetAttribute``, relative to its ``targetRoot``. Spelled through the element's ``value``, an attribute none of the element's types has (``…/value/magnitude`` on a coded text) is a path that does not exist: the mapping is skipped with a warning. Sub-contexts (``slotContext``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A template can hold several nodes of the same archetype with a different meaning each - the International Patient Summary has four ``SECTION.adhoc.v1`` sections, each with its own FHIR section code, title and entries. Extensions are scoped per context mapper, so each such node gets a context mapper of its own, a *sub-context*, and the composition's context dispatches to them with ``slotContext``: .. code-block:: yaml - name: "sectionActiveProblems" with: fhir: "$resource.section" openehr: "$composition/content[openEHR-EHR-SECTION.adhoc.v1 and name/value='Problem List']" slotContext: "IPS_problem.context" fhirCondition: targetRoot: "$resource.section" targetAttribute: "code.coding.code" operator: "one of" criteria: "11450-4" ``slotContext`` sits where ``slotArchetype`` sits and names a context mapper (its ``metadata.name``) instead of a model mapper. The engine enters that context and maps from its ``start`` using that context's own ``archetypes`` and ``extensions``, into the parent composition. * the referencing context lists the sub-contexts it uses under ``context.contexts`` * a sub-context is an ordinary context mapper with ``context.scope: sub`` (``scope`` defaults to ``root``). It is never picked as the context of a request - neither by template id nor by profile - and is only entered via ``slotContext`` * sub-contexts are exempt from the one-context-per-template rule (several of them may declare the same ``template.id``); their ``metadata.name`` has to be unique instead * upload the sub-contexts and the model mappers before the context that references them, so the references can be checked Uploading is rejected when a mapping carries both ``slotArchetype`` and ``slotContext``, when a ``slotContext`` is not listed under ``contexts``, when ``contexts`` references are cyclic, or when a ``slotContext`` mapping uses ``openehr: $reference`` (a sub-context can not create a composition of its own). .. note:: Not validated yet: that the sub-context's ``template.id`` is the template embedded at the slot node, and that its ``start`` archetype matches the archetype in the slot's ``openehr`` path. Entering a ``scope: root`` context via ``slotContext`` is not supported.