Skip to content

KB/framework

The five-level signal scale

Last verified

Every banded signal on the platform is classified onto one universal 5-level scale: Favorable / Leaning / Neutral / Cautionary / Adverse. The colored dot next to a signal value is the level; the alignment dots, sparkline tints, and KB article band tables all map to the same five tiers. One scale, one source of truth β€” the band classifier in app/signals/config/signal_definitions.json.

The five levels

Universal scale β€” every banded metric maps to one of these

FavorableGreen dot (#34d399)Conditions are constructive for the signal's metric. Supports risk-taking. Top score band.
LeaningLime dot (#a3e635)Slightly positive β€” tilting constructive but not at the favorable threshold.
NeutralGray dot (#6b7280)No strong signal either way. Mid-range read.
CautionaryAmber dot (#fbbf24)Deteriorating. Risk management warranted but not yet critical.
AdverseRed dot (#f87171)Danger zone. Immediate attention. Bottom score band.

The hex codes are the canonical dashboard palette pinned in dotColorForLevel() in ui/src/lib/signalDefinitions.ts (the dot_level β†’ hex map) and reused by every signal card (SignalCard.astro), alignment dot, and KB band table tint.

Why five (not three, not seven)

Three levels (red / yellow / green) collapse meaningful gradations β€” a DIX of 0.42 (right at the bear threshold) reads the same as 0.30 (deep bear). Seven levels read like noise on a dashboard; the operator can’t hold β€œvery-mild-cautionary vs mild-cautionary” in working memory while scanning twenty dots.

Five captures the differentiation that matters β€” directional bias (Favorable / Adverse), the soft directional tier next to it (Leaning / Cautionary), and a true middle (Neutral) β€” without overflowing the visual budget. The same five-tier shape recurs across the platform: alert severity (info / warning / critical maps onto Favorable / Cautionary / Adverse), regime label (RISK-ON / CAUTIOUS / TRANSITIONAL / RISK-OFF / PANIC also reads as a 5-band ladder), and score reconciliation labels.

How a metric maps to a level

The mapping path is registry-driven end-to-end:

  1. A metric’s bands[] array in signal_definitions.json declares thresholds and per-band dot_level strings (one of the five).
  2. band_for(metric_key, value) in app/signals/signal_defs.py returns the matching Band object β€” carries label, dot_level, implication, and score_pts.
  3. The dashboard renders Band.dot_level as the colored dot; the alignment surface reads it; the hybrid matcher projects it as a tag; the KB band table tint maps to it.

One classifier, many consumers. The dot you see on the dashboard, the tag the matcher pre-filters on, and the alignment leg’s implication all derive from the same band_for() call β€” drift between them is impossible by construction.

Where the scale surfaces

See also