Description of the issue
Five of the eight node shapes in oekg/shapes/oekg_shapes.ttl select their focus nodes with
sh:targetClass and an OEO class. Three do not — they use sh:targetObjectsOf and select by
the predicate that points at a node instead of by what the node is:
| Shape |
selects by |
could select by |
ex:RegionShape |
oeo:OEO_00020220, oeo:OEO_00020222 |
oeo:OEO_00020032 (study region), oeo:OEO_00020036 (interacting region) |
ex:DatasetShape |
oeo:OEO_00020437, oeo:OEO_00020436 |
oeo:OEO_00030029 (exogenous data), oeo:OEO_00030030 (endogenous data) |
ex:CommonShape |
ten predicates |
— see below, this one must stay as it is |
For the first two the classes are already named in the shape itself: ex:ScenarioShape constrains
exactly those predicates with sh:class to exactly those classes. So the shape already knows what
these nodes are; it just does not use that to find them.
Why this is worth changing. Selecting by class says what the shape is about — "this is what a
study region must look like" rather than "this is what a thing somebody pointed at with
has-study-region must look like". It also makes the shape's own structure uniform: eight shapes,
one targeting style, instead of five-and-three. And a region that exists but is not currently
linked to any scenario is still a region, and would be checked.
Ideas of solution
ex:RegionShape
a sh:NodeShape ;
sh:closed true ;
sh:ignoredProperties ( rdf:type ) ;
- sh:targetObjectsOf oeo:OEO_00020220 ; # has_study_region
- sh:targetObjectsOf oeo:OEO_00020222 ; # has_interacting_region
+ sh:targetClass oeo:OEO_00020032 ; # study region
+ sh:targetClass oeo:OEO_00020036 ; # interacting region
...
ex:DatasetShape
- sh:targetObjectsOf oeo:OEO_00020437 ; # has information input
- sh:targetObjectsOf oeo:OEO_00020436 ; # has information output
+ sh:targetClass oeo:OEO_00030029 ; # exogenous data
+ sh:targetClass oeo:OEO_00030030 ; # endogenous data
Measured, not assumed
Run against a current OEKG export (13,700 triples, 55 scenario bundles, 2026-09-14) with
pyshacl 0.28.1 — the same validator the oeplatform REST API uses — validating each bundle's
subgraph with the shape as it is and with the two shapes converted:
|
conforming |
violations |
sh:targetObjectsOf (today) |
0/55 |
218 |
sh:targetClass (converted) |
0/55 |
218 |
Identical, message for message. Four hand-built cases agree too: a correctly typed region, an
untyped one, a wrongly typed one, and a typed one missing its label all produce the same result
either way — because ex:ScenarioShape's sh:class already catches a wrong or missing type, and a
correctly typed node is selected by both styles.
The one case where they differ — and what it costs
A node that is untyped and violates the shape's own rules is reported three times today and
once after the change:
Region linked from a scenario, no rdf:type, no rdfs:label, plus a property the shape forbids
today 3 violations: "…is closed"
"Region target: This should be a string and have exactly one label."
"Scenario target: This should end in class study region"
converted 1 violation: "Scenario target: This should end in class study region"
Nothing goes unreported — the node is still refused, by ex:ScenarioShape's sh:class. What is
lost is the detail: today the report also says what else is wrong with the node, which is useful
when fixing it. That is a real trade and worth deciding deliberately rather than discovering later.
A middle option exists: keep both targeting styles on those two shapes. SHACL unions targets, so
sh:targetClass and sh:targetObjectsOf together select typed nodes and linked-but-untyped ones,
and nothing is lost at all. That costs two extra lines per shape and no behaviour.
ex:CommonShape must stay as it is
Not a matter of taste. It targets the objects of ten predicates — study descriptor tag, author,
sector division, sector, technology, contact person, organisation, funding source, energy carrier,
scenario type. Those objects are OEO terms picked from the shape's own sh:in lists, and in the
OEKG graph they are bare IRIs with no type triple at all. A sh:targetClass would therefore
select nothing and the shape would silently stop checking.
That is the dangerous version of this change: it would look tidier and validate nothing. Worth
saying out loud in this issue so nobody "finishes the job" later.
Consumer to be aware of
oeplatform fetches this file at a pinned commit
(OpenEnergyPlatform/oeplatform, setting OEKG_SHAPES_PINNED_COMMIT, currently
b4604e02060624b381bdbe2f872df94cfd0f5630) and validates every API write against it. A change here
reaches that platform only when the pin moves, which is the intended behaviour — the validator does
not change without a deploy. The measurement above was taken with that consumer's own validator, so
the two agree by construction.
Workflow checklist
Description of the issue
Five of the eight node shapes in
oekg/shapes/oekg_shapes.ttlselect their focus nodes withsh:targetClassand an OEO class. Three do not — they usesh:targetObjectsOfand select bythe predicate that points at a node instead of by what the node is:
ex:RegionShapeoeo:OEO_00020220,oeo:OEO_00020222oeo:OEO_00020032(study region),oeo:OEO_00020036(interacting region)ex:DatasetShapeoeo:OEO_00020437,oeo:OEO_00020436oeo:OEO_00030029(exogenous data),oeo:OEO_00030030(endogenous data)ex:CommonShapeFor the first two the classes are already named in the shape itself:
ex:ScenarioShapeconstrainsexactly those predicates with
sh:classto exactly those classes. So the shape already knows whatthese nodes are; it just does not use that to find them.
Why this is worth changing. Selecting by class says what the shape is about — "this is what a
study region must look like" rather than "this is what a thing somebody pointed at with
has-study-region must look like". It also makes the shape's own structure uniform: eight shapes,
one targeting style, instead of five-and-three. And a region that exists but is not currently
linked to any scenario is still a region, and would be checked.
Ideas of solution
ex:RegionShape a sh:NodeShape ; sh:closed true ; sh:ignoredProperties ( rdf:type ) ; - sh:targetObjectsOf oeo:OEO_00020220 ; # has_study_region - sh:targetObjectsOf oeo:OEO_00020222 ; # has_interacting_region + sh:targetClass oeo:OEO_00020032 ; # study region + sh:targetClass oeo:OEO_00020036 ; # interacting region ... ex:DatasetShape - sh:targetObjectsOf oeo:OEO_00020437 ; # has information input - sh:targetObjectsOf oeo:OEO_00020436 ; # has information output + sh:targetClass oeo:OEO_00030029 ; # exogenous data + sh:targetClass oeo:OEO_00030030 ; # endogenous dataMeasured, not assumed
Run against a current OEKG export (13,700 triples, 55 scenario bundles, 2026-09-14) with
pyshacl0.28.1 — the same validator the oeplatform REST API uses — validating each bundle'ssubgraph with the shape as it is and with the two shapes converted:
sh:targetObjectsOf(today)sh:targetClass(converted)Identical, message for message. Four hand-built cases agree too: a correctly typed region, an
untyped one, a wrongly typed one, and a typed one missing its label all produce the same result
either way — because
ex:ScenarioShape'ssh:classalready catches a wrong or missing type, and acorrectly typed node is selected by both styles.
The one case where they differ — and what it costs
A node that is untyped and violates the shape's own rules is reported three times today and
once after the change:
Nothing goes unreported — the node is still refused, by
ex:ScenarioShape'ssh:class. What islost is the detail: today the report also says what else is wrong with the node, which is useful
when fixing it. That is a real trade and worth deciding deliberately rather than discovering later.
A middle option exists: keep both targeting styles on those two shapes. SHACL unions targets, so
sh:targetClassandsh:targetObjectsOftogether select typed nodes and linked-but-untyped ones,and nothing is lost at all. That costs two extra lines per shape and no behaviour.
ex:CommonShapemust stay as it isNot a matter of taste. It targets the objects of ten predicates — study descriptor tag, author,
sector division, sector, technology, contact person, organisation, funding source, energy carrier,
scenario type. Those objects are OEO terms picked from the shape's own
sh:inlists, and in theOEKG graph they are bare IRIs with no type triple at all. A
sh:targetClasswould thereforeselect nothing and the shape would silently stop checking.
That is the dangerous version of this change: it would look tidier and validate nothing. Worth
saying out loud in this issue so nobody "finishes the job" later.
Consumer to be aware of
oeplatformfetches this file at a pinned commit(
OpenEnergyPlatform/oeplatform, settingOEKG_SHAPES_PINNED_COMMIT, currentlyb4604e02060624b381bdbe2f872df94cfd0f5630) and validates every API write against it. A change herereaches that platform only when the pin moves, which is the intended behaviour — the validator does
not change without a deploy. The measurement above was taken with that consumer's own validator, so
the two agree by construction.
Workflow checklist