1 Commits

20 changed files with 4770 additions and 1081 deletions
+591
View File
@@ -0,0 +1,591 @@
# Pernod Ricard Data MetaModel — T-Box Processbook
**Version 0.1** — Draft for review — generated 2026-08-03
> Generated from `processes.yaml`. The BPMN files in `bpmn/` are the authoritative form; the views below are derived from the same source. Do not edit this document (EV-007, EV-008).
Procedures for changing the vocabulary of the model. Each procedure names the rules it enforces, the steps a person must perform, and the points at which the work stops rather than continues.
## Procedures
| Id | Family | Procedure | Scope |
|---|---|---|---|
| **X1** | Release | Propagate a change and publish a version | Any validated modification of the vocabulary. |
| **C1** | Create | Create a term | class | relation | attribute | annotation |
| **C2** | Create | Create a controlled value | typed individual | literal |
| **C3** | Create | Create a shape | Any SHACL constraint added to the validation set. |
| **D1** | Delete | Deprecate a term | class | relation | attribute | annotation | individual |
| **D2** | Delete | Withdraw a term permanently | class | relation | attribute | annotation | individual |
| **R1** | Verify | Verify the vocabulary against the rulebook | The whole vocabulary, or the subset touched by a change. |
| **R2** | Verify | Validate instances against the shapes | Any instance graph in the validation perimeter. |
| **R3** | Verify | Audit what the shapes cannot see | The whole vocabulary and its source file. |
## Reading a diagram
- A **rounded** node is a start or end state.
- A **diamond** is a decision; every outgoing path is labelled.
- A **shaded** box is a human task: it cannot be automated, and the work stops there until someone acts.
- A **double-bordered** box calls another procedure by its identifier.
---
# X1 — Propagate a change and publish a version
| | |
|---|---|
| Family | Release |
| Scope | Any validated modification of the vocabulary. |
| Trigger | A change to the vocabulary has been agreed and is ready to apply. |
| Inputs | the agreed change, the target version, the list of affected artifacts |
| Outputs | a merged branch, a tagged version, an execution log |
| Rules enforced | EV-004, EV-005, EV-006, EV-007, EV-008, EV-009, EV-010, EV-014, EV-015, TN-023 |
| Calls | R1, R2 |
**Note.** The most frequently invoked procedure of the processbook: every other procedure ends by calling it. Two of its steps are gateways rather than checks, because an idempotence failure and a validation failure must stop the work rather than be noted in passing.
```mermaid
flowchart TD
start(["Change agreed"])
branch["Create the migration branch"]
apply["Apply the change through an RDF parser"]
labels["Regenerate labels by derivation"]
abox["Propagate to the instances"]
shapes["Update the shapes and align conformsTo"]
artifacts["Regenerate every derived artifact"]
replay["Replay the chain a second time"]
idem{"Second run reports zero change?"}
verify_voc[["R1: Verify the vocabulary"]]
verify_abox[["R2: Validate the instances"]]
log["Write the execution log for this attempt"]
clean{"Any violation?"}
decide["Correct or abandon"]
drop["Destroy the branch and restart from the published version"]
review["Review the merge"]
merge["Merge as a block"]
bump["Increment the ontology version"]
end(["Version published"])
aborted(["Change abandoned"])
start --> branch
branch --> apply
apply --> labels
labels --> abox
abox --> shapes
shapes --> artifacts
artifacts --> replay
replay --> idem
idem -->|zero change| verify_voc
idem -->|the chain is not replayable| decide
verify_voc --> verify_abox
verify_abox --> log
log --> clean
clean -->|none| review
clean -->|at least one| decide
decide -->|correct| apply
decide -->|abandon| drop
drop --> aborted
review --> merge
merge --> bump
bump --> end
class idem decision;
class clean decision;
class decide human;
class review human;
classDef human fill:#FFF4CE,stroke:#B08900;
classDef decision fill:#E8F0F7,stroke:#3E6E96;
```
| Step | Type | Rules |
|---|---|---|
| Create the migration branch | script | EV-009 |
| Apply the change through an RDF parser | script | EV-005, EV-006 |
| Regenerate labels by derivation | script | TN-023 |
| Propagate to the instances | script | EV-009 |
| Update the shapes and align conformsTo | script | EV-010 |
| Regenerate every derived artifact | script | EV-008 |
| Replay the chain a second time | script | EV-006, EV-007 |
| Second run reports zero change? | decision | - |
| Verify the vocabulary (R1) | calls | - |
| Validate the instances (R2) | calls | - |
| Write the execution log for this attempt | script | EV-015 |
| Any violation? | decision | - |
| Correct or abandon | human | EV-004, EV-007 |
| Destroy the branch and restart from the published version | script | EV-007 |
| Review the merge | human | EV-004, EV-009 |
| Merge as a block | script | EV-009 |
| Increment the ontology version | script | EV-014 |
---
# C1 — Create a term
| | |
|---|---|
| Family | Create |
| Scope | class | relation | attribute | annotation |
| Trigger | A missing concept, edge or field has been identified. |
| Inputs | the intended meaning, the nature, the target layer, the provenance, the parent or the domain and range |
| Outputs | a declared term, a published version |
| Rules enforced | EV-004, TN-001, TN-002, TN-003, TN-004, TN-005, TN-006, TN-007, TN-009, TN-010, TN-011, TN-012, TN-013, TN-014, TN-015, TN-018, TN-019, TN-020, TN-021, TN-022, TN-023, TN-024, TN-025, TN-026, TN-027, TN-028 |
| Calls | R1, X1 |
**Parameter.** The nature of the term selects the naming rules applied at the naming step: a class takes TN-001, TN-002, TN-004, TN-005, TN-006, TN-007 and TN-027; a relation takes TN-001, TN-002, TN-009, TN-010, TN-011 and TN-028; an attribute takes TN-001, TN-002, TN-004, TN-012, TN-013, TN-014 and TN-015; an annotation takes TN-001, TN-002 and TN-003.
**Note.** The first step has no tool today. Checking that no existing term already covers the need is the semantic uniqueness control that SHACL cannot perform, and it stays a human task until R3 exists.
```mermaid
flowchart TD
start(["Need identified"])
unique["Check that no existing term covers the need"]
exists{"A term already covers it?"}
reuse(["Reuse the existing term"])
name["Name the term according to its nature"]
label["Derive the label"]
short["Decide the short label and the acronym"]
comment["Write the comment"]
declare["Declare typing"]
validate["Validate before applying"]
check[["R1: Verify against the rulebook"]]
release[["X1: Propagate and publish"]]
end(["Term available"])
start --> unique
unique --> exists
exists -->|yes| reuse
exists -->|no| name
name --> label
label --> short
short --> comment
comment --> declare
declare --> validate
validate --> check
check --> release
release --> end
class unique human;
class exists decision;
class short human;
class comment human;
class validate human;
classDef human fill:#FFF4CE,stroke:#B08900;
classDef decision fill:#E8F0F7,stroke:#3E6E96;
```
| Step | Type | Rules |
|---|---|---|
| Check that no existing term covers the need | human | - |
| A term already covers it? | decision | - |
| Name the term according to its nature | script | TN-001, TN-002, TN-003, TN-004, TN-005, TN-006, TN-009, TN-010, TN-011, TN-012, TN-013, TN-014, TN-015, TN-028 |
| Derive the label | script | TN-018, TN-019, TN-020, TN-021, TN-023 |
| Decide the short label and the acronym | human | TN-022 |
| Write the comment | human | TN-024 |
| Declare typing | script | TN-007, TN-025, TN-026, TN-027 |
| Validate before applying | human | EV-004 |
| Verify against the rulebook (R1) | calls | - |
| Propagate and publish (X1) | calls | - |
---
# C2 — Create a controlled value
| | |
|---|---|
| Family | Create |
| Scope | typed individual | literal |
| Trigger | A new value is needed in a controlled set. |
| Inputs | the value, the set it belongs to, whether a domain may add others |
| Outputs | a declared value, a published version |
| Rules enforced | EV-004, TN-001, TN-016, TN-017, TN-018, TN-022 |
| Calls | C3, R1, X1 |
**Parameter.** The form is not a style choice but the outcome of the first decision. The two branches have different consequences: the literal branch edits the shapes and therefore the vocabulary, so it falls under the version freeze; the individual branch touches nothing else.
```mermaid
flowchart TD
start(["New value needed"])
decide["Must it be defined, owned or extended by a domain?"]
form{"Which form?"}
indiv["Create the typed individual and derive its label"]
literal["Write the literal in upper snake case"]
shape[["C3: Extend the closed list in the shapes"]]
validate["Validate before applying"]
check[["R1: Verify against the rulebook"]]
release[["X1: Propagate and publish"]]
end(["Value available"])
start --> decide
decide --> form
form -->|extensible by a domain| indiv
form -->|closed governance state| literal
indiv --> validate
literal --> shape
shape --> validate
validate --> check
check --> release
release --> end
class decide human;
class form decision;
class validate human;
classDef human fill:#FFF4CE,stroke:#B08900;
classDef decision fill:#E8F0F7,stroke:#3E6E96;
```
| Step | Type | Rules |
|---|---|---|
| Must it be defined, owned or extended by a domain? | human | TN-016 |
| Which form? | decision | - |
| Create the typed individual and derive its label | script | TN-001, TN-018, TN-022 |
| Write the literal in upper snake case | script | TN-017 |
| Extend the closed list in the shapes (C3) | calls | - |
| Validate before applying | human | EV-004 |
| Verify against the rulebook (R1) | calls | - |
| Propagate and publish (X1) | calls | - |
---
# C3 — Create a shape
| | |
|---|---|
| Family | Create |
| Scope | Any SHACL constraint added to the validation set. |
| Trigger | A rule needs an executor, or a new class enters the validation perimeter. |
| Inputs | the rule to enforce, the target class or property |
| Outputs | a shape, a rulebook entry naming it as executor |
| Rules enforced | EV-010, EV-011, EV-012, EV-013 |
| Calls | R2, X1 |
**Note.** The step that is forgotten is the last one before release: recording the shape as the executor of its rule. Without it a rule stays declared blocking with nothing enforcing it, which EV-011 forbids.
```mermaid
flowchart TD
start(["Constraint needed"])
write["Write the constraint"]
pattern["Check that every pattern is expressed positively"]
portable{"Expressible within the specification?"}
demote["Move the rule to the script tier"]
conforms["Align the declared ontology version"]
test[["R2: Test against real instances"]]
record["Record the shape as the executor of its rule"]
release[["X1: Propagate and publish"]]
end(["Shape in force"])
start --> write
write --> pattern
pattern --> portable
portable -->|yes| conforms
portable -->|no| demote
demote --> record
conforms --> test
test --> record
record --> release
release --> end
class write human;
class portable decision;
class demote human;
class record human;
classDef human fill:#FFF4CE,stroke:#B08900;
classDef decision fill:#E8F0F7,stroke:#3E6E96;
```
| Step | Type | Rules |
|---|---|---|
| Write the constraint | human | - |
| Check that every pattern is expressed positively | script | EV-013 |
| Expressible within the specification? | decision | - |
| Move the rule to the script tier | human | EV-012 |
| Align the declared ontology version | script | EV-010 |
| Test against real instances (R2) | calls | - |
| Record the shape as the executor of its rule | human | EV-011, EV-012 |
| Propagate and publish (X1) | calls | - |
---
# D1 — Deprecate a term
| | |
|---|---|
| Family | Delete |
| Scope | class | relation | attribute | annotation | individual |
| Trigger | A term is superseded or no longer needed. |
| Inputs | the term, its replacement if any |
| Outputs | a deprecated stub, a published version |
| Rules enforced | EV-001, EV-015, TN-020 |
| Calls | X1 |
**Note.** The count at step two is informative, not a condition: a term may be deprecated whether or not it is instantiated. It becomes a condition only in D2.
```mermaid
flowchart TD
start(["Term superseded"])
replacement["Identify the replacement"]
count["Count the instances for information"]
mark["Mark deprecated and declare the replacement"]
strip["Strip the stub of every edge"]
label["Remove any mention of state from the label"]
release[["X1: Propagate and publish"]]
end(["Term deprecated"])
start --> replacement
replacement --> count
count --> mark
mark --> strip
strip --> label
label --> release
release --> end
class replacement human;
classDef human fill:#FFF4CE,stroke:#B08900;
classDef decision fill:#E8F0F7,stroke:#3E6E96;
```
| Step | Type | Rules |
|---|---|---|
| Identify the replacement | human | - |
| Count the instances for information | script | EV-015 |
| Mark deprecated and declare the replacement | script | EV-001 |
| Strip the stub of every edge | script | EV-001 |
| Remove any mention of state from the label | script | TN-020 |
| Propagate and publish (X1) | calls | - |
---
# D2 — Withdraw a term permanently
| | |
|---|---|
| Family | Delete |
| Scope | class | relation | attribute | annotation | individual |
| Trigger | A deprecated term is to be removed from the vocabulary. |
| Inputs | the deprecated term |
| Outputs | a vocabulary without the term, a proof of non-instantiation, a published version |
| Rules enforced | EV-002, EV-003, EV-015 |
| Calls | R1, R2, X1 |
**Note.** The reference cleaning step is what prevents phantom nodes: a subject removed while its identifier is still cited elsewhere is reconstructed by inference, present in traversals and absent from every control. It is a step of the procedure, not a check at the end of a script.
```mermaid
flowchart TD
start(["Withdrawal requested"])
count["Run the counting query"]
instantiated{"Any instance found?"}
keep(["Kept deprecated"])
proof["Attach the proof to the commit"]
remove["Remove the subject block"]
refs["Remove every reference naming the subject"]
check_voc[["R1: Verify the vocabulary"]]
check_abox[["R2: Validate the instances"]]
release[["X1: Propagate and publish"]]
end(["Term withdrawn"])
start --> count
count --> instantiated
instantiated -->|yes| keep
instantiated -->|no| proof
proof --> remove
remove --> refs
refs --> check_voc
check_voc --> check_abox
check_abox --> release
release --> end
class instantiated decision;
classDef human fill:#FFF4CE,stroke:#B08900;
classDef decision fill:#E8F0F7,stroke:#3E6E96;
```
| Step | Type | Rules |
|---|---|---|
| Run the counting query | script | EV-002, EV-015 |
| Any instance found? | decision | - |
| Attach the proof to the commit | script | EV-002, EV-015 |
| Remove the subject block | script | - |
| Remove every reference naming the subject | script | EV-003 |
| Verify the vocabulary (R1) | calls | - |
| Validate the instances (R2) | calls | - |
| Propagate and publish (X1) | calls | - |
---
# R1 — Verify the vocabulary against the rulebook
| | |
|---|---|
| Family | Verify |
| Scope | The whole vocabulary, or the subset touched by a change. |
| Trigger | A term has been created, changed or withdrawn, or a release is prepared. |
| Inputs | the vocabulary, the rulebook source |
| Outputs | a conformance report |
| Rules enforced | EV-014, TN-001, TN-002, TN-003, TN-004, TN-005, TN-006, TN-007, TN-008, TN-009, TN-010, TN-011, TN-012, TN-013, TN-014, TN-015, TN-016, TN-017, TN-018, TN-019, TN-020, TN-021, TN-022, TN-023, TN-024, TN-025, TN-026, TN-027, TN-028 |
| Calls | - |
**Note.** Two tiers run in sequence, not in parallel: the script settles everything mechanisable, and a person answers only for the rules no script can judge. Reversing the order wastes review time on findings the script would have caught.
```mermaid
flowchart TD
start(["Verification requested"])
script["Run the naming and declaration checks"]
mechanised{"Any mechanised violation?"}
report_fail(["Report returned with violations"])
human["Review the rules no script can judge"]
judged{"Reviewer raises an issue?"}
report_ok(["Vocabulary conforms"])
start --> script
script --> mechanised
mechanised -->|at least one| report_fail
mechanised -->|none| human
human --> judged
judged -->|yes| report_fail
judged -->|no| report_ok
class mechanised decision;
class human human;
class judged decision;
classDef human fill:#FFF4CE,stroke:#B08900;
classDef decision fill:#E8F0F7,stroke:#3E6E96;
```
| Step | Type | Rules |
|---|---|---|
| Run the naming and declaration checks | script | TN-001, TN-002, TN-003, TN-004, TN-005, TN-006, TN-007, TN-009, TN-010, TN-011, TN-012, TN-013, TN-014, TN-015, TN-017, TN-018, TN-019, TN-020, TN-021, TN-023, TN-025, TN-026, TN-027, TN-028, EV-014 |
| Any mechanised violation? | decision | - |
| Review the rules no script can judge | human | TN-008, TN-016, TN-022, TN-024 |
| Reviewer raises an issue? | decision | - |
---
# R2 — Validate instances against the shapes
| | |
|---|---|
| Family | Verify |
| Scope | Any instance graph in the validation perimeter. |
| Trigger | Instances have changed, shapes have changed, or a release is prepared. |
| Inputs | the instance graph, the shapes, the ontology |
| Outputs | a validation report |
| Rules enforced | EV-010 |
| Calls | - |
**Note.** The version check comes first and aborts rather than warns. Validating against shapes that target another version of the ontology does not fail loudly: it returns a long list of violations that reads exactly like a regression of the model.
```mermaid
flowchart TD
start(["Validation requested"])
conforms["Compare the declared target version with the ontology version"]
match{"Versions match?"}
abort(["Aborted on version mismatch"])
run["Run the shape validation"]
violations{"Any violation?"}
fail(["Report returned with violations"])
ok(["Instances conform"])
start --> conforms
conforms --> match
match -->|no| abort
match -->|yes| run
run --> violations
violations -->|at least one| fail
violations -->|none| ok
class match decision;
class violations decision;
classDef human fill:#FFF4CE,stroke:#B08900;
classDef decision fill:#E8F0F7,stroke:#3E6E96;
```
| Step | Type | Rules |
|---|---|---|
| Compare the declared target version with the ontology version | script | EV-010 |
| Versions match? | decision | - |
| Run the shape validation | script | - |
| Any violation? | decision | - |
---
# R3 — Audit what the shapes cannot see
| | |
|---|---|
| Family | Verify |
| Scope | The whole vocabulary and its source file. |
| Trigger | Periodic audit, or before a version is published. |
| Inputs | the vocabulary, its source file |
| Outputs | a shortlist of suspected duplicates, a list of duplicated blocks |
| Rules enforced | EV-012 |
| Calls | - |
**Note.** Shape validation reads a graph, not a file and not meaning. Two identifiers standing for the same notion produce two individually valid graphs; a duplicated block of text produces identical triples and no complaint. This procedure is the tier those rules fall to, and its output is a shortlist for a person rather than a verdict.
```mermaid
flowchart TD
start(["Audit requested"])
index["Build the normalised label index"]
hash["Hash every normalised subject block"]
shortlist{"Any candidate found?"}
clean(["Nothing to arbitrate"])
review["Arbitrate each candidate"]
act{"Duplication confirmed?"}
merge(["Referred to the merge procedure"])
dismissed(["Candidates dismissed"])
start --> index
index --> hash
hash --> shortlist
shortlist -->|none| clean
shortlist -->|at least one| review
review --> act
act -->|yes| merge
act -->|no| dismissed
class shortlist decision;
class review human;
class act decision;
classDef human fill:#FFF4CE,stroke:#B08900;
classDef decision fill:#E8F0F7,stroke:#3E6E96;
```
| Step | Type | Rules |
|---|---|---|
| Build the normalised label index | script | EV-012 |
| Hash every normalised subject block | script | EV-012 |
| Any candidate found? | decision | - |
| Arbitrate each candidate | human | - |
| Duplication confirmed? | decision | - |
---
# Rule coverage
Which procedures enforce each rule. Derived, not maintained: a rule cited in a diagram appears here automatically.
| Rule | Procedures |
|---|---|
| EV-001 | D1 |
| EV-002 | D2 |
| EV-003 | D2 |
| EV-004 | C1, C2, X1 |
| EV-005 | X1 |
| EV-006 | X1 |
| EV-007 | X1 |
| EV-008 | X1 |
| EV-009 | X1 |
| EV-010 | C3, R2, X1 |
| EV-011 | C3 |
| EV-012 | C3, R3 |
| EV-013 | C3 |
| EV-014 | R1, X1 |
| EV-015 | D1, D2, X1 |
| TN-001 | C1, C2, R1 |
| TN-002 | C1, R1 |
| TN-003 | C1, R1 |
| TN-004 | C1, R1 |
| TN-005 | C1, R1 |
| TN-006 | C1, R1 |
| TN-007 | C1, R1 |
| TN-008 | R1 |
| TN-009 | C1, R1 |
| TN-010 | C1, R1 |
| TN-011 | C1, R1 |
| TN-012 | C1, R1 |
| TN-013 | C1, R1 |
| TN-014 | C1, R1 |
| TN-015 | C1, R1 |
| TN-016 | C2, R1 |
| TN-017 | C2, R1 |
| TN-018 | C1, C2, R1 |
| TN-019 | C1, R1 |
| TN-020 | C1, D1, R1 |
| TN-021 | C1, R1 |
| TN-022 | C1, C2, R1 |
| TN-023 | C1, R1, X1 |
| TN-024 | C1, R1 |
| TN-025 | C1, R1 |
| TN-026 | C1, R1 |
| TN-027 | C1, R1 |
| TN-028 | C1, R1 |
+941
View File
@@ -0,0 +1,941 @@
# Pernod Ricard Data MetaModel — T-Box Rulebook
**Version 1.1** — Draft for review — generated 2026-08-03
> Generated from `rules.yaml`. Do not edit this document: edit the source and regenerate (EV-007, EV-008).
## Scope
Governs the vocabulary of the model itself: the IRIs, labels and declarative axioms of every class, property and enumeration individual. Does not govern instances, which remain under the A-Box rulebook.
Written to be read and applied directly, by a person or by a language model, without further context. Every rule states what must hold, why, and what enforces it.
## Categories
| Category | Subject | Rules |
|---|---|---|
| **Identifier** | Form of the IRI | 17 |
| **Label** | Derivation and display | 6 |
| **Declaration** | Declarative completeness | 5 |
| **Evolution** | Change and propagation | 15 |
## Severity
- **BLOCKING** — The commit does not pass. Enforced pre-commit and in continuous integration by the named executor.
- **MAJOR** — Reviewed and answered for, but not machine-enforceable. Reserved for rules that require human judgement, per EV-011.
- **GUIDELINE** — Recommended practice with no gate.
---
# Identifier — Form of the IRI
The IRI is identity. It is immutable in the RDF sense — changing one is a migration, never an edit — so it must be unambiguous, searchable and free of local convention.
## TN-001 — CamelCase IRI with no separator
**Statement.** The local name of an IRI is strict CamelCase with no separator of any kind: no hyphen, no underscore, no dot, no space. Initial capital for a class or an enumeration individual; initial lower case for a property, whether object, datatype or annotation. Consecutive capitals are admitted only where they carry an acronym reproduced from a target class short label under TN-011, and nowhere else.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class, object_property, datatype_property, annotation_property, individual |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A hyphen forces escaping in certain Turtle and SPARQL contexts and makes decamelisation ambiguous: Sub-Domain has no single correct expansion. Typographic convention is a display concern and belongs to the short label, never to identity.
| From | To | Note |
|---|---|---|
| pr:data_domain | pr:DataDomain | no underscore |
| pr:Sub-Domain | pr:DataSubDomain | no hyphen |
| pr:BelongsTo | pr:belongsTo | a property starts lower case |
## TN-002 — No acronym in the IRI
**Statement.** The local name of an IRI contains no acronym, initialism or abbreviation. Every short form is expanded in full. One exception, and one only: a relation reproducing the short label of the class it targets carries that short label as it stands, acronym included, under TN-011. No other term may carry an acronym, and no exception list is maintained.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class, object_property, datatype_property, annotation_property, individual |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
| Related | A-Box NR-004 |
**Why.** An acronym in an identifier is a local convention pretending to be a name. It is unsearchable by anyone who does not already know it, and it collides across domains: PO is a Product Owner in one team and a Purchase Order in another. Length is not a counter-argument — an IRI is written by tooling and read in context, and the short form remains available through the short label and the acronym annotation. The single exception exists because a relation name is read on every edge of the graph and in every query, where the spoken form is what makes it legible; reproducing the target's short label verbatim also keeps the relation name derivable from the class rather than invented, which a truncated form would not.
| From | To | Note |
|---|---|---|
| pr:KPI | pr:KeyPerformanceIndicator | short form moves to pr:acronym |
| pr:BIDataSource | pr:BusinessIntelligenceDataSource | |
| pr:definitionUri | pr:definitionAddress | URI is itself an acronym |
| a relation targeting a class whose short label is BI Field | pr:containsBIField | the one exception — see TN-011 |
## TN-003 — Display properties are annotations
**Statement.** pr:acronym and pr:shortLabel are declared owl:AnnotationProperty, never owl:DatatypeProperty.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | annotation_property |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A datatype property applies to an individual. Annotating a term of the vocabulary with one takes the ontology out of OWL DL into OWL Full, which reasoners reject and strict validators flag. An annotation property applies to anything — class, property or individual. Nothing is lost on the control side: SHACL validates triples and does not read OWL declarations, so every shape targeting these properties keeps working unchanged.
| From | To | Note |
|---|---|---|
| pr:shortLabel a owl:DatatypeProperty | pr:shortLabel a owl:AnnotationProperty | |
| pr:acronym a owl:DatatypeProperty | pr:acronym a owl:AnnotationProperty | |
## TN-004 — No digit in the IRI
**Statement.** The local name of a vocabulary term contains no digit. A numeric parameter — a window, a threshold, a version — is a value, never an identity.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class, object_property, datatype_property, annotation_property |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
| Related | A-Box NR-014 (deliberate opposite) |
**Why.** An instance identifier does encode numbers, and that is its purpose: it carries a position in the domain tree. A vocabulary term encodes nothing but its own meaning. The opposition is deliberate and is stated in the ontology header so that no reader takes one for a violation of the other. A term named after a thirty-day window starts lying the day the window becomes ninety, and nothing detects it.
| From | To | Note |
|---|---|---|
| pr:queryCount30d | pr:queryCount | the window belongs to the harvesting specification |
## TN-005 — A class is a singular common noun
**Statement.** The local name of a class is a singular noun phrase. No plural, no verb. No type word such as Object, Entity or Item unless it names a genuine abstraction of the model. No suffix rule is imposed on abstract classes.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class |
| Enforced at | script, human |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
| Related | A-Box NR-008, NR-013 |
**Why.** Abstractness is not inferable from a name and must not be encoded in one. The correspondence fails in both directions: classes can be abstract without any suffix, and classes ending in Object can be among the most heavily instantiated in the model. Renaming a central business term to satisfy a naming rule would trade meaning for symmetry. Abstractness is declared instead, by TN-007.
| From | To | Note |
|---|---|---|
| pr:DataDomains | pr:DataDomain | singular |
| pr:ActorObject | pr:Actor | no type word added for its own sake |
## TN-006 — Every layer root is abstract
**Statement.** A class that serves as the root of a layer carries pr:isAbstract true.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A non-abstract layer root allows an instance to be typed as an object of that layer without saying which kind of object it is. That statement carries no information and no downstream control can repair it.
| From | To | Note |
|---|---|---|
| pr:PhysicalLayerObject with no declaration | pr:isAbstract true | |
## TN-007 — Every class declares whether it is abstract
**Statement.** Every owl:Class carries pr:isAbstract with an explicit boolean value. Absence of the property is a violation, not a default.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** Where declaration is optional, nothing distinguishes a class deliberately left concrete from one where the question was never asked. Two situations make the compulsion necessary. A class with subclasses may be perfectly instantiable, and has been wrongly marked abstract by inference. And a class used as a classification axis may have no declared subclass at all, its members being typed by multiple typing on instances — its abstractness is then structurally unverifiable and only the explicit declaration carries it.
| From | To | Note |
|---|---|---|
| a class with no declaration | pr:isAbstract true or false | silence is a violation |
| pr:Metric inferred abstract because it has a subclass | pr:isAbstract false | concrete despite having a subclass |
## TN-008 — Abstractness is declared, never inferred
**Statement.** Abstractness is read from pr:isAbstract and from nothing else. No consumer of the model — viewer, export, script or documentation generator — may derive it from the presence of subclasses, from the absence of instances, or from any other structural signal.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class, consumer |
| Enforced at | human |
| Executor | `consumer contract review` |
| Procedure | _pending_ |
**Why.** A declaration rule holds in the source only if it also binds every consumer. Otherwise each one re-invents its own inference and the model acquires as many answers as it has readers. This is the same failure mode that justified a governed short label rather than a display dictionary per viewer.
| From | To | Note |
|---|---|---|
| isAbstract = term.subclasses.length > 0 | isAbstract = term.isAbstract === true | consumer contract |
## TN-009 — A relation begins with a verb
**Statement.** The local name of an object property begins with a verb, in lower case. Both the active form and the passive or participial form are admitted: a past participle is a verb in first position. No relation begins with a preposition or with a noun.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | object_property |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A relation reads as a verb and an attribute reads as a noun. The distinction is what lets a reader tell an edge from a field without opening the declaration. The explicit clause on participial forms is required: read literally, a verb-first rule would condemn a whole family of sound relations such as computedBy, storedIn and derivedFrom.
| From | To | Note |
|---|---|---|
| pr:inDatabase | pr:isInDatabase | a preposition is not a verb |
| pr:primaryLocation | pr:primarilyStoredIn | a noun is not a verb |
| pr:computedBy | pr:computedBy | participial form is conforming |
## TN-010 — The is prefix is either a copula or a predicate
**Statement.** On an owl:ObjectProperty, is acts as a COPULA — a state verb before a preposition or adjective, whose target is a node of the graph. On a boolean datatype or annotation property, is acts as a PREDICATE — it introduces an adjective or participle and the value is true or false. No other use is admitted.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | object_property, datatype_property, annotation_property |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** Conformance of a name beginning with is is never checkable on the name alone. A control asserting that every such term is a boolean produces false positives on every copula; the reverse control lets real problems through. The correct check reads the declared type of the property first, then applies the matching clause.
| From | To | Note |
|---|---|---|
| pr:isInSchema | copula | object property |
| pr:isNullable | predicate | datatype property of range xsd:boolean |
## TN-011 — A relation names its target by its short label
**Statement.** A relation need not mention its target class at all. When it does, it uses the target class's pr:shortLabel AS IT STANDS — not the full label, not a truncation of the short label, and not a form invented for the occasion. Where the short label carries an acronym, the acronym is reproduced with it: this is the single exception admitted by TN-002, and TN-001 admits the consecutive capitals it produces.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | object_property |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** Relation names are what is read on the edges of the graph, constantly, in every viewer and every query. Full labels make them unreadable on an arrow. The short label is the form the organisation actually speaks, and reusing it makes a relation name predictable from the class it points at instead of arbitrary. Reproducing it whole is what preserves that property: a truncation would be a form the model invented, unpredictable from the class and unverifiable against it. A corollary: relations do not normally carry a short label of their own, since this rule already keeps their IRIs short.
| From | To | Note |
|---|---|---|
| pr:hasDomainOwner | pr:hasDomainOwner | target DataDomainOwner, short label Domain Owner — conforming |
| pr:hasDGL | pr:hasGovernanceLead | target short label is Governance Lead |
| pr:inBIDataSource | pr:isInBIDataSource | short label BI Data Source reproduced whole, acronym included |
| pr:containsField | pr:containsBIField | a truncated short label is not admitted |
## TN-012 — An attribute is a noun
**Statement.** The local name of a datatype property is a noun phrase. It begins with no verb, and in particular with no has.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | datatype_property |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A has prefix carries no information — rdfs:domain already says who has the attribute — and it costs the reader the distinction TN-009 exists to make visible. Under TN-018 the derived label would read as a verb phrase on a field, so a relation and an attribute would become indistinguishable in every display.
| From | To | Note |
|---|---|---|
| pr:hasFormula | pr:formula | |
| pr:hasName | pr:canonicalName | name alone is too generic to stand as an identity |
## TN-013 — A date attribute is a noun, not a participle
**Statement.** An attribute holding a date or timestamp is named as a noun.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | datatype_property |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** The participial form is idiomatic English but creates a standing family of exceptions to TN-012 that must be defended at every review. The nominal form is consistent with the other attributes of the model and needs no defence.
| From | To | Note |
|---|---|---|
| pr:createdOn | pr:creationDate | |
| pr:lastQueriedOn | pr:lastQueryDate | |
## TN-014 — An attribute never takes a relational suffix
**Statement.** A datatype property never takes a form ending in By, In, From or On. Those forms are reserved for object properties. The only exception is the predicative is of booleans, covered by TN-015.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | datatype_property |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** An attribute named like a relation will be read as one. A reader encountering a term in the shape of computedBy or ownedBy expects a node at the other end, not a string. The tell is usually visible in the label: a parenthetical gloss added to explain what the value actually holds is evidence that the name itself is wrong.
| From | To | Note |
|---|---|---|
| pr:definedBy holding a string | pr:definitionAddress | address, not location — location is taken by the storage side |
## TN-015 — A boolean attribute begins with is
**Statement.** A property of range xsd:boolean begins with is, followed by an adjective or participle. This is the single explicit exception to TN-012.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | datatype_property, annotation_property |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A bare adjective does not read as a statement, and some of them collide with reserved words in the languages that consume the model.
| From | To | Note |
|---|---|---|
| pr:nullable | pr:isNullable | |
| pr:abstract | pr:isAbstract | abstract is a reserved word in several target languages |
## TN-016 — A controlled value is an individual or a literal
**Statement.** A value becomes a TYPED INDIVIDUAL if it needs to be defined, owned, or extended by a domain without modifying the model. It stays a LITERAL if it is a closed technical state whose list is the property of governance and must precisely not be extended. The model chooses one form per kind of value and states the choice.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class, individual, datatype_property |
| Enforced at | shacl, human |
| Executor | `pr_metamodel_shapes.ttl` |
| Procedure | _pending_ |
| Related | A-Box NR-016 |
**Why.** A literal cannot carry a definition, an owner or a link, and extending its list means editing the shapes and therefore the frozen vocabulary. A typed individual can be added by a domain without touching the model, appears as a node in every viewer, and can be reached by following an edge. The test is not how the value looks but who is allowed to add one.
| From | To | Note |
|---|---|---|
| an activation level defined in a playbook, not in the graph | literal constrained by sh:in | not domain-extensible |
| a kind of system a domain may need to add | typed individual | extensible and worth defining |
## TN-017 — Controlled literals are UPPER_SNAKE_CASE
**Statement.** Controlled literal values are written UPPER_SNAKE_CASE. This is the only place in the model where that casing is used.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | datatype_property |
| Enforced at | shacl |
| Executor | `pr_metamodel_shapes.ttl` |
| Procedure | _pending_ |
**Why.** The casing exists so that a value drawn from a closed list is distinguishable from free text at a glance, in the source and in any export.
| From | To | Note |
|---|---|---|
| "Full activation" | "FULL_ACTIVATION" | |
| "Published" | "PUBLISHED" | |
---
# Label — Derivation and display
The label is derived, not authored. Anything a reader needs that the derivation cannot produce lives in a dedicated channel: the short label for the spoken form, the comment for meaning.
## TN-018 — The label is derived from the IRI
**Statement.** rdfs:label is obtained from the local name of the IRI by inserting a space at every case boundary. No word added, removed, reordered or substituted. The words of a class label keep their initial capital; the words of a property label are entirely lower case.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class, object_property, datatype_property, annotation_property, individual |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
| Related | A-Box NR-005 (deliberate opposite) |
**Why.** A label carrying meaning the IRI does not is the symptom of a badly named IRI or of an incomplete comment — never of a legitimate exception to the label. Where a label has been quietly enriched to explain a term, the explanation belongs in rdfs:comment and the short form, if the organisation speaks one, in pr:shortLabel.
| From | To | Note |
|---|---|---|
| pr:BusinessIntelligenceDataSource | Business Intelligence Data Source | a class label keeps its capitals |
| pr:primarilyStoredIn | primarily stored in | a property label is lower case |
| "Ownership and Categorization Layer Object" | "Ownership Layer Object" | the added meaning moves to rdfs:comment |
## TN-019 — No acronym in the label
**Statement.** The label introduces no acronym or abbreviation that the IRI does not already carry, including in a recapitalised form that the derivation would not produce. Where the IRI legitimately carries an acronym under TN-011, the derived label carries it too and nothing further is added.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class, object_property, datatype_property, annotation_property, individual |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A direct corollary of TN-018, kept as a separate rule because it catches labels whose IRI is innocent: a term correctly named in full can still carry a hand-written label that reintroduces the short form. Stated as an introduction rather than a prohibition, it stays a pure corollary — whatever the IRI legitimately holds, the derivation reproduces, and nothing else.
| From | To | Note |
|---|---|---|
| "BI expression" | "expression" | the IRI was already conforming |
## TN-020 — No state in the label
**Statement.** The label mentions no status, version or deprecation. State lives in an axiom — owl:deprecated, pr:status — never in text. Deprecated and active terms are never mixed in a single view: a consumer filters on the axiom at query time and presents deprecated terms separately.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class, object_property, datatype_property, annotation_property, individual, consumer |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A term whose label repeats its state carries that state twice. The day one is changed without the other, the two contradict each other and nothing detects it. With the rule in force, the question of whether a term is deprecated has exactly one possible answer.
| From | To | Note |
|---|---|---|
| "belongs to domain (deprecated)" | "belongs to domain" | owl:deprecated carries the state |
## TN-021 — No gloss or parenthesis in the label
**Statement.** No parenthesis, qualification or example appears in the label. They belong in rdfs:comment.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class, object_property, datatype_property, annotation_property |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A parenthetical gloss is almost always the symptom of an IRI that does not say what it holds. The right response is usually to rename the term, not to annotate the label.
| From | To | Note |
|---|---|---|
| "defined by (code URI)" | "definition address" | rename the term rather than gloss the label |
| "query count (30 days)" | "query count" | |
## TN-022 — The short label carries the spoken form
**Statement.** A term carries pr:shortLabel whenever the form derived under TN-018 is not what the organisation writes or says. The short label is free: acronyms, abbreviations, hyphens, short forms.
| | |
|---|---|
| Severity | **MAJOR** |
| Applies to | class, individual |
| Enforced at | human |
| Executor | `review checklist` |
| Procedure | _pending_ |
**Why.** The short label is what makes the derivation rule bearable. Without it, a mechanical label impoverishes every screen; with it, display becomes explicit and centrally governed instead of being improvised by each consumer. It is MAJOR rather than BLOCKING because judging whether a derived form matches what people actually say is human work, and EV-011 forbids declaring a rule blocking with no executor.
| From | To | Note |
|---|---|---|
| "Data Sub Domain Owner" | short label Sub Domain Owner | the word Data is dropped in the spoken form |
| "Key Performance Indicator" | short label KPI, acronym KPI | the acronym channel is what makes TN-002 bearable |
## TN-023 — The label is generated, never typed
**Statement.** rdfs:label is never written by hand. It is produced by the derivation function from the IRI and stored in the source. A hand-typed label is a violation even when its value happens to be correct.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class, object_property, datatype_property, annotation_property, individual |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** Generate and store, rather than store only or compute at read time. The label stays in the source so SPARQL and third-party tooling behave normally, while a script regenerates it at every commit and the gate fails if a stored label differs from the derived one. Redundancy becomes harmless because it is checked: rather than hoping two facts stay consistent, the model makes inconsistency detectable.
| From | To | Note |
|---|---|---|
| label absent | violation | |
| label differing from the derivation | violation — drift | |
---
# Declaration — Declarative completeness
Nothing is left to be guessed. What a term is, what it applies to, where it sits and where it came from are all stated, never inferred from structure or from silence.
## TN-024 — Every active term carries a comment
**Statement.** rdfs:comment is mandatory on every active term. On a property the comment is structured: the definition, plus the scope constraint or the confusion to be avoided. On a class the form is free.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class, object_property, datatype_property, annotation_property |
| Enforced at | script, human |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A comment that paraphrases the name prevents nothing. A good one prevents a specific error: it says where the property may and may not be declared, or which neighbouring term it must not be confused with. The structure is imposed on properties because they are where ambiguity costs most — they are asserted thousands of times by people who will not read the model. The script checks presence; only a reviewer checks value, which is why the rule names two tiers.
| From | To | Note |
|---|---|---|
| "the owner" | definition plus where it may be declared and what it excludes | |
## TN-025 — Every property declares its domain and range
**Statement.** rdfs:domain and rdfs:range are mandatory on every property, EXCEPT where the property is deliberately polymorphic. A polymorphic property states so in its comment and has its scope declared in SHACL. Silent absence is a violation; documented absence is not.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | object_property, datatype_property |
| Enforced at | script, human |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A property with no domain cannot be targeted by any control. But some properties are polymorphic by design, their scope controlled class by class in SHACL rather than by twin properties; giving those an rdfs:domain would trigger the RDFS retyping described in TN-028. The rule therefore separates the two cases rather than demanding a domain everywhere.
| From | To | Note |
|---|---|---|
| a polymorphic property with no domain and no comment | violation | silence is indistinguishable from omission |
| a polymorphic property with no domain | conforming | scope declared in SHACL |
## TN-026 — Every term declares how it was authored
**Statement.** pr:authoringMode is mandatory on every term. pr:harvestSource is mandatory if and only if the mode is HARVESTED.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class, object_property, datatype_property, annotation_property |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** Provenance decides who may edit a term and what a divergence means. A term stating where its data comes from without stating that it is harvested, or declaring itself harvested without naming a source, is half-declared in a way no control can catch. Declared symmetrically, provenance also makes a harvester specifiable from the model itself rather than from a side document.
| From | To | Note |
|---|---|---|
| harvestSource present | authoringMode HARVESTED | |
| authoringMode HARVESTED, harvestSource absent | harvestSource declared | |
## TN-027 — A concrete class has one layer and one provenance
**Statement.** Every concrete class descends from exactly one layer root and from exactly one provenance axis. Controlled-vocabulary classes are outside the layers by nature — a named and closed exception.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | class |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A class with no layer is invisible to every layer-scoped control and to every view organised by layer; a class with two is ambiguous in both. Where a family of classes genuinely sits outside the data layers — actors, governance objects — the answer is an explicit root of its own, not silence. Adding such a root by INSERTION above an existing abstraction, rather than by renaming it, keeps the ranges that point at that abstraction readable: a range must state the nature of its target, not its position in the model.
| From | To | Note |
|---|---|---|
| a class with no parent | attached to its layer root | |
| renaming an abstraction into a layer root | inserting the layer root above it | preserves the meaning of every range that points at it |
## TN-028 — A sub-property inherits the domain of its parent
**Statement.** Declaring rdfs:subPropertyOf does not shield a property from the rdfs:domain of its parent. Every assertion of a sub-property is also an assertion of the parent, so the parent's domain applies and its RDFS retyping takes effect.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | object_property, datatype_property |
| Enforced at | script, human |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** In RDFS a domain is not a rejecting constraint but a retyping inference. Attaching a sub-property to a parent whose domain does not fit will silently retype its subjects — an object of one layer quietly becomes an object of another, entering counts, controls and traversals it was never part of. Where two relations share a verb but not a domain, they must not be merged and must not be linked by subPropertyOf; the unification, if one is needed, belongs on a shared upper property with no domain of its own.
| From | To | Note |
|---|---|---|
| a dashboard field asserted through a property whose domain is a warehouse column | the dashboard field is inferred to BE a warehouse column | silent retyping |
| linking the two by rdfs:subPropertyOf | no protection — the parent's domain still applies | |
| a shared upper property with no domain | end-to-end traversal without merging IRIs | the correct unification |
---
# Evolution — Change and propagation
Every rule here is anchored on a real failure. They govern how the model changes without breaking what depends on it.
## EV-001 — A rename is never an edit in place
**Statement.** A rename creates a new term and keeps the old one with owl:deprecated true and dcterms:isReplacedBy. No IRI is modified in place. The deprecated stub carries no edge: no subClassOf, no label, no comment — only its type and the two axioms.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | procedure |
| Enforced at | human |
| Executor | `commit review` |
| Procedure | _pending_ |
| Related | A-Box LC-002 |
**Why.** Nothing propagates in RDF. An IRI is a string copied into every triple, so renaming means rewriting every triple where it appears as subject, predicate or object — declaration, subclass axioms, domain, range, instance typing, shape targets and paths, queries, viewers, documentation. Edges are moved, not duplicated. The stub exists so that a consumer holding an older export can still be told what replaced the term.
| From | To | Note |
|---|---|---|
| editing an IRI in place | new term plus deprecated stub | |
## EV-002 — A withdrawal requires proof of non-instantiation
**Statement.** A term is removed only after a count proving it was never instantiated, the count being attached to the commit. Without proof, deprecation only.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | procedure |
| Enforced at | script |
| Executor | `count query attached to the commit` |
| Procedure | _pending_ |
**Why.** Removal is the one irreversible operation on a vocabulary. The proof is cheap — a single counting query — and it is what separates a safe withdrawal from a silent data loss.
| From | To | Note |
|---|---|---|
| deleting a term | SELECT (COUNT(?s) AS ?n) WHERE { ?s a pr:Term } | must return zero |
## EV-003 — A deletion removes every reference to the subject
**Statement.** A subject removed while its IRI is still cited elsewhere is recreated by RDFS inference. Every reference is removed in the same operation.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | procedure |
| Enforced at | script |
| Executor | `dangling-reference check in the migration script` |
| Procedure | _pending_ |
**Why.** A graph has no foreign keys. Deleting the block that declares a subject leaves every triple that names it intact, and a reasoner will reconstruct a hollow node from them — present in traversals, absent from every control.
| From | To | Note |
|---|---|---|
| deleting a subject block | deleting the block and every triple naming it | |
## EV-004 — A change is validated before it is applied
**Statement.** Every modification of the model is agreed before any artifact is written. Dry run first, apply second.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | procedure |
| Enforced at | human |
| Executor | `commit review` |
| Procedure | _pending_ |
**Why.** The damage from a bad change is rarely in the change itself but in everything that silently depended on the old state. A dry run that reports what it would touch is what makes that dependency visible while it can still be discussed.
| From | To | Note |
|---|---|---|
| migrate.py --apply as the first run | migrate.py, then --apply | dry run is the default |
## EV-005 — Graph edits go through an RDF parser
**Statement.** Any transformation of the graph is performed with an RDF parser. No regular expression applied line by line to Turtle.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | procedure, script |
| Enforced at | human |
| Executor | `script review` |
| Procedure | _pending_ |
**Why.** Turtle is a structured syntax and a line is not a unit of meaning. A literal may contain the very characters a regex splits on, so a text-level edit that looks correct on every example can truncate a value on the one that matters. A parser knows the difference between a delimiter and a character inside a string; a regex does not, and every workaround for that is a partial reimplementation of the parser.
| From | To | Note |
|---|---|---|
| re.sub on the file text | remove and add on parsed triples | |
| hand-written clause splitting | parser | a workaround is evidence the wrong tool is in use |
## EV-006 — Every script is idempotent
**Statement.** A script may be replayed without changing the result. Its guard tests the TARGET state, not the source state, and it counts its own result. Acceptance test: two consecutive runs, the second reporting zero modifications.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | script |
| Enforced at | script |
| Executor | `double run in continuous integration` |
| Procedure | _pending_ |
**Why.** A chain of transformations is only safe if any step can be replayed. A guard that tests whether work remains to be done breaks as soon as another script has already changed the source; a guard that tests whether the target state exists survives it. The self-count is what turns the property into a verifiable one.
| From | To | Note |
|---|---|---|
| appending unconditionally | appending only if the target is absent | guard on the target state |
## EV-007 — The replayable source is the start of the chain
**Statement.** The replayable source is the starting file plus the ordered sequence of scripts, never an intermediate committed state. Hand-editing a file produced by a script is forbidden, including for a typo: the script is corrected and the chain replayed.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | procedure |
| Enforced at | human |
| Executor | `commit review` |
| Procedure | _pending_ |
**Why.** A committed intermediate file is a convenience, not a source. As soon as one hand edit exists in it that no script performs, replaying the chain no longer reproduces the committed state and nobody can say which of the two is right. Keeping the chain authoritative is also what makes a failed migration recoverable: restart from the last true source with a corrected script, rather than repair a half-migrated file by hand.
| From | To | Note |
|---|---|---|
| fixing a typo in a generated file | fixing the script and replaying | |
## EV-008 — Every artifact has a producer script
**Statement.** No artifact contains hard-coded data. Every deliverable — viewer, documentation, workbook, diagram — is regenerated from a versioned source by a script.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | script, artifact |
| Enforced at | human |
| Executor | `artifact review` |
| Procedure | _pending_ |
**Why.** An artifact that cannot be regenerated becomes stale the moment the model moves, and there is no way to tell whether it is stale or merely different. This rulebook applies the rule to itself: the source is one file, the document and the workbook are generated from it.
| From | To | Note |
|---|---|---|
| a hand-maintained workbook | a source file plus a generator | |
## EV-009 — A change propagates in a single merge
**Statement.** A change is propagated to the vocabulary, the instances, the shapes, the consumers and the documentation in one branch, merged as a block. The main line never sees a state where one has moved and another has not.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | procedure |
| Enforced at | human |
| Executor | `merge review` |
| Procedure | _pending_ |
**Why.** Partial propagation is not a smaller version of a change, it is a different and invalid state. Its worst form is silent: a gate that validates the stale half and reports success. A single merge makes the intermediate state unreachable rather than merely discouraged.
| From | To | Note |
|---|---|---|
| committing the vocabulary and updating the shapes later | one branch | |
## EV-010 — Shapes declare the ontology version they target
**Statement.** The shapes graph declares dcterms:conformsTo equal to the owl:versionIRI of the ontology. Any divergence aborts validation immediately.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | shape, script |
| Enforced at | script |
| Executor | `run_shacl_validation.py` |
| Procedure | _pending_ |
**Why.** Validating against the wrong shapes does not fail loudly. It produces a long list of violations that reads exactly like a regression of the model, and the time goes into interpreting them rather than noticing the mismatch. One declared triple and one check turn that into a single line naming the real problem.
| From | To | Note |
|---|---|---|
| hundreds of violations to interpret | ABORT — shapes target a different ontology version | |
## EV-011 — No blocking rule without an executor
**Statement.** A rule may be declared BLOCKING only if a named script or shape enforces it. A blocking rule with no control is a statement of intent.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | rule |
| Enforced at | script |
| Executor | `rulebook source self-check` |
| Procedure | _pending_ |
**Why.** An unenforced blocking rule is worse than an absent one: it is cited as though it held, and the gap only surfaces when something it should have caught reaches production. Making the executor a mandatory field turns the omission into something a script can detect on the rulebook itself.
| From | To | Note |
|---|---|---|
| severity BLOCKING with no executor | violation | detected by the generators before they write anything |
## EV-012 — Every rule names the tier that enforces it
**Statement.** Controls exist at three tiers — SHACL for the shape of the graph, scripts for the file, the lexicon and naming, human review for semantics. Every rule names its tier. A rule with no assigned tier is a violation.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | rule |
| Enforced at | script |
| Executor | `rulebook source self-check` |
| Procedure | _pending_ |
**Why.** SHACL validates a graph, not a file, and not meaning. Two IRIs standing for the same notion produce two individually valid graphs; a duplicated block of text produces identical triples and no complaint. Naming the tier forces the question of what actually catches each rule, and pushes the ones SHACL cannot see to a control that can: a normalised-label index reviewed by a person, a file-level hash, a naming script, a review checklist.
| From | To | Note |
|---|---|---|
| two IRIs for one notion | normalised label index plus human review | SHACL cannot see it |
| a duplicated text block | file-level block hashing | identical triples |
## EV-013 — No sh:pattern outside the SHACL specification
**Statement.** sh:pattern uses XPath 2.0 regular expressions, which support neither lookbehind nor lookahead. A constraint is expressed positively — what the string must be — rather than negatively. Where that is impossible the rule leaves SHACL and moves to the script tier under EV-012.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | shape |
| Enforced at | script |
| Executor | `run_shacl_validation.py` |
| Procedure | _pending_ |
| Related | A-Box NR-001 |
**Why.** A pattern written in a richer regex dialect does not degrade gracefully: the validator fails to compile it, and the rule that depended on it silently stops being enforced while still being declared blocking.
| From | To | Note |
|---|---|---|
| a pattern using (?<! ... ) | a pattern describing the whole accepted string | express positively |
## EV-014 — The term namespace is never versioned
**Statement.** The namespace of terms is not versioned. The version lives in owl:versionIRI alone.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | ontology |
| Enforced at | script |
| Executor | `check_tbox_naming.py` |
| Procedure | _pending_ |
**Why.** A versioned term namespace changes every IRI at every release, breaking every consumer for no benefit. It also fails silently in the other direction: a file bound to an outdated namespace targets IRIs that no longer exist, so every shape matches nothing and validation passes while testing nothing at all.
| From | To | Note |
|---|---|---|
| a prefix bound to a versioned namespace | a prefix bound to the stable namespace | the version lives in owl:versionIRI |
## EV-015 — Every procedure that writes produces an execution log
**Statement.** Any procedure that modifies the graph writes a versioned execution log, committed alongside the change. The log records the attempt number and timestamp, a checksum of every input consumed, the ontology version and the version the shapes declare they target, a count per step, the full validation report, and a difference against the previous attempt.
| | |
|---|---|
| Severity | **BLOCKING** |
| Applies to | procedure, script |
| Enforced at | script |
| Executor | `the procedure's own runner` |
| Procedure | _pending_ |
**Why.** A counter printed to the terminal disappears with the terminal. When a validation run fails, the question is never only what failed but whether it is the same failure as the previous attempt: a violation that persists unchanged after a correction says the cause lies elsewhere, while one that moves every time says the change is being patched rather than fixed. Those are opposite diagnoses and neither is available without a record of the previous run. The input checksums serve the same purpose one level down, separating a wrong model from a stale file — the ambiguity that made a past incident expensive to explain. The log is also the natural carrier of the proof of non-instantiation required by EV-002, which otherwise has no defined format.
| From | To | Note |
|---|---|---|
| a counter printed to standard output | a versioned log committed with the change | |
| a failed run with no record of the previous one | a difference against the previous attempt | what makes correct-or-abandon decidable |
---
# Annex A — Abstractness
Applies TN-006 and TN-007 to the model. 44 classes, of which **13 abstract** and **31 concrete**. Maintained with the model, not after it.
| Class | isAbstract | Note |
|---|---|---|
| `MetaModelObject` | **true** | root of the model |
| `DefinedObject` | **true** | provenance axis |
| `CapturedObject` | **true** | provenance axis |
| `OwnershipLayerObject` | **true** | layer root |
| `BusinessLayerObject` | **true** | layer root |
| `LogicalLayerObject` | **true** | layer root |
| `PhysicalLayerObject` | **true** | NEW — omission in v1.6 |
| `DeliveryLayerObject` | **true** | layer root |
| `ConsumptionLayerObject` | **true** | layer root |
| `GovernanceLayerObject` | **true** | NEW — seventh layer root |
| `DataStructure` | **true** | harvesting types the concrete sort |
| `KeyConstraint` | **true** | only PrimaryKey and ForeignKey instantiate |
| `Actor` | **true** | renaming would degrade the range of ownedBy and hasPublisher |
| `DataDomain` | false | |
| `DataSubDomain` | false | |
| `BusinessObject` | false | ends in Object and is heavily instantiated - refutes any suffix rule |
| `BusinessConcept` | false | |
| `Metric` | false | concrete despite having KeyPerformanceIndicator beneath it |
| `KeyPerformanceIndicator` | false | |
| `DataObject` | false | |
| `DataElement` | false | |
| `Database` | false | |
| `Schema` | false | |
| `Field` | false | |
| `System` | false | |
| `Transformation` | false | |
| `BaseTable` | false | |
| `ExternalTable` | false | |
| `View` | false | |
| `PrimaryKey` | false | |
| `ForeignKey` | false | |
| `DataProduct` | false | |
| `DataContract` | false | |
| `DataInterface` | false | |
| `BusinessIntelligenceWorkspace` | false | |
| `BusinessIntelligenceDataSource` | false | |
| `BusinessIntelligenceField` | false | |
| `BusinessIntelligenceReport` | false | |
| `DataDomainOwner` | false | |
| `DataSubDomainOwner` | false | |
| `DataProductOwner` | false | |
| `DataSteward` | false | |
| `DataGovernanceLead` | false | |
| `SystemType` | false | must stay concrete — it types five individuals |
---
# Annex B — Display
Applies TN-011 and TN-022. The short label is what screens display and what relation names reuse; the acronym is a search key, never an identity.
| IRI | rdfs:label | shortLabel | acronym | Note |
|---|---|---|---|---|
| `DataDomain` | Data Domain | Domain | DD | DD is already the short form used inside A-Box identifiers under NR-014 |
| `DataSubDomain` | Data Sub Domain | Sub Domain | SD | no hyphen |
| `DataDomainOwner` | Data Domain Owner | Domain Owner | DDO | |
| `DataSubDomainOwner` | Data Sub Domain Owner | Sub Domain Owner | SDO | |
| `DataProductOwner` | Data Product Owner | Product Owner | PO | NOT DPO — the initialism is taken by Data Protection Officer |
| `DataSteward` | Data Steward | Steward | - | |
| `DataGovernanceLead` | Data Governance Lead | Governance Lead | DGL | |
| `DataElement` | Data Element | Element | - | drives the name of hasElement and hasGrainElement |
| `BusinessConcept` | Business Concept | Concept | - | drives the name of usesConcept |
| `BusinessObject` | Business Object | - | BO | |
| `KeyPerformanceIndicator` | Key Performance Indicator | KPI | KPI | the acronym channel is what makes TN-002 bearable |
| `BusinessIntelligenceWorkspace` | Business Intelligence Workspace | BI Workspace | - | |
| `BusinessIntelligenceDataSource` | Business Intelligence Data Source | BI Data Source | - | |
| `BusinessIntelligenceField` | Business Intelligence Field | BI Field | - | |
| `BusinessIntelligenceReport` | Business Intelligence Report | BI Report | - | |
| `OwnershipLayerObject` | Ownership Layer Object | - | - | the categorisation role moves into rdfs:comment |
Binary file not shown.
+209
View File
@@ -0,0 +1,209 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:pr="https://ontology.pernod-ricard.com/process/" id="defs_C1" targetNamespace="https://ontology.pernod-ricard.com/process/" exporter="generate_bpmn.py">
<bpmn:process id="C1" name="Create a term" isExecutable="false">
<bpmn:documentation>Create a term | Scope: class | relation | attribute | annotation | Trigger: A missing concept, edge or field has been identified.</bpmn:documentation>
<bpmn:startEvent id="start" name="Need identified">
<bpmn:outgoing>flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:userTask id="unique" name="Check that no existing term covers the need">
<bpmn:incoming>flow_1</bpmn:incoming>
<bpmn:outgoing>flow_2</bpmn:outgoing>
</bpmn:userTask>
<bpmn:exclusiveGateway id="exists" name="A term already covers it?">
<bpmn:incoming>flow_2</bpmn:incoming>
<bpmn:outgoing>flow_3</bpmn:outgoing>
<bpmn:outgoing>flow_4</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:endEvent id="reuse" name="Reuse the existing term">
<bpmn:incoming>flow_3</bpmn:incoming>
</bpmn:endEvent>
<bpmn:scriptTask id="name" name="Name the term according to its nature">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>TN-001</pr:rule>
<pr:rule>TN-002</pr:rule>
<pr:rule>TN-003</pr:rule>
<pr:rule>TN-004</pr:rule>
<pr:rule>TN-005</pr:rule>
<pr:rule>TN-006</pr:rule>
<pr:rule>TN-009</pr:rule>
<pr:rule>TN-010</pr:rule>
<pr:rule>TN-011</pr:rule>
<pr:rule>TN-012</pr:rule>
<pr:rule>TN-013</pr:rule>
<pr:rule>TN-014</pr:rule>
<pr:rule>TN-015</pr:rule>
<pr:rule>TN-028</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_4</bpmn:incoming>
<bpmn:outgoing>flow_5</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="label" name="Derive the label">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>TN-018</pr:rule>
<pr:rule>TN-019</pr:rule>
<pr:rule>TN-020</pr:rule>
<pr:rule>TN-021</pr:rule>
<pr:rule>TN-023</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_5</bpmn:incoming>
<bpmn:outgoing>flow_6</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:userTask id="short" name="Decide the short label and the acronym">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>TN-022</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_6</bpmn:incoming>
<bpmn:outgoing>flow_7</bpmn:outgoing>
</bpmn:userTask>
<bpmn:userTask id="comment" name="Write the comment">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>TN-024</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_7</bpmn:incoming>
<bpmn:outgoing>flow_8</bpmn:outgoing>
</bpmn:userTask>
<bpmn:scriptTask id="declare" name="Declare typing">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>TN-007</pr:rule>
<pr:rule>TN-025</pr:rule>
<pr:rule>TN-026</pr:rule>
<pr:rule>TN-027</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_8</bpmn:incoming>
<bpmn:outgoing>flow_9</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:userTask id="validate" name="Validate before applying">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-004</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_9</bpmn:incoming>
<bpmn:outgoing>flow_10</bpmn:outgoing>
</bpmn:userTask>
<bpmn:callActivity id="check" name="Verify against the rulebook" calledElement="R1">
<bpmn:incoming>flow_10</bpmn:incoming>
<bpmn:outgoing>flow_11</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:callActivity id="release" name="Propagate and publish" calledElement="X1">
<bpmn:incoming>flow_11</bpmn:incoming>
<bpmn:outgoing>flow_12</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:endEvent id="end" name="Term available">
<bpmn:incoming>flow_12</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="flow_1" sourceRef="start" targetRef="unique" />
<bpmn:sequenceFlow id="flow_2" sourceRef="unique" targetRef="exists" />
<bpmn:sequenceFlow id="flow_3" name="yes" sourceRef="exists" targetRef="reuse" />
<bpmn:sequenceFlow id="flow_4" name="no" sourceRef="exists" targetRef="name" />
<bpmn:sequenceFlow id="flow_5" sourceRef="name" targetRef="label" />
<bpmn:sequenceFlow id="flow_6" sourceRef="label" targetRef="short" />
<bpmn:sequenceFlow id="flow_7" sourceRef="short" targetRef="comment" />
<bpmn:sequenceFlow id="flow_8" sourceRef="comment" targetRef="declare" />
<bpmn:sequenceFlow id="flow_9" sourceRef="declare" targetRef="validate" />
<bpmn:sequenceFlow id="flow_10" sourceRef="validate" targetRef="check" />
<bpmn:sequenceFlow id="flow_11" sourceRef="check" targetRef="release" />
<bpmn:sequenceFlow id="flow_12" sourceRef="release" targetRef="end" />
</bpmn:process>
<bpmndi:BPMNDiagram id="diagram_C1">
<bpmndi:BPMNPlane id="plane_C1" bpmnElement="C1">
<bpmndi:BPMNShape id="shape_start" bpmnElement="start">
<dc:Bounds x="60" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_unique" bpmnElement="unique">
<dc:Bounds x="260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_exists" bpmnElement="exists">
<dc:Bounds x="460" y="80" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_reuse" bpmnElement="reuse">
<dc:Bounds x="660" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_name" bpmnElement="name">
<dc:Bounds x="660" y="210" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_label" bpmnElement="label">
<dc:Bounds x="860" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_short" bpmnElement="short">
<dc:Bounds x="1060" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_comment" bpmnElement="comment">
<dc:Bounds x="1260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_declare" bpmnElement="declare">
<dc:Bounds x="1460" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_validate" bpmnElement="validate">
<dc:Bounds x="1660" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_check" bpmnElement="check">
<dc:Bounds x="1860" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_release" bpmnElement="release">
<dc:Bounds x="2060" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_end" bpmnElement="end">
<dc:Bounds x="2260" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge_flow_1" bpmnElement="flow_1">
<di:waypoint x="96" y="98" />
<di:waypoint x="260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_2" bpmnElement="flow_2">
<di:waypoint x="410" y="115" />
<di:waypoint x="460" y="105" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_3" bpmnElement="flow_3">
<di:waypoint x="510" y="105" />
<di:waypoint x="660" y="98" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_4" bpmnElement="flow_4">
<di:waypoint x="510" y="105" />
<di:waypoint x="660" y="245" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_5" bpmnElement="flow_5">
<di:waypoint x="810" y="245" />
<di:waypoint x="860" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_6" bpmnElement="flow_6">
<di:waypoint x="1010" y="115" />
<di:waypoint x="1060" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_7" bpmnElement="flow_7">
<di:waypoint x="1210" y="115" />
<di:waypoint x="1260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_8" bpmnElement="flow_8">
<di:waypoint x="1410" y="115" />
<di:waypoint x="1460" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_9" bpmnElement="flow_9">
<di:waypoint x="1610" y="115" />
<di:waypoint x="1660" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_10" bpmnElement="flow_10">
<di:waypoint x="1810" y="115" />
<di:waypoint x="1860" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_11" bpmnElement="flow_11">
<di:waypoint x="2010" y="115" />
<di:waypoint x="2060" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_12" bpmnElement="flow_12">
<di:waypoint x="2210" y="115" />
<di:waypoint x="2260" y="98" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
+152
View File
@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:pr="https://ontology.pernod-ricard.com/process/" id="defs_C2" targetNamespace="https://ontology.pernod-ricard.com/process/" exporter="generate_bpmn.py">
<bpmn:process id="C2" name="Create a controlled value" isExecutable="false">
<bpmn:documentation>Create a controlled value | Scope: typed individual | literal | Trigger: A new value is needed in a controlled set.</bpmn:documentation>
<bpmn:startEvent id="start" name="New value needed">
<bpmn:outgoing>flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:userTask id="decide" name="Must it be defined, owned or extended by a domain?">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>TN-016</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_1</bpmn:incoming>
<bpmn:outgoing>flow_2</bpmn:outgoing>
</bpmn:userTask>
<bpmn:exclusiveGateway id="form" name="Which form?">
<bpmn:incoming>flow_2</bpmn:incoming>
<bpmn:outgoing>flow_3</bpmn:outgoing>
<bpmn:outgoing>flow_4</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:scriptTask id="indiv" name="Create the typed individual and derive its label">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>TN-001</pr:rule>
<pr:rule>TN-018</pr:rule>
<pr:rule>TN-022</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_3</bpmn:incoming>
<bpmn:outgoing>flow_5</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="literal" name="Write the literal in upper snake case">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>TN-017</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_4</bpmn:incoming>
<bpmn:outgoing>flow_6</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:callActivity id="shape" name="Extend the closed list in the shapes" calledElement="C3">
<bpmn:incoming>flow_6</bpmn:incoming>
<bpmn:outgoing>flow_7</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:userTask id="validate" name="Validate before applying">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-004</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_5</bpmn:incoming>
<bpmn:incoming>flow_7</bpmn:incoming>
<bpmn:outgoing>flow_8</bpmn:outgoing>
</bpmn:userTask>
<bpmn:callActivity id="check" name="Verify against the rulebook" calledElement="R1">
<bpmn:incoming>flow_8</bpmn:incoming>
<bpmn:outgoing>flow_9</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:callActivity id="release" name="Propagate and publish" calledElement="X1">
<bpmn:incoming>flow_9</bpmn:incoming>
<bpmn:outgoing>flow_10</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:endEvent id="end" name="Value available">
<bpmn:incoming>flow_10</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="flow_1" sourceRef="start" targetRef="decide" />
<bpmn:sequenceFlow id="flow_2" sourceRef="decide" targetRef="form" />
<bpmn:sequenceFlow id="flow_3" name="extensible by a domain" sourceRef="form" targetRef="indiv" />
<bpmn:sequenceFlow id="flow_4" name="closed governance state" sourceRef="form" targetRef="literal" />
<bpmn:sequenceFlow id="flow_5" sourceRef="indiv" targetRef="validate" />
<bpmn:sequenceFlow id="flow_6" sourceRef="literal" targetRef="shape" />
<bpmn:sequenceFlow id="flow_7" sourceRef="shape" targetRef="validate" />
<bpmn:sequenceFlow id="flow_8" sourceRef="validate" targetRef="check" />
<bpmn:sequenceFlow id="flow_9" sourceRef="check" targetRef="release" />
<bpmn:sequenceFlow id="flow_10" sourceRef="release" targetRef="end" />
</bpmn:process>
<bpmndi:BPMNDiagram id="diagram_C2">
<bpmndi:BPMNPlane id="plane_C2" bpmnElement="C2">
<bpmndi:BPMNShape id="shape_start" bpmnElement="start">
<dc:Bounds x="60" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_decide" bpmnElement="decide">
<dc:Bounds x="260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_form" bpmnElement="form">
<dc:Bounds x="460" y="80" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_indiv" bpmnElement="indiv">
<dc:Bounds x="660" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_literal" bpmnElement="literal">
<dc:Bounds x="660" y="210" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_shape" bpmnElement="shape">
<dc:Bounds x="860" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_validate" bpmnElement="validate">
<dc:Bounds x="1060" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_check" bpmnElement="check">
<dc:Bounds x="1260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_release" bpmnElement="release">
<dc:Bounds x="1460" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_end" bpmnElement="end">
<dc:Bounds x="1660" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge_flow_1" bpmnElement="flow_1">
<di:waypoint x="96" y="98" />
<di:waypoint x="260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_2" bpmnElement="flow_2">
<di:waypoint x="410" y="115" />
<di:waypoint x="460" y="105" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_3" bpmnElement="flow_3">
<di:waypoint x="510" y="105" />
<di:waypoint x="660" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_4" bpmnElement="flow_4">
<di:waypoint x="510" y="105" />
<di:waypoint x="660" y="245" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_5" bpmnElement="flow_5">
<di:waypoint x="810" y="115" />
<di:waypoint x="1060" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_6" bpmnElement="flow_6">
<di:waypoint x="810" y="245" />
<di:waypoint x="860" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_7" bpmnElement="flow_7">
<di:waypoint x="1010" y="115" />
<di:waypoint x="1060" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_8" bpmnElement="flow_8">
<di:waypoint x="1210" y="115" />
<di:waypoint x="1260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_9" bpmnElement="flow_9">
<di:waypoint x="1410" y="115" />
<di:waypoint x="1460" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_10" bpmnElement="flow_10">
<di:waypoint x="1610" y="115" />
<di:waypoint x="1660" y="98" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
+151
View File
@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:pr="https://ontology.pernod-ricard.com/process/" id="defs_C3" targetNamespace="https://ontology.pernod-ricard.com/process/" exporter="generate_bpmn.py">
<bpmn:process id="C3" name="Create a shape" isExecutable="false">
<bpmn:documentation>Create a shape | Scope: Any SHACL constraint added to the validation set. | Trigger: A rule needs an executor, or a new class enters the validation perimeter.</bpmn:documentation>
<bpmn:startEvent id="start" name="Constraint needed">
<bpmn:outgoing>flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:userTask id="write" name="Write the constraint">
<bpmn:incoming>flow_1</bpmn:incoming>
<bpmn:outgoing>flow_2</bpmn:outgoing>
</bpmn:userTask>
<bpmn:scriptTask id="pattern" name="Check that every pattern is expressed positively">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-013</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_2</bpmn:incoming>
<bpmn:outgoing>flow_3</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:exclusiveGateway id="portable" name="Expressible within the specification?">
<bpmn:incoming>flow_3</bpmn:incoming>
<bpmn:outgoing>flow_4</bpmn:outgoing>
<bpmn:outgoing>flow_5</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:userTask id="demote" name="Move the rule to the script tier">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-012</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_5</bpmn:incoming>
<bpmn:outgoing>flow_6</bpmn:outgoing>
</bpmn:userTask>
<bpmn:scriptTask id="conforms" name="Align the declared ontology version">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-010</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_4</bpmn:incoming>
<bpmn:outgoing>flow_7</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:callActivity id="test" name="Test against real instances" calledElement="R2">
<bpmn:incoming>flow_7</bpmn:incoming>
<bpmn:outgoing>flow_8</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:userTask id="record" name="Record the shape as the executor of its rule">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-011</pr:rule>
<pr:rule>EV-012</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_6</bpmn:incoming>
<bpmn:incoming>flow_8</bpmn:incoming>
<bpmn:outgoing>flow_9</bpmn:outgoing>
</bpmn:userTask>
<bpmn:callActivity id="release" name="Propagate and publish" calledElement="X1">
<bpmn:incoming>flow_9</bpmn:incoming>
<bpmn:outgoing>flow_10</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:endEvent id="end" name="Shape in force">
<bpmn:incoming>flow_10</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="flow_1" sourceRef="start" targetRef="write" />
<bpmn:sequenceFlow id="flow_2" sourceRef="write" targetRef="pattern" />
<bpmn:sequenceFlow id="flow_3" sourceRef="pattern" targetRef="portable" />
<bpmn:sequenceFlow id="flow_4" name="yes" sourceRef="portable" targetRef="conforms" />
<bpmn:sequenceFlow id="flow_5" name="no" sourceRef="portable" targetRef="demote" />
<bpmn:sequenceFlow id="flow_6" sourceRef="demote" targetRef="record" />
<bpmn:sequenceFlow id="flow_7" sourceRef="conforms" targetRef="test" />
<bpmn:sequenceFlow id="flow_8" sourceRef="test" targetRef="record" />
<bpmn:sequenceFlow id="flow_9" sourceRef="record" targetRef="release" />
<bpmn:sequenceFlow id="flow_10" sourceRef="release" targetRef="end" />
</bpmn:process>
<bpmndi:BPMNDiagram id="diagram_C3">
<bpmndi:BPMNPlane id="plane_C3" bpmnElement="C3">
<bpmndi:BPMNShape id="shape_start" bpmnElement="start">
<dc:Bounds x="60" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_write" bpmnElement="write">
<dc:Bounds x="260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_pattern" bpmnElement="pattern">
<dc:Bounds x="460" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_portable" bpmnElement="portable">
<dc:Bounds x="660" y="80" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_demote" bpmnElement="demote">
<dc:Bounds x="860" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_conforms" bpmnElement="conforms">
<dc:Bounds x="860" y="210" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_test" bpmnElement="test">
<dc:Bounds x="1060" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_record" bpmnElement="record">
<dc:Bounds x="1260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_release" bpmnElement="release">
<dc:Bounds x="1460" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_end" bpmnElement="end">
<dc:Bounds x="1660" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge_flow_1" bpmnElement="flow_1">
<di:waypoint x="96" y="98" />
<di:waypoint x="260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_2" bpmnElement="flow_2">
<di:waypoint x="410" y="115" />
<di:waypoint x="460" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_3" bpmnElement="flow_3">
<di:waypoint x="610" y="115" />
<di:waypoint x="660" y="105" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_4" bpmnElement="flow_4">
<di:waypoint x="710" y="105" />
<di:waypoint x="860" y="245" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_5" bpmnElement="flow_5">
<di:waypoint x="710" y="105" />
<di:waypoint x="860" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_6" bpmnElement="flow_6">
<di:waypoint x="1010" y="115" />
<di:waypoint x="1260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_7" bpmnElement="flow_7">
<di:waypoint x="1010" y="245" />
<di:waypoint x="1060" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_8" bpmnElement="flow_8">
<di:waypoint x="1210" y="115" />
<di:waypoint x="1260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_9" bpmnElement="flow_9">
<di:waypoint x="1410" y="115" />
<di:waypoint x="1460" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_10" bpmnElement="flow_10">
<di:waypoint x="1610" y="115" />
<di:waypoint x="1660" y="98" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
+119
View File
@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:pr="https://ontology.pernod-ricard.com/process/" id="defs_D1" targetNamespace="https://ontology.pernod-ricard.com/process/" exporter="generate_bpmn.py">
<bpmn:process id="D1" name="Deprecate a term" isExecutable="false">
<bpmn:documentation>Deprecate a term | Scope: class | relation | attribute | annotation | individual | Trigger: A term is superseded or no longer needed.</bpmn:documentation>
<bpmn:startEvent id="start" name="Term superseded">
<bpmn:outgoing>flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:userTask id="replacement" name="Identify the replacement">
<bpmn:incoming>flow_1</bpmn:incoming>
<bpmn:outgoing>flow_2</bpmn:outgoing>
</bpmn:userTask>
<bpmn:scriptTask id="count" name="Count the instances for information">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-015</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_2</bpmn:incoming>
<bpmn:outgoing>flow_3</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="mark" name="Mark deprecated and declare the replacement">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-001</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_3</bpmn:incoming>
<bpmn:outgoing>flow_4</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="strip" name="Strip the stub of every edge">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-001</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_4</bpmn:incoming>
<bpmn:outgoing>flow_5</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="label" name="Remove any mention of state from the label">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>TN-020</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_5</bpmn:incoming>
<bpmn:outgoing>flow_6</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:callActivity id="release" name="Propagate and publish" calledElement="X1">
<bpmn:incoming>flow_6</bpmn:incoming>
<bpmn:outgoing>flow_7</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:endEvent id="end" name="Term deprecated">
<bpmn:incoming>flow_7</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="flow_1" sourceRef="start" targetRef="replacement" />
<bpmn:sequenceFlow id="flow_2" sourceRef="replacement" targetRef="count" />
<bpmn:sequenceFlow id="flow_3" sourceRef="count" targetRef="mark" />
<bpmn:sequenceFlow id="flow_4" sourceRef="mark" targetRef="strip" />
<bpmn:sequenceFlow id="flow_5" sourceRef="strip" targetRef="label" />
<bpmn:sequenceFlow id="flow_6" sourceRef="label" targetRef="release" />
<bpmn:sequenceFlow id="flow_7" sourceRef="release" targetRef="end" />
</bpmn:process>
<bpmndi:BPMNDiagram id="diagram_D1">
<bpmndi:BPMNPlane id="plane_D1" bpmnElement="D1">
<bpmndi:BPMNShape id="shape_start" bpmnElement="start">
<dc:Bounds x="60" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_replacement" bpmnElement="replacement">
<dc:Bounds x="260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_count" bpmnElement="count">
<dc:Bounds x="460" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_mark" bpmnElement="mark">
<dc:Bounds x="660" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_strip" bpmnElement="strip">
<dc:Bounds x="860" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_label" bpmnElement="label">
<dc:Bounds x="1060" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_release" bpmnElement="release">
<dc:Bounds x="1260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_end" bpmnElement="end">
<dc:Bounds x="1460" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge_flow_1" bpmnElement="flow_1">
<di:waypoint x="96" y="98" />
<di:waypoint x="260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_2" bpmnElement="flow_2">
<di:waypoint x="410" y="115" />
<di:waypoint x="460" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_3" bpmnElement="flow_3">
<di:waypoint x="610" y="115" />
<di:waypoint x="660" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_4" bpmnElement="flow_4">
<di:waypoint x="810" y="115" />
<di:waypoint x="860" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_5" bpmnElement="flow_5">
<di:waypoint x="1010" y="115" />
<di:waypoint x="1060" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_6" bpmnElement="flow_6">
<di:waypoint x="1210" y="115" />
<di:waypoint x="1260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_7" bpmnElement="flow_7">
<di:waypoint x="1410" y="115" />
<di:waypoint x="1460" y="98" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
+152
View File
@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:pr="https://ontology.pernod-ricard.com/process/" id="defs_D2" targetNamespace="https://ontology.pernod-ricard.com/process/" exporter="generate_bpmn.py">
<bpmn:process id="D2" name="Withdraw a term permanently" isExecutable="false">
<bpmn:documentation>Withdraw a term permanently | Scope: class | relation | attribute | annotation | individual | Trigger: A deprecated term is to be removed from the vocabulary.</bpmn:documentation>
<bpmn:startEvent id="start" name="Withdrawal requested">
<bpmn:outgoing>flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:scriptTask id="count" name="Run the counting query">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-002</pr:rule>
<pr:rule>EV-015</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_1</bpmn:incoming>
<bpmn:outgoing>flow_2</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:exclusiveGateway id="instantiated" name="Any instance found?">
<bpmn:incoming>flow_2</bpmn:incoming>
<bpmn:outgoing>flow_3</bpmn:outgoing>
<bpmn:outgoing>flow_4</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:endEvent id="keep" name="Kept deprecated">
<bpmn:incoming>flow_3</bpmn:incoming>
</bpmn:endEvent>
<bpmn:scriptTask id="proof" name="Attach the proof to the commit">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-002</pr:rule>
<pr:rule>EV-015</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_4</bpmn:incoming>
<bpmn:outgoing>flow_5</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="remove" name="Remove the subject block">
<bpmn:incoming>flow_5</bpmn:incoming>
<bpmn:outgoing>flow_6</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="refs" name="Remove every reference naming the subject">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-003</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_6</bpmn:incoming>
<bpmn:outgoing>flow_7</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:callActivity id="check_voc" name="Verify the vocabulary" calledElement="R1">
<bpmn:incoming>flow_7</bpmn:incoming>
<bpmn:outgoing>flow_8</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:callActivity id="check_abox" name="Validate the instances" calledElement="R2">
<bpmn:incoming>flow_8</bpmn:incoming>
<bpmn:outgoing>flow_9</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:callActivity id="release" name="Propagate and publish" calledElement="X1">
<bpmn:incoming>flow_9</bpmn:incoming>
<bpmn:outgoing>flow_10</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:endEvent id="end" name="Term withdrawn">
<bpmn:incoming>flow_10</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="flow_1" sourceRef="start" targetRef="count" />
<bpmn:sequenceFlow id="flow_2" sourceRef="count" targetRef="instantiated" />
<bpmn:sequenceFlow id="flow_3" name="yes" sourceRef="instantiated" targetRef="keep" />
<bpmn:sequenceFlow id="flow_4" name="no" sourceRef="instantiated" targetRef="proof" />
<bpmn:sequenceFlow id="flow_5" sourceRef="proof" targetRef="remove" />
<bpmn:sequenceFlow id="flow_6" sourceRef="remove" targetRef="refs" />
<bpmn:sequenceFlow id="flow_7" sourceRef="refs" targetRef="check_voc" />
<bpmn:sequenceFlow id="flow_8" sourceRef="check_voc" targetRef="check_abox" />
<bpmn:sequenceFlow id="flow_9" sourceRef="check_abox" targetRef="release" />
<bpmn:sequenceFlow id="flow_10" sourceRef="release" targetRef="end" />
</bpmn:process>
<bpmndi:BPMNDiagram id="diagram_D2">
<bpmndi:BPMNPlane id="plane_D2" bpmnElement="D2">
<bpmndi:BPMNShape id="shape_start" bpmnElement="start">
<dc:Bounds x="60" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_count" bpmnElement="count">
<dc:Bounds x="260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_instantiated" bpmnElement="instantiated">
<dc:Bounds x="460" y="80" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_keep" bpmnElement="keep">
<dc:Bounds x="660" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_proof" bpmnElement="proof">
<dc:Bounds x="660" y="210" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_remove" bpmnElement="remove">
<dc:Bounds x="860" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_refs" bpmnElement="refs">
<dc:Bounds x="1060" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_check_voc" bpmnElement="check_voc">
<dc:Bounds x="1260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_check_abox" bpmnElement="check_abox">
<dc:Bounds x="1460" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_release" bpmnElement="release">
<dc:Bounds x="1660" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_end" bpmnElement="end">
<dc:Bounds x="1860" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge_flow_1" bpmnElement="flow_1">
<di:waypoint x="96" y="98" />
<di:waypoint x="260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_2" bpmnElement="flow_2">
<di:waypoint x="410" y="115" />
<di:waypoint x="460" y="105" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_3" bpmnElement="flow_3">
<di:waypoint x="510" y="105" />
<di:waypoint x="660" y="98" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_4" bpmnElement="flow_4">
<di:waypoint x="510" y="105" />
<di:waypoint x="660" y="245" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_5" bpmnElement="flow_5">
<di:waypoint x="810" y="245" />
<di:waypoint x="860" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_6" bpmnElement="flow_6">
<di:waypoint x="1010" y="115" />
<di:waypoint x="1060" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_7" bpmnElement="flow_7">
<di:waypoint x="1210" y="115" />
<di:waypoint x="1260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_8" bpmnElement="flow_8">
<di:waypoint x="1410" y="115" />
<di:waypoint x="1460" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_9" bpmnElement="flow_9">
<di:waypoint x="1610" y="115" />
<di:waypoint x="1660" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_10" bpmnElement="flow_10">
<di:waypoint x="1810" y="115" />
<di:waypoint x="1860" y="98" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
+131
View File
@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:pr="https://ontology.pernod-ricard.com/process/" id="defs_R1" targetNamespace="https://ontology.pernod-ricard.com/process/" exporter="generate_bpmn.py">
<bpmn:process id="R1" name="Verify the vocabulary against the rulebook" isExecutable="false">
<bpmn:documentation>Verify the vocabulary against the rulebook | Scope: The whole vocabulary, or the subset touched by a change. | Trigger: A term has been created, changed or withdrawn, or a release is prepared.</bpmn:documentation>
<bpmn:startEvent id="start" name="Verification requested">
<bpmn:outgoing>flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:scriptTask id="script" name="Run the naming and declaration checks">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>TN-001</pr:rule>
<pr:rule>TN-002</pr:rule>
<pr:rule>TN-003</pr:rule>
<pr:rule>TN-004</pr:rule>
<pr:rule>TN-005</pr:rule>
<pr:rule>TN-006</pr:rule>
<pr:rule>TN-007</pr:rule>
<pr:rule>TN-009</pr:rule>
<pr:rule>TN-010</pr:rule>
<pr:rule>TN-011</pr:rule>
<pr:rule>TN-012</pr:rule>
<pr:rule>TN-013</pr:rule>
<pr:rule>TN-014</pr:rule>
<pr:rule>TN-015</pr:rule>
<pr:rule>TN-017</pr:rule>
<pr:rule>TN-018</pr:rule>
<pr:rule>TN-019</pr:rule>
<pr:rule>TN-020</pr:rule>
<pr:rule>TN-021</pr:rule>
<pr:rule>TN-023</pr:rule>
<pr:rule>TN-025</pr:rule>
<pr:rule>TN-026</pr:rule>
<pr:rule>TN-027</pr:rule>
<pr:rule>TN-028</pr:rule>
<pr:rule>EV-014</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_1</bpmn:incoming>
<bpmn:outgoing>flow_2</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:exclusiveGateway id="mechanised" name="Any mechanised violation?">
<bpmn:incoming>flow_2</bpmn:incoming>
<bpmn:outgoing>flow_3</bpmn:outgoing>
<bpmn:outgoing>flow_4</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:endEvent id="report_fail" name="Report returned with violations">
<bpmn:incoming>flow_3</bpmn:incoming>
<bpmn:incoming>flow_6</bpmn:incoming>
</bpmn:endEvent>
<bpmn:userTask id="human" name="Review the rules no script can judge">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>TN-008</pr:rule>
<pr:rule>TN-016</pr:rule>
<pr:rule>TN-022</pr:rule>
<pr:rule>TN-024</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_4</bpmn:incoming>
<bpmn:outgoing>flow_5</bpmn:outgoing>
</bpmn:userTask>
<bpmn:exclusiveGateway id="judged" name="Reviewer raises an issue?">
<bpmn:incoming>flow_5</bpmn:incoming>
<bpmn:outgoing>flow_6</bpmn:outgoing>
<bpmn:outgoing>flow_7</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:endEvent id="report_ok" name="Vocabulary conforms">
<bpmn:incoming>flow_7</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="flow_1" sourceRef="start" targetRef="script" />
<bpmn:sequenceFlow id="flow_2" sourceRef="script" targetRef="mechanised" />
<bpmn:sequenceFlow id="flow_3" name="at least one" sourceRef="mechanised" targetRef="report_fail" />
<bpmn:sequenceFlow id="flow_4" name="none" sourceRef="mechanised" targetRef="human" />
<bpmn:sequenceFlow id="flow_5" sourceRef="human" targetRef="judged" />
<bpmn:sequenceFlow id="flow_6" name="yes" sourceRef="judged" targetRef="report_fail" />
<bpmn:sequenceFlow id="flow_7" name="no" sourceRef="judged" targetRef="report_ok" />
</bpmn:process>
<bpmndi:BPMNDiagram id="diagram_R1">
<bpmndi:BPMNPlane id="plane_R1" bpmnElement="R1">
<bpmndi:BPMNShape id="shape_start" bpmnElement="start">
<dc:Bounds x="60" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_script" bpmnElement="script">
<dc:Bounds x="260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_mechanised" bpmnElement="mechanised">
<dc:Bounds x="460" y="80" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_report_fail" bpmnElement="report_fail">
<dc:Bounds x="1060" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_human" bpmnElement="human">
<dc:Bounds x="660" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_judged" bpmnElement="judged">
<dc:Bounds x="860" y="80" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_report_ok" bpmnElement="report_ok">
<dc:Bounds x="1060" y="210" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge_flow_1" bpmnElement="flow_1">
<di:waypoint x="96" y="98" />
<di:waypoint x="260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_2" bpmnElement="flow_2">
<di:waypoint x="410" y="115" />
<di:waypoint x="460" y="105" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_3" bpmnElement="flow_3">
<di:waypoint x="510" y="105" />
<di:waypoint x="1060" y="98" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_4" bpmnElement="flow_4">
<di:waypoint x="510" y="105" />
<di:waypoint x="660" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_5" bpmnElement="flow_5">
<di:waypoint x="810" y="115" />
<di:waypoint x="860" y="105" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_6" bpmnElement="flow_6">
<di:waypoint x="910" y="105" />
<di:waypoint x="1060" y="98" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_7" bpmnElement="flow_7">
<di:waypoint x="910" y="105" />
<di:waypoint x="1060" y="228" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
+104
View File
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:pr="https://ontology.pernod-ricard.com/process/" id="defs_R2" targetNamespace="https://ontology.pernod-ricard.com/process/" exporter="generate_bpmn.py">
<bpmn:process id="R2" name="Validate instances against the shapes" isExecutable="false">
<bpmn:documentation>Validate instances against the shapes | Scope: Any instance graph in the validation perimeter. | Trigger: Instances have changed, shapes have changed, or a release is prepared.</bpmn:documentation>
<bpmn:startEvent id="start" name="Validation requested">
<bpmn:outgoing>flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:scriptTask id="conforms" name="Compare the declared target version with the ontology version">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-010</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_1</bpmn:incoming>
<bpmn:outgoing>flow_2</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:exclusiveGateway id="match" name="Versions match?">
<bpmn:incoming>flow_2</bpmn:incoming>
<bpmn:outgoing>flow_3</bpmn:outgoing>
<bpmn:outgoing>flow_4</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:endEvent id="abort" name="Aborted on version mismatch">
<bpmn:incoming>flow_3</bpmn:incoming>
</bpmn:endEvent>
<bpmn:scriptTask id="run" name="Run the shape validation">
<bpmn:incoming>flow_4</bpmn:incoming>
<bpmn:outgoing>flow_5</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:exclusiveGateway id="violations" name="Any violation?">
<bpmn:incoming>flow_5</bpmn:incoming>
<bpmn:outgoing>flow_6</bpmn:outgoing>
<bpmn:outgoing>flow_7</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:endEvent id="fail" name="Report returned with violations">
<bpmn:incoming>flow_6</bpmn:incoming>
</bpmn:endEvent>
<bpmn:endEvent id="ok" name="Instances conform">
<bpmn:incoming>flow_7</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="flow_1" sourceRef="start" targetRef="conforms" />
<bpmn:sequenceFlow id="flow_2" sourceRef="conforms" targetRef="match" />
<bpmn:sequenceFlow id="flow_3" name="no" sourceRef="match" targetRef="abort" />
<bpmn:sequenceFlow id="flow_4" name="yes" sourceRef="match" targetRef="run" />
<bpmn:sequenceFlow id="flow_5" sourceRef="run" targetRef="violations" />
<bpmn:sequenceFlow id="flow_6" name="at least one" sourceRef="violations" targetRef="fail" />
<bpmn:sequenceFlow id="flow_7" name="none" sourceRef="violations" targetRef="ok" />
</bpmn:process>
<bpmndi:BPMNDiagram id="diagram_R2">
<bpmndi:BPMNPlane id="plane_R2" bpmnElement="R2">
<bpmndi:BPMNShape id="shape_start" bpmnElement="start">
<dc:Bounds x="60" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_conforms" bpmnElement="conforms">
<dc:Bounds x="260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_match" bpmnElement="match">
<dc:Bounds x="460" y="80" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_abort" bpmnElement="abort">
<dc:Bounds x="660" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_run" bpmnElement="run">
<dc:Bounds x="660" y="210" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_violations" bpmnElement="violations">
<dc:Bounds x="860" y="80" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_fail" bpmnElement="fail">
<dc:Bounds x="1060" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_ok" bpmnElement="ok">
<dc:Bounds x="1060" y="210" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge_flow_1" bpmnElement="flow_1">
<di:waypoint x="96" y="98" />
<di:waypoint x="260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_2" bpmnElement="flow_2">
<di:waypoint x="410" y="115" />
<di:waypoint x="460" y="105" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_3" bpmnElement="flow_3">
<di:waypoint x="510" y="105" />
<di:waypoint x="660" y="98" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_4" bpmnElement="flow_4">
<di:waypoint x="510" y="105" />
<di:waypoint x="660" y="245" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_5" bpmnElement="flow_5">
<di:waypoint x="810" y="245" />
<di:waypoint x="860" y="105" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_6" bpmnElement="flow_6">
<di:waypoint x="910" y="105" />
<di:waypoint x="1060" y="98" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_7" bpmnElement="flow_7">
<di:waypoint x="910" y="105" />
<di:waypoint x="1060" y="228" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
+121
View File
@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:pr="https://ontology.pernod-ricard.com/process/" id="defs_R3" targetNamespace="https://ontology.pernod-ricard.com/process/" exporter="generate_bpmn.py">
<bpmn:process id="R3" name="Audit what the shapes cannot see" isExecutable="false">
<bpmn:documentation>Audit what the shapes cannot see | Scope: The whole vocabulary and its source file. | Trigger: Periodic audit, or before a version is published.</bpmn:documentation>
<bpmn:startEvent id="start" name="Audit requested">
<bpmn:outgoing>flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:scriptTask id="index" name="Build the normalised label index">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-012</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_1</bpmn:incoming>
<bpmn:outgoing>flow_2</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="hash" name="Hash every normalised subject block">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-012</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_2</bpmn:incoming>
<bpmn:outgoing>flow_3</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:exclusiveGateway id="shortlist" name="Any candidate found?">
<bpmn:incoming>flow_3</bpmn:incoming>
<bpmn:outgoing>flow_4</bpmn:outgoing>
<bpmn:outgoing>flow_5</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:endEvent id="clean" name="Nothing to arbitrate">
<bpmn:incoming>flow_4</bpmn:incoming>
</bpmn:endEvent>
<bpmn:userTask id="review" name="Arbitrate each candidate">
<bpmn:incoming>flow_5</bpmn:incoming>
<bpmn:outgoing>flow_6</bpmn:outgoing>
</bpmn:userTask>
<bpmn:exclusiveGateway id="act" name="Duplication confirmed?">
<bpmn:incoming>flow_6</bpmn:incoming>
<bpmn:outgoing>flow_7</bpmn:outgoing>
<bpmn:outgoing>flow_8</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:endEvent id="merge" name="Referred to the merge procedure">
<bpmn:incoming>flow_7</bpmn:incoming>
</bpmn:endEvent>
<bpmn:endEvent id="dismissed" name="Candidates dismissed">
<bpmn:incoming>flow_8</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="flow_1" sourceRef="start" targetRef="index" />
<bpmn:sequenceFlow id="flow_2" sourceRef="index" targetRef="hash" />
<bpmn:sequenceFlow id="flow_3" sourceRef="hash" targetRef="shortlist" />
<bpmn:sequenceFlow id="flow_4" name="none" sourceRef="shortlist" targetRef="clean" />
<bpmn:sequenceFlow id="flow_5" name="at least one" sourceRef="shortlist" targetRef="review" />
<bpmn:sequenceFlow id="flow_6" sourceRef="review" targetRef="act" />
<bpmn:sequenceFlow id="flow_7" name="yes" sourceRef="act" targetRef="merge" />
<bpmn:sequenceFlow id="flow_8" name="no" sourceRef="act" targetRef="dismissed" />
</bpmn:process>
<bpmndi:BPMNDiagram id="diagram_R3">
<bpmndi:BPMNPlane id="plane_R3" bpmnElement="R3">
<bpmndi:BPMNShape id="shape_start" bpmnElement="start">
<dc:Bounds x="60" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_index" bpmnElement="index">
<dc:Bounds x="260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_hash" bpmnElement="hash">
<dc:Bounds x="460" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_shortlist" bpmnElement="shortlist">
<dc:Bounds x="660" y="80" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_clean" bpmnElement="clean">
<dc:Bounds x="860" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_review" bpmnElement="review">
<dc:Bounds x="860" y="210" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_act" bpmnElement="act">
<dc:Bounds x="1060" y="80" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_merge" bpmnElement="merge">
<dc:Bounds x="1260" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_dismissed" bpmnElement="dismissed">
<dc:Bounds x="1260" y="210" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge_flow_1" bpmnElement="flow_1">
<di:waypoint x="96" y="98" />
<di:waypoint x="260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_2" bpmnElement="flow_2">
<di:waypoint x="410" y="115" />
<di:waypoint x="460" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_3" bpmnElement="flow_3">
<di:waypoint x="610" y="115" />
<di:waypoint x="660" y="105" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_4" bpmnElement="flow_4">
<di:waypoint x="710" y="105" />
<di:waypoint x="860" y="98" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_5" bpmnElement="flow_5">
<di:waypoint x="710" y="105" />
<di:waypoint x="860" y="245" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_6" bpmnElement="flow_6">
<di:waypoint x="1010" y="245" />
<di:waypoint x="1060" y="105" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_7" bpmnElement="flow_7">
<di:waypoint x="1110" y="105" />
<di:waypoint x="1260" y="98" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_8" bpmnElement="flow_8">
<di:waypoint x="1110" y="105" />
<di:waypoint x="1260" y="228" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
+326
View File
@@ -0,0 +1,326 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:pr="https://ontology.pernod-ricard.com/process/" id="defs_X1" targetNamespace="https://ontology.pernod-ricard.com/process/" exporter="generate_bpmn.py">
<bpmn:process id="X1" name="Propagate a change and publish a version" isExecutable="false">
<bpmn:documentation>Propagate a change and publish a version | Scope: Any validated modification of the vocabulary. | Trigger: A change to the vocabulary has been agreed and is ready to apply.</bpmn:documentation>
<bpmn:startEvent id="start" name="Change agreed">
<bpmn:outgoing>flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:scriptTask id="branch" name="Create the migration branch">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-009</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_1</bpmn:incoming>
<bpmn:outgoing>flow_2</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="apply" name="Apply the change through an RDF parser">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-005</pr:rule>
<pr:rule>EV-006</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_2</bpmn:incoming>
<bpmn:incoming>flow_16</bpmn:incoming>
<bpmn:outgoing>flow_3</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="labels" name="Regenerate labels by derivation">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>TN-023</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_3</bpmn:incoming>
<bpmn:outgoing>flow_4</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="abox" name="Propagate to the instances">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-009</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_4</bpmn:incoming>
<bpmn:outgoing>flow_5</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="shapes" name="Update the shapes and align conformsTo">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-010</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_5</bpmn:incoming>
<bpmn:outgoing>flow_6</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="artifacts" name="Regenerate every derived artifact">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-008</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_6</bpmn:incoming>
<bpmn:outgoing>flow_7</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="replay" name="Replay the chain a second time">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-006</pr:rule>
<pr:rule>EV-007</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_7</bpmn:incoming>
<bpmn:outgoing>flow_8</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:exclusiveGateway id="idem" name="Second run reports zero change?">
<bpmn:incoming>flow_8</bpmn:incoming>
<bpmn:outgoing>flow_9</bpmn:outgoing>
<bpmn:outgoing>flow_10</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:callActivity id="verify_voc" name="Verify the vocabulary" calledElement="R1">
<bpmn:incoming>flow_9</bpmn:incoming>
<bpmn:outgoing>flow_11</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:callActivity id="verify_abox" name="Validate the instances" calledElement="R2">
<bpmn:incoming>flow_11</bpmn:incoming>
<bpmn:outgoing>flow_12</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:scriptTask id="log" name="Write the execution log for this attempt">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-015</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_12</bpmn:incoming>
<bpmn:outgoing>flow_13</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:exclusiveGateway id="clean" name="Any violation?">
<bpmn:incoming>flow_13</bpmn:incoming>
<bpmn:outgoing>flow_14</bpmn:outgoing>
<bpmn:outgoing>flow_15</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:userTask id="decide" name="Correct or abandon">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-004</pr:rule>
<pr:rule>EV-007</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_10</bpmn:incoming>
<bpmn:incoming>flow_15</bpmn:incoming>
<bpmn:outgoing>flow_16</bpmn:outgoing>
<bpmn:outgoing>flow_17</bpmn:outgoing>
</bpmn:userTask>
<bpmn:scriptTask id="drop" name="Destroy the branch and restart from the published version">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-007</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_17</bpmn:incoming>
<bpmn:outgoing>flow_18</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:userTask id="review" name="Review the merge">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-004</pr:rule>
<pr:rule>EV-009</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_14</bpmn:incoming>
<bpmn:outgoing>flow_19</bpmn:outgoing>
</bpmn:userTask>
<bpmn:scriptTask id="merge" name="Merge as a block">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-009</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_19</bpmn:incoming>
<bpmn:outgoing>flow_20</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:scriptTask id="bump" name="Increment the ontology version">
<bpmn:extensionElements>
<pr:rules>
<pr:rule>EV-014</pr:rule>
</pr:rules>
</bpmn:extensionElements>
<bpmn:incoming>flow_20</bpmn:incoming>
<bpmn:outgoing>flow_21</bpmn:outgoing>
</bpmn:scriptTask>
<bpmn:endEvent id="end" name="Version published">
<bpmn:incoming>flow_21</bpmn:incoming>
</bpmn:endEvent>
<bpmn:endEvent id="aborted" name="Change abandoned">
<bpmn:incoming>flow_18</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="flow_1" sourceRef="start" targetRef="branch" />
<bpmn:sequenceFlow id="flow_2" sourceRef="branch" targetRef="apply" />
<bpmn:sequenceFlow id="flow_3" sourceRef="apply" targetRef="labels" />
<bpmn:sequenceFlow id="flow_4" sourceRef="labels" targetRef="abox" />
<bpmn:sequenceFlow id="flow_5" sourceRef="abox" targetRef="shapes" />
<bpmn:sequenceFlow id="flow_6" sourceRef="shapes" targetRef="artifacts" />
<bpmn:sequenceFlow id="flow_7" sourceRef="artifacts" targetRef="replay" />
<bpmn:sequenceFlow id="flow_8" sourceRef="replay" targetRef="idem" />
<bpmn:sequenceFlow id="flow_9" name="zero change" sourceRef="idem" targetRef="verify_voc" />
<bpmn:sequenceFlow id="flow_10" name="the chain is not replayable" sourceRef="idem" targetRef="decide" />
<bpmn:sequenceFlow id="flow_11" sourceRef="verify_voc" targetRef="verify_abox" />
<bpmn:sequenceFlow id="flow_12" sourceRef="verify_abox" targetRef="log" />
<bpmn:sequenceFlow id="flow_13" sourceRef="log" targetRef="clean" />
<bpmn:sequenceFlow id="flow_14" name="none" sourceRef="clean" targetRef="review" />
<bpmn:sequenceFlow id="flow_15" name="at least one" sourceRef="clean" targetRef="decide" />
<bpmn:sequenceFlow id="flow_16" name="correct" sourceRef="decide" targetRef="apply" />
<bpmn:sequenceFlow id="flow_17" name="abandon" sourceRef="decide" targetRef="drop" />
<bpmn:sequenceFlow id="flow_18" sourceRef="drop" targetRef="aborted" />
<bpmn:sequenceFlow id="flow_19" sourceRef="review" targetRef="merge" />
<bpmn:sequenceFlow id="flow_20" sourceRef="merge" targetRef="bump" />
<bpmn:sequenceFlow id="flow_21" sourceRef="bump" targetRef="end" />
</bpmn:process>
<bpmndi:BPMNDiagram id="diagram_X1">
<bpmndi:BPMNPlane id="plane_X1" bpmnElement="X1">
<bpmndi:BPMNShape id="shape_start" bpmnElement="start">
<dc:Bounds x="60" y="80" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_branch" bpmnElement="branch">
<dc:Bounds x="260" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_apply" bpmnElement="apply">
<dc:Bounds x="11660" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_labels" bpmnElement="labels">
<dc:Bounds x="11860" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_abox" bpmnElement="abox">
<dc:Bounds x="12060" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_shapes" bpmnElement="shapes">
<dc:Bounds x="11460" y="80" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_artifacts" bpmnElement="artifacts">
<dc:Bounds x="11660" y="210" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_replay" bpmnElement="replay">
<dc:Bounds x="11860" y="210" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_idem" bpmnElement="idem">
<dc:Bounds x="12060" y="210" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_verify_voc" bpmnElement="verify_voc">
<dc:Bounds x="11460" y="210" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_verify_abox" bpmnElement="verify_abox">
<dc:Bounds x="11660" y="340" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_log" bpmnElement="log">
<dc:Bounds x="11860" y="340" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_clean" bpmnElement="clean">
<dc:Bounds x="12060" y="340" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_decide" bpmnElement="decide">
<dc:Bounds x="11460" y="340" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_drop" bpmnElement="drop">
<dc:Bounds x="11660" y="470" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_review" bpmnElement="review">
<dc:Bounds x="11460" y="470" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_merge" bpmnElement="merge">
<dc:Bounds x="11660" y="600" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_bump" bpmnElement="bump">
<dc:Bounds x="11860" y="470" width="150" height="70" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_end" bpmnElement="end">
<dc:Bounds x="12060" y="470" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape_aborted" bpmnElement="aborted">
<dc:Bounds x="11860" y="600" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge_flow_1" bpmnElement="flow_1">
<di:waypoint x="96" y="98" />
<di:waypoint x="260" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_2" bpmnElement="flow_2">
<di:waypoint x="410" y="115" />
<di:waypoint x="11660" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_3" bpmnElement="flow_3">
<di:waypoint x="11810" y="115" />
<di:waypoint x="11860" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_4" bpmnElement="flow_4">
<di:waypoint x="12010" y="115" />
<di:waypoint x="12060" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_5" bpmnElement="flow_5">
<di:waypoint x="12210" y="115" />
<di:waypoint x="11460" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_6" bpmnElement="flow_6">
<di:waypoint x="11610" y="115" />
<di:waypoint x="11660" y="245" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_7" bpmnElement="flow_7">
<di:waypoint x="11810" y="245" />
<di:waypoint x="11860" y="245" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_8" bpmnElement="flow_8">
<di:waypoint x="12010" y="245" />
<di:waypoint x="12060" y="235" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_9" bpmnElement="flow_9">
<di:waypoint x="12110" y="235" />
<di:waypoint x="11460" y="245" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_10" bpmnElement="flow_10">
<di:waypoint x="12110" y="235" />
<di:waypoint x="11460" y="375" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_11" bpmnElement="flow_11">
<di:waypoint x="11610" y="245" />
<di:waypoint x="11660" y="375" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_12" bpmnElement="flow_12">
<di:waypoint x="11810" y="375" />
<di:waypoint x="11860" y="375" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_13" bpmnElement="flow_13">
<di:waypoint x="12010" y="375" />
<di:waypoint x="12060" y="365" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_14" bpmnElement="flow_14">
<di:waypoint x="12110" y="365" />
<di:waypoint x="11460" y="505" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_15" bpmnElement="flow_15">
<di:waypoint x="12110" y="365" />
<di:waypoint x="11460" y="375" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_16" bpmnElement="flow_16">
<di:waypoint x="11610" y="375" />
<di:waypoint x="11660" y="115" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_17" bpmnElement="flow_17">
<di:waypoint x="11610" y="375" />
<di:waypoint x="11660" y="505" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_18" bpmnElement="flow_18">
<di:waypoint x="11810" y="505" />
<di:waypoint x="11860" y="618" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_19" bpmnElement="flow_19">
<di:waypoint x="11610" y="505" />
<di:waypoint x="11660" y="635" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_20" bpmnElement="flow_20">
<di:waypoint x="11810" y="635" />
<di:waypoint x="11860" y="505" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge_flow_21" bpmnElement="flow_21">
<di:waypoint x="12010" y="505" />
<di:waypoint x="12060" y="488" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+950 -919
View File
File diff suppressed because it is too large Load Diff
+120 -56
View File
@@ -7,11 +7,33 @@
@prefix pr: <https://ontology.pernod-ricard.com/metamodel/> . @prefix pr: <https://ontology.pernod-ricard.com/metamodel/> .
################################################################# #################################################################
# PERNOD RICARD DATA META MODEL - T-Box v1.5 # PERNOD RICARD DATA META MODEL - T-Box v1.6
# Owner: Bastien Gourdon - Data Governance Office # Owner: Bastien Gourdon - Data Governance Office
# #
# WHAT CHANGED vs v1.4 # WHAT CHANGED vs v1.5
# -------------------- # --------------------
# A. NO LAYER CARRIES A PROVENANCE. All six layer roots are now structural:
# they say what an object IS, never how it got here. Every concrete class
# declares its own provenance. v1.5 had fixed this for the physical layer
# only, which left the model saying that Ownership is Defined while
# Physical is neither -- an asymmetry with no meaning behind it. The two
# axes are either orthogonal or they are not.
#
# B. ABSTRACTNESS IS DECLARED, NOT GUESSED. isAbstract marks the classes that
# exist only to give their specialisations a parent. A viewer was inferring
# it from "has subclasses", which flagged Metric as abstract -- one of the
# most instantiated classes in the model, whose only crime is having KPI
# beneath it. Whether a class is instantiated is a modelling decision and
# belongs in the model.
#
# C. ForeignKeyConstraint -> ForeignKey, SubDomainOwner labelled Data
# Sub-Domain Owner to match Data Domain Owner, and the ten classes that
# had no rdfs:comment now have one. Stale descriptions corrected: a Data
# Object is stored in a Data Structure, it is no longer "materialized as a
# Physical Relation".
#
# WHAT CHANGED vs v1.4 (kept for reference)
# -----------------------------------------
# CuratedObject -> DefinedObject, ObservedObject -> CapturedObject, in the # CuratedObject -> DefinedObject, ObservedObject -> CapturedObject, in the
# IRIs and not only in the labels. v1.4 renamed what screens showed and left # IRIs and not only in the labels. v1.4 renamed what screens showed and left
# the terms themselves untouched, so the model said one thing and the # the terms themselves untouched, so the model said one thing and the
@@ -219,12 +241,12 @@
<https://ontology.pernod-ricard.com/metamodel/> <https://ontology.pernod-ricard.com/metamodel/>
a owl:Ontology ; a owl:Ontology ;
owl:versionIRI <https://ontology.pernod-ricard.com/metamodel/1.5> ; owl:versionIRI <https://ontology.pernod-ricard.com/metamodel/1.6> ;
owl:priorVersion <https://ontology.pernod-ricard.com/metamodel/1.4> ; owl:priorVersion <https://ontology.pernod-ricard.com/metamodel/1.5> ;
dcterms:title "Pernod Ricard Data Meta Model" ; dcterms:title "Pernod Ricard Data Meta Model" ;
dcterms:creator "Bastien Gourdon" ; dcterms:creator "Bastien Gourdon" ;
dcterms:modified "2026-07-30"^^xsd:date ; dcterms:modified "2026-07-30"^^xsd:date ;
owl:versionInfo "1.5.0-draft-2026-07-30" ; owl:versionInfo "1.6.0-draft-2026-07-30" ;
rdfs:comment """OWL T-Box. Six layers (Ownership, Business, Logical, Delivery, Physical, Consumption) crossed with a provenance axis (Defined vs Captured). Defined objects are written by governance and reviewed; Captured objects are read from Snowflake, Tableau and Looker and are rebuilt, never edited. Bridge annotation properties are declared for future alignment with domain ontologies.""" . rdfs:comment """OWL T-Box. Six layers (Ownership, Business, Logical, Delivery, Physical, Consumption) crossed with a provenance axis (Defined vs Captured). Defined objects are written by governance and reviewed; Captured objects are read from Snowflake, Tableau and Looker and are rebuilt, never edited. Bridge annotation properties are declared for future alignment with domain ontologies.""" .
@@ -257,6 +279,10 @@ pr:harvestSource a owl:AnnotationProperty ;
rdfs:label "harvest source" ; rdfs:label "harvest source" ;
rdfs:comment "For Captured terms: the system view or API that populates this term. Documents the contract between the meta model and the harvester." . rdfs:comment "For Captured terms: the system view or API that populates this term. Documents the contract between the meta model and the harvester." .
pr:isAbstract a owl:AnnotationProperty ;
rdfs:label "is abstract" ;
rdfs:comment "True on a class that exists only to give its specialisations a common parent and a common shape, and is never instantiated. Declared rather than inferred: a viewer that guessed it from 'has subclasses' flagged Metric as abstract, one of the most instantiated classes in the model, whose only crime is having KPI beneath it. Whether a class is instantiated is a modelling decision." .
pr:authoringMode a owl:AnnotationProperty ; pr:authoringMode a owl:AnnotationProperty ;
rdfs:label "authoring mode" ; rdfs:label "authoring mode" ;
rdfs:comment "Who writes this, in practice: ASSERTED (a person does), HARVESTED (a job does) or PROPOSED_CONFIRMED (a job proposes and a person accepts). Renamed from curationMode along with the classes: 'curation' belonged to the same jargon family as 'curated'." . rdfs:comment "Who writes this, in practice: ASSERTED (a person does), HARVESTED (a job does) or PROPOSED_CONFIRMED (a job proposes and a person accepts). Renamed from curationMode along with the classes: 'curation' belonged to the same jargon family as 'curated'." .
@@ -269,13 +295,15 @@ pr:authoringMode a owl:AnnotationProperty ;
pr:MetaModelObject a owl:Class ; pr:MetaModelObject a owl:Class ;
rdfs:label "Meta Model Object" ; rdfs:label "Meta Model Object" ;
rdfs:comment "Anything the meta model describes. Actors are deliberately NOT Meta Model Objects." ; rdfs:comment "Anything the meta model describes. Actors are deliberately NOT Meta Model Objects." ;
pr:hasShortLabel "Meta model object" . pr:hasShortLabel "Meta model object" ;
pr:isAbstract true .
pr:DefinedObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ; pr:DefinedObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ;
rdfs:label "Defined Object" ; rdfs:label "Defined Object" ;
pr:hasShortLabel "Defined" ; pr:hasShortLabel "Defined" ;
pr:authoringMode "ASSERTED" ; pr:authoringMode "ASSERTED" ;
rdfs:comment "Somebody decided it. Written by governance, edited through a pull request on the domain TTL file, and subject to the full identity and lifecycle shape: identifier, name, owner, owning Data Domain, status, version." . rdfs:comment "Somebody decided it. Written by governance, edited through a pull request on the domain TTL file, and subject to the full identity and lifecycle shape: identifier, name, owner, owning Data Domain, status, version." ;
pr:isAbstract true .
pr:CuratedObject a owl:Class ; pr:CuratedObject a owl:Class ;
rdfs:label "Curated Object (deprecated)" ; rdfs:label "Curated Object (deprecated)" ;
@@ -286,49 +314,67 @@ pr:CapturedObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ;
rdfs:label "Captured Object" ; rdfs:label "Captured Object" ;
pr:hasShortLabel "Captured" ; pr:hasShortLabel "Captured" ;
pr:authoringMode "HARVESTED" ; pr:authoringMode "HARVESTED" ;
rdfs:comment "Read from a running system and rebuilt on every run. Never hand-edited. Carries sourceIdentifier, fullyQualifiedName and harvestedOn instead of declared ownership: attribution is INFERRED through the Data Element it is mapped to, never asserted on the node." . rdfs:comment "Read from a running system and rebuilt on every run. Never hand-edited. Carries sourceIdentifier, fullyQualifiedName and harvestedOn instead of declared ownership: attribution is INFERRED through the Data Element it is mapped to, never asserted on the node." ;
pr:isAbstract true .
pr:ObservedObject a owl:Class ; pr:ObservedObject a owl:Class ;
rdfs:label "Observed Object (deprecated)" ; rdfs:label "Observed Object (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:CapturedObject ; owl:deprecated true ; dcterms:isReplacedBy pr:CapturedObject ;
rdfs:comment "v1.4 term. Renamed because 'observed' is passive and technical; 'captured' says where it came from. No instance ever named this class directly." . rdfs:comment "v1.4 term. Renamed because 'observed' is passive and technical; 'captured' says where it came from. No instance ever named this class directly." .
pr:OwnershipLayerObject a owl:Class ; rdfs:subClassOf pr:DefinedObject ; pr:OwnershipLayerObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ;
rdfs:label "Ownership & Categorization Layer Object" . rdfs:label "Ownership & Categorization Layer Object" ;
pr:BusinessLayerObject a owl:Class ; rdfs:subClassOf pr:DefinedObject ; rdfs:comment "Who is accountable, and how the estate is categorised: domains, sub-domains, and the business objects they answer for. A layer root, so it says nothing about provenance." ;
rdfs:label "Business Layer Object" . pr:isAbstract true ;
pr:LogicalLayerObject a owl:Class ; rdfs:subClassOf pr:DefinedObject ; pr:hasShortLabel "Ownership layer" .
pr:BusinessLayerObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ;
rdfs:label "Business Layer Object" ;
rdfs:comment "What the business means: the shared vocabulary and the measures built on it. A layer root, so it says nothing about provenance." ;
pr:isAbstract true ;
pr:hasShortLabel "Business layer" .
pr:LogicalLayerObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ;
rdfs:label "Logical Layer Object" ; rdfs:label "Logical Layer Object" ;
rdfs:comment "How the data is structured." . rdfs:comment "How the data is structured." ;
pr:DeliveryLayerObject a owl:Class ; rdfs:subClassOf pr:DefinedObject ; pr:isAbstract true ;
pr:hasShortLabel "Logical layer" .
pr:DeliveryLayerObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ;
rdfs:label "Delivery Layer Object" ; rdfs:label "Delivery Layer Object" ;
rdfs:comment "What we promise, to whom, until when. A different question from how the data is structured, hence a layer of its own: version, owner, changelog, support channel, consumers to warn." . rdfs:comment "What we promise, to whom, until when. A different question from how the data is structured, hence a layer of its own: version, owner, changelog, support channel, consumers to warn." ;
pr:isAbstract true ;
pr:hasShortLabel "Delivery layer" .
pr:PhysicalLayerObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ; pr:PhysicalLayerObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ;
rdfs:label "Physical Layer Object" ; rdfs:label "Physical Layer Object" ;
rdfs:comment """The one layer whose members do NOT share a provenance, which is why this class -- unlike the other five layer roots -- says nothing about it. rdfs:comment """What actually runs: the systems, containers, structures, columns, keys and code that hold the data.
Like every layer root, this class says nothing about provenance -- and this layer is the one that shows why that matters.
A System is DEFINED: somebody decides that MDM Product is a system worth tracking, and names it. Nobody harvests the list of systems that matter. Everything beneath a system is CAPTURED: databases, schemas, data structures, fields, keys and transformations are read from what is running. A System is DEFINED: somebody decides that MDM Product is a system worth tracking, and names it. Nobody harvests the list of systems that matter. Everything beneath a system is CAPTURED: databases, schemas, data structures, fields, keys and transformations are read from what is running.
Until v1.4 this class was itself declared a kind of Captured Object, which made every physical class captured by construction and put the five seeded Systems in breach of the harvest rules. The two axes are meant to be orthogonal; that shortcut collapsed them.""" . Until v1.4 this class was itself declared a kind of Captured Object, which made every physical class captured by construction and put the five seeded Systems in breach of the harvest rules. v1.6 extended the correction to all six layers: a layer says what an object IS, never how it got here.""" ;
pr:ConsumptionLayerObject a owl:Class ; rdfs:subClassOf pr:CapturedObject ; pr:isAbstract true ;
pr:hasShortLabel "Physical layer" .
pr:ConsumptionLayerObject a owl:Class ; rdfs:subClassOf pr:MetaModelObject ;
rdfs:label "Consumption Layer Object" ; rdfs:label "Consumption Layer Object" ;
rdfs:comment "The BI estate. Separate from Physical because BI assets have their own lifecycle and their own owner: a dashboard belongs to its author, not to the Data Domain that owns the underlying data." ; rdfs:comment "The BI estate. Separate from Physical because BI assets have their own lifecycle and their own owner: a dashboard belongs to its author, not to the Data Domain that owns the underlying data." ;
pr:hasShortLabel "Consumption" . pr:hasShortLabel "Consumption" ;
pr:isAbstract true .
################################################################# #################################################################
# LAYER 1 - OWNERSHIP & CATEGORIZATION (defined) # LAYER 1 - OWNERSHIP & CATEGORIZATION (defined)
################################################################# #################################################################
pr:DataDomain a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject ; pr:DataDomain a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject , pr:DefinedObject ;
rdfs:label "Data Domain" ; rdfs:label "Data Domain" ;
pr:hasShortLabel "Domain" . pr:hasShortLabel "Domain" ;
rdfs:comment "A durable area of the business that owns the meaning of its data: Sales Performance, Product & Material, Financial Performance. It is the unit of accountability -- one owner, one governance lead -- and the top of the categorisation tree. Domains are stable; the projects and systems inside them are not." .
pr:SubDomain a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject ; pr:SubDomain a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject , pr:DefinedObject ;
rdfs:label "Sub-Domain" ; rdfs:label "Sub-Domain" ;
pr:hasShortLabel "Sub-domain" . pr:hasShortLabel "Sub-domain" ;
rdfs:comment "A coherent slice of a Data Domain, grouping the business objects that belong together: Sell Out inside Sales Performance, Fiscal Calendar inside Financial Performance. It exists so that governance can be delegated and reviewed at a size a person can hold in their head." .
pr:BusinessObject a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject ; pr:BusinessObject a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject , pr:DefinedObject ;
rdfs:label "Business Object" ; rdfs:label "Business Object" ;
rdfs:comment "The single anchor of accountability, which is why it stays in the Ownership layer: it is the one object where a steward is declared, and Data Objects and Data Elements inherit from it. It carries NO business definition of its own -- its meaning comes from its aboutConcept. What it does carry is a perimeter: the subject it is about, and the notions needed to read it." ; rdfs:comment "The single anchor of accountability, which is why it stays in the Ownership layer: it is the one object where a steward is declared, and Data Objects and Data Elements inherit from it. It carries NO business definition of its own -- its meaning comes from its aboutConcept. What it does carry is a perimeter: the subject it is about, and the notions needed to read it." ;
pr:hasShortLabel "Business object" . pr:hasShortLabel "Business object" .
@@ -338,12 +384,12 @@ pr:BusinessObject a owl:Class ; rdfs:subClassOf pr:OwnershipLayerObject ;
# LAYER 2 - BUSINESS (defined) # LAYER 2 - BUSINESS (defined)
################################################################# #################################################################
pr:BusinessConcept a owl:Class ; rdfs:subClassOf pr:BusinessLayerObject ; pr:BusinessConcept a owl:Class ; rdfs:subClassOf pr:BusinessLayerObject , pr:DefinedObject ;
rdfs:label "Business Concept" ; rdfs:label "Business Concept" ;
rdfs:comment "A shared business notion WITHOUT a formula. The presence of a formula is the sole discriminator against Metric (rule BR-003)." ; rdfs:comment "A shared business notion WITHOUT a formula. The presence of a formula is the sole discriminator against Metric (rule BR-003)." ;
pr:hasShortLabel "Business concept" . pr:hasShortLabel "Business concept" .
pr:Metric a owl:Class ; rdfs:subClassOf pr:BusinessLayerObject ; pr:Metric a owl:Class ; rdfs:subClassOf pr:BusinessLayerObject , pr:DefinedObject ;
rdfs:label "Metric" ; rdfs:label "Metric" ;
rdfs:comment "A quantifiable measurement WITH a formula, a unit and a grain. Under BR-013 a Metric carries the harmonised CALCULATION RULE; variants that differ only by analysis context are Data Elements linked back via computedBy." ; rdfs:comment "A quantifiable measurement WITH a formula, a unit and a grain. Under BR-013 a Metric carries the harmonised CALCULATION RULE; variants that differ only by analysis context are Data Elements linked back via computedBy." ;
pr:hasShortLabel "Metric" . pr:hasShortLabel "Metric" .
@@ -358,12 +404,12 @@ pr:KPI a owl:Class ; rdfs:subClassOf pr:Metric ;
# LAYER 3 - LOGICAL (defined) # LAYER 3 - LOGICAL (defined)
################################################################# #################################################################
pr:DataObject a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject ; pr:DataObject a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject , pr:DefinedObject ;
rdfs:label "Data Object" ; rdfs:label "Data Object" ;
rdfs:comment "The pivot: represents exactly one Business Object, is composed of Data Elements, and is materialized as one or more Physical Relations. The single junction between governance and technology." ; rdfs:comment "The pivot of the whole model: it represents exactly one Business Object, groups the Data Elements that make it up, and is stored in one or more Data Structures. The single junction between what governance defines and what actually runs -- everything upstream is meaning, everything downstream is machinery." ;
pr:hasShortLabel "Data object" . pr:hasShortLabel "Data object" .
pr:DataElement a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject ; pr:DataElement a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject , pr:DefinedObject ;
rdfs:label "Data Element" ; rdfs:label "Data Element" ;
pr:authoringMode "PROPOSED_CONFIRMED" ; pr:authoringMode "PROPOSED_CONFIRMED" ;
rdfs:comment "The finest defined unit. Its mapping to a physical Field is the ONLY place where the defined graph and the captured graph meet: proposed automatically by name and type matching, confirmed by a Data Steward." ; rdfs:comment "The finest defined unit. Its mapping to a physical Field is the ONLY place where the defined graph and the captured graph meet: proposed automatically by name and type matching, confirmed by a Data Steward." ;
@@ -380,17 +426,17 @@ pr:DataAsset a owl:Class ; rdfs:subClassOf pr:LogicalLayerObject ;
# LAYER 4 - DELIVERY (defined) # LAYER 4 - DELIVERY (defined)
################################################################# #################################################################
pr:DataProduct a owl:Class ; rdfs:subClassOf pr:DeliveryLayerObject ; pr:DataProduct a owl:Class ; rdfs:subClassOf pr:DeliveryLayerObject , pr:DefinedObject ;
rdfs:label "Data Product" ; rdfs:label "Data Product" ;
rdfs:comment "The unit of delivery: what carries a version number, an owner, a changelog, a support channel and a list of consumers to warn. It has no single technical counterpart, and should not: physically it often lands on a Snowflake schema or a dbt project, but that is not what defines it. Operated by exactly one Sub-Domain -- it is a consumable, and there must be no ambiguity about who runs it. Candidate alignment: dcat:Dataset / DPROD." ; rdfs:comment "The unit of delivery: what carries a version number, an owner, a changelog, a support channel and a list of consumers to warn. It has no single technical counterpart, and should not: physically it often lands on a Snowflake schema or a dbt project, but that is not what defines it. Operated by exactly one Sub-Domain -- it is a consumable, and there must be no ambiguity about who runs it. Candidate alignment: dcat:Dataset / DPROD." ;
pr:hasShortLabel "Data product" . pr:hasShortLabel "Data product" .
pr:DataInterface a owl:Class ; rdfs:subClassOf pr:DeliveryLayerObject ; pr:DataInterface a owl:Class ; rdfs:subClassOf pr:DeliveryLayerObject , pr:DefinedObject ;
rdfs:label "Data Interface" ; rdfs:label "Data Interface" ;
rdfs:comment "The channel through which a consumer actually gets the data: a Snowflake secure share or listing, a consumption schema with its grants, a file extract to a panel partner, an API or reverse-ETL feed. Not a Data Object (that is structure) and not a BI Data Source (that is one consumer among others). Verifiable: a grant, a share or an endpoint either exists or it does not. The output port in data-mesh terms." ; rdfs:comment "The channel through which a consumer actually gets the data: a Snowflake secure share or listing, a consumption schema with its grants, a file extract to a panel partner, an API or reverse-ETL feed. Not a Data Object (that is structure) and not a BI Data Source (that is one consumer among others). Verifiable: a grant, a share or an endpoint either exists or it does not. The output port in data-mesh terms." ;
pr:hasShortLabel "Data interface" . pr:hasShortLabel "Data interface" .
pr:DataContract a owl:Class ; rdfs:subClassOf pr:DeliveryLayerObject ; pr:DataContract a owl:Class ; rdfs:subClassOf pr:DeliveryLayerObject , pr:DefinedObject ;
rdfs:label "Data Contract" ; rdfs:label "Data Contract" ;
rdfs:comment "The service commitment attached to a Data Product: schema, freshness, quality thresholds, support, breaking-change notice period. Candidate alignment: ODCS (Bitol)." ; rdfs:comment "The service commitment attached to a Data Product: schema, freshness, quality thresholds, support, breaking-change notice period. Candidate alignment: ODCS (Bitol)." ;
pr:hasShortLabel "Data contract" . pr:hasShortLabel "Data contract" .
@@ -409,12 +455,14 @@ pr:System a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:DefinedObjec
pr:Database a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ; pr:Database a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ;
rdfs:label "Database" ; rdfs:label "Database" ;
pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.DATABASES" ; pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.DATABASES" ;
pr:hasShortLabel "Database" . pr:hasShortLabel "Database" ;
rdfs:comment "The top container of a storage engine: a Snowflake database, a Databricks catalog, a BigQuery project. Named differently by every vendor, which is why the class carries the generic name and the vendor word lives in the harvest annotation." .
pr:Schema a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ; pr:Schema a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ;
rdfs:label "Schema" ; rdfs:label "Schema" ;
pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.SCHEMATA" ; pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.SCHEMATA" ;
pr:hasShortLabel "Schema" . pr:hasShortLabel "Schema" ;
rdfs:comment "The namespace grouping structures inside a database. Optional on purpose: Neo4j has no schema and BigQuery calls the level a dataset, so nothing in the shapes may require it." .
pr:DataStructure a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ; pr:DataStructure a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ;
rdfs:label "Data Structure" ; rdfs:label "Data Structure" ;
@@ -433,7 +481,8 @@ EXTENSION POINT for non-tabular storage. Nothing at Pernod Ricard is non-tabular
pr:BaseTable rdfs:subClassOf pr:Dataset . # instead of DataStructure pr:BaseTable rdfs:subClassOf pr:Dataset . # instead of DataStructure
pr:inSchema rdfs:domain pr:Dataset . # instead of nothing pr:inSchema rdfs:domain pr:Dataset . # instead of nothing
Existing BaseTable assertions stay DataStructure by entailment, every shape targeting DataStructure keeps working, and storedIn does not move. The extension point is paid once; the extension is paid when it arrives.""" . Existing BaseTable assertions stay DataStructure by entailment, every shape targeting DataStructure keeps working, and storedIn does not move. The extension point is paid once; the extension is paid when it arrives.""" ;
pr:isAbstract true .
pr:PhysicalRelation a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ; pr:PhysicalRelation a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ;
rdfs:label "Physical Relation (deprecated)" ; rdfs:label "Physical Relation (deprecated)" ;
@@ -441,7 +490,8 @@ pr:PhysicalRelation a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:Ca
pr:BaseTable a owl:Class ; rdfs:subClassOf pr:DataStructure ; pr:BaseTable a owl:Class ; rdfs:subClassOf pr:DataStructure ;
rdfs:label "Base Table" ; rdfs:label "Base Table" ;
pr:hasShortLabel "Base table" . pr:hasShortLabel "Base table" ;
rdfs:comment "A structure that physically holds its rows, as opposed to one that derives them. The common case in a warehouse Gold layer, and where impact analysis usually bottoms out." .
pr:View a owl:Class ; rdfs:subClassOf pr:DataStructure ; pr:View a owl:Class ; rdfs:subClassOf pr:DataStructure ;
rdfs:label "View" ; rdfs:label "View" ;
@@ -467,13 +517,23 @@ pr:Field a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObjec
pr:KeyConstraint a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ; pr:KeyConstraint a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject ;
rdfs:label "Key Constraint" ; rdfs:label "Key Constraint" ;
pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.TABLE_CONSTRAINTS + KEY_COLUMN_USAGE" ; pr:harvestSource "SNOWFLAKE.INFORMATION_SCHEMA.TABLE_CONSTRAINTS + KEY_COLUMN_USAGE" ;
rdfs:comment "Gives foreign keys a home. Rule OW-006 says an FK is not a Data Element - true, but it still needs somewhere to live, and it lets the asserted DataObject-references-DataObject edge be checked against reality." . rdfs:comment "Gives foreign keys a home. Rule OW-006 says an FK is not a Data Element - true, but it still needs somewhere to live, and it lets the asserted DataObject-references-DataObject edge be checked against reality." ;
pr:isAbstract true ;
pr:hasShortLabel "Key constraint" .
pr:PrimaryKey a owl:Class ; rdfs:subClassOf pr:KeyConstraint ; pr:PrimaryKey a owl:Class ; rdfs:subClassOf pr:KeyConstraint ;
rdfs:label "Primary Key" . rdfs:label "Primary Key" ;
rdfs:comment "The column or set of columns whose values make a row unique. Once harvested it makes a declared grain checkable: a hasGrainElement that does not match the real key is an automatic finding." ;
pr:hasShortLabel "Primary key" .
pr:ForeignKeyConstraint a owl:Class ; rdfs:subClassOf pr:KeyConstraint ; pr:ForeignKey a owl:Class ; rdfs:subClassOf pr:KeyConstraint ;
rdfs:label "Foreign Key Constraint" . rdfs:label "Foreign Key" ;
pr:hasShortLabel "Foreign key" ;
rdfs:comment "A column, or set of columns, whose values must exist in another structure. Rule OW-006 keeps it out of the Data Elements -- it carries no business notion of its own, it is joining plumbing -- but it still needs somewhere to live, and here it makes the asserted qualifiedBy between Data Objects checkable against what the warehouse really enforces." .
pr:ForeignKeyConstraint a owl:Class ;
rdfs:label "Foreign Key Constraint (deprecated)" ;
owl:deprecated true ; dcterms:isReplacedBy pr:ForeignKey .
pr:Transformation a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject , prov:Activity ; pr:Transformation a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:CapturedObject , prov:Activity ;
rdfs:label "Transformation" ; rdfs:label "Transformation" ;
@@ -486,24 +546,25 @@ pr:Transformation a owl:Class ; rdfs:subClassOf pr:PhysicalLayerObject , pr:Capt
# LAYER 6 - CONSUMPTION (captured) # LAYER 6 - CONSUMPTION (captured)
################################################################# #################################################################
pr:BIWorkspace a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject ; pr:BIWorkspace a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject , pr:CapturedObject ;
rdfs:label "BI Workspace" ; rdfs:label "BI Workspace" ;
pr:harvestSource "Tableau Metadata API (Project/Site) ; Looker API (Model)" ; pr:harvestSource "Tableau Metadata API (Project/Site) ; Looker API (Model)" ;
pr:hasShortLabel "BI workspace" . pr:hasShortLabel "BI workspace" ;
rdfs:comment "The container a BI tool organises its content in: a Tableau project or site, a Looker model. Useful mainly for attribution -- it is where you look to find who publishes what." .
pr:BIDataSource a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject ; pr:BIDataSource a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject , pr:CapturedObject ;
rdfs:label "BI Data Source" ; rdfs:label "BI Data Source" ;
pr:harvestSource "Tableau Published Datasource ; Looker Explore" ; pr:harvestSource "Tableau Published Datasource ; Looker Explore" ;
rdfs:comment "The semantic surface a report is built on. This is where BI meets the warehouse." ; rdfs:comment "The semantic surface a report is built on. This is where BI meets the warehouse." ;
pr:hasShortLabel "BI data source" . pr:hasShortLabel "BI data source" .
pr:BIField a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject ; pr:BIField a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject , pr:CapturedObject ;
rdfs:label "BI Field" ; rdfs:label "BI Field" ;
pr:harvestSource "Tableau calculated fields ; LookML measures and dimensions" ; pr:harvestSource "Tableau calculated fields ; LookML measures and dimensions" ;
rdfs:comment "A field as a business user sees it. exposesMetric is what turns a certified Metric from a definition on paper into something measurable against the real BI estate." ; rdfs:comment "A field as a business user sees it. exposesMetric is what turns a certified Metric from a definition on paper into something measurable against the real BI estate." ;
pr:hasShortLabel "BI field" . pr:hasShortLabel "BI field" .
pr:BIReport a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject ; pr:BIReport a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject , pr:CapturedObject ;
rdfs:label "BI Report" ; rdfs:label "BI Report" ;
pr:harvestSource "Tableau Workbook/Sheet ; Looker Look/Dashboard" ; pr:harvestSource "Tableau Workbook/Sheet ; Looker Look/Dashboard" ;
rdfs:comment "What a Data Governance Lead actually cares about in an impact analysis: not that a column changed, but which reports break and who to warn." ; rdfs:comment "What a Data Governance Lead actually cares about in an impact analysis: not that a column changed, but which reports break and who to warn." ;
@@ -516,17 +577,18 @@ pr:BIReport a owl:Class ; rdfs:subClassOf pr:ConsumptionLayerObject ;
pr:Actor a owl:Class ; rdfs:label "Actor" ; pr:Actor a owl:Class ; rdfs:label "Actor" ;
rdfs:comment "A person or a team. Deliberately NOT a MetaModelObject: people are not subject to ownership and lifecycle rules." ; rdfs:comment "A person or a team. Deliberately NOT a MetaModelObject: people are not subject to ownership and lifecycle rules." ;
pr:hasShortLabel "Actor" . pr:hasShortLabel "Actor" ;
pr:isAbstract true .
pr:DataDomainOwner a owl:Class ; rdfs:subClassOf pr:Actor ; pr:DataDomainOwner a owl:Class ; rdfs:subClassOf pr:Actor ;
rdfs:label "Data Domain Owner" ; rdfs:label "Data Domain Owner" ;
rdfs:comment "Accountable for a Data Domain. Exactly one per domain." ; rdfs:comment "Accountable for a Data Domain. Exactly one per domain." ;
pr:hasShortLabel "Domain owner" . pr:hasShortLabel "Data domain owner" .
pr:SubDomainOwner a owl:Class ; rdfs:subClassOf pr:Actor ; pr:SubDomainOwner a owl:Class ; rdfs:subClassOf pr:Actor ;
rdfs:label "Sub-Domain Owner" ; rdfs:label "Data Sub-Domain Owner" ;
rdfs:comment "Accountable for a Sub-Domain. Exactly one per sub-domain." ; rdfs:comment "Accountable for a Sub-Domain, exactly one per sub-domain. Named to match Data Domain Owner: the two roles are the same responsibility at two levels of the tree, and calling one 'Data Domain Owner' and the other 'Sub-Domain Owner' suggested a difference that does not exist." ;
pr:hasShortLabel "Sub-domain owner" . pr:hasShortLabel "Data sub-domain owner" .
pr:DataGovernanceLead a owl:Class ; rdfs:subClassOf pr:Actor ; pr:DataGovernanceLead a owl:Class ; rdfs:subClassOf pr:Actor ;
rdfs:label "Data Governance Lead" ; rdfs:label "Data Governance Lead" ;
@@ -549,7 +611,8 @@ pr:DataSteward a owl:Class ; rdfs:subClassOf pr:Actor ;
# CONTROLLED VOCABULARIES # CONTROLLED VOCABULARIES
################################################################# #################################################################
pr:ActivationStatus a owl:Class ; rdfs:label "Activation Status" . pr:ActivationStatus a owl:Class ; rdfs:label "Activation Status" ;
rdfs:comment "How far a Data Domain has gone in adopting the governance framework: Not Activated, Light Activation, Full Activation. A statement of maturity, not of quality." .
pr:NotActivated a pr:ActivationStatus ; rdfs:label "Not Activated" . pr:NotActivated a pr:ActivationStatus ; rdfs:label "Not Activated" .
pr:LightActivation a pr:ActivationStatus ; rdfs:label "Light Activation" . pr:LightActivation a pr:ActivationStatus ; rdfs:label "Light Activation" .
pr:FullActivation a pr:ActivationStatus ; rdfs:label "Full Activation" . pr:FullActivation a pr:ActivationStatus ; rdfs:label "Full Activation" .
@@ -560,7 +623,8 @@ pr:Development a pr:Environment ; rdfs:label "Development" .
pr:UserAcceptance a pr:Environment ; rdfs:label "User Acceptance" . pr:UserAcceptance a pr:Environment ; rdfs:label "User Acceptance" .
pr:Production a pr:Environment ; rdfs:label "Production" . pr:Production a pr:Environment ; rdfs:label "Production" .
pr:SystemType a owl:Class ; rdfs:label "System Type" . pr:SystemType a owl:Class ; rdfs:label "System Type" ;
rdfs:comment "What kind of system this is -- source application, master data system, data platform, BI tool, external provider. Coarse on purpose: it exists to group systems in a view, not to model them." .
pr:SourceApplication a pr:SystemType ; rdfs:label "Source Application" . pr:SourceApplication a pr:SystemType ; rdfs:label "Source Application" .
pr:MasterDataSystem a pr:SystemType ; rdfs:label "Master Data System" . pr:MasterDataSystem a pr:SystemType ; rdfs:label "Master Data System" .
pr:DataPlatform a pr:SystemType ; rdfs:label "Data Platform" . pr:DataPlatform a pr:SystemType ; rdfs:label "Data Platform" .
@@ -802,11 +866,11 @@ pr:constrainsField a owl:ObjectProperty ;
pr:fromField a owl:ObjectProperty ; pr:fromField a owl:ObjectProperty ;
rdfs:label "from field" ; rdfs:label "from field" ;
rdfs:domain pr:ForeignKeyConstraint ; rdfs:range pr:Field . rdfs:domain pr:ForeignKey ; rdfs:range pr:Field .
pr:toField a owl:ObjectProperty ; pr:toField a owl:ObjectProperty ;
rdfs:label "to field" ; rdfs:label "to field" ;
rdfs:domain pr:ForeignKeyConstraint ; rdfs:range pr:Field . rdfs:domain pr:ForeignKey ; rdfs:range pr:Field .
pr:consumes a owl:ObjectProperty ; pr:consumes a owl:ObjectProperty ;
rdfs:subPropertyOf prov:used ; rdfs:subPropertyOf prov:used ;
@@ -1044,7 +1108,7 @@ pr:hasExpression a owl:DatatypeProperty ; rdfs:label "BI expression" ; rdfs:rang
[] a owl:AllDisjointClasses ; owl:members ( [] a owl:AllDisjointClasses ; owl:members (
pr:BaseTable pr:View pr:ExternalTable ) . pr:BaseTable pr:View pr:ExternalTable ) .
[] a owl:AllDisjointClasses ; owl:members ( pr:PrimaryKey pr:ForeignKeyConstraint ) . [] a owl:AllDisjointClasses ; owl:members ( pr:PrimaryKey pr:ForeignKey ) .
[] a owl:AllDisjointClasses ; owl:members ( [] a owl:AllDisjointClasses ; owl:members (
pr:BIWorkspace pr:BIDataSource pr:BIField pr:BIReport ) . pr:BIWorkspace pr:BIDataSource pr:BIField pr:BIReport ) .
+13 -13
View File
@@ -36,7 +36,7 @@ DOMAIN_STYLE = OrderedDict([
MULTI = {"composedOf", "usesConcept", "hasElement", "hasMetric", "measures", MULTI = {"composedOf", "usesConcept", "hasElement", "hasMetric", "measures",
"computedBy", "qualifiedBy", "hasGrainElement", "monitoredBy", "hasSynonym", "computedBy", "qualifiedBy", "hasGrainElement", "monitoredBy", "hasSynonym",
"hasUnit", "hasSource", "packages", "exposes"} "unit", "hasSource", "packages", "exposes"}
SUBJECT_DOMAIN = "DD_06" # the hub owner: everything else is a dependency SUBJECT_DOMAIN = "DD_06" # the hub owner: everything else is a dependency
@@ -183,25 +183,25 @@ def build(subjects):
nodes[subj] = { nodes[subj] = {
"id": subj, "id": subj,
"kind": t, "kind": t,
# the model carries both names now: hasShortLabel for screens, # the model carries both names now: shortLabel for screens,
# hasName for the record. The viewer stops keeping its own list. # name for the record. The viewer stops keeping its own list.
"label": LABELS.get(t, t), "label": LABELS.get(t, t),
"shortName": rec.get("hasShortLabel", ""), "shortName": rec.get("shortLabel", ""),
"ident": rec.get("hasIdentifier", ""), "ident": rec.get("identifier", ""),
"name": rec.get("hasName", subj.replace("ex:", "")), "name": rec.get("name", subj.replace("ex:", "")),
"domain": domain_of(subj, rec, subjects), "domain": domain_of(subj, rec, subjects),
"arb": rec.get("arbitrationStatus", "") == "TO_ARBITRATE", "arb": rec.get("arbitrationStatus", "") == "TO_ARBITRATE",
"status": rec.get("hasStatus", ""), "status": rec.get("status", ""),
"definition": rec.get("hasBusinessDefinition", ""), "definition": rec.get("businessDefinition", ""),
"rule": rec.get("hasBusinessRule", ""), "rule": rec.get("businessRule", ""),
"formula": rec.get("hasFormula", ""), "formula": rec.get("formula", ""),
"units": as_list(rec.get("hasUnit")), "units": as_list(rec.get("unit")),
"physical": rec.get("physicalName", ""), "physical": rec.get("physicalName", ""),
"format": rec.get("hasFormat", ""), "format": rec.get("format", ""),
"source": ", ".join(as_list(rec.get("hasSource"))), "source": ", ".join(as_list(rec.get("hasSource"))),
"sourcedFrom": rec.get("sourcedFrom", ""), "sourcedFrom": rec.get("sourcedFrom", ""),
"fqn": "", "fqn": "",
"version": rec.get("hasVersion", ""), "version": rec.get("version", ""),
"operatedBy": rec.get("operatedBy", ""), "operatedBy": rec.get("operatedBy", ""),
"governedBy": rec.get("governedBy", ""), "governedBy": rec.get("governedBy", ""),
"productOwner": rec.get("hasProductOwner", ""), "productOwner": rec.get("hasProductOwner", ""),
+68 -28
View File
@@ -34,7 +34,8 @@ WANTED = {
"owl:inverseOf": "inverseOf", "owl:deprecated": "deprecated", "owl:inverseOf": "inverseOf", "owl:deprecated": "deprecated",
"dcterms:isReplacedBy": "isReplacedBy", "dcterms:isReplacedBy": "isReplacedBy",
"pr:harvestSource": "harvestSource", "pr:authoringMode": "authoringMode", "pr:harvestSource": "harvestSource", "pr:authoringMode": "authoringMode",
"pr:hasAcronym": "acronym", "pr:hasShortLabel": "shortLabel", "pr:acronym": "acronym", "pr:shortLabel": "shortLabel",
"pr:isAbstract": "isAbstract",
} }
LAYER_ROOT = OrderedDict([ LAYER_ROOT = OrderedDict([
@@ -200,7 +201,6 @@ def build(subjects):
return out return out
parents = {c: first(classes[c], "subClassOf") for c in classes} parents = {c: first(classes[c], "subClassOf") for c in classes}
has_kids = set(parents.values())
nodes = {} nodes = {}
for term, rec in classes.items(): for term, rec in classes.items():
@@ -234,8 +234,10 @@ def build(subjects):
"acronym": first(rec, "acronym"), "acronym": first(rec, "acronym"),
"deprecated": first(rec, "deprecated") in ("true", "True"), "deprecated": first(rec, "deprecated") in ("true", "True"),
"replacedBy": first(rec, "isReplacedBy"), "replacedBy": first(rec, "isReplacedBy"),
"abstract": term in has_kids and term not in LAYER_ROOT # read, not guessed: inferring it from "has subclasses" flagged
and term not in ("pr:MetaModelObject",), # Metric as abstract, and Metric is one of the most instantiated
# classes in the model
"abstract": first(rec, "isAbstract") in ("true", "True"),
"members": [{"id": m, "label": first(subjects.get(m, {}), "label", "members": [{"id": m, "label": first(subjects.get(m, {}), "label",
m.split(":")[1])} m.split(":")[1])}
for m in individuals.get(term, [])], for m in individuals.get(term, [])],
@@ -495,13 +497,13 @@ footer{background:var(--navy);color:rgba(255,255,255,.5);padding:7px 20px;
<header> <header>
<div> <div>
<h1>PR Data Meta Model &middot; T-Box</h1> <h1>PR Data Meta Model &middot; T-Box</h1>
<div class="sub">Six layers, crossed with what governance asserts and what a harvester observes</div> <div class="sub">Six layers, crossed with what governance defines and what a harvester captures</div>
</div> </div>
<div class="tabs" role="tablist"> <div class="tabs" role="tablist">
<button id="t-scope" class="on">Overview</button> <button id="t-scope" class="on">Overview</button>
<button id="t-model">Layers</button> <button id="t-model">Layers</button>
<button id="t-graph">Graph</button> <button id="t-graph">Graph</button>
<button id="t-props">Relations</button> <button id="t-props">Properties</button>
<button id="t-dep">Deprecated</button> <button id="t-dep">Deprecated</button>
</div> </div>
</header> </header>
@@ -549,18 +551,22 @@ var SCOPED_PAIRS = [
["pr:owningDomain", "pr:DefinedObject", "pr:DataDomain", "owning domain"], ["pr:owningDomain", "pr:DefinedObject", "pr:DataDomain", "owning domain"],
["pr:ownedBy", "pr:DefinedObject", "pr:Actor", "owned by"] ["pr:ownedBy", "pr:DefinedObject", "pr:Actor", "owned by"]
]; ];
var ORDER = ["pr:MetaModelObject", "pr:DefinedObject", "pr:CapturedObject", var ORDER = [
"pr:MetaModelObject", "pr:DefinedObject", "pr:CapturedObject",
"pr:OwnershipLayerObject", "pr:DataDomain", "pr:SubDomain", "pr:BusinessObject", "pr:OwnershipLayerObject", "pr:DataDomain", "pr:SubDomain", "pr:BusinessObject",
"pr:BusinessLayerObject", "pr:BusinessConcept", "pr:Metric", "pr:KPI", "pr:BusinessLayerObject", "pr:BusinessConcept", "pr:Metric", "pr:KPI",
"pr:LogicalLayerObject", "pr:DataObject", "pr:DataElement", "pr:LogicalLayerObject", "pr:DataObject", "pr:DataElement",
"pr:DeliveryLayerObject", "pr:DataProduct", "pr:DataContract", "pr:DataInterface", "pr:DeliveryLayerObject", "pr:DataProduct", "pr:DataContract", "pr:DataInterface",
"pr:PhysicalLayerObject", "pr:System", "pr:Database", "pr:Schema", "pr:PhysicalLayerObject", "pr:System", "pr:Database", "pr:Schema",
"pr:DataStructure", "pr:BaseTable", "pr:View", "pr:ExternalTable", "pr:Field", "pr:DataStructure", "pr:BaseTable", "pr:View", "pr:ExternalTable", "pr:Field",
"pr:KeyConstraint", "pr:PrimaryKey", "pr:ForeignKeyConstraint", "pr:Transformation", "pr:KeyConstraint", "pr:PrimaryKey", "pr:ForeignKey", "pr:Transformation",
"pr:ConsumptionLayerObject", "pr:BIWorkspace", "pr:BIDataSource", "pr:BIField", "pr:ConsumptionLayerObject", "pr:BIWorkspace", "pr:BIDataSource", "pr:BIField",
"pr:BIReport"]; "pr:BIReport",
var LANES = [["defined","Defined \u00b7 someone decided it","#1E4B9B"], "pr:Actor", "pr:DataDomainOwner", "pr:SubDomainOwner", "pr:DataGovernanceLead",
["captured","Captured \u00b7 read from a running system","#8A6A28"], "pr:DataSteward", "pr:DataProductOwner"
];
var LANES = [["defined","Defined","#1E4B9B"],
["captured","Captured","#8A6A28"],
["structural","Structural","#6B6B6B"]]; ["structural","Structural","#6B6B6B"]];
function live(a){ return a.filter(function(x){ return !x.deprecated; }); } function live(a){ return a.filter(function(x){ return !x.deprecated; }); }
function layerTitle(id){ function layerTitle(id){
@@ -726,7 +732,11 @@ function renderGraph(){
stroke:"#D8D4C8", "stroke-width":1})); stroke:"#D8D4C8", "stroke-width":1}));
var t = el("text", {x:l.x+l.w/2, y:-16, "text-anchor":"middle", var t = el("text", {x:l.x+l.w/2, y:-16, "text-anchor":"middle",
class:"lanelabel", fill:l.colour}); class:"lanelabel", fill:l.colour});
t.textContent = l.label; /* clipped to the column: a label wider than its lane overlapped the
neighbouring one, which read as though it belonged to both */
var cap = Math.floor(l.w / 6.4);
t.textContent = l.label.length > cap ? l.label.slice(0, cap-1) + "\u2026"
: l.label;
bL.appendChild(t); bL.appendChild(t);
}); });
M.layers.forEach(function(b){ M.layers.forEach(function(b){
@@ -776,9 +786,9 @@ function renderGraph(){
specialisations a shape, never instantiated */ specialisations a shape, never instantiated */
g.appendChild(el("rect",{width:NW, height:NH, rx:3, fill:"#FFFFFF", g.appendChild(el("rect",{width:NW, height:NH, rx:3, fill:"#FFFFFF",
stroke:c, "stroke-dasharray": n.n.abstract ? "5 3" : "none"})); stroke:c, "stroke-dasharray": n.n.abstract ? "5 3" : "none"}));
var lines = wrapText(n.n.shortLabel || n.n.label, 21); var lines = wrapText(n.n.shortLabel || n.n.label, 20);
lines.forEach(function(t,i){ lines.forEach(function(t,i){
var e = el("text",{x:NW/2, y:NH/2+3.5+(i-(lines.length-1)/2)*10, fill:"#1A1A1A"}); var e = el("text",{x:NW/2, y:NH/2+3+(i-(lines.length-1)/2)*9.5, fill:"#1A1A1A"});
e.textContent = t; g.appendChild(e); e.textContent = t; g.appendChild(e);
}); });
nL.appendChild(g); nL.appendChild(g);
@@ -789,13 +799,27 @@ function renderGraph(){
document.getElementById("g-count").textContent = document.getElementById("g-count").textContent =
G.nodes.length+" classes \u00b7 "+G.edges.length+" edges \u00b7 "+cd+" seam"; G.nodes.length+" classes \u00b7 "+G.edges.length+" edges \u00b7 "+cd+" seam";
} }
function wrapText(s,max){ /* A node box is NW wide and holds at most three lines. A word longer than a
var w=s.split(" "), out=[], cur=""; line is cut rather than allowed to run past the border, and anything beyond
w.forEach(function(x){ the third line is elided -- overflowing text overlapped the neighbouring
if((cur+" "+x).trim().length>max){ if(cur) out.push(cur); cur=x; } column, which is worse than an ellipsis. */
else cur=(cur+" "+x).trim(); }); function wrapText(s, max){
var out = [], cur = "";
s.split(" ").forEach(function(w){
while(w.length > max){
if(cur){ out.push(cur); cur = ""; }
out.push(w.slice(0, max - 1) + "\u2011");
w = w.slice(max - 1);
}
if((cur + " " + w).trim().length > max){ if(cur) out.push(cur); cur = w; }
else cur = (cur + " " + w).trim();
});
if(cur) out.push(cur); if(cur) out.push(cur);
return out.slice(0,2); if(out.length > 3){
out = out.slice(0, 3);
out[2] = out[2].slice(0, Math.max(0, max - 1)) + "\u2026";
}
return out;
} }
function applyHot(){ function applyHot(){
var id = G.sel || G.hot; var id = G.sel || G.hot;
@@ -904,16 +928,21 @@ function renderModel(){
M.layers.forEach(function(l){ M.layers.forEach(function(l){
var g=live(M.nodes).filter(function(n){return n.layer===l.id;}); var g=live(M.nodes).filter(function(n){return n.layer===l.id;});
if(!g.length) return; if(!g.length) return;
/* ORDER alone, never depth first: sorting by depth grouped all the
parents together and pushed Base table, View and External table below
Field and Key constraint, away from the Data Structure they specialise. */
g.sort(function(a,b){ g.sort(function(a,b){
return a.depth - b.depth return ((ORDER.indexOf(a.id)+1)||99) - ((ORDER.indexOf(b.id)+1)||99)
|| ((ORDER.indexOf(a.id)+1)||99) - ((ORDER.indexOf(b.id)+1)||99) || a.depth - b.depth
|| a.label.localeCompare(b.label); }); || a.label.localeCompare(b.label); });
h.push('<h2 class="sec" style="color:'+provColour(g[0].prov)+'">'+esc(l.title)+' \u00b7 '+esc(l.tag)+'</h2>'); h.push('<h2 class="sec" style="color:'+provColour(g[0].prov)+'">'+esc(l.title)+' \u00b7 '+esc(l.tag)+'</h2>');
h.push('<table><thead><tr><th>Class</th><th>Provenance</th><th class="hide-s">Type of</th>' h.push('<table><thead><tr><th>Class</th><th>Provenance</th><th class="hide-s">Type of</th>'
+'<th class="hide-s">What it is for</th></tr></thead><tbody>'); +'<th class="hide-s">What it is for</th></tr></thead><tbody>');
g.forEach(function(n){ g.forEach(function(n){
h.push('<tr data-id="'+n.id+'"><td><span class="dot" style="background:'+provColour(n.prov) h.push('<tr data-id="'+n.id+'"><td style="padding-left:'+(11+n.depth*16)+'px">'
+'"></span><b>'+esc(n.label)+'</b>'+(n.abstract?' '+pill("abstract"):'')+'</td>' +'<span class="dot" style="background:'+provColour(n.prov)
+'"></span><b>'+esc(n.shortLabel||n.label)+'</b>'
+(n.abstract?' '+pill("abstract"):'')+'</td>'
+'<td class="mono">'+n.prov+'</td><td class="hide-s mono">' +'<td class="mono">'+n.prov+'</td><td class="hide-s mono">'
+(byId[n.parent]?esc(clean(byId[n.parent].label)):"\u2014")+'</td>' +(byId[n.parent]?esc(clean(byId[n.parent].label)):"\u2014")+'</td>'
+'<td class="hide-s">'+esc((n.comment||"").split(". ")[0])+'</td></tr>'); +'<td class="hide-s">'+esc((n.comment||"").split(". ")[0])+'</td></tr>');
@@ -925,7 +954,7 @@ function renderModel(){
/* ------------------------------------------------------------ RELATIONS */ /* ------------------------------------------------------------ RELATIONS */
function renderProps(){ function renderProps(){
var h=['<p class="lede">'+live(M.relations).length+' relations and '+live(M.attributes).length+' attributes.</p>' var h=['<p class="lede">'+live(M.relations).length+' relations, '+live(M.attributes).length+' attributes and '+M.bridges.length+' bridge properties.</p>'
+'<p class="sublede">A property with no declared domain or range is deliberate: it serves several classes ' +'<p class="sublede">A property with no declared domain or range is deliberate: it serves several classes '
+'with different targets, so the scope lives in the SHACL shapes instead, stated per class.</p>']; +'with different targets, so the scope lives in the SHACL shapes instead, stated per class.</p>'];
h.push('<div class="filters"><input id="q" type="search" placeholder="filter\u2026">' h.push('<div class="filters"><input id="q" type="search" placeholder="filter\u2026">'
@@ -934,6 +963,14 @@ function renderProps(){
+'</div><div id="tbl"></div>'); +'</div><div id="tbl"></div>');
return h.join(""); return h.join("");
} }
/* First sentence only: the table is for scanning, the card carries the full
text. Splitting on ". " alone broke on abbreviations and on the multi-line
comments, which start with a newline. */
function firstSentence(t){
t = (t||"").replace(/\s+/g," ").trim();
var m = t.match(/^(.{0,150}?[.!?])(\s|$)/);
return m ? m[1] : (t.length > 150 ? t.slice(0,150).replace(/\s\S*$/,"") + "\u2026" : t);
}
function propRows(){ function propRows(){
var all=M.relations.concat(M.attributes,M.bridges).filter(function(p){ var all=M.relations.concat(M.attributes,M.bridges).filter(function(p){
if(p.deprecated) return false; if(p.deprecated) return false;
@@ -942,8 +979,9 @@ function propRows(){
return true; return true;
}); });
all.sort(function(a,b){ return a.kind.localeCompare(b.kind)||a.label.localeCompare(b.label); }); all.sort(function(a,b){ return a.kind.localeCompare(b.kind)||a.label.localeCompare(b.label); });
var h=['<table><thead><tr><th>Property</th><th class="hide-s">From</th><th class="hide-s">To</th>' var h=['<table><thead><tr><th>Property</th><th class="hide-s">From</th>'
+'<th>Traits</th></tr></thead><tbody>']; +'<th class="hide-s">To</th><th class="hide-s">Traits</th>'
+'<th>What it is for</th></tr></thead><tbody>'];
all.forEach(function(p){ all.forEach(function(p){
var t=[]; var t=[];
if(p.functional) t.push("exactly one"); if(p.functional) t.push("exactly one");
@@ -954,7 +992,9 @@ function propRows(){
+'<td class="hide-s">'+(byId[p.domain]?esc(clean(byId[p.domain].label)):'<span class="mono">any</span>')+'</td>' +'<td class="hide-s">'+(byId[p.domain]?esc(clean(byId[p.domain].label)):'<span class="mono">any</span>')+'</td>'
+'<td class="hide-s">'+(byId[p.range]?esc(clean(byId[p.range].label)) +'<td class="hide-s">'+(byId[p.range]?esc(clean(byId[p.range].label))
:'<span class="mono">'+esc(p.range||"\u2014")+'</span>')+'</td>' :'<span class="mono">'+esc(p.range||"\u2014")+'</span>')+'</td>'
+'<td class="mono">'+(t.join(" \u00b7 ")||"\u2014")+'</td></tr>'); +'<td class="hide-s mono">'+(t.join(" \u00b7 ")||"\u2014")+'</td>'
+'<td>'+(p.comment ? esc(firstSentence(p.comment))
: '<span class="mono">no description</span>')+'</td></tr>');
}); });
h.push('</tbody></table><p class="sublede" style="margin-top:12px">'+all.length+' shown.</p>'); h.push('</tbody></table><p class="sublede" style="margin-top:12px">'+all.length+' shown.</p>');
return h.join(""); return h.join("");
+519
View File
@@ -0,0 +1,519 @@
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix pr: <https://ontology.pernod-ricard.com/metamodel/> .
@prefix prs: <https://ontology.pernod-ricard.com/metamodel/shapes/> .
#################################################################
# PR DATA META MODEL - SHACL SHAPES v0.2
# Aligned with T-Box v1.1 and Rulebook v0.3.x
#
# WHAT CHANGED vs v0.1.1
# ----------------------
# 1. SPLIT BY PROVENANCE. v0.1.1 targeted pr:MetaModelObject and demanded an
# identifier, a name, an owner, an owning domain, a status and a version on
# everything. Applied to the captured half of the graph that would fail on
# every one of ~50 000 Snowflake columns. Defined and Captured objects now
# have separate shapes with separate expectations.
#
# 2. SCOPE FOR THE UNIFIED RELATIONS. belongsTo and represents deliberately
# carry no rdfs:domain/range in the T-Box, because each serves two classes
# with different targets. The scope lives here instead, per class.
#
# 3. MEANING IS AN XOR, NOT A CHECKLIST. A Data Element reaches business
# meaning by exactly one route. sh:xone rejects both zero routes and two.
#
# 4. NO COVERAGE RULES. Nothing here requires a Business Concept to be
# realized, or a Business Object to hold N concepts. That was BR-004, and
# encoding a coverage expectation as a blocking constraint does not produce
# coverage -- it produces padding. Coverage is a dashboard metric.
#
# 5. PORTABLE REGEX. No lookbehind: sh:pattern is normatively XPath 2.0
# regex, which does not support it. Written as sh:not [ sh:pattern ... ].
#
# SEVERITY MAPPING
# sh:Violation -> BLOCKING, cannot reach PUBLISHED, fails CI (DQ-011)
# sh:Warning -> MAJOR, must be reviewed, does not block
# sh:Info -> GUIDELINE, informational
#################################################################
<https://ontology.pernod-ricard.com/metamodel/shapes/>
dcterms:title "PR Data Meta Model - SHACL Shapes" ;
dcterms:modified "2026-07-30"^^xsd:date ;
rdfs:comment "Executable form of the rulebook. Validated by scripts/run_shacl_validation.py." .
#################################################################
# IDENTITY AND LIFECYCLE -- defined objects only
#################################################################
prs:DefinedObjectShape a sh:NodeShape ;
sh:targetClass pr:DefinedObject ;
rdfs:comment "Applies to everything governance writes. Captured objects are deliberately exempt: see CapturedObjectShape." ;
sh:property [
sh:path pr:identifier ; sh:datatype xsd:string ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ;
sh:message "DQ-001 -- every defined object carries exactly one identifier. NOTE: uniqueness across the graph is NOT tested here; SHACL checks cardinality only. The reconciliation job tests uniqueness." ] ;
sh:property [
sh:path pr:name ; sh:datatype xsd:string ;
sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 3 ;
sh:severity sh:Violation ;
sh:message "Every defined object carries exactly one canonical name." ] ;
sh:property [
sh:path pr:status ; sh:minCount 1 ;
sh:in ( "DRAFT" "UNDER_REVIEW" "PUBLISHED" "DEPRECATED" "RETIRED" ) ;
sh:severity sh:Violation ;
sh:message "LC-001 -- status must be one of DRAFT, UNDER_REVIEW, PUBLISHED, DEPRECATED, RETIRED." ] ;
sh:property [
sh:path pr:owningDomain ; sh:maxCount 1 ;
sh:severity sh:Violation ;
sh:message "OW-001 -- at most one owning Data Domain. On a Data Element this may be re-declared to differ from the parent chain: that marks a borrowed element." ] ;
sh:property [
sh:path pr:version ; sh:maxCount 1 ;
sh:severity sh:Warning ;
sh:message "A published object should carry a version." ] .
prs:PublishedShape a sh:NodeShape ;
sh:targetClass pr:DefinedObject ;
rdfs:comment "Extra demands that only bite once an object claims to be PUBLISHED. Keeping these off the base shape is what lets a domain be documented progressively." ;
sh:sparql [
sh:severity sh:Violation ;
sh:message "OW-007 -- an object flagged TO_ARBITRATE cannot be PUBLISHED. Resolve at the Cross-Domain Council first." ;
sh:select """
PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/>
SELECT $this WHERE {
$this pr:status "PUBLISHED" ;
pr:arbitrationStatus "TO_ARBITRATE" .
}""" ] ;
sh:sparql [
sh:severity sh:Violation ;
sh:message "OW-003 -- a PUBLISHED object must have an owning Data Domain." ;
sh:select """
PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/>
SELECT $this WHERE {
$this pr:status "PUBLISHED" .
FILTER NOT EXISTS { $this pr:owningDomain ?d }
}""" ] .
#################################################################
# OBSERVED OBJECTS -- captured, never hand-edited
#################################################################
prs:CapturedObjectShape a sh:NodeShape ;
sh:targetClass pr:CapturedObject ;
rdfs:comment "No identifier, no name, no owner, no status, no version. A harvested column is attributed through the Data Element mapped to it, never asserted directly. What it must carry is enough to be re-found on the next run." ;
sh:property [
sh:path pr:sourceIdentifier ; sh:datatype xsd:string ;
sh:maxCount 1 ;
sh:severity sh:Warning ;
sh:message "A native immutable source id makes reconciliation idempotent and survives renames, which names do not." ] ;
sh:property [
sh:path pr:harvestedOn ; sh:datatype xsd:dateTime ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ;
sh:message "Every captured object records when it was harvested. Without it there is no way to tell a current fact from a stale one." ] ;
sh:property [
sh:path pr:owningDomain ; sh:maxCount 0 ;
sh:severity sh:Warning ;
sh:message "Captured objects are not attributed directly -- ownership flows from the Data Element mapped to them." ] .
# inSchema is deliberately NOT required anywhere. Neo4j has no schema and
# BigQuery calls the level a dataset; a minCount 1 here would break on the
# first harvest outside Snowflake.
prs:DataStructureShape a sh:NodeShape ;
sh:targetClass pr:DataStructure ;
sh:property [
sh:path pr:inSchema ; sh:maxCount 1 ;
sh:severity sh:Info ;
sh:message "Optional on purpose: not every engine has a schema level." ] ;
sh:property [
sh:path pr:sourcedFrom ; sh:maxCount 1 ;
sh:severity sh:Info ] .
prs:FieldShape a sh:NodeShape ;
sh:targetClass pr:Field ;
sh:property [
sh:path pr:fullyQualifiedName ; sh:datatype xsd:string ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:pattern "^[A-Za-z0-9_$]+\\\\.[A-Za-z0-9_$]+\\\\.[A-Za-z0-9_$]+\\\\.[A-Za-z0-9_$]+$" ;
sh:severity sh:Violation ;
sh:message "A Field carries DATABASE.SCHEMA.RELATION.COLUMN. A bare column name is ambiguous as soon as it exists in two tables, so the FQN is the real join key against INFORMATION_SCHEMA." ] ;
sh:property [
sh:path pr:livesIn ; sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ;
sh:message "Every Field lives in exactly one Physical Relation." ] .
#################################################################
# CATEGORIZATION TREE -- scope of the unified belongsTo
#################################################################
prs:SubDomainShape a sh:NodeShape ;
sh:targetClass pr:SubDomain ;
sh:property [
sh:path pr:belongsTo ; sh:class pr:DataDomain ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ;
sh:message "A Sub-Domain belongs to exactly one Data Domain." ] ;
sh:property [
sh:path pr:hasSubDomainOwner ; sh:maxCount 1 ;
sh:severity sh:Warning ;
sh:message "A Sub-Domain should name its owner." ] .
prs:DataDomainShape a sh:NodeShape ;
sh:targetClass pr:DataDomain ;
sh:property [
sh:path pr:hasDomainOwner ; sh:maxCount 1 ;
sh:severity sh:Warning ;
sh:message "A Data Domain should name its owner." ] ;
sh:property [
sh:path pr:hasDGL ; sh:maxCount 1 ;
sh:severity sh:Warning ;
sh:message "A Data Domain should name its Data Governance Lead." ] ;
sh:property [
sh:path pr:hasActivationStatus ; sh:maxCount 1 ;
sh:severity sh:Info ] .
#################################################################
# BUSINESS OBJECT -- subject, perimeter, stewardship
#################################################################
prs:BusinessObjectShape a sh:NodeShape ;
sh:targetClass pr:BusinessObject ;
sh:property [
sh:path pr:belongsTo ; sh:class pr:SubDomain ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ;
sh:message "A Business Object belongs to exactly one Sub-Domain." ] ;
sh:property [
sh:path pr:aboutConcept ; sh:class pr:BusinessConcept ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ;
sh:message "BR-004 -- a Business Object is about exactly one Business Concept. Replaces the old 3..10 minimum, which was satisfied in v0.6 by attaching unrelated concepts. If the subject cannot be named, the object is not scoped." ] ;
sh:property [
sh:path pr:usesConcept ; sh:class pr:BusinessConcept ;
sh:severity sh:Info ;
sh:message "Other notions needed to read this object. Zero is a legitimate answer." ] ;
sh:property [
sh:path pr:monitoredBy ; sh:class pr:DataSteward ;
sh:minCount 1 ;
sh:severity sh:Warning ;
sh:message "The Business Object is where a steward is declared -- Data Objects and Data Elements inherit from here." ] ;
sh:property [
sh:path pr:businessDefinition ; sh:maxCount 0 ;
sh:severity sh:Violation ;
sh:message "A Business Object carries no definition of its own: meaning belongs to the Business Concept named by aboutConcept. Two definitions for one notion are two definitions free to diverge. MIGRATION: move the text to that Concept." ] ;
sh:property [
sh:path pr:sourcedFrom ; sh:maxCount 0 ;
sh:severity sh:Violation ;
sh:message "A Business Object has no system of truth. It is a governance grouping, not data: its data lives in the Data Objects that represent it, and those name the system. In v0.6 this property held a grab-bag on the 15 objects -- physical locations, migration notes, arbitration remarks -- all under one name." ] .
#################################################################
# BUSINESS CONCEPT AND METRIC
#################################################################
prs:BusinessConceptShape a sh:NodeShape ;
sh:targetClass pr:BusinessConcept ;
rdfs:comment "Note what is NOT here: no requirement that a Concept be realized by any Data Element or measured by any Metric. The business layer is allowed to run ahead of the data. Coverage is a dashboard metric, not a rule." ;
sh:property [
sh:path pr:businessDefinition ; sh:datatype xsd:string ;
sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 20 ;
sh:severity sh:Violation ;
sh:message "A Business Concept exists to carry meaning: it must hold exactly one business definition of at least 20 characters." ] ;
sh:property [
sh:path pr:formula ; 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:property [
sh:path pr:sourcedFrom ; sh:maxCount 0 ;
sh:severity sh:Violation ;
sh:message "A Business Concept has no system of truth. Its authority is its owning domain and its definition belongs in the glossary. Naming a system here would ask which of the systems that USE the concept defines it -- a question with no answer." ] .
prs:MetricShape a sh:NodeShape ;
sh:targetClass pr:Metric ;
sh:property [
sh:path pr:formula ; sh:datatype xsd:string ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ;
sh:message "BR-003 -- a Metric is a measurement with a calculation rule. No formula, no Metric." ] ;
sh:property [
sh:path pr:unit ; sh:minCount 1 ;
sh:severity sh:Violation ;
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 ;
sh:minCount 1 ;
sh:severity sh:Violation ;
sh:message "A Metric measures at least one Business Concept -- this is how a measure element reaches business meaning." ] ;
sh:property [
sh:path pr:hasGranularity ; sh:maxCount 0 ;
sh:severity sh:Warning ;
sh:message "Grain moved off the Metric in v1.1. A Metric is a calculation rule and has no rows; grain belongs to the Data Object, stated with hasGrainElement and checkable against the real primary key." ] ;
sh:property [
sh:path pr:physicalName ; sh:maxCount 0 ;
sh:severity sh:Violation ;
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 ;
rdfs:comment "Rewritten in v1.4 to compare System references rather than strings. A string comparison would have called 'SODH Gold' and 'Snowflake (SODH Gold)' two different systems." ;
sh:sparql [
sh:severity sh:Violation ;
sh:message "A Metric's system of truth must match that of at least one element computing it. If they disagree, either the metric points at the wrong system or an element has moved." ;
sh:select """
PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/>
SELECT $this WHERE {
$this pr:sourcedFrom ?sys ; pr:computedBy ?de .
FILTER NOT EXISTS {
$this pr:computedBy ?any .
?any pr:sourcedFrom ?sys .
}
}""" ] ;
sh:sparql [
sh:severity sh:Warning ;
sh:message "Elements computing one Metric come from several systems. Legitimate for a metric blending sources, worth a look otherwise." ;
sh:select """
PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/>
SELECT $this WHERE {
$this pr:computedBy ?a , ?b .
?a pr:sourcedFrom ?sa .
?b pr:sourcedFrom ?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 ;
sh:sparql [
sh:severity sh:Warning ;
sh:message "BR-013 -- a PUBLISHED Metric should be implemented by at least one Data Element through computedBy. A metric no element computes is a definition with nothing behind it." ;
sh:select """
PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/>
SELECT $this WHERE {
$this pr:status "PUBLISHED" .
FILTER NOT EXISTS { $this pr:computedBy ?de }
}""" ] .
#################################################################
# DATA OBJECT -- the pivot, and grain
#################################################################
prs:DataObjectShape a sh:NodeShape ;
sh:targetClass pr:DataObject ;
sh:property [
sh:path pr:represents ; sh:class pr:BusinessObject ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ;
sh:message "BR-006 -- a Data Object represents exactly one Business Object. This is the pivot: the single junction between governance and technology." ] ;
sh:property [
sh:path pr:hasElement ; sh:class pr:DataElement ;
sh:minCount 1 ;
sh:severity sh:Violation ;
sh:message "A Data Object holds at least one Data Element." ] ;
sh:property [
sh:path pr:hasGrainElement ; sh:class pr:DataElement ;
sh:severity sh:Warning ;
sh:message "State the grain: the elements whose combination makes a row unique. Checkable against the harvested primary key." ] ;
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:property [
sh:path pr:sourcedFrom ; sh:class pr:System ; sh:maxCount 1 ;
sh:severity sh:Warning ;
sh:message "Name the System holding the authoritative data, as a reference rather than a string. A string could not answer 'what depends on MDM Product'." ] ;
sh:property [
sh:path pr:storedIn ; sh:class pr:DataStructure ;
sh:severity sh:Warning ;
sh:message "Where the object actually sits: a table, a view, later a node label. The seam between what governance defines and what a harvester captures -- proposed by the reconciliation job, confirmed by a steward." ] ;
sh:property [
sh:path pr:materializedAs ; sh:maxCount 0 ;
sh:severity sh:Warning ;
sh:message "Deprecated in v1.4: storedIn now serves both granularities." ] .
# A declared grain element must actually belong to the object it grains.
prs:GrainConsistencyShape a sh:NodeShape ;
sh:targetClass pr:DataObject ;
sh:sparql [
sh:severity sh:Violation ;
sh:message "A grain element must be one of the object's own Data Elements." ;
sh:select """
PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/>
SELECT $this ?grain WHERE {
$this pr:hasGrainElement ?grain .
FILTER NOT EXISTS { $this pr:hasElement ?grain }
}""" ] .
#################################################################
# DATA ELEMENT -- the XOR of meaning
#################################################################
prs:DataElementShape a sh:NodeShape ;
sh:targetClass pr:DataElement ;
sh:property [
sh:path pr:physicalName ; sh:datatype xsd:string ;
sh:maxCount 1 ;
sh:severity sh:Warning ;
sh:message "NR-009 -- the physical name is recorded verbatim and never altered." ] ;
sh:property [
sh:path pr:fullyQualifiedName ; sh:maxCount 1 ;
sh:severity sh:Info ] ;
sh:property [
sh:path pr:unit ; 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 ;
sh:severity sh:Violation ;
sh:message "A Data Element represents at most one Business Concept. If a column carries two business notions, it should almost certainly be split -- so the case where a steward cannot choose is exactly the one worth looking at." ] ;
sh:property [
sh:path pr:monitoredBy ; sh:maxCount 0 ;
sh:severity sh:Warning ;
sh:message "Stewardship is inherited from the Business Object, not declared here." ] .
# The core rule of v1.1.
prs:DataElementMeaningShape a sh:NodeShape ;
sh:targetClass pr:DataElement ;
rdfs:comment "A Data Element reaches business meaning by exactly one of two routes: it represents a Business Concept (dimensional), or a Metric is computedBy it (measure). sh:xone rejects zero routes and two alike. A measure element must NOT also represent a Concept: the chain Metric -> measures -> Concept already carries the meaning, and carries more, since it names which metric it travels through." ;
sh:xone (
[ sh:property [ sh:path pr:represents ;
sh:minCount 1 ; sh:maxCount 1 ] ]
[ sh:property [ sh:path [ sh:inversePath pr:computedBy ] ;
sh:minCount 1 ] ]
) ;
sh:severity sh:Violation ;
sh:message "A Data Element must reach business meaning by exactly one route: represents a Business Concept (dimensional), or be computedBy a Metric (measure). Never both, never neither. A defined column with no business meaning is debt, not an acceptable state." .
#################################################################
# DELIVERY
#################################################################
prs:DataProductShape a sh:NodeShape ;
sh:targetClass pr:DataProduct ;
sh:property [
sh:path pr:operatedBy ; sh:class pr:SubDomain ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ;
sh:message "A Data Product is operated by exactly one Sub-Domain. It spans several Business Objects and cannot inherit accountability, so it declares its own -- it is a consumable, and there must be no ambiguity about who runs it." ] ;
sh:property [
sh:path pr:hasProductOwner ; sh:class pr:DataProductOwner ;
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 ;
sh:minCount 1 ;
sh:severity sh:Violation ;
sh:message "A Data Product packages at least one Data Object. The Business Objects it makes available are derived from these, which is why no direct link to a Business Object is needed." ] ;
sh:property [
sh:path pr:governedBy ; sh:maxCount 1 ;
sh:severity sh:Warning ;
sh:message "A Data Product should be governed by a Data Contract." ] ;
sh:property [
sh:path pr:exposes ; sh:class pr:DataInterface ;
sh:severity sh:Info ;
sh:message "The channels through which consumers actually get the data." ] .
prs:DataInterfaceShape a sh:NodeShape ;
sh:targetClass pr:DataInterface ;
sh:property [
sh:path pr:servedBy ; sh:minCount 1 ;
sh:severity sh:Warning ;
sh:message "An interface should name what physically backs it. Unlike the old Data Asset test, this is verifiable: a grant, a share or an endpoint either exists or it does not." ] .
#################################################################
# CONSUMPTION
#################################################################
prs:BIFieldShape a sh:NodeShape ;
sh:targetClass pr:BIField ;
rdfs:comment "realizesMetric is a transitional control: asserted directly, with no rule written against the lineage chain. Making the assertion conditional on intermediate mappings existing would create confusion and discourage people from filling it in at all." ;
sh:property [
sh:path pr:exposesMetric ; sh:class pr:Metric ;
sh:maxCount 1 ;
sh:severity sh:Info ;
sh:message "Which certified Metric this BI field implements. Comparing expressions across fields realizing one Metric is the harmonisation measure." ] .
#################################################################
# NAMING -- portable regex, no lookbehind
#################################################################
prs:NamingShape a sh:NodeShape ;
sh:targetClass pr:DefinedObject ;
sh:property [
sh:path pr:name ;
sh:not [ sh:pattern "_NM$" ] ;
sh:severity sh:Warning ;
sh:message "NR-001 -- a canonical name must not end in a physical suffix. Written as sh:not[sh:pattern] rather than a lookbehind: sh:pattern is normatively XPath 2.0 regex, which has no lookbehind, so the v0.1.1 form only worked under pySHACL." ] ;
sh:property [
sh:path pr:name ;
sh:not [ sh:pattern "^[a-z_]+$" ] ;
sh:severity sh:Warning ;
sh:message "NR-002 -- a canonical name is written for humans, not as a snake_case identifier." ] .
+15 -15
View File
@@ -53,19 +53,19 @@ prs:DefinedObjectShape a sh:NodeShape ;
rdfs:comment "Applies to everything governance writes. Captured objects are deliberately exempt: see CapturedObjectShape." ; rdfs:comment "Applies to everything governance writes. Captured objects are deliberately exempt: see CapturedObjectShape." ;
sh:property [ sh:property [
sh:path pr:hasIdentifier ; sh:datatype xsd:string ; sh:path pr:identifier ; sh:datatype xsd:string ;
sh:minCount 1 ; sh:maxCount 1 ; sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ; sh:severity sh:Violation ;
sh:message "DQ-001 -- every defined object carries exactly one identifier. NOTE: uniqueness across the graph is NOT tested here; SHACL checks cardinality only. The reconciliation job tests uniqueness." ] ; sh:message "DQ-001 -- every defined object carries exactly one identifier. NOTE: uniqueness across the graph is NOT tested here; SHACL checks cardinality only. The reconciliation job tests uniqueness." ] ;
sh:property [ sh:property [
sh:path pr:hasName ; sh:datatype xsd:string ; sh:path pr:name ; sh:datatype xsd:string ;
sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 3 ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 3 ;
sh:severity sh:Violation ; sh:severity sh:Violation ;
sh:message "Every defined object carries exactly one canonical name." ] ; sh:message "Every defined object carries exactly one canonical name." ] ;
sh:property [ sh:property [
sh:path pr:hasStatus ; sh:minCount 1 ; sh:path pr:status ; sh:minCount 1 ;
sh:in ( "DRAFT" "UNDER_REVIEW" "PUBLISHED" "DEPRECATED" "RETIRED" ) ; sh:in ( "DRAFT" "UNDER_REVIEW" "PUBLISHED" "DEPRECATED" "RETIRED" ) ;
sh:severity sh:Violation ; sh:severity sh:Violation ;
sh:message "LC-001 -- status must be one of DRAFT, UNDER_REVIEW, PUBLISHED, DEPRECATED, RETIRED." ] ; sh:message "LC-001 -- status must be one of DRAFT, UNDER_REVIEW, PUBLISHED, DEPRECATED, RETIRED." ] ;
@@ -76,7 +76,7 @@ prs:DefinedObjectShape a sh:NodeShape ;
sh:message "OW-001 -- at most one owning Data Domain. On a Data Element this may be re-declared to differ from the parent chain: that marks a borrowed element." ] ; sh:message "OW-001 -- at most one owning Data Domain. On a Data Element this may be re-declared to differ from the parent chain: that marks a borrowed element." ] ;
sh:property [ sh:property [
sh:path pr:hasVersion ; sh:maxCount 1 ; sh:path pr:version ; sh:maxCount 1 ;
sh:severity sh:Warning ; sh:severity sh:Warning ;
sh:message "A published object should carry a version." ] . sh:message "A published object should carry a version." ] .
@@ -91,7 +91,7 @@ prs:PublishedShape a sh:NodeShape ;
sh:select """ sh:select """
PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/> PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/>
SELECT $this WHERE { SELECT $this WHERE {
$this pr:hasStatus "PUBLISHED" ; $this pr:status "PUBLISHED" ;
pr:arbitrationStatus "TO_ARBITRATE" . pr:arbitrationStatus "TO_ARBITRATE" .
}""" ] ; }""" ] ;
@@ -101,7 +101,7 @@ prs:PublishedShape a sh:NodeShape ;
sh:select """ sh:select """
PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/> PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/>
SELECT $this WHERE { SELECT $this WHERE {
$this pr:hasStatus "PUBLISHED" . $this pr:status "PUBLISHED" .
FILTER NOT EXISTS { $this pr:owningDomain ?d } FILTER NOT EXISTS { $this pr:owningDomain ?d }
}""" ] . }""" ] .
@@ -223,7 +223,7 @@ prs:BusinessObjectShape a sh:NodeShape ;
sh:message "The Business Object is where a steward is declared -- Data Objects and Data Elements inherit from here." ] ; sh:message "The Business Object is where a steward is declared -- Data Objects and Data Elements inherit from here." ] ;
sh:property [ sh:property [
sh:path pr:hasBusinessDefinition ; sh:maxCount 0 ; sh:path pr:businessDefinition ; sh:maxCount 0 ;
sh:severity sh:Violation ; sh:severity sh:Violation ;
sh:message "A Business Object carries no definition of its own: meaning belongs to the Business Concept named by aboutConcept. Two definitions for one notion are two definitions free to diverge. MIGRATION: move the text to that Concept." ] ; sh:message "A Business Object carries no definition of its own: meaning belongs to the Business Concept named by aboutConcept. Two definitions for one notion are two definitions free to diverge. MIGRATION: move the text to that Concept." ] ;
@@ -242,13 +242,13 @@ prs:BusinessConceptShape a sh:NodeShape ;
rdfs:comment "Note what is NOT here: no requirement that a Concept be realized by any Data Element or measured by any Metric. The business layer is allowed to run ahead of the data. Coverage is a dashboard metric, not a rule." ; rdfs:comment "Note what is NOT here: no requirement that a Concept be realized by any Data Element or measured by any Metric. The business layer is allowed to run ahead of the data. Coverage is a dashboard metric, not a rule." ;
sh:property [ sh:property [
sh:path pr:hasBusinessDefinition ; sh:datatype xsd:string ; sh:path pr:businessDefinition ; sh:datatype xsd:string ;
sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 20 ; sh:minCount 1 ; sh:maxCount 1 ; sh:minLength 20 ;
sh:severity sh:Violation ; sh:severity sh:Violation ;
sh:message "A Business Concept exists to carry meaning: it must hold exactly one business definition of at least 20 characters." ] ; sh:message "A Business Concept exists to carry meaning: it must hold exactly one business definition of at least 20 characters." ] ;
sh:property [ sh:property [
sh:path pr:hasFormula ; sh:maxCount 0 ; sh:path pr:formula ; sh:maxCount 0 ;
sh:severity sh:Violation ; 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." ] ;
@@ -262,13 +262,13 @@ prs:MetricShape a sh:NodeShape ;
sh:targetClass pr:Metric ; sh:targetClass pr:Metric ;
sh:property [ sh:property [
sh:path pr:hasFormula ; sh:datatype xsd:string ; sh:path pr:formula ; sh:datatype xsd:string ;
sh:minCount 1 ; sh:maxCount 1 ; sh:minCount 1 ; sh:maxCount 1 ;
sh:severity sh:Violation ; sh:severity sh:Violation ;
sh:message "BR-003 -- a Metric is a measurement with a calculation rule. No formula, no Metric." ] ; sh:message "BR-003 -- a Metric is a measurement with a calculation rule. No formula, no Metric." ] ;
sh:property [ sh:property [
sh:path pr:hasUnit ; sh:minCount 1 ; sh:path pr:unit ; sh:minCount 1 ;
sh:severity sh:Violation ; sh:severity sh:Violation ;
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: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." ] ;
@@ -331,7 +331,7 @@ prs:MotherMetricShape a sh:NodeShape ;
sh:select """ sh:select """
PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/> PREFIX pr: <https://ontology.pernod-ricard.com/metamodel/>
SELECT $this WHERE { SELECT $this WHERE {
$this pr:hasStatus "PUBLISHED" . $this pr:status "PUBLISHED" .
FILTER NOT EXISTS { $this pr:computedBy ?de } FILTER NOT EXISTS { $this pr:computedBy ?de }
}""" ] . }""" ] .
@@ -413,7 +413,7 @@ prs:DataElementShape a sh:NodeShape ;
sh:severity sh:Info ] ; sh:severity sh:Info ] ;
sh:property [ sh:property [
sh:path pr:hasUnit ; sh:maxCount 1 ; sh:path pr:unit ; sh:maxCount 1 ;
sh:severity sh:Violation ; 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: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." ] ;
@@ -508,12 +508,12 @@ prs:BIFieldShape a sh:NodeShape ;
prs:NamingShape a sh:NodeShape ; prs:NamingShape a sh:NodeShape ;
sh:targetClass pr:DefinedObject ; sh:targetClass pr:DefinedObject ;
sh:property [ sh:property [
sh:path pr:hasName ; sh:path pr:name ;
sh:not [ sh:pattern "_NM$" ] ; sh:not [ sh:pattern "_NM$" ] ;
sh:severity sh:Warning ; sh:severity sh:Warning ;
sh:message "NR-001 -- a canonical name must not end in a physical suffix. Written as sh:not[sh:pattern] rather than a lookbehind: sh:pattern is normatively XPath 2.0 regex, which has no lookbehind, so the v0.1.1 form only worked under pySHACL." ] ; sh:message "NR-001 -- a canonical name must not end in a physical suffix. Written as sh:not[sh:pattern] rather than a lookbehind: sh:pattern is normatively XPath 2.0 regex, which has no lookbehind, so the v0.1.1 form only worked under pySHACL." ] ;
sh:property [ sh:property [
sh:path pr:hasName ; sh:path pr:name ;
sh:not [ sh:pattern "^[a-z_]+$" ] ; sh:not [ sh:pattern "^[a-z_]+$" ] ;
sh:severity sh:Warning ; sh:severity sh:Warning ;
sh:message "NR-002 -- a canonical name is written for humans, not as a snake_case identifier." ] . sh:message "NR-002 -- a canonical name is written for humans, not as a snake_case identifier." ] .