Added operator evaluation for R4 enableWhen

This commit is contained in:
Okko Kauhanen 2018-11-05 17:03:50 +02:00
parent 38a2b17a5e
commit 4c4c8ba87a
7 changed files with 123 additions and 86 deletions

View File

@ -36,8 +36,6 @@ import org.hl7.fhir.dstu3.model.Type;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.instance.validation.DefaultEnableWhenEvaluator;
import org.hl7.fhir.instance.validation.IEnableWhenEvaluator;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
@ -57,7 +55,6 @@ public class QuestionnaireResponseValidator extends BaseValidator {
*/
private IWorkerContext myWorkerCtx;
private IEnableWhenEvaluator myEnableWhenEvaluator = new DefaultEnableWhenEvaluator();
// this is here not to introduce enabledWhen validation unless wanted
private boolean skipEnabledCheck = true;
@ -229,7 +226,7 @@ public class QuestionnaireResponseValidator extends BaseValidator {
List<QuestionnaireResponseItemComponent> responseItems = findResponsesByLinkId(theResponseItems, linkId);
if (responseItems.isEmpty()) {
if ((skipEnabledCheck || myEnableWhenEvaluator.isQuestionEnabled(nextQuestionnaireItem, theResponseItems)) && nextQuestionnaireItem.getRequired() ) {
if ((skipEnabledCheck /*|| myEnableWhenEvaluator.isQuestionEnabled(nextQuestionnaireItem, theResponseItems)*/) && nextQuestionnaireItem.getRequired() ) {
if (theValidateRequired) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Missing required {0} with linkId[{1}]", itemType, linkId);
} else {

View File

@ -19,7 +19,7 @@ public class EnableWhenResult {
* LinkId of the questionnaire item
* @param enableWhenCondition
* Evaluated enableWhen condition
* @param responseItem
* @param answerItem
* item in QuestionnaireResponse
*/
public EnableWhenResult(boolean enabled, String linkId, QuestionnaireItemEnableWhenComponent enableWhenCondition,

View File

@ -1,15 +0,0 @@
package org.hl7.fhir.instance.validation;
import java.util.List;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemComponent;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemComponent;
import org.hl7.fhir.r4.elementmodel.Element;
public interface IEnableWhenEvaluator {
public boolean isQuestionEnabled(org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent questionnaireItem,
Element questionnaireResponse);
public boolean isQuestionEnabled(QuestionnaireItemComponent item, List<QuestionnaireResponseItemComponent> theResponseItems);
}

View File

@ -1,13 +1,13 @@
package org.hl7.fhir.instance.validation;
package org.hl7.fhir.r4.validation;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.*;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemComponent;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.elementmodel.Element;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Questionnaire.*;
/**
* Evaluates Questionnaire.item.enableWhen against a QuestionnaireResponse.
* Ignores possible modifierExtensions and extensions.
@ -18,42 +18,6 @@ public class DefaultEnableWhenEvaluator implements IEnableWhenEvaluator {
public static final String ITEM_ELEMENT = "item";
public static final String ANSWER_ELEMENT = "answer";
@Override
public boolean isQuestionEnabled(org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemComponent item,
List<QuestionnaireResponseItemComponent> resp) {
boolean enabled = true;
if(item.hasEnableWhen()) {
enabled = false;
for(org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemEnableWhenComponent enable : item.getEnableWhen()) {
if(enable.getHasAnswer()) {
// check if referenced question has answer
String itemId = enable.getQuestion();
for(QuestionnaireResponseItemComponent respItem : resp) {
if(respItem.getLinkId().equalsIgnoreCase(itemId) && respItem.hasAnswer()) {
//TODO check answer value
enabled = true;
}
}
} else {
// and if not
String itemId = enable.getQuestion();
for(QuestionnaireResponseItemComponent respItem : resp) {
if(respItem.getLinkId().equalsIgnoreCase(itemId) && !respItem.hasAnswer()) {
//TODO check answer value
enabled = true;
}
}
}
}
}
return enabled;
}
@Override
public boolean isQuestionEnabled(QuestionnaireItemComponent questionnaireItem, Element questionnaireResponse) {
if (!questionnaireItem.hasEnableWhen()) {
@ -80,38 +44,69 @@ public class DefaultEnableWhenEvaluator implements IEnableWhenEvaluator {
}
public EnableWhenResult evaluateCondition(QuestionnaireItemEnableWhenComponent enableCondition,
protected EnableWhenResult evaluateCondition(QuestionnaireItemEnableWhenComponent enableCondition,
Element questionnaireResponse, String linkId) {
//TODO: Fix EnableWhenResult stuff
List<Element> answerItems = findQuestionAnswers(questionnaireResponse,
enableCondition.getQuestion());
if (enableCondition.hasAnswer()) {
boolean result = answerItems.stream().anyMatch(answer -> evaluateAnswer(answer, enableCondition.getAnswer()));
return new EnableWhenResult(result, linkId, enableCondition, questionnaireResponse);
}
return new EnableWhenResult(false, linkId, enableCondition, questionnaireResponse);
enableCondition.getQuestion());
QuestionnaireItemOperator operator = enableCondition.getOperator();
if (operator == QuestionnaireItemOperator.EXISTS){
Type answer = enableCondition.getAnswer();
if (!(answer instanceof BooleanType)){
throw new RuntimeException("Exists-operator requires answerBoolean");
}
return new EnableWhenResult(((BooleanType)answer).booleanValue() != answerItems.isEmpty(),
linkId, enableCondition, questionnaireResponse);
}
boolean result = answerItems
.stream()
.anyMatch(answer -> evaluateAnswer(answer, enableCondition.getAnswer(), enableCondition.getOperator()));
return new EnableWhenResult(result, linkId, enableCondition, questionnaireResponse);
}
private boolean evaluateAnswer(Element answer, Type expectedAnswer) {
org.hl7.fhir.r4.model.Type actualAnswer;
protected boolean evaluateAnswer(Element answer, Type expectedAnswer, QuestionnaireItemOperator questionnaireItemOperator) {
Type actualAnswer;
try {
actualAnswer = answer.asType();
} catch (FHIRException e) {
throw new RuntimeException("Unexpected answer type", e);
}
if (!actualAnswer.getClass().isAssignableFrom(expectedAnswer.getClass())) {
if (!actualAnswer.getClass().equals(expectedAnswer.getClass())) {
throw new RuntimeException("Expected answer and actual answer have incompatible types");
}
}
if (expectedAnswer instanceof Coding) {
return validateCodingAnswer((Coding)expectedAnswer, (Coding)actualAnswer);
} else if (expectedAnswer instanceof PrimitiveType) {
return actualAnswer.equalsShallow(expectedAnswer);
return compareCodingAnswer((Coding)expectedAnswer, (Coding)actualAnswer, questionnaireItemOperator);
} else if ((expectedAnswer instanceof PrimitiveType)) {
return comparePrimitiveAnswer((PrimitiveType<?>)actualAnswer, (PrimitiveType<?>)expectedAnswer, questionnaireItemOperator);
}
// TODO: Quantity, Attachment, reference?
throw new RuntimeException("Unimplemented answer type: " + expectedAnswer.getClass());
}
private boolean comparePrimitiveAnswer(PrimitiveType<?> actualAnswer, PrimitiveType<?> expectedAnswer, QuestionnaireItemOperator questionnaireItemOperator) {
if (actualAnswer.getValue() instanceof Comparable){
@SuppressWarnings({ "rawtypes", "unchecked" })
int result = ((Comparable)actualAnswer.getValue()).compareTo(expectedAnswer.getValue());
if (questionnaireItemOperator == QuestionnaireItemOperator.EQUAL){
return result == 0;
} else if (questionnaireItemOperator == QuestionnaireItemOperator.NOT_EQUAL){
return result != 0;
} else if (questionnaireItemOperator == QuestionnaireItemOperator.GREATER_OR_EQUAL){
return result >= 0;
} else if (questionnaireItemOperator == QuestionnaireItemOperator.LESS_OR_EQUAL){
return result <= 0;
} else if (questionnaireItemOperator == QuestionnaireItemOperator.LESS_THAN){
return result < 0;
} else if (questionnaireItemOperator == QuestionnaireItemOperator.GREATER_THAN){
return result > 0;
}
throw new RuntimeException("Bad operator for PrimitiveType comparison");
} else if (questionnaireItemOperator == QuestionnaireItemOperator.EQUAL){
return actualAnswer.equalsShallow(expectedAnswer);
} else if (questionnaireItemOperator == QuestionnaireItemOperator.NOT_EQUAL){
return !actualAnswer.equalsShallow(expectedAnswer);
}
throw new RuntimeException("Bad operator for PrimitiveType comparison");
}
private List<Element> findQuestionAnswers(Element questionnaireResponse, String question) {
List<Element> matchingItems = questionnaireResponse.getChildren(ITEM_ELEMENT)
@ -132,8 +127,14 @@ public class DefaultEnableWhenEvaluator implements IEnableWhenEvaluator {
.collect(Collectors.toList());
}
private boolean validateCodingAnswer(Coding expectedAnswer, Coding actualAnswer) {
return compareSystems(expectedAnswer, actualAnswer) && compareCodes(expectedAnswer, actualAnswer);
private boolean compareCodingAnswer(Coding expectedAnswer, Coding actualAnswer, QuestionnaireItemOperator questionnaireItemOperator) {
boolean result = compareSystems(expectedAnswer, actualAnswer) && compareCodes(expectedAnswer, actualAnswer);
if (questionnaireItemOperator == QuestionnaireItemOperator.EQUAL){
return result == true;
} else if (questionnaireItemOperator == QuestionnaireItemOperator.NOT_EQUAL){
return result == false;
}
throw new RuntimeException("Bad operator for Coding comparison");
}
private boolean compareCodes(Coding expectedCoding, Coding value) {

View File

@ -0,0 +1,10 @@
package org.hl7.fhir.r4.validation;
import org.hl7.fhir.r4.elementmodel.Element;
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent;
public interface IEnableWhenEvaluator {
public boolean isQuestionEnabled(QuestionnaireItemComponent questionnaireItem,
Element questionnaireResponse);
}

View File

@ -10,8 +10,6 @@ import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.instance.validation.DefaultEnableWhenEvaluator;
import org.hl7.fhir.instance.validation.IEnableWhenEvaluator;
import org.hl7.fhir.r4.conformance.ProfileUtilities;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult;
@ -2780,16 +2778,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// ok, now we have a list of known items, grouped by linkId. We"ve made an error for anything out of order
for (QuestionnaireItemComponent qItem : qItems) {
List<Element> mapItem = map.get(qItem.getLinkId());
if (mapItem != null)
if (mapItem != null){
validateQuestionannaireResponseItem(qsrc, qItem, errors, mapItem, stack, inProgress);
else {
//item is missing, is the question enabled?
if(! myEnableWhenEvaluator.isQuestionEnabled(qItem, element)) {
rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), !qItem.getRequired(), "No response found for required item "+qItem.getLinkId());
} else {
//item is missing, is the question enabled?
if (!myEnableWhenEvaluator.isQuestionEnabled(qItem, element)) {
rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), !qItem.getRequired(), "No response found for required item "+qItem.getLinkId());
}
}
}
}
}
private void validateQuestionnaireResponseItemQuantity( List<ValidationMessage> errors, Element answer, NodeStack stack) {

View File

@ -0,0 +1,47 @@
package org.hl7.fhir.dstu3.hapi.validation;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemEnableWhenComponent;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemComponent;
public class EnableWhenResult {
private final boolean enabled;
private final QuestionnaireItemEnableWhenComponent enableWhenCondition;
private final QuestionnaireResponseItemComponent responseItem;
private final String linkId;
/**
* Evaluation result of enableWhen condition
*
* @param enabled
* Evaluation result
* @param linkId
* LinkId of the questionnaire item
* @param enableWhenCondition
* Evaluated enableWhen condition
* @param responseItem
* item in QuestionnaireResponse
*/
public EnableWhenResult(boolean enabled, String linkId, QuestionnaireItemEnableWhenComponent enableWhenCondition,
QuestionnaireResponseItemComponent responseItem) {
this.enabled = enabled;
this.linkId = linkId;
this.responseItem = responseItem;
this.enableWhenCondition = enableWhenCondition;
}
public boolean isEnabled() {
return enabled;
}
public String getLinkId() {
return linkId;
}
public QuestionnaireResponseItemComponent getResponseItem() {
return responseItem;
}
public QuestionnaireItemEnableWhenComponent getEnableWhenCondition() {
return enableWhenCondition;
}
}