Skip to content

Commit

Permalink
EnableWhenExpression context literal fhirpath supplement implementati…
Browse files Browse the repository at this point in the history
…on (#1957)

* Enable when expression catalog and fhir supplements context implementation

* spotless apply

* fix kdoc

* spotless apply

* Update catalog/src/main/res/values/strings.xml

Co-authored-by: Jing Tang <[email protected]>

* Simplify questionnaire and rename to conext data

* Update catalog/src/main/res/values/strings.xml

---------

Co-authored-by: Benjamin Mwalimu <[email protected]>
Co-authored-by: Jing Tang <[email protected]>
  • Loading branch information
3 people committed May 4, 2023
1 parent 58fc05b commit 70277d3
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 7 deletions.
54 changes: 54 additions & 0 deletions catalog/src/main/assets/behavior_context_variables.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"resourceType": "Questionnaire",
"item": [
{
"linkId": "1",
"type": "choice",
"text": "Choose an option below?",
"answerOption": [
{
"valueCoding": {
"code": "Y",
"display": "Yes",
"system": "custom"
}
},
{
"valueCoding": {
"code": "N",
"display": "No",
"system": "custom"
}
}
]
},
{
"text": "Why 'Yes' was selected?",
"type": "string",
"linkId": "yes",
"extension": [
{
"url": "http:https://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression",
"valueExpression": {
"expression": "%resource.descendants().where(linkId='1').answer.value.display.lower() = %context.linkId",
"language": "text/fhirpath"
}
}
]
},
{
"text": "Here 'No' was selected.",
"type": "display",
"linkId": "no",
"extension": [
{
"url": "http:https://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression",
"valueExpression": {
"expression": "%resource.descendants().where(linkId='1').answer.value.display.lower() = %context.linkId",
"language": "text/fhirpath"
}
}
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class BehaviorListViewModel(application: Application) : AndroidViewModel(applica
R.string.behavior_name_calculated_expression,
"behavior_calculated_expression.json"
),
CONTEXT_VARIABLES(
R.drawable.ic_context,
R.string.behavior_name_context_variables,
"behavior_context_variables.json"
),
SKIP_LOGIC(
R.drawable.ic_skiplogic_behavior,
R.string.behavior_name_skip_logic,
Expand Down
15 changes: 15 additions & 0 deletions catalog/src/main/res/drawable/ic_context.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<vector
xmlns:android="http:https://schemas.android.com/apk/res/android"
android:name="vector"
android:width="50dp"
android:height="50dp"
android:viewportWidth="24"
android:viewportHeight="24"
>
<path
android:name="path"
android:fillColor="#1A73E8"
android:pathData="M 16.66 4.52 L 19.49 7.35 L 16.66 10.18 L 13.83 7.35 L 16.66 4.52 M 9 5 L 9 9 L 5 9 L 5 5 L 9 5 M 19 15 L 19 19 L 15 19 L 15 15 L 19 15 M 9 15 L 9 19 L 5 19 L 5 15 L 9 15 M 16.66 1.69 L 11 7.34 L 16.66 13 L 22.32 7.34 L 16.66 1.69 Z M 11 3 L 3 3 L 3 11 L 11 11 L 11 3 Z M 21 13 L 13 13 L 13 21 L 21 21 L 21 13 Z M 11 13 L 3 13 L 3 21 L 11 21 L 11 13 Z"
android:strokeColor="#4285F4"
/>
</vector>
1 change: 1 addition & 0 deletions catalog/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<string
name="behavior_name_skip_logic_info"
>If Yes is selected, a follow-up question is displayed. If No is selected, no follow-up questions are displayed.</string>
<string name="behavior_name_context_variables">Context variable</string>
<string
name="behavior_name_calculated_expression_info"
>Input age to automatically calculate birthdate until birthdate is updated manually.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package com.google.android.fhir.datacapture.enablement
import com.google.android.fhir.compareTo
import com.google.android.fhir.datacapture.extensions.allItems
import com.google.android.fhir.datacapture.extensions.enableWhenExpression
import com.google.android.fhir.datacapture.fhirpath.fhirPathEngine
import com.google.android.fhir.datacapture.fhirpath.evaluateToBoolean
import com.google.android.fhir.equals
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
Expand Down Expand Up @@ -110,8 +110,10 @@ internal class EnablementEvaluator(val questionnaireResponse: QuestionnaireRespo

// Evaluate `enableWhenExpression`.
if (enableWhenExpression != null && enableWhenExpression.hasExpression()) {
return fhirPathEngine.convertToBoolean(
fhirPathEngine.evaluate(questionnaireResponse, enableWhenExpression.expression)
return evaluateToBoolean(
questionnaireResponse,
questionnaireResponseItem,
enableWhenExpression.expression
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package com.google.android.fhir.datacapture.fhirpath
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.FhirVersionEnum
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemComponent
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.utils.FHIRPathEngine

Expand All @@ -34,3 +36,22 @@ internal val fhirPathEngine: FHIRPathEngine =
*/
internal fun evaluateToDisplay(expressions: List<String>, data: Resource) =
expressions.joinToString(" ") { fhirPathEngine.evaluateToString(data, it) }

/**
* Evaluates the expression and returns the boolean result. The resources [QuestionnaireResponse]
* and [QuestionnaireResponseItemComponent] are passed as fhirPath supplements as defined in fhir
* specs https://build.fhir.org/ig/HL7/sdc/expressions.html#fhirpath-supplements
*
* %resource = [QuestionnaireResponse], %context = [QuestionnaireResponseItemComponent]
*/
internal fun evaluateToBoolean(
questionnaireResponse: QuestionnaireResponse,
questionnaireResponseItemComponent: QuestionnaireResponseItemComponent,
expression: String
) =
fhirPathEngine.evaluateToBoolean(
questionnaireResponse,
null,
questionnaireResponseItemComponent,
expression
)
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class EnablementEvaluatorTest {
val iParser: IParser = FhirContext.forR4Cached().newJsonParser()

@Test
fun evaluate_noEnableWhen_shouldReturnTrue() {
assertEnableWhen().isTrue()
Expand Down Expand Up @@ -159,8 +161,6 @@ class EnablementEvaluatorTest {
}
""".trimIndent()

val iParser: IParser = FhirContext.forR4().newJsonParser()

val questionnaire =
iParser.parseResource(Questionnaire::class.java, questionnaireJson) as Questionnaire

Expand Down Expand Up @@ -241,8 +241,6 @@ class EnablementEvaluatorTest {
}
""".trimIndent()

val iParser: IParser = FhirContext.forR4().newJsonParser()

val questionnaire =
iParser.parseResource(Questionnaire::class.java, questionnaireJson) as Questionnaire

Expand All @@ -263,6 +261,85 @@ class EnablementEvaluatorTest {
.isFalse()
}

@Test
fun `evaluate() should evaluate enableWhenExpression with context fhirpath supplement literal`() =
runBlocking {
@Language("JSON")
val questionnaireJson =
"""
{
"resourceType": "Questionnaire",
"item": [
{
"linkId": "1",
"definition": "http:https://hl7.org/fhir/StructureDefinition/Patient#Patient.gender",
"type": "choice",
"text": "Gender"
},
{
"extension": [
{
"url": "http:https://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression",
"valueExpression": {
"language": "text/fhirpath",
"expression": "%resource.repeat(item).where(linkId='1').answer.value.code = %context.linkId"
}
}
],
"linkId" : "female",
"text": "Have you had mammogram before?(enableWhenExpression = only when gender is female)",
"type": "choice",
"answerValueSet": "http:https://hl7.org/fhir/ValueSet/yesnodontknow"
}
]
}
""".trimIndent()

@Language("JSON")
val questionnaireResponseJson =
"""
{
"resourceType": "QuestionnaireResponse",
"item": [
{
"linkId": "1",
"answer": [
{
"valueCoding": {
"system": "http:https://hl7.org/fhir/administrative-gender",
"code": "female",
"display": "Female"
}
}
]
},
{
"linkId": "female"
}
]
}
""".trimIndent()

val questionnaire =
iParser.parseResource(Questionnaire::class.java, questionnaireJson) as Questionnaire

val questionnaireItem: Questionnaire.QuestionnaireItemComponent =
questionnaire.item.find { it.linkId == "female" }!!

val questionnaireResponse =
iParser.parseResource(QuestionnaireResponse::class.java, questionnaireResponseJson)
as QuestionnaireResponse

assertThat(
EnablementEvaluator(questionnaireResponse)
.evaluate(
questionnaireItem,
questionnaireResponse.item[1],
)
)
.isTrue()
}

@Test
fun evaluate_expectAnswerDoesNotExist_answerDoesNotExist_shouldReturnTrue() {
assertEnableWhen(
Expand Down

0 comments on commit 70277d3

Please sign in to comment.