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

Exclude questionnaire items from population #3460

Merged
merged 9 commits into from
Sep 19, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -169,5 +169,5 @@ enum class LinkIdType : Parcelable {
READ_ONLY,
BARCODE,
LOCATION,
IDENTIFIER,
PREPOPULATION_EXCLUSION,
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import org.smartregister.fhircore.engine.BuildConfig
import org.smartregister.fhircore.engine.configuration.ConfigType
import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry
import org.smartregister.fhircore.engine.configuration.GroupResourceConfig
import org.smartregister.fhircore.engine.configuration.LinkIdType
import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig
import org.smartregister.fhircore.engine.configuration.app.ApplicationConfiguration
import org.smartregister.fhircore.engine.configuration.app.CodingSystemUsage
Expand Down Expand Up @@ -1118,14 +1119,49 @@ constructor(
null
}

// Exclude the configured fields from QR
if (questionnaireResponse != null) {
val exclusionLinkIdsMap: Map<String, Boolean> =
questionnaireConfig.linkIds
?.asSequence()
?.filter { it.type == LinkIdType.PREPOPULATION_EXCLUSION }
?.associateBy { it.linkId }
?.mapValues { it.value.type == LinkIdType.PREPOPULATION_EXCLUSION } ?: emptyMap()

questionnaireResponse.item =
excludePrepopulationFields(questionnaireResponse.item.toMutableList(), exclusionLinkIdsMap)
}
return Pair(questionnaireResponse, launchContextResources)
}

fun excludePrepopulationFields(
items: MutableList<QuestionnaireResponseItemComponent>,
exclusionMap: Map<String, Boolean>,
): MutableList<QuestionnaireResponseItemComponent> {
val stack = LinkedList<MutableList<QuestionnaireResponseItemComponent>>()
stack.push(items)
while (stack.isNotEmpty()) {
val currentItems = stack.pop()
val iterator = currentItems.iterator()
while (iterator.hasNext()) {
val item = iterator.next()
if (exclusionMap.containsKey(item.linkId)) {
iterator.remove()
} else if (item.item.isNotEmpty()) {
stack.push(item.item)
}
}
}
return items
}

private fun List<QuestionnaireResponseItemComponent>.removeUnAnsweredItems():
List<QuestionnaireResponseItemComponent> {
return this.filter { it.hasAnswer() || it.item.isNotEmpty() }
return this.asSequence()
.filter { it.hasAnswer() || it.item.isNotEmpty() }
.onEach { it.item = it.item.removeUnAnsweredItems() }
.filter { it.hasAnswer() || it.item.isNotEmpty() }
.toList()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ class QuestionnaireViewModelTest : RobolectricTest() {
@ExperimentalCoroutinesApi
fun setUp() {
hiltRule.inject()

// Write practitioner and organization to shared preferences
sharedPreferencesHelper.write(
SharedPreferenceKey.PRACTITIONER_ID.name,
Expand Down Expand Up @@ -1840,6 +1839,92 @@ class QuestionnaireViewModelTest : RobolectricTest() {
Assert.assertTrue(initialValueDate.isToday)
}

@Test
fun testThatPopulateQuestionnaireSetInitialDefaultValueButExcludesFieldFromResponse() =
runTest(timeout = 90.seconds) {
val thisQuestionnaireConfig =
questionnaireConfig.copy(
resourceType = ResourceType.Patient,
resourceIdentifier = patient.logicalId,
type = QuestionnaireType.EDIT.name,
linkIds =
listOf(
LinkIdConfig("dateToday", LinkIdType.PREPOPULATION_EXCLUSION),
),
)
val questionnaireViewModelInstance =
QuestionnaireViewModel(
defaultRepository = defaultRepository,
dispatcherProvider = defaultRepository.dispatcherProvider,
fhirCarePlanGenerator = fhirCarePlanGenerator,
resourceDataRulesExecutor = resourceDataRulesExecutor,
transformSupportServices = mockk(),
sharedPreferencesHelper = sharedPreferencesHelper,
fhirOperator = fhirOperator,
fhirValidatorProvider = fhirValidatorProvider,
fhirPathDataExtractor = fhirPathDataExtractor,
configurationRegistry = configurationRegistry,
)
val questionnaireWithDefaultDate =
Questionnaire().apply {
id = thisQuestionnaireConfig.id
addItem(
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "dateToday"
type = Questionnaire.QuestionnaireItemType.DATE
addExtension(
Extension(
"http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression",
Expression().apply {
language = "text/fhirpath"
expression = "today()"
},
),
)
},
)
}

val questionnaireResponse =
QuestionnaireResponse().apply {
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "dateToday"
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = DateType(Date())
},
)
},
)
setQuestionnaire(
thisQuestionnaireConfig.id.asReference(ResourceType.Questionnaire).reference,
)
}

coEvery {
fhirEngine.get(
thisQuestionnaireConfig.resourceType!!,
thisQuestionnaireConfig.resourceIdentifier!!,
)
} returns patient

coEvery { fhirEngine.search<QuestionnaireResponse>(any<Search>()) } returns
listOf(
SearchResult(questionnaireResponse, included = null, revIncluded = null),
)

val (result, _) =
questionnaireViewModelInstance.populateQuestionnaire(
questionnaire = questionnaireWithDefaultDate,
questionnaireConfig = thisQuestionnaireConfig,
actionParameters = emptyList(),
)

Assert.assertNotNull(result?.item)
Assert.assertTrue(result!!.item.isEmpty())
}

@Test
fun testThatPopulateQuestionnaireReturnsQuestionnaireResponseWithUnAnsweredRemoved() = runTest {
val questionnaireViewModelInstance =
Expand Down Expand Up @@ -1947,4 +2032,44 @@ class QuestionnaireViewModelTest : RobolectricTest() {
Assert.assertNotNull(result.first)
Assert.assertTrue(result.first!!.find("linkid-1") == null)
}

@Test
fun testExcludeNestedItemFromQuestionnairePrepopulation() {
val item1 = QuestionnaireResponse.QuestionnaireResponseItemComponent().apply { linkId = "1" }
val item2 = QuestionnaireResponse.QuestionnaireResponseItemComponent().apply { linkId = "2" }
val item3 =
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "3"
item =
mutableListOf(
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply { linkId = "3.1" },
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "3.2"
item =
mutableListOf(
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "3.2.1"
},
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "3.2.2"
},
)
},
)
}

val items = mutableListOf(item1, item2, item3)
val exclusionMap = mapOf("2" to true, "3.1" to true, "3.2.2" to true)
val filteredItems = questionnaireViewModel.excludePrepopulationFields(items, exclusionMap)
Assert.assertEquals(2, filteredItems.size)
Assert.assertEquals("1", filteredItems.first().linkId)
val itemThree = filteredItems.last()
Assert.assertEquals("3", itemThree.linkId)
Assert.assertEquals(1, itemThree.item.size)
val itemThreePointTwo = itemThree.item.first()
Assert.assertEquals("3.2", itemThreePointTwo.linkId)
Assert.assertEquals(1, itemThreePointTwo.item.size)
val itemThreePointTwoOne = itemThreePointTwo.item.first()
Assert.assertEquals("3.2.1", itemThreePointTwoOne.linkId)
}
}
16 changes: 16 additions & 0 deletions docs/engineering/app/configuring/forms/forms.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -441,3 +441,19 @@ The QR code widget supports adding an arbitrary number of QR codes, implemented
}
```
The extension's implementation can be found [here](https://github.com/opensrp/fhircore/blob/main/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/sdc/qrCode/EditTextQrCodeViewHolderFactory.kt)

## Excluding questionnaire fields from prepopulation

Use the `linkIds` property to provide linkIds for the Questionnaire fields that should not be pre-field with data during editing or when opening the questionnaire in a read only format.
The `LinkIdType` required for the exclusion to work is `PREPOPULATION_EXCLUSION`. Nested fields can also be excluded from pre-population of forms.

Example:

```json
"linkIds": [
{
"linkId": "ad29c7bd-8041-427f-8e63-b066afe5b438-009",
"type": "PREPOPULATION_EXCLUSION"
}
]
```
Loading