Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow usage of variable and context in answer expression #2039

Merged
merged 23 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ce5a189
Allow usage of variable and context in answer expression
maimoonak Jun 13, 2023
96e31f4
Add test
maimoonak Jun 13, 2023
ee9f18e
Merge branch 'master' into answer-exp-variable
maimoonak Jun 21, 2023
a55aca6
Merge branch 'master' into answer-exp-variable
maimoonak Jul 25, 2023
f1a20d9
resolve conflicts
maimoonak Jul 25, 2023
2a53d2d
Merge branch 'master' into answer-exp-variable
maimoonak Jul 31, 2023
d7d9c47
resolve conflicts and handle enabled items options
maimoonak Jul 31, 2023
e5daf70
Merge branch 'master' into answer-exp-variable
maimoonak Aug 8, 2023
6e55fe1
Merge branch 'master' into answer-exp-variable
f-odhiambo Aug 31, 2023
6ef89c0
Merge branch 'master' into answer-exp-variable
f-odhiambo Sep 4, 2023
36ecc87
Merge branch 'master' into answer-exp-variable
f-odhiambo Sep 7, 2023
dc963bd
Merge branch 'master' into answer-exp-variable
maimoonak Sep 11, 2023
24d4ab2
Extend variable context test
maimoonak Sep 11, 2023
fc5a31d
Merge branch 'answer-exp-variable' of https://github.com/opensrp/andr…
maimoonak Sep 11, 2023
6021cdd
Merge branch 'master' into answer-exp-variable
maimoonak Sep 12, 2023
3af708b
Resolve merge conflicts
maimoonak Sep 12, 2023
b373aa3
Merge branch 'master' into answer-exp-variable
maimoonak Sep 12, 2023
ccef36d
Breakdown fhirpath supplement context test
maimoonak Sep 12, 2023
fee87cb
Merge branch 'answer-exp-variable' of https://github.com/opensrp/andr…
maimoonak Sep 12, 2023
0b4c49f
Merge branch 'master' into answer-exp-variable
maimoonak Sep 12, 2023
44a92be
Merge branch 'master' into answer-exp-variable
maimoonak Sep 13, 2023
d2c5c85
Merge branch 'master' into answer-exp-variable
jingtang10 Sep 13, 2023
b2ff6b3
Run spotlessApply
jingtang10 Sep 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,19 @@ internal class EnabledAnswerOptionsEvaluator(
*/
internal suspend fun evaluate(
questionnaireItem: QuestionnaireItemComponent,
questionnaireResponseItem: QuestionnaireResponseItemComponent,
questionnaireResponseItem: QuestionnaireResponseItemComponent
): Pair<
List<Questionnaire.QuestionnaireItemAnswerOptionComponent>,
List<QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent>
> {

val resolvedAnswerOptions = answerOptions(questionnaireItem)
val resolvedAnswerOptions =
answerOptions(
questionnaireItem,
questionnaireResponseItem,
questionnaireResponse,
questionnaireItemParentMap
)

if (questionnaireItem.answerOptionsToggleExpressions.isEmpty())
return Pair(resolvedAnswerOptions, emptyList())
Expand Down Expand Up @@ -133,12 +139,21 @@ internal class EnabledAnswerOptionsEvaluator(
*/
private suspend fun answerOptions(
questionnaireItem: QuestionnaireItemComponent,
questionnaireResponseItem: QuestionnaireResponseItemComponent,
questionnaireResponse: QuestionnaireResponse,
questionnaireItemParentMap: Map<QuestionnaireItemComponent, QuestionnaireItemComponent>
): List<Questionnaire.QuestionnaireItemAnswerOptionComponent> =
when {
questionnaireItem.answerOption.isNotEmpty() -> questionnaireItem.answerOption
!questionnaireItem.answerValueSet.isNullOrEmpty() ->
resolveAnswerValueSet(questionnaireItem.answerValueSet)
questionnaireItem.answerExpression != null -> resolveAnswerExpression(questionnaireItem)
questionnaireItem.answerExpression != null ->
resolveAnswerExpression(
questionnaireItem,
questionnaireResponseItem,
questionnaireResponse,
questionnaireItemParentMap
)
else -> emptyList()
}

Expand Down Expand Up @@ -185,6 +200,9 @@ internal class EnabledAnswerOptionsEvaluator(
// https://build.fhir.org/ig/HL7/sdc/expressions.html#x-fhir-query-enhancements
private suspend fun resolveAnswerExpression(
item: QuestionnaireItemComponent,
responseItem: QuestionnaireResponseItemComponent,
questionnaireResponse: QuestionnaireResponse,
questionnaireItemParentMap: Map<QuestionnaireItemComponent, QuestionnaireItemComponent>
): List<Questionnaire.QuestionnaireItemAnswerOptionComponent> {
// Check cache first for database queries
val answerExpression = item.answerExpression ?: return emptyList()
Expand Down Expand Up @@ -212,7 +230,7 @@ internal class EnabledAnswerOptionsEvaluator(
)
}
answerExpression.isFhirPath -> {
val data = fhirPathEngine.evaluate(questionnaireResponse, answerExpression.expression)
val data = expressionEvaluator.evaluateExpression(item, responseItem, answerExpression)
item.extractAnswerOptions(data)
}
else ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4282,6 +4282,117 @@ class QuestionnaireViewModelTest {
)
}

@Test
fun `should return questionnaire item answer options for answer expression with fhirpath variable`() =
runTest {
val questionnaire =
Questionnaire().apply {
addItem(
QuestionnaireItemComponent().apply {
linkId = "a"
text = "Question 1"
type = Questionnaire.QuestionnaireItemType.CHOICE
repeats = true
initial =
listOf(
Questionnaire.QuestionnaireItemInitialComponent(Coding("test", "1", "One")),
Questionnaire.QuestionnaireItemInitialComponent(Coding("test", "2", "Two"))
)
}
)
addItem(
QuestionnaireItemComponent().apply {
linkId = "b"
text = "Question 2"
type = Questionnaire.QuestionnaireItemType.STRING
extension =
listOf(
Extension(
"http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-answerExpression",
Expression().apply {
this.expression = "%resource.item[0].answer.value.select(%VAR1 + code)"
this.language = Expression.ExpressionLanguage.TEXT_FHIRPATH.toCode()
}
),
Extension(
"http://hl7.org/fhir/StructureDefinition/variable",
Expression().apply {
this.name = "VAR1"
this.expression = "'Class '"
this.language = Expression.ExpressionLanguage.TEXT_FHIRPATH.toCode()
}
)
)
}
)
}

state.set(EXTRA_QUESTIONNAIRE_JSON_STRING, printer.encodeResourceToString(questionnaire))
val viewModel = QuestionnaireViewModel(context, state)

viewModel.runViewModelBlocking {
val viewItem =
viewModel
.getQuestionnaireItemViewItemList()
.map { it.asQuestion() }
.single { it.questionnaireItem.linkId == "b" }
assertThat(viewItem.enabledAnswerOptions.map { it.valueStringType.value })
.containsExactly("Class 1", "Class 2")
}
}

@Test
fun `should return questionnaire item answer options for answer expression with fhirpath supplement context`() =
runTest {
val questionnaire =
Questionnaire().apply {
addItem(
QuestionnaireItemComponent().apply {
linkId = "a"
text = "Question 1"
type = Questionnaire.QuestionnaireItemType.CHOICE
repeats = true
initial =
listOf(
Questionnaire.QuestionnaireItemInitialComponent(Coding("test", "1", "One")),
Questionnaire.QuestionnaireItemInitialComponent(Coding("test", "2", "Two"))
)
}
)
addItem(
QuestionnaireItemComponent().apply {
linkId = "b"
text = "Question 2"
type = Questionnaire.QuestionnaireItemType.STRING
extension =
listOf(
Extension(
"http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-answerExpression",
Expression().apply {
this.expression =
"%resource.item[0].answer.value.select('Code ' + code + '-' + %context.linkId)"
this.language = Expression.ExpressionLanguage.TEXT_FHIRPATH.toCode()
}
)
)
}
)
}

state.set(EXTRA_QUESTIONNAIRE_JSON_STRING, printer.encodeResourceToString(questionnaire))
val viewModel = QuestionnaireViewModel(context, state)

viewModel.runViewModelBlocking {
val viewItem =
viewModel
.getQuestionnaireItemViewItemList()
.map { it.asQuestion() }
.single { it.questionnaireItem.linkId == "b" }
assertThat(viewItem.enabledAnswerOptions.map { it.valueStringType.value })
.containsExactly("Code 1-b", "Code 2-b")
}
}

// ==================================================================== //
// //
// Answer Options Toggle Expression //
Expand Down