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

Quantity unit options with initial quantity value #2395

Merged
merged 13 commits into from
Feb 26, 2024
37 changes: 29 additions & 8 deletions catalog/src/main/assets/behavior_calculated_expression.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"item": [
{
"linkId": "a-birthdate",
"text": "Birth Date",
"text": "Birth Date (select age to auto calculate if not known)",
"type": "date",
"extension": [
{
Expand All @@ -17,15 +17,36 @@
},
{
"linkId": "a-age-years",
"text": "Age years",
"text": "Age",
"type": "quantity",
"initial": [{
"valueQuantity": {
"unit": "years",
"system": "http://unitsofmeasure.org",
"code": "years"
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption",
"valueCoding": {
"system": "http://unitsofmeasure.org",
"code": "years",
"display": "years"
}
},
{
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption",
"valueCoding": {
"system": "http://unitsofmeasure.org",
"code": "months",
"display": "months"
}
}
}]
],
"initial": [
{
"valueQuantity": {
"value": 1,
"unit": "months",
"system": "http://unitsofmeasure.org",
"code": "months"
}
}
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ class QuestionnaireUiEspressoTest {
.perform(ViewActions.typeTextIntoFocusedView("01052005"))

onView(withId(R.id.time_input_layout)).perform(clickIcon(true))
clickOnText("AM")
clickOnText("6")
clickOnText("10")
clickOnText("OK")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022-2023 Google LLC
* Copyright 2022-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,12 @@ internal val Questionnaire.QuestionnaireItemComponent.unitOption: List<Coding>
return this.extension
.filter { it.url == EXTENSION_QUESTIONNAIRE_UNIT_OPTION_URL }
.map { it.value as Coding }
.plus(
// https://build.fhir.org/ig/HL7/sdc/behavior.html#initial
maimoonak marked this conversation as resolved.
Show resolved Hide resolved
// quantity given as initial without value is for default unit reference purpose
this.initial.map { it.valueQuantity.toCoding() },
)
.distinctBy { it.code }
}

// ********************************************************************************************** //
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Google LLC
* Copyright 2023-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -124,6 +124,14 @@ internal fun Coding.toCodeType(): CodeType {
return CodeType(code)
}

/**
* Converts Quantity to Coding type. The resulting Coding properties are equivalent of Coding.system
* = Quantity.system Coding.code = Quantity.code Coding.display = Quantity.unit
*/
internal fun Quantity.toCoding(): Coding {
return Coding(this.system, this.code, this.unit)
}

fun Type.valueOrCalculateValue(): Type {
return if (this.hasExtension()) {
this.extension
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022-2023 Google LLC
* Copyright 2022-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022-2023 Google LLC
* Copyright 2022-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -28,6 +28,7 @@ import androidx.core.widget.doAfterTextChanged
import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.extensions.getRequiredOrOptionalText
import com.google.android.fhir.datacapture.extensions.localizedFlyoverSpanned
import com.google.android.fhir.datacapture.extensions.toCoding
import com.google.android.fhir.datacapture.extensions.unitOption
import com.google.android.fhir.datacapture.validation.Invalid
import com.google.android.fhir.datacapture.validation.NotValidated
Expand Down Expand Up @@ -112,6 +113,7 @@ internal object QuantityViewHolderFactory :

textInputEditText.removeTextChangedListener(textWatcher)
updateUI()

textWatcher =
textInputEditText.doAfterTextChanged { editable: Editable? ->
handleInput(editable!!, null)
Expand Down Expand Up @@ -185,10 +187,12 @@ internal object QuantityViewHolderFactory :
}

val unit =
questionnaireViewItem.answers.singleOrNull()?.valueQuantity?.let {
Coding(it.system, it.code, it.unit)
}
questionnaireViewItem.answers.singleOrNull()?.valueQuantity?.toCoding()
?: questionnaireViewItem.draftAnswer?.let { if (it is Coding) it else null }
?: questionnaireViewItem.questionnaireItem.initial
?.firstOrNull()
?.valueQuantity
?.toCoding()
unitAutoCompleteTextView.setText(unit?.display ?: "")

val unitAdapter =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Google LLC
* Copyright 2022-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Google LLC
* Copyright 2023-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -1498,6 +1498,113 @@ class MoreQuestionnaireItemComponentsTest {
assertThat(questionItemList.first().localizedFlyoverSpanned.toString()).isEqualTo("gợi ý")
}

@Test
fun `unitOption should return list of coding for multiple available unit options`() {
val question =
Questionnaire.QuestionnaireItemComponent().apply {
addExtension(
EXTENSION_QUESTIONNAIRE_UNIT_OPTION_URL,
Coding("http://unit.org", "yr", "years"),
)
addExtension(
EXTENSION_QUESTIONNAIRE_UNIT_OPTION_URL,
Coding("http://unit.org", "mn", "months"),
)
}

val result = question.unitOption

assertThat(result).hasSize(2)
assertThat((result[0].equalsDeep(Coding("http://unit.org", "yr", "years"))))
assertThat((result[1].equalsDeep(Coding("http://unit.org", "mn", "months"))))
}

@Test
fun `unitOption should return list with single coding for single available unit option`() {
val question =
Questionnaire.QuestionnaireItemComponent().apply {
addExtension(
EXTENSION_QUESTIONNAIRE_UNIT_OPTION_URL,
Coding("http://unit.org", "yr", "years"),
)
}

val result = question.unitOption

assertThat(result).hasSize(1)
assertThat((result[0].equalsDeep(Coding("http://unit.org", "yr", "years"))))
}

@Test
fun `unitOption should return empty list for no available unit option`() {
val question = Questionnaire.QuestionnaireItemComponent()

val result = question.unitOption

assertThat(result).hasSize(0)
}

@Test
fun `unitOption should return list with single coding when initial value of type quantity is defined`() {
val question =
Questionnaire.QuestionnaireItemComponent().apply {
addInitial(
Questionnaire.QuestionnaireItemInitialComponent(
Quantity().apply {
this.system = "http://unit.org"
this.code = "yr"
this.unit = "years"
},
),
)
}

val result = question.unitOption

assertThat(result).hasSize(1)
assertThat((result[0].equalsDeep(Coding("http://unit.org", "yr", "years"))))
}

@Test
fun `unitOption should return list with de-duplicated coding when multiple initial values of type quantity is defined`() {
val question =
Questionnaire.QuestionnaireItemComponent().apply {
addInitial(
Questionnaire.QuestionnaireItemInitialComponent(
Quantity().apply {
this.system = "http://unit.org"
this.code = "yr"
this.unit = "years"
},
),
)
addInitial(
Questionnaire.QuestionnaireItemInitialComponent(
Quantity().apply {
this.system = "http://unit.org"
this.code = "yr"
this.unit = "years"
},
),
)
addInitial(
Questionnaire.QuestionnaireItemInitialComponent(
Quantity().apply {
this.system = "http://unit.org"
this.code = "mo"
this.unit = "months"
},
),
)
}

val result = question.unitOption

assertThat(result).hasSize(2)
assertThat((result[0].equalsDeep(Coding("http://unit.org", "yr", "years"))))
assertThat((result[1].equalsDeep(Coding("http://unit.org", "mo", "months"))))
}

@Test
fun createQuestionResponseWithoutGroupAndNestedQuestions() {
val question =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022-2023 Google LLC
* Copyright 2022-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -205,6 +205,18 @@ class MoreTypesTest {
assertThat(code.equalsDeep(CodeType("fakeCode"))).isTrue()
}

@Test
fun `should return coding for quantity`() {
val quantity =
Quantity(1).apply {
this.code = "yr"
this.unit = "years"
this.system = "http://unit.org"
}
val result = quantity.toCoding()
assertThat(result.equalsDeep(Coding("http://unit.org", "yr", "years")))
}

@Test
fun `should return identifier string for coding containing system, version and code`() {
val coding = Coding("fakeSystem", "fakeCode", "fakeDisplay").apply { version = "2.0" }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Google LLC
* Copyright 2023-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -149,6 +149,35 @@ class QuantityViewHolderFactoryTest {
.isEqualTo("kg")
}

@Test
fun `should set unit value from initial when answer is missing`() {
viewHolder.bind(
QuestionnaireViewItem(
Questionnaire.QuestionnaireItemComponent().apply {
addInitial(
Questionnaire.QuestionnaireItemInitialComponent(
Quantity().apply {
this.unit = "kg"
this.code = "kilo"
},
),
)
},
QuestionnaireResponse.QuestionnaireResponseItemComponent(),
validationResult = NotValidated,
answersChangedCallback = { _, _, _, _ -> },
),
)

assertThat(
viewHolder.itemView
.findViewById<AutoCompleteTextView>(R.id.unit_auto_complete)
.text
.toString(),
)
.isEqualTo("kg")
}

@Test
fun `should clear unit value`() {
viewHolder.bind(
Expand Down
Loading