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
This commit is contained in:
@@ -35,13 +35,13 @@ DOMAIN_STYLE = OrderedDict([
|
||||
])
|
||||
|
||||
MULTI = {"composedOf", "usesConcept", "hasElement", "hasMetric", "measures",
|
||||
"computedBy", "references", "hasGrainElement", "monitoredBy", "hasSynonym",
|
||||
"computedBy", "qualifiedBy", "hasGrainElement", "monitoredBy", "hasSynonym",
|
||||
"hasUnit", "hasSource", "packages", "exposes"}
|
||||
|
||||
SUBJECT_DOMAIN = "DD_06" # the hub owner: everything else is a dependency
|
||||
|
||||
# Every user-facing label in one place. Hyphenated throughout, and never the
|
||||
# raw class name: "Physical Relation" and "Physicalized in" told a business
|
||||
# raw class name: "Data Structure" and "Physicalized in" told a business
|
||||
# reader nothing.
|
||||
LABELS = {
|
||||
"DataDomain": "Domain", "SubDomain": "Sub-domain",
|
||||
@@ -49,14 +49,17 @@ LABELS = {
|
||||
"Metric": "Metric", "DataObject": "Data object", "DataElement": "Data element",
|
||||
"BaseTable": "Base table", "Field": "Field",
|
||||
"DataProduct": "Data product", "DataContract": "Data contract",
|
||||
"System": "System",
|
||||
"DataInterface": "Data interface",
|
||||
}
|
||||
|
||||
# Which ownership roles a card shows. The Steward section is gone: one section,
|
||||
# different roles per object, so the reader always looks in the same place.
|
||||
ROLES = {
|
||||
"DataDomain": [("Domain owner", "owner"), ("Data-governance lead", "dgl")],
|
||||
"SubDomain": [("Sub-domain owner", "subOwner"), ("Data-governance lead", "dgl_up")],
|
||||
# role labels mirror the short labels the T-Box carries on the actor
|
||||
# classes, so the two viewers name the same role the same way
|
||||
"DataDomain": [("Domain owner", "owner"), ("Data governance lead", "dgl")],
|
||||
"SubDomain": [("Sub-domain owner", "subOwner"), ("Data governance lead", "dgl_up")],
|
||||
"BusinessObject": [("Steward", "steward")],
|
||||
"BusinessConcept": [("Steward", "steward")],
|
||||
"Metric": [("Steward", "steward")],
|
||||
@@ -180,7 +183,10 @@ def build(subjects):
|
||||
nodes[subj] = {
|
||||
"id": subj,
|
||||
"kind": t,
|
||||
# the model carries both names now: hasShortLabel for screens,
|
||||
# hasName for the record. The viewer stops keeping its own list.
|
||||
"label": LABELS.get(t, t),
|
||||
"shortName": rec.get("hasShortLabel", ""),
|
||||
"ident": rec.get("hasIdentifier", ""),
|
||||
"name": rec.get("hasName", subj.replace("ex:", "")),
|
||||
"domain": domain_of(subj, rec, subjects),
|
||||
@@ -193,6 +199,8 @@ def build(subjects):
|
||||
"physical": rec.get("physicalName", ""),
|
||||
"format": rec.get("hasFormat", ""),
|
||||
"source": ", ".join(as_list(rec.get("hasSource"))),
|
||||
"sourcedFrom": rec.get("sourcedFrom", ""),
|
||||
"fqn": "",
|
||||
"version": rec.get("hasVersion", ""),
|
||||
"operatedBy": rec.get("operatedBy", ""),
|
||||
"governedBy": rec.get("governedBy", ""),
|
||||
@@ -208,7 +216,7 @@ def build(subjects):
|
||||
"computedBy": as_list(rec.get("computedBy")),
|
||||
"hasElement": as_list(rec.get("hasElement")),
|
||||
"grain": as_list(rec.get("hasGrainElement")),
|
||||
"references": as_list(rec.get("references")),
|
||||
"qualifiedBy": as_list(rec.get("qualifiedBy")),
|
||||
"steward": as_list(rec.get("monitoredBy")) or as_list(rec.get("ownedBy")),
|
||||
"dgl": rec.get("hasDGL", ""),
|
||||
"owner": rec.get("hasDomainOwner", ""),
|
||||
@@ -239,6 +247,7 @@ def build(subjects):
|
||||
phys.setdefault(pid, {"id": pid, "kind": "BaseTable",
|
||||
"label": LABELS["BaseTable"],
|
||||
"name": n["physical"], "domain": n["domain"],
|
||||
"fqn": n["physical"],
|
||||
"derived": True, "backs": []})
|
||||
phys[pid]["backs"].append(n["id"])
|
||||
n["materializedAs"] = pid
|
||||
@@ -251,6 +260,7 @@ def build(subjects):
|
||||
phys.setdefault(pid, {"id": pid, "kind": "Field",
|
||||
"label": LABELS["Field"], "name": n["physical"],
|
||||
"domain": n["domain"], "derived": True,
|
||||
"fqn": (rel + "." if rel else "") + n["physical"],
|
||||
"relation": "phys:" + rel if rel else "",
|
||||
"backs": []})
|
||||
phys[pid]["backs"].append(n["id"])
|
||||
@@ -258,13 +268,14 @@ def build(subjects):
|
||||
for p in phys.values():
|
||||
p.setdefault("ident", ""); p.setdefault("arb", False)
|
||||
p.setdefault("label", LABELS.get(p["kind"], p["kind"]))
|
||||
p.setdefault("shortName", "")
|
||||
for k in ("definition","rule","formula","physical","format","source","belongsTo",
|
||||
"about","represents","status","dgl","owner","subOwner","activation",
|
||||
"version","operatedBy","governedBy","productOwner","storedIn",
|
||||
"materializedAs","relation","stewardFrom"):
|
||||
"materializedAs","relation","stewardFrom","fqn","sourcedFrom"):
|
||||
p.setdefault(k, "")
|
||||
for k in ("uses","measures","hasMetric","computedBy","hasElement","grain",
|
||||
"references","steward","units","computedFrom","packages","exposes",
|
||||
"qualifiedBy","steward","units","computedFrom","packages","exposes",
|
||||
"steward_inherited"):
|
||||
p.setdefault(k, [])
|
||||
p.setdefault("route", "")
|
||||
@@ -777,7 +788,7 @@ var G = {mode:"strata", doms:[], sel:null, hot:null, labels:true,
|
||||
show:{DataDomain:true, SubDomain:true, BusinessObject:true,
|
||||
BusinessConcept:true, Metric:true, DataObject:true,
|
||||
DataElement:false, DataProduct:true, DataContract:true,
|
||||
DataInterface:true, BaseTable:true, Field:false},
|
||||
DataInterface:true, System:true, BaseTable:true, Field:false},
|
||||
view:{x:0,y:0,s:1}, nodes:[], edges:[]};
|
||||
var NW=126, NH=32, LANEPAD=26, ROWGAP=15, BANDGAP=34, GUT=142;
|
||||
|
||||
@@ -787,12 +798,12 @@ var ROW = {DataDomain:0, SubDomain:1, BusinessObject:2,
|
||||
BusinessConcept:0, Metric:1,
|
||||
DataObject:0, DataElement:1,
|
||||
DataProduct:0, DataContract:1, DataInterface:1,
|
||||
BaseTable:0, Field:1};
|
||||
System:0, BaseTable:1, Field:2};
|
||||
var LAYER = {DataDomain:0, SubDomain:0, BusinessObject:0,
|
||||
BusinessConcept:1, Metric:1,
|
||||
DataObject:2, DataElement:2,
|
||||
DataProduct:3, DataInterface:3, DataContract:3,
|
||||
BaseTable:4, Field:4};
|
||||
System:4, BaseTable:4, Field:4};
|
||||
var BANDS = [
|
||||
{t:"Ownership & Categorization", g:"who is accountable"},
|
||||
{t:"Business", g:"what it means"},
|
||||
@@ -805,7 +816,7 @@ var KINDS = [["DataDomain","Domains"],["SubDomain","Sub-domains"],
|
||||
["Metric","Metrics"],["DataObject","Data objects"],
|
||||
["DataElement","Data elements"],["DataProduct","Data products"],
|
||||
["DataContract","Data contracts"],["DataInterface","Data interfaces"],
|
||||
["BaseTable","Base tables"],["Field","Fields"]];
|
||||
["System","Systems"],["BaseTable","Base tables"],["Field","Fields"]];
|
||||
|
||||
function subjectColour(dom){ return dom === M.subject ? "#1E4B9B" : "#8A6A28"; }
|
||||
|
||||
@@ -831,8 +842,8 @@ function buildGraph(){
|
||||
n.measures.forEach(function(c){ edge(n.id, c, "measures"); });
|
||||
n.computedBy.forEach(function(d){ edge(n.id, d, "computed by"); });
|
||||
n.hasElement.forEach(function(d){ edge(n.id, d, "has element"); });
|
||||
n.references.forEach(function(d){ edge(n.id, d, "references"); });
|
||||
if(n.materializedAs) edge(n.id, n.materializedAs, "materialized as");
|
||||
n.qualifiedBy.forEach(function(d){ edge(n.id, d, "qualified by"); });
|
||||
if(n.sourcedFrom) edge(n.id, n.sourcedFrom, "sourced from");
|
||||
if(n.storedIn) edge(n.id, n.storedIn, "stored in");
|
||||
if(n.operatedBy) edge(n.id, n.operatedBy, "operated by");
|
||||
if(n.governedBy) edge(n.id, n.governedBy, "governed by");
|
||||
@@ -1113,10 +1124,12 @@ function renderGraphShell(){
|
||||
}).join("")
|
||||
+ '<div class="gsec">Options</div>'
|
||||
+ '<label><input type="checkbox" id="g-lab" checked> Relation names</label>'
|
||||
+ '<select id="g-dom"><option value="">All domains</option>'
|
||||
+ M.domains.map(function(d){ return '<option value="'+d.code+'"'
|
||||
+ (G.dom===d.code?" selected":"") + '>'+esc(d.label)+'</option>'; }).join("")
|
||||
+ '</select>'
|
||||
+ '<div class="gsec">Domains</div>'
|
||||
+ M.domains.map(function(d){
|
||||
return '<label><input type="checkbox" data-dm="' + d.code + '"'
|
||||
+ (!G.doms.length || G.doms.indexOf(d.code) > -1 ? " checked" : "")
|
||||
+ '> <i style="background:' + (d.code === M.subject ? "#1E4B9B" : "#8A6A28")
|
||||
+ '"></i>' + esc(d.label) + '</label>'; }).join("")
|
||||
+ '<div class="mono" id="g-count"></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">'
|
||||
@@ -1222,7 +1235,7 @@ var HIST = [];
|
||||
|
||||
/* reverse indexes, built once */
|
||||
var PARENT_DO = {}, SUBS_OF = {}, BOS_OF = {}, BACKS = {},
|
||||
METRICS_OF = {}, REALISED_BY = {};
|
||||
METRICS_OF = {}, REALISED_BY = {}, ABOUT_OF = {}, USES_OF = {};
|
||||
M.nodes.forEach(function(n){
|
||||
n.hasElement.forEach(function(de){ PARENT_DO[de] = n.id; });
|
||||
if(n.kind === "SubDomain" && n.belongsTo) (SUBS_OF[n.belongsTo] = SUBS_OF[n.belongsTo]||[]).push(n.id);
|
||||
@@ -1230,6 +1243,8 @@ M.nodes.forEach(function(n){
|
||||
(n.backs||[]).forEach(function(b){ (BACKS[n.id] = BACKS[n.id]||[]).push(b); });
|
||||
/* a concept needs to show the metrics that measure it, not only the reverse */
|
||||
n.measures.forEach(function(bc){ (METRICS_OF[bc] = METRICS_OF[bc]||[]).push(n.id); });
|
||||
if(n.about) (ABOUT_OF[n.about] = ABOUT_OF[n.about]||[]).push(n.id);
|
||||
n.uses.forEach(function(u){ (USES_OF[u] = USES_OF[u]||[]).push(n.id); });
|
||||
if(n.kind === "DataObject" && n.represents)
|
||||
(REALISED_BY[n.represents] = REALISED_BY[n.represents]||[]).push(n.id);
|
||||
});
|
||||
@@ -1256,8 +1271,15 @@ function paint(id){
|
||||
+ esc(byId[HIST[HIST.length-2]].name) + '</button>');
|
||||
h.push('<div class="kind" style="color:' + subjectColour(n.domain) + '">'
|
||||
+ esc(n.label || n.kind) + '</div>');
|
||||
h.push('<h3>' + esc(n.name) + '</h3>');
|
||||
h.push('<div class="iri">' + esc(n.ident || n.id) + '</div>');
|
||||
h.push('<h3>' + esc(n.shortName || n.name) + '</h3>');
|
||||
if(n.shortName && n.shortName !== n.name)
|
||||
h.push('<div class="row" style="border:0;padding:0 0 8px"><span class="mono">'
|
||||
+ esc(n.name) + '</span><span></span></div>');
|
||||
/* a derived physical node has no governance identifier -- showing its
|
||||
internal id as a subtitle read like one. The qualified name is an
|
||||
attribute, and belongs in the attributes block with everything else. */
|
||||
if(n.ident) h.push('<div class="iri">' + esc(n.ident) + '</div>');
|
||||
else h.push('<div style="height:8px"></div>');
|
||||
if(n.arb) h.push('<div class="note"><b>Awaiting arbitration.</b> Proposed by the '
|
||||
+ 'Data Governance Office; ' + esc(domLabel(n.domain))
|
||||
+ ' owns this perimeter and has not ratified it. Cannot be published (OW-007).</div>');
|
||||
@@ -1270,15 +1292,20 @@ function paint(id){
|
||||
var rows = [];
|
||||
function at(label, v){ if(v) rows.push([label, v]); }
|
||||
at("Status", n.status);
|
||||
at("Source-of-truth", n.source);
|
||||
if(n.kind !== "DataProduct") at("Source-of-truth", n.source);
|
||||
if(n.kind === "DataElement"){
|
||||
at("Base table", byId[n.storedIn] && byId[byId[n.storedIn].relation]
|
||||
? byId[byId[n.storedIn].relation].name : "");
|
||||
at("Table field", n.physical);
|
||||
at("Unit", n.units.join(", "));
|
||||
at("Format", n.format);
|
||||
} else if(n.kind === "DataObject"){
|
||||
at("Base table", n.physical);
|
||||
} else if(n.kind === "BaseTable" || n.kind === "Field"){
|
||||
at("Fully qualified name", n.fqn);
|
||||
} else {
|
||||
at("Unit" + (n.units.length > 1 ? "s" : ""), n.units.join(", "));
|
||||
at("Format", n.format);
|
||||
if(n.kind !== "DataElement") at("Physical name", n.physical);
|
||||
}
|
||||
at("Activation", n.activation.replace(/([A-Z])/g," $1").trim());
|
||||
at("Version", n.version);
|
||||
@@ -1287,7 +1314,8 @@ function paint(id){
|
||||
+ esc(r[1])+'</span></div>'; }).join(""));
|
||||
|
||||
/* ---- 2. what it means ---- */
|
||||
if(n.definition) h.push('<h4>Definition</h4><p>' + esc(n.definition) + '</p>');
|
||||
h.push('<h4>What it is for</h4><p>' + (n.definition ? esc(n.definition)
|
||||
: '<span class="mono">No definition yet.</span>') + '</p>');
|
||||
if(n.formula) h.push('<h4>Calculation rule</h4><p class="mono">'
|
||||
+ esc(n.formula) + '</p>');
|
||||
if(n.rule) h.push('<h4>' + (n.kind === "DataContract" ? "Commitment" : "Open question")
|
||||
@@ -1304,8 +1332,11 @@ function paint(id){
|
||||
else if(r[1] === "productOwner") ids = n.productOwner ? [n.productOwner] : [];
|
||||
else if(Array.isArray(n[r[1]])) ids = n[r[1]];
|
||||
else if(n[r[1]]) ids = [n[r[1]]];
|
||||
var names = ids.map(function(i){ return byId[i] ? esc(byId[i].name) : ""; })
|
||||
.filter(Boolean);
|
||||
/* actors carry a short label too; the ownership block shows it rather
|
||||
than the full record name */
|
||||
var names = ids.map(function(i){
|
||||
return byId[i] ? esc(byId[i].shortName || byId[i].name) : ""; })
|
||||
.filter(Boolean);
|
||||
var inherited = (r[1] === "steward_inherited" && n.stewardFrom && names.length);
|
||||
return '<div class="row"><span>' + r[0] + '</span><span class="mono">'
|
||||
+ (names.length ? names.join(", ") : "to be defined")
|
||||
@@ -1331,22 +1362,24 @@ function paint(id){
|
||||
link("Business objects", BOS_OF[n.id]);
|
||||
link("Is about", [n.about]);
|
||||
link("Also uses", n.uses);
|
||||
/* a concept is the subject of the objects that point at it: shown before
|
||||
the metrics, because the objects are the coarser grain */
|
||||
link("Defines", ABOUT_OF[n.id]);
|
||||
link("Used by", USES_OF[n.id]);
|
||||
link("Metrics", (n.hasMetric || []).concat(METRICS_OF[n.id] || []));
|
||||
link("Measures", n.measures);
|
||||
link("Represents", [n.represents]);
|
||||
link("Implements metric", n.computedFrom);
|
||||
link("Governed by", [n.governedBy]);
|
||||
link("Exposes", n.exposes);
|
||||
link("Implements", n.computedFrom);
|
||||
link("Packages", n.packages);
|
||||
link("Data objects", REALISED_BY[n.id]);
|
||||
link("References", n.references);
|
||||
link("Qualified by", n.qualifiedBy);
|
||||
link("Grain", n.grain);
|
||||
link("Held by data object", [PARENT_DO[n.id]]);
|
||||
link("Held by", [PARENT_DO[n.id]]);
|
||||
link("Computed by", n.computedBy);
|
||||
if(n.hasElement.length) links.push(["Data elements", n.hasElement]);
|
||||
link("Materialized as", [n.materializedAs]);
|
||||
link("Stored in", [n.storedIn]);
|
||||
link("Base table", [n.relation]);
|
||||
link("Sourced from", [n.sourcedFrom]);
|
||||
link("Held by", [n.relation]);
|
||||
link("Provides data for", BACKS[n.id]);
|
||||
|
||||
links.forEach(function(l){
|
||||
|
||||
Reference in New Issue
Block a user