From 023ed70b6d158a8d70c05c877205cd0fdddebe0d Mon Sep 17 00:00:00 2001 From: Alexander Kiel Date: Tue, 11 Jun 2024 14:59:10 +0200 Subject: [PATCH] Fix Translation on Expanded Criteria with Reference Attribute Filters Closes: #118 --- pom.xml | 2 +- .../de/numcodex/sq2cql/model/Mapping.java | 25 ++- .../sq2cql/model/cql/AliasedQuerySource.java | 7 + .../sq2cql/model/cql/BetweenExpression.java | 8 + .../de/numcodex/sq2cql/model/cql/Clause.java | 4 + .../model/cql/ComparatorExpression.java | 8 + .../numcodex/sq2cql/model/cql/Container.java | 35 +++- .../sq2cql/model/cql/ExistsExpression.java | 7 + .../numcodex/sq2cql/model/cql/Expression.java | 5 + .../model/cql/ExpressionDefinition.java | 5 + .../model/cql/ExpressionDefinitions.java | 26 +++ .../sq2cql/model/cql/IntervalSelector.java | 8 + .../model/cql/InvocationExpression.java | 7 + .../model/cql/MembershipExpression.java | 8 + .../sq2cql/model/cql/OrExpression.java | 10 +- .../sq2cql/model/cql/QueryExpression.java | 6 +- .../model/cql/QueryInclusionClause.java | 5 + .../sq2cql/model/cql/RetrieveExpression.java | 3 +- .../sq2cql/model/cql/ReturnClause.java | 7 + .../sq2cql/model/cql/SourceClause.java | 7 + .../cql/SuffixedIdentifierExpression.java | 13 ++ .../sq2cql/model/cql/TypeExpression.java | 7 + .../sq2cql/model/cql/UnionExpression.java | 10 +- .../sq2cql/model/cql/WhereClause.java | 6 + .../numcodex/sq2cql/model/cql/WithClause.java | 8 + .../structured_query/ReferenceModifier.java | 2 +- .../de/numcodex/sq2cql/AcceptanceTest.java | 2 +- .../java/de/numcodex/sq2cql/EvaluationIT.java | 2 +- .../java/de/numcodex/sq2cql/SpecimenTest.java | 142 ++++++++++--- .../de/numcodex/sq2cql/TranslatorTest.java | 190 +++++++++--------- 30 files changed, 417 insertions(+), 158 deletions(-) create mode 100644 src/main/java/de/numcodex/sq2cql/model/cql/ExpressionDefinitions.java diff --git a/pom.xml b/pom.xml index dd4ddd3..b276521 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 6.10.3 1.19.4 2.0.11 - 2.1.27 + 2.2.0-RC diff --git a/src/main/java/de/numcodex/sq2cql/model/Mapping.java b/src/main/java/de/numcodex/sq2cql/model/Mapping.java index 96c1749..970fb74 100644 --- a/src/main/java/de/numcodex/sq2cql/model/Mapping.java +++ b/src/main/java/de/numcodex/sq2cql/model/Mapping.java @@ -32,41 +32,42 @@ public final class Mapping { private final String termCodeFhirPath; public Mapping(ContextualTermCode key, String resourceType, String valueFhirPath, String valueType, List fixedCriteria, - List attributeMappings, String timeRestrictionFhirPath, TermCode primaryCode, String termCodeFhirPath) { + Map attributeMappings, String timeRestrictionFhirPath, TermCode primaryCode, String termCodeFhirPath) { this.key = requireNonNull(key); this.resourceType = requireNonNull(resourceType); this.valueFhirPath = valueFhirPath; this.valueType = valueType; - this.fixedCriteria = List.copyOf(fixedCriteria); - this.attributeMappings = (attributeMappings == null ? Map.of() : attributeMappings.stream() - .collect(Collectors.toMap(AttributeMapping::key, Function.identity()))); + this.fixedCriteria = fixedCriteria; + this.attributeMappings = attributeMappings; this.timeRestrictionFhirPath = timeRestrictionFhirPath; this.primaryCode = primaryCode; this.termCodeFhirPath = termCodeFhirPath; } public static Mapping of(ContextualTermCode key, String resourceType) { - return new Mapping(key, resourceType, "value", null, List.of(), List.of(), null, null, null); + return new Mapping(key, resourceType, "value", null, List.of(), Map.of(), null, null, null); } public static Mapping of(ContextualTermCode key, String resourceType, String valueFhirPath) { - return new Mapping(key, resourceType, valueFhirPath, null, List.of(), List.of(), null, null, null); + return new Mapping(key, resourceType, valueFhirPath, null, List.of(), Map.of(), null, null, null); } public static Mapping of(ContextualTermCode key, String resourceType, String valueFhirPath, String valueType) { - return new Mapping(key, resourceType, valueFhirPath, valueType, List.of(), List.of(), null, null, null); + return new Mapping(key, resourceType, valueFhirPath, valueType, List.of(), Map.of(), null, null, null); } public static Mapping of(ContextualTermCode key, String resourceType, String valueFhirPath, String valueType, List fixedCriteria, List attributeMappings) { return new Mapping(key, resourceType, valueFhirPath == null ? "value" : valueFhirPath, valueType, fixedCriteria == null ? List.of() : List.copyOf(fixedCriteria), - attributeMappings, null, null, null); + (attributeMappings == null ? Map.of() : attributeMappings.stream() + .collect(Collectors.toMap(AttributeMapping::key, Function.identity()))), null, null, null); } public static Mapping of(ContextualTermCode key, String resourceType, String valueFhirPath, String valueType, List fixedCriteria, List attributeMappings, String timeRestrictionFhirPath) { return new Mapping(key, resourceType, valueFhirPath == null ? "value" : valueFhirPath, valueType, fixedCriteria == null ? List.of() : List.copyOf(fixedCriteria), - attributeMappings, timeRestrictionFhirPath, null, null); + (attributeMappings == null ? Map.of() : attributeMappings.stream() + .collect(Collectors.toMap(AttributeMapping::key, Function.identity()))), timeRestrictionFhirPath, null, null); } @JsonCreator @@ -86,7 +87,8 @@ public static Mapping of(@JsonProperty("context") TermCode context, valueFhirPath == null ? "value" : valueFhirPath, valueType, fixedCriteria == null ? List.of() : List.copyOf(fixedCriteria), - attributeMappings, + (attributeMappings == null ? Map.of() : attributeMappings.stream() + .collect(Collectors.toMap(AttributeMapping::key, Function.identity()))), timeRestrictionFhirPath, primaryCode, termCodeFhirPath); @@ -123,7 +125,8 @@ public Optional timeRestrictionFhirPath() { /** * Returns the primary code of this mapping. The primary code is used in the retrieve CQL * expression to identify the resources of interest. The path for the primary code path is - * implicitly given in CQL but can be looked up at https://github.com/cqframework/clinical_quality_language/blob/master/Src/java/quick/src/main/resources/org/hl7/fhir/fhir-modelinfo-4.0.1.xml + * implicitly given in CQL but can be looked up + * here * * @return the primary code of this mapping or the key if no primary code is defined */ diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/AliasedQuerySource.java b/src/main/java/de/numcodex/sq2cql/model/cql/AliasedQuerySource.java index 4795f79..e012e58 100644 --- a/src/main/java/de/numcodex/sq2cql/model/cql/AliasedQuerySource.java +++ b/src/main/java/de/numcodex/sq2cql/model/cql/AliasedQuerySource.java @@ -2,6 +2,8 @@ import de.numcodex.sq2cql.PrintContext; +import java.util.Map; + import static java.util.Objects.requireNonNull; public record AliasedQuerySource(Expression querySource, IdentifierExpression alias) { @@ -19,4 +21,9 @@ public String print(PrintContext printContext) { assert printContext.precedence() == 0; return "%s %s".formatted(querySource.print(printContext.increase()), alias.print(printContext)); } + + public AliasedQuerySource withIncrementedSuffixes(Map increments) { + return new AliasedQuerySource(querySource.withIncrementedSuffixes(increments), + alias.withIncrementedSuffixes(increments)); + } } diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/BetweenExpression.java b/src/main/java/de/numcodex/sq2cql/model/cql/BetweenExpression.java index 29791da..d79c15c 100644 --- a/src/main/java/de/numcodex/sq2cql/model/cql/BetweenExpression.java +++ b/src/main/java/de/numcodex/sq2cql/model/cql/BetweenExpression.java @@ -2,6 +2,8 @@ import de.numcodex.sq2cql.PrintContext; +import java.util.Map; + import static java.util.Objects.requireNonNull; public record BetweenExpression(Expression value, @@ -26,4 +28,10 @@ public String print(PrintContext printContext) { return printContext.parenthesize(PRECEDENCE, "%s between %s and %s".formatted(value.print(childPrintContext), lowerBound.print(childPrintContext), upperBound.print(childPrintContext))); } + + @Override + public DefaultExpression withIncrementedSuffixes(Map increments) { + return new BetweenExpression(value.withIncrementedSuffixes(increments), + lowerBound.withIncrementedSuffixes(increments), upperBound.withIncrementedSuffixes(increments)); + } } diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/Clause.java b/src/main/java/de/numcodex/sq2cql/model/cql/Clause.java index 8901fb0..93ba4bb 100644 --- a/src/main/java/de/numcodex/sq2cql/model/cql/Clause.java +++ b/src/main/java/de/numcodex/sq2cql/model/cql/Clause.java @@ -2,7 +2,11 @@ import de.numcodex.sq2cql.PrintContext; +import java.util.Map; + public interface Clause { String print(PrintContext printContext); + + Clause withIncrementedSuffixes(Map increments); } diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/ComparatorExpression.java b/src/main/java/de/numcodex/sq2cql/model/cql/ComparatorExpression.java index d964bb0..c791589 100644 --- a/src/main/java/de/numcodex/sq2cql/model/cql/ComparatorExpression.java +++ b/src/main/java/de/numcodex/sq2cql/model/cql/ComparatorExpression.java @@ -3,6 +3,8 @@ import de.numcodex.sq2cql.PrintContext; import de.numcodex.sq2cql.model.common.Comparator; +import java.util.Map; + import static java.util.Objects.requireNonNull; public record ComparatorExpression(Expression a, Comparator comparator, @@ -29,4 +31,10 @@ public String print(PrintContext printContext) { return printContext.parenthesize(precedence, "%s %s %s".formatted(a.print(childPrintContext), comparator, b.print(childPrintContext))); } + + @Override + public DefaultExpression withIncrementedSuffixes(Map increments) { + return new ComparatorExpression(a.withIncrementedSuffixes(increments), comparator, + b.withIncrementedSuffixes(increments)); + } } diff --git a/src/main/java/de/numcodex/sq2cql/model/cql/Container.java b/src/main/java/de/numcodex/sq2cql/model/cql/Container.java index 7749b3d..f5918d4 100644 --- a/src/main/java/de/numcodex/sq2cql/model/cql/Container.java +++ b/src/main/java/de/numcodex/sq2cql/model/cql/Container.java @@ -1,6 +1,5 @@ package de.numcodex.sq2cql.model.cql; -import de.numcodex.sq2cql.Lists; import de.numcodex.sq2cql.Maps; import de.numcodex.sq2cql.Sets; @@ -116,10 +115,16 @@ public static > BinaryOperator> combiner(Bi b.expression.withIncrementedSuffixes(bIncrements)), Sets.union(a.codeSystemDefinitions, b.codeSystemDefinitions), Sets.union(a.unfilteredDefinitions, b.unfilteredDefinitions), - Lists.concat(a.withIncrementedSuffixes(aIncrements), b.withIncrementedSuffixes(bIncrements))); + ExpressionDefinitions.unionByName(a.withIncrementedSuffixes(aIncrements), + b.withIncrementedSuffixes(bIncrements))); }; } + /** + * Returns a map of patient definitions name identifier prefixes to their maximum numerical suffixes. + * + * @return a map of identifier prefixes to numerical suffixes + */ private Map suffixes() { return patientDefinitions.stream() .map(ExpressionDefinition::suffixes) @@ -198,7 +203,29 @@ public Container 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(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