Closed dbooth-boston closed 11 months ago
In our EU https://www.interopehrate.eu/ project, we needed to extend the Patient's birthplace to add Patient's birthplace Address and we did following way Patient.extension.url Patient.extension.valueAddress.city Patient.extension.valueAddress.country Patient.extension.valueAddress.line
@subhashishhh , thanks for this use case. But is this really a modifier extension? It looks to me more like a regular (non-modifier) extension, but I may be misunderstanding your use case. The difference is that a modifier extension MUST be understood by the processor in order to safely process the record, because it changes the meaning of the rest of the element. In contrast, a regular extension can be safely ignored if the processor does not understand it. In other words, a regular extension adds more information without changing the meaning of the rest of the element.
From minutes of 10/14/21:
AGREED: Provisionally go with option 2c (origValue) for modifier extension
Eric: not yet in the FHIR RDF playground
Note also that option 2c depends on hoisting scalars #77 .
Given that we've decided not to hoist scalars (#77), we now need to reconsider how to handle modifier extensions. The big challenge is RDF's monotonicity principle. (Monotonicity means that new data cannot invalidate previous conclusions; non-monotonicity means that previous conclusions can be invalidated by new data.) I think it will be messy if we try to be fully monotonic, because EVERY assertion that otherwise would have been inside a modified element should no longer be asserted, because it might no longer be true. This seems inescapable to me. Two options come to mind. Neither of them seem very good to me, but here they are.
And reify the modified element itself, too.
. . . and also the modified element itself. For example, instead of:
##### Example ex5turtleBefore
PREFIX fhir: <http://hl7.org/fhir/>
[] a fhir:Patient ; ...
we could have:
##### Example ex5turtleAfter
PREFIX maybefhir: <http://hl7.org/fhir/maybe/>
[] a maybefhir:Patient ; ...
However, even this might not be fully monotonic if there were assertions in other namespaces.
eric: Propose that for modified resources, change the type arc to have a leading underbar, to prevent it from accidentally being interpreted as the original resource even though its semantics had changed (e.g. a modified Observation would have type fhir:_Observation
).
... For other elements that have some modiefierExtension, prepend an underbar to the modified property.
Option 6 is the same idea as option 2 above, but will reflect the issue #77 decision not to hoist scalars.
Three types of modifier:
e.g. a status or a value
##### Example ex6scalarJson
{ "resourceType": "Observation",
"id": "f001",
"status": "final",
"code": {
"coding": [
{ "system": "http://loinc.org",
"code": "15074-8",
"display": "Glucose [Moles/volume] in Blood" }
] },
"valueQuantity": {
"value": 6.3,
"_value": {
"modifierExtension": [
{ "url": "http://example.org/fhir/StructureDefinition/pure-conjecture",
"valueBoolean": true }
] },
"unit": "mmol/l",
"system": "http://unitsofmeasure.org",
"code": "mmol/L"
} }
##### Example ex6scalarTurtle
<http://hl7.org/fhir/Observation/f001> a fhir:Observation;
fhir:nodeRole fhir:treeRoot;
fhir:id [ fhir:v "f001" ];
fhir:status [ fhir:v "final" ];
fhir:code [
fhir:coding (
[ a <http://loinc.org/rdf#15074-8>;
fhir:system [ fhir:v "http://loinc.org"^^xsd:anyURI ];
fhir:code [ fhir:v "15074-8" ];
fhir:display [ fhir:v "Glucose [Moles/volume] in Blood" ]
] )
];
fhir:_value [
a fhir:Quantity;
fhir:modifierExtension: (
[ fhir:url <http://example.org/fhir/StructureDefinition/pure-conjecture> ;
fhir:value true ] );
fhir:value [ fhir:v 6.3 ];
fhir:unit [ fhir:v "mmol/l" ];
fhir:system [ fhir:v "http://unitsofmeasure.org"^^xsd:anyURI ];
fhir:code [ fhir:v "mmol/L" ]
].
##### Example ex6propertyJson
{ "resourceType": "Observation",
"id": "f001",
"status": "final",
"code": {
"coding": [
{ "system": "http://loinc.org",
"code": "15074-8",
"display": "Glucose [Moles/volume] in Blood" }
] },
"valueQuantity": {
"modifierExtension": [
{ "url": "http://example.org/fhir/StructureDefinition/pure-conjecture",
"valueBoolean": true } ]
"value": 6.3,
"unit": "mmol/l",
"system": "http://unitsofmeasure.org",
"code": "mmol/L"
} }
##### Example ex6propertyTurtle
<http://hl7.org/fhir/Observation/f001> a fhir:Observation;
fhir:nodeRole fhir:treeRoot;
fhir:id [ fhir:v "f001" ];
fhir:status [ fhir:v "final" ];
fhir:code [
fhir:coding (
[ a <http://loinc.org/rdf#15074-8>;
fhir:system [ fhir:v "http://loinc.org"^^xsd:anyURI ];
fhir:code [ fhir:v "15074-8" ];
fhir:display [ fhir:v "Glucose [Moles/volume] in Blood" ]
] )
];
fhir:_value [
a fhir:Quantity;
fhir:modifierExtension: (
[ fhir:url <http://example.org/fhir/StructureDefinition/pure-conjecture> ;
fhir:value true ] );
fhir:value [ fhir:v 6.3 ];
fhir:unit [ fhir:v "mmol/l" ];
fhir:system [ fhir:v "http://unitsofmeasure.org"^^xsd:anyURI ];
fhir:code [ fhir:v "mmol/L" ]
].
##### Example ex6resourceJson
{ "resourceType": "Observation",
"id": "f001",
"status": "final",
"code": {
"coding": [
{ "system": "http://loinc.org",
"code": "15074-8",
"display": "Glucose [Moles/volume] in Blood" }
] },
"valueQuantity": {
"value": 6.3,
"_value": {
"modifierExtension": [
{ "url": "http://example.org/fhir/StructureDefinition/pure-conjecture",
"valueBoolean": true }
] },
"unit": "mmol/l",
"system": "http://unitsofmeasure.org",
"code": "mmol/L"
} }
##### Example ex6resourceTurtle
<http://hl7.org/fhir/Observation/f001> a fhir:_Observation;
fhir:nodeRole fhir:treeRoot;
fhir:modifierExtension: (
[ fhir:url <http://example.org/fhir/StructureDefinition/pure-conjecture> ;
fhir:value true ] );
fhir:id [ fhir:v "f001" ];
fhir:status [ fhir:v "final" ];
fhir:code [
fhir:coding (
[ a <http://loinc.org/rdf#15074-8>;
fhir:system [ fhir:v "http://loinc.org"^^xsd:anyURI ];
fhir:code [ fhir:v "15074-8" ];
fhir:display [ fhir:v "Glucose [Moles/volume] in Blood" ]
] )
];
fhir:value [
a fhir:Quantity;
fhir:value [ fhir:v 6.3 ];
fhir:unit [ fhir:v "mmol/l" ];
fhir:system [ fhir:v "http://unitsofmeasure.org"^^xsd:anyURI ];
fhir:code [ fhir:v "mmol/L" ]
].
This is not yet implemented in the playground (but then neither are any of the other alternatives, including the R4 no-change proposal).
I notice that some FHIR resources have properties that are implicitly modifiers, such as MedicationRequest.doNotPerform. It has the Is Modifier flag set to true. Another example is Observation.status, which if set to "entered-in-error", means that the data is NOT actually an observation. This is leading me to think that monotonicity is already so screwed in FHIR that it probably is not beneficial for FHIR RDF to try to correct it.
I notice that some FHIR resources have properties that are implicitly modifiers, such as MedicationRequest.doNotPerform. It has the Is Modifier flag set to true. Another example is Observation.status, which if set to "entered-in-error", means that the data is NOT actually an observation. This is leading me to think that monotonicity is already so screwed in FHIR that it probably is not beneficial for FHIR RDF to try to correct it.
This is what I have raised in the Chat during our last meeting. We need to see this to see how it works. There should be heuristics for using modifier extensions. There are several rules at https://build.fhir.org/extensibility.html. However, these rules are not sufficient. We need to prevent end users from differently writing the same FHIR-based statement to preserve interoperability.
: Propose that for modified resources, change the resource name to have a leading underbar, to prevent it from accidentally being interpreted as the original resource even though its semantics had changed. ... For other elements that are modified, prepend an underbar to the modified property.
I fully support @ericprud.
Since Eric and I were working on this earlier today, here is a basic example of a modifier extension that passes validation at https://validator.fhir.org/:
##### Example ex6Resource2Turtle
{
"resourceType": "Basic",
"code": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/basic-resource-type",
"code": "referral"
}]
},
"modifierExtension": [{
"url": "http://hl7.org/fhir/StructureDefinition/capabilitystatement-prohibited",
"valueBoolean": false
}]
}
Discussed Jul 28, 2022, Aug 02, 2022, Aug 04, 2022, Aug 09, 2022, Aug 11, 2022
Note that in FHIR R5: "All data types (including primitives) may have extensions, but only the following data types may include Modifier Extensions:
Thus, you can't add a modifierExtension
to value[x]
fields as described above.
Here is an example of a MedicationRequest in FHIR JSON (based on https://build.fhir.org/medicationrequest0312.json.html) that includes modifier extensions in every place I could find. In additional, the three fields status
, intent
and doNotPerform
have isModifier
set, which means that the values in these fields cannot be ignored when interpreting this MedicationRequest.
##### Example ex6allModJson
{
"resourceType": "MedicationRequest",
"id": "medrx0312",
"status": "active",
"intent": "order",
"doNotPerform": false,
"medication": {
"concept": {
"coding": [
{
"system": "http://www.nlm.nih.gov/research/umls/rxnorm",
"code": "1313112",
"display": "phenytoin 25 MG/ML Oral Suspension"
}
]
}
},
"subject": {
"reference": "Patient/pat1",
"display": "Donald Duck"
},
"authoredOn": "2015-01-15",
"requester": {
"reference": "Practitioner/f007",
"display": "Patrick Pump"
},
"reason": [
{
"concept": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "230456007",
"display": "Status epilepticus (disorder)"
}
]
}
}
],
"dose": {
"modifierExtension": [{
"url": "http://hl7.org/fhir/StructureDefinition/artifact-status",
"valueCode": "retired"
}],
"dosageInstruction": [
{
"sequence": 1,
"text": "100mg (4ml) three times daily",
"timing": {
"repeat": {
"frequency": 3,
"period": 1,
"periodUnit": "d"
}
},
"route": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "26643006",
"display": "Oral Route (qualifier value)"
}
]
},
"doseAndRate": [
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/dose-rate-type",
"code": "ordered",
"display": "Ordered"
}
]
},
"doseQuantity": {
"value": 100,
"unit": "mg",
"system": "http://unitsofmeasure.org",
"code": "mg"
}
}
]
}
]
},
"dispenseRequest": {
"modifierExtension": [{
"url": "http://hl7.org/fhir/StructureDefinition/artifact-status",
"valueCode": "draft"
}],
"validityPeriod": {
"start": "2015-01-15",
"end": "2016-01-15"
},
"numberOfRepeatsAllowed": 3,
"quantity": {
"value": 360,
"unit": "mL",
"system": "http://unitsofmeasure.org",
"code": "mL"
},
"expectedSupplyDuration": {
"value": 30,
"unit": "days",
"system": "http://unitsofmeasure.org",
"code": "d"
}
},
"substitution": {
"modifierExtension": [{
"url": "http://hl7.org/fhir/StructureDefinition/artifact-status",
"valueCode": "unknown"
}],
"allowedBoolean": true,
"reason": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ActReason",
"code": "CT",
"display": "Continuing therapy"
}
]
}
},
"modifierExtension": [{
"url": "http://hl7.org/fhir/StructureDefinition/artifact-status",
"valueCode": "draft"
}]
}
As another example, consider a FHIR Patient. The isModifier fields here are active, deceased[x], and link. As a DomainResource, it can contain modifierExtension
s (which are marked as isModifier=true
), and contains a large number of BackboneElements, each of which also have the modifierExtension
field marked as isModifier=true
. Thus, the following Patient passes validation on https://validator.fhir.org/:
##### Example ex6allMod2Json
{
"resourceType": "Patient",
"id": "f001",
"active": true,
"deceasedBoolean": false,
"link": [{
"type": "seealso",
"other": { "reference": "Patient/p101" },
"modifierExtension": [{
"url": "http://hl7.org/fhir/StructureDefinition/artifact-status",
"valueCode": "draft"
}]
}],
"modifierExtension": [{
"url": "http://hl7.org/fhir/StructureDefinition/artifact-status",
"valueCode": "draft"
}]
}
@gaurav , how would these examples appear in Turtle under option 6?
Good question! The MedicationRequest example can't be translated as-is at https://ericprud.github.io/fhircat-playground/playground/, since I'm not sure how to switch from R4 to R5. But the other one can be translated to Turtle under RDVch. The examples below are hand-modified to bring it in line with the option 6 proposal.
With option 6 + shortened names as used above, I think the overall definition would change to:
##### Example ex6patientTurtle
<http://hl7.org/fhir/Patient/f001> a fhir:_Patient; # Note underscore to indicate that this has been modified.
fhir:modifierExtension [
fhir:url <http://hl7.org/fhir/StructureDefinition/artifact-status> ; # Or possibly fhir:url [ fhir:v "http://..." ]
fhir:valueCode "draft" ;
fhir:index 0
];
Next up, we have the three isModifier
fields: active, deceased[x], and link. For deceased[x]
, this is fairly straightforward: it is "labeled as a modifier because once a patient is marked as deceased, the actions that are appropriate to perform on the patient may be significantly different", and that "If there's no value in the instance, it means there is no statement on whether or not the individual is deceased. Most systems will interpret the absence of a value as a sign of the person being alive." However, for active
and link
, this is a bit more complicated: link
"is labeled as a modifier because it might not be the main Patient resource, and the referenced patient should be used instead of this Patient record. This is when the link.type value is 'replaced-by'", and I think systems are allows to use active
as well when making this determination.
@ericprud and I have been chatting over the weekend about the best way to indicate that these could potentially modify the patient. I'm currently inclined to try to deal with this at the ShEx level, and instead we should focus on making sure that we can round trip modifier fields (including whether they have been set or not) from FHIR JSON to FHIR RDF and back. Another option would be to add triples to indicate e.g. <http://hl7.org/fhir/Patient/f001> fhir:modifierFieldsSet fhir:active, fhir:deceasedBoolean, fhir:link
. We could also conservatively assume that if any of these fields are set, the value may be modified, and to change the type to fhir:_Patient
just in case.
Finally, we have field that are themselves modified with modifier extensions, such as Patient.contact
, which under option 6 would be:
##### Example ex6contactTurtle
<http://hl7.org/fhir/Patient/f001> fhir:_contact [ # Note the underscore to indicate a modified field
fhir:modifierExtension [
fhir:url <http://hl7.org/fhir/StructureDefinition/artifact-status> ; # Or fhir:url [ fhir:v "http://..." ]
fhir:valueCode "draft" ;
fhir:index 0
]
fhir:relationship [
fhir:coding [
a <http://terminology.hl7.org/CodeSystem/v2-0131/C>;
fhir:system [
fhir:v "http://terminology.hl7.org/CodeSystem/v2-0131"^^xsd:anyURI
];
fhir:code [
fhir:v "C"
];
fhir:index 0
];
fhir:index 0
];
fhir:contact.name [
fhir:use [
fhir:v "usual", "usual"
];
fhir:family [
fhir:v "Abels"
];
fhir:given [
fhir:v "Sarah";
fhir:index 0
]
];
fhir:contact.telecom [
fhir:system [
fhir:v "phone", "phone"
];
fhir:value [
fhir:v "0690383372"
];
fhir:use [
fhir:v "mobile", "mobile"
];
fhir:index 0
];
fhir:index 0
]
And for Patient.link
:
##### Example ex6patientLinkTurtle
<http://hl7.org/fhir/Patient/f001> fhir:Patient.link [
fhir:modifierExtension [
fhir:url [
fhir:v "http://hl7.org/fhir/StructureDefinition/artifact-status"
]
fhir:valueCode [
fhir:v "draft"
];
fhir:index 0
];
fhir:link.other [
fhir:link <http://hl7.org/Patient/p101>;
fhir:reference [
fhir:value "Patient/p101"
]
];
fhir:link.type [
fhir:v "seealso", "seealso"
];
fhir:index 0
]
Since AFAICT FHIR doesn't allow scalar values to have modifier extensions (although they can have extensions), the other examples from option 6 description above aren't possible, so we will never have a situation where we need to set fhir:value
as well as fhir:_value
.
@ericprud and I have been chatting over the weekend about the best way to indicate that these could potentially modify the patient. I'm currently inclined to try to deal with this at the ShEx level...
The ShEx for modifierExtension
s came out pretty nice. Here's the first Resource when using rdf lists:
##### Example ex6accountShex
<Account> CLOSED {
(
a [fhir:Account]
|
a [fhir:_Account] ;
fhir:modifierExtension @<OneOrMore_Extension> +
) ;
fhir:nodeRole [fhir:treeRoot] ? ;
fhir:id @<id> ? ;
…
fhir:extension @<OneOrMore_Extension> * ;
fhir:identifier @<OneOrMore_Identifier> * ;
fhir:status @<code> AND { fhir:v @fhirvs:account-status } ;
…
fhir:coverage @<OneOrMore_Account.coverage> * ;
fhir:_coverage @<OneOrMore_Account._coverage> * ;
…
}
<Account.coverage> CLOSED {
fhir:id @<string> ? ;
fhir:extension @<OneOrMore_Extension> * ;
fhir:coverage @<Reference> ;
fhir:priority @<positiveInt> ?
}
<Account._coverage> CLOSED {
fhir:id @<string> ? ;
fhir:extension @<OneOrMore_Extension> * ;
fhir:modifierExtension @<OneOrMore_Extension> + ;
fhir:coverage @<Reference> ;
fhir:priority @<positiveInt> ?
}
This means that an Account shape has either a type of fhir:Account
and 0 modifierExtensions or a type of fhir:_Account
and 1+ modifierExtensions.
Account can have coverages of type BackboneElement, so they can have modifierExtensions. If any coverage
has a modifierExtension, it is moved to the _coverage
list. Each element in the _coverage
list has at least one modifierExtension
.
All but 39 BackboneElements in all the FHIR Resources have a min cardinality of 0. The remaining 39 have a min cardinality of 1 ¹, e.g. NutritionIntake.consumedItem
:
##### Example ex6nutritionShex
<NutritionIntake> CLOSED {
(
a [fhir:NutritionIntake]
|
a [fhir:_NutritionIntake] ;
fhir:modifierExtension @<OneOrMore_Extension> +
) ;
fhir:nodeRole [fhir:treeRoot] ? ;
fhir:id @<id> ? ;
…
(
fhir:consumedItem @<OneOrMore_NutritionIntake.consumedItem> + ;
fhir:_consumedItem @<OneOrMore_NutritionIntake._consumedItem> *
|
fhir:_consumedItem @<OneOrMore_NutritionIntake._consumedItem> +
)
…
}
The X+_X*|_X+
idiom ensures that the min cardinality is met. There are no BackboneElement cardinalities apart from 0 or 1.
¹
$ jq -c '.entry[].resource | select(.resourceType == "StructureDefinition") | .differential.element[] | select(.type[0].code == "BackboneElement" and .min == 0) | {min, id}' profiles-resources.json | wc
531 531 25256
$ jq -c '.entry[].resource | select(.resourceType == "StructureDefinition") | .differential.element[] | select(.type[0].code == "BackboneElement" and .min == 1) | {min, id}' profiles-resources.json | wc
39 39 1699
$ jq -c '.entry[].resource | select(.resourceType == "StructureDefinition") | .differential.element[] | select(.type[0].code == "BackboneElement" and .min > 1) | {min, id}' profiles-resources.json | wc
0 0 0
In example ex6patientTurtle above,
Should the URL of the modifierExtension be declared as the class, like this?
<http://hl7.org/fhir/Patient/f001> a fhir:_Patient; # Note underscore to indicate that this has been modified.
fhir:modifierExtension [
a <http://hl7.org/fhir/StructureDefinition/artifact-status> ;
fhir:url [ fhir:v "http://hl7.org/fhir/StructureDefinition/artifact-status" ] ;
fhir:value [ fhir:v "draft" ] ;
fhir:index 0
];
BTW, I changed fhir:valueCode
to use fhir:value [ fhir:v ... ]
.
On today's call we reached consensus to go with option 6:
AGREED: Go with option 6.
One detail we may have missed in deciding how to handle modifier extensions: How should the relationship between the modified class or property and the original be indicated? Something like this?
fhir:_Patient fhir:originalClass fhir:Patient .
Jim found an example of a modifier extension: https://build.fhir.org/basic-example.ttl.html
Those are root extensions. It'd be nice to also have/contribute examples with modifier extensions on elements, backbone elements, datatypes and pritimitveValues. Should this be closed given the resolutions for the R5 ballot or kept open 'till we dot 'i's and cross 't's?
Implemented in R5.
(Separating this out from #77 .)
A modifier extension is one that MUST be understood in order to correctly interpret the meaning of the element to which it is attached. In other words, it (non-monotonically) modifies the meaning of the associated element, thus making it dangerous for a processor that does not understand that modifier extension to process that element. An egregious example might be an anti-prescription that attaches a modifierExtension to a MedicationRequest element to indicate that a particular medication should not be taken. It would be dangerous to process that element if that modifierExtension is not understood by the processor, because it could result in a patient being given a medication that he/she is explicitly not supposed to take.
RDF semantics forbids non-monotonicity, as explained in the introduction to FHIR RDF.
Example JSON:
Option 0: Do nothing. Keep the R4 representation for modifierExtensions.
FHIR RDF:
Comment from EricP, copied from #77 :
For modifying extensions, It seems reasonable to say that we should
@contexts
and ShEx, but instead have to be compile in on a feature-by-feature basis.Option 1: (???Needs more explanation and example) Harold has a scheme to make an interpreter step through the modification to get to the standard part of the data object. This ensures that a naive query doesn't accidentally pick up modified data and misinterpret it.
Option 2: Hide the element that is modified We could also co-opt FHIR/JSON's convention of a leading '_' on the property name and use it to hide any modified property from naive interpretation. (To David's point below) where JSON has a
foo
with the standard data and a_foo
with a (modifying) extension, this proposal would involve moving the standard (but modified) data from JSON'sfoo
to an_foo
property.2 on resource root
2 on property
2 on scalar
using Certainty
In JSON:
Option 2a: In Turtle:
Option 2b: In Turtle __:
Option 2c: In Turtle origValue
Comment from DBooth, copied from #77 :
@ericprud , I generally agree with your philosophy, but can you clarify option 2? The FHIR/JSON convention uses both
foo
and_foo
, with the non-extended part infoo
and the extension part in_foo
. If the non-extended partfoo
were still there in RDF, then processors might not notice the existence of the modifying extension in_foo
, which could be bad. So are you suggesting eliminating thefoo
property and putting everything under_foo
?There is an example of a FHIR/JSON modifierExtension here: https://www.hl7.org/fhir/extensibility.html
Comment from DBooth, copied form #77 :
@ericprud , option 2 sounds like it would work, and I like the idea of removing the data from the regular
foo
property (since a modifierExtension means that the semantics have changed), but a potential downside is that it may be confusingly similar to the existing JSON_foo
convention for non-modifier extensions. How about if it the regularfoo
data is moved under a new property calledmodifierExtension
?Option 3: keeping non-mod and modifying extensions parallel
(same as #77 Option 3b.1)
scalar extension non-modifying extension
scalar extension modifying extension
This would correspond to the the FHIR/JSON: