diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..c1dd12f Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..b74bf7f --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..91e527a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at platform@better.care. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f49a4e1 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f08364f --- /dev/null +++ b/README.md @@ -0,0 +1,526 @@ +# Specification of FHIR Connect mapping files + +## Purpose + +This mapping file specification lays the foundation for + +- bi-directional +- and reusable + +mappings between openEHR and FHIR data. + +Its goal is it to create one common definition, to enable a community driven effort to collaboratively + +- build +- discuss and maintain +- create a globally shared library of + +mappings for commonly used modeling artifacts. + +This document specifies the concepts, structure and properties of such mappings. + +A reference implementation was created and is maintained by [Better](https://better.care). + +## Content + + + +* [Specification of FHIR Connect mapping files](#specification-of-fhir-connect-mapping-files) + * [Purpose](#purpose) + * [Content](#content) + * [1. Model Mappings](#1-model-mappings) + * [Foundations](#foundations) + * [Paths](#paths) + * [Variables](#variables) + * [Data Types](#data-types) + * [Condition](#condition) + * [Slots](#slots) + * [Dependent Mappings](#dependent-mappings) + * [Slot Targets](#slot-targets) + * [Class Mappings](#class-mappings) + * [FHIR Reference Mapping](#fhir-reference-mapping) + * [Structure](#structure) + * [Format](#format) + * [2. Contextual Mappings](#2-contextual-mappings) + * [Foundations](#foundations) + * [Coming from openEHR](#coming-from-openehr) + * [Coming from FHIR](#coming-from-fhir) + * [Overview](#overview) + * [Format](#format) + * [Examples & Schema Test Suite](#examples--schema-test-suite) + * [Implementation](#implementation) + * [Limitations](#limitations) + * [Future Work](#future-work) +* [Appendix](#appendix) + * [Tooling](#tooling) + * [Changelog](#changelog) + * [v0.1.0](#v010) + * [Added](#added) + * [Changed](#changed) + * [Removed](#removed) + * [v0.0.3](#v003) + * [Added](#added) + + +-------------------------------------------------------- + +Note: As a general specification decision, the following supported features, cases, types and so on are kept to a minimal set on purpose. +The idea is to add support step-by-step and maintain backwards-compatibility. + +There are two kinds of mapping files: + +1. Model Mappings +2. Contextual Mappings + +## 1. Model Mappings + +The Model Mappings are the core mappings, aiming at providing a translation from and to core artifacts of openEHR and FHIR. + +### Foundations + +Due to openEHR`s maximal modeling approach and the fact that openEHR data carries more metadata, the Model Mappings are written in the direction +"from FHIR to openEHR". The backwards direction is implicitly included. + +The mapping processing application is supposed to have access to the openEHR template, with the embedded Archetype definitions. +Thus, the scope's openEHR modeling information is available and mappings can be simpler than technically necessary (for instance, see [Path](#paths)). + +### Paths + +Pointing to source and result attributes happens with *paths*. +On the root level of a Model Mapping, paths are preceded with a context variable like `$fhirResource` and `$openEhrArchetype`. +Paths are always terminated with the node *before* the actual data type attribute, +i.e. pointing to the `DV_QUANTITY` container, rather than the `magnitude` attribute. + +The following paths are defined in +- openEHR: Custom path, derived by simplifying the FLAT format and using the common separator (`.`) + - Example: `"$openEhrArchetype.blood_pressure.any_event.systolic"` +- FHIR: Simple [FHIRPath](https://hl7.org/fhirpath/#path-selection) compatible selection paths + - Example: `"$fhirResource.component.value"` + +openEHR paths omit cardinality information to simplify the mapping data. +For instance, the following valid FLAT path `xyz.blood_pressure/any_event:0/systolic|magnitude` +will be simplified to `xyz.blood_pressure.any_event.systolic`. +This simplification makes use of having the modeling information (in form of Templates) available at runtime. +(Additionally, this example makes use of Data Type simplification, as explained in [Data Types](#data-types)). + +The path element separator is always a single, simple `.`. + +### Variables + +Modeling variables/references are required to create reusable paths: + +| Variable | Description | +|---------------------|------------------------------------------------------------------------------------------| +| `$fhirResource` | FHIR object at root, if Resource | +| `$fhirRoot` | FHIR object at root, if no Resource | +| `$openEhrArchetype` | openEHR placeholder marking the beginning of the context's Archetype | +| `$reference` | Helper to indicate a skipped path, due to a [Reference Mapping](#fhir-reference-mapping) | + +It is also possible to ask the implementation to provide further information from the execution context. +Here, the openEHR execution environment - with `$openEhrContext` as root and available with each of the following attached after a separator: + +| openEHR Context | Description | +|-----------------|-----------------------------| +| `$ehr` | EHR ID of request's context | + +Context variables are meant to be of primitive type only, i.e. natively transformable to String, to avoid cross-type compatibility problems. + +Future versions of this spec could reflect support for further data like: language, committer, subject and so on. + +Terminologies and coding systems can be references by: + +| System | Identifier | +|--------|------------| +| SNOMED | `$snomed` | +| LOINC | `$loinc` | + +Currently, no algorithmic difference is made by this choice. This information aids the authoring of the mappings only, as of now. +This will be needed for the advanced terminology support though (see [Future Work](#future-work)). + +### Data Types + +Data types, such as `DV_QUANTITY`, `DV_TEXT` and so on, and their FHIR counterparts (`Quantity`, `string`, ...) are handled implicitly, +**except** the need to set the type for a mapping with `with.type`. + +The internal mapping of data types: + +| Type ID / FHIR | openEHR | Primitive | "FLAT / FHIR" Attributes Pairs | +|-----------------|---------------|-----------|------------------------------------------| +| NONE | NONE | false | / | +| QUANTITY | DV_QUANTITY | true | magnitude / value
unit / unit | +| DATETIME | DV_DATE_TIME | true | (direct) | +| CODEABLECONCEPT | DV_CODED_TEXT | false | **_nested_** / coding
value / text | +| CODING | CODE_PHRASE | true | code / code
terminology / system | +| STRING | DV_TEXT | true | (direct) | +| DOSAGE | NONE | false | (special) | + +The `nested` keyword indicates, a non-primitive case, where the final resulting structure is a nested structure, composed of multiple types. + +A simple `QUANTITY` example is illustrated in the following code block. +It implicitly maps the matching attributes (openEHR FLAT: `magnitude`, `value`; FHIR: `value`, `unit`) too. + +```yaml +- name: "height" + with: + fhir: "$fhirResource.value" + openehr: "$openEhrArchetype.height_length.any_event.height_length" + type: "QUANTITY" +``` + +`DOSAGE` is a one of the few ["special purpose data types"](http://hl7.org/fhir/datatypes.html) in FHIR and needs separate handling. +Currently, it is the first custom class data type. +As special type it needs to be declared with one extra step, to allow the mappings of its attributes (see formal description at [Class Mappings](#class-mappings)). +It can be utilized in the following way: + +```yaml +mappings: + - name: "dosage" + with: + fhir: "$fhirRoot" + openehr: "$openEhrArchetype" + type: "DOSAGE" + followedBy: + mappings: + - name: "doseQuantityValue" + with: + fhir: "$fhirRoot.doseAndRate.dose" + openehr: "$openEhrArchetype.dose_amount.quantity_value" + type: "QUANTITY" +``` + +### Condition + +The `condition` structure is used to find the correct node within ambiguous lists. It also is used to set one condition to `identifying: true`, which allows +to define the Condition to use in the evaluation of which Model Mapping matches the given FHIR Resource. +(openEHR data has the Archetype ID and needs no further evaluation.) + +Having one *identifying* `condition` for one Model Mapping is required. +This is not the case for [Dependent Mappings](#dependent-mappings). + +The condition's `targetRoot` path points to the root element, which this evaluation should find (matching the condition). +It most cases this root is a list. + +In contrast, the `targetAttribute` paths points to the attribute of the root objects the evaluation will be executed with. +This path is always a direct child-path of the root. + +The chosen `operator` defines the algorithmic behavior, when evaluating this condition. +Depending on the operator, a specific criteria can be set. +The available options are: + +| Name | Description | Criteria | +|----------|-----------------------------------------------------|-------------------------------------------| +| "one of" | Checks for existence of either one of the criteria. | Comma separated list, enclosed by `[...]` | + + +**Example**: + +```yaml +condition: + targetRoot: "$fhirResource.component" # Scope of object for 'condition' + targetAttribute: "code.coding.code" # Actual attribute to check for 'condition' + operator: "one of" + criteria: "[$loinc.8480-6, $snomed.271649006]" + identifying: true # The *one* condition able to identify FHIR input to match this mapping +``` + +A FHIR Resource like Observation can have multiple components, containing different data points. +This condition is used to look into each component, checking its coding, finally finding the matching one. + +Further, Conditions are only supported for Observations right now. +(While technically more types can be supported, this is a specification decision to add support step-by-step and keep backwards-compatibility.) + +### Slots + +In openEHR Slots are commonly used. A model mapping can utilize `slotArchetype` to configure a Slot. +The following example shows how a Slot can be described in a mapping. +The `type` on the root level of a Slot mapping needs to be `NONE`. +It is necessary to pipe down a FHIR object as the root object for the following step (the linked Archetype's mapping) with `with.fhir`. +In contrast, the openEHR path is basically predefined by the model and points to the actual place of the Slot. + +```yaml +# openEHR data is linking to another Archetype (Cluster Slot) +- name: "therapeutic-direction" + with: + fhir: "$fhirResource" # Only forwarding the root resource, as attributes will be handled on following mappings. + openehr: "$openEhrArchetype.medication_order.order.therapeutic_direction" + type: "NONE" + slotArchetype: "openEHR-EHR-CLUSTER.therapeutic_direction.v1" # acts as link to Model Mapping of given Archetype +``` + +### Dependent Mappings + +Sometimes the given openEHR model structure utilizes Archetypes with no direct FHIR Resource equivalent. +In those cases a "dependent" Model Mapping is required, which can never be a root level mapping itself. +They are identified by **not** having a `fhirConfig` section. + +Dependent mappings never can have a `condition`. + +The following **concrete** Dependent Mapping subclasses are existing: + +#### Slot Targets + +A Slot mapping can point to a normal, independent Model Mapping of the matching Archetype. +But it can also point to a *Slot Target*. +The following example illustrates a case, where the Archetype is used as intermediate link and forwards the mapping chain to another Slot mapping. + +```yaml +format: "0.1.0" +version: "0.0.1" + +# No fhirConfig, because this model has no direct Resource equivalent in FHIR. It can only be used in openEHR Archetype slots. + +openEhrConfig: + archetype: "openEHR-EHR-CLUSTER.therapeutic_direction.v1" + +mappings: + - name: "dosage" + with: + fhir: "$fhirResource.dosageInstruction" + openehr: "$openEhrArchetype.dosage" + type: "NONE" + slotArchetype: "openEHR-EHR-CLUSTER.dosage.v1" # acts as link to Model Mapping of given Archetype +``` + +Note: `with.fhir` utilized the Resource-scope `$fhirResource` variable, because the resource is piped down here, see [Slots](#slots). + +#### Class Mappings + +The Class Mapping is a another subclass of the Dependent Mapping. +It has no direct FHIR Resource equivalent, because it maps to a non-Resource-level FHIR class. +One example (as explained in the ["Data Types"](#data-types) section) is `DOSAGE`. + +The mapping is facilitated by defining a shadow root mapping with the special case class as type. +It is required to indicate the custom type at `type`. +Further, a semantically correct variable is available to reference the object's root: `$fhirRoot` +(in contrast to `$fhirResource`, because by definition this is not a FHIR Resource in this case). + +The following data points are mapped using `followedBy`. +The listed mappings are directly populating the class' attributes. +They never can contain a non-trivial mapping, like another Slot or Class mapping. + +A simple `DOSAGE` example would look like this: + +```yaml +format: "0.1.0" +version: "0.0.1" + +# No fhirConfig, because this model has no direct Resource equivalent in FHIR. It can only be used in openEHR Archetype slots or as FHIR Resource attribute. + +openEhrConfig: + archetype: "openEHR-EHR-CLUSTER.dosage.v1" + +mappings: + - name: "dosage" + with: + fhir: "$fhirRoot" + openehr: "$openEhrArchetype" + type: "DOSAGE" # Special FHIR type class + followedBy: + mappings: + - name: "doseQuantityValue" + with: + fhir: "doseAndRate.dose" + openehr: "$openEhrArchetype.dose_amount.quantity_value" + type: "QUANTITY" +``` + +#### FHIR Reference Mapping + +FHIR models often make use of [Resource References](https://www.hl7.org/fhir/references.html). +They can be mapped with the `reference` property. +Here, the `with.fhir` path indicates the path of the Reference in the root Resource. +In contrast, `with.openehr` is virtually ignored, because the path of the following Reference mapping will be considered. +To help the editorial process, a `$reference` helper can be used here. + +The `reference` structure contains the explicit Resource typing and a list of mappings. +Only one of those sub-mappings is allowed, to cancel out any ambiguity. +This structure results in a separate sub-Resource, which will be added to the Bundle and reference by the root Resource. +For convenience, the `alwaysBundled` property is used to set this Model Mapping's result as a Bundle, by definition. +(Also see [2. Contextual Mappings](#2-contextual-mappings) for more info on configuring the result scope.) + +```yaml +fhirConfig: + # For this Resource: http://hl7.org/fhir/medicationrequest.html + resource: "MedicationRequest" + # Convenience property to indicate at least one mapping directly works with a FHIR reference -> the resulting FHIR has to be a Bundle. + alwaysBundled: true +openEhrConfig: + # For this Archetype: https://ckm.openehr.org/ckm/archetypes/1013.1.5946 + archetype: "openEHR-EHR-INSTRUCTION.medication_order.v2" # Note: Is actually at v3 but the use-case has v2 + +mappings: + - name: "medication" + with: + fhir: "$fhirResource.medication" + openehr: "$reference" # the path of the following reference mapping will be used + type: "NONE" + # States the FHIR target/source is a Reference. + reference: + # Implicitly is a "direct" reference, i.e. creates ad-hoc sub-resource. It will be added to the Bundle and referenced in the root Resource. + resourceType: "Medication" + mappings: + - name: "medication-item" + with: + fhir: "code.text" + openehr: "$openEhrArchetype.medication_order.order.medication_item" + type: "STRING" +``` + +### Structure + +The exact format is defined in a JSON schema (see below). This section will give a descriptive overview of the structure. + +- `format` containing the current format's version. +- `version` the version of this particular mapping. +- `fhirConfig` defines the target and source FHIR Resource type. +- `openEhrConfig` defines the target and source openEHR Archetype. +- `mappings` is a list of mappings. + +Mapping instances consist of: + +- `name` is a descriptor for documentation purposes. +- `with` sets the target/source attributes for both openEHR and FHIR, as well as type. +- `condition` can contain a set of properties to describe the condition on which this mapping should be applied on. +- `followedBy` can contain a nested (recursive) list of further mappings, which shall be applied in the same context as the root one, without the need to + re-evaluate the condition again. Only applicable in the case of a special data type. +- `reference` can be used to define a reference mapping. It allows to map attributes to a separate FHIR resource, which will be referenced by the root resource. + (And vice versa) +- `slotArchetype` can specify the Slot Archetype. + +### Format + +[Model Mapping JSON Schema](src/test/resources/mapping-files-spec/v0-1-0/model-mapping.schema.json) + +File extension: `*.model.yml` or `*.model.yaml` + +-------------------------------------------------------- + +## 2. Contextual Mappings + +The main purpose of the Contextual Mapping is to make use of the Model Mappings in certain contexts. + +### Foundations + +#### Coming from openEHR + +The usual openEHR request to create a Composition carries all relevant (meta) data. +This includes the used Template and other useful information like the target EHR ID. + +The `templateId` property will be used to evaluate the matching Contextual Mapping. +Therefore, the payload doesn't have to match all the listed Archetypes (but in compliance with the Template constraints). + +#### Coming from FHIR + +There's a lack of metadata. In consequence, it is not directly known, for instance, what kind of clinical context a set of Resources in a Bundle represents. + +The list of `archetypes` will be used to evaluate the matching Contextual Mapping. In consequence, the FHIR input payload need to 100% match the given list. + +### Overview + +The exact format is defined in a JSON schema (see below). This section will give a descriptive overview of the structure. + +- `format` containing the current format's version. +- `openEHR` configures the openEHR context, i.e. Template and Archetypes. +- `fhir` configures the FHIR context, i.e. Resource type or Bundle. + +The `openEHR` configuration contains this context's Template ID and the list of root level Archetypes to consider. + +The `fhir` configuration sets the resulting type. This can either be a `Bundle` or any of the following single Resources: + +| Value | Resource Type | +|---------------------|-------------------| +| `Observation` | Observation | +| `MedicationRequest` | MedicationRequest | +| `Medication` | Medication | + + +### Format + +[Contextual Mapping JSON Schema](src/test/resources/mapping-files-spec/v0-1-0/contextual-mapping.schema.json) + +File extension: `*.context.yml` or `*.context.yaml` + +-------------------------------------------------------- + +## Examples & Schema Test Suite + +Please explore the test code and resources in this repository. +It contains a testing suite, evaluating multiple examples with the given schema definition - both for Model and Contextual Mappings. + +-------------------------------------------------------- + +## Implementation + +On top of the formal definition, further properties are expected by implementations: + +- Mapped attributes, which aren't present in the input, should be ignored. +If at least one mapping has an input value a valid result artifact is expected as output. +(Bidirectional, and for missing input for each direct mappings or data type parameters.) +- An implementation needs the functionality to load three kind of resources into its runtime: + - Model Mappings + - Context Mappings + - openEHR Templates + +-------------------------------------------------------- + +## Limitations + +- Cardinalities of path objects should be covered by structural and semantic context (e.g. Condition). +In cases of doubt the first element will be used. + +- Final attributes of data types (say, unit of Quantity) will be handled without any transformation in the primitive type (continuing with unit: String). +The input therefore has to provide the correct representation for the target. +For instance, a unit `mmHG` might be provided, while the target system (either FHIR or openEHR) might only work with `mm[Hg]`. +In those cases the input has to reflect the expected output already, for now. + +## Future Work + +- More data types: Depending on usage and community requirements, more data types should be supported and internal mappings added. +- Profiles and Implementation Guides: FHIR specialization artifacts will need to integration to some degree. +They carry more modeling- and meta-data. They should allow more fine-grained mappings and more simplifications. +The current spec covers vanilla Resources and thus specifies mapping for the use-cases with *less* additional data. +- Units: Units needs alignment, possibly as per UCUM. +- Terminology: Currently, the terminology/coding system information is only aiding the authoring of mappings. +- Community library: Kick-off community library of global and reusable mappings. + +-------------------------------------------------------- + +# Appendix + +## Tooling + +To utilize JSON schema validation and autocompletion within YAML files check out tooling like: + +- https://github.com/redhat-developer/vscode-yaml +- https://www.jetbrains.com/help/idea/json.html#ws_json_schema_add_custom + +## Changelog + +### v0.1.0 + +#### Added + +- Complete data type handling, based on `with.type` +- Simplification of condition FHIR paths + +#### Changed + +- Paths are now omitting data type attributes and instead point to parent node +- FHIRPath spec for the FHIR paths in the mappings + +### Removed + +- General purpose `followedBy` + +### v0.0.3 + +#### Added + +Model Mapping: + +- `version` 1..1 +- `fhirConfig.alwaysBundled` 0..1 +- `mappings[{reference}]` 0..1 +- With `reference` having `.resourceType` and `.mappings` +- `mappings[{slotArchetype}]` 0..1 +- `with.type` 1..1 \ No newline at end of file diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..8a8fb22 --- /dev/null +++ b/mvnw @@ -0,0 +1,316 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..1d8ab01 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,188 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e29eea4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.0.0 + + + care.better + fhir-connect-mapping-spec + 0.0.1-SNAPSHOT + fhir-connect-mapping-spec + fhir-connect-mapping-spec + + 17 + 1.7.21 + + + + org.springframework.boot + spring-boot-starter + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + + org.springframework.boot + spring-boot-starter-test + test + + + com.github.erosb + everit-json-schema + 1.14.1 + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/src/main/kotlin/care/better/fhirconnectmappingspec/FhirConnectMappingSpecApplication.kt b/src/main/kotlin/care/better/fhirconnectmappingspec/FhirConnectMappingSpecApplication.kt new file mode 100644 index 0000000..5a6e2e3 --- /dev/null +++ b/src/main/kotlin/care/better/fhirconnectmappingspec/FhirConnectMappingSpecApplication.kt @@ -0,0 +1,11 @@ +package care.better.fhirconnectmappingspec + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +class FhirConnectMappingSpecApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/src/test/kotlin/care/better/fhirconnectmappingspec/FhirConnectMappingSpecApplicationTests.kt b/src/test/kotlin/care/better/fhirconnectmappingspec/FhirConnectMappingSpecApplicationTests.kt new file mode 100644 index 0000000..f253eb6 --- /dev/null +++ b/src/test/kotlin/care/better/fhirconnectmappingspec/FhirConnectMappingSpecApplicationTests.kt @@ -0,0 +1,13 @@ +package care.better.fhirconnectmappingspec + +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +class FhirConnectMappingSpecApplicationTests { + + @Test + fun contextLoads() { + } + +} diff --git a/src/test/kotlin/care/better/fhirconnectmappingspec/MappingSpecTest.kt b/src/test/kotlin/care/better/fhirconnectmappingspec/MappingSpecTest.kt new file mode 100644 index 0000000..eb035fd --- /dev/null +++ b/src/test/kotlin/care/better/fhirconnectmappingspec/MappingSpecTest.kt @@ -0,0 +1,82 @@ +package care.better.fhirconnectmappingspec + +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import com.fasterxml.jackson.module.kotlin.KotlinModule +import org.everit.json.schema.ValidationException +import org.everit.json.schema.loader.SchemaLoader +import org.json.JSONObject +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import java.io.File + +/** + * Testing and keeping track of format schemas for Model and Contextual Mappings and their compliance. + */ +class MappingSpecTest { + private val mapper = ObjectMapper(YAMLFactory()).registerModule(KotlinModule()).enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + + companion object { + @JvmStatic + fun testModelMappingsV0_0_2(): Array = File(javaClass.classLoader.getResource("mapping-files-spec/v0-0-2/model-mappings").file).listFiles() + @JvmStatic + fun testContextualMappingsV0_0_2(): Array = File(javaClass.classLoader.getResource("mapping-files-spec/v0-0-2/contextual-mappings").file).listFiles() + + @JvmStatic + fun testModelMappingsV0_1_0(): Array = File(javaClass.classLoader.getResource("mapping-files-spec/v0-1-0/model-mappings").file).listFiles() + @JvmStatic + fun testContextualMappingsV0_1_0(): Array = File(javaClass.classLoader.getResource("mapping-files-spec/v0-1-0/contextual-mappings").file).listFiles() + } + + private val v0_0_2_modelMappingSchema = File(javaClass.classLoader.getResource("mapping-files-spec/v0-0-2/model-mapping.schema.json").file) + private val v0_0_2_contextualMappingSchema = File(javaClass.classLoader.getResource("mapping-files-spec/v0-0-2/contextual-mapping.schema.json").file) + + private val v0_1_0_modelMappingSchema = File(javaClass.classLoader.getResource("mapping-files-spec/v0-1-0/model-mapping.schema.json").file) + private val v0_1_0_contextualMappingSchema = File(javaClass.classLoader.getResource("mapping-files-spec/v0-1-0/contextual-mapping.schema.json").file) + + @ParameterizedTest(name = "Validate Model mapping {0}") + @MethodSource("testModelMappingsV0_0_2") + fun `validate Model Mapping with schema v0_0_2`(input: File) { + validate(v0_0_2_modelMappingSchema, input) + } + + @ParameterizedTest(name = "Validate Contextual mapping {0}") + @MethodSource("testContextualMappingsV0_0_2") + fun `validate Contextual Mapping with schema v0_0_2`(input: File) { + validate(v0_0_2_contextualMappingSchema, input) + } + + @ParameterizedTest(name = "Validate Model mapping {0}") + @MethodSource("testModelMappingsV0_1_0") + fun `validate Model Mapping with schema v0_1_0`(input: File) { + validate(v0_1_0_modelMappingSchema, input) + } + + @ParameterizedTest(name = "Validate Contextual mapping {0}") + @MethodSource("testContextualMappingsV0_1_0") + fun `validate Contextual Mapping with schema v0_1_0`(input: File) { + validate(v0_1_0_contextualMappingSchema, input) + } + + /* + * Utilizing JSON schema validator from https://github.com/everit-org/json-schema. + * So it is necessary to convert the mappings YAML to a JSON before validation. + */ + private fun validate(schemaFile: File, mappingFile: File) { + val rawSchema = JSONObject(schemaFile.readText()) + val schema = SchemaLoader.load(rawSchema) + val rawYamlObj = mapper.readValue(mappingFile.readText(), Any::class.java) + val jsonFromYaml = ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(rawYamlObj) + + val json = JSONObject(jsonFromYaml) + try { + schema.validate(json) // throws a ValidationException if this object is invalid + } catch (e: ValidationException) { + val causes = e.causingExceptions.map { it.message }.toList() + val debug = 0 + throw e + } + } + +} \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-0-2/contextual-mapping.schema.json b/src/test/resources/mapping-files-spec/v0-0-2/contextual-mapping.schema.json new file mode 100644 index 0000000..bde33df --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-0-2/contextual-mapping.schema.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Contextual Mapping schema for file format v0.0.2", + "title": "Contextual Mapping", + "type": "object", + "properties": { + "format": { + "type": "string" + }, + "openEHR": { + "$ref": "#/$defs/openEHR" + }, + "fhir": { + "$ref": "#/$defs/fhir" + } + }, + "required": [ + "fhir", + "format", + "openEHR" + ], + "additionalProperties": false, + "$defs": { + "fhir": { + "type": "object", + "additionalProperties": false, + "properties": { + "resourceType": { + "type": "string" + } + }, + "required": [ + "resourceType" + ], + "title": "FHIR" + }, + "openEHR": { + "type": "object", + "additionalProperties": false, + "properties": { + "templateId": { + "type": "string" + }, + "archetypes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "archetypes", + "templateId" + ], + "title": "openEHR" + } + } +} diff --git a/src/test/resources/mapping-files-spec/v0-0-2/contextual-mappings/example-002-growth-chart.context.yml b/src/test/resources/mapping-files-spec/v0-0-2/contextual-mappings/example-002-growth-chart.context.yml new file mode 100644 index 0000000..029aef1 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-0-2/contextual-mappings/example-002-growth-chart.context.yml @@ -0,0 +1,12 @@ +format: "0.0.2" + +openEHR: + templateId: "CATTEDRA JDM Growth chart" + archetypes: + - "openEHR-EHR-OBSERVATION.body_weight.v2" + - "openEHR-EHR-OBSERVATION.height.v2" + - "openEHR-EHR-OBSERVATION.body_mass_index.v2" + - "openEHR-EHR-OBSERVATION.head_circumference.v1" + +fhir: + resourceType: "Bundle" \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-0-2/contextual-mappings/simple-blood-pressure.context.yml b/src/test/resources/mapping-files-spec/v0-0-2/contextual-mappings/simple-blood-pressure.context.yml new file mode 100644 index 0000000..0eacd64 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-0-2/contextual-mappings/simple-blood-pressure.context.yml @@ -0,0 +1,16 @@ +# Instance/Context related mapping of concepts. + +# In this case, a simple example mapping a wrapper template to a FHIR resource. + +format: "0.0.2" # Version of format, to catch invalid older iterations during quick early development. Will get stable at some point, of course. + +openEHR: + templateId: "simple-encounter-blood-pressure.v0" + # List of archetypes contained in that template. A unique set of Archetypes must have only one unique instance mapping at max. + # WIP Note: This could be calculated at runtime, but only with major implications on performance. In contrast, calculating at design time is a one-time single effort. + archetypes: + # This list could be computed by the (GUI) tooling, which will help to create these mappings. + - "openEHR-EHR-OBSERVATION.blood_pressure.v2" + +fhir: + resourceType: "Observation" \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-0-2/model-mapping.schema.json b/src/test/resources/mapping-files-spec/v0-0-2/model-mapping.schema.json new file mode 100644 index 0000000..2554be8 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-0-2/model-mapping.schema.json @@ -0,0 +1,121 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Model Mapping schema for file format v0.0.2", + "title": "Model Mapping", + "type": "object", + "properties": { + "format": { + "type": "string" + }, + "fhirConfig": { + "$ref": "#/$defs/fhirConfig" + }, + "openEhrConfig": { + "$ref": "#/$defs/openEhrConfig" + }, + "mappings": { + "$ref": "#/$defs/mappings" + } + }, + "required": [ + "format", + "fhirConfig", + "openEhrConfig", + "mappings" + ], + "additionalProperties": false, + "$defs": { + "fhirConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "title": "FHIR Config" + }, + "openEhrConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "archetype": { + "type": "string" + } + }, + "required": [ + "archetype" + ], + "title": "openEHR Config" + }, + "mappings": { + "type": "array", + "items": { "$ref": "#/$defs/mapping" }, + "additionalProperties": false, + "title": "Mappings" + }, + "mapping": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "with": { + "properties": { + "fhir": { + "type": "string" + }, + "openehr": { + "type": "string" + } + }, + "required": ["fhir", "openehr"] + }, + "condition": { + "type": "object", + "properties": { + "targetRoot": { + "type": "string" + }, + "targetAttribute": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "criteria": { + "type": "string" + }, + "identifying": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": ["targetRoot", "targetAttribute", "operator", "criteria"] + }, + "followedBy": { + "type": "object", + "properties": { + "mappings": { + "type": "array", + "items": { "$ref": "#/$defs/mapping" }, + "additionalProperties": false, + "title": "Following Mappings" + } + }, + "additionalProperties": false, + "required": ["mappings"] + } + }, + "required": [ + "name", "with" + ], + "title": "Model Mapping" + } + } +} + \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-0-2/model-mappings/blood-pressure.model.yml b/src/test/resources/mapping-files-spec/v0-0-2/model-mappings/blood-pressure.model.yml new file mode 100644 index 0000000..1cdc7d7 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-0-2/model-mappings/blood-pressure.model.yml @@ -0,0 +1,69 @@ +# Listing of one bi-directional model mapping. + +# General philosophy: The reading direction of those mappings is FHIR->openEHR, because +# openEHR is more descriptive, due to its "maximal modeling" approach. All ambiguities +# regarding the bi-directionality will be handled by the program/code. + +format: "0.0.2" # Version of format, to catch invalid older iterations during quick early development. Will get stable at some point, of course. + +# Configuration of contexts. +fhirConfig: + # For this example: http://hl7.org/fhir/observation-example-bloodpressure.json.html + resource: "Observation" + # ... more? +openEhrConfig: + # For this example: https://ckm.openehr.org/ckm/archetypes/1013.1.3574 + archetype: "openEHR-EHR-OBSERVATION.blood_pressure.v2" + +# Actual mapping points: +mappings: + # Each mapping has a unique (self documenting) name. + - name: "patient" + # A simple mapping, which maps one attribute to another. + with: + # Access the FHIR resource, either in input or output direction. + fhir: "$fhirResource.subject.reference" + # Some context variable are necessary to be accessible from the mapping. + openehr: "$openEhrContext.$ehr" + + # Another mapping which maps on a met condition. This condition is used to identify + # the correct "component" in the FHIR resource (array). + - name: "systolic" + with: + fhir: "$fhirResource.component.value.value" + openehr: "$openEhrArchetype.blood_pressure.any_event.systolic.magnitude" + condition: + # One of the codings has to be the matching LOINC or SNOMED code. + # incl. implicit handling of arrays, as ".coding" is a set of codings. + # -> Here the backwards direction is ambiguous. The program will interpret "one of" + # as "put those in the target resource" if the direction is openEHR->FHIR. + targetRoot: "$fhirResource.component" # Scope of object for 'condition' + targetAttribute: "code.coding.code" # Actual attribute to check for 'condition' + operator: "one of" + criteria: "[$loinc.8480-6, $snomed.271649006]" + identifying: true # The *one* condition able to identify FHIR input to match this mapping + # The next step allows a mapping to have a list of succeeding mappings, which + # are executed in the same scope of the parent mapping. + followedBy: + mappings: + - name: "unit" + with: + fhir: "$fhirResource.component.value.unit" + openehr: "$openEhrArchetype.blood_pressure.any_event.systolic.unit" + # Implicit handling of array/single object conversion. + + - name: "diastolic" + with: + fhir: "$fhirResource.component.value.value" + openehr: "$openEhrArchetype.blood_pressure.any_event.diastolic.magnitude" + condition: + targetRoot: "$fhirResource.component" # Scope of object for 'condition' + targetAttribute: "code.coding.code" # Actual attribute to check for 'condition' + operator: "one of" + criteria: "[$loinc.8462-4]" + followedBy: + mappings: + - name: "unit" + with: + fhir: "$fhirResource.component.value.unit" + openehr: "$openEhrArchetype.blood_pressure.any_event.diastolic.unit" \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-0-2/model-mappings/head-circumference.model.yml b/src/test/resources/mapping-files-spec/v0-0-2/model-mappings/head-circumference.model.yml new file mode 100644 index 0000000..e233946 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-0-2/model-mappings/head-circumference.model.yml @@ -0,0 +1,27 @@ +format: "0.0.2" + +fhirConfig: + # For this Resource: https://www.hl7.org/fhir/observation-example.json.html + resource: "Observation" +openEhrConfig: + archetype: "openEHR-EHR-OBSERVATION.head_circumference.v1" + +mappings: + - name: "head-circumference" + with: + fhir: "$fhirResource.value.value" + openehr: "$openEhrArchetype.head_circumference.any_event.head_circumference.magnitude" + condition: + targetRoot: "$fhirResource" + targetAttribute: "$fhirResource.code.coding.code" + operator: "one of" + criteria: "[$loinc.8287-5]" + identifying: true + - name: "unit" + with: + fhir: "$fhirResource.value.unit" + openehr: "$openEhrArchetype.head_circumference.any_event.head_circumference.unit" + - name: "time" + with: + fhir: "$fhirResource.effective" + openehr: "$openEhrArchetype.head_circumference.any_event.time" \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/contextual-mapping.schema.json b/src/test/resources/mapping-files-spec/v0-1-0/contextual-mapping.schema.json new file mode 100644 index 0000000..b824c8e --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/contextual-mapping.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://schema.better.care/fhirconnect/contextual/0.1.0/contextual-mapping-schema", + "description": "Contextual Mapping schema for file format v0.1.0", + "title": "Contextual Mapping", + "type": "object", + "properties": { + "format": { + "type": "string" + }, + "openEHR": { + "$ref": "#/$defs/openEHR" + }, + "fhir": { + "$ref": "#/$defs/fhir" + } + }, + "required": [ + "fhir", + "format", + "openEHR" + ], + "additionalProperties": false, + "$defs": { + "fhir": { + "type": "object", + "additionalProperties": false, + "properties": { + "resourceType": { + "type": "string" + } + }, + "required": [ + "resourceType" + ], + "title": "FHIR" + }, + "openEHR": { + "type": "object", + "additionalProperties": false, + "properties": { + "templateId": { + "type": "string" + }, + "archetypes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "archetypes", + "templateId" + ], + "title": "openEHR" + } + } +} diff --git a/src/test/resources/mapping-files-spec/v0-1-0/contextual-mappings/example-002-growth-chart.context.yml b/src/test/resources/mapping-files-spec/v0-1-0/contextual-mappings/example-002-growth-chart.context.yml new file mode 100644 index 0000000..0856b19 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/contextual-mappings/example-002-growth-chart.context.yml @@ -0,0 +1,12 @@ +format: "0.1.0" + +openEHR: + templateId: "CATTEDRA JDM Growth chart" + archetypes: + - "openEHR-EHR-OBSERVATION.body_weight.v2" + - "openEHR-EHR-OBSERVATION.height.v2" + - "openEHR-EHR-OBSERVATION.body_mass_index.v2" + - "openEHR-EHR-OBSERVATION.head_circumference.v1" + +fhir: + resourceType: "Bundle" \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/contextual-mappings/inpatient-prescription.context.yml b/src/test/resources/mapping-files-spec/v0-1-0/contextual-mappings/inpatient-prescription.context.yml new file mode 100644 index 0000000..1070336 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/contextual-mappings/inpatient-prescription.context.yml @@ -0,0 +1,9 @@ +format: "0.1.0" + +openEHR: + templateId: "OPENeP - Inpatient Prescription" + archetypes: + - "openEHR-EHR-INSTRUCTION.medication_order.v2" + +fhir: + resourceType: "Bundle" \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/contextual-mappings/simple-blood-pressure.context.yml b/src/test/resources/mapping-files-spec/v0-1-0/contextual-mappings/simple-blood-pressure.context.yml new file mode 100644 index 0000000..97c3258 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/contextual-mappings/simple-blood-pressure.context.yml @@ -0,0 +1,15 @@ +# Context related mapping of concepts. +# In this case, a simple example mapping a wrapper template to a FHIR resource. + +format: "0.1.0" # Version of format + +openEHR: + templateId: "simple-encounter-blood-pressure.v0" + # List of archetypes contained in that template. A unique set of Archetypes must have only one unique instance mapping at max. + # This could be calculated at runtime, but only with major implications on performance. In contrast, calculating at design time is a one-time single effort. + archetypes: + # This list could be computed by the (GUI) tooling, which will help to create these mappings. + - "openEHR-EHR-OBSERVATION.blood_pressure.v2" + +fhir: + resourceType: "Observation" \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mapping.schema.json b/src/test/resources/mapping-files-spec/v0-1-0/model-mapping.schema.json new file mode 100644 index 0000000..b6039d7 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mapping.schema.json @@ -0,0 +1,172 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://schema.better.care/fhirconnect/model/0.1.0/model-mapping-schema", + "description": "Model Mapping schema for file format v0.1.0", + "title": "Model Mapping", + "type": "object", + "properties": { + "format": { + "type": "string" + }, + "version": { + "type": "string" + }, + "fhirConfig": { + "$ref": "#/$defs/fhirConfig" + }, + "openEhrConfig": { + "$ref": "#/$defs/openEhrConfig" + }, + "mappings": { + "$ref": "#/$defs/mappings" + } + }, + "required": [ + "format", + "version", + "openEhrConfig", + "mappings" + ], + "additionalProperties": false, + "$defs": { + "fhirConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "resource": { + "type": "string" + }, + "alwaysBundled": { + "type": "boolean" + } + }, + "required": [ + "resource" + ], + "title": "FHIR Config" + }, + "openEhrConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "archetype": { + "type": "string" + } + }, + "required": [ + "archetype" + ], + "title": "openEHR Config" + }, + "mappings": { + "type": "array", + "items": { + "$ref": "#/$defs/mapping" + }, + "additionalProperties": false, + "title": "Mappings" + }, + "mapping": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "with": { + "properties": { + "fhir": { + "type": "string" + }, + "openehr": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "fhir", + "openehr", + "type" + ] + }, + "condition": { + "type": "object", + "properties": { + "targetRoot": { + "type": "string" + }, + "targetAttribute": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "criteria": { + "type": "string" + }, + "identifying": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "targetRoot", + "targetAttribute", + "operator", + "criteria" + ] + }, + "followedBy": { + "type": "object", + "properties": { + "mappings": { + "type": "array", + "items": { + "$ref": "#/$defs/mapping" + }, + "additionalProperties": false, + "title": "Following Mappings" + } + }, + "additionalProperties": false, + "required": [ + "mappings" + ] + }, + "reference": { + "type": "object", + "properties": { + "resourceType": { + "type": "string" + }, + "mappings": { + "type": "array", + "items": { + "$ref": "#/$defs/mapping" + }, + "maxContains": 1, + "additionalProperties": false, + "title": "Reference Mappings" + } + }, + "additionalProperties": false, + "required": [ + "resourceType", + "mappings" + ] + }, + "slotArchetype": { + "type": "string" + } + }, + "required": [ + "name", + "with" + ], + "title": "Model Mapping" + } + } +} + \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/blood-pressure.model.yml b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/blood-pressure.model.yml new file mode 100644 index 0000000..b72f1cd --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/blood-pressure.model.yml @@ -0,0 +1,52 @@ +# Listing of one bi-directional model mapping. + +# General philosophy: The reading direction of those mappings is FHIR->openEHR, because +# openEHR is more descriptive, due to its "maximal modeling" approach. All ambiguities +# regarding the bi-directionality will be handled by the program/code. + +version: "0.0.2" +format: "0.1.0" # Version of format, to catch invalid older iterations during quick early development. Will get stable at some point, of course. + +# Configuration of contexts. +fhirConfig: + # For this example: http://hl7.org/fhir/observation-example-bloodpressure.json.html + resource: "Observation" +openEhrConfig: + # For this example: https://ckm.openehr.org/ckm/archetypes/1013.1.3574 + archetype: "openEHR-EHR-OBSERVATION.blood_pressure.v2" + +# Actual mapping points: +mappings: + # Each mapping has a unique (self documenting) name. + - name: "patient" + # A simple mapping, which maps one attribute to another. + with: + # Access the FHIR resource, either in input or output direction. + fhir: "$fhirResource.subject.reference" + # Some context variable are necessary to be accessible from the mapping. + openehr: "$openEhrContext.$ehr" + type: "STRING" + + # Another mapping which maps on a met condition. This condition is used to identify + # the correct "component" in the FHIR resource (array). + - name: "systolic" + with: + fhir: "$fhirResource.component.value" + openehr: "$openEhrArchetype.blood_pressure.any_event.systolic" + type: "QUANTITY" + condition: + # One of the codings has to be the matching LOINC or SNOMED code. + # incl. implicit handling of arrays, as ".coding" is a set of codings. + # -> Here the backwards direction is ambiguous. The program will interpret "one of" + # as "put those in the target resource" if the direction is openEHR->FHIR. + targetRoot: "$fhirResource.component" # Scope of object for 'condition' + targetAttribute: "code.coding.code" # Actual attribute to check for 'condition' + operator: "one of" + criteria: "[$loinc.8480-6, $snomed.271649006]" + identifying: true # The *one* condition able to identify FHIR input to match this mapping + +# The real mapping would continue to map: +# - performer +# - diastolic +# - clinical interpretation +# - body site \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/body-height.model.yml b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/body-height.model.yml new file mode 100644 index 0000000..d1e810f --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/body-height.model.yml @@ -0,0 +1,27 @@ +format: "0.1.0" +version: "0.0.2" + +fhirConfig: + # For this Resource: https://www.hl7.org/fhir/observation-example-body-height.json.html + resource: "Observation" +openEhrConfig: + # For this Archetype: https://ckm.openehr.org/ckm/archetypes/1013.1.3210 + archetype: "openEHR-EHR-OBSERVATION.height.v2" + +mappings: + - name: "patient" + with: + fhir: "$fhirResource.subject.reference" + openehr: "$openEhrContext.$ehr" + type: "STRING" + - name: "height" + with: + fhir: "$fhirResource.value" + openehr: "$openEhrArchetype.height_length.any_event.height_length" + type: "QUANTITY" + condition: + targetRoot: "$fhirResource" + targetAttribute: "code.coding.code" + operator: "one of" + criteria: "[$loinc.8302-2]" + identifying: true \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/body-weight.model.yml b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/body-weight.model.yml new file mode 100644 index 0000000..df3be6c --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/body-weight.model.yml @@ -0,0 +1,27 @@ +format: "0.1.0" +version: "0.0.2" + +fhirConfig: + # For this Resource: https://www.hl7.org/fhir/observation-example.json.html + resource: "Observation" +openEhrConfig: + # For this Archetype: https://ckm.openehr.org/ckm/archetypes/1013.1.2960 + archetype: "openEHR-EHR-OBSERVATION.body_weight.v2" + +mappings: + - name: "patient" + with: + fhir: "$fhirResource.subject.reference" + openehr: "$openEhrContext.$ehr" + type: "STRING" + - name: "weight" + with: + fhir: "$fhirResource.value" + openehr: "$openEhrArchetype.body_weight.any_event.weight" + type: "QUANTITY" + condition: + targetRoot: "$fhirResource" + targetAttribute: "code.coding.code" + operator: "one of" + criteria: "[$loinc.29463-7, $snomed.27113001]" + identifying: true \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/dosage.model.yml b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/dosage.model.yml new file mode 100644 index 0000000..7345385 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/dosage.model.yml @@ -0,0 +1,21 @@ +format: "0.1.0" +version: "0.0.1" + +# No fhirConfig, because this model has no direct Resource equivalent in FHIR. It can only be used in openEHR Archetype slots or as FHIR Resource attribute. + +openEhrConfig: + archetype: "openEHR-EHR-CLUSTER.dosage.v1" + +mappings: + - name: "dosage" + with: + fhir: "$fhirRoot" + openehr: "$openEhrArchetype" + type: "DOSAGE" + followedBy: + mappings: + - name: "doseQuantityValue" + with: + fhir: "doseAndRate.dose" + openehr: "$openEhrArchetype.dose_amount.quantity_value" + type: "QUANTITY" diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/himss-body-height.model.yml b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/himss-body-height.model.yml new file mode 100644 index 0000000..9965525 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/himss-body-height.model.yml @@ -0,0 +1,27 @@ +format: "0.1.0" +version: "0.0.1" + +fhirConfig: + # For this Resource: https://www.hl7.org/fhir/observation-example-body-height.json.html + resource: "Observation" +openEhrConfig: + # For this Archetype: https://ckm.openehr.org/ckm/archetypes/1013.1.3210 + archetype: "openEHR-EHR-OBSERVATION.height.v2" + +mappings: + - name: "height" + with: + fhir: "$fhirResource.value" + openehr: "$openEhrArchetype.height_length.any_event.height_length" + type: "QUANTITY" + condition: + targetRoot: "$fhirResource" + targetAttribute: "$fhirResource.code.coding.code" + operator: "one of" + criteria: "[$loinc.8302-2]" + identifying: true + - name: "time" + with: + fhir: "$fhirResource.effective" + openehr: "$openEhrArchetype.height_length.any_event.time" + type: "DATETIME" \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/himss-body-mass-index.model.yml b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/himss-body-mass-index.model.yml new file mode 100644 index 0000000..057ce96 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/himss-body-mass-index.model.yml @@ -0,0 +1,26 @@ +format: "0.1.0" +version: "0.0.1" + +fhirConfig: + # For this Resource: https://www.hl7.org/fhir/observation-example.json.html + resource: "Observation" +openEhrConfig: + archetype: "openEHR-EHR-OBSERVATION.body_mass_index.v2" + +mappings: + - name: "bmi" + with: + fhir: "$fhirResource.value" + openehr: "$openEhrArchetype.body_mass_index.any_event.body_mass_index" + type: "QUANTITY" + condition: + targetRoot: "$fhirResource" + targetAttribute: "$fhirResource.code.coding.code" + operator: "one of" + criteria: "[$loinc.39156-5]" + identifying: true + - name: "time" + with: + fhir: "$fhirResource.effective" + openehr: "$openEhrArchetype.body_mass_index.any_event.time" + type: "DATETIME" \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/himss-body-weight.model.yml b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/himss-body-weight.model.yml new file mode 100644 index 0000000..454f631 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/himss-body-weight.model.yml @@ -0,0 +1,27 @@ +format: "0.1.0" +version: "0.0.1" + +fhirConfig: + # For this Resource: https://www.hl7.org/fhir/observation-example.json.html + resource: "Observation" +openEhrConfig: + # For this Archetype: https://ckm.openehr.org/ckm/archetypes/1013.1.2960 + archetype: "openEHR-EHR-OBSERVATION.body_weight.v2" + +mappings: + - name: "weight" + with: + fhir: "$fhirResource.value" + openehr: "$openEhrArchetype.body_weight.any_event.weight" + type: "QUANTITY" + condition: + targetRoot: "$fhirResource" + targetAttribute: "$fhirResource.code.coding.code" + operator: "one of" + criteria: "[$loinc.29463-7, $snomed.27113001]" + identifying: true + - name: "time" + with: + fhir: "$fhirResource.effective" + openehr: "$openEhrArchetype.body_weight.any_event.time" + type: "DATETIME" \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/himss-head-circumference.model.yml b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/himss-head-circumference.model.yml new file mode 100644 index 0000000..4d8e622 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/himss-head-circumference.model.yml @@ -0,0 +1,26 @@ +format: "0.1.0" +version: "0.0.1" + +fhirConfig: + # For this Resource: https://www.hl7.org/fhir/observation-example.json.html + resource: "Observation" +openEhrConfig: + archetype: "openEHR-EHR-OBSERVATION.head_circumference.v1" + +mappings: + - name: "head-circumference" + with: + fhir: "$fhirResource.value" + openehr: "$openEhrArchetype.head_circumference.any_event.head_circumference" + type: "QUANTITY" + condition: + targetRoot: "$fhirResource" + targetAttribute: "$fhirResource.code.coding.code" + operator: "one of" + criteria: "[$loinc.8287-5]" + identifying: true + - name: "time" + with: + fhir: "$fhirResource.effective" + openehr: "$openEhrArchetype.head_circumference.any_event.time" + type: "DATETIME" \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/medication-order-only-with-slot.model.yml b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/medication-order-only-with-slot.model.yml new file mode 100644 index 0000000..b869803 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/medication-order-only-with-slot.model.yml @@ -0,0 +1,20 @@ +format: "0.1.0" +version: "0.0.1" + +fhirConfig: + # For this Resource: http://hl7.org/fhir/medicationrequest.html + resource: "MedicationRequest" + # Convenience property to indicate at least one mapping directly works with a FHIR reference -> the resulting FHIR has to be a Bundle. + alwaysBundled: true +openEhrConfig: + # For this Archetype: https://ckm.openehr.org/ckm/archetypes/1013.1.5946 + archetype: "openEHR-EHR-INSTRUCTION.medication_order.v2" # Note: Is actually at v3 but the use-case has v2 + +mappings: + # openEHR data is linking to another Archetype (Cluster Slot) + - name: "therapeutic-direction" + with: + fhir: "$fhirResource" # Only forwarding the root resource, as attributes will be handled on following mappings. + openehr: "$openEhrArchetype.medication_order.order.therapeutic_direction" + type: "NONE" + slotArchetype: "openEHR-EHR-CLUSTER.therapeutic_direction.v1" # acts as link to Model Mapping of given Archetype \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/medication-order.model.yml b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/medication-order.model.yml new file mode 100644 index 0000000..77caa5c --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/medication-order.model.yml @@ -0,0 +1,36 @@ +format: "0.1.0" +version: "0.0.1" + +fhirConfig: + # For this Resource: http://hl7.org/fhir/medicationrequest.html + resource: "MedicationRequest" + # Convenience property to indicate at least one mapping directly works with a FHIR reference -> the resulting FHIR has to be a Bundle. + alwaysBundled: true +openEhrConfig: + # For this Archetype: https://ckm.openehr.org/ckm/archetypes/1013.1.5946 + archetype: "openEHR-EHR-INSTRUCTION.medication_order.v2" # Note: Is actually at v3 but the use-case has v2 + +mappings: + - name: "medication" + with: + fhir: "$fhirResource.medication" + openehr: "$reference" # the path of the following reference mapping will be used + type: "NONE" + # States the FHIR target/source is a Reference. + reference: + # Implicitly is a "direct" reference, i.e. creates ad-hoc sub-resource. It will be added to the Bundle and referenced in the root Resource. + resourceType: "Medication" + mappings: + - name: "medication-item" + with: + fhir: "code.text" + openehr: "$openEhrArchetype.medication_order.order.medication_item" + type: "STRING" + + # openEHR data is linking to another Archetype (Cluster Slot) + - name: "therapeutic-direction" + with: + fhir: "$fhirResource" # Only forwarding the root resource, as attributes will be handled on following mappings. + openehr: "$openEhrArchetype.medication_order.order.therapeutic_direction" + type: "NONE" + slotArchetype: "openEHR-EHR-CLUSTER.therapeutic_direction.v1" # acts as link to Model Mapping of given Archetype \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/model-mappingv2.model.yml b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/model-mappingv2.model.yml new file mode 100644 index 0000000..c2bd027 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/model-mappingv2.model.yml @@ -0,0 +1,59 @@ +# Listing of one bi-directional model mapping. + +# General philosophy: The reading direction of those mappings is FHIR->openEHR, because +# openEHR is more descriptive, due to its "maximal modeling" approach. All ambiguities +# regarding the bi-directionality will be handled by the program/code. + +version: "0.0.2" +format: "0.1.0" # Version of format, to catch invalid older iterations during quick early development. Will get stable at some point, of course. + +# Configuration of contexts. +fhirConfig: + # For this example: http://hl7.org/fhir/observation-example-bloodpressure.json.html + resource: "Observation" + # ... more? +openEhrConfig: + # For this example: https://ckm.openehr.org/ckm/archetypes/1013.1.3574 + archetype: "openEHR-EHR-OBSERVATION.blood_pressure.v2" + +# Actual mapping points: +mappings: + # Each mapping has a unique (self documenting) name. + - name: "patient" + # A simple mapping, which maps one attribute to another. + with: + # Access the FHIR resource, either in input or output direction. + fhir: "$fhirResource.subject.reference" + # Some context variable are necessary to be accessible from the mapping. + openehr: "$openEhrContext.$ehr" + type: "STRING" + + # Another mapping which maps on a met condition. This condition is used to identify + # the correct "component" in the FHIR resource (array). + - name: "systolic" + with: + fhir: "$fhirResource.component.value" + openehr: "$openEhrArchetype.blood_pressure.any_event.systolic" + type: "QUANTITY" + condition: + # One of the codings has to be the matching LOINC or SNOMED code. + # incl. implicit handling of arrays, as ".coding" is a set of codings. + # -> Here the backwards direction is ambiguous. The program will interpret "one of" + # as "put those in the target resource" if the direction is openEHR->FHIR. + targetRoot: "$fhirResource.component" # Scope of object for 'condition' + targetAttribute: "code.coding.code" # Actual attribute to check for 'condition' + operator: "one of" + criteria: "[$loinc.8480-6, $snomed.271649006]" + identifying: true # The *one* condition able to identify FHIR input to match this mapping + + - name: "diastolic" + with: + fhir: "$fhirResource.component.value" + openehr: "$openEhrArchetype.blood_pressure.any_event.diastolic" + type: "QUANTITY" + condition: + targetRoot: "$fhirResource.component" # Scope of object for 'condition' + targetAttribute: "code.coding.code" # Actual attribute to check for 'condition' + operator: "one of" + criteria: "[$loinc.8462-4]" + identifying: true # The *one* condition able to identify FHIR input to match this mapping \ No newline at end of file diff --git a/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/therapeutic-direction.model.yml b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/therapeutic-direction.model.yml new file mode 100644 index 0000000..6013728 --- /dev/null +++ b/src/test/resources/mapping-files-spec/v0-1-0/model-mappings/therapeutic-direction.model.yml @@ -0,0 +1,15 @@ +format: "0.1.0" +version: "0.0.1" + +# No fhirConfig, because this model has no direct Resource equivalent in FHIR. It can only be used in openEHR Archetype slots. + +openEhrConfig: + archetype: "openEHR-EHR-CLUSTER.therapeutic_direction.v1" + +mappings: + - name: "dosage" + with: + fhir: "$fhirResource.dosageInstruction" + openehr: "$openEhrArchetype.dosage" + type: "NONE" + slotArchetype: "openEHR-EHR-CLUSTER.dosage.v1" # acts as link to Model Mapping of given Archetype \ No newline at end of file