cqframework / clinical_quality_language

Clinical Quality Language (CQL) is an HL7 specification for the expression of clinical knowledge that can be used within both the Clinical Decision Support (CDS) and Clinical Quality Measurement (CQM) domains. This repository contains complementary tooling in support of that specification.
https://confluence.hl7.org/display/CDS/Clinical+Quality+Language
Apache License 2.0
257 stars 120 forks source link

IdentifierRef present in cast in sort-by clause when ELM shows field resolution #281

Closed WilliamParker closed 6 years ago

WilliamParker commented 6 years ago

I'm getting an IdentifierRef in my ELM when it appears that the field should be resolvable, and the ELM includes information that it couldn't if the CQL2ELM translator couldn't resolve the field. Specifically, the field in question is a choice type, and the IdentifierRef includes correct information on the available choice types.

define "MostRecentLDL":
   ("LDLResults") L sort by (effective as dateTime)

"LDLResults" is a list of Observation, which can be seen in the ELM. The ELM generated is below:

{:tag :def,
 :attrs
 {:accessLevel "Public", :context "Patient", :name "MostRecentLDL"},
 :content
 [{:tag :resultTypeSpecifier,
   :attrs {:xsi:type "ListTypeSpecifier"},
   :content
   [{:tag :elementType,
     :attrs {:xsi:type "NamedTypeSpecifier", :name "fhir:Observation"},
     :content nil}]}
  {:tag :expression,
   :attrs {:xsi:type "Query"},
   :content
   [{:tag :resultTypeSpecifier,
     :attrs {:xsi:type "ListTypeSpecifier"},
     :content
     [{:tag :elementType,
       :attrs
       {:xsi:type "NamedTypeSpecifier", :name "fhir:Observation"},
       :content nil}]}
    {:tag :source,
     :attrs {:alias "L"},
     :content
     [{:tag :resultTypeSpecifier,
       :attrs {:xsi:type "ListTypeSpecifier"},
       :content
       [{:tag :elementType,
         :attrs
         {:xsi:type "NamedTypeSpecifier", :name "fhir:Observation"},
         :content nil}]}
      {:tag :expression,
       :attrs {:xsi:type "ExpressionRef", :name "LDLResults"},
       :content
       [{:tag :resultTypeSpecifier,
         :attrs {:xsi:type "ListTypeSpecifier"},
         :content
         [{:tag :elementType,
           :attrs
           {:xsi:type "NamedTypeSpecifier", :name "fhir:Observation"},
           :content nil}]}]}]}
    {:tag :sort,
     :attrs nil,
     :content
     [{:tag :by,
       :attrs
       {:xsi:type "ByExpression",
        :direction "asc",
        :resultTypeName "fhir:dateTime"},
       :content
       [{:tag :expression,
         :attrs
         {:xsi:type "As",
          :strict "false",
          :resultTypeName "fhir:dateTime"},
         :content
         [{:tag :operand,
           :attrs {:xsi:type "IdentifierRef", :name "effective"},
           :content
           [{:tag :resultTypeSpecifier,
             :attrs {:xsi:type "ChoiceTypeSpecifier"},
             :content
             [{:tag :type,
               :attrs
               {:xsi:type "NamedTypeSpecifier", :name "fhir:dateTime"},
               :content nil}
              {:tag :type,
               :attrs
               {:xsi:type "NamedTypeSpecifier", :name "fhir:Period"},
               :content nil}]}]}
          {:tag :asTypeSpecifier,
           :attrs
           {:xsi:type "NamedTypeSpecifier",
            :name "fhir:dateTime",
            :resultTypeName "fhir:dateTime"},
           :content nil}]}]}]}]}]}

Specifically, note the following segment:

{:tag :expression,
  :attrs
  {:xsi:type "As",
   :strict "false",
   :resultTypeName "fhir:dateTime"},
  :content
  [{:tag :operand,
    :attrs {:xsi:type "IdentifierRef", :name "effective"},
    :content
    [{:tag :resultTypeSpecifier,
      :attrs {:xsi:type "ChoiceTypeSpecifier"},
      :content
      [{:tag :type,
        :attrs
        {:xsi:type "NamedTypeSpecifier", :name "fhir:dateTime"},
        :content nil}
       {:tag :type,
        :attrs
        {:xsi:type "NamedTypeSpecifier", :name "fhir:Period"},
        :content nil}]}]}
   {:tag :asTypeSpecifier,
    :attrs
    {:xsi:type "NamedTypeSpecifier",
     :name "fhir:dateTime",
     :resultTypeName "fhir:dateTime"},
    :content nil}]}

There is an IdentifierRef of "effective", which is a field on Observation. The "ChoiceTypeSpecifier" correctly lists the possible types which indicates that the field has been resolved, but if so why do we have an IdentifierRef, which the expression.xsd indicates is for something that isn't resolvable?

brynrhodes commented 6 years ago

When visiting a sort by, the translator uses an IdentifierRef to track the reference to a column, and then replaces it with a ByColumn element if it can unambiguously determine that it's a column reference. In this case, because the sort expression has a cast, the sort translation views it as a sort by an expression and so it leaves the IdentifierRef in place.

You're right that the documentation for IdentifierRef states that you're safe not implementing them, but this translation output is inconsistent with that. The Java-based CQL engine deals with IdentifierRefs by resolving against the current stack. I'm not sure what the JavaScript implementation would do, but I suspect it behaves similarly since this issue has not come up so far.

So I think this is a case where that documentation needs to be updated with respect to the evaluation behavior of an expression in an order by. What is the difficulty level of implementing an identifier resolution in your environment?

WilliamParker commented 6 years ago

@brynrhodes thanks for the info. I'll look into what it what it would take to implement the identifier resolution. I'd surmise that we can figure out a way. My larger concern would be of conflating things that actually should be errors with those that aren't (like this).. I'll have to look at it some more in the coming days.

brynrhodes commented 6 years ago

I have submitted STU comment #1540 to correct the documentation for IdentifierRef.