feat: Effektiver Status — Knotenfarbe zeigt ihn, Diskrepanz-Marke unten links (SPEC §4/§9, D39)
Jeder Status bekommt einen Fortschritts-Rang entlang der Ergebnis-Skala (D5); effektiv ist das Minimum über die Abhängigkeits-Hülle (Fixpunkt — Zyklen teilen ihr Minimum, wörtlich das „wird gemeinsam fertig" aus D34). Neutral und [-] zählen als 0, [!] als 1; bei doppelter ID gilt die erste Vergabe. Entschieden (Nutzer): Die Knotenfarbe zeigt den effektiven Status — das Diagramm beantwortet „wie weit ist das wirklich?". Wo der eigene Status weiter ist, sitzt unten links die eigene Statusbox als Marke in den eigenen §4-Farben (auch im Export/Druck); Tooltip und a11yEffective benennen beides. XOR-„realisiert" und „Was ist neu?" bleiben bewusst intrinsisch. i18n in allen 9 Sprachen; 12 neue Tests (tests/effective.test.js). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
cbe798d746
commit
15c3e85e26
@@ -78,6 +78,51 @@ export function cheapCls(n, cheapSet){
|
||||
return leaf ? 'cheap cheap-leaf' : 'cheap';
|
||||
}
|
||||
|
||||
/* ---------- Effektiver Status (SPEC §4/§9, D39) ----------
|
||||
Fortschritts-Rang entlang der Ergebnis-Skala (D5). Außerhalb der Skala:
|
||||
neutrale und verworfene Knoten zählen als 0 („nichts Anrechenbares"),
|
||||
High Risk als 1 (Absicht ohne Investition, D35). */
|
||||
export const PROGRESS_RANK = { idee:0, geplant:1, highrisk:1, arbeit:2, durchstich:3, fertig:4, prod:5 };
|
||||
const RANK_STATUS_KEY = ['idee', 'geplant', 'arbeit', 'durchstich', 'fertig', 'prod'];
|
||||
export function progressRank(n){
|
||||
if(!n.status) return 0;
|
||||
const r = PROGRESS_RANK[n.status.key];
|
||||
return r === undefined ? 0 : r; /* verworfen -> 0 */
|
||||
}
|
||||
/* Diskrepanzen des effektiven Status: Map Knoten -> effektiver Status-KEY,
|
||||
nur für Knoten, deren eigener Status weiter ist. Effektiver Rang = Minimum
|
||||
des intrinsischen Rangs über die Abhängigkeits-Hülle (Knoten selbst plus
|
||||
alles per `:#…` Erreichbare), als Fixpunkt-Iteration — Ränge sinken nur,
|
||||
Zyklen teilen so von selbst ihr Minimum („wird gemeinsam fertig", §1).
|
||||
Unbekannte IDs zählen nicht (schon gewarnt, `unknownDep`); bei doppelter
|
||||
ID gilt die ERSTE Vergabe (D36/D39). */
|
||||
export function effectiveStatus(roots){
|
||||
const nodes = [], byId = new Map();
|
||||
(function walk(ns){
|
||||
for(const n of ns){
|
||||
nodes.push(n);
|
||||
if(n.id != null && !byId.has(n.id)) byId.set(n.id, n);
|
||||
walk(n.children);
|
||||
}
|
||||
})(roots);
|
||||
const eff = new Map(nodes.map(n => [n, progressRank(n)]));
|
||||
let changed = true;
|
||||
while(changed){
|
||||
changed = false;
|
||||
for(const n of nodes){
|
||||
let v = eff.get(n);
|
||||
for(const d of n.deps || []){
|
||||
const target = byId.get(d);
|
||||
if(target && eff.get(target) < v) v = eff.get(target);
|
||||
}
|
||||
if(v < eff.get(n)){ eff.set(n, v); changed = true; }
|
||||
}
|
||||
}
|
||||
const map = new Map();
|
||||
eff.forEach((r, n) => { if(r < progressRank(n)) map.set(n, RANK_STATUS_KEY[r]); });
|
||||
return map;
|
||||
}
|
||||
|
||||
/* ---------- Was ist neu? (D28) ----------
|
||||
„Neu" heißt hier bewusst nicht „Zeile hinzugefügt", sondern **neu in
|
||||
Produktion**: ein Knoten, der jetzt `[^]` trägt und es in der zuletzt
|
||||
|
||||
Reference in New Issue
Block a user