Skip to content

Commit

Permalink
Implement an optional and configurable code path that uses $apply for…
Browse files Browse the repository at this point in the history
… 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 <[email protected]>
  • Loading branch information
owais-vd and pld authored Sep 18, 2023
1 parent 0e7a95e commit b8403ad
Show file tree
Hide file tree
Showing 9 changed files with 668 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ data class QuestionnaireConfig(
val extractedResourceUniquePropertyExpressions: List<ExtractedResourceUniquePropertyExpression>? =
null,
val saveQuestionnaireResponse: Boolean = true,
val generateCarePlanWithWorkflowApi: Boolean = false,
) : java.io.Serializable, Parcelable {

fun interpolate(computedValuesMap: Map<String, Any>) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -83,15 +85,19 @@ constructor(
planDefinitionId: String,
subject: Resource,
data: Bundle = Bundle(),
generateCarePlanWithWorkflowApi: Boolean = false,
): CarePlan? {
val planDefinition = defaultRepository.loadResource<PlanDefinition>(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 =
Expand All @@ -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<StructureMap>(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<StructureMap>(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
}
}

Expand Down
Loading

0 comments on commit b8403ad

Please sign in to comment.