Runnable FHIR artifacts for four measure scoring types — proportion, ratio, continuous-variable, and cohort — built on deliberately non-clinical, resource-existence CQL so the focus stays on population membership mechanics rather than clinical logic.
All four measures evaluate one shared set of nine patients
(shared/Bundle-patients.json). Each expected/ folder holds the summary
MeasureReport a conformant engine should produce for that measure over that bundle.
shared/
Bundle-patients.json the nine patients — the single input for every measure
proportion/ ratio/ continuous-variable/ cohort/
IGProportion.cql IGRatio.cql IGContinuousVariable.cql IGCohort.cql
Library-*.json Library-*.json Library-*.json Library-*.json
Measure-*.json Measure-*.json Measure-*.json Measure-*.json
expected/ expected/ expected/ expected/
MeasureReport-*-summary.json (one summary report per measure)
individual/ (one report per patient: -ada … -ivy)
MeasureReport-*-<patient>.json
INDEX.md overview.svg matrices.md README.md (top-level map and docs)
gen.py (regenerates the bundle, measures, expected reports, matrices)
Earlier drafts gave each measure its own patient bundle, which reused ids like
Patient/ada across folders — loading two of them into the same FHIR server made the
second overwrite the first, and evaluating one measure over a server holding all of
them would count patients meant for other measures. A single shared roster removes
both problems: one Patient per id, and one intended population that every measure
scores its own way.
Profiles over the resources the measures retrieve (E=Encounter, Pr=Procedure,
D=deceased, O=Observation count, R=ResearchSubject, C=Condition,
Im=Immunization). Each patient is chosen to fire a distinct membership rule in one
or more measures — the interesting cases are where a criterion evaluates true but the
patient is not counted.
| id | E | Pr | D | O | R | C | Im | notable roles |
|---|---|---|---|---|---|---|---|---|
| ada | 1 | 1 | 0 | 2 | 0 | 0 | 1 | clean member everywhere |
| ben | 1 | 1 | 0 | 0 | 0 | 0 | 0 | denominator/measure-pop member with no numerator; CV value 0 |
| cyd | 0 | 1 | 0 | 9 | 0 | 0 | 1 | no Encounter → gated out of proportion/CV; numerator with no denominator in ratio |
| dev | 1 | 1 | 1 | 4 | 0 | 0 | 1 | deceased → excluded (prop/CV); still counts in the ratio numerator |
| eve | 1 | 1 | 0 | 1 | 1 | 0 | 1 | numerator-excluded via ResearchSubject (prop/ratio) |
| fin | 1 | 1 | 0 | 0 | 0 | 1 | 0 | denominator exception (prop); denominator-only (ratio); CV value 0 |
| gus | 1 | 1 | 0 | 3 | 0 | 1 | 1 | met numerator, so the Condition-driven exception is suppressed (prop) |
| hana | 0 | 0 | 1 | 0 | 0 | 0 | 0 | deceased but never in a denominator → exclusion criterion gated |
| ivy | 1 | 0 | 0 | 7 | 0 | 0 | 0 | Encounter but no Procedure → initial-population-only (prop/CV) |
- Proportion — IPP 7, DENOM 6, DENEX 1, NUMER 3, NUMEX 1, DENEXCEP 1; score = (3 − 1) / (6 − 1 − 1) = 0.5
- Ratio — IPP 1 = 7, DENOM 6, DENEX 1, IPP 2 = 5, NUMER 5, NUMEX 1; ratio = (5 − 1) / (6 − 1) = 0.8 (numerator and denominator counted independently)
- Continuous variable — IPP 7, MPOP 6, MPOPEX 1; observations [0, 0, 1, 2, 3]; median = 1 (sum 6, mean 1.2, count 5)
- Cohort — initial-population 7; the score is that count
See matrices.md for the per-patient breakdown behind each of these.
Each measure folder also has expected/individual/ with one type: individual
MeasureReport per patient (MeasureReport-<measure>-<patient>.json). Each is that
patient's row from matrices.md expressed as FHIR: subject points to the Patient,
and every group.population carries a count of 0 or 1 for that patient's membership.
Summed across the nine individual reports, the population counts equal the summary
report — that is the check these are built to support.
Conventions used, since individual measureScore handling varies by engine:
- Proportion —
measureScoreis included only for patients in the scored denominator:1if they meet the numerator,0otherwise. Patients who are out of scope, excluded, or excepted carry population counts but nomeasureScore. - Continuous variable — scored patients carry the measure observation as a
containedObservation (referenced fromevaluatedResource) and asgroup.measureScore; non-members havemeasure-observationcount 0 and no score. - Ratio, cohort — population memberships only, no individual
measureScore(a ratio has no clean per-subject score; a cohort individual is just membership).
The two ratio initial populations are disambiguated by the population element id
(ipp-1 for the denominator branch, ipp-2 for the numerator branch) in both the
summary and individual reports.
Every MeasureReport in this package — summary and individual — is marked as a test
case with two CQF Measures IG extensions, so anyone opening one sees that it's an
expected-outcome fixture and what it exercises:
cqfm-isTestCase(a boolean modifier extension, so it lives inmodifierExtension) set totrue.cqfm-testCaseDescription(markdown, inextension) describing that specific case — e.g. a proportion individual report for a deceased patient explains that the Numerator is gated out by the exclusion even though its criterion evaluates true.
Both are from http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/, matching the
cqfm- extensions used on the Measure resources. If you are targeting the universal
hl7.fhir.uv.cqm IG instead, the equivalents are cqm-isTestCase and
cqm-testCaseDescription — change the two URLs in gen.py.
The Library resources carry the CQL as text/cql content. Most execution engines
(fqm-execution, cqf-ruler, the reference CQL engine) additionally need compiled
ELM in Library.content — the one step these files don't pre-bake, because ELM
must come from the translator.
-
Compile each CQL to ELM with the CQL-to-ELM translator (the
cql-translation-service, thetranslator-clijar, orfqm-execution's bundled translator), then add anapplication/elm+jsonentry alongside the existingtext/cqlentry in each Library. -
Assemble a measure bundle per scoring type (Measure + Library, plus
FHIRHelpers). -
Calculate against the shared patient bundle. With fqm-execution:
fqm-execution calculate \ --measure-bundle measure-bundle.json \ --patient-bundle shared/Bundle-patients.json \ --report-type summaryOr POST
shared/Bundle-patients.jsonto a FHIR server and callMeasure/$evaluate-measure. -
Diff the returned summary
MeasureReport.group.population[].countandmeasureScoreagainst the file in that folder'sexpected/.
Re-run python3 gen.py to regenerate the bundle, all four measures, the expected
reports, and matrices.md from the single ROSTER definition, so they can't drift.
- Evaluation scope. Each measure counts every patient in the shared bundle — that
is the intent here (one population, four scorings). To score a subset on a shared
server, evaluate with a
subject/Groupparameter. Numerator ExclusionusesResearchSubject. A patient's participation in a study is aResearchSubject, which links to the patient viaindividual, soexists([ResearchSubject])resolves correctly in Patient context. It affects onlyeve. The bundle includes one sharedResearchStudy(cqm-example-study) for eachResearchSubject.studyto reference, since that element is required in R4.- Aggregate-method code casing. The continuous-variable measure uses
valueCode: "Median"; confirm the casing your engine expects against its boundaggregate-methodvalue set. - Gating precedence (exclusion before numerator, numerator before exception) is the reference-engine behavior, not spelled out in the IG prose — worth stating explicitly wherever you reuse these.
- Cohort
measureScore. The expected cohort report setsmeasureScoreto the initial-population count (7), since the cohort score is the member count. Some engines report only theinitial-populationcount and leavemeasureScoreunset — treat the count as authoritative.