CQFlow Overview

CQFlow is a tool for designing and executing Clinical Decision Support flow diagrams.

Flow diagrams are valuable tools for modeling clinical algorithms due to their ability to visually represent logic in a clear and structured manner. They are intuitive to read and author for all parties involved, can express complex scenarios, and can easily be translated directly into the type of computational logic that makes up executable code. They are also already widely used and published in the medical literature, and most medical professionals are already very familiar with them.

CQFlow is specifically designed for computable CDS, and each feature has been carefully chosen to streamline the process of creating flow diagrams that are fully executable. It is designed so that a clinical team can seamlessly pass on a specification (Flow Definition) to an engineering team, where a Flow Implementation that leverages medical data can be built, tested, and deployed.

Before reading on and if you have not already done so, we suggest checking out the Introductory Example for a walkthrough of CQFlow.

Architecture

There are three primary layers to CQFlow:

  1. The Flow Definition layer
  2. The Flow Implementation layer
  3. The Output layer

Let's dive into each in more detail.

Flow Definition

Formally, the Flow Definition is a JSON representation of the CQFlow flow chart. It describes a series of nodes and edges that make up the flow chart.

As an example, the following is the Flow Definition for the Introductory Example.

bcs-flow-definition.json
{
  "id": "1756dfc1-f50e-49a6-b5c0-25ef29fbd8ac",
  "createdAt": "2023-09-09T13:29:17.489Z",
  "bindId": "docs-breast-cancer-screening",
  "version": "0.0.1",
  "type": "",
  "nodes": {
    "start_gkhjglgd": {
      "id": "start_gkhjglgd",
      "next": {
        "id": "truefalse_qgriynff",
        "type": "Unary"
      },
      "label": "Start",
      "bindId": "start_gkhjglgd",
      "nodeType": "Start",
      "position": {
        "x": 405.5479534159969,
        "y": 63.87311805294871,
        "width": 60,
        "height": 60
      }
    },
    "end_taemrzwt": {
      "id": "end_taemrzwt",
      "label": "End",
      "bindId": "end_taemrzwt",
      "nodeType": "End",
      "position": {
        "x": 1081.1988680648637,
        "y": 421.61760356626036,
        "width": 60,
        "height": 60
      }
    },
    "truefalse_qgriynff": {
      "id": "truefalse_qgriynff",
      "next": {
        "type": "Binary",
        "trueId": "truefalse_pzfoshqc",
        "falseId": "end_kqtxswcv",
        "trueToHandle": "left",
        "falseToHandle": "top",
        "trueFromHandle": "right",
        "falseFromHandle": "bottom"
      },
      "label": "Is Female",
      "bindId": "is_female",
      "nodeType": "TrueFalse",
      "position": {
        "x": 387.2296353091672,
        "y": 201.95758149517818,
        "width": 100,
        "height": 100
      }
    },
    "truefalse_hegecwii": {
      "id": "truefalse_hegecwii",
      "next": {
        "type": "Binary",
        "trueId": "emitdata_ontsplwu",
        "falseId": "emitdata_yjvwqwfv",
        "trueToHandle": "top",
        "falseToHandle": "left",
        "trueFromHandle": "bottom",
        "falseFromHandle": "right"
      },
      "label": "Has had Breast Cancer Screening in the last 2 years",
      "bindId": "has_had_breast_cancer_screening_in_last_2_years",
      "nodeType": "TrueFalse",
      "position": {
        "x": 808.9517092230577,
        "y": 186.9452852116524,
        "width": 128,
        "height": 128
      }
    },
    "truefalse_pzfoshqc": {
      "id": "truefalse_pzfoshqc",
      "next": {
        "type": "Binary",
        "trueId": "truefalse_hegecwii",
        "falseId": "end_kqtxswcv",
        "trueToHandle": "left",
        "falseToHandle": "top",
        "trueFromHandle": "right",
        "falseFromHandle": "bottom"
      },
      "label": "Is over 45 years old",
      "bindId": "is_over_45_years_old",
      "nodeType": "TrueFalse",
      "position": {
        "x": 595.0938494209624,
        "y": 201.75631953579062,
        "width": 100,
        "height": 100
      }
    },
    "emitdata_ontsplwu": {
      "id": "emitdata_ontsplwu",
      "next": {
        "id": "end_taemrzwt",
        "type": "Unary",
        "toHandle": "left",
        "fromHandle": "right"
      },
      "label": "Schedule Breast Cancer Screening",
      "bindId": "emitdata_ontsplwu",
      "nodeType": "EmitData",
      "position": {
        "x": 796.1413540259276,
        "y": 408.3493576669764,
        "width": 150,
        "height": 83
      }
    },
    "emitdata_yjvwqwfv": {
      "id": "emitdata_yjvwqwfv",
      "next": {
        "id": "end_taemrzwt",
        "type": "Unary"
      },
      "label": "Recommend Breast Cancer Screening",
      "bindId": "recommend_breast_cancer_screening",
      "nodeType": "EmitData",
      "position": {
        "x": 1037.4226216453985,
        "y": 211.37025760761492,
        "width": 150,
        "height": 78
      },
      "dataItems": []
    },
    "end_kqtxswcv": {
      "nodeType": "End",
      "label": "End",
      "id": "end_kqtxswcv",
      "bindId": "end_kqtxswcv",
      "position": {
        "x": 510.2422330759433,
        "y": 392.7072400118986,
        "width": 60,
        "height": 60
      }
    }
  }
}

Notice this flow definition does not contain any information about the data model, the query logic, data structures, the cloud infastrucutre, or any other implementation concepts.

By keeping this layer lightweight and minimalistic, work on the Flow Definition becomes very focused on the clinical domain logic and the complexities and inter-relationships of clinical concepts and rules. The main goal is to avoid getting bogged down in engineering implementation details at this stage. Due to intuitive and relatively simple building blocks, the clinical team is able to become productive in hours; not weeks, months, or even years as is the case with approaches that embed engineering logic into the flow chart layer itself (such as through a DSL or other data query and execution approaches).

One important concept of the Flow Definition is a node's bindId. In the above flow definition, the "Is Female" node has a bindId of is_female. This is a unique identifier that provides the engineering team with a hook that enables them to implement the query using the SDK.

Flow Implementation

The Flow Implementation layer is where software engineers use the flow definition defined by the clinical team and transform it into computable software. Software engineers are able to leverage their knowledge of data structures, APIs, databases, software design patterns, testing infrastructure, deployment pipelines, and other engineering concepts to build production grade software that implement the clinical specifications defined by the clinical team.

It is rare to find a single person that is an expert in both clinical medicine and software engineering. By fostering collaboration through CQLab's architecture and tools, two separate groups of domain experts are able to work together in a way that maximizes the contribution of their strengths to the final solution.

IsFemale Implementation

We have many code examples and discuss the Flow Implementation in more detail later, but for now we will introduce an example of how the TypeScript SDK can be used to implement IsFemale execution logic with a FHIR Bundle as the data source.


// An ExecNode node implementation of a TrueFalse node definition
class IsFemale extends ExecNode<BreastCancerScreeningContext> {
  override async evaluate(context: BreastCancerScreeningContext): Promise<TernaryEnum> {
    
    // Find patient resource in the FHIR Bundle
    const patient = context.getPatientBundle().find(
      (entry) => entry.resource?.resourceType === 'Patient'
    )?.resource as fhir4.Patient;

    if (!patient) {
      throw new Error('Patient resource not found in bundle');
    }
    
    return patient.gender === 'female' ? TernaryEnum.TRUE : TernaryEnum.FALSE;
  }
}

// Declare the flow implementation
export const breastCancerScreeningImplemenation =
  new InteractiveFlowImplementation<BreastCancerScreeningContext>();

// Register the node implementation with the flow implementation
breastCancerScreeningImplemenation.registerTrueFalse(
  "is_female",
  (nodeDef) => new IsFemale(nodeDef)
);

Here we are looking at an ExecNode node implementation for a True/False node definition. There can be several types of node implementations for each node definition, each providing different capabilities and customizations.

A major benefit of this approach is that a single flow definition can power many different flow implementations. If a clinical team wanted to share a flow definition with multiple engineering teams, either internally or externally, each team could create a unique flow implementation with their own product needs, data, and technical architecture in mind.

In this example, we use a FHIR Bundle as the data source. However, we could just as easily use a FHIR Server, or any other data source, if the solution required it.

There are a few more things to notice in the above code snippet. First, since this is TypeScript, it can be unit and integration tested, debugged, it has robust type support, it can be optimized/cached, it can work with any data model or database, it can be hosted on any infrastructure. Second, as TypeScript is one of the most common and well supported programming languages today, it is much easier to find developers, tooling, technical support, code examples, and learning resources when compared to more niche approaches.

Using a mature and well supported technology stack has provided countless benefits over less mature solutions. It has enabled us to ship higher quality solutions, up to 3-10x faster than competing approaches.

Flow Output

The final output of a CDS solution is dependent upon the unique needs of the project. CQFlow is designed to be flexible to meet the varied requirements of different projects.

In general, there is usually either a:

  • UI output
  • Data output

A user interface may include a questionnaire embedded inside a web or mobile app, an EHR, or other standalone application. Alternatively, calculated information or recommendations may be displayed to a user to be read or clicked to perform specific actions.

A data based output may be exposed through a REST API and leveraged by other services or applications to perform automated actions. For example, if a patient is identified needing a breast cancer screening, an automatic email could be sent to the patient.

CQLab exposes a React UI component library that can be used to build a UI. It is also possible to build a custom branded UI and interact directly with a data service.