Skip to content

multi-component stratifiers: Ensure FHIR MeasureReport stratumPopulations contain scores and aggregation extensions, including criteriaReference #1005

Merged
JPercival merged 1 commit into
mainfrom
ld-20260422-fix-stratifier-def-to-r4-copy-bug
Apr 23, 2026
Merged

multi-component stratifiers: Ensure FHIR MeasureReport stratumPopulations contain scores and aggregation extensions, including criteriaReference #1005
JPercival merged 1 commit into
mainfrom
ld-20260422-fix-stratifier-def-to-r4-copy-bug

Conversation

@lukedegruchy
Copy link
Copy Markdown
Contributor

@lukedegruchy lukedegruchy commented Apr 22, 2026

Summary

R4MeasureReportBuilder#copyStratifierScores silently failed to copy stratum scores and per-stratum population aggregation extensions (cqfm-criteriaReference, cqfm-aggregateMethod, cqfm-aggregationMethodResult) for any measure using multi-component stratifiers (2+ components in stratifier.component[]). The root cause was matchesStratumValue only checking the top-level stratum.value field, which multi-component strata never populate -- they store data on stratum.component[] entries instead. This fix adds component-aware matching and closes the test coverage gap that allowed the bug to go undetected.

  1. Fix matchesStratumValue to handle multi-component strata: When StratumDef.isComponent() is true, delegate to a new matchesComponentStratumValues method that pairs report components with StratumDef value definitions by code text and value text, with order-independent matching and size validation. The single-value path is unchanged.

  2. Close the test coverage gap: All prior multi-component stratifier tests used COHORT scoring (which never scores strata -- scorer returns null), and all RATIO/PROPORTION tests used single-component stratifiers. Added a new RATIO measure fixture with a 2-component stratifier (Gender + Age) and an integration test that asserts stratum scores and per-stratum population counts -- the exact combination that was missing.

Code Review Suggestions

  • Verify that matchesComponentStratumValues correctly mirrors the "write" side in R4StratifierBuilder#buildStratum: the builder sets component value via expressionResultToCodableConcept(value) which uses StratumValueWrapper.getValueAsString(), and the matcher reads rc.getValue().getText(). Confirm these are always symmetric for all value types the CQL engine can produce (Codes, integers, strings, booleans, etc.).
  • Check whether StratumValueWrapper.getValueAsString() can ever diverge from what expressionResultToCodableConcept stores as CodeableConcept.text. For example, if a CQL expression returns a CodeableConcept (not just a Code), does getValueAsString() return the same thing that gets set as the component value text?
  • The matching uses valueDef.value().getValueAsString() on the StratumDef side, but the report side uses rc.getValue().getText(). These come from different code paths (scorer vs builder). Confirm there is no edge case where one normalizes whitespace, casing, or null/empty differently than the other.
  • The PRP mentions a secondary bug in getStratumDefText for multi-component strata (returns component code text instead of value text for CodeableConcept values). While the fix bypasses getStratumDefText entirely for component matching, verify no other callers depend on getStratumDefText for multi-component strata.

QA Test Suggestions

Setup

  • Deploy a FHIR server with the clinical-reasoning module
  • Load a Measure with RATIO or PROPORTION scoring that has a multi-component stratifier (2+ entries in stratifier.component[]), e.g., stratifying by both Gender and Age
  • Load test Patient and Encounter resources with varied demographics to produce multiple strata
  • Ensure the measurement period covers the encounter dates

Test Cases

  • Multi-component RATIO stratum scores are present: Run $evaluate-measure on the RATIO measure with multi-component stratifier. Inspect the resulting MeasureReport's group.stratifier.stratum entries. Each stratum should have a non-null measureScore value matching the expected numerator/denominator ratio for that stratum's subject subset.
  • Multi-component RATIO stratum populations are correct: In the same MeasureReport, verify that each stratum's population entries (IP, Denominator, Numerator) have correct counts that partition the group-level populations by the stratifier components.
  • Single-component stratifier still works (regression): Run $evaluate-measure on a RATIO measure with a single-component stratifier. Verify stratum scores and population counts are unchanged from prior behavior.
  • COHORT multi-component stratifier still works (regression): Run $evaluate-measure on a COHORT measure with a multi-component stratifier. Verify strata are present with correct population counts (scores should remain absent since COHORT doesn't score strata).
  • RATIO with MEASUREOBSERVATION populations + multi-component stratifier: If applicable, run $evaluate-measure on a RATIO CV measure with multi-component stratifier. Verify that stratum observation populations have cqfm-criteriaReference, cqfm-aggregateMethod, and cqfm-aggregationMethodResult extensions populated.
  • CQIS downstream aggregation (if integrated): After updating the clinical-reasoning dependency in CQIS, run an end-to-end measure evaluation with a multi-component stratifier RCV combo measure. Verify that StratifierReportAggregator no longer encounters null criteriaReference values on stratum observation populations.

matchesStratumValue only checked the top-level stratum.value field,
which is never populated for multi-component strata. Component strata
store data on stratum.component[] entries instead, so matching always
failed and stratum scores were never copied to the MeasureReport.

Add matchesComponentStratumValues that pairs report components with
StratumDef valueDefs by code text and value text, with order-independent
matching and size validation.

Add integration test with a RATIO measure using a 2-component stratifier
(Gender + Age) that asserts stratum scores are present. This was the
exact gap that allowed the bug to go undetected: all prior multi-component
tests used COHORT scoring (which never scores strata), and all RATIO
tests used single-component stratifiers.

Closes: DQM-693

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

Formatting check succeeded!

@lukedegruchy lukedegruchy changed the title Fix copyStratifierScores matching for multi-component stratifiers multi-component stratifiers: Ensure FHIR MeasureReport stratumPopulations contain scores and aggregation extensions, including criteriaReference Apr 22, 2026
@sonarqubecloud
Copy link
Copy Markdown

@lukedegruchy lukedegruchy marked this pull request as ready for review April 22, 2026 21:33
@JPercival JPercival merged commit 463a18e into main Apr 23, 2026
9 checks passed
@JPercival JPercival deleted the ld-20260422-fix-stratifier-def-to-r4-copy-bug branch April 23, 2026 00:27
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