Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
* This class provides functionality to resolve parameters passed into an operation as CQL Resource parameters
* for evaluation. e.g. "%subject"
*/
@SuppressWarnings("UnstableApiUsage")
public class InputParameterResolver implements IInputParameterResolver {
private static final Logger logger = LoggerFactory.getLogger(InputParameterResolver.class);

Expand Down Expand Up @@ -62,7 +63,7 @@ public InputParameterResolver(
}

protected final IRepository resolveRepository(IRepository serverRepository, IBaseBundle data) {
return data == null
return data == null || BundleHelper.getEntry(data).isEmpty()
? serverRepository
: new FederatedRepository(
serverRepository, new InMemoryFhirRepository(serverRepository.fhirContext(), data));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.opencds.cqf.fhir.cr.common.ExtensionBuilders.buildReferenceExt;
import static org.opencds.cqf.fhir.cr.common.ExtensionBuilders.pertainToGoalExtension;
import static org.opencds.cqf.fhir.cr.questionnaire.Helpers.getQuestionnaireFromContained;
import static org.opencds.cqf.fhir.utility.BundleHelper.addEntry;
import static org.opencds.cqf.fhir.utility.BundleHelper.getEntry;
import static org.opencds.cqf.fhir.utility.BundleHelper.getEntryResources;
Expand All @@ -15,6 +16,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.opencds.cqf.fhir.cr.common.ExtensionProcessor;
Expand All @@ -24,6 +26,7 @@
import org.opencds.cqf.fhir.cr.questionnaireresponse.QuestionnaireResponseProcessor;
import org.opencds.cqf.fhir.utility.Constants;
import org.opencds.cqf.fhir.utility.Ids;
import org.opencds.cqf.fhir.utility.adapter.IQuestionnaireResponseAdapter;
import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -116,63 +119,94 @@ public IBaseBundle applyR5(ApplyRequest request) {
}

protected void initApply(ApplyRequest request) {
var questionnaireResponses = request.getQuestionnaireResponses();
var url = request.getPlanDefinitionAdapter().getUrl();
// If the PlanDefinition has no URL we will not generate a Questionnaire
// We will also add a warning to the result informing the user
if (url != null) {
var questionnaireUrl = url.replace("/PlanDefinition/", "/Questionnaire/");
List<IBaseResource> entryResources =
request.getData() == null ? List.of() : getEntryResources(request.getData());
var questionnaire = entryResources.stream()
.filter(r -> r.fhirType().equals("Questionnaire"))
.map(q -> request.getAdapterFactory().createQuestionnaire(q))
.filter(q -> q.getUrl().equals(questionnaireUrl))
// In the case of an adaptive Questionnaire it should be contained within the QuestionnaireResponse
// We are assuming a single QuestionnaireResponse in this instance
var questionnaireResponse = questionnaireResponses.stream()
.filter(r -> r.hasQuestionnaire() && r.getQuestionnaire().contains("#"))
.findFirst()
.orElse(request.getAdapterFactory()
.createQuestionnaire(generateProcessor.generate(
request.getPlanDefinition().getIdElement().getIdPart())));
questionnaire.setUrl(questionnaireUrl);
.orElse(null);
var containedQuestionnaire = getQuestionnaireFromContained(questionnaireResponse);
var questionnaire = containedQuestionnaire == null
? null
: request.getAdapterFactory().createQuestionnaire(containedQuestionnaire);
// Otherwise if we have any Questionnaire in the data Bundle
// with a url that matches the PlanDefinition we will use it
if (questionnaire == null) {
questionnaire = getEntryResources(request.getData()).stream()
.filter(r -> r.fhirType().equals("Questionnaire"))
.map(q -> request.getAdapterFactory().createQuestionnaire(q))
.filter(q -> q.getUrl().equals(questionnaireUrl))
.findFirst()
.orElse(null);
}
// If we still don't have a Questionnaire we will generate one and give it the correct url
if (questionnaire == null) {
questionnaire = request.getAdapterFactory()
.createQuestionnaire(generateProcessor.generate(
request.getPlanDefinition().getIdElement().getIdPart()));
questionnaire.setUrl(questionnaireUrl);
}
// Update the version
var version = request.getPlanDefinitionAdapter().getVersion();
if (version != null) {
var formatter = new SimpleDateFormat("yyyy-MM-dd-hh.mm.ss");
questionnaire.setVersion(version.concat(
"-%s-%s".formatted(request.getSubjectId().getIdPart(), formatter.format(new Date()))));
}
// If we don't have a questionnaireResponse check for one in the data bundle
if (questionnaireResponse == null) {
var canonical = questionnaire.getCanonical();
questionnaireResponse = questionnaireResponses.stream()
.filter(IQuestionnaireResponseAdapter::hasQuestionnaire)
.filter(r -> r.getQuestionnaire().equals(canonical))
.findFirst()
.orElse(null);
}
request.setQuestionnaire(questionnaire);
request.setQuestionnaireResponse(questionnaireResponse);
request.addCqlLibraryExtension();
} else {
request.logException("PlanDefinition %s is missing a canonical url."
.formatted(request.getPlanDefinition().getIdElement().getValue()));
}
extractQuestionnaireResponse(request);
extractQuestionnaireResponse(request, questionnaireResponses);
}

protected void extractQuestionnaireResponse(ApplyRequest request) {
if (request.getData() != null) {
getEntryResources(request.getData()).stream()
.filter(r -> r.fhirType().equals("QuestionnaireResponse"))
.map(qr -> request.getAdapterFactory().createQuestionnaireResponse(qr))
.forEach(questionnaireResponse -> {
try {
var extractBundle = extractProcessor.extract(
Eithers.forRight(questionnaireResponse.get()),
Eithers.forRight(request.getQuestionnaire()),
request.getParameters(),
request.getData(),
request.getLibraryEngine());
for (var entry : getEntry(extractBundle)) {
addEntry(request.getData(), entry);
// Not adding extracted resources back into the response to reduce size of payload
// $extract can be called on the QuestionnaireResponse if these are desired
// addEntry(request.getExtractedResources(), getEntryResource(request.getFhirVersion(),
// entry))
}
} catch (Exception e) {
request.logException("Error encountered extracting %s: %s"
.formatted(questionnaireResponse.getId().getIdPart(), e.getMessage()));
}
});
}
protected void extractQuestionnaireResponse(ApplyRequest request, List<IQuestionnaireResponseAdapter> responses) {
var questionnaireUrl = request.getQuestionnaireAdapter() != null
? request.getQuestionnaireAdapter().getUrl()
: null;
responses.forEach(questionnaireResponse -> {
try {
var questionnaire = StringUtils.isNotBlank(questionnaireUrl)
&& questionnaireResponse.hasQuestionnaire()
&& questionnaireResponse.getQuestionnaire().equals(questionnaireUrl)
? request.getQuestionnaire()
: null;
var extractBundle = extractProcessor.extract(
Eithers.forRight(questionnaireResponse.get()),
questionnaire == null ? null : Eithers.forRight(questionnaire),
request.getParameters(),
request.getData(),
request.getLibraryEngine());
for (var entry : getEntry(extractBundle)) {
addEntry(request.getData(), entry);
// Not adding extracted resources back into the response to reduce size of payload
// $extract can be called on the QuestionnaireResponse if these are desired
// addEntry(request.getExtractedResources(), getEntryResource(request.getFhirVersion(),
// entry))
}
} catch (Exception e) {
request.logException("Error encountered extracting %s: %s"
.formatted(questionnaireResponse.getId().getIdPart(), e.getMessage()));
}
});
}

public IBaseResource applyPlanDefinition(ApplyRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static com.google.common.base.Preconditions.checkNotNull;
import static org.opencds.cqf.fhir.cr.common.IInputParameterResolver.createResolver;
import static org.opencds.cqf.fhir.utility.BundleHelper.getEntryResources;
import static org.opencds.cqf.fhir.utility.BundleHelper.newBundle;
import static org.opencds.cqf.fhir.utility.Constants.APPLY_PARAMETER_ACTIVITY_DEFINITION;
import static org.opencds.cqf.fhir.utility.Constants.APPLY_PARAMETER_DATA;
Expand Down Expand Up @@ -44,6 +45,7 @@
import org.opencds.cqf.fhir.utility.adapter.IPlanDefinitionAdapter;
import org.opencds.cqf.fhir.utility.adapter.IQuestionnaireAdapter;
import org.opencds.cqf.fhir.utility.adapter.IQuestionnaireItemComponentAdapter;
import org.opencds.cqf.fhir.utility.adapter.IQuestionnaireResponseAdapter;

public class ApplyRequest implements ICpgRequest {
private static final String ACTIVITY_DEFINITION = "ActivityDefinition";
Expand All @@ -68,6 +70,7 @@ public class ApplyRequest implements ICpgRequest {
private final Collection<IBaseResource> extractedResources;
private IBaseOperationOutcome operationOutcome;
private IQuestionnaireAdapter questionnaireAdapter;
private IQuestionnaireResponseAdapter questionnaireResponseAdapter;
private Boolean containResources;
private Set<String> questionnaireDefinitions;
// actionId is used to ensure all actions have an Id so they can be mapped
Expand Down Expand Up @@ -105,10 +108,10 @@ public ApplyRequest(
this.setting = setting;
this.settingContext = settingContext;
this.parameters = parameters;
if (data == null) {
data = newBundle(fhirVersion);
}
if (prefetchData != null && !prefetchData.isEmpty()) {
if (data == null) {
data = newBundle(fhirVersion);
}
resolvePrefetchData(data, prefetchData);
}
this.data = data;
Expand Down Expand Up @@ -149,6 +152,7 @@ public ApplyRequest copy(IBaseResource planDefinition) {
modelResolver,
inputParameterResolver)
.setQuestionnaire(getQuestionnaireAdapter())
.setQuestionnaireResponse(getQuestionnaireResponseAdapter())
.setContainResources(containResources);
}

Expand Down Expand Up @@ -232,7 +236,15 @@ public PopulateRequest toPopulateRequest() {
}
});
return new PopulateRequest(
questionnaireAdapter.get(), subjectId, context, null, data, libraryEngine, modelResolver);
questionnaireAdapter.get(),
// Not yet ready to pass QR to $populate
// getQuestionnaireResponse(),
subjectId,
context,
null,
data,
libraryEngine,
modelResolver);
}

public IBaseResource getPlanDefinition() {
Expand Down Expand Up @@ -373,6 +385,28 @@ public ApplyRequest setQuestionnaire(IQuestionnaireAdapter questionnaire) {
return this;
}

public ApplyRequest setQuestionnaireResponse(IQuestionnaireResponseAdapter questionnaireResponse) {
questionnaireResponseAdapter = questionnaireResponse;
return this;
}

public IBaseResource getQuestionnaireResponse() {
return questionnaireResponseAdapter == null ? null : questionnaireResponseAdapter.get();
}

public IQuestionnaireResponseAdapter getQuestionnaireResponseAdapter() {
return questionnaireResponseAdapter;
}

public List<IQuestionnaireResponseAdapter> getQuestionnaireResponses() {
return data == null
? new ArrayList<>()
: getEntryResources(data).stream()
.filter(r -> r.fhirType().equals("QuestionnaireResponse"))
.map(qr -> getAdapterFactory().createQuestionnaireResponse(qr))
.toList();
}

public ApplyRequest setData(IBaseBundle bundle) {
data = bundle;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import ca.uhn.fhir.context.FhirVersionEnum;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.opencds.cqf.fhir.utility.adapter.IQuestionnaireResponseAdapter;

public class Helpers {
public static final String QUESTIONNAIRE = "Questionnaire";
private static final String CHOICE = "choice";
private static final String QUESTION = "question";
private static final String GROUP = "group";
Expand Down Expand Up @@ -50,4 +53,20 @@ public static String getSliceName(String elementId) {
}
return sliceName;
}

public static IBaseResource getQuestionnaireFromContained(IQuestionnaireResponseAdapter questionnaireResponse) {
return questionnaireResponse == null || !questionnaireResponse.hasQuestionnaire()
? null
: questionnaireResponse.getContained().stream()
.filter(r -> r.fhirType().equals(QUESTIONNAIRE)
&& questionnaireResponse
.getQuestionnaire()
.equals(getContainedId(r.getIdElement().getIdPart())))
.findFirst()
.orElse(null);
}

private static String getContainedId(String id) {
return id.startsWith("#") ? id : "#" + id;
}
}
Loading
Loading