Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.opencds.cqf.cql.engine.model.ModelResolver;
import org.opencds.cqf.fhir.cql.LibraryEngine;
import org.opencds.cqf.fhir.cr.inputparameters.IInputParameterResolver;
import org.opencds.cqf.fhir.cr.common.IInputParameterResolver;
import org.opencds.cqf.fhir.cr.library.evaluate.EvaluateRequest;
import org.opencds.cqf.fhir.cr.questionnaire.generate.GenerateRequest;
import org.opencds.cqf.fhir.cr.questionnaire.populate.PopulateRequest;
Expand Down Expand Up @@ -118,7 +118,6 @@ public static org.opencds.cqf.fhir.cr.plandefinition.apply.ApplyRequest newPDApp
null,
null,
null,
true,
null,
null,
libraryEngine,
Expand Down Expand Up @@ -156,7 +155,6 @@ public static PopulateRequest newPopulateRequestForVersion(
null,
null,
null,
true,
libraryEngine,
FhirModelResolverCache.resolverForVersion(fhirVersion));
}
Expand All @@ -172,7 +170,6 @@ public static ExtractRequest newExtractRequestForVersion(
Ids.newId(fhirVersion, Ids.ensureIdType(PATIENT_ID, "Patient")),
null,
null,
true,
libraryEngine,
FhirModelResolverCache.resolverForVersion(fhirVersion),
null);
Expand All @@ -185,7 +182,6 @@ public static EvaluateRequest newEvaluateRequestForVersion(
Ids.newId(fhirVersion, Ids.ensureIdType(PATIENT_ID, "Patient")),
null,
null,
true,
null,
null,
libraryEngine,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.opencds.cqf.fhir.cql;

import java.util.List;
import java.util.stream.Collectors;
import java.util.Map;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
Expand Down Expand Up @@ -31,54 +31,40 @@ public ExtensionResolver(

@SuppressWarnings({"unchecked", "rawtypes"})
public <E extends IBaseExtension> void resolveExtensions(
IBase resource, List<E> extensions, String defaultLibraryUrl) {
IBase resource, List<E> extensions, Map<String, String> referencedLibraries) {
for (var extension : extensions) {
var nestedExtensions = extension.getExtension();
if (nestedExtensions != null && !nestedExtensions.isEmpty()) {
resolveExtensions(resource, nestedExtensions, defaultLibraryUrl);
resolveExtensions(resource, nestedExtensions, referencedLibraries);
}
var value = extension.getValue();
if (value instanceof IBaseHasExtensions) {
var valueExtensions = ((IBaseHasExtensions) value).getExtension();
if (valueExtensions != null) {
var expressionExtensions = valueExtensions.stream()
.filter(e -> e.getUrl() != null && e.getUrl().equals(Constants.CQF_EXPRESSION))
.collect(Collectors.toList());
if (expressionExtensions != null && !expressionExtensions.isEmpty()) {
var expression = expressionExtensions.get(0).getValue();
if (expression != null) {
var result = getExpressionResult(expression, defaultLibraryUrl, resource);
if (result != null) {
extension.setValue(result);
}
.findFirst();
if (expressionExtensions.isPresent()) {
var result = getExpressionResult(expressionExtensions.get(), referencedLibraries, resource);
if (result != null) {
extension.setValue(result);
}
}
}
}
}
}

protected IBaseDatatype getExpressionResult(IBaseDatatype expression, String defaultLibraryUrl, IBase resource) {
List<IBase> result = null;
if (expression instanceof org.hl7.fhir.r4.model.Expression) {
result = libraryEngine.resolveExpression(
subjectId.getIdPart(),
CqfExpression.of((org.hl7.fhir.r4.model.Expression) expression, defaultLibraryUrl),
parameters,
bundle,
resource,
null);
}

if (expression instanceof org.hl7.fhir.r5.model.Expression) {
result = libraryEngine.resolveExpression(
subjectId.getIdPart(),
CqfExpression.of((org.hl7.fhir.r5.model.Expression) expression, defaultLibraryUrl),
parameters,
bundle,
resource,
null);
}
protected <E extends IBaseExtension<?, ?>> IBaseDatatype getExpressionResult(
E expressionExtension, Map<String, String> referencedLibraries, IBase resource) {
var result = libraryEngine.resolveExpression(
subjectId.getIdPart(),
CqfExpression.of(expressionExtension, referencedLibraries),
parameters,
null,
bundle,
resource,
null);

return result != null && !result.isEmpty() ? (IBaseDatatype) result.get(0) : null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.fhirpath.IFhirPath;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.opencds.cqf.fhir.cql.engine.parameters.CqlParameterDefinition;
import org.opencds.cqf.fhir.utility.FhirPathCache;
import org.slf4j.Logger;
Expand All @@ -28,12 +27,16 @@ public LibraryConstructor(FhirContext fhirContext) {
}

public String constructCqlLibrary(
String expression, List<Pair<String, String>> libraries, List<CqlParameterDefinition> parameters) {
String name,
String version,
String expression,
Map<String, String> libraries,
List<CqlParameterDefinition> parameters) {
logger.debug("Constructing expression for local evaluation");
return constructCqlLibrary(
"expression",
"1.0.0",
Arrays.asList(String.format("%ndefine \"return\":%n %s", expression)),
name,
version,
List.of(String.format("%ndefine \"return\":%n %s", expression)),
libraries,
parameters);
}
Expand All @@ -42,7 +45,7 @@ public String constructCqlLibrary(
String name,
String version,
List<String> expressions,
List<Pair<String, String>> libraries,
Map<String, String> libraries,
List<CqlParameterDefinition> parameters) {

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

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

if (libraries != null) {
for (Pair<String, String> library : libraries) {
var vi = VersionedIdentifiers.forUrl(library.getLeft());
for (var library : libraries.entrySet()) {
var vi = VersionedIdentifiers.forUrl(library.getValue());
sb.append(String.format("include \"%s\"", vi.getId()));
if (vi.getVersion() != null) {
sb.append(String.format(" version '%s'", vi.getVersion()));
}
if (library.getRight() != null) {
sb.append(String.format(" called \"%s\"", library.getRight()));
}
sb.append(String.format(" called \"%s\"", library.getKey()));
sb.append("\n");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import jakarta.annotation.Nullable;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.cqframework.cql.cql2elm.StringLibrarySourceProvider;
import org.hl7.elm.r1.VersionedIdentifier;
Expand All @@ -25,14 +27,13 @@
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.cql.engine.parameters.CqlFhirParametersConverter;
import org.opencds.cqf.fhir.cql.engine.parameters.CqlParameterDefinition;
import org.opencds.cqf.fhir.utility.Canonicals;
import org.opencds.cqf.fhir.utility.CqfExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LibraryEngine {

private static Logger logger = LoggerFactory.getLogger(LibraryEngine.class);
private static final Logger logger = LoggerFactory.getLogger(LibraryEngine.class);

protected final Repository repository;
protected final FhirContext fhirContext;
Expand Down Expand Up @@ -96,50 +97,67 @@ public IBaseParameters evaluate(
return cqlFhirParametersConverter.toFhirParameters(result);
}

protected String getModelName(Object base) {
if (base instanceof List<?> list) {
return getModelName(list.get(0));
}
var fhirType = ((IBase) base).fhirType();
if (fhirType.contains(".")) {
var split = fhirType.split("\\.");
fhirType = Arrays.stream(split).map(StringUtils::capitalize).collect(Collectors.joining("."));
}
return String.format("FHIR.%s", fhirType);
}

public IBaseParameters evaluateExpression(
String expression,
IBaseParameters parameters,
Map<String, Object> rawParameters,
String patientId,
List<Pair<String, String>> libraries,
Map<String, String> referencedLibraries,
IBaseBundle bundle,
IBase contextParameter,
IBase resourceParameter) {
var libraryConstructor = new LibraryConstructor(fhirContext);
var cqlFhirParametersConverter = Engines.getCqlFhirParametersConverter(fhirContext);
var cqlParameters = cqlFhirParametersConverter.toCqlParameterDefinitions(parameters);
var fhirPathContextName = "%fhirpathcontext";
if (contextParameter != null) {
var contextType = contextParameter.getClass().getSimpleName();
cqlParameters.add(new CqlParameterDefinition("%fhirpathcontext", contextType, false, contextParameter));
var resourceType = resourceParameter == null
? contextType
: resourceParameter.getClass().getSimpleName();
cqlParameters.add(new CqlParameterDefinition(
"%resource",
resourceType, false, resourceParameter == null ? contextParameter : resourceParameter));
var contextType = getModelName(contextParameter);
cqlParameters.add(new CqlParameterDefinition(fhirPathContextName, contextType, false));
var resourceType = resourceParameter == null ? contextType : getModelName(resourceParameter);
cqlParameters.add(new CqlParameterDefinition("%resource", resourceType, false));
}
if (rawParameters != null) {
rawParameters.forEach(
(k, v) -> cqlParameters.add(new CqlParameterDefinition(k, getModelName(v), v instanceof List<?>)));
}
// There is currently a bug in the CQL compiler that causes the FHIRPath %context variable to fail.
// This bit of hackery finds any uses of %context in the expression being evaluated and switches it to
// fhirpathcontext to allow for successful evaluation.
if (expression.contains("%context")) {
expression = expression.replace("%context", "%fhirpathcontext");
expression = expression.replace("%context", fhirPathContextName);
}
var cql = libraryConstructor.constructCqlLibrary(expression, libraries, cqlParameters);

var libraryName = "expression";
var libraryVersion = "1.0.0";
var cql = libraryConstructor.constructCqlLibrary(
libraryName, libraryVersion, expression, referencedLibraries, cqlParameters);
Set<String> expressions = new HashSet<>();
expressions.add("return");

var requestSettings = new EvaluationSettings(settings);

requestSettings.getLibrarySourceProviders().add(new StringLibrarySourceProvider(Lists.newArrayList(cql)));

var engine = Engines.forRepository(repository, requestSettings, bundle);

var evaluationParameters = cqlFhirParametersConverter.toCqlParameters(parameters);
if (contextParameter != null) {
evaluationParameters.put("%fhirpathcontext", contextParameter);
evaluationParameters.put(fhirPathContextName, contextParameter);
evaluationParameters.put("%resource", resourceParameter == null ? contextParameter : resourceParameter);
}
var id = new VersionedIdentifier().withId("expression").withVersion("1.0.0");
if (rawParameters != null) {
evaluationParameters.putAll(rawParameters);
}
var id = new VersionedIdentifier().withId(libraryName).withVersion(libraryVersion);
var result = engine.evaluate(id.getId(), expressions, buildContextParameter(patientId), evaluationParameters);

return cqlFhirParametersConverter.toFhirParameters(result);
Expand All @@ -150,7 +168,9 @@ public List<IBase> getExpressionResult(
String expression,
String language,
String libraryToBeEvaluated,
Map<String, String> referencedLibraries,
IBaseParameters parameters,
Map<String, Object> rawParameters,
IBaseBundle bundle,
IBase contextParameter,
IBase resourceParameter) {
Expand All @@ -162,13 +182,15 @@ public List<IBase> getExpressionResult(
case "text/cql.expression":
case "text/cql-expression":
case "text/fhirpath":
var libraries = new ArrayList<Pair<String, String>>();
if (!StringUtils.isBlank(libraryToBeEvaluated)) {
libraries.add(
new ImmutablePair<>(libraryToBeEvaluated, Canonicals.getIdPart(libraryToBeEvaluated)));
}
parametersResult = this.evaluateExpression(
expression, parameters, subjectId, libraries, bundle, contextParameter, resourceParameter);
expression,
parameters,
rawParameters,
subjectId,
referencedLibraries,
bundle,
contextParameter,
resourceParameter);
// The expression is assumed to be the parameter component name
// The expression evaluator creates a library with a single expression defined as "return"
results = resolveParameterValues(
Expand Down Expand Up @@ -256,6 +278,7 @@ public List<IBase> resolveExpression(
String patientId,
CqfExpression expression,
IBaseParameters params,
Map<String, Object> rawParameters,
IBaseBundle bundle,
IBase contextParameter,
IBase resourceParameter) {
Expand All @@ -264,7 +287,9 @@ public List<IBase> resolveExpression(
expression.getExpression(),
expression.getLanguage(),
expression.getLibraryUrl(),
expression.getReferencedLibraries(),
params,
rawParameters,
bundle,
contextParameter,
resourceParameter);
Expand All @@ -274,7 +299,9 @@ public List<IBase> resolveExpression(
expression.getAltExpression(),
expression.getAltLanguage(),
expression.getAltLibraryUrl(),
expression.getReferencedLibraries(),
params,
rawParameters,
bundle,
contextParameter,
resourceParameter);
Expand Down
Loading