diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt
index 5d8e480214..c62db7a27c 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt
@@ -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
@@ -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
@@ -565,36 +566,21 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
}
}
- private fun resolveCqfExpression(
- questionnaireItem: QuestionnaireItemComponent,
- questionnaireResponseItem: QuestionnaireResponseItemComponent,
- element: Element,
- ): List {
- 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(
@@ -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.
@@ -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 =
@@ -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,
@@ -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 {
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireViewItem.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireViewItem.kt
index 4d75941954..4aa4f822c5 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireViewItem.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireViewItem.kt
@@ -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].
@@ -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
@@ -73,6 +74,8 @@ data class QuestionnaireViewItem(
) -> Unit,
val enabledAnswerOptions: List =
questionnaireItem.answerOption.ifEmpty { emptyList() },
+ val minValue: Type? = null,
+ val maxValue: Type? = null,
val draftAnswer: Any? = null,
val enabledDisplayItems: List = emptyList(),
val questionViewTextConfiguration: QuestionTextConfiguration = QuestionTextConfiguration(),
@@ -93,10 +96,6 @@ data class QuestionnaireViewItem(
val answers: List =
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: