-
Notifications
You must be signed in to change notification settings - Fork 25
Cooking with CQL Q&A Index Using Fast Healthcare Interoperability Resources (FHIR) Category
Commonly Used QDM Datatypes in FHIR: How would some of the most commonly used datatypes in Quality Data Model (QDM) be expressed in a Fast Healthcare Interoperability Resource (FHIR)-based measure? (Session 37 - 7/25/19)
- The common datatype examples below are expressed in Fast Healthcare Interoperability Resource (FHIR)-using Quality Data Model version 5.4, FHIR version 4.0.0, and include FHIRHelpers version 4.0.0.
- QDM Diagnosis to FHIR Diagnosis from CMS72v8:
define "QDM Diagnosis":
[Diagnosis: "Intravenous or Intra arterial Thrombolytic (tPA) Therapy Prior to Arrival"]
FHIR Equivalent:
define "FHIR Diagnosis":
[FHIR.Condition: "Intravenous or Intra arterial Thrombolytic (tPA) Therapy Prior to Arrival"] PriorTPA
where PriorTPA.clinicalStatus in "Active Condition"
NOTE: onset and abatement need to be considered, and there is an outstanding question as to whether the clinical status element would always be consistent with onset and abatement.
- Next datatype is Encounter, Performed using the example from MATGlobalCommonFunctions:
define "QDM Encounter Performed":
["Encounter, Performed": "Encounter Inpatient"]
FHIR Equivalent:
define "FHIR Encounter Performed":
[Encounter: "Encounter Inpatient"] EncounterInpatient
where EncounterInpatient.status = 'finished'
NOTE: Encounter, Performed in QDM can include "in progress" encounter, so it's not necessarily always just 'finished', the point is status needs to be checked consistent with measure intent.
QDM to QI Core Mapping would be a good starting point. This goes through all of the QDM datatypes and what the mapping to FHIR looks like.
- Next datatype is Medication, Order using the example from VTE-1.
define "QDM Medication Order":
["Medication, Order": "Low Dose Unfractionated Heparin for VTE Prophylaxis"]
FHIR Equivalent:
define "FHIR Medication Order":
["MedicationRequest": "Low Dose Unfractionated Heparin for VTE Prophylaxis"] M
where M.intent = 'order'
- Last datatype is Procedure, Performed using the example from VTE-1.
define "QDM Procedure Performed":
["Procedure, Performed": "Comfort Measures"]
FHIR Equivalent:
Define "FHIR Procedure Performed":
["Procedure": "Comfort Measures"] P
where P.status = 'completed'
EXM130 - FHIR.Timing and FHIR.String: In the EXM130 expression below, would it be better to move the FHIR.Timing and the FHIR.string to the bottom of the list if one were to prioritize the list? (Session 41 - 1/16/20)
define function "Normalize Interval"(choice Choice<FHIR.dateTime, FHIR.Period, FHIR.Timing, FHIR.instant, FHIR.string,
FHIR.Age, FHIR.Range>):
case
when choice is FHIR.dateTime then
Interval[FHIRHelpers.ToDateTime(choice as FHIR.dateTime), FHIRHelpers.ToDateTime(choice as FHIR.dateTime)]
when choice is FHIR.Period then
FHIRHelpers.ToInterval(choice as FHIR.Period)
when choice is FHIR.instant then
Interval[FHIRHelpers.ToDateTime(choice as FHIR.instant), FHIRHelpers.ToDateTime(choice as
FHIR.instant)]
when choice is FHIR.Age then
Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(choice as FHIR.Age),
FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(choice as FHIR.Age) + 1
year)
when choice is FHIR.Range then
Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((choice as
FHIR.Range).low),
FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((choice as
FHIR.Range).high) + 1 year)
when choice is FHIR.Timing then
Message(null as Interval<DateTime>, true, '1', 'Error', 'Cannot compute a single interval from a
Timing type')
when choice is FHIR.string then
Message(null as Interval<DateTime>, true, '1', 'Error', 'Cannot compute an interval from a String value')
else null as Interval<DateTime>
end
- The case expression in Clinical Quality Language (CQL) doesn’t carry any implication of ordered processing and in any given instance choices are exclusive. Therefore, you will only have one set of the types chosen at a given time. For example, you would not have a FHIR.dateTime and a FHIR.Timing in the same element. However, implementations may walk through each of the instances from top to bottom so it would be reasonable to move the FHIR.Timing and the FHIR.string to the bottom to allow for the more common instances to be hit first, but it doesn’t change the actual semantics of the function.
EXM130 - Message Function: Based on the EXM130 expression below, can you explain the “Message” function and each component of it? (Session 41 - 1/16/20)
define function "Normalize Interval"(choice Choice<FHIR.dateTime, FHIR.Period, FHIR.Timing, FHIR.instant, FHIR.string,
FHIR.Age, FHIR.Range>):
case
when choice is FHIR.dateTime then
Interval[FHIRHelpers.ToDateTime(choice as FHIR.dateTime), FHIRHelpers.ToDateTime(choice as FHIR.dateTime)]
when choice is FHIR.Period then
FHIRHelpers.ToInterval(choice as FHIR.Period)
when choice is FHIR.instant then
Interval[FHIRHelpers.ToDateTime(choice as FHIR.instant), FHIRHelpers.ToDateTime(choice as
FHIR.instant)]
when choice is FHIR.Age then
Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(choice as FHIR.Age),
FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(choice as FHIR.Age) + 1 year)
when choice is FHIR.Range then
Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((choice as FHIR.Range).low),
FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((choice as FHIR.Range).high) + 1
year)
when choice is FHIR.Timing then
Message(null as Interval<DateTime>, true, '1', 'Error', 'Cannot compute a single interval from a Timing
type')
when choice is FHIR.string then
Message(null as Interval<DateTime>, true, '1', 'Error', 'Cannot compute an interval from a String value')
else null as Interval<DateTime>
end
- Clinical Quality Language (CQL) is a functional language, meaning everything in CQL is going to return a value. Even in the case where we’re throwing an ‘Error’ or returning a ‘Warning,’ it will still return a value. The first argument to the Message function is the value it will return as an Interval. You can also add conditions to log the Message. Below are the components of this line broken down: Message(null as Interval, true, '1', 'Error', 'Cannot compute a single interval from a Timing type') • null as Interval = the first argument is the value you want to return • true = the condition, you can conditionally return or log the message. • 1 = the error code. • Error = the message you want to send back - this can be ‘Error,’ ‘Warning,’ or ‘Information.’ If it’s an ‘Error,’ it also stops processing. If it’s a ‘Warning,’ it’s a message that comes back as part of the evaluation. Since it doesn’t stop processing, it needs to send back a result. • Cannot compute a single interval from a Timing type = the message you actually want to send out.
EXM130 - Normalize Interval Function: Based on EXM130 – consider the Procedure.performed element:
http://hl7.org/fhir/STU3/procedure-definitions.html#Procedure.performed_x_
http://hl7.org/fhir/procedure-definitions.html#Procedure.performed_x_
In the Fast Healthcare Interoperability Resources (FHIR) Standard for Trial Use (STU) 3, it is defined as a choice of dateTime|Period and in R4, it is defined as a choice of dateTime|Period|string|Age|Range where the Quality Improvement (QI) Core constrains out the “string” type.
For STU3, the standard “Normalize Interval” function works:
define "Total Colectomy Performed":
[Procedure: "Total Colectomy"] Colectomy
where Colectomy.status = 'completed'
and Global."Normalize Interval"(Colectomy.performed) starts on or before end of "Measurement
Period"
But for R4, we need to expand the “Normalize Interval” function to allow for the new types:
define function "Normalize Interval"(choice Choice<FHIR.dateTime, FHIR.Period, FHIR.Timing, FHIR.instant, FHIR.string,
FHIR.Age, FHIR.Range>):
case
when choice is FHIR.dateTime then
Interval[FHIRHelpers.ToDateTime(choice as FHIR.dateTime), FHIRHelpers.ToDateTime(choice as FHIR.dateTime)]
when choice is FHIR.Period then
FHIRHelpers.ToInterval(choice as FHIR.Period)
when choice is FHIR.instant then
Interval[FHIRHelpers.ToDateTime(choice as FHIR.instant), FHIRHelpers.ToDateTime(choice as
FHIR.instant)]
when choice is FHIR.Age then
Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(choice as FHIR.Age),
FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(choice as FHIR.Age) + 1
year)
when choice is FHIR.Range then
Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((choice as
FHIR.Range).low),
FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((choice as
FHIR.Range).high) + 1 year)
when choice is FHIR.Timing then
Message(null as Interval<DateTime>, true, '1', 'Error', 'Cannot compute a single interval from a
Timing type')
when choice is FHIR.string then
Message(null as Interval<DateTime>, true, '1', 'Error', 'Cannot compute an interval from a String value')
else null as Interval<DateTime>
end
It’s a great idea to have a “Normalize Interval” function in the global library, but will you have one function, “Normalize Interval” function, that contains the flexible arguments in the parameter or will you have multiple ones depending on the FHIR resource? How many choice data type elements will the resource have? (Session 41 - 1/16/20)
For STU3, the standard “Normalize Interval” function works:
define "Total Colectomy Performed":
[Procedure: "Total Colectomy"] Colectomy
where Colectomy.status = 'completed'
and Global."Normalize Interval"(Colectomy.performed) starts on or before end of "Measurement
Period"
define function "Normalize Interval"(choice Choice<FHIR.dateTime, FHIR.Period, FHIR.Timing, FHIR.instant, FHIR.string,
FHIR.Age, FHIR.Range>):
case
when choice is FHIR.dateTime then
Interval[FHIRHelpers.ToDateTime(choice as FHIR.dateTime), FHIRHelpers.ToDateTime(choice as FHIR.dateTime)]
when choice is FHIR.Period then
FHIRHelpers.ToInterval(choice as FHIR.Period)
when choice is FHIR.instant then
Interval[FHIRHelpers.ToDateTime(choice as FHIR.instant), FHIRHelpers.ToDateTime(choice as
FHIR.instant)]
when choice is FHIR.Age then
Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(choice as FHIR.Age),
FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(choice as FHIR.Age) + 1
year)
when choice is FHIR.Range then
Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((choice as
FHIR.Range).low),
FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((choice as
FHIR.Range).high) + 1 year)
when choice is FHIR.Timing then
Message(null as Interval<DateTime>, true, '1', 'Error', 'Cannot compute a single interval from a
Timing type')
when choice is FHIR.string then
Message(null as Interval<DateTime>, true, '1', 'Error', 'Cannot compute an interval from a String value')
else null as Interval<DateTime>
end
- We could define “Normalize Interval” versions that had only the choices for what we wanted but when you start doing that, the differences between the sets of types defined in the different elements becomes unwieldy. By defining one version of “Normalize Interval” that has all the possible choice types we encounter in timing elements, we can define one function that can handle all of those and then as long as the types that the elements can be are a subset of these, then the Clinical Quality Language (CQL) can work with it.
EXM130 - Timing, Instance, and String: Based on the EXM130 expression below, why does the message only happen to the timing, instance, and string? Does it not apply for all resources? (Session 41 - 1/16/20)
define function "Normalize Interval"(choice Choice<FHIR.dateTime, FHIR.Period, FHIR.Timing, FHIR.instant, FHIR.string,
FHIR.Age, FHIR.Range>):
case
when choice is FHIR.dateTime then
Interval[FHIRHelpers.ToDateTime(choice as FHIR.dateTime), FHIRHelpers.ToDateTime(choice as FHIR.dateTime)]
when choice is FHIR.Period then
FHIRHelpers.ToInterval(choice as FHIR.Period)
when choice is FHIR.instant then
Interval[FHIRHelpers.ToDateTime(choice as FHIR.instant), FHIRHelpers.ToDateTime(choice as
FHIR.instant)]
when choice is FHIR.Age then
Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(choice as FHIR.Age),
FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(choice as FHIR.Age) + 1
year)
when choice is FHIR.Range then
Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((choice as
FHIR.Range).low),
FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((choice as
FHIR.Range).high) + 1 year)
when choice is FHIR.Timing then
Message(null as Interval<DateTime>, true, '1', 'Error', 'Cannot compute a single interval from a
Timing type')
when choice is FHIR.string then
Message(null as Interval<DateTime>, true, '1', 'Error', 'Cannot compute an interval from a String value')
else null as Interval<DateTime>
end
- In the case of a timing element as highlighted above, a FHIR.string means you have a string that the user entered as the value for that time. For example, in this condition the resource string could have a value of “last spring.” With Fast Healthcare Interoperability Resources (FHIR) in the condition resource, it is possible to communicate that the user has actually entered the timing as just a natural language string and that’s what the string choice type means. Regarding FHIR.Instant as highlighted above, it is okay since it is just a DateTime that has to be to the millisecond. Regarding timing, in R4 it is an observation which was expanded to support not only DateTime and Period, but also Timing and Instant.
Timing is a very complex object that lets you build up very complex schedules, therefore, it is very flexible. However, if there is a use case where there is a system that wants to use the timing type of the effective element of an observation it means that the data received cannot be processed, which will result in an error message.
EXM135 - ConditionOnsetAbatementPrevalencePeriod for Delivery: The measure EXM135 is an example of ConditionOnsetAbatementPrevalencePeriod where we determine the Onset/Abatement and Prevalence Period for a condition. We define the Prevalence Period:
define function "Prevalence Period"(condition Condition):
Interval[start of "Normalize Onset"(condition.onset), end of "Normalize Abatement"(condition))
This makes sense for Pregnancy, but could you also use the procedure ‘Delivery’ to say that it ended? (Session 41 - 1/16/20)
define function "Prevalence Period"(condition Condition):
Interval[start of "Normalize Onset"(condition.onset), end of "Normalize Abatement"(condition))
- Yes, this is correct. For all other conditions you can follow the example but for pregnancy you can also use ‘Delivery’.
EXM135 - Use of Parentheses and Brackets in Timepoint of the Abatement: For the measure EXM135, is there a reason you exclude the timepoint of the abatement time by using parentheses “)” instead of brackets “]”? (Session 41 - 1/16/20)
- Yes, there are two reasons:
- This is when they said the condition was over so it really shouldn’t be included during the time that it is happening. It is an exclusive boundary.
- More importantly, if this element is not present and we use the inclusive boundary, then it would mean that this condition goes until the end of time. This is the opposite of what we are trying to say. Using the inclusive boundary means that we don’t know when that interval ends.
So, excluding the abatement time assumes that the condition abated some time before the recorded date. If this is false, then it means the condition goes on until the end of time.
FHIR Bulk Export: From a performance perspective of a Quality Data Model (QDM) approach vs. a Fast Healthcare Interoperability Resource (FHIR) approach, you might be able to have a pretty efficient database access with QDM. With the FHIR measure doing one patient at time, you’re going to be making a lot of FHIR application programming interface (API) calls. One option might be to use a FHIR bulk export, which would return a large Newline-Delimited JavaScript Object Notation (ND JSON) file (the file format used for bulk data transfer in FHIR), that might be a way to get a lot of information at one time. Is there some thought to using a bulk concept to frontload the data?(Session 39 - 10/24/19)
- The bulk export is certainly one option for retrieving all the data at one time. The way Clinical Quality Language (CQL) is built, all of the data access is expressed in terms of these retrieves so you can characterize the overall data requirements for this library by only looking at this set of retrieves and use that as parameters for an export operation to say “export all of the data required for this whole population given these data requirements.” You can think of evaluating this patient at the time against a Fast Healthcare Interoperability Resources (FHIR) endpoint where these retrieves are converted into actual FHIR Uniform Resource Locators (URLs) is one approach to implementation. You can also imagine an implementation that went behind the scenes to access the same infrastructure that is supporting the FHIR application programming interface (API) and dig into that directly. With the reference implementation that is plugged into a HAPI FHIR server that reaches into the data access layer, you’re hitting the underlying data access layer instead of going back out through the endpoint when you run the CQL in process in the HAPI FHIR server. It’s still the case that in the reference implementation, the processing is happening a patient at a time, but you can imagine an implementation that does the same kind of thing but across the population rather than a patient at a time. The key aspect that’s being communicated from the logic perspective is what the structure looks like and what the criteria are. It may be the case that you actually have a structured query language (SQL) database that has your FHIR resources in it and the way that you take the CQL and run it in that environment may be to translate it to an SQL query across patients. So there are a lot of potential approaches to evaluating the CQL and one of the primary goals of this approach is to make sure the logic can be shared and evaluated in all those different environments that makes the most sense for those environments.
Quality Improvement (QI)-Core QUICK: Once the Quality Improvement (QI)-Core QUICK model is fully mature and supported, will that be the expected mechanism to write a Fast Healthcare Interoperability Resources (FHIR)-based measure? (Session 39 - 10/24/19)
- We’re currently exploring that and making sure that the specifications can be expressed that way and making sure that it’s a reasonable and feasible way of doing this. At this point, we’re still in the exploration phase. We are making sure the specification fully supports the specification of electronic clinical quality measure and quality reporting using this mechanism.
Status with Negation: In the draft of the Exclusive Breast Milk Feeding Measure - PC-05 (snippet from EXM9_FHIR4 version 8.1.000), there are some specific changes on how to represent some of the concepts used in this measure within Fast Healthcare Interoperability Resources (FHIR). Specifically, there are some changes around representation of total parenteral nutrition and nutrition intake resource that are planned for R5. When looking at a single newborn with no parenteral nutrition given, generally does status matter if it’s a negation? (Session 39 - 10/24/19)
- The example below states that “there is no evidence that parenteral nutrition was applied because there is no record that it happened.” That’s not a guarantee that it didn’t happen since some occurrences may not be reflected in the database. The statement is merely saying that there is no evidence that parenteral nutrition was applied. We could take it one step further and say we are looking for positive evidence (for example, documentation) that it wasn’t applied, but such documentation rarely, if ever, exists. Appropriate statuses are: in-progress, not-done, on-hold, completed, entered-in-error, stopped, and unknown. Other than entered-in-error and unknown, this set of responses are valid statuses in this context. However, when you get Fast Healthcare Interoperability Resources (FHIR) to indicate the Quality Data Model (QDM) concept of negation rationale, you have to add a status to say it didn’t happen and there is no evidence of it. In the example below, the expression only evaluates that there is no evidence of MedicationAdministration: Parenteral Nutrition. A reason for absence of the activity is not requested. Note, the code below was tested and the updated measure was presented at the 11/4/2019 QDM Weekly Touchpoint meeting.
define "Single Live Birth Encounter Without Galactosemia and Parenteral Nutrition":
"Single Live Birth Encounter" SingleLiveBirthEncounter
without ["MedicationAdministration": "Parenteral Nutrition"] ParenteralNutrition
such that ParenteralNutrition.effective as Period starts during SingleLiveBirthEncounter.period
//where not (Global.EncounterDiagnosis(SingleLiveBirthEncounter).code in "Galactosemia")
where not exists (
(Global.EncounterDiagnosis(SingleLiveBirthEncounter)) EncounterDiagnosis
where EncounterDiagnosis.code in "Galactosemia"
)
Value Sets with Procedure Codes: With the Exclusive Breast Milk Feeding Measure - PC-05 (snippet from EXM9_FHIR4 version 8.1.001), currently Breast Milk is a value set with a substance code. In order to use the Procedure resource, can we create a new value set containing all the Procedure codes for Breast Milk? If we have that value set, can we just say procedure breast milk? (Session 39 - 10/24/19)
- If you can find specific procedure codes that say “feeding breast milk” then that would be your procedure, but if you’re looking for exclusive breast milk feeding and no other substances then you would have to find a procedure code for “exclusive breast milk feeding” or indicate without “procedure enteral feeding for any substance other than breast milk.” It will be easier to find a procedure code (or codes) more generic to enteral feeding, meaning feeding through the gastrointestinal track and then indicate a Procedure.usedCode of breast milk substances and indicate no feeding with anything else.
When the Quality Data Model says Substance Administered, empirically we know what that means, but the only substances that Fast Healthcare Interoperability Resources (FHIR) references as administered are those that can be classified as medications. We count total parenteral nutrition like a medication but breast milk isn’t handled that way. Until such time as FHIR includes a way to represent intake and output of substances, the best approach is to use the Procedure resource to reference “feeding” as a procedure and Procedure.usedCode as the substance used to accomplish the feeding procedure. The implementation question and the terminology choice require consideration regarding how the data would be represented in the systems. A procedure that used Enteral Feeding as the code would capture more of the intent. Note, the code below was tested and the updated measure was presented at the 11/4/2019 QDM Weekly Touchpoint meeting.
define "Single Live Birth Encounter With Newborn Fed Breast Milk Only Since Birth":
"Single Live Birth Encounter With Gestational Age 37 Weeks or More" QualifyingEncounter
with ["Procedure": "Enteral Feeding"] BreastMilkFeeding
such that BreastMilkFeeding.status in { 'complete', 'in-progress' }
and BreastMilkFeeding.performed as Period starts during QualifyingEncounter.period
and BreastMilkFeeding.usedCode in "Breast Milk"
//with ["Procedure": usedCode in "Breast Milk"] BreastMilkFeeding
// such that BreastMilkFeeding.status = 'complete'
// and BreastMilkFeeding.performed as Period starts during QualifyingEncounter.period
without ["Procedure": "Enteral Feeding"] OtherFeeding
such that OtherFeeding.status in {'complete', 'in progress'}
and OtherFeeding.performed as Period starts during QualifyingEncounter.period
and not (OtherFeeding.usedCode in "Breast Milk")
//without ["Procedure": usedCode in "Dietary Intake Other than Breast Milk"] OtherFeeding
// such that OtherFeeding.status in {'complete', 'in progress'}
// and OtherFeeding.performed as Period starts during QualifyingEncounter.period
Authoring Patterns - QICore v4.1.1
Authoring Patterns - QICore v5.0.0
Authoring Patterns - QICore v6.0.0
Cooking with CQL Q&A All Categories
Additional Q&A Examples
Developers Introduction to CQL
Specifying Population Criteria