> ## Documentation Index
> Fetch the complete documentation index at: https://sdlc-rstack.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Data Visualizations

> Quality & Risk dials, Cost & Budget spend visuals, and Run Analytics — how the Hub's charts are computed and why an empty state renders instead of a fabricated number.

The Business Hub renders three families of visual, non-gating charts: the **Quality & Risk**
dials (Command Center), the **Cost & Budget** spend panels, and **Run Analytics** (Gantt +
per-stage cost/tokens + the parallel benchmark). All three are server-owned projections — the
client never re-derives a score or re-computes a band — and all three follow one rule that this
codebase enforces deliberately: **a missing signal renders as `unknown`, never as a fabricated
zero.** A real `0` (contracts exist, nothing was flagged, no spend was recorded against a real
cap) is a different, reportable state from "no source was ever present." These are BI surfaces
only — nothing here blocks a gate or changes a verdict.

## Quality & Risk dials

Computed in `src/observability/dashboard/state/quality-risk.js` by `computeQualityRisk(run, ...)`,
and assembled per-focus-run by `buildQualityRiskProjection(state, ...)`. The focus run is the
active run, else the newest run with a `pipelineRollup`, else the newest run (`focusRun`).

**Aggregated Risk Score (0-100, higher = riskier).** Rolled from three real sources, not a bare
count:

* Builder-reported risks, read from either `task.builder.risks` (live) or the index-persisted
  `task.builder_risk.severity_tally` (#527) — the same tally, so a run scores identically whether
  served fully-parsed or from the Hub's rollup index. Each risk is weighted by severity
  (`RISK_SEVERITY_WEIGHTS`: `critical: 25, high: 12, medium: 5, low: 2`) and discounted to `0.25`
  of its weight only when genuinely **mitigated** (`isRiskMitigated` — status
  `mitigated`/`resolved`/`closed`/`fixed`, or a non-empty `mitigation` string). An **accepted**
  risk is explicitly *not* mitigated — a knowingly-retained residual keeps its full weight.
* `guardrail_triggered` events (`GUARDRAIL_BLOCK_WEIGHT: 15`), `guardrail_overridden` events
  (`GUARDRAIL_OVERRIDE_WEIGHT: 8`), and `task_blocked_by_validator` events
  (`VALIDATOR_BLOCK_WEIGHT: 10`).

The raw weighted sum is clamped to 0-100 (`clampScore`). The score is `null` — honest unknown —
only when **none** of those sources exist at all (`riskSignalPresent` is false): no builder
contract with risks, no guardrail block, no guardrail override, no validator block. If contracts
exist and genuinely report nothing, the score is a real `0`.

**Complexity Index (0-100, higher = more complex).** Built from structural signal only, never
self-report: files touched across builder tasks (capped at `FILES_CAP = 200`, worth up to 50 pts),
number of builder tasks (capped at 15, up to 25 pts), and `execution_recorded` events from the
transient-sandbox execution evidence (capped at 15, up to 25 pts). Same honest-null rule: `null`
only when there are zero builder tasks, zero files touched, and zero executions.

Both scores map to a labelled band via `bandFor` — a `null` score always maps to the literal band
`'unknown'`:

| Risk band  | Range  | Complexity band | Range  |
| ---------- | ------ | --------------- | ------ |
| `low`      | 0-19   | `low`           | 0-24   |
| `elevated` | 20-49  | `moderate`      | 25-49  |
| `high`     | 50-79  | `high`          | 50-74  |
| `critical` | 80-100 | `very_high`     | 75-100 |

**Cost-to-Value** (optional) pairs cumulative cost (summed from `pipelineRollup.stages[].cost_usd`)
against proof coverage percent (read from the readiness projection or the Evidence Center
summary), yielding `cost_per_coverage_point` — `null` whenever coverage is unknown or `0` (a
division the code refuses to fake). **Execution posture** reports how many recorded executions
were container-verified vs. self-reported-only, alongside PASS/FAIL counts.

### Rendering

`renderQualityRiskCard` in `src/observability/dashboard/ui/pages/command-center.js` reads
`s.overview.qualityRisk` (or the top-level `s.qualityRisk` fallback) and draws each score as an
SVG ring (`qrDialHtml`) using `pathLength="100"` so the dash length **is** the score — a stable
node whose `stroke-dasharray` attribute is patched by the morph renderer, which is what makes the
value change animate as a draw rather than a re-render. A non-numeric or out-of-range score is
re-guarded client-side (`qrNumericScore`) even though the projection already clamps 0-100 — belt
and braces against a `NaN` ever reaching `stroke-dasharray`. An unknown score draws the empty
track only, never a zero-length arc (a drawn arc implies a *measured* value, even a zero one).
Chips beneath the dials surface the severity breakdown, mitigated/accepted/guardrail/validator
counts, files touched, builder task count, and execution count — all read straight off the
projection, no re-derivation.

## Cost & Budget

Rendered by `src/observability/dashboard/ui/pages/cost-budget.js` into `#page-cost-budget`. Three
concerns are kept **visually separate** so a configured cap can never be mistaken for actual
spend:

1. **Current Enforced Policy** (`configuredBudgetPolicyHtml`) — the project's `.rstack/budget.json`
   as validated, per project in scope. `availability` is one of `configured` / `invalid` /
   `inaccessible` / (implicitly) missing; only `configured` renders the run/day/month caps
   (`configuredCapHtml`), and only the **run** cap is annotated "Enforced by goal loop" — day/month
   caps are configured policy only, with no observed loop enforcement claimed for them.
2. **Budget Consumption — Loop Cost Brake** (`budgetGovernanceHtml`) — actual measured spend
   against `run.loopBudgetUsd`, the exact `run_budget_usd` value the goal loop's cost brake reads
   before every iteration. Runs render only when `cap.runBudgetUsd !== null`; each row shows
   percent-of-cap used, headroom, or (if `cap.status === 'exhausted'`) "cap reached — the loop will
   not start another iteration." A distinct `enforcement_stale` status renders "position
   unavailable" when the loop brake's own metrics.json is stale or missing relative to the
   event-derived spend — the UI won't claim a live position it can't back.
3. **Cost per Run / Spend by Stage** (`costSummaryHtml`, `costRunRowsHtml`,
   `stageCostAcrossRunsHtml`) — actual tracked spend, sourced from either persisted `metrics.json`
   totals or a recomputation from the event stream, tagged with a provenance pill
   (`moneySourcePill`: "persisted metrics" / "recomputed from events" / "no telemetry"). Zero runs
   in scope, no telemetry recorded, and "runs exist but nothing reports cost yet" are three
   distinct empty states — none of them render `$0.00`.

`spendPolicyHistoryHtml` additionally compares each run's budget-policy **snapshot at start time**
against the current `.rstack/budget.json`, flagging drift (`comparison: 'differs'`) per field.

## Run Analytics

Rendered by `src/observability/dashboard/ui/pages/run-analytics.js` into
`#page-run-analytics`, per selected run (`ANALYTICS_RUN_ID`):

* **KPI row** (`analyticsKpisHtml`) — duration, tool calls, tasks passed/failed, average quality,
  cost, and tokens. Cost only renders when `source !== 'none'` and either `cost_usd > 0` or a
  `tokenTotals` object exists; otherwise it shows `—` with "no cost telemetry yet" rather than
  `$0.0000`. Tokens carry the same provenance pill as Cost & Budget.
* **Gantt** (`ganttHtml`, shared with the run drawer in `ui/lib.js`) — one row per task segment
  with a `started_at`, positioned/sized proportionally across the run's real time span; bar color
  is `pass` / `fail` / `running` from the segment's status, and a still-running segment (no
  `ended_at`) labels its duration "running" instead of computing a fake elapsed time.
* **Cost & Tokens by Stage** (`stageMoneyHtml`) — per-stage bars from the persisted
  `metrics.json` `stageCost`/`stageTokens` maps; a stage with no cost entry shows "cost n/a" for
  that bar rather than treating a missing key as zero.
* **Parallel Benchmark** (`benchmarkPanelHtml`) — reads `artifacts/parallel-benchmark.json`
  (produced by `scripts/bench-parallel.mjs`) and renders sequential-vs-parallel bars plus an
  honest mode badge: `mode === 'real'` draws "measured — real stages" (green), anything else draws
  "modelled — mock workload" (amber) — a mock measurement is never allowed to look like a live one.
  A missing artifact, an unparseable one, and one with non-finite `seq_time_ms`/`par_time_ms` are
  three distinct, clearly-labelled failure states.
* **Stage durations / run trend table** (`renderStageBars`, `renderTrendTable`) — average elapsed
  time per stage across runs, and a sortable per-run history row (duration, tool calls,
  passed/failed, quality, cost) built from `s.trends`.

## Try it

Open the Business Hub and select a run in Run Analytics to see its Gantt and per-stage spend:

```bash theme={null}
npx rstack-agents hub          # starts/opens the hub on :3008
```

Then navigate to **Command Center** for the Quality & Risk dials, **Cost & Budget** for spend and
policy, and **Run Analytics** for the Gantt/benchmark view. All three update live as the run's
`events.jsonl` and `metrics.json` change — nothing here requires a page reload.

<Note>
  Every score, band, and dollar figure on these pages is computed server-side from the run's real
  artifacts and events (`state/quality-risk.js`, the metrics/rollup readers). If a chart looks
  empty, it's because the underlying signal — a builder contract, a `metrics.json` write, a
  `parallel-benchmark.json` artifact — hasn't been produced yet, not because the Hub swallowed an
  error.
</Note>

## Related

* [Business Hub](/business-hub/overview-and-navigation) — the full page list and how the Hub launches
* [Business Flex profiles](/getting-started/business-flex-profiles) — the planned budget
  envelopes shown alongside actual spend on Cost & Budget
