jvmMain.com.iprd.report.FhirPathEvaluator.kt Maven / Gradle / Ivy
/* IPRD Group 2022 */
package com.iprd.report
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.FhirVersionEnum
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport
import com.iprd.report.model.Transform
import com.iprd.report.model.data.DataItem
import com.iprd.report.model.definition.FhirPathTransformation
import com.iprd.report.ui.utilities.asStringValue
import net.objecthunter.exp4j.ExpressionBuilder
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.DateTimeType
import org.hl7.fhir.r4.model.DateType
import org.hl7.fhir.r4.model.DecimalType
import org.hl7.fhir.r4.model.IntegerType
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.utils.FHIRPathEngine
import java.time.Duration
import java.time.Instant
import java.util.Date
object FhirPathEvaluator {
private const val REG_EX_FHIR_PATH = "(Bundle\\.entry\\.resource.+?\\.count\\(\\)|Bundle\\.entry\\.resource.+?\\.aggregate\\(\\\$total \\+ \\\$this,0\\))"
private const val REG_EX_FHIR_PATH_FOR_FHIRPATH_TRANSFORMATION_OBJECT = "(Bundle\\.entry\\.resource.+?\\.gender|Bundle\\.entry\\.resource.+?\\.vaccineCode|Bundle\\.entry\\.resource.+?\\.occurrence|Bundle\\.entry\\.resource.+?\\.address|Bundle\\.entry\\.resource.+?\\.count\\(\\)|Bundle\\.entry\\.resource.+?\\.aggregate\\(\\\$total \\+ \\\$this,0\\)|Bundle\\.entry\\.resource.+?\\.distinct\\(\\)|Bundle\\.entry\\.resource.+?\\.exists\\(\\)|Bundle\\.entry\\.resource.+?\\.display|Bundle\\.entry\\.resource.+?\\.value|Bundle\\.entry\\.resource.+?\\.name|Bundle\\.entry\\.resource.+?\\.birthDate|Bundle\\.entry\\.resource.+?\\).code|Bundle\\.entry\\.resource.+?\\.status|Bundle\\.entry\\.resource.+?\\.start)"
private val fhirPathEngine: FHIRPathEngine =
with(FhirContext.forCached(FhirVersionEnum.R4)) {
FHIRPathEngine(HapiWorkerContext(this, DefaultProfileValidationSupport(this)))
}
fun extractValuesFromExpressions(resource: Resource, expressions: List): MutableList {
val dataItems = mutableListOf()
expressions.forEach {
dataItems.add(DataItem(extractValueFromResource(resource, it)))
}
return dataItems
}
fun extractValuesFromExpressionsUsingTransform(resource: Resource, expressions: List): MutableList {
val dataItems = mutableListOf()
expressions.forEach {
dataItems.add(DataItem(extractValueFromResourceAndFhirPathExpressionUsingReflection(resource, it)))
}
return dataItems
}
fun extractValueFromResource(resource: Resource, expression: String, transformList: List?): String {
if (transformList == null || transformList.isEmpty())
return extractValueFromResourceAndFhirPathExpression(resource, expression)
val evaluateResult = fhirPathEngine.evaluate(resource, expression)
val finalResult: MutableList = mutableListOf()
evaluateResult.forEach { item ->
val date = when (item) {
is DateType -> item.value
is DateTimeType -> item.value
else -> null
}
date?.let {
val differenceInSeconds = getSecondsWithCurrentTime(date)
transformList.map { t ->
when (t.operator) {
"=" -> differenceInSeconds == t.operand
">" -> differenceInSeconds > t.operand
">=" -> differenceInSeconds >= t.operand
"<" -> differenceInSeconds < t.operand
"<=" -> differenceInSeconds <= t.operand
else -> false
}
}.all {
it
}.let { result ->
if (result)
finalResult.add(item)
}
}
}
return finalResult.size.toString()
}
fun getSecondsWithCurrentTime(date: Date): Long {
return Duration.between(date.toInstant(), Instant.now()).seconds
}
fun extractValueFromResource(resource: Resource, expression: String): String {
return fhirPathEngine.evaluate(resource, expression).joinToString { it.asStringValue() }
}
fun extractValueFromResource(resource: Resource, fhirPath: FhirPath): String {
check(fhirPath.operand.isNotEmpty()) {
"FhirPath operand can't be empty"
}
if (fhirPath.operand.size == 1) {
return fhirPathEngine.evaluate(resource, fhirPath.operand.first()).joinToString { it.asStringValue() }
}
check(fhirPath.operator != null && fhirPath.operand.size > 1) {
"FhirPath should have operator when operand size is greater than 1"
}
val firstEvaluation =
fhirPathEngine.evaluate(resource, fhirPath.operand[0]).joinToString { it.asStringValue() }.toDoubleOrNull()
val secondEvaluation =
fhirPathEngine.evaluate(resource, fhirPath.operand[1]).joinToString { it.asStringValue() }.toDoubleOrNull()
if (firstEvaluation == null || secondEvaluation == null) return "0.0"
return when (fhirPath.operator) {
"+" -> (firstEvaluation + secondEvaluation).toString()
"-" -> (firstEvaluation - secondEvaluation).toString()
"*" -> (firstEvaluation * secondEvaluation).toString()
"/" -> {
if (secondEvaluation == 0.0) return "0.0"
return (firstEvaluation / secondEvaluation).toString()
}
"%" -> (firstEvaluation % secondEvaluation).toString()
else -> "0.0"
}
}
fun extractValueFromResourceIpt(resource: Resource, fhirPath: FhirPath):
MutableList {
// return fhirPathEngine.evaluate(resource, fhirPath).joinToString { it.asStringValue() }
val firstEvaluation =
fhirPathEngine.evaluate(resource, fhirPath.operand[0]).joinToString { it.asStringValue() }
val secondEvaluation =
fhirPathEngine.evaluate(resource, fhirPath.operand[1]).joinToString { it.asStringValue() }
val numbers: MutableList = mutableListOf()
numbers.add(firstEvaluation)
numbers.add(secondEvaluation)
return numbers
}
fun extractValueFromResourceAndFhirPathExpression(resource: Resource, fhirPathExpression: String): String {
var fhirPathExpressionCopy = fhirPathExpression
val regex = Regex(REG_EX_FHIR_PATH)
val fhirPathList = regex.findAll(fhirPathExpression).map { it.groupValues[0] }.toList()
fhirPathList.forEach { fhirPath ->
val value = fhirPathEngine.evaluate(resource, fhirPath).joinToString { it.asStringValue() }
fhirPathExpressionCopy = fhirPathExpressionCopy.replace(fhirPath, value)
}
return try {
ExpressionBuilder(fhirPathExpressionCopy).build().evaluate().toString()
} catch (e: ArithmeticException) {
// e.printStackTrace()
"0.0"
}
}
private fun processFhirPathExpression(fhirPathList: List, resource: Resource, fhirPathExpression: String): Double {
// This function calculates the fhirpath expression value
var fhirPathExpressionCopy = fhirPathExpression
fhirPathList.forEach { fhirPath ->
val value = fhirPathEngine.evaluate(resource, fhirPath).joinToString { it.asStringValue() }
fhirPathExpressionCopy = fhirPathExpressionCopy.replace(fhirPath, value)
}
return try {
ExpressionBuilder(fhirPathExpressionCopy).build().evaluate()
} catch (e: ArithmeticException) {
// e.printStackTrace()
0.0
}
}
fun extractValueFromResourceAndFhirPathExpressionUsingReflection(resource: Resource, expression: FhirPathTransformation): String {
val transformation = Transformation()
val values = mutableListOf ()
val fhirPathExpressionCopy = expression.expression
val regex = Regex(REG_EX_FHIR_PATH_FOR_FHIRPATH_TRANSFORMATION_OBJECT)
val additionalArguments = mutableListOf ()
if (fhirPathExpressionCopy.contains(",")) {
// processing fhirpath expression if it is comma separated
val subString = fhirPathExpressionCopy.split(",")
subString.forEach { subStringItem ->
val fhirPathList = regex.findAll(subStringItem).map { it.groupValues[0] }.toList()
if (fhirPathList.size> 1) {
// we calculate the expression value by calling processFhirPathExpression function
values.add(DecimalType(processFhirPathExpression(fhirPathList, resource, subStringItem)))
} else {
val value = fhirPathEngine.evaluate(resource, fhirPathList.first())
values.addAll(value)
}
}
} else {
// processing single fhirpath/fhirpath expression
val fhirPathList = regex.findAll(expression.expression).map { it.groupValues[0] }.toList()
if (fhirPathList.size> 1) {
// we calculate the expression value by calling processFhirPathExpression function
values.add(DecimalType(processFhirPathExpression(fhirPathList, resource, fhirPathExpressionCopy)))
} else {
if (expression.transformReport.isNullOrEmpty()) {
// processing just single fhirpath
return fhirPathEngine.evaluate(resource, fhirPathList.first()).joinToString { it.asStringValue() }
}
val value = fhirPathEngine.evaluate(resource, fhirPathList.first())
// if parameter is empty for a transformation function then we are just returning empty string
if (value.isEmpty()) return fhirPathEngine.evaluate(resource, fhirPathList.first()).joinToString { it.asStringValue() }
values.addAll(value)
}
}
return try {
if (expression.transformReport != null) {
if (expression.transformReport!!.contains("(")) {
val argumentList = extractValues(expression.transformReport!!)
// adding operands to additional arguments
additionalArguments.addAll(argumentList)
transformation.processFhirPath(values, expression.transformReport!!.split("(")[0], additionalArguments)
} else {
transformation.processFhirPath(values, expression.transformReport!!, null)
}
} else {
// if fhirpath is comma separated with no transformation function specified then we are converting the values array to string and returning it
values.joinToString { it.asStringValue() }
}
} catch (e: ArithmeticException) {
// e.printStackTrace()
"0.0"
}
}
fun extractValues(inputString: String): List {
val pattern = Regex("""\((.*?)\)""")
val matchResult = pattern.find(inputString)
val stringValues = matchResult?.groupValues?.get(1)?.split(',') ?: emptyList()
return stringValues.mapNotNull { it.toIntOrNull()?.let { item -> IntegerType(item) } }
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy