Skip to content

Commit

Permalink
Check extracted fhir resources are valid on submit
Browse files Browse the repository at this point in the history
  • Loading branch information
LZRS committed Jan 25, 2024
1 parent 96d01d0 commit b57ec78
Show file tree
Hide file tree
Showing 13 changed files with 374 additions and 59 deletions.
8 changes: 8 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ allprojects {
url = uri("/Users/ndegwamartin/.m2.dev/fhirsdk")
}
}

configurations {
configureEach {
resolutionStrategy {
force "ca.uhn.hapi.fhir:hapi-fhir-validation:6.0.1"
}
}
}
}

subprojects {
Expand Down
9 changes: 3 additions & 6 deletions android/engine/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,6 @@ android {
}
}
}

configurations.all {
resolutionStrategy {
force "ca.uhn.hapi.fhir:org.hl7.fhir.utilities:5.5.7"
}
}
}

dependencies {
Expand Down Expand Up @@ -147,6 +141,9 @@ dependencies {
implementation(group: "com.github.java-json-tools", name: "msg-simple", version: "1.2");
implementation 'org.codehaus.woodstox:woodstox-core-asl:4.4.1'
implementation "ca.uhn.hapi.fhir:hapi-fhir-android:5.4.0"
implementation ("ca.uhn.hapi.fhir:hapi-fhir-validation:6.0.1"){
exclude module: "commons-logging"
}
implementation 'org.opencds.cqf.cql:engine:1.5.4'
implementation 'org.opencds.cqf.cql:engine.fhir:1.5.4'
implementation 'org.opencds.cqf.cql:evaluator:1.4.2'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2021 Ona Systems, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.smartregister.fhircore.engine.di

import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport
import ca.uhn.fhir.context.support.IValidationSupport
import ca.uhn.fhir.validation.FhirValidator
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport
import org.hl7.fhir.common.hapi.validation.support.UnknownCodeSystemWarningValidationSupport
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator

@Module
@InstallIn(SingletonComponent::class)
class FhirValidatorModule {

@Provides
@Singleton
fun provideFhirValidator(): FhirValidator {
val fhirContext = FhirContext.forR4()

val validationSupportChain =
ValidationSupportChain(
DefaultProfileValidationSupport(fhirContext),
InMemoryTerminologyServerValidationSupport(fhirContext),
CommonCodeSystemsTerminologyService(fhirContext),
UnknownCodeSystemWarningValidationSupport(fhirContext).apply {
setNonExistentCodeSystemSeverity(IValidationSupport.IssueSeverity.WARNING)
},
)
val instanceValidator = FhirInstanceValidator(validationSupportChain)
instanceValidator.isAssumeValidRestReferences = true
// instanceValidator.validatorResourceFetcher
// instanceValidator.setCustomExtensionDomains()
instanceValidator.invalidateCaches()
return fhirContext.newValidator().apply { registerValidatorModule(instanceValidator) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,6 @@ constructor(
}
}
)

override fun getFhirEngine(): FhirEngine = engine
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ import org.hl7.fhir.r4.model.Resource

sealed class ExtractionProgress {
class Success(val extras: List<Resource>? = null) : ExtractionProgress()
object Failed : ExtractionProgress()
class Failed(val errorMessages: String? = null) : ExtractionProgress()
}
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,13 @@ open class QuestionnaireActivity : BaseMultiLanguageActivity(), View.OnClickList
if (result is ExtractionProgress.Success) {
onPostSave(true, questionnaireResponse, result.extras)
} else {
result as ExtractionProgress.Failed
AlertDialogue.showErrorAlert(
this,
result.errorMessages ?: "",
getString(R.string.questionnaire_alert_extraction_fail)
)

onPostSave(false, questionnaireResponse)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ package org.smartregister.fhircore.engine.ui.questionnaire

import android.content.Context
import android.content.Intent
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.FhirVersionEnum
import ca.uhn.fhir.rest.gclient.TokenClientParam
import ca.uhn.fhir.rest.param.ParamPrefixEnum
import ca.uhn.fhir.validation.FhirValidator
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.datacapture.mapping.ResourceMapper
import com.google.android.fhir.datacapture.mapping.StructureMapExtractionContext
Expand Down Expand Up @@ -82,8 +85,10 @@ import org.smartregister.fhircore.engine.util.USER_INFO_SHARED_PREFERENCE_KEY
import org.smartregister.fhircore.engine.util.extension.addTags
import org.smartregister.fhircore.engine.util.extension.asReference
import org.smartregister.fhircore.engine.util.extension.assertSubject
import org.smartregister.fhircore.engine.util.extension.checkResourceValid
import org.smartregister.fhircore.engine.util.extension.cqfLibraryIds
import org.smartregister.fhircore.engine.util.extension.deleteRelatedResources
import org.smartregister.fhircore.engine.util.extension.errorMessages
import org.smartregister.fhircore.engine.util.extension.extractId
import org.smartregister.fhircore.engine.util.extension.extractLogicalIdUuid
import org.smartregister.fhircore.engine.util.extension.filterByResourceTypeId
Expand All @@ -110,14 +115,20 @@ constructor(
val dispatcherProvider: DispatcherProvider,
val sharedPreferencesHelper: SharedPreferencesHelper,
val libraryEvaluatorProvider: Provider<LibraryEvaluator>,
val fhirValidatorProvider: Provider<FhirValidator>,
var tracer: PerformanceReporter
) : ViewModel() {
@Inject lateinit var fhirCarePlanGenerator: FhirCarePlanGenerator

val extractionProgress = MutableLiveData<ExtractionProgress>()
private val _extractionProgress = MutableLiveData<ExtractionProgress>()
val extractionProgress: LiveData<ExtractionProgress> = _extractionProgress

val questionnaireResponseLiveData = MutableLiveData<QuestionnaireResponse?>(null)

val extractionProgressMessage = MutableLiveData<String>()
@Suppress("PropertyName")
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
val _extractionProgressMessage = MutableLiveData<String>()
val extractionProgressMessage: LiveData<String> = _extractionProgressMessage

var editQuestionnaireResponse: QuestionnaireResponse? = null

Expand Down Expand Up @@ -334,7 +345,23 @@ constructor(
Timber.w(
"${questionnaire.name}(${questionnaire.logicalId}) is experimental and not save any data"
)
} else saveBundleResources(bundle)
} else {
val unsuccessfulValidationResults =
bundle.entry
.flatMap { fhirValidatorProvider.get().checkResourceValid(it.resource) }
.filter { it.errorMessages.isNotBlank() }

if (unsuccessfulValidationResults.isNotEmpty()) {
val mergedErrorMessages = buildString {
unsuccessfulValidationResults.forEach { appendLine(it.errorMessages) }
}
Timber.e(mergedErrorMessages)
_extractionProgress.postValue(ExtractionProgress.Failed(mergedErrorMessages))
return@launch
}

saveBundleResources(bundle)
}

if (questionnaireType.isEditMode() && editQuestionnaireResponse != null) {
questionnaireResponse.retainMetadata(editQuestionnaireResponse!!)
Expand All @@ -357,7 +384,7 @@ constructor(
}
tracer.stopTrace(QUESTIONNAIRE_TRACE)
viewModelScope.launch(Dispatchers.Main) {
extractionProgress.postValue(ExtractionProgress.Success(extras))
_extractionProgress.postValue(ExtractionProgress.Success(extras))
}
}
}
Expand Down Expand Up @@ -398,7 +425,7 @@ constructor(
.runCatching { fhirCarePlanGenerator.generateCarePlan(planId, subject, data) }
.onFailure {
Timber.e(it)
extractionProgressMessage.postValue("Error extracting care plan. ${it.message}")
_extractionProgressMessage.postValue("Error extracting care plan. ${it.message}")

Check warning on line 428 in android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt#L428

Added line #L428 was not covered by tests
}
}
}
Expand All @@ -420,7 +447,7 @@ constructor(
libraryEvaluatorProvider.get().runCqlLibrary(it, patient, data, defaultRepository)
}
.forEach { output ->
if (output.isNotEmpty()) extractionProgressMessage.postValue(output.joinToString("\n"))
if (output.isNotEmpty()) _extractionProgressMessage.postValue(output.joinToString("\n"))
}
}
}
Expand Down Expand Up @@ -466,7 +493,6 @@ constructor(
questionnaire.useContext.filter { it.hasValueCodeableConcept() }.forEach {
it.valueCodeableConcept.coding.forEach { questionnaireResponse.meta.addTag(it) }
}

defaultRepository.addOrUpdate(true, questionnaireResponse)
}

Expand Down Expand Up @@ -615,10 +641,6 @@ constructor(
return tasks.filter { it.status in arrayOf(TaskStatus.READY, TaskStatus.INPROGRESS) }
}

fun saveResource(resource: Resource) {
viewModelScope.launch { defaultRepository.save(resource = resource) }
}

fun extractRelevantObservation(
resource: Bundle,
questionnaireLogicalId: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2021 Ona Systems, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.smartregister.fhircore.engine.util.extension

import ca.uhn.fhir.validation.FhirValidator
import ca.uhn.fhir.validation.ResultSeverityEnum
import ca.uhn.fhir.validation.ValidationResult
import kotlin.coroutines.coroutineContext
import kotlinx.coroutines.withContext
import org.hl7.fhir.r4.model.Resource

suspend fun FhirValidator.checkResourceValid(
vararg resource: Resource,
): List<ValidationResult> {
return withContext(coroutineContext) {
resource.map { this@checkResourceValid.validateWithResult(it) }
}
}

val ValidationResult.errorMessages
get() = buildString {
for (validationMsg in
messages.filter { it.severity.ordinal >= ResultSeverityEnum.WARNING.ordinal }) {
appendLine("${validationMsg.message} - ${validationMsg.locationString}")
}
}
1 change: 1 addition & 0 deletions android/engine/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
<string name="questionnaire_alert_confirm_button_title">Yes</string>
<string name="questionnaire_alert_invalid_message">Given details have validation errors. Resolve errors and submit again</string>
<string name="questionnaire_alert_invalid_title">Validation Failed</string>
<string name="questionnaire_alert_extraction_fail">Extraction did not succeed</string>
<string name="questionnaire_alert_ack_button_title">Ok</string>
<string name="username">Username</string>
<string name="password">Password</string>
Expand Down
Loading

0 comments on commit b57ec78

Please sign in to comment.