Stream: cql
Topic: How does CQL 'data model' match perform context matching?
John Silva (May 04 2021 at 21:45):
I've run into problems trying to execute a CQL rule in Atom with cql-language plugin. One case in particular is for Coverage; here's this simple rule but in the plugin it is always returning 'null'. I have this specific problem but this brings up the more general question, how does a CQL rule developer know how a particular resource does context matching? If the CQL is in the Patient context Coverage has 2 possible ways to match patient, the beneficiary reference property (most likely/sensible) and the subscriber reference property. I imagine there are other resources too that have multiple possible context matching properties, e.g. for Practitioner context many FHIR resources have multiple references to Practitioners.
Michael Riley (May 05 2021 at 16:54):
I'm not the developer, but the cql-fhir-engine defines a "context" (Patient context) and a "contextPath", which is where to find the patient. It then transforms that contextPath into a fhirpath definition using a crawl of hapi-fhir's built in models of the fhir resources. This all happens in some pretty gnarly hapi fhir reflection-like code in "getContextPath" here. https://github.com/DBCG/cql_engine/blob/48c95b816724f2558ef7516cd6fb6494787e7aa6/engine.fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/model/FhirModelResolver.java#L72
So in the case of multiple hits, where multiple search parameters can forfill the "Patient" search parameter type, I believe it's just down to which reference is resolved first (undefined behavior essentially). You can override the behavior to force a specific path for the Coverage resource to use if you have access to source, you can update the child class's getContextPath similar to what the STU3 version did here. at line 270 https://github.com/DBCG/cql_engine/blob/48c95b816724f2558ef7516cd6fb6494787e7aa6/engine.fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/model/Dstu3FhirModelResolver.java
John Silva (May 05 2021 at 18:21):
@Michael Riley - thanks for the info -- but this seems to be unreliable and fragile. It seems like this should be defined in CQL language. Also for CQL rule authors, they don't have the 'luxury' of changing source code in the CQL execution engine. Maybe I'm missing something in the CQL language (I'm new to it) but it seems like there should be a declarative way to define this context relationship.
JP (May 05 2021 at 18:57):
Michael has accurately portrayed the current state of things, but that's not the intended future state. CQL is not FHIR-specific meaning that it can be used with other clinical data models such as QDM. The CQL translator uses something called a "modelinfo" to understand the properties of the data model. At the time that code was written the "modelinfo" did not include information about context relationships. The latest versions of the FHIR modelinfo have the specific properties used for context relationships.
JP (May 05 2021 at 18:58):
In the future the expectation is that the CQL engine would use the properties listed in the modelinfo.
John Silva (May 05 2021 at 20:24):
@JP Thanks again! OK, CQL is not FHIR specific but for its use with FHIR is the data model specified (or balloted)? It seems like it would be hard to know (as a CQL author) if this model isn't explicit.
I did query the modelinfo and found Coverage has 4 reference prop's -- is the order of matching these specified or does it return all where the referenced value matched the context value, e.g. Patient?
<contextRelationship context="Patient" relatedKeyElement="policyHolder"/>
<contextRelationship context="Patient" relatedKeyElement="subscriber"/>
<contextRelationship context="Patient" relatedKeyElement="beneficiary"/>
<contextRelationship context="Patient" relatedKeyElement="payor"/>
JP (May 05 2021 at 21:10):
The modelinfo is currently explicitly NOT part of the normative spec:
https://cql.hl7.org/elm.html
That said, the tooling that is used to generate modelinfos for FHIR is open-source:
https://github.com/cqframework/cqf-tooling/blob/master/src/main/java/org/opencds/cqf/tooling/modelinfo/StructureDefinitionToModelInfo.java
I recognize none of that answers the question "how does a CQL author look up the context relationship" and that's because there's no answer for that right now. Most of the Resource types that are patient compartment only relate to patient one way, so it's been something of an edge case thus far to handle things like Coverage. :smile:
As far as how the multiple context relationships will be handled: It's not specified and there's no existing implementation so that's a TBD. Eventually it'll show up when you Right Click -> CQL -> View ELM in Atom. That shows you the Retrieve that's being generated for some given CQL.
John Silva (May 05 2021 at 23:23):
Thanks for all this good info. I suspected it wasn't part of any normative spec -- is there an intention for it to be part of some informative spec to at least help guide CQL authors. (informative of course means that a CQL tool implementer doesn't have to implement it but it leads to at least some consistency in implementation I would hope.)
True, most of the simple Patient context ones are 'mostly obvious' and typically only one relationship. However, I believe the Practitioner compartment might have resources with many reference relationships. Also, the CQL spec pages only seem to have these 'simple examples' so it's hard to figure out these more non-obvious situations. (BTW, the use case for patient's Coverage is that for things like DaVinci CRD/DTR/PAS you'd need to know the relationship to get the rules to figure out the patient's coverage for prior authorization and more. I guess the Connectathon next week might uncover some of this ...)
JP (May 06 2021 at 17:01):
Well, there are a several areas of activity related to that question. One is the hope that the tooling I referenced will allow the use of profiles that are more constrained (or extended) than the current base FHIR spec. Meaning you could eventually do:
library Demo
using QICore version '3.0.2'
That machinery was built to support QICore and USCore in a first-class way in CQL, but it could in theory support any arbitrary FHIR profile. Then'd it'd be up to the profile maintainers to decide those relationships.
Another is the CQL language server (which powers the Atom plugin). It uses the VSCode language server API and has stubs in place to allow for tips, snippets, warnings, auto-completion, etc. that may help out the author. IOW, once the modelinfo is wired up to the language server correctly, that type of information would be available to the CQL author inline in the authoring environment for any given model.
Another is the "Cooking with CQL" training series which has quite a bit of info about the practical use of CQL:
https://github.com/cqframework/CQL-Formatting-and-Usage-Wiki/wiki/Cooking-with-CQL-Examples
I don't know that we've had an explicit conversation about making the FHIR model to CQL modelinfo mapping (and in particular the context relationships) part of the spec in any fashion but happy to raise that with @Bryn Rhodes and see where it goes. :smile:
John Silva (May 07 2021 at 01:00):
This is more great info; thank you.
On the context question again; yes, it is based on the specific model "in play" but doesn't it seem like the CQL language should expose a mechanism to allow an author to make these decisions declaratively. In the SQL db world the data model is up to the schema developer but the SQL language allows a query writer to 'peek into' a specific schema in order to create well-formed and executable queries. (I guess is just feels kind of 'unnatural' to be left to the variances of the model as to how context elements are matched.)
JP (May 07 2021 at 17:43):
That's true for SQL, but SQL doesn't have the quite same concept of data models that CQL does. The model as far as SQL is concerned is the schema you've defined for your database and most relationships are represented by foreign keys, and you generally have to be explicit about the relationship between two tables (i.e. X join Y on X.id = Y.x_id). There's no higher-level representation of something like "Patient" or "Encounter" context. CQL is purposefully trying to make it so that the author doesn't need to worry about those concerns. It's targeted towards clinicians who may not even understand the concept of a join rather than database administrators .
JP (May 07 2021 at 17:47):
That said, it's not the case that we're trying to hide how things relate either. You should be able to see how those things relate while authoring CQL. It's just an area that hasn't been fully developed yet. :smile:
John Silva (May 07 2021 at 20:30):
@JP OK, makes sense. I was thinking of some kind of operator where the author can explicitly say:
[Coverage: Patient -> beneficiary]
For example, if I'm only looking for linking to the actual patient not the subscriber (e.g. the parent)
Rob Reynolds (May 10 2021 at 20:55):
You do have that in:
//context Patient
[Coverage] C
with [Patient] P
such that P.id ~ C.beneficiary
Rob Reynolds (May 10 2021 at 21:09):
context Patient is syntactic sugar. If it's not doing what you want, don't use it and be explicit about what you want. :grinning:
You can even then add context Patient afterward and go back to the sugar.
Personally, I don't think the exceptional case warrants a new operator. Others might disagree.
Rob Reynolds (May 10 2021 at 21:33):
It would definitely be nice if the tooling provided insight into the magic of the sugar. But that's an editor/tooling issue, not CQL.
John Silva (May 10 2021 at 22:09):
Ah, that's the answer I was looking for -- an explicit way to define this reference linkage. Yes, syntactic sugar can be problematic when it hides what's really going on.
Can I still use that even if within the Patient content or do I need to be in the Unfiltered context or is the syntax you show the Unfiltered context?
John Silva (May 11 2021 at 14:57):
I just got around to trying this syntax in Atom with cql-language plugin and it doesn't like it:
Screen-Shot-2021-05-11-at-10.56.46-AM.png
Rob Reynolds (May 11 2021 at 15:48):
It needs to be in unfiltered context, which is just not having context patient set, which is why I commented that line in my example.
John Silva (May 11 2021 at 15:57):
OK, can I mixed Unfiltered directive within a CQL rule file that also has Patient context directive?
Rob Reynolds (May 11 2021 at 16:00):
Sorry, I kinda pseudo coded that based on the [Coverage: Patient -> beneficiary]. I should have looked at the types more closely.
You need some kind of helper to compare a reference to an id.
I've seen things like this used:
define function GetId(uri String):
Last(Split(uri, '/'))
And then the example becomes:
//context Patient
[Coverage] C
with [Patient] P
such that P.id ~ GetId(C.beneficiary.reference.value)
Rob Reynolds (May 11 2021 at 16:02):
You can put things you don't want in patient context above the context Patient statement and then things you want in patient context below it.
define PatientCoverage ...
context Patient
define MedicationsInLastSixMonths...
Bryn Rhodes (May 11 2021 at 17:05):
To add to this, around contexts and context-related joins, this test case illustrates where we are headed:
define TestMedicationRequest1C:
[MedicationRequest] MR
let M: singleton from ([MR.medication -> Medication])
where M.code ~ "aspirin 325 MG / oxycodone hydrochloride 4.84 MG Oral Tablet"
Bryn Rhodes (May 11 2021 at 17:06):
Not there yet, but the idea is that the context relationships declared in the model can be used to enable reference following.
Rob Reynolds (May 11 2021 at 17:08):
Well, I guess Bryn disagreed re the "warrants a new operator". :rolling_on_the_floor_laughing:
And the syntax is shockingly close to what John suggested. :)
Bryn Rhodes (May 11 2021 at 17:08):
And a significant step towards that has just been added to the 1.5.4-SNAPSHOT, the ability to detect and optimize joins as includes, like this, for example:
define MedicationRequestWithAspirinInFrom:
from
[MedicationRequest] R,
[Medication] M
where M.id = Last(Split(R.medication.reference, '/'))
and M.code in "Aspirin"
Bryn Rhodes (May 11 2021 at 17:09):
Yeah, the syntax is related-context retrieves, a language feature that's still trial-use because we're still working out support for it in the engines.
Rob Reynolds (May 11 2021 at 17:24):
By "includes" there you're referring to the parameters of the from?
Like:
from
include,
include,
...
?
Rob Reynolds (May 11 2021 at 17:27):
And it's always been valid CQL, right? But with 1.5.4-SNAPSHOT it recognizes it as a join and optimizes accordingly?
Bryn Rhodes (May 11 2021 at 17:30):
So that particular example would result in a FHIR query like: [base]/MedicationRequest?patient=123&_include=MedicationRequest:medication
Bryn Rhodes (May 11 2021 at 17:31):
And yes, that's always been valid CQL, there's just an additional optimization step available now that detects that pattern and optimizes it as an include.
Bryn Rhodes (May 11 2021 at 17:31):
It's not part of the core translator, it's an additional step, since optimization strategies will vary based on target environment.
Bryn Rhodes (May 11 2021 at 17:32):
There's a new elm-fhir package in the 1.5.4-SNAPSHOT that does it, and we're incorporating that capability into the engine and evaluator now.
Rob Reynolds (May 11 2021 at 17:32):
Oh that include. Gotcha.
Very cool.
John Silva (May 11 2021 at 18:03):
Hmm, it starts to look like SQL ;-)
Rob Hausam (May 11 2021 at 19:03):
Yes, quite a lot - which probably isn't a bad thing. :)
Bryn Rhodes (May 11 2021 at 20:07):
It's not an accident either :)
Michael Riley (May 14 2021 at 20:27):
Bryn, how new is the includes definition? This would be very helpful for me.
Bryn Rhodes (May 17 2021 at 14:27):
Hi @Michael Riley , the capability was added as part of the 1.5 ballot, I've just pushed a branch that supports adding applying include optimizations to a compiled ELM tree (https://github.com/cqframework/clinical_quality_language/tree/feature-requirements-visitor) and I'm currently working on incorporating that functionality in to the engine, targeting the next week or so to have support for that in the engine and evaluator components.
Brian Kaney (Dec 28 2021 at 18:38):
Just found this thread. So to be clear using AllergyIntolerance
example, I see this in the modelinfo for R4.0.1:
<contextRelationship context="Patient" relatedKeyElement="patient"/>
<contextRelationship context="Patient" relatedKeyElement="recorder"/>
<contextRelationship context="Patient" relatedKeyElement="asserter"/>
Given
context Patient
define Allergies: [AllergyIntolerance]
How does it know to use the "patient" relationship (and not "recorder" or "asserter")?
JP (Dec 28 2021 at 19:07):
Currently the attribute for the context relationship is not specified in the ELM for the Retrieve generated by the CQL translator. IOW, that specific bit of the ModelInfo is not used. It's left up the runtime. The Java cql-engine works it out by looking at the properties of AllgeryIntolerance and using a simple (but currently undocumented) heuristic to figure out which key element it should use.
Brian Kaney (Dec 28 2021 at 21:11):
Thanks @JP - would it make sense to propose an attribute added to contextRelationship (e.g. primary=boolean) to identify which relationship ought to be used in the implied case?
Brian Kaney (Dec 28 2021 at 21:14):
(And @Chris Moesel - how does the cql-execution behave?)
JP (Dec 28 2021 at 21:40):
As I recall, something along those lines is on Bryn's todo list. @Bryn Rhodes ?
Bryn Rhodes (Jan 03 2022 at 17:47):
Given that the intent of the retrieve is to provide all the resources associated with the context, the default behavior would be to return all AllergyIntolerance resources that have any relationship to the Patient (i.e. the union of the results here). The current engine doesn't behave that way (as JP noted), but in thinking through this, I think it probably should, because there's no way to know ahead of time which one is the "primary" one, without knowing the intent of the CQL. Related-context retrieves would allow authors to be more specific if they wanted to.
Chris Moesel (Jan 03 2022 at 18:19):
I feel like this thread has gone in a few different directions. So I'll just throw out some different statements / thoughts:
- @Brian Kaney - If the question is "how does cql-execution know which property to use to scope a query to the patient in context?" then the answer is: it doesn't. In fact, as currently designed, the engine isn't responsible for that at all, because it does not execute FHIR queries. Instead, the person invoking the engine passes in a FHIR
Bundle
containing all of the patient's data (already scoped to only the patient's information). So the FHIR queries and patient scoping happens prior to cql-execution doing anything. (And to be clear, it's actually thePatientSource
implementation in cql-exec-fhir that takes in the bundles since cql-execution is completely unaware of specific data models like FHIR). - BUT... in the open source CQL Services framework and the Pain Management Summary SMART app (both of which use cql-execution), we hard-coded a map containing the properties to use for scoping queries to a specific patient. That way those apps can perform the right queries to build the bundle that gets passed along to cql-exection.
- There was also some talk of the related context retrieves feature of CQL. Just to be clear, cql-execution (the javascript engine) does not support that at all. We'd be glad to take a PR though. ;-)
- @Bryn Rhodes -- I feel like using all of the contextRelationships to do a query could be problematic because it is not what authors would expect. If you are in
Patient
context, then that seems to me like enough expressed intent in the CQL to justify using whatever property represents the patient. We already support aprimaryCodePath
in ELM to indicate the default property to use for code/VS matching in a retrieve; so it seems to me that introducing aprimaryPatientPath
(or something like it) would be consistent with that. Or am I misunderstanding something in this conversation?
JP (Jan 03 2022 at 18:48):
The CQL spec says that Models may define their own contexts. Is there any existing guidance that ties the available contexts in FHIR to the compartment definitions? If so, it does seem like we should be retrieving all the resources in the compartment. The ModelInfo code-generation works that way:
Chris Moesel (Jan 03 2022 at 19:09):
I think it's smart to use the Patient compartment from FHIR, but I guess I don't fully understand why the patient compartment indicates additional properties like recorder
, asserter
, and performer
.
The description says:
The patient compartment includes any resources where the subject of the resource is the patient, and some other resources that are directly linked to resources in the patient compartment
But if we look at something like AllergyIntolerance
, it says patient or recorder or asserter
. If we are truly interested in "resources where the subject of the resource is the patient", then patient
is the right answer. At first I thought maybe recorder
and asserter
were there as fallbacks if patient
is blank, but patient
is 1..1, so... I am stumped. Why would anyone want a different patient's AllergyIntolerance in the compartment just because the primary patient asserted the allergy? It means that clients need to verify that the resulting records actually reference the primary patient in the patient
field -- which I doubt most clients do.
I know this is not the fault of anyone working on CQL, but... I'm not sure that we should just embrace it as-is. To me, it still makes sense to have a more specific qualifier to say "this is the field that really means the patient is the subject." And that field may need to be maintained by hand, just like primaryCodePath
.
Brian Kaney (Jan 03 2022 at 19:37):
There are also some inconsistencies to consider -- for example in the Patient compartment, why is recorder
missing from Condition? Also, I wonder if the compartment is too restrictive -- e.g. Patient compartment explicitly restricts practitioner. (But patient $everything operation explicitly mentions linked records, including practitioners). I've thus far been leaning toward $everything operation as a way to build the patient bundle for context.
Bryn Rhodes (Jan 03 2022 at 19:57):
@Chris Moesel , we could definitely take that approach, and it would make sense, I was basing my answer on the intended alignment of a "context" in CQL with the concept of a "compartment" in FHIR, though even that notion is still under some development in FHIR. The Retrieve in 1.5 has context
, contextProperty
and contextSearch
, so we could definitely implement a primaryContextPath
in the ModelInfo to support this. I'll add a feature request for it.
Richard Stanley (Jan 03 2022 at 20:02):
I hope folks don't mind a slight detour to earlier messages, but I'm stepping through this thread and I see an error (inline) with the above example. Is this correct? Edit: Here's the pastebin of the full error: https://pastebin.com/SvBMGu2G
library Unfiltered version '0.1.0'
using FHIR version '4.0.1'
include FHIRHelpers version '4.0.1'
include FHIRCommon version '4.0.1' called FC
context Unfiltered
// per fhir chat: this helper is for converting a reference to an id
define function "GetId"(uri String):
Last(Split(uri, '/'))
// this expression results in org.opencds.cqf.cql.engine.fhir.exception.UnknownType: Could not resolve type Unfiltered.
define "some test":
[Coverage] C
with [Patient] P
such that P.id ~ GetId(C.beneficiary.reference.value)
context Patient
define "Meow": "some test"
Bryn Rhodes (Jan 03 2022 at 20:07):
@Richard Stanley , that looks like a bug in the Unfiltered
context, which the Java engine currently doesn't have great support for. Would you mind submitting that repro as an issue to the engine?
Richard Stanley (Jan 03 2022 at 20:07):
Yep, thanks!
Bryn Rhodes (Jan 03 2022 at 20:08):
Here's the issue to add primaryContextPath
and context
details explicitly to the translator: https://github.com/cqframework/clinical_quality_language/issues/710
Chris Moesel (Jan 03 2022 at 20:08):
Yeah, cool. I totally dig that, @Bryn Rhodes.
JP (Jan 03 2022 at 20:15):
that looks like a bug in the
Unfiltered
context
This is due to the fact that CQL engine expects the context
attribute in the ELM to be absent, rather than explicitly be set to Unfiltered
. Translator bug or it should handle that case?
Bryn Rhodes (Jan 03 2022 at 20:31):
It is optional in the spec (https://cql.hl7.org/04-logicalspecification.html#expressiondef) so the engines (I would think) should be prepared to deal with it either way.
JP (Jan 05 2022 at 17:15):
Related issue for ModelInfo code gen:
https://github.com/cqframework/cqf-tooling/issues/335
Last updated: Apr 12 2022 at 19:14 UTC