All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jvmMain.com.iprd.report.FhirPathEvaluator.kt Maven / Gradle / Ivy

There is a newer version: 1.0.28
Show newest version
/* 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