Skip to content

Fix error handling for non-subject value and value stratifiers#993

Merged
JPercival merged 6 commits into
mainfrom
ld-20260401-non-subject-value-stratifier-error-handling-bug
Apr 24, 2026
Merged

Fix error handling for non-subject value and value stratifiers#993
JPercival merged 6 commits into
mainfrom
ld-20260401-non-subject-value-stratifier-error-handling-bug

Conversation

@lukedegruchy
Copy link
Copy Markdown
Contributor

@lukedegruchy lukedegruchy commented Apr 1, 2026

Summary

PR #913 (issue #909) added support for non-subject value stratifiers to use both CQL functions and scalar expressions, but left gaps in error handling: expressions that don't exist in the CQL library produced raw CqlException stack traces, and expressions returning invalid types (e.g. a list of FHIR resources instead of a scalar) were silently accepted, producing nonsense strata. This MR closes those gaps with proper validation and domain-specific exceptions.

  1. Wrap unresolvable stratifier expressions in StratifierExpressionNotFoundException: In FunctionEvaluationHandler.processNonSubValueStratifier(), the call to isExpressionFunctionRef() now catches CqlException and re-throws as a contextual StratifierExpressionNotFoundException that includes the expression name and measure URL.
  2. Validate non-function expression result types against allowed stratifier types: PopulationBasisValidator.validateExpressionResultType() now throws InvalidStratifierExpressionTypeException (instead of InvalidRequestException) for value stratifier expressions whose result types are not in the allowed set (e.g. Encounter instead of String/Code/Integer). This applies to both VALUE and NON_SUBJECT_VALUE stratifier types.
  3. Compact and actionable error messages: Value stratifier error messages now deduplicate result types, include the population basis for non-subject value stratifiers, and state what was expected ("Expected a scalar type" or "Expected a scalar or scalar-returning function").
  4. New domain exceptions follow architecture decision: Both StratifierExpressionNotFoundException and InvalidStratifierExpressionTypeException extend RuntimeException directly, not HAPI FHIR REST exceptions, per the project's architecture decision.
  5. 7 new integration tests and 6 new Measure JSON fixtures covering: non-existent expression, non-scalar/non-function expression (resource list), valid scalar only, valid function only, function with wrong parameter type, and boolean-basis VALUE stratifier with a resource list expression.

Code Review Suggestions

  • Verify that StratifierExpressionNotFoundException and InvalidStratifierExpressionTypeException are caught by the catch-all Exception handlers in MeasureEvaluationResultHandler and routed to contained OperationOutcome (not thrown to the caller). The tests assert OperationOutcome, but confirm the catch-all behavior is intentional for these specific exception types vs. failing fast.
  • The prettyDistinctClassNames() helper deduplicates by Class::getSimpleName. Confirm there are no cases where two distinct classes share a simple name (e.g. org.hl7.fhir.r4.model.Encounter vs a hypothetical CQL Encounter type) that would lose information in the error message.
  • The multi-catch InvalidRequestException | InvalidStratifierExpressionTypeException in R4PopulationBasisValidatorTest.validateStratifierBasisTypeErrorPath() is a test-only convenience. Confirm the production code paths throw the correct specific exception for each stratifier type: InvalidRequestException for criteria-based, InvalidStratifierExpressionTypeException for value/non-subject-value.
  • The InvalidStratifierExpressionTypeException has two constructors: a String message form and a structured form with 5 parameters. Only the String message form is used in production code. Confirm whether the structured constructor should be kept or removed.

QA Test Suggestions

Setup

Evaluate a FHIR R4 Measure with $evaluate-measure using the test data from the MeasureTest/input directory (LibrarySimple CQL library, patient-9 with 2 encounters). All tests use cohort scoring.

  • Non-existent stratifier expression: Evaluate a Measure with a non-subject value stratifier component pointing to an expression that does not exist in the CQL library (e.g. "This Expression Does Not Exist"). Verify the MeasureReport contains a contained OperationOutcome referencing both the bad expression name and the measure URL, and the report has ERROR status.
  • Resource list expression as non-subject value stratifier: Evaluate a Measure with Encounter population basis and a stratifier component pointing to a CQL define that returns [Encounter] E (a list of resources, not a scalar). Verify the MeasureReport contains a contained OperationOutcome with a message indicating "Expected a scalar or scalar-returning function".
  • Resource list expression as boolean-basis VALUE stratifier: Same as above but with boolean/subject population basis. Verify the OperationOutcome message says "Expected a scalar type" (without mentioning function, since VALUE stratifiers don't support functions).
  • Valid scalar stratifier: Evaluate a Measure with Encounter population basis and a stratifier component pointing to a CQL define returning a String (e.g. "Patient Age Bracket" returning "21--41"). Verify the MeasureReport completes successfully with the expected stratum values.
  • Valid function stratifier: Evaluate a Measure with Encounter population basis and a stratifier component pointing to a CQL define function that takes an Encounter and returns a String (e.g. "Encounter Status Stratifier"). Verify the MeasureReport completes with per-encounter strata (e.g. "finished", "in-progress").
  • Function with wrong parameter type: Evaluate a Measure with Encounter population basis and a stratifier component pointing to a CQL function whose parameter type doesn't match the population basis (e.g. a function taking Interval<DateTime> instead of Encounter). Verify the MeasureReport contains a contained OperationOutcome referencing "CQL Exception while evaluating function".
  • Regression: existing stratifier tests: Run the full MeasureStratifierTest suite and verify no regressions in existing cohort, proportion, and ratio stratifier scenarios (criteria-based, value-based, multi-component).

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 1, 2026

Formatting check succeeded!

lukedegruchy and others added 5 commits April 24, 2026 09:53
Wrap CqlException from unresolvable stratifier expressions in a new
StratifierExpressionNotFoundException, and validate non-function
expression result types against ALLOWED_STRATIFIER_BOOLEAN_BASIS_TYPES
by removing the blanket NON_SUBJECT_VALUE bypass in
R4PopulationBasisValidator. Both new exceptions are domain
RuntimeExceptions (not HAPI FHIR REST exceptions) per architecture
decision. Add 7 tests covering scalar-only, function-only,
non-existent expression, wrong-type expression, wrong function param
type, and boolean-basis list expression scenarios.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Deduplicate result types in error messages (e.g. [Encounter, Encounter]
becomes [Encounter]) and add expected-type guidance: "Expected a scalar
type" for VALUE stratifiers, "Expected a scalar or scalar-returning
function" for NON_SUBJECT_VALUE. Replace InvalidRequestException with
InvalidStratifierExpressionTypeException in PopulationBasisValidator
where validation logic now lives after upstream refactor. Update
assertions in R4PopulationBasisValidatorTest and MeasureStratifierTest
to match new message format, and ensure both branches of
buildValueStratifierErrorMessage are covered in MeasureStratifierTest.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lukedegruchy lukedegruchy changed the title Add failing tests for invalid non-subject value stratifiers. Fix error handling for non-subject value and value stratifiers Apr 24, 2026
@lukedegruchy lukedegruchy marked this pull request as ready for review April 24, 2026 15:20
@sonarqubecloud
Copy link
Copy Markdown

@JPercival JPercival merged commit 433be04 into main Apr 24, 2026
9 checks passed
@JPercival JPercival deleted the ld-20260401-non-subject-value-stratifier-error-handling-bug branch April 24, 2026 17:40
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.

2 participants