Skip to content

Commit

Permalink
Use ExpressionEvaluator to evaluate cqf-calculatedValue expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
LZRS committed Nov 2, 2023
1 parent c26f654 commit 6ae9c5e
Show file tree
Hide file tree
Showing 18 changed files with 167 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,10 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
QuestionnaireResponseItemValidator.validate(
questionnaireItem,
questionnaireResponseItem.answer,
this@QuestionnaireViewModel.getApplication(),
expressionEvaluator = {
expressionEvaluator.evaluateExpression(questionnaireItem, questionnaireResponseItem, it)
},
context = this@QuestionnaireViewModel.getApplication(),
)
} else {
NotValidated
Expand Down Expand Up @@ -761,6 +764,13 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
questionnaireResponseItem,
)
},
expressionEvaluator = {
expressionEvaluator.evaluateExpression(
questionnaireItem,
questionnaireResponseItem,
it,
)
},
questionViewTextConfiguration =
QuestionTextConfiguration(
showAsterisk = showAsterisk,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.google.android.fhir.datacapture.views.factories.localDate
import com.google.android.fhir.datacapture.views.factories.localTime
import com.google.android.fhir.getLocalizedText
import org.hl7.fhir.r4.model.Attachment
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.BooleanType
import org.hl7.fhir.r4.model.CodeType
import org.hl7.fhir.r4.model.Coding
Expand Down Expand Up @@ -124,13 +125,16 @@ internal fun Coding.toCodeType(): CodeType {
return CodeType(code)
}

fun Type.valueOrCalculateValue(): Type {
fun Type.valueOrCalculateValue(
expressionEvaluator: (Expression) -> List<Base> = {
fhirPathEngine.evaluate(this, it.expression)
},
): Type {
return if (this.hasExtension()) {
this.extension
.firstOrNull { it.url == EXTENSION_CQF_CALCULATED_VALUE_URL }
?.let { extension ->
val expression = (extension.value as Expression).expression
fhirPathEngine.evaluate(this, expression).singleOrNull()?.let { it as Type }
expressionEvaluator.invoke(extension.value as Expression).singleOrNull()?.let { it as Type }
}
?: this
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package com.google.android.fhir.datacapture.validation

import android.content.Context
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse

Expand All @@ -38,6 +40,7 @@ internal interface AnswerConstraintValidator {
fun validate(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
expressionEvaluator: (Expression) -> List<Base>,
context: Context,
): Result

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package com.google.android.fhir.datacapture.validation

import android.content.Context
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
Expand All @@ -35,17 +37,22 @@ import org.hl7.fhir.r4.model.QuestionnaireResponse
internal open class AnswerExtensionConstraintValidator(
val url: String,
val predicate:
(Extension, QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent) -> Boolean,
(
Extension,
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
expressionEvaluator: (Expression) -> List<Base>,
) -> Boolean,
val messageGenerator: (Extension, Context) -> String,
) : AnswerConstraintValidator {
override fun validate(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
expressionEvaluator: (Expression) -> List<Base>,
context: Context,
): AnswerConstraintValidator.Result {
if (questionnaireItem.hasExtension(url)) {
val extension = questionnaireItem.getExtensionByUrl(url)
if (predicate(extension, answer)) {
if (predicate(extension, answer, expressionEvaluator)) {
return AnswerConstraintValidator.Result(false, messageGenerator(extension, context))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ package com.google.android.fhir.datacapture.validation
*/
import android.content.Context
import com.google.android.fhir.datacapture.R
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.IntegerType
import org.hl7.fhir.r4.model.QuestionnaireResponse
Expand All @@ -35,6 +37,7 @@ internal object MaxDecimalPlacesValidator :
predicate = {
extension: Extension,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
_: (Expression) -> List<Base>,
->
val maxDecimalPlaces = (extension.value as? IntegerType)?.value
answer.hasValueDecimalType() &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package com.google.android.fhir.datacapture.validation

import android.content.Context
import com.google.android.fhir.datacapture.extensions.asStringValue
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse

Expand All @@ -31,6 +33,7 @@ internal object MaxLengthValidator : AnswerConstraintValidator {
override fun validate(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
expressionEvaluator: (Expression) -> List<Base>,
context: Context,
): AnswerConstraintValidator.Result {
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import com.google.android.fhir.compareTo
import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.extensions.getValueAsString
import com.google.android.fhir.datacapture.extensions.valueOrCalculateValue
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
Expand All @@ -35,8 +37,9 @@ internal object MaxValueValidator :
predicate = {
extension: Extension,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
expressionEvaluator: (Expression) -> List<Base>,
->
answer.value > extension.value?.valueOrCalculateValue()!!
answer.value > extension.value?.valueOrCalculateValue(expressionEvaluator)!!
},
messageGenerator = { extension: Extension, context: Context ->
context.getString(
Expand All @@ -46,9 +49,12 @@ internal object MaxValueValidator :
},
) {

fun getMaxValue(questionnaireItemComponent: Questionnaire.QuestionnaireItemComponent): Type? {
fun getMaxValue(
expressionEvaluator: (Expression) -> List<Base>,
questionnaireItemComponent: Questionnaire.QuestionnaireItemComponent,
): Type? {
return questionnaireItemComponent.extension
.firstOrNull { it.url == MAX_VALUE_EXTENSION_URL }
?.let { it.value?.valueOrCalculateValue() }
?.let { it.value?.valueOrCalculateValue(expressionEvaluator) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import org.hl7.fhir.r4.model.PrimitiveType
internal object MinLengthValidator :
AnswerExtensionConstraintValidator(
url = MIN_LENGTH_EXTENSION_URL,
predicate = { extension, answer ->
predicate = { extension, answer, _ ->
answer.value.isPrimitive &&
(answer.value as PrimitiveType<*>).asStringValue().length <
(extension.value as IntegerType).value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import com.google.android.fhir.compareTo
import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.extensions.getValueAsString
import com.google.android.fhir.datacapture.extensions.valueOrCalculateValue
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
Expand All @@ -35,8 +37,9 @@ internal object MinValueValidator :
predicate = {
extension: Extension,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
expressionEvaluator: (Expression) -> List<Base>,
->
answer.value < extension.value?.valueOrCalculateValue()!!
answer.value < extension.value?.valueOrCalculateValue(expressionEvaluator)!!
},
messageGenerator = { extension: Extension, context: Context ->
context.getString(
Expand All @@ -47,10 +50,11 @@ internal object MinValueValidator :
) {

internal fun getMinValue(
expressionEvaluator: (Expression) -> List<Base>,
questionnaireItemComponent: Questionnaire.QuestionnaireItemComponent,
): Type? {
return questionnaireItemComponent.extension
.firstOrNull { it.url == MIN_VALUE_EXTENSION_URL }
?.let { it.value?.valueOrCalculateValue() }
?.let { it.value?.valueOrCalculateValue(expressionEvaluator) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package com.google.android.fhir.datacapture.validation

import android.content.Context
import com.google.android.fhir.datacapture.extensions.isHidden
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse

Expand All @@ -44,6 +46,7 @@ internal object QuestionnaireResponseItemValidator {
fun validate(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
answers: List<QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent>,
expressionEvaluator: (Expression) -> List<Base>,
context: Context,
): ValidationResult {
if (questionnaireItem.isHidden) return NotValidated
Expand All @@ -54,7 +57,9 @@ internal object QuestionnaireResponseItemValidator {
}
val questionnaireResponseItemAnswerConstraintValidationResult =
answerConstraintValidators.flatMap { validator ->
answers.map { answer -> validator.validate(questionnaireItem, answer, context) }
answers.map { answer ->
validator.validate(questionnaireItem, answer, expressionEvaluator, context)
}
}

return if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ package com.google.android.fhir.datacapture.validation
import android.content.Context
import com.google.android.fhir.datacapture.enablement.EnablementEvaluator
import com.google.android.fhir.datacapture.extensions.packRepeatedGroups
import com.google.android.fhir.datacapture.fhirpath.ExpressionEvaluator
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Resource
Expand Down Expand Up @@ -69,6 +72,13 @@ object QuestionnaireResponseValidator {
}

val linkIdToValidationResultMap = mutableMapOf<String, MutableList<ValidationResult>>()
val expressionEvaluator =
ExpressionEvaluator(
questionnaire,
questionnaireResponse,
questionnaireItemParentMap,
launchContextMap,
)

validateQuestionnaireResponseItems(
questionnaire.item,
Expand All @@ -80,6 +90,13 @@ object QuestionnaireResponseValidator {
questionnaireItemParentMap,
launchContextMap,
),
{ questionnaireItemComponent, questionnaireResponseItemComponent, expression ->
expressionEvaluator.evaluateExpression(
questionnaireItemComponent,
questionnaireResponseItemComponent,
expression,
)
},
linkIdToValidationResultMap,
)

Expand All @@ -91,6 +108,12 @@ object QuestionnaireResponseValidator {
questionnaireResponseItemList: List<QuestionnaireResponse.QuestionnaireResponseItemComponent>,
context: Context,
enablementEvaluator: EnablementEvaluator,
expressionEvaluator:
(
Questionnaire.QuestionnaireItemComponent,
QuestionnaireResponse.QuestionnaireResponseItemComponent,
Expression,
) -> List<Base>,
linkIdToValidationResultMap: MutableMap<String, MutableList<ValidationResult>>,
): Map<String, List<ValidationResult>> {
val questionnaireItemListIterator = questionnaireItemList.iterator()
Expand Down Expand Up @@ -118,6 +141,13 @@ object QuestionnaireResponseValidator {
questionnaireResponseItem,
context,
enablementEvaluator,
{ questionnaireItemComponent, questionnaireResponseItemComponent, expression ->
expressionEvaluator.invoke(
questionnaireItemComponent,
questionnaireResponseItemComponent,
expression,
)
},
linkIdToValidationResultMap,
)
}
Expand All @@ -130,6 +160,12 @@ object QuestionnaireResponseValidator {
questionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent,
context: Context,
enablementEvaluator: EnablementEvaluator,
expressionEvaluator:
(
Questionnaire.QuestionnaireItemComponent,
QuestionnaireResponse.QuestionnaireResponseItemComponent,
Expression,
) -> List<Base>,
linkIdToValidationResultMap: MutableMap<String, MutableList<ValidationResult>>,
): Map<String, List<ValidationResult>> {
when (checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }) {
Expand All @@ -143,6 +179,7 @@ object QuestionnaireResponseValidator {
questionnaireResponseItem.item,
context,
enablementEvaluator,
expressionEvaluator,
linkIdToValidationResultMap,
)
else -> {
Expand All @@ -156,6 +193,7 @@ object QuestionnaireResponseValidator {
it.item,
context,
enablementEvaluator,
expressionEvaluator,
linkIdToValidationResultMap,
)
}
Expand All @@ -165,6 +203,7 @@ object QuestionnaireResponseValidator {
QuestionnaireResponseItemValidator.validate(
questionnaireItem,
questionnaireResponseItem.answer,
{ expressionEvaluator.invoke(questionnaireItem, questionnaireResponseItem, it) },
context,
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.extensions.asStringValue
import java.util.regex.Pattern
import java.util.regex.PatternSyntaxException
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.PrimitiveType
import org.hl7.fhir.r4.model.QuestionnaireResponse
Expand All @@ -39,6 +41,7 @@ internal object RegexValidator :
predicate@{
extension: Extension,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
_: (Expression) -> List<Base>,
->
if (!extension.value.isPrimitive || !answer.value.isPrimitive) {
return@predicate false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import com.google.android.fhir.datacapture.extensions.toSpanned
import com.google.android.fhir.datacapture.validation.NotValidated
import com.google.android.fhir.datacapture.validation.Valid
import com.google.android.fhir.datacapture.validation.ValidationResult
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse

Expand Down Expand Up @@ -72,6 +74,7 @@ data class QuestionnaireViewItem(
List<QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent>,
Any?,
) -> Unit,
val expressionEvaluator: (expression: Expression) -> List<Base> = { emptyList() },
val enabledAnswerOptions: List<Questionnaire.QuestionnaireItemAnswerOptionComponent> =
questionnaireItem.answerOption.ifEmpty { emptyList() },
val draftAnswer: Any? = null,
Expand Down
Loading

0 comments on commit 6ae9c5e

Please sign in to comment.