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

Repeated Group: Fix Definition based extraction for multiple answers #1911

Merged
merged 11 commits into from
Dec 8, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -302,28 +302,26 @@ object ResourceMapper {
extractionResult: MutableList<Resource>,
profileLoader: ProfileLoader
) {
val questionnaireItemListIterator = questionnaireItemList.iterator()
val questionnaireResponseItemListIterator = questionnaireResponseItemList.iterator()
while (questionnaireItemListIterator.hasNext() &&
questionnaireResponseItemListIterator.hasNext()) {
// Questionnaire item with type = group and repeats = true can have multiple questionnaire
// response item components for the same
// questionnaire item linkId. Therefore, it must traverse the structure based on the linkId
// present in the response item component to avoid missing any remaining response items with the
// same linkId.
while (questionnaireResponseItemListIterator.hasNext()) {
omarismail94 marked this conversation as resolved.
Show resolved Hide resolved
val currentQuestionnaireResponseItem = questionnaireResponseItemListIterator.next()
var currentQuestionnaireItem = questionnaireItemListIterator.next()
// Find the next questionnaire item with the same link ID. This is necessary because some
// questionnaire items that are disabled might not have corresponding questionnaire response
// items.
while (questionnaireItemListIterator.hasNext() &&
currentQuestionnaireItem.linkId != currentQuestionnaireResponseItem.linkId) {
currentQuestionnaireItem = questionnaireItemListIterator.next()
}
if (currentQuestionnaireItem.linkId == currentQuestionnaireResponseItem.linkId) {
extractByDefinition(
currentQuestionnaireItem,
currentQuestionnaireResponseItem,
extractionContext,
extractionResult,
profileLoader
)
val currentQuestionnaireItem =
questionnaireItemList.find { it.linkId == currentQuestionnaireResponseItem.linkId }
check(currentQuestionnaireItem != null) {
"Missing questionnaire item for questionnaire response item ${currentQuestionnaireResponseItem.linkId}"
}
extractByDefinition(
currentQuestionnaireItem,
currentQuestionnaireResponseItem,
extractionContext,
extractionResult,
profileLoader
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1017,27 +1017,6 @@ class ResourceMapperTest {
}
]
},
{
"linkId": "PR-address",
"item": [
{
"linkId": "PR-address-city",
"answer": [
{
"valueString": "Nairobi"
}
]
},
{
"linkId": "PR-address-country",
"answer": [
{
"valueString": "Kenya"
}
]
}
]
},
{
"linkId": "PR-active"
}
Expand Down Expand Up @@ -1164,6 +1143,119 @@ class ResourceMapperTest {
assertThat(observation.valueQuantity.value).isEqualTo(BigDecimal(90))
}

@Test
fun `extract() should perform definition-based extraction for repeated groups`() = runBlocking {
@Language("JSON")
val questionnaireJson =
"""
{
"resourceType": "Questionnaire",
"item": [
{
"linkId": "repeated-parent",
"type": "group",
"repeats": true,
"extension": [
{
"url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemExtractionContext",
"valueExpression": {
"expression": "Observation"
}
}
],
"item": [
{
"linkId": "1.0",
"type": "group",
"definition": "http://hl7.org/fhir/StructureDefinition/Observation#Observation.valueCodeableConcept",
"item": [
{
"linkId": "1.0.1",
"type": "choice",
"definition": "http://hl7.org/fhir/StructureDefinition/Observation#Observation.valueCodeableConcept.coding"
}
]
}
]
}
]
}
""".trimIndent()

@Language("JSON")
val questionnaireResponseJson =
"""
{
"resourceType": "QuestionnaireResponse",
"item": [
{
"linkId": "repeated-parent",
"item": [
{
"linkId": "1.0",
"item": [
{
"linkId": "1.0.1",
"answer": [
{
"valueCoding": {
"system": "test-coding-system",
"code": "test-coding-code-1",
"display": "Test Coding Display 1"
}
}
]
}
]
}
]
},
{
"linkId": "repeated-parent",
"item": [
{
"linkId": "1.0",
"item": [
{
"linkId": "1.0.1",
"answer": [
{
"valueCoding": {
"system": "test-coding-system",
"code": "test-coding-code-2",
"display": "Test Coding Display 2"
}
}
]
}
]
}
]
}
]
}
""".trimIndent()

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

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

val bundle = ResourceMapper.extract(uriTestQuestionnaire, uriTestQuestionnaireResponse)

val observation1 = bundle.entry.first().resource as Observation
assertThat(observation1.valueCodeableConcept.coding[0].code).isEqualTo("test-coding-code-1")
assertThat(observation1.valueCodeableConcept.coding[0].display)
.isEqualTo("Test Coding Display 1")

val observation2 = bundle.entry[1].resource as Observation
assertThat(observation2.valueCodeableConcept.coding[0].code).isEqualTo("test-coding-code-2")
assertThat(observation2.valueCodeableConcept.coding[0].display)
.isEqualTo("Test Coding Display 2")
}

@Test
fun `populate() should fill QuestionnaireResponse with values when given a single Resource`() =
runBlocking {
Expand Down