From 9cfa73b373e87cf19f347bd51b74da93ff2f6c33 Mon Sep 17 00:00:00 2001 From: Bastien Gourdon Date: Sun, 26 Jul 2026 21:28:14 +0200 Subject: [PATCH] tbox: import initial meta model v1.0 - 5 couches, axe curated/observed, couches physique et consommation modelisees, proprietes de pontage declarees. Etat connu : instances/ contient un TTL v0.6 et un back-doc v0.7 divergents, migration BR-013 au backlog --- .gitignore | 10 + docs/sodh_viewer_legacy_v0.6.html | 260 ++++++++ generated/pr_metamodel_viewer.html | 636 +++++++++++++++++++ instances/SODH_data.xlsx | Bin 0 -> 31480 bytes instances/sodh.ttl | 564 +++++++++++++++++ ontology/pr_metamodel.ttl | 652 +++++++++++++++++++ scripts/generate_tbox_viewer.py | 979 +++++++++++++++++++++++++++++ shapes/pr_metamodel_shapes.ttl | 127 ++++ 8 files changed, 3228 insertions(+) create mode 100644 .gitignore create mode 100644 docs/sodh_viewer_legacy_v0.6.html create mode 100644 generated/pr_metamodel_viewer.html create mode 100644 instances/SODH_data.xlsx create mode 100644 instances/sodh.ttl create mode 100644 ontology/pr_metamodel.ttl create mode 100644 scripts/generate_tbox_viewer.py create mode 100644 shapes/pr_metamodel_shapes.ttl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..60c8e77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.env +venv/ +__pycache__/ +*.pyc +*.log +*.pid +*.bak +*.bak_* +@eaDir/ +.DS_Store diff --git a/docs/sodh_viewer_legacy_v0.6.html b/docs/sodh_viewer_legacy_v0.6.html new file mode 100644 index 0000000..a1a62d6 --- /dev/null +++ b/docs/sodh_viewer_legacy_v0.6.html @@ -0,0 +1,260 @@ + + + + + +SODH Data - Instance Graph Viewer (A-Box) + + + +
+
+ +

SODH Data · Instance Graph (A-Box)

+
Sell Out Data Hub back-documentation — NODES · woven relationships
+
+ v0.6 · 23 JUL 2026 +
+
+ +
+ + + + + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/pr_metamodel_viewer.html b/generated/pr_metamodel_viewer.html new file mode 100644 index 0000000..52aa585 --- /dev/null +++ b/generated/pr_metamodel_viewer.html @@ -0,0 +1,636 @@ + + + + + +PR Data Meta Model · T-Box v1.0 + + + + +
+ +
+

PR Data Meta Model · T-Box

+
Five layers, crossed with a provenance axis: what governance asserts, what harvesters observe
+
+
+
0Classes
+
0Relations
+
0Attributes
+
0Seam edges
+
+
+ +
+ + +
+ + + + + + + + + + + + +
+ + + +
+
drag to pan · scroll to zoom · click a class
+
+ + +
+ + + + + + diff --git a/instances/SODH_data.xlsx b/instances/SODH_data.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..e04a0640607feed8953ea105b2a59679256151b6 GIT binary patch literal 31480 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 literal 0 HcmV?d00001 diff --git a/instances/sodh.ttl b/instances/sodh.ttl new file mode 100644 index 0000000..a46889a --- /dev/null +++ b/instances/sodh.ttl @@ -0,0 +1,564 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . +@prefix pr: . +@prefix ex: . + +################################################################# +# SODH back-documentation - A-Box instances - v0.6 (WOVEN) +# Full lineage: BO-hasMetric->Metric-measures->BC ; BO-composedOf->BC ; +# DO-implements->BO ; DO-hasElement->DE ; DO-references->DO ; +# SD-belongsToDomain->DD. +################################################################# + + +ex:ST_DD_06 a pr:DataSteward ; pr:hasName "Sales Performance Steward" . +ex:DGL_DD_06 a pr:DataGovernanceLead ; pr:hasName "Bastien Gourdon" . +ex:ST_DD_10 a pr:DataSteward ; pr:hasName "Product & Material Steward" . +ex:DGL_DD_10 a pr:DataGovernanceLead ; pr:hasName "Anas El Kesri" . +ex:ST_DD_04 a pr:DataSteward ; pr:hasName "Customer & Channels Steward" . +ex:DGL_DD_04 a pr:DataGovernanceLead ; pr:hasName "TBD (vacant)" . +ex:ST_DD_16 a pr:DataSteward ; pr:hasName "Financial Performance Steward" . +ex:DGL_DD_16 a pr:DataGovernanceLead ; pr:hasName "Gaelle Seret" . +ex:ST_DD_21 a pr:DataSteward ; pr:hasName "Data Governance Steward" . +ex:DGL_DD_21 a pr:DataGovernanceLead ; pr:hasName "Bastien Gourdon" . +ex:ST_DD_05 a pr:DataSteward ; pr:hasName "Sales Activation Steward" . +ex:DGL_DD_05 a pr:DataGovernanceLead ; pr:hasName "Alberic Piot" . + +ex:DD_06 a pr:DataDomain ; + pr:hasIdentifier "DD-06" ; pr:hasName "Sales Performance" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:DGL_DD_06 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" ; + pr:hasDGL ex:DGL_DD_06 ; pr:hasActivationStatus pr:FullActivation . +ex:DD_10 a pr:DataDomain ; + pr:hasIdentifier "DD-10" ; pr:hasName "Product & Material" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:DGL_DD_10 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" ; + pr:hasDGL ex:DGL_DD_10 ; pr:hasActivationStatus pr:FullActivation . +ex:DD_04 a pr:DataDomain ; + pr:hasIdentifier "DD-04" ; pr:hasName "Customer & Channels" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:DGL_DD_04 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" ; + pr:hasDGL ex:DGL_DD_04 ; pr:hasActivationStatus pr:LightActivation . +ex:DD_16 a pr:DataDomain ; + pr:hasIdentifier "DD-16" ; pr:hasName "Financial Performance" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:DGL_DD_16 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" ; + pr:hasDGL ex:DGL_DD_16 ; pr:hasActivationStatus pr:FullActivation . +ex:DD_21 a pr:DataDomain ; + pr:hasIdentifier "DD-21" ; pr:hasName "Data Governance" ; + pr:owningDomain ex:DD_21 ; pr:ownedBy ex:DGL_DD_21 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" ; + pr:hasDGL ex:DGL_DD_21 ; pr:hasActivationStatus pr:FullActivation . +ex:DD_05 a pr:DataDomain ; + pr:hasIdentifier "DD-05" ; pr:hasName "Sales Activation" ; + pr:owningDomain ex:DD_05 ; pr:ownedBy ex:DGL_DD_05 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" ; + pr:hasDGL ex:DGL_DD_05 ; pr:hasActivationStatus pr:FullActivation . + +ex:SD_06_01 a pr:SubDomain ; + pr:hasIdentifier "SD-06.01" ; pr:hasName "Sell Out" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:belongsToDomain ex:DD_06 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" . +ex:SD_10_01 a pr:SubDomain ; + pr:hasIdentifier "SD-10.01" ; pr:hasName "Product Structure" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:belongsToDomain ex:DD_10 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" . +ex:SD_04_01 a pr:SubDomain ; + pr:hasIdentifier "SD-04.01" ; pr:hasName "Customer & Outlet" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:belongsToDomain ex:DD_04 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" . +ex:SD_16_01 a pr:SubDomain ; + pr:hasIdentifier "SD-16.01" ; pr:hasName "Currency & Exchange Rates" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:belongsToDomain ex:DD_16 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" . +ex:SD_16_02 a pr:SubDomain ; + pr:hasIdentifier "SD-16.02" ; pr:hasName "Marketing Entity & Fiscal Calendar" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:belongsToDomain ex:DD_16 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" . +ex:SD_21_01 a pr:SubDomain ; + pr:hasIdentifier "SD-21.01" ; pr:hasName "Calendar" ; + pr:owningDomain ex:DD_21 ; pr:ownedBy ex:ST_DD_21 ; + pr:belongsToDomain ex:DD_21 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" . +ex:SD_05_01 a pr:SubDomain ; + pr:hasIdentifier "SD-05.01" ; pr:hasName "Promotion" ; + pr:owningDomain ex:DD_05 ; pr:ownedBy ex:ST_DD_05 ; + pr:belongsToDomain ex:DD_05 ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" . + +ex:BC_06_01_001 a pr:BusinessConcept ; + pr:hasIdentifier "BC-06.01-001" ; pr:hasName "Sell Out" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasBusinessDefinition "The sale of Pernod Ricard products by retail customers to end consumers, observed via panel or direct feed, as opposed to shipments from Pernod Ricard to its customers (Sell In)." ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" ; pr:hasSource "SODH Gold" . +ex:BC_06_01_002 a pr:BusinessConcept ; + pr:hasIdentifier "BC-06.01-002" ; pr:hasName "Retailer Stock" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasBusinessDefinition "Inventory of Pernod Ricard products held at the retailer level, downstream of Pernod Ricard shipments." ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" ; pr:hasSource "SODH Gold" . +ex:BC_06_01_003 a pr:BusinessConcept ; + pr:hasIdentifier "BC-06.01-003" ; pr:hasName "Panel Coverage" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasBusinessDefinition "The scope of retail outlets observed by a panel provider, defining the universe over which distribution is computed." ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" ; pr:hasSource "SODH Gold" . +ex:BC_06_01_004 a pr:BusinessConcept ; + pr:hasIdentifier "BC-06.01-004" ; pr:hasName "Distribution" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasBusinessDefinition "The presence of a product across retail outlets: the number of points of sale carrying at least one Pernod Ricard product, related to the total number of points of sale in the Pernod Ricard universe." ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" ; pr:hasSource "SODH Gold" . +ex:BC_05_01_001 a pr:BusinessConcept ; + pr:hasIdentifier "BC-05.01-001" ; pr:hasName "Promotion" ; + pr:owningDomain ex:DD_05 ; pr:ownedBy ex:ST_DD_05 ; + pr:hasBusinessDefinition "A commercial mechanism applied at retail to stimulate sales (for example Temporary Price Reduction, Multibuy, Feature Display)." ; + pr:hasStatus "PUBLISHED" ; pr:hasVersion "1.0" ; pr:hasSource "SODH Gold" . + +ex:BO_06_01_001 a pr:BusinessObject ; + pr:hasIdentifier "BO-06.01-001" ; pr:hasName "Sell Out Volume" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:belongsToSubDomain ex:SD_06_01 ; pr:hasSteward ex:ST_DD_06 ; + pr:composedOf ex:BC_06_01_001 , ex:BC_06_01_003 , ex:BC_05_01_001 ; + pr:hasMetric ex:M_06_01_001 , ex:M_06_01_002 , ex:M_06_01_003 , ex:M_06_01_004 ; + pr:hasBusinessDefinition "Quantities of Pernod Ricard products sold by retail customers to end consumers, observed at retail." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / F_SELL_OUT" . +ex:BO_06_01_002 a pr:BusinessObject ; + pr:hasIdentifier "BO-06.01-002" ; pr:hasName "Sell Out Value" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:belongsToSubDomain ex:SD_06_01 ; pr:hasSteward ex:ST_DD_06 ; + pr:composedOf ex:BC_06_01_001 , ex:BC_06_01_003 , ex:BC_05_01_001 ; + pr:hasMetric ex:M_06_01_010 , ex:M_06_01_011 , ex:M_06_01_012 ; + pr:hasBusinessDefinition "Monetary value of Pernod Ricard products sold to end consumers, expressed in multiple currencies (Euro, US Dollar, or local currency)." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / F_SELL_OUT" . +ex:BO_06_01_003 a pr:BusinessObject ; + pr:hasIdentifier "BO-06.01-003" ; pr:hasName "Sell Out Distribution" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:belongsToSubDomain ex:SD_06_01 ; pr:hasSteward ex:ST_DD_06 ; + pr:composedOf ex:BC_06_01_004 , ex:BC_06_01_003 , ex:BC_06_01_001 ; + pr:hasMetric ex:M_06_01_020 , ex:M_06_01_021 , ex:M_06_01_022 ; + pr:hasBusinessDefinition "Presence of Pernod Ricard products in retail: the number of points of sale carrying at least one Pernod Ricard product, related to the total number of points of sale in the Pernod Ricard universe." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / F_SELL_OUT_DISTRIBUTION" . +ex:BO_06_01_004 a pr:BusinessObject ; + pr:hasIdentifier "BO-06.01-004" ; pr:hasName "Retailer Stock" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:belongsToSubDomain ex:SD_06_01 ; pr:hasSteward ex:ST_DD_06 ; + pr:composedOf ex:BC_06_01_002 , ex:BC_06_01_001 , ex:BC_06_01_003 ; + pr:hasMetric ex:M_06_01_030 , ex:M_06_01_031 , ex:M_06_01_032 ; + pr:hasBusinessDefinition "Inventory of Pernod Ricard products held at retailer level: forward stock volume, days of coverage and stock share." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / F_SELL_OUT" . +ex:BO_06_01_005 a pr:BusinessObject ; + pr:hasIdentifier "BO-06.01-005" ; pr:hasName "Sell Out Baseline" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:belongsToSubDomain ex:SD_06_01 ; pr:hasSteward ex:ST_DD_06 ; + pr:composedOf ex:BC_06_01_001 , ex:BC_05_01_001 , ex:BC_06_01_003 ; + pr:hasMetric ex:M_06_01_040 , ex:M_06_01_041 ; + pr:hasBusinessDefinition "Modelled non-promoted volume and the incremental effect attributed to promotions, computed from observed Sell Out." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / F_SELL_OUT_BASELINE" . +ex:BO_10_01_001 a pr:BusinessObject ; + pr:hasIdentifier "BO-10.01-001" ; pr:hasName "Product Structure" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:belongsToSubDomain ex:SD_10_01 ; pr:hasSteward ex:ST_DD_10 ; + pr:composedOf ex:BC_06_01_001 , ex:BC_06_01_003 , ex:BC_06_01_004 ; + pr:hasBusinessDefinition "The corporate product hierarchy from Brand down to Stock Keeping Unit, with Category, Sub-Category, Flavor and unit-of-measure standards. Owned by Product & Material; consumed by SODH." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product -> SODH [concepts TBC by DD-10]" . +ex:BO_04_01_001 a pr:BusinessObject ; + pr:hasIdentifier "BO-04.01-001" ; pr:hasName "Customer & Outlet" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:belongsToSubDomain ex:SD_04_01 ; pr:hasSteward ex:ST_DD_04 ; + pr:composedOf ex:BC_06_01_001 , ex:BC_06_01_003 , ex:BC_06_01_004 ; + pr:hasBusinessDefinition "Retail customer hierarchy: Distributor, Tier-1 and Tier-2 Customer, Outlet and Channel. Owned by Customer & Channels; consumed by SODH." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Customer -> SODH [concepts TBC by DD-04]" . +ex:BO_16_01_001 a pr:BusinessObject ; + pr:hasIdentifier "BO-16.01-001" ; pr:hasName "Currency & Exchange Rates" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:belongsToSubDomain ex:SD_16_01 ; pr:hasSteward ex:ST_DD_16 ; + pr:composedOf ex:BC_06_01_001 , ex:BC_06_01_003 , ex:BC_06_01_004 ; + pr:hasBusinessDefinition "Currency codes and exchange rates used to convert local Sell Out Value into Euro or US Dollar for group reporting. Owned by Financial Performance; consumed by SODH." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Group Finance -> SODH [concepts TBC by DD-16]" . +ex:BO_21_01_001 a pr:BusinessObject ; + pr:hasIdentifier "BO-21.01-001" ; pr:hasName "Calendar" ; + pr:owningDomain ex:DD_21 ; pr:ownedBy ex:ST_DD_21 ; + pr:belongsToSubDomain ex:SD_21_01 ; pr:hasSteward ex:ST_DD_21 ; + pr:composedOf ex:BC_06_01_001 , ex:BC_06_01_003 , ex:BC_06_01_004 ; + pr:hasBusinessDefinition "The Gregorian calendar dimension shared across all domains: Date, Day, Week, Month, Quarter and Public Holiday. Owned by Data Governance; consumed by SODH." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Standard Calendar -> SODH [concepts TBC by DD-21]" . +ex:BO_05_01_001 a pr:BusinessObject ; + pr:hasIdentifier "BO-05.01-001" ; pr:hasName "Promotion Type" ; + pr:owningDomain ex:DD_05 ; pr:ownedBy ex:ST_DD_05 ; + pr:belongsToSubDomain ex:SD_05_01 ; pr:hasSteward ex:ST_DD_05 ; + pr:composedOf ex:BC_05_01_001 , ex:BC_06_01_001 , ex:BC_06_01_004 ; + pr:hasBusinessDefinition "The controlled taxonomy of promotion mechanisms used to segment Sell Out measures. Owned by Sales Activation; consumed by SODH." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Sales Activation -> SODH [concepts TBC by DD-05]" . + +ex:M_06_01_001 a pr:Metric ; + pr:hasIdentifier "M-06.01-001" ; pr:hasName "Sell Out Volume (9L)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_001 ; + pr:hasFormula "SUM(GL_VOLUME_9L_NM)" ; pr:hasUnit "9L" ; pr:hasGranularity "SKU x Customer x Day" ; + pr:physicalName "GL_VOLUME_9L_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_VOLUME_9L_NM" . +ex:M_06_01_002 a pr:Metric ; + pr:hasIdentifier "M-06.01-002" ; pr:hasName "Sell Out Volume (L)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_001 ; + pr:hasFormula "SUM(GL_VOLUME_L_NM)" ; pr:hasUnit "L" ; pr:hasGranularity "SKU x Customer x Day" ; + pr:physicalName "GL_VOLUME_L_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_VOLUME_L_NM" . +ex:M_06_01_003 a pr:Metric ; + pr:hasIdentifier "M-06.01-003" ; pr:hasName "Sell Out Volume Non-Promoted (L)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_001 ; + pr:hasFormula "SUM(GL_VOLUME_L_NO_PROMO_NM)" ; pr:hasUnit "L" ; pr:hasGranularity "SKU x Customer x Day" ; + pr:physicalName "GL_VOLUME_L_NO_PROMO_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_VOLUME_L_NO_PROMO_NM" . +ex:M_06_01_004 a pr:Metric ; + pr:hasIdentifier "M-06.01-004" ; pr:hasName "Sell Out Volume Promoted (L)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_001 ; + pr:hasFormula "SUM(GL_VOLUME_L_ANY_PROMO_NM)" ; pr:hasUnit "L" ; pr:hasGranularity "SKU x Customer x Day" ; + pr:physicalName "GL_VOLUME_L_ANY_PROMO_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_VOLUME_L_ANY_PROMO_NM" . +ex:M_06_01_010 a pr:Metric ; + pr:hasIdentifier "M-06.01-010" ; pr:hasName "Sell Out Value (EUR)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_001 ; + pr:hasFormula "SUM(GL_VALUE_EUR_NM)" ; pr:hasUnit "EUR" ; pr:hasGranularity "SKU x Customer x Day" ; + pr:physicalName "GL_VALUE_EUR_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_VALUE_EUR_NM" . +ex:M_06_01_011 a pr:Metric ; + pr:hasIdentifier "M-06.01-011" ; pr:hasName "Sell Out Value (LC)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_001 ; + pr:hasFormula "SUM(GL_VALUE_LC_NM)" ; pr:hasUnit "LC" ; pr:hasGranularity "SKU x Customer x Day" ; + pr:physicalName "GL_VALUE_LC_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_VALUE_LC_NM" . +ex:M_06_01_012 a pr:Metric ; + pr:hasIdentifier "M-06.01-012" ; pr:hasName "Sell Out Value (USD)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_001 ; + pr:hasFormula "SUM(GL_VALUE_USD_NM)" ; pr:hasUnit "USD" ; pr:hasGranularity "SKU x Customer x Day" ; + pr:physicalName "GL_VALUE_USD_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_VALUE_USD_NM" . +ex:M_06_01_020 a pr:Metric ; + pr:hasIdentifier "M-06.01-020" ; pr:hasName "Sell Out Numeric Distribution (%)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_004 ; + pr:hasFormula "COUNT(outlets carrying SKU) / COUNT(universe outlets) * 100" ; pr:hasUnit "%" ; pr:hasGranularity "SKU x Country x Period" ; + pr:physicalName "GL_NUMERIC_DISTRIBUTION_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_NUMERIC_DISTRIBUTION_NM" . +ex:M_06_01_021 a pr:Metric ; + pr:hasIdentifier "M-06.01-021" ; pr:hasName "Sell Out Weighted Distribution (%)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_004 ; + pr:hasFormula "SUM(cat. turnover of outlets carrying SKU) / SUM(total cat. turnover) * 100" ; pr:hasUnit "%" ; pr:hasGranularity "SKU x Country x Period" ; + pr:physicalName "GL_WEIGHTED_DISTRIBUTION_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_WEIGHTED_DISTRIBUTION_NM" . +ex:M_06_01_022 a pr:Metric ; + pr:hasIdentifier "M-06.01-022" ; pr:hasName "Sell Out Total Distribution Points (Index)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_004 ; + pr:hasFormula "SUM(Weighted Distribution across SKUs of the brand)" ; pr:hasUnit "Index" ; pr:hasGranularity "Brand x Country x Period" ; + pr:physicalName "GL_TDP_NM" ; + pr:hasAcronym "TDP" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_TDP_NM" . +ex:M_06_01_030 a pr:Metric ; + pr:hasIdentifier "M-06.01-030" ; pr:hasName "Retailer Stock Forward Volume (L)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_002 ; + pr:hasFormula "SUM(GL_STOCK_FORWARD_VOLUME_L_NM)" ; pr:hasUnit "L" ; pr:hasGranularity "SKU x Customer x Day" ; + pr:physicalName "GL_STOCK_FORWARD_VOLUME_L_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_STOCK_FORWARD_VOLUME_L_NM" . +ex:M_06_01_031 a pr:Metric ; + pr:hasIdentifier "M-06.01-031" ; pr:hasName "Retailer Stock Days of Coverage (Days)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_002 ; + pr:hasFormula "Retailer Forward Stock Volume / avg daily Sell Out Volume" ; pr:hasUnit "Days" ; pr:hasGranularity "SKU x Customer x Day" ; + pr:physicalName "GL_DAYS_IN_COVERAGE" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_DAYS_IN_COVERAGE" . +ex:M_06_01_032 a pr:Metric ; + pr:hasIdentifier "M-06.01-032" ; pr:hasName "Retailer Stock Share by-SKU (%)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_002 ; + pr:hasFormula "SKU retailer stock / category retailer stock * 100" ; pr:hasUnit "%" ; pr:hasGranularity "SKU x Category x Period" ; + pr:physicalName "GL_SHARES_OF_FORWARD_STOCK_VOLUME_9L_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_SHARES_OF_FORWARD_STOCK_VOLUME_9L_NM" . +ex:M_06_01_040 a pr:Metric ; + pr:hasIdentifier "M-06.01-040" ; pr:hasName "Sell Out Baseline Volume Modelled (9L)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_001 ; + pr:hasFormula "MODEL(no-promo scenario volume)" ; pr:hasUnit "9L" ; pr:hasGranularity "SKU x Customer x Week" ; + pr:physicalName "GL_BASELINE_VOLUME_9L_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_BASELINE_VOLUME_9L_NM" . +ex:M_06_01_041 a pr:Metric ; + pr:hasIdentifier "M-06.01-041" ; pr:hasName "Sell Out Incremental Volume vs-Baseline (9L)" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:measures ex:BC_06_01_001 ; + pr:hasFormula "Observed Volume - Baseline Volume" ; pr:hasUnit "9L" ; pr:hasGranularity "SKU x Customer x Week" ; + pr:physicalName "GL_INCREMENTAL_VOLUME_9L_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / GL_INCREMENTAL_VOLUME_9L_NM" . + +ex:DE_10_01_0001 a pr:DataElement ; + pr:hasIdentifier "DE-10.01-0001" ; pr:hasName "Brand Code" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasFormat "STRING(10)" ; pr:hasSourceSystem "MDM Product" ; + pr:physicalName "BRAND_CD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product / BRAND_CD" . +ex:DE_10_01_0002 a pr:DataElement ; + pr:hasIdentifier "DE-10.01-0002" ; pr:hasName "Brand Name" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasFormat "STRING(100)" ; pr:hasSourceSystem "MDM Product" ; + pr:physicalName "BRAND_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product / BRAND_NM" . +ex:DE_10_01_0003 a pr:DataElement ; + pr:hasIdentifier "DE-10.01-0003" ; pr:hasName "Product Category" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasFormat "STRING(50)" ; pr:hasSourceSystem "MDM Product" ; + pr:physicalName "CATEGORY_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product / CATEGORY_NM" . +ex:DE_10_01_0004 a pr:DataElement ; + pr:hasIdentifier "DE-10.01-0004" ; pr:hasName "Product Sub-Category" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasFormat "STRING(50)" ; pr:hasSourceSystem "MDM Product" ; + pr:physicalName "SUB_CATEGORY_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product / SUB_CATEGORY_NM" . +ex:DE_10_01_0005 a pr:DataElement ; + pr:hasIdentifier "DE-10.01-0005" ; pr:hasName "SKU Code" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasFormat "STRING(20)" ; pr:hasSourceSystem "MDM Product" ; + pr:physicalName "SKU_CD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product / SKU_CD" . +ex:DE_10_01_0006 a pr:DataElement ; + pr:hasIdentifier "DE-10.01-0006" ; pr:hasName "SKU Description" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasFormat "STRING(255)" ; pr:hasSourceSystem "MDM Product" ; + pr:physicalName "SKU_DESC" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product / SKU_DESC" . +ex:DE_10_01_0007 a pr:DataElement ; + pr:hasIdentifier "DE-10.01-0007" ; pr:hasName "Bottle Size (mL)" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasFormat "INTEGER" ; pr:hasSourceSystem "MDM Product" ; + pr:physicalName "BOTTLE_SIZE_ML" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product / BOTTLE_SIZE_ML" . +ex:DE_10_01_0008 a pr:DataElement ; + pr:hasIdentifier "DE-10.01-0008" ; pr:hasName "Pack Size (Count)" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasFormat "INTEGER" ; pr:hasSourceSystem "MDM Product" ; + pr:physicalName "PACK_SIZE_NR" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product / PACK_SIZE_NR" . +ex:DE_10_01_0009 a pr:DataElement ; + pr:hasIdentifier "DE-10.01-0009" ; pr:hasName "Flavor" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasFormat "STRING(50)" ; pr:hasSourceSystem "MDM Product" ; + pr:physicalName "FLAVOR_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product / FLAVOR_NM" . +ex:DE_10_01_0010 a pr:DataElement ; + pr:hasIdentifier "DE-10.01-0010" ; pr:hasName "Innovation Flag" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasFormat "BOOLEAN" ; pr:hasSourceSystem "MDM Product" ; + pr:physicalName "INNOVATION_FLG" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product / INNOVATION_FLG" . +ex:DE_10_01_0011 a pr:DataElement ; + pr:hasIdentifier "DE-10.01-0011" ; pr:hasName "Strategic Priority Flag" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasFormat "BOOLEAN" ; pr:hasSourceSystem "MDM Product" ; + pr:physicalName "STRATEGIC_PRIORITY_FLG" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product / STRATEGIC_PRIORITY_FLG" . +ex:DE_10_01_0012 a pr:DataElement ; + pr:hasIdentifier "DE-10.01-0012" ; pr:hasName "Unit of Measure Standard" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasFormat "STRING(10)" ; pr:hasSourceSystem "MDM Product" ; + pr:physicalName "UOM_STANDARD_CD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Product / UOM_STANDARD_CD" . +ex:DE_04_01_0001 a pr:DataElement ; + pr:hasIdentifier "DE-04.01-0001" ; pr:hasName "Distributor Code" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasFormat "STRING(20)" ; pr:hasSourceSystem "MDM Customer" ; + pr:physicalName "DISTRIBUTOR_CD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Customer / DISTRIBUTOR_CD" . +ex:DE_04_01_0002 a pr:DataElement ; + pr:hasIdentifier "DE-04.01-0002" ; pr:hasName "Distributor Name" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasFormat "STRING(150)" ; pr:hasSourceSystem "MDM Customer" ; + pr:physicalName "DISTRIBUTOR_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Customer / DISTRIBUTOR_NM" . +ex:DE_04_01_0003 a pr:DataElement ; + pr:hasIdentifier "DE-04.01-0003" ; pr:hasName "Distributor Branch Code" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasFormat "STRING(20)" ; pr:hasSourceSystem "MDM Customer" ; + pr:physicalName "DISTRIBUTOR_BRANCH_CD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Customer / DISTRIBUTOR_BRANCH_CD" . +ex:DE_04_01_0004 a pr:DataElement ; + pr:hasIdentifier "DE-04.01-0004" ; pr:hasName "Customer Tier-1 Code" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasFormat "STRING(20)" ; pr:hasSourceSystem "MDM Customer" ; + pr:physicalName "CUSTOMER_T1_CD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Customer / CUSTOMER_T1_CD" . +ex:DE_04_01_0005 a pr:DataElement ; + pr:hasIdentifier "DE-04.01-0005" ; pr:hasName "Customer Tier-2 Code" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasFormat "STRING(20)" ; pr:hasSourceSystem "MDM Customer" ; + pr:physicalName "CUSTOMER_T2_CD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Customer / CUSTOMER_T2_CD" . +ex:DE_04_01_0006 a pr:DataElement ; + pr:hasIdentifier "DE-04.01-0006" ; pr:hasName "Outlet Type" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasFormat "STRING(30)" ; pr:hasSourceSystem "MDM Customer" ; + pr:physicalName "OUTLET_TYPE_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Customer / OUTLET_TYPE_NM" . +ex:DE_04_01_0007 a pr:DataElement ; + pr:hasIdentifier "DE-04.01-0007" ; pr:hasName "Channel" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasFormat "STRING(30)" ; pr:hasSourceSystem "MDM Customer" ; + pr:physicalName "CHANNEL_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Customer / CHANNEL_NM" . +ex:DE_04_01_0008 a pr:DataElement ; + pr:hasIdentifier "DE-04.01-0008" ; pr:hasName "Country Code" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasFormat "STRING(2)" ; pr:hasSourceSystem "MDM Customer" ; + pr:physicalName "COUNTRY_CD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Customer / COUNTRY_CD" . +ex:DE_04_01_0009 a pr:DataElement ; + pr:hasIdentifier "DE-04.01-0009" ; pr:hasName "Country Name" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasFormat "STRING(100)" ; pr:hasSourceSystem "MDM Customer" ; + pr:physicalName "COUNTRY_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Customer / COUNTRY_NM" . +ex:DE_04_01_0010 a pr:DataElement ; + pr:hasIdentifier "DE-04.01-0010" ; pr:hasName "Point of Sale Count (Count)" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasFormat "INTEGER" ; pr:hasSourceSystem "MDM Customer" ; + pr:physicalName "POS_COUNT_NR" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "MDM Customer / POS_COUNT_NR" . +ex:DE_16_01_0001 a pr:DataElement ; + pr:hasIdentifier "DE-16.01-0001" ; pr:hasName "Currency Code" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:hasFormat "STRING(3)" ; pr:hasSourceSystem "Group Finance" ; + pr:physicalName "CURRENCY_CD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Group Finance / CURRENCY_CD" . +ex:DE_16_01_0002 a pr:DataElement ; + pr:hasIdentifier "DE-16.01-0002" ; pr:hasName "FX Rate to-Euro (Rate)" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:hasFormat "DECIMAL(18,8)" ; pr:hasSourceSystem "Group Finance" ; + pr:physicalName "FX_RATE_TO_EUR" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Group Finance / FX_RATE_TO_EUR" . +ex:DE_16_01_0003 a pr:DataElement ; + pr:hasIdentifier "DE-16.01-0003" ; pr:hasName "FX Rate to-USD (Rate)" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:hasFormat "DECIMAL(18,8)" ; pr:hasSourceSystem "Group Finance" ; + pr:physicalName "FX_RATE_TO_USD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Group Finance / FX_RATE_TO_USD" . +ex:DE_16_02_0001 a pr:DataElement ; + pr:hasIdentifier "DE-16.02-0001" ; pr:hasName "Marketing Entity Code" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:hasFormat "STRING(10)" ; pr:hasSourceSystem "Group Finance" ; + pr:physicalName "MARKETING_ENTITY_CD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Group Finance / MARKETING_ENTITY_CD" . +ex:DE_16_02_0002 a pr:DataElement ; + pr:hasIdentifier "DE-16.02-0002" ; pr:hasName "Fiscal Period Code" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:hasFormat "STRING(10)" ; pr:hasSourceSystem "Group Finance" ; + pr:physicalName "PR_FISCAL_PERIOD_CD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Group Finance / PR_FISCAL_PERIOD_CD" . +ex:DE_16_02_0003 a pr:DataElement ; + pr:hasIdentifier "DE-16.02-0003" ; pr:hasName "Fiscal Quarter Code" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:hasFormat "STRING(10)" ; pr:hasSourceSystem "Group Finance" ; + pr:physicalName "PR_FISCAL_QUARTER_CD" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Group Finance / PR_FISCAL_QUARTER_CD" . +ex:DE_21_01_0001 a pr:DataElement ; + pr:hasIdentifier "DE-21.01-0001" ; pr:hasName "Calendar Date" ; + pr:owningDomain ex:DD_21 ; pr:ownedBy ex:ST_DD_21 ; + pr:hasFormat "DATE" ; pr:hasSourceSystem "Standard Calendar" ; + pr:physicalName "DATE_DT" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Standard Calendar / DATE_DT" . +ex:DE_21_01_0002 a pr:DataElement ; + pr:hasIdentifier "DE-21.01-0002" ; pr:hasName "Day of Week Name" ; + pr:owningDomain ex:DD_21 ; pr:ownedBy ex:ST_DD_21 ; + pr:hasFormat "STRING(10)" ; pr:hasSourceSystem "Standard Calendar" ; + pr:physicalName "DAY_OF_WEEK_NM" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Standard Calendar / DAY_OF_WEEK_NM" . +ex:DE_21_01_0003 a pr:DataElement ; + pr:hasIdentifier "DE-21.01-0003" ; pr:hasName "ISO Week Number (Count)" ; + pr:owningDomain ex:DD_21 ; pr:ownedBy ex:ST_DD_21 ; + pr:hasFormat "INTEGER" ; pr:hasSourceSystem "Standard Calendar" ; + pr:physicalName "ISO_WEEK_NR" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Standard Calendar / ISO_WEEK_NR" . +ex:DE_21_01_0004 a pr:DataElement ; + pr:hasIdentifier "DE-21.01-0004" ; pr:hasName "Month Number (Count)" ; + pr:owningDomain ex:DD_21 ; pr:ownedBy ex:ST_DD_21 ; + pr:hasFormat "INTEGER" ; pr:hasSourceSystem "Standard Calendar" ; + pr:physicalName "MONTH_NR" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Standard Calendar / MONTH_NR" . +ex:DE_21_01_0005 a pr:DataElement ; + pr:hasIdentifier "DE-21.01-0005" ; pr:hasName "Public Holiday Flag" ; + pr:owningDomain ex:DD_21 ; pr:ownedBy ex:ST_DD_21 ; + pr:hasFormat "BOOLEAN" ; pr:hasSourceSystem "Standard Calendar" ; + pr:physicalName "PUBLIC_HOLIDAY_FLG" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "Standard Calendar / PUBLIC_HOLIDAY_FLG" . +ex:DE_05_01_0001 a pr:DataElement ; + pr:hasIdentifier "DE-05.01-0001" ; pr:hasName "Promotion Type Temporary Price Reduction Flag" ; + pr:owningDomain ex:DD_05 ; pr:ownedBy ex:ST_DD_05 ; + pr:hasFormat "BOOLEAN" ; pr:hasSourceSystem "SODH Gold (flag)" ; + pr:physicalName "GL_PROMO_TPR_FLG" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold (flag) / GL_PROMO_TPR_FLG" . +ex:DE_05_01_0002 a pr:DataElement ; + pr:hasIdentifier "DE-05.01-0002" ; pr:hasName "Promotion Type Multibuy Flag" ; + pr:owningDomain ex:DD_05 ; pr:ownedBy ex:ST_DD_05 ; + pr:hasFormat "BOOLEAN" ; pr:hasSourceSystem "SODH Gold (flag)" ; + pr:physicalName "GL_PROMO_MULTIBUY_FLG" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold (flag) / GL_PROMO_MULTIBUY_FLG" . +ex:DE_05_01_0003 a pr:DataElement ; + pr:hasIdentifier "DE-05.01-0003" ; pr:hasName "Promotion Type Feature Display Flag" ; + pr:owningDomain ex:DD_05 ; pr:ownedBy ex:ST_DD_05 ; + pr:hasFormat "BOOLEAN" ; pr:hasSourceSystem "SODH Gold (flag)" ; + pr:physicalName "GL_PROMO_FEATURE_DISPLAY_FLG" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold (flag) / GL_PROMO_FEATURE_DISPLAY_FLG" . +ex:DE_05_01_0004 a pr:DataElement ; + pr:hasIdentifier "DE-05.01-0004" ; pr:hasName "Promotion Type Brochure Flag" ; + pr:owningDomain ex:DD_05 ; pr:ownedBy ex:ST_DD_05 ; + pr:hasFormat "BOOLEAN" ; pr:hasSourceSystem "SODH Gold (flag)" ; + pr:physicalName "GL_PROMO_BROCHURE_FLG" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold (flag) / GL_PROMO_BROCHURE_FLG" . + +ex:DO_06_01_001 a pr:DataObject ; + pr:hasIdentifier "DO-06.01-001" ; pr:hasName "Sell Out Volume Dataset" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:implements ex:BO_06_01_001 ; pr:hasSteward ex:ST_DD_06 ; + pr:hasElement ex:DE_05_01_0001 , ex:DE_05_01_0002 , ex:DE_05_01_0003 , ex:DE_05_01_0004 ; + pr:references ex:DO_04_01_001 , ex:DO_10_01_001 , ex:DO_21_01_001 , ex:DO_16_01_001 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / F_SELL_OUT" . +ex:DO_06_01_003 a pr:DataObject ; + pr:hasIdentifier "DO-06.01-003" ; pr:hasName "Sell Out Distribution Dataset" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:implements ex:BO_06_01_003 ; pr:hasSteward ex:ST_DD_06 ; + pr:hasElement ex:DE_05_01_0001 , ex:DE_05_01_0002 , ex:DE_05_01_0003 , ex:DE_05_01_0004 ; + pr:references ex:DO_04_01_001 , ex:DO_10_01_001 , ex:DO_21_01_001 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / F_SELL_OUT_DISTRIBUTION" . +ex:DO_10_01_001 a pr:DataObject ; + pr:hasIdentifier "DO-10.01-001" ; pr:hasName "Product Dimension" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:implements ex:BO_10_01_001 ; pr:hasSteward ex:ST_DD_10 ; + pr:hasElement ex:DE_10_01_0001 , ex:DE_10_01_0002 , ex:DE_10_01_0003 , ex:DE_10_01_0004 , ex:DE_10_01_0005 , ex:DE_10_01_0006 , ex:DE_10_01_0007 , ex:DE_10_01_0008 , ex:DE_10_01_0009 , ex:DE_10_01_0010 , ex:DE_10_01_0011 , ex:DE_10_01_0012 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / D_FINISHED_PRODUCT" . +ex:DO_04_01_001 a pr:DataObject ; + pr:hasIdentifier "DO-04.01-001" ; pr:hasName "Customer Dimension" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:implements ex:BO_04_01_001 ; pr:hasSteward ex:ST_DD_04 ; + pr:hasElement ex:DE_04_01_0001 , ex:DE_04_01_0002 , ex:DE_04_01_0003 , ex:DE_04_01_0004 , ex:DE_04_01_0005 , ex:DE_04_01_0006 , ex:DE_04_01_0007 , ex:DE_04_01_0008 , ex:DE_04_01_0009 , ex:DE_04_01_0010 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / D_CUSTOMER" . +ex:DO_16_01_001 a pr:DataObject ; + pr:hasIdentifier "DO-16.01-001" ; pr:hasName "Currency Dimension" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:implements ex:BO_16_01_001 ; pr:hasSteward ex:ST_DD_16 ; + pr:hasElement ex:DE_16_01_0001 , ex:DE_16_01_0002 , ex:DE_16_01_0003 , ex:DE_16_02_0001 , ex:DE_16_02_0002 , ex:DE_16_02_0003 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / D_CURRENCY" . +ex:DO_21_01_001 a pr:DataObject ; + pr:hasIdentifier "DO-21.01-001" ; pr:hasName "Calendar Dimension" ; + pr:owningDomain ex:DD_21 ; pr:ownedBy ex:ST_DD_21 ; + pr:implements ex:BO_21_01_001 ; pr:hasSteward ex:ST_DD_21 ; + pr:hasElement ex:DE_21_01_0001 , ex:DE_21_01_0002 , ex:DE_21_01_0003 , ex:DE_21_01_0004 , ex:DE_21_01_0005 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / D_DATE" . diff --git a/ontology/pr_metamodel.ttl b/ontology/pr_metamodel.ttl new file mode 100644 index 0000000..f45e75c --- /dev/null +++ b/ontology/pr_metamodel.ttl @@ -0,0 +1,652 @@ +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . +@prefix dcterms: . +@prefix prov: . +@prefix pr: . + +################################################################# +# PERNOD RICARD DATA META MODEL - T-Box v1.0 +# Owner: Bastien Gourdon - Data Governance Office +# +# WHAT CHANGED vs v0.9.2 +# ---------------------- +# 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: CuratedObject vs ObservedObject. +# LAYER says WHAT an object is. PROVENANCE says HOW IT GOT HERE. +# - CuratedObject : asserted by governance, edited via pull request, +# subject to the full ownership shape. +# - ObservedObject: harvested 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 realizesMetric, +# 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). +################################################################# + + + a owl:Ontology ; + owl:versionIRI ; + owl:priorVersion ; + dcterms:title "Pernod Ricard Data Meta Model" ; + dcterms:creator "Bastien Gourdon" ; + dcterms:modified "2026-07-25"^^xsd:date ; + owl:versionInfo "1.0.0-draft-2026-07-25" ; + rdfs:comment """OWL T-Box. Five layers (Ownership, Business, Logical, Physical, Consumption) crossed with a provenance axis (Curated vs Observed). Curated objects are asserted by governance and reviewed; Observed objects are harvested 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 Observed terms: the system view or API that populates this term. Documents the contract between the meta model and the harvester." . + +pr:curationMode a owl:AnnotationProperty ; + rdfs:label "curation mode" ; + rdfs:comment "ASSERTED (governance writes it), HARVESTED (a job writes it) or PROPOSED_CONFIRMED (a job proposes, a steward confirms)." . + + +################################################################# +# 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:CuratedObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ; + rdfs:label "Curated Object" ; + pr:curationMode "ASSERTED" ; + rdfs:comment "Asserted by governance, edited through pull request on the domain TTL file, subject to the full ownership and lifecycle shape (identifier, name, owner, owning Data Domain, status, version)." . + +pr:ObservedObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ; + rdfs:label "Observed Object" ; + pr:curationMode "HARVESTED" ; + rdfs:comment "Harvested from a running system and rebuilt on every run. Never hand-edited. Carries sourceIdentifier, fullyQualifiedName and harvestedOn instead of curated ownership: attribution is INFERRED through the Data Element it is mapped to, not asserted on the node." . + +pr:OwnershipLayerObject a owl:Class ; rdfs:subClassOf pr:CuratedObject ; + rdfs:label "Ownership & Categorization Layer Object" . +pr:BusinessLayerObject a owl:Class ; rdfs:subClassOf pr:CuratedObject ; + rdfs:label "Business Layer Object" . +pr:LogicalLayerObject a owl:Class ; rdfs:subClassOf pr:CuratedObject ; + rdfs:label "Logical Layer Object" . +pr:PhysicalLayerObject a owl:Class ; rdfs:subClassOf pr:ObservedObject ; + rdfs:label "Physical Layer Object" . +pr:ConsumptionLayerObject a owl:Class ; rdfs:subClassOf pr:ObservedObject ; + 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." . + + +################################################################# +# LAYER 1 - OWNERSHIP & CATEGORIZATION (curated) +################################################################# + +pr:DataDomain a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject ; + rdfs:label "Data Domain" . + +pr:SubDomain a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject ; + rdfs:label "Sub-Domain" . + +pr:BusinessObject a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject ; + rdfs:label "Business Object" ; + rdfs:comment "The unit of ownership attribution. Sits in the Ownership layer rather than the Business layer on purpose: a Business Object is what a Data Domain is accountable for, and what a Data Object implements. Its business meaning is carried by the Business Concepts it groups." . + + +################################################################# +# LAYER 2 - BUSINESS (curated) +################################################################# + +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: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: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." . + + +################################################################# +# LAYER 3 - LOGICAL (curated) +################################################################# + +pr:DataObject a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject ; + rdfs:label "Data Object" ; + rdfs:comment "The pivot: implements 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:DataElement a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject ; + rdfs:label "Data Element" ; + pr:curationMode "PROPOSED_CONFIRMED" ; + rdfs:comment "The finest curated unit. Its mapping to a physical Field is the ONLY place where the curated graph and the observed graph meet: proposed automatically by name and type matching, confirmed by a Data Steward." . + +pr:DataAsset a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject ; + rdfs:label "Data Asset" ; + rdfs:comment "Directly consumable by a business user. A reusable building block is a Data Object, not a Data Asset (rule BR-009)." . + +pr:DataProduct a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject ; + rdfs:label "Data Product" ; + rdfs:comment "The unit of delivery: packages Data Assets and is governed by a Data Contract. Candidate alignment: dcat:Dataset / DPROD." . + +pr:DataContract a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject ; + rdfs:label "Data Contract" ; + rdfs:comment "The service commitment attached to a Data Product: schema, freshness, quality thresholds, support. Candidate alignment: ODCS (Bitol)." . + + +################################################################# +# LAYER 4 - PHYSICAL (observed) +################################################################# + +pr:System a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject ; + rdfs:label "System" ; + pr:harvestSource "Manually seeded once, then referenced. Replaces the free-text hasSourceSystem." ; + 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:Database a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject ; + rdfs:label "Database" ; + pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.DATABASES" . + +pr:Schema a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject ; + rdfs:label "Schema" ; + pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.SCHEMATA" . + +pr:PhysicalRelation a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject ; + rdfs:label "Physical Relation" ; + pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.TABLES" ; + rdfs:comment "Abstract parent of everything with columns. Specialised because impact traversal differs by kind: a View propagates impact to whatever it reads." . + +pr:BaseTable a owl:Class ; rdfs:subClassOf pr:PhysicalRelation ; + rdfs:label "Base Table" . + +pr:View a owl:Class ; rdfs:subClassOf pr:PhysicalRelation ; + rdfs:label "View" ; + rdfs:comment "Depends on other relations through dependsOn, so impact is transitive." . + +pr:MaterializedView a owl:Class ; rdfs:subClassOf pr:PhysicalRelation ; + rdfs:label "Materialized View" . + +pr:ExternalTable a owl:Class ; rdfs:subClassOf pr:PhysicalRelation ; + rdfs:label "External Table" ; + rdfs:comment "Backed by files outside the warehouse. Marks the boundary where lineage leaves Snowflake." . + +pr:Table a owl:Class ; rdfs:subClassOf pr:PhysicalRelation ; + rdfs:label "Table (deprecated)" ; + owl:deprecated true ; + dcterms:isReplacedBy pr:BaseTable ; + rdfs:comment "v0.9 term. Kept so that existing assertions still classify. Use BaseTable." . + +pr:Field a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject ; + 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:KeyConstraint a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject ; + 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 , 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." . + + +################################################################# +# LAYER 5 - CONSUMPTION (observed) +################################################################# + +pr:BIWorkspace a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject ; + rdfs:label "BI Workspace" ; + pr:harvestSource "Tableau Metadata API (Project/Site) ; Looker API (Model)" . + +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: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. realizesMetric is what turns a certified Metric from a definition on paper into something measurable against the real BI estate." . + +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." . + + +################################################################# +# 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:DataDomainOwner a owl:Class ; rdfs:subClassOf pr:Actor ; rdfs:label "Data Domain Owner" . +pr:DataGovernanceLead a owl:Class ; rdfs:subClassOf pr:Actor ; rdfs:label "Data Governance Lead (DGL)" . +pr:DataSteward a owl:Class ; rdfs:subClassOf pr:Actor ; rdfs:label "Data 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:belongsToDomain a owl:ObjectProperty , owl:FunctionalProperty ; + rdfs:label "belongs to domain" ; + rdfs:domain pr:SubDomain ; rdfs:range pr:DataDomain . + +pr:belongsToSubDomain a owl:ObjectProperty , owl:FunctionalProperty ; + rdfs:label "belongs to sub-domain" ; + rdfs:domain pr:BusinessObject ; rdfs:range pr:SubDomain . + +pr:composedOf a owl:ObjectProperty ; + rdfs:label "composed of" ; + rdfs:domain pr:BusinessObject ; rdfs:range pr:BusinessConcept ; + owl:inverseOf pr:isComponentOf ; + rdfs:comment "Part-whole. In v0.9 this was declared owl:inverseOf pr:describes, which was semantically wrong: composition and aboutness are different relations, and a reasoner would infer that every Concept describing an Object is also a component of it." . + +pr:isComponentOf a owl:ObjectProperty ; + rdfs:label "is component of" ; + rdfs:domain pr:BusinessConcept ; rdfs:range pr:BusinessObject . + +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:hasGrainDimension a owl:ObjectProperty ; + rdfs:label "has grain dimension" ; + rdfs:domain pr:Metric ; rdfs:range pr:DataObject ; + rdfs:comment "The dimensional Data Objects that define the Metric's grain. Replaces reading grain out of the free-text hasGranularity string: only a real reference lets you detect that two Metrics cannot be aggregated together." . + +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:implements a owl:ObjectProperty , owl:FunctionalProperty ; + rdfs:label "implements" ; + rdfs:domain pr:DataObject ; rdfs:range pr:BusinessObject ; + rdfs:comment "The pivot. Exactly one, enforced by SHACL (BR-006)." . + +pr:hasElement a owl:ObjectProperty ; + rdfs:label "has element" ; + rdfs:domain pr:DataObject ; rdfs:range pr:DataElement . + +pr:references a owl:ObjectProperty ; + rdfs:label "references" ; + rdfs:domain pr:DataObject ; rdfs:range pr:DataObject ; + rdfs:comment "Foreign-key reference between Data Objects. NOT ownership. Now checkable against the harvested ForeignKeyConstraint objects." . + +pr:usesDataObject a owl:ObjectProperty ; + rdfs:label "uses Data Object" ; + rdfs:domain pr:DataAsset ; rdfs:range pr:DataObject . + +pr:packages a owl:ObjectProperty ; + rdfs:label "packages" ; + rdfs:domain pr:DataProduct ; rdfs:range pr:DataAsset . + +pr:governedBy a owl:ObjectProperty ; + rdfs:label "governed by" ; + rdfs:domain pr:DataProduct ; rdfs:range pr:DataContract . + + +################################################################# +# OBJECT PROPERTIES - the curated / observed 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" ; + rdfs:domain pr:DataObject ; rdfs:range pr:PhysicalRelation ; + pr:curationMode "PROPOSED_CONFIRMED" ; + rdfs:comment "Data Object to physical relation. Not functional: the same Data Object exists in DEV, UAT and PROD, and sometimes across several tables." . + +pr:physicalizedIn a owl:ObjectProperty ; + rdfs:label "physicalized in" ; + rdfs:domain pr:DataElement ; rdfs:range pr:Field ; + pr:curationMode "PROPOSED_CONFIRMED" ; + rdfs:comment "Data Element to physical column, 1..n. Proposed by the reconciliation job on fullyQualifiedName and type, confirmed by a Data Steward. This single edge is what makes end-to-end impact analysis possible." . + + +################################################################# +# OBJECT PROPERTIES - Physical +################################################################# + +pr:sourcedFrom a owl:ObjectProperty ; + rdfs:label "sourced from" ; + rdfs:range pr:System ; + rdfs:comment "Replaces the free-text hasSourceSystem. Domain deliberately omitted: applies to Data Elements, Physical Relations and BI Data Sources alike." . + +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:domain pr:PhysicalRelation ; rdfs:range pr:Schema . + +pr:hasField a owl:ObjectProperty ; + rdfs:label "has field" ; + rdfs:domain pr:PhysicalRelation ; 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:PhysicalRelation . + +pr:deployedIn a owl:ObjectProperty , owl:FunctionalProperty ; + rdfs:label "deployed in" ; + rdfs:domain pr:ObservedObject ; rdfs:range pr:Environment . + +pr:constrainsRelation a owl:ObjectProperty , owl:FunctionalProperty ; + rdfs:label "constrains relation" ; + rdfs:domain pr:KeyConstraint ; rdfs:range pr:PhysicalRelation . + +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:PhysicalRelation . + +pr:produces a owl:ObjectProperty ; + rdfs:label "produces" ; + rdfs:domain pr:Transformation ; rdfs:range pr:PhysicalRelation . + +pr:dependsOn a owl:ObjectProperty , owl:TransitiveProperty ; + rdfs:label "depends on" ; + rdfs:domain pr:PhysicalRelation ; rdfs:range pr:PhysicalRelation ; + 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:PhysicalRelation ; + 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:realizesMetric a owl:ObjectProperty , owl:FunctionalProperty ; + rdfs:label "realizes metric" ; + rdfs:domain pr:BIField ; rdfs:range pr:Metric ; + pr:curationMode "PROPOSED_CONFIRMED" ; + rdfs:comment "The harmonisation proof. Counting distinct BI Fields realizing one certified Metric, and comparing their expressions, turns 'we harmonised the definitions' from a claim into a measurement over the real BI estate." . + +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. Required on Curated Objects, absent on Observed Objects: a Snowflake column is attributed through the Data Element mapped to it, never asserted directly." . + +pr:hasSteward a owl:ObjectProperty ; + rdfs:label "has Data Steward" ; rdfs:range pr:DataSteward . + +pr:hasDGL a owl:ObjectProperty , owl:FunctionalProperty ; + rdfs:label "has DGL" ; + rdfs:domain pr:DataDomain ; rdfs:range pr:DataGovernanceLead . + +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: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 "back-doc source" ; rdfs:range xsd:string . +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 . +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 (label)" ; rdfs:range xsd:string ; + rdfs:comment "Human-readable grain label, e.g. 'SKU x Customer x Day'. Keep in sync with hasGrainDimension, which is the machine-readable form." . +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. An Observed 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" ; rdfs:range xsd:string . +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" ; rdfs:range xsd:integer . +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:CuratedObject pr:ObservedObject ) . + +[] a owl:AllDisjointClasses ; owl:members ( + pr:OwnershipLayerObject pr:BusinessLayerObject pr:LogicalLayerObject + pr:PhysicalLayerObject pr:ConsumptionLayerObject ) . + +[] a owl:AllDisjointClasses ; owl:members ( + pr:BusinessObject pr:DataObject pr:DataAsset pr:DataProduct ) . + +[] a owl:AllDisjointClasses ; owl:members ( pr:BusinessConcept pr:Metric ) . + +[] a owl:AllDisjointClasses ; owl:members ( + pr:BaseTable pr:View pr:MaterializedView pr:ExternalTable ) . + +[] a owl:AllDisjointClasses ; owl:members ( pr:PrimaryKey pr:ForeignKeyConstraint ) . + +[] a owl:AllDisjointClasses ; owl:members ( + pr:BIWorkspace pr:BIDataSource pr:BIField pr:BIReport ) . diff --git a/scripts/generate_tbox_viewer.py b/scripts/generate_tbox_viewer.py new file mode 100644 index 0000000..daa8df3 --- /dev/null +++ b/scripts/generate_tbox_viewer.py @@ -0,0 +1,979 @@ +#!/usr/bin/env python3 +""" +PR Data Meta Model - T-Box viewer generator +=========================================== +Reads the T-Box Turtle file and emits a self-contained, zero-dependency HTML +viewer. The TTL is the source of truth; the viewer is a build artefact and must +never be edited by hand. + +USAGE + python generate_tbox_viewer.py [tbox.ttl] [out.html] + + Defaults: pr_datametamodel_v1_0.ttl -> pr_metamodel_viewer_v1_0.html + +Uses rdflib when available; otherwise falls back to a restricted Turtle reader +that handles the subset of syntax used by this T-Box. Run it in CI on every +merge so the viewer can never drift from the model. +""" +import json +import re +import sys +from collections import OrderedDict + +PR = "https://ontology.pernod-ricard.com/metamodel/" + +WANTED = { + "a": "type", "rdf:type": "type", + "rdfs:label": "label", "rdfs:comment": "comment", + "rdfs:subClassOf": "subClassOf", "rdfs:subPropertyOf": "subPropertyOf", + "rdfs:domain": "domain", "rdfs:range": "range", + "owl:inverseOf": "inverseOf", "owl:deprecated": "deprecated", + "dcterms:isReplacedBy": "isReplacedBy", + "pr:harvestSource": "harvestSource", "pr:curationMode": "curationMode", +} + +# -------------------------------------------------------------------------- +# Turtle reading +# -------------------------------------------------------------------------- + +def strip_comments(text): + """Remove # comments that sit outside string literals.""" + out, i, n = [], 0, len(text) + while i < n: + c = text[i] + if text.startswith('"""', i): + j = text.find('"""', i + 3) + j = n if j == -1 else j + 3 + out.append(text[i:j]); i = j; continue + if c == '"': + j = i + 1 + while j < n and text[j] != '"': + j += 2 if text[j] == "\\" else 1 + j = min(j + 1, n) + out.append(text[i:j]); i = j; continue + if c == "#": + j = text.find("\n", i) + i = n if j == -1 else j + continue + out.append(c); i += 1 + return "".join(out) + + +def split_top(text, sep): + """Split on `sep` at nesting depth 0, ignoring string literals.""" + parts, buf, depth, i, n = [], [], 0, 0, len(text) + while i < n: + c = text[i] + if text.startswith('"""', i): + j = text.find('"""', i + 3) + j = n if j == -1 else j + 3 + buf.append(text[i:j]); i = j; continue + if c == '"': + j = i + 1 + while j < n and text[j] != '"': + j += 2 if text[j] == "\\" else 1 + j = min(j + 1, n) + buf.append(text[i:j]); i = j; continue + if c in "([": + depth += 1 + elif c in ")]": + depth -= 1 + if c == sep and depth == 0: + parts.append("".join(buf)); buf = []; i += 1; continue + buf.append(c); i += 1 + if "".join(buf).strip(): + parts.append("".join(buf)) + return parts + + +def tokens(text): + """Split an object list into individual terms.""" + return [t.strip() for t in split_top(text, ",") if t.strip()] + + +def unquote(tok): + tok = tok.strip() + if tok.startswith('"""'): + return tok[3:tok.rindex('"""')] if tok.count('"""') > 1 else tok[3:] + if tok.startswith('"'): + end = tok.rindex('"') + return tok[1:end] if end > 0 else tok[1:] + return tok + + +def parse_ttl_fallback(text): + text = strip_comments(text) + subjects = OrderedDict() + for stmt in split_top(text, "."): + stmt = stmt.strip() + if not stmt: + continue + chunks = split_top(stmt, ";") + head = chunks[0].strip() + m = re.match(r"^(\S+)\s+(.*)$", head, re.S) + if not m: + continue + subj, rest = m.group(1), m.group(2) + if not subj.startswith("pr:"): + continue + rec = subjects.setdefault(subj, {}) + for chunk in [rest] + [c.strip() for c in chunks[1:]]: + chunk = chunk.strip() + if not chunk: + continue + pm = re.match(r"^(\S+)\s+(.*)$", chunk, re.S) + if not pm: + continue + pred, objs = pm.group(1), pm.group(2) + key = WANTED.get(pred) + if not key: + continue + rec.setdefault(key, []).extend(unquote(t) for t in tokens(objs)) + return subjects + + +def parse_ttl_rdflib(path): + from rdflib import Graph, RDF, RDFS, OWL, URIRef, Namespace + DCT = Namespace("http://purl.org/dc/terms/") + g = Graph(); g.parse(path, format="turtle") + inv = { + RDF.type: "type", RDFS.label: "label", RDFS.comment: "comment", + RDFS.subClassOf: "subClassOf", RDFS.subPropertyOf: "subPropertyOf", + RDFS.domain: "domain", RDFS.range: "range", + OWL.inverseOf: "inverseOf", OWL.deprecated: "deprecated", + DCT.isReplacedBy: "isReplacedBy", + URIRef(PR + "harvestSource"): "harvestSource", + URIRef(PR + "curationMode"): "curationMode", + } + + def short(term): + s = str(term) + for pfx, ns in (("pr:", PR), + ("owl:", "http://www.w3.org/2002/07/owl#"), + ("rdfs:", "http://www.w3.org/2000/01/rdf-schema#"), + ("xsd:", "http://www.w3.org/2001/XMLSchema#"), + ("prov:", "http://www.w3.org/ns/prov#")): + if s.startswith(ns): + return pfx + s[len(ns):] + return s + + subjects = OrderedDict() + for s, p, o in g: + key = inv.get(p) + if key is None or not str(s).startswith(PR): + continue + rec = subjects.setdefault(short(s), {}) + rec.setdefault(key, []).append(str(o) if key in ("label", "comment", "harvestSource", "curationMode", "deprecated") else short(o)) + return subjects + + +def read_tbox(path): + try: + return parse_ttl_rdflib(path), "rdflib" + except ImportError: + with open(path, encoding="utf-8") as fh: + return parse_ttl_fallback(fh.read()), "built-in reader" + + +# -------------------------------------------------------------------------- +# Model assembly +# -------------------------------------------------------------------------- + +BANDS = [ + ("roots", "Model roots", "How a term got here", "#0A1F44"), + ("ownership", "Ownership & Categorization", "Who is accountable", "#B8935A"), + ("business", "Business", "What the business means", "#8B3A3A"), + ("logical", "Logical", "What we commit to deliver", "#7A4E8B"), + ("physical", "Physical", "What actually runs", "#1E4B9B"), + ("consumption", "Consumption", "What people open in the morning", "#1E7A5F"), + ("context", "Actors & vocabularies", "Outside the object stack", "#6B6B6B"), +] + +LAYER_ROOT = { + "pr:OwnershipLayerObject": "ownership", + "pr:BusinessLayerObject": "business", + "pr:LogicalLayerObject": "logical", + "pr:PhysicalLayerObject": "physical", + "pr:ConsumptionLayerObject": "consumption", +} + + +def first(rec, key, default=""): + v = rec.get(key) + return v[0] if v else default + + +def build(subjects): + classes, objprops, dataprops, annprops, individuals = {}, {}, {}, {}, {} + for term, rec in subjects.items(): + types = rec.get("type", []) + if "owl:Class" in types: + classes[term] = rec + elif "owl:ObjectProperty" in types: + objprops[term] = rec + elif "owl:DatatypeProperty" in types: + dataprops[term] = rec + elif "owl:AnnotationProperty" in types: + annprops[term] = rec + elif types and types[0].startswith("pr:"): + individuals.setdefault(types[0], []).append(term) + + # layer + provenance by walking subClassOf upward + def ancestry(term, seen=None): + seen = seen or set() + if term in seen: + return [] + seen.add(term) + out = [term] + for parent in classes.get(term, {}).get("subClassOf", []): + out += ancestry(parent, seen) + return out + + nodes = [] + for term, rec in classes.items(): + anc = ancestry(term) + band = next((LAYER_ROOT[a] for a in anc if a in LAYER_ROOT), None) + if band is None: + if "pr:Actor" in anc or term in ("pr:ActivationStatus", "pr:Environment", "pr:SystemType"): + band = "context" + else: + band = "roots" + provenance = ("observed" if "pr:ObservedObject" in anc + else "curated" if "pr:CuratedObject" in anc else "neutral") + nodes.append({ + "id": term, + "label": first(rec, "label", term.split(":")[1]), + "band": band, + "prov": provenance, + "parent": first(rec, "subClassOf"), + "comment": first(rec, "comment"), + "harvest": first(rec, "harvestSource"), + "curation": first(rec, "curationMode"), + "deprecated": first(rec, "deprecated") in ("true", "True"), + "replacedBy": first(rec, "isReplacedBy"), + "members": [ + {"id": m, "label": first(subjects.get(m, {}), "label", m.split(":")[1])} + for m in individuals.get(term, []) + ], + }) + + known = {n["id"] for n in nodes} + edges, universal = [], [] + for term, rec in objprops.items(): + dom, rng = first(rec, "domain"), first(rec, "range") + item = { + "id": term, + "label": first(rec, "label", term.split(":")[1]), + "s": dom, "t": rng, + "comment": first(rec, "comment"), + "harvest": first(rec, "harvestSource"), + "curation": first(rec, "curationMode"), + "inverseOf": first(rec, "inverseOf"), + "subPropertyOf": first(rec, "subPropertyOf"), + "functional": "owl:FunctionalProperty" in rec.get("type", []), + "transitive": "owl:TransitiveProperty" in rec.get("type", []), + "deprecated": first(rec, "deprecated") in ("true", "True"), + "replacedBy": first(rec, "isReplacedBy"), + } + if dom in known and rng in known: + edges.append(item) + else: + item["s"] = dom or "pr:MetaModelObject" + universal.append(item) + + datatypes = [{ + "id": t, "label": first(r, "label", t.split(":")[1]), + "range": first(r, "range"), "comment": first(r, "comment"), + "harvest": first(r, "harvestSource"), + "functional": "owl:FunctionalProperty" in r.get("type", []), + "deprecated": first(r, "deprecated") in ("true", "True"), + "replacedBy": first(r, "isReplacedBy"), + } for t, r in dataprops.items()] + + bridges = [{ + "id": t, "label": first(r, "label", t.split(":")[1]), + "comment": first(r, "comment"), + } for t, r in annprops.items() if t.startswith("pr:denotes")] + + onto = subjects.get("", {}) + return { + "bands": [{"id": b[0], "title": b[1], "tag": b[2], "color": b[3]} for b in BANDS], + "nodes": nodes, "edges": edges, "universal": universal, + "datatypes": datatypes, "bridges": bridges, + } + + +# -------------------------------------------------------------------------- +# HTML emission +# -------------------------------------------------------------------------- + +def emit(model, version, src_name, out_path): + payload = json.dumps(model, ensure_ascii=False, separators=(",", ":")) + html = (TEMPLATE.replace("__MODEL__", payload) + .replace("__SRC__", src_name) + .replace("__VERSION__", version)) + with open(out_path, "w", encoding="utf-8") as fh: + fh.write(html) + + +TEMPLATE = r""" + + + + +PR Data Meta Model · T-Box __VERSION__ + + + + +
+ +
+

PR Data Meta Model · T-Box

+
Five layers, crossed with a provenance axis: what governance asserts, what harvesters observe
+
+
+
0Classes
+
0Relations
+
0Attributes
+
0Seam edges
+
+
+ +
+ + +
+ + + + + + + + + + + + +
+ + + +
+
drag to pan · scroll to zoom · click a class
+
+ + +
+ +
+ Build artefact of __SRC__ — regenerate, never edit + T-Box __VERSION__ +
+ + + + +""" + + +def main(): + src = sys.argv[1] if len(sys.argv) > 1 else "pr_datametamodel_v1_0.ttl" + out = sys.argv[2] if len(sys.argv) > 2 else "pr_metamodel_viewer_v1_0.html" + subjects, engine = read_tbox(src) + model = build(subjects) + import os + emit(model, "v1.0", os.path.basename(src), out) + print("PR META MODEL - T-Box viewer") + print(" source : %s (parsed with %s)" % (src, engine)) + print(" output : %s" % out) + print(" classes %d | object properties %d | datatype properties %d | bridge annotations %d" + % (len(model["nodes"]), + len(model["edges"]) + len(model["universal"]), + len(model["datatypes"]), len(model["bridges"]))) + for b in model["bands"]: + c = len([n for n in model["nodes"] if n["band"] == b["id"]]) + if c: + print(" %-26s %d" % (b["title"], c)) + + +if __name__ == "__main__": + main() diff --git a/shapes/pr_metamodel_shapes.ttl b/shapes/pr_metamodel_shapes.ttl new file mode 100644 index 0000000..d97ffc2 --- /dev/null +++ b/shapes/pr_metamodel_shapes.ttl @@ -0,0 +1,127 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . +@prefix sh: . +@prefix pr: . +@prefix prsh: . + +################################################################# +# PR Data Meta Model - SHACL Shapes v0.1.1 +# Executable encoding of BLOCKING (sh:Violation) & MAJOR (sh:Warning) rules. +# FIX v0.1.1 (2026-07-23): NR-002 uniqueness query now restricts ?cls to +# LEAF classes (via FILTER ... IN) so that objects of different layers that +# share a name (e.g. Sub-Domain "Sell Out" vs Concept "Sell Out") are NOT +# reported as duplicates. Rule stays "unique WITHIN its class". +# Run: python run_shacl_validation.py SODH_data.ttl pr_metamodel_shapes_v0.1.ttl pr_metamodel_v0.9.ttl +################################################################# + +prsh:MetaModelObjectShape a sh:NodeShape ; + sh:targetClass pr:MetaModelObject ; + sh:property [ sh:path pr:hasIdentifier ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ; + sh:severity sh:Violation ; sh:message "DQ-001 (BLOCKING): exactly one hasIdentifier." ] ; + sh:property [ sh:path pr:hasName ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ; sh:minLength 2 ; + sh:severity sh:Violation ; sh:message "DQ-002 (BLOCKING): exactly one non-empty hasName." ] ; + sh:property [ sh:path pr:ownedBy ; sh:minCount 1 ; sh:class pr:Actor ; + sh:severity sh:Violation ; sh:message "OW-001 (BLOCKING): at least one owner (Actor)." ] ; + sh:property [ sh:path pr:owningDomain ; sh:minCount 1 ; sh:maxCount 1 ; sh:class pr:DataDomain ; + sh:severity sh:Violation ; sh:message "OW-001 (BLOCKING): exactly one owning Data Domain." ] ; + sh:property [ sh:path pr:hasStatus ; sh:minCount 1 ; sh:in ( "DRAFT" "UNDER_REVIEW" "PUBLISHED" "DEPRECATED" ) ; + sh:severity sh:Warning ; sh:message "DQ-012 (MAJOR): hasStatus in controlled list." ] ; + sh:property [ sh:path pr:hasVersion ; sh:minCount 1 ; + sh:severity sh:Warning ; sh:message "DQ-012 (MAJOR): hasVersion required." ] . + +prsh:BackDocSourceShape a sh:NodeShape ; + sh:targetClass pr:MetaModelObject ; + sh:sparql [ sh:severity sh:Violation ; + sh:message "DQ-006 (BLOCKING): a DRAFT back-doc object MUST carry hasSource." ; + sh:select """ + PREFIX pr: + SELECT $this WHERE { $this pr:hasStatus "DRAFT" . + FILTER NOT EXISTS { $this pr:hasSource ?s } } + """ ] . + +prsh:CanonicalNameShape a sh:NodeShape ; + sh:targetClass pr:MetaModelObject ; + sh:property [ sh:path pr:hasName ; + sh:pattern "^(?!GL_)(?!ID_).*(? + SELECT $this WHERE { + $this a ?cls ; pr:hasName ?n ; pr:owningDomain ?d . + ?other a ?cls ; pr:hasName ?n ; pr:owningDomain ?d . + FILTER ( $this != ?other ) + FILTER ( ?cls IN ( + pr:DataDomain, pr:SubDomain, pr:BusinessObject, + pr:BusinessConcept, pr:Metric, pr:KPI, + pr:DataAsset, pr:DataObject, pr:DataElement, + pr:DataContract, pr:DataProduct, pr:Table, pr:Field ) ) + } + """ ] . + +prsh:ArbitrationGateShape a sh:NodeShape ; + sh:targetClass pr:MetaModelObject ; + sh:sparql [ sh:severity sh:Violation ; + sh:message "OW-007 (BLOCKING): a TO_ARBITRATE object MUST NOT be PUBLISHED." ; + sh:select """ + PREFIX pr: + SELECT $this WHERE { + $this pr:hasStatus "PUBLISHED" . + $this pr:arbitrationStatus "TO_ARBITRATE" . } + """ ] .