Skip to content

Commit

Permalink
Refactor evaluation of cqf-calculatedValue expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
LZRS committed Nov 21, 2023
1 parent 02d2c7d commit 79fdbe7
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ import com.google.android.fhir.datacapture.extensions.validateLaunchContextExten
import com.google.android.fhir.datacapture.extensions.zipByLinkId
import com.google.android.fhir.datacapture.fhirpath.ExpressionEvaluator
import com.google.android.fhir.datacapture.validation.Invalid
import com.google.android.fhir.datacapture.validation.MAX_VALUE_EXTENSION_URL
import com.google.android.fhir.datacapture.validation.MIN_VALUE_EXTENSION_URL
import com.google.android.fhir.datacapture.validation.NotValidated
import com.google.android.fhir.datacapture.validation.QuestionnaireResponseItemValidator
import com.google.android.fhir.datacapture.validation.QuestionnaireResponseValidator
Expand All @@ -68,7 +70,6 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.withIndex
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Element
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent
Expand Down Expand Up @@ -565,36 +566,21 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
}
}

private fun resolveCqfExpression(
questionnaireItem: QuestionnaireItemComponent,
questionnaireResponseItem: QuestionnaireResponseItemComponent,
element: Element,
): List<Base> {
val cqfExpression = element.cqfExpression ?: return emptyList()

if (!cqfExpression.isFhirPath) {
throw UnsupportedOperationException("${cqfExpression.language} not supported yet")
}
return expressionEvaluator.evaluateExpression(
questionnaireItem,
questionnaireResponseItem,
cqfExpression,
)
}

private fun resolveCqfCalculatedValueExpression(
private fun resolveExpression(
questionnaireItem: QuestionnaireItemComponent,
questionnaireResponseItem: QuestionnaireResponseItemComponent,
expression: Expression,
): Type? {
): Base? {
if (!expression.isFhirPath) {
throw UnsupportedOperationException("${expression.language} not supported yet")
}

return expressionEvaluator
.evaluateExpression(questionnaireItem, questionnaireResponseItem, expression)
.evaluateExpression(
questionnaireItem,
questionnaireResponseItem,
expression,
)
.singleOrNull()
?.let { it as Type }
}

private fun removeDisabledAnswers(
Expand Down Expand Up @@ -687,6 +673,28 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
)
}

private fun resolveCqfCalculatedValueExpressions(
questionnaireItem: QuestionnaireItemComponent,
questionnaireResponseItem: QuestionnaireResponseItemComponent,
) {
questionnaireItem.extension
.filter { it.hasValue() && it.value.hasExtension(EXTENSION_CQF_CALCULATED_VALUE_URL) }
.forEach { extension ->
val currentExtensionValue = extension.value
val currentExtensionValueExtensions = currentExtensionValue.extension
val evaluatedCqfCalculatedValue =
currentExtensionValue.getExtensionByUrl(EXTENSION_CQF_CALCULATED_VALUE_URL).value.let {
it as Expression
resolveExpression(questionnaireItem, questionnaireResponseItem, it) as? Type
}
// add previous extensions to the evaluated value
evaluatedCqfCalculatedValue?.setExtension(currentExtensionValueExtensions)
if (evaluatedCqfCalculatedValue != null) {
extension.setValue(evaluatedCqfCalculatedValue)
}
}
}

/**
* Returns the list of [QuestionnaireViewItem]s generated for the questionnaire items and
* questionnaire response items.
Expand Down Expand Up @@ -726,21 +734,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
restoreFromDisabledQuestionnaireItemAnswersCache(questionnaireResponseItem)

// Evaluate cqf-calculatedValues
questionnaireItem.extension
.filter { it.hasValue() && it.value.hasExtension(EXTENSION_CQF_CALCULATED_VALUE_URL) }
.forEach { extension ->
val expression =
extension.value.getExtensionByUrl(EXTENSION_CQF_CALCULATED_VALUE_URL).value as Expression
resolveCqfCalculatedValueExpression(
questionnaireItem,
questionnaireResponseItem,
expression,
)
?.let {
it.apply { setExtension(extension.value.extension) }
extension.setValue(it)
}
}
resolveCqfCalculatedValueExpressions(questionnaireItem, questionnaireResponseItem)

// Determine the validation result, which will be displayed on the item itself
val validationResult =
Expand All @@ -759,11 +753,12 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
}

// Set question text dynamically from CQL expression
questionnaireResponseItem.apply {
resolveCqfExpression(questionnaireItem, this, questionnaireItem.textElement)
.firstOrNull()
?.let { text = it.primitiveValue() }
questionnaireItem.textElement.cqfExpression?.let { expression ->
resolveExpression(questionnaireItem, questionnaireResponseItem, expression)
?.primitiveValue()
?.let { questionnaireResponseItem.text = it }
}

val (enabledQuestionnaireAnswerOptions, disabledQuestionnaireResponseAnswers) =
answerOptionsEvaluator.evaluate(
questionnaireItem,
Expand All @@ -787,6 +782,8 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
validationResult = validationResult,
answersChangedCallback = answersChangedCallback,
enabledAnswerOptions = enabledQuestionnaireAnswerOptions,
minValue = questionnaireItem.getExtensionByUrl(MIN_VALUE_EXTENSION_URL)?.value,
maxValue = questionnaireItem.getExtensionByUrl(MAX_VALUE_EXTENSION_URL)?.value,
draftAnswer = draftAnswerMap[questionnaireResponseItem],
enabledDisplayItems =
questionnaireItem.item.filter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.extensions.displayString
import com.google.android.fhir.datacapture.extensions.localizedTextSpanned
import com.google.android.fhir.datacapture.extensions.toSpanned
import com.google.android.fhir.datacapture.validation.MAX_VALUE_EXTENSION_URL
import com.google.android.fhir.datacapture.validation.MIN_VALUE_EXTENSION_URL
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.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Type

/**
* Data item for [QuestionnaireItemViewHolder] in [RecyclerView].
Expand All @@ -54,7 +53,9 @@ import org.hl7.fhir.r4.model.QuestionnaireResponse
* @param validationResult the [ValidationResult] of the answer(s) against the `questionnaireItem`
* @param answersChangedCallback the callback to notify the view model that the answers have been
* changed for the [QuestionnaireResponse.QuestionnaireResponseItemComponent]
* @param enabledAnswerOptions the enabled answer options in [questionnaireItem]]
* @param enabledAnswerOptions the enabled answer options in [questionnaireItem]
* @param minValue the inclusive lower bound on the range of allowed answer values
* @param maxValue the inclusive upper bound on the range of allowed answer values
* @param draftAnswer the draft input that cannot be stored in the [QuestionnaireResponse].
* @param enabledDisplayItems the enabled display items in the given [questionnaireItem]
* @param questionViewTextConfiguration configuration to show asterisk, required and optional text
Expand All @@ -73,6 +74,8 @@ data class QuestionnaireViewItem(
) -> Unit,
val enabledAnswerOptions: List<Questionnaire.QuestionnaireItemAnswerOptionComponent> =
questionnaireItem.answerOption.ifEmpty { emptyList() },
val minValue: Type? = null,
val maxValue: Type? = null,
val draftAnswer: Any? = null,
val enabledDisplayItems: List<Questionnaire.QuestionnaireItemComponent> = emptyList(),
val questionViewTextConfiguration: QuestionTextConfiguration = QuestionTextConfiguration(),
Expand All @@ -93,10 +96,6 @@ data class QuestionnaireViewItem(
val answers: List<QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent> =
questionnaireResponseItem.answer.map { it.copy() }

val minValue by lazy { questionnaireItem.getExtensionByUrl(MIN_VALUE_EXTENSION_URL)?.value }

val maxValue by lazy { questionnaireItem.getExtensionByUrl(MAX_VALUE_EXTENSION_URL)?.value }

/** Updates the answers. This will override any existing answers and removes the draft answer. */
fun setAnswer(
vararg questionnaireResponseItemAnswerComponent:
Expand Down

0 comments on commit 79fdbe7

Please sign in to comment.