Skip to content

CQL Python API

This document describes the Python API for evaluating CQL (Clinical Quality Language) expressions and libraries.

Quick Start

from fhirkit.engine.cql import CQLEvaluator

# Create evaluator
evaluator = CQLEvaluator()

# Evaluate a simple expression
result = evaluator.evaluate_expression("1 + 2 * 3")
print(result)  # 7

# Compile and run a library
lib = evaluator.compile("""
    library Example version '1.0'
    define Sum: 1 + 2 + 3
    define Greeting: 'Hello, CQL!'
""")

# Evaluate definitions
sum_result = evaluator.evaluate_definition("Sum")  # 6
greeting = evaluator.evaluate_definition("Greeting")  # 'Hello, CQL!'

CQLEvaluator

The main class for CQL evaluation.

Constructor

from fhirkit.engine.cql import CQLEvaluator

evaluator = CQLEvaluator(
    data_source=None,       # Optional DataSource for retrieve operations
    library_manager=None    # Optional LibraryManager for library dependencies
)

Methods

compile(source: str) -> CQLLibrary

Compile CQL source code into a library.

lib = evaluator.compile("""
    library MyLibrary version '1.0'
    using FHIR version '4.0.1'

    define Sum: 1 + 2 + 3
    define Product: 4 * 5
""")

print(lib.name)     # 'MyLibrary'
print(lib.version)  # '1.0'

evaluate_expression(expression, resource=None, parameters=None) -> Any

Evaluate a CQL expression directly.

# Simple expression
result = evaluator.evaluate_expression("1 + 2 * 3")

# With patient resource
patient = {"resourceType": "Patient", "birthDate": "1990-05-15"}
result = evaluator.evaluate_expression(
    "years between @1990-05-15 and Today()",
    resource=patient
)

# With parameters
result = evaluator.evaluate_expression(
    "X + Y",
    parameters={"X": 10, "Y": 20}
)

evaluate_definition(name, resource=None, parameters=None, library=None) -> Any

Evaluate a named definition from a library.

# Compile library first
lib = evaluator.compile("""
    library Test
    define Sum: 1 + 2 + 3
    define Double: Sum * 2
""")

# Evaluate specific definition
result = evaluator.evaluate_definition("Sum")  # 6
result = evaluator.evaluate_definition("Double")  # 12

# With patient context
patient = {"resourceType": "Patient", "birthDate": "1990-05-15"}
result = evaluator.evaluate_definition("PatientAge", resource=patient)

evaluate_all_definitions(resource=None, parameters=None, library=None) -> dict

Evaluate all definitions in a library.

lib = evaluator.compile("""
    library Test
    define A: 1
    define B: 2
    define C: A + B
""")

results = evaluator.evaluate_all_definitions()
# {'A': 1, 'B': 2, 'C': 3}

load_library(name, version=None) -> CQLLibrary

Load a previously compiled library.

# After compiling
evaluator.compile("library MyLib ...")

# Later load by name
lib = evaluator.load_library("MyLib")

get_definitions(library=None) -> list[str]

Get list of definition names.

lib = evaluator.compile("""
    library Test
    define A: 1
    define B: 2
""")

names = evaluator.get_definitions()  # ['A', 'B']

get_parameters(library=None) -> dict

Get parameter definitions and defaults.

lib = evaluator.compile("""
    library Test
    parameter X Integer default 10
    parameter Y String
""")

params = evaluator.get_parameters()
# {'X': 10, 'Y': None}

CQLLibrary

Represents a compiled CQL library.

Properties

lib.name           # Library name
lib.version        # Library version
lib.using          # List of UsingDefinition
lib.includes       # List of IncludeDefinition
lib.codesystems    # Dict of CodeSystemDefinition
lib.valuesets      # Dict of ValueSetDefinition
lib.codes          # Dict of CodeDefinition
lib.concepts       # Dict of ConceptDefinition
lib.parameters     # Dict of ParameterDefinition
lib.definitions    # Dict of ExpressionDefinition
lib.functions      # Dict of FunctionDefinition
lib.contexts       # List of context names

Methods

# Get a specific definition
defn = lib.get_definition("Sum")

# Get a function
func = lib.get_function("MyFunc")

# Resolve a code reference
code = lib.resolve_code("DiabetesCode")

Convenience Functions

compile_library(source) -> CQLLibrary

Quick way to compile a library.

from fhirkit.engine.cql import compile_library

lib = compile_library("""
    library Test
    define Sum: 1 + 2 + 3
""")

evaluate(expression, resource=None) -> Any

Quick way to evaluate an expression.

from fhirkit.engine.cql import evaluate

result = evaluate("1 + 2 * 3")  # 7
result = evaluate("Today()")    # Current date

Working with Patient Data

Basic Patient Context

evaluator = CQLEvaluator()

lib = evaluator.compile("""
    library PatientLib version '1.0'
    using FHIR version '4.0.1'

    context Patient

    define PatientAge:
        years between Patient.birthDate and Today()

    define IsAdult:
        PatientAge >= 18

    define PatientGender:
        Patient.gender
""")

patient = {
    "resourceType": "Patient",
    "birthDate": "1990-05-15",
    "gender": "male",
    "name": [{"family": "Smith", "given": ["John"]}]
}

age = evaluator.evaluate_definition("PatientAge", resource=patient)
is_adult = evaluator.evaluate_definition("IsAdult", resource=patient)
gender = evaluator.evaluate_definition("PatientGender", resource=patient)

With Parameters

lib = evaluator.compile("""
    library MeasureLib

    parameter "Measurement Period" Interval<DateTime>
    parameter "Age Threshold" Integer default 18

    define IsAdultInPeriod:
        PatientAge >= "Age Threshold"
""")

from datetime import datetime

params = {
    "Measurement Period": {
        "low": datetime(2024, 1, 1),
        "high": datetime(2024, 12, 31)
    },
    "Age Threshold": 21
}

result = evaluator.evaluate_definition(
    "IsAdultInPeriod",
    resource=patient,
    parameters=params
)

Built-in FHIRHelpers Library

FHIRKit includes a built-in FHIRHelpers library that is automatically available without any configuration. FHIRHelpers provides standard conversion functions from FHIR types to CQL types.

Basic Usage

Simply include FHIRHelpers in your CQL library:

library MyLibrary version '1.0'
using FHIR version '4.0.1'
include FHIRHelpers version '4.0.1'

context Patient

define LatestWeight:
    (Last([Observation] O where O.code.coding.code = '29463-7' sort by effective)).value

Available Functions

FHIRHelpers provides these conversion functions:

Type Conversion Functions

Function Description
ToQuantity(FHIR.Quantity) Convert FHIR Quantity to CQL Quantity
ToCode(FHIR.Coding) Convert FHIR Coding to CQL Code
ToConcept(FHIR.CodeableConcept) Convert FHIR CodeableConcept to CQL Concept
ToInterval(FHIR.Period) Convert FHIR Period to Interval<DateTime>
ToInterval(FHIR.Range) Convert FHIR Range to Interval<Quantity>

Primitive Conversion Functions

Function Description
ToString(FHIR.string) Extract string value
ToString(FHIR.code) Extract code as string
ToString(FHIR.uri) Extract URI as string
ToString(FHIR.id) Extract ID as string
ToDateTime(FHIR.dateTime) Convert to CQL DateTime
ToDateTime(FHIR.instant) Convert instant to CQL DateTime
ToDate(FHIR.date) Convert to CQL Date
ToTime(FHIR.time) Convert to CQL Time
ToBoolean(FHIR.boolean) Extract boolean value
ToInteger(FHIR.integer) Extract integer value
ToInteger(FHIR.positiveInt) Extract positive integer
ToInteger(FHIR.unsignedInt) Extract unsigned integer
ToDecimal(FHIR.decimal) Extract decimal value

Examples

Converting Quantities

library QuantityExample version '1.0'
using FHIR version '4.0.1'
include FHIRHelpers version '4.0.1'

context Patient

define LatestWeightObs:
    Last([Observation] O
        where O.code.coding.code = '29463-7'
        sort by effective)

// Use ToQuantity to convert FHIR Quantity
define LatestWeight:
    FHIRHelpers.ToQuantity(LatestWeightObs.value as FHIR.Quantity)

// Compare quantities
define IsOverweight:
    LatestWeight > 100 'kg'

Converting Codes and Concepts

library CodeExample version '1.0'
using FHIR version '4.0.1'
include FHIRHelpers version '4.0.1'

context Patient

define Conditions: [Condition]

// Convert CodeableConcept to Concept for comparison
define DiabetesConditions:
    Conditions C
        where FHIRHelpers.ToConcept(C.code) ~ Concept {
            codes: { Code '44054006' from "SNOMED" }
        }

Converting Periods to Intervals

library PeriodExample version '1.0'
using FHIR version '4.0.1'
include FHIRHelpers version '4.0.1'

context Patient

define Encounters: [Encounter]

// Convert FHIR Period to CQL Interval for date operations
define RecentEncounters:
    Encounters E
        where FHIRHelpers.ToInterval(E.period) overlaps Interval[Today() - 30 days, Today()]

Using Aliases

You can use an alias for shorter references:

library AliasExample version '1.0'
using FHIR version '4.0.1'
include FHIRHelpers version '4.0.1' called FH

context Patient

define Weight: FH.ToQuantity(...)

Without FHIRHelpers

If you need to disable the automatic loading of FHIRHelpers (e.g., to provide your own version):

from fhirkit.engine.cql import CQLEvaluator

# Disable built-in libraries
evaluator = CQLEvaluator(include_builtins=False)

# Now you must provide FHIRHelpers yourself if needed

Python API

The built-in resolver can be accessed directly:

from fhirkit.engine.cql.builtins import get_builtin_resolver

# Get the resolver with FHIRHelpers loaded
resolver = get_builtin_resolver()

# Check available libraries
source = resolver.resolve("FHIRHelpers", "4.0.1")
print(source)  # FHIRHelpers CQL source

CQL Types

Intervals

from fhirkit.engine.cql.types import CQLInterval

# Create interval programmatically
interval = CQLInterval(low=1, high=10, low_closed=True, high_closed=True)

# Evaluate interval expressions
result = evaluator.evaluate_expression("Interval[1, 10] contains 5")  # True
result = evaluator.evaluate_expression("5 in Interval[1, 10]")  # True

Interval Property Access

Access interval bounds using dot notation:

// Get boundaries
Interval[1, 10].low          // 1
Interval[1, 10].high         // 10

// Check if bounds are closed/open
Interval[1, 10].lowClosed    // true (closed with [)
Interval[1, 10).highClosed   // false (open with ))

// Alternative: start/end operators
start of Interval[1, 10]     // 1
end of Interval[1, 10]       // 10
width of Interval[1, 10]     // 9

Interval Operations

Operation Example Result
Contains Interval[1, 10] contains 5 true
In 5 in Interval[1, 10] true
Overlaps Interval[1, 5] overlaps Interval[3, 8] true
Includes Interval[1, 10] includes Interval[3, 7] true
Before Interval[1, 3] before Interval[5, 10] true
After Interval[5, 10] after Interval[1, 3] true
Meets Interval[1, 3] meets Interval[4, 10] true
Union Interval[1, 5] union Interval[3, 10] Interval[1, 10]
Intersect Interval[1, 10] intersect Interval[5, 15] Interval[5, 10]
Except Interval[1, 10] except Interval[5, 15] Interval[1, 4]

Tuples

from fhirkit.engine.cql.types import CQLTuple

# Evaluate tuple expression
result = evaluator.evaluate_expression("""
    Tuple { name: 'John', age: 30, active: true }
""")
# CQLTuple with elements {'name': 'John', 'age': 30, 'active': True}

# Access elements
print(result.elements['name'])  # 'John'

Codes and Concepts

from fhirkit.engine.cql.types import CQLCode, CQLConcept

# In CQL
lib = evaluator.compile("""
    library TermLib

    codesystem "LOINC": 'http://loinc.org'
    code "Glucose": '2339-0' from "LOINC"

    define GlucoseCode: "Glucose"
""")

result = evaluator.evaluate_definition("GlucoseCode")
# CQLCode(code='2339-0', system='http://loinc.org', display=None)

Quantities

# Evaluate quantity expressions
result = evaluator.evaluate_expression("100 'mg'")
# Quantity(value=100, unit='mg')

result = evaluator.evaluate_expression("70 'kg' + 5 'kg'")
# Quantity(value=75, unit='kg')

User-Defined Functions

lib = evaluator.compile("""
    library FuncLib

    // Simple function
    define function Add(a Integer, b Integer) returns Integer:
        a + b

    // Function with null handling
    define function SafeDivide(num Decimal, denom Decimal) returns Decimal:
        if denom is null or denom = 0 then null
        else num / denom

    // Recursive function
    define function Factorial(n Integer) returns Integer:
        if n <= 1 then 1
        else n * Factorial(n - 1)

    // Using functions
    define Sum: Add(5, 3)
    define Division: SafeDivide(10.0, 2.0)
    define Fact5: Factorial(5)
""")

results = evaluator.evaluate_all_definitions()
# {'Sum': 8, 'Division': 5.0, 'Fact5': 120}

Error Handling

from fhirkit.engine.exceptions import CQLError

try:
    evaluator.compile("invalid cql syntax !!!")
except CQLError as e:
    print(f"Compilation error: {e}")

try:
    evaluator.evaluate_definition("NonExistent")
except CQLError as e:
    print(f"Evaluation error: {e}")

Complete Example

from fhirkit.engine.cql import CQLEvaluator
import json

# Load patient data
with open("patient.json") as f:
    patient = json.load(f)

# Create evaluator
evaluator = CQLEvaluator()

# Compile clinical logic
lib = evaluator.compile("""
    library DiabetesRisk version '1.0'
    using FHIR version '4.0.1'

    context Patient

    // Patient demographics
    define PatientAge:
        years between Patient.birthDate and Today()

    define IsFemale:
        Patient.gender = 'female'

    // Risk factors
    define HasDiabetesRiskFactors:
        PatientAge >= 45

    // BMI calculation helper
    define function CalculateBMI(weightKg Decimal, heightCm Decimal) returns Decimal:
        if weightKg is null or heightCm is null or heightCm = 0 then null
        else Round(weightKg / Power(heightCm / 100, 2), 1)

    // Age categories
    define AgeCategory:
        case
            when PatientAge < 18 then 'Pediatric'
            when PatientAge < 65 then 'Adult'
            else 'Geriatric'
        end
""")

# Evaluate all definitions
results = evaluator.evaluate_all_definitions(resource=patient)

print("Patient Analysis:")
for name, value in results.items():
    print(f"  {name}: {value}")

FHIR Data Sources

CQL evaluation can query FHIR resources using data sources. Three implementations are provided:

InMemoryDataSource

Store resources in memory for testing and simple use cases:

from fhirkit.engine.cql import CQLEvaluator, InMemoryDataSource

# Create data source and add resources
ds = InMemoryDataSource()
ds.add_resource({"resourceType": "Patient", "id": "p1", "birthDate": "1990-01-01"})
ds.add_resources([
    {
        "resourceType": "Condition",
        "id": "c1",
        "subject": {"reference": "Patient/p1"},
        "code": {"coding": [{"system": "http://snomed.info/sct", "code": "44054006"}]},
    },
    {
        "resourceType": "Observation",
        "id": "o1",
        "subject": {"reference": "Patient/p1"},
        "code": {"coding": [{"system": "http://loinc.org", "code": "2339-0"}]},
    },
])

# Create evaluator with data source
evaluator = CQLEvaluator(data_source=ds)

lib = evaluator.compile("""
    library Example
    using FHIR version '4.0.1'
    context Patient

    define Conditions: [Condition]
    define Observations: [Observation]
""")

patient = {"resourceType": "Patient", "id": "p1"}
conditions = evaluator.evaluate_definition("Conditions", resource=patient)
# Returns: [{"resourceType": "Condition", ...}]

BundleDataSource

Load resources from a FHIR Bundle:

from fhirkit.engine.cql import CQLEvaluator, BundleDataSource

bundle = {
    "resourceType": "Bundle",
    "entry": [
        {"resource": {"resourceType": "Patient", "id": "p1", "birthDate": "1990-01-01"}},
        {"resource": {
            "resourceType": "Condition",
            "id": "c1",
            "subject": {"reference": "Patient/p1"},
            "code": {"coding": [{"system": "http://snomed.info/sct", "code": "44054006"}]},
        }},
    ],
}

ds = BundleDataSource(bundle)
evaluator = CQLEvaluator(data_source=ds)

# Compile and evaluate...

PatientBundleDataSource

For patient-centric bundles, automatically filters to the patient:

from fhirkit.engine.cql import CQLEvaluator, PatientBundleDataSource

# Load patient bundle (e.g., from a FHIR server)
with open("patient_bundle.json") as f:
    bundle = json.load(f)

ds = PatientBundleDataSource(bundle)
evaluator = CQLEvaluator(data_source=ds)

# The patient is automatically extracted
patient = ds.patient  # {"resourceType": "Patient", ...}

# Retrieve operations are automatically scoped to this patient
conditions = evaluator.evaluate_definition("Conditions", resource=patient)

Retrieve Operations

CQL retrieve syntax queries the data source:

library Example
using FHIR version '4.0.1'

context Patient

// Simple retrieve - all resources of type
define AllConditions: [Condition]

// Retrieve with code filter
codesystem "SNOMED": 'http://snomed.info/sct'
valueset "Diabetes": 'http://example.org/vs/diabetes'

define DiabetesConditions: [Condition: code in "Diabetes"]

// Retrieve uses patient context automatically
define PatientObservations: [Observation]  // Filtered to current patient

Adding ValueSets

For code filtering with valuesets:

from fhirkit.engine.cql import CQLCode, InMemoryDataSource

ds = InMemoryDataSource()

# Add expanded valueset
ds.add_valueset(
    "http://example.org/vs/diabetes",
    [
        CQLCode(code="44054006", system="http://snomed.info/sct"),
        CQLCode(code="E11", system="http://hl7.org/fhir/sid/icd-10"),
    ],
)

# Now [Condition: code in "Diabetes"] will filter appropriately

Advanced Examples

Batch Patient Processing

from fhirkit.engine.cql import CQLEvaluator, InMemoryDataSource
import json
from pathlib import Path

def process_patient_population(
    cql_source: str,
    patient_bundles: list[Path],
    definitions: list[str]
) -> list[dict]:
    """Process multiple patients through CQL logic."""
    evaluator = CQLEvaluator()
    evaluator.compile(cql_source)

    results = []

    for bundle_path in patient_bundles:
        with open(bundle_path) as f:
            bundle = json.load(f)

        # Create data source for this patient
        ds = PatientBundleDataSource(bundle)
        evaluator._data_source = ds

        # Evaluate requested definitions
        patient_result = {
            "patient_id": ds.patient.get("id"),
            "file": str(bundle_path),
        }

        for defn in definitions:
            try:
                value = evaluator.evaluate_definition(defn, resource=ds.patient)
                patient_result[defn] = value
            except Exception as e:
                patient_result[defn] = f"Error: {e}"

        results.append(patient_result)

    return results

# Example usage
cql = """
    library PatientAnalysis version '1.0'
    using FHIR version '4.0.1'
    context Patient

    define PatientAge: years between Patient.birthDate and Today()
    define ConditionCount: Count([Condition])
    define HasDiabetes: exists([Condition] C where C.code.coding.code = '44054006')
"""

patient_files = list(Path("patients/").glob("*.json"))
results = process_patient_population(
    cql,
    patient_files,
    ["PatientAge", "ConditionCount", "HasDiabetes"]
)

for r in results:
    print(f"Patient {r['patient_id']}: Age={r['PatientAge']}, Conditions={r['ConditionCount']}")

Dynamic CQL Generation

from fhirkit.engine.cql import CQLEvaluator

def create_age_filter_cql(min_age: int, max_age: int, condition_codes: list[str]) -> str:
    """Dynamically generate CQL for custom filtering."""
    code_checks = " or ".join([
        f"C.code.coding.code = '{code}'" for code in condition_codes
    ])

    return f"""
        library DynamicFilter version '1.0'
        using FHIR version '4.0.1'
        context Patient

        define PatientAge: years between Patient.birthDate and Today()

        define MeetsAgeCriteria:
            PatientAge >= {min_age} and PatientAge <= {max_age}

        define HasTargetCondition:
            exists([Condition] C where {code_checks})

        define IncludePatient:
            MeetsAgeCriteria and HasTargetCondition
    """

# Generate and evaluate
evaluator = CQLEvaluator()
cql = create_age_filter_cql(
    min_age=40,
    max_age=75,
    condition_codes=["44054006", "73211009"]  # Diabetes codes
)
evaluator.compile(cql)

patient = {"resourceType": "Patient", "id": "p1", "birthDate": "1970-01-01"}
include = evaluator.evaluate_definition("IncludePatient", resource=patient)

Caching and Performance

from fhirkit.engine.cql import CQLEvaluator, compile_library
from functools import lru_cache
import hashlib

class CachedCQLEvaluator:
    """CQL evaluator with compiled library caching."""

    def __init__(self):
        self._evaluator = CQLEvaluator()
        self._compiled_cache = {}

    def _hash_source(self, source: str) -> str:
        return hashlib.md5(source.encode()).hexdigest()

    def compile(self, source: str):
        """Compile CQL with caching."""
        cache_key = self._hash_source(source)

        if cache_key not in self._compiled_cache:
            lib = self._evaluator.compile(source)
            self._compiled_cache[cache_key] = lib
        else:
            # Load from cache
            self._evaluator._current_library = self._compiled_cache[cache_key]

        return self._compiled_cache[cache_key]

    def evaluate(self, definition: str, resource=None):
        return self._evaluator.evaluate_definition(definition, resource=resource)

# Usage
cached_evaluator = CachedCQLEvaluator()

# First call compiles
cached_evaluator.compile(cql_source)
result1 = cached_evaluator.evaluate("SomeDefinition", resource=patient1)

# Second call uses cache
cached_evaluator.compile(cql_source)  # Same source, uses cache
result2 = cached_evaluator.evaluate("SomeDefinition", resource=patient2)

Error Handling Patterns

from fhirkit.engine.cql import CQLEvaluator
from fhirkit.engine.exceptions import CQLError

class SafeCQLEvaluator:
    """CQL evaluator with comprehensive error handling."""

    def __init__(self):
        self._evaluator = CQLEvaluator()
        self._compiled = False

    def compile_safe(self, source: str) -> tuple[bool, str | None]:
        """Compile with error handling."""
        try:
            self._evaluator.compile(source)
            self._compiled = True
            return True, None
        except SyntaxError as e:
            return False, f"Syntax error: {e}"
        except CQLError as e:
            return False, f"CQL error: {e}"
        except Exception as e:
            return False, f"Unexpected error: {e}"

    def evaluate_safe(
        self,
        definition: str,
        resource=None,
        default=None
    ) -> tuple[any, str | None]:
        """Evaluate with error handling."""
        if not self._compiled:
            return default, "No library compiled"

        try:
            result = self._evaluator.evaluate_definition(definition, resource=resource)
            return result, None
        except KeyError:
            return default, f"Definition not found: {definition}"
        except CQLError as e:
            return default, f"Evaluation error: {e}"
        except Exception as e:
            return default, f"Unexpected error: {e}"

# Usage
evaluator = SafeCQLEvaluator()

success, error = evaluator.compile_safe(cql_source)
if not success:
    print(f"Compilation failed: {error}")
else:
    result, error = evaluator.evaluate_safe("PatientAge", resource=patient, default=0)
    if error:
        print(f"Evaluation warning: {error}")
    print(f"Result: {result}")

Working with Complex Data

from fhirkit.engine.cql import CQLEvaluator, InMemoryDataSource
from datetime import datetime, timedelta
import random

def generate_test_data(num_patients: int, seed: int = 42) -> InMemoryDataSource:
    """Generate synthetic test data for CQL evaluation."""
    random.seed(seed)
    ds = InMemoryDataSource()

    condition_codes = [
        ("44054006", "Type 2 diabetes"),
        ("38341003", "Hypertension"),
        ("84114007", "Heart failure"),
    ]

    for i in range(num_patients):
        # Generate patient
        birth_year = random.randint(1940, 2000)
        patient = {
            "resourceType": "Patient",
            "id": f"patient-{i}",
            "birthDate": f"{birth_year}-{random.randint(1,12):02d}-{random.randint(1,28):02d}",
            "gender": random.choice(["male", "female"]),
        }
        ds.add_resource(patient)

        # Add random conditions
        num_conditions = random.randint(0, 3)
        for j in range(num_conditions):
            code, display = random.choice(condition_codes)
            condition = {
                "resourceType": "Condition",
                "id": f"condition-{i}-{j}",
                "subject": {"reference": f"Patient/patient-{i}"},
                "clinicalStatus": {
                    "coding": [{"code": "active"}]
                },
                "code": {
                    "coding": [{"system": "http://snomed.info/sct", "code": code, "display": display}]
                },
            }
            ds.add_resource(condition)

        # Add random observations
        for j in range(random.randint(1, 5)):
            obs_date = datetime.now() - timedelta(days=random.randint(1, 365))
            observation = {
                "resourceType": "Observation",
                "id": f"obs-{i}-{j}",
                "subject": {"reference": f"Patient/patient-{i}"},
                "status": "final",
                "effectiveDateTime": obs_date.isoformat(),
                "code": {
                    "coding": [{"system": "http://loinc.org", "code": "4548-4", "display": "HbA1c"}]
                },
                "valueQuantity": {"value": round(random.uniform(5.0, 12.0), 1), "unit": "%"},
            }
            ds.add_resource(observation)

    return ds

# Generate test data
ds = generate_test_data(100)

# Evaluate CQL against test population
evaluator = CQLEvaluator(data_source=ds)
evaluator.compile("""
    library TestAnalysis version '1.0'
    using FHIR version '4.0.1'
    context Patient

    define HasDiabetes: exists([Condition] C where C.code.coding.code = '44054006')
    define LatestHbA1c: Last([Observation] O where O.code.coding.code = '4548-4' sort by effective).value.value
""")

# Analyze population
patients = ds.retrieve("Patient")
diabetes_count = 0
hba1c_values = []

for patient in patients:
    has_diabetes = evaluator.evaluate_definition("HasDiabetes", resource=patient)
    hba1c = evaluator.evaluate_definition("LatestHbA1c", resource=patient)

    if has_diabetes:
        diabetes_count += 1
    if hba1c is not None:
        hba1c_values.append(hba1c)

print(f"Total patients: {len(patients)}")
print(f"Diabetes prevalence: {diabetes_count/len(patients)*100:.1f}%")
print(f"Average HbA1c: {sum(hba1c_values)/len(hba1c_values):.1f}%")

Library Dependencies and Includes

from fhirkit.engine.cql import CQLEvaluator
from fhirkit.engine.cql.library import InMemoryLibraryResolver

# Create library resolver for dependencies
resolver = InMemoryLibraryResolver()

# Add base library
resolver.add_library("CommonFunctions", "1.0", """
    library CommonFunctions version '1.0'

    define function IsAdult(birthDate Date) returns Boolean:
        years between birthDate and Today() >= 18

    define function FormatAge(birthDate Date) returns String:
        ToString(years between birthDate and Today()) + ' years'
""")

# Add library that depends on CommonFunctions
resolver.add_library("PatientAnalysis", "1.0", """
    library PatientAnalysis version '1.0'
    using FHIR version '4.0.1'

    include CommonFunctions version '1.0'

    context Patient

    define PatientIsAdult: CommonFunctions.IsAdult(Patient.birthDate)
    define PatientAgeFormatted: CommonFunctions.FormatAge(Patient.birthDate)
""")

# Create evaluator with resolver
evaluator = CQLEvaluator(library_resolver=resolver)

# Load and evaluate
evaluator.load_library("PatientAnalysis", "1.0")
result = evaluator.evaluate_definition("PatientIsAdult", resource=patient)

Custom Type Conversions

from fhirkit.engine.cql import CQLEvaluator
from fhirkit.engine.types import FHIRDate, FHIRDateTime, FHIRTime
from datetime import date, datetime, time

def convert_cql_result(value):
    """Convert CQL results to Python native types."""
    if value is None:
        return None

    # Handle FHIR date/time types
    if isinstance(value, FHIRDate):
        return value.to_date()
    if isinstance(value, FHIRDateTime):
        return value.to_datetime()
    if isinstance(value, FHIRTime):
        return time(
            hour=value.hour or 0,
            minute=value.minute or 0,
            second=value.second or 0
        )

    # Handle CQL types
    if hasattr(value, 'elements'):  # CQLTuple
        return {k: convert_cql_result(v) for k, v in value.elements.items()}

    if hasattr(value, 'low') and hasattr(value, 'high'):  # CQLInterval
        return {
            "low": convert_cql_result(value.low),
            "high": convert_cql_result(value.high),
            "low_closed": value.low_closed,
            "high_closed": value.high_closed,
        }

    # Handle lists
    if isinstance(value, list):
        return [convert_cql_result(v) for v in value]

    # Return as-is for basic types
    return value

# Usage
evaluator = CQLEvaluator()
evaluator.compile("""
    library TypeDemo version '1.0'
    define DateResult: @2024-06-15
    define IntervalResult: Interval[1, 10]
    define TupleResult: Tuple { name: 'John', age: 30 }
""")

# Evaluate and convert
date_val = convert_cql_result(evaluator.evaluate_definition("DateResult"))
print(f"Date: {date_val}")  # datetime.date object

interval_val = convert_cql_result(evaluator.evaluate_definition("IntervalResult"))
print(f"Interval: {interval_val}")  # dict with low, high, etc.

tuple_val = convert_cql_result(evaluator.evaluate_definition("TupleResult"))
print(f"Tuple: {tuple_val}")  # dict with name, age

Integration Patterns

With FastAPI

from fastapi import FastAPI
from fhirkit.engine.cql import CQLEvaluator

app = FastAPI()
evaluator = CQLEvaluator()

@app.post("/evaluate")
async def evaluate_cql(expression: str, patient: dict = None):
    result = evaluator.evaluate_expression(expression, resource=patient)
    return {"result": result}

@app.post("/library/compile")
async def compile_library(source: str):
    lib = evaluator.compile(source)
    return {"name": lib.name, "definitions": list(lib.definitions.keys())}

With Pandas

import pandas as pd
from fhirkit.engine.cql import CQLEvaluator

evaluator = CQLEvaluator()
lib = evaluator.compile("""
    library Analysis
    context Patient
    define PatientAge: years between Patient.birthDate and Today()
""")

# Process multiple patients
patients = [
    {"resourceType": "Patient", "id": "1", "birthDate": "1990-01-01"},
    {"resourceType": "Patient", "id": "2", "birthDate": "1985-06-15"},
    {"resourceType": "Patient", "id": "3", "birthDate": "2000-03-22"},
]

results = []
for patient in patients:
    age = evaluator.evaluate_definition("PatientAge", resource=patient)
    results.append({"id": patient["id"], "age": age})

df = pd.DataFrame(results)
print(df)

Quality Measure Evaluation

The MeasureEvaluator class provides support for evaluating CQL-based clinical quality measures.

Basic Usage

from fhirkit.engine.cql import MeasureEvaluator

# Create measure evaluator
evaluator = MeasureEvaluator()

# Load a measure from CQL source
evaluator.load_measure("""
    library DiabetesMeasure version '1.0'
    using FHIR version '4.0.1'

    context Patient

    define "Initial Population":
        AgeInYears() >= 18

    define "Denominator":
        "Initial Population"

    define "Numerator":
        AgeInYears() >= 40
""")

# Evaluate for a single patient
patient = {"resourceType": "Patient", "id": "p1", "birthDate": "1990-01-01"}
result = evaluator.evaluate_patient(patient)

print(result.patient_id)  # 'p1'
print(result.populations)  # {'initial-population': True, 'denominator': True, 'numerator': False}

Population Evaluation

Evaluate a measure across multiple patients:

patients = [
    {"resourceType": "Patient", "id": "p1", "birthDate": "1990-01-01"},
    {"resourceType": "Patient", "id": "p2", "birthDate": "1980-01-01"},
    {"resourceType": "Patient", "id": "p3", "birthDate": "1970-01-01"},
]

# Evaluate for entire population
report = evaluator.evaluate_population(patients)

print(report.measure_id)  # 'DiabetesMeasure'
print(len(report.patient_results))  # 3

# Get population counts
for group in report.groups:
    print(f"Group: {group.id}")
    for pop_type, count in group.populations.items():
        print(f"  {pop_type}: {count.count}")
    print(f"  Measure Score: {group.measure_score}")

With Data Sources

from fhirkit.engine.cql import MeasureEvaluator, InMemoryDataSource

# Create data source with conditions
ds = InMemoryDataSource()
ds.add_resource({"resourceType": "Patient", "id": "p1", "birthDate": "1980-01-01"})
ds.add_resource({
    "resourceType": "Condition",
    "id": "c1",
    "subject": {"reference": "Patient/p1"},
    "code": {"coding": [{"system": "http://snomed.info/sct", "code": "44054006"}]},
})

evaluator = MeasureEvaluator(data_source=ds)
evaluator.load_measure("""
    library DiabetesMeasure version '1.0'
    using FHIR version '4.0.1'

    context Patient

    define "Initial Population":
        exists([Patient])

    define "Denominator":
        "Initial Population"

    define "Numerator":
        exists([Condition])
""")

patient = {"resourceType": "Patient", "id": "p1", "birthDate": "1980-01-01"}
result = evaluator.evaluate_patient(patient, data_source=ds)
print(result.populations["numerator"])  # True (patient has conditions)

Stratification

Measures can include stratifiers for population breakdown:

evaluator.load_measure("""
    library StratifiedMeasure version '1.0'

    context Patient

    define "Initial Population":
        true

    define "Denominator":
        true

    define "Numerator":
        AgeInYears() >= 50

    define "Stratifier Age Group":
        if AgeInYears() < 50 then 'Under 50'
        else '50+'
""")

patients = [
    {"resourceType": "Patient", "id": "p1", "birthDate": "2000-01-01"},  # Under 50
    {"resourceType": "Patient", "id": "p2", "birthDate": "1960-01-01"},  # 50+
]

report = evaluator.evaluate_population(patients)

# Access stratified results
for group in report.groups:
    for strat_name, strat_results in group.stratifiers.items():
        print(f"Stratifier: {strat_name}")
        for result in strat_results:
            print(f"  {result.value}: {result.populations}")

MeasureReport to FHIR

Convert measure results to a FHIR MeasureReport:

report = evaluator.evaluate_population(patients)

# Convert to FHIR MeasureReport
fhir_report = report.to_fhir()
print(json.dumps(fhir_report, indent=2))

# Output:
# {
#   "resourceType": "MeasureReport",
#   "status": "complete",
#   "type": "summary",
#   "measure": "DiabetesMeasure",
#   "date": "2025-12-13T10:30:00",
#   "group": [{
#     "id": "default",
#     "population": [
#       {"code": {"coding": [{"code": "initial-population"}]}, "count": 3},
#       {"code": {"coding": [{"code": "denominator"}]}, "count": 3},
#       {"code": {"coding": [{"code": "numerator"}]}, "count": 2}
#     ],
#     "measureScore": {"value": 0.6667}
#   }]
# }

Population Summary

Get a quick summary of population counts:

report = evaluator.evaluate_population(patients)
summary = evaluator.get_population_summary(report)

print(summary)
# {
#   'measure': 'DiabetesMeasure',
#   'total_patients': 3,
#   'groups': [{
#     'id': 'default',
#     'populations': {
#       'initial-population': 3,
#       'denominator': 3,
#       'numerator': 2
#     },
#     'measure_score': 0.6667
#   }]
# }

Measure Scoring Types

from fhirkit.engine.cql import MeasureScoring

evaluator = MeasureEvaluator()
evaluator.set_scoring(MeasureScoring.PROPORTION)  # Default
# evaluator.set_scoring(MeasureScoring.RATIO)
# evaluator.set_scoring(MeasureScoring.COHORT)
# evaluator.set_scoring(MeasureScoring.CONTINUOUS_VARIABLE)

Population Types

The following population types are supported:

Type CQL Definition Names
Initial Population Initial Population, InitialPopulation
Denominator Denominator
Denominator Exclusion Denominator Exclusion, DenominatorExclusion
Denominator Exception Denominator Exception, DenominatorException
Numerator Numerator
Numerator Exclusion Numerator Exclusion, NumeratorExclusion
Measure Population Measure Population, MeasurePopulation
Measure Observation Measure Observation, MeasureObservation

Proportion Score Calculation

For proportion measures, the score is calculated as:

Score = (Numerator - Numerator Exclusion) /
        (Denominator - Denominator Exclusion - Denominator Exception)

Complete Example

from fhirkit.engine.cql import MeasureEvaluator, InMemoryDataSource
import json

# Create data source with patient data
ds = InMemoryDataSource()

# Add patients
patients = []
for i in range(10):
    age = 30 + i * 5  # Ages 30, 35, 40, ..., 75
    birth_year = 2025 - age
    patient = {
        "resourceType": "Patient",
        "id": f"p{i}",
        "birthDate": f"{birth_year}-01-01",
        "gender": "male" if i % 2 == 0 else "female",
    }
    ds.add_resource(patient)
    patients.append(patient)

    # Add diabetes condition for some patients
    if age >= 50:
        ds.add_resource({
            "resourceType": "Condition",
            "id": f"cond-{i}",
            "subject": {"reference": f"Patient/p{i}"},
            "code": {"coding": [{"system": "http://snomed.info/sct", "code": "44054006"}]},
        })

# Create evaluator
evaluator = MeasureEvaluator(data_source=ds)

# Load diabetes screening measure
evaluator.load_measure("""
    library DiabetesScreening version '1.0'
    using FHIR version '4.0.1'

    context Patient

    define "Initial Population":
        AgeInYears() >= 18

    define "Denominator":
        "Initial Population" and AgeInYears() >= 45

    define "Denominator Exclusion":
        AgeInYears() > 75

    define "Numerator":
        exists([Condition])

    define "Stratifier Gender":
        Patient.gender
""")

# Evaluate population
report = evaluator.evaluate_population(patients, data_source=ds)

# Print summary
summary = evaluator.get_population_summary(report)
print("Diabetes Screening Measure Results")
print("=" * 40)
print(f"Total Patients: {summary['total_patients']}")
for group in summary['groups']:
    print(f"\nGroup: {group['id']}")
    for pop, count in group['populations'].items():
        print(f"  {pop}: {count}")
    if group['measure_score'] is not None:
        print(f"  Score: {group['measure_score']:.1%}")

# Export FHIR MeasureReport
fhir_report = report.to_fhir()
print("\nFHIR MeasureReport:")
print(json.dumps(fhir_report, indent=2))