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

This commit is contained in:
Bastien Gourdon
2026-07-26 21:28:14 +02:00
commit 9cfa73b373
8 changed files with 3228 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
.env
venv/
__pycache__/
*.pyc
*.log
*.pid
*.bak
*.bak_*
@eaDir/
.DS_Store
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
+564
View File
@@ -0,0 +1,564 @@
@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 pr: <https://ontology.pernod-ricard.com/metamodel/0.9/> .
@prefix ex: <https://data.pernod-ricard.com/sodh/> .
#################################################################
# 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" .
+652
View File
@@ -0,0 +1,652 @@
@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.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).
#################################################################
<https://ontology.pernod-ricard.com/metamodel/>
a owl:Ontology ;
owl:versionIRI <https://ontology.pernod-ricard.com/metamodel/1.0> ;
owl:priorVersion <https://ontology.pernod-ricard.com/metamodel/0.9> ;
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 ) .
+979
View File
@@ -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("<https://ontology.pernod-ricard.com/metamodel/>", {})
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"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PR Data Meta Model &middot; T-Box __VERSION__</title>
<style>
:root{
--navy:#0A1F44; --gold:#B8935A; --ink:#1A1A1A;
--bg:#F7F6F2; --panel:#FFFFFF; --rule:#E4E2DA; --muted:#6B6B6B;
--c-roots:#0A1F44; --c-ownership:#B8935A; --c-business:#8B3A3A;
--c-logical:#7A4E8B; --c-physical:#1E4B9B; --c-consumption:#1E7A5F;
--c-context:#6B6B6B;
--mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{height:100%;margin:0}
body{font-family:var(--sans);background:var(--bg);color:var(--ink);
display:flex;flex-direction:column;overflow:hidden}
:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* ---------- chrome ---------- */
header{background:var(--navy);color:#fff;padding:10px 18px;display:flex;
align-items:center;gap:16px;flex-shrink:0;box-shadow:0 2px 6px rgba(0,0,0,.15)}
header h1{margin:0;font-family:var(--mono);font-size:13px;font-weight:600;
letter-spacing:.16em;text-transform:uppercase}
header .sub{font-size:11px;color:rgba(255,255,255,.62);margin-top:3px}
.stats{margin-left:auto;display:flex;gap:20px}
.stat{text-align:right;line-height:1.15}
.stat b{display:block;font-family:var(--mono);font-size:16px;color:var(--gold)}
.stat span{font-size:9px;text-transform:uppercase;letter-spacing:.09em;
color:rgba(255,255,255,.55)}
#mtoggle{display:none;background:transparent;color:#fff;
border:1px solid rgba(255,255,255,.4);padding:5px 10px;border-radius:3px;
font-size:11px;font-weight:600;cursor:pointer;font-family:inherit}
main{flex:1;display:flex;min-height:0;position:relative}
/* ---------- sidebar ---------- */
aside{width:255px;background:var(--panel);border-right:1px solid var(--rule);
overflow-y:auto;padding:16px;flex-shrink:0}
aside h2{font-family:var(--mono);font-size:9.5px;text-transform:uppercase;
letter-spacing:.14em;color:var(--muted);margin:22px 0 9px;font-weight:600}
aside h2:first-child{margin-top:0}
#q{width:100%;padding:8px 10px;border:1px solid var(--rule);border-radius:3px;
font-family:var(--mono);font-size:12px;background:var(--bg)}
#q:focus{border-color:var(--navy);outline:none;background:#fff}
.chip{display:flex;align-items:center;gap:9px;padding:5px 0;font-size:12.5px;
cursor:pointer;user-select:none;border:none;background:none;width:100%;
text-align:left;font-family:inherit;color:inherit}
.chip .swatch{width:11px;height:11px;flex-shrink:0;border-radius:2px}
.chip .n{margin-left:auto;font-family:var(--mono);font-size:10px;color:var(--muted)}
.chip.off{opacity:.34}
.seg{display:flex;border:1px solid var(--rule);border-radius:3px;overflow:hidden}
.seg button{flex:1;padding:7px 4px;background:#fff;border:none;cursor:pointer;
font-family:var(--mono);font-size:9.5px;letter-spacing:.07em;
text-transform:uppercase;color:var(--muted)}
.seg button+button{border-left:1px solid var(--rule)}
.seg button.on{background:var(--navy);color:#fff}
.opt{display:flex;align-items:center;gap:8px;padding:4px 0;font-size:12px;cursor:pointer}
.opt input{accent-color:var(--navy);width:15px;height:15px;cursor:pointer}
.note{font-size:11px;line-height:1.55;color:var(--muted)}
.note code{font-family:var(--mono);font-size:10.5px;background:var(--bg);padding:1px 4px}
/* ---------- canvas ---------- */
#wrap{flex:1;min-width:0;position:relative;overflow:hidden;touch-action:none;
background:
linear-gradient(90deg,rgba(10,31,68,.035) 1px,transparent 1px) 0 0/28px 28px,
linear-gradient(180deg,#FCFBF7,#EFEDE3)}
svg{display:block;width:100%;height:100%;cursor:grab}
svg.drag{cursor:grabbing}
.bandrect{opacity:.5}
.bandline{stroke:var(--rule);stroke-width:1}
.bandlabel{font-family:var(--mono);font-size:10px;letter-spacing:.15em;
text-transform:uppercase;font-weight:600}
.bandtag{font-family:var(--sans);font-size:9.5px;fill:var(--muted);font-style:italic}
.node{cursor:pointer}
.node rect{stroke-width:1.5}
.node text{font-size:10px;pointer-events:none;text-anchor:middle;font-weight:500}
.node.dim{opacity:.13}
.node.sel rect{stroke-width:3}
.edge{fill:none;stroke:#9AA0AB;stroke-width:1.2;marker-end:url(#ar)}
.edge.bridge{stroke:var(--navy);stroke-width:2;marker-end:url(#arb)}
.edge.uni{stroke:#C3BFB4;stroke-dasharray:3 3;marker-end:url(#aru)}
.edge.dim{opacity:.07}
.edge.hot{stroke:var(--gold);stroke-width:2.6;marker-end:url(#arg)}
.elabel{font-family:var(--mono);font-size:8px;fill:#5A6270;text-anchor:middle;
pointer-events:none}
.elabel.dim{opacity:.06}
.ebg{fill:#F4F2EC}
#zoom{position:absolute;left:14px;bottom:14px;display:flex;flex-direction:column;
gap:3px;background:#fff;border:1px solid var(--rule);border-radius:4px;padding:3px}
#zoom button{width:32px;height:32px;background:#fff;border:none;border-radius:3px;
font-size:16px;cursor:pointer;color:var(--navy);font-weight:600;font-family:inherit}
#zoom button:hover{background:var(--bg)}
#hint{position:absolute;right:14px;bottom:14px;font-family:var(--mono);
font-size:9.5px;color:var(--muted);background:rgba(255,255,255,.82);
padding:5px 9px;border-radius:3px;letter-spacing:.04em}
/* ---------- inspector ---------- */
#insp{position:absolute;top:0;right:0;width:352px;max-width:100%;height:100%;
background:var(--panel);border-left:1px solid var(--rule);
box-shadow:-6px 0 24px rgba(10,31,68,.10);overflow-y:auto;padding:20px 20px 40px;
transform:translateX(100%);transition:transform .22s ease}
#insp.open{transform:translateX(0)}
#insp .close{position:absolute;top:12px;right:14px;border:none;background:none;
font-size:22px;cursor:pointer;color:var(--muted);line-height:1;padding:2px 6px}
#insp .kicker{font-family:var(--mono);font-size:9px;letter-spacing:.16em;
text-transform:uppercase;font-weight:600}
#insp h3{margin:6px 0 3px;font-size:19px;letter-spacing:-.01em}
#insp .iri{font-family:var(--mono);font-size:10.5px;color:var(--muted);
word-break:break-all;margin-bottom:14px}
#insp h4{font-family:var(--mono);font-size:9px;text-transform:uppercase;
letter-spacing:.14em;color:var(--muted);margin:20px 0 7px;font-weight:600;
border-top:1px solid var(--rule);padding-top:10px}
#insp p{font-size:12.5px;line-height:1.62;margin:0 0 8px}
.pill{display:inline-block;padding:3px 8px;border-radius:2px;font-family:var(--mono);
font-size:9px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;
margin:0 5px 5px 0;border:1px solid}
.rel{display:grid;grid-template-columns:1fr auto;gap:4px 10px;align-items:baseline;
padding:5px 0;border-bottom:1px dotted var(--rule);font-size:12px}
.rel .p{font-family:var(--mono);font-size:10.5px;color:var(--navy)}
.rel .o{color:var(--muted);font-size:11.5px;text-align:right}
.rel .flag{font-family:var(--mono);font-size:8.5px;color:var(--gold);
letter-spacing:.06em;text-transform:uppercase}
.jump{background:none;border:none;padding:0;font:inherit;color:var(--navy);
cursor:pointer;text-decoration:underline;text-underline-offset:2px}
.harvest{background:var(--bg);border-left:2px solid var(--gold);padding:9px 11px;
font-family:var(--mono);font-size:10.5px;line-height:1.55;color:#4A5160}
footer{background:var(--navy);color:rgba(255,255,255,.55);padding:6px 18px;
font-family:var(--mono);font-size:9.5px;letter-spacing:.06em;display:flex;
justify-content:space-between;flex-shrink:0}
@media(max-width:860px){
aside{position:absolute;z-index:30;height:100%;transform:translateX(-100%);
transition:transform .22s;box-shadow:4px 0 18px rgba(0,0,0,.16)}
aside.open{transform:translateX(0)}
#mtoggle{display:inline-block}
.stats{display:none}
#insp{width:100%}
#hint{display:none}
}
@media(prefers-reduced-motion:reduce){*{transition:none!important}}
</style>
</head>
<body>
<header>
<button id="mtoggle" aria-label="Toggle filters">&#9776;</button>
<div>
<h1>PR Data Meta Model &middot; T-Box</h1>
<div class="sub">Five layers, crossed with a provenance axis: what governance asserts, what harvesters observe</div>
</div>
<div class="stats">
<div class="stat"><b id="s-cls">0</b><span>Classes</span></div>
<div class="stat"><b id="s-obj">0</b><span>Relations</span></div>
<div class="stat"><b id="s-dat">0</b><span>Attributes</span></div>
<div class="stat"><b id="s-brg">0</b><span>Seam edges</span></div>
</div>
</header>
<main>
<aside id="side">
<h2>Find a term</h2>
<input id="q" type="search" placeholder="metric, field, lineage&hellip;" autocomplete="off">
<h2>Layout</h2>
<div class="seg" role="group">
<button id="m-strata" class="on">Strata</button>
<button id="m-tax">Taxonomy</button>
<button id="m-free">Network</button>
</div>
<h2>Layers</h2>
<div id="bands"></div>
<h2>Show</h2>
<label class="opt"><input type="checkbox" id="o-uni"> Universal relations</label>
<label class="opt"><input type="checkbox" id="o-dep"> Deprecated terms</label>
<label class="opt"><input type="checkbox" id="o-lab" checked> Relation names</label>
<h2>Reading it</h2>
<p class="note">
Solid fill is <b>curated</b>: governance writes it, a pull request reviews it.
Hatched outline is <b>observed</b>: a harvester writes it nightly and no one
edits it by hand.<br><br>
The two navy edges are the seam &mdash; <code>materializedAs</code> and
<code>physicalizedIn</code>. They are the only place the two worlds meet,
and the only mapping a steward has to confirm.
</p>
</aside>
<div id="wrap">
<svg id="svg" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="ar" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#9AA0AB"/></marker>
<marker id="arb" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#0A1F44"/></marker>
<marker id="aru" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#C3BFB4"/></marker>
<marker id="arg" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#B8935A"/></marker>
<pattern id="hatch" width="5" height="5" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="5" stroke="#0A1F44" stroke-width="1.1" opacity=".16"/>
</pattern>
</defs>
<g id="vp"><g id="bL"></g><g id="eL"></g><g id="tL"></g><g id="nL"></g></g>
</svg>
<div id="zoom">
<button id="z-in" aria-label="Zoom in">+</button>
<button id="z-out" aria-label="Zoom out">&minus;</button>
<button id="z-fit" aria-label="Fit to view" style="font-size:13px">&#8635;</button>
</div>
<div id="hint">drag to pan &middot; scroll to zoom &middot; click a class</div>
</div>
<div id="insp" role="dialog" aria-label="Term details">
<button class="close" id="insp-x" aria-label="Close">&times;</button>
<div id="insp-body"></div>
</div>
</main>
<footer>
<span>Build artefact of __SRC__ &mdash; regenerate, never edit</span>
<span id="f-ver">T-Box __VERSION__</span>
</footer>
<script>
var M = __MODEL__;
/* ---------------- state ---------------- */
var NW=168, NH=46, BANDGAP=52, GUT=206;
var byId={}, off={}, mode='strata', showUni=false, showDep=false, showLab=true;
var sel=null, hot=null, view={x:0,y:0,s:1};
var svg=document.getElementById('svg'), vp=document.getElementById('vp');
var bL=document.getElementById('bL'), eL=document.getElementById('eL');
var tL=document.getElementById('tL'), nL=document.getElementById('nL');
var SEAM={'pr:materializedAs':1,'pr:physicalizedIn':1};
M.nodes.forEach(function(n){byId[n.id]=n;});
document.getElementById('s-cls').textContent=M.nodes.length;
document.getElementById('s-obj').textContent=M.edges.length+M.universal.length;
document.getElementById('s-dat').textContent=M.datatypes.length;
document.getElementById('s-brg').textContent=M.edges.filter(function(e){return SEAM[e.id];}).length;
function el(t,a){var e=document.createElementNS('http://www.w3.org/2000/svg',t);
if(a)for(var k in a)e.setAttribute(k,a[k]);return e;}
function bandOf(id){return M.bands.filter(function(b){return b.id===id;})[0];}
function visible(n){ if(off[n.band])return false; if(n.deprecated&&!showDep)return false; return true; }
/* ---------------- layout ---------------- */
function layout(){
var live=M.nodes.filter(visible);
if(mode==='tax'){ layoutTax(live); return live; }
var y=0;
M.bands.forEach(function(b){
var group=live.filter(function(n){return n.band===b.id;});
if(!group.length){b._h=0;b._y=y;return;}
var perRow=Math.max(3,Math.ceil(Math.sqrt(group.length*2.1)));
var rows=Math.ceil(group.length/perRow);
b._y=y; b._h=rows*(NH+20)+48; b._rows=rows; b._per=perRow;
group.forEach(function(n,i){
n.row=Math.floor(i/perRow);
n.x=GUT+(i%perRow)*(NW+26)+NW/2;
n.y=y+34+n.row*(NH+20)+NH/2;
});
y+=b._h+BANDGAP;
});
if(mode==='strata') relax(live);
else force(live);
return live;
}
/* barycentre relaxation on x only: connected classes drift together,
strata stay intact, which is the whole point of the drawing */
function relax(live){
var idx={}; live.forEach(function(n){idx[n.id]=n;});
var adj={}; live.forEach(function(n){adj[n.id]=[];});
M.edges.forEach(function(e){
if(idx[e.s]&&idx[e.t]&&(showDep||!e.deprecated)){
adj[e.s].push(e.t); adj[e.t].push(e.s);}
});
for(var it=0;it<70;it++){
live.forEach(function(n){
var ns=adj[n.id]; if(!ns.length)return;
var sum=0,c=0;
ns.forEach(function(m){if(idx[m]){sum+=idx[m].x;c++;}});
if(c) n.x += (sum/c - n.x)*0.22;
});
/* de-overlap inside each row */
var rows={};
live.forEach(function(n){var k=n.band+'|'+n.row;(rows[k]=rows[k]||[]).push(n);});
for(var k in rows){
var r=rows[k].sort(function(a,b){return a.x-b.x;});
for(var i=1;i<r.length;i++){
var need=(NW+22)-(r[i].x-r[i-1].x);
if(need>0){ r[i].x+=need*0.55; r[i-1].x-=need*0.45; }
}
r.forEach(function(n){ if(n.x<GUT+NW/2) n.x=GUT+NW/2; });
}
}
/* final strict pass: guarantee no overlap, preserve the order relaxation found */
var rows2={};
live.forEach(function(n){var k=n.band+'|'+n.row;(rows2[k]=rows2[k]||[]).push(n);});
for(var k2 in rows2){
var r2=rows2[k2].sort(function(a,b){return a.x-b.x;});
var cursor=Math.max(GUT+NW/2, r2[0].x);
r2.forEach(function(n){ n.x=Math.max(n.x,cursor); cursor=n.x+NW+22; });
}
}
/* plain force-directed network: no strata, for people who want the usual view */
function force(live){
var idx={}; live.forEach(function(n){idx[n.id]=n;});
var links=M.edges.filter(function(e){
return idx[e.s]&&idx[e.t]&&e.s!==e.t&&(showDep||!e.deprecated);});
var cx=live.reduce(function(a,n){return a+n.x;},0)/live.length;
var cy=live.reduce(function(a,n){return a+n.y;},0)/live.length;
for(var it=0;it<420;it++){
var k=1-it/420;
for(var i=0;i<live.length;i++){
var a=live[i];
for(var j=i+1;j<live.length;j++){
var b=live[j], dx=b.x-a.x, dy=(b.y-a.y)*1.9;
var d2=dx*dx+dy*dy||1, f=42000/d2;
if(f>9)f=9;
var d=Math.sqrt(d2);
a.x-=dx/d*f; a.y-=dy/d*f*0.5; b.x+=dx/d*f; b.y+=dy/d*f*0.5;
}
a.x+=(cx-a.x)*0.004*k; a.y+=(cy-a.y)*0.006*k;
}
links.forEach(function(e){
var a=idx[e.s], b=idx[e.t], dx=b.x-a.x, dy=b.y-a.y;
var d=Math.sqrt(dx*dx+dy*dy)||1, f=(d-235)*0.018*k;
a.x+=dx/d*f; a.y+=dy/d*f; b.x-=dx/d*f; b.y-=dy/d*f;
});
}
}
function layoutTax(live){
var kids={}, roots=[];
live.forEach(function(n){ if(n.parent&&byId[n.parent]&&visible(byId[n.parent]))
(kids[n.parent]=kids[n.parent]||[]).push(n); else roots.push(n); });
var cursor=0;
function place(n,depth){
var ch=kids[n.id]||[];
n.x=depth*(NW+58)+NW/2+30;
if(!ch.length){ n.y=cursor*(NH+13)+NH/2+20; cursor++; }
else{ ch.forEach(function(c){place(c,depth+1);});
n.y=(ch[0].y+ch[ch.length-1].y)/2; }
}
roots.forEach(function(r){place(r,0); cursor+=0.7;});
M.bands.forEach(function(b){b._h=0;});
}
/* ---------------- render ---------------- */
function render(){
var live=layout();
var idx={}; live.forEach(function(n){idx[n.id]=n;});
bL.innerHTML=''; eL.innerHTML=''; tL.innerHTML=''; nL.innerHTML='';
if(mode==='strata'){
var maxx=Math.max.apply(null,live.map(function(n){return n.x;}).concat([600]))+NW;
M.bands.forEach(function(b){
if(!b._h)return;
bL.appendChild(el('rect',{x:0,y:b._y,width:maxx+60,height:b._h,
fill:b.color,opacity:.045,class:'bandrect'}));
bL.appendChild(el('line',{x1:0,y1:b._y,x2:maxx+60,y2:b._y,class:'bandline'}));
bL.appendChild(el('rect',{x:GUT-5,y:b._y+10,width:3,height:b._h-22,
fill:b.color,opacity:.85}));
var t=el('text',{x:GUT-20,y:b._y+26,class:'bandlabel',fill:b.color,
'text-anchor':'end'});
t.textContent=b.title; bL.appendChild(t);
var g=el('text',{x:GUT-20,y:b._y+41,class:'bandtag','text-anchor':'end'});
g.textContent=b.tag; bL.appendChild(g);
});
}
var list=M.edges.slice();
if(showUni) list=list.concat(M.universal);
list.forEach(function(e){
if(!idx[e.s]||!idx[e.t])return;
if(e.deprecated&&!showDep)return;
var a=idx[e.s], b=idx[e.t], uni=M.universal.indexOf(e)>-1;
var cls='edge'+(uni?' uni':SEAM[e.id]?' bridge':'');
var p, mx, my;
if(a===b){
p='M'+(a.x-24)+','+(a.y-NH/2)+' C'+(a.x-70)+','+(a.y-70)+' '+(a.x+70)+','+(a.y-70)+' '+(a.x+24)+','+(a.y-NH/2);
mx=a.x; my=a.y-52;
}else{
var dx=b.x-a.x, dy=b.y-a.y, k=Math.abs(dy)>4?0.38:0;
var cx=a.x+dx/2+(-dy)*k*0.14, cy=a.y+dy/2+dx*k*0.05;
p='M'+a.x+','+a.y+' Q'+cx+','+cy+' '+b.x+','+b.y;
mx=(a.x+2*cx+b.x)/4; my=(a.y+2*cy+b.y)/4;
}
var path=el('path',{d:p,class:cls}); path.dataset.e=e.id;
path.dataset.a=e.s; path.dataset.b=e.t; eL.appendChild(path);
if(showLab&&!uni){
var w=e.label.length*4.6+8;
var bg=el('rect',{x:mx-w/2,y:my-6,width:w,height:11,rx:2,class:'ebg'});
bg.dataset.el=e.id; tL.appendChild(bg);
var tx=el('text',{x:mx,y:my+2.5,class:'elabel'});
tx.dataset.el=e.id; tx.textContent=e.label; tL.appendChild(tx);
}
});
live.forEach(function(n){
var col=bandOf(n.band).color;
var g=el('g',{class:'node','tabindex':'0','role':'button',
'aria-label':n.label,transform:'translate('+(n.x-NW/2)+','+(n.y-NH/2)+')'});
g.dataset.id=n.id;
g.appendChild(el('rect',{width:NW,height:NH,rx:3,
fill:n.prov==='observed'?'#fff':col,
'fill-opacity':n.prov==='observed'?1:.13,
stroke:col,'stroke-dasharray':n.deprecated?'4 3':'none'}));
if(n.prov==='observed')
g.appendChild(el('rect',{width:NW,height:NH,rx:3,fill:'url(#hatch)',stroke:'none'}));
var lines=wrap(n.label,22);
lines.forEach(function(s,i){
var t=el('text',{x:NW/2,y:NH/2+4+(i-(lines.length-1)/2)*11.8,
fill:n.deprecated?'#9A9A9A':'#1A1A1A',
'text-decoration':n.deprecated?'line-through':'none'});
t.textContent=s; g.appendChild(t);
});
nL.appendChild(g);
});
applyHot();
}
function wrap(s,max){
var w=s.split(' '), out=[], cur='';
w.forEach(function(x){
if((cur+' '+x).trim().length>max){ if(cur)out.push(cur); cur=x; }
else cur=(cur+' '+x).trim();
});
if(cur)out.push(cur);
return out.slice(0,3);
}
/* ---------------- focus / highlight ---------------- */
function neighbours(id){
var s={}; s[id]=1;
M.edges.concat(M.universal).forEach(function(e){
if(e.s===id)s[e.t]=1; if(e.t===id)s[e.s]=1;});
return s;
}
function applyHot(){
var id=hot||sel;
var nodes=nL.querySelectorAll('.node');
var paths=eL.querySelectorAll('.edge');
var labs=tL.querySelectorAll('[data-el]');
if(!id){
nodes.forEach(function(n){n.classList.remove('dim','sel');});
paths.forEach(function(p){p.classList.remove('dim','hot');});
labs.forEach(function(l){l.classList.remove('dim');});
return;
}
var keep=neighbours(id), live={};
paths.forEach(function(p){
var on=(p.dataset.a===id||p.dataset.b===id);
p.classList.toggle('hot',on); p.classList.toggle('dim',!on);
if(on)live[p.dataset.e]=1;
});
labs.forEach(function(l){l.classList.toggle('dim',!live[l.dataset.el]);});
nodes.forEach(function(n){
n.classList.toggle('dim',!keep[n.dataset.id]);
n.classList.toggle('sel',n.dataset.id===sel);
});
}
/* ---------------- inspector ---------------- */
var insp=document.getElementById('insp'), ibody=document.getElementById('insp-body');
function esc(s){return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;');}
function open(id){
var n=byId[id]; if(!n)return;
sel=id; var b=bandOf(n.band), h=[];
h.push('<div class="kicker" style="color:'+b.color+'">'+esc(b.title)+'</div>');
h.push('<h3>'+esc(n.label)+'</h3>');
h.push('<div class="iri">'+esc(n.id)+'</div>');
h.push('<span class="pill" style="color:'+b.color+';border-color:'+b.color+'">'+
(n.prov==='observed'?'Observed &middot; harvested':n.prov==='curated'?'Curated &middot; asserted':'Structural')+'</span>');
if(n.curation) h.push('<span class="pill" style="color:#B8935A;border-color:#B8935A">'+esc(n.curation)+'</span>');
if(n.deprecated) h.push('<span class="pill" style="color:#8B3A3A;border-color:#8B3A3A">Deprecated</span>');
if(n.comment) h.push('<h4>What it is for</h4><p>'+esc(n.comment)+'</p>');
if(n.replacedBy) h.push('<p><b>Replaced by</b> '+link(n.replacedBy)+'</p>');
if(n.harvest) h.push('<h4>Harvested from</h4><div class="harvest">'+esc(n.harvest)+'</div>');
if(n.parent) h.push('<h4>Specialises</h4><div class="rel"><span class="p">rdfs:subClassOf</span><span class="o">'+link(n.parent)+'</span></div>');
var kids=M.nodes.filter(function(m){return m.parent===id;});
if(kids.length) h.push('<h4>Specialised by</h4>'+kids.map(function(k){
return '<div class="rel"><span class="p">'+esc(k.label)+'</span><span class="o">'+
(k.deprecated?'deprecated':bandOf(k.band).title)+'</span></div>';}).join(''));
if(n.members&&n.members.length) h.push('<h4>Allowed values</h4>'+n.members.map(function(m){
return '<div class="rel"><span class="p">'+esc(m.label)+'</span><span class="o">'+esc(m.id)+'</span></div>';}).join(''));
var out=M.edges.concat(M.universal).filter(function(e){return e.s===id&&(showDep||!e.deprecated);});
var inc=M.edges.concat(M.universal).filter(function(e){return e.t===id&&(showDep||!e.deprecated);});
if(out.length) h.push('<h4>Points to</h4>'+out.map(rel).join(''));
if(inc.length) h.push('<h4>Pointed to by</h4>'+inc.map(function(e){
return '<div class="rel"><span class="p">'+esc(e.label)+'</span><span class="o">'+link(e.s)+'</span></div>';}).join(''));
ibody.innerHTML=h.join('');
insp.classList.add('open');
bindJumps(); applyHot();
}
function rel(e){
var flags=[]; if(e.functional)flags.push('exactly one');
if(e.transitive)flags.push('transitive');
if(SEAM[e.id])flags.push('seam');
return '<div class="rel"><span class="p">'+esc(e.label)+
(flags.length?' <span class="flag">'+flags.join(' &middot; ')+'</span>':'')+
'</span><span class="o">'+link(e.t)+'</span>'+
(e.comment?'<p style="grid-column:1/-1;margin:4px 0 0;font-size:11.5px;color:#6B6B6B">'+esc(e.comment)+'</p>':'')+
'</div>';
}
function link(id){
var n=byId[id];
return n?'<button class="jump" data-j="'+id+'">'+esc(n.label)+'</button>':esc(id);
}
function bindJumps(){
ibody.querySelectorAll('.jump').forEach(function(b){
b.onclick=function(){open(b.dataset.j); focusOn(b.dataset.j);};});
}
document.getElementById('insp-x').onclick=function(){
insp.classList.remove('open'); sel=null; applyHot();};
document.addEventListener('keydown',function(e){
if(e.key==='Escape'){insp.classList.remove('open'); sel=null; applyHot();}});
/* ---------------- view ---------------- */
function apply(){vp.setAttribute('transform','translate('+view.x+','+view.y+') scale('+view.s+')');}
function fit(){
var live=M.nodes.filter(visible); if(!live.length)return;
var xs=live.map(function(n){return n.x;}), ys=live.map(function(n){return n.y;});
var pad=(mode==='strata')?GUT+30:NW;
var mnx=Math.min.apply(null,xs)-pad, mxx=Math.max.apply(null,xs)+NW;
var mny=Math.min.apply(null,ys)-90, mxy=Math.max.apply(null,ys)+70;
var r=svg.getBoundingClientRect();
view.s=Math.min(r.width/(mxx-mnx),r.height/(mxy-mny),1.25);
view.x=(r.width-(mxx-mnx)*view.s)/2-mnx*view.s;
view.y=(r.height-(mxy-mny)*view.s)/2-mny*view.s;
apply();
}
function focusOn(id){
var n=byId[id]; if(!n)return;
var r=svg.getBoundingClientRect();
view.x=r.width/2-n.x*view.s-120; view.y=r.height/2-n.y*view.s; apply();
}
document.getElementById('z-in').onclick=function(){zoom(1.22);};
document.getElementById('z-out').onclick=function(){zoom(.82);};
document.getElementById('z-fit').onclick=fit;
function zoom(f){
var r=svg.getBoundingClientRect(), cx=r.width/2, cy=r.height/2;
var ns=Math.max(.18,Math.min(view.s*f,2.6));
view.x=cx-(cx-view.x)*(ns/view.s); view.y=cy-(cy-view.y)*(ns/view.s);
view.s=ns; apply();
}
var drag=false,ds,vs,pinch=null;
svg.addEventListener('mousedown',function(e){drag=true;svg.classList.add('drag');
ds={x:e.clientX,y:e.clientY};vs={x:view.x,y:view.y};});
window.addEventListener('mousemove',function(e){if(!drag)return;
view.x=vs.x+(e.clientX-ds.x);view.y=vs.y+(e.clientY-ds.y);apply();});
window.addEventListener('mouseup',function(){drag=false;svg.classList.remove('drag');});
svg.addEventListener('wheel',function(e){e.preventDefault();
var f=e.deltaY<0?1.1:.9, r=svg.getBoundingClientRect();
var mx=e.clientX-r.left,my=e.clientY-r.top;
var ns=Math.max(.18,Math.min(view.s*f,2.6));
view.x=mx-(mx-view.x)*(ns/view.s);view.y=my-(my-view.y)*(ns/view.s);
view.s=ns;apply();},{passive:false});
svg.addEventListener('touchstart',function(e){
if(e.touches.length===1){drag=true;ds={x:e.touches[0].clientX,y:e.touches[0].clientY};
vs={x:view.x,y:view.y};}
else if(e.touches.length===2){drag=false;
var dx=e.touches[0].clientX-e.touches[1].clientX,dy=e.touches[0].clientY-e.touches[1].clientY;
pinch={d:Math.sqrt(dx*dx+dy*dy),s:view.s};}},{passive:true});
svg.addEventListener('touchmove',function(e){
if(e.touches.length===1&&drag){view.x=vs.x+(e.touches[0].clientX-ds.x);
view.y=vs.y+(e.touches[0].clientY-ds.y);apply();e.preventDefault();}
else if(e.touches.length===2&&pinch){
var dx=e.touches[0].clientX-e.touches[1].clientX,dy=e.touches[0].clientY-e.touches[1].clientY;
var nd=Math.sqrt(dx*dx+dy*dy),ns=Math.max(.18,Math.min(pinch.s*(nd/pinch.d),2.6));
var r=svg.getBoundingClientRect(),cx=r.width/2,cy=r.height/2;
view.x=cx-(cx-view.x)*(ns/view.s);view.y=cy-(cy-view.y)*(ns/view.s);
view.s=ns;apply();e.preventDefault();}},{passive:false});
svg.addEventListener('touchend',function(){drag=false;pinch=null;});
nL.addEventListener('click',function(e){
var g=e.target.closest('.node'); if(g)open(g.dataset.id);});
nL.addEventListener('keydown',function(e){
var g=e.target.closest('.node');
if(g&&(e.key==='Enter'||e.key===' ')){e.preventDefault();open(g.dataset.id);}});
nL.addEventListener('mouseover',function(e){
var g=e.target.closest('.node'); if(g&&!sel){hot=g.dataset.id;applyHot();}});
nL.addEventListener('mouseout',function(e){
if(!e.relatedTarget||!e.relatedTarget.closest||!e.relatedTarget.closest('.node')){
if(!sel){hot=null;applyHot();}}});
/* ---------------- controls ---------------- */
var bandsBox=document.getElementById('bands');
M.bands.forEach(function(b){
var n=M.nodes.filter(function(x){return x.band===b.id;}).length;
if(!n)return;
var btn=document.createElement('button');
btn.className='chip'; btn.setAttribute('aria-pressed','true');
btn.innerHTML='<span class="swatch" style="background:'+b.color+'"></span>'+
b.title+'<span class="n">'+n+'</span>';
btn.onclick=function(){
off[b.id]=!off[b.id];
btn.classList.toggle('off',!!off[b.id]);
btn.setAttribute('aria-pressed',String(!off[b.id]));
render(); fit();};
bandsBox.appendChild(btn);
});
function setMode(m){
mode=m;
['strata','tax','free'].forEach(function(k){
document.getElementById('m-'+k).classList.toggle('on',k===m);});
render(); fit();
}
document.getElementById('m-strata').onclick=function(){setMode('strata');};
document.getElementById('m-tax').onclick=function(){setMode('tax');};
document.getElementById('m-free').onclick=function(){setMode('free');};
document.getElementById('o-uni').onchange=function(){showUni=this.checked;render();};
document.getElementById('o-dep').onchange=function(){showDep=this.checked;render();fit();};
document.getElementById('o-lab').onchange=function(){showLab=this.checked;render();};
document.getElementById('mtoggle').onclick=function(){
document.getElementById('side').classList.toggle('open');};
document.getElementById('q').addEventListener('input',function(){
var v=this.value.trim().toLowerCase();
if(!v){hot=null;applyHot();return;}
var hit=M.nodes.filter(visible).filter(function(n){
return (n.label+' '+n.id+' '+(n.comment||'')).toLowerCase().indexOf(v)>-1;});
nL.querySelectorAll('.node').forEach(function(g){
var on=hit.some(function(n){return n.id===g.dataset.id;});
g.classList.toggle('dim',!on);});
if(hit.length===1){open(hit[0].id);focusOn(hit[0].id);}
});
render(); fit();
window.addEventListener('resize',fit);
</script>
</body>
</html>
"""
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()
+127
View File
@@ -0,0 +1,127 @@
@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 sh: <http://www.w3.org/ns/shacl#> .
@prefix pr: <https://ontology.pernod-ricard.com/metamodel/0.9/> .
@prefix prsh: <https://ontology.pernod-ricard.com/metamodel/0.9/shapes/> .
#################################################################
# 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: <https://ontology.pernod-ricard.com/metamodel/0.9/>
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_).*(?<!_NM)(?<!_CD)(?<!_FLG)(?<!_NR)(?<!_DT)$" ; sh:flags "i" ;
sh:severity sh:Warning ;
sh:message "NR-001 (MAJOR): canonical name free of physical prefixes/suffixes." ] .
prsh:MetricShape a sh:NodeShape ;
sh:targetClass pr:Metric ;
sh:property [ sh:path pr:hasFormula ; sh:minCount 1 ; sh:datatype xsd:string ; sh:minLength 2 ;
sh:severity sh:Violation ; sh:message "BR-003 / DQ-004 (BLOCKING): a Metric MUST have a formula." ] ;
sh:property [ sh:path pr:hasUnit ; sh:minCount 1 ;
sh:severity sh:Violation ; sh:message "DQ-004 (BLOCKING): a Metric MUST have a hasUnit." ] ;
sh:property [ sh:path pr:hasGranularity ; sh:minCount 1 ;
sh:severity sh:Violation ; sh:message "DQ-004 (BLOCKING): a Metric MUST have a hasGranularity." ] .
prsh:BusinessConceptShape a sh:NodeShape ;
sh:targetClass pr:BusinessConcept ;
sh:property [ sh:path pr:hasFormula ; sh:maxCount 0 ;
sh:severity sh:Violation ; sh:message "BR-003 (BLOCKING): a Business Concept MUST NOT have a formula." ] .
prsh:BusinessObjectShape a sh:NodeShape ;
sh:targetClass pr:BusinessObject ;
sh:property [ sh:path pr:composedOf ; sh:minCount 3 ; sh:maxCount 10 ; sh:class pr:BusinessConcept ;
sh:severity sh:Warning ; sh:message "BR-004 (MAJOR): a Business Object MUST group 3 to 10 Business Concepts." ] ;
sh:property [ sh:path pr:belongsToSubDomain ; sh:minCount 1 ; sh:maxCount 1 ; sh:class pr:SubDomain ;
sh:severity sh:Warning ; sh:message "NR-012/OW (MAJOR): a Business Object belongs to exactly one Sub-Domain." ] ;
sh:property [ sh:path pr:hasSteward ; sh:minCount 1 ; sh:class pr:DataSteward ;
sh:severity sh:Warning ; sh:message "OW-005 (MAJOR): a Business Object MUST have a Data Steward." ] .
prsh:DataObjectShape a sh:NodeShape ;
sh:targetClass pr:DataObject ;
sh:property [ sh:path pr:implements ; sh:minCount 1 ; sh:maxCount 1 ; sh:class pr:BusinessObject ;
sh:severity sh:Violation ; sh:message "BR-006 (BLOCKING): a Data Object MUST implement exactly one Business Object." ] ;
sh:property [ sh:path pr:hasElement ; sh:minCount 1 ; sh:class pr:DataElement ;
sh:severity sh:Violation ; sh:message "BR-006 (BLOCKING): a Data Object MUST contain at least one Data Element." ] ;
sh:property [ sh:path pr:hasSteward ; sh:minCount 1 ; sh:class pr:DataSteward ;
sh:severity sh:Warning ; sh:message "OW-005 (MAJOR): a Data Object MUST have a Data Steward." ] .
prsh:DataElementShape a sh:NodeShape ;
sh:targetClass pr:DataElement ;
sh:property [ sh:path pr:hasFormat ; sh:minCount 1 ;
sh:severity sh:Warning ; sh:message "DQ-005 (MAJOR): a Data Element MUST have a hasFormat." ] ;
sh:property [ sh:path pr:hasSourceSystem ; sh:minCount 1 ;
sh:severity sh:Warning ; sh:message "DQ-005 (MAJOR): a Data Element MUST have a hasSourceSystem." ] ;
sh:property [ sh:path pr:physicalName ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ;
sh:severity sh:Violation ; sh:message "DQ-010 (BLOCKING): a mapped Data Element MUST have a physical_name." ] .
prsh:DataDomainShape a sh:NodeShape ;
sh:targetClass pr:DataDomain ;
sh:property [ sh:path pr:hasDGL ; sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ; sh:message "OW-004 (BLOCKING): a Data Domain MUST have exactly one DGL." ] ;
sh:property [ sh:path pr:hasActivationStatus ; sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ; sh:message "OW-004 (BLOCKING): a Data Domain MUST have exactly one Activation Status." ] .
# FIX v0.1.1 - NR-002 now restricts ?cls to LEAF classes so cross-layer
# homonyms (Sub-Domain vs Concept, etc.) are not flagged as duplicates.
prsh:NameUniquenessShape a sh:NodeShape ;
sh:targetClass pr:MetaModelObject ;
sh:sparql [ sh:severity sh:Violation ;
sh:message "NR-002 (BLOCKING): canonical name MUST be unique within its (leaf) class and owning Data Domain." ;
sh:select """
PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/0.9/>
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: <https://ontology.pernod-ricard.com/metamodel/0.9/>
SELECT $this WHERE {
$this pr:hasStatus "PUBLISHED" .
$this pr:arbitrationStatus "TO_ARBITRATE" . }
""" ] .