moveToPatientContext(String name) {
}
var identifier = SuffixedIdentifierExpression.of(name, suffixes().getOrDefault(name, 0));
return new Container<>(new WrapperExpression(identifier), codeSystemDefinitions, unfilteredDefinitions,
- Lists.append(patientDefinitions, ExpressionDefinition.of(identifier, expression)));
+ ExpressionDefinitions.appendByUniqueName(patientDefinitions, ExpressionDefinition.of(identifier, expression)));
+ }
+
+ /**
+ * Moves the expression of this container into the patient context and returns a Container with an
+ * {@link IdentifierExpression} holding {@code name}.
+ *
+ * If this Container just holds an {@link IdentifierExpression} it's not moved.
+ *
+ * The difference to {@link #moveToPatientContext(String)} is that the name given has to be already unique as it
+ * will not get a suffix that gets incremented on collision. So the caller has to be sure that there will be no
+ * expression with different content but the same name.
+ *
+ * @param name the name of the expression definition in the Patient context that has to be already unique
+ * @return a Container with an {@link IdentifierExpression} holding {@code name}
+ */
+ public Container moveToPatientContextWithUniqueName(String name) {
+ if (expression == null) {
+ return map(WrapperExpression::new);
+ }
+ var identifier = StandardIdentifierExpression.of(name);
+ return new Container<>(new WrapperExpression(identifier), codeSystemDefinitions, unfilteredDefinitions,
+ ExpressionDefinitions.appendByUniqueName(patientDefinitions, ExpressionDefinition.of(identifier, expression)));
}
/**
@@ -227,7 +254,7 @@ public > Container flatMap(Function super T, Contai
return new Container<>(container.expression.withIncrementedSuffixes(increments),
Sets.union(codeSystemDefinitions, container.codeSystemDefinitions),
Sets.union(unfilteredDefinitions, container.unfilteredDefinitions),
- Lists.concat(patientDefinitions, container.patientDefinitions.stream()
+ ExpressionDefinitions.unionByName(patientDefinitions, container.patientDefinitions.stream()
.map(d -> d.withIncrementedSuffixes(increments))
.toList()));
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/ExistsExpression.java b/src/main/java/de/numcodex/sq2cql/model/cql/ExistsExpression.java
index 8f9b8aa..23576ec 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/ExistsExpression.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/ExistsExpression.java
@@ -2,6 +2,8 @@
import de.numcodex.sq2cql.PrintContext;
+import java.util.Map;
+
import static java.util.Objects.requireNonNull;
public record ExistsExpression(Expression> expression) implements DefaultExpression {
@@ -21,4 +23,9 @@ public String print(PrintContext printContext) {
return printContext.parenthesize(PRECEDENCE, "exists " + expression.print(printContext
.withPrecedence(PRECEDENCE)));
}
+
+ @Override
+ public DefaultExpression withIncrementedSuffixes(Map increments) {
+ return new ExistsExpression(expression.withIncrementedSuffixes(increments));
+ }
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/Expression.java b/src/main/java/de/numcodex/sq2cql/model/cql/Expression.java
index 4c8f900..57e13b1 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/Expression.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/Expression.java
@@ -18,6 +18,11 @@ public interface Expression> {
String print(PrintContext printContext);
+ /**
+ * Returns a map of identifier prefixes to numerical suffixes of this expression and all children.
+ *
+ * @return a map of identifier prefixes to numerical suffixes
+ */
default Map suffixes() {
return Map.of();
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/ExpressionDefinition.java b/src/main/java/de/numcodex/sq2cql/model/cql/ExpressionDefinition.java
index 09bd425..f1f8f68 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/ExpressionDefinition.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/ExpressionDefinition.java
@@ -29,6 +29,11 @@ public String print(PrintContext printContext) {
expression.print(newPrintContext));
}
+ /**
+ * Returns a map of the {@code name} identifier prefix to its numerical suffix if the name is suffixed.
+ *
+ * @return a map of identifier prefix to its numerical suffix
+ */
public Map suffixes() {
return name.suffixes();
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/ExpressionDefinitions.java b/src/main/java/de/numcodex/sq2cql/model/cql/ExpressionDefinitions.java
new file mode 100644
index 0000000..ca35abe
--- /dev/null
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/ExpressionDefinitions.java
@@ -0,0 +1,26 @@
+package de.numcodex.sq2cql.model.cql;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public interface ExpressionDefinitions {
+
+ static List appendByUniqueName(List a, ExpressionDefinition b) {
+ if (a.stream().noneMatch(d -> d.name().equals(b.name()))) {
+ var newList = new ArrayList<>(a);
+ newList.add(b);
+ return List.copyOf(newList);
+ }
+ return a;
+ }
+
+ static List unionByName(List a, List b) {
+ var newList = new ArrayList<>(a);
+ for (var def : b) {
+ if (newList.stream().noneMatch(d -> d.name().equals(def.name()))) {
+ newList.add(def);
+ }
+ }
+ return List.copyOf(newList);
+ }
+}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/IntervalSelector.java b/src/main/java/de/numcodex/sq2cql/model/cql/IntervalSelector.java
index 6048687..e3cbd84 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/IntervalSelector.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/IntervalSelector.java
@@ -2,6 +2,8 @@
import de.numcodex.sq2cql.PrintContext;
+import java.util.Map;
+
import static java.util.Objects.requireNonNull;
public record IntervalSelector(Expression> intervalStart, Expression> intervalEnd) implements DefaultExpression {
@@ -19,4 +21,10 @@ public static IntervalSelector of(Expression> intervalStart, Expression> int
public String print(PrintContext printContext) {
return "Interval[%s, %s]".formatted(intervalStart.print(printContext), intervalEnd.print(printContext));
}
+
+ @Override
+ public DefaultExpression withIncrementedSuffixes(Map increments) {
+ return new IntervalSelector(intervalStart.withIncrementedSuffixes(increments),
+ intervalEnd.withIncrementedSuffixes(increments));
+ }
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/InvocationExpression.java b/src/main/java/de/numcodex/sq2cql/model/cql/InvocationExpression.java
index a185d29..97fbcbd 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/InvocationExpression.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/InvocationExpression.java
@@ -2,6 +2,8 @@
import de.numcodex.sq2cql.PrintContext;
+import java.util.Map;
+
import static java.util.Objects.requireNonNull;
/**
@@ -22,4 +24,9 @@ public static InvocationExpression of(Expression> expression, String invocatio
public String print(PrintContext printContext) {
return "%s.%s".formatted(expression.print(printContext), invocation);
}
+
+ @Override
+ public DefaultExpression withIncrementedSuffixes(Map increments) {
+ return new InvocationExpression(expression.withIncrementedSuffixes(suffixes()), invocation);
+ }
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/MembershipExpression.java b/src/main/java/de/numcodex/sq2cql/model/cql/MembershipExpression.java
index 82a0799..a2cf875 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/MembershipExpression.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/MembershipExpression.java
@@ -2,6 +2,8 @@
import de.numcodex.sq2cql.PrintContext;
+import java.util.Map;
+
import static java.util.Objects.requireNonNull;
public record MembershipExpression(Expression> a, String op, Expression> b) implements DefaultExpression {
@@ -27,4 +29,10 @@ public String print(PrintContext printContext) {
return printContext.parenthesize(PRECEDENCE, "%s %s %s".formatted(a.print(childPrintContext), op,
b.print(childPrintContext)));
}
+
+ @Override
+ public DefaultExpression withIncrementedSuffixes(Map increments) {
+ return new MembershipExpression(a.withIncrementedSuffixes(increments), op,
+ b.withIncrementedSuffixes(increments));
+ }
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/OrExpression.java b/src/main/java/de/numcodex/sq2cql/model/cql/OrExpression.java
index 94ccf27..ca7e2af 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/OrExpression.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/OrExpression.java
@@ -28,15 +28,15 @@ public static , U extends Expression> DefaultExpressi
}
}
- @Override
- public DefaultExpression withIncrementedSuffixes(Map increments) {
- return new OrExpression(expressions.stream().map(e -> e.withIncrementedSuffixes(increments)).toList());
- }
-
@Override
public String print(PrintContext printContext) {
return printContext.parenthesize(PRECEDENCE, expressions.stream()
.map(printContext.withPrecedence(PRECEDENCE)::print)
.collect(joining(" or\n" + printContext.getIndent())));
}
+
+ @Override
+ public DefaultExpression withIncrementedSuffixes(Map increments) {
+ return new OrExpression(expressions.stream().map(e -> e.withIncrementedSuffixes(increments)).toList());
+ }
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/QueryExpression.java b/src/main/java/de/numcodex/sq2cql/model/cql/QueryExpression.java
index 8659ba0..b341b05 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/QueryExpression.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/QueryExpression.java
@@ -58,8 +58,10 @@ public String print(PrintContext printContext) {
@Override
public QueryExpression withIncrementedSuffixes(Map increments) {
- //TODO: decent into clauses
- return this;
+ return new QueryExpression(sourceClause.withIncrementedSuffixes(increments),
+ queryInclusionClauses.stream().map(e -> e.withIncrementedSuffixes(increments)).toList(),
+ whereClause.withIncrementedSuffixes(increments),
+ returnClause == null ? null : returnClause.withIncrementedSuffixes(increments));
}
private List clauses() {
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/QueryInclusionClause.java b/src/main/java/de/numcodex/sq2cql/model/cql/QueryInclusionClause.java
index 6142482..fd1b13c 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/QueryInclusionClause.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/QueryInclusionClause.java
@@ -1,4 +1,9 @@
package de.numcodex.sq2cql.model.cql;
+import java.util.Map;
+
public interface QueryInclusionClause extends Clause {
+
+ @Override
+ QueryInclusionClause withIncrementedSuffixes(Map increments);
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/RetrieveExpression.java b/src/main/java/de/numcodex/sq2cql/model/cql/RetrieveExpression.java
index a839c72..44d3d68 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/RetrieveExpression.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/RetrieveExpression.java
@@ -29,7 +29,8 @@ public String print(PrintContext printContext) {
@Override
public RetrieveExpression withIncrementedSuffixes(Map increments) {
- return new RetrieveExpression(resourceType, terminology.withIncrementedSuffixes(increments));
+ return new RetrieveExpression(resourceType,
+ terminology == null ? null : terminology.withIncrementedSuffixes(increments));
}
public IdentifierExpression alias() {
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/ReturnClause.java b/src/main/java/de/numcodex/sq2cql/model/cql/ReturnClause.java
index fed78c7..e4facfe 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/ReturnClause.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/ReturnClause.java
@@ -2,6 +2,8 @@
import de.numcodex.sq2cql.PrintContext;
+import java.util.Map;
+
import static java.util.Objects.requireNonNull;
public record ReturnClause(Expression> expression) implements Clause {
@@ -19,4 +21,9 @@ public String print(PrintContext printContext) {
assert printContext.precedence() == 0;
return "return " + expression.print(printContext.resetPrecedence().increase());
}
+
+ @Override
+ public ReturnClause withIncrementedSuffixes(Map increments) {
+ return new ReturnClause(expression.withIncrementedSuffixes(increments));
+ }
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/SourceClause.java b/src/main/java/de/numcodex/sq2cql/model/cql/SourceClause.java
index f5dabd5..6b883ff 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/SourceClause.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/SourceClause.java
@@ -2,6 +2,8 @@
import de.numcodex.sq2cql.PrintContext;
+import java.util.Map;
+
import static java.util.Objects.requireNonNull;
public record SourceClause(AliasedQuerySource source) implements Clause {
@@ -19,4 +21,9 @@ public String print(PrintContext printContext) {
assert printContext.precedence() == 0;
return "from %s".formatted(source.print(printContext.increase()));
}
+
+ @Override
+ public SourceClause withIncrementedSuffixes(Map increments) {
+ return new SourceClause(source.withIncrementedSuffixes(increments));
+ }
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/SuffixedIdentifierExpression.java b/src/main/java/de/numcodex/sq2cql/model/cql/SuffixedIdentifierExpression.java
index 6d9edb4..9b3e4e7 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/SuffixedIdentifierExpression.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/SuffixedIdentifierExpression.java
@@ -6,6 +6,19 @@
import static java.util.Objects.requireNonNull;
+/**
+ * An {@link IdentifierExpression} that consists of a prefix and numerical suffix rather than a simple string.
+ *
+ * Both prefix and the suffix together form the actual name of the identifier. In case the suffix is zero, the name
+ * consists only of the prefix. Otherwise both are separated by a space.
+ *
+ * The idea is to use that identifiers to ensure identifiers are unique in a library. In case to libraries are merged,
+ * the numerical suffix will be incremented using {@link #withIncrementedSuffixes(Map)} in one of the libraries if both
+ * contain identifiers with identical prefix.
+ *
+ * @param prefix the prefix of the name
+ * @param suffix the numerical suffix of the name
+ */
public record SuffixedIdentifierExpression(String prefix, int suffix) implements IdentifierExpression {
public SuffixedIdentifierExpression {
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/TypeExpression.java b/src/main/java/de/numcodex/sq2cql/model/cql/TypeExpression.java
index d05e287..dc131d1 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/TypeExpression.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/TypeExpression.java
@@ -2,6 +2,8 @@
import de.numcodex.sq2cql.PrintContext;
+import java.util.Map;
+
import static java.util.Objects.requireNonNull;
public record TypeExpression(Expression> expression, String typeSpecifier) implements DefaultExpression {
@@ -22,4 +24,9 @@ public String print(PrintContext printContext) {
return printContext.parenthesize(PRECEDENCE, "%s as %s".formatted(expression.print(printContext
.withPrecedence(PRECEDENCE)), typeSpecifier));
}
+
+ @Override
+ public DefaultExpression withIncrementedSuffixes(Map increments) {
+ return new TypeExpression(expression.withIncrementedSuffixes(increments), typeSpecifier);
+ }
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/UnionExpression.java b/src/main/java/de/numcodex/sq2cql/model/cql/UnionExpression.java
index 1bb3c97..84347f8 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/UnionExpression.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/UnionExpression.java
@@ -28,15 +28,15 @@ public static , U extends Expression> UnionExpression
}
}
- @Override
- public DefaultExpression withIncrementedSuffixes(Map increments) {
- return new UnionExpression(expressions.stream().map(e -> e.withIncrementedSuffixes(increments)).toList());
- }
-
@Override
public String print(PrintContext printContext) {
return printContext.parenthesize(PRECEDENCE, expressions.stream()
.map(printContext.withPrecedence(PRECEDENCE)::print)
.collect(joining(" union\n" + printContext.getIndent())));
}
+
+ @Override
+ public DefaultExpression withIncrementedSuffixes(Map increments) {
+ return new UnionExpression(expressions.stream().map(e -> e.withIncrementedSuffixes(increments)).toList());
+ }
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/WhereClause.java b/src/main/java/de/numcodex/sq2cql/model/cql/WhereClause.java
index a12e31b..85f82a9 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/WhereClause.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/WhereClause.java
@@ -2,6 +2,7 @@
import de.numcodex.sq2cql.PrintContext;
+import java.util.Map;
import java.util.function.Function;
import static java.util.Objects.requireNonNull;
@@ -25,4 +26,9 @@ public String print(PrintContext printContext) {
assert printContext.precedence() == 0;
return "where " + expression.print(printContext.increase());
}
+
+ @Override
+ public WhereClause withIncrementedSuffixes(Map increments) {
+ return new WhereClause(expression.withIncrementedSuffixes(increments));
+ }
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/WithClause.java b/src/main/java/de/numcodex/sq2cql/model/cql/WithClause.java
index d68234e..0a9b384 100644
--- a/src/main/java/de/numcodex/sq2cql/model/cql/WithClause.java
+++ b/src/main/java/de/numcodex/sq2cql/model/cql/WithClause.java
@@ -2,6 +2,8 @@
import de.numcodex.sq2cql.PrintContext;
+import java.util.Map;
+
import static java.util.Objects.requireNonNull;
public record WithClause(AliasedQuerySource source, Expression> expression) implements QueryInclusionClause {
@@ -22,4 +24,10 @@ public String print(PrintContext printContext) {
return "with " + source.print(increasedPrintContext) + "\n" +
increasedPrintContext.getIndent() + "such that " + expression.print(increasedPrintContext.increase());
}
+
+ @Override
+ public WithClause withIncrementedSuffixes(Map increments) {
+ return new WithClause(source.withIncrementedSuffixes(increments),
+ expression.withIncrementedSuffixes(increments));
+ }
}
diff --git a/src/main/java/de/numcodex/sq2cql/model/structured_query/ReferenceModifier.java b/src/main/java/de/numcodex/sq2cql/model/structured_query/ReferenceModifier.java
index c64c385..4a1abb3 100644
--- a/src/main/java/de/numcodex/sq2cql/model/structured_query/ReferenceModifier.java
+++ b/src/main/java/de/numcodex/sq2cql/model/structured_query/ReferenceModifier.java
@@ -24,7 +24,7 @@ public static ReferenceModifier of(String path, String targetType, List updateQuery(MappingContext mappingContext, Container queryContainer) {
return queryContainer.flatMap(query -> getReferenceExpr(mappingContext)
- .moveToPatientContext(referenceExprName())
+ .moveToPatientContextWithUniqueName(referenceExprName())
.map(referencesExprName -> {
var referenceExpr = InvocationExpression.of(query.sourceAlias(), path);
var alias = StandardIdentifierExpression.of(targetType.substring(0, 1));
diff --git a/src/test/java/de/numcodex/sq2cql/AcceptanceTest.java b/src/test/java/de/numcodex/sq2cql/AcceptanceTest.java
index 4a5653d..fe1a38e 100644
--- a/src/test/java/de/numcodex/sq2cql/AcceptanceTest.java
+++ b/src/test/java/de/numcodex/sq2cql/AcceptanceTest.java
@@ -49,7 +49,7 @@ public class AcceptanceTest {
private static final Logger logger = LoggerFactory.getLogger(AcceptanceTest.class);
private final GenericContainer> blaze = new GenericContainer<>(
- DockerImageName.parse("samply/blaze:0.25"))
+ DockerImageName.parse("samply/blaze:0.27"))
.withImagePullPolicy(PullPolicy.alwaysPull())
.withEnv("LOG_LEVEL", "debug")
.withExposedPorts(8080)
diff --git a/src/test/java/de/numcodex/sq2cql/EvaluationIT.java b/src/test/java/de/numcodex/sq2cql/EvaluationIT.java
index c40b4d1..46eede3 100644
--- a/src/test/java/de/numcodex/sq2cql/EvaluationIT.java
+++ b/src/test/java/de/numcodex/sq2cql/EvaluationIT.java
@@ -50,7 +50,7 @@ public class EvaluationIT {
static final Map CODE_SYSTEM_ALIASES = Map.of("http://loinc.org", "loinc");
@Container
- private final GenericContainer> blaze = new GenericContainer<>(DockerImageName.parse("samply/blaze:0.25"))
+ private final GenericContainer> blaze = new GenericContainer<>(DockerImageName.parse("samply/blaze:0.27"))
.withImagePullPolicy(PullPolicy.alwaysPull())
.withExposedPorts(8080)
.waitingFor(Wait.forHttp("/health").forStatusCode(200))
diff --git a/src/test/java/de/numcodex/sq2cql/SpecimenTest.java b/src/test/java/de/numcodex/sq2cql/SpecimenTest.java
index c823cd8..d883a5f 100644
--- a/src/test/java/de/numcodex/sq2cql/SpecimenTest.java
+++ b/src/test/java/de/numcodex/sq2cql/SpecimenTest.java
@@ -38,22 +38,37 @@ public void translate() throws Exception {
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 = '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 = '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 = '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 = '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 = '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 = 'Condition/' + C.id)
-
+
define InInitialPopulation:
Criterion
""");
@@ -70,35 +85,50 @@ public void translateExclusion() throws Exception {
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 = '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 = '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 = '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 = '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 = '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 = 'Condition/' + C.id)
-
+
define Exclusion:
"Criterion 2"
-
+
define InInitialPopulation:
Inclusion and
not Exclusion
- """);
+ """);
}
@Test
@@ -112,29 +142,44 @@ public void translateTwoInclusion() throws Exception {
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 = '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 = '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 = '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 = '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 = '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 = 'Condition/' + C.id)
-
+
define InInitialPopulation:
"Criterion 1" and
"Criterion 2"
- """);
+ """);
}
@Test
@@ -148,24 +193,39 @@ public void translateTwoReferenceCriteria() throws Exception {
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 = '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 = '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 = '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 = '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 = '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 = 'Condition/' + C.id)
-
+
define InInitialPopulation:
Criterion
""");
@@ -182,24 +242,44 @@ public void translateAndBodySite() throws Exception {
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 = '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 = '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 = '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 = '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 = '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 = '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 96d8369..7708375 100644
--- a/src/test/java/de/numcodex/sq2cql/TranslatorTest.java
+++ b/src/test/java/de/numcodex/sq2cql/TranslatorTest.java
@@ -147,14 +147,14 @@ void usage_Documentation() {
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'
-
+
context Patient
-
+
define Criterion:
exists [Condition: Code 'C71.1' from icd10]
-
+
define InInitialPopulation:
Criterion
""");
@@ -179,16 +179,16 @@ void timeRestriction() {
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'
-
+
context Patient
-
+
define Criterion:
exists (from [Condition: Code 'C71.1' from icd10] C
where ToDate(C.onset as dateTime) in Interval[@2020-01-01T, @2020-01-02T] or
C.onset overlaps Interval[@2020-01-01T, @2020-01-02T])
-
+
define InInitialPopulation:
Criterion
""");
@@ -236,27 +236,27 @@ void test_Task1() {
library Retrieve version '1.0.0'
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'
-
+
codesystem atc: 'http://fhir.de/CodeSystem/dimdi/atc'
codesystem icd10: 'http://fhir.de/CodeSystem/bfarm/icd-10-gm'
codesystem loinc: 'http://loinc.org'
codesystem ver_status: 'http://terminology.hl7.org/CodeSystem/condition-ver-status'
-
+
context Patient
-
+
define "Criterion 1":
exists (from [Condition: Code 'C71.0' from icd10] C
where C.verificationStatus.coding contains Code 'confirmed' from ver_status) or
exists (from [Condition: Code 'C71.1' from icd10] C
where C.verificationStatus.coding contains Code 'confirmed' from ver_status)
-
+
define "Criterion 2":
exists (from [Observation: Code '26515-7' from loinc] O
where O.value as Quantity < 50 'g/dl')
-
+
define "Criterion 3":
exists [MedicationStatement: Code 'L01AX03' from atc]
-
+
define InInitialPopulation:
"Criterion 1" and
"Criterion 2" and
@@ -290,16 +290,16 @@ void test_Task2() {
codesystem icd10: 'http://fhir.de/CodeSystem/bfarm/icd-10-gm'
codesystem sample: 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType'
codesystem ver_status: 'http://terminology.hl7.org/CodeSystem/condition-ver-status'
-
+
context Patient
define "Criterion 1":
exists (from [Condition: Code 'I10' from icd10] C
where C.verificationStatus.coding contains Code 'confirmed' from ver_status)
-
+
define "Criterion 2":
exists [Specimen: Code 'Serum' from sample]
-
+
define Inclusion:
"Criterion 1" and
"Criterion 2"
@@ -309,7 +309,7 @@ void test_Task2() {
define Exclusion:
"Criterion 3"
-
+
define InInitialPopulation:
Inclusion and
not Exclusion
@@ -345,34 +345,34 @@ void geccoTask2() {
codesystem loinc: 'http://loinc.org'
codesystem snomed: 'http://snomed.info/sct'
codesystem ver_status: 'http://terminology.hl7.org/CodeSystem/condition-ver-status'
-
+
context Patient
-
+
define "Criterion 1":
exists (from [Observation: Code '713636003' from snomed] O
where O.value.coding contains Code '1' from frailty-score or
O.value.coding contains Code '2' from frailty-score)
-
+
define Inclusion:
"Criterion 1"
-
+
define "Criterion 2":
exists (from [Condition: Code '13645005' from snomed] C
where C.verificationStatus.coding contains Code 'confirmed' from ver_status)
-
+
define "Criterion 3":
exists (from [Condition: Code 'G47.31' from icd10] C
where C.verificationStatus.coding contains Code 'confirmed' from ver_status)
-
+
define "Criterion 4":
exists (from [Observation: Code '72166-2' from loinc] O
where O.value.coding contains Code 'LA18976-3' from loinc)
-
+
define Exclusion:
"Criterion 2" and
"Criterion 3" or
"Criterion 4"
-
+
define InInitialPopulation:
Inclusion and
not Exclusion
@@ -477,18 +477,18 @@ void onlyFixedCriteria() throws Exception {
library Retrieve version '1.0.0'
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'
-
+
codesystem consent: 'urn:oid:2.16.840.1.113883.3.1937.777.24.5.3'
codesystem loinc: 'http://loinc.org'
-
+
context Patient
-
+
define Criterion:
exists (from [Consent: Code '54133-1' from loinc] C
where C.status = 'active' and
C.provision.provision.code.coding contains Code '2.16.840.1.113883.3.1937.777.24.5.3.5' from consent and
C.provision.provision.code.coding contains Code '2.16.840.1.113883.3.1937.777.24.5.3.2' from consent)
-
+
define InInitialPopulation:
Criterion
""");
@@ -559,12 +559,12 @@ void numericAgeTranslation() throws Exception {
library Retrieve version '1.0.0'
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'
-
+
context Patient
-
+
define Criterion:
AgeInYears() > 5
-
+
define InInitialPopulation:
Criterion
""");
@@ -635,12 +635,12 @@ void ageRangeTranslation() throws Exception {
library Retrieve version '1.0.0'
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'
-
+
context Patient
-
+
define Criterion:
AgeInYears() between 5 and 10
-
+
define InInitialPopulation:
Criterion
""");
@@ -711,12 +711,12 @@ void numericAgeTranslationInHours() throws Exception {
library Retrieve version '1.0.0'
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'
-
+
context Patient
-
+
define Criterion:
AgeInHours() < 5
-
+
define InInitialPopulation:
Criterion
""");
@@ -787,12 +787,12 @@ void patientGender() throws Exception {
library Retrieve version '1.0.0'
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'
-
+
context Patient
-
+
define Criterion:
Patient.gender = 'female'
-
+
define InInitialPopulation:
Criterion
""");
@@ -854,15 +854,15 @@ void consent() throws Exception {
library Retrieve version '1.0.0'
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'
-
+
codesystem consent: 'urn:oid:2.16.840.1.113883.3.1937.777.24.5.3'
-
+
context Patient
-
+
define Criterion:
exists (from [Consent] C
where C.provision.provision.code.coding contains Code '2.16.840.1.113883.3.1937.777.24.5.3.8' from consent)
-
+
define InInitialPopulation:
Criterion
""");
@@ -948,15 +948,15 @@ void bloodPressure() throws Exception {
library Retrieve version '1.0.0'
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'
-
+
codesystem loinc: 'http://loinc.org'
-
+
context Patient
-
+
define Criterion:
exists (from [Observation: Code '85354-9' from loinc] O
where O.component.where(code.coding.exists(system = 'http://loinc.org' and code = '8462-4')).value.first() as Quantity < 80 'mm[Hg]')
-
+
define InInitialPopulation:
Criterion
""");
@@ -973,10 +973,10 @@ void oneDisjunctionWithOneCriterion() {
assertThat(library).patientContextPrintsTo("""
context Patient
-
+
define Criterion:
true
-
+
define InInitialPopulation:
Criterion
""");
@@ -990,13 +990,13 @@ void oneDisjunctionWithTwoCriteria() {
assertThat(library).patientContextPrintsTo("""
context Patient
-
+
define "Criterion 1":
true
-
+
define "Criterion 2":
false
-
+
define InInitialPopulation:
"Criterion 1" or
"Criterion 2"
@@ -1011,13 +1011,13 @@ void twoDisjunctionsWithOneCriterionEach() {
assertThat(library).patientContextPrintsTo("""
context Patient
-
+
define "Criterion 1":
true
-
+
define "Criterion 2":
false
-
+
define InInitialPopulation:
"Criterion 1" and
"Criterion 2"
@@ -1033,19 +1033,19 @@ void twoDisjunctionsWithTwoCriterionEach() {
assertThat(library).patientContextPrintsTo("""
context Patient
-
+
define "Criterion 1":
true
-
+
define "Criterion 2":
true
-
+
define "Criterion 3":
false
-
+
define "Criterion 4":
false
-
+
define InInitialPopulation:
("Criterion 1" or
"Criterion 2") and
@@ -1066,19 +1066,19 @@ void oneConjunctionWithOneCriterion() {
assertThat(library).patientContextPrintsTo("""
context Patient
-
+
define "Criterion 1":
true
-
+
define Inclusion:
"Criterion 1"
-
+
define "Criterion 2":
false
-
+
define Exclusion:
"Criterion 2"
-
+
define InInitialPopulation:
Inclusion and
not Exclusion
@@ -1094,23 +1094,23 @@ void oneConjunctionWithTwoCriteria() {
assertThat(library).patientContextPrintsTo("""
context Patient
-
+
define "Criterion 1":
true
-
+
define Inclusion:
"Criterion 1"
-
+
define "Criterion 2":
false
-
+
define "Criterion 3":
false
-
+
define Exclusion:
"Criterion 2" and
"Criterion 3"
-
+
define InInitialPopulation:
Inclusion and
not Exclusion
@@ -1127,23 +1127,23 @@ void twoConjunctionsWithOneCriterionEach() {
assertThat(library).patientContextPrintsTo("""
context Patient
-
+
define "Criterion 1":
true
-
+
define Inclusion:
"Criterion 1"
-
+
define "Criterion 2":
true
-
+
define "Criterion 3":
false
-
+
define Exclusion:
"Criterion 2" or
"Criterion 3"
-
+
define InInitialPopulation:
Inclusion and
not Exclusion
@@ -1160,31 +1160,31 @@ void twoConjunctionsWithTwoCriterionEach() {
assertThat(library).patientContextPrintsTo("""
context Patient
-
+
define "Criterion 1":
true
-
+
define Inclusion:
"Criterion 1"
-
+
define "Criterion 2":
false
-
+
define "Criterion 3":
false
-
+
define "Criterion 4":
false
-
+
define "Criterion 5":
false
-
+
define Exclusion:
"Criterion 2" and
"Criterion 3" or
"Criterion 4" and
"Criterion 5"
-
+
define InInitialPopulation:
Inclusion and
not Exclusion
@@ -1200,27 +1200,27 @@ void twoInclusionAndTwoExclusionCriteria() {
assertThat(library).patientContextPrintsTo("""
context Patient
-
+
define "Criterion 1":
true
-
+
define "Criterion 2":
false
-
+
define Inclusion:
"Criterion 1" and
"Criterion 2"
-
+
define "Criterion 3":
true
-
+
define "Criterion 4":
false
-
+
define Exclusion:
"Criterion 3" and
"Criterion 4"
-
+
define InInitialPopulation:
Inclusion and
not Exclusion