w3c / hcls-fhir-rdf

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

Simplifying primitive properties while still supporting FHIR extensions, aka hoisting scalars #77

Closed dbooth-boston closed 2 years ago

dbooth-boston commented 4 years ago

The problem is that when we designed FHIR RDF R4 (the initial version of FHIR RDF), we introduced an extra blank node and fhir:value property on primitive properties. For example, fhir:gender is represented like this:

fhir:Patient.gender [ fhir:value "male" ] .

instead of simply being:

fhir:Patient.gender "male" .

We designed it this way intentionally, because FHIR allows any property to be extended, so the extra blank node provides a place to attach the extension (if needed). (See example below.) However, in the years since that design, after accumulating some practical experience using FHIR RDF, it has become clear that the extra blank node and fhir:value property makes it significantly more difficult to access and query those simple properties, which usually are not extended anyway. In other words, we made every use of a primitive property more difficult, just to accommodate the rare case in which someone wants to add a FHIR extension to it. This violates the general design guidance of making the common use cases easy, and the rare use cases possible. Hence, the point of this issue is to reconsider this design choice, to see if we can make the common use case easy -- without requiring the extra blank node and property -- while still somehow enabling the property to have a FHIR extension if needed.

[Sub-issue: How should modifying extensions be represented? NOTE: Discussion of modifier extensions has now been moved to issue #93 .]

Option 0: Do nothing. Keep the R4 approach of everything being an object property

JSON example of non-modifying extension, from R4:

...
"gender": "male",
  "birthDate": "1974-12-25",
  "_birthDate": {
    "extension": [
      {
        "url": "http://hl7.org/fhir/StructureDefinition/patient-birthTime",
        "valueDateTime": "1974-12-25T14:35:45-05:00"
      }
    ]
  },

Turtle equivalent in R4:

...
fhir:Patient.gender [ fhir:value "male" ] ;
fhir:Patient.birthDate [
     fhir:value "1974-12-25"^^xsd:date;
     fhir:Element.extension [
       fhir:index 0;
       fhir:Extension.url [ fhir:value "http://hl7.org/fhir/StructureDefinition/patient-birthTime" ];
       fhir:Extension.valueDateTime [ fhir:value "1974-12-25T14:35:45-05:00"^^xsd:dateTime ]
     ]
  ];
dbooth-boston commented 4 years ago

REJECTED: Extension option 1a: Map underscore attributes to non-underscore (non-tree version). See https://github.com/fhircat/fhir_rdf_validator/blob/master/tutorial/FHIRR5.md#primitive-type-and-id-extensions---extensions-option-1-map-underscore-attributes-to-non-underscore

NOTE: This option was eliminated on 9/9/21

Example JSON:

{
    "resourceType": "Patient",
    "id": "example",
    "birthDate": "1974-12-25",
    "_birthDate": {
        "extension": [
          {
            "url": "http://hl7.org/fhir/StructureDefinition/patient-birthTime",
            "valueDateTime": "1974-12-25T14:35:45-05:00"
          }
        ]
    }
}

Corresponding Turtle 1a:

@prefix fhir:  <http://hl7.org/fhir/> .
@prefix pt:  <http://build.fhir.org/Patient/> .

pt:example a fhir:Patient ;
    fhir:Patient.birthDate  "1974-12-25" ;
        fhir:Patient.birthDate  [    
        fhir:DomainResource.extension  _:b1 ;
        fhir:DomainResource.extension  [
            fhir:ordered  ( _:b1 ) .
            ] ;
        ] ;
        fhir:Resource.id        "example" ;
        fhir:nodeRole           "fhir:treeRoot" .

_:b1 fhir:Extension.url "http://hl7.org/fhir/StructureDefinition/patient-birthTime" ;
    fhir:Extension.valueDateTime  "1974-12-25T14:35:45-05:00" .

Pros:

Cons:

REJECTED: Extension option 1b: Map underscore attributes to non-underscore (tree version). Similar to 1a, but simplified to use a tree structure (to eliminate explicit blank nodes), and to use the same extension structure that R4 used, in addition to directly indicating the primitive value.

NOTE: This option was eliminated on 9/9/21

Turtle 1b:

pt:example a fhir:Patient ;
    fhir:Patient.birthDate  "1974-12-25" ;
    fhir:Patient.birthDate [
         fhir:value "1974-12-25"^^xsd:date;
         fhir:Element.extension [
           fhir:index 0;
           fhir:Extension.url [ fhir:value "http://hl7.org/fhir/StructureDefinition/patient-birthTime" ];
           fhir:Extension.valueDateTime [ fhir:value "1974-12-25T14:35:45-05:00"^^xsd:dateTime ]
         ]
      ];

Pros:

Cons:

dbooth-boston commented 4 years ago

REJECTED: Option 2: Sibling extension property with punning. In this option, an extension for Patient / birthDate would be placed under Patient / extension / birthDate, and an extension for Patient / name would be placed under Patient / extension / name, and so on. This means that the resulting object would have properties/attributes like this (using approximate SPARQL path syntax):

NOTE: This option was eliminated on 9/9/21

Patient / id
Patient / birthDate
Patient / name
Patient / extension / birthDate
Patient / extension / name
etc.

Primitive example in JSON:

{
    "resourceType": "Patient",
    "id": "example",
    "birthDate": "1974-12-25",
    "_birthDate": {
        "extension": [
          {
            "url": "http://hl7.org/fhir/StructureDefinition/patient-birthTime",
            "valueDateTime": "1974-12-25T14:35:45-05:00"
          }
        ]
    }
}

Primitive example as Turtle (option 2, REJECTED on 9/9/21):

<http://build.fhir.org/Patient/example>
  fhir:nodeRole> "fhir:treeRoot" . 
  fhir:Resource.id "example" . 
  fhir:Patient.birthDate "1974-12-25" . 
  fhir:Patient.extension [ 
    fhir:Patient.birthDate [ 
      fhir:index 0;                               # Ordering mechanism TBD
      fhir:Extension.url "http://hl7.org/fhir/StructureDefinition/patient-birthTime" . 
      fhir:Extension.valueDateTime> "1974-12-25T14:35:45-05:00" . 
    ] .
  ] .

Pros:

Cons:

dbooth-boston commented 4 years ago

REJECTED: Extension option 3: Use underscore attributes for extensions, like in FHIR/JSON. This option would directly follow existing FHIR/JSON, but use ontology relationships to connect attributes to their underscore versions. See https://github.com/fhircat/FHIRCat/issues/4

NOTE: This option was eliminated on 9/9/21

JSON:

 "active": true,
 "_active": {
   "extension" : [ {
      "url" : "http://example.org/fhir/boolean/Certainty",
      "valueDecimal" : 0.75
   }]
 }

Turtle (option 3):

[] fhir:active   true ;
   fhir:_active  [
     fhir:extension [
       fhir:index 0 ;       # To retain ordering of multiple extensions
       fhir:url  "http://example.org/fhir/boolean/Certainty" ;
       fhir:valueDecimal  0.75
     ]
   ] .
. . .
# Ontology relationship:
fhir:active fhir:extensionProperty fhir:_active  .

Pros:

Cons:

Turtle (option 3U):

[] fhir:active   true ;
   fhir:_active  [
     fhir:extension [
       fhir:index 0 ;       # To retain ordering of multiple extensions
       <http://example.org/fhir/boolean/Certainty> 0.75 # i.e. "0.75"^^xsd:Decimal
     ]
   ] .
. . . 
# Ontology relationships:
fhir:active fhir:extensionHook fhir:_active  .
         fhir:extensionProperty <http://example.org/fhir/boolean/Certainty>  .

Pros:

Cons:

Option 3b:

[] fhir:active   true ;
   fhir:_active  [
       a fhir:Extension ;
       fhir:index 0 ;       # To retain ordering of multiple extensions
       fhir:url  "http://example.org/fhir/boolean/Certainty" ;
       fhir:valueDecimal  "0.75"^^xsd:decimal
     ] .
. . .
# Ontology relationship:
fhir:active fhir:extensionProperty fhir:_active .

Pros:

Cons:

Option 3b.1:

Same as 3b but keeps the original value in the extension for parity with modifier extensions:

[] 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"^^xsd:decimal
     ] .

Option 3c:

Keep non-modifier extension processing uniform regardless of whether there's a modifier extension on the same scalar

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

Pros:

Cons:

Option 3bU:

[] fhir:active   true ;
   fhir:_active  [
       a fhir:Extension ;
       fhir:index 0 ;       # To retain ordering of multiple extensions
       <http://example.org/fhir/boolean/Certainty> 0.75
     ] .
. . .
# Ontology relationships:
fhir:active fhir:extensionHook fhir:_active  .
         fhir:extensionProperty <http://example.org/fhir/boolean/Certainty>  .

Pros:

Cons:

dbooth-boston commented 4 years ago

FYI, there was a question about this in the FHIR Community forum: http://community.fhir.org/t/anonymous-blank-nodes-in-ttl-files-vs-resource-nodes/1940/2

gaurav commented 3 years ago

I like extension option 2, suboption "Primitive example converted to R5 JSON-LD". However, I think the Turtle example is slightly wrong, since I don't think you can include dictionary-type structures directly in Turtle confusing, because then we can't set a domain on the fhir:Patient.birthDate property, which could be either an extension or a patient. I think this would work better:

REJECTED: Option 2b: Sibling extension property connected by fhir:extensionPath:

NOTE: This option was eliminated on 9/9/21

<http://build.fhir.org/Patient/example>
  fhir:nodeRole           "fhir:treeRoot" ;
  fhir:Resource.id        "example" ;
  fhir:Patient.birthDate  "1974-12-25" ;
  fhir:extension 
    [ 
      fhir:extensionPath fhir:Patient.birthDate ;
      fhir:index 0 ; # Ordering mechanism TBD
      fhir:Extension.url <http://hl7.org/fhir/StructureDefinition/patient-birthTime> ;
      fhir:Extension.valueDateTime "1974-12-25T14:35:45-05:00"
    ]

Note: I (@dbooth-boston ) took the liberty of slightly editing this to remove the unneeded outer list around the extension, since the fhir:index property is intended to retain the ordering of multiple extensions.

Pros:

Cons:

dbooth-boston commented 3 years ago

REJECTED Extension option 4a: Have both a primary object property and a short-cut datatype property

Note: This option was eliminated on 9/16/21.

This is conceptually similar to option 3, in that it uses two properties, though the naming convention differs and the object would include the entire R4 object instead of only holding the extension:

fhir:Patient.birthDateValue "1974-12-25"^^xsd:date ;
fhir:Patient.birthDate [
     fhir:value "1974-12-25"^^xsd:date;
     fhir:Element.extension [
       fhir:index 0;
       fhir:Extension.url [ fhir:value "http://hl7.org/fhir/StructureDefinition/patient-birthTime" ];
       fhir:Extension.valueDateTime [ fhir:value "1974-12-25T14:35:45-05:00"^^xsd:dateTime ]
     ]
  ];

. . .
# Ontology relationship:
fhir:Patient.birthDateValue fhir:extensionProperty fhir:Patient.birthDate .

Pros:

Cons:

REJECTED: Extension option 4b: Have both a primary datatype property and an adjunct object property

Note: This option was eliminated on 9/16/21.

Similar to option 4a, but reversing the naming convention:

fhir:Patient.birthDate "1974-12-25"^^xsd:date ;
fhir:Patient.birthDateExtension [
     fhir:value "1974-12-25"^^xsd:date;
     fhir:Element.extension [
       fhir:index 0;
       fhir:Extension.url [ fhir:value "http://hl7.org/fhir/StructureDefinition/patient-birthTime" ];
       fhir:Extension.valueDateTime [ fhir:value "1974-12-25T14:35:45-05:00"^^xsd:dateTime ]
     ]
  ];

. . .
# Ontology relationship:
fhir:Patient.birthDate fhir:extensionProperty fhir:Patient.birthDateExtension .

Pros:

Cons:

ericprud commented 3 years ago

Option 5a: Use extension name as predicate

fhir:Patient.birthDate [
    fhir:value "1974-12-25"^^xsd:date ;
    <http://hl7.org/fhir/StructureDefinition/patient-birthTime> [
        fhir:index 0;
        fhir:Extension.valueDateTime "1974-12-25T14:35:45-05:00"^^xsd:dateTime
    ] ;

# Ontology:
<http://hl7.org/fhir/StructureDefinition/patient-birthTime> a fhir:ExtensionProperty .
# Extension ontology:
fhir:Patient.birthDate fhir:extensionProperty <http://hl7.org/fhir/StructureDefinition/patient-birthTime> .

Pros:

Cons:

dbooth-boston commented 2 years ago

Copied this checklist item from the preprocessing checklist issue: Connect FHIR extension element "_foo" to element "foo", so that an RDF query can traverse from any element ?foo to its extensions, without hard-coding the element names. See slide 8: https://lists.w3.org/Archives/Public/www-archive/2019Nov/att-0000/FHIRRdf.pdf

dbooth-boston commented 2 years ago

The issue of connecting FHIR extension element "_foo" to element "foo" does not necessarily have to be addressed in the preprocessor, because it is a static relationship -- it does not depend on the instance data -- so it could be included in the ontology instead of being generated in the instance data.

ericprud commented 2 years ago

While hoisted scalars are wayyy nicer to query or access via graph API, hoisting conflicts with the fact that OWL (DL?) forces a choice between ObjectProperties and DatatypeProperties. This arises with e.g. rd**H fhir:code:

  fhir:code [
    fhir:coding (
      [
        a <http://loinc.org/rdf#15074-8>;
        fhir:system "http://loinc.org"^^xsd:anyURI;
        fhir:code "15074-8";
        fhir:display "Glucose [Moles/volume] in Blood"
      ]
    )
  ];

where fhir:code appears to be both an ObjectProperty:

<Obvservation> rdfs:subClassOf [owl:obProperty fhir:code ; owl:allValuesFrom <CodeableConcept>].

and a DatatypeProperty:

<Coding> rdfs:subClassOf [owl:obProperty fhir:code ; owl:allValuesFrom xsd:string].

Another example is rdv*H fhir:value:

  fhir:value [
    a fhir:Quantity;
    fhir:value 6.3;
    fhir:system "http://unitsofmeasure.org"^^xsd:anyURI;
    fhir:code "mmol/L"
  ];

with fhir:value an ObjectProperty:

<Observation> rdfs:subClassOf [
  owl:onProperty fhir:value ;
  owl:allValuesFrom [owl:unionOf (<Quantity>, <CodeableConcept>, <string> …) ] ] .

and a DatatypeProperty:

<Quantity> rdfs:subClassOf [owl:onProperty fhir:value; owl:allValuesFrom xsd:decimal].
dbooth-boston commented 2 years ago

In resolving #102, we also resolved this issue: AGREED: option 5 [of issue 102], not hoist scalars (same as R4)

https://www.w3.org/2022/07/28-hcls-minutes.html#t01