From b8403ad9b14cff9d2518c2784c6888c26648914b Mon Sep 17 00:00:00 2001 From: Owais <62104757+owais-vd@users.noreply.github.com> Date: Tue, 19 Sep 2023 02:47:08 +0500 Subject: [PATCH] Implement an optional and configurable code path that uses $apply for PlanDefenition execution (#2746) * WIP - updated fhir careplan generation and added workflow implemntation * WIP - address the feedback * Update android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirCarePlanGenerator.kt * added samle test * spotless and fixes * removed unused code * updated workflow manager and resolve dynamic values for careplan * address the feedback * formatting and remove commented lines --------- Co-authored-by: Peter Lubell-Doughtie --- .../configuration/QuestionnaireConfig.kt | 1 + .../engine/task/FhirCarePlanGenerator.kt | 114 +++--- .../engine/task/WorkflowCarePlanGenerator.kt | 363 ++++++++++++++++++ .../engine/task/FhirCarePlanGeneratorTest.kt | 45 +++ .../sample_request_example-1.0.0.cql | 7 + ...sample_request_example-1.0.0.cql.fhir.json | 23 ++ .../sample_request_patient.json | 15 + .../sample_request_plan_definition.json | 148 +++++++ .../questionnaire/QuestionnaireViewModel.kt | 1 + 9 files changed, 668 insertions(+), 49 deletions(-) create mode 100644 android/engine/src/main/java/org/smartregister/fhircore/engine/task/WorkflowCarePlanGenerator.kt create mode 100644 android/engine/src/test/resources/plans/sample-request/sample_request_example-1.0.0.cql create mode 100644 android/engine/src/test/resources/plans/sample-request/sample_request_example-1.0.0.cql.fhir.json create mode 100644 android/engine/src/test/resources/plans/sample-request/sample_request_patient.json create mode 100644 android/engine/src/test/resources/plans/sample-request/sample_request_plan_definition.json diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/QuestionnaireConfig.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/QuestionnaireConfig.kt index b202cc6216..437546ed37 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/QuestionnaireConfig.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/QuestionnaireConfig.kt @@ -54,6 +54,7 @@ data class QuestionnaireConfig( val extractedResourceUniquePropertyExpressions: List? = null, val saveQuestionnaireResponse: Boolean = true, + val generateCarePlanWithWorkflowApi: Boolean = false, ) : java.io.Serializable, Parcelable { fun interpolate(computedValuesMap: Map) = diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirCarePlanGenerator.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirCarePlanGenerator.kt index 44d4f26517..d7836fd462 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirCarePlanGenerator.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirCarePlanGenerator.kt @@ -39,6 +39,7 @@ import org.hl7.fhir.r4.model.Expression import org.hl7.fhir.r4.model.IdType import org.hl7.fhir.r4.model.IntegerType import org.hl7.fhir.r4.model.Parameters +import org.hl7.fhir.r4.model.Patient import org.hl7.fhir.r4.model.Period import org.hl7.fhir.r4.model.PlanDefinition import org.hl7.fhir.r4.model.Resource @@ -74,6 +75,7 @@ constructor( val transformSupportServices: TransformSupportServices, val defaultRepository: DefaultRepository, val fhirResourceUtil: FhirResourceUtil, + val workflowCarePlanGenerator: WorkflowCarePlanGenerator, ) { private val structureMapUtilities by lazy { StructureMapUtilities(transformSupportServices.simpleWorkerContext, transformSupportServices) @@ -83,15 +85,19 @@ constructor( planDefinitionId: String, subject: Resource, data: Bundle = Bundle(), + generateCarePlanWithWorkflowApi: Boolean = false, ): CarePlan? { val planDefinition = defaultRepository.loadResource(planDefinitionId) - return planDefinition?.let { generateOrUpdateCarePlan(it, subject, data) } + return planDefinition?.let { + generateOrUpdateCarePlan(it, subject, data, generateCarePlanWithWorkflowApi) + } } suspend fun generateOrUpdateCarePlan( planDefinition: PlanDefinition, subject: Resource, data: Bundle = Bundle(), + generateCarePlanWithWorkflowApi: Boolean = false, ): CarePlan? { // Only one CarePlan per plan, update or init a new one if not exists val output = @@ -112,59 +118,69 @@ constructor( var carePlanModified = false - planDefinition.action.forEach { action -> - val input = Bundle().apply { entry.addAll(data.entry) } - - if (action.passesConditions(input, planDefinition, subject)) { - val definition = action.activityDefinition(planDefinition) - - if (action.hasTransform()) { - val taskPeriods = action.taskPeriods(definition, output) - - taskPeriods.forEachIndexed { index, period -> - val source = - Parameters().apply { - addResourceParameter(CarePlan.SP_SUBJECT, subject) - addResourceParameter(PlanDefinition.SP_DEFINITION, definition) - // TODO find some other way (activity definition based) to pass additional data - addResourceParameter(PlanDefinition.SP_DEPENDS_ON, data) - } - source.setParameter(Task.SP_PERIOD, period) - source.setParameter(ActivityDefinition.SP_VERSION, IntegerType(index)) - - val structureMap = fhirEngine.get(IdType(action.transform).idPart) - structureMapUtilities.transform( - transformSupportServices.simpleWorkerContext, - source, - structureMap, - output, - ) + if (generateCarePlanWithWorkflowApi) { + workflowCarePlanGenerator.applyPlanDefinitionOnPatient( + planDefinition = planDefinition, + patient = subject as Patient, + data = data, + output = output, + ) + carePlanModified = true + } else { + planDefinition.action.forEach { action -> + val input = Bundle().apply { entry.addAll(data.entry) } + + if (action.passesConditions(input, planDefinition, subject)) { + val definition = action.activityDefinition(planDefinition) + + if (action.hasTransform()) { + val taskPeriods = action.taskPeriods(definition, output) + + taskPeriods.forEachIndexed { index, period -> + val source = + Parameters().apply { + addResourceParameter(CarePlan.SP_SUBJECT, subject) + addResourceParameter(PlanDefinition.SP_DEFINITION, definition) + // TODO find some other way (activity definition based) to pass additional data + addResourceParameter(PlanDefinition.SP_DEPENDS_ON, data) + } + source.setParameter(Task.SP_PERIOD, period) + source.setParameter(ActivityDefinition.SP_VERSION, IntegerType(index)) + + val structureMap = fhirEngine.get(IdType(action.transform).idPart) + structureMapUtilities.transform( + transformSupportServices.simpleWorkerContext, + source, + structureMap, + output, + ) + } } - } - if (definition.hasDynamicValue()) { - definition.dynamicValue.forEach { dynamicValue -> - if (definition.kind == ActivityDefinition.ActivityDefinitionKind.CAREPLAN) { - dynamicValue.expression.expression - .let { fhirPathEngine.evaluate(null, input, planDefinition, subject, it) } - ?.takeIf { it.isNotEmpty() } - ?.let { evaluatedValue -> - // TODO handle cases where we explicitly need to set previous value as null, when - // passing null to Terser, it gives error NPE - Timber.d("${dynamicValue.path}, evaluatedValue: $evaluatedValue") - TerserUtil.setFieldByFhirPath( - FhirContext.forR4Cached(), - dynamicValue.path.removePrefix("${definition.kind.display}."), - output, - evaluatedValue.first(), - ) - } - } else { - throw UnsupportedOperationException("${definition.kind} not supported") + if (definition.hasDynamicValue()) { + definition.dynamicValue.forEach { dynamicValue -> + if (definition.kind == ActivityDefinition.ActivityDefinitionKind.CAREPLAN) { + dynamicValue.expression.expression + .let { fhirPathEngine.evaluate(null, input, planDefinition, subject, it) } + ?.takeIf { it.isNotEmpty() } + ?.let { evaluatedValue -> + // TODO handle cases where we explicitly need to set previous value as null, + // when passing null to Terser, it gives error NPE + Timber.d("${dynamicValue.path}, evaluatedValue: $evaluatedValue") + TerserUtil.setFieldByFhirPath( + FhirContext.forR4Cached(), + dynamicValue.path.removePrefix("${definition.kind.display}."), + output, + evaluatedValue.first(), + ) + } + } else { + throw UnsupportedOperationException("${definition.kind} not supported") + } } } + carePlanModified = true } - carePlanModified = true } } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/task/WorkflowCarePlanGenerator.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/task/WorkflowCarePlanGenerator.kt new file mode 100644 index 0000000000..631eed0827 --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/task/WorkflowCarePlanGenerator.kt @@ -0,0 +1,363 @@ +/* + * Copyright 2021-2023 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.task + +import android.content.Context +import ca.uhn.fhir.context.FhirContext +import ca.uhn.fhir.context.FhirVersionEnum +import ca.uhn.fhir.util.TerserUtil +import com.google.android.fhir.FhirEngine +import com.google.android.fhir.knowledge.KnowledgeManager +import com.google.android.fhir.search.Search +import com.google.android.fhir.workflow.FhirOperator +import dagger.hilt.android.qualifiers.ApplicationContext +import java.io.File +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.jvm.isAccessible +import org.hl7.fhir.instance.model.api.IBaseParameters +import org.hl7.fhir.instance.model.api.IBaseResource +import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.CarePlan +import org.hl7.fhir.r4.model.IdType +import org.hl7.fhir.r4.model.Library +import org.hl7.fhir.r4.model.MetadataResource +import org.hl7.fhir.r4.model.Parameters +import org.hl7.fhir.r4.model.Patient +import org.hl7.fhir.r4.model.PlanDefinition +import org.hl7.fhir.r4.model.Reference +import org.hl7.fhir.r4.model.Resource +import org.hl7.fhir.r4.model.ResourceType +import org.hl7.fhir.r4.model.Task +import org.hl7.fhir.r4.utils.FHIRPathEngine +import org.opencds.cqf.cql.evaluator.activitydefinition.r4.ActivityDefinitionProcessor +import org.opencds.cqf.cql.evaluator.expression.ExpressionEvaluator +import org.opencds.cqf.cql.evaluator.fhir.dal.FhirDal +import org.opencds.cqf.cql.evaluator.library.LibraryProcessor +import org.opencds.cqf.cql.evaluator.plandefinition.OperationParametersParser +import org.opencds.cqf.cql.evaluator.plandefinition.r4.PlanDefinitionProcessor +import org.smartregister.fhircore.engine.data.local.DefaultRepository +import timber.log.Timber + +@Singleton +class WorkflowCarePlanGenerator +@Inject +constructor( + val knowledgeManager: KnowledgeManager, + val fhirOperator: FhirOperator, + val defaultRepository: DefaultRepository, + val fhirPathEngine: FHIRPathEngine, + @ApplicationContext val context: Context, +) { + + private var cqlLibraryIdList = ArrayList() + private val fhirContext = FhirContext.forCached(FhirVersionEnum.R4) + private val jsonParser = fhirContext.newJsonParser() + + private fun writeToFile(resource: Resource): File { + val fileName = + if (resource is MetadataResource && resource.name != null) { + resource.name + } else { + resource.idElement.idPart + } + return File(context.filesDir, fileName).apply { + writeText(jsonParser.encodeResourceToString(resource)) + } + } + + /** + * Extracts resources present in PlanDefinition.contained field + * + * We cannot use $data-requirements on the [PlanDefinition] yet. So, we assume that all knowledge + * resources required to $apply a [PlanDefinition] are present within `PlanDefinition.contained` + * + * @param planDefinition PlanDefinition resource for which dependent resources are extracted + */ + suspend fun getPlanDefinitionDependentResources( + planDefinition: PlanDefinition, + ): Collection { + var bundleCollection: Collection = mutableListOf() + + for (resource in planDefinition.contained) { + resource.meta.lastUpdated = planDefinition.meta.lastUpdated + if (resource is Library) { + cqlLibraryIdList.add(IdType(resource.id).idPart) + } + knowledgeManager.install(writeToFile(resource)) + + bundleCollection += resource + } + return bundleCollection + } + + /** + * Knowledge resources are loaded from [FhirEngine] and installed so that they may be used when + * running $apply on a [PlanDefinition] + */ + private suspend fun loadPlanDefinitionResourcesFromDb() { + // Load Library resources + val availableCqlLibraries = defaultRepository.search(Search(ResourceType.Library)) + val availablePlanDefinitions = + defaultRepository.search(Search(ResourceType.PlanDefinition)) + for (cqlLibrary in availableCqlLibraries) { + fhirOperator.loadLib(cqlLibrary) + knowledgeManager.install(writeToFile(cqlLibrary)) + cqlLibraryIdList.add(IdType(cqlLibrary.id).idPart) + } + for (planDefinition in availablePlanDefinitions) { + getPlanDefinitionDependentResources(planDefinition) + } + } + + /** + * Executes $apply on a [PlanDefinition] for a [Patient] and creates the request resources as per + * the proposed [CarePlan] + * + * @param planDefinitionId PlanDefinition resource ID for which $apply is run + * @param patient Patient resource for which the [PlanDefinition] $apply is run + * @param requestResourceConfigs List of configurations that need to be applied to the request + * resources as a result of the proposed [CarePlan] + */ + suspend fun applyPlanDefinitionOnPatient( + planDefinition: PlanDefinition, + patient: Patient, + data: Bundle = Bundle(), + output: CarePlan, + ) { + val patientId = IdType(patient.id).idPart + val planDefinitionId = IdType(planDefinition.id).idPart + + if (cqlLibraryIdList.isEmpty()) { + loadPlanDefinitionResourcesFromDb() + } + + val r4PlanDefinitionProcessor = createPlanDefinitionProcessor() + val carePlanProposal = + r4PlanDefinitionProcessor.apply( + IdType("PlanDefinition", planDefinitionId), + patientId, + null, + null, + null, + null, + null, + null, + null, + null, + null, + Parameters(), + null, + null, + null, + null, + null, + null, + ) as CarePlan + + // Accept the proposed (transient) CarePlan by default and add tasks to the CarePlan of record + acceptCarePlan(carePlanProposal, output) + + resolveDynamicValues( + planDefinition = planDefinition, + input = data, + subject = patient, + output, + ) + } + + private fun createPlanDefinitionProcessor(): R4PlanDefinitionProcessor { + val fhirDal = getPrivateProperty("fhirEngineDal", fhirOperator) as FhirDal + val libraryProcessor = getPrivateProperty("libraryProcessor", fhirOperator) as LibraryProcessor + val expressionEvaluator = + getPrivateProperty("expressionEvaluator", fhirOperator) as ExpressionEvaluator + val activityDefinitionProcessor = + getPrivateProperty("activityDefinitionProcessor", fhirOperator) as ActivityDefinitionProcessor + val operationParametersParser = + getPrivateProperty("operationParametersParser", fhirOperator) as OperationParametersParser + + return R4PlanDefinitionProcessor( + fhirContext = fhirContext, + fhirDal = fhirDal, + libraryProcessor = libraryProcessor, + expressionEvaluator = expressionEvaluator, + activityDefinitionProcessor = activityDefinitionProcessor, + operationParametersParser = operationParametersParser, + ) + } + + private fun resolveDynamicValues( + planDefinition: PlanDefinition, + input: Bundle, + subject: Patient, + output: CarePlan, + ) { + for (action in planDefinition.action) { + if (action.hasDynamicValue()) { + action.dynamicValue.forEach { dynamicValue -> + dynamicValue.expression.expression + .let { fhirPathEngine.evaluate(null, input, planDefinition, subject, it) } + ?.takeIf { it.isNotEmpty() } + ?.let { evaluatedValue -> + Timber.d("${dynamicValue.path}, evaluatedValue: $evaluatedValue") + TerserUtil.setFieldByFhirPath( + FhirContext.forR4Cached(), + dynamicValue.path, + output, + evaluatedValue.first(), + ) + } + } + } + } + } + + /** Link the request resources created for the [Patient] back to the [CarePlan] of record */ + private fun addRequestResourcesToCarePlanOfRecord( + carePlan: CarePlan, + requestResourceList: List, + ) { + for (resource in requestResourceList) { + when (resource.fhirType()) { + "Task" -> + carePlan.addActivity().setReference(Reference(resource)).detail.status = + mapRequestResourceStatusToCarePlanStatus(resource as Task) + "ServiceRequest" -> TODO("Not supported yet") + "MedicationRequest" -> TODO("Not supported yet") + "SupplyRequest" -> TODO("Not supported yet") + "Procedure" -> TODO("Not supported yet") + "DiagnosticReport" -> TODO("Not supported yet") + "Communication" -> TODO("Not supported yet") + "CommunicationRequest" -> TODO("Not supported yet") + else -> TODO("Not a valid request resource") + } + } + } + + /** + * Invokes the respective [RequestResourceManager] to create new request resources as per the + * proposed [CarePlan] + * + * @param resourceList List of request resources to be created + * @param requestResourceConfigs Application-specific configurations to be applied on the created + * request resources + */ + private suspend fun createProposedRequestResources(resourceList: List): List { + val createdRequestResources = ArrayList() + for (resource in resourceList) { + when (resource.fhirType()) { + "Task" -> { + defaultRepository.create(true, resource) + createdRequestResources.add(resource) + } + "ServiceRequest" -> TODO("Not supported yet") + "MedicationRequest" -> TODO("Not supported yet") + "SupplyRequest" -> TODO("Not supported yet") + "Procedure" -> TODO("Not supported yet") + "DiagnosticReport" -> TODO("Not supported yet") + "Communication" -> TODO("Not supported yet") + "CommunicationRequest" -> TODO("Not supported yet") + "RequestGroup" -> {} + else -> TODO("Not a valid request resource") + } + } + return createdRequestResources + } + + /** + * Accept the proposed [CarePlan] and create the proposed request resources as per the + * configurations + * + * @param proposedCarePlan Proposed [CarePlan] generated when $apply is run on a [PlanDefinition] + * @param carePlanOfRecord CarePlan of record for a [Patient] which needs to be updated with the + * new request resources created as per the proposed CarePlan + * @param requestResourceConfigs Application-specific configurations to be applied on the created + * request resources + */ + private suspend fun acceptCarePlan( + proposedCarePlan: CarePlan, + carePlanOfRecord: CarePlan, + ) { + val resourceList = createProposedRequestResources(proposedCarePlan.contained) + addRequestResourcesToCarePlanOfRecord(carePlanOfRecord, resourceList) + } + + /** Map [Task] status to [CarePlan] status */ + fun mapRequestResourceStatusToCarePlanStatus( + resource: Task, + ): CarePlan.CarePlanActivityStatus { + // Refer: http://hl7.org/fhir/R4/valueset-care-plan-activity-status.html for some mapping + // guidelines + return when (resource.status) { + Task.TaskStatus.ACCEPTED -> CarePlan.CarePlanActivityStatus.SCHEDULED + Task.TaskStatus.DRAFT -> CarePlan.CarePlanActivityStatus.NOTSTARTED + Task.TaskStatus.REQUESTED -> CarePlan.CarePlanActivityStatus.NOTSTARTED + Task.TaskStatus.RECEIVED -> CarePlan.CarePlanActivityStatus.NOTSTARTED + Task.TaskStatus.REJECTED -> CarePlan.CarePlanActivityStatus.STOPPED + Task.TaskStatus.READY -> CarePlan.CarePlanActivityStatus.NOTSTARTED + Task.TaskStatus.CANCELLED -> CarePlan.CarePlanActivityStatus.CANCELLED + Task.TaskStatus.INPROGRESS -> CarePlan.CarePlanActivityStatus.INPROGRESS + Task.TaskStatus.ONHOLD -> CarePlan.CarePlanActivityStatus.ONHOLD + Task.TaskStatus.FAILED -> CarePlan.CarePlanActivityStatus.STOPPED + Task.TaskStatus.COMPLETED -> CarePlan.CarePlanActivityStatus.COMPLETED + Task.TaskStatus.ENTEREDINERROR -> CarePlan.CarePlanActivityStatus.ENTEREDINERROR + Task.TaskStatus.NULL -> CarePlan.CarePlanActivityStatus.NULL + else -> CarePlan.CarePlanActivityStatus.NULL + } + } + + private inline fun getPrivateProperty(property: String, obj: T): Any? { + return T::class + .declaredMemberProperties + .find { it.name == property }!! + .apply { isAccessible = true } + .get(obj) + } + + inner class R4PlanDefinitionProcessor + constructor( + fhirContext: FhirContext, + fhirDal: FhirDal, + libraryProcessor: LibraryProcessor, + expressionEvaluator: ExpressionEvaluator, + activityDefinitionProcessor: ActivityDefinitionProcessor, + operationParametersParser: OperationParametersParser, + ) : + PlanDefinitionProcessor( + fhirContext, + fhirDal, + libraryProcessor, + expressionEvaluator, + activityDefinitionProcessor, + operationParametersParser, + ) { + override fun resolveDynamicValue( + language: String?, + expression: String?, + path: String?, + altLanguage: String?, + altExpression: String?, + altPath: String?, + libraryUrl: String?, + resource: IBaseResource?, + params: IBaseParameters?, + ) { + // no need to add dynamic value in RequestGroup resource + } + } +} diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/task/FhirCarePlanGeneratorTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/task/FhirCarePlanGeneratorTest.kt index 5a9a5773b6..795b7f6b40 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/task/FhirCarePlanGeneratorTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/task/FhirCarePlanGeneratorTest.kt @@ -46,6 +46,7 @@ import java.util.Date import java.util.UUID import javax.inject.Inject import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest @@ -64,6 +65,7 @@ import org.hl7.fhir.r4.model.Encounter import org.hl7.fhir.r4.model.Expression import org.hl7.fhir.r4.model.Group import org.hl7.fhir.r4.model.Immunization +import org.hl7.fhir.r4.model.Library import org.hl7.fhir.r4.model.Patient import org.hl7.fhir.r4.model.Period import org.hl7.fhir.r4.model.PlanDefinition @@ -124,6 +126,8 @@ class FhirCarePlanGeneratorTest : RobolectricTest() { @Inject lateinit var transformSupportServices: TransformSupportServices + @Inject lateinit var workflowCarePlanGenerator: WorkflowCarePlanGenerator + @Inject lateinit var fhirPathEngine: FHIRPathEngine @Inject lateinit var fhirEngine: FhirEngine @@ -162,6 +166,7 @@ class FhirCarePlanGeneratorTest : RobolectricTest() { fhirPathEngine = fhirPathEngine, defaultRepository = defaultRepository, fhirResourceUtil = fhirResourceUtil, + workflowCarePlanGenerator = workflowCarePlanGenerator, ) immunizationResource = @@ -2059,6 +2064,46 @@ class FhirCarePlanGeneratorTest : RobolectricTest() { assertFalse(conditionsMet) } + @Test + @ExperimentalCoroutinesApi + fun `generateOrUpdateCarePlan should generate a sample careplan using apply`(): Unit = + runBlocking(Dispatchers.IO) { + val planDefinition = + "plans/sample-request/sample_request_plan_definition.json" + .readFile() + .decodeResourceFromString() + val patient = + "plans/sample-request/sample_request_patient.json" + .readFile() + .decodeResourceFromString() + val library = + "plans/sample-request/sample_request_example-1.0.0.cql.fhir.json" + .readFile() + .decodeResourceFromString() + + fhirEngine.create(planDefinition) + fhirEngine.create(library) + fhirEngine.create(patient) + + val resourceSlot = slot() + coEvery { defaultRepository.create(any(), capture(resourceSlot)) } answers + { + runBlocking(Dispatchers.IO) { fhirEngine.create(resourceSlot.captured) } + listOf() + } + val carePlan = + fhirCarePlanGenerator.generateOrUpdateCarePlan( + planDefinition = planDefinition, + subject = patient, + generateCarePlanWithWorkflowApi = true, + )!! + + assertNotNull(carePlan) + assertNotNull(UUID.fromString(carePlan.id)) + assertEquals(planDefinition.title, carePlan.title) + assertEquals(planDefinition.description, carePlan.description) + } + data class PlanDefinitionResources( val planDefinition: PlanDefinition, val patient: Patient, diff --git a/android/engine/src/test/resources/plans/sample-request/sample_request_example-1.0.0.cql b/android/engine/src/test/resources/plans/sample-request/sample_request_example-1.0.0.cql new file mode 100644 index 0000000000..3259dfa709 --- /dev/null +++ b/android/engine/src/test/resources/plans/sample-request/sample_request_example-1.0.0.cql @@ -0,0 +1,7 @@ +library sample_request_example version '1.0.0' +using FHIR version '4.0.1' +include FHIRHelpers version '4.0.1' + +context Patient + +define "Check Sample Request": true diff --git a/android/engine/src/test/resources/plans/sample-request/sample_request_example-1.0.0.cql.fhir.json b/android/engine/src/test/resources/plans/sample-request/sample_request_example-1.0.0.cql.fhir.json new file mode 100644 index 0000000000..91abea79d8 --- /dev/null +++ b/android/engine/src/test/resources/plans/sample-request/sample_request_example-1.0.0.cql.fhir.json @@ -0,0 +1,23 @@ +{ + "resourceType": "Library", + "id": "sample_request_example-1.0.0", + "url": "http://localhost/Library/sample_request_example|1.0.0", + "version": "1.0.0", + "name": "sample_request_example", + "status": "active", + "experimental": true, + "content": [ + { + "contentType": "text/cql", + "data": "bGlicmFyeSBzYW1wbGVfcmVxdWVzdF9leGFtcGxlIHZlcnNpb24gJzEuMC4wJwp1c2luZyBGSElSIHZlcnNpb24gJzQuMC4xJwppbmNsdWRlIEZISVJIZWxwZXJzIHZlcnNpb24gJzQuMC4xJwoKY29udGV4dCBQYXRpZW50CgpkZWZpbmUgIkNoZWNrIFNhbXBsZSBSZXF1ZXN0IjogdHJ1ZQo=" + }, + { + "contentType": "application/elm+json", + "data": "ewogICJsaWJyYXJ5IiA6IHsKICAgICJ0eXBlIiA6ICJMaWJyYXJ5IiwKICAgICJpZGVudGlmaWVyIiA6IHsKICAgICAgInR5cGUiIDogIlZlcnNpb25lZElkZW50aWZpZXIiLAogICAgICAiaWQiIDogInNhbXBsZV9yZXF1ZXN0X2V4YW1wbGUiLAogICAgICAidmVyc2lvbiIgOiAiMS4wLjAiCiAgICB9LAogICAgInNjaGVtYUlkZW50aWZpZXIiIDogewogICAgICAidHlwZSIgOiAiVmVyc2lvbmVkSWRlbnRpZmllciIsCiAgICAgICJpZCIgOiAidXJuOmhsNy1vcmc6ZWxtIiwKICAgICAgInZlcnNpb24iIDogInIxIgogICAgfSwKICAgICJ1c2luZ3MiIDogewogICAgICAidHlwZSIgOiAiTGlicmFyeSRVc2luZ3MiLAogICAgICAiZGVmIiA6IFsgewogICAgICAgICJ0eXBlIiA6ICJVc2luZ0RlZiIsCiAgICAgICAgImxvY2FsSWRlbnRpZmllciIgOiAiU3lzdGVtIiwKICAgICAgICAidXJpIiA6ICJ1cm46aGw3LW9yZzplbG0tdHlwZXM6cjEiCiAgICAgIH0sIHsKICAgICAgICAidHlwZSIgOiAiVXNpbmdEZWYiLAogICAgICAgICJhbm5vdGF0aW9uIiA6IFsgewogICAgICAgICAgInR5cGUiIDogIkFubm90YXRpb24iLAogICAgICAgICAgInMiIDogewogICAgICAgICAgICAicyIgOiBbIHsKICAgICAgICAgICAgICAibmFtZSIgOiAie3VybjpobDctb3JnOmNxbC1hbm5vdGF0aW9uczpyMX1zIiwKICAgICAgICAgICAgICAiZGVjbGFyZWRUeXBlIiA6ICJvcmcuaGw3LmNxbF9hbm5vdGF0aW9ucy5yMS5OYXJyYXRpdmUiLAogICAgICAgICAgICAgICJzY29wZSIgOiAiamF2YXgueG1sLmJpbmQuSkFYQkVsZW1lbnQkR2xvYmFsU2NvcGUiLAogICAgICAgICAgICAgICJ2YWx1ZSIgOiB7CiAgICAgICAgICAgICAgICAicyIgOiBbICIiLCAidXNpbmcgIiBdCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmlsIiA6IGZhbHNlLAogICAgICAgICAgICAgICJnbG9iYWxTY29wZSIgOiB0cnVlLAogICAgICAgICAgICAgICJ0eXBlU3Vic3RpdHV0ZWQiIDogZmFsc2UKICAgICAgICAgICAgfSwgewogICAgICAgICAgICAgICJuYW1lIiA6ICJ7dXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxfXMiLAogICAgICAgICAgICAgICJkZWNsYXJlZFR5cGUiIDogIm9yZy5obDcuY3FsX2Fubm90YXRpb25zLnIxLk5hcnJhdGl2ZSIsCiAgICAgICAgICAgICAgInNjb3BlIiA6ICJqYXZheC54bWwuYmluZC5KQVhCRWxlbWVudCRHbG9iYWxTY29wZSIsCiAgICAgICAgICAgICAgInZhbHVlIiA6IHsKICAgICAgICAgICAgICAgICJzIiA6IFsgewogICAgICAgICAgICAgICAgICAibmFtZSIgOiAie3VybjpobDctb3JnOmNxbC1hbm5vdGF0aW9uczpyMX1zIiwKICAgICAgICAgICAgICAgICAgImRlY2xhcmVkVHlwZSIgOiAib3JnLmhsNy5jcWxfYW5ub3RhdGlvbnMucjEuTmFycmF0aXZlIiwKICAgICAgICAgICAgICAgICAgInNjb3BlIiA6ICJqYXZheC54bWwuYmluZC5KQVhCRWxlbWVudCRHbG9iYWxTY29wZSIsCiAgICAgICAgICAgICAgICAgICJ2YWx1ZSIgOiB7CiAgICAgICAgICAgICAgICAgICAgInMiIDogWyAiRkhJUiIgXQogICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAibmlsIiA6IGZhbHNlLAogICAgICAgICAgICAgICAgICAiZ2xvYmFsU2NvcGUiIDogdHJ1ZSwKICAgICAgICAgICAgICAgICAgInR5cGVTdWJzdGl0dXRlZCIgOiBmYWxzZQogICAgICAgICAgICAgICAgfSBdCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmlsIiA6IGZhbHNlLAogICAgICAgICAgICAgICJnbG9iYWxTY29wZSIgOiB0cnVlLAogICAgICAgICAgICAgICJ0eXBlU3Vic3RpdHV0ZWQiIDogZmFsc2UKICAgICAgICAgICAgfSwgewogICAgICAgICAgICAgICJuYW1lIiA6ICJ7dXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxfXMiLAogICAgICAgICAgICAgICJkZWNsYXJlZFR5cGUiIDogIm9yZy5obDcuY3FsX2Fubm90YXRpb25zLnIxLk5hcnJhdGl2ZSIsCiAgICAgICAgICAgICAgInNjb3BlIiA6ICJqYXZheC54bWwuYmluZC5KQVhCRWxlbWVudCRHbG9iYWxTY29wZSIsCiAgICAgICAgICAgICAgInZhbHVlIiA6IHsKICAgICAgICAgICAgICAgICJzIiA6IFsgIiB2ZXJzaW9uICIsICInNC4wLjEnIiBdCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmlsIiA6IGZhbHNlLAogICAgICAgICAgICAgICJnbG9iYWxTY29wZSIgOiB0cnVlLAogICAgICAgICAgICAgICJ0eXBlU3Vic3RpdHV0ZWQiIDogZmFsc2UKICAgICAgICAgICAgfSBdLAogICAgICAgICAgICAiciIgOiAiMSIKICAgICAgICAgIH0KICAgICAgICB9IF0sCiAgICAgICAgImxvY2FsSWQiIDogIjEiLAogICAgICAgICJsb2NhdG9yIiA6ICIyOjEtMjoyNiIsCiAgICAgICAgImxvY2FsSWRlbnRpZmllciIgOiAiRkhJUiIsCiAgICAgICAgInVyaSIgOiAiaHR0cDovL2hsNy5vcmcvZmhpciIsCiAgICAgICAgInZlcnNpb24iIDogIjQuMC4xIgogICAgICB9IF0KICAgIH0sCiAgICAiaW5jbHVkZXMiIDogewogICAgICAidHlwZSIgOiAiTGlicmFyeSRJbmNsdWRlcyIsCiAgICAgICJkZWYiIDogWyB7CiAgICAgICAgInR5cGUiIDogIkluY2x1ZGVEZWYiLAogICAgICAgICJhbm5vdGF0aW9uIiA6IFsgewogICAgICAgICAgInR5cGUiIDogIkFubm90YXRpb24iLAogICAgICAgICAgInMiIDogewogICAgICAgICAgICAicyIgOiBbIHsKICAgICAgICAgICAgICAibmFtZSIgOiAie3VybjpobDctb3JnOmNxbC1hbm5vdGF0aW9uczpyMX1zIiwKICAgICAgICAgICAgICAiZGVjbGFyZWRUeXBlIiA6ICJvcmcuaGw3LmNxbF9hbm5vdGF0aW9ucy5yMS5OYXJyYXRpdmUiLAogICAgICAgICAgICAgICJzY29wZSIgOiAiamF2YXgueG1sLmJpbmQuSkFYQkVsZW1lbnQkR2xvYmFsU2NvcGUiLAogICAgICAgICAgICAgICJ2YWx1ZSIgOiB7CiAgICAgICAgICAgICAgICAicyIgOiBbICIiLCAiaW5jbHVkZSAiIF0KICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJuaWwiIDogZmFsc2UsCiAgICAgICAgICAgICAgImdsb2JhbFNjb3BlIiA6IHRydWUsCiAgICAgICAgICAgICAgInR5cGVTdWJzdGl0dXRlZCIgOiBmYWxzZQogICAgICAgICAgICB9LCB7CiAgICAgICAgICAgICAgIm5hbWUiIDogInt1cm46aGw3LW9yZzpjcWwtYW5ub3RhdGlvbnM6cjF9cyIsCiAgICAgICAgICAgICAgImRlY2xhcmVkVHlwZSIgOiAib3JnLmhsNy5jcWxfYW5ub3RhdGlvbnMucjEuTmFycmF0aXZlIiwKICAgICAgICAgICAgICAic2NvcGUiIDogImphdmF4LnhtbC5iaW5kLkpBWEJFbGVtZW50JEdsb2JhbFNjb3BlIiwKICAgICAgICAgICAgICAidmFsdWUiIDogewogICAgICAgICAgICAgICAgInMiIDogWyB7CiAgICAgICAgICAgICAgICAgICJuYW1lIiA6ICJ7dXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxfXMiLAogICAgICAgICAgICAgICAgICAiZGVjbGFyZWRUeXBlIiA6ICJvcmcuaGw3LmNxbF9hbm5vdGF0aW9ucy5yMS5OYXJyYXRpdmUiLAogICAgICAgICAgICAgICAgICAic2NvcGUiIDogImphdmF4LnhtbC5iaW5kLkpBWEJFbGVtZW50JEdsb2JhbFNjb3BlIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIiA6IHsKICAgICAgICAgICAgICAgICAgICAicyIgOiBbICJGSElSSGVscGVycyIgXQogICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAibmlsIiA6IGZhbHNlLAogICAgICAgICAgICAgICAgICAiZ2xvYmFsU2NvcGUiIDogdHJ1ZSwKICAgICAgICAgICAgICAgICAgInR5cGVTdWJzdGl0dXRlZCIgOiBmYWxzZQogICAgICAgICAgICAgICAgfSBdCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmlsIiA6IGZhbHNlLAogICAgICAgICAgICAgICJnbG9iYWxTY29wZSIgOiB0cnVlLAogICAgICAgICAgICAgICJ0eXBlU3Vic3RpdHV0ZWQiIDogZmFsc2UKICAgICAgICAgICAgfSwgewogICAgICAgICAgICAgICJuYW1lIiA6ICJ7dXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxfXMiLAogICAgICAgICAgICAgICJkZWNsYXJlZFR5cGUiIDogIm9yZy5obDcuY3FsX2Fubm90YXRpb25zLnIxLk5hcnJhdGl2ZSIsCiAgICAgICAgICAgICAgInNjb3BlIiA6ICJqYXZheC54bWwuYmluZC5KQVhCRWxlbWVudCRHbG9iYWxTY29wZSIsCiAgICAgICAgICAgICAgInZhbHVlIiA6IHsKICAgICAgICAgICAgICAgICJzIiA6IFsgIiB2ZXJzaW9uICIsICInNC4wLjEnIiBdCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmlsIiA6IGZhbHNlLAogICAgICAgICAgICAgICJnbG9iYWxTY29wZSIgOiB0cnVlLAogICAgICAgICAgICAgICJ0eXBlU3Vic3RpdHV0ZWQiIDogZmFsc2UKICAgICAgICAgICAgfSBdLAogICAgICAgICAgICAiciIgOiAiMiIKICAgICAgICAgIH0KICAgICAgICB9IF0sCiAgICAgICAgImxvY2FsSWQiIDogIjIiLAogICAgICAgICJsb2NhdG9yIiA6ICIzOjEtMzozNSIsCiAgICAgICAgImxvY2FsSWRlbnRpZmllciIgOiAiRkhJUkhlbHBlcnMiLAogICAgICAgICJwYXRoIiA6ICJGSElSSGVscGVycyIsCiAgICAgICAgInZlcnNpb24iIDogIjQuMC4xIgogICAgICB9IF0KICAgIH0sCiAgICAiY29udGV4dHMiIDogewogICAgICAidHlwZSIgOiAiTGlicmFyeSRDb250ZXh0cyIsCiAgICAgICJkZWYiIDogWyB7CiAgICAgICAgInR5cGUiIDogIkNvbnRleHREZWYiLAogICAgICAgICJsb2NhdG9yIiA6ICI1OjEtNToxNSIsCiAgICAgICAgIm5hbWUiIDogIlBhdGllbnQiCiAgICAgIH0gXQogICAgfSwKICAgICJzdGF0ZW1lbnRzIiA6IHsKICAgICAgInR5cGUiIDogIkxpYnJhcnkkU3RhdGVtZW50cyIsCiAgICAgICJkZWYiIDogWyB7CiAgICAgICAgInR5cGUiIDogIkV4cHJlc3Npb25EZWYiLAogICAgICAgICJleHByZXNzaW9uIiA6IHsKICAgICAgICAgICJ0eXBlIiA6ICJTaW5nbGV0b25Gcm9tIiwKICAgICAgICAgICJvcGVyYW5kIiA6IHsKICAgICAgICAgICAgInR5cGUiIDogIlJldHJpZXZlIiwKICAgICAgICAgICAgImxvY2F0b3IiIDogIjU6MS01OjE1IiwKICAgICAgICAgICAgImRhdGFUeXBlIiA6ICJ7aHR0cDovL2hsNy5vcmcvZmhpcn1QYXRpZW50IiwKICAgICAgICAgICAgInRlbXBsYXRlSWQiIDogImh0dHA6Ly9obDcub3JnL2ZoaXIvU3RydWN0dXJlRGVmaW5pdGlvbi9QYXRpZW50IgogICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgImxvY2F0b3IiIDogIjU6MS01OjE1IiwKICAgICAgICAibmFtZSIgOiAiUGF0aWVudCIsCiAgICAgICAgImNvbnRleHQiIDogIlBhdGllbnQiCiAgICAgIH0sIHsKICAgICAgICAidHlwZSIgOiAiRXhwcmVzc2lvbkRlZiIsCiAgICAgICAgImV4cHJlc3Npb24iIDogewogICAgICAgICAgInR5cGUiIDogIkxpdGVyYWwiLAogICAgICAgICAgImxvY2FsSWQiIDogIjMiLAogICAgICAgICAgImxvY2F0b3IiIDogIjc6MzItNzozNSIsCiAgICAgICAgICAidmFsdWVUeXBlIiA6ICJ7dXJuOmhsNy1vcmc6ZWxtLXR5cGVzOnIxfUJvb2xlYW4iLAogICAgICAgICAgInZhbHVlIiA6ICJ0cnVlIgogICAgICAgIH0sCiAgICAgICAgImFubm90YXRpb24iIDogWyB7CiAgICAgICAgICAidHlwZSIgOiAiQW5ub3RhdGlvbiIsCiAgICAgICAgICAicyIgOiB7CiAgICAgICAgICAgICJzIiA6IFsgewogICAgICAgICAgICAgICJuYW1lIiA6ICJ7dXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxfXMiLAogICAgICAgICAgICAgICJkZWNsYXJlZFR5cGUiIDogIm9yZy5obDcuY3FsX2Fubm90YXRpb25zLnIxLk5hcnJhdGl2ZSIsCiAgICAgICAgICAgICAgInNjb3BlIiA6ICJqYXZheC54bWwuYmluZC5KQVhCRWxlbWVudCRHbG9iYWxTY29wZSIsCiAgICAgICAgICAgICAgInZhbHVlIiA6IHsKICAgICAgICAgICAgICAgICJzIiA6IFsgIiIsICJkZWZpbmUgIiwgIlwiQ2hlY2sgU2FtcGxlIFJlcXVlc3RcIiIsICI6ICIsICJ0cnVlIiBdLAogICAgICAgICAgICAgICAgInIiIDogIjMiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmlsIiA6IGZhbHNlLAogICAgICAgICAgICAgICJnbG9iYWxTY29wZSIgOiB0cnVlLAogICAgICAgICAgICAgICJ0eXBlU3Vic3RpdHV0ZWQiIDogZmFsc2UKICAgICAgICAgICAgfSBdLAogICAgICAgICAgICAiciIgOiAiNCIKICAgICAgICAgIH0KICAgICAgICB9IF0sCiAgICAgICAgImxvY2FsSWQiIDogIjQiLAogICAgICAgICJsb2NhdG9yIiA6ICI3OjEtNzozNSIsCiAgICAgICAgIm5hbWUiIDogIkNoZWNrIFNhbXBsZSBSZXF1ZXN0IiwKICAgICAgICAiY29udGV4dCIgOiAiUGF0aWVudCIsCiAgICAgICAgImFjY2Vzc0xldmVsIiA6ICJQdWJsaWMiCiAgICAgIH0gXQogICAgfSwKICAgICJhbm5vdGF0aW9uIiA6IFsgewogICAgICAidHlwZSIgOiAiQ3FsVG9FbG1JbmZvIiwKICAgICAgInRyYW5zbGF0b3JPcHRpb25zIiA6ICJFbmFibGVBbm5vdGF0aW9ucyxFbmFibGVMb2NhdG9ycyxEaXNhYmxlTGlzdERlbW90aW9uLERpc2FibGVMaXN0UHJvbW90aW9uIgogICAgfSwgewogICAgICAidHlwZSIgOiAiQW5ub3RhdGlvbiIsCiAgICAgICJzIiA6IHsKICAgICAgICAicyIgOiBbIHsKICAgICAgICAgICJuYW1lIiA6ICJ7dXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxfXMiLAogICAgICAgICAgImRlY2xhcmVkVHlwZSIgOiAib3JnLmhsNy5jcWxfYW5ub3RhdGlvbnMucjEuTmFycmF0aXZlIiwKICAgICAgICAgICJzY29wZSIgOiAiamF2YXgueG1sLmJpbmQuSkFYQkVsZW1lbnQkR2xvYmFsU2NvcGUiLAogICAgICAgICAgInZhbHVlIiA6IHsKICAgICAgICAgICAgInMiIDogWyAiIiwgImxpYnJhcnkgc2FtcGxlX3JlcXVlc3RfZXhhbXBsZSB2ZXJzaW9uICcxLjAuMCciIF0KICAgICAgICAgIH0sCiAgICAgICAgICAibmlsIiA6IGZhbHNlLAogICAgICAgICAgImdsb2JhbFNjb3BlIiA6IHRydWUsCiAgICAgICAgICAidHlwZVN1YnN0aXR1dGVkIiA6IGZhbHNlCiAgICAgICAgfSBdLAogICAgICAgICJyIiA6ICI0IgogICAgICB9CiAgICB9IF0KICB9Cn0=" + }, + { + "contentType": "application/elm+xml", + "data": "PD94bWwgdmVyc2lvbj0nMS4xJyBlbmNvZGluZz0nVVRGLTgnPz4KPExpYnJhcnkgdHlwZT0iTGlicmFyeSI+CiAgPHdzdHhuczE6aWRlbnRpZmllciB4bWxuczp3c3R4bnMxPSJ1cm46aGw3LW9yZzplbG06cjEiIHdzdHhuczE6dHlwZT0iVmVyc2lvbmVkSWRlbnRpZmllciIgaWQ9InNhbXBsZV9yZXF1ZXN0X2V4YW1wbGUiIHZlcnNpb249IjEuMC4wIi8+CiAgPHdzdHhuczI6c2NoZW1hSWRlbnRpZmllciB4bWxuczp3c3R4bnMyPSJ1cm46aGw3LW9yZzplbG06cjEiIHdzdHhuczI6dHlwZT0iVmVyc2lvbmVkSWRlbnRpZmllciIgaWQ9InVybjpobDctb3JnOmVsbSIgdmVyc2lvbj0icjEiLz4KICA8d3N0eG5zMzp1c2luZ3MgeG1sbnM6d3N0eG5zMz0idXJuOmhsNy1vcmc6ZWxtOnIxIiB3c3R4bnMzOnR5cGU9IkxpYnJhcnkkVXNpbmdzIj4KICAgIDx3c3R4bnMzOmRlZj4KICAgICAgPHdzdHhuczM6ZGVmIHdzdHhuczM6dHlwZT0iVXNpbmdEZWYiIGxvY2FsSWRlbnRpZmllcj0iU3lzdGVtIiB1cmk9InVybjpobDctb3JnOmVsbS10eXBlczpyMSIvPgogICAgICA8d3N0eG5zMzpkZWYgd3N0eG5zMzp0eXBlPSJVc2luZ0RlZiIgbG9jYWxJZD0iMSIgbG9jYXRvcj0iMjoxLTI6MjYiIGxvY2FsSWRlbnRpZmllcj0iRkhJUiIgdXJpPSJodHRwOi8vaGw3Lm9yZy9maGlyIiB2ZXJzaW9uPSI0LjAuMSI+CiAgICAgICAgPHdzdHhuczM6YW5ub3RhdGlvbj4KICAgICAgICAgIDx3c3R4bnMzOmFubm90YXRpb24gd3N0eG5zMzp0eXBlPSJBbm5vdGF0aW9uIj4KICAgICAgICAgICAgPHdzdHhuczQ6cyB4bWxuczp3c3R4bnM0PSJ1cm46aGw3LW9yZzpjcWwtYW5ub3RhdGlvbnM6cjEiIHI9IjEiPgogICAgICAgICAgICAgIDxzPgogICAgICAgICAgICAgICAgPHM+CiAgICAgICAgICAgICAgICAgIDxuYW1lPnt1cm46aGw3LW9yZzpjcWwtYW5ub3RhdGlvbnM6cjF9czwvbmFtZT4KICAgICAgICAgICAgICAgICAgPGRlY2xhcmVkVHlwZT5vcmcuaGw3LmNxbF9hbm5vdGF0aW9ucy5yMS5OYXJyYXRpdmU8L2RlY2xhcmVkVHlwZT4KICAgICAgICAgICAgICAgICAgPHNjb3BlPmphdmF4LnhtbC5iaW5kLkpBWEJFbGVtZW50JEdsb2JhbFNjb3BlPC9zY29wZT4KICAgICAgICAgICAgICAgICAgPHZhbHVlPgogICAgICAgICAgICAgICAgICAgIDxzPgogICAgICAgICAgICAgICAgICAgICAgPHM+PC9zPgogICAgICAgICAgICAgICAgICAgICAgPHM+dXNpbmcgPC9zPgogICAgICAgICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgICAgICAgPC92YWx1ZT4KICAgICAgICAgICAgICAgICAgPG5pbD5mYWxzZTwvbmlsPgogICAgICAgICAgICAgICAgICA8Z2xvYmFsU2NvcGU+dHJ1ZTwvZ2xvYmFsU2NvcGU+CiAgICAgICAgICAgICAgICAgIDx0eXBlU3Vic3RpdHV0ZWQ+ZmFsc2U8L3R5cGVTdWJzdGl0dXRlZD4KICAgICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgICAgIDxzPgogICAgICAgICAgICAgICAgICA8bmFtZT57dXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxfXM8L25hbWU+CiAgICAgICAgICAgICAgICAgIDxkZWNsYXJlZFR5cGU+b3JnLmhsNy5jcWxfYW5ub3RhdGlvbnMucjEuTmFycmF0aXZlPC9kZWNsYXJlZFR5cGU+CiAgICAgICAgICAgICAgICAgIDxzY29wZT5qYXZheC54bWwuYmluZC5KQVhCRWxlbWVudCRHbG9iYWxTY29wZTwvc2NvcGU+CiAgICAgICAgICAgICAgICAgIDx2YWx1ZT4KICAgICAgICAgICAgICAgICAgICA8cz4KICAgICAgICAgICAgICAgICAgICAgIDxzPgogICAgICAgICAgICAgICAgICAgICAgICA8bmFtZT57dXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxfXM8L25hbWU+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkZWNsYXJlZFR5cGU+b3JnLmhsNy5jcWxfYW5ub3RhdGlvbnMucjEuTmFycmF0aXZlPC9kZWNsYXJlZFR5cGU+CiAgICAgICAgICAgICAgICAgICAgICAgIDxzY29wZT5qYXZheC54bWwuYmluZC5KQVhCRWxlbWVudCRHbG9iYWxTY29wZTwvc2NvcGU+CiAgICAgICAgICAgICAgICAgICAgICAgIDx2YWx1ZT4KICAgICAgICAgICAgICAgICAgICAgICAgICA8cz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxzPkZISVI8L3M+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC9zPgogICAgICAgICAgICAgICAgICAgICAgICA8L3ZhbHVlPgogICAgICAgICAgICAgICAgICAgICAgICA8bmlsPmZhbHNlPC9uaWw+CiAgICAgICAgICAgICAgICAgICAgICAgIDxnbG9iYWxTY29wZT50cnVlPC9nbG9iYWxTY29wZT4KICAgICAgICAgICAgICAgICAgICAgICAgPHR5cGVTdWJzdGl0dXRlZD5mYWxzZTwvdHlwZVN1YnN0aXR1dGVkPgogICAgICAgICAgICAgICAgICAgICAgPC9zPgogICAgICAgICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgICAgICAgPC92YWx1ZT4KICAgICAgICAgICAgICAgICAgPG5pbD5mYWxzZTwvbmlsPgogICAgICAgICAgICAgICAgICA8Z2xvYmFsU2NvcGU+dHJ1ZTwvZ2xvYmFsU2NvcGU+CiAgICAgICAgICAgICAgICAgIDx0eXBlU3Vic3RpdHV0ZWQ+ZmFsc2U8L3R5cGVTdWJzdGl0dXRlZD4KICAgICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgICAgIDxzPgogICAgICAgICAgICAgICAgICA8bmFtZT57dXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxfXM8L25hbWU+CiAgICAgICAgICAgICAgICAgIDxkZWNsYXJlZFR5cGU+b3JnLmhsNy5jcWxfYW5ub3RhdGlvbnMucjEuTmFycmF0aXZlPC9kZWNsYXJlZFR5cGU+CiAgICAgICAgICAgICAgICAgIDxzY29wZT5qYXZheC54bWwuYmluZC5KQVhCRWxlbWVudCRHbG9iYWxTY29wZTwvc2NvcGU+CiAgICAgICAgICAgICAgICAgIDx2YWx1ZT4KICAgICAgICAgICAgICAgICAgICA8cz4KICAgICAgICAgICAgICAgICAgICAgIDxzPiB2ZXJzaW9uIDwvcz4KICAgICAgICAgICAgICAgICAgICAgIDxzPic0LjAuMSc8L3M+CiAgICAgICAgICAgICAgICAgICAgPC9zPgogICAgICAgICAgICAgICAgICA8L3ZhbHVlPgogICAgICAgICAgICAgICAgICA8bmlsPmZhbHNlPC9uaWw+CiAgICAgICAgICAgICAgICAgIDxnbG9iYWxTY29wZT50cnVlPC9nbG9iYWxTY29wZT4KICAgICAgICAgICAgICAgICAgPHR5cGVTdWJzdGl0dXRlZD5mYWxzZTwvdHlwZVN1YnN0aXR1dGVkPgogICAgICAgICAgICAgICAgPC9zPgogICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgPC93c3R4bnM0OnM+CiAgICAgICAgICA8L3dzdHhuczM6YW5ub3RhdGlvbj4KICAgICAgICA8L3dzdHhuczM6YW5ub3RhdGlvbj4KICAgICAgPC93c3R4bnMzOmRlZj4KICAgIDwvd3N0eG5zMzpkZWY+CiAgPC93c3R4bnMzOnVzaW5ncz4KICA8d3N0eG5zNTppbmNsdWRlcyB4bWxuczp3c3R4bnM1PSJ1cm46aGw3LW9yZzplbG06cjEiIHdzdHhuczU6dHlwZT0iTGlicmFyeSRJbmNsdWRlcyI+CiAgICA8d3N0eG5zNTpkZWY+CiAgICAgIDx3c3R4bnM1OmRlZiB3c3R4bnM1OnR5cGU9IkluY2x1ZGVEZWYiIGxvY2FsSWQ9IjIiIGxvY2F0b3I9IjM6MS0zOjM1IiBsb2NhbElkZW50aWZpZXI9IkZISVJIZWxwZXJzIiBwYXRoPSJGSElSSGVscGVycyIgdmVyc2lvbj0iNC4wLjEiPgogICAgICAgIDx3c3R4bnM1OmFubm90YXRpb24+CiAgICAgICAgICA8d3N0eG5zNTphbm5vdGF0aW9uIHdzdHhuczU6dHlwZT0iQW5ub3RhdGlvbiI+CiAgICAgICAgICAgIDx3c3R4bnM2OnMgeG1sbnM6d3N0eG5zNj0idXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxIiByPSIyIj4KICAgICAgICAgICAgICA8cz4KICAgICAgICAgICAgICAgIDxzPgogICAgICAgICAgICAgICAgICA8bmFtZT57dXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxfXM8L25hbWU+CiAgICAgICAgICAgICAgICAgIDxkZWNsYXJlZFR5cGU+b3JnLmhsNy5jcWxfYW5ub3RhdGlvbnMucjEuTmFycmF0aXZlPC9kZWNsYXJlZFR5cGU+CiAgICAgICAgICAgICAgICAgIDxzY29wZT5qYXZheC54bWwuYmluZC5KQVhCRWxlbWVudCRHbG9iYWxTY29wZTwvc2NvcGU+CiAgICAgICAgICAgICAgICAgIDx2YWx1ZT4KICAgICAgICAgICAgICAgICAgICA8cz4KICAgICAgICAgICAgICAgICAgICAgIDxzPjwvcz4KICAgICAgICAgICAgICAgICAgICAgIDxzPmluY2x1ZGUgPC9zPgogICAgICAgICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgICAgICAgPC92YWx1ZT4KICAgICAgICAgICAgICAgICAgPG5pbD5mYWxzZTwvbmlsPgogICAgICAgICAgICAgICAgICA8Z2xvYmFsU2NvcGU+dHJ1ZTwvZ2xvYmFsU2NvcGU+CiAgICAgICAgICAgICAgICAgIDx0eXBlU3Vic3RpdHV0ZWQ+ZmFsc2U8L3R5cGVTdWJzdGl0dXRlZD4KICAgICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgICAgIDxzPgogICAgICAgICAgICAgICAgICA8bmFtZT57dXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxfXM8L25hbWU+CiAgICAgICAgICAgICAgICAgIDxkZWNsYXJlZFR5cGU+b3JnLmhsNy5jcWxfYW5ub3RhdGlvbnMucjEuTmFycmF0aXZlPC9kZWNsYXJlZFR5cGU+CiAgICAgICAgICAgICAgICAgIDxzY29wZT5qYXZheC54bWwuYmluZC5KQVhCRWxlbWVudCRHbG9iYWxTY29wZTwvc2NvcGU+CiAgICAgICAgICAgICAgICAgIDx2YWx1ZT4KICAgICAgICAgICAgICAgICAgICA8cz4KICAgICAgICAgICAgICAgICAgICAgIDxzPgogICAgICAgICAgICAgICAgICAgICAgICA8bmFtZT57dXJuOmhsNy1vcmc6Y3FsLWFubm90YXRpb25zOnIxfXM8L25hbWU+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkZWNsYXJlZFR5cGU+b3JnLmhsNy5jcWxfYW5ub3RhdGlvbnMucjEuTmFycmF0aXZlPC9kZWNsYXJlZFR5cGU+CiAgICAgICAgICAgICAgICAgICAgICAgIDxzY29wZT5qYXZheC54bWwuYmluZC5KQVhCRWxlbWVudCRHbG9iYWxTY29wZTwvc2NvcGU+CiAgICAgICAgICAgICAgICAgICAgICAgIDx2YWx1ZT4KICAgICAgICAgICAgICAgICAgICAgICAgICA8cz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxzPkZISVJIZWxwZXJzPC9zPgogICAgICAgICAgICAgICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgICAgICAgICAgICAgPC92YWx1ZT4KICAgICAgICAgICAgICAgICAgICAgICAgPG5pbD5mYWxzZTwvbmlsPgogICAgICAgICAgICAgICAgICAgICAgICA8Z2xvYmFsU2NvcGU+dHJ1ZTwvZ2xvYmFsU2NvcGU+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0eXBlU3Vic3RpdHV0ZWQ+ZmFsc2U8L3R5cGVTdWJzdGl0dXRlZD4KICAgICAgICAgICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgICAgICAgICA8L3M+CiAgICAgICAgICAgICAgICAgIDwvdmFsdWU+CiAgICAgICAgICAgICAgICAgIDxuaWw+ZmFsc2U8L25pbD4KICAgICAgICAgICAgICAgICAgPGdsb2JhbFNjb3BlPnRydWU8L2dsb2JhbFNjb3BlPgogICAgICAgICAgICAgICAgICA8dHlwZVN1YnN0aXR1dGVkPmZhbHNlPC90eXBlU3Vic3RpdHV0ZWQ+CiAgICAgICAgICAgICAgICA8L3M+CiAgICAgICAgICAgICAgICA8cz4KICAgICAgICAgICAgICAgICAgPG5hbWU+e3VybjpobDctb3JnOmNxbC1hbm5vdGF0aW9uczpyMX1zPC9uYW1lPgogICAgICAgICAgICAgICAgICA8ZGVjbGFyZWRUeXBlPm9yZy5obDcuY3FsX2Fubm90YXRpb25zLnIxLk5hcnJhdGl2ZTwvZGVjbGFyZWRUeXBlPgogICAgICAgICAgICAgICAgICA8c2NvcGU+amF2YXgueG1sLmJpbmQuSkFYQkVsZW1lbnQkR2xvYmFsU2NvcGU8L3Njb3BlPgogICAgICAgICAgICAgICAgICA8dmFsdWU+CiAgICAgICAgICAgICAgICAgICAgPHM+CiAgICAgICAgICAgICAgICAgICAgICA8cz4gdmVyc2lvbiA8L3M+CiAgICAgICAgICAgICAgICAgICAgICA8cz4nNC4wLjEnPC9zPgogICAgICAgICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgICAgICAgPC92YWx1ZT4KICAgICAgICAgICAgICAgICAgPG5pbD5mYWxzZTwvbmlsPgogICAgICAgICAgICAgICAgICA8Z2xvYmFsU2NvcGU+dHJ1ZTwvZ2xvYmFsU2NvcGU+CiAgICAgICAgICAgICAgICAgIDx0eXBlU3Vic3RpdHV0ZWQ+ZmFsc2U8L3R5cGVTdWJzdGl0dXRlZD4KICAgICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgICA8L3M+CiAgICAgICAgICAgIDwvd3N0eG5zNjpzPgogICAgICAgICAgPC93c3R4bnM1OmFubm90YXRpb24+CiAgICAgICAgPC93c3R4bnM1OmFubm90YXRpb24+CiAgICAgIDwvd3N0eG5zNTpkZWY+CiAgICA8L3dzdHhuczU6ZGVmPgogIDwvd3N0eG5zNTppbmNsdWRlcz4KICA8d3N0eG5zNzpjb250ZXh0cyB4bWxuczp3c3R4bnM3PSJ1cm46aGw3LW9yZzplbG06cjEiIHdzdHhuczc6dHlwZT0iTGlicmFyeSRDb250ZXh0cyI+CiAgICA8d3N0eG5zNzpkZWY+CiAgICAgIDx3c3R4bnM3OmRlZiB3c3R4bnM3OnR5cGU9IkNvbnRleHREZWYiIGxvY2F0b3I9IjU6MS01OjE1IiBuYW1lPSJQYXRpZW50Ii8+CiAgICA8L3dzdHhuczc6ZGVmPgogIDwvd3N0eG5zNzpjb250ZXh0cz4KICA8d3N0eG5zODpzdGF0ZW1lbnRzIHhtbG5zOndzdHhuczg9InVybjpobDctb3JnOmVsbTpyMSIgd3N0eG5zODp0eXBlPSJMaWJyYXJ5JFN0YXRlbWVudHMiPgogICAgPHdzdHhuczg6ZGVmPgogICAgICA8d3N0eG5zODpkZWYgd3N0eG5zODp0eXBlPSJFeHByZXNzaW9uRGVmIiBsb2NhdG9yPSI1OjEtNToxNSIgbmFtZT0iUGF0aWVudCIgY29udGV4dD0iUGF0aWVudCI+CiAgICAgICAgPHdzdHhuczg6ZXhwcmVzc2lvbiB3c3R4bnM4OnR5cGU9IlNpbmdsZXRvbkZyb20iPgogICAgICAgICAgPHdzdHhuczg6b3BlcmFuZCB3c3R4bnM4OnR5cGU9IlJldHJpZXZlIiBsb2NhdG9yPSI1OjEtNToxNSIgZGF0YVR5cGU9IntodHRwOi8vaGw3Lm9yZy9maGlyfVBhdGllbnQiIHRlbXBsYXRlSWQ9Imh0dHA6Ly9obDcub3JnL2ZoaXIvU3RydWN0dXJlRGVmaW5pdGlvbi9QYXRpZW50Ii8+CiAgICAgICAgPC93c3R4bnM4OmV4cHJlc3Npb24+CiAgICAgIDwvd3N0eG5zODpkZWY+CiAgICAgIDx3c3R4bnM4OmRlZiB3c3R4bnM4OnR5cGU9IkV4cHJlc3Npb25EZWYiIGxvY2FsSWQ9IjQiIGxvY2F0b3I9Ijc6MS03OjM1IiBuYW1lPSJDaGVjayBTYW1wbGUgUmVxdWVzdCIgY29udGV4dD0iUGF0aWVudCIgYWNjZXNzTGV2ZWw9IlB1YmxpYyI+CiAgICAgICAgPHdzdHhuczg6ZXhwcmVzc2lvbiB3c3R4bnM4OnR5cGU9IkxpdGVyYWwiIGxvY2FsSWQ9IjMiIGxvY2F0b3I9Ijc6MzItNzozNSIgdmFsdWVUeXBlPSJ7dXJuOmhsNy1vcmc6ZWxtLXR5cGVzOnIxfUJvb2xlYW4iIHZhbHVlPSJ0cnVlIi8+CiAgICAgICAgPHdzdHhuczg6YW5ub3RhdGlvbj4KICAgICAgICAgIDx3c3R4bnM4OmFubm90YXRpb24gd3N0eG5zODp0eXBlPSJBbm5vdGF0aW9uIj4KICAgICAgICAgICAgPHdzdHhuczk6cyB4bWxuczp3c3R4bnM5PSJ1cm46aGw3LW9yZzpjcWwtYW5ub3RhdGlvbnM6cjEiIHI9IjQiPgogICAgICAgICAgICAgIDxzPgogICAgICAgICAgICAgICAgPHM+CiAgICAgICAgICAgICAgICAgIDxuYW1lPnt1cm46aGw3LW9yZzpjcWwtYW5ub3RhdGlvbnM6cjF9czwvbmFtZT4KICAgICAgICAgICAgICAgICAgPGRlY2xhcmVkVHlwZT5vcmcuaGw3LmNxbF9hbm5vdGF0aW9ucy5yMS5OYXJyYXRpdmU8L2RlY2xhcmVkVHlwZT4KICAgICAgICAgICAgICAgICAgPHNjb3BlPmphdmF4LnhtbC5iaW5kLkpBWEJFbGVtZW50JEdsb2JhbFNjb3BlPC9zY29wZT4KICAgICAgICAgICAgICAgICAgPHZhbHVlIHI9IjMiPgogICAgICAgICAgICAgICAgICAgIDxzPgogICAgICAgICAgICAgICAgICAgICAgPHM+PC9zPgogICAgICAgICAgICAgICAgICAgICAgPHM+ZGVmaW5lIDwvcz4KICAgICAgICAgICAgICAgICAgICAgIDxzPiJDaGVjayBTYW1wbGUgUmVxdWVzdCI8L3M+CiAgICAgICAgICAgICAgICAgICAgICA8cz46IDwvcz4KICAgICAgICAgICAgICAgICAgICAgIDxzPnRydWU8L3M+CiAgICAgICAgICAgICAgICAgICAgPC9zPgogICAgICAgICAgICAgICAgICA8L3ZhbHVlPgogICAgICAgICAgICAgICAgICA8bmlsPmZhbHNlPC9uaWw+CiAgICAgICAgICAgICAgICAgIDxnbG9iYWxTY29wZT50cnVlPC9nbG9iYWxTY29wZT4KICAgICAgICAgICAgICAgICAgPHR5cGVTdWJzdGl0dXRlZD5mYWxzZTwvdHlwZVN1YnN0aXR1dGVkPgogICAgICAgICAgICAgICAgPC9zPgogICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgPC93c3R4bnM5OnM+CiAgICAgICAgICA8L3dzdHhuczg6YW5ub3RhdGlvbj4KICAgICAgICA8L3dzdHhuczg6YW5ub3RhdGlvbj4KICAgICAgPC93c3R4bnM4OmRlZj4KICAgIDwvd3N0eG5zODpkZWY+CiAgPC93c3R4bnM4OnN0YXRlbWVudHM+CiAgPHdzdHhuczEwOmFubm90YXRpb24geG1sbnM6d3N0eG5zMTA9InVybjpobDctb3JnOmVsbTpyMSI+CiAgICA8d3N0eG5zMTA6YW5ub3RhdGlvbiB3c3R4bnMxMDp0eXBlPSJDcWxUb0VsbUluZm8iIHRyYW5zbGF0b3JPcHRpb25zPSJFbmFibGVBbm5vdGF0aW9ucyxFbmFibGVMb2NhdG9ycyxEaXNhYmxlTGlzdERlbW90aW9uLERpc2FibGVMaXN0UHJvbW90aW9uIi8+CiAgICA8d3N0eG5zMTA6YW5ub3RhdGlvbiB3c3R4bnMxMDp0eXBlPSJBbm5vdGF0aW9uIj4KICAgICAgPHdzdHhuczExOnMgeG1sbnM6d3N0eG5zMTE9InVybjpobDctb3JnOmNxbC1hbm5vdGF0aW9uczpyMSIgcj0iNCI+CiAgICAgICAgPHM+CiAgICAgICAgICA8cz4KICAgICAgICAgICAgPG5hbWU+e3VybjpobDctb3JnOmNxbC1hbm5vdGF0aW9uczpyMX1zPC9uYW1lPgogICAgICAgICAgICA8ZGVjbGFyZWRUeXBlPm9yZy5obDcuY3FsX2Fubm90YXRpb25zLnIxLk5hcnJhdGl2ZTwvZGVjbGFyZWRUeXBlPgogICAgICAgICAgICA8c2NvcGU+amF2YXgueG1sLmJpbmQuSkFYQkVsZW1lbnQkR2xvYmFsU2NvcGU8L3Njb3BlPgogICAgICAgICAgICA8dmFsdWU+CiAgICAgICAgICAgICAgPHM+CiAgICAgICAgICAgICAgICA8cz48L3M+CiAgICAgICAgICAgICAgICA8cz5saWJyYXJ5IHNhbXBsZV9yZXF1ZXN0X2V4YW1wbGUgdmVyc2lvbiAnMS4wLjAnPC9zPgogICAgICAgICAgICAgIDwvcz4KICAgICAgICAgICAgPC92YWx1ZT4KICAgICAgICAgICAgPG5pbD5mYWxzZTwvbmlsPgogICAgICAgICAgICA8Z2xvYmFsU2NvcGU+dHJ1ZTwvZ2xvYmFsU2NvcGU+CiAgICAgICAgICAgIDx0eXBlU3Vic3RpdHV0ZWQ+ZmFsc2U8L3R5cGVTdWJzdGl0dXRlZD4KICAgICAgICAgIDwvcz4KICAgICAgICA8L3M+CiAgICAgIDwvd3N0eG5zMTE6cz4KICAgIDwvd3N0eG5zMTA6YW5ub3RhdGlvbj4KICA8L3dzdHhuczEwOmFubm90YXRpb24+CjwvTGlicmFyeT4K" + } + ] +} \ No newline at end of file diff --git a/android/engine/src/test/resources/plans/sample-request/sample_request_patient.json b/android/engine/src/test/resources/plans/sample-request/sample_request_patient.json new file mode 100644 index 0000000000..5aa0cf38fc --- /dev/null +++ b/android/engine/src/test/resources/plans/sample-request/sample_request_patient.json @@ -0,0 +1,15 @@ +{ + "resourceType": "Patient", + "id": "Patient-Example", + "active": true, + "name": [ + { + "family": "Hadi", + "given": [ + "Bareera" + ] + } + ], + "gender": "female", + "birthDate": "1999-01-14" +} diff --git a/android/engine/src/test/resources/plans/sample-request/sample_request_plan_definition.json b/android/engine/src/test/resources/plans/sample-request/sample_request_plan_definition.json new file mode 100644 index 0000000000..883826b2ba --- /dev/null +++ b/android/engine/src/test/resources/plans/sample-request/sample_request_plan_definition.json @@ -0,0 +1,148 @@ +{ + "resourceType": "PlanDefinition", + "meta": { + "versionId": "1", + "lastUpdated": "2022-06-20T22:30:39.217+00:00" + }, + "id" : "SampleRequest-Example", + "url" : "http://localhost/PlanDefinition/SampleRequest-Example", + "title" : "This example illustrates a medication request", + "status" : "active", + "contained": [ + { + "resourceType" : "ActivityDefinition", + "id" : "SampleRequest-1", + "url" : "http://localhost/ActivityDefinition/SampleRequest-1", + "status": "active", + "kind" : "Task", + "productCodeableConcept" : { + "text" : "Sample 1" + } + } + ], + "action": [ + { + "id": "sample-action-1", + "title" : "Administer Medication 1", + "prefix": "1", + "priority": "routine", + "dynamicValue": [ + { + "path": "title", + "expression": { + "language": "text/fhirpath", + "expression": "%rootResource.title" + } + }, + { + "path": "description", + "expression": { + "language": "text/fhirpath", + "expression": "%rootResource.description" + } + }, + { + "path": "instantiatesCanonical", + "expression": { + "language": "text/fhirpath", + "expression": "%rootResource.id.replaceMatches('/_history/.*', '')" + } + }, + { + "path": "status", + "expression": { + "language": "text/fhirpath", + "expression": "'active'" + } + }, + { + "path": "intent", + "expression": { + "language": "text/fhirpath", + "expression": "'plan'" + } + }, + { + "path": "created", + "expression": { + "language": "text/fhirpath", + "expression": "now()" + } + }, + { + "path": "subject", + "expression": { + "language": "text/fhirpath", + "expression": "%resource.entry.where(resource is QuestionnaireResponse).resource.subject" + } + }, + { + "path": "author", + "expression": { + "language": "text/fhirpath", + "expression": "$this.generalPractitioner.first()" + } + }, + { + "path": "period.start", + "expression": { + "language": "text/fhirpath", + "expression": "%resource.entry.where(resource is QuestionnaireResponse).resource.descendants().where(linkId='245679f2-6172-456e-8ff3-425f5cea3243').answer.value" + } + }, + { + "path": "period.end", + "expression": { + "language": "text/fhirpath", + "expression": "%resource.entry.where(resource is QuestionnaireResponse).resource.descendants().where(linkId='245679f2-6172-456e-8ff3-425f5cea3243').answer.value + 9 'months'" + } + }, + { + "path": "activity.detail.kind", + "expression": { + "language": "text/fhirpath", + "expression": "'Task'" + } + }, + { + "path": "activity.detail.status", + "expression": { + "language": "text/fhirpath", + "expression": "'in-progress'" + } + }, + { + "path": "activity.detail.description", + "expression": { + "language": "text/fhirpath", + "expression": "'This action will assess careplan on registration to init careplan'" + } + }, + { + "path": "activity.detail.performer", + "expression": { + "language": "text/fhirpath", + "expression": "$this.generalPractitioner.first()" + } + } + ] + }, + { + "id": "sample-action-2", + "title": "Administer Medication 1", + "prefix": "1", + "priority": "routine", + "condition": [ + { + "kind": "applicability", + "expression": { + "language": "text/cql-identifier", + "expression": "Check Sample Request" + } + } + ], + "definitionCanonical": "#SampleRequest-1" + } + ], + "library": [ "http://localhost/Library/sample_request_example|1.0.0" ] +} \ No newline at end of file diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt index 500ddc83f8..429a283f01 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt @@ -623,6 +623,7 @@ constructor( planDefinitionId = planId, subject = subject, data = bundle, + generateCarePlanWithWorkflowApi = questionnaireConfig.generateCarePlanWithWorkflowApi, ) } .onFailure { Timber.e(it) }