Skip to content

RegionShape and DatasetShape could select by OEO class instead of by predicate #60

Description

@jh-RLI

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

  • I am aware of the workflow in CONTRIBUTING.md

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions