diff --git a/pom.xml b/pom.xml index c8b12a0..676259d 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 7.2.1 1.20.0 2.0.13 - 2.2.0 + 3.0.0-test.1 @@ -177,7 +177,7 @@ download-single - https://github.com/medizininformatik-initiative/fhir-ontology-generator/raw/v${ontology.version}/example/mii_core_data_set/ontology/mapping.zip + https://github.com/medizininformatik-initiative/fhir-ontology-generator/raw/v${ontology.version}/example/fdpg-ontology/mapping.zip ${project.build.directory} diff --git a/src/main/java/de/numcodex/sq2cql/model/MappingContext.java b/src/main/java/de/numcodex/sq2cql/model/MappingContext.java index 8c328c2..f42576e 100644 --- a/src/main/java/de/numcodex/sq2cql/model/MappingContext.java +++ b/src/main/java/de/numcodex/sq2cql/model/MappingContext.java @@ -21,10 +21,10 @@ public class MappingContext { private final Map mappings; - private final TermCodeNode conceptTree; + private final MappingTreeBase conceptTree; private final Map codeSystemDefinitions; - private MappingContext(Map mappings, TermCodeNode conceptTree, + private MappingContext(Map mappings, MappingTreeBase conceptTree, Map codeSystemDefinitions) { this.mappings = mappings; this.conceptTree = conceptTree; @@ -48,7 +48,7 @@ public static MappingContext of() { * @param codeSystemAliases a map of code system URLs to their aliases * @return the mapping context */ - public static MappingContext of(Map mappings, TermCodeNode conceptTree, + public static MappingContext of(Map mappings, MappingTreeBase conceptTree, Map codeSystemAliases) { return new MappingContext(Map.copyOf(mappings), conceptTree, codeSystemAliases.entrySet().stream() .collect(Collectors.toConcurrentMap(Map.Entry::getKey, diff --git a/src/main/java/de/numcodex/sq2cql/model/MappingTreeBase.java b/src/main/java/de/numcodex/sq2cql/model/MappingTreeBase.java new file mode 100644 index 0000000..c3072b6 --- /dev/null +++ b/src/main/java/de/numcodex/sq2cql/model/MappingTreeBase.java @@ -0,0 +1,17 @@ +package de.numcodex.sq2cql.model; + + +import de.numcodex.sq2cql.model.structured_query.ContextualTermCode; + +import java.util.List; +import java.util.stream.Stream; + +public record MappingTreeBase(List moduleRoots) { + + public Stream expand(ContextualTermCode termCode) { + var key = termCode.termCode().code(); + + return moduleRoots.stream().flatMap(moduleRoot -> + moduleRoot.isModuleMatching(termCode) ? moduleRoot.expand(key) : Stream.empty()); + } +} diff --git a/src/main/java/de/numcodex/sq2cql/model/MappingTreeModuleEntry.java b/src/main/java/de/numcodex/sq2cql/model/MappingTreeModuleEntry.java new file mode 100644 index 0000000..e220ccd --- /dev/null +++ b/src/main/java/de/numcodex/sq2cql/model/MappingTreeModuleEntry.java @@ -0,0 +1,16 @@ +package de.numcodex.sq2cql.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record MappingTreeModuleEntry(String key, List children) { + @JsonCreator + static MappingTreeModuleEntry fromJson(@JsonProperty("key") String key, + @JsonProperty("children") List children) { + return new MappingTreeModuleEntry(key, children); + } +} diff --git a/src/main/java/de/numcodex/sq2cql/model/MappingTreeModuleRoot.java b/src/main/java/de/numcodex/sq2cql/model/MappingTreeModuleRoot.java new file mode 100644 index 0000000..1d0cae2 --- /dev/null +++ b/src/main/java/de/numcodex/sq2cql/model/MappingTreeModuleRoot.java @@ -0,0 +1,40 @@ +package de.numcodex.sq2cql.model; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import de.numcodex.sq2cql.model.common.TermCode; +import de.numcodex.sq2cql.model.structured_query.ContextualTermCode; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.function.Function.identity; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record MappingTreeModuleRoot(TermCode context, String system, Map entries) { + @JsonCreator + static MappingTreeModuleRoot fromJson(@JsonProperty("context") TermCode context, + @JsonProperty("system") String system, + @JsonProperty("entries") List entries) { + return new MappingTreeModuleRoot( + context, + system, + entries.stream().collect(Collectors.toMap(MappingTreeModuleEntry::key, identity()))); + } + + public Stream expand(String key) { + var newTermCode = new ContextualTermCode(context, new TermCode(system, key, "")); + + return Stream.concat(Stream.of(newTermCode), entries.get(key).children().stream().flatMap(this::expand)); + } + + boolean isModuleMatching(ContextualTermCode contextualTermCode) { + return context.equals(contextualTermCode.context()) && + system.equals(contextualTermCode.termCode().system()) && + entries.containsKey(contextualTermCode.termCode().code()); + } +} diff --git a/src/main/java/de/numcodex/sq2cql/model/TermCodeNode.java b/src/main/java/de/numcodex/sq2cql/model/TermCodeNode.java deleted file mode 100644 index 99cad59..0000000 --- a/src/main/java/de/numcodex/sq2cql/model/TermCodeNode.java +++ /dev/null @@ -1,61 +0,0 @@ -package de.numcodex.sq2cql.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import de.numcodex.sq2cql.model.common.TermCode; -import de.numcodex.sq2cql.model.structured_query.ContextualTermCode; - -import java.util.List; -import java.util.stream.Stream; - -import static java.util.Objects.requireNonNull; - -/** - * @author Alexander Kiel - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public record TermCodeNode(ContextualTermCode contextualTermCode, List children) { - - public TermCodeNode { - requireNonNull(contextualTermCode); - children = List.copyOf(children); - } - - public static TermCodeNode of(ContextualTermCode termCode) { - return new TermCodeNode(termCode, List.of()); - } - - public static TermCodeNode of(ContextualTermCode termCode, TermCodeNode... children) { - return new TermCodeNode(termCode, List.of(children)); - } - - @JsonCreator - public static TermCodeNode of(@JsonProperty("context") TermCode context, - @JsonProperty("termCode") TermCode termCode, - @JsonProperty("children") TermCodeNode... children) { - var contextualTermCode = ContextualTermCode.of(context, - requireNonNull(termCode, "missing JSON property: termCode")); - return new TermCodeNode(contextualTermCode, - children == null ? List.of() : List.of(children)); - } - - public Stream expand(ContextualTermCode termCode) { - if (requireNonNull(termCode).equals(this.contextualTermCode)) { - return leafConcepts(); - } else if (children.isEmpty()) { - return Stream.of(); - } else { - return children.stream().flatMap(n -> n.expand(termCode)); - } - } - - private Stream leafConcepts() { - if (children.isEmpty()) { - return Stream.of(contextualTermCode); - } else { - return Stream.concat(Stream.of(contextualTermCode), - children.stream().flatMap(TermCodeNode::leafConcepts)); - } - } -} diff --git a/src/main/java/de/numcodex/sq2cql/model/structured_query/AbstractCriterion.java b/src/main/java/de/numcodex/sq2cql/model/structured_query/AbstractCriterion.java index 544fd3c..c3446f2 100644 --- a/src/main/java/de/numcodex/sq2cql/model/structured_query/AbstractCriterion.java +++ b/src/main/java/de/numcodex/sq2cql/model/structured_query/AbstractCriterion.java @@ -71,7 +71,7 @@ static Container retrieveExpr(MappingContext mappingContext, } private static String referenceName(TermCode termCode) { - return "%s".formatted(termCode.display() + termCode.code() + "Ref"); + return termCode.code() + "Ref"; } public abstract T appendAttributeFilter(AttributeFilter attributeFilter); diff --git a/src/test/java/de/numcodex/sq2cql/AcceptanceTest.java b/src/test/java/de/numcodex/sq2cql/AcceptanceTest.java index 98e143d..8efc183 100644 --- a/src/test/java/de/numcodex/sq2cql/AcceptanceTest.java +++ b/src/test/java/de/numcodex/sq2cql/AcceptanceTest.java @@ -7,7 +7,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import de.numcodex.sq2cql.model.structured_query.StructuredQuery; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Library; +import org.hl7.fhir.r4.model.Measure; +import org.hl7.fhir.r4.model.MeasureReport; +import org.hl7.fhir.r4.model.Parameters; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -32,9 +36,8 @@ import java.nio.file.Paths; import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.UUID; -import java.util.zip.ZipFile; +import java.util.stream.Stream; import static de.numcodex.sq2cql.Util.createTranslator; import static java.lang.String.format; @@ -78,41 +81,14 @@ private static Bundle createBundle(Library library, Measure measure) { } public static List getTestQueriesReturningOnePatient() throws URISyntaxException, IOException { - var exclusions = Set.of("new_testdata/1-age.json", - // Blaze can't parse the unit [arb'U]/mL https://github.com/samply/blaze/issues/1234 - "new_testdata/ObservationLab-38dfe76b-ae35-8290-6d80-ab08c963d148", - "new_testdata/ObservationLab-16408169-a38d-8afc-fdd2-ed7af97ccc57", - "new_testdata/ObservationLab-0fa07a3f-2e29-5065-6fa2-31e959acdd98", - "new_testdata/ObservationLab-43eb280e-7901-7990-64e3-22cfa51de78b", - "new_testdata/ObservationLab-09c67417-306a-a871-feef-71cbc915d113", - "new_testdata/ObservationLab-26184c80-edf6-b1e0-ee8f-0e0999755cb9", - "new_testdata/ObservationLab-9d44c93e-7799-a8e2-b368-c5539c30ceaa", - "new_testdata/ObservationLab-755a3ac1-32ae-2a20-1ac9-02ee25777cf0", - "new_testdata/ObservationLab-8ec9ea98-6581-f934-9bcf-b1c4f87e3560", - "new_testdata/ObservationLab-315e8080-7425-f4e9-3891-aef5ebe0572c", - "new_testdata/ObservationLab-44c8fd00-1a0f-f218-9eb8-83257add8fed", - "new_testdata/ObservationLab-7a2be049-40d2-d16f-3db6-12f46df2fc82", - "new_testdata/ObservationLab-78c5a976-1786-72e8-006b-8fd6af157ed9", - "new_testdata/ObservationLab-254bf7ae-1d0a-b994-f20b-575d4e28e674", - "new_testdata/ObservationLab-4bf41e10-1c62-2f82-d081-3d923aca43f2", - // Blaze can't parse the unit /[HPF] - "new_testdata/ObservationLab-bf7b68ae-1f89-41b6-e6a1-a40bf031f4b9", - "new_testdata/ObservationLab-b080e003-5e7f-503c-4b13-47f601d6d903", - "new_testdata/ObservationLab-3dd0c866-0649-def5-0fb2-de1ea0b976c2", - "new_testdata/ObservationLab-98b33c6e-0a14-b90a-7795-e98680ee526e", - // Blaze can't parse the unit /100{WBCs} - "new_testdata/ObservationLab-d2d07223-0b20-ee0f-8505-0a17d2e1ed4d" - ); - try (var zipFile = new ZipFile(resourcePath("returningOnePatient.zip").toString())) { - return zipFile.stream() - .filter(entry -> !exclusions.contains(entry.toString())) - .map(entry -> { - try { - return new ObjectMapper().readValue(zipFile.getInputStream(entry), StructuredQuery.class); - } catch (IOException e) { - throw new RuntimeException(e); - } - }).toList(); + try (Stream paths = Files.list(resourcePath("returningOnePatient"))) { + return paths.map(path -> { + try { + return new ObjectMapper().readValue(Files.readString(path), StructuredQuery.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).toList(); } } diff --git a/src/test/java/de/numcodex/sq2cql/EvaluationIT.java b/src/test/java/de/numcodex/sq2cql/EvaluationIT.java index a607782..4db9a2c 100644 --- a/src/test/java/de/numcodex/sq2cql/EvaluationIT.java +++ b/src/test/java/de/numcodex/sq2cql/EvaluationIT.java @@ -5,9 +5,7 @@ import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.StringParam; import com.fasterxml.jackson.databind.ObjectMapper; -import de.numcodex.sq2cql.model.Mapping; -import de.numcodex.sq2cql.model.MappingContext; -import de.numcodex.sq2cql.model.TermCodeNode; +import de.numcodex.sq2cql.model.*; import de.numcodex.sq2cql.model.common.TermCode; import de.numcodex.sq2cql.model.structured_query.ContextualConcept; import de.numcodex.sq2cql.model.structured_query.ContextualTermCode; @@ -31,6 +29,7 @@ import java.util.Map; import java.util.UUID; +import static de.numcodex.sq2cql.Util.createTreeWithoutChildren; import static de.numcodex.sq2cql.model.common.Comparator.LESS_THAN; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; @@ -42,8 +41,9 @@ public class EvaluationIT { static final TermCode CONTEXT = TermCode.of("context", "context", "context"); - static final ContextualTermCode ROOT = ContextualTermCode.of(CONTEXT, TermCode.of("", "", "")); - static final ContextualTermCode BLOOD_PRESSURE = ContextualTermCode.of(CONTEXT, TermCode.of("http://loinc.org", "85354-9", + static final String BLOOD_PRESSURE_CODE = "85354-9"; + static final String BLOOD_PRESSURE_SYSTEM = "http://loinc.org"; + static final ContextualTermCode BLOOD_PRESSURE = ContextualTermCode.of(CONTEXT, TermCode.of(BLOOD_PRESSURE_SYSTEM, BLOOD_PRESSURE_CODE, "Blood pressure panel with all children optional")); static final TermCode DIASTOLIC_BLOOD_PRESSURE = TermCode.of("http://loinc.org", "8462-4", "Diastolic blood pressure"); @@ -99,7 +99,7 @@ public void evaluateBloodPressure() throws Exception { var valueFhirPath = format("component.where(code.coding.exists(system = '%s' and code = '%s')).value.first()", DIASTOLIC_BLOOD_PRESSURE.system(), DIASTOLIC_BLOOD_PRESSURE.code()); var mappings = Map.of(BLOOD_PRESSURE, Mapping.of(BLOOD_PRESSURE, "Observation", valueFhirPath)); - var conceptTree = TermCodeNode.of(ROOT, TermCodeNode.of(BLOOD_PRESSURE)); + var conceptTree = createTreeWithoutChildren(BLOOD_PRESSURE); var mappingContext = MappingContext.of(mappings, conceptTree, CODE_SYSTEM_ALIASES); var translator = Translator.of(mappingContext); var criterion = NumericCriterion.of(ContextualConcept.of(BLOOD_PRESSURE), LESS_THAN, BigDecimal.valueOf(80), "mm[Hg]"); @@ -157,7 +157,7 @@ public void evaluateBloodPressureAttribute() throws Exception { } """); var mappings = Map.of(BLOOD_PRESSURE, mapping); - var conceptTree = TermCodeNode.of(ROOT, TermCodeNode.of(BLOOD_PRESSURE)); + var conceptTree = createTreeWithoutChildren(BLOOD_PRESSURE); var mappingContext = MappingContext.of(mappings, conceptTree, CODE_SYSTEM_ALIASES); var translator = Translator.of(mappingContext); var structuredQuery = readStructuredQuery(""" diff --git a/src/test/java/de/numcodex/sq2cql/MedicationAdministrationTest.java b/src/test/java/de/numcodex/sq2cql/MedicationAdministrationTest.java index 7f6cc7c..7b174e7 100644 --- a/src/test/java/de/numcodex/sq2cql/MedicationAdministrationTest.java +++ b/src/test/java/de/numcodex/sq2cql/MedicationAdministrationTest.java @@ -43,7 +43,7 @@ public void translateMedicationAdministration() throws Exception { context Unfiltered - define HeparinB01AB01Ref: + define B01AB01Ref: from [Medication: Code 'B01AB01' from atc] M return 'Medication/' + M.id @@ -51,7 +51,7 @@ public void translateMedicationAdministration() throws Exception { define Criterion: exists (from [MedicationAdministration] M - where M.medication.reference in HeparinB01AB01Ref) + where M.medication.reference in B01AB01Ref) define InInitialPopulation: Criterion @@ -74,7 +74,7 @@ public void translateMedicationAdministrationTimeRestriction() throws Exception context Unfiltered - define HeparinB01AB01Ref: + define B01AB01Ref: from [Medication: Code 'B01AB01' from atc] M return 'Medication/' + M.id @@ -82,7 +82,7 @@ public void translateMedicationAdministrationTimeRestriction() throws Exception define Criterion: exists (from [MedicationAdministration] M - where M.medication.reference in HeparinB01AB01Ref and + where M.medication.reference in B01AB01Ref and (ToDate(M.effective as dateTime) in Interval[@2024-01-01, @2024-02-01] or M.effective overlaps Interval[@2024-01-01, @2024-02-01])) @@ -107,7 +107,7 @@ public void translateMedicationAdministrationDoubleCriteria() throws Exception { context Unfiltered - define HeparinB01AB01Ref: + define B01AB01Ref: from [Medication: Code 'B01AB01' from atc] M return 'Medication/' + M.id @@ -115,13 +115,13 @@ public void translateMedicationAdministrationDoubleCriteria() throws Exception { define "Criterion 1": exists (from [MedicationAdministration] M - where M.medication.reference in HeparinB01AB01Ref and + where M.medication.reference in B01AB01Ref and (ToDate(M.effective as dateTime) in Interval[@2024-01-01, @2024-02-01] or M.effective overlaps Interval[@2024-01-01, @2024-02-01])) define "Criterion 2": exists (from [MedicationAdministration] M - where M.medication.reference in HeparinB01AB01Ref and + where M.medication.reference in B01AB01Ref and (ToDate(M.effective as dateTime) in Interval[@2023-01-01, @2023-02-01] or M.effective overlaps Interval[@2023-01-01, @2023-02-01])) @@ -142,29 +142,29 @@ public void translateMedicationAdministrationTwoCriteria() throws Exception { library Retrieve version '1.0.0' using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' - + codesystem atc: 'http://fhir.de/CodeSystem/bfarm/atc' - + context Unfiltered - - define "AcetylsalicylsäureB01AC06Ref": - from [Medication: Code 'B01AC06' from atc] M - return 'Medication/' + M.id - - define HeparinB01AB01Ref: + + define B01AB01Ref: from [Medication: Code 'B01AB01' from atc] M return 'Medication/' + M.id - + + define B01AC06Ref: + from [Medication: Code 'B01AC06' from atc] M + return 'Medication/' + M.id + context Patient - + define "Criterion 1": exists (from [MedicationAdministration] M - where M.medication.reference in HeparinB01AB01Ref) - + where M.medication.reference in B01AB01Ref) + define "Criterion 2": exists (from [MedicationAdministration] M - where M.medication.reference in "AcetylsalicylsäureB01AC06Ref") - + where M.medication.reference in B01AC06Ref) + define InInitialPopulation: "Criterion 1" and "Criterion 2" diff --git a/src/test/java/de/numcodex/sq2cql/SpecimenTest.java b/src/test/java/de/numcodex/sq2cql/SpecimenTest.java index 11eaab2..59c8c77 100644 --- a/src/test/java/de/numcodex/sq2cql/SpecimenTest.java +++ b/src/test/java/de/numcodex/sq2cql/SpecimenTest.java @@ -35,45 +35,54 @@ public void translate() throws Exception { var library = translator.toCql(structuredQuery); assertThat(library).printsTo(""" - library Retrieve version '1.0.0' - using FHIR version '4.0.0' - include FHIRHelpers version '4.0.0' - - codesystem icd10: 'http://fhir.de/CodeSystem/bfarm/icd-10-gm' - codesystem snomed: 'http://snomed.info/sct' - - context Patient - - define "Diagnose E13.9": - [Condition: Code 'E13.9' from icd10] union - [Condition: Code 'E13.91' from icd10] union - [Condition: Code 'E13.90' from icd10] - - define Criterion: - exists (from [Specimen: Code '119364003' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '258590006' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '866034009' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '2421000181104' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '866035005' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '442427000' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '737089009' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) - - define InInitialPopulation: - Criterion + library Retrieve version '1.0.0' + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + codesystem icd10: 'http://fhir.de/CodeSystem/bfarm/icd-10-gm' + codesystem snomed: 'http://snomed.info/sct' + + context Patient + + define "Diagnose E13.9": + [Condition: Code 'E13.9' from icd10] union + [Condition: Code 'E13.91' from icd10] union + [Condition: Code 'E13.90' from icd10] + + define Criterion: + exists (from [Specimen: Code '119364003' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '737089009' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '442427000' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '122591000' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '258590006' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '122589008' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '866035005' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '122590004' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '866034009' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '442166002' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) + + define InInitialPopulation: + Criterion """); } @@ -85,55 +94,64 @@ public void translateExclusion() throws Exception { var library = translator.toCql(structuredQuery); assertThat(library).printsTo(""" - library Retrieve version '1.0.0' - using FHIR version '4.0.0' - include FHIRHelpers version '4.0.0' - - codesystem icd10: 'http://fhir.de/CodeSystem/bfarm/icd-10-gm' - codesystem snomed: 'http://snomed.info/sct' - - context Patient - - define "Criterion 1": - Patient.gender = 'female' - - define Inclusion: - "Criterion 1" - - define "Diagnose E13.9": - [Condition: Code 'E13.9' from icd10] union - [Condition: Code 'E13.91' from icd10] union - [Condition: Code 'E13.90' from icd10] - - define "Criterion 2": - exists (from [Specimen: Code '119364003' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '258590006' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '866034009' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '2421000181104' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '866035005' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '442427000' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '737089009' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) - - define Exclusion: - "Criterion 2" - - define InInitialPopulation: - Inclusion and - not Exclusion + library Retrieve version '1.0.0' + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + codesystem icd10: 'http://fhir.de/CodeSystem/bfarm/icd-10-gm' + codesystem snomed: 'http://snomed.info/sct' + + context Patient + + define "Criterion 1": + Patient.gender = 'female' + + define Inclusion: + "Criterion 1" + + define "Diagnose E13.9": + [Condition: Code 'E13.9' from icd10] union + [Condition: Code 'E13.91' from icd10] union + [Condition: Code 'E13.90' from icd10] + + define "Criterion 2": + exists (from [Specimen: Code '119364003' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '737089009' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '442427000' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '122591000' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '258590006' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '122589008' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '866035005' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '122590004' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '866034009' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '442166002' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) + + define Exclusion: + "Criterion 2" + + define InInitialPopulation: + Inclusion and + not Exclusion """); } @@ -145,49 +163,58 @@ public void translateTwoInclusion() throws Exception { var library = translator.toCql(structuredQuery); assertThat(library).printsTo(""" - library Retrieve version '1.0.0' - using FHIR version '4.0.0' - include FHIRHelpers version '4.0.0' - - codesystem icd10: 'http://fhir.de/CodeSystem/bfarm/icd-10-gm' - codesystem snomed: 'http://snomed.info/sct' - - context Patient - - define "Criterion 1": - Patient.gender = 'female' - - define "Diagnose E13.9": - [Condition: Code 'E13.9' from icd10] union - [Condition: Code 'E13.91' from icd10] union - [Condition: Code 'E13.90' from icd10] - - define "Criterion 2": - exists (from [Specimen: Code '119364003' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '258590006' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '866034009' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '2421000181104' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '866035005' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '442427000' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '737089009' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) - - define InInitialPopulation: - "Criterion 1" and - "Criterion 2" + library Retrieve version '1.0.0' + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + codesystem icd10: 'http://fhir.de/CodeSystem/bfarm/icd-10-gm' + codesystem snomed: 'http://snomed.info/sct' + + context Patient + + define "Criterion 1": + Patient.gender = 'female' + + define "Diagnose E13.9": + [Condition: Code 'E13.9' from icd10] union + [Condition: Code 'E13.91' from icd10] union + [Condition: Code 'E13.90' from icd10] + + define "Criterion 2": + exists (from [Specimen: Code '119364003' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '737089009' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '442427000' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '122591000' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '258590006' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '122589008' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '866035005' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '122590004' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '866034009' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '442166002' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) + + define InInitialPopulation: + "Criterion 1" and + "Criterion 2" """); } @@ -199,47 +226,56 @@ public void translateTwoReferenceCriteria() throws Exception { var library = translator.toCql(structuredQuery); assertThat(library).printsTo(""" - library Retrieve version '1.0.0' - using FHIR version '4.0.0' - include FHIRHelpers version '4.0.0' - - codesystem icd10: 'http://fhir.de/CodeSystem/bfarm/icd-10-gm' - codesystem snomed: 'http://snomed.info/sct' - - context Patient - - define "Diagnose E13.9 and Diagnose E13.1": - [Condition: Code 'E13.9' from icd10] union - [Condition: Code 'E13.91' from icd10] union - [Condition: Code 'E13.90' from icd10] union - [Condition: Code 'E13.1' from icd10] union - [Condition: Code 'E13.11' from icd10] - - define Criterion: - exists (from [Specimen: Code '119364003' from snomed] S - with "Diagnose E13.9 and Diagnose E13.1" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '258590006' from snomed] S - with "Diagnose E13.9 and Diagnose E13.1" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '866034009' from snomed] S - with "Diagnose E13.9 and Diagnose E13.1" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '2421000181104' from snomed] S - with "Diagnose E13.9 and Diagnose E13.1" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '866035005' from snomed] S - with "Diagnose E13.9 and Diagnose E13.1" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '442427000' from snomed] S - with "Diagnose E13.9 and Diagnose E13.1" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or - exists (from [Specimen: Code '737089009' from snomed] S - with "Diagnose E13.9 and Diagnose E13.1" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) - - define InInitialPopulation: - Criterion + library Retrieve version '1.0.0' + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + codesystem icd10: 'http://fhir.de/CodeSystem/bfarm/icd-10-gm' + codesystem snomed: 'http://snomed.info/sct' + + context Patient + + define "Diagnose E13.9 and Diagnose E13.1": + [Condition: Code 'E13.9' from icd10] union + [Condition: Code 'E13.91' from icd10] union + [Condition: Code 'E13.90' from icd10] union + [Condition: Code 'E13.1' from icd10] union + [Condition: Code 'E13.11' from icd10] + + define Criterion: + exists (from [Specimen: Code '119364003' from snomed] S + with "Diagnose E13.9 and Diagnose E13.1" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '737089009' from snomed] S + with "Diagnose E13.9 and Diagnose E13.1" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '442427000' from snomed] S + with "Diagnose E13.9 and Diagnose E13.1" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '122591000' from snomed] S + with "Diagnose E13.9 and Diagnose E13.1" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '258590006' from snomed] S + with "Diagnose E13.9 and Diagnose E13.1" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '122589008' from snomed] S + with "Diagnose E13.9 and Diagnose E13.1" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '866035005' from snomed] S + with "Diagnose E13.9 and Diagnose E13.1" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '122590004' from snomed] S + with "Diagnose E13.9 and Diagnose E13.1" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '866034009' from snomed] S + with "Diagnose E13.9 and Diagnose E13.1" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) or + exists (from [Specimen: Code '442166002' from snomed] S + with "Diagnose E13.9 and Diagnose E13.1" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id) + + define InInitialPopulation: + Criterion """); } @@ -251,53 +287,65 @@ public void translateAndBodySite() throws Exception { var library = translator.toCql(structuredQuery); assertThat(library).printsTo(""" - library Retrieve version '1.0.0' - using FHIR version '4.0.0' - include FHIRHelpers version '4.0.0' - - codesystem icd10: 'http://fhir.de/CodeSystem/bfarm/icd-10-gm' - codesystem icd_o_3: 'urn:oid:2.16.840.1.113883.6.43.1' - codesystem snomed: 'http://snomed.info/sct' - - context Patient - - define "Diagnose E13.9": - [Condition: Code 'E13.9' from icd10] union - [Condition: Code 'E13.91' from icd10] union - [Condition: Code 'E13.90' from icd10] - - define Criterion: - exists (from [Specimen: Code '119364003' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id - where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or - exists (from [Specimen: Code '258590006' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id - where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or - exists (from [Specimen: Code '866034009' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id - where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or - exists (from [Specimen: Code '2421000181104' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id - where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or - exists (from [Specimen: Code '866035005' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id - where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or - exists (from [Specimen: Code '442427000' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id - where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or - exists (from [Specimen: Code '737089009' from snomed] S - with "Diagnose E13.9" C - such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id - where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) - - define InInitialPopulation: - Criterion + library Retrieve version '1.0.0' + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + codesystem icd10: 'http://fhir.de/CodeSystem/bfarm/icd-10-gm' + codesystem icd_o_3: 'urn:oid:2.16.840.1.113883.6.43.1' + codesystem snomed: 'http://snomed.info/sct' + + context Patient + + define "Diagnose E13.9": + [Condition: Code 'E13.9' from icd10] union + [Condition: Code 'E13.91' from icd10] union + [Condition: Code 'E13.90' from icd10] + + define Criterion: + exists (from [Specimen: Code '119364003' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id + where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or + exists (from [Specimen: Code '737089009' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id + where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or + exists (from [Specimen: Code '442427000' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id + where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or + exists (from [Specimen: Code '122591000' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id + where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or + exists (from [Specimen: Code '258590006' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id + where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or + exists (from [Specimen: Code '122589008' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id + where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or + exists (from [Specimen: Code '866035005' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id + where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or + exists (from [Specimen: Code '122590004' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id + where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or + exists (from [Specimen: Code '866034009' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id + where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) or + exists (from [Specimen: Code '442166002' from snomed] S + with "Diagnose E13.9" C + such that S.extension.where(url='https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Diagnose').first().value.as(Reference).reference contains 'Condition/' + C.id + where S.collection.bodySite.coding contains Code 'C44.6' from icd_o_3) + + define InInitialPopulation: + Criterion """); } } diff --git a/src/test/java/de/numcodex/sq2cql/TranslatorTest.java b/src/test/java/de/numcodex/sq2cql/TranslatorTest.java index 7708375..a079034 100644 --- a/src/test/java/de/numcodex/sq2cql/TranslatorTest.java +++ b/src/test/java/de/numcodex/sq2cql/TranslatorTest.java @@ -1,10 +1,7 @@ package de.numcodex.sq2cql; import com.fasterxml.jackson.databind.ObjectMapper; -import de.numcodex.sq2cql.model.AttributeMapping; -import de.numcodex.sq2cql.model.Mapping; -import de.numcodex.sq2cql.model.MappingContext; -import de.numcodex.sq2cql.model.TermCodeNode; +import de.numcodex.sq2cql.model.*; import de.numcodex.sq2cql.model.common.TermCode; import de.numcodex.sq2cql.model.structured_query.*; import org.junit.jupiter.api.Nested; @@ -15,6 +12,7 @@ import java.util.Map; import static de.numcodex.sq2cql.Assertions.assertThat; +import static de.numcodex.sq2cql.Util.*; import static de.numcodex.sq2cql.model.common.Comparator.LESS_THAN; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -118,7 +116,7 @@ void nonExpandableConcept() { @Test void nonMappableConcept() { - var conceptTree = TermCodeNode.of(C71, TermCodeNode.of(C71_0), TermCodeNode.of(C71_1)); + var conceptTree = createTreeWithChildren(C71, C71_0, C71_1); var mappingContext = MappingContext.of(Map.of(), conceptTree, CODE_SYSTEM_ALIASES); var message = assertThrows(TranslationException.class, () -> Translator.of(mappingContext) @@ -136,7 +134,7 @@ void usage_Documentation() { TermCode.of("http://fhir.de/CodeSystem/bfarm/icd-10-gm", "C71.1", "Malignant neoplasm of brain")); var mappings = Map.of(c71_1, Mapping.of(c71_1, "Condition")); - var conceptTree = TermCodeNode.of(c71_1); + var conceptTree = createTreeWithoutChildren(c71_1); var codeSystemAliases = Map.of("http://fhir.de/CodeSystem/bfarm/icd-10-gm", "icd10"); var mappingContext = MappingContext.of(mappings, conceptTree, codeSystemAliases); @@ -167,7 +165,7 @@ void timeRestriction() { "Malignant neoplasm of brain")); var mappings = Map.of(c71_1, Mapping.of(c71_1, "Condition", null, null, List.of(), List.of(), "onset")); - var conceptTree = TermCodeNode.of(c71_1); + var conceptTree = createTreeWithoutChildren(c71_1); var codeSystemAliases = Map.of("http://fhir.de/CodeSystem/bfarm/icd-10-gm", "icd10"); var mappingContext = MappingContext.of(mappings, conceptTree, codeSystemAliases); @@ -201,7 +199,7 @@ void timeRestriction_missingPathInMapping() { "Malignant neoplasm of brain")); var mappings = Map.of(c71_1, Mapping.of(c71_1, "Condition", null, null, List.of(), List.of(), null)); - var conceptTree = TermCodeNode.of(c71_1); + var conceptTree = createTreeWithoutChildren(c71_1); var codeSystemAliases = Map.of("http://fhir.de/CodeSystem/bfarm/icd-10-gm", "icd10"); var mappingContext = MappingContext.of(mappings, conceptTree, codeSystemAliases); var query = StructuredQuery.of(List.of(List.of(ConceptCriterion.of(ContextualConcept.of(c71_1), @@ -220,8 +218,9 @@ void test_Task1() { Mapping.of(C71_1, "Condition", null, null, List.of(), List.of(VERIFICATION_STATUS_ATTR_MAPPING)), TMZ, Mapping.of(TMZ, "MedicationStatement")); - var conceptTree = TermCodeNode.of(ROOT, TermCodeNode.of(TMZ), - TermCodeNode.of(C71, TermCodeNode.of(C71_0), TermCodeNode.of(C71_1))); + var conceptTree = new MappingTreeBase(List.of( + createTreeRootWithoutChildren(TMZ), + createTreeRootWithChildren(C71, C71_0, C71_1))); var mappingContext = MappingContext.of(mappings, conceptTree, CODE_SYSTEM_ALIASES); var structuredQuery = StructuredQuery.of(List.of(List.of( ConceptCriterion.of(ContextualConcept.of(C71)) @@ -270,8 +269,10 @@ void test_Task2() { Mapping.of(HYPERTENSION, "Condition", null, null, List.of(), List.of(VERIFICATION_STATUS_ATTR_MAPPING)), SERUM, Mapping.of(SERUM, "Specimen"), LIPID, Mapping.of(LIPID, "MedicationStatement")); - var conceptTree = TermCodeNode.of(ROOT, TermCodeNode.of(HYPERTENSION), TermCodeNode.of(SERUM), - TermCodeNode.of(LIPID)); + var conceptTree = new MappingTreeBase(List.of( + createTreeRootWithoutChildren(HYPERTENSION), + createTreeRootWithoutChildren(SERUM), + createTreeRootWithoutChildren(LIPID))); var mappingContext = MappingContext.of(mappings, conceptTree, CODE_SYSTEM_ALIASES); var structuredQuery = StructuredQuery.of(List.of(List.of( ConceptCriterion.of(ContextualConcept.of(HYPERTENSION)) @@ -324,7 +325,7 @@ void geccoTask2() { Mapping.of(G47_31, "Condition", null, null, List.of(CodingModifier.of("verificationStatus.coding", CONFIRMED)), List.of()), TOBACCO_SMOKING_STATUS, Mapping.of(TOBACCO_SMOKING_STATUS, "Observation", "value")); - var conceptTree = TermCodeNode.of(ROOT, TermCodeNode.of(COPD), TermCodeNode.of(G47_31)); + var conceptTree = new MappingTreeBase(List.of(createTreeRootWithoutChildren(COPD), createTreeRootWithoutChildren(G47_31))); var mappingContext = MappingContext.of(mappings, conceptTree, CODE_SYSTEM_ALIASES); var structuredQuery = StructuredQuery.of( List.of(List.of(ValueSetCriterion.of(ContextualConcept.of(FRAILTY_SCORE), VERY_FIT, WELL))), @@ -467,7 +468,7 @@ void onlyFixedCriteria() throws Exception { } """); - var conceptTree = TermCodeNode.of(ROOT, TermCodeNode.of(COMBINED_CONSENT)); + var conceptTree = createTreeWithoutChildren(COMBINED_CONSENT); var mappings = Map.of(COMBINED_CONSENT, mapping); var mappingContext = MappingContext.of(mappings, conceptTree, CODE_SYSTEM_ALIASES); @@ -549,7 +550,7 @@ void numericAgeTranslation() throws Exception { ] } """); - var conceptTree = TermCodeNode.of(ROOT, TermCodeNode.of(AGE)); + var conceptTree = createTreeWithoutChildren(AGE); var mappings = Map.of(AGE, mapping); var mappingContext = MappingContext.of(mappings, conceptTree, CODE_SYSTEM_ALIASES); @@ -625,7 +626,7 @@ void ageRangeTranslation() throws Exception { ] } """); - var conceptTree = TermCodeNode.of(ROOT, TermCodeNode.of(AGE)); + var conceptTree = createTreeWithoutChildren(AGE); var mappings = Map.of(AGE, mapping); var mappingContext = MappingContext.of(mappings, conceptTree, CODE_SYSTEM_ALIASES); @@ -701,7 +702,7 @@ void numericAgeTranslationInHours() throws Exception { ] } """); - var conceptTree = TermCodeNode.of(ROOT, TermCodeNode.of(AGE)); + var conceptTree = createTreeWithoutChildren(AGE); var mappings = Map.of(AGE, mapping); var mappingContext = MappingContext.of(mappings, conceptTree, CODE_SYSTEM_ALIASES); @@ -777,7 +778,7 @@ void patientGender() throws Exception { ] } """); - var conceptTree = TermCodeNode.of(ROOT, TermCodeNode.of(GENDER)); + var conceptTree = createTreeWithoutChildren(GENDER); var mappings = Map.of(GENDER, mapping); var mappingContext = MappingContext.of(mappings, conceptTree, CODE_SYSTEM_ALIASES); @@ -844,7 +845,7 @@ void consent() throws Exception { ] } """); - var conceptTree = TermCodeNode.of(ROOT, TermCodeNode.of(CONSENT_MDAT)); + var conceptTree = createTreeWithoutChildren(CONSENT_MDAT); var mappings = Map.of(CONSENT_MDAT, mapping); var mappingContext = MappingContext.of(mappings, conceptTree, CODE_SYSTEM_ALIASES); @@ -938,7 +939,7 @@ void bloodPressure() throws Exception { ] } """); - var conceptTree = TermCodeNode.of(ROOT, TermCodeNode.of(BLOOD_PRESSURE)); + var conceptTree = createTreeWithoutChildren(BLOOD_PRESSURE); var mappings = Map.of(BLOOD_PRESSURE, mapping); var mappingContext = MappingContext.of(mappings, conceptTree, CODE_SYSTEM_ALIASES); diff --git a/src/test/java/de/numcodex/sq2cql/Util.java b/src/test/java/de/numcodex/sq2cql/Util.java index 8afcd58..46f305f 100644 --- a/src/test/java/de/numcodex/sq2cql/Util.java +++ b/src/test/java/de/numcodex/sq2cql/Util.java @@ -2,13 +2,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Functions; -import de.numcodex.sq2cql.model.Mapping; -import de.numcodex.sq2cql.model.MappingContext; -import de.numcodex.sq2cql.model.TermCodeNode; +import de.numcodex.sq2cql.model.*; import de.numcodex.sq2cql.model.structured_query.ContextualTermCode; import java.io.IOException; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.zip.ZipFile; @@ -38,17 +37,18 @@ public interface Util { entry("http://fhir.de/CodeSystem/bfarm/ops", "oops")); private static Map readMappings(ZipFile zipFile) throws IOException { - try (var in = zipFile.getInputStream(zipFile.getEntry("ontology/mapping/mapping_cql.json"))) { + try (var in = zipFile.getInputStream(zipFile.getEntry("mapping/cql/mapping_cql.json"))) { var mapper = new ObjectMapper(); return Arrays.stream(mapper.readValue(in, Mapping[].class)) .collect(Collectors.toMap(Mapping::key, Functions.identity())); } } - private static TermCodeNode readConceptTree(ZipFile zipFile) throws IOException { - try (var in = zipFile.getInputStream(zipFile.getEntry("ontology/mapping/mapping_tree.json"))) { + private static MappingTreeBase readConceptTree(ZipFile zipFile) throws IOException { + try (var in = zipFile.getInputStream(zipFile.getEntry("mapping/mapping_tree.json"))) { var mapper = new ObjectMapper(); - return mapper.readValue(in, TermCodeNode.class); + return new MappingTreeBase( + Arrays.stream(mapper.readValue(in, MappingTreeModuleRoot[].class)).toList()); } } @@ -60,4 +60,25 @@ static Translator createTranslator() throws Exception { return Translator.of(mappingContext); } } + + static MappingTreeBase createTreeWithoutChildren(ContextualTermCode c) { + return new MappingTreeBase(List.of(new MappingTreeModuleRoot(c.context(), c.termCode().system(), Map.of(c.termCode().code(), + new MappingTreeModuleEntry(c.termCode().code(), List.of()))))); + } + + static MappingTreeBase createTreeWithChildren(ContextualTermCode c, ContextualTermCode child1, ContextualTermCode child2) { + return new MappingTreeBase(List.of(createTreeRootWithChildren(c, child1, child2))); + } + + static MappingTreeModuleRoot createTreeRootWithChildren(ContextualTermCode c, ContextualTermCode child1, ContextualTermCode child2) { + return new MappingTreeModuleRoot(c.context(), c.termCode().system(), Map.of( + c.termCode().code(), new MappingTreeModuleEntry(c.termCode().code(), List.of(child1.termCode().code(), child2.termCode().code())), + child1.termCode().code(), new MappingTreeModuleEntry(child1.termCode().code(), List.of()), + child2.termCode().code(), new MappingTreeModuleEntry(child2.termCode().code(), List.of()))); + } + + static MappingTreeModuleRoot createTreeRootWithoutChildren(ContextualTermCode c) { + return new MappingTreeModuleRoot(c.context(), c.termCode().system(), Map.of( + c.termCode().code(), new MappingTreeModuleEntry(c.termCode().code(), List.of()))); + } } diff --git a/src/test/java/de/numcodex/sq2cql/model/MappingContextTest.java b/src/test/java/de/numcodex/sq2cql/model/MappingContextTest.java index 917b451..9163eac 100644 --- a/src/test/java/de/numcodex/sq2cql/model/MappingContextTest.java +++ b/src/test/java/de/numcodex/sq2cql/model/MappingContextTest.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; +import static de.numcodex.sq2cql.Util.createTreeWithoutChildren; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -38,7 +39,7 @@ void expandConcept_EmptyTree() { @Test void expandConcept_MissingMapping() { - var context = MappingContext.of(Map.of(), TermCodeNode.of(C1), Map.of()); + var context = MappingContext.of(Map.of(), createTreeWithoutChildren(C1), Map.of()); var termCodes = context.expandConcept(ContextualConcept.of(C1)).toList(); diff --git a/src/test/java/de/numcodex/sq2cql/model/MappingTreeBaseTest.java b/src/test/java/de/numcodex/sq2cql/model/MappingTreeBaseTest.java new file mode 100644 index 0000000..53cc47d --- /dev/null +++ b/src/test/java/de/numcodex/sq2cql/model/MappingTreeBaseTest.java @@ -0,0 +1,288 @@ +package de.numcodex.sq2cql.model; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.numcodex.sq2cql.model.common.TermCode; +import de.numcodex.sq2cql.model.structured_query.ContextualTermCode; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class MappingTreeBaseTest { + static final TermCode CONTEXT_1 = TermCode.of("context1", "context1", "context1"); + static final TermCode CONTEXT_2 = TermCode.of("context2", "context2", "context2"); + static final String SYSTEM_1 = "sys1"; + static final String SYSTEM_2 = "sys2"; + static final String C1 = "c1"; + static final String C2 = "c2"; + static final String C3 = "c3"; + static final String C4 = "c4"; + static final String C5 = "c5"; + + + private static ContextualTermCode contextualTermCodeOf(TermCode context, String system, String code) { + return new ContextualTermCode(context, new TermCode(system, code, "display")); + } + + @Test + void expand_empty() { + var base = new MappingTreeBase(List.of(new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, Map.of()))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList(); + + assertThat(result).isEmpty(); + } + + @Test + void expand_noMatch_differentCode() { + var base = new MappingTreeBase(List.of(new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2)).toList(); + + assertThat(result).isEmpty(); + } + + @Test + void expand_noMatch_differentContext() { + var base = new MappingTreeBase(List.of(new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of()))))); + + var result = base.expand( + contextualTermCodeOf(new TermCode("", "different-context", ""), SYSTEM_1, C2)) + .toList(); + + assertThat(result).isEmpty(); + } + + @Test + void expand_noMatch_differentSystem() { + var base = new MappingTreeBase(List.of(new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, "system2", C2)).toList(); + + assertThat(result).isEmpty(); + } + + @Test + void expand_oneModule_twoEntries_withoutChildren() { + var base = new MappingTreeBase(List.of(new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of()), + C2, new MappingTreeModuleEntry(C2, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList(); + + assertThat(result).containsExactlyInAnyOrder(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)); + } + + @Test + void expand_twoModules_sameContext_differentSystem_withoutChildren() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of()), + C2, new MappingTreeModuleEntry(C2, List.of()))), + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_2, + Map.of(C3, new MappingTreeModuleEntry(C3, List.of()), + C4, new MappingTreeModuleEntry(C4, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2)).toList(); + + assertThat(result).containsExactlyInAnyOrder(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2)); + } + + @Test + void expand_twoModules_sameSystem_differentContext_withoutChildren() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of()), + C2, new MappingTreeModuleEntry(C2, List.of()))), + new MappingTreeModuleRoot(CONTEXT_2, SYSTEM_1, + Map.of(C3, new MappingTreeModuleEntry(C3, List.of()), + C4, new MappingTreeModuleEntry(C4, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2)).toList(); + + assertThat(result).containsExactlyInAnyOrder(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2)); + } + + @Test + void expand_oneChild_withNoReference() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of(C2)))))); + + assertThatThrownBy(() -> base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList()) + .isInstanceOf(NullPointerException.class); + } + + @Test + void expand_oneChild_onFirstLayer() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of(C2)), + C2, new MappingTreeModuleEntry(C2, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList(); + + assertThat(result).containsExactlyInAnyOrder( + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2)); + } + + @Test + void expand_twoChildren_onFirstLayer() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of(C2, C3)), + C2, new MappingTreeModuleEntry(C2, List.of()), + C3, new MappingTreeModuleEntry(C3, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList(); + + assertThat(result).containsExactlyInAnyOrder( + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C3)); + } + + @Test + void expand_twoChildren_onFirstAndSecondLayer() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of(C2, C3)), + C2, new MappingTreeModuleEntry(C2, List.of(C4)), + C3, new MappingTreeModuleEntry(C3, List.of(C5)), + C4, new MappingTreeModuleEntry(C4, List.of()), + C5, new MappingTreeModuleEntry(C5, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList(); + + assertThat(result).containsExactlyInAnyOrder( + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C3), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C4), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C5)); + } + + @Test + void expand_oneChild_onThreeLayers() { + var base = new MappingTreeBase(List.of( + new MappingTreeModuleRoot(CONTEXT_1, SYSTEM_1, + Map.of(C1, new MappingTreeModuleEntry(C1, List.of(C2)), + C2, new MappingTreeModuleEntry(C2, List.of(C3)), + C3, new MappingTreeModuleEntry(C3, List.of(C4, C5)), + C4, new MappingTreeModuleEntry(C4, List.of()), + C5, new MappingTreeModuleEntry(C5, List.of()))))); + + var result = base.expand(contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1)).toList(); + + assertThat(result).containsExactlyInAnyOrder( + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C1), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C2), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C3), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C4), + contextualTermCodeOf(CONTEXT_1, SYSTEM_1, C5)); + } + + @Test + void fromJson() throws Exception { + var base = parse(""" + [ + { + "entries": [ + { + "key": "C1", + "parents": [], + "children": [] + } + ], + "context": { + "system": "sys1", + "code": "code1", + "display": "display" + }, + "system": "module-system" + } + ] + """); + + assertThat(base.moduleRoots().get(0).context()) + .isEqualTo(new TermCode("sys1", "code1", "display")); + assertThat(base.moduleRoots().get(0).system()).isEqualTo("module-system"); + assertThat(base.moduleRoots().get(0).entries().get("C1")).isNotNull(); + } + + @Test + void fromJson_AdditionalPropertyIsIgnored() throws Exception { + var base = parse(""" + [ + { + "foo-133831": "bar-133841", + "entries": [ + { + "key": "C1", + "parents": [], + "children": [] + } + ], + "context": { + "system": "sys1", + "code": "code1", + "display": "display" + }, + "system": "module-system" + } + ] + """); + + assertThat(base.moduleRoots().get(0).context()) + .isEqualTo(new TermCode("sys1", "code1", "display")); + assertThat(base.moduleRoots().get(0).system()).isEqualTo("module-system"); + assertThat(base.moduleRoots().get(0).entries().get("C1")).isNotNull(); + } + + @Test + void fromJson_withChildren() throws Exception { + var base = parse(""" + [ + { + "entries": [ + { + "key": "C1", + "parents": [], + "children": ["C2"] + }, + { + "key": "C2", + "parents": [], + "children": [] + } + ], + "context": { + "system": "sys1", + "code": "code1", + "display": "display" + }, + "system": "module-system" + } + ] + """); + + assertThat(base.moduleRoots().get(0).context()) + .isEqualTo(new TermCode("sys1", "code1", "display")); + assertThat(base.moduleRoots().get(0).system()).isEqualTo("module-system"); + assertThat(base.moduleRoots().get(0).entries().get("C1").children()).containsExactly("C2"); + assertThat(base.moduleRoots().get(0).entries().get("C2")).isNotNull(); + } + + static MappingTreeBase parse(String s) throws JsonProcessingException { + return new MappingTreeBase(Arrays.stream(new ObjectMapper().readValue(s, MappingTreeModuleRoot[].class)).toList()); + } +} \ No newline at end of file diff --git a/src/test/java/de/numcodex/sq2cql/model/TermCodeNodeTest.java b/src/test/java/de/numcodex/sq2cql/model/TermCodeNodeTest.java deleted file mode 100644 index b1d9894..0000000 --- a/src/test/java/de/numcodex/sq2cql/model/TermCodeNodeTest.java +++ /dev/null @@ -1,163 +0,0 @@ -package de.numcodex.sq2cql.model; - -import com.fasterxml.jackson.databind.ObjectMapper; -import de.numcodex.sq2cql.model.common.TermCode; -import de.numcodex.sq2cql.model.structured_query.ContextualTermCode; -import org.junit.jupiter.api.Test; - -import java.util.Set; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author Alexander Kiel - */ -class TermCodeNodeTest { - - - static final TermCode CONTEXT = TermCode.of("context", "context", "context"); - static final ContextualTermCode ROOT = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "root", "root")); - static final ContextualTermCode C1 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c1", "c1")); - static final ContextualTermCode C2 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c2", "c2")); - static final ContextualTermCode C11 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c11", "c11")); - static final ContextualTermCode C12 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c12", "c12")); - static final ContextualTermCode C111 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c111", "c111")); - static final ContextualTermCode C112 = ContextualTermCode.of(CONTEXT, TermCode.of("foo", "c112", "c112")); - - @Test - void noChildren() { - var node = TermCodeNode.of(ROOT); - - assertTrue(node.children().isEmpty()); - } - - @Test - void expandSelfLeaf() { - var node = TermCodeNode.of(ROOT); - - assertEquals(Set.of(ROOT), node.expand(ROOT).collect(Collectors.toSet())); - } - - @Test - void expandSelf() { - var node = TermCodeNode.of(ROOT, TermCodeNode.of(C1), TermCodeNode.of(C2)); - - assertEquals(Set.of(ROOT, C1, C2), node.expand(ROOT).collect(Collectors.toSet())); - } - - @Test - void expandChildAndSelf() { - var c1 = TermCodeNode.of(C1, TermCodeNode.of(C11), TermCodeNode.of(C12)); - var node = TermCodeNode.of(ROOT, c1, TermCodeNode.of(C2)); - - assertEquals(Set.of(C1, C11, C12), node.expand(C1).collect(Collectors.toSet())); - } - - @Test - void expandChildDeep() { - var c11 = TermCodeNode.of(C11, TermCodeNode.of(C111), TermCodeNode.of(C112)); - var c1 = TermCodeNode.of(C1, c11, TermCodeNode.of(C12)); - var node = TermCodeNode.of(ROOT, c1, TermCodeNode.of(C2)); - - assertEquals(Set.of(C1, C11, C12, C111, C112), node.expand(C1).collect(Collectors.toSet())); - } - - @Test - void fromJson() throws Exception { - var mapper = new ObjectMapper(); - - var conceptNode = mapper.readValue(""" - { - "context": { - "system": "context-152133", - "code": "context-152136", - "display": "context-152144" - }, - "termCode": { - "system": "system-143705", - "code": "code-143708", - "display": "display-143716" - }, - "children": [] - } - """, TermCodeNode.class); - assertEquals(ContextualTermCode.of(TermCode.of("context-152133", "context-152136", "context-152144"), - TermCode.of("system-143705", "code-143708", "display-143716")), conceptNode.contextualTermCode()); - } - - @Test - void fromJson_AdditionalPropertyIsIgnored() throws Exception { - var mapper = new ObjectMapper(); - - var conceptNode = mapper.readValue(""" - {"foo-152133": "bar-152136", - "termCode": { - "system": "system-143705", - "code": "code-143708", - "display": "display-143716" - }, - "children": [] - } - """, TermCodeNode.class); - - assertEquals("system-143705", conceptNode.contextualTermCode().termCode().system()); - } - - @Test - void fromJson_WithChildren() throws Exception { - var mapper = new ObjectMapper(); - - var conceptNode = mapper.readValue(""" - { - "context": { - "system": "context-152133", - "code": "context-152136", - "display": "context-152144" - }, - "termCode": { - "system": "system-143705", - "code": "code-143708", - "display": "display-143716" - }, - "children": [ - {"context": { - "system": "child-1-context-155856", - "code": "child-1-context-155858", - "display": "child-1-context-155900" - }, - "termCode": { - "system": "child-1-system-155856", - "code": "child-1-code-155858", - "display": "child-1-display-155900" - }}, - { - "context": { - "system": "child-2-context-155956", - "code": "child-2-context-155958", - "display": "child-2-context-160000" - }, - "termCode": { - "system": "child-2-system-155958", - "code": "child-2-code-160000", - "display": "child-2-display-160002" - }} - ] - } - """, TermCodeNode.class); - - assertEquals(ContextualTermCode.of( - TermCode.of("context-152133", "context-152136", "context-152144"), - TermCode.of("system-143705", "code-143708", "display-143716")), conceptNode.contextualTermCode()); - assertEquals(ContextualTermCode.of( - TermCode.of("child-1-context-155856", "child-1-context-155858", "child-1-context-155900"), - TermCode.of("child-1-system-155856", "child-1-code-155858", "child-1-display-155900")), - conceptNode.children().get(0).contextualTermCode()); - assertEquals(ContextualTermCode.of( - TermCode.of("child-2-context-155956", "child-2-context-155958", "child-2-context-160000"), - TermCode.of("child-2-system-155958", "child-2-code-160000", "child-2-display-160002")), - conceptNode.children().get(1).contextualTermCode()); - - } -} diff --git a/src/test/java/de/numcodex/sq2cql/model/structured_query/ConceptCriterionTest.java b/src/test/java/de/numcodex/sq2cql/model/structured_query/ConceptCriterionTest.java index bc51885..1c14cf5 100644 --- a/src/test/java/de/numcodex/sq2cql/model/structured_query/ConceptCriterionTest.java +++ b/src/test/java/de/numcodex/sq2cql/model/structured_query/ConceptCriterionTest.java @@ -1,10 +1,7 @@ package de.numcodex.sq2cql.model.structured_query; import com.fasterxml.jackson.databind.ObjectMapper; -import de.numcodex.sq2cql.model.AttributeMapping; -import de.numcodex.sq2cql.model.Mapping; -import de.numcodex.sq2cql.model.MappingContext; -import de.numcodex.sq2cql.model.TermCodeNode; +import de.numcodex.sq2cql.model.*; import de.numcodex.sq2cql.model.common.TermCode; import de.numcodex.sq2cql.model.cql.CodeSystemDefinition; import org.junit.jupiter.api.Test; @@ -15,6 +12,8 @@ import java.util.Set; import static de.numcodex.sq2cql.Assertions.assertThat; +import static de.numcodex.sq2cql.Util.createTreeWithChildren; +import static de.numcodex.sq2cql.Util.createTreeWithoutChildren; import static de.numcodex.sq2cql.model.common.Comparator.LESS_THAN; import static java.lang.String.format; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -209,7 +208,7 @@ void fromJson_BloodPressureRange() throws Exception { void toCql() { var criterion = ConceptCriterion.of(ContextualConcept.of(C71)); var mappingContext = MappingContext.of(Map.of(C71, Mapping.of(C71, "Condition")), - TermCodeNode.of(C71), CODE_SYSTEM_ALIASES); + createTreeWithoutChildren(C71), CODE_SYSTEM_ALIASES); var container = criterion.toCql(mappingContext); @@ -233,7 +232,7 @@ void toCql_WithMultipleTermCodes() { ContextualConcept.of(CONTEXT, Concept.of(C71_1_TC, C71_2_TC))); var mappings = Map.of(C71_1, Mapping.of(C71_1, "Condition"), C71_2, Mapping.of(C71_2, "Condition")); - var mappingContext = MappingContext.of(mappings, TermCodeNode.of(C71), CODE_SYSTEM_ALIASES); + var mappingContext = MappingContext.of(mappings, createTreeWithoutChildren(C71), CODE_SYSTEM_ALIASES); var container = criterion.toCql(mappingContext); @@ -258,7 +257,7 @@ void toCql_WithAttributeFilter() { .appendAttributeFilter(ValueSetAttributeFilter.of(VERIFICATION_STATUS, CONFIRMED)); var mapping = Mapping.of(C71, "Condition", null, null, List.of(), List.of(AttributeMapping.of("Coding", VERIFICATION_STATUS, "verificationStatus.coding"))); - var mappingContext = MappingContext.of(Map.of(C71, mapping), TermCodeNode.of(C71), + var mappingContext = MappingContext.of(Map.of(C71, mapping), createTreeWithoutChildren(C71), CODE_SYSTEM_ALIASES); var container = criterion.toCql(mappingContext); @@ -290,7 +289,7 @@ void toCql_Expanded_WithAttributeFilter() { var mapping2 = Mapping.of(C71_2, "Condition", null, null, List.of(), List.of(AttributeMapping.of("Coding", VERIFICATION_STATUS, "verificationStatus.coding"))); var mappingContext = MappingContext.of(Map.of(C71_1, mapping1, C71_2, mapping2), - TermCodeNode.of(C71, TermCodeNode.of(C71_1), TermCodeNode.of(C71_2)), CODE_SYSTEM_ALIASES); + createTreeWithChildren(C71, C71_1, C71_2), CODE_SYSTEM_ALIASES); var container = criterion.toCql(mappingContext); @@ -373,7 +372,7 @@ void toCql_FixedCriteria_Coding() { var criterion = ConceptCriterion.of(ContextualConcept.of(C71)); var mappingContext = MappingContext.of(Map.of(C71, Mapping.of(C71, "Condition", null, null, List.of(CodingModifier.of("verificationStatus.coding", CONFIRMED)), List.of())), - TermCodeNode.of(C71), CODE_SYSTEM_ALIASES); + createTreeWithoutChildren(C71), CODE_SYSTEM_ALIASES); var container = criterion.toCql(mappingContext); diff --git a/src/test/resources/de/numcodex/sq2cql/returningOnePatient.zip b/src/test/resources/de/numcodex/sq2cql/returningOnePatient.zip deleted file mode 100644 index 23a9807..0000000 Binary files a/src/test/resources/de/numcodex/sq2cql/returningOnePatient.zip and /dev/null differ diff --git a/src/test/resources/de/numcodex/sq2cql/returningOnePatient/Diagnose-f728b4fa-4248-5e3a-0a5d-2f346baa9455 b/src/test/resources/de/numcodex/sq2cql/returningOnePatient/Diagnose-f728b4fa-4248-5e3a-0a5d-2f346baa9455 new file mode 100644 index 0000000..cc10a80 --- /dev/null +++ b/src/test/resources/de/numcodex/sq2cql/returningOnePatient/Diagnose-f728b4fa-4248-5e3a-0a5d-2f346baa9455 @@ -0,0 +1 @@ +{"inclusionCriteria": [[{"attributeFilters": [], "termCodes": [{"code": "I08.0", "display": "", "system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm"}], "context": {"code": "Diagnose", "display": "Diagnose", "system": "fdpg.mii.cds", "version": "1.0.0"}}]], "version": "http://to_be_decided.com/draft-1/schema#"} \ No newline at end of file diff --git a/src/test/resources/de/numcodex/sq2cql/returningOnePatient/MedicationAdministration-aedab7b5-e2aa-55a7-4951-03edfd05a5f5 b/src/test/resources/de/numcodex/sq2cql/returningOnePatient/MedicationAdministration-aedab7b5-e2aa-55a7-4951-03edfd05a5f5 new file mode 100644 index 0000000..c17b868 --- /dev/null +++ b/src/test/resources/de/numcodex/sq2cql/returningOnePatient/MedicationAdministration-aedab7b5-e2aa-55a7-4951-03edfd05a5f5 @@ -0,0 +1 @@ +{"inclusionCriteria": [[{"attributeFilters": [], "termCodes": [{"code": "P01CA", "display": "", "system": "http://fhir.de/CodeSystem/bfarm/atc"}], "context": {"code": "Medikamentenverabreichung", "display": "Verabreichung von Medikamenten", "system": "fdpg.mii.cds", "version": "1.0.0"}}]], "version": "http://to_be_decided.com/draft-1/schema#"} \ No newline at end of file diff --git a/src/test/resources/de/numcodex/sq2cql/returningOnePatient/ObservationLab-0a02da60-749d-4601-df98-377981bd5336 b/src/test/resources/de/numcodex/sq2cql/returningOnePatient/ObservationLab-0a02da60-749d-4601-df98-377981bd5336 new file mode 100644 index 0000000..f3e8de4 --- /dev/null +++ b/src/test/resources/de/numcodex/sq2cql/returningOnePatient/ObservationLab-0a02da60-749d-4601-df98-377981bd5336 @@ -0,0 +1 @@ +{"inclusionCriteria": [[{"attributeFilters": [], "termCodes": [{"code": "800-3", "display": "", "system": "http://loinc.org"}], "context": {"code": "Laboruntersuchung", "display": "Laboruntersuchung", "system": "fdpg.mii.cds", "version": "1.0.0"}}]], "version": "http://to_be_decided.com/draft-1/schema#"} \ No newline at end of file diff --git a/src/test/resources/de/numcodex/sq2cql/returningOnePatient/Procedure-d7de701d-8eaf-b323-5f65-380029c836e0 b/src/test/resources/de/numcodex/sq2cql/returningOnePatient/Procedure-d7de701d-8eaf-b323-5f65-380029c836e0 new file mode 100644 index 0000000..d2c20ad --- /dev/null +++ b/src/test/resources/de/numcodex/sq2cql/returningOnePatient/Procedure-d7de701d-8eaf-b323-5f65-380029c836e0 @@ -0,0 +1 @@ +{"inclusionCriteria": [[{"attributeFilters": [], "termCodes": [{"code": "5-403.05", "display": "", "system": "http://fhir.de/CodeSystem/bfarm/ops"}], "context": {"code": "Procedure", "display": "Prozedur", "system": "fdpg.mii.cds", "version": "1.0.0"}}]], "version": "http://to_be_decided.com/draft-1/schema#"} \ No newline at end of file diff --git a/src/test/resources/de/numcodex/sq2cql/returningOnePatient/Specimen-e3e70682-c209-4cac-629f-6fbed82c07cd b/src/test/resources/de/numcodex/sq2cql/returningOnePatient/Specimen-e3e70682-c209-4cac-629f-6fbed82c07cd new file mode 100644 index 0000000..3b9734e --- /dev/null +++ b/src/test/resources/de/numcodex/sq2cql/returningOnePatient/Specimen-e3e70682-c209-4cac-629f-6fbed82c07cd @@ -0,0 +1 @@ +{"inclusionCriteria": [[{"attributeFilters": [], "termCodes": [{"code": "396997002", "display": "", "system": "http://snomed.info/sct"}], "context": {"code": "Specimen", "display": "Bioprobe", "system": "fdpg.mii.cds", "version": "1.0.0"}}]], "version": "http://to_be_decided.com/draft-1/schema#"} \ No newline at end of file diff --git a/src/test/resources/de/numcodex/sq2cql/returningOnePatient/Todesursache-31307e46-af79-7546-7e51-68dc5690f74d b/src/test/resources/de/numcodex/sq2cql/returningOnePatient/Todesursache-31307e46-af79-7546-7e51-68dc5690f74d new file mode 100644 index 0000000..8f72173 --- /dev/null +++ b/src/test/resources/de/numcodex/sq2cql/returningOnePatient/Todesursache-31307e46-af79-7546-7e51-68dc5690f74d @@ -0,0 +1 @@ +{"inclusionCriteria": [[{"attributeFilters": [], "termCodes": [{"code": "S14.11", "display": "", "system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm"}], "context": {"code": "Diagnose", "display": "Diagnose", "system": "fdpg.mii.cds", "version": "1.0.0"}}]], "version": "http://to_be_decided.com/draft-1/schema#"} \ No newline at end of file