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
@@ -1,5 +1,6 @@
package org.opencds.cqf.fhir.cr.plandefinition.apply;

import static org.opencds.cqf.fhir.utility.Constants.CQF_APPLICABILITY_BEHAVIOR;
import static org.opencds.cqf.fhir.utility.SearchHelper.searchRepositoryByCanonical;

import ca.uhn.fhir.context.FhirVersionEnum;
Expand All @@ -21,6 +22,7 @@
import org.opencds.cqf.fhir.cr.common.ExtensionProcessor;
import org.opencds.cqf.fhir.cr.questionnaire.generate.GenerateProcessor;
import org.opencds.cqf.fhir.utility.Constants;
import org.opencds.cqf.fhir.utility.Constants.CqfApplicabilityBehavior;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -57,15 +59,7 @@ public IBaseBackboneElement processAction(
metConditions.put(request.resolvePathString(action, "id"), action);
var requestAction = generateRequestAction(request.getFhirVersion(), action);
extensionProcessor.processExtensions(request, requestAction, action, new ArrayList<>());
var childActions = request.resolvePathList(action, "action", IBaseBackboneElement.class);
for (var childAction : childActions) {
request.getModelResolver()
.setValue(
requestAction,
"action",
Collections.singletonList(
processAction(request, requestOrchestration, metConditions, childAction)));
}
processChildActions(request, requestOrchestration, metConditions, action, requestAction);
var resource = processDefinition.resolveDefinition(request, requestOrchestration, action, requestAction);
dynamicValueProcessor.processDynamicValues(
request, request.getPlanDefinition(), resource, action, requestAction);
Expand All @@ -75,6 +69,45 @@ public IBaseBackboneElement processAction(
return null;
}

protected void processChildActions(
ApplyRequest request,
IBaseResource requestOrchestration,
Map<String, IBaseBackboneElement> metConditions,
IBaseBackboneElement action,
IBaseBackboneElement requestAction) {
var childActions = request.resolvePathList(action, "action", IBaseBackboneElement.class);
if (childActions.isEmpty()) {
return;
}
var applicabilityBehavior = CqfApplicabilityBehavior.ALL;
var applicabilityBehaviorExt = request.getExtensionByUrl(action, CQF_APPLICABILITY_BEHAVIOR);
if (applicabilityBehaviorExt != null
&& applicabilityBehaviorExt.getValue() instanceof IPrimitiveType<?> primitiveType) {
try {
applicabilityBehavior = CqfApplicabilityBehavior.valueOf(
primitiveType.getValueAsString().toUpperCase());
} catch (IllegalArgumentException e) {
var message = String.format(
"Encountered invalid value for applicabilityBehavior extension %s. Expected `all` or `any`.",
primitiveType.getValueAsString());
logger.error(message);
request.logException(message);
}
}
var metConditionsCount = metConditions.size();
for (var childAction : childActions) {
var childRequestAction = processAction(request, requestOrchestration, metConditions, childAction);
if (childRequestAction != null) {
request.getModelResolver()
.setValue(requestAction, "action", Collections.singletonList(childRequestAction));
}
if (applicabilityBehavior.equals(CqfApplicabilityBehavior.ANY)
&& metConditionsCount < metConditions.size()) {
break;
}
}
}

@SuppressWarnings("unchecked")
protected void addQuestionnaireItemForInput(ApplyRequest request, IBaseBackboneElement action) {
try {
Expand Down Expand Up @@ -173,17 +206,12 @@ protected boolean validateResult(IBase result, String expression) {
}

protected IBaseBackboneElement generateRequestAction(FhirVersionEnum fhirVersion, IBaseBackboneElement action) {
switch (fhirVersion) {
case DSTU3:
return generateRequestActionDstu3(action);
case R4:
return generateRequestActionR4(action);
case R5:
return generateRequestActionR5(action);

default:
return null;
}
return switch (fhirVersion) {
case DSTU3 -> generateRequestActionDstu3(action);
case R4 -> generateRequestActionR4(action);
case R5 -> generateRequestActionR5(action);
default -> null;
};
}

protected IBaseBackboneElement generateRequestActionDstu3(IBaseBackboneElement a) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
package org.opencds.cqf.fhir.cr.plandefinition.apply;

import static org.junit.Assert.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.opencds.cqf.fhir.utility.Constants.CQF_APPLICABILITY_BEHAVIOR;

import ca.uhn.fhir.context.FhirVersionEnum;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Expression;
import org.hl7.fhir.r4.model.PlanDefinition.ActionConditionKind;
import org.hl7.fhir.r4.model.PlanDefinition.PlanDefinitionActionComponent;
import org.hl7.fhir.r4.model.RequestGroup;
import org.hl7.fhir.r4.model.RequestGroup.RequestGroupActionComponent;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r5.model.RequestOrchestration.RequestOrchestrationActionComponent;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
Expand Down Expand Up @@ -60,23 +72,21 @@ void unsupportedVersionShouldReturnNull() {
void dstu3Request() {
var action = new org.hl7.fhir.dstu3.model.PlanDefinition.PlanDefinitionActionComponent();
var requestAction = fixture.generateRequestAction(FhirVersionEnum.DSTU3, action);
assertTrue(requestAction instanceof org.hl7.fhir.dstu3.model.RequestGroup.RequestGroupActionComponent);
assertInstanceOf(org.hl7.fhir.dstu3.model.RequestGroup.RequestGroupActionComponent.class, requestAction);
}

@Test
void r4Request() {
var action = new org.hl7.fhir.r4.model.PlanDefinition.PlanDefinitionActionComponent();
var requestAction = fixture.generateRequestAction(FhirVersionEnum.R4, action);
assertTrue(requestAction instanceof org.hl7.fhir.r4.model.RequestGroup.RequestGroupActionComponent);
assertInstanceOf(RequestGroupActionComponent.class, requestAction);
}

@Test
void r5Request() {
var action = new org.hl7.fhir.r5.model.PlanDefinition.PlanDefinitionActionComponent();
var requestAction = fixture.generateRequestAction(FhirVersionEnum.R5, action);
assertTrue(
requestAction
instanceof org.hl7.fhir.r5.model.RequestOrchestration.RequestOrchestrationActionComponent);
assertInstanceOf(RequestOrchestrationActionComponent.class, requestAction);
}

@Test
Expand Down Expand Up @@ -127,7 +137,7 @@ void testConditionResultNull() {
.when(libraryEngine)
.resolveExpression(eq(RequestHelpers.PATIENT_ID), any(), eq(null), any(), eq(null), any(), eq(null));
var result = fixture.meetsConditions(request, action);
assertFalse(result);
Assertions.assertFalse(result);
assertNull(request.getOperationOutcome());
}

Expand All @@ -138,11 +148,68 @@ void testConditionResultNotBoolean() {
action.addCondition().setKind(ActionConditionKind.APPLICABILITY).setExpression(expression);
var request = RequestHelpers.newPDApplyRequestForVersion(
FhirVersionEnum.R4, libraryEngine, null, inputParameterResolver);
doReturn(Arrays.asList(new org.hl7.fhir.r4.model.StringType("Test")))
doReturn(List.of(new StringType("Test")))
.when(libraryEngine)
.resolveExpression(eq(RequestHelpers.PATIENT_ID), any(), eq(null), any(), eq(null), any(), eq(null));
var result = fixture.meetsConditions(request, action);
assertFalse(result);
Assertions.assertFalse(result);
assertNull(request.getOperationOutcome());
}

org.hl7.fhir.r4.model.PlanDefinition.PlanDefinitionActionComponent actionWithChildren() {
var action = new org.hl7.fhir.r4.model.PlanDefinition.PlanDefinitionActionComponent();
action.setId("action1");
var expression = new Expression().setLanguage("text/cql-expression").setExpression("1 = 1");
var childAction1 = new PlanDefinitionActionComponent();
childAction1.setId("child1");
childAction1.addCondition().setKind(ActionConditionKind.APPLICABILITY).setExpression(expression);
var childAction2 = new PlanDefinitionActionComponent();
childAction2.setId("child2");
childAction2.addCondition().setKind(ActionConditionKind.APPLICABILITY).setExpression(expression);
var childAction3 = new PlanDefinitionActionComponent();
childAction3.setId("child3");
childAction3.addCondition().setKind(ActionConditionKind.APPLICABILITY).setExpression(expression);
action.setAction(List.of(childAction1, childAction2, childAction3));
return action;
}

@Test
void testProcessChildActionsApplicabilityBehavior() {
var action = actionWithChildren();
var requestOrchestration = new RequestGroup();
var requestAction = (RequestGroupActionComponent) fixture.generateRequestActionR4(action);
var request = RequestHelpers.newPDApplyRequestForVersion(
FhirVersionEnum.R4, libraryEngine, null, inputParameterResolver);
var metConditions = new HashMap<String, IBaseBackboneElement>();
doReturn(List.of(new BooleanType(true)))
.when(libraryEngine)
.resolveExpression(eq(RequestHelpers.PATIENT_ID), any(), eq(null), any(), eq(null), any(), eq(null));
fixture.processChildActions(request, requestOrchestration, metConditions, action, requestAction);
assertEquals(3, requestAction.getAction().size());

requestAction.setAction(null);
assertTrue(requestAction.getAction().isEmpty());
metConditions = new HashMap<String, IBaseBackboneElement>();
action.addExtension(CQF_APPLICABILITY_BEHAVIOR, new CodeType("any"));
fixture.processChildActions(request, requestOrchestration, metConditions, action, requestAction);
assertEquals(1, requestAction.getAction().size());
}

@Test
void testProcessChildActionsDoesNotThrowOnInvalidApplicabilityBehavior() {
var action = actionWithChildren();
action.addExtension(CQF_APPLICABILITY_BEHAVIOR, new BooleanType(true));
var requestOrchestration = new RequestGroup();
var requestAction = (RequestGroupActionComponent) fixture.generateRequestActionR4(action);
var request = RequestHelpers.newPDApplyRequestForVersion(
FhirVersionEnum.R4, libraryEngine, null, inputParameterResolver);
var metConditions = new HashMap<String, IBaseBackboneElement>();
fixture.processChildActions(request, requestOrchestration, metConditions, action, requestAction);
assertTrue(requestAction.getAction().isEmpty());

action.setExtension(null);
action.addExtension(CQF_APPLICABILITY_BEHAVIOR, new CodeType("bad"));
fixture.processChildActions(request, requestOrchestration, metConditions, action, requestAction);
assertTrue(requestAction.getAction().isEmpty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ private Constants() {}
public static final String CQIF_LIBRARY = "http://hl7.org/fhir/StructureDefinition/cqif-library";
public static final String CQIF_CQL_EXPRESSION = "http://hl7.org/fhir/StructureDefinition/cqif-cqlExpression";

public static final String CQF_APPLICABILITY_BEHAVIOR =
"http://hl7.org/fhir/StructureDefinition/cqf-applicabilityBehavior";

public enum CqfApplicabilityBehavior {
ALL,
ANY
}

public static final String CQF_CQL_OPTIONS = "http://hl7.org/fhir/StructureDefinition/cqf-cqlOptions";
public static final String CQF_EXPANSION_PARAMETERS =
"http://hl7.org/fhir/StructureDefinition/cqf-expansionParameters";
Expand Down