diff --git a/state-hub/dashboard/observablehq.config.js b/state-hub/dashboard/observablehq.config.js index 28ff4f4..54ade8e 100644 --- a/state-hub/dashboard/observablehq.config.js +++ b/state-hub/dashboard/observablehq.config.js @@ -11,6 +11,7 @@ export default { pages: [ { name: "Live Data", path: "/docs/live-data" }, { name: "Workstreams", path: "/docs/workstreams" }, + { name: "Workstream Health", path: "/docs/workstream-health-index" }, { name: "Decisions", path: "/docs/decisions" }, { name: "Decision Health", path: "/docs/decisions-kpi" }, { name: "Progress Log", path: "/docs/progress-log" }, diff --git a/state-hub/dashboard/src/docs/workstream-health-index.md b/state-hub/dashboard/src/docs/workstream-health-index.md new file mode 100644 index 0000000..dca138b --- /dev/null +++ b/state-hub/dashboard/src/docs/workstream-health-index.md @@ -0,0 +1,161 @@ +--- +title: Workstream Health Index โ Reference +--- + +# Workstream Health Index (WHI) + +The **Workstream Health Index** is a composite score in the range [0, 1] that measures how well the workstream network is structured for parallel execution and stable progress. It is displayed as a live KPI card in the right margin of the Workstreams page and recomputes on every poll (every 15 seconds). + +**1.0 = ideal independence ยท 0.0 = severe systemic dysfunction** + +--- + +## Health states + +| Score | Color | Label | Meaning | +|---|---|---|---| +| โฅ 0.75 | ๐ข green | Healthy | Parallel execution effective, delays localized | +| 0.50 โ 0.74 | ๐ orange | Optimizable | Noticeable coordination cost; review decomposition | +| < 0.50 | ๐ด red | Critical | Serial execution dominates; immediate replanning required | + +--- + +## The six base metrics + +### DD โ Dependency Density + +``` +DD = total dependency edges / (active + blocked workstreams) +``` + +Measures structural coupling. Low DD means independent, parallelizable work. Completed and archived workstreams are excluded โ they no longer constrain progress. + +| DD | Warning | +|---|---| +| > 1.0 | ๐ด red โ more than one dependency per workstream on average | +| 0.5 โ 1.0 | ๐ orange | +| โค 0.5 | ok | + +--- + +### BR โ Blocked Ratio + +``` +BR = blocked workstreams / (active + blocked workstreams) +``` + +Measures immediate operational impact. BR โ 0 means flow is unobstructed. + +| BR | Warning | +|---|---| +| > 40% | ๐ด red | +| 20โ40% | ๐ orange | +| โค 20% | ok | + +--- + +### SPR โ Single-Point Risk + +``` +SPR = max dependents on one incomplete workstream / (active + blocked) +``` + +Detects concentration of blocking power. High SPR means one delay propagates widely โ a structural SPOF. + +| SPR | Warning | +|---|---| +| > 40% | ๐ด red | +| 25โ40% | ๐ orange | +| โค 25% | ok | + +--- + +### PEP โ Parallel Execution Potential + +``` +PEP = active workstreams with all deps completed / (active + blocked) +``` + +Estimates how much work can proceed right now. A workstream is eligible if its status is `active` and every workstream it depends on has reached `completed` or `archived`. + +| PEP | Warning | +|---|---| +| < 30% | ๐ด red | +| 30โ60% | ๐ orange | +| โฅ 60% | ok | + +--- + +### CDDR โ Cross-Domain Dependency Ratio + +``` +CDDR = dependency edges crossing domain boundaries / total edges +``` + +Measures architectural entanglement. High CDDR indicates loss of modularity across the six project domains. + +| CDDR | Warning | +|---|---| +| > 40% | ๐ orange | + +--- + +### CPI โ Cycle Presence Indicator + +``` +CPI = 0 โ no cycles +CPI = 1 โ at least one circular dependency detected +``` + +Detected via DFS with inStack colouring. Any cycle means no feasible execution order exists โ a structural deadlock. When CPI = 1, the final WHI score is **halved** as a hard penalty. + +--- + +## Aggregation formula + +``` +DDnorm = min(1, DD / 1.0) โ saturates at DD_critical = 1.0 + +WHI = 0.30 ร (1 โ DDnorm) + + 0.25 ร (1 โ BR) + + 0.15 ร (1 โ SPR) + + 0.20 ร PEP + + 0.10 ร (1 โ CDDR) + +if CPI = 1: WHI = WHI ร 0.5 +``` + +Result is clamped to [0, 1]. + +--- + +## Domain breakdown + +The card also shows a per-domain WHI computed using **intra-domain workstreams and intra-domain edges only**. This measures each domain's internal autonomy โ how well its workstreams are decomposed relative to each other, independent of cross-domain dependencies. + +A domain with WHI = 100% is fully self-contained and parallelizable internally. Its global contribution to the program-level WHI may still be reduced by cross-domain dependencies (captured in CDDR). + +The domain breakdown is shown when at least two domains have active workstreams. + +--- + +## How to improve a poor score + +| Symptom | Action | +|---|---| +| High DD | Decompose tightly coupled workstreams; remove unnecessary dependencies | +| High BR | Unblock workstreams โ resolve the blocking condition, or mark dependency as completed if done | +| High SPR | Split the bottleneck workstream into independent deliverables | +| Low PEP | Complete prerequisite workstreams or re-sequence work | +| High CDDR | Refactor cross-domain dependencies into shared contracts or invert the dependency | +| CPI = 1 | Find and break the cycle โ identify which dependency edge is incorrect and remove it | + +--- + +## What WHI is not + +WHI measures **structural health of the work graph** โ not individual performance, not velocity, not burn-down rate. A team can be moving fast with a poor WHI (serially but quickly), or slowly with a perfect WHI (fully parallel but under-resourced). Use WHI alongside velocity metrics, not instead of them. + +--- + +*Specification: `state-hub/dashboard/src/docs/workstream-kpi.md`* diff --git a/state-hub/dashboard/src/workstreams.md b/state-hub/dashboard/src/workstreams.md index 20856a7..b564998 100644 --- a/state-hub/dashboard/src/workstreams.md +++ b/state-hub/dashboard/src/workstreams.md @@ -44,12 +44,98 @@ const _ok = wsState.ok ?? false; const _ts = wsState.ts; ``` +```js +// โโ Workstream Health Index (WHI) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ +const _idToDomain = Object.fromEntries(data.map(w => [w.id, w.domain ?? "unknown"])); +const _completedIds = new Set(data.filter(w => w.status === "completed" || w.status === "archived").map(w => w.id)); +const _openCount = openWs.length; +const _allEdges = openWs.flatMap(w => w.depends_on.map(d => ({from: w.id, to: d.workstream_id}))); +const _totalEdges = _allEdges.length; + +// Dependency Density +const _DD = _openCount > 0 ? _totalEdges / _openCount : 0; + +// Blocked Ratio +const _BR = _openCount > 0 ? openWs.filter(w => w.status === "blocked").length / _openCount : 0; + +// Single-Point Risk โ max inbound edges on one incomplete workstream +const _inbound = {}; +for (const e of _allEdges) { + if (!_completedIds.has(e.to)) _inbound[e.to] = (_inbound[e.to] ?? 0) + 1; +} +const _SPR = _openCount > 0 + ? (Object.keys(_inbound).length > 0 ? Math.max(...Object.values(_inbound)) : 0) / _openCount + : 0; + +// Parallel Execution Potential โ active workstreams with all deps completed +const _PEP = _openCount > 0 + ? openWs.filter(w => w.status === "active" && w.depends_on.every(d => _completedIds.has(d.workstream_id))).length / _openCount + : 0; + +// Cross-Domain Dependency Ratio +const _crossEdges = _allEdges.filter(e => (_idToDomain[e.from] ?? "?") !== (_idToDomain[e.to] ?? "?")).length; +const _CDDR = _totalEdges > 0 ? _crossEdges / _totalEdges : 0; + +// Cycle Presence Indicator โ DFS with visited/inStack colouring +function _detectCycle(nodes, edges) { + const adj = Object.fromEntries(nodes.map(n => [n.id, []])); + for (const e of edges) { if (adj[e.from] !== undefined) adj[e.from].push(e.to); } + const visited = new Set(), inStack = new Set(); + function dfs(id) { + if (inStack.has(id)) return true; + if (visited.has(id)) return false; + visited.add(id); inStack.add(id); + for (const nx of (adj[id] ?? [])) { if (dfs(nx)) return true; } + inStack.delete(id); + return false; + } + for (const n of nodes) { if (!visited.has(n.id) && dfs(n.id)) return 1; } + return 0; +} +const _CPI = _detectCycle(openWs, _allEdges); + +// WHI aggregation โ DD normalised at DD_critical = 1.0, CPI halves the score +const _DDnorm = Math.min(1, _DD / 1.0); +let _WHI = 0.30*(1 - _DDnorm) + 0.25*(1 - _BR) + 0.15*(1 - _SPR) + 0.20*_PEP + 0.10*(1 - _CDDR); +if (_CPI === 1) _WHI *= 0.5; +_WHI = Math.max(0, Math.min(1, _WHI)); + +// Per-domain breakdown โ intra-domain edges only (measures domain autonomy) +const _domainBreakdown = [...new Set(openWs.map(w => _idToDomain[w.id] ?? "unknown"))].sort().map(domain => { + const nodes = openWs.filter(w => (_idToDomain[w.id] ?? "unknown") === domain); + const edges = nodes.flatMap(w => + w.depends_on + .filter(d => (_idToDomain[d.workstream_id] ?? "unknown") === domain) + .map(d => ({from: w.id, to: d.workstream_id})) + ); + const oc = nodes.length; + if (oc === 0) return null; + const te = edges.length; + const dd = oc > 0 ? te / oc : 0; + const br = oc > 0 ? nodes.filter(w => w.status === "blocked").length / oc : 0; + const pep = oc > 0 ? nodes.filter(w => { + if (w.status !== "active") return false; + const intraDeps = w.depends_on.filter(d => (_idToDomain[d.workstream_id] ?? "unknown") === domain); + return intraDeps.every(d => _completedIds.has(d.workstream_id)); + }).length / oc : 0; + const inb = {}; + for (const e of edges) inb[e.to] = (inb[e.to] ?? 0) + 1; + const spr = oc > 0 ? (Object.keys(inb).length > 0 ? Math.max(...Object.values(inb)) : 0) / oc : 0; + const cpi = _detectCycle(nodes, edges); + const ddN = Math.min(1, dd / 1.0); + let whi = 0.30*(1 - ddN) + 0.25*(1 - br) + 0.15*(1 - spr) + 0.20*pep + 0.10; // CDDR=0 within domain + if (cpi === 1) whi *= 0.5; + return {domain, whi: Math.max(0, Math.min(1, whi)), br, pep, cpi, openCount: oc}; +}).filter(Boolean); +``` + # Workstreams ```js import {injectTocTop} from "./components/toc-sidebar.js"; import {withDocHelp} from "./components/doc-overlay.js"; +// โโ Live indicator โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ const _liveEl = html`
make api`}