Pre-validate measure libraries before per-subject CQL evaluation#1024
Merged
Conversation
Measures whose CQL referenced a missing ValueSet (or had other fatal
compile errors) failed in two awkward ways. With a function-based
component stratifier, FunctionEvaluationHandler.processNonSubValueStratifier
observed missing expression results when the IP failed and threw
"Expression result: Initial Population is missing" — masking the
underlying CqlException. In multi-measure evaluations, that masking
exception then escaped the per-library loop in MeasureEvaluationResultHandler
and was caught by the outer subject-scoped try/catch, which wrote the
error to every measure def — poisoning unrelated libraries that would
otherwise have evaluated cleanly.
Two complementary layers:
- MeasureLibraryPreValidator resolves each library before the per-subject
loop, collects fatal compile errors via LibraryManager.resolveLibrary's
error-collecting overload, and walks the compiled ELM ValueSet refs
against the terminology provider. Failed libraries drop from the
evaluation set with the engine-level diagnostic ("Unable to locate
ValueSet …", or the original compile message) recorded against their
measure defs. Hard library-resolution failures still propagate so
existing throw-based contracts in InvalidMeasureTest are preserved.
- MeasureEvaluationResultHandler.getEvaluationResults now checks
getExceptionFor(libraryId) before invoking cqlFunctionEvaluation, so
any per-subject CqlException that slips past pre-validation is
surfaced through the existing per-subject error path instead of being
masked by the function-evaluation handler.
Tests:
- MeasureLibraryPreValidatorTest exercises compile-error, missing-
ValueSet, fall-through, short-circuit, and multi-library isolation
cases against real MultiLibraryIdMeasureEngineDetails / MeasureDef /
CompositeEvaluationResultsPerMeasure instances.
- MultiMeasureIsolationTest evaluates three measures (clean / broken /
clean) and asserts the broken one's failure does not poison the
siblings' MeasureReports.
- Four cohortEncounterMissingValueSet* integration tests live in
InvalidMeasureTest covering no-stratifier, scalar, value-component,
and function-component stratifier shapes — all surfacing the same
"Unable to locate ValueSet" diagnostic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Formatting check succeeded! |
|
JPercival
approved these changes
May 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Measures whose CQL referenced an unresolvable ValueSet (or had other fatal compile errors) failed in two awkward ways. With a function-based component stratifier,
FunctionEvaluationHandler.processNonSubValueStratifierobserved missing expression results when the IP failed and threw"Expression result: Initial Population is missing"— masking the underlyingCqlException. In a multi-measure evaluation that masking exception then escaped the per-library inner loop inMeasureEvaluationResultHandlerand was caught by the outer subject-scopedtry/catch, which wrote the error to every measure def — poisoning unrelated libraries that would otherwise have evaluated cleanly. This MR introduces an upfront library-validation pass and a complementary runtime safety net so library failures are isolated and the engine-level diagnostic surfaces uniformly across all stratifier shapes.Library pre-validation before per-subject evaluation: A new
MeasureLibraryPreValidatorresolves each library viaLibraryManager.resolveLibrary(id, errors), surfaces any severity=Error compile diagnostics, and walks the compiled ELMvalueSetDefreferences against the engine'sTerminologyProvider. Libraries that fail are recorded with the engine-level diagnostic and removed from the per-subject loop entirely. Hard library-resolution failures (CqlIncludeException, etc.) are deliberately allowed to propagate so the existing throw-based contracts inInvalidMeasureTest.evaluateThrowsErrorWhenLibraryIsMissingContentare preserved.Per-library exception check before function-evaluation handler: In
MeasureEvaluationResultHandler.getEvaluationResults, the per-library inner loop now readsevaluationResultsForMultiLib.getExceptionFor(libraryVersionedIdentifier)before callingFunctionEvaluationHandler.cqlFunctionEvaluation. Function evaluation is skipped when the base CQL eval already failed, so any per-subject exception that slips past pre-validation reaches the existing per-subject error path instead of being masked by the function handler — and crucially, no longer escapes to the outer subject-scoped catch where it would pollute sibling measure defs.Multi-measure isolation guarantee: When one library in a multi-measure evaluation has unresolvable references, only that library's measure(s) are flagged. Sibling measures continue to evaluate normally — including their function-based stratifiers — and produce
status=COMPLETEreports without spurious containedOperationOutcomeentries.Closes: #1022