From 4ce6afd370ed65a98d71d5bb4cefd84bcf335b7e Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:35:03 +0100 Subject: [PATCH 1/3] Add HealthMeasurement shape (Schema.org Health-Lifesci subset) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a SHACL shape for schema:Observation focused on the common health and fitness measurements emitted by Apple Health, Google Fit, and Strava exports — heart rate, steps, sleep duration, body weight, distance, exercise duration, and energy burned. Constrains schema:value, schema:unitCode, schema:unitText, schema:observationDate, schema:observationAbout, schema:measuredProperty, and schema:measurementMethod. Cites HL7 FHIR Observation as a heavier clinical alternative without depending on it. Co-Authored-By: Claude Opus 4.7 (1M context) --- shapes/health_measurement.ttl | 99 +++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 shapes/health_measurement.ttl diff --git a/shapes/health_measurement.ttl b/shapes/health_measurement.ttl new file mode 100644 index 0000000..6f95bbb --- /dev/null +++ b/shapes/health_measurement.ttl @@ -0,0 +1,99 @@ +@prefix dc: . +@prefix dct: . +@prefix prov: . +@prefix schema: . +@prefix sh: . +@prefix vs: . +@prefix xsd: . + +@prefix health_measurement_shape: . + +health_measurement_shape:HealthMeasurementShape + a sh:NodeShape ; + sh:targetClass schema:Observation ; + sh:name "Health Measurement Shape" ; + sh:description "SHACL shape for a single health or fitness measurement modelled as a Schema.org Observation. Covers the common cases an Apple Health, Google Fit, or Strava export emits — heart rate, steps, sleep duration, body weight, distance, exercise duration, and energy burned." ; + dct:created "2026-04-23"^^xsd:date ; + vs:term_status "testing" ; + dc:source ; + prov:wasDerivedFrom ; + dct:references ; # references the schema:Observation class (currently in https://pending.schema.org/) which the Schema.org Health-Lifesci extension and broader Schema.org community use to describe a single point measurement, together with schema:value, schema:unitCode, schema:observationDate, schema:observationAbout, and schema:measuredProperty. + dct:references ; # references the Schema.org Health-Lifesci hosted extension which defines health-specific vocabulary (e.g. schema:ExerciseAction usage, body-weight terms) reused alongside the core Observation pattern. + dct:references ; # references HL7 FHIR Observation as a heavier-duty alternative model used by clinical systems; this shape does not depend on FHIR but acknowledges the conceptual mapping. + sh:codeIdentifier "HealthMeasurement"; + + # Numeric value of the measurement. + sh:property [ + sh:path schema:value ; + sh:datatype xsd:decimal ; + sh:maxCount 1 ; + sh:name "Value" ; + sh:description "Numeric value of the measurement (e.g., 72 for a heart-rate reading, 8500 for a step count, 5.2 for a kilometre distance)." ; + sh:codeIdentifier "value"; + ] ; + + # Unit of measurement. UN/CEFACT Common Code (3 characters) or any URL + # (e.g. a QUDT unit IRI). Per Schema.org, schema:unitCode permits both. + sh:property [ + sh:path schema:unitCode ; + sh:or ( + [ sh:datatype xsd:string ] + [ sh:nodeKind sh:IRI ] + ) ; + sh:maxCount 1 ; + sh:name "Unit Code" ; + sh:description "Unit of measurement, given as a UN/CEFACT Common Code 3-character string (e.g. \"BPM\" for beats per minute, \"KGM\" for kilograms, \"KMT\" for kilometres) or a URL such as a QUDT unit IRI." ; + sh:codeIdentifier "unitCode"; + ] ; + + # Optional human-readable unit text (Schema.org provides schema:unitText). + sh:property [ + sh:path schema:unitText ; + sh:datatype xsd:string ; + sh:maxCount 1 ; + sh:name "Unit Text" ; + sh:description "Optional human-readable string for the unit of measurement, used when no UN/CEFACT or URL code is available (e.g. \"steps\", \"hours\")." ; + sh:codeIdentifier "unitText"; + ] ; + + # Date and time at which the observation was made. + sh:property [ + sh:path schema:observationDate ; + sh:datatype xsd:dateTime ; + sh:maxCount 1 ; + sh:name "Observation Date" ; + sh:description "Date and time at which the measurement was taken (Schema.org schema:observationDate)." ; + sh:codeIdentifier "observationDate"; + ] ; + + # The entity the observation is about. For self-reported health and + # fitness data this is the user's WebID. + sh:property [ + sh:path schema:observationAbout ; + sh:nodeKind sh:IRI ; + sh:maxCount 1 ; + sh:name "Observation About" ; + sh:description "The entity the observation is about. For personal health and fitness data this is typically the user's WebID (Schema.org schema:observationAbout)." ; + sh:codeIdentifier "observationAbout"; + ] ; + + # What property is being measured. Left open to any IRI so that apps can + # use either Schema.org properties (e.g. schema:weight) or vocabulary from + # other systems (HL7 FHIR, LOINC, QUDT, Wikidata, Data Commons). + sh:property [ + sh:path schema:measuredProperty ; + sh:nodeKind sh:IRI ; + sh:maxCount 1 ; + sh:name "Measured Property" ; + sh:description "The property being measured, identified by IRI. Apps may use Schema.org properties (e.g. schema:weight), Health-Lifesci terms, or external vocabularies such as HL7 FHIR, LOINC, or QUDT (Schema.org schema:measuredProperty)." ; + sh:codeIdentifier "measuredProperty"; + ] ; + + # Optional method/device used to capture the measurement (e.g. a smart + # watch, a manual entry, an exercise activity). + sh:property [ + sh:path schema:measurementMethod ; + sh:name "Measurement Method" ; + sh:description "Method or device used to capture the measurement (Schema.org schema:measurementMethod). Free-form: may be an IRI to a device, an enumeration value, or a literal label." ; + sh:codeIdentifier "measurementMethod"; + ] . From a6a2ef21a7083d2171681c890f71e0ea150d8699 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:37:02 +0100 Subject: [PATCH 2/3] Loosen value/observationDate datatypes; tighten measurementMethod - schema:value: accept xsd:decimal, xsd:integer, xsd:double, or xsd:float so that integer step counts and double-precision sensor readings are not rejected by the exact sh:datatype match. - schema:observationDate: accept either xsd:dateTime or xsd:date so daily aggregates (weight, daily step total) validate alongside instantaneous readings. - schema:measurementMethod: add sh:maxCount 1 and an IRI-or-literal node-kind constraint so it matches the rest of the shape. Co-Authored-By: Claude Opus 4.7 (1M context) --- shapes/health_measurement.ttl | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/shapes/health_measurement.ttl b/shapes/health_measurement.ttl index 6f95bbb..8d93b00 100644 --- a/shapes/health_measurement.ttl +++ b/shapes/health_measurement.ttl @@ -22,13 +22,21 @@ health_measurement_shape:HealthMeasurementShape dct:references ; # references HL7 FHIR Observation as a heavier-duty alternative model used by clinical systems; this shape does not depend on FHIR but acknowledges the conceptual mapping. sh:codeIdentifier "HealthMeasurement"; - # Numeric value of the measurement. + # Numeric value of the measurement. Accepts the common XSD numeric + # datatypes since SHACL sh:datatype matches exactly (no XSD subtype + # widening), and exporters emit any of decimal, integer, double, or + # float depending on the metric. sh:property [ sh:path schema:value ; - sh:datatype xsd:decimal ; + sh:or ( + [ sh:datatype xsd:decimal ] + [ sh:datatype xsd:integer ] + [ sh:datatype xsd:double ] + [ sh:datatype xsd:float ] + ) ; sh:maxCount 1 ; sh:name "Value" ; - sh:description "Numeric value of the measurement (e.g., 72 for a heart-rate reading, 8500 for a step count, 5.2 for a kilometre distance)." ; + sh:description "Numeric value of the measurement (e.g., 72 for a heart-rate reading, 8500 for a step count, 5.2 for a kilometre distance). May be xsd:decimal, xsd:integer, xsd:double, or xsd:float." ; sh:codeIdentifier "value"; ] ; @@ -56,13 +64,18 @@ health_measurement_shape:HealthMeasurementShape sh:codeIdentifier "unitText"; ] ; - # Date and time at which the observation was made. + # Date or date-time at which the observation was made. Schema.org's + # observationDate range is Date or DateTime; daily metrics (e.g. weight, + # daily step total) are typically stored at date granularity. sh:property [ sh:path schema:observationDate ; - sh:datatype xsd:dateTime ; + sh:or ( + [ sh:datatype xsd:dateTime ] + [ sh:datatype xsd:date ] + ) ; sh:maxCount 1 ; sh:name "Observation Date" ; - sh:description "Date and time at which the measurement was taken (Schema.org schema:observationDate)." ; + sh:description "Date or date-time at which the measurement was taken (Schema.org schema:observationDate). Use xsd:dateTime for instantaneous readings (heart rate, exercise samples) and xsd:date for daily aggregates." ; sh:codeIdentifier "observationDate"; ] ; @@ -90,10 +103,16 @@ health_measurement_shape:HealthMeasurementShape ] ; # Optional method/device used to capture the measurement (e.g. a smart - # watch, a manual entry, an exercise activity). + # watch, a manual entry, an exercise activity). May be an IRI (device or + # enumeration value) or a literal label. sh:property [ sh:path schema:measurementMethod ; + sh:or ( + [ sh:nodeKind sh:IRI ] + [ sh:nodeKind sh:Literal ] + ) ; + sh:maxCount 1 ; sh:name "Measurement Method" ; - sh:description "Method or device used to capture the measurement (Schema.org schema:measurementMethod). Free-form: may be an IRI to a device, an enumeration value, or a literal label." ; + sh:description "Method or device used to capture the measurement (Schema.org schema:measurementMethod). May be an IRI to a device or enumeration value, or a literal label." ; sh:codeIdentifier "measurementMethod"; ] . From 64244a02b6a12ac71885fa518422ef6a9855b3fa Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:39:09 +0100 Subject: [PATCH 3/3] Use sh:IRIOrLiteral; soften shape description re: pending Schema.org - schema:unitCode and schema:measurementMethod: replace the longhand sh:or node-kind disjunctions with the built-in sh:IRIOrLiteral so language-tagged strings are accepted and intent is clearer. - Top-level sh:description now flags that schema:Observation and the schema:observation* properties currently sit in the Schema.org pending area and that consumer-health exporters do not emit them natively (an importer is expected to map proprietary formats to this shape). Co-Authored-By: Claude Opus 4.7 (1M context) --- shapes/health_measurement.ttl | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/shapes/health_measurement.ttl b/shapes/health_measurement.ttl index 8d93b00..4a16a09 100644 --- a/shapes/health_measurement.ttl +++ b/shapes/health_measurement.ttl @@ -12,7 +12,7 @@ health_measurement_shape:HealthMeasurementShape a sh:NodeShape ; sh:targetClass schema:Observation ; sh:name "Health Measurement Shape" ; - sh:description "SHACL shape for a single health or fitness measurement modelled as a Schema.org Observation. Covers the common cases an Apple Health, Google Fit, or Strava export emits — heart rate, steps, sleep duration, body weight, distance, exercise duration, and energy burned." ; + sh:description "SHACL shape for a single health or fitness measurement modelled as a Schema.org Observation. The intended representation for the common cases consumer health and fitness exports (Apple Health, Google Fit, Strava) describe — heart rate, steps, sleep duration, body weight, distance, exercise duration, and energy burned. Note: schema:Observation and the schema:observation* properties currently sit in the pending area of Schema.org (https://pending.schema.org/); native exporters typically emit proprietary formats and an importer is expected to map them to this shape." ; dct:created "2026-04-23"^^xsd:date ; vs:term_status "testing" ; dc:source ; @@ -40,14 +40,11 @@ health_measurement_shape:HealthMeasurementShape sh:codeIdentifier "value"; ] ; - # Unit of measurement. UN/CEFACT Common Code (3 characters) or any URL - # (e.g. a QUDT unit IRI). Per Schema.org, schema:unitCode permits both. + # Unit of measurement. Per Schema.org, schema:unitCode is Text or URL, + # so accept any IRI or literal (including language-tagged strings). sh:property [ sh:path schema:unitCode ; - sh:or ( - [ sh:datatype xsd:string ] - [ sh:nodeKind sh:IRI ] - ) ; + sh:nodeKind sh:IRIOrLiteral ; sh:maxCount 1 ; sh:name "Unit Code" ; sh:description "Unit of measurement, given as a UN/CEFACT Common Code 3-character string (e.g. \"BPM\" for beats per minute, \"KGM\" for kilograms, \"KMT\" for kilometres) or a URL such as a QUDT unit IRI." ; @@ -107,10 +104,7 @@ health_measurement_shape:HealthMeasurementShape # enumeration value) or a literal label. sh:property [ sh:path schema:measurementMethod ; - sh:or ( - [ sh:nodeKind sh:IRI ] - [ sh:nodeKind sh:Literal ] - ) ; + sh:nodeKind sh:IRIOrLiteral ; sh:maxCount 1 ; sh:name "Measurement Method" ; sh:description "Method or device used to capture the measurement (Schema.org schema:measurementMethod). May be an IRI to a device or enumeration value, or a literal label." ;