Stream: IG creation
Topic: Clarification on contentReference
Chris Moesel (Feb 10 2020 at 16:58):
I'd like to ensure that we are handling contentReference
correctly. There are two points in the wording of the spec documentation that we'd like clarified.
The definition says:
Identifies an element defined elsewhere in the definition whose content rules should be applied to the current element. ContentReferences bring across all the rules that are in the ElementDefinition for the element, including definitions, cardinality constraints, bindings, invariants etc.
The comment says:
ContentReferences can only be defined in specializations, not constrained types, and they cannot be changed and always reference the non-constrained definition.
We find the current wording a little confusing, as the definition implies that "rules" (including cardinality constraints and bindings) should be "brought over" -- but the comment implies that "contraints" should not be brought over (i.e., contentReferences "always reference the non-constrained definition"). We're not sure if we're misunderstanding the definition (e.g., is it actually referring to "all the rules" on the element w/ the contentReference, not the pointed-to element?), or if we're misunderstanding what is meant by the "non-constrained definition."
Chris Moesel (Feb 10 2020 at 17:02):
So... Let's say a profile on Questionnaire
has some constraints on item
-- say a required extension
and a binding on code
. Does item.item
inherit those constraints or not? We think the intent is that it does not. We think that the proper way to think of contentReferences is that they start w/ the unconstrained definition from the base FHIR resource, and then retain any constraints/definitions that were put on the element that contains the contentReference
. Any constraints in the profile on the original element pointed to by the contentReference are not inherited -- as the context of the contentReference may be quite different than the original occurrence in the profile. Is that right?
Lloyd McKenzie (Feb 10 2020 at 17:53):
It's a bit of a mess - as there are constraints that apply to the 'type' and others that apply to the 'element'. We'd like the former to apply to all uses and the latter to not carry across. Unfortunately we don't have a mechanism to differentiate. @Grahame Grieve, do you remember where we ended with this?
Grahame Grieve (Feb 10 2020 at 19:55):
I'm not sure that the type/element thing matters here.
Grahame Grieve (Feb 10 2020 at 19:57):
I'm not actually sure what the answer is (or should be). The implication of the rules cited is that @Chris Moesel is right, but then I'm not sure how to get re-use in a profile - I can't remember whether we discussed that or not
Grahame Grieve (Feb 10 2020 at 19:58):
the comment implies that "contraints" should not be brought over
no, it means that you can't do it in a constraint, and anything in the constraint doesn't matter.
Chris Moesel (Feb 10 2020 at 20:00):
Thanks, @Grahame Grieve -- I was specifically referring to "always reference the non-constrained definition" when I suggested it implies that constraints should not be brought over (from the referenced element to the element w/ the contentReference). Sorry for the confusion.
Grahame Grieve (Feb 10 2020 at 20:02):
actually, what I think it's saying is that you don't touch contentReference in the profiles. It always refers back to the base definition.
Grahame Grieve (Feb 10 2020 at 20:02):
you can reuse constraints using this extension:
Grahame Grieve (Feb 10 2020 at 20:04):
http://hl7.org/fhir/StructureDefinition/elementdefinition-profile-element
Chris Moesel (Feb 10 2020 at 20:04):
actually, what I think it's saying is that you don't touch contentReference in the profiles. It always refers back to the base definition.
Ah. OK. That's a clearer interpretation. Got it.
Chris Moesel (Feb 10 2020 at 20:14):
you can reuse constraints using this extension:
http://hl7.org/fhir/StructureDefinition/elementdefinition-profile-element
I think I'm going to need to think about this one some more... Let me see if I understand... So if an element with a contentReference
uses this extension to point to the constrained version of the element it references (in its own profile), then it takes on the constraints of that one? Is that what you mean?
The element w/ the contentReference
doesn't have a type
-- and this extension is supposed to go on type.profile
-- so is the implication that you have to copy over the type from the referenced element into the element with contentReference, add a profile
in the type that points to itself, and then apply this extension on that profile
, pointing to the same path as the contentReference
?
Grahame Grieve (Feb 10 2020 at 22:46):
I'm not sure. What I'm sure about is that we don't have a test case for this for the validator. So let me design one and then we can talk about. Do you have a scenario you are working with?
Grahame Grieve (Feb 10 2020 at 22:46):
(e.g. what element are you looking to constrain?)
Chris Moesel (Feb 11 2020 at 00:44):
I don't have a very specific use case. It came up because someone was using FSH to write an instance of SDC Questionnaire -- and asking about the extensions on item
and if they applied to item.item
. They pointed out that the individual narrative definitions of the extensions said they applied to both item
and item.item
, but that this is not as clear in the profile view.
Chris Moesel (Feb 11 2020 at 00:47):
They assumed that any constraints/extensions on item
automatically applied to item.item
because of the contentReference
-- and since that was not my understanding (as a general concept), I wanted to double-check with the FHIR community. (Especially since we implemented contentReferences
in SUSHI based on the interpretation that I expressed in the first messages of this thread).
Grahame Grieve (Feb 11 2020 at 02:06):
ok I'll do my exmaple based on that
Chris Moesel (Jul 24 2020 at 12:53):
Hi @Grahame Grieve -- this question has come up again in the #shorthand stream (this time in reference to PlanDefinition.action
and how you can have an extension apply to all recursive instances of action
(e.g., PlanDefinition.action.action
, PlanDefinition.action.action.action
, etc.)). Did you ever get a chance to flesh this out in an example and think more on how it actually should work?
David Hay (Jul 24 2020 at 19:06):
If it helps - this is the IG I'm working on that triggered the question. It uses PlanDefinition that I'm trying to simplify for a regimen use case - using one of the examples of that resource type.
@Bryn Rhodes - I had intended to ping you at some point for comment on whether I was doing this correctly anyway...
Grahame Grieve (Jul 24 2020 at 20:31):
the extension automatically applies to the recursive instances
Chris Moesel (Jan 07 2021 at 13:43):
Resurrecting this topic... We've established that in a profile a contentReference
will inherit extensions declared on the source element (in the profile) that the contentReference
points to. Are there any other contraints, potentially added by the profile to the source element, that should be inherited as well? Or only extensions? While it would be easy to say they all apply, I imagine this gets complicated when considering implications for something like ValueSet.compose.exclude
(since its source element, ValueSet.compose.include
, has literally the opposite meaning -- so all constraints from include
might not be semantically appropriate for exclude
).
Chris Moesel (Jan 07 2021 at 14:28):
Furthermore, even if we do bring over extensions from the source element to the contentReference
, which constraints on the extension element(s) are brought over? Is cardinality carried over? What about metadata like "short" and "definition" if those were modified in the profile use of the extension? How about if the extension's valueCodeableConcept
was bound to a value set _in the profile use_ (not the extension definition). Does that get carried along into the content reference? Or is it just the original extension definition that gets carried over?
Chris Moesel (Jan 20 2021 at 16:44):
Grahame Grieve said:
the extension automatically applies to the recursive instances
Hi @Grahame Grieve. I'm looking into this further and have a question. I'm testing using a slimmed down PlanDefinition profile from the NZ CcaImplementationGuide. In the profile, action.extension
contains a sact-plan-instructions
extension, and act.act.extension
adds a sact-timing-of-days
extension. This is what the differential looks like:
image.png
Chris Moesel (Jan 20 2021 at 16:45):
Based on your comment above, I assumed I do not need to redeclare thesact-plan-instructions
on action.action.extension
in the differential, because "the extension automatically applies to recursive instances."
Chris Moesel (Jan 20 2021 at 16:49):
But when I look at the snapshot (after running IG Publisher), I don't see the sact-plan-instructions
applied to action.action.extension
. It shows only the extension I explicitly declared on action.action.extension
. Is this an issue w/ the publisher, or do I need to redeclare the extension from the element the contentreference refers to (even though it is supposed to be automatically applied)?
image.png
Chris Moesel (Jan 21 2021 at 14:14):
@Ewout Kramer @Ward Weistra -- you have a snapshot generator, so I'll ask you too. ;-). Any thoughts on how differentials should work w/ contentReferences, particularly in regard to extensions on the element the contentReference points to? See above few messages for details...
Ewout Kramer (Feb 01 2021 at 16:38):
Hi Chris. These are all great questions, and the reason it remains silent is probably because......I don't know. I do remember discussing this years ago at FHRI-I, but we've just let the topic rest since we did not came up with definite answers. We can check what our current snapshot generator is doing, but that's not to say that's the correct thing to do!
Chris Moesel (Feb 01 2021 at 16:47):
Thanks, @Ewout Kramer. I suspected that the silence might reflect that this is a topic to no one has clarity on. For now, we've implemented it like this:
- When a content reference refers to an element that has an extension defined, SUSHI will consider that extension -- and any constraints on that extension -- as applicable the content reference as well.
- If SUSHI has to manifest any sub-paths of the content reference in the differential, it will also manifest the "inherited" extension in the differential.
The second bullet above ensures that the snapshot will be generated with the extension in place and should be harmless since redundant elements in the differential are basically like no-ops (except when they're not, like in this case!).
Ewout Kramer (Feb 02 2021 at 08:43):
Hi Chris - I looked at our implementation. We are applying the constraints on the referring element on top of the referred element. Actually, we are applying them over the original definition in the core profile of the referred element. So, if you profile Questionnaire and put constraints on Questionnaire.item, Questionnaire.item.item will not inherit the constraints from the profiled Questionnaire.item but rather on top of the original element in the core definition. It's clear from the code that this was not always the case - originally they were applied on top of the constraints on the referred element (profiled or not) - but in april 2018 Michel has changed this bit - no doubt after a discussion with Grahame on this topic.
Chris Moesel (Feb 03 2021 at 20:28):
Thanks, @Ewout Kramer. If I understand you correctly, what you're describing is exactly how SUSHI used to work. We understood the content reference to refer to the element from the base resource, not the current profile. So constraints on the referring element were merged w/ a copy of the referred element from the base resource. This effectively meant that if an extension was required on Questionnaire.item
in a profile, that extension was not implied to be required (or even defined) on Questionnaire.item.item
.
But... in the thread above, @Grahame Grieve said "the extension automatically applies to the recursive instances", which means that the former approach of referring only to the element in the base resource definition was incorrect. We then implemented it that way in SUSHI, but it led to more questions, as evidenced here. Still, we have the basic use case of recursively applying extensions implemented and working now. But.. it's not yet released, so if that is in fact wrong, I'd like to fix it before it goes out to the world in a release.
Grahame Grieve (Feb 03 2021 at 20:32):
umm there might be a misunderstanding here
Grahame Grieve (Feb 03 2021 at 20:33):
it may be on my part
Grahame Grieve (Feb 03 2021 at 20:34):
There's 2 different things that we could be talking about
- how to understand StructureDefinition.context
- how to profile an element
I thought we were talking about the first. I agree with Ewout about the second
Chris Moesel (Feb 03 2021 at 20:37):
Oh... So when you said "the extension automatically applies to the recursive instances", you meant that the Extension definition context
does not need to list out Questionnaire.item
, Questionnaire.item.item
, etc. If the StructureDefinition.context
references an element that is the target of a contentReference
, then it implies that it is valid for all referencing elements as well.
Grahame Grieve (Feb 03 2021 at 20:37):
yes
Chris Moesel (Feb 03 2021 at 20:39):
But if a profile on Questionnaire
adds a required extension on Questionnaire.item
, then it is required only for Questionnaire.item
, and not Questionnaire.item.item
. If you wanted to have the extension be required on all nested item
s, you'd need to do that via an invariant (if that's even possible to express)?
Grahame Grieve (Feb 03 2021 at 20:40):
no, you could refer back to your profile recursively by applying it to it's own children
Chris Moesel (Feb 03 2021 at 20:44):
Do you have an example of what that would look like? I see that you did reference the profile-element extension in the thread above, but it's still not clear to me how it is used in a use case like this.
Chris Moesel (Feb 03 2021 at 20:47):
Actually, I guess I already asked this in a more detailed way here. So all those questions still apply. ;-)
Grahame Grieve (Feb 03 2021 at 20:50):
well, here:
Grahame Grieve (Feb 03 2021 at 20:50):
<StructureDefinition xmlns="http://hl7.org/fhir">
<url value="http://hl7.org/fhir/test/StructureDefinition/document-structure"/>
<differential>
<element>
<path value="Composition.section"/>
<min value="1"/>
<type>
<profile value="http://hl7.org/fhir/test/StructureDefinition/document-structure">
<extension url="http://hl7.org/fhir/StructureDefinition/elementdefinition-profile-element">
<valueString value="Composition.section"/>
</extension>
</profile>
</type>
</element>
</differential>
</StructureDefinition>
Grahame Grieve (Feb 03 2021 at 20:51):
this says that there must be a composition section, and that each section must have a section, recursively.
Grahame Grieve (Feb 03 2021 at 20:51):
good luck trying to satisfy that ;-)
Chris Moesel (Feb 03 2021 at 21:05):
OK. So the extension goes on the element that is being referred to. So in the case of the Questionnaire
example, that extension would go on Questionnaire.item
's type[0].profile[0]
element. Then when I process Questionnaire.item.item
, when I see the contentReference
to #Questionnaire.item
, I first look at Questionnaire.item
in the current profile. If it has that extension, I copy over all the constraints to Questionnaire.item.item
. If it doesn't, I just use the element definition from the base Questionnaire
resource. Is that right?
Grahame Grieve (Feb 03 2021 at 21:20):
So in the case of the Questionnaire example, that extension would go on Questionnaire.item's type[0].profile[0] element.
well, no - it would be a type[0].profile[0] on Questionnaire.item.extension
Then when I process Questionnaire.item.item, when I see the contentReference to #Questionnaire.item, I first look at Questionnaire.item in the current profile.
no. as you are processing, you have 2 levels of reference. contentReference points at the underlying base definition. The current profile has nothing to say about that. But when you want to know what profile is applied to the underlying content found in contentReference, you look at the stated profile
If it has that extension, I copy over all the constraints to Questionnaire.item.item. If it doesn't, I just use the element definition from the base Questionnaire resource. Is that right?
I think you're reading the element-profile extension backwards. it points forward - 'this profile applies' comes from type[0].profile[0]. the element-profile says 'when you apply this profile, start at the element' (since profiles must start at an resource, but you are already inside the resource (the element types must match)
Chris Moesel (Feb 03 2021 at 21:33):
OK. I am obviously confused. I'll see if someone else on my team can help me understand it better, because I think I am a little lost.
I am also confused because up until an hour ago, I thought that if a type
has a profile
, then the profile must be a constraint on the definition specified by type.code
(e.g., if type.code
is Quantity
you can't specify type.profile
to be Observation
) -- but this approach seems to violate that. And if the extension changes the meaning of type.profile
, then shouldn't it be a modifierExtension
?
Grahame Grieve (Feb 03 2021 at 21:42):
I am also confused because up until an hour ago, I thought that if a
type
has aprofile
, then the profile must be a constraint on the definition specified bytype.code
(e.g., iftype.code
isQuantity
you can't specifytype.profile
to beObservation
) -- but this approach seems to violate that.
sort of. The profile must express a constraint on the type. In the absence of the element-profile extension, that will be assessed on the root element in the profile (which must match StructureDefinition.type). But with this extension present, then you base the decision on a different element. The type must still match.
And if the extension changes the meaning of
type.profile
, then shouldn't it be amodifierExtension
?
It doesn't change the meaning; it changes how you use it.
Chris Moesel (Feb 03 2021 at 21:55):
OK. I guess it depends on how you interpret "If any profiles are specified, then the content must conform to at least one of them." But anyway... I'll try to wrap my head around the rest then. I want to be sure I get this right. Because I already have to delete a lot of code from when I got it wrong the last time. ;-)
One more question (which may be misguided based on my lack of understanding), but... when this extension is used to point to an element in a profile (e.g., Foo.x
), would constraints on subpaths of Foo.x
(e.g., Foo.x.y
) in the profile also apply? Or would you also need to independently apply the extension to Foo.x.y
?
Grahame Grieve (Feb 03 2021 at 21:59):
would constraints on subpaths apply
yes.
Chris Moesel (Feb 03 2021 at 23:20):
I think fleshing out another example might help. The following is a differential from the MyQuestionnaire profile. Is this the right differential array to attempt to say:
- all recursive instances of
item
(e.g.,item.item
,item.item.item
) should conform toQuestionnaire.item
from the MyQuestionnaire profile - and
Questionnaire.item
from MyQuestionnaire requires aquestionnaire-itemControl
extension - therefore all recursive
item
's also require aquestionnaire-itemControl
extension
Differential from MyQuestionnaire profile structure definition:
[
{
"id": "Questionnaire.item",
"path": "Questionnaire.item",
"type": [{
"code": "BackboneElement",
"profile": ["http://example.org/StructureDefinition/MyQuestionnaire"],
"_profile": [{
"extension": [{
"url": "http://hl7.org/fhir/StructureDefinition/elementdefinition-profile-element",
"valueString": "Questionnaire.item"
}]
}]
}]
},
{
"id": "Questionnaire.item.extension:item-ctl",
"path": "Questionnaire.item.extension",
"sliceName": "item-ctl",
"min": 1,
"max": "1",
"type": [{
"code": "Extension",
"profile": ["http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl"]
}]
}
]
Grahame Grieve (Feb 05 2021 at 02:18):
yes that's what it says
Ewout Kramer (Mar 01 2021 at 10:55):
Thanks for the example, @Chris Moesel . Never thought we'd use this extension to solve this problem. Quite elegant.
Last updated: Apr 12 2022 at 19:14 UTC