Here is how you can run the CQL execution with unstructured data and FHIR terminology support using our LLM-in-the-Loop stack as presented at AMIA #CIC25. The use of MedpromptJS allows the injection of LLM. So, you can use any LLM (either self-hosted or commercial) supported by LangChain in this stack.

Update: CQL is commonly used for clinical decision support (CDS) queries, such as determining whether a patient with Type 2 Diabetes should be prescribed statins for primary prevention of cardiovascular disease. Additionally, it can be utilized for clinical quality measure queries, like calculating the percentage of patients aged 50–75 who received appropriate colorectal cancer screening.

LLM-in-the-Loop CQL execution
Image credit: GDJ, CC0, via Wikimedia Commons

CQL is a domain-specific language that allows clinicians and researchers to express queries and retrieve data from electronic health records (EHRs) in a standardized and interoperable way. Let us start with a simple example of CQL:

// NOTE:  This is a simplified example, designed only for the purpose of demonstrating how to use the cql-execution, cql-exec-fhir, and cql-exec-vsac javascript modules.  This CQL is NOT clinically validated and should NOT be used in a clinical setting.

library DiabeticFootExam version '1.0.0'
using FHIR version '4.0.1'
include FHIRHelpers version '4.0.1' called FHIRHelpers

// Value set and codes loosely borrowed from CMS 123v7
codesystem "SNOMEDCT": 'http://snomed.info/sct'
codesystem "CONDITION-VER-STATUS": 'http://terminology.hl7.org/CodeSystem/condition-ver-status'
valueset "Diabetes": '1'
code "Confirmed code": 'confirmed' from "CONDITION-VER-STATUS" display 'Confirmed'
concept "Confirmed": { "Confirmed code" } display 'Confirmed'

context Patient

define InDemographic:
  AgeInYears() between 18 and 75

define HasDiabetes:
  exists(
    [Condition: "Diabetes"] C
      where C.verificationStatus ~ "Confirmed"
  )

define IsDiabetic:
  InDemographic and
  HasDiabetes and

What is a CQL engine?

A Clinical Quality Language (CQL) execution engine is a software tool that processes and evaluates clinical logic written in CQL. This language, developed by HL7, is used to express clinical knowledge in a way that is both human-readable and machine-executable.

The execution engine runs CQL expressions against patient data, typically in FHIR (Fast Healthcare Interoperability Resources) format, to determine whether certain clinical criteria are met. This makes it particularly useful for quality measurement, clinical decision support, and automated healthcare analytics.

Different execution engines exist, each with variations in performance, compatibility, and the supported versions of CQL. Here is a popular CQL engine implementation. This engine can run the above CQL with FHIRHelpers. We have added a tweak that lets you use ValueSets in a FHIR server for terminology reference. Read about it here in a previous post.

CQL with an unstructured data element.

// NOTE:  This is a simplified example, designed only for the purpose of demonstrating how to use the cql-execution, cql-exec-fhir, and cql-exec-vsac javascript modules.  This CQL is NOT clinically validated and should NOT be used in a clinical setting.

library DiabeticFootExam version '1.0.0'
using FHIR version '4.0.1'
include FHIRHelpers version '4.0.1' called FHIRHelpers

// Value set and codes loosely borrowed from CMS 123v7
codesystem "SNOMEDCT": 'http://snomed.info/sct'
codesystem "CONDITION-VER-STATUS": 'http://terminology.hl7.org/CodeSystem/condition-ver-status'
valueset "Diabetes": '1'
code "Confirmed code": 'confirmed' from "CONDITION-VER-STATUS" display 'Confirmed'
concept "Confirmed": { "Confirmed code" } display 'Confirmed'

context Patient

define InDemographic:
  AgeInYears() between 18 and 75

define HasDiabetes:
  exists(
    [Condition: "Diabetes"] C
      where C.verificationStatus ~ "Confirmed"
  )

define HasDocumentedSignsOfInfection:
  exists(
    [DocumentReference] D
      // Adjusted: compare to a string or code, not a concept
      where D.content[0].attachment.data = 'Visual foot exam showed active infection?'
  )

define IsDiabeticWithInfection:
  InDemographic and
  HasDiabetes and
  HasDocumentedSignsOfInfection

The CQL differs from the previous version in that it includes DocumentReference, an unstructured FHIR resource. It inquires whether the unstructured attachment indicates evidence of a Visual foot exam revealing an active infection. However, it’s important to note that no CQL engine can currently process this query because the query on an unstructured FHIR resource cannot be translated into a database query.

This is where LLM in the Loop comes in. But before we go into how we solve this problem, let us delve a little deeper into how the CQL engine processes any CQL.

CQL is first converted into an intermediary form called the Expression Logical Model (ELM). ELM complements CQL by serving as its machine-readable representation, facilitating seamless execution and integration within electronic health record systems. Together, CQL and ELM enhance interoperability, precision, and automation. Therefore, we need to convert the provided CQL (with DocumentReference) to ELM. Part I demonstrates how this conversion can be achieved. Below is a part of the ELM representation of our CQL in JSON format.

 "library": {
    "annotation": [
      {
        "translatorOptions": "",
        "type": "CqlToElmInfo"
      }
    ],
    "identifier": {
      "id": "DiabeticFootExam",
      "version": "1.0.0"
    },
    "schemaIdentifier": {
      "id": "urn:hl7-org:elm",
      "version": "r1"
    },
    "usings": {
      "def": [
        {
          "localIdentifier": "System",
          "uri": "urn:hl7-org:elm-types:r1"
        },
        {
          "localIdentifier": "FHIR",
          "uri": "http://hl7.org/fhir",
          "version": "4.0.1"
        }
      ]
    },
    "includes": {
      "def": [
        {
          "localIdentifier": "FHIRHelpers",
          "path": "FHIRHelpers",
          "version": "4.0.1"
        }
      ]
    },
    "codeSystems": {
      "def": [
        {
          "name": "SNOMEDCT",
          "id": "http://snomed.info/sct",
          "accessLevel": "Public"
        },
..........
..........

The current CQL engine can process most of the ELM in FHIR bundles, except for the DocumentReference (DR) shown below.

define HasDocumentedSignsOfInfection:
  exists(
    [DocumentReference] D
      // Adjusted: compare to a string or code, not a concept
      where D.content[0].attachment.data = 'Visual foot exam showed active infection?'
  )

To process DR, we need to pass only the above part that references DR and the content of DR itself to an LLM. We have discussed how we achieved this in Part IV.

Part IV above discusses a hook pattern that triggers a function interface with the DocumentReference content as its payload. The next step is to write an implementation of the triggered function interface. We have described how we implemented it in a scalable and maintainable way with the LLM and hyperparameters injected at runtime in Part III (MedpromptJS).

We have all the components required for an end-to-end application of LLM-in-the-Loop. All we need now is a set of FHIR bundles containing patient context, on which we can execute the above CQL with unstructured data references. You can obtain this from your electronic health record’s (EHR) FHIR API. Alternatively, if your organization has a FHIR data warehouse (self-hosted, Google Healthcare API, or Microsoft FHIR server), you can retrieve it using an appropriate search query. Below is a portion of the sample FHIR bundle.

[
    {
  "resourceType": "Bundle",
  "id": "Recent_Foot_Exam_Bundle",
  "type": "collection",
  "entry": [
    {
      "resource": {
        "resourceType": "Patient",
        "id": "Patient_1",
        "gender": "female",
        "birthDate": "1979-01-30"
      }
    },
    {
      "resource": {
        "resourceType": "Condition",
        "id": "2-1",
        "clinicalStatus": {
          "coding": [
            {
              "system": "http://terminology.hl7.org/CodeSystem/condition-clinical",
              "code": "active",
              "display": "Active"
            }
          ]
        },
.........
.........

Finally, below is a simple end-to-end application using ExpressJS that you can host internally. It takes the FHIR bundles, CQL as ELM JSON and a reference to a FHIR server for terminology (ValueSet) support. Here is a sample request that you can send to get a response as below:

......       
 }
      },
      "InDemographic": true,
      "HasDiabetes": false,
      "HasDocumentedSignsOfInfection": true,
      "IsDiabeticWithInfection": false
    }
  },
......

The use of MedpromptJS allows the injection of LLM. So, you can use any LLM (either self-hosted or commercial) supported by LangChain in this stack. See bootstrap.ts to see the usage pattern. MedpromptJS comes with the prompts required for the pipeline. However, you can change the prompts in the llmService.ts to customize it for your LLM and data. Additionally, if required, you can customize RAG (chunking, retrieval, etc.) by overriding the corresponding method in llm_loop.ts (in MedPromptJS).

Feel free to reach out if you have any questions.

Update: Please note that this application incorporates the artifacts we discussed as dependencies.

 "dependencies": {
    ......    
    "cql-exec-fhir": "^2.1.5",
    "cql-exec-vsac": "github:dermatologist/cql-exec-vsac",
    "cql-execution": "github:dermatologist/cql-execution",
    "express": "^5.1.0",
    "langchain": "^0.3.27",
    "@langchain/community": "0.3.44",
    "medpromptjs": "^0.4.3",
    ......
  },