Skip to content

Commit db89bc6

Browse files
authored
Populate and evaluate enhancements (#655)
* populate and evaluate refactor * cleanup * fix inputparameterresolver * cleanup * add tests and cleanup * add tests * cleanup * add tests * update GMTP Questionnaire test * add javadocs * repository can be final now * cleanup * fix use of useServerData parameter in evaluate request
1 parent 71a2c08 commit db89bc6

142 files changed

Lines changed: 5747 additions & 4717 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cqf-fhir-benchmark/src/test/java/org/opencds/cqf/fhir/benchmark/helpers/RequestHelpers.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import org.hl7.fhir.instance.model.api.IBaseResource;
77
import org.opencds.cqf.cql.engine.model.ModelResolver;
88
import org.opencds.cqf.fhir.cql.LibraryEngine;
9-
import org.opencds.cqf.fhir.cr.inputparameters.IInputParameterResolver;
9+
import org.opencds.cqf.fhir.cr.common.IInputParameterResolver;
1010
import org.opencds.cqf.fhir.cr.library.evaluate.EvaluateRequest;
1111
import org.opencds.cqf.fhir.cr.questionnaire.generate.GenerateRequest;
1212
import org.opencds.cqf.fhir.cr.questionnaire.populate.PopulateRequest;
@@ -118,7 +118,6 @@ public static org.opencds.cqf.fhir.cr.plandefinition.apply.ApplyRequest newPDApp
118118
null,
119119
null,
120120
null,
121-
true,
122121
null,
123122
null,
124123
libraryEngine,
@@ -156,7 +155,6 @@ public static PopulateRequest newPopulateRequestForVersion(
156155
null,
157156
null,
158157
null,
159-
true,
160158
libraryEngine,
161159
FhirModelResolverCache.resolverForVersion(fhirVersion));
162160
}
@@ -172,7 +170,6 @@ public static ExtractRequest newExtractRequestForVersion(
172170
Ids.newId(fhirVersion, Ids.ensureIdType(PATIENT_ID, "Patient")),
173171
null,
174172
null,
175-
true,
176173
libraryEngine,
177174
FhirModelResolverCache.resolverForVersion(fhirVersion),
178175
null);
@@ -185,7 +182,6 @@ public static EvaluateRequest newEvaluateRequestForVersion(
185182
Ids.newId(fhirVersion, Ids.ensureIdType(PATIENT_ID, "Patient")),
186183
null,
187184
null,
188-
true,
189185
null,
190186
null,
191187
libraryEngine,

cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/ExtensionResolver.java

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.opencds.cqf.fhir.cql;
22

33
import java.util.List;
4-
import java.util.stream.Collectors;
4+
import java.util.Map;
55
import org.hl7.fhir.instance.model.api.IBase;
66
import org.hl7.fhir.instance.model.api.IBaseBundle;
77
import org.hl7.fhir.instance.model.api.IBaseDatatype;
@@ -31,54 +31,40 @@ public ExtensionResolver(
3131

3232
@SuppressWarnings({"unchecked", "rawtypes"})
3333
public <E extends IBaseExtension> void resolveExtensions(
34-
IBase resource, List<E> extensions, String defaultLibraryUrl) {
34+
IBase resource, List<E> extensions, Map<String, String> referencedLibraries) {
3535
for (var extension : extensions) {
3636
var nestedExtensions = extension.getExtension();
3737
if (nestedExtensions != null && !nestedExtensions.isEmpty()) {
38-
resolveExtensions(resource, nestedExtensions, defaultLibraryUrl);
38+
resolveExtensions(resource, nestedExtensions, referencedLibraries);
3939
}
4040
var value = extension.getValue();
4141
if (value instanceof IBaseHasExtensions) {
4242
var valueExtensions = ((IBaseHasExtensions) value).getExtension();
4343
if (valueExtensions != null) {
4444
var expressionExtensions = valueExtensions.stream()
4545
.filter(e -> e.getUrl() != null && e.getUrl().equals(Constants.CQF_EXPRESSION))
46-
.collect(Collectors.toList());
47-
if (expressionExtensions != null && !expressionExtensions.isEmpty()) {
48-
var expression = expressionExtensions.get(0).getValue();
49-
if (expression != null) {
50-
var result = getExpressionResult(expression, defaultLibraryUrl, resource);
51-
if (result != null) {
52-
extension.setValue(result);
53-
}
46+
.findFirst();
47+
if (expressionExtensions.isPresent()) {
48+
var result = getExpressionResult(expressionExtensions.get(), referencedLibraries, resource);
49+
if (result != null) {
50+
extension.setValue(result);
5451
}
5552
}
5653
}
5754
}
5855
}
5956
}
6057

61-
protected IBaseDatatype getExpressionResult(IBaseDatatype expression, String defaultLibraryUrl, IBase resource) {
62-
List<IBase> result = null;
63-
if (expression instanceof org.hl7.fhir.r4.model.Expression) {
64-
result = libraryEngine.resolveExpression(
65-
subjectId.getIdPart(),
66-
CqfExpression.of((org.hl7.fhir.r4.model.Expression) expression, defaultLibraryUrl),
67-
parameters,
68-
bundle,
69-
resource,
70-
null);
71-
}
72-
73-
if (expression instanceof org.hl7.fhir.r5.model.Expression) {
74-
result = libraryEngine.resolveExpression(
75-
subjectId.getIdPart(),
76-
CqfExpression.of((org.hl7.fhir.r5.model.Expression) expression, defaultLibraryUrl),
77-
parameters,
78-
bundle,
79-
resource,
80-
null);
81-
}
58+
protected <E extends IBaseExtension<?, ?>> IBaseDatatype getExpressionResult(
59+
E expressionExtension, Map<String, String> referencedLibraries, IBase resource) {
60+
var result = libraryEngine.resolveExpression(
61+
subjectId.getIdPart(),
62+
CqfExpression.of(expressionExtension, referencedLibraries),
63+
parameters,
64+
null,
65+
bundle,
66+
resource,
67+
null);
8268

8369
return result != null && !result.isEmpty() ? (IBaseDatatype) result.get(0) : null;
8470
}

cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryConstructor.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
import ca.uhn.fhir.context.FhirContext;
66
import ca.uhn.fhir.context.FhirVersionEnum;
77
import ca.uhn.fhir.fhirpath.IFhirPath;
8-
import java.util.Arrays;
98
import java.util.List;
9+
import java.util.Map;
1010
import org.apache.commons.lang3.StringUtils;
11-
import org.apache.commons.lang3.tuple.Pair;
1211
import org.opencds.cqf.fhir.cql.engine.parameters.CqlParameterDefinition;
1312
import org.opencds.cqf.fhir.utility.FhirPathCache;
1413
import org.slf4j.Logger;
@@ -28,12 +27,16 @@ public LibraryConstructor(FhirContext fhirContext) {
2827
}
2928

3029
public String constructCqlLibrary(
31-
String expression, List<Pair<String, String>> libraries, List<CqlParameterDefinition> parameters) {
30+
String name,
31+
String version,
32+
String expression,
33+
Map<String, String> libraries,
34+
List<CqlParameterDefinition> parameters) {
3235
logger.debug("Constructing expression for local evaluation");
3336
return constructCqlLibrary(
34-
"expression",
35-
"1.0.0",
36-
Arrays.asList(String.format("%ndefine \"return\":%n %s", expression)),
37+
name,
38+
version,
39+
List.of(String.format("%ndefine \"return\":%n %s", expression)),
3740
libraries,
3841
parameters);
3942
}
@@ -42,7 +45,7 @@ public String constructCqlLibrary(
4245
String name,
4346
String version,
4447
List<String> expressions,
45-
List<Pair<String, String>> libraries,
48+
Map<String, String> libraries,
4649
List<CqlParameterDefinition> parameters) {
4750

4851
StringBuilder sb = new StringBuilder();
@@ -67,21 +70,19 @@ private String getFhirVersionString(FhirVersionEnum fhirVersion) {
6770
return fhirVersion == FhirVersionEnum.DSTU3 ? "3.0.1" : fhirVersion.getFhirVersionString();
6871
}
6972

70-
private void constructIncludes(StringBuilder sb, List<Pair<String, String>> libraries) {
73+
private void constructIncludes(StringBuilder sb, Map<String, String> libraries) {
7174
sb.append(String.format(
7275
"include FHIRHelpers version '%s' called FHIRHelpers%n",
7376
getFhirVersionString(fhirContext.getVersion().getVersion())));
7477

7578
if (libraries != null) {
76-
for (Pair<String, String> library : libraries) {
77-
var vi = VersionedIdentifiers.forUrl(library.getLeft());
79+
for (var library : libraries.entrySet()) {
80+
var vi = VersionedIdentifiers.forUrl(library.getValue());
7881
sb.append(String.format("include \"%s\"", vi.getId()));
7982
if (vi.getVersion() != null) {
8083
sb.append(String.format(" version '%s'", vi.getVersion()));
8184
}
82-
if (library.getRight() != null) {
83-
sb.append(String.format(" called \"%s\"", library.getRight()));
84-
}
85+
sb.append(String.format(" called \"%s\"", library.getKey()));
8586
sb.append("\n");
8687
}
8788
}

cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
import jakarta.annotation.Nullable;
99
import java.time.ZonedDateTime;
1010
import java.util.ArrayList;
11+
import java.util.Arrays;
1112
import java.util.Collections;
1213
import java.util.HashSet;
1314
import java.util.List;
15+
import java.util.Map;
1416
import java.util.Set;
17+
import java.util.stream.Collectors;
1518
import org.apache.commons.lang3.StringUtils;
16-
import org.apache.commons.lang3.tuple.ImmutablePair;
1719
import org.apache.commons.lang3.tuple.Pair;
1820
import org.cqframework.cql.cql2elm.StringLibrarySourceProvider;
1921
import org.hl7.elm.r1.VersionedIdentifier;
@@ -25,14 +27,13 @@
2527
import org.opencds.cqf.fhir.api.Repository;
2628
import org.opencds.cqf.fhir.cql.engine.parameters.CqlFhirParametersConverter;
2729
import org.opencds.cqf.fhir.cql.engine.parameters.CqlParameterDefinition;
28-
import org.opencds.cqf.fhir.utility.Canonicals;
2930
import org.opencds.cqf.fhir.utility.CqfExpression;
3031
import org.slf4j.Logger;
3132
import org.slf4j.LoggerFactory;
3233

3334
public class LibraryEngine {
3435

35-
private static Logger logger = LoggerFactory.getLogger(LibraryEngine.class);
36+
private static final Logger logger = LoggerFactory.getLogger(LibraryEngine.class);
3637

3738
protected final Repository repository;
3839
protected final FhirContext fhirContext;
@@ -96,50 +97,67 @@ public IBaseParameters evaluate(
9697
return cqlFhirParametersConverter.toFhirParameters(result);
9798
}
9899

100+
protected String getModelName(Object base) {
101+
if (base instanceof List<?> list) {
102+
return getModelName(list.get(0));
103+
}
104+
var fhirType = ((IBase) base).fhirType();
105+
if (fhirType.contains(".")) {
106+
var split = fhirType.split("\\.");
107+
fhirType = Arrays.stream(split).map(StringUtils::capitalize).collect(Collectors.joining("."));
108+
}
109+
return String.format("FHIR.%s", fhirType);
110+
}
111+
99112
public IBaseParameters evaluateExpression(
100113
String expression,
101114
IBaseParameters parameters,
115+
Map<String, Object> rawParameters,
102116
String patientId,
103-
List<Pair<String, String>> libraries,
117+
Map<String, String> referencedLibraries,
104118
IBaseBundle bundle,
105119
IBase contextParameter,
106120
IBase resourceParameter) {
107121
var libraryConstructor = new LibraryConstructor(fhirContext);
108122
var cqlFhirParametersConverter = Engines.getCqlFhirParametersConverter(fhirContext);
109123
var cqlParameters = cqlFhirParametersConverter.toCqlParameterDefinitions(parameters);
124+
var fhirPathContextName = "%fhirpathcontext";
110125
if (contextParameter != null) {
111-
var contextType = contextParameter.getClass().getSimpleName();
112-
cqlParameters.add(new CqlParameterDefinition("%fhirpathcontext", contextType, false, contextParameter));
113-
var resourceType = resourceParameter == null
114-
? contextType
115-
: resourceParameter.getClass().getSimpleName();
116-
cqlParameters.add(new CqlParameterDefinition(
117-
"%resource",
118-
resourceType, false, resourceParameter == null ? contextParameter : resourceParameter));
126+
var contextType = getModelName(contextParameter);
127+
cqlParameters.add(new CqlParameterDefinition(fhirPathContextName, contextType, false));
128+
var resourceType = resourceParameter == null ? contextType : getModelName(resourceParameter);
129+
cqlParameters.add(new CqlParameterDefinition("%resource", resourceType, false));
130+
}
131+
if (rawParameters != null) {
132+
rawParameters.forEach(
133+
(k, v) -> cqlParameters.add(new CqlParameterDefinition(k, getModelName(v), v instanceof List<?>)));
119134
}
120135
// There is currently a bug in the CQL compiler that causes the FHIRPath %context variable to fail.
121136
// This bit of hackery finds any uses of %context in the expression being evaluated and switches it to
122137
// fhirpathcontext to allow for successful evaluation.
123138
if (expression.contains("%context")) {
124-
expression = expression.replace("%context", "%fhirpathcontext");
139+
expression = expression.replace("%context", fhirPathContextName);
125140
}
126-
var cql = libraryConstructor.constructCqlLibrary(expression, libraries, cqlParameters);
127-
141+
var libraryName = "expression";
142+
var libraryVersion = "1.0.0";
143+
var cql = libraryConstructor.constructCqlLibrary(
144+
libraryName, libraryVersion, expression, referencedLibraries, cqlParameters);
128145
Set<String> expressions = new HashSet<>();
129146
expressions.add("return");
130147

131148
var requestSettings = new EvaluationSettings(settings);
132-
133149
requestSettings.getLibrarySourceProviders().add(new StringLibrarySourceProvider(Lists.newArrayList(cql)));
134-
135150
var engine = Engines.forRepository(repository, requestSettings, bundle);
136151

137152
var evaluationParameters = cqlFhirParametersConverter.toCqlParameters(parameters);
138153
if (contextParameter != null) {
139-
evaluationParameters.put("%fhirpathcontext", contextParameter);
154+
evaluationParameters.put(fhirPathContextName, contextParameter);
140155
evaluationParameters.put("%resource", resourceParameter == null ? contextParameter : resourceParameter);
141156
}
142-
var id = new VersionedIdentifier().withId("expression").withVersion("1.0.0");
157+
if (rawParameters != null) {
158+
evaluationParameters.putAll(rawParameters);
159+
}
160+
var id = new VersionedIdentifier().withId(libraryName).withVersion(libraryVersion);
143161
var result = engine.evaluate(id.getId(), expressions, buildContextParameter(patientId), evaluationParameters);
144162

145163
return cqlFhirParametersConverter.toFhirParameters(result);
@@ -150,7 +168,9 @@ public List<IBase> getExpressionResult(
150168
String expression,
151169
String language,
152170
String libraryToBeEvaluated,
171+
Map<String, String> referencedLibraries,
153172
IBaseParameters parameters,
173+
Map<String, Object> rawParameters,
154174
IBaseBundle bundle,
155175
IBase contextParameter,
156176
IBase resourceParameter) {
@@ -162,13 +182,15 @@ public List<IBase> getExpressionResult(
162182
case "text/cql.expression":
163183
case "text/cql-expression":
164184
case "text/fhirpath":
165-
var libraries = new ArrayList<Pair<String, String>>();
166-
if (!StringUtils.isBlank(libraryToBeEvaluated)) {
167-
libraries.add(
168-
new ImmutablePair<>(libraryToBeEvaluated, Canonicals.getIdPart(libraryToBeEvaluated)));
169-
}
170185
parametersResult = this.evaluateExpression(
171-
expression, parameters, subjectId, libraries, bundle, contextParameter, resourceParameter);
186+
expression,
187+
parameters,
188+
rawParameters,
189+
subjectId,
190+
referencedLibraries,
191+
bundle,
192+
contextParameter,
193+
resourceParameter);
172194
// The expression is assumed to be the parameter component name
173195
// The expression evaluator creates a library with a single expression defined as "return"
174196
results = resolveParameterValues(
@@ -256,6 +278,7 @@ public List<IBase> resolveExpression(
256278
String patientId,
257279
CqfExpression expression,
258280
IBaseParameters params,
281+
Map<String, Object> rawParameters,
259282
IBaseBundle bundle,
260283
IBase contextParameter,
261284
IBase resourceParameter) {
@@ -264,7 +287,9 @@ public List<IBase> resolveExpression(
264287
expression.getExpression(),
265288
expression.getLanguage(),
266289
expression.getLibraryUrl(),
290+
expression.getReferencedLibraries(),
267291
params,
292+
rawParameters,
268293
bundle,
269294
contextParameter,
270295
resourceParameter);
@@ -274,7 +299,9 @@ public List<IBase> resolveExpression(
274299
expression.getAltExpression(),
275300
expression.getAltLanguage(),
276301
expression.getAltLibraryUrl(),
302+
expression.getReferencedLibraries(),
277303
params,
304+
rawParameters,
278305
bundle,
279306
contextParameter,
280307
resourceParameter);

0 commit comments

Comments
 (0)