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-onlyKPI_METAlookup 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:
- ADR-007's principle — every widget carries a
MetricDefinitionblock.unitalready lives there (drivesvalue_formaton KpiTile).directionis 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. - Custom widgets are blind to direction today. A user who builds a custom KPI for "active escalations" through the Add Widget Clarifier will get
directionGooddefaulted toup_good(currentKpiTiledefault), 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. - 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
directionfrom 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:
schema_extend+ddl_extend(pair these in one PR; the Pydantic and DDL must agree).catalog_crud+catalog_seed(catalog now stores and reads direction).clarifier_question+spec_synthesizer_lift(Clarifier captures direction; spec synthesizer threads it through).frontend_metric_type+kpitile_use_metric(custom widgets render honestly).dashboard_strip_drop_local_lookup(delete the temporary KPI_META; built-in tiles read from API).tests+validation(gate).
Out of scope¶
- A "direction" column on
widgets.specitself — 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
_pctor_avoidedis 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-downTailwind utility classes —directionGoodis 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.