From 0dcc4fa18918dfc83da7aee154d8b9f5411f436b Mon Sep 17 00:00:00 2001 From: Okko Kauhanen Date: Mon, 5 Nov 2018 12:01:25 +0200 Subject: [PATCH] First implementation of enableWhen evaluator --- .../DefaultEnableWhenEvaluator.java | 253 +++++++++++------- .../validation/IEnableWhenEvaluator.java | 11 +- 2 files changed, 164 insertions(+), 100 deletions(-) diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/validation/DefaultEnableWhenEvaluator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/validation/DefaultEnableWhenEvaluator.java index 5e63f463c01..b4cf1912c44 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/validation/DefaultEnableWhenEvaluator.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/validation/DefaultEnableWhenEvaluator.java @@ -1,110 +1,177 @@ package org.hl7.fhir.instance.validation; -import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemComponent; -import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemEnableWhenComponent; +import java.util.*; +import java.util.stream.Collectors; -import java.util.ArrayList; -import java.util.List; - -import org.hl7.fhir.dstu3.model.QuestionnaireResponse; 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. + * + */ public class DefaultEnableWhenEvaluator implements IEnableWhenEvaluator { + public static final String LINKID_ELEMENT = "linkId"; + public static final String ITEM_ELEMENT = "item"; + public static final String ANSWER_ELEMENT = "answer"; - @Override - public boolean isQuestionEnabled(QuestionnaireItemComponent item, List resp) { + @Override + public boolean isQuestionEnabled(org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemComponent item, + List 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 - boolean enabled = true; + enabled = true; + } + } + + } + } + } + return enabled; + } - if(item.hasEnableWhen()) { - - enabled = false; - - for( 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 + + @Override + public boolean isQuestionEnabled(QuestionnaireItemComponent questionnaireItem, Element questionnaireResponse) { + if (!questionnaireItem.hasEnableWhen()) { + return true; + } + List evaluationResults = questionnaireItem.getEnableWhen() + .stream() + .map(enableCondition -> evaluateCondition(enableCondition, questionnaireResponse, + questionnaireItem.getLinkId())) + .collect(Collectors.toList()); + return checkConditionResults(evaluationResults, questionnaireItem); + } + + + public boolean checkConditionResults(List evaluationResults, + QuestionnaireItemComponent questionnaireItem) { + if (questionnaireItem.hasEnableBehavior() && questionnaireItem.getEnableBehavior() == EnableWhenBehavior.ANY){ + return evaluationResults.stream().anyMatch(EnableWhenResult::isEnabled); + } if (questionnaireItem.hasEnableBehavior() && questionnaireItem.getEnableBehavior() == EnableWhenBehavior.ALL){ + return evaluationResults.stream().allMatch(EnableWhenResult::isEnabled); + } + //TODO: Throw exception? enableBehavior is mandatory when there are multiple conditions + return true; + } - enabled = true; - } - } - - } - } - - } - - - return enabled; - } - @Override - public boolean isQuestionEnabled(org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent item, - Element element) { - boolean enabled = true; + public EnableWhenResult evaluateCondition(QuestionnaireItemEnableWhenComponent enableCondition, + Element questionnaireResponse, String linkId) { + //TODO: Fix EnableWhenResult stuff + List 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); - if(item.hasEnableWhen()) { - - enabled = false; - - for( org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemEnableWhenComponent enable : item.getEnableWhen()) { - - if(enable.hasAnswer()) { - // check if referenced question has answer - - String itemId = enable.getQuestion(); - - List items = new ArrayList(); - element.getNamedChildren("item", items); - - for(Element respItem : items) { + } + return new EnableWhenResult(false, linkId, enableCondition, questionnaireResponse); + } - // TODO toteuta uudelleen + private boolean evaluateAnswer(Element answer, Type expectedAnswer) { + org.hl7.fhir.r4.model.Type actualAnswer; + try { + actualAnswer = answer.asType(); + } catch (FHIRException e) { + throw new RuntimeException("Unexpected answer type", e); + } + if (!actualAnswer.getClass().isAssignableFrom(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); + } + // TODO: Quantity, Attachment, reference? + throw new RuntimeException("Unimplemented answer type: " + expectedAnswer.getClass()); + } - - } - - } else { - // and if not - - String itemId = enable.getQuestion(); - - List items = new ArrayList(); - element.getNamedChildren("item", items); - - for(Element respItem : items) { + private List findQuestionAnswers(Element questionnaireResponse, String question) { + List matchingItems = questionnaireResponse.getChildren(ITEM_ELEMENT) + .stream() + .flatMap(i -> findSubItems(i).stream()) + .filter(i -> hasLinkId(i, question)) + .collect(Collectors.toList()); + return matchingItems + .stream() + .flatMap(e -> extractAnswer(e).stream()) + .collect(Collectors.toList()); + } + + private List extractAnswer(Element item) { + return item.getChildrenByName(ANSWER_ELEMENT) + .stream() + .flatMap(c -> c.getChildren().stream()) + .collect(Collectors.toList()); + } - - } - - } - } - - } - - - return enabled; - } + private boolean validateCodingAnswer(Coding expectedAnswer, Coding actualAnswer) { + return compareSystems(expectedAnswer, actualAnswer) && compareCodes(expectedAnswer, actualAnswer); + } + private boolean compareCodes(Coding expectedCoding, Coding value) { + if (expectedCoding.hasCode() != value.hasCode()) { + return false; + } + if (expectedCoding.hasCode()) { + return expectedCoding.getCode().equals(value.getCode()); + } + return true; + } + + private boolean compareSystems(Coding expectedCoding, Coding value) { + if (expectedCoding.hasSystem() != value.hasSystem()) { + return false; + } + if (expectedCoding.hasSystem()) { + return expectedCoding.getSystem().equals(value.getSystem()); + } + return true; + } + private List findSubItems(Element item) { + List results = item.getChildren(LINKID_ELEMENT) + .stream() + .flatMap(i -> findSubItems(i).stream()) + .collect(Collectors.toList()); + results.add(item); + return results; + } + + private boolean hasLinkId(Element item, String linkId) { + Element linkIdChild = item.getNamedChild(LINKID_ELEMENT); + if (linkIdChild != null && linkIdChild.getValue().equals(linkId)){ + return true; + } + return false; + } + + + } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/validation/IEnableWhenEvaluator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/validation/IEnableWhenEvaluator.java index 10501484723..a81500dfb4b 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/validation/IEnableWhenEvaluator.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/validation/IEnableWhenEvaluator.java @@ -3,16 +3,13 @@ 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; import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemComponent; import org.hl7.fhir.r4.elementmodel.Element; public interface IEnableWhenEvaluator { - - public boolean isQuestionEnabled(QuestionnaireItemComponent item, List theResponseItems); - - - public boolean isQuestionEnabled(org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent qItem, - Element element); + public boolean isQuestionEnabled(org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent questionnaireItem, + Element questionnaireResponse); + public boolean isQuestionEnabled(QuestionnaireItemComponent item, List theResponseItems); + }