Skip to content

Commit

Permalink
Merge branch 'master' into 971_calc_exp
Browse files Browse the repository at this point in the history
  • Loading branch information
maimoonak committed Sep 15, 2022
2 parents 54a8f11 + 1443db1 commit 9089ceb
Show file tree
Hide file tree
Showing 15 changed files with 868 additions and 444 deletions.
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Releases.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ object Releases {

object DataCapture : LibraryArtifact {
override val artifactId = "data-capture"
override val version = "0.1.0-beta04"
override val version = "0.1.0-beta05"
override val name = "Android FHIR Structured Data Capture Library"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ internal fun Questionnaire.QuestionnaireItemComponent.isReferencedBy(
item: Questionnaire.QuestionnaireItemComponent
) =
item.expressionBasedExtensions.any {
it.castToExpression(it.value).expression.contains("'${this.linkId}'")
it.castToExpression(it.value).expression.replace(" ", "").contains(Regex(".*linkId='${this.linkId}'.*"))
}

// Item control code, or null
Expand Down Expand Up @@ -330,7 +330,7 @@ fun QuestionnaireResponse.QuestionnaireResponseItemComponent.addNestedItemsToAns
*/
fun List<Questionnaire.QuestionnaireItemComponent>.flattened():
List<Questionnaire.QuestionnaireItemComponent> {
return this + this.flatMap { if (it.hasItem()) it.item.flattened() else it.item }
return this + this.flatMap { it.item.flattened() }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,16 +387,11 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
questionnaireResponseItemPreOrderList.find { it.linkId == updatedCalculable.first.linkId }

val evaluatedAnswer = updatedCalculable.second
val currentAnswer = updatedCalculableResponse?.answer?.map { it.value }

// update and notify only if answer has changed to prevent any event loop
// if current and previous both are not empty and the answer is changed in count or
// content
if ((evaluatedAnswer + currentAnswer).isNotEmpty() &&
(evaluatedAnswer != currentAnswer ||
evaluatedAnswer
.filterIndexed { i, v -> currentAnswer[i].equalsDeep(v).not() }
.isEmpty())
val currentAnswer = updatedCalculableResponse?.answer?.map { it.value } ?: emptyList()

// update and notify only if new answer has changed to prevent any event loop
if (evaluatedAnswer.size != currentAnswer.size ||
evaluatedAnswer.zip(currentAnswer).any { (v1, v2) -> v1.equalsDeep(v2).not() }
) {
updatedCalculableResponse?.let {
it.answer =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.google.android.fhir.datacapture.fhirpath
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.FhirVersionEnum
import com.google.android.fhir.datacapture.calculatedExpression
import com.google.android.fhir.datacapture.expressionBasedExtensions
import com.google.android.fhir.datacapture.findVariableExpression
import com.google.android.fhir.datacapture.flattened
import com.google.android.fhir.datacapture.isReferencedBy
Expand Down Expand Up @@ -85,6 +86,17 @@ object ExpressionEvaluator {
}
}

/** Detects if any item into list is referencing a dependent item in its calculated expression */
internal fun extractExpressionReferenceMap(
items: List<Questionnaire.QuestionnaireItemComponent>
) {
items.flattened().filter { it.expressionBasedExtensions.isNotEmpty() }.run {
forEach { current ->

}
}
}

/**
* Returns a pair of item and the calculated and evaluated value for all items with calculated
* expression extension, which is dependent on value of updated response
Expand All @@ -101,7 +113,10 @@ object ExpressionEvaluator {
.item
.flattened()
.filter { item ->
// item is calculable and not modified yet and depends on the updated answer
// 1- item is calculable
// 2- item answer is not modified and touched by user;
// https://build.fhir.org/ig/HL7/sdc/StructureDefinition-sdc-questionnaire-calculatedExpression.html
// 3- item answer depends on the updated item answer
item.calculatedExpression != null &&
modifiedResponses.none { it.linkId == item.linkId } &&
updatedQuestionnaireItem.isReferencedBy(item)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.hl7.fhir.r4.model.CodeType
import org.hl7.fhir.r4.model.CodeableConcept
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.DecimalType
import org.hl7.fhir.r4.model.DomainResource
import org.hl7.fhir.r4.model.Enumeration
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Extension
Expand Down Expand Up @@ -444,9 +445,47 @@ object ResourceMapper {
.javaClass
.getMethod("setValue", Type::class.java)
.invoke(base, questionnaireResponseItem.answer.singleOrNull()?.value)
return
} catch (e: NoSuchMethodException) {
// Do nothing
}

if (base.javaClass.getFieldOrNull(fieldName) == null) {
// If field not found in resource class, assume this is an extension
addDefinitionBasedCustomExtension(questionnaireItem, questionnaireResponseItem, base)
}
}
}

/**
* Adds custom extension for Resource.
* @param questionnaireItem QuestionnaireItemComponent with details for extension
* @param questionnaireResponseItem QuestionnaireResponseItemComponent for response value
* @param base
* - resource's Base class instance See
* https://hapifhir.io/hapi-fhir/docs/model/profiles_and_extensions.html#extensions for more on
* custom extensions
*/
private fun addDefinitionBasedCustomExtension(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
questionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent,
base: Base
) {
if (base is Type) {
// Create an extension
val ext = Extension()
ext.url = questionnaireItem.definition
ext.setValue(questionnaireResponseItem.answer.first().value)
// Add the extension to the resource
base.addExtension(ext)
}
if (base is DomainResource) {
// Create an extension
val ext = Extension()
ext.url = questionnaireItem.definition
ext.setValue(questionnaireResponseItem.answer.first().value)
// Add the extension to the resource
base.addExtension(ext)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import java.time.ZoneId
import java.time.chrono.IsoChronology
import java.time.format.DateTimeFormatterBuilder
import java.time.format.FormatStyle
import java.util.Date
import java.util.Locale
import kotlin.math.abs
import kotlin.math.log10
Expand Down Expand Up @@ -103,16 +104,22 @@ internal object QuestionnaireItemDatePickerViewHolderFactory :
textInputLayout.hint = localePattern
textInputEditText.removeTextChangedListener(textWatcher)

textInputEditText.setText(
questionnaireItemViewItem
.answers
.singleOrNull()
?.takeIf { it.hasValue() }
?.valueDateType
?.localDate
?.localizedString
)

if (isTextUpdateRequired(
textInputEditText.context,
questionnaireItemViewItem.answers.singleOrNull()?.valueDateType,
textInputEditText.text.toString()
)
) {
textInputEditText.setText(
questionnaireItemViewItem
.answers
.singleOrNull()
?.takeIf { it.hasValue() }
?.valueDateType
?.localDate
?.localizedString
)
}
textWatcher = textInputEditText.doAfterTextChanged { text -> updateAnswer(text.toString()) }
}

Expand Down Expand Up @@ -159,6 +166,20 @@ internal object QuestionnaireItemDatePickerViewHolderFactory :
}
}
}

private fun isTextUpdateRequired(
context: Context,
answer: DateType?,
inputText: String?
): Boolean {
val inputDate =
try {
parseDate(inputText, context)
} catch (e: Exception) {
null
}
return answer?.localDate != inputDate
}
}

internal const val TAG = "date-picker"
Expand Down Expand Up @@ -196,14 +217,17 @@ internal val DateType.localDate
internal val LocalDate.dateType
get() = DateType(year, monthValue - 1, dayOfMonth)

internal val Date.localDate
get() = LocalDate.of(year + 1900, month + 1, date)

internal fun parseDate(text: CharSequence?, context: Context): LocalDate {
val date =
val localDate =
if (isAndroidIcuSupported()) {
DateFormat.getDateInstance(DateFormat.SHORT).parse(text.toString())
} else {
android.text.format.DateFormat.getDateFormat(context).parse(text.toString())
}
val localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()
DateFormat.getDateInstance(DateFormat.SHORT).parse(text.toString())
} else {
android.text.format.DateFormat.getDateFormat(context).parse(text.toString())
}
.localDate
// date/localDate with year more than 4 digit throws data format exception if deep copy
// operation get performed on QuestionnaireResponse,
// QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent in org.hl7.fhir.r4.model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,10 @@ internal object QuestionnaireItemDateTimePickerViewHolderFactory :
val dateTime = questionnaireItemViewItem.answers.singleOrNull()?.valueDateTimeType
updateDateTimeInput(
dateTime?.let {
LocalDateTime.of(it.year, it.month + 1, it.day, it.hour, it.minute, it.second)
it.localDateTime.also {
localDate = it.toLocalDate()
localTime = it.toLocalTime()
}
}
)
textWatcher =
Expand Down Expand Up @@ -197,7 +200,12 @@ internal object QuestionnaireItemDateTimePickerViewHolderFactory :
/** Update the date and time input fields in the UI. */
private fun updateDateTimeInput(localDateTime: LocalDateTime?) {
enableOrDisableTimePicker(enableIt = localDateTime != null)
if (dateInputEditText.text.isNullOrEmpty()) {
if (isTextUpdateRequired(
dateInputEditText.context,
localDateTime,
dateInputEditText.text.toString()
)
) {
dateInputEditText.setText(localDateTime?.localizedDateString ?: "")
}
timeInputEditText.setText(
Expand Down Expand Up @@ -281,6 +289,21 @@ internal object QuestionnaireItemDateTimePickerViewHolderFactory :
timeInputLayout.isEnabled = enableIt
timeInputLayout.isEnabled = enableIt
}

private fun isTextUpdateRequired(
context: Context,
answer: LocalDateTime?,
inputText: String?
): Boolean {
val inputDate =
try {
generateLocalDateTime(parseDate(inputText, context), localTime)
} catch (e: Exception) {
null
}
if (answer == null || inputDate == null) return true
return answer.toLocalDate() != inputDate.toLocalDate()
}
}
}

Expand All @@ -301,3 +324,14 @@ internal val DateTimeType.localTime
minute,
second,
)

internal val DateTimeType.localDateTime
get() =
LocalDateTime.of(
year,
month + 1,
day,
hour,
minute,
second,
)
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,11 @@ internal object QuestionnaireItemRadioGroupViewHolderFactory :
}

private fun updateAnswer(answerOption: Questionnaire.QuestionnaireItemAnswerOptionComponent) {
// if-else block to prevent over-writing of "items" nested within "answer"
if (questionnaireItemViewItem.answers.isNotEmpty()) {
questionnaireItemViewItem.answers.apply { this[0].value = answerOption.value }
} else {
questionnaireItemViewItem.setAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = answerOption.value
}
)
}
questionnaireItemViewItem.setAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = answerOption.value
}
)
}
}
}
Loading

0 comments on commit 9089ceb

Please sign in to comment.