Skip to content

Commit

Permalink
Merge branch 'master' into mark-internal
Browse files Browse the repository at this point in the history
  • Loading branch information
santosh-pingle authored Mar 28, 2024
2 parents e1ee29c + ae5a2ec commit 1ef58fd
Show file tree
Hide file tree
Showing 625 changed files with 649 additions and 38,687 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ buildscript {
}
dependencies {
classpath(Plugins.androidGradlePlugin)
classpath(Plugins.kspGradlePlugin)
classpath(Plugins.benchmarkGradlePlugin)
classpath(Plugins.flankGradlePlugin)
classpath(Plugins.kotlinGradlePlugin)
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ object Dependencies {

object Kotlin {
const val kotlinCoroutinesCore = "1.7.2"
const val stdlib = "1.8.20"
const val stdlib = "1.9.22"
}

const val androidFhirCommon = "0.1.0-alpha05"
Expand Down
6 changes: 4 additions & 2 deletions buildSrc/src/main/kotlin/Plugins.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@ object Plugins {
const val androidLib = "com.android.library"
const val application = "com.android.application"
const val benchmark = "androidx.benchmark"
const val jetbrainsKotlinAndroid = "org.jetbrains.kotlin.android"
const val dokka = "org.jetbrains.dokka"
const val kotlin = "kotlin"
const val kotlinAndroid = "kotlin-android"
const val kotlinKapt = "kotlin-kapt"
const val kotlinKsp = "com.google.devtools.ksp"
const val mavenPublish = "maven-publish"
const val fladle = "com.osacky.fladle"
const val navSafeArgs = "androidx.navigation.safeargs.kotlin"
Expand All @@ -42,10 +41,13 @@ object Plugins {
"androidx.navigation:navigation-safe-args-gradle-plugin:${Dependencies.Versions.Androidx.navigation}"
const val rulerGradlePlugin = "com.spotify.ruler:ruler-gradle-plugin:1.2.1"
const val flankGradlePlugin = "com.osacky.flank.gradle:fladle:0.17.4"
const val kspGradlePlugin =
"com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:${Versions.kspPlugin}"

object Versions {
const val androidGradlePlugin = "8.0.2"
const val benchmarkPlugin = "1.1.0"
const val dokka = "1.9.20"
const val kspPlugin = "1.9.22-1.0.18"
}
}
86 changes: 86 additions & 0 deletions catalog/src/main/assets/behavior_questionnaire_constraint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"resourceType": "Questionnaire",
"item": [
{
"linkId": "1",
"text": "Password",
"type": "string",
"item": [
{
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
"valueCodeableConcept": {
"coding": [
{
"system": "http://hl7.org/fhir/questionnaire-display-category",
"code": "instructions"
}
]
}
}
],
"linkId": "1.1",
"text": "Fill the password first",
"type": "display"
}
]
},
{
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-constraint",
"extension": [
{
"url": "key",
"valueId": "constraint-1"
},
{
"url": "requirements",
"valueString": "Confirm password field must have the same value as password field"
},
{
"url": "severity",
"valueCode": "error"
},
{
"url": "expression",
"valueString": "%context.answer.value = %resource.descendants().where(linkId='1').answer.value"
},
{
"url": "human",
"valueString": "Password does not match"
},
{
"url": "location",
"valueString": "1"
}
]
}
],
"linkId": "2",
"text": "Confirm password",
"type": "string",
"item": [
{
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
"valueCodeableConcept": {
"coding": [
{
"system": "http://hl7.org/fhir/questionnaire-display-category",
"code": "instructions"
}
]
}
}
],
"linkId": "2.1",
"text": "Show error message if confirm password does not match with password",
"type": "display"
}
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ class BehaviorListViewModel(application: Application) : AndroidViewModel(applica
R.string.behavior_name_dynamic_question_text,
"behavior_dynamic_question_text.json",
),
QUESTIONNAIRE_CONSTRAINT(
R.drawable.ic_rule,
R.string.behavior_name_questionnaire_constraint,
"behavior_questionnaire_constraint.json",
),
}

fun isBehavior(context: Context, title: String) =
Expand Down
12 changes: 12 additions & 0 deletions catalog/src/main/res/drawable/ic_rule.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
>
<path
android:fillColor="#1A73E8"
android:pathData="M576,800L534,758L645,647L534,536L576,494L687,605L798,494L840,536L729,647L840,758L798,800L687,689L576,800ZM659,426L517,284L559,242L659,341L838,162L880,205L659,426ZM80,670L80,610L440,610L440,670L80,670ZM80,350L80,290L440,290L440,350L80,350Z"
/>
</vector>
1 change: 1 addition & 0 deletions catalog/src/main/res/layout/behavior_list_fragment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/component_horizontal_margin"
android:layout_marginBottom="@dimen/bottom_navigation_view_height"
/>

</FrameLayout>
3 changes: 3 additions & 0 deletions catalog/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
<string
name="behavior_name_dynamic_question_text"
>Dynamic question text</string>
<string
name="behavior_name_questionnaire_constraint"
>Questionnaire constraint</string>
<string name="component_name_initial_value">Initial Value</string>
<string
name="behavior_name_calculated_expression_info"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,9 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
externalValueSetResolver,
)

private val questionnaireResponseItemValidator: QuestionnaireResponseItemValidator =
QuestionnaireResponseItemValidator(expressionEvaluator)

/**
* Adds empty [QuestionnaireResponseItemComponent]s to `responseItems` so that each
* [QuestionnaireItemComponent] in `questionnaireItems` has at least one corresponding
Expand Down Expand Up @@ -729,17 +732,11 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
forceValidation ||
isInReviewModeFlow.value
) {
QuestionnaireResponseItemValidator.validate(
questionnaireResponseItemValidator.validate(
questionnaireItem,
questionnaireResponseItem.answer,
questionnaireResponseItem,
this@QuestionnaireViewModel.getApplication(),
) {
expressionEvaluator.evaluateExpressionValue(
questionnaireItem,
questionnaireResponseItem,
it,
)
}
)
} else {
NotValidated
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ internal class EnabledAnswerOptionsEvaluator(
checkNotNull(xFhirQueryResolver) {
"XFhirQueryResolver cannot be null. Please provide the XFhirQueryResolver via DataCaptureConfig."
}
val variablesMap = expressionEvaluator.extractDependentVariables(answerExpression, item)
val variablesMap = expressionEvaluator.extractItemDependentVariables(answerExpression, item)
val xFhirExpressionString =
expressionEvaluator.createXFhirQueryFromExpression(answerExpression, variablesMap)
if (answerExpressionMap.containsKey(xFhirExpressionString)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import org.hl7.fhir.r4.model.DecimalType
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.IntegerType
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Reference
import org.hl7.fhir.r4.model.Resource
Expand Down Expand Up @@ -113,6 +114,27 @@ internal const val EXTENSION_MAX_SIZE = "http://hl7.org/fhir/StructureDefinition

internal const val EXTENSION_MIME_TYPE = "http://hl7.org/fhir/StructureDefinition/mimeType"

/**
* Extension for questionnaire and its items, representing a rule that must be satisfied before
* [QuestionnaireResponse] can be considered valid.
*
* See https://hl7.org/fhir/extensions/StructureDefinition-questionnaire-constraint.html.
*/
internal const val EXTENSION_QUESTIONNAIRE_CONSTRAINT_URL =
"http://hl7.org/fhir/StructureDefinition/questionnaire-constraint"

internal const val EXTENSION_QUESTIONNAIRE_CONSTRAINT_KEY = "key"

internal const val EXTENSION_QUESTIONNAIRE_CONSTRAINT_REQUIREMENTS = "requirements"

internal const val EXTENSION_QUESTIONNAIRE_CONSTRAINT_SEVERITY = "severity"

internal const val EXTENSION_QUESTIONNAIRE_CONSTRAINT_EXPRESSION = "expression"

internal const val EXTENSION_QUESTIONNAIRE_CONSTRAINT_HUMAN = "human"

internal const val EXTENSION_QUESTIONNAIRE_CONSTRAINT_LOCATION = "location"

/**
* Extension for questionnaire items of integer and decimal types including a single unit to be
* displayed.
Expand Down Expand Up @@ -775,6 +797,14 @@ internal fun Questionnaire.QuestionnaireItemComponent.extractAnswerOptions(
}.map { Questionnaire.QuestionnaireItemAnswerOptionComponent(it) }
}

/** See http://hl7.org/fhir/constraint-severity */
enum class ConstraintSeverityTypes(
val code: String,
) {
ERROR("error"),
WARNING("warning"),
}

// ********************************************************************************************** //
// //
// Utilities: zip with questionnaire response item list, nested items, create response items, //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,30 @@ internal class ExpressionEvaluator(
private val xFhirQueryResolver: XFhirQueryResolver? = null,
) {

private val reservedVariables =
listOf("sct", "loinc", "ucum", "resource", "rootResource", "context", "map-codes")
private val reservedItemVariables =
listOf(
"sct",
"loinc",
"ucum",
"resource",
"rootResource",
"context",
"map-codes",
"questionnaire",
"qItem",
)

private val reservedRootVariables =
listOf(
"sct",
"loinc",
"ucum",
"resource",
"rootResource",
"context",
"map-codes",
"questionnaire",
)

/**
* Finds all the matching occurrences of variables. For example, when we apply regex to the
Expand Down Expand Up @@ -132,7 +154,7 @@ internal class ExpressionEvaluator(
questionnaireResponseItem: QuestionnaireResponseItemComponent?,
expression: Expression,
): List<Base> {
val appContext = extractDependentVariables(expression, questionnaireItem)
val appContext = extractItemDependentVariables(expression, questionnaireItem)
return evaluateToBase(
questionnaireResponse,
questionnaireResponseItem,
Expand Down Expand Up @@ -222,7 +244,7 @@ internal class ExpressionEvaluator(
) {
"The expression should come from the same questionnaire item"
}
extractDependentVariables(
extractItemDependentVariables(
expression,
questionnaireItem,
variablesMap,
Expand All @@ -243,21 +265,23 @@ internal class ExpressionEvaluator(
* @param variablesMap the [Map<String, Base>] of variables, the default value is empty map is
* defined
*/
internal suspend fun extractDependentVariables(
internal suspend fun extractItemDependentVariables(
expression: Expression,
questionnaireItem: QuestionnaireItemComponent,
variablesMap: MutableMap<String, Base?> = mutableMapOf(),
): MutableMap<String, Base?> {
questionnaireLaunchContextMap?.let { variablesMap.putAll(it) }
findDependentVariables(expression).forEach { variableName ->
if (variablesMap[variableName] == null) {
findAndEvaluateVariable(
variableName,
questionnaireItem,
variablesMap,
)
findDependentVariables(expression)
.filterNot { variable -> reservedItemVariables.contains(variable) }
.forEach { variableName ->
if (variablesMap[variableName] == null) {
findAndEvaluateVariable(
variableName,
questionnaireItem,
variablesMap,
)
}
}
}
return variablesMap.apply {
put(questionnaireFhirPathSupplement, questionnaire)
put(questionnaireItemFhirPathSupplement, questionnaireItem)
Expand All @@ -282,17 +306,19 @@ internal class ExpressionEvaluator(
expression: Expression,
variablesMap: MutableMap<String, Base?> = mutableMapOf(),
): Base? {
findDependentVariables(expression).forEach { variableName ->
questionnaire.findVariableExpression(variableName)?.let { expression ->
if (variablesMap[expression.name] == null) {
variablesMap[expression.name] =
evaluateQuestionnaireVariableExpression(
expression,
variablesMap,
)
findDependentVariables(expression)
.filterNot { variable -> reservedRootVariables.contains(variable) }
.forEach { variableName ->
questionnaire.findVariableExpression(variableName)?.let { expression ->
if (variablesMap[expression.name] == null) {
variablesMap[expression.name] =
evaluateQuestionnaireVariableExpression(
expression,
variablesMap,
)
}
}
}
}

return evaluateVariable(
expression,
Expand Down Expand Up @@ -371,11 +397,7 @@ internal class ExpressionEvaluator(
}

private fun findDependentVariables(expression: Expression) =
variableRegex
.findAll(expression.expression)
.map { it.groupValues[1] }
.toList()
.filterNot { variable -> reservedVariables.contains(variable) }
variableRegex.findAll(expression.expression).map { it.groupValues[1] }.toList()

/**
* Finds the dependent variables at questionnaire item level first, then in ancestors and then at
Expand Down
Loading

0 comments on commit 1ef58fd

Please sign in to comment.