tbox: v2.0 - 47 renommages de vocabulaire, alignement T-Box/A-Box sur les attributs transverses, GovernanceLayerObject inseree au-dessus d'Actor, isAbstract declare sur les 44 classes, ActivationStatus et Environment fermes en litteraux, labels derives de l'IRI, conformsTo pose sur les shapes, requetes SPARQL embarquees reecrites. Validation SHACL sans violation bloquante, rejeu a zero modification. Viewer T-Box regenere mais encore porteur d'IRI en dur, cf backlog
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"attempt_timestamp": "2026-08-05T09:31:13",
|
||||
"mode": "dry-run",
|
||||
"from_version": "1.6",
|
||||
"to_version": "2.0",
|
||||
"input_checksums": {
|
||||
"ontology/pr_metamodel.ttl": "552bbe0e9c0883d869ac5d6de40503bb",
|
||||
"instances/sodh.ttl": "9eb1dbb96e325dbd61c6e880b43eaa44",
|
||||
"shapes/pr_metamodel_shapes.ttl": "f6a1b4681e5817f8852c81424312e3d9"
|
||||
},
|
||||
"steps": {
|
||||
"deprecated_dropped": 119,
|
||||
"deprecated_kept": 2,
|
||||
"rename_order_1": 244,
|
||||
"rename_order_2": 256,
|
||||
"rename_order_3": 14,
|
||||
"merge": 5,
|
||||
"reclassified": 2,
|
||||
"classes_created": 1,
|
||||
"reparented": 1,
|
||||
"to_literal_property": 2,
|
||||
"to_literal_values": 6,
|
||||
"to_literal_dropped": 18,
|
||||
"isAbstract_declared": 31,
|
||||
"display_removed": 22,
|
||||
"display_set": 25,
|
||||
"labels_regenerated": 78,
|
||||
"version_bumped": 1
|
||||
},
|
||||
"total_changes": 827,
|
||||
"triples_before": {
|
||||
"ontology/pr_metamodel.ttl": 853,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 395
|
||||
},
|
||||
"triples_after": {
|
||||
"ontology/pr_metamodel.ttl": 734,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 395
|
||||
},
|
||||
"notes": [
|
||||
"deprecated_kept: materializedAs still used 1 times",
|
||||
"deprecated_kept: hasGranularity still used 1 times",
|
||||
"reclassified: shortLabel",
|
||||
"reclassified: acronym",
|
||||
"classes_created: GovernanceLayerObject",
|
||||
"reparented: Actor -> GovernanceLayerObject",
|
||||
"to_literal_property: activationStatus",
|
||||
"to_literal_property: environment",
|
||||
"version_bumped: https://ontology.pernod-ricard.com/metamodel/2.0"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"attempt_timestamp": "2026-08-05T14:11:08",
|
||||
"mode": "dry-run",
|
||||
"from_version": "1.6",
|
||||
"to_version": "2.0",
|
||||
"input_checksums": {
|
||||
"ontology/pr_metamodel.ttl": "552bbe0e9c0883d869ac5d6de40503bb",
|
||||
"instances/sodh.ttl": "9eb1dbb96e325dbd61c6e880b43eaa44",
|
||||
"shapes/pr_metamodel_shapes.ttl": "f6a1b4681e5817f8852c81424312e3d9"
|
||||
},
|
||||
"steps": {
|
||||
"deprecated_dropped": 132,
|
||||
"rename_order_1": 244,
|
||||
"rename_order_2": 256,
|
||||
"rename_order_3": 14,
|
||||
"abox_aligned": 353,
|
||||
"merge": 5,
|
||||
"reclassified": 2,
|
||||
"classes_created": 1,
|
||||
"reparented": 1,
|
||||
"to_literal_property": 2,
|
||||
"to_literal_values": 6,
|
||||
"to_literal_dropped": 18,
|
||||
"isAbstract_declared": 31,
|
||||
"display_removed": 22,
|
||||
"display_set": 25,
|
||||
"labels_regenerated": 76,
|
||||
"version_bumped": 1,
|
||||
"shapes_conformsTo": 1
|
||||
},
|
||||
"total_changes": 1190,
|
||||
"triples_before": {
|
||||
"ontology/pr_metamodel.ttl": 853,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 395
|
||||
},
|
||||
"triples_after": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 396
|
||||
},
|
||||
"notes": [
|
||||
"reclassified: shortLabel",
|
||||
"reclassified: acronym",
|
||||
"classes_created: GovernanceLayerObject",
|
||||
"reparented: Actor -> GovernanceLayerObject",
|
||||
"to_literal_property: activationStatus",
|
||||
"to_literal_property: environment",
|
||||
"version_bumped: https://ontology.pernod-ricard.com/metamodel/2.0",
|
||||
"shapes_conformsTo: https://ontology.pernod-ricard.com/metamodel/2.0"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"attempt_timestamp": "2026-08-05T14:14:31",
|
||||
"mode": "apply",
|
||||
"from_version": "1.6",
|
||||
"to_version": "2.0",
|
||||
"input_checksums": {
|
||||
"ontology/pr_metamodel.ttl": "552bbe0e9c0883d869ac5d6de40503bb",
|
||||
"instances/sodh.ttl": "9eb1dbb96e325dbd61c6e880b43eaa44",
|
||||
"shapes/pr_metamodel_shapes.ttl": "f6a1b4681e5817f8852c81424312e3d9"
|
||||
},
|
||||
"steps": {
|
||||
"deprecated_dropped": 132,
|
||||
"rename_order_1": 244,
|
||||
"rename_order_2": 256,
|
||||
"rename_order_3": 14,
|
||||
"abox_aligned": 353,
|
||||
"merge": 5,
|
||||
"reclassified": 2,
|
||||
"classes_created": 1,
|
||||
"reparented": 1,
|
||||
"to_literal_property": 2,
|
||||
"to_literal_values": 6,
|
||||
"to_literal_dropped": 18,
|
||||
"isAbstract_declared": 31,
|
||||
"display_removed": 22,
|
||||
"display_set": 25,
|
||||
"labels_regenerated": 76,
|
||||
"version_bumped": 1,
|
||||
"shapes_conformsTo": 1
|
||||
},
|
||||
"total_changes": 1190,
|
||||
"triples_before": {
|
||||
"ontology/pr_metamodel.ttl": 853,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 395
|
||||
},
|
||||
"triples_after": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 396
|
||||
},
|
||||
"notes": [
|
||||
"reclassified: shortLabel",
|
||||
"reclassified: acronym",
|
||||
"classes_created: GovernanceLayerObject",
|
||||
"reparented: Actor -> GovernanceLayerObject",
|
||||
"to_literal_property: activationStatus",
|
||||
"to_literal_property: environment",
|
||||
"version_bumped: https://ontology.pernod-ricard.com/metamodel/2.0",
|
||||
"shapes_conformsTo: https://ontology.pernod-ricard.com/metamodel/2.0"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"attempt_timestamp": "2026-08-05T14:14:43",
|
||||
"mode": "dry-run",
|
||||
"from_version": "1.6",
|
||||
"to_version": "2.0",
|
||||
"input_checksums": {
|
||||
"ontology/pr_metamodel.ttl": "32929c506d402738807b8b30c04fbb48",
|
||||
"instances/sodh.ttl": "6bef7bad65c091738cd58cf5f0f1a395",
|
||||
"shapes/pr_metamodel_shapes.ttl": "f87572aa372d4f6944c21e3cb12fb164"
|
||||
},
|
||||
"steps": {
|
||||
"rename_order_1": 0,
|
||||
"rename_order_2": 0,
|
||||
"rename_order_3": 0,
|
||||
"abox_aligned": 0,
|
||||
"merge": 0
|
||||
},
|
||||
"total_changes": 0,
|
||||
"triples_before": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 396
|
||||
},
|
||||
"triples_after": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 396
|
||||
},
|
||||
"notes": []
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"attempt_timestamp": "2026-08-05T14:17:11",
|
||||
"mode": "dry-run",
|
||||
"from_version": "1.6",
|
||||
"to_version": "2.0",
|
||||
"input_checksums": {
|
||||
"ontology/pr_metamodel.ttl": "32929c506d402738807b8b30c04fbb48",
|
||||
"instances/sodh.ttl": "6bef7bad65c091738cd58cf5f0f1a395",
|
||||
"shapes/pr_metamodel_shapes.ttl": "f6a1b4681e5817f8852c81424312e3d9"
|
||||
},
|
||||
"steps": {
|
||||
"rename_order_1": 9,
|
||||
"rename_order_2": 2,
|
||||
"rename_order_3": 0,
|
||||
"abox_aligned": 0,
|
||||
"merge": 0,
|
||||
"shapes_conformsTo": 1
|
||||
},
|
||||
"total_changes": 12,
|
||||
"triples_before": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 395
|
||||
},
|
||||
"triples_after": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 398
|
||||
},
|
||||
"notes": [
|
||||
"shapes_conformsTo: https://ontology.pernod-ricard.com/metamodel/2.0"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"attempt_timestamp": "2026-08-05T14:18:45",
|
||||
"mode": "apply",
|
||||
"from_version": "1.6",
|
||||
"to_version": "2.0",
|
||||
"input_checksums": {
|
||||
"ontology/pr_metamodel.ttl": "32929c506d402738807b8b30c04fbb48",
|
||||
"instances/sodh.ttl": "6bef7bad65c091738cd58cf5f0f1a395",
|
||||
"shapes/pr_metamodel_shapes.ttl": "f6a1b4681e5817f8852c81424312e3d9"
|
||||
},
|
||||
"steps": {
|
||||
"rename_order_1": 9,
|
||||
"rename_order_2": 2,
|
||||
"rename_order_3": 0,
|
||||
"abox_aligned": 0,
|
||||
"merge": 0,
|
||||
"shapes_conformsTo": 1
|
||||
},
|
||||
"total_changes": 12,
|
||||
"triples_before": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 395
|
||||
},
|
||||
"triples_after": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 398
|
||||
},
|
||||
"notes": [
|
||||
"shapes_conformsTo: https://ontology.pernod-ricard.com/metamodel/2.0"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"attempt_timestamp": "2026-08-05T14:18:59",
|
||||
"mode": "dry-run",
|
||||
"from_version": "1.6",
|
||||
"to_version": "2.0",
|
||||
"input_checksums": {
|
||||
"ontology/pr_metamodel.ttl": "ab4100d1eb8f0e16231339f675f80797",
|
||||
"instances/sodh.ttl": "6bef7bad65c091738cd58cf5f0f1a395",
|
||||
"shapes/pr_metamodel_shapes.ttl": "baa6fdf5edae6d8e5b1772f5ee467df9"
|
||||
},
|
||||
"steps": {
|
||||
"rename_order_1": 0,
|
||||
"rename_order_2": 0,
|
||||
"rename_order_3": 0,
|
||||
"abox_aligned": 0,
|
||||
"merge": 0
|
||||
},
|
||||
"total_changes": 0,
|
||||
"triples_before": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 398
|
||||
},
|
||||
"triples_after": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 398
|
||||
},
|
||||
"notes": []
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"attempt_timestamp": "2026-08-05T14:25:50",
|
||||
"mode": "dry-run",
|
||||
"from_version": "1.6",
|
||||
"to_version": "2.0",
|
||||
"input_checksums": {
|
||||
"ontology/pr_metamodel.ttl": "552bbe0e9c0883d869ac5d6de40503bb",
|
||||
"instances/sodh.ttl": "9eb1dbb96e325dbd61c6e880b43eaa44",
|
||||
"shapes/pr_metamodel_shapes.ttl": "f6a1b4681e5817f8852c81424312e3d9"
|
||||
},
|
||||
"steps": {
|
||||
"deprecated_dropped": 130,
|
||||
"rename_order_1": 244,
|
||||
"rename_order_2": 256,
|
||||
"rename_order_3": 14,
|
||||
"abox_aligned": 356,
|
||||
"queries_rewritten": 1,
|
||||
"merge": 5,
|
||||
"reclassified": 2,
|
||||
"classes_created": 1,
|
||||
"reparented": 1,
|
||||
"to_literal_property": 2,
|
||||
"to_literal_values": 6,
|
||||
"to_literal_dropped": 18,
|
||||
"isAbstract_declared": 31,
|
||||
"display_removed": 22,
|
||||
"display_set": 25,
|
||||
"labels_regenerated": 76,
|
||||
"version_bumped": 1,
|
||||
"shapes_conformsTo": 1
|
||||
},
|
||||
"total_changes": 1192,
|
||||
"triples_before": {
|
||||
"ontology/pr_metamodel.ttl": 853,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 395
|
||||
},
|
||||
"triples_after": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 398
|
||||
},
|
||||
"notes": [
|
||||
"reclassified: shortLabel",
|
||||
"reclassified: acronym",
|
||||
"classes_created: GovernanceLayerObject",
|
||||
"reparented: Actor -> GovernanceLayerObject",
|
||||
"to_literal_property: activationStatus",
|
||||
"to_literal_property: environment",
|
||||
"version_bumped: https://ontology.pernod-ricard.com/metamodel/2.0",
|
||||
"shapes_conformsTo: https://ontology.pernod-ricard.com/metamodel/2.0"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"attempt_timestamp": "2026-08-05T14:27:09",
|
||||
"mode": "apply",
|
||||
"from_version": "1.6",
|
||||
"to_version": "2.0",
|
||||
"input_checksums": {
|
||||
"ontology/pr_metamodel.ttl": "552bbe0e9c0883d869ac5d6de40503bb",
|
||||
"instances/sodh.ttl": "9eb1dbb96e325dbd61c6e880b43eaa44",
|
||||
"shapes/pr_metamodel_shapes.ttl": "f6a1b4681e5817f8852c81424312e3d9"
|
||||
},
|
||||
"steps": {
|
||||
"deprecated_dropped": 130,
|
||||
"rename_order_1": 244,
|
||||
"rename_order_2": 256,
|
||||
"rename_order_3": 14,
|
||||
"abox_aligned": 356,
|
||||
"queries_rewritten": 1,
|
||||
"merge": 5,
|
||||
"reclassified": 2,
|
||||
"classes_created": 1,
|
||||
"reparented": 1,
|
||||
"to_literal_property": 2,
|
||||
"to_literal_values": 6,
|
||||
"to_literal_dropped": 18,
|
||||
"isAbstract_declared": 31,
|
||||
"display_removed": 22,
|
||||
"display_set": 25,
|
||||
"labels_regenerated": 76,
|
||||
"version_bumped": 1,
|
||||
"shapes_conformsTo": 1
|
||||
},
|
||||
"total_changes": 1192,
|
||||
"triples_before": {
|
||||
"ontology/pr_metamodel.ttl": 853,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 395
|
||||
},
|
||||
"triples_after": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 398
|
||||
},
|
||||
"notes": [
|
||||
"reclassified: shortLabel",
|
||||
"reclassified: acronym",
|
||||
"classes_created: GovernanceLayerObject",
|
||||
"reparented: Actor -> GovernanceLayerObject",
|
||||
"to_literal_property: activationStatus",
|
||||
"to_literal_property: environment",
|
||||
"version_bumped: https://ontology.pernod-ricard.com/metamodel/2.0",
|
||||
"shapes_conformsTo: https://ontology.pernod-ricard.com/metamodel/2.0"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"attempt_timestamp": "2026-08-05T14:27:20",
|
||||
"mode": "dry-run",
|
||||
"from_version": "1.6",
|
||||
"to_version": "2.0",
|
||||
"input_checksums": {
|
||||
"ontology/pr_metamodel.ttl": "32929c506d402738807b8b30c04fbb48",
|
||||
"instances/sodh.ttl": "6bef7bad65c091738cd58cf5f0f1a395",
|
||||
"shapes/pr_metamodel_shapes.ttl": "d67b69756389a9864e306241d53114f1"
|
||||
},
|
||||
"steps": {
|
||||
"rename_order_1": 0,
|
||||
"rename_order_2": 0,
|
||||
"rename_order_3": 0,
|
||||
"abox_aligned": 0,
|
||||
"queries_rewritten": 0,
|
||||
"merge": 0
|
||||
},
|
||||
"total_changes": 0,
|
||||
"triples_before": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 398
|
||||
},
|
||||
"triples_after": {
|
||||
"ontology/pr_metamodel.ttl": 723,
|
||||
"instances/sodh.ttl": 2308,
|
||||
"shapes/pr_metamodel_shapes.ttl": 398
|
||||
},
|
||||
"notes": []
|
||||
}
|
||||
@@ -31,6 +31,7 @@ from collections import OrderedDict
|
||||
|
||||
import yaml
|
||||
from rdflib import Graph, Literal, Namespace, RDF, RDFS, OWL, URIRef, XSD
|
||||
from rdflib.namespace import SH
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SPEC = os.path.join(HERE, "renames.yaml")
|
||||
@@ -133,7 +134,13 @@ def drop_subject(graph, subject, report, step):
|
||||
|
||||
|
||||
def count_instances(graphs, term):
|
||||
"""EV-002. The proof required before any permanent withdrawal."""
|
||||
"""EV-002. The proof required before any permanent withdrawal.
|
||||
|
||||
Counted on INSTANCE graphs only. A mention inside a shapes graph is not an
|
||||
instantiation: a shape carrying sh:maxCount 0 on a term is a guard forbidding
|
||||
its reappearance, which is the opposite of a use, and it keeps working after
|
||||
the term is gone since it checks an identifier rather than a declaration.
|
||||
"""
|
||||
n = 0
|
||||
for g in graphs:
|
||||
n += len(list(g.triples((None, RDF.type, term))))
|
||||
@@ -142,34 +149,84 @@ def count_instances(graphs, term):
|
||||
return n
|
||||
|
||||
|
||||
def migrate(onto, others, spec, rules, report):
|
||||
def rewrite_queries(graph, renames, report):
|
||||
"""Rename inside embedded SPARQL queries.
|
||||
|
||||
A sh:select or sh:ask body is a literal, so no triple-level rename reaches
|
||||
it: the query keeps naming a term that no longer exists and the constraint
|
||||
silently stops matching. This is not the line-by-line editing EV-005
|
||||
forbids: the graph is parsed, the literal is retrieved as a value, and what
|
||||
is rewritten is a structured query language where a prefixed name has clear
|
||||
token boundaries.
|
||||
"""
|
||||
n = 0
|
||||
for prop in (SH.select, SH.ask, SH.construct):
|
||||
for s, p, o in list(graph.triples((None, prop, None))):
|
||||
text = str(o)
|
||||
new = text
|
||||
for old, target in renames:
|
||||
new = re.sub(r"\bpr:%s\b" % re.escape(old), "pr:%s" % target, new)
|
||||
if new != text:
|
||||
graph.remove((s, p, o))
|
||||
graph.add((s, p, Literal(new)))
|
||||
n += 1
|
||||
report.add("queries_rewritten", n)
|
||||
return n
|
||||
|
||||
|
||||
def migrate(onto, instances, shapes, spec, rules, report):
|
||||
ns = spec["meta"]["namespace"]
|
||||
pr = Namespace(ns)
|
||||
others = instances + shapes
|
||||
all_graphs = [onto] + others
|
||||
|
||||
# 1 — deprecated terms are withdrawn outright (phase clause), proof first
|
||||
# 1 — deprecated terms are withdrawn outright (phase clause), proof first.
|
||||
# The proof is taken on instance graphs only: a mention in a shapes graph
|
||||
# is a guard against reappearance, not a use.
|
||||
# Reference cleaning likewise stops at the ontology and the instances.
|
||||
# EV-003 guards against phantom nodes rebuilt by inference in the model
|
||||
# graph; a mention inside a shape is a constraint on an identifier, in a
|
||||
# separate graph, taking part in no inference over the ontology. Removing
|
||||
# it would strip the sh:path from a guard and leave a property shape
|
||||
# carrying a maxCount and no path — an invalid shape, and the loss of the
|
||||
# very rule that forbids the term from coming back.
|
||||
if spec["structural"].get("drop_deprecated"):
|
||||
for subj in list(onto.subjects(OWL.deprecated, Literal(True))):
|
||||
name = local(subj, ns) or str(subj)
|
||||
used = sum(count_instances([g], subj) for g in others)
|
||||
used = count_instances(instances, subj)
|
||||
if used:
|
||||
report.add("deprecated_kept", 1, "%s still used %d times" % (name, used))
|
||||
report.add("deprecated_kept", 1, "%s instantiated %d times" % (name, used))
|
||||
continue
|
||||
for g in all_graphs:
|
||||
for g in [onto] + instances:
|
||||
drop_subject(g, subj, report, "deprecated_dropped")
|
||||
|
||||
# 2 — renames, in dependency order
|
||||
# 2 — vocabulary renames, in dependency order
|
||||
for order in (1, 2, 3):
|
||||
for r in [x for x in spec["renames"] if x["order"] == order]:
|
||||
for g in all_graphs:
|
||||
rename(g, pr, r["from"], r["to"], report, "rename_order_%d" % order)
|
||||
|
||||
# 3 — merges
|
||||
# 3 — instance-side alignment: forms the instances use that the vocabulary
|
||||
# never declared and that the TN-012 renames do not converge on.
|
||||
# Applied to the shapes too: they target these forms in sh:path.
|
||||
for r in spec.get("abox_renames") or []:
|
||||
for g in all_graphs:
|
||||
rename(g, pr, r["from"], r["to"], report, "abox_aligned")
|
||||
|
||||
# 4 — embedded SPARQL queries carry term names as text, invisible to a
|
||||
# triple-level rename. Rewritten with the same map, after it.
|
||||
pairs = [(r["from"], r["to"]) for r in spec["renames"]]
|
||||
pairs += [(r["from"], r["to"]) for r in (spec.get("abox_renames") or [])]
|
||||
pairs += [(m["from"], m["into"]) for m in (spec.get("merges") or [])]
|
||||
for g in all_graphs:
|
||||
rewrite_queries(g, pairs, report)
|
||||
|
||||
# 5 — merges
|
||||
for m in spec.get("merges") or []:
|
||||
for g in all_graphs:
|
||||
merge(g, pr, m["from"], m["into"], report)
|
||||
|
||||
# 4 — reclassify display properties (TN-003)
|
||||
# 6 — reclassify display properties (TN-003)
|
||||
for rc in spec["structural"]["reclassify"]:
|
||||
term = pr[rc["term"]]
|
||||
if (term, RDF.type, OWL.AnnotationProperty) not in onto:
|
||||
@@ -177,7 +234,7 @@ def migrate(onto, others, spec, rules, report):
|
||||
onto.add((term, RDF.type, OWL.AnnotationProperty))
|
||||
report.add("reclassified", 1, rc["term"])
|
||||
|
||||
# 5 — create the governance layer root (TN-027)
|
||||
# 7 — create the governance layer root (TN-027)
|
||||
for c in spec["structural"].get("create_classes") or []:
|
||||
term = pr[c["term"]]
|
||||
if (term, RDF.type, OWL.Class) not in onto:
|
||||
@@ -188,21 +245,21 @@ def migrate(onto, others, spec, rules, report):
|
||||
onto.add((term, pr.isAbstract, Literal(True)))
|
||||
report.add("classes_created", 1, c["term"])
|
||||
|
||||
# 6 — reparent (TN-027)
|
||||
# 8 — reparent (TN-027)
|
||||
for rp in spec["structural"].get("reparent") or []:
|
||||
term, parent = pr[rp["term"]], pr[rp["parent"]]
|
||||
if (term, RDFS.subClassOf, parent) not in onto:
|
||||
onto.add((term, RDFS.subClassOf, parent))
|
||||
report.add("reparented", 1, "%s -> %s" % (rp["term"], rp["parent"]))
|
||||
|
||||
# 7 — sub-properties (TN-028)
|
||||
# 9 — sub-properties (TN-028)
|
||||
for sp in spec["structural"].get("subproperties") or []:
|
||||
term, parent = pr[sp["term"]], pr[sp["parent"]]
|
||||
if (term, RDFS.subPropertyOf, parent) not in onto:
|
||||
onto.add((term, RDFS.subPropertyOf, parent))
|
||||
report.add("subproperties", 1, "%s -> %s" % (sp["term"], sp["parent"]))
|
||||
|
||||
# 8 — controlled values become literals (TN-016, TN-017)
|
||||
# 10 — controlled values become literals (TN-016, TN-017)
|
||||
for conv in spec["structural"].get("to_literal") or []:
|
||||
prop = pr[conv["property"]]
|
||||
if (prop, RDF.type, OWL.DatatypeProperty) not in onto:
|
||||
@@ -222,10 +279,10 @@ def migrate(onto, others, spec, rules, report):
|
||||
for ind in conv["drop_individuals"] + [conv["drop_class"]]:
|
||||
subj = pr[ind]
|
||||
if (subj, None, None) in onto:
|
||||
for g in all_graphs:
|
||||
for g in [onto] + instances: # shapes keep their guards
|
||||
drop_subject(g, subj, report, "to_literal_dropped")
|
||||
|
||||
# 9 — abstractness, from the rulebook annex (TN-006, TN-007)
|
||||
# 11 — abstractness, from the rulebook annex (TN-006, TN-007)
|
||||
for a in rules["abstractness"]:
|
||||
term = pr[a["term"]]
|
||||
if (term, RDF.type, OWL.Class) not in onto:
|
||||
@@ -237,7 +294,7 @@ def migrate(onto, others, spec, rules, report):
|
||||
onto.add((term, pr.isAbstract, want))
|
||||
report.add("isAbstract_declared", 1)
|
||||
|
||||
# 10 — display annotations, from the rulebook annex (TN-022)
|
||||
# 12 — display annotations, from the rulebook annex (TN-022)
|
||||
# The annex is authoritative AND exhaustive: a null value means any existing
|
||||
# annotation is removed. A short label identical to the label carries no
|
||||
# information and is one more thing to keep in step.
|
||||
@@ -255,7 +312,7 @@ def migrate(onto, others, spec, rules, report):
|
||||
onto.remove((term, prop, None))
|
||||
report.add("display_removed", len(current))
|
||||
|
||||
# 11 — regenerate every label by derivation (TN-018, TN-023)
|
||||
# 13 — regenerate every label by derivation (TN-018, TN-023)
|
||||
kinds = (OWL.Class, OWL.ObjectProperty, OWL.DatatypeProperty, OWL.AnnotationProperty)
|
||||
for kind in kinds:
|
||||
for term in set(onto.subjects(RDF.type, kind)):
|
||||
@@ -268,7 +325,7 @@ def migrate(onto, others, spec, rules, report):
|
||||
onto.add((term, RDFS.label, want))
|
||||
report.add("labels_regenerated", 1)
|
||||
|
||||
# 12 — bump the ontology version (EV-014: the namespace itself never moves)
|
||||
# 14 — bump the ontology version (EV-014: the namespace itself never moves)
|
||||
target = URIRef(ns.rstrip("/") + "/" + spec["meta"]["to_version"])
|
||||
for onto_iri in set(onto.subjects(RDF.type, OWL.Ontology)):
|
||||
if (onto_iri, OWL.versionIRI, target) not in onto:
|
||||
@@ -276,6 +333,19 @@ def migrate(onto, others, spec, rules, report):
|
||||
onto.add((onto_iri, OWL.versionIRI, target))
|
||||
report.add("version_bumped", 1, str(target))
|
||||
|
||||
# 15 — the shapes declare the ontology version they target (EV-010).
|
||||
# The shapes graph carries no ontology node, so it is created here.
|
||||
so = spec["structural"].get("shapes_ontology")
|
||||
if so:
|
||||
node = URIRef(so["iri"])
|
||||
for g in shapes:
|
||||
if (node, DCTERMS.conformsTo, target) not in g:
|
||||
g.remove((node, DCTERMS.conformsTo, None))
|
||||
g.add((node, RDF.type, OWL.Ontology))
|
||||
g.add((node, RDFS.label, Literal(so["label"])))
|
||||
g.add((node, DCTERMS.conformsTo, target))
|
||||
report.add("shapes_conformsTo", 1, str(target))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------- main
|
||||
|
||||
@@ -303,15 +373,22 @@ def main():
|
||||
|
||||
onto = Graph()
|
||||
onto.parse(args.ontology, format="turtle")
|
||||
others, other_paths = [], args.instances + args.shapes
|
||||
for p in other_paths:
|
||||
instances, shapes = [], []
|
||||
for p in args.instances:
|
||||
g = Graph()
|
||||
g.parse(p, format="turtle")
|
||||
others.append(g)
|
||||
instances.append(g)
|
||||
for p in args.shapes:
|
||||
g = Graph()
|
||||
g.parse(p, format="turtle")
|
||||
g.bind("dcterms", DCTERMS)
|
||||
shapes.append(g)
|
||||
others = instances + shapes
|
||||
other_paths = args.instances + args.shapes
|
||||
|
||||
before = [len(onto)] + [len(g) for g in others]
|
||||
report = Report()
|
||||
migrate(onto, others, spec, rules, report)
|
||||
migrate(onto, instances, shapes, spec, rules, report)
|
||||
after = [len(onto)] + [len(g) for g in others]
|
||||
|
||||
print("MIGRATION %s -> %s %s"
|
||||
|
||||
@@ -98,6 +98,19 @@ merges:
|
||||
controlled per class by SHACL. Ranges are compatible, so no RDFS retyping
|
||||
is introduced (TN-028).
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# A-BOX ALIGNMENT
|
||||
# The instance graph was written against nominal forms that the vocabulary never
|
||||
# declared: pr:identifier, pr:version, pr:status, pr:businessDefinition, pr:unit,
|
||||
# pr:formula, pr:shortLabel, pr:physicalName. The TN-012 renames reconcile the
|
||||
# two sides, since the vocabulary moves to the forms the instances already use.
|
||||
# Two of them do not converge on their own and are renamed on the instance side.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
abox_renames:
|
||||
- {from: name, to: canonicalName, reason: "the vocabulary target is canonicalName; name alone is too generic to stand as an identity"}
|
||||
- {from: format, to: logicalFormat, reason: "the vocabulary target is logicalFormat; format alone does not say which of the two"}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# STRUCTURAL OPERATIONS
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -152,3 +165,11 @@ structural:
|
||||
# deprecated terms are removed outright rather than kept as stubs. EV-002
|
||||
# still applies — the count must be zero.
|
||||
drop_deprecated: true
|
||||
|
||||
# EV-010: the shapes graph carries no ontology node at all, so the declaration
|
||||
# has to be created rather than aligned. Without it, an instance graph can be
|
||||
# validated against shapes written for another version and the run reports
|
||||
# violations that read exactly like a regression of the model.
|
||||
shapes_ontology:
|
||||
iri: "https://ontology.pernod-ricard.com/shapes"
|
||||
label: PR Data MetaModel SHACL Shapes
|
||||
|
||||
Reference in New Issue
Block a user