Skip to content

Surface empty stratifiers as contained OperationOutcomes instead of failing the Measure#1021

Draft
lukedegruchy wants to merge 1 commit into
mainfrom
ld-20260430-empty-stratifier-error-operation-outcome
Draft

Surface empty stratifiers as contained OperationOutcomes instead of failing the Measure#1021
lukedegruchy wants to merge 1 commit into
mainfrom
ld-20260430-empty-stratifier-error-operation-outcome

Conversation

@lukedegruchy
Copy link
Copy Markdown
Contributor

@lukedegruchy lukedegruchy commented Apr 30, 2026

Summary

Follow-up to #1019. The empty-stratifier validation introduced in that PR
threw InvalidMeasureDefinitionException from R4MeasureDefBuilder,
which aborted the entire $evaluate-measure call before any populations
were calculated. For real-world MADIE exports that contain one
malformed stratifier alongside otherwise-valid populations, this meant
the consumer got nothing usable back. This branch downgrades the
empty-stratifier case to a non-fatal report-level error: the rest of
the measure evaluates as normal and each offending stratifier produces
its own contained OperationOutcome on the returned MeasureReport.

  1. R4MeasureDefBuilder no longer throws when a stratifier has no
    criteria.expression and no components. It builds a placeholder
    StratifierDef (null type, no components) and records the error,
    which is later attached to the MeasureDef so the existing
    contained-OperationOutcome pipeline picks it up. The placeholder
    evaluates to a no-op on both the evaluator and the report builder
    while preserving the 1:1 ordering with Measure.group.stratifier.
  2. R4MeasureReportBuilderContext.addOperationOutcomes now assigns
    each generated OperationOutcome a distinct id (operation-outcome-N)
    so that multiple errors are no longer collapsed by addContained's
    putIfAbsent dedup-by-id.
  3. The now-unused InvalidMeasureDefinitionException class is removed.
  4. Test fixtures consolidate onto the existing LibrarySimple
    library/CQL — no separate EmptyStratifier.cql or library JSON,
    no enhancements to LibrarySimple.cql were needed since all
    referenced defines already exist.

…ailing the Measure

Follow-up to #1019. Previously, R4MeasureDefBuilder.getStratifierType
threw InvalidMeasureDefinitionException when a stratifier had no
criteria.expression and no components, aborting the entire Measure
evaluation. Now the rest of the Measure evaluates and the offending
stratifiers are reported as contained OperationOutcomes on the
MeasureReport.

Build-time changes (R4MeasureDefBuilder):
- Detect empty stratifiers in buildStratifierDef and emit a placeholder
  StratifierDef with a null type and no components. The placeholder is
  a no-op for the evaluator (empty components list) and the report
  builder (no strata produced), and keeps the 1:1 ordering with
  Measure.group.stratifier that R4MeasureReportBuilder.buildGroup
  asserts on.
- Collect a non-fatal error per offending stratifier in a buildErrors
  list on the builder, then push them onto MeasureDef.errors after the
  MeasureDef is constructed so the existing OperationOutcome pipeline
  picks them up.
- Drop the now-unused InvalidMeasureDefinitionException class.

Report-builder change (R4MeasureReportBuilderContext):
- addContained deduplicates by fhirType()/idPart, and freshly-built
  OperationOutcomes all collapsed to "OperationOutcome/", so only the
  first error ever made it into the report. addOperationOutcomes now
  assigns each OperationOutcome a distinct id (operation-outcome-N)
  so every MeasureDef error surfaces as its own contained resource.

Test changes:
- The regression test now reuses LibrarySimple.cql/LibrarySimple.json
  (no enhancements needed — all referenced defines already exist) and
  the standalone EmptyStratifier.cql and EmptyStratifier Library JSON
  are removed.
- MeasureStratifierTest.emptyStratifier asserts a contained
  OperationOutcome for both stratifier-1 and stratifier-2, that the
  group still has both stratifier slots, and that Initial Population
  evaluates over the expected 10 patients.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Formatting check succeeded!

@lukedegruchy lukedegruchy changed the title Surface empty stratifiers as contained OperationOutcomes instead of f… Surface empty stratifiers as contained OperationOutcomes instead of failing the Measure Apr 30, 2026
@lukedegruchy lukedegruchy marked this pull request as ready for review April 30, 2026 20:40
@sonarqubecloud
Copy link
Copy Markdown

@lukedegruchy lukedegruchy marked this pull request as draft April 30, 2026 21:01
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.

1 participant