Abhängigkeitsgraph: klarere Anordnung (Abhängigkeiten links, Prozess

Mitte, erzeugte Assets rechts)

- Anzeige-Kanten rollenabhängig orientiert: primäre/erzeugte Assets
  rechts vom Prozess, unterstützende Abhängigkeiten links (Pfeile
  fließen sauber links→rechts, Label "erzeugt" bzw. "unterstützt")
- Analyse-Adjazenz (SPOF, kritischer Pfad, transitive Abhängigkeiten)
  von der Anzeige entkoppelt und über alle Asset-Bezüge berechnet

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Martin
2026-07-03 10:09:06 +02:00
co-authored by Claude Opus 4.8
parent d9685b65b9
commit 18bd82e54a
+43 -21
View File
@@ -124,9 +124,15 @@ export async function buildDependencyGraph(
});
}
// Kanten: Prozess → Asset (Rolle), Asset → Asset (Relation)
// Zwei Sichten:
// - adjacency (Analyse): Abhängigkeitsrichtung (Abhängiger → Abhängigkeit),
// Basis für kritischen Pfad, SPOF, transitive Abhängigkeiten.
// - edges (Anzeige): links = unterstützende Abhängigkeit, Mitte = Prozess,
// rechts = erzeugtes primäres Asset. Pfeile fließen sauber von links nach
// rechts, daher zeigen unterstützende Kanten VON der Abhängigkeit zum
// Nutzer/Prozess.
const edges: GraphEdge[] = [];
const adjacency = new Map<string, string[]>(); // gerichtete Abhängigkeit source→target
const adjacency = new Map<string, string[]>();
const addAdj = (s: string, t: string) => {
if (!adjacency.has(s)) adjacency.set(s, []);
adjacency.get(s)!.push(t);
@@ -136,31 +142,46 @@ export async function buildDependencyGraph(
for (const p of processes) {
for (const pa of p.processAssets) {
const s = pid(p.id);
const t = aid(pa.assetId);
edges.push({
id: `${s}->${t}`,
source: s,
target: t,
label: pa.role === "PRIMARY" ? "erzeugt" : "nutzt",
critical: isCritEdge(s, t),
});
addAdj(s, t);
const proc = pid(p.id);
const asset = aid(pa.assetId);
// Analyse: Prozess ist auf alle zugeordneten Assets angewiesen (SPOF/Pfad)
addAdj(proc, asset);
if (pa.role === "PRIMARY") {
// erzeugtes Asset rechts vom Prozess
edges.push({
id: `${proc}->${asset}`,
source: proc,
target: asset,
label: "erzeugt",
critical: isCritEdge(proc, asset),
});
} else {
// unterstützendes Asset links vom Prozess (Kante zeigt zum Prozess)
edges.push({
id: `${asset}->${proc}`,
source: asset,
target: proc,
label: "unterstützt",
critical: isCritEdge(asset, proc),
});
}
}
}
const assetIds = new Set(assets.map((a) => a.id));
for (const r of relations) {
if (!assetIds.has(r.assetId) || !assetIds.has(r.relatedAssetId)) continue;
const s = aid(r.assetId);
const t = aid(r.relatedAssetId);
// Analyse: asset hängt von relatedAsset ab
addAdj(aid(r.assetId), aid(r.relatedAssetId));
// Anzeige: Abhängigkeit (relatedAsset) links, Nutzer (asset) rechts
const s = aid(r.relatedAssetId);
const t = aid(r.assetId);
edges.push({
id: `${s}->${t}`,
source: s,
target: t,
label: r.type === "depends_on" ? "hängt ab" : r.type,
label: "unterstützt",
critical: isCritEdge(s, t),
});
addAdj(s, t);
}
// Transitive Abhängigkeiten je kritischem Prozess → SPOF-Zählung je Asset
@@ -191,12 +212,13 @@ export async function buildDependencyGraph(
}
spofs.sort((a, b) => b.count - a.count);
// Längster kritischer Pfad (DFS über kritische Kanten ab kritischen Prozessen)
// Längster kritischer Pfad (Abhängigkeitskette): DFS über die Analyse-Adjazenz,
// beschränkt auf kritische Knoten, ab kritischen Prozessen.
const critAdj = new Map<string, string[]>();
for (const e of edges) {
if (!e.critical) continue;
if (!critAdj.has(e.source)) critAdj.set(e.source, []);
critAdj.get(e.source)!.push(e.target);
for (const [s, targets] of adjacency) {
if ((critOf.get(s) ?? 1) < threshold) continue;
const critTargets = targets.filter((t) => (critOf.get(t) ?? 1) >= threshold);
if (critTargets.length) critAdj.set(s, critTargets);
}
const nameOf = new Map(nodes.map((n) => [n.id, n.name]));
let longest: string[] = [];