Skip to content

Commit 2c2a0ad

Browse files
author
Alexandra Pavlyshina
committed
Add cql-engine example: Library/$evaluate with cqframework 4.5.0
1 parent f25f53a commit 2c2a0ad

35 files changed

Lines changed: 201505 additions & 0 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
target/
2+
.idea/
3+
*.iml
4+
*.class
5+
.DS_Store
6+
.env
7+
*.log
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Build stage
2+
FROM maven:3.8-openjdk-17 AS build
3+
WORKDIR /app
4+
COPY pom.xml .
5+
# Download dependencies first (cacheable layer)
6+
RUN mvn dependency:resolve -q || true
7+
COPY src ./src
8+
RUN mvn clean package -DskipTests
9+
10+
# Run stage
11+
FROM eclipse-temurin:17-jre-jammy
12+
WORKDIR /app
13+
COPY --from=build /app/target/*.jar app.jar
14+
EXPOSE 8080
15+
ENTRYPOINT ["java", "-jar", "app.jar"]
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
---
2+
features: [CQL, Custom operations, Spring Boot, FHIR operations, Clinical decision support, Quality measures]
3+
languages: [Java, Kotlin]
4+
---
5+
# Aidbox CQL Integration with Spring Boot
6+
7+
This Spring Boot application integrates the [cqframework CQL engine](https://github.com/cqframework/clinical_quality_language) with Aidbox and implements the [Library/$evaluate](https://build.fhir.org/ig/HL7/cql-ig/OperationDefinition-cql-library-evaluate.html) operation.
8+
9+
Includes sample data and CQL libraries for 4 CMS quality measures: CMS130, CMS125, CMS131, CMS165.
10+
11+
## Stack
12+
13+
| Component | Version |
14+
|---|---|
15+
| CQL engine | cqframework 4.5.0 (Kotlin Multiplatform) |
16+
| HAPI FHIR | 8.8.0 |
17+
| Spring Boot | 3.4.1 |
18+
| Aidbox | edge |
19+
20+
## Prerequisites
21+
22+
- [Docker](https://docs.docker.com/get-docker/) with Docker Compose
23+
- [curl](https://curl.se/)
24+
## Quick Start
25+
26+
### 1. Start Aidbox and the CQL app
27+
28+
```
29+
docker compose up --build
30+
```
31+
32+
### 2. Activate Aidbox
33+
34+
Open http://localhost:8888 in your browser and activate the Aidbox instance.
35+
36+
### 3. Verify with the simple example
37+
38+
Create a sample patient:
39+
40+
```bash
41+
curl -u root:secret -X POST http://localhost:8888/fhir/Patient \
42+
-H "Content-Type: application/json" \
43+
-d '{"resourceType":"Patient","gender":"male","name":[{"family":"Smith"}]}'
44+
```
45+
46+
Evaluate the `example` CQL library:
47+
48+
```bash
49+
curl -u root:secret -X POST http://localhost:8888/Library/example/\$evaluate
50+
```
51+
52+
Expected response:
53+
```json
54+
{
55+
"resourceType": "Parameters",
56+
"parameters": [
57+
{ "name": "MalePatients", "valueHumanName": { "family": "Smith" } }
58+
]
59+
}
60+
```
61+
62+
## Running CMS Quality Measures
63+
64+
### 4. Load shared terminology (once)
65+
66+
```bash
67+
curl -u root:secret -X POST http://localhost:8888/fhir \
68+
-H "Content-Type: application/json" -d @data/codesystems-bundle.json
69+
```
70+
71+
Create stub resources (required by test data references):
72+
73+
```bash
74+
curl -u root:secret -X PUT http://localhost:8888/fhir/Organization/example \
75+
-H "Content-Type: application/json" \
76+
-d '{"resourceType":"Organization","id":"example","name":"Example Organization"}'
77+
78+
curl -u root:secret -X PUT http://localhost:8888/fhir/Practitioner/example \
79+
-H "Content-Type: application/json" \
80+
-d '{"resourceType":"Practitioner","id":"example","name":[{"family":"Example","given":["Practitioner"]}]}'
81+
```
82+
83+
### 5. Load a measure and evaluate
84+
85+
**CMS130 — Colorectal Cancer Screening** (64 test patients):
86+
87+
```bash
88+
# Load ValueSets and clinical data
89+
curl -u root:secret -X POST http://localhost:8888/fhir \
90+
-H "Content-Type: application/json" -d @data/cms130-valuesets.json
91+
curl -u root:secret -X POST http://localhost:8888/fhir \
92+
-H "Content-Type: application/json" -d @data/cms130-clinical-data.json
93+
94+
# Evaluate for a patient
95+
curl -u root:secret -X POST \
96+
'http://localhost:8888/Library/CMS130FHIRColorectalCancerScrn/$evaluate?patientId=007ec5f1-08cf-474a-a472-f6a92cca4b79'
97+
```
98+
99+
Expected response:
100+
```json
101+
{
102+
"resourceType": "Parameters",
103+
"parameters": [
104+
{ "name": "Initial Population", "valueBoolean": true },
105+
{ "name": "Denominator", "valueBoolean": true },
106+
{ "name": "Denominator Exclusions", "valueBoolean": true },
107+
{ "name": "Numerator", "valueBoolean": false }
108+
]
109+
}
110+
```
111+
112+
**CMS125 — Breast Cancer Screening** (66 test patients):
113+
114+
```bash
115+
curl -u root:secret -X POST http://localhost:8888/fhir \
116+
-H "Content-Type: application/json" -d @data/cms125-valuesets.json
117+
curl -u root:secret -X POST http://localhost:8888/fhir \
118+
-H "Content-Type: application/json" -d @data/cms125-clinical-data.json
119+
120+
curl -u root:secret -X POST \
121+
'http://localhost:8888/Library/CMS125FHIRBreastCancerScreen/$evaluate?patientId=01c88972-84e2-4594-835b-924481b9990a'
122+
```
123+
124+
**CMS131 — Diabetes Eye Exam** (63 test patients):
125+
126+
```bash
127+
curl -u root:secret -X POST http://localhost:8888/fhir \
128+
-H "Content-Type: application/json" -d @data/cms131-valuesets.json
129+
curl -u root:secret -X POST http://localhost:8888/fhir \
130+
-H "Content-Type: application/json" -d @data/cms131-clinical-data.json
131+
132+
curl -u root:secret -X POST \
133+
'http://localhost:8888/Library/CMS131FHIRDiabetesEyeExam/$evaluate?patientId=89073685-3807-41f5-bc32-2cf44c1b8227'
134+
```
135+
136+
**CMS165 — Controlling High Blood Pressure** (68 test patients):
137+
138+
```bash
139+
curl -u root:secret -X POST http://localhost:8888/fhir \
140+
-H "Content-Type: application/json" -d @data/cms165-valuesets.json
141+
curl -u root:secret -X POST http://localhost:8888/fhir \
142+
-H "Content-Type: application/json" -d @data/cms165-clinical-data.json
143+
144+
curl -u root:secret -X POST \
145+
'http://localhost:8888/Library/CMS165FHIRControllingHighBP/$evaluate?patientId=45e01fed-56bb-483d-a860-af3d566bda11'
146+
```
147+
148+
## How It Works
149+
150+
```
151+
Client → POST /Library/{name}/$evaluate?patientId={id}
152+
→ Aidbox (custom operation routing via App resource)
153+
→ Spring Boot CQL app (port 8080)
154+
→ cqframework engine
155+
→ reads CQL from classpath
156+
→ retrieves FHIR data from Aidbox
157+
→ expands ValueSets via Aidbox $expand
158+
← FHIR Parameters response
159+
← proxied back
160+
← returned to client
161+
```
162+
163+
## Adding Your Own CQL Library
164+
165+
1. Place `.cql` file in `src/main/resources/`
166+
2. Rebuild: `docker compose build cql-app && docker compose up -d cql-app`
167+
3. Evaluate: `POST /Library/{library-name}/$evaluate`
168+
169+
## Known Limitations
170+
171+
- **ToConcept(Quantity) bug** — measures using `USCoreBMIProfile` with value comparison fail with `Could not resolve call to operator 'ToConcept'`. This is an upstream cqframework issue ([#564](https://github.com/cqframework/clinical_quality_language/issues/564)).
172+
173+
## Aidbox-Specific Adaptations
174+
175+
These adaptations are needed because Aidbox handles terminology differently from HAPI FHIR server (the reference test environment for cqframework):
176+
177+
| What | Why |
178+
|---|---|
179+
| `FullExpandTerminologyWrapper` | Aidbox `$expand` may return incomplete results without explicit `count` parameter. Wrapper adds `count=10000`. |
180+
| `maxCodesPerQuery=64` | Large ValueSets cause HTTP 414 (URI Too Long) when codes are inlined in search URLs |
181+
| QICore data provider registration | CMS measures use QICore profiles. Engine looks up data by model URI — without registering `http://hl7.org/fhir/us/qicore`, it silently returns no data. |
182+
| kotlinx-io bridge | CQL engine 4.x (Kotlin) uses `kotlinx.io.Source` instead of `java.io.InputStream` for loading CQL files. Bridge code converts between the two. |
183+
| `buildResponse()` | CQL engine returns Java objects (`Map<String, ExpressionResult>`). This method serializes them into a FHIR Parameters JSON response. |
184+
| `BOX_FHIR_TERMINOLOGY_ENGINE: hybrid` | Aidbox requires explicit terminology engine configuration for `$expand` |
185+
| Stub `Organization/example` + `Practitioner/example` | Test data Coverage and MedicationRequest resources reference these |
186+
| ValueSet `description` + `valueDate` fix | Aidbox requires `description` (FHIR says optional) and rejects `valueDate` extensions |
187+
188+
## Test Data Source
189+
190+
Clinical data and ValueSets come from [dqm-content-qicore-2025](https://github.com/cqframework/dqm-content-qicore-2025), the official test suite for CQL quality measures.

0 commit comments

Comments
 (0)