FirelyTeam / firely-cql-sdk

BSD 3-Clause "New" or "Revised" License
28 stars 16 forks source link

Support "profile informed" ModelInfo #407

Open ewoutkramer opened 2 months ago

ewoutkramer commented 2 months ago

It is possible to specify another model than FHIR, i.e. "QICore", in which case the modelinfo introduces first-class members into the resources for extensions and slices.

E.g..


define "InDemographic":
    AgeInYearsAt(start of MeasurementPeriod) >= 16 and AgeInYearsAt(start of MeasurementPeriod) < 24
        and "Patient"."birthsex" in test."Female Administrative Sex"

define "Bla":
    ["observation-bp"] O where O.SystolicBP.value < 140 'mm[Hg]'

Introduces a direct reference to the birthsex extension, and the SystolicBP slice.

All of this information is available in the profile-specific modelinfos, available from here: https://github.com/cqframework/clinical_quality_language/tree/master/Src/java/quick/src/main/resources/org/hl7/fhir

The syntax in modelinfo is actually pretty generic, elements are augmented with a target attribute that specifies a (XPath?) query to replace the element and map to the target model.

I.e. the birthsex extension looks like this:

  <element name="birthsex" elementType="USCore.BirthSexExtension" target="%parent.extension[url='http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex'].value.value"/>

Note that this is not just a search/replace, as the syntax describing the replacement is not CQL itself, but XPath.

ewoutkramer commented 2 months ago

Here is an analysis of the forms of XPath queries encountered in USCore and QICore:

(from USCore)

<any resource name>
'%value.value'
'FHIRHelpers.ToInterval(%value)'   // and many other functions from FHIRHelpers instead of ToInterval()
'FHIRHelpers.ToCode(%parent.extension[url='ombCategory'].value)'
'%parent.extension[url='text'].value.value'
'%parent.extension[url='http://hl7.org/fhir/us/core/StructureDefinition/us-core-race']'
'%parent.extension[url='http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex'].value.value'
'FHIRHelpers.ToConcept(%parent.category[coding.system='http://terminology.hl7.org/CodeSystem/observation-category',coding.code='vital-signs'])'
'FHIRHelpers.ToCode(%parent.code.coding[system='http://loinc.org',code='2708-6'])'

(from QICore)

%value.value
FHIRHelpers.ToInterval(%value)
FHIRHelpers.ToQuantity(%parent.extension[url='http://hl7.org/fhir/StructureDefinition/allergyintolerance-resolutionAge'].value)
FHIRHelpers.ToQuantity(%parent.reaction.extension[url='http://hl7.org/fhir/StructureDefinition/allergyintolerance-duration'].value)
%parent.extension[url='http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-recorded'].value.value
System.Concept:FHIRHelpers.ToConcept(%value);System.ValueSet:FHIRHelpers.ToValueSet(%parent.topic.extension[url='http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-notDoneValueSet'].value.value)
System.Concept:FHIRHelpers.ToConcept(%value);System.ValueSet:FHIRHelpers.ToValueSet(%parent.code[x].extension[url='http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-notDoneValueSet'].value.value)
%parent.extension[url='null']
System.Concept:FHIRHelpers.ToConcept(%value);System.ValueSet:FHIRHelpers.ToValueSet(%parent.vaccineCode.extension[url='http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-notDoneValueSet'].value.value)
System.Concept:FHIRHelpers.ToConcept(%value);System.ValueSet:FHIRHelpers.ToValueSet(%parent.medication[x].extension[url='http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-notDoneValueSet'].value.value)
ewoutkramer commented 2 months ago

As can be glanced from the above, the XPath language needs at minimum a feature to recognize qualified identifiers (FHIRHelpers.ToInterval), placeholders (%value, %parent), path navigation (both relative and based on placeholders!) (%parent.code.coding, url), selection with multiple criteria ([coding.system='something', coding.code='vital signs'])

Also, the expression can actually be a list of XPath statements, selected based on the static(?) type of the expression.

This probably needs a specific sub-language parser for which we could use superpower or something comparably light.

ewoutkramer commented 2 months ago

And we need to study the CQL that is being generated for it by the Java stack. I.e. a simple extension looks like:

 <operand xsi:type="Query">
                     <source alias="$this">
                        <expression path="extension" xsi:type="Property">
                           <source localId="33" locator="15:7-15:15" resultTypeName="fhir:Patient" name="Patient" xsi:type="ExpressionRef"/>
                        </expression>
                     </source>
                     <where xsi:type="Equal">
                        <operand name="ToString" libraryName="FHIRHelpers" xsi:type="FunctionRef">
                           <operand path="url" scope="$this" xsi:type="Property"/>
                        </operand>
                        <operand valueType="t:String" value="http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex" xsi:type="Literal"/>
                     </where>
                     <return distinct="false">
                        <expression path="value.value" xsi:type="Property">
                           <source name="$this" xsi:type="AliasRef"/>
                        </expression>
                     </return>
                  </operand>
ewoutkramer commented 2 months ago

Here's a bit of profile-informed CQL to get you started:

library TestRetrieve version '1.0.1'

using USCore version '3.1.1'

include FHIRHelpers version '4.0.1' called FHIRHelpers

valueset "Female Administrative Sex": '2.16.840.1.113883.3.560.100.2'

parameter MeasurementPeriod default Interval[DateTime(2013, 1, 1, 0, 0, 0, 0), DateTime(2014, 1, 1, 0, 0, 0, 0))

context Patient

define "InDemographic":
    AgeInYearsAt(start of MeasurementPeriod) >= 16 and AgeInYearsAt(start of MeasurementPeriod) < 24
        and "Patient"."birthsex" in "Female Administrative Sex"

define "Bla":
    ["observation-bp"] O where O.SystolicBP.value < 140 'mm[Hg]'
ewoutkramer commented 2 months ago

Complexity may arise from which slicing is supported, e.g. the slices in the above (systolic) are simple fix-value slices on coding.code and system. But may there be slices on valuesets? We probably don't need to support everything now, as long as we keep an eye on new updates to the most common (QICore, USCore) modelinfos....