From 76e95de471feeaac26d8c6abc8bbda18dd03582a Mon Sep 17 00:00:00 2001 From: Bastien Gourdon Date: Wed, 29 Jul 2026 09:03:59 +0200 Subject: [PATCH] tbox: v1.2 - physicalizedIn devient storedIn, hasSource ramene a un seul sens (systeme detenant la donnee faisant autorite) et interdit sur les Business Concepts. Table supprime, MaterializedView / constrainsRelation / approximateRowCount / hasVendor deprecies. shapes: controle que la source d'une metrique correspond a celle de ses elements, unite multiple sur Metric et unique sur DataElement, hasProductOwner en avertissement. instances: Data Product SODH avec contrat et deux interfaces, 6 proprietaires de domaine nommes, Sell Out Distribution devient Retail Distribution. generated: les deux viewers partagent desormais le meme moteur --- generated/pr_metamodel_viewer.html | 1381 +++++++++++++--------- generated/sodh_viewer.html | 1096 +++++++++++++++++ instances/sodh.ttl | 707 ++++++----- ontology/pr_metamodel.ttl | 87 +- scripts/apply_fixes_v1_2.py | 202 ++++ scripts/generate_sodh_viewer.py | 1448 +++++++++++++++++++++++ scripts/generate_tbox_viewer.py | 1748 ++++++++++++++++------------ scripts/migrate_instances_v1_2.py | 207 ++++ shapes/pr_metamodel_shapes.ttl | 64 +- 9 files changed, 5314 insertions(+), 1626 deletions(-) create mode 100644 generated/sodh_viewer.html create mode 100644 scripts/apply_fixes_v1_2.py create mode 100644 scripts/generate_sodh_viewer.py create mode 100644 scripts/migrate_instances_v1_2.py diff --git a/generated/pr_metamodel_viewer.html b/generated/pr_metamodel_viewer.html index 752af9e..a39032b 100644 --- a/generated/pr_metamodel_viewer.html +++ b/generated/pr_metamodel_viewer.html @@ -3,634 +3,915 @@ -PR Data Meta Model · T-Box v1.0 +PR Data Meta Model · T-Box -
-

PR Data Meta Model · T-Box

-
Five layers, crossed with a provenance axis: what governance asserts, what harvesters observe
+
Six layers, crossed with what governance asserts and what a harvester observes
-
-
0Classes
-
0Relations
-
0Attributes
-
0Seam edges
+
+ + + + +
- -
- - -
- - - - - - - - - - - - -
- - - -
-
drag to pan · scroll to zoom · click a class
-
- - -
- +
+ - diff --git a/generated/sodh_viewer.html b/generated/sodh_viewer.html new file mode 100644 index 0000000..8ca1009 --- /dev/null +++ b/generated/sodh_viewer.html @@ -0,0 +1,1096 @@ + + + + + +Sell Out Data Hub · scope and dependencies + + + +
+
+

Sell Out Data Hub

+
Scope, dependencies and pending arbitrations
+
+
+ + + + + +
+
+ +
+ + + + + + + + diff --git a/instances/sodh.ttl b/instances/sodh.ttl index 74566bd..91ed185 100644 --- a/instances/sodh.ttl +++ b/instances/sodh.ttl @@ -12,6 +12,16 @@ ################################################################# +# --- data domain owners ---------------------------------------------- +# Accountable for the domain. Distinct from the DGL, who runs its +# governance: two roles that hasDGL alone was conflating. +ex:DDO_DD_06 a pr:DataDomainOwner ; pr:hasName "Ludovic Cottier" . +ex:DDO_DD_04 a pr:DataDomainOwner ; pr:hasName "David Carpio" . +ex:DDO_DD_10 a pr:DataDomainOwner ; pr:hasName "Alberto Lupano" . +ex:DDO_DD_05 a pr:DataDomainOwner ; pr:hasName "David Carpio" . +ex:DDO_DD_16 a pr:DataDomainOwner ; pr:hasName "Julien Soisson" . +ex:DDO_DD_21 a pr:DataDomainOwner ; pr:hasName "Vincent Meunier" . + 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" . @@ -26,35 +36,47 @@ 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 . + pr:hasDomainOwner ex:DDO_DD_06 ; + 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 . + pr:hasDomainOwner ex:DDO_DD_10 ; + 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 . + pr:hasDomainOwner ex:DDO_DD_04 ; + 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 . + pr:hasDomainOwner ex:DDO_DD_16 ; + 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 . + pr:hasDomainOwner ex:DDO_DD_21 ; + 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 . + pr:hasDomainOwner ex:DDO_DD_05 ; + 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" ; @@ -122,101 +144,115 @@ ex:SD_16_03 a pr:SubDomain ; pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "DGO proposal, split of a compound sub-domain" . 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" . + 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" + . 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" . + 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" + . 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" . + 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" + . ex:BC_06_01_004 a pr:BusinessConcept ; - pr:hasIdentifier "BC-06.01-004" ; pr:hasName "Retail 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" . + pr:hasIdentifier "BC-06.01-004" ; pr:hasName "Retail 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" + . 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" . + 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" + . # --- concepts added in v1.1 migration ------------------------------- # Six of these belong to other domains: DRAFT definitions proposed by the # DGO, flagged TO_ARBITRATE. OW-007 blocks publication until ratified. ex:BC_06_01_005 a pr:BusinessConcept ; - pr:hasIdentifier "BC-06.01-005" ; pr:hasName "Sell Out Baseline" ; - pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; - pr:hasBusinessDefinition "The modelled level of retail sales that would have occurred without promotional activity, used as the reference against which promotional uplift is measured. Distinct from Sell Out, which is observed rather than modelled." ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . + pr:hasIdentifier "BC-06.01-005" ; pr:hasName "Sell Out Baseline" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasBusinessDefinition "The modelled level of retail sales that would have occurred without promotional activity, used as the reference against which promotional uplift is measured. Distinct from Sell Out, which is observed rather than modelled." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:BC_10_01_001 a pr:BusinessConcept ; - pr:hasIdentifier "BC-10.01-001" ; pr:hasName "Product" ; - pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; - pr:hasBusinessDefinition "A commercial item sold by Pernod Ricard, identified at Stock Keeping Unit level and positioned in the corporate hierarchy from Brand through Category, Sub-Category and Flavor." ; - pr:arbitrationStatus "TO_ARBITRATE" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "DGO proposal, pending ratification by the owning domain" . + pr:hasIdentifier "BC-10.01-001" ; pr:hasName "Product" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:hasBusinessDefinition "A commercial item sold by Pernod Ricard, identified at Stock Keeping Unit level and positioned in the corporate hierarchy from Brand through Category, Sub-Category and Flavor." ; + pr:arbitrationStatus "TO_ARBITRATE" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:BC_04_01_001 a pr:BusinessConcept ; - pr:hasIdentifier "BC-04.01-001" ; pr:hasName "Customer" ; - pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; - pr:hasBusinessDefinition "A commercial counterparty that buys Pernod Ricard products for resale, positioned in the trade hierarchy from Distributor through Tier-1 and Tier-2 Customer. Distinct from Outlet, which is a physical point of sale." ; - pr:arbitrationStatus "TO_ARBITRATE" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "DGO proposal, pending ratification by the owning domain" . + pr:hasIdentifier "BC-04.01-001" ; pr:hasName "Customer" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasBusinessDefinition "A commercial counterparty that buys Pernod Ricard products for resale, positioned in the trade hierarchy from Distributor through Tier-1 and Tier-2 Customer. Distinct from Outlet, which is a physical point of sale." ; + pr:arbitrationStatus "TO_ARBITRATE" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:BC_04_01_002 a pr:BusinessConcept ; - pr:hasIdentifier "BC-04.01-002" ; pr:hasName "Outlet" ; - pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; - pr:hasBusinessDefinition "A physical point of sale where products are made available to end consumers, characterised by its Channel. An Outlet belongs to a Customer but is not the same notion: one Customer operates many Outlets." ; - pr:arbitrationStatus "TO_ARBITRATE" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "DGO proposal, pending ratification by the owning domain" . + pr:hasIdentifier "BC-04.01-002" ; pr:hasName "Outlet" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasBusinessDefinition "A physical point of sale where products are made available to end consumers, characterised by its Channel. An Outlet belongs to a Customer but is not the same notion: one Customer operates many Outlets." ; + pr:arbitrationStatus "TO_ARBITRATE" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:BC_16_01_001 a pr:BusinessConcept ; - pr:hasIdentifier "BC-16.01-001" ; pr:hasName "Currency" ; - pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; - pr:hasBusinessDefinition "A monetary unit in which a transaction or a reported amount is denominated, identified by its ISO 4217 code." ; - pr:arbitrationStatus "TO_ARBITRATE" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "DGO proposal, pending ratification by the owning domain" . + pr:hasIdentifier "BC-16.01-001" ; pr:hasName "Currency" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:hasBusinessDefinition "A monetary unit in which a transaction or a reported amount is denominated, identified by its ISO 4217 code." ; + pr:arbitrationStatus "TO_ARBITRATE" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:BC_16_01_002 a pr:BusinessConcept ; - pr:hasIdentifier "BC-16.01-002" ; pr:hasName "Exchange Rate" ; - pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; - pr:hasBusinessDefinition "The conversion factor applied between two currencies at a given date, used to restate local amounts into group reporting currency. Distinct from Currency: a rate is a dated relationship between two of them." ; - pr:arbitrationStatus "TO_ARBITRATE" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "DGO proposal, pending ratification by the owning domain" . + pr:hasIdentifier "BC-16.01-002" ; pr:hasName "Exchange Rate" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:hasBusinessDefinition "The conversion factor applied between two currencies at a given date, used to restate local amounts into group reporting currency. Distinct from Currency: a rate is a dated relationship between two of them." ; + pr:arbitrationStatus "TO_ARBITRATE" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:BC_21_01_001 a pr:BusinessConcept ; - pr:hasIdentifier "BC-21.01-001" ; pr:hasName "Calendar Date" ; - pr:owningDomain ex:DD_21 ; pr:ownedBy ex:ST_DD_21 ; - pr:hasBusinessDefinition "A single day in the Gregorian calendar, the atomic unit from which Week, Month, Quarter and Year are derived and against which all time-based measures are positioned." ; - pr:arbitrationStatus "TO_ARBITRATE" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "DGO proposal, pending ratification by the owning domain" . + pr:hasIdentifier "BC-21.01-001" ; pr:hasName "Calendar Date" ; + pr:owningDomain ex:DD_21 ; pr:ownedBy ex:ST_DD_21 ; + pr:hasBusinessDefinition "A single day in the Gregorian calendar, the atomic unit from which Week, Month, Quarter and Year are derived and against which all time-based measures are positioned." ; + pr:arbitrationStatus "TO_ARBITRATE" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . # --- concepts required by the meaning XOR --------------------------- # Country, Marketing Entity and Fiscal Period had Data Elements but no # notion behind them. The rule forced them to be named. ex:BC_04_03_001 a pr:BusinessConcept ; - pr:hasIdentifier "BC-04.03-001" ; pr:hasName "Country" ; - pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; - pr:hasBusinessDefinition "A sovereign territory used as the geographic frame for retail measurement, identified by its ISO 3166 code. Distinct from Customer and Outlet: it is where they operate, not what they are." ; - pr:arbitrationStatus "TO_ARBITRATE" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "DGO proposal, pending ratification by the owning domain" . -ex:BC_16_03_001 a pr:BusinessConcept ; - pr:hasBusinessRule "Named Marketing Entity, not Management Entity: the element is Marketing Entity Code and the column is MARKETING_ENTITY_CD. To be confirmed by DD-16." ; - pr:hasIdentifier "BC-16.03-001" ; pr:hasName "Marketing Entity" ; - pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; - pr:hasBusinessDefinition "An organisational unit of the group holding commercial responsibility for a market, and the level at which financial results are consolidated." ; + pr:hasIdentifier "BC-04.03-001" ; pr:hasName "Country" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:hasBusinessDefinition "A sovereign territory used as the geographic frame for retail measurement, identified by its ISO 3166 code. Distinct from Customer and Outlet: it is where they operate, not what they are." ; pr:arbitrationStatus "TO_ARBITRATE" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "DGO proposal, pending ratification by the owning domain" + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . +ex:BC_16_03_001 a pr:BusinessConcept ; + pr:hasBusinessRule "Named Marketing Entity, not Management Entity: the element is Marketing Entity Code and the column is MARKETING_ENTITY_CD. To be confirmed by DD-16." ; + pr:hasIdentifier "BC-16.03-001" ; pr:hasName "Marketing Entity" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:hasBusinessDefinition "An organisational unit of the group holding commercial responsibility for a market, and the level at which financial results are consolidated." ; + pr:arbitrationStatus "TO_ARBITRATE" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" . ex:BC_16_02_001 a pr:BusinessConcept ; - pr:hasIdentifier "BC-16.02-001" ; pr:hasName "Fiscal Period" ; - pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; - pr:hasBusinessDefinition "A reporting interval of the Pernod Ricard fiscal year, which runs July to June. Distinct from Calendar Date: the fiscal frame does not align with the Gregorian one." ; - pr:arbitrationStatus "TO_ARBITRATE" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "DGO proposal, pending ratification by the owning domain" . + pr:hasIdentifier "BC-16.02-001" ; pr:hasName "Fiscal Period" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:hasBusinessDefinition "A reporting interval of the Pernod Ricard fiscal year, which runs July to June. Distinct from Calendar Date: the fiscal frame does not align with the Gregorian one." ; + pr:arbitrationStatus "TO_ARBITRATE" ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:BO_06_01_001 a pr:BusinessObject ; pr:hasIdentifier "BO-06.01-001" ; pr:hasName "Sell Out Volume" ; @@ -235,13 +271,15 @@ ex:BO_06_01_002 a pr:BusinessObject ; pr:hasMetric ex:M_06_01_002 ; 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:belongsTo ex:SD_06_01 ; pr:monitoredBy ex:ST_DD_06 ; - pr:aboutConcept ex:BC_06_01_004 ; - pr:usesConcept ex:BC_06_01_003 ; - pr:hasMetric ex:M_06_01_003 , ex:M_06_01_004 , ex:M_06_01_005 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / F_SELL_OUT_DISTRIBUTION" . + pr:hasName "Retail Distribution" ; + pr:hasIdentifier "BO-06.01-003" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:belongsTo ex:SD_06_01 ; pr:monitoredBy ex:ST_DD_06 ; + pr:aboutConcept ex:BC_06_01_004 ; + pr:usesConcept ex:BC_06_01_003 ; + pr:hasMetric ex:M_06_01_003 , ex:M_06_01_004 , ex:M_06_01_005 ; + 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 ; @@ -347,9 +385,11 @@ ex:BO_16_03_001 a pr:BusinessObject ; . 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:represents ex:BO_06_01_001 ; + pr:physicalName "F_SELL_OUT" ; + pr:hasSource "SODH Gold" ; + pr:hasIdentifier "DO-06.01-001" ; pr:hasName "Sell Out Volume Dataset" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:represents ex:BO_06_01_001 ; pr:hasElement ex:DE_06_01_0001 , ex:DE_06_01_0002 , ex:DE_06_01_0003 , ex:DE_06_01_0004 , ex:DE_06_01_0005 , ex:DE_06_01_0006 , ex:DE_06_01_0007 , ex:DE_06_01_0008 , ex:DE_06_01_0009 , ex:DE_06_01_0010 , ex:DE_06_01_0011 , ex:DE_06_01_0012 , @@ -370,53 +410,69 @@ ex:DO_06_01_001 a pr:DataObject ; ex:DE_06_01_0084 , ex:DE_06_01_0085 , ex:DE_06_01_0086 , ex:DE_06_01_0087 , ex:DE_06_01_0088 , ex:DE_06_01_0089 , ex:DE_06_01_0090 , ex:DE_06_01_0091 , ex:DE_06_01_0092 , ex:DE_06_01_0093 , ex:DE_06_01_0094 , ex:DE_06_01_0095 , - 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: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" + . 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:represents ex:BO_06_01_003 ; + pr:physicalName "F_SELL_OUT_DISTRIBUTION" ; + pr:hasSource "SODH Gold" ; + pr:hasIdentifier "DO-06.01-003" ; pr:hasName "Sell Out Distribution Dataset" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:represents ex:BO_06_01_003 ; pr:hasElement ex:DE_06_01_0061 , ex:DE_06_01_0062 , ex:DE_06_01_0063 , ex:DE_06_01_0064 , ex:DE_06_01_0065 , ex:DE_06_01_0066 , ex:DE_06_01_0067 , ex:DE_06_01_0068 , ex:DE_06_01_0069 , ex:DE_06_01_0070 , ex:DE_06_01_0071 , ex:DE_06_01_0072 , - ex:DE_06_01_0073 , ex:DE_06_01_0074 , ex:DE_06_01_0075 ; - 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:DE_06_01_0073 , ex:DE_06_01_0074 , ex:DE_06_01_0075 ; + pr:references ex:DO_04_01_001 , ex:DO_10_01_001 , ex:DO_21_01_001 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" + . 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:represents ex:BO_10_01_001 ; + pr:physicalName "D_FINISHED_PRODUCT" ; + pr:hasSource "SODH Gold" ; + pr:hasIdentifier "DO-10.01-001" ; pr:hasName "Product Dimension" ; + pr:owningDomain ex:DD_10 ; pr:ownedBy ex:ST_DD_10 ; + pr:represents ex:BO_10_01_001 ; 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:hasGrainElement ex:DE_10_01_0005 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / D_FINISHED_PRODUCT" . + ex:DE_10_01_0009 , ex:DE_10_01_0010 , ex:DE_10_01_0011 , ex:DE_10_01_0012 ; + pr:hasGrainElement ex:DE_10_01_0005 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" + . 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:represents ex:BO_04_01_001 ; + pr:physicalName "D_CUSTOMER" ; + pr:hasSource "SODH Gold" ; + pr:hasIdentifier "DO-04.01-001" ; pr:hasName "Customer Dimension" ; + pr:owningDomain ex:DD_04 ; pr:ownedBy ex:ST_DD_04 ; + pr:represents ex:BO_04_01_001 ; 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:hasGrainElement ex:DE_04_01_0005 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / D_CUSTOMER" . + ex:DE_04_01_0009 , ex:DE_04_01_0010 ; + pr:hasGrainElement ex:DE_04_01_0005 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" + . 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:represents ex:BO_16_01_001 ; + pr:physicalName "D_CURRENCY" ; + pr:hasSource "SODH Gold" ; + pr:hasIdentifier "DO-16.01-001" ; pr:hasName "Currency Dimension" ; + pr:owningDomain ex:DD_16 ; pr:ownedBy ex:ST_DD_16 ; + pr:represents ex:BO_16_01_001 ; 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:hasGrainElement ex:DE_16_01_0001 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / D_CURRENCY" . + ex:DE_16_02_0002 , ex:DE_16_02_0003 ; + pr:hasGrainElement ex:DE_16_01_0001 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" + . 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:represents ex:BO_21_01_001 ; + pr:physicalName "D_DATE" ; + pr:hasSource "SODH Gold" ; + pr:hasIdentifier "DO-21.01-001" ; pr:hasName "Calendar Dimension" ; + pr:owningDomain ex:DD_21 ; pr:ownedBy ex:ST_DD_21 ; + pr:represents ex:BO_21_01_001 ; 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:hasGrainElement ex:DE_21_01_0001 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" ; pr:hasSource "SODH Gold / D_DATE" . + ex:DE_21_01_0005 ; + pr:hasGrainElement ex:DE_21_01_0001 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "0.6" + . # --- mother metrics, BR-013 ----------------------------------------- @@ -425,75 +481,95 @@ ex:DO_21_01_001 a pr:DataObject ; # granularity (a calculation rule has no rows). ex:M_06_01_001 a pr:Metric ; - pr:hasIdentifier "M-06.01-001" ; pr:hasName "Sell Out Volume" ; - pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; - pr:hasFormula "SUM(validated sell-out volumes over the period)" ; - pr:hasUnit "9L" ; - pr:measures ex:BC_06_01_001 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH back-doc v0.7" . + pr:hasSource "SODH Gold" ; + pr:hasUnit "9L" , "L" ; + pr:hasIdentifier "M-06.01-001" ; pr:hasName "Sell Out Volume" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasFormula "SUM(validated sell-out volumes over the period)" ; + pr:measures ex:BC_06_01_001 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:M_06_01_002 a pr:Metric ; - pr:hasIdentifier "M-06.01-002" ; pr:hasName "Sell Out Value" ; - pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; - pr:hasFormula "SUM(validated sell-out value over the period)" ; - pr:hasUnit "EUR" ; - pr:measures ex:BC_06_01_001 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH back-doc v0.7" . + pr:hasSource "SODH Gold" ; + pr:hasUnit "EUR" , "LC" , "USD" ; + pr:hasIdentifier "M-06.01-002" ; pr:hasName "Sell Out Value" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasFormula "SUM(validated sell-out value over the period)" ; + pr:measures ex:BC_06_01_001 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:M_06_01_003 a pr:Metric ; - pr:hasIdentifier "M-06.01-003" ; pr:hasName "Sell Out Numeric Distribution" ; - pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; - pr:hasFormula "COUNT(outlets carrying the SKU) / COUNT(universe outlets) * 100" ; - pr:hasUnit "%" ; - pr:measures ex:BC_06_01_004 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH back-doc v0.7" . + pr:hasSource "SODH Gold" ; + pr:hasUnit "%" ; + pr:hasIdentifier "M-06.01-003" ; pr:hasName "Sell Out Numeric Distribution" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasFormula "COUNT(outlets carrying the SKU) / COUNT(universe outlets) * 100" ; + pr:measures ex:BC_06_01_004 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:M_06_01_004 a pr:Metric ; - pr:hasIdentifier "M-06.01-004" ; pr:hasName "Sell Out Weighted Distribution" ; - pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; - pr:hasFormula "SUM(category turnover of outlets carrying SKU) / SUM(total category turnover) * 100" ; - pr:hasUnit "%" ; - pr:measures ex:BC_06_01_004 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH back-doc v0.7" . + pr:hasSource "SODH Gold" ; + pr:hasUnit "%" ; + pr:hasIdentifier "M-06.01-004" ; pr:hasName "Sell Out Weighted Distribution" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasFormula "SUM(category turnover of outlets carrying SKU) / SUM(total category turnover) * 100" ; + pr:measures ex:BC_06_01_004 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:M_06_01_005 a pr:Metric ; - pr:hasIdentifier "M-06.01-005" ; pr:hasName "Sell Out Total Distribution Points" ; - pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; - pr:hasFormula "SUM(Weighted Distribution across SKUs of the brand)" ; - pr:hasUnit "Index" ; - pr:measures ex:BC_06_01_004 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH back-doc v0.7" . + pr:hasSource "SODH Gold" ; + pr:hasUnit "Index" ; + pr:hasIdentifier "M-06.01-005" ; pr:hasName "Sell Out Total Distribution Points" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasFormula "SUM(Weighted Distribution across SKUs of the brand)" ; + pr:measures ex:BC_06_01_004 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:M_06_01_006 a pr:Metric ; - pr:hasIdentifier "M-06.01-006" ; pr:hasName "Retailer Stock Forward Volume" ; - pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; - pr:hasFormula "SUM(retailer forward stock volume at period end)" ; - pr:hasUnit "9L" ; - pr:measures ex:BC_06_01_002 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH back-doc v0.7" . + pr:hasSource "SODH Gold" ; + pr:hasUnit "9L" , "L" ; + pr:hasIdentifier "M-06.01-006" ; pr:hasName "Retailer Stock Forward Volume" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasFormula "SUM(retailer forward stock volume at period end)" ; + pr:measures ex:BC_06_01_002 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:M_06_01_007 a pr:Metric ; - pr:hasIdentifier "M-06.01-007" ; pr:hasName "Retailer Stock Days of Coverage" ; - pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; - pr:hasFormula "Retailer Forward Stock Volume / average daily Sell Out Volume" ; - pr:hasUnit "Days" ; - pr:measures ex:BC_06_01_002 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH back-doc v0.7" . + pr:hasSource "SODH Gold" ; + pr:hasUnit "Days" ; + pr:hasIdentifier "M-06.01-007" ; pr:hasName "Retailer Stock Days of Coverage" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasFormula "Retailer Forward Stock Volume / average daily Sell Out Volume" ; + pr:measures ex:BC_06_01_002 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:M_06_01_008 a pr:Metric ; - pr:hasIdentifier "M-06.01-008" ; pr:hasName "Retailer Stock Share" ; - pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; - pr:hasFormula "SKU retailer stock / category retailer stock * 100" ; - pr:hasUnit "%" ; - pr:measures ex:BC_06_01_002 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH back-doc v0.7" . + pr:hasSource "SODH Gold" ; + pr:hasUnit "%" ; + pr:hasIdentifier "M-06.01-008" ; pr:hasName "Retailer Stock Share" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasFormula "SKU retailer stock / category retailer stock * 100" ; + pr:measures ex:BC_06_01_002 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:M_06_01_009 a pr:Metric ; - pr:hasIdentifier "M-06.01-009" ; pr:hasName "Sell Out Baseline Volume Modelled" ; - pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; - pr:hasFormula "MODEL(expected volume under a no-promotion scenario)" ; - pr:hasUnit "9L" ; - pr:measures ex:BC_06_01_005 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH back-doc v0.7" . + pr:hasSource "SODH Gold" ; + pr:hasUnit "9L" , "L" ; + pr:hasIdentifier "M-06.01-009" ; pr:hasName "Sell Out Baseline Volume Modelled" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasFormula "MODEL(expected volume under a no-promotion scenario)" ; + pr:measures ex:BC_06_01_005 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . ex:M_06_01_010 a pr:Metric ; - pr:hasIdentifier "M-06.01-010" ; pr:hasName "Sell Out Incremental Volume Modelled" ; - pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; - pr:hasFormula "Observed Sell Out Volume - Baseline Volume" ; - pr:hasUnit "9L" ; - pr:measures ex:BC_06_01_005 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH back-doc v0.7" . + pr:hasSource "SODH Gold" ; + pr:hasUnit "9L" , "L" ; + pr:hasIdentifier "M-06.01-010" ; pr:hasName "Sell Out Incremental Volume Modelled" ; + pr:owningDomain ex:DD_06 ; pr:ownedBy ex:ST_DD_06 ; + pr:hasFormula "Observed Sell Out Volume - Baseline Volume" ; + pr:measures ex:BC_06_01_005 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" + . # --- dimensional data elements -------------------------------------- # Each represents the Concept it carries: the first of the two routes @@ -744,28 +820,28 @@ ex:DE_05_01_0001 a pr:DataElement ; pr:hasFormat "BOOLEAN" ; pr:physicalName "GL_PROMO_TPR_FLG" ; pr:represents ex:BC_05_01_001 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold (flag)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . 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:hasFormat "BOOLEAN" ; pr:physicalName "GL_PROMO_MULTIBUY_FLG" ; pr:represents ex:BC_05_01_001 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold (flag)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . 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:hasFormat "BOOLEAN" ; pr:physicalName "GL_PROMO_FEATURE_DISPLAY_FLG" ; pr:represents ex:BC_05_01_001 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold (flag)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . 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:hasFormat "BOOLEAN" ; pr:physicalName "GL_PROMO_BROCHURE_FLG" ; pr:represents ex:BC_05_01_001 ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold (flag)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . # --- granular measure data elements --------------------------------- # Same calculation rule as their mother metric, different analysis @@ -777,665 +853,665 @@ ex:DE_06_01_0001 a pr:DataElement ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_VOLUME_ANY_PROMO_W_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0002 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0002" ; pr:hasName "Sell Out Volume Any-Promotion Weekly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_VOLUME_ANY_PROMO_W_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0003 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0003" ; pr:hasName "Sell Out Volume Any-Promotion 4-Weekly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_VOLUME_ANY_PROMO_4W_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0004 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0004" ; pr:hasName "Sell Out Volume Any-Promotion 4-Weekly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_VOLUME_ANY_PROMO_4W_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0005 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0005" ; pr:hasName "Sell Out Volume Any-Promotion Monthly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_VOLUME_ANY_PROMO_M_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0006 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0006" ; pr:hasName "Sell Out Volume Any-Promotion Monthly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_VOLUME_ANY_PROMO_M_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0007 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0007" ; pr:hasName "Sell Out Volume Any-Promotion MAT (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_VOLUME_ANY_PROMO_MAT_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0008 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0008" ; pr:hasName "Sell Out Volume Any-Promotion MAT (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_VOLUME_ANY_PROMO_MAT_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0009 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0009" ; pr:hasName "Sell Out Volume Non-Promoted Weekly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_VOLUME_NO_PROMO_W_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0010 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0010" ; pr:hasName "Sell Out Volume Non-Promoted Weekly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_VOLUME_NO_PROMO_W_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0011 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0011" ; pr:hasName "Sell Out Volume Non-Promoted 4-Weekly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_VOLUME_NO_PROMO_4W_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0012 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0012" ; pr:hasName "Sell Out Volume Non-Promoted 4-Weekly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_VOLUME_NO_PROMO_4W_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0013 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0013" ; pr:hasName "Sell Out Volume Non-Promoted Monthly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_VOLUME_NO_PROMO_M_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0014 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0014" ; pr:hasName "Sell Out Volume Non-Promoted Monthly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_VOLUME_NO_PROMO_M_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0015 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0015" ; pr:hasName "Sell Out Volume Non-Promoted MAT (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_VOLUME_NO_PROMO_MAT_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0016 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0016" ; pr:hasName "Sell Out Volume Non-Promoted MAT (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_VOLUME_NO_PROMO_MAT_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0017 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0017" ; pr:hasName "Sell Out Volume Promoted Weekly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_VOLUME_PROMO_W_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0018 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0018" ; pr:hasName "Sell Out Volume Promoted Weekly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_VOLUME_PROMO_W_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0019 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0019" ; pr:hasName "Sell Out Volume Promoted 4-Weekly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_VOLUME_PROMO_4W_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0020 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0020" ; pr:hasName "Sell Out Volume Promoted 4-Weekly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_VOLUME_PROMO_4W_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0021 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0021" ; pr:hasName "Sell Out Volume Promoted Monthly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_VOLUME_PROMO_M_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0022 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0022" ; pr:hasName "Sell Out Volume Promoted Monthly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_VOLUME_PROMO_M_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0023 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0023" ; pr:hasName "Sell Out Volume Promoted MAT (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_VOLUME_PROMO_MAT_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0024 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0024" ; pr:hasName "Sell Out Volume Promoted MAT (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_VOLUME_PROMO_MAT_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0025 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0025" ; pr:hasName "Sell Out Value Any-Promotion Weekly (EUR)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "EUR" ; pr:physicalName "GL_VALUE_ANY_PROMO_W_EUR_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0026 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0026" ; pr:hasName "Sell Out Value Any-Promotion Weekly (USD)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "USD" ; pr:physicalName "GL_VALUE_ANY_PROMO_W_USD_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0027 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0027" ; pr:hasName "Sell Out Value Any-Promotion Weekly (LC)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "LC" ; pr:physicalName "GL_VALUE_ANY_PROMO_W_LC_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0028 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0028" ; pr:hasName "Sell Out Value Any-Promotion 4-Weekly (EUR)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "EUR" ; pr:physicalName "GL_VALUE_ANY_PROMO_4W_EUR_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0029 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0029" ; pr:hasName "Sell Out Value Any-Promotion 4-Weekly (USD)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "USD" ; pr:physicalName "GL_VALUE_ANY_PROMO_4W_USD_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0030 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0030" ; pr:hasName "Sell Out Value Any-Promotion 4-Weekly (LC)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "LC" ; pr:physicalName "GL_VALUE_ANY_PROMO_4W_LC_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0031 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0031" ; pr:hasName "Sell Out Value Any-Promotion Monthly (EUR)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "EUR" ; pr:physicalName "GL_VALUE_ANY_PROMO_M_EUR_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0032 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0032" ; pr:hasName "Sell Out Value Any-Promotion Monthly (USD)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "USD" ; pr:physicalName "GL_VALUE_ANY_PROMO_M_USD_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0033 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0033" ; pr:hasName "Sell Out Value Any-Promotion Monthly (LC)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "LC" ; pr:physicalName "GL_VALUE_ANY_PROMO_M_LC_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0034 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0034" ; pr:hasName "Sell Out Value Any-Promotion MAT (EUR)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "EUR" ; pr:physicalName "GL_VALUE_ANY_PROMO_MAT_EUR_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0035 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0035" ; pr:hasName "Sell Out Value Any-Promotion MAT (USD)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "USD" ; pr:physicalName "GL_VALUE_ANY_PROMO_MAT_USD_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0036 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0036" ; pr:hasName "Sell Out Value Any-Promotion MAT (LC)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "LC" ; pr:physicalName "GL_VALUE_ANY_PROMO_MAT_LC_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0037 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0037" ; pr:hasName "Sell Out Value Non-Promoted Weekly (EUR)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "EUR" ; pr:physicalName "GL_VALUE_NO_PROMO_W_EUR_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0038 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0038" ; pr:hasName "Sell Out Value Non-Promoted Weekly (USD)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "USD" ; pr:physicalName "GL_VALUE_NO_PROMO_W_USD_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0039 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0039" ; pr:hasName "Sell Out Value Non-Promoted Weekly (LC)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "LC" ; pr:physicalName "GL_VALUE_NO_PROMO_W_LC_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0040 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0040" ; pr:hasName "Sell Out Value Non-Promoted 4-Weekly (EUR)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "EUR" ; pr:physicalName "GL_VALUE_NO_PROMO_4W_EUR_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0041 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0041" ; pr:hasName "Sell Out Value Non-Promoted 4-Weekly (USD)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "USD" ; pr:physicalName "GL_VALUE_NO_PROMO_4W_USD_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0042 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0042" ; pr:hasName "Sell Out Value Non-Promoted 4-Weekly (LC)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "LC" ; pr:physicalName "GL_VALUE_NO_PROMO_4W_LC_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0043 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0043" ; pr:hasName "Sell Out Value Non-Promoted Monthly (EUR)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "EUR" ; pr:physicalName "GL_VALUE_NO_PROMO_M_EUR_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0044 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0044" ; pr:hasName "Sell Out Value Non-Promoted Monthly (USD)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "USD" ; pr:physicalName "GL_VALUE_NO_PROMO_M_USD_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0045 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0045" ; pr:hasName "Sell Out Value Non-Promoted Monthly (LC)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "LC" ; pr:physicalName "GL_VALUE_NO_PROMO_M_LC_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0046 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0046" ; pr:hasName "Sell Out Value Non-Promoted MAT (EUR)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "EUR" ; pr:physicalName "GL_VALUE_NO_PROMO_MAT_EUR_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0047 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0047" ; pr:hasName "Sell Out Value Non-Promoted MAT (USD)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "USD" ; pr:physicalName "GL_VALUE_NO_PROMO_MAT_USD_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0048 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0048" ; pr:hasName "Sell Out Value Non-Promoted MAT (LC)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "LC" ; pr:physicalName "GL_VALUE_NO_PROMO_MAT_LC_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0049 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0049" ; pr:hasName "Sell Out Value Promoted Weekly (EUR)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "EUR" ; pr:physicalName "GL_VALUE_PROMO_W_EUR_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0050 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0050" ; pr:hasName "Sell Out Value Promoted Weekly (USD)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "USD" ; pr:physicalName "GL_VALUE_PROMO_W_USD_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0051 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0051" ; pr:hasName "Sell Out Value Promoted Weekly (LC)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "LC" ; pr:physicalName "GL_VALUE_PROMO_W_LC_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0052 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0052" ; pr:hasName "Sell Out Value Promoted 4-Weekly (EUR)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "EUR" ; pr:physicalName "GL_VALUE_PROMO_4W_EUR_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0053 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0053" ; pr:hasName "Sell Out Value Promoted 4-Weekly (USD)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "USD" ; pr:physicalName "GL_VALUE_PROMO_4W_USD_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0054 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0054" ; pr:hasName "Sell Out Value Promoted 4-Weekly (LC)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "LC" ; pr:physicalName "GL_VALUE_PROMO_4W_LC_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0055 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0055" ; pr:hasName "Sell Out Value Promoted Monthly (EUR)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "EUR" ; pr:physicalName "GL_VALUE_PROMO_M_EUR_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0056 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0056" ; pr:hasName "Sell Out Value Promoted Monthly (USD)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "USD" ; pr:physicalName "GL_VALUE_PROMO_M_USD_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0057 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0057" ; pr:hasName "Sell Out Value Promoted Monthly (LC)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "LC" ; pr:physicalName "GL_VALUE_PROMO_M_LC_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0058 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0058" ; pr:hasName "Sell Out Value Promoted MAT (EUR)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "EUR" ; pr:physicalName "GL_VALUE_PROMO_MAT_EUR_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0059 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0059" ; pr:hasName "Sell Out Value Promoted MAT (USD)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "USD" ; pr:physicalName "GL_VALUE_PROMO_MAT_USD_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0060 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0060" ; pr:hasName "Sell Out Value Promoted MAT (LC)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "LC" ; pr:physicalName "GL_VALUE_PROMO_MAT_LC_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0061 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0061" ; pr:hasName "Sell Out Numeric Distribution Total 4-Weekly (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_NUM_DIST_TOT_4W_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0062 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0062" ; pr:hasName "Sell Out Numeric Distribution Total Monthly (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_NUM_DIST_TOT_M_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0063 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0063" ; pr:hasName "Sell Out Numeric Distribution Total MAT (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_NUM_DIST_TOT_MAT_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0064 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0064" ; pr:hasName "Sell Out Numeric Distribution Promoted 4-Weekly (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_NUM_DIST_PROMO_4W_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0065 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0065" ; pr:hasName "Sell Out Numeric Distribution Promoted Monthly (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_NUM_DIST_PROMO_M_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0066 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0066" ; pr:hasName "Sell Out Numeric Distribution Promoted MAT (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_NUM_DIST_PROMO_MAT_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0067 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0067" ; pr:hasName "Sell Out Weighted Distribution Total 4-Weekly (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_WGT_DIST_TOT_4W_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0068 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0068" ; pr:hasName "Sell Out Weighted Distribution Total Monthly (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_WGT_DIST_TOT_M_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0069 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0069" ; pr:hasName "Sell Out Weighted Distribution Total MAT (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_WGT_DIST_TOT_MAT_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0070 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0070" ; pr:hasName "Sell Out Weighted Distribution Promoted 4-Weekly (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_WGT_DIST_PROMO_4W_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0071 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0071" ; pr:hasName "Sell Out Weighted Distribution Promoted Monthly (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_WGT_DIST_PROMO_M_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0072 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0072" ; pr:hasName "Sell Out Weighted Distribution Promoted MAT (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_WGT_DIST_PROMO_MAT_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0073 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0073" ; pr:hasName "Sell Out Total Distribution Points 4-Weekly (Index)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "Index" ; pr:physicalName "GL_TDP_4W_IDX_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0074 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0074" ; pr:hasName "Sell Out Total Distribution Points Monthly (Index)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "Index" ; pr:physicalName "GL_TDP_M_IDX_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0075 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0075" ; pr:hasName "Sell Out Total Distribution Points MAT (Index)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "Index" ; pr:physicalName "GL_TDP_MAT_IDX_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0076 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0076" ; pr:hasName "Retailer Stock Forward Volume Weekly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_STOCK_FWD_W_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0077 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0077" ; pr:hasName "Retailer Stock Forward Volume Weekly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_STOCK_FWD_W_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0078 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0078" ; pr:hasName "Retailer Stock Forward Volume 4-Weekly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_STOCK_FWD_4W_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0079 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0079" ; pr:hasName "Retailer Stock Forward Volume 4-Weekly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_STOCK_FWD_4W_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0080 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0080" ; pr:hasName "Retailer Stock Forward Volume Monthly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_STOCK_FWD_M_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0081 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0081" ; pr:hasName "Retailer Stock Forward Volume Monthly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_STOCK_FWD_M_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0082 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0082" ; pr:hasName "Retailer Stock Days of Coverage Weekly (Days)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "Days" ; pr:physicalName "GL_STOCK_COV_W_DAYS_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0083 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0083" ; pr:hasName "Retailer Stock Days of Coverage 4-Weekly (Days)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "Days" ; pr:physicalName "GL_STOCK_COV_4W_DAYS_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0084 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0084" ; pr:hasName "Retailer Stock Days of Coverage Monthly (Days)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "Days" ; pr:physicalName "GL_STOCK_COV_M_DAYS_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0085 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0085" ; pr:hasName "Retailer Stock Share Weekly (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_STOCK_SHARE_W_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0086 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0086" ; pr:hasName "Retailer Stock Share 4-Weekly (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_STOCK_SHARE_4W_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0087 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0087" ; pr:hasName "Retailer Stock Share Monthly (%)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "%" ; pr:physicalName "GL_STOCK_SHARE_M_PCT_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0088 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0088" ; pr:hasName "Sell Out Baseline Volume Modelled 4-Weekly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_BASELINE_VOL_4W_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0089 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0089" ; pr:hasName "Sell Out Baseline Volume Modelled 4-Weekly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_BASELINE_VOL_4W_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0090 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0090" ; pr:hasName "Sell Out Baseline Volume Modelled Monthly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_BASELINE_VOL_M_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0091 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0091" ; pr:hasName "Sell Out Baseline Volume Modelled Monthly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_BASELINE_VOL_M_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0092 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0092" ; pr:hasName "Sell Out Incremental Volume Modelled 4-Weekly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_INCR_VOL_4W_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0093 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0093" ; pr:hasName "Sell Out Incremental Volume Modelled 4-Weekly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_INCR_VOL_4W_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0094 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0094" ; pr:hasName "Sell Out Incremental Volume Modelled Monthly (9L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "9L" ; pr:physicalName "GL_INCR_VOL_M_9L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . ex:DE_06_01_0095 a pr:DataElement ; pr:hasIdentifier "DE-06.01-0095" ; pr:hasName "Sell Out Incremental Volume Modelled Monthly (L)" ; pr:owningDomain ex:DD_06 ; pr:hasFormat "DECIMAL(18,4)" ; pr:hasUnit "L" ; pr:physicalName "GL_INCR_VOL_M_L_NM" ; - pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "Snowflake (SODH Gold)" . + pr:hasStatus "DRAFT" ; pr:hasVersion "1.1" ; pr:hasSource "SODH Gold" . # --- BR-013 wiring: each mother metric and the variants that implement it @@ -1465,3 +1541,42 @@ ex:M_06_01_007 pr:computedBy ex:DE_06_01_0082 , ex:DE_06_01_0083 , ex:DE_06_01_0 ex:M_06_01_008 pr:computedBy ex:DE_06_01_0085 , ex:DE_06_01_0086 , ex:DE_06_01_0087 . ex:M_06_01_009 pr:computedBy ex:DE_06_01_0088 , ex:DE_06_01_0089 , ex:DE_06_01_0090 , ex:DE_06_01_0091 . ex:M_06_01_010 pr:computedBy ex:DE_06_01_0092 , ex:DE_06_01_0093 , ex:DE_06_01_0094 , ex:DE_06_01_0095 . + + +# --- data product ------------------------------------------------------ +# The unit of delivery: what carries a version, an owner, a changelog and a +# list of consumers to warn. It has no single technical counterpart on purpose +# -- rename the schema and the product is unchanged; change its SLA and it is +# not. Its physical footprint is derivable through packages, never asserted. + +ex:DP_06_01_001 a pr:DataProduct ; + pr:hasIdentifier "DP-06.01-001" ; pr:hasName "Sell Out Data Hub - Gold" ; + pr:owningDomain ex:DD_06 ; + pr:operatedBy ex:SD_06_01 ; + pr:packages ex:DO_06_01_001 , + ex:DO_06_01_003 , + ex:DO_10_01_001 , + ex:DO_04_01_001 , + ex:DO_16_01_001 , + ex:DO_21_01_001 ; + pr:governedBy ex:DC_06_01_001 ; + pr:exposes ex:DI_06_01_001 , ex:DI_06_01_002 ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.2" ; pr:hasSource "SODH Gold" . + +ex:DC_06_01_001 a pr:DataContract ; + pr:hasIdentifier "DC-06.01-001" ; pr:hasName "Sell Out Data Hub - Gold service commitment" ; + pr:owningDomain ex:DD_06 ; + pr:hasBusinessRule "Refreshed weekly, available Tuesday 06:00 CET. Panel coverage published with every load. Schema stable within a minor version; 60 days notice on any breaking change. Support through the Sales Performance data team." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.2" . + +ex:DI_06_01_001 a pr:DataInterface ; + pr:hasIdentifier "DI-06.01-001" ; pr:hasName "SODH Gold consumption schema" ; + pr:owningDomain ex:DD_06 ; + pr:hasBusinessDefinition "Read access to the Gold layer through Snowflake grants. The channel BI tools and analysts consume."@en ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.2" ; pr:hasSource "SODH Gold" . + +ex:DI_06_01_002 a pr:DataInterface ; + pr:hasIdentifier "DI-06.01-002" ; pr:hasName "Panel partner weekly extract" ; + pr:owningDomain ex:DD_06 ; + pr:hasBusinessDefinition "File extract delivered to the panel provider each week. Outside Snowflake, and the boundary where lineage leaves the warehouse."@en ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.2" ; pr:hasSource "SODH Gold" . diff --git a/ontology/pr_metamodel.ttl b/ontology/pr_metamodel.ttl index c0289e6..a9e9a09 100644 --- a/ontology/pr_metamodel.ttl +++ b/ontology/pr_metamodel.ttl @@ -7,11 +7,45 @@ @prefix pr: . ################################################################# -# PERNOD RICARD DATA META MODEL - T-Box v1.1 +# PERNOD RICARD DATA META MODEL - T-Box v1.2 # Owner: Bastien Gourdon - Data Governance Office # -# WHAT CHANGED vs v1.0 +# WHAT CHANGED vs v1.1 # -------------------- +# A. physicalizedIn -> storedIn. "Physicalized in" is jargon; the relation +# says where the data sits, and "stored in" says that to anyone. +# +# B. hasSource MEANS ONE THING: the system that holds the authoritative data. +# It was drifting into a third meaning on Business Concepts and Metrics, +# where it named the DOCUMENT a record came from ("SODH back-doc v0.7"). +# A business concept has no data source -- it has an owning domain, which +# is its authority, and a definition published in the glossary. The +# property is now forbidden on concepts by SHACL. +# +# C. FIVE TERMS REMOVED OR DEPRECATED, none of them instantiated anywhere: +# Table deleted -- deprecated since v1.0, replaced by +# BaseTable, and never used. A deprecation that +# never ends stops meaning anything. +# constrainsRelation derivable: constrainsField -> livesIn gives the +# relation. An asserted edge redundant with an +# existing path is a future divergence. +# MaterializedView a class standing in for an attribute; being +# derived is what dependsOn already says. +# approximateRowCount volumetry with no identified use. queryCount30d +# and lastQueriedOn carry the rationalisation case. +# hasVendor marginal. +# +# D. hasProductOwner drops from BLOCKING to a warning in the shapes. The SODH +# product owner is genuinely not appointed yet, and a blocking rule that +# cannot be satisfied truthfully forces invention -- the BR-004 lesson. +# +# Environment and deployedIn stay, unused. Without them a DEV table and its +# PROD twin are indistinguishable and lineage pollutes silently on the first +# multi-environment harvest. Unused because nothing is harvested yet, not +# because they are useless. +# +# WHAT CHANGED vs v1.0 (kept for reference) +# ----------------------------------------- # A. RELATIONS SIMPLIFIED AND GIVEN HONEST NAMES # belongsToDomain + belongsToSubDomain -> belongsTo (one property, # scoped per class by SHACL; reusable if a domain family appears). @@ -117,12 +151,12 @@ a owl:Ontology ; - owl:versionIRI ; - owl:priorVersion ; + owl:versionIRI ; + owl:priorVersion ; dcterms:title "Pernod Ricard Data Meta Model" ; dcterms:creator "Bastien Gourdon" ; - dcterms:modified "2026-07-26"^^xsd:date ; - owl:versionInfo "1.1.0-draft-2026-07-26" ; + dcterms:modified "2026-07-27"^^xsd:date ; + owl:versionInfo "1.2.0-draft-2026-07-27" ; rdfs:comment """OWL T-Box. Six layers (Ownership, Business, Logical, Delivery, 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.""" . @@ -294,18 +328,14 @@ pr:View a owl:Class ; rdfs:subClassOf pr:PhysicalRelation ; 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" . + rdfs:label "Materialized View (deprecated)" ; + owl:deprecated true ; dcterms:isReplacedBy pr:View ; + rdfs:comment "A class standing in for an attribute: what distinguishes it is being derived, which dependsOn already states." . pr:ExternalTable a owl:Class ; rdfs:subClassOf pr: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" ; @@ -550,11 +580,15 @@ pr:materializedAs a owl:ObjectProperty ; 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" ; +pr:storedIn a owl:ObjectProperty ; + rdfs:label "stored 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." . + 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. Renamed from physicalizedIn, which was jargon: the relation says where the data sits." . + +pr:physicalizedIn a owl:ObjectProperty ; + rdfs:label "physicalized in (deprecated)" ; + owl:deprecated true ; dcterms:isReplacedBy pr:storedIn . ################################################################# @@ -597,8 +631,9 @@ pr:deployedIn a owl:ObjectProperty , owl:FunctionalProperty ; 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 . + rdfs:label "constrains relation (deprecated)" ; + owl:deprecated true ; + rdfs:comment "Derivable: constrainsField then livesIn gives the relation. An asserted edge redundant with an existing path is a divergence waiting to happen." . pr:constrainsField a owl:ObjectProperty ; rdfs:label "constrains field" ; @@ -738,7 +773,14 @@ pr:hasStatus a owl:DatatypeProperty ; rdfs:label "status" ; rdfs:range xsd:s 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:hasSource a owl:DatatypeProperty ; rdfs:label "source of truth" ; rdfs:range xsd:string ; + rdfs:comment """The system holding the authoritative data for this object: SODH Gold, MDM Product, Group Finance. + +ONE MEANING ONLY. It had drifted into naming the document a record came from ("SODH back-doc v0.7"), and into packing a system and a relation into one string ("SODH Gold / F_SELL_OUT"). The relation belongs in physicalName; the document belongs nowhere -- provenance of a record is not a property of the thing it describes. + +MUST NOT appear on a Business Concept: a concept has no data source. Its authority is its owning domain and its definition is published in the glossary. Enforced by SHACL. + +On a Metric it is asserted rather than derived, by exception, because "where can I get this metric" is a first-order question and the derivation chain (computedBy, then the data object holding the element) will have gaps before harvesting. A shape checks the assertion against the elements.""" . pr:arbitrationStatus a owl:DatatypeProperty ; rdfs:label "arbitration status" ; rdfs:range xsd:string ; rdfs:comment "TO_ARBITRATE blocks publication (OW-007). Use this on the object rather than tracking arbitration in a side spreadsheet." . @@ -798,12 +840,15 @@ pr:hasPhysicalDataType a owl:DatatypeProperty ; rdfs:label "physical data type" 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:hasVendor a owl:DatatypeProperty ; rdfs:label "vendor (deprecated)" ; rdfs:range xsd:string ; + owl:deprecated true . pr:definedBy a owl:DatatypeProperty ; rdfs:label "defined by (code URI)" ; rdfs:range xsd:anyURI ; rdfs:comment "Git URI of the code behind a Transformation. Connects the graph to the repository." . # ---- Usage evidence (the rationalisation argument) -pr:approximateRowCount a owl:DatatypeProperty ; rdfs:label "approximate row count" ; rdfs:range xsd:integer . +pr:approximateRowCount a owl:DatatypeProperty ; rdfs:label "approximate row count (deprecated)" ; rdfs:range xsd:integer ; + owl:deprecated true ; + rdfs:comment "Volumetry with no identified use. queryCount30d and lastQueriedOn carry the rationalisation argument; row count does not." . pr:lastQueriedOn a owl:DatatypeProperty ; rdfs:label "last queried on" ; rdfs:range xsd:dateTime ; pr:harvestSource "SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY" ; rdfs:comment "Lets the model say 'documented but unused for eight months'. A rationalisation argument that costs nothing and is denominated in warehouse spend." . diff --git a/scripts/apply_fixes_v1_2.py b/scripts/apply_fixes_v1_2.py new file mode 100644 index 0000000..01655b2 --- /dev/null +++ b/scripts/apply_fixes_v1_2.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +""" +SODH - data fixes v1.2 +======================= +Four corrections found while reviewing the viewer. + +USAGE + python3 scripts/apply_fixes_v1_2.py # dry run + python3 scripts/apply_fixes_v1_2.py --apply # rewrite, .bak kept + +1. DATA DOMAIN OWNERS + Six named owners added, plus the hasDomainOwner link. Ownership was + readable only through the DGL until now, which conflates two roles: the + owner is accountable for the domain, the DGL runs its governance. + +2. hasSource CARRIED TWO MEANINGS + On a Data Element it named the system of origin (MDM Product, Group + Finance). On a Data Object it named the physical location (SODH Gold / + F_SELL_OUT). Same property, two concepts -- the defect we removed from + composedOf, still present one layer down. Split: physicalName holds the + relation name, hasSource holds the system. This is also what makes the + physical layer derivable: F_SELL_OUT plus 132 column names. + +3. METRIC UNITS ARE PLURAL + Five mother metrics compute elements in several units (9L and L, EUR and + LC and USD). A metric states every unit its variants are expressed in; a + Data Element states exactly one. Recomputed from the elements rather than + asserted, so the two can never drift. + +4. Sell Out Distribution -> Retail Distribution, matching the concept: sell + out counts units sold, distribution measures presence in store. +""" +import os +import re +import shutil +import sys + +REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +TTL = os.path.join(REPO, "instances", "sodh.ttl") +W = 78 + +OWNERS = [ + ("ex:DDO_DD_06", "Ludovic Cottier", "ex:DD_06"), + ("ex:DDO_DD_04", "David Carpio", "ex:DD_04"), + ("ex:DDO_DD_10", "Alberto Lupano", "ex:DD_10"), + ("ex:DDO_DD_05", "David Carpio", "ex:DD_05"), + ("ex:DDO_DD_16", "Julien Soisson", "ex:DD_16"), + ("ex:DDO_DD_21", "Vincent Meunier", "ex:DD_21"), +] + +RENAME = {"ex:BO_06_01_003": ("Sell Out Distribution", "Retail Distribution")} + +# system of origin, once the physical location is taken out of it +SOURCE_FIX = { + "Snowflake (SODH Gold)": "SODH Gold", + "SODH Gold (flag)": "SODH Gold", +} + + +def clauses(body): + out, buf, i, n = [], [], 0, len(body) + while i < n: + c = body[i] + if c == '"': + j = i + 1 + while j < n and body[j] != '"': + j += 2 if body[j] == "\\" else 1 + j = min(j + 1, n) + buf.append(body[i:j]); i = j; continue + if c == ";": + out.append("".join(buf)); buf = []; i += 1; continue + buf.append(c); i += 1 + out.append("".join(buf)) + return out + + +def set_prop(text, subject, prop, value): + """Clause-level set: survives mid-line properties and semicolons in literals.""" + pat = re.compile(r'(^%s a pr:\w+ ;)(.*?)(\.\s*\n)' % re.escape(subject), re.S | re.M) + m = pat.search(text) + if not m: + return text, False + kept = [c for c in clauses(m.group(2)) + if c.strip() and not re.match(r'\s*%s\s' % re.escape(prop), c)] + if value is not None: + kept.insert(0, "\n %s %s " % (prop, value)) + body = " ;".join(kept).rstrip() + "\n " + return text[:m.start()] + m.group(1) + body + m.group(3) + text[m.end():], True + + +def get_prop(text, subject, prop): + m = re.search(r'^%s a pr:\w+ ;(.*?)\.\s*\n' % re.escape(subject), text, re.S | re.M) + if not m: + return None + for c in clauses(m.group(1)): + mm = re.match(r'\s*%s\s+(.*)$' % re.escape(prop), c, re.S) + if mm: + return mm.group(1).strip() + return None + + +def main(): + apply_changes = "--apply" in sys.argv + if not os.path.exists(TTL): + print("Not found: %s" % TTL); sys.exit(2) + text = original = open(TTL, encoding="utf-8").read() + report = [] + + # ---- 1. domain owners ------------------------------------------------- + todo = [o for o in OWNERS if not re.search(r'^%s a ' % re.escape(o[0]), text, re.M)] + if todo: + blocks = "\n".join('%s a pr:DataDomainOwner ; pr:hasName "%s" .' % (i, n) + for i, n, _ in todo) + anchor = re.search(r'^ex:ST_\w+ a pr:DataSteward', text, re.M) + pos = anchor.start() if anchor else 0 + text = (text[:pos] + + "# --- data domain owners ----------------------------------------------\n" + + "# Accountable for the domain. Distinct from the DGL, who runs its\n" + + "# governance: two roles that hasDGL alone was conflating.\n" + + blocks + "\n\n" + text[pos:]) + for i, n, dd in OWNERS: + text, _ = set_prop(text, dd, "pr:hasDomainOwner", i) + report.append("%d data domain owners named and linked" % len(OWNERS)) + + # ---- 2. physical location out of hasSource ---------------------------- + n_do = 0 + for m in list(re.finditer(r'^(ex:DO_\w+) a pr:DataObject ;', text, re.M)): + subj = m.group(1) + src = get_prop(text, subj, "pr:hasSource") + if src and "/" in src: + system, relation = [p.strip().strip('"') for p in src.strip('"').split("/", 1)] + text, _ = set_prop(text, subj, "pr:hasSource", '"%s"' % system) + text, _ = set_prop(text, subj, "pr:physicalName", '"%s"' % relation) + n_do += 1 + report.append("%d data objects: physical relation moved out of hasSource" % n_do) + + n_de = 0 + for old, new in SOURCE_FIX.items(): + text, k = re.subn(r'pr:hasSource "%s"' % re.escape(old), + 'pr:hasSource "%s"' % new, text) + n_de += k + report.append("%d source labels normalised to the system of origin" % n_de) + + # ---- 3. metric units, recomputed from the elements --------------------- + de_unit = {} + for m in re.finditer(r'^(ex:DE_\w+) a pr:DataElement ;(.*?)\.\s*\n', text, re.S | re.M): + u = re.search(r'pr:hasUnit\s+"([^"]*)"', m.group(2)) + if u: + de_unit[m.group(1)] = u.group(1) + wired = {} + for m in re.finditer(r'(ex:M_\w+)\s+pr:computedBy(.*?)\.', text, re.S): + wired[m.group(1)] = re.findall(r'ex:DE_\w+', m.group(2)) + n_m = 0 + for metric, des in wired.items(): + units = sorted({de_unit[d] for d in des if d in de_unit and de_unit[d] != "-"}) + if not units: + continue + value = " , ".join('"%s"' % u for u in units) + before = get_prop(text, metric, "pr:hasUnit") + text, ok = set_prop(text, metric, "pr:hasUnit", value) + if ok and before != value: + n_m += 1 + report.append("%d metrics now list every unit their elements use" % n_m) + + # ---- 4. rename -------------------------------------------------------- + for subj, (old, new) in RENAME.items(): + if get_prop(text, subj, "pr:hasName") == '"%s"' % old: + text, _ = set_prop(text, subj, "pr:hasName", '"%s"' % new) + report.append("renamed %s -> %s" % (old, new)) + + # ---- report ----------------------------------------------------------- + print() + print("SODH DATA FIXES v1.2 %s" % ("APPLY" if apply_changes else "DRY RUN")) + print("=" * W) + for line in report: + print(" " + line) + print("=" * W) + multi = len(re.findall(r'pr:hasUnit\s+"[^"]*"\s*,', text)) + phys = len(re.findall(r'^ex:DO_\w+ a pr:DataObject ;(?:(?!\.\s*\n).)*?physicalName', + text, re.S | re.M)) + doubled = [] + for m in re.finditer(r'^(ex:\w+) a pr:\w+ ;(.*?)\.\s*\n', text, re.S | re.M): + for p in ("pr:hasName", "pr:hasSource", "pr:physicalName", "pr:hasDomainOwner"): + if len(re.findall(r'(? 1: + doubled.append("%s/%s" % (m.group(1), p)) + print(" metrics with several units %d | data objects with a physical name %d" + % (multi, phys)) + print(" duplicated single-valued properties: %s" + % (", ".join(doubled) if doubled else "none")) + print("=" * W) + + if text != original and apply_changes: + shutil.copy2(TTL, TTL + ".bak") + open(TTL, "w", encoding="utf-8").write(text) + print(" written, backup at %s.bak" % os.path.basename(TTL)) + elif text != original: + print(" dry run -- re-run with --apply to write") + print() + + +if __name__ == "__main__": + main() diff --git a/scripts/generate_sodh_viewer.py b/scripts/generate_sodh_viewer.py new file mode 100644 index 0000000..5978977 --- /dev/null +++ b/scripts/generate_sodh_viewer.py @@ -0,0 +1,1448 @@ +#!/usr/bin/env python3 +""" +SODH instance viewer generator +=============================== +Reads instances/sodh.ttl and emits a self-contained HTML view of the Sell Out +Data Hub. Build artefact: regenerate, never edit. + +USAGE + python3 scripts/generate_sodh_viewer.py [in.ttl] [out.html] + defaults: instances/sodh.ttl -> generated/sodh_viewer.html + +WHAT IT IS FOR + Making one thing obvious to a business audience: SODH is owned by Sales + Performance but it cannot exist without five other domains. The default + view is the scope, not the graph -- the point is federation, not topology. + + Objects flagged TO_ARBITRATE are shown, not hidden. A declared gap is the + argument: it says which decisions are pending and who owes them. +""" +import json +import os +import re +import sys +from collections import OrderedDict + +PR = "https://ontology.pernod-ricard.com/metamodel/" + +DOMAIN_STYLE = OrderedDict([ + ("DD_06", ("#0A1F44", "Sales Performance", "owner")), + ("DD_10", ("#1E4B9B", "Product & Material", "contributor")), + ("DD_04", ("#1E7A5F", "Customer & Channels", "contributor")), + ("DD_16", ("#8B3A3A", "Financial Performance", "contributor")), + ("DD_21", ("#7A4E8B", "Data Governance", "contributor")), + ("DD_05", ("#B8935A", "Sales Activation", "contributor")), +]) + +MULTI = {"composedOf", "usesConcept", "hasElement", "hasMetric", "measures", + "computedBy", "references", "hasGrainElement", "monitoredBy", "hasSynonym", + "hasUnit", "hasSource", "packages", "exposes"} + +SUBJECT_DOMAIN = "DD_06" # the hub owner: everything else is a dependency + +# Every user-facing label in one place. Hyphenated throughout, and never the +# raw class name: "Physical Relation" and "Physicalized in" told a business +# reader nothing. +LABELS = { + "DataDomain": "Domain", "SubDomain": "Sub-domain", + "BusinessObject": "Business object", "BusinessConcept": "Business concept", + "Metric": "Metric", "DataObject": "Data object", "DataElement": "Data element", + "BaseTable": "Base table", "Field": "Field", + "DataProduct": "Data product", "DataContract": "Data contract", + "DataInterface": "Data interface", +} + +# Which ownership roles a card shows. The Steward section is gone: one section, +# different roles per object, so the reader always looks in the same place. +ROLES = { + "DataDomain": [("Domain owner", "owner"), ("Data-governance lead", "dgl")], + "SubDomain": [("Sub-domain owner", "subOwner"), ("Data-governance lead", "dgl_up")], + "BusinessObject": [("Steward", "steward")], + "BusinessConcept": [("Steward", "steward")], + "Metric": [("Steward", "steward")], + "DataObject": [("Product owner", "productOwner")], + "DataElement": [("Steward", "steward_inherited")], + "DataProduct": [("Product owner", "productOwner")], +} + + +# ---------------------------------------------------------------- parsing +def strip_comments(text): + out, i, n = [], 0, len(text) + while i < n: + c = text[i] + 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_outside(text, sep): + parts, buf, i, n = [], [], 0, len(text) + while i < n: + c = text[i] + 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 == sep: + parts.append("".join(buf)); buf = []; i += 1; continue + buf.append(c); i += 1 + parts.append("".join(buf)) + return parts + + +def unquote(tok): + tok = tok.strip() + if tok.startswith('"'): + end = tok.rindex('"') + return tok[1:end].replace('\\"', '"').replace("\\\\", "\\") + return tok + + +def parse(path): + """Turtle subset reader: enough for an instance file, keeps ordering.""" + text = strip_comments(open(path, encoding="utf-8").read()) + subjects = OrderedDict() + for stmt in split_outside(text, "."): + stmt = stmt.strip() + if not stmt or not stmt.startswith("ex:"): + continue + clauses = split_outside(stmt, ";") + head = clauses[0].strip() + m = re.match(r"^(\S+)\s+(.*)$", head, re.S) + if not m: + continue + subj, rest = m.group(1), m.group(2) + rec = subjects.setdefault(subj, {"id": subj}) + for chunk in [rest] + [c.strip() for c in clauses[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 = "type" if pred in ("a", "rdf:type") else pred.split(":")[-1] + vals = [unquote(t) for t in split_outside(objs, ",") if t.strip()] + if key in MULTI or key in rec: + rec.setdefault(key, []) + if not isinstance(rec[key], list): + rec[key] = [rec[key]] + rec[key] += vals + else: + rec[key] = vals[0] if len(vals) == 1 else vals + return subjects + + +def as_list(v): + if v is None: + return [] + return v if isinstance(v, list) else [v] + + +# ---------------------------------------------------------------- model +def domain_of(subj, rec, subjects): + """Owning domain, walking up belongsTo when not declared.""" + d = rec.get("owningDomain") + if d: + return d.replace("ex:", "") + seen = set() + cur = rec + while cur is not None: + parent = cur.get("belongsTo") or cur.get("represents") + if not parent or parent in seen: + break + seen.add(parent) + cur = subjects.get(parent) + if cur and cur.get("owningDomain"): + return cur["owningDomain"].replace("ex:", "") + m = re.match(r"ex:[A-Z]+_(\d\d)_", subj) + return "DD_" + m.group(1) if m else "DD_06" + + +def build(subjects): + nodes = {} + for subj, rec in subjects.items(): + t = (rec.get("type") or "").replace("pr:", "") + if not t: + continue + nodes[subj] = { + "id": subj, + "kind": t, + "label": LABELS.get(t, t), + "ident": rec.get("hasIdentifier", ""), + "name": rec.get("hasName", subj.replace("ex:", "")), + "domain": domain_of(subj, rec, subjects), + "arb": rec.get("arbitrationStatus", "") == "TO_ARBITRATE", + "status": rec.get("hasStatus", ""), + "definition": rec.get("hasBusinessDefinition", ""), + "rule": rec.get("hasBusinessRule", ""), + "formula": rec.get("hasFormula", ""), + "units": as_list(rec.get("hasUnit")), + "physical": rec.get("physicalName", ""), + "format": rec.get("hasFormat", ""), + "source": ", ".join(as_list(rec.get("hasSource"))), + "version": rec.get("hasVersion", ""), + "operatedBy": rec.get("operatedBy", ""), + "governedBy": rec.get("governedBy", ""), + "productOwner": rec.get("hasProductOwner", ""), + "packages": as_list(rec.get("packages")), + "exposes": as_list(rec.get("exposes")), + "belongsTo": rec.get("belongsTo", ""), + "about": rec.get("aboutConcept", ""), + "uses": as_list(rec.get("usesConcept")), + "represents": rec.get("represents", ""), + "measures": as_list(rec.get("measures")), + "hasMetric": as_list(rec.get("hasMetric")), + "computedBy": as_list(rec.get("computedBy")), + "hasElement": as_list(rec.get("hasElement")), + "grain": as_list(rec.get("hasGrainElement")), + "references": as_list(rec.get("references")), + "steward": as_list(rec.get("monitoredBy")) or as_list(rec.get("ownedBy")), + "dgl": rec.get("hasDGL", ""), + "owner": rec.get("hasDomainOwner", ""), + "subOwner": rec.get("hasSubDomainOwner", ""), + "activation": rec.get("hasActivationStatus", "").replace("pr:", ""), + } + + # reverse index: which metric computes an element + for n in nodes.values(): + for de in n["computedBy"]: + if de in nodes: + nodes[de].setdefault("computedFrom", []).append(n["id"]) + for n in nodes.values(): + n.setdefault("computedFrom", []) + n["route"] = ("measure" if n["computedFrom"] + else "dimensional" if n["represents"] else "") + + # ---- derived physical layer ------------------------------------------- + # Not asserted anywhere: these nodes are PROJECTED from physicalName on the + # data objects and elements. The real physical layer is meant to be + # harvested from Snowflake, not typed by hand, so showing it as derived is + # the honest option -- it makes the gap visible instead of pretending it is + # filled. + phys = {} + for n in list(nodes.values()): + if n["kind"] == "DataObject" and n["physical"]: + pid = "phys:" + n["physical"] + phys.setdefault(pid, {"id": pid, "kind": "BaseTable", + "label": LABELS["BaseTable"], + "name": n["physical"], "domain": n["domain"], + "derived": True, "backs": []}) + phys[pid]["backs"].append(n["id"]) + n["materializedAs"] = pid + for n in list(nodes.values()): + if n["kind"] == "DataElement" and n["physical"]: + owner = [d for d in nodes.values() + if d["kind"] == "DataObject" and n["id"] in d["hasElement"]] + rel = owner[0]["physical"] if owner and owner[0]["physical"] else "" + pid = "phys:" + (rel + "." if rel else "") + n["physical"] + phys.setdefault(pid, {"id": pid, "kind": "Field", + "label": LABELS["Field"], "name": n["physical"], + "domain": n["domain"], "derived": True, + "relation": "phys:" + rel if rel else "", + "backs": []}) + phys[pid]["backs"].append(n["id"]) + n["storedIn"] = pid + for p in phys.values(): + p.setdefault("ident", ""); p.setdefault("arb", False) + p.setdefault("label", LABELS.get(p["kind"], p["kind"])) + for k in ("definition","rule","formula","physical","format","source","belongsTo", + "about","represents","status","dgl","owner","subOwner","activation", + "version","operatedBy","governedBy","productOwner","storedIn", + "materializedAs","relation","stewardFrom"): + p.setdefault(k, "") + for k in ("uses","measures","hasMetric","computedBy","hasElement","grain", + "references","steward","units","computedFrom","packages","exposes", + "steward_inherited"): + p.setdefault(k, []) + p.setdefault("route", "") + nodes[p["id"]] = p + + # ---- inherited roles, resolved once ----------------------------------- + # Stewardship is declared on the Business Object and inherited: a Data + # Object through represents, a Data Element through its Data Object. The + # card shows the inherited holder and says so, rather than leaving a blank + # that reads as "nobody". + for n in nodes.values(): + if n["kind"] == "DataObject": + bo = nodes.get(n["represents"]) + n["steward_inherited"] = bo["steward"] if bo else [] + n["stewardFrom"] = n["represents"] + if n["kind"] == "DataElement": + do = [d for d in nodes.values() + if d["kind"] == "DataObject" and n["id"] in d["hasElement"]] + if do: + n["steward_inherited"] = do[0].get("steward_inherited", []) + n["stewardFrom"] = do[0]["id"] + + domains = [] + for code, (colour, label, role) in DOMAIN_STYLE.items(): + members = [n for n in nodes.values() if n["domain"] == code] + if not members: + continue + d = nodes.get("ex:" + code, {}) + domains.append({ + "code": code, + "colour": colour, + "label": d.get("name") or label, + "role": role, + "dgl": nodes.get(d.get("dgl", ""), {}).get("name", "—"), + "owner": nodes.get(d.get("owner", ""), {}).get("name", "—"), + "activation": d.get("activation", ""), + "counts": {k: len([m for m in members if m["kind"] == k]) + for k in ("SubDomain", "BusinessObject", "BusinessConcept", + "Metric", "DataObject", "DataElement", + "BaseTable", "Field")}, + "arb": len([m for m in members if m["arb"]]), + }) + + return {"nodes": list(nodes.values()), "domains": domains, + "subject": SUBJECT_DOMAIN, "labels": LABELS, + "roles": ROLES, + "styles": {k: v[0] for k, v in DOMAIN_STYLE.items()}} + + +# ---------------------------------------------------------------- output +def main(): + src = sys.argv[1] if len(sys.argv) > 1 else os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "instances", "sodh.ttl") + out = sys.argv[2] if len(sys.argv) > 2 else os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "generated", "sodh_viewer.html") + + model = build(parse(src)) + payload = json.dumps(model, ensure_ascii=False, separators=(",", ":")) + html = TEMPLATE.replace("__MODEL__", payload).replace( + "__SRC__", os.path.basename(src)) + os.makedirs(os.path.dirname(out), exist_ok=True) + open(out, "w", encoding="utf-8").write(html) + + print("SODH VIEWER") + print(" source : %s" % src) + print(" output : %s" % out) + tot = {} + for n in model["nodes"]: + tot[n["kind"]] = tot.get(n["kind"], 0) + 1 + print(" " + " | ".join("%s %d" % (k, v) for k, v in sorted(tot.items()))) + print(" domains %d | to arbitrate %d" + % (len(model["domains"]), len([n for n in model["nodes"] if n["arb"]]))) + for d in model["domains"]: + c = d["counts"] + print(" %-6s %-24s BO %2d BC %2d M %2d DE %3d arb %2d %s" + % (d["code"].replace("DD_", "DD-"), d["label"], c["BusinessObject"], + c["BusinessConcept"], c["Metric"], c["DataElement"], d["arb"], + "OWNER" if d["role"] == "owner" else "")) + + +TEMPLATE = r""" + + + + +Sell Out Data Hub · scope and dependencies + + + +
+
+

Sell Out Data Hub

+
Scope, dependencies and pending arbitrations
+
+
+ + + + + +
+
+ +
+ + + +
+ Build artefact of __SRC__ — regenerate, never edit + +
+ + + + +""" + + +if __name__ == "__main__": + main() diff --git a/scripts/generate_tbox_viewer.py b/scripts/generate_tbox_viewer.py index 957b795..81e225f 100644 --- a/scripts/generate_tbox_viewer.py +++ b/scripts/generate_tbox_viewer.py @@ -1,21 +1,25 @@ #!/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. +============================================ +Reads the T-Box Turtle file and emits a self-contained HTML view. +Build artefact: regenerate, never edit. USAGE - python generate_tbox_viewer.py [tbox.ttl] [out.html] + python3 scripts/generate_tbox_viewer.py [in.ttl] [out.html] + defaults: ontology/pr_metamodel.ttl -> generated/pr_metamodel_viewer.html - Defaults: pr_datametamodel_v1_0.ttl -> pr_metamodel_viewer_v1_0.html +Same visual language as the SODH viewer, with one substitution. Where SODH +lays its columns out by owning domain, the T-Box has no domains -- its +orthogonal axis is PROVENANCE: what governance asserts against what a +harvester observes. The two-colour code carries that instead, blue for +curated and dark gold for observed, and the seam is what crosses between them. -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. +No dependency: parses the Turtle subset this file uses, so it runs on the DSM +system python without a venv. """ import json +import os import re import sys from collections import OrderedDict @@ -30,21 +34,31 @@ WANTED = { "owl:inverseOf": "inverseOf", "owl:deprecated": "deprecated", "dcterms:isReplacedBy": "isReplacedBy", "pr:harvestSource": "harvestSource", "pr:curationMode": "curationMode", + "pr:hasAcronym": "acronym", } -# -------------------------------------------------------------------------- -# Turtle reading -# -------------------------------------------------------------------------- +LAYER_ROOT = OrderedDict([ + ("pr:OwnershipLayerObject", ("ownership", "Ownership & Categorization", + "who is accountable")), + ("pr:BusinessLayerObject", ("business", "Business", "what it means")), + ("pr:LogicalLayerObject", ("logical", "Logical", + "how it is structured")), + ("pr:DeliveryLayerObject", ("delivery", "Delivery", + "what we promise, to whom")), + ("pr:PhysicalLayerObject", ("physical", "Physical", + "what actually runs")), + ("pr:ConsumptionLayerObject", ("consumption", "Consumption", + "what people open in the morning")), +]) +# The only relations crossing between what is asserted and what is harvested. +SEAM = {"pr:materializedAs", "pr:storedIn", "pr:physicalizedIn"} + +# ---------------------------------------------------------------- parsing 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] != '"': @@ -59,65 +73,47 @@ def strip_comments(text): 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) +def split_outside(text, sep): + parts, buf, 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 - 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: + if c == sep: parts.append("".join(buf)); buf = []; i += 1; continue buf.append(c); i += 1 - if "".join(buf).strip(): - parts.append("".join(buf)) + 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[1:end].replace('\\"', '"').replace("\\\\", "\\") return tok -def parse_ttl_fallback(text): - text = strip_comments(text) +def parse(path): + """Turtle subset reader: enough for an instance file, keeps ordering.""" + text = strip_comments(open(path, encoding="utf-8").read()) subjects = OrderedDict() - for stmt in split_top(text, "."): + for stmt in split_outside(text, "."): stmt = stmt.strip() - if not stmt: + if not stmt or not stmt.startswith("ex:"): continue - chunks = split_top(stmt, ";") - head = chunks[0].strip() + clauses = split_outside(stmt, ";") + head = clauses[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:]]: + rec = subjects.setdefault(subj, {"id": subj}) + for chunk in [rest] + [c.strip() for c in clauses[1:]]: chunk = chunk.strip() if not chunk: continue @@ -125,102 +121,74 @@ def parse_ttl_fallback(text): 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)) + key = "type" if pred in ("a", "rdf:type") else pred.split(":")[-1] + vals = [unquote(t) for t in split_outside(objs, ",") if t.strip()] + if key in MULTI or key in rec: + rec.setdefault(key, []) + if not isinstance(rec[key], list): + rec[key] = [rec[key]] + rec[key] += vals + else: + rec[key] = vals[0] if len(vals) == 1 else vals 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 as_list(v): + if v is None: + return [] + return v if isinstance(v, list) else [v] -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", "How the data is structured", "#7A4E8B"), - ("delivery", "Delivery", "What we promise, to whom", "#B8935A"), - ("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:DeliveryLayerObject": "delivery", - "pr:PhysicalLayerObject": "physical", - "pr:ConsumptionLayerObject": "consumption", -} +# ---------------------------------------------------------------- model def first(rec, key, default=""): v = rec.get(key) return v[0] if v else default +def parse_tbox(path): + """Turtle subset reader for the T-Box: pr: subjects, WANTED predicates.""" + text = strip_comments(open(path, encoding="utf-8").read()) + subjects = OrderedDict() + for stmt in split_outside(text, "."): + stmt = stmt.strip() + if not stmt.startswith("pr:"): + continue + chunks = split_outside(stmt, ";") + m = re.match(r"^(\S+)\s+(.*)$", chunks[0].strip(), re.S) + if not m: + continue + rec = subjects.setdefault(m.group(1), {}) + for chunk in [m.group(2)] + [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 + key = WANTED.get(pm.group(1)) + if key: + rec.setdefault(key, []).extend( + unquote(t) for t in split_outside(pm.group(2), ",") if t.strip()) + return subjects + + def build(subjects): - classes, objprops, dataprops, annprops, individuals = {}, {}, {}, {}, {} + classes, objp, datp, annp, 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 + objp[term] = rec elif "owl:DatatypeProperty" in types: - dataprops[term] = rec + datp[term] = rec elif "owl:AnnotationProperty" in types: - annprops[term] = rec + annp[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: @@ -231,91 +199,108 @@ def build(subjects): out += ancestry(parent, seen) return out - nodes = [] + parents = {c: first(classes[c], "subClassOf") for c in classes} + has_kids = set(parents.values()) + + 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" + layer = next((LAYER_ROOT[a][0] for a in anc if a in LAYER_ROOT), None) + if layer is None: + if "pr:Actor" in anc: + layer = "actors" + elif term in ("pr:ActivationStatus", "pr:Environment", "pr:SystemType"): + layer = "vocab" else: - band = "roots" - provenance = ("observed" if "pr:ObservedObject" in anc - else "curated" if "pr:CuratedObject" in anc else "neutral") - nodes.append({ - "id": term, + layer = "roots" + prov = ("observed" if "pr:ObservedObject" in anc + else "curated" if "pr:CuratedObject" in anc else "structural") + depth, p, guard = 0, parents.get(term), 0 + while p and p in classes and guard < 8: + depth += 1; guard += 1; p = parents.get(p) + nodes[term] = { + "id": term, "kind": "Class", "label": first(rec, "label", term.split(":")[1]), - "band": band, - "prov": provenance, - "parent": first(rec, "subClassOf"), + "layer": layer, "prov": prov, "depth": depth, + "parent": parents.get(term, ""), "comment": first(rec, "comment"), "harvest": first(rec, "harvestSource"), "curation": first(rec, "curationMode"), + "acronym": first(rec, "acronym"), "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, []) - ], - }) + "abstract": term in has_kids and term not in LAYER_ROOT + and term not in ("pr:MetaModelObject",), + "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, + def prop(term, rec, kind): + return { + "id": term, "kind": kind, "label": first(rec, "label", term.split(":")[1]), - "s": dom, "t": rng, "comment": first(rec, "comment"), - "harvest": first(rec, "harvestSource"), - "curation": first(rec, "curationMode"), + "domain": first(rec, "domain"), "range": first(rec, "range"), "inverseOf": first(rec, "inverseOf"), "subPropertyOf": first(rec, "subPropertyOf"), + "curation": first(rec, "curationMode"), + "harvest": first(rec, "harvestSource"), "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"), + "seam": term in SEAM, + "prov": "", "layer": "", } - 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()] + layers = [{"id": v[0], "title": v[1], "tag": v[2]} for v in LAYER_ROOT.values()] + layers = ([{"id": "roots", "title": "Model roots", "tag": "how a term got here"}] + + layers + + [{"id": "actors", "title": "Actors", "tag": "people and teams"}, + {"id": "vocab", "title": "Vocabularies", "tag": "controlled value lists"}]) - bridges = [{ - "id": t, "label": first(r, "label", t.split(":")[1]), - "comment": first(r, "comment"), - } for t, r in annprops.items() if t.startswith("pr:denotes")] - - onto = subjects.get("", {}) - return { - "bands": [{"id": b[0], "title": b[1], "tag": b[2], "color": b[3]} for b in BANDS], - "nodes": nodes, "edges": edges, "universal": universal, - "datatypes": datatypes, "bridges": bridges, - } + return {"nodes": list(nodes.values()), + "relations": [prop(t, r, "Relation") for t, r in objp.items()], + "attributes": [prop(t, r, "Attribute") for t, r in datp.items()], + "bridges": [prop(t, r, "Bridge") for t, r in annp.items() + if t.startswith("pr:denotes")], + "layers": layers} -# -------------------------------------------------------------------------- -# HTML emission -# -------------------------------------------------------------------------- +# ---------------------------------------------------------------- output +def main(): + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + src = sys.argv[1] if len(sys.argv) > 1 else os.path.join( + root, "ontology", "pr_metamodel.ttl") + out = sys.argv[2] if len(sys.argv) > 2 else os.path.join( + root, "generated", "pr_metamodel_viewer.html") -def emit(model, version, src_name, out_path): + model = build(parse_tbox(src)) 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) + html = TEMPLATE.replace("__MODEL__", payload).replace( + "__SRC__", os.path.basename(src)) + os.makedirs(os.path.dirname(out), exist_ok=True) + open(out, "w", encoding="utf-8").write(html) + + live_c = [n for n in model["nodes"] if not n["deprecated"]] + dep = ([n for n in model["nodes"] if n["deprecated"]] + + [r for r in model["relations"] + model["attributes"] if r["deprecated"]]) + print("PR META MODEL - T-Box viewer") + print(" source : %s" % src) + print(" output : %s" % out) + print(" classes %d | relations %d | attributes %d | bridges %d | deprecated %d" + % (len(live_c), + len([r for r in model["relations"] if not r["deprecated"]]), + len([a for a in model["attributes"] if not a["deprecated"]]), + len(model["bridges"]), len(dep))) + for l in model["layers"]: + c = len([n for n in live_c if n["layer"] == l["id"]]) + if c: + print(" %-28s %d" % (l["title"], c)) + for p in ("curated", "observed", "structural"): + c = len([n for n in live_c if n["prov"] == p]) + print(" %-28s %d" % (p, c)) TEMPLATE = r""" @@ -323,659 +308,920 @@ TEMPLATE = r""" -PR Data Meta Model · T-Box __VERSION__ +PR Data Meta Model · T-Box -
-

PR Data Meta Model · T-Box

-
Five layers, crossed with a provenance axis: what governance asserts, what harvesters observe
+
Six layers, crossed with what governance asserts and what a harvester observes
-
-
0Classes
-
0Relations
-
0Attributes
-
0Seam edges
+
+ + + + +
- -
- - -
- - - - - - - - - - - - -
- - - -
-
drag to pan · scroll to zoom · click a class
-
- - -
- +
+
Build artefact of __SRC__ — regenerate, never edit - T-Box __VERSION__ +
- """ -def main(): - src = sys.argv[1] if len(sys.argv) > 1 else "pr_datametamodel_v1_0.ttl" - out = sys.argv[2] if len(sys.argv) > 2 else "pr_metamodel_viewer_v1_0.html" - subjects, engine = read_tbox(src) - model = build(subjects) - import os - emit(model, "v1.0", os.path.basename(src), out) - print("PR META MODEL - T-Box viewer") - print(" source : %s (parsed with %s)" % (src, engine)) - print(" output : %s" % out) - print(" classes %d | object properties %d | datatype properties %d | bridge annotations %d" - % (len(model["nodes"]), - len(model["edges"]) + len(model["universal"]), - len(model["datatypes"]), len(model["bridges"]))) - for b in model["bands"]: - c = len([n for n in model["nodes"] if n["band"] == b["id"]]) - if c: - print(" %-26s %d" % (b["title"], c)) - - if __name__ == "__main__": main() diff --git a/scripts/migrate_instances_v1_2.py b/scripts/migrate_instances_v1_2.py new file mode 100644 index 0000000..b80d649 --- /dev/null +++ b/scripts/migrate_instances_v1_2.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +""" +SODH - migration v1.1 -> v1.2 +============================== +USAGE + python3 scripts/migrate_instances_v1_2.py # dry run + python3 scripts/migrate_instances_v1_2.py --apply # rewrite, .bak kept + +WHAT IT DOES + 1. physicalizedIn -> storedIn everywhere (no instances yet, done for + completeness so the term never appears again). + 2. hasSource removed from every Business Concept. A concept has no source + of truth: its authority is its owning domain, its definition belongs in + the glossary. Asking which system defines it has no answer, because + every system that uses it would be a candidate. + 3. hasSource on Metrics recomputed from the elements that implement them. + It said "SODH back-doc v0.7" -- the provenance of the record, not the + source of the data. Asserted rather than derived, by exception, because + "where can I get this metric" is a first-order question and the + derivation chain will have gaps until harvesting; a shape then checks + the assertion against the elements. + 4. The SODH Data Product, its contract and its two interfaces. + +WHY THE PRODUCT OWNER IS EMPTY + Nobody is appointed. The field stays absent and the shape reports a + warning rather than a violation. Inventing a name to satisfy a blocking + rule is exactly how BR-004 produced padding in v0.6. +""" +import os +import re +import shutil +import sys + +REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +TTL = os.path.join(REPO, "instances", "sodh.ttl") +W = 78 + +PRODUCT = "ex:DP_06_01_001" +CONTRACT = "ex:DC_06_01_001" +IFACE_WH = "ex:DI_06_01_001" +IFACE_FILE = "ex:DI_06_01_002" + + +def clauses(body): + out, buf, i, n = [], [], 0, len(body) + while i < n: + c = body[i] + if c == '"': + j = i + 1 + while j < n and body[j] != '"': + j += 2 if body[j] == "\\" else 1 + j = min(j + 1, n) + buf.append(body[i:j]); i = j; continue + if c == ";": + out.append("".join(buf)); buf = []; i += 1; continue + buf.append(c); i += 1 + out.append("".join(buf)) + return out + + +def set_prop(text, subject, prop, value): + pat = re.compile(r'(^%s a pr:\w+ ;)(.*?)(\.\s*\n)' % re.escape(subject), re.S | re.M) + m = pat.search(text) + if not m: + return text, False + kept = [c for c in clauses(m.group(2)) + if c.strip() and not re.match(r'\s*%s\s' % re.escape(prop), c)] + if value is not None: + kept.insert(0, "\n %s %s " % (prop, value)) + body = " ;".join(kept).rstrip() + "\n " + return text[:m.start()] + m.group(1) + body + m.group(3) + text[m.end():], True + + +def get_prop(text, subject, prop): + m = re.search(r'^%s a pr:\w+ ;(.*?)\.\s*\n' % re.escape(subject), text, re.S | re.M) + if not m: + return None + for c in clauses(m.group(1)): + mm = re.match(r'\s*%s\s+(.*)$' % re.escape(prop), c, re.S) + if mm: + return mm.group(1).strip() + return None + + +def product_blocks(objects): + packages = " ,\n ".join(objects) + return """# --- data product ------------------------------------------------------ +# The unit of delivery: what carries a version, an owner, a changelog and a +# list of consumers to warn. It has no single technical counterpart on purpose +# -- rename the schema and the product is unchanged; change its SLA and it is +# not. Its physical footprint is derivable through packages, never asserted. + +%s a pr:DataProduct ; + pr:hasIdentifier "DP-06.01-001" ; pr:hasName "Sell Out Data Hub - Gold" ; + pr:owningDomain ex:DD_06 ; + pr:operatedBy ex:SD_06_01 ; + pr:packages %s ; + pr:governedBy %s ; + pr:exposes %s , %s ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.2" ; pr:hasSource "SODH Gold" . + +%s a pr:DataContract ; + pr:hasIdentifier "DC-06.01-001" ; pr:hasName "Sell Out Data Hub - Gold service commitment" ; + pr:owningDomain ex:DD_06 ; + pr:hasBusinessRule "Refreshed weekly, available Tuesday 06:00 CET. Panel coverage published with every load. Schema stable within a minor version; 60 days notice on any breaking change. Support through the Sales Performance data team." ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.2" . + +%s a pr:DataInterface ; + pr:hasIdentifier "DI-06.01-001" ; pr:hasName "SODH Gold consumption schema" ; + pr:owningDomain ex:DD_06 ; + pr:hasBusinessDefinition "Read access to the Gold layer through Snowflake grants. The channel BI tools and analysts consume."@en ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.2" ; pr:hasSource "SODH Gold" . + +%s a pr:DataInterface ; + pr:hasIdentifier "DI-06.01-002" ; pr:hasName "Panel partner weekly extract" ; + pr:owningDomain ex:DD_06 ; + pr:hasBusinessDefinition "File extract delivered to the panel provider each week. Outside Snowflake, and the boundary where lineage leaves the warehouse."@en ; + pr:hasStatus "DRAFT" ; pr:hasVersion "1.2" ; pr:hasSource "SODH Gold" . +""" % (PRODUCT, packages, CONTRACT, IFACE_WH, IFACE_FILE, + CONTRACT, IFACE_WH, IFACE_FILE) + + +def main(): + apply_changes = "--apply" in sys.argv + if not os.path.exists(TTL): + print("Not found: %s" % TTL); sys.exit(2) + text = original = open(TTL, encoding="utf-8").read() + report = [] + + # 1. term rename + text, n = re.subn(r'\bpr:physicalizedIn\b', 'pr:storedIn', text) + report.append("physicalizedIn -> storedIn: %d occurrence(s)" % n) + + # 2. no source of truth on a concept + n_bc = 0 + for m in list(re.finditer(r'^(ex:BC_\w+) a pr:BusinessConcept ;', text, re.M)): + if get_prop(text, m.group(1), "pr:hasSource") is not None: + text, _ = set_prop(text, m.group(1), "pr:hasSource", None) + n_bc += 1 + report.append("hasSource removed from %d business concepts" % n_bc) + + # 3. metric source of truth, from the elements that implement it + de_src = {} + for m in re.finditer(r'^(ex:DE_\w+) a pr:DataElement ;(.*?)\.\s*\n', text, re.S | re.M): + v = re.search(r'pr:hasSource\s+"([^"]*)"', m.group(2)) + if v: + de_src[m.group(1)] = v.group(1) + wired = {} + for m in re.finditer(r'(ex:M_\w+)\s+pr:computedBy(.*?)\.', text, re.S): + wired[m.group(1)] = re.findall(r'ex:DE_\w+', m.group(2)) + n_m, mixed = 0, [] + for metric, des in wired.items(): + srcs = sorted({de_src[d] for d in des if d in de_src}) + if not srcs: + continue + if len(srcs) > 1: + mixed.append(metric) + text, ok = set_prop(text, metric, "pr:hasSource", + " , ".join('"%s"' % x for x in srcs)) + n_m += ok + report.append("source of truth recomputed on %d metrics%s" + % (n_m, "" if not mixed else + " (%d blend several systems)" % len(mixed))) + + # 4. the data product + if not re.search(r'^%s a ' % re.escape(PRODUCT), text, re.M): + objs = re.findall(r'^(ex:DO_\w+) a pr:DataObject ;', text, re.M) + text = text.rstrip() + "\n\n\n" + product_blocks(objs) + report.append("data product created, packaging %d data objects" % len(objs)) + else: + report.append("data product already present, skipped") + + # ---- report + print() + print("SODH MIGRATION v1.1 -> v1.2 %s" % ("APPLY" if apply_changes else "DRY RUN")) + print("=" * W) + for line in report: + print(" " + line) + print("=" * W) + bc_src = len(re.findall(r'^ex:BC_\w+ a pr:BusinessConcept ;(?:(?!\.\s*\n).)*?hasSource', + text, re.S | re.M)) + defined = set(re.findall(r'^(ex:\w+) a ', text, re.M)) + dangling = sorted(set(re.findall(r'\bex:\w+', text)) - defined) + doubled = [] + for m in re.finditer(r'^(ex:\w+) a pr:\w+ ;(.*?)\.\s*\n', text, re.S | re.M): + for p in ("pr:hasName", "pr:hasSource", "pr:operatedBy", "pr:governedBy"): + if len(re.findall(r'(? 1: + doubled.append("%s/%s" % (m.group(1), p)) + print(" concepts still carrying a source: %d (must be 0)" % bc_src) + print(" physicalizedIn left: %d (must be 0)" + % len(re.findall(r'pr:physicalizedIn', text))) + print(" dangling references: %s" % (", ".join(dangling) if dangling else "none")) + print(" duplicated single-valued properties: %s" + % (", ".join(doubled) if doubled else "none")) + print("=" * W) + + if text != original and apply_changes: + shutil.copy2(TTL, TTL + ".bak") + open(TTL, "w", encoding="utf-8").write(text) + print(" written, backup at %s.bak" % os.path.basename(TTL)) + elif text != original: + print(" dry run -- re-run with --apply to write") + print() + + +if __name__ == "__main__": + main() diff --git a/shapes/pr_metamodel_shapes.ttl b/shapes/pr_metamodel_shapes.ttl index 8d316b6..2d9dd62 100644 --- a/shapes/pr_metamodel_shapes.ttl +++ b/shapes/pr_metamodel_shapes.ttl @@ -40,7 +40,7 @@ dcterms:title "PR Data Meta Model - SHACL Shapes" ; - dcterms:modified "2026-07-26"^^xsd:date ; + dcterms:modified "2026-07-27"^^xsd:date ; rdfs:comment "Executable form of the rulebook. Validated by scripts/run_shacl_validation.py." . @@ -231,7 +231,12 @@ prs:BusinessConceptShape a sh:NodeShape ; sh:property [ sh:path pr:hasFormula ; sh:maxCount 0 ; sh:severity sh:Violation ; - sh:message "BR-003 -- the presence of a formula is what makes something a Metric. A Concept with a formula is a misclassified Metric." ] . + sh:message "BR-003 -- the presence of a formula is what makes something a Metric. A Concept with a formula is a misclassified Metric." ] ; + + sh:property [ + sh:path pr:hasSource ; sh:maxCount 0 ; + sh:severity sh:Violation ; + sh:message "A Business Concept has no source of truth. Its authority is its owning domain and its definition belongs in the glossary. Naming a system here would ask which of the systems that USE the concept defines it -- a question with no answer." ] . prs:MetricShape a sh:NodeShape ; @@ -244,9 +249,9 @@ prs:MetricShape a sh:NodeShape ; sh:message "BR-003 -- a Metric is a measurement with a calculation rule. No formula, no Metric." ] ; sh:property [ - sh:path pr:hasUnit ; sh:minCount 1 ; sh:maxCount 1 ; + sh:path pr:hasUnit ; sh:minCount 1 ; sh:severity sh:Violation ; - sh:message "A Metric states its unit." ] ; + sh:message "A Metric states every unit its variants are expressed in. NOT capped at one: Sell Out Volume is computed in 9L and in L, Sell Out Value in EUR, USD and local currency. The single-unit rule belongs on the Data Element, where it holds -- see DataElementShape." ] ; sh:property [ sh:path pr:measures ; sh:class pr:BusinessConcept ; @@ -265,6 +270,38 @@ prs:MetricShape a sh:NodeShape ; sh:message "A Metric must not carry a physical name -- that is a layer leak. Under BR-013 the physical name belongs on the Data Element." ] . +# The source of truth of a Metric is asserted by exception, then checked. +# "Where can I get this metric" is a first-order question and the derivation +# chain will have gaps before harvesting, so the assertion stands on its own -- +# but it must agree with the elements that implement the metric, or the two +# will drift the day an element moves. +prs:MetricSourceShape a sh:NodeShape ; + sh:targetClass pr:Metric ; + sh:sparql [ + sh:severity sh:Violation ; + sh:message "A Metric's source of truth must match the source of at least one element that computes it. If they disagree, either the metric points at the wrong system or an element has moved." ; + sh:select """ + PREFIX pr: + SELECT $this WHERE { + $this pr:hasSource ?src ; pr:computedBy ?de . + FILTER NOT EXISTS { + $this pr:computedBy ?any . + ?any pr:hasSource ?src . + } + }""" ] ; + sh:sparql [ + sh:severity sh:Warning ; + sh:message "Elements computing one Metric come from several systems of truth. Legitimate for a metric blending sources, worth a look otherwise." ; + sh:select """ + PREFIX pr: + SELECT $this WHERE { + $this pr:computedBy ?a , ?b . + ?a pr:hasSource ?sa . + ?b pr:hasSource ?sb . + FILTER(?sa != ?sb) + }""" ] . + + # BR-013, finally executable. v0.1.1 declared it BLOCKING with no shape. prs:MotherMetricShape a sh:NodeShape ; sh:targetClass pr:Metric ; @@ -306,7 +343,13 @@ prs:DataObjectShape a sh:NodeShape ; sh:property [ sh:path pr:monitoredBy ; sh:maxCount 0 ; sh:severity sh:Warning ; - sh:message "Stewardship is declared on the Business Object and inherited through represents. Declaring it here allows two answers to the same question." ] . + sh:message "Stewardship is declared on the Business Object and inherited through represents. Declaring it here allows two answers to the same question." ] ; + + sh:property [ + sh:path pr:hasSource ; sh:maxCount 1 ; + sh:not [ sh:pattern "/" ] ; + sh:severity sh:Warning ; + sh:message "hasSource names the SYSTEM of origin, nothing else. A value like 'SODH Gold / F_SELL_OUT' packs a system and a physical relation into one string -- the physical relation belongs in physicalName." ] . # A declared grain element must actually belong to the object it grains. @@ -340,6 +383,11 @@ prs:DataElementShape a sh:NodeShape ; sh:path pr:fullyQualifiedName ; sh:maxCount 1 ; sh:severity sh:Info ] ; + sh:property [ + sh:path pr:hasUnit ; sh:maxCount 1 ; + sh:severity sh:Violation ; + sh:message "A Data Element carries exactly one unit. This is the counterpart of the Metric rule: the mother metric lists the units its variants use, each variant states the single unit it is actually expressed in. An element with two units is two elements." ] ; + sh:property [ sh:path pr:represents ; sh:class pr:BusinessConcept ; sh:maxCount 1 ; @@ -381,9 +429,9 @@ prs:DataProductShape a sh:NodeShape ; sh:property [ sh:path pr:hasProductOwner ; sh:class pr:DataProductOwner ; - sh:minCount 1 ; sh:maxCount 1 ; - sh:severity sh:Violation ; - sh:message "A Data Product has exactly one Product Owner." ] ; + sh:maxCount 1 ; + sh:severity sh:Warning ; + sh:message "A Data Product should name exactly one Product Owner. A warning rather than a block: the SODH owner is genuinely not appointed, and a blocking rule that cannot be satisfied truthfully is how BR-004 produced padding." ] ; sh:property [ sh:path pr:packages ; sh:class pr:DataObject ;