Skip to content

Update the engine's evaluate API. Evaluation of expressions and functions from multiple libraries.#1646

Merged
antvaset merged 12 commits intomasterfrom
feature-engine-function-evaluation-api
Dec 4, 2025
Merged

Update the engine's evaluate API. Evaluation of expressions and functions from multiple libraries.#1646
antvaset merged 12 commits intomasterfrom
feature-engine-function-evaluation-api

Conversation

@antvaset
Copy link
Copy Markdown
Contributor

@antvaset antvaset commented Nov 21, 2025

A DSL for engine's evaluate API, supporting expression and function evaluation:

val evaluationResults = engine.evaluate {
    // Evaluates all expressions from the referenced library
    library(versionedIdentifier)

    // Repeat when multiple libraries need to be evaluated
    library("Library1") // Short for library(VersionedIdentifier().apply { id = "Library1" })

    // Evaluate specific expressions and functions.
    library("Library2") {
        // Use expressions(...) to pass in "evaluation expression refs" (the name comes from
        // ELM's ExpressionRef and its subclass FunctionRef)
        expressions(EvaluationExpressionRef("expr1"), EvaluationExpressionRef("expr2"), ...)

        // Same as above
        expressions("expr1", "expr2", ...)

        // Evaluate func1 with parameters (1, 2)
        expressions(EvaluationFunctionRef("func1", /*signature*/ null, /*args*/ listOf(1, 2)))
    }

    // Additional params
    contextParameter = "Patient" to pat1
    parameters = mapOf(...)
    debugMap = DebugMap()
    evaluationDateTime = ZonedDateTime.of(2025, 12, 31, 0, 0, 0, 0, ZoneOffset.UTC)
}

// Get results for library using versionedIdentifier
val libraryResults = evaluationResults.results[versionedIdentifier]
// Or use shorthand in the single-lib case (returns non-nullable EvaluationResult), same as before:
// val libraryResults = evaluationResults.onlyResultOrThrow

// Use the brackets operator [...] to get the results for expressions and
// function evaluations from EvaluationResult
val exprValue = libraryResults?["expr1"]?.value
val funcValue = libraryResults?[evaluationFunctionRef]?.value

@github-actions
Copy link
Copy Markdown

Formatting check succeeded!

@antvaset antvaset changed the title Update the engine's evaluate API. Evaluation of expressions and functions from multiple libraries. [DRAFT] Update the engine's evaluate API. Evaluation of expressions and functions from multiple libraries. Nov 21, 2025
@antvaset antvaset marked this pull request as draft November 21, 2025 21:34
@antvaset antvaset changed the title [DRAFT] Update the engine's evaluate API. Evaluation of expressions and functions from multiple libraries. Update the engine's evaluate API. Evaluation of expressions and functions from multiple libraries. Nov 21, 2025
@antvaset antvaset marked this pull request as ready for review November 21, 2025 21:37
@codecov
Copy link
Copy Markdown

codecov bot commented Nov 21, 2025

Codecov Report

❌ Patch coverage is 89.74359% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.82%. Comparing base (6100aee) to head (20501ce).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
.../org/opencds/cqf/cql/engine/execution/CqlEngine.kt 79.10% 12 Missing and 2 partials ⚠️
...encds/cqf/cql/engine/execution/EvaluationParams.kt 97.14% 1 Missing ⚠️
...encds/cqf/cql/engine/execution/EvaluationResult.kt 83.33% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1646      +/-   ##
============================================
+ Coverage     65.78%   65.82%   +0.03%     
  Complexity     1645     1645              
============================================
  Files           472      474       +2     
  Lines         27426    27448      +22     
  Branches       5458     5463       +5     
============================================
+ Hits          18043    18067      +24     
+ Misses         7085     7083       -2     
  Partials       2298     2298              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

for (libraryIdentifier in reversedOrderLibraryIdentifiers) {
val library = loadMultiLibResult.retrieveLibrary(libraryIdentifier)
val expressionSet = expressions ?: this.getExpressionSet(library!!)
val expressions =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't comment on unchanged code: can you refactor the code block for reversedOrderLibraryIdentifiers to use a standard Kotlin construct that's more readable? I added this as a hack when it was still Java

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed using Kotlin's reversed()

*/
class CompiledLibraryResult(
data class CompiledLibraryResult(
val identifier: VersionedIdentifier,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: why do we need this now, and not the identifier from the CompiledLibrary?

Copy link
Copy Markdown
Contributor Author

@antvaset antvaset Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expressions are now grouped by library in the evaluation request. We load and validate the libraries and for every compiled library look for the expressions requested for that library. The identifier from the evaluation request was needed in CompiledLibraryResult to keep it associated with the compiled library.

The name and version in compiledLibrary.identifier and compiledLibrary.library.identifier are set based on what library Lib1 version '1.0.0' says in the library. But the identifier in the request may e.g. omit the version so they won't be strictly equal.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense. could you please add a comment that makes this clear?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks for highlighting this!

Copy link
Copy Markdown
Contributor

@lukedegruchy lukedegruchy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved with a small suggestion and a question

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Dec 3, 2025

@antvaset antvaset merged commit 23f71e3 into master Dec 4, 2025
9 checks passed
@antvaset antvaset deleted the feature-engine-function-evaluation-api branch December 4, 2025 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants