Files
data-meta-model/ontology/pr_metamodel.ttl
T

1051 lines
63 KiB
Turtle
Raw Normal View History

@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix pr: <https://ontology.pernod-ricard.com/metamodel/> .
#################################################################
# PERNOD RICARD DATA META MODEL - T-Box v1.5
# Owner: Bastien Gourdon - Data Governance Office
#
# WHAT CHANGED vs v1.4
# --------------------
# CuratedObject -> DefinedObject, ObservedObject -> CapturedObject, in the
# IRIs and not only in the labels. v1.4 renamed what screens showed and left
# the terms themselves untouched, so the model said one thing and the
# interface another -- exactly the split the short-label work was meant to
# end. Every reference follows: subclass declarations, the disjointness
# axiom, the shapes and their messages, and the prose in the comments.
#
# No instance changes. Nothing in the A-Box ever named these two classes
# directly: an object reaches them through its layer, by entailment. The old
# IRIs are kept deprecated per LC-002 so a domain file written against them
# still classifies.
#
# WHAT CHANGED vs v1.3 (kept for reference)
# -----------------------------------------
# A. PhysicalRelation -> DataStructure. "Relation" meant a table in Codd's
# sense, which reads as a property in an ontology and locks the class into
# the tabular world. A Neo4j node label, a Mongo collection and a Kafka
# topic are data structures; none of them is a relation. The class is also
# the declared EXTENSION POINT for non-tabular storage -- see its comment
# for the four lines that would insert Dataset and Graph beneath it.
#
# B. hasSource -> sourcedFrom, pointing at a System instead of holding a
# string. "Which objects depend on MDM Product?" was unanswerable because
# MDM Product was text. It also settles the naming collision the string
# created with BIDataSource: one names a system, the other a BI surface,
# and now they are different kinds of thing rather than two readings of
# one property.
#
# C. storedIn serves two granularities, like represents before it:
# DataObject storedIn DataStructure
# DataElement storedIn Field
# materializedAs is therefore redundant and deprecated. The seam between
# what governance defines and what a harvester captures now counts ONE
# relation instead of two.
#
# D. inSchema moves from DataStructure to nothing declared, and must stay
# OPTIONAL in the shapes. Neo4j has no schema and BigQuery calls the level
# a dataset; a minCount 1 would break on the first non-Snowflake harvest.
#
# WHAT CHANGED vs v1.2 (kept for reference)
# -----------------------------------------
# A. references -> qualifiedBy. "References" named the mechanism, a foreign
# key; qualifiedBy names the reason: the Sell Out dataset is qualified by
# Product, Customer, Currency and Date. It reads without explanation, and
# it is what gives a fact its grain and its analysis context.
#
# B. primaryLocation, a SUB-PROPERTY of storedIn. One Data Element can sit in
# several Fields, one authoritative and the others copies. Two parallel
# properties would be free to disagree -- a field marked primary in one and
# missing from the other. As a sub-property, entailment guarantees the
# primary location is always among the locations. Primary/replica rather
# than master/slave, which the industry has moved away from and which
# reads badly to a business audience.
#
# C. hasShortLabel. rdfs:label carries the exhaustive name (Data Domain, Sell
# Out Data Hub - Gold), hasShortLabel the one screens use (Domain, SODH
# Gold). Same object was being called two things depending on where you
# looked; now both names are declared and the viewers stop hard-coding a
# display dictionary of their own.
#
# D. hasSource is now forbidden on a Business Object as well as on a Business
# Concept. Same argument: a Business Object is a governance grouping, not
# data. Its data lives in the Data Objects that represent it, and those
# carry the system. In v0.6 the 15 objects carried a grab-bag -- physical
# locations, migration notes, arbitration remarks -- under one property.
#
# WHAT CHANGED vs v1.1 (kept for reference)
# -----------------------------------------
# A. physicalizedIn -> storedIn. "Physicalized in" is jargon; the relation
# says where the data sits, and "stored in" says that to anyone.
#
# B. hasSource MEANS ONE THING: the system that holds the authoritative data.
# It was drifting into a third meaning on Business Concepts and Metrics,
# where it named the DOCUMENT a record came from ("SODH back-doc v0.7").
# A business concept has no data source -- it has an owning domain, which
# is its authority, and a definition published in the glossary. The
# property is now forbidden on concepts by SHACL.
#
# C. FIVE TERMS REMOVED OR DEPRECATED, none of them instantiated anywhere:
# Table deleted -- deprecated since v1.0, replaced by
# BaseTable, and never used. A deprecation that
# never ends stops meaning anything.
# constrainsRelation derivable: constrainsField -> livesIn gives the
# relation. An asserted edge redundant with an
# existing path is a future divergence.
# MaterializedView a class standing in for an attribute; being
# derived is what dependsOn already says.
# approximateRowCount volumetry with no identified use. queryCount30d
# and lastQueriedOn carry the rationalisation case.
# hasVendor marginal.
#
# D. hasProductOwner drops from BLOCKING to a warning in the shapes. The SODH
# product owner is genuinely not appointed yet, and a blocking rule that
# cannot be satisfied truthfully forces invention -- the BR-004 lesson.
#
# Environment and deployedIn stay, unused. Without them a DEV table and its
# PROD twin are indistinguishable and lineage pollutes silently on the first
# multi-environment harvest. Unused because nothing is harvested yet, not
# because they are useless.
#
# WHAT CHANGED vs v1.0 (kept for reference)
# -----------------------------------------
# A. RELATIONS SIMPLIFIED AND GIVEN HONEST NAMES
# belongsToDomain + belongsToSubDomain -> belongsTo (one property,
# scoped per class by SHACL; reusable if a domain family appears).
# implements -> represents
# composedOf -> aboutConcept (exactly 1, the
# subject) + usesConcept (0..n, notions needed to read the object).
# composedOf carried three different meanings depending on the object
# (dependency, subject, part-whole); that vagueness is what let BR-004
# be satisfied with padding in v0.6.
#
# B. represents WORKS AT TWO GRANULARITIES
# DataObject represents BusinessObject
# DataElement represents BusinessConcept
# Same relation -- "this logical artefact is the data-side counterpart of
# that business notion" -- at two levels of detail.
# A Data Element reaches business meaning by EXACTLY ONE of two routes:
# dimensional -> represents a BusinessConcept
# measure -> is computedBy a Metric, which measures the Concept
# Never both, never neither. Enforced by sh:xone.
#
# C. STEWARDSHIP DECLARED ONCE, INHERITED EVERYWHERE
# monitoredBy sits on BusinessObject only (plus DataProduct, which
# spans several Business Objects and cannot inherit). Data Objects and
# Data Elements inherit through represents / hasElement. Declaring a
# steward at several levels means two answers to "who watches this
# column". BusinessObject therefore STAYS in the Ownership layer: it is
# the single anchor of accountability, which is what that layer is for.
# owningDomain is the exception: inherited by default, but re-declarable
# on a Data Element, because OW-002 attributes an element to the domain
# that defines its rule. A DE that re-declares is a borrowed element --
# a signal to check against its represents, not an anomaly.
#
# D. BUSINESS MEANING LIVES IN ONE PLACE
# hasBusinessDefinition is removed from BusinessObject. Meaning belongs to
# the BusinessConcept; a Business Object gets its meaning through
# aboutConcept. Two definitions for one notion are two definitions free to
# diverge.
#
# E. GRAIN MOVED OFF THE METRIC
# hasGrainDimension (Metric -> DataObject) and hasGranularity are gone.
# Grain is a property of a DATASET, not of a calculation rule: a Metric
# has no rows. hasGrainElement (DataObject -> DataElement) replaces them
# and is CHECKABLE against the harvested PrimaryKey. A Metric's grain
# becomes derivable from where it lands.
#
# F. DELIVERY IS ITS OWN LAYER
# DataProduct / DataInterface / DataContract answer "what do we promise,
# to whom, until when" -- a different question from "how is it
# structured". DataAsset is renamed DataInterface and redefined as the
# CHANNEL through which a consumer actually gets data (a Snowflake share
# or grant, a file extract, an API), which is verifiable, rather than
# "directly consumable by a business user", which was a judgement call and
# produced zero instances in a year.
# Every Data Product is operated by exactly one Sub-Domain and has exactly
# one Product Owner: it is a consumable, and there must be no ambiguity
# about who runs it.
#
# G. exposesMetric KEPT AS A TRANSITIONAL CONTROL
# Declared, required on certified BI fields, and that is all. No rule is
# 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.
#
# Deprecated terms are kept, never deleted (LC-002): existing assertions keep
# classifying, and the migration can be staged.
#
# WHAT CHANGED vs v0.9.2 (kept for reference)
# -------------------------------------------
# 1. TERM NAMESPACE IS NOW VERSION-INDEPENDENT.
# Terms live at .../metamodel/ (no version segment).
# The VERSION lives in owl:versionIRI only.
# Rationale: in v0.9 the version was baked into every term IRI, so every
# minor release invalidated the whole A-Box. W3C-recommended pattern is to
# freeze the term namespace and version the ontology document.
# MIGRATION: sed -i 's|/metamodel/0.9/|/metamodel/|g' instances/*.ttl
#
# 2. NEW ORTHOGONAL AXIS: DefinedObject vs CapturedObject.
# LAYER says WHAT an object is. PROVENANCE says HOW IT GOT HERE.
# - DefinedObject : written by governance, edited via pull request,
# subject to the full ownership shape.
# - CapturedObject: read from a running system, rebuilt every night,
# NEVER hand-edited, NOT required to carry owningDomain.
# Without this split, the SHACL ownership shape would demand an owning Data
# Domain and a named owner on every one of the ~50 000 Snowflake columns.
#
# 3. PHYSICAL LAYER ACTUALLY MODELLED (was declared, never instantiable).
# System / Database / Schema / PhysicalRelation / Field / KeyConstraint /
# Transformation, plus column-level lineage (derivedFrom).
#
# 4. NEW CONSUMPTION LAYER (Tableau + Looker).
# BIWorkspace / BIDataSource / BIField / BIReport, plus exposesMetric,
# which closes the loop between a certified Metric and its actual
# implementations in the BI estate.
#
# 5. BRIDGE TO CONCEPTUAL ONTOLOGIES: denotes / denotesProperty /
# denotesActivity. Declared now, left empty. Annotation properties, not
# object properties, to stay in OWL 2 DL without punning.
#
# 6. FIXES: composedOf/describes false inverse; hasField/livesIn inverse
# declared; string-typed source system and grain replaced by real
# references; deprecations recorded rather than deleted (LC-002).
#################################################################
<https://ontology.pernod-ricard.com/metamodel/>
a owl:Ontology ;
owl:versionIRI <https://ontology.pernod-ricard.com/metamodel/1.5> ;
owl:priorVersion <https://ontology.pernod-ricard.com/metamodel/1.4> ;
dcterms:title "Pernod Ricard Data Meta Model" ;
dcterms:creator "Bastien Gourdon" ;
dcterms:modified "2026-07-30"^^xsd:date ;
owl:versionInfo "1.5.0-draft-2026-07-30" ;
rdfs:comment """OWL T-Box. Six layers (Ownership, Business, Logical, Delivery, Physical, Consumption) crossed with a provenance axis (Defined vs Captured). Defined objects are written by governance and reviewed; Captured objects are read from Snowflake, Tableau and Looker and are rebuilt, never edited. Bridge annotation properties are declared for future alignment with domain ontologies.""" .
#################################################################
# ANNOTATION PROPERTIES
#################################################################
# ---- Bridge to conceptual (domain) ontologies -----------------
# A meta model instance is a DOCUMENTATION RECORD about a concept.
# A domain ontology holds the CONCEPT ITSELF, axiomatised.
# "Customer Invoice" is an INSTANCE of pr:BusinessObject here,
# and a CLASS (fin:Invoice) there. These three properties join the two.
# Annotation properties (not object properties) so that pointing an
# individual at a class stays inside OWL 2 DL without punning.
pr:denotes a owl:AnnotationProperty ;
rdfs:label "denotes" ;
rdfs:comment "Links a documentation record (Business Object, Business Concept, Data Object) to the CLASS it describes in a domain ontology. Example: ex:BO_16_03_001 pr:denotes fin:Invoice ." .
pr:denotesProperty a owl:AnnotationProperty ;
rdfs:label "denotes property" ;
rdfs:comment "Links a Data Element or Business Concept to the PROPERTY it describes in a domain ontology. Example: ex:DE_16_03_0001 pr:denotesProperty fin:netAmount ." .
pr:denotesActivity a owl:AnnotationProperty ;
rdfs:label "denotes activity" ;
rdfs:comment "Links a Metric or Business Object to the PROCESS or EVENT it describes, typically a prov:Activity subclass. Example: ex:M_16_03_001 pr:denotesActivity fin:InvoiceIssuance ." .
# ---- Operational documentation --------------------------------
pr:harvestSource a owl:AnnotationProperty ;
rdfs:label "harvest source" ;
rdfs:comment "For Captured terms: the system view or API that populates this term. Documents the contract between the meta model and the harvester." .
pr:authoringMode a owl:AnnotationProperty ;
rdfs:label "authoring mode" ;
rdfs:comment "Who writes this, in practice: ASSERTED (a person does), HARVESTED (a job does) or PROPOSED_CONFIRMED (a job proposes and a person accepts). Renamed from curationMode along with the classes: 'curation' belonged to the same jargon family as 'curated'." .
#################################################################
# ROOTS - layer axis x provenance axis
#################################################################
pr:MetaModelObject a owl:Class ;
rdfs:label "Meta Model Object" ;
rdfs:comment "Anything the meta model describes. Actors are deliberately NOT Meta Model Objects." ;
pr:hasShortLabel "Meta model object" .
pr:DefinedObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ;
rdfs:label "Defined Object" ;
pr:hasShortLabel "Defined" ;
pr:authoringMode "ASSERTED" ;
rdfs:comment "Somebody decided it. Written by governance, edited through a pull request on the domain TTL file, and subject to the full identity and lifecycle shape: identifier, name, owner, owning Data Domain, status, version." .
pr:CuratedObject a owl:Class ;
rdfs:label "Curated Object (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:DefinedObject ;
rdfs:comment "v1.4 term. Renamed because 'curated' is governance jargon; 'defined' says the same thing to a business reader. No instance ever named this class directly." .
pr:CapturedObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ;
rdfs:label "Captured Object" ;
pr:hasShortLabel "Captured" ;
pr:authoringMode "HARVESTED" ;
rdfs:comment "Read from a running system and rebuilt on every run. Never hand-edited. Carries sourceIdentifier, fullyQualifiedName and harvestedOn instead of declared ownership: attribution is INFERRED through the Data Element it is mapped to, never asserted on the node." .
pr:ObservedObject a owl:Class ;
rdfs:label "Observed Object (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:CapturedObject ;
rdfs:comment "v1.4 term. Renamed because 'observed' is passive and technical; 'captured' says where it came from. No instance ever named this class directly." .
pr:OwnershipLayerObject a owl:Class ; rdfs:subClassOf pr:DefinedObject ;
rdfs:label "Ownership & Categorization Layer Object" .
pr:BusinessLayerObject a owl:Class ; rdfs:subClassOf pr:DefinedObject ;
rdfs:label "Business Layer Object" .
pr:LogicalLayerObject a owl:Class ; rdfs:subClassOf pr:DefinedObject ;
rdfs:label "Logical Layer Object" ;
rdfs:comment "How the data is structured." .
pr:DeliveryLayerObject a owl:Class ; rdfs:subClassOf pr:DefinedObject ;
rdfs:label "Delivery Layer Object" ;
rdfs:comment "What we promise, to whom, until when. A different question from how the data is structured, hence a layer of its own: version, owner, changelog, support channel, consumers to warn." .
pr:PhysicalLayerObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ;
rdfs:label "Physical Layer Object" ;
rdfs:comment """The one layer whose members do NOT share a provenance, which is why this class -- unlike the other five layer roots -- says nothing about it.
A System is DEFINED: somebody decides that MDM Product is a system worth tracking, and names it. Nobody harvests the list of systems that matter. Everything beneath a system is CAPTURED: databases, schemas, data structures, fields, keys and transformations are read from what is running.
Until v1.4 this class was itself declared a kind of Captured Object, which made every physical class captured by construction and put the five seeded Systems in breach of the harvest rules. The two axes are meant to be orthogonal; that shortcut collapsed them.""" .
pr:ConsumptionLayerObject a owl:Class ; rdfs:subClassOf pr:CapturedObject ;
rdfs:label "Consumption Layer Object" ;
rdfs:comment "The BI estate. Separate from Physical because BI assets have their own lifecycle and their own owner: a dashboard belongs to its author, not to the Data Domain that owns the underlying data." ;
pr:hasShortLabel "Consumption" .
#################################################################
# LAYER 1 - OWNERSHIP & CATEGORIZATION (defined)
#################################################################
pr:DataDomain a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject ;
rdfs:label "Data Domain" ;
pr:hasShortLabel "Domain" .
pr:SubDomain a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject ;
rdfs:label "Sub-Domain" ;
pr:hasShortLabel "Sub-domain" .
pr:BusinessObject a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject ;
rdfs:label "Business Object" ;
rdfs:comment "The single anchor of accountability, which is why it stays in the Ownership layer: it is the one object where a steward is declared, and Data Objects and Data Elements inherit from it. It carries NO business definition of its own -- its meaning comes from its aboutConcept. What it does carry is a perimeter: the subject it is about, and the notions needed to read it." ;
pr:hasShortLabel "Business object" .
#################################################################
# LAYER 2 - BUSINESS (defined)
#################################################################
pr:BusinessConcept a owl:Class ; rdfs:subClassOf pr:BusinessLayerObject ;
rdfs:label "Business Concept" ;
rdfs:comment "A shared business notion WITHOUT a formula. The presence of a formula is the sole discriminator against Metric (rule BR-003)." ;
pr:hasShortLabel "Business concept" .
pr:Metric a owl:Class ; rdfs:subClassOf pr:BusinessLayerObject ;
rdfs:label "Metric" ;
rdfs:comment "A quantifiable measurement WITH a formula, a unit and a grain. Under BR-013 a Metric carries the harmonised CALCULATION RULE; variants that differ only by analysis context are Data Elements linked back via computedBy." ;
pr:hasShortLabel "Metric" .
pr:KPI a owl:Class ; rdfs:subClassOf pr:Metric ;
rdfs:label "Key Performance Indicator" ;
rdfs:comment "An elevated Metric. Use this class rather than the deprecated isKPI flag." ;
pr:hasShortLabel "KPI" .
#################################################################
# LAYER 3 - LOGICAL (defined)
#################################################################
pr:DataObject a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject ;
rdfs:label "Data Object" ;
rdfs:comment "The pivot: represents exactly one Business Object, is composed of Data Elements, and is materialized as one or more Physical Relations. The single junction between governance and technology." ;
pr:hasShortLabel "Data object" .
pr:DataElement a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject ;
rdfs:label "Data Element" ;
pr:authoringMode "PROPOSED_CONFIRMED" ;
rdfs:comment "The finest defined unit. Its mapping to a physical Field is the ONLY place where the defined graph and the captured graph meet: proposed automatically by name and type matching, confirmed by a Data Steward." ;
pr:hasShortLabel "Data element" .
pr:DataAsset a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject ;
rdfs:label "Data Asset (deprecated)" ;
owl:deprecated true ;
dcterms:isReplacedBy pr:DataInterface ;
rdfs:comment "v1.0 term, defined as 'directly consumable by a business user' -- a judgement, not a structural test, which is why it collected zero instances in a year. Use DataInterface." .
#################################################################
# LAYER 4 - DELIVERY (defined)
#################################################################
pr:DataProduct a owl:Class ; rdfs:subClassOf pr:DeliveryLayerObject ;
rdfs:label "Data Product" ;
rdfs:comment "The unit of delivery: what carries a version number, an owner, a changelog, a support channel and a list of consumers to warn. It has no single technical counterpart, and should not: physically it often lands on a Snowflake schema or a dbt project, but that is not what defines it. Operated by exactly one Sub-Domain -- it is a consumable, and there must be no ambiguity about who runs it. Candidate alignment: dcat:Dataset / DPROD." ;
pr:hasShortLabel "Data product" .
pr:DataInterface a owl:Class ; rdfs:subClassOf pr:DeliveryLayerObject ;
rdfs:label "Data Interface" ;
rdfs:comment "The channel through which a consumer actually gets the data: a Snowflake secure share or listing, a consumption schema with its grants, a file extract to a panel partner, an API or reverse-ETL feed. Not a Data Object (that is structure) and not a BI Data Source (that is one consumer among others). Verifiable: a grant, a share or an endpoint either exists or it does not. The output port in data-mesh terms." ;
pr:hasShortLabel "Data interface" .
pr:DataContract a owl:Class ; rdfs:subClassOf pr:DeliveryLayerObject ;
rdfs:label "Data Contract" ;
rdfs:comment "The service commitment attached to a Data Product: schema, freshness, quality thresholds, support, breaking-change notice period. Candidate alignment: ODCS (Bitol)." ;
pr:hasShortLabel "Data contract" .
#################################################################
# LAYER 5 - PHYSICAL (captured)
#################################################################
pr:System a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:DefinedObject ;
rdfs:label "System" ;
pr:authoringMode "ASSERTED" ;
rdfs:comment "A real system: Snowflake, MDM Product, MDM Customer, a panel provider feed, Tableau Server, Looker. Making this a node rather than a string is what makes 'what depends on MDM Product?' answerable." ;
pr:hasShortLabel "System" .
pr:Database a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ;
rdfs:label "Database" ;
pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.DATABASES" ;
pr:hasShortLabel "Database" .
pr:Schema a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ;
rdfs:label "Schema" ;
pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.SCHEMATA" ;
pr:hasShortLabel "Schema" .
pr:DataStructure a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ;
rdfs:label "Data Structure" ;
pr:hasShortLabel "Table or view" ;
pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.TABLES" ;
rdfs:comment """Anything that carries Fields and reads as a set of rows. Never instantiated directly: a harvest types the concrete kind, and a viewer shows that kind rather than this parent.
WHY THE PARENT EXISTS. Three properties need exactly this set as their scope: storedIn from a Data Object, hasField and its inverse livesIn, and dependsOn. Without it each would have to be tripled, once per concrete kind, and the checks would go with them.
WHY THE NAME CHANGED. It was PhysicalRelation, from Codd, where a relation IS a table. In an ontology "relation" reads as a property, and the word locks the class into the tabular world. A Neo4j node label, a Mongo collection and a Kafka topic all carry fields; none is a relation.
EXTENSION POINT for non-tabular storage. Nothing at Pernod Ricard is non-tabular today, so no such class is declared -- four terms were deprecated in v1.2 precisely because nobody instantiated them. The day a graph enters scope, the insertion is additive and touches no instance, because subClassOf is transitive:
pr:Dataset rdfs:subClassOf pr:DataStructure .
pr:Graph rdfs:subClassOf pr:DataStructure .
pr:BaseTable rdfs:subClassOf pr:Dataset . # instead of DataStructure
pr:inSchema rdfs:domain pr:Dataset . # instead of nothing
Existing BaseTable assertions stay DataStructure by entailment, every shape targeting DataStructure keeps working, and storedIn does not move. The extension point is paid once; the extension is paid when it arrives.""" .
pr:PhysicalRelation a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ;
rdfs:label "Physical Relation (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:DataStructure .
pr:BaseTable a owl:Class ; rdfs:subClassOf pr:DataStructure ;
rdfs:label "Base Table" ;
pr:hasShortLabel "Base table" .
pr:View a owl:Class ; rdfs:subClassOf pr:DataStructure ;
rdfs:label "View" ;
rdfs:comment "Depends on other relations through dependsOn, so impact is transitive." ;
pr:hasShortLabel "View" .
pr:MaterializedView a owl:Class ; rdfs:subClassOf pr:DataStructure ;
rdfs:label "Materialized View (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:View ;
rdfs:comment "A class standing in for an attribute: what distinguishes it is being derived, which dependsOn already states." .
pr:ExternalTable a owl:Class ; rdfs:subClassOf pr:DataStructure ;
rdfs:label "External Table" ;
rdfs:comment "Backed by files outside the warehouse. Marks the boundary where lineage leaves Snowflake." ;
pr:hasShortLabel "External table" .
pr:Field a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ;
rdfs:label "Field" ;
pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.COLUMNS" ;
rdfs:comment "A physical column. The anchor of impact analysis: everything upstream reaches a Field, everything downstream starts from one." ;
pr:hasShortLabel "Field" .
pr:KeyConstraint a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ;
rdfs:label "Key Constraint" ;
pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.TABLE_CONSTRAINTS + KEY_COLUMN_USAGE" ;
rdfs:comment "Gives foreign keys a home. Rule OW-006 says an FK is not a Data Element - true, but it still needs somewhere to live, and it lets the asserted DataObject-references-DataObject edge be checked against reality." .
pr:PrimaryKey a owl:Class ; rdfs:subClassOf pr:KeyConstraint ;
rdfs:label "Primary Key" .
pr:ForeignKeyConstraint a owl:Class ; rdfs:subClassOf pr:KeyConstraint ;
rdfs:label "Foreign Key Constraint" .
pr:Transformation a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject , prov:Activity ;
rdfs:label "Transformation" ;
pr:harvestSource "dbt manifest.json, Snowflake TASKS, Azure Data Factory pipelines" ;
rdfs:comment "The code that turns inputs into outputs: a dbt model, a Snowflake task, an ADF pipeline. Subclass of prov:Activity so that lineage is expressed in a standard vocabulary rather than a private one." ;
pr:hasShortLabel "Transformation" .
#################################################################
# LAYER 6 - CONSUMPTION (captured)
#################################################################
pr:BIWorkspace a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject ;
rdfs:label "BI Workspace" ;
pr:harvestSource "Tableau Metadata API (Project/Site) ; Looker API (Model)" ;
pr:hasShortLabel "BI workspace" .
pr:BIDataSource a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject ;
rdfs:label "BI Data Source" ;
pr:harvestSource "Tableau Published Datasource ; Looker Explore" ;
rdfs:comment "The semantic surface a report is built on. This is where BI meets the warehouse." ;
pr:hasShortLabel "BI data source" .
pr:BIField a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject ;
rdfs:label "BI Field" ;
pr:harvestSource "Tableau calculated fields ; LookML measures and dimensions" ;
rdfs:comment "A field as a business user sees it. exposesMetric is what turns a certified Metric from a definition on paper into something measurable against the real BI estate." ;
pr:hasShortLabel "BI field" .
pr:BIReport a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject ;
rdfs:label "BI Report" ;
pr:harvestSource "Tableau Workbook/Sheet ; Looker Look/Dashboard" ;
rdfs:comment "What a Data Governance Lead actually cares about in an impact analysis: not that a column changed, but which reports break and who to warn." ;
pr:hasShortLabel "BI report" .
#################################################################
# ACTORS (outside MetaModelObject on purpose)
#################################################################
pr:Actor a owl:Class ; rdfs:label "Actor" ;
rdfs:comment "A person or a team. Deliberately NOT a MetaModelObject: people are not subject to ownership and lifecycle rules." ;
pr:hasShortLabel "Actor" .
pr:DataDomainOwner a owl:Class ; rdfs:subClassOf pr:Actor ;
rdfs:label "Data Domain Owner" ;
rdfs:comment "Accountable for a Data Domain. Exactly one per domain." ;
pr:hasShortLabel "Domain owner" .
pr:SubDomainOwner a owl:Class ; rdfs:subClassOf pr:Actor ;
rdfs:label "Sub-Domain Owner" ;
rdfs:comment "Accountable for a Sub-Domain. Exactly one per sub-domain." ;
pr:hasShortLabel "Sub-domain owner" .
pr:DataGovernanceLead a owl:Class ; rdfs:subClassOf pr:Actor ;
rdfs:label "Data Governance Lead" ;
pr:hasAcronym "DGL" ;
rdfs:comment "Runs governance for a Data Domain. The acronym belongs in hasAcronym, not in the label (rule NR-005)." ;
pr:hasShortLabel "Data governance lead" .
pr:DataProductOwner a owl:Class ; rdfs:subClassOf pr:Actor ;
rdfs:label "Data Product Owner" ;
rdfs:comment "Runs a Data Product day to day: releases, changelog, support, consumer communication. Belongs to the Sub-Domain that operates the product." ;
pr:hasShortLabel "Product owner" .
pr:DataSteward a owl:Class ; rdfs:subClassOf pr:Actor ;
rdfs:label "Data Steward" ;
rdfs:comment "Monitors a Business Object. Declared there and nowhere else: Data Objects and Data Elements inherit their steward, so that no column can ever have two answers to 'who watches this'." ;
pr:hasShortLabel "Steward" .
#################################################################
# CONTROLLED VOCABULARIES
#################################################################
pr:ActivationStatus a owl:Class ; rdfs:label "Activation Status" .
pr:NotActivated a pr:ActivationStatus ; rdfs:label "Not Activated" .
pr:LightActivation a pr:ActivationStatus ; rdfs:label "Light Activation" .
pr:FullActivation a pr:ActivationStatus ; rdfs:label "Full Activation" .
pr:Environment a owl:Class ; rdfs:label "Environment" ;
rdfs:comment "Same logical object, several physical incarnations. Kept as a vocabulary rather than folded into the name so that DEV drift never pollutes PROD lineage." .
pr:Development a pr:Environment ; rdfs:label "Development" .
pr:UserAcceptance a pr:Environment ; rdfs:label "User Acceptance" .
pr:Production a pr:Environment ; rdfs:label "Production" .
pr:SystemType a owl:Class ; rdfs:label "System Type" .
pr:SourceApplication a pr:SystemType ; rdfs:label "Source Application" .
pr:MasterDataSystem a pr:SystemType ; rdfs:label "Master Data System" .
pr:DataPlatform a pr:SystemType ; rdfs:label "Data Platform" .
pr:BITool a pr:SystemType ; rdfs:label "BI Tool" .
pr:ExternalProvider a pr:SystemType ; rdfs:label "External Provider" .
#################################################################
# OBJECT PROPERTIES - Ownership & Business
#################################################################
pr:belongsTo a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "belongs to" ;
rdfs:comment "Position in the categorization tree. One property for the whole tree: SubDomain belongs to DataDomain, BusinessObject belongs to SubDomain. Scope is enforced per class by SHACL rather than by two near-identical properties, and the same relation will serve if a domain family ever appears. Deliberately NOT transitive: entailment would produce two values and break the maxCount 1 shape. To walk the ancestry, use the property path belongsTo+ in the query." .
pr:belongsToDomain a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "belongs to domain (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:belongsTo .
pr:belongsToSubDomain a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "belongs to sub-domain (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:belongsTo .
pr:aboutConcept a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "is about" ;
rdfs:domain pr:BusinessObject ; rdfs:range pr:BusinessConcept ;
rdfs:comment "The single subject of this Business Object, and where its meaning comes from -- a Business Object carries no definition of its own. Exactly one. If the subject cannot be named, the object is not scoped, and that is the most useful thing the rule can tell you. Replaces the 3..10 minimum of BR-004, which was satisfied in v0.6 by attaching unrelated concepts." .
pr:usesConcept a owl:ObjectProperty ;
rdfs:label "uses concept" ;
rdfs:domain pr:BusinessObject ; rdfs:range pr:BusinessConcept ;
rdfs:comment "The other notions required to read this object correctly. Zero or more, and zero is a legitimate answer." .
pr:composedOf a owl:ObjectProperty ;
rdfs:label "composed of (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:usesConcept ;
rdfs:comment "Carried three different meanings depending on the object: dependency (Sell Out Volume), subject (Retailer Stock), part-whole (Calendar). One word for three relations is what allowed the v0.6 padding. Split into aboutConcept and usesConcept." .
pr:isComponentOf a owl:ObjectProperty ;
rdfs:label "is component of (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:usesConcept .
pr:describes a owl:ObjectProperty ;
rdfs:label "describes (deprecated)" ;
owl:deprecated true ;
dcterms:isReplacedBy pr:isComponentOf ;
rdfs:comment "Removed as a genuine relation. Its only use was as a false inverse of composedOf." .
pr:hasMetric a owl:ObjectProperty ;
rdfs:label "has metric" ;
rdfs:domain pr:BusinessObject ; rdfs:range pr:Metric .
pr:measures a owl:ObjectProperty ;
rdfs:label "measures" ;
rdfs:domain pr:Metric ; rdfs:range pr:BusinessConcept .
pr:hasGrainElement a owl:ObjectProperty ;
rdfs:label "has grain element" ;
rdfs:domain pr:DataObject ; rdfs:range pr:DataElement ;
rdfs:comment "The elements whose combination makes a row unique. Grain belongs to a DATASET, not to a calculation rule: a Metric has no rows, so it cannot have a grain of its own -- its grain is derivable from the Data Objects it lands in. Stated at element level rather than object level because it is then CHECKABLE against the harvested PrimaryKey: a declared grain that does not match the real key is an automatic finding." .
pr:hasGrainDimension a owl:ObjectProperty ;
rdfs:label "has grain dimension (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:hasGrainElement ;
rdfs:comment "Put grain on the Metric, which confounded a declarative statement with a structural fact. See hasGrainElement." .
pr:computedBy a owl:ObjectProperty ;
rdfs:subPropertyOf prov:wasDerivedFrom ;
rdfs:label "computed by" ;
rdfs:domain pr:Metric ; rdfs:range pr:DataElement ;
rdfs:comment "BR-013. Links a mother Metric to the granular Data Elements that implement it in a given analysis context (period, promo status, scope, unit, currency)." .
#################################################################
# OBJECT PROPERTIES - Logical
#################################################################
pr:represents a owl:ObjectProperty ;
rdfs:label "represents" ;
rdfs:comment """The business notion a logical artefact is the data-side counterpart of. One relation at two granularities:
DataObject represents BusinessObject (exactly one -- the pivot, BR-006)
DataElement represents BusinessConcept (at most one)
Renamed from 'implements', which carried software-engineering baggage: a Data Object does not implement a Business Object the way a class implements an interface.
Range is deliberately left open and scoped by SHACL per class, because the two uses have different targets.
A Data Element uses this ONLY when it is dimensional. A measure element reaches meaning through Metric -> computedBy, which says more: it names WHICH metric the meaning travels through. Never both routes on one element.
Functional at DataObject level only -- not declared owl:FunctionalProperty here, since a single global cardinality would be wrong for one of the two uses.""" .
pr:implements a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "implements (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:represents .
pr:hasElement a owl:ObjectProperty ;
rdfs:label "has element" ;
rdfs:domain pr:DataObject ; rdfs:range pr:DataElement .
pr:qualifiedBy a owl:ObjectProperty ;
rdfs:label "qualified by" ;
rdfs:domain pr:DataObject ; rdfs:range pr:DataObject ;
rdfs:comment "The Data Objects that give this one its analysis context: a fact dataset is qualified by Product, Customer, Currency and Date. Physically a foreign key, which is why it is checkable against the harvested ForeignKeyConstraint objects -- but the name says the reason rather than the mechanism. NOT ownership. It is also what carries the grain of a fact object, whose own elements are measures and whose keys are not Data Elements (OW-006)." .
pr:references a owl:ObjectProperty ;
rdfs:label "references (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:qualifiedBy .
#################################################################
# OBJECT PROPERTIES - Delivery
#################################################################
pr:packages a owl:ObjectProperty ;
rdfs:label "packages" ;
rdfs:domain pr:DataProduct ; rdfs:range pr:DataObject ;
rdfs:comment "The scope of the product. Through these Data Objects and their represents, the Business Objects a product makes available are derivable -- which is why a product needs no direct link to a Business Object." .
pr:exposes a owl:ObjectProperty ;
rdfs:label "exposes" ;
rdfs:domain pr:DataProduct ; rdfs:range pr:DataInterface ;
rdfs:comment "The channels through which the product is actually consumed." .
pr:servedBy a owl:ObjectProperty ;
rdfs:label "served by" ;
rdfs:domain pr:DataInterface ; rdfs:range pr:DataStructure ;
rdfs:comment "What physically backs the interface: the shared relations, the extract source." .
pr:governedBy a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "governed by" ;
rdfs:domain pr:DataProduct ; rdfs:range pr:DataContract .
pr:operatedBy a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "operated by" ;
rdfs:domain pr:DataProduct ; rdfs:range pr:SubDomain ;
rdfs:comment "The single Sub-Domain that runs this product. A Data Product spans several Business Objects and therefore cannot inherit accountability the way a Data Object does -- so it declares its own, and exactly one. It is a consumable: there must be no ambiguity about who runs it." .
pr:hasProductOwner a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "has product owner" ;
rdfs:domain pr:DataProduct ; rdfs:range pr:DataProductOwner ;
rdfs:comment "Exactly one. Belongs to the operating Sub-Domain -- checkable, not just conventional." .
pr:usesDataObject a owl:ObjectProperty ;
rdfs:label "uses Data Object (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:packages .
#################################################################
# OBJECT PROPERTIES - the defined / captured seam
# These two are the ONLY edges crossing from asserted to harvested.
# Everything else is either pure governance or pure machine.
#################################################################
pr:materializedAs a owl:ObjectProperty ;
rdfs:label "materialized as (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:storedIn ;
rdfs:comment "Redundant once storedIn serves two granularities. One verb fewer, and the seam counts one relation instead of two." .
pr:storedIn a owl:ObjectProperty ;
rdfs:label "stored in" ;
pr:authoringMode "PROPOSED_CONFIRMED" ;
rdfs:comment """Where a logical artefact actually sits. One relation at two granularities, like represents:
DataObject storedIn DataStructure (a table, a view, later a node label)
DataElement storedIn Field (a column, later a node property)
Range left open and scoped by SHACL per class, because the two uses have different targets.
THE SEAM. This is the one relation crossing from what governance defines to what a harvester captures, and the one thing a steward confirms: the reconciliation job proposes the mapping on fully qualified name and type, a person accepts it. It is also what makes end-to-end impact analysis possible.
Not functional: the same object exists in DEV, UAT and PROD, and a Data Element may sit in several Fields -- see primaryLocation for the authoritative one. Renamed from physicalizedIn, which was jargon.""" .
pr:primaryLocation a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:subPropertyOf pr:storedIn ;
rdfs:label "primary location" ;
rdfs:domain pr:DataElement ; rdfs:range pr:Field ;
pr:authoringMode "PROPOSED_CONFIRMED" ;
rdfs:comment """The authoritative Field among the several a Data Element may sit in -- the others being copies, replicas or downstream materialisations.
A SUB-PROPERTY of storedIn on purpose, not a parallel relation. Entailment then guarantees the primary location is always among the locations, so the two can never disagree; two independent properties would allow a Field marked primary here and absent from storedIn there.
Primary and replica rather than master and slave: the industry has moved on, and it reads better to the business.""" .
pr:physicalizedIn a owl:ObjectProperty ;
rdfs:label "physicalized in (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:storedIn .
#################################################################
# OBJECT PROPERTIES - Physical
#################################################################
pr:sourcedFrom a owl:ObjectProperty ;
rdfs:label "sourced from" ;
rdfs:range pr:System ;
rdfs:comment """The System holding the authoritative data for this object. Replaces the free-text hasSource, and answers a question that text could not: which objects depend on MDM Product.
Domain deliberately omitted -- it applies to Data Elements, Data Structures, Metrics and BI Data Sources alike, and the scope lives in the shapes.
It also settles a naming collision. As a string, "source" meant a system here and a BI surface in BIDataSource, two readings of one word. As a relation towards System, the two are different kinds of thing and the ambiguity disappears.""" .
pr:hasSystemType a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "has system type" ;
rdfs:domain pr:System ; rdfs:range pr:SystemType .
pr:hostedOn a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "hosted on" ;
rdfs:domain pr:Database ; rdfs:range pr:System .
pr:inDatabase a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "in database" ;
rdfs:domain pr:Schema ; rdfs:range pr:Database .
pr:inSchema a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "in schema" ;
rdfs:range pr:Schema ;
rdfs:comment "Domain deliberately not declared, and the shapes must keep this OPTIONAL: Neo4j has no schema and BigQuery calls the level a dataset. A minCount 1 would break on the first harvest outside Snowflake. It is also the only property that would distinguish a tabular Dataset from a Graph, which is why that intermediate level is not declared yet -- one client does not pay for a level of hierarchy." .
pr:hasField a owl:ObjectProperty ;
rdfs:label "has field" ;
rdfs:domain pr:DataStructure ; rdfs:range pr:Field ;
owl:inverseOf pr:livesIn ;
rdfs:comment "Domain widened from Table to PhysicalRelation so that views and external tables carry columns too. Inverse now declared - in v0.9 hasField and livesIn were inverses in fact but not in the model, so they were free to diverge." .
pr:livesIn a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "lives in" ;
rdfs:domain pr:Field ; rdfs:range pr:DataStructure .
pr:deployedIn a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "deployed in" ;
rdfs:domain pr:CapturedObject ; rdfs:range pr:Environment .
pr:constrainsRelation a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "constrains relation (deprecated)" ;
owl:deprecated true ;
rdfs:comment "Derivable: constrainsField then livesIn gives the relation. An asserted edge redundant with an existing path is a divergence waiting to happen." .
pr:constrainsField a owl:ObjectProperty ;
rdfs:label "constrains field" ;
rdfs:domain pr:KeyConstraint ; rdfs:range pr:Field ;
rdfs:comment "Several fields for a composite key." .
pr:fromField a owl:ObjectProperty ;
rdfs:label "from field" ;
rdfs:domain pr:ForeignKeyConstraint ; rdfs:range pr:Field .
pr:toField a owl:ObjectProperty ;
rdfs:label "to field" ;
rdfs:domain pr:ForeignKeyConstraint ; rdfs:range pr:Field .
pr:consumes a owl:ObjectProperty ;
rdfs:subPropertyOf prov:used ;
rdfs:label "consumes" ;
rdfs:domain pr:Transformation ; rdfs:range pr:DataStructure .
pr:produces a owl:ObjectProperty ;
rdfs:label "produces" ;
rdfs:domain pr:Transformation ; rdfs:range pr:DataStructure .
pr:dependsOn a owl:ObjectProperty , owl:TransitiveProperty ;
rdfs:label "depends on" ;
rdfs:domain pr:DataStructure ; rdfs:range pr:DataStructure ;
pr:harvestSource "SNOWFLAKE.ACCOUNT_USAGE.OBJECT_DEPENDENCIES" ;
rdfs:comment "Relation-level dependency, view to table. Transitive by nature; in practice compute the closure at query time in Snowflake rather than materialising it with a reasoner." .
pr:derivedFrom a owl:ObjectProperty , owl:TransitiveProperty ;
rdfs:subPropertyOf prov:wasDerivedFrom ;
rdfs:label "derived from" ;
rdfs:domain pr:Field ; rdfs:range pr:Field ;
pr:harvestSource "SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY (column lineage) ; dbt manifest" ;
rdfs:comment "Column-to-column lineage. The single highest-value edge in the model, and largely free: Snowflake already computes it." .
#################################################################
# OBJECT PROPERTIES - Consumption
#################################################################
pr:inWorkspace a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "in workspace" ;
rdfs:domain pr:ConsumptionLayerObject ; rdfs:range pr:BIWorkspace .
pr:biConsumes a owl:ObjectProperty ;
rdfs:label "BI consumes" ;
rdfs:domain pr:BIDataSource ; rdfs:range pr:DataStructure ;
pr:harvestSource "Tableau Metadata API (GraphQL) ; Looker Explore SQL" .
pr:hasBIField a owl:ObjectProperty ;
rdfs:label "has BI field" ;
rdfs:domain pr:BIDataSource ; rdfs:range pr:BIField ;
owl:inverseOf pr:inBIDataSource .
pr:inBIDataSource a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "in BI data source" ;
rdfs:domain pr:BIField ; rdfs:range pr:BIDataSource .
pr:biDerivedFrom a owl:ObjectProperty ;
rdfs:subPropertyOf prov:wasDerivedFrom ;
rdfs:label "BI derived from" ;
rdfs:domain pr:BIField ; rdfs:range pr:Field ;
rdfs:comment "Closes the last gap in the impact chain: from a dashboard field back to the warehouse column." .
pr:exposesMetric a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "exposes metric" ;
rdfs:domain pr:BIField ; rdfs:range pr:Metric ;
pr:authoringMode "PROPOSED_CONFIRMED" ;
rdfs:comment """The harmonisation measure: how many distinct BI Fields expose one certified Metric, and do their expressions agree. Turns 'we harmonised the definitions' from a claim into a count over the real BI estate.
In principle this is derivable from the lineage chain Metric -> DataElement -> Field -> BIField, since lineage carries semantic inheritance. In practice that chain will have holes -- four hops, one of them PROPOSED_CONFIRMED, another harvested from an API that does not resolve custom SQL, extracts or cascaded calculated fields -- and the question it answers is too important to depend on them.
So: TRANSITIONAL CONTROL. Asserted directly, and that is all. No rule is written against the lineage chain: making the assertion conditional on intermediate mappings would create confusion and discourage people from filling it in.
Retirement criterion, to be measured rather than debated: when the lineage chain reproduces the asserted link for more than 95% of certified BI fields across three consecutive reconciliation cycles, the direct link is derivable and can go.""" .
pr:usesBIDataSource a owl:ObjectProperty ;
rdfs:label "uses BI data source" ;
rdfs:domain pr:BIReport ; rdfs:range pr:BIDataSource .
pr:hasBIOwner a owl:ObjectProperty ;
rdfs:label "has BI owner" ;
rdfs:domain pr:ConsumptionLayerObject ; rdfs:range pr:Actor ;
rdfs:comment "The author or publisher. This is who gets notified by an impact analysis, and it is NOT the Data Domain owner." .
#################################################################
# OBJECT PROPERTIES - Ownership (universal: domain omitted on purpose)
#################################################################
pr:ownedBy a owl:ObjectProperty ;
rdfs:label "owned by" ; rdfs:range pr:Actor .
pr:owningDomain a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "owning Data Domain" ; rdfs:range pr:DataDomain ;
rdfs:comment """The single Data Domain with authority to define the object's rule. Absent on Captured Objects: a Snowflake column is attributed through the Data Element mapped to it, never asserted directly.
Unlike the steward, this one is RE-DECLARABLE on a Data Element. It is inherited by default from the parent chain, but OW-002 attributes an element to the domain that defines its RULE, and a borrowed element -- Brand Code sitting in a Sell Out fact object -- has its rule elsewhere. A Data Element whose owningDomain differs from its parent's is a borrowed element: a signal to check against its represents, not an anomaly to fix.""" .
pr:monitoredBy a owl:ObjectProperty ;
rdfs:label "monitored by" ; rdfs:range pr:DataSteward ;
rdfs:comment """Declared on BusinessObject, and on DataProduct, and NOWHERE ELSE. Data Objects inherit through represents, Data Elements through hasElement. Declaring a steward at several levels would allow two answers to 'who watches this column'.
Says what the steward DOES, where the deprecated hasSteward only stated a membership.
DataProduct is the one exception to inheritance: it packages Data Objects belonging to several Business Objects, so it cannot inherit a single steward and declares its own.""" .
pr:hasSteward a owl:ObjectProperty ;
rdfs:label "has Data Steward (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:monitoredBy .
pr:hasDGL a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "has DGL" ;
rdfs:domain pr:DataDomain ; rdfs:range pr:DataGovernanceLead .
pr:hasDomainOwner a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "has domain owner" ;
rdfs:domain pr:DataDomain ; rdfs:range pr:DataDomainOwner .
pr:hasSubDomainOwner a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "has sub-domain owner" ;
rdfs:domain pr:SubDomain ; rdfs:range pr:SubDomainOwner .
pr:hasActivationStatus a owl:ObjectProperty , owl:FunctionalProperty ;
rdfs:label "activation status" ;
rdfs:domain pr:DataDomain ; rdfs:range pr:ActivationStatus .
#################################################################
# DATATYPE PROPERTIES - identity & lifecycle (domain omitted)
#################################################################
pr:hasIdentifier a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:label "identifier" ; rdfs:range xsd:string .
pr:hasName a owl:DatatypeProperty , owl:FunctionalProperty ; rdfs:label "canonical name" ; rdfs:range xsd:string .
pr:hasShortLabel a owl:DatatypeProperty , owl:FunctionalProperty ;
rdfs:label "short label" ; rdfs:range xsd:string ;
rdfs:comment """The name screens use, where the canonical name is the exhaustive one: Domain for Data Domain, SODH Gold for Sell Out Data Hub - Gold.
Declared rather than left to each interface. The same object was being called two different things depending on where you looked, because every viewer kept a display dictionary of its own. One name is authoritative, the other is short, and both live in the model.""" .
pr:hasStatus a owl:DatatypeProperty ; rdfs:label "status" ; rdfs:range xsd:string .
pr:hasVersion a owl:DatatypeProperty ; rdfs:label "version" ; rdfs:range xsd:string .
pr:createdOn a owl:DatatypeProperty ; rdfs:label "created on" ; rdfs:range xsd:date .
pr:lastReviewedOn a owl:DatatypeProperty ; rdfs:label "last reviewed on" ; rdfs:range xsd:date .
pr:hasSource a owl:DatatypeProperty ; rdfs:label "source of truth (deprecated)" ; rdfs:range xsd:string ;
owl:deprecated true ; dcterms:isReplacedBy pr:sourcedFrom ;
rdfs:comment """The system holding the authoritative data for this object: SODH Gold, MDM Product, Group Finance.
ONE MEANING ONLY. It had drifted into naming the document a record came from ("SODH back-doc v0.7"), and into packing a system and a relation into one string ("SODH Gold / F_SELL_OUT"). The relation belongs in physicalName; the document belongs nowhere -- provenance of a record is not a property of the thing it describes.
MUST NOT appear on a Business Concept: a concept has no data source. Its authority is its owning domain and its definition is published in the glossary. Enforced by SHACL.
On a Metric it is asserted rather than derived, by exception, because "where can I get this metric" is a first-order question and the derivation chain (computedBy, then the data object holding the element) will have gaps before harvesting. A shape checks the assertion against the elements.""" .
pr:arbitrationStatus a owl:DatatypeProperty ; rdfs:label "arbitration status" ; rdfs:range xsd:string ;
rdfs:comment "TO_ARBITRATE blocks publication (OW-007). Use this on the object rather than tracking arbitration in a side spreadsheet." .
# ---- Business semantics
pr:hasBusinessDefinition a owl:DatatypeProperty ; rdfs:label "business definition" ; rdfs:range xsd:string ;
rdfs:comment """The meaning of a business notion. Belongs to the BusinessConcept.
MUST NOT be carried by a BusinessObject: an object gets its meaning through aboutConcept. Two definitions for one notion are two definitions free to diverge. Enforced by SHACL rather than by rdfs:domain, because Data Elements legitimately carry one too and a declared domain would make them entail BusinessConcept -- which the disjointness axioms would then reject.
MIGRATION: text currently on a Business Object moves to the Concept named by its aboutConcept.""" .
pr:hasTechnicalDefinition a owl:DatatypeProperty ; rdfs:label "technical definition" ; rdfs:range xsd:string .
pr:hasAcronym a owl:DatatypeProperty ; rdfs:label "acronym" ; rdfs:range xsd:string .
pr:hasSynonym a owl:DatatypeProperty ; rdfs:label "synonym" ; rdfs:range xsd:string .
pr:hasBusinessRule a owl:DatatypeProperty ; rdfs:label "business rule" ; rdfs:range xsd:string .
# ---- Metric
pr:hasFormula a owl:DatatypeProperty ; rdfs:label "formula" ; rdfs:range xsd:string .
pr:hasUnit a owl:DatatypeProperty ; rdfs:label "unit" ; rdfs:range xsd:string ;
rdfs:comment "From the Units LoV. Note the LoV currently mixes physical units, currencies (ISO 4217) and value types (Index, Rate, Count) - three different concepts that should eventually be split." .
pr:hasGranularity a owl:DatatypeProperty ; rdfs:label "granularity (deprecated)" ; rdfs:range xsd:string ;
owl:deprecated true ; dcterms:isReplacedBy pr:hasGrainElement ;
rdfs:comment "Free-text grain label on a Metric, e.g. 'SKU x Customer x Day'. Kept a second, unverifiable copy of something now stated structurally on the Data Object. Grain is a property of a dataset, not of a calculation rule." .
pr:hasTimeAggregation a owl:DatatypeProperty ; rdfs:label "time aggregation" ; rdfs:range xsd:string .
pr:isKPI a owl:DatatypeProperty ; rdfs:label "is KPI (deprecated)" ; rdfs:range xsd:boolean ;
owl:deprecated true ; dcterms:isReplacedBy pr:KPI ;
rdfs:comment "Duplicated the KPI class. Two ways to say the same thing are two ways to diverge." .
# ---- Data Element
pr:hasFormat a owl:DatatypeProperty ; rdfs:label "logical format" ; rdfs:range xsd:string .
pr:hasExampleValue a owl:DatatypeProperty ; rdfs:label "example value" ; rdfs:range xsd:string .
pr:hasSourceSystem a owl:DatatypeProperty ; rdfs:label "source system (deprecated)" ; rdfs:range xsd:string ;
owl:deprecated true ; dcterms:isReplacedBy pr:sourcedFrom ;
rdfs:comment "Free text. 'Which objects depend on MDM Product?' was unanswerable because MDM Product was a string, not a node." .
pr:hasSchemaName a owl:DatatypeProperty ; rdfs:label "schema name (deprecated)" ; rdfs:range xsd:string ;
owl:deprecated true ; dcterms:isReplacedBy pr:Schema .
# ---- Physical identity (the join keys with the real warehouse)
pr:physicalName a owl:DatatypeProperty , owl:FunctionalProperty ;
rdfs:label "physical name" ; rdfs:range xsd:string ;
rdfs:comment "Recorded verbatim, never altered (NR-009). Must NOT be carried by a Metric - that is a layer leak; under BR-013 the physical name belongs on the Data Element." .
pr:fullyQualifiedName a owl:DatatypeProperty , owl:FunctionalProperty ;
rdfs:label "fully qualified name" ; rdfs:range xsd:string ;
rdfs:comment "DATABASE.SCHEMA.RELATION[.COLUMN]. The actual join key against INFORMATION_SCHEMA: a bare physical name is ambiguous as soon as the same column name exists in two tables." .
pr:sourceIdentifier a owl:DatatypeProperty , owl:FunctionalProperty ;
rdfs:label "source identifier" ; rdfs:range xsd:string ;
rdfs:comment "Native immutable id in the source system (Snowflake object id, Tableau LUID, Looker id). Makes reconciliation idempotent and survives renames, which names do not." .
pr:harvestedOn a owl:DatatypeProperty ; rdfs:label "harvested on" ; rdfs:range xsd:dateTime ;
rdfs:comment "Staleness marker. A Captured Object not refreshed by the last run is a deletion candidate, not a fact." .
pr:hasPhysicalDataType a owl:DatatypeProperty ; rdfs:label "physical data type" ; rdfs:range xsd:string .
pr:isNullable a owl:DatatypeProperty ; rdfs:label "is nullable" ; rdfs:range xsd:boolean .
pr:ordinalPosition a owl:DatatypeProperty ; rdfs:label "ordinal position" ; rdfs:range xsd:integer .
pr:hasViewDefinition a owl:DatatypeProperty ; rdfs:label "view definition" ; rdfs:range xsd:string .
pr:hasVendor a owl:DatatypeProperty ; rdfs:label "vendor (deprecated)" ; rdfs:range xsd:string ;
owl:deprecated true .
pr:definedBy a owl:DatatypeProperty ; rdfs:label "defined by (code URI)" ; rdfs:range xsd:anyURI ;
rdfs:comment "Git URI of the code behind a Transformation. Connects the graph to the repository." .
# ---- Usage evidence (the rationalisation argument)
pr:approximateRowCount a owl:DatatypeProperty ; rdfs:label "approximate row count (deprecated)" ; rdfs:range xsd:integer ;
owl:deprecated true ;
rdfs:comment "Volumetry with no identified use. queryCount30d and lastQueriedOn carry the rationalisation argument; row count does not." .
pr:lastQueriedOn a owl:DatatypeProperty ; rdfs:label "last queried on" ; rdfs:range xsd:dateTime ;
pr:harvestSource "SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY" ;
rdfs:comment "Lets the model say 'documented but unused for eight months'. A rationalisation argument that costs nothing and is denominated in warehouse spend." .
pr:queryCount30d a owl:DatatypeProperty ; rdfs:label "query count (30 days)" ; rdfs:range xsd:integer .
# ---- BI
pr:hasExpression a owl:DatatypeProperty ; rdfs:label "BI expression" ; rdfs:range xsd:string ;
rdfs:comment "The calculated-field formula, Tableau or LookML. Compared against the Metric formula to detect definition drift." .
#################################################################
# DISJOINTNESS
#################################################################
[] a owl:AllDisjointClasses ; owl:members ( pr:DefinedObject pr:CapturedObject ) .
[] a owl:AllDisjointClasses ; owl:members (
pr:OwnershipLayerObject pr:BusinessLayerObject pr:LogicalLayerObject
pr:DeliveryLayerObject pr:PhysicalLayerObject pr:ConsumptionLayerObject ) .
[] a owl:AllDisjointClasses ; owl:members (
pr:BusinessObject pr:DataObject pr:DataInterface pr:DataProduct ) .
[] a owl:AllDisjointClasses ; owl:members ( pr:BusinessConcept pr:Metric ) .
[] a owl:AllDisjointClasses ; owl:members (
pr:BaseTable pr:View pr:ExternalTable ) .
[] a owl:AllDisjointClasses ; owl:members ( pr:PrimaryKey pr:ForeignKeyConstraint ) .
[] a owl:AllDisjointClasses ; owl:members (
pr:BIWorkspace pr:BIDataSource pr:BIField pr:BIReport ) .