tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees

This commit is contained in:
Bastien Gourdon
2026-07-30 15:13:03 +02:00
parent 76e95de471
commit f83b7d7491
9 changed files with 2751 additions and 1504 deletions
+59 -30
View File
@@ -13,8 +13,8 @@
# ----------------------
# 1. SPLIT BY PROVENANCE. v0.1.1 targeted pr:MetaModelObject and demanded an
# identifier, a name, an owner, an owning domain, a status and a version on
# everything. Applied to the harvested half of the graph that would fail on
# every one of ~50 000 Snowflake columns. Curated and Observed objects now
# everything. Applied to the captured half of the graph that would fail on
# every one of ~50 000 Snowflake columns. Defined and Captured objects now
# have separate shapes with separate expectations.
#
# 2. SCOPE FOR THE UNIFIED RELATIONS. belongsTo and represents deliberately
@@ -40,29 +40,29 @@
<https://ontology.pernod-ricard.com/metamodel/shapes/>
dcterms:title "PR Data Meta Model - SHACL Shapes" ;
dcterms:modified "2026-07-27"^^xsd:date ;
dcterms:modified "2026-07-30"^^xsd:date ;
rdfs:comment "Executable form of the rulebook. Validated by scripts/run_shacl_validation.py." .
#################################################################
# IDENTITY AND LIFECYCLE -- curated objects only
# IDENTITY AND LIFECYCLE -- defined objects only
#################################################################
prs:CuratedObjectShape a sh:NodeShape ;
sh:targetClass pr:CuratedObject ;
rdfs:comment "Applies to everything governance asserts. Observed objects are deliberately exempt: see ObservedObjectShape." ;
prs:DefinedObjectShape a sh:NodeShape ;
sh:targetClass pr:DefinedObject ;
rdfs:comment "Applies to everything governance writes. Captured objects are deliberately exempt: see CapturedObjectShape." ;
sh:property [
sh:path pr:hasIdentifier ; sh:datatype xsd:string ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ;
sh:message "DQ-001 -- every curated object carries exactly one identifier. NOTE: uniqueness across the graph is NOT tested here; SHACL checks cardinality only. The reconciliation job tests uniqueness." ] ;
sh:message "DQ-001 -- every defined object carries exactly one identifier. NOTE: uniqueness across the graph is NOT tested here; SHACL checks cardinality only. The reconciliation job tests uniqueness." ] ;
sh:property [
sh:path pr:hasName ; sh:datatype xsd:string ;
sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 3 ;
sh:severity sh:Violation ;
sh:message "Every curated object carries exactly one canonical name." ] ;
sh:message "Every defined object carries exactly one canonical name." ] ;
sh:property [
sh:path pr:hasStatus ; sh:minCount 1 ;
@@ -82,7 +82,7 @@ prs:CuratedObjectShape a sh:NodeShape ;
prs:PublishedShape a sh:NodeShape ;
sh:targetClass pr:CuratedObject ;
sh:targetClass pr:DefinedObject ;
rdfs:comment "Extra demands that only bite once an object claims to be PUBLISHED. Keeping these off the base shape is what lets a domain be documented progressively." ;
sh:sparql [
@@ -107,11 +107,11 @@ prs:PublishedShape a sh:NodeShape ;
#################################################################
# OBSERVED OBJECTS -- harvested, never hand-edited
# OBSERVED OBJECTS -- captured, never hand-edited
#################################################################
prs:ObservedObjectShape a sh:NodeShape ;
sh:targetClass pr:ObservedObject ;
prs:CapturedObjectShape a sh:NodeShape ;
sh:targetClass pr:CapturedObject ;
rdfs:comment "No identifier, no name, no owner, no status, no version. A harvested column is attributed through the Data Element mapped to it, never asserted directly. What it must carry is enough to be re-found on the next run." ;
sh:property [
@@ -124,12 +124,26 @@ prs:ObservedObjectShape a sh:NodeShape ;
sh:path pr:harvestedOn ; sh:datatype xsd:dateTime ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ;
sh:message "Every observed object records when it was harvested. Without it there is no way to tell a current fact from a stale one." ] ;
sh:message "Every captured object records when it was harvested. Without it there is no way to tell a current fact from a stale one." ] ;
sh:property [
sh:path pr:owningDomain ; sh:maxCount 0 ;
sh:severity sh:Warning ;
sh:message "Observed objects are not attributed directly -- ownership flows from the Data Element mapped to them." ] .
sh:message "Captured objects are not attributed directly -- ownership flows from the Data Element mapped to them." ] .
# inSchema is deliberately NOT required anywhere. Neo4j has no schema and
# BigQuery calls the level a dataset; a minCount 1 here would break on the
# first harvest outside Snowflake.
prs:DataStructureShape a sh:NodeShape ;
sh:targetClass pr:DataStructure ;
sh:property [
sh:path pr:inSchema ; sh:maxCount 1 ;
sh:severity sh:Info ;
sh:message "Optional on purpose: not every engine has a schema level." ] ;
sh:property [
sh:path pr:sourcedFrom ; sh:maxCount 1 ;
sh:severity sh:Info ] .
prs:FieldShape a sh:NodeShape ;
@@ -211,7 +225,12 @@ prs:BusinessObjectShape a sh:NodeShape ;
sh:property [
sh:path pr:hasBusinessDefinition ; sh:maxCount 0 ;
sh:severity sh:Violation ;
sh:message "A Business Object carries no definition of its own: meaning belongs to the Business Concept named by aboutConcept. Two definitions for one notion are two definitions free to diverge. MIGRATION: move the text to that Concept." ] .
sh:message "A Business Object carries no definition of its own: meaning belongs to the Business Concept named by aboutConcept. Two definitions for one notion are two definitions free to diverge. MIGRATION: move the text to that Concept." ] ;
sh:property [
sh:path pr:sourcedFrom ; sh:maxCount 0 ;
sh:severity sh:Violation ;
sh:message "A Business Object has no system of truth. It is a governance grouping, not data: its data lives in the Data Objects that represent it, and those name the system. In v0.6 this property held a grab-bag on the 15 objects -- physical locations, migration notes, arbitration remarks -- all under one name." ] .
#################################################################
@@ -234,9 +253,9 @@ prs:BusinessConceptShape a sh:NodeShape ;
sh:message "BR-003 -- the presence of a formula is what makes something a Metric. A Concept with a formula is a misclassified Metric." ] ;
sh:property [
sh:path pr:hasSource ; sh:maxCount 0 ;
sh:path pr:sourcedFrom ; sh:maxCount 0 ;
sh:severity sh:Violation ;
sh:message "A Business Concept has no source of truth. Its authority is its owning domain and its definition belongs in the glossary. Naming a system here would ask which of the systems that USE the concept defines it -- a question with no answer." ] .
sh:message "A Business Concept has no system of truth. Its authority is its owning domain and its definition belongs in the glossary. Naming a system here would ask which of the systems that USE the concept defines it -- a question with no answer." ] .
prs:MetricShape a sh:NodeShape ;
@@ -277,27 +296,28 @@ prs:MetricShape a sh:NodeShape ;
# will drift the day an element moves.
prs:MetricSourceShape a sh:NodeShape ;
sh:targetClass pr:Metric ;
rdfs:comment "Rewritten in v1.4 to compare System references rather than strings. A string comparison would have called 'SODH Gold' and 'Snowflake (SODH Gold)' two different systems." ;
sh:sparql [
sh:severity sh:Violation ;
sh:message "A Metric's source of truth must match the source of at least one element that computes it. If they disagree, either the metric points at the wrong system or an element has moved." ;
sh:message "A Metric's system of truth must match that of at least one element computing it. If they disagree, either the metric points at the wrong system or an element has moved." ;
sh:select """
PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/>
SELECT $this WHERE {
$this pr:hasSource ?src ; pr:computedBy ?de .
$this pr:sourcedFrom ?sys ; pr:computedBy ?de .
FILTER NOT EXISTS {
$this pr:computedBy ?any .
?any pr:hasSource ?src .
?any pr:sourcedFrom ?sys .
}
}""" ] ;
sh:sparql [
sh:severity sh:Warning ;
sh:message "Elements computing one Metric come from several systems of truth. Legitimate for a metric blending sources, worth a look otherwise." ;
sh:message "Elements computing one Metric come from several systems. Legitimate for a metric blending sources, worth a look otherwise." ;
sh:select """
PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/>
SELECT $this WHERE {
$this pr:computedBy ?a , ?b .
?a pr:hasSource ?sa .
?b pr:hasSource ?sb .
?a pr:sourcedFrom ?sa .
?b pr:sourcedFrom ?sb .
FILTER(?sa != ?sb)
}""" ] .
@@ -346,10 +366,19 @@ prs:DataObjectShape a sh:NodeShape ;
sh:message "Stewardship is declared on the Business Object and inherited through represents. Declaring it here allows two answers to the same question." ] ;
sh:property [
sh:path pr:hasSource ; sh:maxCount 1 ;
sh:not [ sh:pattern "/" ] ;
sh:path pr:sourcedFrom ; sh:class pr:System ; sh:maxCount 1 ;
sh:severity sh:Warning ;
sh:message "hasSource names the SYSTEM of origin, nothing else. A value like 'SODH Gold / F_SELL_OUT' packs a system and a physical relation into one string -- the physical relation belongs in physicalName." ] .
sh:message "Name the System holding the authoritative data, as a reference rather than a string. A string could not answer 'what depends on MDM Product'." ] ;
sh:property [
sh:path pr:storedIn ; sh:class pr:DataStructure ;
sh:severity sh:Warning ;
sh:message "Where the object actually sits: a table, a view, later a node label. The seam between what governance defines and what a harvester captures -- proposed by the reconciliation job, confirmed by a steward." ] ;
sh:property [
sh:path pr:materializedAs ; sh:maxCount 0 ;
sh:severity sh:Warning ;
sh:message "Deprecated in v1.4: storedIn now serves both granularities." ] .
# A declared grain element must actually belong to the object it grains.
@@ -411,7 +440,7 @@ prs:DataElementMeaningShape a sh:NodeShape ;
sh:minCount 1 ] ]
) ;
sh:severity sh:Violation ;
sh:message "A Data Element must reach business meaning by exactly one route: represents a Business Concept (dimensional), or be computedBy a Metric (measure). Never both, never neither. A curated column with no business meaning is debt, not an acceptable state." .
sh:message "A Data Element must reach business meaning by exactly one route: represents a Business Concept (dimensional), or be computedBy a Metric (measure). Never both, never neither. A defined column with no business meaning is debt, not an acceptable state." .
#################################################################
@@ -466,7 +495,7 @@ prs:BIFieldShape a sh:NodeShape ;
sh:targetClass pr:BIField ;
rdfs:comment "realizesMetric is a transitional control: asserted directly, with no rule written against the lineage chain. Making the assertion conditional on intermediate mappings existing would create confusion and discourage people from filling it in at all." ;
sh:property [
sh:path pr:realizesMetric ; sh:class pr:Metric ;
sh:path pr:exposesMetric ; sh:class pr:Metric ;
sh:maxCount 1 ;
sh:severity sh:Info ;
sh:message "Which certified Metric this BI field implements. Comparing expressions across fields realizing one Metric is the harmonisation measure." ] .
@@ -477,7 +506,7 @@ prs:BIFieldShape a sh:NodeShape ;
#################################################################
prs:NamingShape a sh:NodeShape ;
sh:targetClass pr:CuratedObject ;
sh:targetClass pr:DefinedObject ;
sh:property [
sh:path pr:hasName ;
sh:not [ sh:pattern "_NM$" ] ;