Measure/$evaluate-measure via SQL
FHIR Measure/$evaluate-measure implemented as native PostgreSQL SQL on Aidbox. No CQL engine required. 12 CMS quality measures, 484 test patients, interactive demo.
| Component | Version | Link |
|---|---|---|
| Aidbox | edge | docs.aidbox.app |
| Python / Flask | 3.11 / 3.x | flask.palletsprojects.com |
| Measure/$evaluate-measure | FHIR R4 | hl7.org/fhir/R4 — $evaluate-measure |
| CQL Specification | HL7 | build.fhir.org/ig/HL7/cql |
| CQL measures | dqm-content-qicore-2025 | github.com/cqframework/dqm-content-qicore-2025 — input/cql |
| Test data | dqm-content-qicore-2025 | github.com/cqframework/dqm-content-qicore-2025 — input/tests |
⚑ Solution
A working FHIR R4 Measure/$evaluate-measure implementation on Aidbox, without a CQL engine. Four things to know:
-
12 measures translated from CQL to native PostgreSQL SQL.
Each measure is a single SQL file (
sql/measures/cmsXXX/02-cmsXXX-measure.sql, ~200–400 lines) that replaces the original CQL definition. The translation was verified against the reference MeasureReports from dqm-content-qicore-2025 and independently with the cqframework CQL engine. -
Every supported measure is stored as a FHIR
Measureresource in Aidbox (plus itsLibraryresources), with canonical URL, version, groups and scoring metadata. Loaded viasetup.pyfromdata/measures-bundle.json. You can list them withGET /Measureand look one up by alias, FHIR Resource ID, or canonical URL. -
The operation is exposed on the
Measureresource type — per the FHIR R4 spec the measure is passed as a parameter:POST /Measure/$evaluate-measure?measure=cms130&reportType=population&periodStart=2026-01-01&periodEnd=2026-12-31
Instance-level invocationPOST /Measure/{id}/$evaluate-measureis allowed by the spec but not implemented in this example. -
Response is a FHIR R4
MeasureReportwithgroup.populationcounts (initial-population, denominator, denominator-exclusion, numerator),group.measureScore, and — for single-patient reports —evaluatedResource[]with references to the actual Encounter / Condition / Observation that triggered each population. Output aligns with the DEQMindv-measurereport-deqmprofile.
Supported parameters: measure, subject, reportType (subject | subject-list | population), periodStart, periodEnd. Full details in API Parameters.
Jump to Quick Start to run it in five minutes, or Architecture to see how the request flows end to end.
01 Quick Start
Prerequisites: Docker with Docker Compose, Python 3, curl (optional, for manual API calls).
Build and start the full stack:
docker compose up --build
This starts three containers: PostgreSQL, Aidbox (port 8888), and the SQL evaluate app (port 8090).
02 Activate Aidbox
Open http://localhost:8888 in your browser and follow the activation prompt.
03 Load Data
In a second terminal, from this directory:
python3 setup.py
This creates shared views, loads terminology and clinical data for all 12 measures (~2 min).
04 Evaluate a Measure
Open Aidbox REST Console and evaluate CMS130 for a single patient:
POST /Measure/$evaluate-measure?measure=cms130&subject=Patient/007ec5f1-08cf-474a-a472-f6a92cca4b79&periodStart=2026-01-01&periodEnd=2026-12-31
Summary report for CMS125 Breast Cancer Screening (all patients, read-only):
GET /Measure/$evaluate-measure?measure=cms125&reportType=population
Response — a FHIR MeasureReport with population counts and measure score:
{
"resourceType": "MeasureReport",
"status": "complete",
"type": "individual",
"measure": "http://ecqi.healthit.gov/ecqms/Measure/CMS130FHIRColorectalCancerScrn",
"subject": {
"reference": "Patient/007ec5f1-08cf-474a-a472-f6a92cca4b79"
},
"period": {
"start": "2026-01-01",
"end": "2026-12-31"
},
"group": [{
"population": [
{
"code": { "coding": [{ "code": "initial-population" }] },
"count": 1
},
{
"code": { "coding": [{ "code": "denominator" }] },
"count": 1
},
{
"code": { "coding": [{ "code": "denominator-exclusion" }] },
"count": 1
},
{
"code": { "coding": [{ "code": "numerator" }] },
"count": 0
}
],
"measureScore": {
"value": null
}
}]
}
05 REST Console Examples
Open the Aidbox REST Console at http://localhost:8888/ui/console and paste any of the snippets below. The adapter resolves the measure parameter in three forms: short alias (cms130), FHIR Resource ID (CMS130FHIRColorectalCancerScrn), or canonical URL.
By alias — individual patient
CMS130 Colorectal Cancer Screening for a single patient:
POST /Measure/$evaluate-measure?measure=cms130&subject=Patient/007ec5f1-08cf-474a-a472-f6a92cca4b79
By canonical URL — summary (all patients)
CMS125 Breast Cancer Screening, population-level MeasureReport:
POST /Measure/$evaluate-measure?measure=https://madie.cms.gov/Measure/CMS125FHIRBreastCancerScreen
By alias with explicit period
CMS165 Controlling High Blood Pressure for a single patient, measurement period 2026:
POST /Measure/$evaluate-measure?measure=cms165&subject=Patient/045f7e0b-bfb3-4ee0-a06d-83c853f6a81e&periodStart=2026-01-01&periodEnd=2026-12-31
By FHIR Resource ID — individual patient
CMS131 Diabetes Eye Exam for a single patient:
POST /Measure/$evaluate-measure?measure=CMS131FHIRDiabetesEyeExam&subject=Patient/01a1241d-fd97-4c72-b288-fd31c4c7ae80
subject for a summary report across all patients. Omit periodStart/periodEnd to use defaults (2026-01-01 to 2026-12-31).
06 Demo App
Start a local HTTP server from this directory:
python3 -m http.server 3000
Then open the demo in your browser:
→ http://localhost:3000/demo/app.html
The demo shows all 12 measures with:
- Patient-level care gaps
- Step-by-step decision chains with evidence
- Measure scores (population-level)
$sql endpoint directly for fast batch evaluation across all patients.
Included Measures
12 measures 484 patients
CMS130
CMS125
CMS131
CMS165
CMS124
CMS139
CMS75
CMS1154
CMS149
CMS153
CMS155
CMS143
Aliases & Canonical URLs
Each measure can be referenced by short alias, FHIR Resource ID, or canonical URL in the measure parameter. Click any value to copy.
| Alias | FHIR Resource ID | Canonical URL |
|---|---|---|
cms130 | CMS130FHIRColorectalCancerScrn | https://madie.cms.gov/Measure/CMS130FHIRColorectalCancerScrn |
cms125 | CMS125FHIRBreastCancerScreen | https://madie.cms.gov/Measure/CMS125FHIRBreastCancerScreen |
cms131 | CMS131FHIRDiabetesEyeExam | https://madie.cms.gov/Measure/CMS131FHIRDiabetesEyeExam |
cms165 | CMS165FHIRControllingHighBP | https://madie.cms.gov/Measure/CMS165FHIRControllingHighBP |
cms124 | CMS124FHIRCervicalCancerScreen | https://madie.cms.gov/Measure/CMS124FHIRCervicalCancerScreen |
cms139 | CMS139FHIRFallRiskScreening | https://madie.cms.gov/Measure/CMS139FHIRFallRiskScreening |
cms75 | CMS75FHIRChildrenDentalDecay | https://madie.cms.gov/Measure/CMS75FHIRChildrenDentalDecay |
cms1154 | CMS1154ScreeningPrediabetesFHIR | https://madie.cms.gov/Measure/CMS1154ScreeningPrediabetesFHIR |
cms149 | CMS149FHIRDementiaCognitiveAssess | https://madie.cms.gov/Measure/CMS149FHIRDementiaCognitiveAssess |
cms153 | CMS153FHIRChlamydiaScreening | https://madie.cms.gov/Measure/CMS153FHIRChlamydiaScreening |
cms155 | CMS155FHIRWeightAssessment | https://madie.cms.gov/Measure/CMS155FHIRWeightAssessment |
cms143 | CMS143FHIRPOAGOpticNerveEval | https://madie.cms.gov/Measure/CMS143FHIRPOAGOpticNerveEval |
⚙ Architecture
End-to-end request flow for one Measure/$evaluate-measure call:
?measure=cms130&reportType=population
+ evaluatedResource[] with Encounter/Condition refs
Inside the database, the SQL flows through four stable layers:
Each measure is a single SQL file (~200–400 lines) that uses shared flat views and a concepts table for offline terminology matching. No CQL engine, no Java, no external terminology service calls at runtime.
How it works
- Client sends
POST /Measure/$evaluate-measure?measure=cms130&subject=Patient/123 - Aidbox looks up
App/com.sql.evaluate.appand forwards the request to the Flask app on port8090 - Flask reads the measure SQL file (
sql/measures/cmsXXX/02-cmsXXX-measure.sql), substitutes the measurement period, POSTs to Aidbox/$sql - PostgreSQL runs the query: JSONB → flat views → concepts join → measure-specific CTEs (IP, exclusions, numerator)
- Flask aggregates the per-patient rows into populations and builds a FHIR R4 MeasureReport
- For single-patient reports, Flask also runs an evidence query and populates
evaluatedResource[]with real resource references
⚙ API Parameters
| Parameter | Status | Description |
|---|---|---|
measure | required | Short alias (cms130), FHIR Resource ID (CMS130FHIRColorectalCancerScrn), or canonical URL (https://madie.cms.gov/Measure/CMS130FHIRColorectalCancerScrn). |
subject | supported | Patient reference (e.g., Patient/123). Omit for summary report. |
periodStart | supported | Measurement period start. Default: 2026-01-01. |
periodEnd | supported | Measurement period end. Default: 2026-12-31. |
reportType | supported | subject (default when subject is provided), subject-list, or population (default when no subject). |
practitioner | not supported | Practitioner-scoped evaluation is out of scope for this example. |
lastReceivedOn | not supported | Incremental reporting is out of scope for this example. |
5 of 7 FHIR R4 $evaluate-measure parameters supported. See the R4 OperationDefinition for the full spec.
■ Shutdown
Stop all containers (data preserved in Docker volume):
docker compose down
Or stop and delete all data (clean slate for next run):
docker compose down -v