slanted W3C logo
Cover page images (keys)

Semantic Representations of Clinical Care Data Leveraging HL7 FHIR

SWAT4LS
4 Dec, 2016

http://tinyurl.com/swat4lsfhir

http://www.w3.org/2016/Talks/1205-swat4ls-egp/validation/

FHIR Resource Schemas

FHIR Observation resource

Network of Schemas

simultaneous display of diagnosis-related resources

Data Integrity

Before we use data we have to trust it.

Folks responsible for clinical data are especially conservative/sensitive/paranoid.

Schema Languages

How do we enforce data integrity?

SQLXML
missing propertiesreportedBy UNSIGNED INT NOT NULLelement reportedBy { User },
missing/bad type arcsN/AN/A
missing referentsFOREIGN KEY (reportedBy)
REFERENCES Users(ID)
<keyref refer="UserID">
inconsistent stateCHECK(assignedOn>reportedOn)[schematron]
value set violationsENUM('unasigned', 'assigned')attribute state
{ "unassigned" | "assigned" }

RDF Data Integrity

Validation in RDF

Useful data needs consistent structure:

Detect and correct errors:

@prefix : <http://www.w3.org/2012/12/rdf-val/SOTA-ex#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/'> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<issue7> a :Issue , :SecurityIssue ;
    :state :unassigned ;
    :reportedBy <user6> , <user2> ; # cardinality 1
    :reportedOn "2012-12-31T23:57:00"^^xsd:dateTime ;
    :assignedTo <user2>, <user1> ;
    :assignedOn "2012-11-31T23:57:00"^^xsd:dateTime ;
                       # reproduced before being reported
    :related <issue4>, <issue3>, <issue2> .
                       # referenced issues not included

<issue4> # a ???         missing type arc
    :state :unsinged ; # misspelled
    # :reportedBy ??? -  missing
    :reportedOn "2012-12-31T23:57:00"^^xsd:dateTime .

<user2> a foaf:Person ;
    foaf:givenName "Alice" ;
    foaf:familyName "Smith" ;
    foaf:phone <tel:+1.555.222.2222> ;
    foaf:mbox <mailto:alice@example.com> .

<user6> a foaf:Agent ; # should be foaf:Person
    foaf:givenName "Bob" ; # foaf:familyName "???" - missing
    foaf:phone <tel:+.555.222.2222> ; # malformed tel: URL
    foaf:mbox <mailto:alice@example.com> .

Value Sets

observation status value set

Technologies

in order of specificity:

Keep calm and validate.

Use Case: BP Observation

<Obs1>
  :component [ :code "systolic"; :value "140"^^:mmHg ] .

Resource Shapes

<BPShape> a rs:ResourceShape ;
  rs:property [
    rs:name "component" ;
    rs:propertyDefinition :component ;
    rs:valueShape [
      a rs:ResourceShape ;
      rs:property [
        rs:name "code" ;
        rs:propertyDefinition :code ;
        rs:allowedValue "systolic" ;
        rs:occurs rs:Exactly-one ;
      ] ;
      rs:property [
        rs:name "value" ;
        rs:propertyDefinition :value ;
        rs:valueType :mmHg ;
        rs:occurs rs:Exactly-one ;
      ]
    ] ;
    rs:occurs rs:Exactly-one ;
  ] .

SHACL

PREFIX : <http://a.example/#>
PREFIX sh:<http://www.w3.org/ns/shacl#>
<BPShape> a sh:Shape ;
  sh:property [
    sh:predicate :component ;
    sh:minCount 1 ; sh:maxCount 1 ;
    sh:shape [
      sh:property [
        sh:predicate :code ;
        sh:in ("systolic") ;
        sh:minCount 1 ; sh:maxCount 1 ;
      ] ;
      sh:property [
        sh:predicate :value ;
        sh:valueType :mmHg ;
        sh:minCount 1 ; sh:maxCount 1 ;
      ]
    ] ;
    sh:minCount 1 ; sh:maxCount 1 ;
  ] .

 

visible diffs from Resource Shapes:

(many more diffs not seen in these examples)

What about the diastolic?

<Obs1>
  :component [ :code "systolic"; :value "140"^^:mmHg ]
  :component [ :code "diastolic"; :value "80"^^:mmHg ] .

Resource Shapes

<BPShape> a rs:ResourceShape ;
  rs:property [
    rs:name "component" ;
    rs:propertyDefinition :component ;
    rs:valueShape [
      a rs:ResourceShape ;
      rs:property [
        rs:name "code" ;
        rs:propertyDefinition :code ;
        rs:allowedValue "systolic" ;
        rs:occurs rs:Exactly-one ;
      ] ;
      rs:property [
        rs:name "value" ;
        rs:propertyDefinition :value ;
        rs:valueType :mmHg ;
        rs:occurs rs:Exactly-one ;
      ]
    ] ;
    rs:occurs rs:Exactly-one ;
  ] ;
rs:property [
    rs:name "component" ;
    rs:propertyDefinition :component ;
    rs:valueShape [
      a rs:ResourceShape ;
      rs:property [
        rs:name "code" ;
        rs:propertyDefinition :code ;
        rs:allowedValue "diastolic" ;
        rs:occurs rs:Exactly-one ;
      ] ;
      rs:property [
        rs:name "value" ;
        rs:propertyDefinition :value ;
        rs:valueType :mmHg ;
        rs:occurs rs:Exactly-one ;
      ]
    ] ;
    rs:occurs rs:Exactly-one ;
  ] .
<Obs1>
  :component [ :code "systolic"; :value "140"^^:mmHg ]
  :component [ :code "diastolic"; :value "80"^^:mmHg ] .

SHACL

PREFIX : <http://a.example/#>
PREFIX sh:<http://www.w3.org/ns/shacl#>
<BPShape> a sh:Shape ;
  sh:property [
    sh:predicate :component ;
    sh:qualifiedMinCount 1 ; sh:qualifiedMaxCount 1 ;
    sh:qualifiedShape [
      sh:property [
        sh:predicate :code ;
        sh:in ("systolic") ;
        sh:minCount 1 ; sh:maxCount 1 ;
      ] ;
      sh:property [
        sh:predicate :value ;
        sh:valueType :mmHg ;
        sh:minCount 1 ; sh:maxCount 1 ;
      ]
    ] ;
  ] ;
sh:property [
    sh:predicate :component ;
    sh:qualifiedMinCount 1 ; sh:qualifiedMaxCount 1 ;
    sh:qualifiedShape [
      sh:property [
        sh:predicate :code ;
        sh:in ("diastolic") ;
        sh:minCount 1 ; sh:maxCount 1 ;
      ] ;
      sh:property [
        sh:predicate :value ;
        sh:valueType :mmHg ;
        sh:minCount 1 ; sh:maxCount 1 ;
      ]
    ] ;
  ] .
        
<Obs1>
  :component [ :code "systolic"; :value "140"^^:mmHg ]
  :component [ :code "diastolic"; :value "80"^^:mmHg ] .
<Obs2>
  :component [ :code "systolic"; :value "140"^^:mmHg ]
  :component [ :code "diastolic"; :value "80"^^:mmHg ]
  :component "something invalid" .

SHACL

PREFIX : <http://a.example/#>
PREFIX sh:<http://www.w3.org/ns/shacl#>
<BPShape> a sh:Shape ;
  sh:property [
    sh:predicate :component ;
    sh:qualifiedMinCount 1 ; sh:qualifiedMaxCount 1 ;
    sh:qualifiedShape [
      …
    ] ;
    sh:minCount 1 ; sh:maxCount 1 ;
  ] ;
  sh:property [
    sh:predicate :component ;
    sh:qualifiedMinCount 1 ; sh:qualifiedMaxCount 1 ;
    sh:qualifiedShape [
      …
    ] ;
  ] .
  sh:property [
    sh:predicate :component ;
    sh:Shape [
      sh:Or (
        [ sh:property [
            sh:predicate :code ;
            sh:in ("systolic") ;
            sh:minCount 1 ; sh:maxCount 1 ;
          ] ;
          sh:property [
            sh:predicate :value ;
            sh:valueType :mmHg ;
            sh:minCount 1 ; sh:maxCount 1 ;
          ]
        ]
        [ sh:property [
            sh:predicate :code ;
            sh:in ("diastolic") ;
            sh:minCount 1 ; sh:maxCount 1 ;
          ] ;
          sh:property [
            sh:predicate :value ;
            sh:valueType :mmHg ;
            sh:minCount 1 ; sh:maxCount 1 ;
          ]
        ]
      )
    ]
  ]
.
        

Shape Expressions

PREFIX : <http://a.example/#>
<BPShape> {
  :component { :code ["systolic"]; :value :mmHg }
}

Shape Expressions

PREFIX : <http://a.example/#>
<BPShape> {
  :component { :code ["systolic"]; :value :mmHg } ;
  :component { :code ["diastolic"]; :value :mmHg }
}

ShEx deeper dive

What is Shape Expressions?

PREFIX issue: <http://ex.example/>
PREFIX foaf: <http://xmlns.com/foaf/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX myco: <http://myco.example/#>

start = <IssueShape>

<IssueShape> {
    issue:status (issue:unassigned issue:assigned),
    issue:reportedBy @<UserShape>,
    issue:reportedOn xsd:dateTime,
    ( issue:assignedTo @<EmployeeShape>,
      issue:assignedOn xsd:dateTime )?
}

<UserShape> {
    (foaf:name LITERAL
     | foaf:givenName LITERAL+,
       foaf:familyName LITERAL),
    foaf:mbox IRI
}

<EmployeeShape> {
    foaf:page (myco:Employee~),
    foaf:givenName LITERAL+,
    foaf:familyName LITERAL,
    foaf:phone IRI*,
    foaf:mbox IRI
}

User example

What's my user profile look like?

PREFIX foaf: <http://xmlns.com/foaf/>

<UserShape> {
    (foaf:name LITERAL
     | foaf:givenName LITERAL+,
       foaf:familyName LITERAL),
    foaf:mbox IRI
}

User example

What's my user profile look like?

PREFIX foaf: <http://xmlns.com/foaf/>

<UserShape> {
    (foaf:name LITERAL
     |foaf:givenName LITERAL+,
       foaf:familyName LITERAL),
    foaf:mbox IRI
}

User example

What's my user profile look like?

PREFIX foaf: <http://xmlns.com/foaf/>

<UserShape> {
    (foaf:name LITERAL
     |foaf:givenName LITERAL+,
       foaf:familyName LITERAL),
    foaf:mbox IRI
}

compare with other schema languages...

RelaxNG Compact Syntax

    (element foaf:name { xsd:string }
     | (element foaf:givenName { xsd:string }+,
        element foaf:familyName { xsd:string })),
    element foaf:mbox { xsd:anyURI }

Regex

(N|(G+F))M
NM
GFM
GGGFM

W3C XML Schema

  <xs:complexType name="UserContent">
    <xs:sequence>
      <xs:choice>
        <xs:element name="name" type="xs:string"/>
        <xs:sequence>
          <xs:element maxOccurs="unbounded" name="givenName" type="xs:string"/>
          <xs:element name="familyName" type="xs:string"/>
        </xs:sequence>
      </xs:choice>
      <xs:element name="mbox" type="xs:anyURI"/>
    </xs:sequence>
  </xs:complexType>

User example

Trying it out

PREFIX foaf: <http://xmlns.com/foaf/>

start = <UserShape>

<UserShape> {
    (foaf:name LITERAL
     | foaf:givenName LITERAL+,
       foaf:familyName LITERAL),
    foaf:mbox IRI
}

Try it at ericP's indulgent interface
or Jose's more correct interface:
<Alice>, <Bob>, <Gene>, <Pat>,
<Eve> (passes, OR is non-exclusive)

PREFIX foaf: <http://xmlns.com/foaf/>

<Alice>
  foaf:name "Alice" ;
  foaf:mbox <mailto:alice@example.org> .

<Bob>
  foaf:givenName "Robert", "Edward" ; foaf:familyName "Smith" ;
  foaf:mbox <mailto:bob@example.org> .

<Gene>
  foaf:givenname "Gene" ; foaf:familyName "Smith" ; # misspelling
  foaf:mbox <mailto:gene@example.org> .

<Pat>
  foaf:givenName "Patricia" ; # no foaf:familyName
  foaf:mbox <mailto:pat@example.org> .

<Eve>
  foaf:name "Eve" ; # both name and givenName/familyName
  foaf:givenName "Eve" ; foaf:familyName "石川" ;
  foaf:mbox <mailto:eve@example.org> .

Exercise 1

Fix Gene, Pat and Eve

PREFIX foaf: <http://xmlns.com/foaf/>

start = <UserShape>

<UserShape> {
    (foaf:name LITERAL
     | foaf:givenName LITERAL+,
       foaf:familyName LITERAL),
    foaf:mbox IRI
}

Test at ericP's or Jose's interface

PREFIX foaf: <http://xmlns.com/foaf/>

<Gene>
  foaf:givenname "Gene" ; foaf:familyName "Smith" ;
  foaf:mbox <mailto:gene@example.org> .

<Pat>
  foaf:givenName "Patricia" ;
  foaf:mbox <mailto:pat@example.org> .

<Eve>
  foaf:name "Eve" ;
  foaf:givenName "Eve" ; foaf:familyName "石川" ;
  foaf:mbox <mailto:eve@example.org> .

Now break it;
it's more fun to demonstrate what validators whine about than what they accept.

Solution 1

Fixed Gene, Pat and Eve

PREFIX foaf: <http://xmlns.com/foaf/>

start = <UserShape>

<UserShape> {
    (foaf:name LITERAL
     | foaf:givenName LITERAL+,
       foaf:familyName LITERAL),
    foaf:mbox IRI
}

Test at ericP's or Jose's interface

PREFIX foaf: <http://xmlns.com/foaf/>

<Gene>
  foaf:givenName "Gene" ; foaf:familyName "Smith" ;
  foaf:mbox <mailto:gene@example.org> .

<Pat>
  foaf:givenName "Patricia" ; foaf:familyName "Gordon"  ;
  foaf:mbox <mailto:pat@example.org> .

<Eve1>
  foaf:name "Eve" ;
  foaf:mbox <mailto:eve@example.org> .

<Eve2>
  foaf:givenName "Eve" ; foaf:familyName "石川" ;
  foaf:mbox <mailto:eve@example.org> .

ShExC is a grammar

Issue +:reportedOn: xsd:date +:assignedOn: xsd:date User +foaf:name: LITERAL +foaf:givenName: LITERAL +foaf:familyName: LITERAL +foaf:mbox: IRI :reportedBy 1 0..* :assignedTo 0..* 0..1

ShExC is a BNF for RDF:

Issue: reportedOn
       reportedBy
       ( assignedOn
         assignedTo )?

reportedBy: User

assignedTo: User

User: ( name
        | givenName+
          familyName )
      mbox

example instance

<Issue1>
    issue:reportedBy <Bob> ;
    issue:reportedOn "2013-01-23T10:18:00"^^xsd:dateTime .

<Bob>
    foaf:givenName "Robert", "Edward" ; foaf:familyName "Smith" ;
    foaf:mbox <mailto:bob@example.org> .

ShExC is a grammar

Issue +:reportedOn: xsd:date +:assignedOn: xsd:date User +foaf:name: LITERAL +foaf:givenName: LITERAL +foaf:familyName: LITERAL +foaf:mbox: IRI :reportedBy 1 0..* :assignedTo 0..* 0..1

ShExC is a BNF for RDF:

start = <IssueShape>

<IssueShape> {
    issue:reportedBy @<UserShape>,
    issue:reportedOn xsd:dateTime,
    ( issue:assignedTo @<UserShape>,
      issue:assignedOn xsd:dateTime )?
}

<UserShape> {
    (foaf:name LITERAL
     | foaf:givenName LITERAL+,
       foaf:familyName LITERAL),
    foaf:mbox IRI
}

example instance

<Issue1>
    issue:reportedBy <Bob> ;
    issue:reportedOn "2013-01-23T10:18:00"^^xsd:dateTime .

<Bob>
    foaf:givenName "Robert", "Edward" ; foaf:familyName "Smith" ;
    foaf:mbox <mailto:bob@example.org> .

Nested shapes

Schema:

PREFIX : <http://a.example/#>
<BPShape> {
  :component { :code ["systolic"]; :value :mmHg };
  :component { :code ["diastolic"]; :value :mmHg }
}

Nested patterns can be labeled:

<BPShape> {
  :component @<systolicComponent>;
  :component @<diastolicComponent>
}

<systolicComponent> {
  :code ["systolic"];
  :value :mmHg
}

<diastolicComponent> {
  :code ["diastolic"];
  :value :mmHg
}

Data:

<Obs1>
  :component [ :code "systolic"; :value "140"^^:mmHg ]
  :component [ :code "diastolic"; :value "80"^^:mmHg ] .

Exercise 2

Decompose this schema:

PREFIX obs: <http://hl7.org/fhir/Observations.>
<Observation> {
  obs:code @<CodeableConcept>;
  (
    obs:valueQuantity @<Quantity>  |
    obs:valueCodeableConcept @<CodeableConcept>
  )?;
  obs:component {
    obs:component.code @<CodeableConcept>;
    (
      obs:component.valueQuantity @<Quantity>  |
      obs:component.valueCodeableConcept @<CodeableConcept>
    )?;
  }*;
}

Split the obs:component object into a <Observation.component>.

example instance:

<Obs1> obs:code [
    cc:coding [
      cd:system [
        fhir:value "http://a.example/sys1";
        fhir:code "code1" ] ] ];
  obs:valueCodeableConcept [
    cc:coding [
      cd:system [
        fhir:value "http://a.example/sys1";
        fhir:code "code2" ] ] ];
  obs:component [
    obs:component.code [
      cc:coding [
        cd:system [
          fhir:value "http://a.example/sys1";
          fhir:code "code3" ] ] ];
    obs:component.valueQuantity [
      fhir:Quantity.value [ fhir:value 1.23] ];
  ] .

Solution 2

Decomposed schema:

PREFIX obs: <http://hl7.org/fhir/Observations.>
<Observation> {
  obs:code @<CodeableConcept>;
  (
    obs:valueQuantity @<Quantity>  |
    obs:valueCodeableConcept @<CodeableConcept>
  )?;
  obs:component @<Observation.component>*;
}

<Observation.component> {
  obs:component.code @<CodeableConcept>;
  (
    obs:component.valueQuantity @<Quantity>  |
    obs:component.valueCodeableConcept @<CodeableConcept>
  )?;
}

Create a <EmployeeShape> distinct from an <UserShape>.

example instance:

<Obs1> obs:code [
    cc:coding [
      cd:system [
        fhir:value "http://a.example/sys1";
        fhir:code "code1" ] ] ];
  obs:valueCodeableConcept [
    cc:coding [
      cd:system [
        fhir:value "http://a.example/sys1";
        fhir:code "code2" ] ] ];
  obs:component [
    obs:component.code [
      cc:coding [
        cd:system [
          fhir:value "http://a.example/sys1";
          fhir:code "code3" ] ] ];
    obs:component.valueQuantity [
      fhir:Quantity.value [ fhir:value 1.23] ];
  ] .

ShEx Value sets

A value set is a set of possible values.

ex:mood ["happy" "sad" "indigo"]
ex:mood [mood:happy mood:sad mood:indigo]
ex:mood [mood:~]

Exercise 3

Schema:

PREFIX fhir: <http://hl7.org/fhir/>
PREFIX fhirvs: <http://hl7.org/fhir/ValueSet/>
<Observation> {
  a [fhir:Observation];
  fhir:nodeRole [fhir:treeRoot]?;
  fhir:DomainResource.text {
    fhir:Narrative.status @<code> AND
      {fhir:value .; # <--
    fhir:Narrative.div LITERAL;
  }?;
  fhir:Observation.status @<code> AND
    {fhir:value .; # <--
  fhir:Observation.related {
    fhir:Observation.related.type @<code> AND
      {fhir:value .?; # <--
    fhir:Observation.related.target {
      fhir:link @<Observation> OR CLOSED {a [fhir:Observation]};
    }
  }*;
}
<code>     NONLITERAL { fhir:value LITERAL?; }

Data:

PREFIX fhir: <http://hl7.org/fhir/>
<Obs1> a fhir:Observation ;
  fhir:nodeRole fhir:treeRoot ;
  fhir:DomainResource.text [
    fhir:Narrative.status [ fhir:value "generated" ];
    fhir:Narrative.div "blah blah" ;
  ];
  fhir:Observation.status [ fhir:value "registered" ] ;
  fhir:Observation.related [
    fhir:Observation.related.type [ fhir:value "replaces" ] ;
    fhir:Observation.related.target [ fhir:link <Obs2> ]
  ] .
<Obs2> a fhir:Observation .

Solution 3

Schema:

PREFIX fhir: <http://hl7.org/fhir/>
PREFIX fhirvs: <http://hl7.org/fhir/ValueSet/>
<Observation> {
  a [fhir:Observation];
  fhir:nodeRole [fhir:treeRoot]?;
  fhir:DomainResource.text {
    fhir:Narrative.status @<code> AND
      {fhir:value @fhirvs:narrative-status};
    fhir:Narrative.div LITERAL;
  }?;
  fhir:Observation.status @<code> AND
    {fhir:value @fhirvs:observation-status};
  fhir:Observation.related {
    fhir:Observation.related.type @<code> AND
      {fhir:value @fhirvs:observation-relationshiptypes}?;
    fhir:Observation.related.target {
      fhir:link @<Observation> OR CLOSED {a [fhir:Observation]};
    }
  }*;
}
<code>     NONLITERAL { fhir:value LITERAL?; }
fhirvs:narrative-status
  ["generated" "extensions" "additional" "empty"]
fhirvs:observation-status
  ["registered" "preliminary" "final" "amended"
   "cancelled" "entered-in-error" "unknown"]
fhirvs:observation-relationshiptypes
  ["has-member" "derived-from" "sequel-to"
   "replaces" "qualified-by" "interfered-by"]

Data:

PREFIX fhir: <http://hl7.org/fhir/>
<Obs1> a fhir:Observation ;
  fhir:nodeRole fhir:treeRoot ;
  fhir:DomainResource.text [
    fhir:Narrative.status [ fhir:value "generated" ];
    fhir:Narrative.div "blah blah" ;
  ];
  fhir:Observation.status [ fhir:value "registered" ] ;
  fhir:Observation.related [
    fhir:Observation.related.type [ fhir:value "replaces" ] ;
    fhir:Observation.related.target [ fhir:link <Obs2> ]
  ] .
<Obs2> a fhir:Observation .

Value sets in FHIR

EXTRA

Schema:

PREFIX fhir: <http://hl7.org/fhir/>
PREFIX fhirvs: <http://hl7.org/fhir/ValueSet/>
fhirvs:device-action EXTRA fhir:CodeableConcept.coding {
    fhir:CodeableConcept.coding {
        fhir:Coding.system {fhir:value ["http://hl7.org/fhir/device-action"]}?;
        fhir:Coding.code {fhir:value ["implanted" "explanted" "manipulated"]}
    }
}

Data:

PREFIX fhir: <http://hl7.org/fhir/>
<da1>
  fhir:CodeableConcept.coding [
    fhir:Coding.system [fhir:value "http://hl7.org/fhir/device-action"];
    fhir:Coding.code [fhir:value "implanted"]
  ];
  fhir:CodeableConcept.coding [ ] .

Reverse arcs

Test arcs coming into an object.

Useful for e.g. two Observations connected through a common Encounter.

PREFIX issue: <http://ex.example/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

start = <IssueShape>

<IssueShape> {
    issue:status (issue:assigned issue:resolved ),
    issue:reportedOn xsd:dateTime,
    issue:assignedOn xsd:dateTime,
    issue:related @<RefdIssueShape>
}

<RefdIssueShape> {
    issue:name LITERAL,
    ^issue:related <IssueShape>
}
PREFIX issue: <http://ex.example/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

<Issue1>
    issue:status     issue:assigned ;
    issue:reportedOn "2013-01-23T10:18:00"^^xsd:dateTime ;
    issue:assignedOn "2013-01-23T11:00:00"^^xsd:dateTime ;
    issue:related    <Issue3> .

<Issue3>
    issue:name "smokey" .

Closed shapes

Use case: storing application data in a

"If I tell you X, will you understand it?"

Closed shapes

What about re-used nodes?

Closed shapes:

PREFIX issue: <http://ex.example/>
PREFIX foaf: <http://xmlns.com/foaf/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

start = <IssueShape>

<IssueShape> {
    issue:reportedBy @<UserShape>,
    issue:assignedTo @<EmployeeShape>?
}

<UserShape> {
    foaf:name LITERAL
    foaf:mbox IRI
}

<EmployeeShape> {
    foaf:givenName LITERAL+,
    foaf:familyName LITERAL,
    foaf:mbox IRI
}

<User2> fits multiple shapes:

PREFIX ex: <http://ex.example/>
PREFIX foaf: <http://xmlns.com/foaf/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

<Issue1>
    ex:reportedBy   <User2> ;
    ex:assignedTo   <User2> ;

<User2>
    foaf:name "Bob Smith" ;
    foaf:givenName "Bob" ;
    foaf:familyName "Smith" ;
    foaf:mbox <mailto:bob@example.org> .

Semantic actions

For extensibility:

For actions:

ShExMap

ShExMap arrows

see ShExMap docs

API-native

Provides purpose-fit expressivity with minimal implementation cost.

Can count on order.

Javascript API actions embedded in %js{ … %}.

PREFIX issue: <http://ex.example/>
PREFIX foaf: <http://xmlns.com/foaf/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

start = <IssueShape>

<IssueShape> {
    issue:state (issue:unassigned issue:assigned),
    issue:reportedBy @<UserShape>,
    issue:reportedOn xsd:dateTime
        %js{ report = _.o; return true; %},
    (issue:reproducedBy @<EmployeeShape>,
     issue:reproducedOn xsd:dateTime
        %js{ return _.o.lex > report.lex; %}
    ),
    issue:related @<IssueShape>*
}

<UserShape> {
    (foaf:name xsd:string
     | foaf:givenName xsd:string+,
       foaf:familyName xsd:string),
    foaf:mbox IRI
}

<EmployeeShape> {
    foaf:givenName xsd:string+,
    foaf:familyName xsd:string,
    foaf:phone IRI*,
    foaf:mbox IRI
}
PREFIX issue: <http://ex.example/>
PREFIX foaf: <http://xmlns.com/foaf/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

<Issue1>
    ex:state        ex:unassigned ;
    ex:reportedBy   <User2> ;
    ex:reportedOn   "2013-01-23T10:18:00"^^xsd:dateTime ;
    ex:reproducedBy <Thompson.J> ;
    ex:reproducedOn "2013-01-23T10:00:00"^^xsd:dateTime .

<User2>
    foaf:givenName "Bob" ;
    foaf:familyName "Smith" ;
    foaf:mbox <mail:bob@example.org> .

<Thompson.J>
    foaf:givenName "Joe", "Joseph" ;
    foaf:familyName "Thompson" ;
    foaf:phone <tel:+456> ;
    foaf:mbox <mail:joe@example.org> .

SPARQL

Isn't there an RDF query language?

%sparql{ … %} can complement our %js{ … %} actions.

PREFIX issue: <http://ex.example/>
PREFIX foaf: <http://xmlns.com/foaf/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

start = <IssueShape>

<IssueShape> {
    issue:state (issue:unassigned issue:assigned),
    issue:reportedBy @<UserShape>,
    issue:reportedOn xsd:dateTime
        %js{ report = _.o; return true; %},
    (issue:reproducedBy @<EmployeeShape>,
     issue:reproducedOn xsd:dateTime
        %js{ return _.o.lex > report.lex; %}
        %sparql{ ?s issue:reportedOn ?rpt . FILTER (?o > ?rpt) %}
    ),
    issue:related @<IssueShape>*
}

<UserShape> {
    (foaf:name xsd:string
     | foaf:givenName xsd:string+,
       foaf:familyName xsd:string),
    foaf:mbox IRI
}

<EmployeeShape> {
    foaf:givenName xsd:string+,
    foaf:familyName xsd:string,
    foaf:phone IRI*,
    foaf:mbox IRI
}
PREFIX issue: <http://ex.example/>
PREFIX foaf: <http://xmlns.com/foaf/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

<Issue1>
    ex:state        ex:unassigned ;
    ex:reportedBy   <User2> ;
    ex:reportedOn   "2013-01-23T10:18:00"^^xsd:dateTime ;
    ex:reproducedBy <Thompson.J> ;
    ex:reproducedOn "2013-01-23T10:00:00"^^xsd:dateTime .

<User2>
    foaf:givenName "Bob" ;
    foaf:familyName "Smith" ;
    foaf:mbox <mail:bob@example.org> .

<Thompson.J>
    foaf:givenName "Joe", "Joseph" ;
    foaf:familyName "Thompson" ;
    foaf:phone <tel:+456> ;
    foaf:mbox <mail:joe@example.org> .

Generators

Semantic actions can have side-effects.

PREFIX ex: <http://ex.example/#>
PREFIX foaf: <http://xmlns.com/foaf/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

      %js{
           action.arguments[1].message("in init");
           doc = document.implementation.createDocument
                   ("http://ex.example/xml", "Issue", undefined);
           issue = doc.childNodes[0];
      %}

start = <IssueShape>

<IssueShape> {
    ex:state (ex:unassigned ex:assigned)
      %js{ issue.setAttribute("id", _.s.lex);
           issue.setAttribute("state", _.o.lex.substr(19));
           person = user = doc.createElement("Reported");
      %},
    ex:reportedBy @<Person>
      %js{{ issue.appendChild(user); %},
    ex:reportedOn xsd:dateTime
      %js{{ user.setAttribute("reportedOn", _.o.lex);
           person = reproducer = doc.createElement("Reproduced");
      %},
    (ex:reproducedBy @<EmployeeShape>
      %js{{ issue.appendChild(person);%},
     ex:reproducedOn xsd:dateTime
      %js{{ reproducer.setAttribute("reportedOn", _.o.lex);
      %})?,
    ex:related @<IssueShape>*
} %js{ 
window.open("data:application/xml;charset=utf-8;base64,"
  + Base64.encode((new XMLSerializer()).serializeToString(doc)),
  "toy");
 %}
VIRTUAL <Person> {
    (foaf:name xsd:string
     | foaf:givenName xsd:string+
      %js{ gn = doc.createElement("GivenName");
           gn.appendChild(doc.createTextNode(_.o.lex));
           person.appendChild(gn);
      %},
       foaf:familyName xsd:string
      %js{ fn = doc.createElement("FamilyName");
           fn.appendChild(doc.createTextNode(_.o.lex));
           person.appendChild(fn);
      %}
    ),
    foaf:mbox IRI
      %js{ fn = doc.createElement("Email-address");
           fn.appendChild(doc.createTextNode(_.o.lex));
           person.appendChild(fn);
      %}
}
<UserShape> & <Person> {
}
<EmployeeShape> {
    & <Person>,
    foaf:phone IRI+
      %js{ fn = doc.createElement("Telephone-Number");
           fn.appendChild(doc.createTextNode(_.o.lex));
           person.appendChild(fn);
      %}
}
<Issue xmlns="http://ex.example/xml" id="Issue1" state="unassigned">
  <Reported reportedOn="2013-01-23T10:18:00">
    <GivenName>Bob</GivenName>
    <FamilyName>Smith</FamilyName>
    <Email-address>mailto:bob@example.org</Email-address>
  </Reported>
  <Reproduced reportedOn="2013-01-23T11:00:00">
    <GivenName>Joe</GivenName>
    <GivenName>Joseph</GivenName>
    <FamilyName>Thompson</FamilyName>
    <Email-address>mailto:joe@example.org</Email-address>
    <Telephone-Number>tel:+456</Telephone-Number>
  </Reproduced>
</Issue>

GenX

GenX details.

=<function>?, $<namespace>, !debugger

PREFIX ex: <http://ex.example/#>
PREFIX foaf: <http://xmlns.com/foaf/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

                            %GenX{ issue $http://ex.example/xml %}

start = <IssueShape>

<IssueShape> {
    ex:state (ex:unassigned ex:assigned) %GenX{ @state =substr(19) %},
    ex:reportedBy @<Person>              %GenX{ reported = %},
    ex:reportedOn xsd:dateTime           %GenX{ [-1]@date %},
    (ex:reproducedBy @<EmployeeShape>    ,
     ex:reproducedOn xsd:dateTime        %GenX{ @date %}
    )?                                   %GenX{ reproduced = %},
    ex:related @<IssueShape>*
}                                        %GenX{ @id %}
VIRTUAL <Person> {
    (foaf:name xsd:string                %GenX{ full-name %}
     | foaf:givenName xsd:string+        %GenX{ given-name %},
       foaf:familyName xsd:string        %GenX{ family-name %}
    ),
    foaf:mbox IRI               %GenX{ email-addr %}
}
<UserShape> & <Person> {
}
<EmployeeShape> {
    & <Person>,
    foaf:phone IRI+             %GenX{ tel-number %}
}
<Issue xmlns="http://ex.example/xml" id="Issue1" state="unassigned">
  <Reported reportedOn="2013-01-23T10:18:00">
    <GivenName>Bob</GivenName>
    <FamilyName>Smith</FamilyName>
    <Email-address>mailto:bob@example.org</Email-address>
  </Reported>
  <Reproduced reportedOn="2013-01-23T11:00:00">
    <GivenName>Joe</GivenName>
    <GivenName>Joseph</GivenName>
    <FamilyName>Thompson</FamilyName>
    <Email-address>mailto:joe@example.org</Email-address>
    <Telephone-Number>tel:+456</Telephone-Number>
  </Reproduced>
</Issue>

GenJ

GenJ is GenX's JSON counterpart.

PREFIX ex: <http://ex.example/#>
PREFIX foaf: <http://xmlns.com/foaf/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
                                         %GenJ{ @id %}

start = <IssueShape>

<IssueShape> {
    ex:state (ex:unassigned ex:assigned) %GenJ{ state %},
    ex:reportedBy @<User>                %GenJ{ rprtBy @id %},
    ex:reportedOn xsd:dateTime           %GenJ{ rprtOn %},
    (ex:reproducedBy @<User>             %GenJ{ rpdcBy @id %},
     ex:reproducedOn xsd:dateTime        %GenJ{ rpdcOn %}
    )?,
    ex:related @<IssueShape>*
}
<User> {
    (foaf:name xsd:string                %GenJ{ name %}
     | foaf:givenName xsd:string+        %GenJ{ fname %},
       foaf:familyName xsd:string        %GenJ{ lname %}
    ),
    foaf:mbox IRI                        %GenJ{ email %},
    foaf:phone IRI?                      %GenJ{ tel %}
}
{"@context":
 {"ns0":"http://ex.example/#", "state":"ns0:state",
  "rprtBy":"ns0:reportedBy", "ns1":"http://xmlns.com/foaf/",
  "fname":"ns1:givenName", "lname":"ns1:familyName",
  "email":"ns1:mbox", "ns2":"http://www.w3.org/2001/XMLSchema#",
  "rprtOn":{
      "@id":"ns0:reportedOn", "@type":"ns2:dateTime" },
  "rpdcBy":"ns0:reproducedBy",
  "tel":"ns1:phone",
  "rpdcOn":{
      "@id":"ns0:reproducedOn", "@type":"ns2:dateTime" } },
 "@id":"Issue1",
 "state":"http://ex.example/#unassigned",
 "rprtBy":{
     "@id":"User2", "fname":"Bob", "lname":"Smith",
     "email":"mailto:bob@example.org" },
 "rprtOn":"2013-01-23T10:18:00",
 "rpdcBy":{
     "@id":"Thompson.J", "fname":["Joe","Joseph"], "lname":"Thompson",
     "email":"mailto:joe@example.org", "tel":"tel:+456"
 },
 "rpdcOn":"2013-01-23T11:00:00"
}

GenR

GenR is GenJ's RDF counterpart.

Generate RDF from RDF?!?

PREFIX ex: <http://ex.example/#>
PREFIX my: <http://my.example/#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

                                         %GenR{ %}
start = <IssueShape>

<IssueShape> {
    ex:state (ex:unassigned ex:assigned )
    %GenR{ ISSUE = IRI(CONCAT(STR(s), "/myMark")) .
           STATE = SUBSTR(STR(o), 19) .
           ISSUE my:status STATE %},
    ex:reportedOn xsd:dateTime 
          %GenR{ ON = BNODE("on") .
                 ISSUE my:on ON .
                 DATE=CONCAT(YEAR(o),'-'MONTH(o),'-',DAY(o)) .
                 TIME=CONCAT(HOURS(o),':'MINUTES(o),':',SECONDS(o),TZ(o)) .
                 ON ex:date STRDT(DATE, xsd:date) .
                 ON ex:time STRDT(TIME, xsd:time) %}
}
PREFIX ex: <http://ex.example/#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

<Issue1>
    ex:state        ex:unassigned ;
    ex:reportedOn   "2013-01-23T10:18:12Z"^^xsd:dateTime .
<Issue1/myMark> my:status "unassigned" ;
  my:on [
    my:date "2013-01-23"^^xsd:date ;
    my:time "10:18:12Z"^^xsd:time
  ] .

this example

example converting between clinical data schemas

GenN

Normalize RDF.

Limited use:

%GenN{ %}

example

Resources