2026-07-26 21:28:14 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""
|
|
|
|
|
PR Data Meta Model - T-Box viewer generator
|
2026-07-29 09:03:59 +02:00
|
|
|
============================================
|
|
|
|
|
Reads the T-Box Turtle file and emits a self-contained HTML view.
|
|
|
|
|
Build artefact: regenerate, never edit.
|
2026-07-26 21:28:14 +02:00
|
|
|
|
|
|
|
|
USAGE
|
2026-07-29 09:03:59 +02:00
|
|
|
python3 scripts/generate_tbox_viewer.py [in.ttl] [out.html]
|
|
|
|
|
defaults: ontology/pr_metamodel.ttl -> generated/pr_metamodel_viewer.html
|
2026-07-26 21:28:14 +02:00
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
Same visual language as the SODH viewer, with one substitution. Where SODH
|
|
|
|
|
lays its columns out by owning domain, the T-Box has no domains -- its
|
|
|
|
|
orthogonal axis is PROVENANCE: what governance asserts against what a
|
|
|
|
|
harvester observes. The two-colour code carries that instead, blue for
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
defined and dark gold for captured, and the seam is what crosses between them.
|
2026-07-26 21:28:14 +02:00
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
No dependency: parses the Turtle subset this file uses, so it runs on the DSM
|
|
|
|
|
system python without a venv.
|
2026-07-26 21:28:14 +02:00
|
|
|
"""
|
|
|
|
|
import json
|
2026-07-29 09:03:59 +02:00
|
|
|
import os
|
2026-07-26 21:28:14 +02:00
|
|
|
import re
|
|
|
|
|
import sys
|
|
|
|
|
from collections import OrderedDict
|
|
|
|
|
|
|
|
|
|
PR = "https://ontology.pernod-ricard.com/metamodel/"
|
|
|
|
|
|
|
|
|
|
WANTED = {
|
|
|
|
|
"a": "type", "rdf:type": "type",
|
|
|
|
|
"rdfs:label": "label", "rdfs:comment": "comment",
|
|
|
|
|
"rdfs:subClassOf": "subClassOf", "rdfs:subPropertyOf": "subPropertyOf",
|
|
|
|
|
"rdfs:domain": "domain", "rdfs:range": "range",
|
|
|
|
|
"owl:inverseOf": "inverseOf", "owl:deprecated": "deprecated",
|
|
|
|
|
"dcterms:isReplacedBy": "isReplacedBy",
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
"pr:harvestSource": "harvestSource", "pr:authoringMode": "authoringMode",
|
|
|
|
|
"pr:hasAcronym": "acronym", "pr:hasShortLabel": "shortLabel",
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
LAYER_ROOT = OrderedDict([
|
|
|
|
|
("pr:OwnershipLayerObject", ("ownership", "Ownership & Categorization",
|
|
|
|
|
"who is accountable")),
|
|
|
|
|
("pr:BusinessLayerObject", ("business", "Business", "what it means")),
|
|
|
|
|
("pr:LogicalLayerObject", ("logical", "Logical",
|
|
|
|
|
"how it is structured")),
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
("pr:DeliveryLayerObject", ("delivery", "Delivery",
|
|
|
|
|
"what we promise, to whom, until when")),
|
2026-07-29 09:03:59 +02:00
|
|
|
("pr:PhysicalLayerObject", ("physical", "Physical",
|
|
|
|
|
"what actually runs")),
|
|
|
|
|
("pr:ConsumptionLayerObject", ("consumption", "Consumption",
|
|
|
|
|
"what people open in the morning")),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
# The only relations crossing between what is asserted and what is harvested.
|
|
|
|
|
SEAM = {"pr:materializedAs", "pr:storedIn", "pr:physicalizedIn"}
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------- parsing
|
2026-07-26 21:28:14 +02:00
|
|
|
def strip_comments(text):
|
|
|
|
|
out, i, n = [], 0, len(text)
|
|
|
|
|
while i < n:
|
|
|
|
|
c = text[i]
|
|
|
|
|
if c == '"':
|
|
|
|
|
j = i + 1
|
|
|
|
|
while j < n and text[j] != '"':
|
|
|
|
|
j += 2 if text[j] == "\\" else 1
|
|
|
|
|
j = min(j + 1, n)
|
|
|
|
|
out.append(text[i:j]); i = j; continue
|
|
|
|
|
if c == "#":
|
|
|
|
|
j = text.find("\n", i)
|
|
|
|
|
i = n if j == -1 else j
|
|
|
|
|
continue
|
|
|
|
|
out.append(c); i += 1
|
|
|
|
|
return "".join(out)
|
|
|
|
|
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
def split_outside(text, sep):
|
|
|
|
|
parts, buf, i, n = [], [], 0, len(text)
|
2026-07-26 21:28:14 +02:00
|
|
|
while i < n:
|
|
|
|
|
c = text[i]
|
|
|
|
|
if c == '"':
|
|
|
|
|
j = i + 1
|
|
|
|
|
while j < n and text[j] != '"':
|
|
|
|
|
j += 2 if text[j] == "\\" else 1
|
|
|
|
|
j = min(j + 1, n)
|
|
|
|
|
buf.append(text[i:j]); i = j; continue
|
2026-07-29 09:03:59 +02:00
|
|
|
if c == sep:
|
2026-07-26 21:28:14 +02:00
|
|
|
parts.append("".join(buf)); buf = []; i += 1; continue
|
|
|
|
|
buf.append(c); i += 1
|
2026-07-29 09:03:59 +02:00
|
|
|
parts.append("".join(buf))
|
2026-07-26 21:28:14 +02:00
|
|
|
return parts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def unquote(tok):
|
|
|
|
|
tok = tok.strip()
|
|
|
|
|
if tok.startswith('"'):
|
|
|
|
|
end = tok.rindex('"')
|
2026-07-29 09:03:59 +02:00
|
|
|
return tok[1:end].replace('\\"', '"').replace("\\\\", "\\")
|
2026-07-26 21:28:14 +02:00
|
|
|
return tok
|
|
|
|
|
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
def parse(path):
|
|
|
|
|
"""Turtle subset reader: enough for an instance file, keeps ordering."""
|
|
|
|
|
text = strip_comments(open(path, encoding="utf-8").read())
|
2026-07-26 21:28:14 +02:00
|
|
|
subjects = OrderedDict()
|
2026-07-29 09:03:59 +02:00
|
|
|
for stmt in split_outside(text, "."):
|
2026-07-26 21:28:14 +02:00
|
|
|
stmt = stmt.strip()
|
2026-07-29 09:03:59 +02:00
|
|
|
if not stmt or not stmt.startswith("ex:"):
|
2026-07-26 21:28:14 +02:00
|
|
|
continue
|
2026-07-29 09:03:59 +02:00
|
|
|
clauses = split_outside(stmt, ";")
|
|
|
|
|
head = clauses[0].strip()
|
2026-07-26 21:28:14 +02:00
|
|
|
m = re.match(r"^(\S+)\s+(.*)$", head, re.S)
|
|
|
|
|
if not m:
|
|
|
|
|
continue
|
|
|
|
|
subj, rest = m.group(1), m.group(2)
|
2026-07-29 09:03:59 +02:00
|
|
|
rec = subjects.setdefault(subj, {"id": subj})
|
|
|
|
|
for chunk in [rest] + [c.strip() for c in clauses[1:]]:
|
2026-07-26 21:28:14 +02:00
|
|
|
chunk = chunk.strip()
|
|
|
|
|
if not chunk:
|
|
|
|
|
continue
|
|
|
|
|
pm = re.match(r"^(\S+)\s+(.*)$", chunk, re.S)
|
|
|
|
|
if not pm:
|
|
|
|
|
continue
|
|
|
|
|
pred, objs = pm.group(1), pm.group(2)
|
2026-07-29 09:03:59 +02:00
|
|
|
key = "type" if pred in ("a", "rdf:type") else pred.split(":")[-1]
|
|
|
|
|
vals = [unquote(t) for t in split_outside(objs, ",") if t.strip()]
|
|
|
|
|
if key in MULTI or key in rec:
|
|
|
|
|
rec.setdefault(key, [])
|
|
|
|
|
if not isinstance(rec[key], list):
|
|
|
|
|
rec[key] = [rec[key]]
|
|
|
|
|
rec[key] += vals
|
|
|
|
|
else:
|
|
|
|
|
rec[key] = vals[0] if len(vals) == 1 else vals
|
2026-07-26 21:28:14 +02:00
|
|
|
return subjects
|
|
|
|
|
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
def as_list(v):
|
|
|
|
|
if v is None:
|
|
|
|
|
return []
|
|
|
|
|
return v if isinstance(v, list) else [v]
|
2026-07-26 21:28:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
# ---------------------------------------------------------------- model
|
2026-07-26 21:28:14 +02:00
|
|
|
def first(rec, key, default=""):
|
|
|
|
|
v = rec.get(key)
|
|
|
|
|
return v[0] if v else default
|
|
|
|
|
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
def parse_tbox(path):
|
|
|
|
|
"""Turtle subset reader for the T-Box: pr: subjects, WANTED predicates."""
|
|
|
|
|
text = strip_comments(open(path, encoding="utf-8").read())
|
|
|
|
|
subjects = OrderedDict()
|
|
|
|
|
for stmt in split_outside(text, "."):
|
|
|
|
|
stmt = stmt.strip()
|
|
|
|
|
if not stmt.startswith("pr:"):
|
|
|
|
|
continue
|
|
|
|
|
chunks = split_outside(stmt, ";")
|
|
|
|
|
m = re.match(r"^(\S+)\s+(.*)$", chunks[0].strip(), re.S)
|
|
|
|
|
if not m:
|
|
|
|
|
continue
|
|
|
|
|
rec = subjects.setdefault(m.group(1), {})
|
|
|
|
|
for chunk in [m.group(2)] + [c.strip() for c in chunks[1:]]:
|
|
|
|
|
chunk = chunk.strip()
|
|
|
|
|
if not chunk:
|
|
|
|
|
continue
|
|
|
|
|
pm = re.match(r"^(\S+)\s+(.*)$", chunk, re.S)
|
|
|
|
|
if not pm:
|
|
|
|
|
continue
|
|
|
|
|
key = WANTED.get(pm.group(1))
|
|
|
|
|
if key:
|
|
|
|
|
rec.setdefault(key, []).extend(
|
|
|
|
|
unquote(t) for t in split_outside(pm.group(2), ",") if t.strip())
|
|
|
|
|
return subjects
|
|
|
|
|
|
|
|
|
|
|
2026-07-26 21:28:14 +02:00
|
|
|
def build(subjects):
|
2026-07-29 09:03:59 +02:00
|
|
|
classes, objp, datp, annp, individuals = {}, {}, {}, {}, {}
|
2026-07-26 21:28:14 +02:00
|
|
|
for term, rec in subjects.items():
|
|
|
|
|
types = rec.get("type", [])
|
|
|
|
|
if "owl:Class" in types:
|
|
|
|
|
classes[term] = rec
|
|
|
|
|
elif "owl:ObjectProperty" in types:
|
2026-07-29 09:03:59 +02:00
|
|
|
objp[term] = rec
|
2026-07-26 21:28:14 +02:00
|
|
|
elif "owl:DatatypeProperty" in types:
|
2026-07-29 09:03:59 +02:00
|
|
|
datp[term] = rec
|
2026-07-26 21:28:14 +02:00
|
|
|
elif "owl:AnnotationProperty" in types:
|
2026-07-29 09:03:59 +02:00
|
|
|
annp[term] = rec
|
2026-07-26 21:28:14 +02:00
|
|
|
elif types and types[0].startswith("pr:"):
|
|
|
|
|
individuals.setdefault(types[0], []).append(term)
|
|
|
|
|
|
|
|
|
|
def ancestry(term, seen=None):
|
|
|
|
|
seen = seen or set()
|
|
|
|
|
if term in seen:
|
|
|
|
|
return []
|
|
|
|
|
seen.add(term)
|
|
|
|
|
out = [term]
|
|
|
|
|
for parent in classes.get(term, {}).get("subClassOf", []):
|
|
|
|
|
out += ancestry(parent, seen)
|
|
|
|
|
return out
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
parents = {c: first(classes[c], "subClassOf") for c in classes}
|
|
|
|
|
has_kids = set(parents.values())
|
|
|
|
|
|
|
|
|
|
nodes = {}
|
2026-07-26 21:28:14 +02:00
|
|
|
for term, rec in classes.items():
|
|
|
|
|
anc = ancestry(term)
|
2026-07-29 09:03:59 +02:00
|
|
|
layer = next((LAYER_ROOT[a][0] for a in anc if a in LAYER_ROOT), None)
|
|
|
|
|
if layer is None:
|
|
|
|
|
if "pr:Actor" in anc:
|
|
|
|
|
layer = "actors"
|
|
|
|
|
elif term in ("pr:ActivationStatus", "pr:Environment", "pr:SystemType"):
|
|
|
|
|
layer = "vocab"
|
2026-07-26 21:28:14 +02:00
|
|
|
else:
|
2026-07-29 09:03:59 +02:00
|
|
|
layer = "roots"
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
# provenance is INFERRED from the type-of chain, never asserted:
|
|
|
|
|
# a Sub-domain is Defined because it is a kind of Ownership Layer
|
|
|
|
|
# Object, which is a kind of Defined Object. No separate edge exists
|
|
|
|
|
# because none is needed -- the subclass chain already says it.
|
|
|
|
|
prov = ("captured" if "pr:CapturedObject" in anc
|
|
|
|
|
else "defined" if "pr:DefinedObject" in anc else "structural")
|
2026-07-29 09:03:59 +02:00
|
|
|
depth, p, guard = 0, parents.get(term), 0
|
|
|
|
|
while p and p in classes and guard < 8:
|
|
|
|
|
depth += 1; guard += 1; p = parents.get(p)
|
|
|
|
|
nodes[term] = {
|
|
|
|
|
"id": term, "kind": "Class",
|
2026-07-26 21:28:14 +02:00
|
|
|
"label": first(rec, "label", term.split(":")[1]),
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
"shortLabel": first(rec, "shortLabel"),
|
2026-07-29 09:03:59 +02:00
|
|
|
"layer": layer, "prov": prov, "depth": depth,
|
|
|
|
|
"parent": parents.get(term, ""),
|
2026-07-26 21:28:14 +02:00
|
|
|
"comment": first(rec, "comment"),
|
|
|
|
|
"harvest": first(rec, "harvestSource"),
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
"authoring": first(rec, "authoringMode"),
|
2026-07-29 09:03:59 +02:00
|
|
|
"acronym": first(rec, "acronym"),
|
2026-07-26 21:28:14 +02:00
|
|
|
"deprecated": first(rec, "deprecated") in ("true", "True"),
|
|
|
|
|
"replacedBy": first(rec, "isReplacedBy"),
|
2026-07-29 09:03:59 +02:00
|
|
|
"abstract": term in has_kids and term not in LAYER_ROOT
|
|
|
|
|
and term not in ("pr:MetaModelObject",),
|
|
|
|
|
"members": [{"id": m, "label": first(subjects.get(m, {}), "label",
|
|
|
|
|
m.split(":")[1])}
|
|
|
|
|
for m in individuals.get(term, [])],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def prop(term, rec, kind):
|
|
|
|
|
return {
|
|
|
|
|
"id": term, "kind": kind,
|
2026-07-26 21:28:14 +02:00
|
|
|
"label": first(rec, "label", term.split(":")[1]),
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
"shortLabel": first(rec, "shortLabel"),
|
2026-07-26 21:28:14 +02:00
|
|
|
"comment": first(rec, "comment"),
|
2026-07-29 09:03:59 +02:00
|
|
|
"domain": first(rec, "domain"), "range": first(rec, "range"),
|
2026-07-26 21:28:14 +02:00
|
|
|
"inverseOf": first(rec, "inverseOf"),
|
|
|
|
|
"subPropertyOf": first(rec, "subPropertyOf"),
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
"authoring": first(rec, "authoringMode"),
|
2026-07-29 09:03:59 +02:00
|
|
|
"harvest": first(rec, "harvestSource"),
|
2026-07-26 21:28:14 +02:00
|
|
|
"functional": "owl:FunctionalProperty" in rec.get("type", []),
|
|
|
|
|
"transitive": "owl:TransitiveProperty" in rec.get("type", []),
|
|
|
|
|
"deprecated": first(rec, "deprecated") in ("true", "True"),
|
|
|
|
|
"replacedBy": first(rec, "isReplacedBy"),
|
2026-07-29 09:03:59 +02:00
|
|
|
"seam": term in SEAM,
|
|
|
|
|
"prov": "", "layer": "",
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
|
|
|
|
|
layers = [{"id": v[0], "title": v[1], "tag": v[2]} for v in LAYER_ROOT.values()]
|
|
|
|
|
layers = ([{"id": "roots", "title": "Model roots", "tag": "how a term got here"}]
|
|
|
|
|
+ layers
|
|
|
|
|
+ [{"id": "actors", "title": "Actors", "tag": "people and teams"},
|
|
|
|
|
{"id": "vocab", "title": "Vocabularies", "tag": "controlled value lists"}])
|
|
|
|
|
|
|
|
|
|
return {"nodes": list(nodes.values()),
|
|
|
|
|
"relations": [prop(t, r, "Relation") for t, r in objp.items()],
|
|
|
|
|
"attributes": [prop(t, r, "Attribute") for t, r in datp.items()],
|
|
|
|
|
"bridges": [prop(t, r, "Bridge") for t, r in annp.items()
|
|
|
|
|
if t.startswith("pr:denotes")],
|
|
|
|
|
"layers": layers}
|
2026-07-26 21:28:14 +02:00
|
|
|
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
# ---------------------------------------------------------------- output
|
|
|
|
|
def main():
|
|
|
|
|
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
src = sys.argv[1] if len(sys.argv) > 1 else os.path.join(
|
|
|
|
|
root, "ontology", "pr_metamodel.ttl")
|
|
|
|
|
out = sys.argv[2] if len(sys.argv) > 2 else os.path.join(
|
|
|
|
|
root, "generated", "pr_metamodel_viewer.html")
|
2026-07-26 21:28:14 +02:00
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
model = build(parse_tbox(src))
|
2026-07-26 21:28:14 +02:00
|
|
|
payload = json.dumps(model, ensure_ascii=False, separators=(",", ":"))
|
2026-07-29 09:03:59 +02:00
|
|
|
html = TEMPLATE.replace("__MODEL__", payload).replace(
|
|
|
|
|
"__SRC__", os.path.basename(src))
|
|
|
|
|
os.makedirs(os.path.dirname(out), exist_ok=True)
|
|
|
|
|
open(out, "w", encoding="utf-8").write(html)
|
|
|
|
|
|
|
|
|
|
live_c = [n for n in model["nodes"] if not n["deprecated"]]
|
|
|
|
|
dep = ([n for n in model["nodes"] if n["deprecated"]]
|
|
|
|
|
+ [r for r in model["relations"] + model["attributes"] if r["deprecated"]])
|
|
|
|
|
print("PR META MODEL - T-Box viewer")
|
|
|
|
|
print(" source : %s" % src)
|
|
|
|
|
print(" output : %s" % out)
|
|
|
|
|
print(" classes %d | relations %d | attributes %d | bridges %d | deprecated %d"
|
|
|
|
|
% (len(live_c),
|
|
|
|
|
len([r for r in model["relations"] if not r["deprecated"]]),
|
|
|
|
|
len([a for a in model["attributes"] if not a["deprecated"]]),
|
|
|
|
|
len(model["bridges"]), len(dep)))
|
|
|
|
|
for l in model["layers"]:
|
|
|
|
|
c = len([n for n in live_c if n["layer"] == l["id"]])
|
|
|
|
|
if c:
|
|
|
|
|
print(" %-28s %d" % (l["title"], c))
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
for p in ("defined", "captured", "structural"):
|
2026-07-29 09:03:59 +02:00
|
|
|
c = len([n for n in live_c if n["prov"] == p])
|
|
|
|
|
print(" %-28s %d" % (p, c))
|
2026-07-26 21:28:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
TEMPLATE = r"""<!DOCTYPE html>
|
|
|
|
|
<html lang="en">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
2026-07-29 09:03:59 +02:00
|
|
|
<title>PR Data Meta Model · T-Box</title>
|
2026-07-26 21:28:14 +02:00
|
|
|
<style>
|
|
|
|
|
:root{
|
2026-07-29 09:03:59 +02:00
|
|
|
--navy:#0A1F44; --gold:#B8935A; --ink:#1A1A1A; --bg:#F7F6F2;
|
|
|
|
|
--panel:#fff; --rule:#E4E2DA; --muted:#6B6B6B; --soft:#F1EFE8;
|
|
|
|
|
--mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace;
|
2026-07-26 21:28:14 +02:00
|
|
|
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
|
2026-07-29 09:03:59 +02:00
|
|
|
--serif:Georgia,"Times New Roman",serif;
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
|
|
|
|
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
|
2026-07-29 09:03:59 +02:00
|
|
|
html,body{margin:0;height:100%}
|
2026-07-26 21:28:14 +02:00
|
|
|
body{font-family:var(--sans);background:var(--bg);color:var(--ink);
|
|
|
|
|
display:flex;flex-direction:column;overflow:hidden}
|
|
|
|
|
:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
header{background:var(--navy);color:#fff;padding:11px 20px;display:flex;
|
|
|
|
|
align-items:center;gap:18px;flex-shrink:0}
|
|
|
|
|
header h1{margin:0;font-family:var(--mono);font-size:12.5px;font-weight:600;
|
|
|
|
|
letter-spacing:.17em;text-transform:uppercase}
|
|
|
|
|
header .sub{font-size:11px;color:rgba(255,255,255,.6);margin-top:3px}
|
|
|
|
|
.tabs{margin-left:auto;display:flex;gap:2px;background:rgba(255,255,255,.08);
|
|
|
|
|
padding:3px;border-radius:4px}
|
|
|
|
|
.tabs button{padding:6px 14px;border:none;background:transparent;color:rgba(255,255,255,.7);
|
|
|
|
|
font-family:var(--mono);font-size:10px;letter-spacing:.09em;text-transform:uppercase;
|
|
|
|
|
cursor:pointer;border-radius:3px;font-weight:600}
|
|
|
|
|
.tabs button.on{background:#fff;color:var(--navy)}
|
|
|
|
|
|
|
|
|
|
main{flex:1;overflow-y:auto;padding:22px 20px 60px}
|
|
|
|
|
.wrap{max-width:1220px;margin:0 auto}
|
|
|
|
|
|
|
|
|
|
.lede{font-family:var(--serif);font-size:21px;line-height:1.45;color:var(--navy);
|
|
|
|
|
margin:0 0 6px;max-width:860px}
|
|
|
|
|
.lede b{color:var(--gold)}
|
|
|
|
|
.sublede{font-size:13px;color:var(--muted);margin:0 0 22px;max-width:860px;line-height:1.55}
|
|
|
|
|
|
|
|
|
|
.kpis{display:flex;gap:26px;flex-wrap:wrap;padding:16px 0 20px;
|
|
|
|
|
border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);margin-bottom:24px}
|
|
|
|
|
.kpi b{display:block;font-family:var(--serif);font-size:29px;color:var(--navy);line-height:1}
|
|
|
|
|
.kpi.warn b{color:var(--gold)}
|
|
|
|
|
.kpi span{font-size:9.5px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);
|
|
|
|
|
display:block;margin-top:5px}
|
|
|
|
|
|
|
|
|
|
h2.sec{font-family:var(--mono);font-size:10px;letter-spacing:.15em;text-transform:uppercase;
|
|
|
|
|
color:var(--muted);margin:30px 0 12px;font-weight:600}
|
|
|
|
|
|
|
|
|
|
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}
|
|
|
|
|
.card{background:var(--panel);border:1px solid var(--rule);border-radius:4px;
|
|
|
|
|
padding:0;overflow:hidden;cursor:pointer;transition:box-shadow .15s}
|
|
|
|
|
.card:hover{box-shadow:0 3px 14px rgba(10,31,68,.10)}
|
|
|
|
|
.card .top{height:4px}
|
|
|
|
|
.card .body{padding:15px 17px 16px}
|
|
|
|
|
.card .role{font-family:var(--mono);font-size:8.5px;letter-spacing:.13em;
|
2026-07-26 21:28:14 +02:00
|
|
|
text-transform:uppercase;font-weight:600}
|
2026-07-29 09:03:59 +02:00
|
|
|
.card h3{margin:5px 0 2px;font-family:var(--serif);font-size:19px;color:var(--navy)}
|
|
|
|
|
.card .who{font-size:11.5px;color:var(--muted);margin-bottom:12px}
|
|
|
|
|
.card .nums{display:flex;gap:15px;flex-wrap:wrap;padding-top:11px;border-top:1px solid var(--rule)}
|
|
|
|
|
.card .nums div{font-size:10.5px;color:var(--muted)}
|
|
|
|
|
.card .nums b{display:block;font-family:var(--mono);font-size:15px;color:var(--ink)}
|
|
|
|
|
.flag{display:inline-block;margin-top:11px;padding:4px 9px;border-radius:3px;
|
|
|
|
|
background:#FBF3E4;border:1px solid var(--gold);color:#7A5B2A;
|
|
|
|
|
font-family:var(--mono);font-size:9.5px;letter-spacing:.05em}
|
|
|
|
|
|
|
|
|
|
table{width:100%;border-collapse:collapse;background:var(--panel);font-size:12.5px}
|
|
|
|
|
th{text-align:left;padding:9px 11px;font-family:var(--mono);font-size:9px;
|
|
|
|
|
letter-spacing:.1em;text-transform:uppercase;color:var(--muted);
|
|
|
|
|
border-bottom:1.5px solid var(--navy);white-space:nowrap;background:var(--panel);
|
|
|
|
|
position:sticky;top:0;cursor:pointer}
|
|
|
|
|
td{padding:8px 11px;border-bottom:1px solid var(--rule);vertical-align:top}
|
|
|
|
|
tr.arb td{background:#FDFAF3}
|
|
|
|
|
tr:hover td{background:var(--soft)}
|
|
|
|
|
tr{cursor:pointer}
|
|
|
|
|
.dot{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:7px;
|
|
|
|
|
vertical-align:baseline}
|
|
|
|
|
.mono{font-family:var(--mono);font-size:10.5px;color:var(--muted)}
|
|
|
|
|
.pill{display:inline-block;padding:2px 7px;border-radius:2px;font-family:var(--mono);
|
|
|
|
|
font-size:8.5px;letter-spacing:.06em;text-transform:uppercase;border:1px solid}
|
|
|
|
|
|
|
|
|
|
.bar{display:flex;height:26px;border-radius:3px;overflow:hidden;margin:6px 0 4px}
|
|
|
|
|
.bar div{position:relative}
|
|
|
|
|
.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:11px;color:var(--muted);margin-bottom:20px}
|
|
|
|
|
.legend i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:6px}
|
|
|
|
|
|
|
|
|
|
.filters{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px;align-items:center}
|
|
|
|
|
.filters input{padding:7px 11px;border:1px solid var(--rule);border-radius:3px;
|
|
|
|
|
font-family:var(--mono);font-size:12px;min-width:180px;background:#fff}
|
|
|
|
|
.filters button{padding:6px 12px;border:1px solid var(--rule);background:#fff;
|
|
|
|
|
border-radius:3px;font-family:var(--mono);font-size:10px;cursor:pointer;
|
|
|
|
|
text-transform:uppercase;letter-spacing:.07em;color:var(--muted)}
|
|
|
|
|
.filters button.on{background:var(--navy);color:#fff;border-color:var(--navy)}
|
|
|
|
|
|
|
|
|
|
#insp{position:fixed;top:0;right:0;width:390px;max-width:100%;height:100%;
|
2026-07-26 21:28:14 +02:00
|
|
|
background:var(--panel);border-left:1px solid var(--rule);
|
2026-07-29 09:03:59 +02:00
|
|
|
box-shadow:-8px 0 30px rgba(10,31,68,.13);overflow-y:auto;padding:22px 22px 60px;
|
|
|
|
|
transform:translateX(100%);transition:transform .22s;z-index:50}
|
2026-07-26 21:28:14 +02:00
|
|
|
#insp.open{transform:translateX(0)}
|
2026-07-29 09:03:59 +02:00
|
|
|
#insp .x{position:absolute;top:13px;right:15px;border:none;background:none;
|
|
|
|
|
font-size:23px;cursor:pointer;color:var(--muted);line-height:1}
|
|
|
|
|
.inh{font-style:italic;color:#8A6A28;font-size:9px;text-transform:uppercase;
|
|
|
|
|
letter-spacing:.06em;font-family:var(--mono)}
|
|
|
|
|
.backlink{background:none;border:none;padding:0 0 10px;font-family:var(--mono);
|
|
|
|
|
font-size:10px;color:var(--navy);cursor:pointer;letter-spacing:.05em}
|
|
|
|
|
.kind{font-family:var(--mono);font-size:9px;letter-spacing:.14em;
|
2026-07-26 21:28:14 +02:00
|
|
|
text-transform:uppercase;font-weight:600}
|
2026-07-29 09:03:59 +02:00
|
|
|
.gctl .gsec{font-family:var(--mono);font-size:8.5px;letter-spacing:.12em;
|
|
|
|
|
text-transform:uppercase;color:var(--muted);margin:9px 0 4px;font-weight:600}
|
|
|
|
|
.gctl select{width:100%;padding:4px;font-family:var(--mono);font-size:10px;
|
|
|
|
|
border:1px solid var(--rule);border-radius:3px;margin-top:5px}
|
|
|
|
|
.lanelabel{font-family:var(--mono);font-size:10px;letter-spacing:.1em;font-weight:600}
|
|
|
|
|
.elabel{font-family:var(--mono);font-size:7.5px;fill:#5A6270;text-anchor:middle;
|
|
|
|
|
pointer-events:none}
|
|
|
|
|
.elbg{fill:#F4F2EC;pointer-events:none}
|
|
|
|
|
.elabel.dim,.elbg.dim{opacity:.06}
|
|
|
|
|
#insp h3{margin:7px 0 2px;font-family:var(--serif);font-size:21px;color:var(--navy)}
|
|
|
|
|
#insp .iri{font-family:var(--mono);font-size:10.5px;color:var(--muted);margin-bottom:15px}
|
|
|
|
|
#insp h4{font-family:var(--mono);font-size:9px;letter-spacing:.14em;text-transform:uppercase;
|
|
|
|
|
color:var(--muted);margin:19px 0 7px;border-top:1px solid var(--rule);padding-top:11px}
|
|
|
|
|
#insp p{font-size:12.5px;line-height:1.6;margin:0 0 8px}
|
|
|
|
|
.row{display:flex;justify-content:space-between;gap:12px;padding:5px 0;
|
|
|
|
|
border-bottom:1px dotted var(--rule);font-size:12px}
|
|
|
|
|
.row span:last-child{color:var(--muted);text-align:right}
|
2026-07-26 21:28:14 +02:00
|
|
|
.jump{background:none;border:none;padding:0;font:inherit;color:var(--navy);
|
2026-07-29 09:03:59 +02:00
|
|
|
cursor:pointer;text-decoration:underline;text-underline-offset:2px;font-size:12px}
|
|
|
|
|
.note{background:#FBF3E4;border-left:3px solid var(--gold);padding:10px 12px;
|
|
|
|
|
font-size:11.5px;line-height:1.55;color:#6B5426}
|
|
|
|
|
|
|
|
|
|
/* ---- graph ---- */
|
|
|
|
|
.gwrap{position:relative;height:calc(100vh - 250px);min-height:420px;
|
|
|
|
|
border:1px solid var(--rule);border-radius:4px;overflow:hidden;background:
|
|
|
|
|
linear-gradient(90deg,rgba(10,31,68,.03) 1px,transparent 1px) 0 0/26px 26px,
|
|
|
|
|
linear-gradient(180deg,#FCFBF7,#F0EEE5);touch-action:none}
|
|
|
|
|
.gwrap svg{display:block;width:100%;height:100%;cursor:grab}
|
|
|
|
|
.gwrap svg.drag{cursor:grabbing}
|
|
|
|
|
.bandrect{opacity:.5}
|
|
|
|
|
.bandlabel{font-family:var(--mono);font-size:9.5px;letter-spacing:.14em;
|
|
|
|
|
text-transform:uppercase;font-weight:600}
|
|
|
|
|
.bandtag{font-size:9px;fill:var(--muted);font-style:italic}
|
|
|
|
|
.gnode{cursor:pointer}
|
|
|
|
|
.gnode rect{stroke-width:1.4}
|
|
|
|
|
.gnode text{font-size:9px;text-anchor:middle;pointer-events:none;font-weight:500}
|
|
|
|
|
.gnode.dim{opacity:.11}
|
|
|
|
|
.gnode.sel rect{stroke-width:3}
|
|
|
|
|
.gedge{fill:none;stroke:#A6ABB5;stroke-width:1;marker-end:url(#ga)}
|
|
|
|
|
.gedge.cross{stroke:#B8935A;stroke-width:1.8;marker-end:url(#gg)}
|
|
|
|
|
.gedge.dim{opacity:.05}
|
|
|
|
|
.gedge.hot{stroke:var(--navy);stroke-width:2.4;marker-end:url(#gn)}
|
|
|
|
|
.gctl{position:absolute;left:12px;top:12px;background:rgba(255,255,255,.94);
|
|
|
|
|
border:1px solid var(--rule);border-radius:4px;padding:9px 11px;font-size:11px;
|
|
|
|
|
max-width:230px}
|
|
|
|
|
.gctl .seg{display:flex;border:1px solid var(--rule);border-radius:3px;overflow:hidden;
|
|
|
|
|
margin-bottom:8px}
|
|
|
|
|
.gctl .seg button{flex:1;padding:5px;border:none;background:#fff;cursor:pointer;
|
|
|
|
|
font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.06em;
|
|
|
|
|
color:var(--muted)}
|
|
|
|
|
.gctl .seg button+button{border-left:1px solid var(--rule)}
|
|
|
|
|
.gctl .seg button.on{background:var(--navy);color:#fff}
|
|
|
|
|
.gctl label{display:flex;align-items:center;gap:7px;padding:3px 0;cursor:pointer}
|
|
|
|
|
.gctl label i{display:inline-block;width:8px;height:8px;border-radius:2px;flex-shrink:0}
|
|
|
|
|
.gctl input{accent-color:var(--navy)}
|
|
|
|
|
.gzoom{position:absolute;right:12px;bottom:12px;display:flex;gap:3px;
|
|
|
|
|
background:#fff;border:1px solid var(--rule);border-radius:4px;padding:3px}
|
|
|
|
|
.gzoom button{width:30px;height:30px;border:none;background:#fff;cursor:pointer;
|
|
|
|
|
font-size:15px;color:var(--navy);border-radius:3px}
|
|
|
|
|
.ghint{position:absolute;left:12px;bottom:12px;font-family:var(--mono);font-size:9px;
|
|
|
|
|
color:var(--muted);background:rgba(255,255,255,.85);padding:4px 8px;border-radius:3px}
|
|
|
|
|
|
|
|
|
|
footer{background:var(--navy);color:rgba(255,255,255,.5);padding:7px 20px;
|
|
|
|
|
font-family:var(--mono);font-size:9px;letter-spacing:.06em;flex-shrink:0;
|
|
|
|
|
display:flex;justify-content:space-between}
|
|
|
|
|
@media(max-width:760px){
|
|
|
|
|
.tabs{width:100%;margin:8px 0 0}header{flex-wrap:wrap}
|
|
|
|
|
.kpis{gap:18px}.kpi b{font-size:23px}
|
|
|
|
|
table{font-size:11.5px}td,th{padding:7px 8px}
|
|
|
|
|
.hide-s{display:none}
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
|
|
|
|
@media(prefers-reduced-motion:reduce){*{transition:none!important}}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<header>
|
|
|
|
|
<div>
|
|
|
|
|
<h1>PR Data Meta Model · T-Box</h1>
|
2026-07-29 09:03:59 +02:00
|
|
|
<div class="sub">Six layers, crossed with what governance asserts and what a harvester observes</div>
|
2026-07-26 21:28:14 +02:00
|
|
|
</div>
|
2026-07-29 09:03:59 +02:00
|
|
|
<div class="tabs" role="tablist">
|
|
|
|
|
<button id="t-scope" class="on">Overview</button>
|
|
|
|
|
<button id="t-model">Layers</button>
|
|
|
|
|
<button id="t-graph">Graph</button>
|
|
|
|
|
<button id="t-props">Relations</button>
|
|
|
|
|
<button id="t-dep">Deprecated</button>
|
2026-07-26 21:28:14 +02:00
|
|
|
</div>
|
|
|
|
|
</header>
|
2026-07-29 09:03:59 +02:00
|
|
|
<main><div class="wrap" id="view"></div></main>
|
|
|
|
|
<div id="insp" role="dialog" aria-label="Details">
|
|
|
|
|
<button class="x" id="insp-x" aria-label="Close">×</button>
|
|
|
|
|
<div id="insp-body"></div>
|
|
|
|
|
</div>
|
2026-07-26 21:28:14 +02:00
|
|
|
<footer>
|
|
|
|
|
<span>Build artefact of __SRC__ — regenerate, never edit</span>
|
2026-07-29 09:03:59 +02:00
|
|
|
<span id="f-count"></span>
|
2026-07-26 21:28:14 +02:00
|
|
|
</footer>
|
|
|
|
|
<script>
|
|
|
|
|
var M = __MODEL__;
|
2026-07-29 09:03:59 +02:00
|
|
|
var byId = {};
|
|
|
|
|
M.nodes.forEach(function(n){ byId[n.id] = n; });
|
|
|
|
|
M.relations.concat(M.attributes, M.bridges).forEach(function(p){ byId[p.id] = p; });
|
|
|
|
|
var view = "scope", filt = {q:"", kind:""};
|
|
|
|
|
|
|
|
|
|
function esc(s){ return (s||"").replace(/&/g,"&").replace(/</g,"<"); }
|
|
|
|
|
function clean(s){ return (s||"").replace(" (deprecated)",""); }
|
|
|
|
|
document.getElementById("f-count").textContent =
|
|
|
|
|
M.nodes.filter(function(n){return !n.deprecated;}).length + " classes \u00b7 "
|
|
|
|
|
+ M.relations.filter(function(r){return !r.deprecated;}).length + " relations";
|
|
|
|
|
|
|
|
|
|
/* ============================================================ GRAPH */
|
|
|
|
|
var G = {mode:"strata", provs:[], sel:null, hot:null, labels:true,
|
|
|
|
|
layers:{},
|
|
|
|
|
view:{x:0,y:0,s:1}, nodes:[], edges:[]};
|
|
|
|
|
var NW=126, NH=32, LANEPAD=26, ROWGAP=15, BANDGAP=34, GUT=142;
|
|
|
|
|
|
|
|
|
|
function provColour(p){
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
return p === "captured" ? "#8A6A28" : p === "defined" ? "#1E4B9B" : "#6B6B6B";
|
2026-07-29 09:03:59 +02:00
|
|
|
}
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
/* Scoped relations: these carry no rdfs:domain or rdfs:range on purpose,
|
|
|
|
|
because each serves several classes with different targets and the scope
|
|
|
|
|
belongs in the SHACL shapes. Both the graph and the cards read this list,
|
|
|
|
|
so an edge drawn in one is never missing from the other. */
|
|
|
|
|
var SCOPED_PAIRS = [
|
|
|
|
|
["pr:belongsTo", "pr:SubDomain", "pr:DataDomain", "belongs to"],
|
|
|
|
|
["pr:belongsTo", "pr:BusinessObject", "pr:SubDomain", "belongs to"],
|
|
|
|
|
["pr:represents", "pr:DataObject", "pr:BusinessObject", "represents"],
|
|
|
|
|
["pr:represents", "pr:DataElement", "pr:BusinessConcept", "represents"],
|
|
|
|
|
["pr:monitoredBy", "pr:BusinessObject", "pr:DataSteward", "monitored by"],
|
|
|
|
|
["pr:owningDomain", "pr:DefinedObject", "pr:DataDomain", "owning domain"],
|
|
|
|
|
["pr:ownedBy", "pr:DefinedObject", "pr:Actor", "owned by"]
|
|
|
|
|
];
|
|
|
|
|
var ORDER = ["pr:MetaModelObject", "pr:DefinedObject", "pr:CapturedObject",
|
|
|
|
|
"pr:OwnershipLayerObject", "pr:DataDomain", "pr:SubDomain", "pr:BusinessObject",
|
|
|
|
|
"pr:BusinessLayerObject", "pr:BusinessConcept", "pr:Metric", "pr:KPI",
|
|
|
|
|
"pr:LogicalLayerObject", "pr:DataObject", "pr:DataElement",
|
|
|
|
|
"pr:DeliveryLayerObject", "pr:DataProduct", "pr:DataContract", "pr:DataInterface",
|
|
|
|
|
"pr:PhysicalLayerObject", "pr:System", "pr:Database", "pr:Schema",
|
|
|
|
|
"pr:DataStructure", "pr:BaseTable", "pr:View", "pr:ExternalTable", "pr:Field",
|
|
|
|
|
"pr:KeyConstraint", "pr:PrimaryKey", "pr:ForeignKeyConstraint", "pr:Transformation",
|
|
|
|
|
"pr:ConsumptionLayerObject", "pr:BIWorkspace", "pr:BIDataSource", "pr:BIField",
|
|
|
|
|
"pr:BIReport"];
|
|
|
|
|
var LANES = [["defined","Defined \u00b7 someone decided it","#1E4B9B"],
|
|
|
|
|
["captured","Captured \u00b7 read from a running system","#8A6A28"],
|
2026-07-29 09:03:59 +02:00
|
|
|
["structural","Structural","#6B6B6B"]];
|
|
|
|
|
function live(a){ return a.filter(function(x){ return !x.deprecated; }); }
|
|
|
|
|
function layerTitle(id){
|
|
|
|
|
var l = M.layers.filter(function(x){ return x.id === id; })[0];
|
|
|
|
|
return l ? l.title : id;
|
|
|
|
|
}
|
2026-07-26 21:28:14 +02:00
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
function buildGraph(){
|
|
|
|
|
var keep = {};
|
|
|
|
|
live(M.nodes).forEach(function(n){
|
|
|
|
|
if(!G.layers[n.layer]) return;
|
|
|
|
|
if(G.provs.length && G.provs.indexOf(n.prov) === -1) return;
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
/* row = rank inside the layer, so the hierarchy reads DOWNWARD:
|
|
|
|
|
layer root, then Domain, then Sub-domain, then Business object.
|
|
|
|
|
Depth alone put siblings side by side, which read left to right. */
|
|
|
|
|
var rank = ORDER.indexOf(n.id);
|
2026-07-29 09:03:59 +02:00
|
|
|
keep[n.id] = {n:n, x:0, y:0,
|
|
|
|
|
layer: M.layers.map(function(l){return l.id;}).indexOf(n.layer),
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
row: rank > -1 ? rank : 90 + Math.min(n.depth, 5)};
|
2026-07-29 09:03:59 +02:00
|
|
|
});
|
|
|
|
|
var E = [];
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
/* Properties deliberately left without rdfs:domain/range serve several
|
|
|
|
|
classes with different targets, and their scope lives in the SHACL
|
|
|
|
|
shapes instead. The graph reads domain and range, so those edges were
|
|
|
|
|
simply absent -- Business Object to Sub-domain among them. The pairs are
|
|
|
|
|
declared here, mirroring the shapes, rather than added to the T-Box where
|
|
|
|
|
they would over-constrain the property. */
|
|
|
|
|
SCOPED_PAIRS.forEach(function(sc){
|
|
|
|
|
if(byId[sc[0]] && !byId[sc[0]].deprecated && keep[sc[1]] && keep[sc[2]]
|
|
|
|
|
&& sc[1] !== sc[2])
|
|
|
|
|
E.push({s:sc[1], t:sc[2], l:sc[3], cross:false});
|
|
|
|
|
});
|
|
|
|
|
var _unused = [
|
|
|
|
|
["pr:belongsTo", "pr:SubDomain", "pr:DataDomain", "belongs to"],
|
|
|
|
|
["pr:belongsTo", "pr:BusinessObject", "pr:SubDomain", "belongs to"],
|
|
|
|
|
["pr:represents", "pr:DataObject", "pr:BusinessObject", "represents"],
|
|
|
|
|
["pr:represents", "pr:DataElement", "pr:BusinessConcept", "represents"],
|
|
|
|
|
["pr:monitoredBy", "pr:BusinessObject", "pr:DataSteward", "monitored by"],
|
|
|
|
|
["pr:owningDomain", "pr:DefinedObject", "pr:DataDomain", "owning domain"],
|
|
|
|
|
["pr:ownedBy", "pr:DefinedObject", "pr:Actor", "owned by"]
|
|
|
|
|
];
|
2026-07-29 09:03:59 +02:00
|
|
|
live(M.relations).forEach(function(r){
|
|
|
|
|
if(keep[r.domain] && keep[r.range] && r.domain !== r.range)
|
|
|
|
|
E.push({s:r.domain, t:r.range, l:r.label, cross:r.seam});
|
2026-07-26 21:28:14 +02:00
|
|
|
});
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
live(M.nodes).forEach(function(n){
|
|
|
|
|
if(n.parent && keep[n.id] && keep[n.parent])
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
E.push({s:n.id, t:n.parent, l:"type of", cross:false});
|
2026-07-29 09:03:59 +02:00
|
|
|
});
|
|
|
|
|
G.nodes = Object.keys(keep).map(function(k){ return keep[k]; });
|
|
|
|
|
G.edges = E;
|
|
|
|
|
return keep;
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
|
|
|
|
|
/* Strata = a matrix. Columns are domains, rows are meta model layers.
|
|
|
|
|
Everything owned by one domain sits in one vertical lane, so a cross-domain
|
|
|
|
|
dependency is literally a line leaving its column. */
|
|
|
|
|
function layoutGraph(idx){
|
|
|
|
|
if(G.mode === "network"){ layoutForce(idx); return; }
|
|
|
|
|
var lanes = LANES.map(function(L){ return L[0]; })
|
|
|
|
|
.filter(function(c){ return G.nodes.some(function(n){ return n.n.prov === c; }); });
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
/* One class per row. A lane is one column wide, so a layer reads DOWNWARDS:
|
|
|
|
|
the layer class on top, then Domain, then Sub-domain, then Business
|
|
|
|
|
Object. That is how a hierarchy is read; laying them left to right made
|
|
|
|
|
the reader scan across for something that descends. */
|
2026-07-29 09:03:59 +02:00
|
|
|
var laneW = {}, x = GUT;
|
|
|
|
|
G._lanes = [];
|
|
|
|
|
lanes.forEach(function(code){
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
var w = NW + LANEPAD*2;
|
|
|
|
|
laneW[code] = {x:x, w:w};
|
2026-07-29 09:03:59 +02:00
|
|
|
var L = LANES.filter(function(z){ return z[0] === code; })[0];
|
|
|
|
|
G._lanes.push({code:code, x:x, w:w, label:L[1], colour:L[2]});
|
|
|
|
|
x += w;
|
2026-07-26 21:28:14 +02:00
|
|
|
});
|
2026-07-29 09:03:59 +02:00
|
|
|
G._width = x;
|
|
|
|
|
|
|
|
|
|
var y = 0;
|
|
|
|
|
M.layers.forEach(function(b, li){
|
|
|
|
|
b._y = y;
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
var any = G.nodes.filter(function(n){ return n.layer === li; });
|
|
|
|
|
if(!any.length){ b._h = 0; return; }
|
|
|
|
|
var cursor = y + 36, rows = 0;
|
|
|
|
|
lanes.forEach(function(code){
|
|
|
|
|
var g = any.filter(function(n){ return n.n.prov === code; });
|
|
|
|
|
if(!g.length) return;
|
|
|
|
|
g.sort(function(p, q){
|
|
|
|
|
return ((ORDER.indexOf(p.n.id)+1)||99) - ((ORDER.indexOf(q.n.id)+1)||99)
|
|
|
|
|
|| p.n.label.localeCompare(q.n.label); });
|
|
|
|
|
g.forEach(function(n, i){
|
|
|
|
|
n.x = laneW[code].x + laneW[code].w/2;
|
|
|
|
|
n.y = cursor + i*(NH+ROWGAP) + NH/2;
|
2026-07-29 09:03:59 +02:00
|
|
|
});
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
rows = Math.max(rows, g.length);
|
2026-07-26 21:28:14 +02:00
|
|
|
});
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
b._h = rows*(NH+ROWGAP) + 48;
|
|
|
|
|
y += b._h + BANDGAP;
|
2026-07-29 09:03:59 +02:00
|
|
|
});
|
|
|
|
|
G._height = y;
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
|
|
|
|
|
function layoutForce(idx){
|
|
|
|
|
var links = G.edges.filter(function(e){ return idx[e.s] && idx[e.t]; });
|
|
|
|
|
G.nodes.forEach(function(n, i){
|
|
|
|
|
var a = i*2.399963;
|
|
|
|
|
n.x = 600 + Math.sqrt(i)*46*Math.cos(a);
|
|
|
|
|
n.y = 400 + Math.sqrt(i)*46*Math.sin(a);
|
|
|
|
|
});
|
|
|
|
|
var iters = G.nodes.length > 90 ? 200 : 380;
|
|
|
|
|
for(var it=0; it<iters; it++){
|
|
|
|
|
var k = 1 - it/iters;
|
|
|
|
|
for(var i=0; i<G.nodes.length; i++){
|
|
|
|
|
var a = G.nodes[i];
|
|
|
|
|
for(var j=i+1; j<G.nodes.length; j++){
|
|
|
|
|
var b = G.nodes[j], dx = b.x-a.x, dy = (b.y-a.y)*1.7;
|
|
|
|
|
var d2 = dx*dx+dy*dy || 1, f = Math.min(30000/d2, 8), d = Math.sqrt(d2);
|
|
|
|
|
a.x -= dx/d*f; a.y -= dy/d*f*.6; b.x += dx/d*f; b.y += dy/d*f*.6;
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
a.x += (600-a.x)*.003*k; a.y += (400-a.y)*.004*k;
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
|
|
|
|
links.forEach(function(e){
|
2026-07-29 09:03:59 +02:00
|
|
|
var a = idx[e.s], b = idx[e.t], dx = b.x-a.x, dy = b.y-a.y;
|
|
|
|
|
var d = Math.sqrt(dx*dx+dy*dy) || 1, f = (d-190)*.02*k;
|
|
|
|
|
a.x += dx/d*f; a.y += dy/d*f; b.x -= dx/d*f; b.y -= dy/d*f;
|
2026-07-26 21:28:14 +02:00
|
|
|
});
|
|
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
for(var pass=0; pass<40; pass++){
|
|
|
|
|
var moved = 0;
|
|
|
|
|
for(var i=0; i<G.nodes.length; i++){
|
|
|
|
|
for(var j=i+1; j<G.nodes.length; j++){
|
|
|
|
|
var a = G.nodes[i], b = G.nodes[j];
|
|
|
|
|
var ox = (NW+10)-Math.abs(b.x-a.x), oy = (NH+8)-Math.abs(b.y-a.y);
|
|
|
|
|
if(ox>0 && oy>0){
|
|
|
|
|
moved++;
|
|
|
|
|
if(ox<oy){ var s=(b.x>=a.x?1:-1)*ox/2; a.x-=s; b.x+=s; }
|
|
|
|
|
else { var t=(b.y>=a.y?1:-1)*oy/2; a.y-=t; b.y+=t; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(!moved) break;
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
function renderGraph(){
|
|
|
|
|
var idx = buildGraph();
|
|
|
|
|
G.nodes.forEach(function(n){ idx[n.n.id] = n; });
|
|
|
|
|
layoutGraph(idx);
|
|
|
|
|
|
|
|
|
|
var bL=document.getElementById("g-b"), eL=document.getElementById("g-e"),
|
|
|
|
|
lL=document.getElementById("g-l"), nL=document.getElementById("g-n");
|
|
|
|
|
bL.innerHTML=eL.innerHTML=lL.innerHTML=nL.innerHTML="";
|
|
|
|
|
var NS="http://www.w3.org/2000/svg";
|
|
|
|
|
function el(t,a){ var e=document.createElementNS(NS,t);
|
|
|
|
|
for(var k in a) e.setAttribute(k,a[k]); return e; }
|
|
|
|
|
|
|
|
|
|
if(G.mode === "strata" && G.nodes.length){
|
|
|
|
|
/* domain lanes: alternating tint, so a column reads as one domain */
|
|
|
|
|
(G._lanes||[]).forEach(function(l, i){
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
var isSubject = l.code === "defined";
|
2026-07-29 09:03:59 +02:00
|
|
|
bL.appendChild(el("rect", {x:l.x, y:-6, width:l.w, height:G._height+10,
|
|
|
|
|
fill:l.colour, opacity:isSubject ? .05 : .028}));
|
|
|
|
|
if(i) bL.appendChild(el("line", {x1:l.x, y1:-6, x2:l.x, y2:G._height+4,
|
|
|
|
|
stroke:"#D8D4C8", "stroke-width":1}));
|
|
|
|
|
var t = el("text", {x:l.x+l.w/2, y:-16, "text-anchor":"middle",
|
|
|
|
|
class:"lanelabel", fill:l.colour});
|
|
|
|
|
t.textContent = l.label;
|
|
|
|
|
bL.appendChild(t);
|
|
|
|
|
});
|
|
|
|
|
M.layers.forEach(function(b){
|
|
|
|
|
if(!b._h) return;
|
|
|
|
|
bL.appendChild(el("line", {x1:0, y1:b._y, x2:(G._width||900)+10, y2:b._y,
|
|
|
|
|
stroke:"#CFCABA", "stroke-width":1}));
|
|
|
|
|
var t=el("text",{x:GUT-16, y:b._y+18, class:"bandlabel", "text-anchor":"end",
|
|
|
|
|
fill:"#0A1F44"}); t.textContent=b.title; bL.appendChild(t);
|
|
|
|
|
var g=el("text",{x:GUT-16, y:b._y+31, class:"bandtag", "text-anchor":"end"});
|
2026-07-26 21:28:14 +02:00
|
|
|
g.textContent=b.tag; bL.appendChild(g);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
/* clip both ends to the node rectangle: an arrow head drawn to the centre
|
|
|
|
|
disappears under the box, which is what hid every marker until now */
|
|
|
|
|
function clip(from, to){
|
|
|
|
|
var dx = to.x-from.x, dy = to.y-from.y;
|
|
|
|
|
if(!dx && !dy) return {x:to.x, y:to.y};
|
|
|
|
|
var sx = (NW/2+5)/Math.abs(dx||1e-6), sy = (NH/2+5)/Math.abs(dy||1e-6);
|
|
|
|
|
var t = Math.min(sx, sy, 1);
|
|
|
|
|
return {x: to.x - dx*t, y: to.y - dy*t};
|
|
|
|
|
}
|
|
|
|
|
G.edges.forEach(function(e){
|
|
|
|
|
var a=idx[e.s], b=idx[e.t]; if(!a||!b) return;
|
|
|
|
|
var dx=b.x-a.x, dy=b.y-a.y;
|
|
|
|
|
var cx=a.x+dx/2+(-dy)*.09, cy=a.y+dy/2+dx*.035;
|
|
|
|
|
var p0=clip(b, a), p1=clip(a, b);
|
|
|
|
|
var p=el("path",{d:"M"+p0.x+","+p0.y+" Q"+cx+","+cy+" "+p1.x+","+p1.y,
|
|
|
|
|
class:"gedge"+(e.cross?" cross":"")});
|
|
|
|
|
p.dataset.a=e.s; p.dataset.b=e.t; eL.appendChild(p);
|
|
|
|
|
if(G.labels){
|
|
|
|
|
var mx=(a.x+2*cx+b.x)/4, my=(a.y+2*cy+b.y)/4;
|
|
|
|
|
var w=e.l.length*4.3+7;
|
|
|
|
|
var bg=el("rect",{x:mx-w/2, y:my-6, width:w, height:11, rx:2, class:"elbg"});
|
|
|
|
|
bg.dataset.a=e.s; bg.dataset.b=e.t; lL.appendChild(bg);
|
|
|
|
|
var tx=el("text",{x:mx, y:my+2.4, class:"elabel"});
|
|
|
|
|
tx.dataset.a=e.s; tx.dataset.b=e.t; tx.textContent=e.l; lL.appendChild(tx);
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
G.nodes.forEach(function(n){
|
|
|
|
|
var c = provColour(n.n.prov);
|
|
|
|
|
var g = el("g",{class:"gnode", tabindex:"0", role:"button",
|
|
|
|
|
transform:"translate("+(n.x-NW/2)+","+(n.y-NH/2)+")"});
|
|
|
|
|
g.dataset.id = n.n.id;
|
|
|
|
|
/* dashed outline = abstract: a class that exists to give its
|
|
|
|
|
specialisations a shape, never instantiated */
|
|
|
|
|
g.appendChild(el("rect",{width:NW, height:NH, rx:3, fill:"#FFFFFF",
|
|
|
|
|
stroke:c, "stroke-dasharray": n.n.abstract ? "5 3" : "none"}));
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
var lines = wrapText(n.n.shortLabel || n.n.label, 21);
|
2026-07-29 09:03:59 +02:00
|
|
|
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"});
|
|
|
|
|
e.textContent = t; g.appendChild(e);
|
2026-07-26 21:28:14 +02:00
|
|
|
});
|
|
|
|
|
nL.appendChild(g);
|
|
|
|
|
});
|
|
|
|
|
applyHot();
|
2026-07-29 09:03:59 +02:00
|
|
|
fitGraph();
|
|
|
|
|
var cd = G.edges.filter(function(e){ return e.cross; }).length;
|
|
|
|
|
document.getElementById("g-count").textContent =
|
|
|
|
|
G.nodes.length+" classes \u00b7 "+G.edges.length+" edges \u00b7 "+cd+" seam";
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
function wrapText(s,max){
|
|
|
|
|
var w=s.split(" "), out=[], cur="";
|
2026-07-26 21:28:14 +02:00
|
|
|
w.forEach(function(x){
|
2026-07-29 09:03:59 +02:00
|
|
|
if((cur+" "+x).trim().length>max){ if(cur) out.push(cur); cur=x; }
|
|
|
|
|
else cur=(cur+" "+x).trim(); });
|
|
|
|
|
if(cur) out.push(cur);
|
|
|
|
|
return out.slice(0,2);
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
|
|
|
|
function applyHot(){
|
2026-07-29 09:03:59 +02:00
|
|
|
var id = G.sel || G.hot;
|
|
|
|
|
var ns=document.querySelectorAll(".gnode"), es=document.querySelectorAll(".gedge"),
|
|
|
|
|
ls=document.querySelectorAll(".elabel,.elbg");
|
2026-07-26 21:28:14 +02:00
|
|
|
if(!id){
|
2026-07-29 09:03:59 +02:00
|
|
|
ns.forEach(function(n){ n.classList.remove("dim","sel"); });
|
|
|
|
|
es.forEach(function(e){ e.classList.remove("dim","hot"); });
|
|
|
|
|
ls.forEach(function(l){ l.classList.remove("dim"); });
|
2026-07-26 21:28:14 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
var keep={}; keep[id]=1;
|
|
|
|
|
G.edges.forEach(function(e){
|
|
|
|
|
if(e.s===id) keep[e.t]=1; if(e.t===id) keep[e.s]=1; });
|
|
|
|
|
es.forEach(function(p){
|
|
|
|
|
var on = p.dataset.a===id || p.dataset.b===id;
|
|
|
|
|
p.classList.toggle("hot",on); p.classList.toggle("dim",!on); });
|
|
|
|
|
ls.forEach(function(l){
|
|
|
|
|
l.classList.toggle("dim", !(l.dataset.a===id || l.dataset.b===id)); });
|
|
|
|
|
ns.forEach(function(n){
|
|
|
|
|
n.classList.toggle("dim", !keep[n.dataset.id]);
|
|
|
|
|
n.classList.toggle("sel", n.dataset.id===G.sel); });
|
|
|
|
|
}
|
|
|
|
|
function clearSel(){ G.sel=null; G.hot=null; applyHot(); }
|
|
|
|
|
function applyView(){
|
|
|
|
|
document.getElementById("g-vp").setAttribute("transform",
|
|
|
|
|
"translate("+G.view.x+","+G.view.y+") scale("+G.view.s+")");
|
|
|
|
|
}
|
|
|
|
|
function fitGraph(){
|
|
|
|
|
if(!G.nodes.length) return;
|
|
|
|
|
var svg=document.getElementById("g-svg"), r=svg.getBoundingClientRect();
|
|
|
|
|
var xs=G.nodes.map(function(n){return n.x;}), ys=G.nodes.map(function(n){return n.y;});
|
|
|
|
|
var pad = G.mode==="strata" ? GUT+20 : NW;
|
|
|
|
|
var mnx=Math.min.apply(null,xs)-pad, mxx=Math.max.apply(null,xs)+NW;
|
|
|
|
|
var mny=Math.min.apply(null,ys)-(G.mode==="strata"?66:60), mxy=Math.max.apply(null,ys)+50;
|
|
|
|
|
G.view.s=Math.min(r.width/(mxx-mnx), r.height/(mxy-mny), 1.15);
|
|
|
|
|
G.view.x=(r.width-(mxx-mnx)*G.view.s)/2-mnx*G.view.s;
|
|
|
|
|
G.view.y=(r.height-(mxy-mny)*G.view.s)/2-mny*G.view.s;
|
|
|
|
|
applyView();
|
|
|
|
|
}
|
|
|
|
|
function zoomG(f){
|
|
|
|
|
var svg=document.getElementById("g-svg"), r=svg.getBoundingClientRect();
|
|
|
|
|
var cx=r.width/2, cy=r.height/2;
|
|
|
|
|
var ns=Math.max(.1,Math.min(G.view.s*f,3));
|
|
|
|
|
G.view.x=cx-(cx-G.view.x)*(ns/G.view.s);
|
|
|
|
|
G.view.y=cy-(cy-G.view.y)*(ns/G.view.s);
|
|
|
|
|
G.view.s=ns; applyView();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------ OVERVIEW */
|
|
|
|
|
function kpi(v,l,w){ return '<div class="kpi'+(w?' warn':'')+'"><b>'+v+'</b><span>'+l+'</span></div>'; }
|
|
|
|
|
function renderScope(){
|
|
|
|
|
var cls=live(M.nodes), rel=live(M.relations), att=live(M.attributes);
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
var cur=cls.filter(function(n){return n.prov==="defined";});
|
|
|
|
|
var obs=cls.filter(function(n){return n.prov==="captured";});
|
2026-07-29 09:03:59 +02:00
|
|
|
var str=cls.filter(function(n){return n.prov==="structural";});
|
|
|
|
|
var seam=rel.filter(function(r){return r.seam;});
|
|
|
|
|
var dep=M.nodes.filter(function(n){return n.deprecated;})
|
|
|
|
|
.concat(M.relations.concat(M.attributes).filter(function(r){return r.deprecated;}));
|
|
|
|
|
var h=[];
|
|
|
|
|
h.push('<p class="lede">The layer says what an object <b>is</b>. The provenance says <b>how it got here</b>.</p>');
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
h.push('<p class="sublede">Defined objects are written by governance and reviewed through a pull request. '
|
|
|
|
|
+ 'Captured objects are read from Snowflake, Tableau and Looker, rebuilt on every run and never edited '
|
2026-07-29 09:03:59 +02:00
|
|
|
+ 'by hand. Without that split the ownership rules would demand an owner, a status and a version on every '
|
|
|
|
|
+ 'one of some fifty thousand warehouse columns.</p>');
|
|
|
|
|
h.push('<div class="kpis">'+kpi(cls.length,"Classes")+kpi(rel.length,"Relations")
|
|
|
|
|
+kpi(att.length,"Attributes")+kpi(M.bridges.length,"Bridge properties")
|
|
|
|
|
+kpi(seam.length,"Seam edges",true)+kpi(dep.length,"Deprecated",true)+'</div>');
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
h.push('<h2 class="sec">Defined against captured</h2><div class="bar">'
|
2026-07-29 09:03:59 +02:00
|
|
|
+'<div style="width:'+(cur.length/cls.length*100)+'%;background:#1E4B9B"></div>'
|
|
|
|
|
+'<div style="width:'+(obs.length/cls.length*100)+'%;background:#8A6A28"></div>'
|
|
|
|
|
+'<div style="width:'+(str.length/cls.length*100)+'%;background:#B9B4A7"></div></div>');
|
|
|
|
|
h.push('<div class="legend">'
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
+'<span><i style="background:#1E4B9B"></i>Defined \u2014 '+cur.length+'</span>'
|
|
|
|
|
+'<span><i style="background:#8A6A28"></i>Captured \u2014 '+obs.length+'</span>'
|
2026-07-29 09:03:59 +02:00
|
|
|
+'<span><i style="background:#B9B4A7"></i>Structural \u2014 '+str.length+'</span></div>');
|
|
|
|
|
h.push('<div class="note" style="max-width:880px;margin-bottom:26px"><b>Only '+seam.length
|
|
|
|
|
+' relations cross between the two.</b> '+seam.map(function(r){return esc(r.label);}).join(" and ")
|
|
|
|
|
+' \u2014 both proposed by the reconciliation job and confirmed by a steward. Everywhere else the '
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
+'graph is either pure governance or pure machine, which is what keeps the mapping effort bounded.</div>');
|
2026-07-29 09:03:59 +02:00
|
|
|
h.push('<h2 class="sec">The layers</h2><div class="grid">');
|
|
|
|
|
M.layers.forEach(function(l){
|
|
|
|
|
var g=cls.filter(function(n){return n.layer===l.id;});
|
|
|
|
|
if(!g.length) return;
|
|
|
|
|
var c=provColour(g[0].prov);
|
|
|
|
|
h.push('<div class="card" data-layer="'+l.id+'"><div class="top" style="background:'+c+'"></div>'
|
|
|
|
|
+'<div class="body"><div class="role" style="color:'+c+'">'+esc(l.tag)+'</div>'
|
|
|
|
|
+'<h3>'+esc(l.title)+'</h3><p>'+g.slice(0,5).map(function(n){return esc(n.label);}).join(" \u00b7 ")
|
|
|
|
|
+(g.length>5?" \u00b7 \u2026":"")+'</p><div class="nums"><div><b>'+g.length+'</b>classes</div>'
|
|
|
|
|
+'<div><b>'+rel.filter(function(r){return byId[r.domain]&&byId[r.domain].layer===l.id;}).length
|
|
|
|
|
+'</b>relations from</div></div></div></div>');
|
2026-07-26 21:28:14 +02:00
|
|
|
});
|
2026-07-29 09:03:59 +02:00
|
|
|
h.push('</div>');
|
|
|
|
|
return h.join("");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------ LAYERS */
|
|
|
|
|
function pill(t,c){ c=c||"#8A6A28";
|
|
|
|
|
return '<span class="pill" style="color:'+c+';border-color:'+c+';background:#FBF3E4">'+t+'</span>'; }
|
|
|
|
|
function renderModel(){
|
|
|
|
|
var h=['<p class="lede">Every class, in the layer it belongs to.</p>'
|
|
|
|
|
+'<p class="sublede">Abstract classes are never instantiated \u2014 they exist to give their '
|
|
|
|
|
+'specialisations a common parent and a common shape. Superseded terms are kept rather than deleted '
|
|
|
|
|
+'so assertions written against them keep classifying; they live on the Deprecated tab.</p>'];
|
|
|
|
|
M.layers.forEach(function(l){
|
|
|
|
|
var g=live(M.nodes).filter(function(n){return n.layer===l.id;});
|
|
|
|
|
if(!g.length) return;
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
g.sort(function(a,b){
|
|
|
|
|
return a.depth - b.depth
|
|
|
|
|
|| ((ORDER.indexOf(a.id)+1)||99) - ((ORDER.indexOf(b.id)+1)||99)
|
|
|
|
|
|| a.label.localeCompare(b.label); });
|
2026-07-29 09:03:59 +02:00
|
|
|
h.push('<h2 class="sec" style="color:'+provColour(g[0].prov)+'">'+esc(l.title)+' \u00b7 '+esc(l.tag)+'</h2>');
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
h.push('<table><thead><tr><th>Class</th><th>Provenance</th><th class="hide-s">Type of</th>'
|
2026-07-29 09:03:59 +02:00
|
|
|
+'<th class="hide-s">What it is for</th></tr></thead><tbody>');
|
|
|
|
|
g.forEach(function(n){
|
|
|
|
|
h.push('<tr data-id="'+n.id+'"><td><span class="dot" style="background:'+provColour(n.prov)
|
|
|
|
|
+'"></span><b>'+esc(n.label)+'</b>'+(n.abstract?' '+pill("abstract"):'')+'</td>'
|
|
|
|
|
+'<td class="mono">'+n.prov+'</td><td class="hide-s mono">'
|
|
|
|
|
+(byId[n.parent]?esc(clean(byId[n.parent].label)):"\u2014")+'</td>'
|
|
|
|
|
+'<td class="hide-s">'+esc((n.comment||"").split(". ")[0])+'</td></tr>');
|
|
|
|
|
});
|
|
|
|
|
h.push('</tbody></table>');
|
2026-07-26 21:28:14 +02:00
|
|
|
});
|
2026-07-29 09:03:59 +02:00
|
|
|
return h.join("");
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
|
|
|
|
|
2026-07-29 09:03:59 +02:00
|
|
|
/* ------------------------------------------------------------ RELATIONS */
|
|
|
|
|
function renderProps(){
|
|
|
|
|
var h=['<p class="lede">'+live(M.relations).length+' relations and '+live(M.attributes).length+' attributes.</p>'
|
|
|
|
|
+'<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>'];
|
|
|
|
|
h.push('<div class="filters"><input id="q" type="search" placeholder="filter\u2026">'
|
|
|
|
|
+'<button data-k="" class="on">All</button><button data-k="Relation">Relations</button>'
|
|
|
|
|
+'<button data-k="Attribute">Attributes</button><button data-k="Bridge">Bridges</button>'
|
|
|
|
|
+'</div><div id="tbl"></div>');
|
|
|
|
|
return h.join("");
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
function propRows(){
|
|
|
|
|
var all=M.relations.concat(M.attributes,M.bridges).filter(function(p){
|
|
|
|
|
if(p.deprecated) return false;
|
|
|
|
|
if(filt.kind && p.kind!==filt.kind) return false;
|
|
|
|
|
if(filt.q && (p.label+" "+p.id+" "+(p.comment||"")).toLowerCase().indexOf(filt.q)===-1) return false;
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
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>'
|
|
|
|
|
+'<th>Traits</th></tr></thead><tbody>'];
|
|
|
|
|
all.forEach(function(p){
|
|
|
|
|
var t=[];
|
|
|
|
|
if(p.functional) t.push("exactly one");
|
|
|
|
|
if(p.transitive) t.push("transitive");
|
|
|
|
|
if(p.seam) t.push("seam");
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
if(p.authoring) t.push(p.authoring.toLowerCase().replace(/_/g," "));
|
2026-07-29 09:03:59 +02:00
|
|
|
h.push('<tr data-id="'+p.id+'"><td><b>'+esc(p.label)+'</b><div class="mono">'+esc(p.id)+'</div></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))
|
|
|
|
|
:'<span class="mono">'+esc(p.range||"\u2014")+'</span>')+'</td>'
|
|
|
|
|
+'<td class="mono">'+(t.join(" \u00b7 ")||"\u2014")+'</td></tr>');
|
|
|
|
|
});
|
|
|
|
|
h.push('</tbody></table><p class="sublede" style="margin-top:12px">'+all.length+' shown.</p>');
|
|
|
|
|
return h.join("");
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------ DEPRECATED */
|
|
|
|
|
function renderDep(){
|
|
|
|
|
var dep=M.nodes.filter(function(n){return n.deprecated;})
|
|
|
|
|
.concat(M.relations.concat(M.attributes).filter(function(r){return r.deprecated;}));
|
|
|
|
|
var h=['<p class="lede">'+dep.length+' terms kept, none of them to be used.</p>'
|
|
|
|
|
+'<p class="sublede">Rule LC-002: a superseded term is deprecated rather than deleted, so assertions '
|
|
|
|
|
+'written against it keep classifying while a migration runs. The obligation is symmetric \u2014 a '
|
|
|
|
|
+'deprecation that never ends stops meaning anything, which is why Table was removed outright in v1.2 '
|
|
|
|
|
+'once nothing used it.</p>'];
|
|
|
|
|
h.push('<table><thead><tr><th>Term</th><th>Kind</th><th>Replaced by</th>'
|
|
|
|
|
+'<th class="hide-s">Why</th></tr></thead><tbody>');
|
|
|
|
|
dep.forEach(function(d){
|
|
|
|
|
h.push('<tr class="dep" data-id="'+d.id+'"><td><b>'+esc(clean(d.label))+'</b>'
|
|
|
|
|
+'<div class="mono">'+esc(d.id)+'</div></td><td class="mono">'
|
|
|
|
|
+(d.kind==="Class"?"class":d.kind.toLowerCase())+'</td><td>'
|
|
|
|
|
+(byId[d.replacedBy]?esc(clean(byId[d.replacedBy].label)):'<span class="mono">\u2014</span>')
|
|
|
|
|
+'</td><td class="hide-s">'+esc((d.comment||"").split(". ")[0])+'</td></tr>');
|
|
|
|
|
});
|
|
|
|
|
h.push('</tbody></table>');
|
|
|
|
|
return h.join("");
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------ SHELL */
|
|
|
|
|
function renderGraphShell(){
|
|
|
|
|
var h=['<p class="lede">The meta model as a graph.</p>'
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
+'<p class="sublede">Columns are provenance: <b style="color:#1E4B9B">defined</b> on the left, '
|
|
|
|
|
+'<b style="color:#8A6A28">captured</b> on the right. Rows are layers. A <b>navy edge</b> crosses '
|
2026-07-29 09:03:59 +02:00
|
|
|
+'between the two \u2014 that is the seam, the only place a steward confirms anything. Dashed outlines '
|
|
|
|
|
+'are abstract classes, never instantiated.</p>'];
|
|
|
|
|
h.push('<div class="gwrap"><svg id="g-svg" xmlns="http://www.w3.org/2000/svg"><defs>'
|
|
|
|
|
+'<marker id="ga" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" orient="auto">'
|
|
|
|
|
+'<path d="M0,0 L10,5 L0,10 z" fill="#A6ABB5"/></marker>'
|
|
|
|
|
+'<marker id="gg" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" orient="auto">'
|
|
|
|
|
+'<path d="M0,0 L10,5 L0,10 z" fill="#0A1F44"/></marker>'
|
|
|
|
|
+'<marker id="gn" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" orient="auto">'
|
|
|
|
|
+'<path d="M0,0 L10,5 L0,10 z" fill="#B8935A"/></marker></defs>'
|
|
|
|
|
+'<g id="g-vp"><g id="g-b"></g><g id="g-e"></g><g id="g-l"></g><g id="g-n"></g></g></svg>');
|
|
|
|
|
h.push('<div class="gctl"><div class="seg"><button id="g-strata" class="on">Strata</button>'
|
|
|
|
|
+'<button id="g-network">Network</button></div><div class="gsec">Layers</div>'
|
|
|
|
|
+ M.layers.map(function(l){
|
|
|
|
|
var n=live(M.nodes).filter(function(x){return x.layer===l.id;}).length;
|
|
|
|
|
if(!n) return "";
|
|
|
|
|
return '<label><input type="checkbox" data-ly="'+l.id+'"'+(G.layers[l.id]?" checked":"")
|
|
|
|
|
+'> '+esc(l.title)+' <span class="mono">'+n+'</span></label>'; }).join("")
|
|
|
|
|
+'<div class="gsec">Provenance</div>'
|
|
|
|
|
+ LANES.map(function(L){
|
|
|
|
|
var n=live(M.nodes).filter(function(x){return x.prov===L[0];}).length;
|
|
|
|
|
if(!n) return "";
|
|
|
|
|
return '<label><input type="checkbox" data-pv="'+L[0]+'"'
|
|
|
|
|
+(!G.provs.length||G.provs.indexOf(L[0])>-1?" checked":"")+'> <i style="background:'+L[2]
|
|
|
|
|
+'"></i>'+L[0]+' <span class="mono">'+n+'</span></label>'; }).join("")
|
|
|
|
|
+'<div class="gsec">Options</div>'
|
|
|
|
|
+'<label><input type="checkbox" id="g-lab" checked> Relation names</label>'
|
|
|
|
|
+'<div class="mono" id="g-count" style="margin-top:8px"></div></div>');
|
|
|
|
|
h.push('<div class="gzoom"><button id="g-in">+</button><button id="g-out">\u2212</button>'
|
|
|
|
|
+'<button id="g-fit" style="font-size:12px">\u21ba</button></div>');
|
|
|
|
|
h.push('<div class="ghint">drag to pan \u00b7 scroll to zoom \u00b7 click empty space to clear</div></div>');
|
|
|
|
|
return h.join("");
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
|
|
|
|
|
function wireGraph(){
|
|
|
|
|
renderGraph();
|
|
|
|
|
var svg = document.getElementById("g-svg");
|
|
|
|
|
function mode(m){
|
|
|
|
|
G.mode = m;
|
|
|
|
|
document.getElementById("g-strata").classList.toggle("on", m==="strata");
|
|
|
|
|
document.getElementById("g-network").classList.toggle("on", m==="network");
|
|
|
|
|
renderGraph();
|
|
|
|
|
}
|
|
|
|
|
document.getElementById("g-strata").onclick = function(){ mode("strata"); };
|
|
|
|
|
document.getElementById("g-network").onclick = function(){ mode("network"); };
|
|
|
|
|
document.querySelectorAll("[data-ly]").forEach(function(cb){
|
|
|
|
|
cb.onchange = function(){ G.layers[cb.dataset.ly] = cb.checked; renderGraph(); };
|
|
|
|
|
});
|
|
|
|
|
document.getElementById("g-lab").onchange = function(){
|
|
|
|
|
G.labels = this.checked; renderGraph(); };
|
|
|
|
|
document.querySelectorAll("[data-pv]").forEach(function(cb){
|
|
|
|
|
cb.onchange = function(){
|
|
|
|
|
var on = [];
|
|
|
|
|
document.querySelectorAll("[data-pv]").forEach(function(x){
|
|
|
|
|
if(x.checked) on.push(x.dataset.pv); });
|
|
|
|
|
G.provs = (on.length === LANES.length) ? [] : on;
|
|
|
|
|
renderGraph();
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
document.getElementById("g-in").onclick = function(){ zoomG(1.25); };
|
|
|
|
|
document.getElementById("g-out").onclick = function(){ zoomG(.8); };
|
|
|
|
|
document.getElementById("g-fit").onclick = fitGraph;
|
|
|
|
|
|
|
|
|
|
var drag=false, moved=false, ds, vs, pinch=null;
|
|
|
|
|
svg.addEventListener("mousedown", function(e){
|
|
|
|
|
drag=true; moved=false; svg.classList.add("drag");
|
|
|
|
|
ds={x:e.clientX,y:e.clientY}; vs={x:G.view.x,y:G.view.y}; });
|
|
|
|
|
window.addEventListener("mousemove", function(e){
|
|
|
|
|
if(!drag) return;
|
|
|
|
|
if(Math.abs(e.clientX-ds.x)+Math.abs(e.clientY-ds.y) > 4) moved=true;
|
|
|
|
|
G.view.x=vs.x+(e.clientX-ds.x); G.view.y=vs.y+(e.clientY-ds.y); applyView(); });
|
|
|
|
|
window.addEventListener("mouseup", function(){
|
|
|
|
|
drag=false; svg.classList.remove("drag"); });
|
|
|
|
|
/* click on empty canvas clears the selection -- a highlight that cannot be
|
|
|
|
|
dismissed traps the reader in one node */
|
|
|
|
|
svg.addEventListener("click", function(e){
|
|
|
|
|
if(!moved && !e.target.closest(".gnode")){ clearSel(); insp.classList.remove("open"); }
|
|
|
|
|
});
|
|
|
|
|
svg.addEventListener("wheel", function(e){ e.preventDefault();
|
|
|
|
|
var f=e.deltaY<0?1.1:.9, r=svg.getBoundingClientRect();
|
|
|
|
|
var mx=e.clientX-r.left, my=e.clientY-r.top;
|
|
|
|
|
var ns=Math.max(.1,Math.min(G.view.s*f,3));
|
|
|
|
|
G.view.x=mx-(mx-G.view.x)*(ns/G.view.s); G.view.y=my-(my-G.view.y)*(ns/G.view.s);
|
|
|
|
|
G.view.s=ns; applyView(); }, {passive:false});
|
|
|
|
|
svg.addEventListener("touchstart", function(e){
|
|
|
|
|
if(e.touches.length===1){ drag=true; moved=false;
|
|
|
|
|
ds={x:e.touches[0].clientX,y:e.touches[0].clientY}; vs={x:G.view.x,y:G.view.y}; }
|
|
|
|
|
else if(e.touches.length===2){ drag=false;
|
|
|
|
|
var dx=e.touches[0].clientX-e.touches[1].clientX,
|
|
|
|
|
dy=e.touches[0].clientY-e.touches[1].clientY;
|
|
|
|
|
pinch={d:Math.sqrt(dx*dx+dy*dy), s:G.view.s}; } }, {passive:true});
|
|
|
|
|
svg.addEventListener("touchmove", function(e){
|
|
|
|
|
if(e.touches.length===1 && drag){ moved=true;
|
|
|
|
|
G.view.x=vs.x+(e.touches[0].clientX-ds.x);
|
|
|
|
|
G.view.y=vs.y+(e.touches[0].clientY-ds.y); applyView(); e.preventDefault(); }
|
|
|
|
|
else if(e.touches.length===2 && pinch){
|
|
|
|
|
var dx=e.touches[0].clientX-e.touches[1].clientX,
|
|
|
|
|
dy=e.touches[0].clientY-e.touches[1].clientY;
|
|
|
|
|
var nd=Math.sqrt(dx*dx+dy*dy), ns=Math.max(.1,Math.min(pinch.s*(nd/pinch.d),3));
|
|
|
|
|
var r=svg.getBoundingClientRect();
|
|
|
|
|
G.view.x=r.width/2-(r.width/2-G.view.x)*(ns/G.view.s);
|
|
|
|
|
G.view.y=r.height/2-(r.height/2-G.view.y)*(ns/G.view.s);
|
|
|
|
|
G.view.s=ns; applyView(); e.preventDefault(); } }, {passive:false});
|
|
|
|
|
svg.addEventListener("touchend", function(){ drag=false; pinch=null; });
|
|
|
|
|
|
|
|
|
|
var nL = document.getElementById("g-n");
|
|
|
|
|
nL.addEventListener("click", function(e){
|
|
|
|
|
var g = e.target.closest(".gnode"); if(!g) return;
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
if(G.sel === g.dataset.id){ clearSel(); insp.classList.remove("open"); return; }
|
|
|
|
|
G.sel = g.dataset.id; G.hot = null; applyHot(); openCard(g.dataset.id, true);
|
|
|
|
|
});
|
|
|
|
|
nL.addEventListener("keydown", function(e){
|
|
|
|
|
var g = e.target.closest(".gnode");
|
|
|
|
|
if(g && (e.key==="Enter"||e.key===" ")){ e.preventDefault();
|
|
|
|
|
G.sel=g.dataset.id; applyHot(); openCard(g.dataset.id, true); } });
|
|
|
|
|
nL.addEventListener("mouseover", function(e){
|
|
|
|
|
var g=e.target.closest(".gnode"); if(g && !G.sel){ G.hot=g.dataset.id; applyHot(); } });
|
|
|
|
|
nL.addEventListener("mouseout", function(e){
|
|
|
|
|
if(!G.sel && (!e.relatedTarget || !e.relatedTarget.closest
|
|
|
|
|
|| !e.relatedTarget.closest(".gnode"))){ G.hot=null; applyHot(); } });
|
|
|
|
|
window.addEventListener("resize", fitGraph);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------ INSPECTOR */
|
|
|
|
|
var insp=document.getElementById("insp"), ibody=document.getElementById("insp-body");
|
|
|
|
|
var HIST=[];
|
|
|
|
|
var CHILDREN={}, FROM={}, TO={}, ATTRS={};
|
|
|
|
|
M.nodes.forEach(function(n){
|
|
|
|
|
if(n.parent) (CHILDREN[n.parent]=CHILDREN[n.parent]||[]).push(n.id); });
|
|
|
|
|
M.relations.forEach(function(r){
|
|
|
|
|
if(r.deprecated) return;
|
|
|
|
|
if(r.domain) (FROM[r.domain]=FROM[r.domain]||[]).push(r.id);
|
|
|
|
|
if(r.range) (TO[r.range]=TO[r.range]||[]).push(r.id); });
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
/* The same scoped pairs the graph uses. Without them a Business Object shows
|
|
|
|
|
no incoming relation at all, because belongsTo and represents carry no
|
|
|
|
|
rdfs:domain -- their scope lives in the shapes. The card was silent about
|
|
|
|
|
edges the graph was drawing. */
|
|
|
|
|
SCOPED_PAIRS.forEach(function(sc){
|
|
|
|
|
if(!byId[sc[0]] || byId[sc[0]].deprecated) return;
|
|
|
|
|
(FROM[sc[1]] = FROM[sc[1]]||[]).push(sc[0]);
|
|
|
|
|
(TO[sc[2]] = TO[sc[2]]||[]).push(sc[0]);
|
|
|
|
|
});
|
|
|
|
|
/* the scoped properties again: without this a Sub-domain card showed no
|
|
|
|
|
incoming Business Object, and a Business Object card had no incoming list
|
|
|
|
|
at all, because neither relation declares a domain or a range */
|
|
|
|
|
[["pr:belongsTo","pr:SubDomain","pr:DataDomain"],
|
|
|
|
|
["pr:belongsTo","pr:BusinessObject","pr:SubDomain"],
|
|
|
|
|
["pr:represents","pr:DataObject","pr:BusinessObject"],
|
|
|
|
|
["pr:represents","pr:DataElement","pr:BusinessConcept"],
|
|
|
|
|
["pr:monitoredBy","pr:BusinessObject","pr:DataSteward"],
|
|
|
|
|
["pr:owningDomain","pr:DefinedObject","pr:DataDomain"],
|
|
|
|
|
["pr:ownedBy","pr:DefinedObject","pr:Actor"]].forEach(function(sc){
|
|
|
|
|
if(!byId[sc[0]] || byId[sc[0]].deprecated) return;
|
|
|
|
|
if((FROM[sc[1]]||[]).indexOf(sc[0])===-1) (FROM[sc[1]]=FROM[sc[1]]||[]).push(sc[0]);
|
|
|
|
|
if((TO[sc[2]]||[]).indexOf(sc[0])===-1) (TO[sc[2]]=TO[sc[2]]||[]).push(sc[0]);
|
|
|
|
|
});
|
2026-07-29 09:03:59 +02:00
|
|
|
M.attributes.forEach(function(a){
|
|
|
|
|
if(a.deprecated||!a.domain) return;
|
|
|
|
|
(ATTRS[a.domain]=ATTRS[a.domain]||[]).push(a.id); });
|
|
|
|
|
|
|
|
|
|
function openCard(id, reset){
|
|
|
|
|
if(reset) HIST=[];
|
|
|
|
|
var cur=HIST[HIST.length-1];
|
|
|
|
|
if(cur&&cur!==id) HIST.push(id); else if(!cur) HIST.push(id);
|
|
|
|
|
paint(id);
|
|
|
|
|
}
|
|
|
|
|
function back(){
|
|
|
|
|
HIST.pop();
|
|
|
|
|
var prev=HIST[HIST.length-1];
|
|
|
|
|
if(prev){ paint(prev); if(G.sel){ G.sel=prev; applyHot(); } }
|
|
|
|
|
else insp.classList.remove("open");
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
function open(id){ openCard(id,true); }
|
|
|
|
|
|
|
|
|
|
function paint(id){
|
|
|
|
|
var n=byId[id]; if(!n) return;
|
|
|
|
|
var h=[];
|
|
|
|
|
if(HIST.length>1)
|
|
|
|
|
h.push('<button class="backlink" id="i-back">\u2190 back to '
|
|
|
|
|
+ esc(clean(byId[HIST[HIST.length-2]].label)) + '</button>');
|
|
|
|
|
var kindLabel = n.kind==="Class" ? "Class" : n.kind==="Relation" ? "Relation"
|
|
|
|
|
: n.kind==="Attribute" ? "Attribute" : "Bridge property";
|
|
|
|
|
h.push('<div class="kind" style="color:'+provColour(n.prov||"structural")+'">'
|
|
|
|
|
+ kindLabel + (n.layer ? ' \u00b7 ' + esc(layerTitle(n.layer)) : '') + '</div>');
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
h.push('<h3>'+esc(n.shortLabel || clean(n.label))+'</h3>');
|
|
|
|
|
if(n.shortLabel && n.shortLabel !== clean(n.label))
|
|
|
|
|
h.push('<div class="row" style="border:0;padding:0 0 6px"><span class="mono">'
|
|
|
|
|
+ esc(clean(n.label)) + '</span><span></span></div>');
|
2026-07-29 09:03:59 +02:00
|
|
|
h.push('<div class="iri">'+esc(n.id)+'</div>');
|
|
|
|
|
if(n.deprecated) h.push('<div class="note"><b>Deprecated.</b> Kept so assertions written '
|
|
|
|
|
+ 'against it keep classifying (LC-002)'
|
|
|
|
|
+ (byId[n.replacedBy] ? ', replaced by <b>'+esc(clean(byId[n.replacedBy].label))+'</b>' : '')
|
|
|
|
|
+ '. Not to be used in new work.</div>');
|
|
|
|
|
|
|
|
|
|
/* 1. attributes, always first and always in the same place */
|
|
|
|
|
var rows=[];
|
|
|
|
|
function at(l,v){ if(v) rows.push([l,v]); }
|
|
|
|
|
if(n.kind==="Class"){
|
|
|
|
|
at("Provenance", n.prov);
|
|
|
|
|
at("Instantiated", n.abstract ? "never \u2014 abstract" : "yes");
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
at("Authoring mode", (n.authoring||"").toLowerCase().replace(/_/g," "));
|
2026-07-29 09:03:59 +02:00
|
|
|
at("Acronym", n.acronym);
|
|
|
|
|
} else {
|
|
|
|
|
at("Cardinality", n.functional ? "exactly one" : "many");
|
|
|
|
|
at("Transitive", n.transitive ? "yes" : "");
|
|
|
|
|
at("Crosses the seam", n.seam ? "yes" : "");
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
at("Authoring mode", (n.authoring||"").toLowerCase().replace(/_/g," "));
|
2026-07-29 09:03:59 +02:00
|
|
|
if(!byId[n.range]) at("Range", n.range);
|
|
|
|
|
if(!n.domain) at("Domain", "any \u2014 scoped by SHACL per class");
|
|
|
|
|
}
|
|
|
|
|
if(rows.length) h.push('<h4>Attributes</h4>'+rows.map(function(r){
|
|
|
|
|
return '<div class="row"><span>'+r[0]+'</span><span class="mono">'+esc(r[1])+'</span></div>';
|
|
|
|
|
}).join(""));
|
|
|
|
|
|
|
|
|
|
/* 2. what it is for */
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
h.push('<h4>What it is for</h4><p>' + (n.comment
|
|
|
|
|
? esc(n.comment).replace(/\n/g,"<br>")
|
|
|
|
|
: '<em style="color:var(--muted)">No definition yet \u2014 this term needs one '
|
|
|
|
|
+ 'before it can be published.</em>') + '</p>');
|
2026-07-29 09:03:59 +02:00
|
|
|
if(n.harvest) h.push('<h4>Harvested from</h4><div class="harv">'+esc(n.harvest)+'</div>');
|
|
|
|
|
|
|
|
|
|
/* 3. related terms, from the coarsest down */
|
|
|
|
|
function link(label, ids){
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
/* deprecated terms live on their own tab; showing them in a link list
|
|
|
|
|
invites someone to use one */
|
|
|
|
|
ids=(ids||[]).filter(function(i){
|
|
|
|
|
return byId[i] && (!byId[i].deprecated || label === "Replaced by"); });
|
2026-07-29 09:03:59 +02:00
|
|
|
if(!ids.length) return;
|
|
|
|
|
h.push('<h4>'+label+'</h4>');
|
|
|
|
|
ids.slice(0,16).forEach(function(i){
|
|
|
|
|
h.push('<div class="row"><button class="jump" data-j="'+i+'">'+esc(clean(byId[i].label))
|
|
|
|
|
+'</button><span class="mono">'+(byId[i].deprecated?"deprecated"
|
|
|
|
|
:byId[i].kind==="Class"?(byId[i].prov||""):byId[i].kind.toLowerCase())+'</span></div>');
|
|
|
|
|
});
|
|
|
|
|
if(ids.length>16) h.push('<div class="row"><span class="mono">\u2026 and '
|
|
|
|
|
+(ids.length-16)+' more</span><span></span></div>');
|
|
|
|
|
}
|
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
|
|
|
link("Type of", [n.parent, n.subPropertyOf]);
|
|
|
|
|
link("Types", CHILDREN[n.id]);
|
2026-07-29 09:03:59 +02:00
|
|
|
link("Replaced by", [n.replacedBy]);
|
|
|
|
|
link("From", [n.domain]);
|
|
|
|
|
link("To", [n.range]);
|
|
|
|
|
link("Inverse of", [n.inverseOf]);
|
|
|
|
|
link("Relations from here", (FROM[n.id]||[]));
|
|
|
|
|
link("Relations to here", (TO[n.id]||[]));
|
|
|
|
|
link("Attributes carried", ATTRS[n.id]);
|
|
|
|
|
if(n.members && n.members.length)
|
|
|
|
|
h.push('<h4>Allowed values</h4>'+n.members.map(function(m){
|
|
|
|
|
return '<div class="row"><span>'+esc(m.label)+'</span><span class="mono">'
|
|
|
|
|
+esc(m.id)+'</span></div>'; }).join(""));
|
|
|
|
|
|
|
|
|
|
ibody.innerHTML=h.join("");
|
|
|
|
|
insp.classList.add("open");
|
|
|
|
|
var bk=document.getElementById("i-back"); if(bk) bk.onclick=back;
|
|
|
|
|
ibody.querySelectorAll(".jump").forEach(function(b){
|
|
|
|
|
b.onclick=function(){ HIST.push(b.dataset.j); paint(b.dataset.j);
|
|
|
|
|
if(G.sel){ G.sel=b.dataset.j; applyHot(); } }; });
|
|
|
|
|
}
|
|
|
|
|
document.getElementById("insp-x").onclick=function(){
|
|
|
|
|
insp.classList.remove("open"); clearSel(); };
|
|
|
|
|
document.addEventListener("keydown",function(e){
|
|
|
|
|
if(e.key==="Escape"){ insp.classList.remove("open"); clearSel(); } });
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------ WIRING */
|
|
|
|
|
var host=document.getElementById("view");
|
|
|
|
|
function render(){
|
|
|
|
|
host.innerHTML = view==="scope" ? renderScope()
|
|
|
|
|
: view==="model" ? renderModel()
|
|
|
|
|
: view==="graph" ? renderGraphShell()
|
|
|
|
|
: view==="props" ? renderProps() : renderDep();
|
|
|
|
|
if(view==="graph") wireGraph();
|
|
|
|
|
if(view==="props"){
|
|
|
|
|
document.getElementById("tbl").innerHTML=propRows();
|
|
|
|
|
document.getElementById("q").oninput=function(){
|
|
|
|
|
filt.q=this.value.trim().toLowerCase();
|
|
|
|
|
document.getElementById("tbl").innerHTML=propRows(); bindRows(); };
|
|
|
|
|
host.querySelectorAll("[data-k]").forEach(function(b){
|
|
|
|
|
b.onclick=function(){
|
|
|
|
|
filt.kind=b.dataset.k;
|
|
|
|
|
host.querySelectorAll("[data-k]").forEach(function(x){
|
|
|
|
|
x.classList.toggle("on",x===b); });
|
|
|
|
|
document.getElementById("tbl").innerHTML=propRows(); bindRows(); }; });
|
|
|
|
|
}
|
|
|
|
|
bindRows();
|
|
|
|
|
host.querySelectorAll(".card").forEach(function(c){
|
|
|
|
|
c.onclick=function(){
|
|
|
|
|
M.layers.forEach(function(l){ G.layers[l.id]=(l.id===c.dataset.layer); });
|
|
|
|
|
setView("graph"); }; });
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
function bindRows(){
|
|
|
|
|
host.querySelectorAll("tr[data-id]").forEach(function(tr){
|
|
|
|
|
tr.onclick=function(){ open(tr.dataset.id); }; });
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
function setView(v){
|
|
|
|
|
view=v;
|
|
|
|
|
["scope","model","graph","props","dep"].forEach(function(k){
|
|
|
|
|
document.getElementById("t-"+k).classList.toggle("on",k===v); });
|
|
|
|
|
render();
|
|
|
|
|
document.querySelector("main").scrollTop=0;
|
2026-07-26 21:28:14 +02:00
|
|
|
}
|
2026-07-29 09:03:59 +02:00
|
|
|
["scope","model","graph","props","dep"].forEach(function(k){
|
|
|
|
|
document.getElementById("t-"+k).onclick=function(){
|
|
|
|
|
if(k==="graph") M.layers.forEach(function(l){ G.layers[l.id]=(l.id!=="vocab"); });
|
|
|
|
|
setView(k); }; });
|
|
|
|
|
render();
|
|
|
|
|
|
2026-07-26 21:28:14 +02:00
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|