Skip to content

Commit

Permalink
v0.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jakesmolka committed Aug 1, 2023
1 parent b4e063a commit 1852b4e
Show file tree
Hide file tree
Showing 63 changed files with 2,440 additions and 38 deletions.
46 changes: 34 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ Here, the openEHR execution environment - with `$openEhrContext` as root and ava
| `$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.
Additionally, due to overlapping data types (see [Data Types](#data-types)), context variables can only have non-overlapping types. Because they have no openEHR modeling information to infer the specific type from.

Future versions of this spec could reflect support for further data like: language, committer, subject and so on.

Expand All @@ -144,19 +145,21 @@ Data types, such as `DV_QUANTITY`, `DV_TEXT` and so on, and their FHIR counterpa

The internal mapping of data types:

| Type ID / FHIR | openEHR | Primitive | "FLAT / FHIR" Attributes Pairs |
|-----------------|---------------|-----------|------------------------------------------|
| NONE | NONE | false | / |
| QUANTITY | DV_QUANTITY | true | magnitude / value <br/> unit / unit |
| DATETIME | DV_DATE_TIME | true | (direct) |
| CODEABLECONCEPT | DV_CODED_TEXT | false | **_nested_** / coding <br/> value / text |
| CODING | CODE_PHRASE | true | code / code <br/> terminology / system |
| STRING | DV_TEXT | true | (direct) |
| DOSAGE | NONE | false | (special) |
| Type ID / FHIR | openEHR | Primitive | "FLAT / FHIR" Attributes Pairs |
|-----------------|---------------|-----------|------------------------------------------------------|
| NONE | NONE | false | / |
| QUANTITY | DV_QUANTITY | true | magnitude / value <br/> unit / unit |
| QUANTITY | DV_ORDINAL | true | ordinal / value <br/> value / unit <br/> code / code |
| QUANTITY | DV_COUNT | true | (direct) |
| DATETIME | DV_DATE_TIME | true | (direct) |
| CODEABLECONCEPT | DV_CODED_TEXT | false | **_nested_** / coding <br/> value / text |
| CODING | CODE_PHRASE | true | code / code <br/> 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.
A simple `QUANTITY`/`DV_QUANTITY` example is illustrated in the following code block.
It implicitly maps the matching attributes (openEHR FLAT: `magnitude`, `value`; FHIR: `value`, `unit`) too.

```yaml
Expand All @@ -167,6 +170,13 @@ It implicitly maps the matching attributes (openEHR FLAT: `magnitude`, `value`;
type: "QUANTITY"
```
The Type ID `QUANTITY` also shows overlapping types.
This is construct is necessary, because FHIR and openEHR have different paradigms on base data types.
In Model Mappings, usage of `type: "QUANTITY"` would refer to either of the linked openEHR types (as per table above).
Using the openEHR modeling information from the Template, the mapping engine can infer the correct type at the given path.
It will select the matching supported type.
For instance, in the case of `QUANTITY`, either one of `DV_QUANTITY`, `DV_ORDINAL`, or `DV_COUNT`.

`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 supported 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)).
Expand Down Expand Up @@ -389,7 +399,7 @@ Mapping instances consist of:

### Format

[Model Mapping JSON Schema](src/test/resources/mapping-files-spec/v0-1-0/model-mapping.schema.json)
[Model Mapping JSON Schema](src/test/resources/mapping-files-spec/v0-2-0/model-mapping.schema.json)

File extension: `*.model.yml` or `*.model.yaml`

Expand Down Expand Up @@ -436,7 +446,7 @@ The `fhir` configuration sets the resulting type. This can either be a `Bundle`

### Format

[Contextual Mapping JSON Schema](src/test/resources/mapping-files-spec/v0-1-0/contextual-mapping.schema.json)
[Contextual Mapping JSON Schema](src/test/resources/mapping-files-spec/v0-2-0/contextual-mapping.schema.json)

File extension: `*.context.yml` or `*.context.yaml`

Expand Down Expand Up @@ -496,6 +506,18 @@ To utilize JSON schema validation and autocompletion within YAML files check out

## Changelog

### v0.2.0

#### Added

- DV_ORDINAL and DV_COUNT support

#### Changed

- Data types can be overlapping now
- Context variables need non-overlapping types
- Model Mapping schema: improved validation

### v0.1.0

#### Added
Expand Down
10 changes: 5 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<version>3.1.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>care.better</groupId>
<artifactId>fhir-connect-mapping-spec</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>0.2.0</version>
<name>fhir-connect-mapping-spec</name>
<description>fhir-connect-mapping-spec</description>
<properties>
Expand Down Expand Up @@ -46,9 +46,9 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.erosb</groupId>
<artifactId>everit-json-schema</artifactId>
<version>1.14.1</version>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>1.0.86</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@

package care.better.fhirconnectmappingspec

import com.fasterxml.jackson.databind.JsonNode
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 com.networknt.schema.JsonSchemaFactory
import com.networknt.schema.SpecVersion
import jakarta.xml.bind.ValidationException
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
import java.io.File
Expand All @@ -42,6 +43,11 @@ class MappingSpecTest {
fun testModelMappingsV0_1_0(): Array<File> = File(javaClass.classLoader.getResource("mapping-files-spec/v0-1-0/model-mappings").file).listFiles()
@JvmStatic
fun testContextualMappingsV0_1_0(): Array<File> = File(javaClass.classLoader.getResource("mapping-files-spec/v0-1-0/contextual-mappings").file).listFiles()

@JvmStatic
fun testModelMappingsV0_2_0(): Array<File> = File(javaClass.classLoader.getResource("mapping-files-spec/v0-2-0/model-mappings").file).listFiles()
@JvmStatic
fun testContextualMappingsV0_2_0(): Array<File> = File(javaClass.classLoader.getResource("mapping-files-spec/v0-2-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)
Expand All @@ -50,6 +56,9 @@ class MappingSpecTest {
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)

private val v0_2_0_modelMappingSchema = File(javaClass.classLoader.getResource("mapping-files-spec/v0-2-0/model-mapping.schema.json").file)
private val v0_2_0_contextualMappingSchema = File(javaClass.classLoader.getResource("mapping-files-spec/v0-2-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) {
Expand All @@ -74,24 +83,25 @@ class MappingSpecTest {
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
}
@ParameterizedTest(name = "Validate Model mapping {0}")
@MethodSource("testModelMappingsV0_2_0")
fun `validate Model Mapping with schema v0_2_0`(input: File) {
validate(v0_2_0_modelMappingSchema, input)
}

@ParameterizedTest(name = "Validate Contextual mapping {0}")
@MethodSource("testContextualMappingsV0_2_0")
fun `validate Contextual Mapping with schema v0_2_0`(input: File) {
validate(v0_2_0_contextualMappingSchema, input)
}

private fun validate(schemaStream: File, rawYamlFile: File) {
val factory: JsonSchemaFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012)).objectMapper(mapper).build()
val schema = factory.getSchema(schemaStream.inputStream())

val jsonNode: JsonNode = mapper.readTree(rawYamlFile)
val validateMsg = schema.validate(jsonNode)
if (validateMsg.isNotEmpty()) throw ValidationException(validateMsg.joinToString(";\n") { it.message })
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "http://schema.better.care/fhirconnect/contextual/0.2.0/contextual-mapping-schema",
"description": "Contextual Mapping schema for file format v0.2.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"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
format: "0.2.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"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
format: "0.2.0"

openEHR:
templateId: "OPENeP - Inpatient Prescription"
archetypes:
- "openEHR-EHR-INSTRUCTION.medication_order.v2"

fhir:
resourceType: "Bundle"
Original file line number Diff line number Diff line change
@@ -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.2.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"
Loading

0 comments on commit 1852b4e

Please sign in to comment.