w3c / hcls-fhir-rdf

Sketching out an RDF representation for FHIR
39 stars 15 forks source link

How should modifier extensions be handled in R5? #93

Closed dbooth-boston closed 11 months ago

dbooth-boston commented 2 years ago

(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:

##### Example ex0json
{
  "resourceType": "Basic",
  "id": "referral01",
  "modifierExtension": [
    {
      "url": "http://example.org/do-not-use/fhir-extensions/referral#referredForService",
      "valueBoolean": true
    }
  ],
  "subject": {
    "reference": "Patient/f201",
    "display": "Roel"
  }
}

Option 0: Do nothing. Keep the R4 representation for modifierExtensions.

FHIR RDF:

##### Example ex0turtle
@prefix exPatient:  <http://hl7.org/Patient/> .
@prefix fhir:  <http://hl7.org/fhir/> .
@prefix exBasic:  <http://hl7.org/fhir/Basic/> .
@prefix owl:  <http://www.w3.org/2002/07/owl#> .
@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

exBasic:referral01  a   fhir:Basic ;
  fhir:Resource.id    [ fhir:value  "referral01"  ] ;
  fhir:DomainResource.modifierExtension  [ 
      fhir:Extension.url      [ 
        fhir:value  "http://example.org/do-not-use/fhir-extensions/referral#referredForService"  ] ;
      fhir:Extension.valueBoolean  [ fhir:value  true  ]  ;
      fhir:index  0  ] ;

  fhir:Basic.subject  [
    fhir:Reference.display    [ fhir:value  "Roel"  ] ;
    fhir:Reference.reference  [ fhir:value  "Patient/f201"  ]  ;
    fhir:link  exPatient:f201
  ] .

exPatient:f201  a  fhir:Patient .

exBasic:referral.ttl  a  owl:Ontology ;
        owl:imports     fhir:fhir.ttl ;
        owl:versionIRI  exBasic:referral.ttl .

Comment from EricP, copied from #77 :

For modifying extensions, It seems reasonable to say that we should

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's foo to an _foo property.

2 on resource root

##### Example ex2rootTurtle
<Observation/obs01>  a   fhir:_Observation ;
  fhir:Resource.id    [ fhir:value  "obs01"  ] ;
  fhir:DomainResource.modifierExtension  [ 
      fhir:Extension.url      [
        fhir:value  "http://example.org/do-not-use/fhir-extensions/referral#referredForService"  ] ;
      fhir:Extension.valueBoolean  [ fhir:value  true  ] ;
      fhir:index  0  ] ;

  fhir:Basic.subject  [
    fhir:Reference.display    [ fhir:value  "Roel" ] ;
    fhir:Reference.reference  [ fhir:value  "Patient/f201" ] ;
    fhir:link  exPatient:f201
  ] .

2 on property

##### Example ex2propertyTurtle
<Observation/obs01>  a   fhir:Observation ;
  fhir:Resource.id    [ fhir:value  "obs01"  ] ;
  fhir:_subject [
    fhir:DomainResource.modifierExtension  [ 
        fhir:Extension.url      [
          fhir:value  "http://example.org/do-not-use/fhir-extensions/referral#referredForService"  ] ;
        fhir:Extension.valueBoolean  [ fhir:value  true  ] ;
        fhir:index  0  ] ;

    fhir:Basic.subject  [
      fhir:Reference.display    [ fhir:value  "Roel" ] ;
      fhir:Reference.reference  [ fhir:value  "Patient/f201" ] ;
      fhir:link  exPatient:f201
    ]
  ] .

2 on scalar

using Certainty

In JSON:

##### Example ex2scalarJson
 "active": true,
 "_active": {
   "extension" : [ {
      "url" : "http://example.org/fhir/boolean/Certainty",
      "valueDecimal" : 0.75
   }, {
      "url" : "http://example.org/fhir/boolean/Attribution",
      "valueResource" { "reference": "...myMachine/run=123" }
   } ]
 }

Option 2a: In Turtle:

##### Example ex2aScalarTurtle
[] fhir:_active  [
       fhir:active   true ;
       a fhir:ModifierExtension ;
       fhir:index 0 ;       # To retain ordering of multiple extensions
       fhir:url  "http://example.org/fhir/boolean/Certainty" ;
       fhir:valueDecimal  0.75
     ], [
       fhir:active   true ;
       a fhir:Extension ;
       fhir:index 1 ;       # To retain ordering of multiple extensions
       fhir:url  "http://example.org/fhir/boolean/Attribution" ;
       fhir:valueResource [ fhir:link <...myMachine/run=123> ]
     ] .

Option 2b: In Turtle __:

##### Example ex2bScalarTurtle
[] fhir:__active  true ;
   fhir:_active  [
       a fhir:ModifierExtension ;
       fhir:index 0 ;       # To retain ordering of multiple extensions
       fhir:url  "http://example.org/fhir/boolean/Certainty" ;
       fhir:valueDecimal  0.75
     ], [
       a fhir:Extension ;
       fhir:index 1 ;       # To retain ordering of multiple extensions
       fhir:url  "http://example.org/fhir/boolean/Attribution" ;
       fhir:valueResource [ fhir:link <...myMachine/run=123> ]
     ] .

Option 2c: In Turtle origValue

##### Example ex2cScalarTurtle
[] fhir:_active  [
       fhir:origValueBoolean  true ;
       a fhir:ModifierExtension ;
       fhir:index 0 ;       # To retain ordering of multiple extensions
       fhir:url  "http://example.org/fhir/boolean/Certainty" ;
       fhir:valueDecimal  0.75
     ], [
       fhir:origValueBoolean   true ;
       a fhir:Extension ;
       fhir:index 1 ;       # To retain ordering of multiple extensions
       fhir:url  "http://example.org/fhir/boolean/Attribution" ;
       fhir:valueResource [ fhir:link <...myMachine/run=123> ]
     ] .

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 in foo and the extension part in _foo. If the non-extended part foo 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 the foo 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 regular foo data is moved under a new property called modifierExtension?

Option 3: keeping non-mod and modifying extensions parallel

(same as #77 Option 3b.1)

scalar extension non-modifying extension

##### Example ex3nonModTurtle
[] fhir:active   true ;
   fhir:_active  [
       a fhir:Extension ;
       fhir:originalValue true ;
       fhir:index 0 ;       # To retain ordering of multiple extensions
       fhir:url  "http://example.org/fhir/boolean/Certainty" ;
       fhir:valueDecimal  0.75
     ] .

scalar extension modifying extension

##### Example ex3modTurtle
[] fhir:_active  [
       a fhir:Extension ;
       fhir:originalValue true ;
       fhir:index 0 ;
       fhir:url  "http://example.org/fhir/boolean/Certainty" ;
       fhir:valueDecimal  0.75
     ], [
       a fhir:ModifierExtension ;
       fhir:originalValue true ;
       fhir:index 1 ;
       fhir:url  "http://example.org/fhir/boolean/Negated" ;
       fhir:valueBoolean true
     ] .

This would correspond to the the FHIR/JSON:

##### Example ex3json
 "active": true,
 "_active": {
   "extension" : [ {
      "url" : "http://example.org/fhir/boolean/Certainty",
      "valueDecimal" : 0.75
   }],
   "modifierExtension" : [ {
      "url" : "http://example.org/fhir/boolean/Negated",
      "valueBoolean" : true
   }]
 }
subhashishhh commented 2 years 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

dbooth-boston commented 2 years ago

@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.

dbooth-boston commented 2 years ago

From minutes of 10/14/21:

AGREED: Provisionally go with option 2c (origValue) for modifier extension
dbooth-boston commented 2 years ago

Eric: not yet in the FHIR RDF playground

dbooth-boston commented 1 year ago

Note also that option 2c depends on hoisting scalars #77 .

dbooth-boston commented 1 year ago

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.

Option 4: Reify everything inside a modified element

And reify the modified element itself, too.

Option 5: Use a non-fhir namespace for everything inside a modified element

. . . 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.

dbooth-boston commented 1 year ago

Option 6: Rename the top-level item that was modified

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:

scalar 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" ]
  ].

property modifier (i.e. on a backbone element)

##### 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" ]
  ].

resource modifier

##### 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).

dbooth-boston commented 1 year ago

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.

csisc commented 1 year ago

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.

csisc commented 1 year ago

: 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.

gaurav commented 1 year ago

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
  }]
}
dbooth-boston commented 1 year ago

Discussed Jul 28, 2022, Aug 02, 2022, Aug 04, 2022, Aug 09, 2022, Aug 11, 2022

gaurav commented 1 year ago

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"
  }]
}
gaurav commented 1 year ago

As another example, consider a FHIR Patient. The isModifier fields here are active, deceased[x], and link. As a DomainResource, it can contain modifierExtensions (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"
  }]
}
dbooth-boston commented 1 year ago

@gaurav , how would these examples appear in Turtle under option 6?

gaurav commented 1 year ago

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 commented 1 year ago

@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 modifierExtensions 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
dbooth-boston commented 1 year ago

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 ... ] .

dbooth-boston commented 1 year ago

On today's call we reached consensus to go with option 6:

AGREED: Go with option 6.

dbooth-boston commented 1 year ago

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 .

dbooth-boston commented 1 year ago

Jim found an example of a modifier extension: https://build.fhir.org/basic-example.ttl.html

ericprud commented 1 year ago

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?

dbooth-boston commented 11 months ago

Implemented in R5.