Skip to content
This repository has been archived by the owner on Apr 8, 2024. It is now read-only.

3. Add new mapping

Severin Kohler edited this page May 10, 2022 · 74 revisions

After seting up and running the FHIR-Bridge (as explained here) a mapping can be added by following the steps explained beyond. All steps are explained following the example of implementing a mapping for body temperature.

1. Create a new branch

Each change to the FHIR bridge should have a ticket created, explaining the change. Create a new feature branch with ticket number like: feature/123_mapping_body_temperature, where 123 stands for the issue number

    # optional: make a new checkout
    #git clone https://github.com/ehrbase/fhir-bridge.git
    git clone [email protected]:ehrbase/fhir-bridge.git
    # default:
    cd fhir-bridge
    git checkout -b feature/123_mapping_body_temperature
    # At the first push:
    git push -u origin feature/123_mapping_body_temperature
    # For later pushes:
    git push

    # start docker, if it is not already running (analog to readme)
    cd fhir-bridge/docker
    docker-compose -f docker-compose-light.yml  up

    # build everything initially
    cd fhir-bridge # bzw. 'cd ..' , wenn der vorherige Befehl ausgeführt wurde
    mvn clean install


2. Add the operational template and FHIR profile for the mapping

2a. Operational template (OPT format)

  1. visit http://88.198.146.13/ckm/projects/1246.152.26/resourcecentre
  2. download the Körpertemperatur.opt by clicking on it
  3. add the file to the directory fhir-bridge/src/main/resources/opt
  • Optional: Check the OPT in Pablos Tool: toolkit.cabolabs.com

2b. Generate the openEHR Composition classes

  1. leave the fhir-bridge folder
  2. clone the following project with git clone https://github.com/ehrbase/openEHR_SDK
  3. enter the directory and execute mvn clean install
  4. execute java -jar YOUR_OPENEHR-SDK_PATH/generator/target/generator-VERSION.jar -opt YOUR_FHIR-BRIDGE_PATH/src/main/resources/opt/Körpertemperatur.opt -out YOUR_FHIR-BRIDGE_PATH/src/main/java/ -package org.ehrbase.fhirbridge.ehr.opt This serializes Java classes for the Body temp Composition.
  5. Make class fhir-bridge/src/main/java/org/ehrbase/fhirbridge/ehr/opt/YOUR-COMP/YOUR-COMP-Composition implement the Composition interface. Troubleshooting: In case IntelliJ can not resolve basic imports anymore, e.g. java.xxxx, close the project. Delete the ".idea" folder and open project again.

2c. FHIR profile

  1. visit https://simplifier.net/ForschungsnetzCovid-19/~resources?fhirVersion=R4&sortBy=RankScore_desc
  2. search for Body Temperature (Profile)
  3. click on the Profile and Download a Snapshot as XML
  4. move the Downloaded XML into the directory fhir-bridge/src/main/resources/profiles

Note: If your Profile includes extensions, don't forget to download and add them, too.

2d. FHIR profile example

To later test the implemented mapping, an example of the profile also needs to be provided

  1. visit https://simplifier.net/ForschungsnetzCovid-19/~resources?fhirVersion=R4&sortBy=RankScore_desc
  2. search for Body Temperature (Example)
  3. click on the Example and Download it as JSON
  4. open the JSON and search for the value of the field ¨resourceType¨, in this case it is an Observation
  5. move it to fhir-bridge/src/test/resources/Observation
  6. rename it to create-body-temp.json
  7. open the file and replace the JSON object subject with the following lines:
"subject": {
    "identifier": {
      "system": "urn:ietf:rfc:4122",
      "value": "{{patientId}}"
    }
  },

Note: Step 7 depends on the resource type, e.g. for Patient it's just the identifier part that needs to be included.

3. Add the routing for the mapping

To add a mapping to the fhir-bridge, the routing for the input has to be defined. Since the basic functionality is already provided only two classes need to be edited.

3a. Adding the fhir profile to the profile Enum

  1. open fhir-bridge/src/main/java/org/ehrbase/fhir-bridge/fhir/common/Profile.java
  2. add the line
BODY_TEMP(Observation.class, "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/body-temperature"),

to the enum class e.g.:

public enum Profile {
    PATIENT(Patient.class, "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/Patient"),
    BODY_TEMP(Observation.class, "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/body-temperature"),
    BODY_WEIGHT(Observation.class, "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/body-weight");

The values entered here derive from your fhir resource you want to map. In case of our example, body temperature is a Observation. This may vary depending on the resourceType of your fhir resource! The URL contained as the second parameter is found in your fhir JSON example within the JSON object meta e.g.:

"meta": {
        "profile":  [
            "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/body-temperature"
        ]
    },

3b. Adding the profile to the routing of camel

  1. open fhir-bridge/src/main/org/ehrbase/fhirbridge/config/ConversionConfiguration
  2. identify the appropriate "register" method for your converter
  3. add
conversionService.registerConverter(Profile.BODY_HEIGHT, new BodyHeightCompositionConverter());

to the method.

Hereby camel can route the input of an BodyHeight to the BodyHeightCompositionConverter where the mapping logic is to be contained.

3c. Creating the Composition Converter class

  1. create a package for your conversion in fhir-bridge/src/main/java/org/ehrbase/fhirbridge/ehr/converter/specific
  2. create the class BodyHeightCompositionConverter, consider using Alt + Enter while your cursor is in IntelliJ on the red highlighted converter class name, you just wrote)
  3. this class needs to implement the YOUR_FHIR_TYPE_ToCompositionConverter interface using the KoerpergroesseComposition Example:
public class BodyHeightCompositionConverter extends ObservationToCompositionConverter<KoerpergroesseComposition> {
  1. the CompositionConverter interface has one method that needs to be set for each mapping that is done: the convertInternal method. It is part of a process to map a openEHR composition. The convertInternal method is used to set the mapping of specific entries. Many fields are identical within the mappings. As an example startTimeValue is a field of each observation mapping and therefore doesn't need to be mapped every time. Instead it gets mapped in the abstract class, contained in the generic package. These abstract classes call the specific convertInternal method as part of the process. In our example, the method convertInternal would look something like that:
    @Override
    public KoerpergroesseComposition convertInternal(@NonNull Observation resource) {
        KoerpergroesseComposition composition = new KoerpergroesseComposition();
        composition.setGroesseLaenge(new GroesseLaengeObservationConverter().convert(resource));
        return composition;
    }

3d. Creating the entry entity converter classes containing the mapping logic

  1. Each composition and each entry entity can contain other entry entries. Each of these entries has specific and generic mappings. In order to avoid duplicating generic mappings, we create new classes for each entry entity inheriting generic converters.
  2. In our case the composition has an GroesseLaengeObservation entry. To convert this observation we create a new classYOUR_MAPPING_Converter, e.g. GroesseLaengeObservationConverter.
  3. this class needs to implement the YOUR_EHR_TYPE_Converter interface using the openEHR observation GroesseLaengeObservation. There are different converters provided for specific combinations, if not provided use the default EntryEntityConverter. In our case we convert an Fhir Observation to an openEHR observation and therefore use the ObservationToObservationConverter. Example:
public class GroesseLaengeObservationConverter extends ObservationToObservationConverter<GroesseLaengeObservation> {

  1. the Converter interface has one methods that needs to be set for each mapping that is done: the convertInternal method. It maps fhir resource into the openEHR equivalent. In our example, the method would look something like that:
    @Override
    protected GroesseLaengeObservation convertInternal(Observation resource) {
        GroesseLaengeObservation groesseLaengeObservation = new GroesseLaengeObservation();
        groesseLaengeObservation.setGroesseLaengeUnits(resource.getValueQuantity().getCode());
        groesseLaengeObservation.setGroesseLaengeMagnitude(resource.getValueQuantity().getValue().doubleValue());
        return groesseLaengeObservation;
    }

4. Designing the mapping

  1. download the following graphic
  2. open draw.io
  3. import the graphic (file -> open from -> device)
  4. Go to Simplifier and open the resource you want to map (not the example !) e.g. body temp
  5. compare the fields with the ones of the template, to do so open this link, search your template and click "View template"
  6. add this value mappings to the grapic (see examples, also in the left top corner is a legend)

(Final file can be added to the Wiki under "Fertige Dokus")

To see all fields of the template you can use better designer instead of ckm. Therefore, you have to:

  1. Open the "Gecco Core Project" in ckm
  2. Export the project as zip
  3. Sign in with github-Account (https://tools.openehr.org/designer/#/)[https://tools.openehr.org/designer/#/]
  4. Create a new repository
  5. Import the zip into the repositry ("import", "upload", wait, "close")

5. Programming your mappings

In the next step the mapping needs to be implemented in your new Converter, in our case GroesseLangeObservationConverter / TemperatureObservationConverter.

To run what you implemented:

  1. Be sure, docker is running
(base) birgit@birgit-Latitude-7390:~$ docker ps
CONTAINER ID        IMAGE                             COMMAND                  CREATED             STATUS              PORTS                    NAMES
308cf2833064        ehrbase/ehrbase:next              "/bin/sh -c ./docker…"   2 minutes ago       Up 2 minutes        0.0.0.0:8080->8080/tcp   docker_ehrbase_1
71abf5d575ae        ehrbase/ehrbase-postgres:latest   "docker-entrypoint.s…"   3 minutes ago       Up 2 minutes        0.0.0.0:5432->5432/tcp   docker_ehrbase-db_1
  1. Start your local fhir-bridge by clicking 'Maven' in the upper right corner of IntelliJ. Then navigate to 'FHIR Bridge | Plugins | spring-boot ' und start the 'spring-boot:run' command
  2. Run the Junit tests of interest. If you just implemented your mapping you probably want to continue reading first ;-)

Troubleshooting for Windows user in IntelliJ: If "command line command is to long" error occurs. IntelliJ will already propose how to fix it (first option). If not: Run, Edit Configurations, testclass-name (e.g. ObservationIT). If you don't see the "Shorten command line". Click on arrow next to Modify options on right side and activate "Shorten command line". Now select "@argfile ...". Now it should run.

Postman

Afterwards POST the example fhir json to the fhir-bridge ({base_url/fhir/Observation}). Within the log of the fhir-bridge server, the composition version uid is returned. Copy this uid and send an request to ehrbase to return this composition. Retrieve related composition from ehrbase via an AQL query, i.e.

POST {{ehrbase_url}}/query/aql
Content-Type: application/json
Authorization: Basic bXl1c2VyOm15UGFzc3dvcmQ0MzI=

# body/payload
{
  "q": "SELECT c FROM EHR e [ehr_id/value='{{ehr_id}}'] CONTAINS COMPOSITION c"
}

#or

{
  "q": "SELECT c FROM EHR e CONTAINS COMPOSITION c ORDER BY c/context/start_time DESC"
}

Check if the composition contains all the values as intended.

Preparing steps

  • open Postman
  • import config/postman/fhir-bridge.postman_collection.json
  • run Ehrbase -> Create EHR
  • run Patient -> Create Patient
  • then post your composition

6. Test your mappings

Tests shall cover the following cases:

  1. In fhir-bridge/src/test/java/org/ehrbase/fhirbridge/fhir create a new testfile for your mapping within the proper package, e.g. observation. The new class itself must end with IT, e.g. observation/BodyTempIT.java.
  2. Make the class extends AbstractMappingTestSetupIT
  • Add the constructor,the executeMappingUnprocessableEntityException()-method and the getJavers()-method.
  • In the getJavers()-method: add every openEHR Observation, Composition, Element etc. included in your mapping as a ValueObject.
  • The Composition has to include a location string, used to ignore the location when comparing object (example).
  • Implementation hint: When running the mapping that uses javers, the class that is not comparable will be thrown in the error stack, copy this class name and add it to the getJavers()-method.
  1. In test/resources/yourPackage create a package for your mapping
  2. In order to provide an object that the mapping test can be validated against, the output of your current one is to be used. It is crucial that you manually check this Composition and ensure its validity (to display your latest mapping, go to src/main/resources/application.yml and set debug to true, further more a specific path for the mapping output needs to be specified. It is recommended to use the default path: src/main/resources/mapping-output.json).
  • Copy this json and add it to the package you just created. Suggested name prefix:paragon-
  1. Write the mapping test using this file for the specific fhir input, do the same for all other valid testfiles.

Example:

    @Test
    void mappingNormalFinding() throws IOException {
        DiagnosticReport diagnosticReport = (DiagnosticReport) super.testFileLoader.loadResource("create-radiology-report-normal-finding.json");
        RadiologischerBefundConverter radiologischerBefundConverter = new RadiologischerBefundConverter();
        GECCORadiologischerBefundComposition mappedGeccoRadiologischerBefundComposition = radiologischerBefundConverter.toComposition(diagnosticReport);
        Diff diff = compareCompositions(getJavers(), "paragon-radiology-report-normal-finding.json", mappedGeccoRadiologischerBefundComposition);

        assertEquals(0, diff.getChanges().size());
    }
  1. Add one more test that map a resource and send it to the ehrbase (entire workflow).
    @Test
    void createNormalFinding() throws IOException {
        create("create-radiology-report-normal-finding.json");
    }
  1. If there are some exceptions you added that are not covered yet, add tests for those.

7. Wait for the CI

8. Refactor your Code

9. Submit Pull Request

Clone this wiki locally