Skip to content

Promote direction from frontend lookup → MetricDefinition.direction

Status: active. Direct follow-up to dashboard-widget-fixes-from-review (todo kpi_delta_polarity) — that plan added per-KPI direction in a frontend-only KPI_META lookup as the fast hackathon fix and left this principled follow-up explicitly TODO'd in the code.

Why this plan exists

Three forces converge here:

  1. ADR-007's principle — every widget carries a MetricDefinition block. unit already lives there (drives value_format on KpiTile). direction is the obvious sibling: it's also a per-metric attribute the renderer needs to render honestly. Splitting them across two layers (unit on the catalog, direction in a frontend lookup) is incidental, not principled.
  2. Custom widgets are blind to direction today. A user who builds a custom KPI for "active escalations" through the Add Widget Clarifier will get directionGood defaulted to up_good (current KpiTile default), so a positive delta paints green. The bug we fixed for the built-in KPIs reappears the moment a custom one matters. Lessons-learned § KPI delta color must derive from per-metric direction, not delta sign spells out the cost.
  3. The Clarifier already asks the right shape of questions. Adding "is up good or bad?" to the question_prioritizer is one new question, not a new subsystem. The metric matcher already handles "user picked an existing metric" → just propagate direction from the catalog row.

Cross-references — read before starting

Reference Why
ADR-007 (in prd.md ADR appendix) Establishes the metric-block-per-widget pattern. This plan extends it; do not deviate.
Lessons-learned § KPI delta color must derive from per-metric direction, not delta sign The motivating bug class. Compliance: every new KPI tile call site must take directionGood explicitly.
Lessons-learned § HITL answers must be lifted into intent before synthesis Direct application — direction is a new HITL answer that must be lifted by _lift_answers_into_intent. Forgetting this regresses the lesson.
Lessons-learned § Bedrock tool-use rejects top-level oneOf schemas Indirect — adding direction to MetricDefinition does NOT change the discriminated WidgetSpec's top-level shape, but every per-variant flat schema (KpiSpec/ChartSpec/TableSpec/CustomSpec) re-derives from MetricDefinition. Re-run a real-Bedrock smoke after the schema edit to confirm tool-use still accepts the schema.
frontend/src/dashboard/sections.tsx KPI_META The temporary hardcoded lookup this plan deletes. The TODO comment in frontend/src/components/KpiTile.tsx points at this work.

Scope summary

File Touched by todos
backend/app/metrics/schemas.py schema_extend
db/init.sql ddl_extend
backend/app/metrics/catalog.py catalog_crud
backend/app/metrics/seed.py catalog_seed
backend/app/widgets/routes.py (_validate_and_promote_metric) catalog_crud
backend/app/widgets/nodes/question_prioritizer.py, backend/app/widgets/prompts/intent_extractor.md, backend/app/widgets/nodes/metric_matcher.py clarifier_question
backend/app/widgets/nodes/spec_synthesizer.py (_lift_answers_into_intent, custom-path Python wrapper) spec_synthesizer_lift
frontend/src/widgets/types.ts frontend_metric_type
frontend/src/widgets/WidgetPreview.tsx kpitile_use_metric
frontend/src/dashboard/sections.tsx (KPI_META removal) + backend/app/dashboard_state.py (KPI block extension) dashboard_strip_drop_local_lookup

No changes to: - WidgetSpec discriminator (still kpi | chart | table | custom). - The dashboard layout doc (dashboard_layouts) — direction is a metric concern, not a layout concern. - Makefile / docker-compose.yml — pure code/schema work. - ADR list — this is an ADR-007 follow-through, not a new architectural decision.

Suggested ordering

Strictly serial — the schema is the bottleneck:

  1. schema_extend + ddl_extend (pair these in one PR; the Pydantic and DDL must agree).
  2. catalog_crud + catalog_seed (catalog now stores and reads direction).
  3. clarifier_question + spec_synthesizer_lift (Clarifier captures direction; spec synthesizer threads it through).
  4. frontend_metric_type + kpitile_use_metric (custom widgets render honestly).
  5. dashboard_strip_drop_local_lookup (delete the temporary KPI_META; built-in tiles read from API).
  6. tests + validation (gate).

Out of scope

  • A "direction" column on widgets.spec itself — direction lives on the metric, not on the widget. A widget binding to the catalog gets it automatically; a custom one-off captures it through the Clarifier and persists via the metric promotion path.
  • Auto-inferring direction from the metric name ("anything ending in _pct or _avoided is up_good"). Heuristics here will misfire on custom domains; ask the user once and store the answer.
  • Color theming beyond the existing delta-up / delta-down Tailwind utility classes — directionGood is a semantics flag; the visual treatment is already done in the dashboard-widget-fixes plan.

Lessons-learned candidates

  • If we end up needing different visual treatment for neutral (e.g. ink-700 instead of green/red), append a one-liner: "Neutral KPIs render delta in ink-700; never green-by-default for absence of direction signal."
  • If the Clarifier's new direction question generates noisy answers (users picking 'neither' too often), promote a lesson about question phrasing — currently every example UI in the BI ecosystem (Holistics, Lightdash, Looker) phrases this as "is higher better?" with a yes/no rather than three buttons.