Stream: fhirpath
Topic: data extraction for Questionnaires
Brian Postlethwaite (May 19 2020 at 00:44):
I'm doing a pre-population fhirpath expression for converting what is a string in the source data, and need to convert it to a Coding for the QuestionnaireResponse item (as it is a choice type).
Has this been covered anywhere already?
@Lloyd McKenzie , @Paul Lynch - come across this in any forms you guys have looked at?
Was considering using the %terminologies
stuff, but that seemed odd.
what would feel more natural would be a shortcut like
%LaunchPatient.address.first().postalCode.toCoding('http://exmaple.org/CodeSystem/codedPostcodes')
the other possibility I considered was using the translate function
%terminologies.translate(conceptMap, code, params) : Parameters
Even this would have been more natural as .translate from a string/code/coding as the context
e.g. %LaunchPatient.address.first().postalCode.translate('http://exmaple.org/ConceptMap/pcConverter').parameter.where(name='match').part.where(name='concept')
Lloyd McKenzie (May 19 2020 at 01:27):
Not something I've seen yet, no.
Bryn Rhodes (May 19 2020 at 05:19):
Yeah, FHIRPath doesn't have syntax or operators to support this currently.
Bryn Rhodes (May 19 2020 at 05:20):
The translate is an interesting option, but I agree, a function to construct the value would be more natural.
Michael Lawley (May 19 2020 at 05:21):
What is the value you are converting?
Michael Lawley (May 19 2020 at 05:21):
i.e., do you have a code, or are you trying to find a code from some display text?
Michael Lawley (May 19 2020 at 05:23):
If you have a known code and system then I would have expected you could go via token syntax -- http://exmaple.org/CodeSystem/postcode|1234
Paul Lynch (May 19 2020 at 17:02):
I don't think I have had to translate a string to Coding.
Grahame Grieve (May 20 2020 at 20:39):
I have defined a number of factory functions for this kind of use - mainly creating complex data types
Brian Postlethwaite (May 20 2020 at 21:28):
@Michael Lawley , its a string in the source content (e.g. Address.State) and being populated into a coded field on a questionnaire form.
Michael Lawley (May 20 2020 at 21:31):
So you just looking for some kind of type cast? string -> code
(I'm not a FHIRPath expert, just looking to understand the interactions with terminology.)
Grahame Grieve (May 20 2020 at 23:36):
string -> Coding.
Michael Lawley (May 20 2020 at 23:44):
Which is why I was thinking of "token syntax" as the literal form of a Coding, but I see now from http://hl7.org/fhirpath/#literals that it is missing.
Still, why not 'http://exmaple.org/CodeSystem/postcode|1234' as Coding
or
''http://exmaple.org/CodeSystem/postcode|' + %LaunchPatient.address.first().postalCode as Coding
?
Paul Lynch (May 20 2020 at 23:57):
That is an interesting idea. More generally, it might be useful to have a way in FHIRPath to create a structure (Coding, or something else) from primitives.
Michael Lawley (May 21 2020 at 01:02):
Based on https://www.hl7.org/fhir/search.html#token it might make sense if the token syntax could be converted to at least Coding
, CodeableConcept
, Identifier
, and ContactPoint
using the as
mechanism?
Bryn Rhodes (May 21 2020 at 01:06):
as
is a type cast though, not a conversion function. A string as
a Coding already has a defined meaning in FHIRPath. A conversion function for strings to and from Coding/CodeableConcept would make sense though, something like 'http://exmaple.org/CodeSystem/postcode|1234'.toCoding()
Grahame Grieve (May 21 2020 at 01:32):
we shouldn't invent crazy syntaxes. Factory.toCocing('http://exmaple.org/CodeSystem/postcode', '1234') is nicer
Brian Postlethwaite (May 21 2020 at 01:53):
toCocing :grinning_face_with_smiling_eyes:
Brian Postlethwaite (May 21 2020 at 01:53):
Factory rather than Fluent?
Brian Postlethwaite (May 21 2020 at 01:54):
The other operations .toString() or .toInteger() are fluent like this (without the factory).
Brian Postlethwaite (May 21 2020 at 01:56):
So for my example at the top this would be:
Factory.toCoding('http://exmaple.org/CodeSystem/codedPostcodes', %LaunchPatient.address.first().postalCode)
Grahame Grieve (May 21 2020 at 03:27):
not sure what you mean by fluent?
Michael Lawley (May 21 2020 at 04:26):
or re-use crazy syntaxes :-)
Brian Postlethwaite (Sep 10 2020 at 17:58):
Has this got any legs?
We're doing a sample quesitonnaire and kinda want to use this?
Brian Postlethwaite (Sep 10 2020 at 18:03):
My sample form want's to fill a coded question for the language based on the Patient.language(s)
Paul Lynch (Sep 11 2020 at 14:15):
Isn't language already a CodeableConcept from which you can get Codings? Did you mean a different field in Patient?
Grahame Grieve (Sep 13 2020 at 10:17):
has what got legs?
Brian Postlethwaite (Sep 13 2020 at 21:39):
The toCoding factory.
Grahame Grieve (Sep 13 2020 at 22:09):
I think it would be good to have a factory API like the terminology service API as part of the FHIR adoption of FHIRPath
Brian Postlethwaite (Sep 14 2020 at 01:20):
Since this was a type conversion, that's why I was encouraging the same format as toString() and toInteger()
Paul Lynch (Oct 01 2020 at 23:56):
@Bryn Rhodes Has this issue (of making Codings) been addressed by the recent changes? I ran into a case where I wanted to convert ValueSet.expansion.contains elements into Codings, and couldn't. (They are close enough that in fhirpath.js I could get away without converting, and just pretend they were Codings, but it is not really "proper" from a type perspective.)
Bryn Rhodes (Oct 02 2020 at 20:16):
This hasn't been addressed by any spec changes I'm aware of, no. There are different potential approaches, we'd have to land one to move forward here.
Paul Lynch (Oct 02 2020 at 22:56):
Here is a proposal that would allow construction of coding from ValueSet.expansion.contains (or from other things).
toCoding(displayExpr, codeExpr, systemExpr)
It applies the expressions given as parameters to the list of nodes on which toCoding is called. (Yes, Coding also has fields "version" and "userSelected". Parameters could be added for those as well, if desired.)
Examples:
1) If %vs is a ValueSet expansion:
%vs.expansion.contains.toCoding(display, code, system)
This takes the display, code, and system properties of each node in "contains", and uses them to create a list of Codings with those properties.
2) If you just want to convert a list of codes into codings with same system (borrowing from Brian's example above):
%patient.address.postalCode.toCoding($this, $this, 'http://exmaple.org/CodeSystem/codedPostcodes')
3) If you really wanted to start from scratch (though I am not sure why you would do this), you could do:
(1).toCoding('Retired', 'retired', 'http://hl7.org/fhir/publication-status')
In this case this value of the input would be ignored, so it is a bit of hack, but I am not sure this is a real use case.
Grahame Grieve (Oct 02 2020 at 23:49):
I would not want to add something specific for this. In general, the FHIR profile on FHIRPath should define a factory class that constructs data type instances. I already have informal constructors for Quantity, Coding, Identifier and Period.
Bryn Rhodes (Oct 03 2020 at 15:23):
What does that syntax look like, and are there any language facilities that need to be in place to support that?
Grahame Grieve (Oct 04 2020 at 04:48):
I don't think it needs language features. I've just defined a Factory class that expresses a bunch of functions on it. E.g.
Factory.coding(system, code, display)
Paul Lynch (Oct 05 2020 at 14:30):
How would you use that to convert ValueSet.expansion.contains to Codings?
Bryn Rhodes (Oct 05 2020 at 15:22):
ValueSet.expansion.contains.select(Factory.coding(system, code, display))
Paul Lynch (Oct 05 2020 at 21:37):
That does not look like fhirpath to me. What is Factory.coding returning? select takes an expression.
Paul Lynch (Oct 05 2020 at 21:43):
Grahame Grieve said:
I would not want to add something specific for this. In general, the FHIR profile on FHIRPath should define a factory class that constructs data type instances. I already have informal constructors for Quantity, Coding, Identifier and Period.
So, toCoding
is specific, but Factory.coding
is not? The toCoding
I suggested above has the advantage of being consistent with the Fluent pattern used in FHIRPath.
Grahame Grieve (Oct 05 2020 at 22:18):
So, toCoding is specific, but Factory.coding is not?
I don't want to do something that only works for Coding. It should handle at least the commonly used complex data types I listed above. That's not about syntax, but about scope
In terms of syntax, I think it's better to have a Factory based syntax rather than defining .toX with probably 20+ variants for data types and parameter combinations on every data type, which appears to be what you're doing. I guess if you have a root base class in the implementation, for all possible types, then that doesn't matter, but some/many implementations won't, and will have to do special case logic for the functions. Where as "Factory" that returns a global object with all those functions defined once seems less intrusive to me
Paul Lynch (Oct 05 2020 at 22:28):
I like the idea of being able to construct more than just Codings, and I would actually prefer something that lets you construct any structure and assign a type as needed. But, I don't see how Factory.coding(...) works if you are starting with a list of objects (like my ValueSet.expansion.contains example) that you want to turn into Codings. I can see it working if you have a fixed set of strings, or if the parameters are independent of each other and just need to reach into (say) a QuestionnaireResponse for data.
Grahame Grieve (Oct 05 2020 at 22:30):
well, scope is the enemy whatever. This, for instance:
%vs.expansion.contains.toCoding(display, code, system)
it doesn't do what you think, since display, code and system come from your context... null, in other words
Paul Lynch (Oct 05 2020 at 22:31):
That depends on how toCoding is defined. I was defining it like "where" or "select", where the expression has access to the collection elements.
Grahame Grieve (Oct 05 2020 at 22:32):
well, that's exactly why I don't want to special case this stuff. resetting the context is a language specification, but specific to the function.
Paul Lynch (Oct 05 2020 at 22:34):
It's a feature of the language that already exists. I am just suggesting a new function (or set of functions) that use it.
Grahame Grieve (Oct 05 2020 at 22:56):
That's what I don't agree with. I don't think that profiles on the language -as this one is - should use that feature
Paul Lynch (Oct 05 2020 at 23:30):
Just to make sure we're talking about the same "feature"-- are you referring to the feature in which a function can take an expression as a parameter, with access to $this and $index, as described as http://hl7.org/fhirpath/#functions?
Paul Lynch (Oct 07 2020 at 17:10):
How about defining something in FHIRPath (not the FHIR profile), very general, like:
toElement(type, fields, expressions)
where type is the type of the structure you are creating, fields is an array of field names (or possibly paths, if you want to handle nested structures like Identifier.period), and expressions are the FHIRPath expressions for obtaining values for those field names. So, for example, you could do:
%vs.expansion.contains.toElement('Coding', 'display' | 'code' | 'system', 'display' | 'code' | 'system')
In this case the expressions and field names happen to be the same, but they might be different.
Grahame Grieve (Oct 07 2020 at 23:34):
sounds slippery to me. You really don't like the factory idea?
Bryn Rhodes (Oct 08 2020 at 03:49):
Yeah, I'm not seeing the problem with the Factory idea, I think that nicely solves the problem without needing to introduce any features in FHIRPath.
Bryn Rhodes (Oct 08 2020 at 03:51):
CQL has instance selector syntax for this purpose. If we really wanted to do something in FHIRPath directly, I'd suggest adopting that syntax as something that's already defined and implemented in the CQL engines.
Paul Lynch (Oct 08 2020 at 12:58):
Bryn Rhodes said:
Yeah, I'm not seeing the problem with the Factory idea, I think that nicely solves the problem without needing to introduce any features in FHIRPath.
As I mentioned above, I don't see how the Factory idea would work to convert %vs.expansion.contains into Codings.
Bryn Rhodes (Oct 08 2020 at 13:47):
That does not look like fhirpath to me. What is Factory.coding returning? select takes an expression.
Is Factory.coding(system, code, display)
not an expression?
Paul Lynch (Oct 08 2020 at 14:10):
It looked like it was passing a Coding into select(), but I see... everything inside the select is an expression that gets evaluated for each item in the "contains".
Paul Lynch (Oct 08 2020 at 14:23):
But, I think the syntax has some problems. 1) Normally in fhirpath, if you have Abc.def, Abc acts as an assertion that the context object is a resource of type Abc. The spec says is can be a path on the context, but that it should try to match the type of the context object first, and actually I don't think there has been any case yet where a path starts with an uppercase letter. "Factory" looks like a resource type.
Paul Lynch (Oct 08 2020 at 14:27):
2) A second problem is that ".coding" should be acting on the node returned by "Factory", which therefore means system
, etc., would get evaluated with the context of the Factory object, not the objects in the contains.
Bryn Rhodes (Oct 08 2020 at 14:31):
The .coding()
function isn't iterative, so doesn't introduce any context, aso the system, code, and display references fall back to the .select()
context. The reference to Factory
is part of the FHIRProfile, in the same way that terminology services are introduced.
Paul Lynch (Oct 08 2020 at 14:41):
terminology services are introduced with a variable syntax -- %terminologies, not Terminology. (I am not sure how to handle that either, but it might be better to have one way of doing it).
Paul Lynch (Oct 08 2020 at 14:44):
My main point in (2) is that (except I guess for %terminologies), in when you have a.b(...)
, a
sets the context for the b(...)
Paul Lynch (Oct 08 2020 at 14:52):
I guess what is happening is that %terminologies and Factory are introducing the idea of a namespace for functions, where the namespace does not modify the context but just provides access to a set of functions. I don't see such a concept the core FHIRPath spec (but maybe I've missed it), and it seems like a new language feature that is being introduced by the FHIR profile.
Bryn Rhodes (Oct 08 2020 at 15:15):
in a.b(...)
, a
only sets the context for b(...)
if b
is an iterative function, and that only applies to things like .where()
and .select()
. So I don't see it as introducing a new language feature, it's just taking advantage of the fact that implementations can introduce types.
Paul Lynch (Oct 08 2020 at 15:35):
By "context", I mean the data nodes against which the next segment is applied. So, in Patient.name.count()
, you are getting the count of the names, not the count of Patient, because name
has set the context for count()
.
Bryn Rhodes (Oct 08 2020 at 16:09):
Does the JS engine not have a stack?
Paul Lynch (Oct 08 2020 at 16:15):
A stack of contexts? No. But how would that help? The pattern in FHIRPath is to apply the immediate context of the thing on the left to the thing on the right.
Grahame Grieve (Oct 08 2020 at 19:43):
I'll review my implementation. I think you have a point about Factory. - I would be ok with %factory
Paul Lynch (Oct 08 2020 at 20:58):
Yes, I think I like %factory better, though it still seems odd to me (but at least it is the same oddness as %terminologies). %variable usually points to some data object (e.g. %questionnaire) which then provides the context for what follows after the .
. With %factory, you don't get a data object, but instead allow an extra set of functions to be called on whatever the context was previously. It is like a variable that holds functions rather than data. Definitely implementable, but it's a new thing compared to what I've encountered in the core FHIRPath spec.
Paul Lynch (Oct 16 2020 at 12:55):
Since %factory seems acceptable, should there be a tracker item created for it?
Grahame Grieve (Oct 20 2020 at 22:14):
yes please
Paul Lynch (Oct 21 2020 at 17:07):
Last updated: Apr 12 2022 at 19:14 UTC