/** * Trend-delta computation for log10x_top_patterns envelope. * * Produces a compact, pre-rendered TrendDelta per pattern row, driven * entirely from the existing state field + the 24h trend array already * fetched in Phase 2. No new Prometheus queries. * * Design contract: * GROWING → glyph ↗, scope 'wow', pct change from first half vs second half of trend array * SHRINKING → glyph ↘, scope 'wow', signed negative pct * STABLE → glyph ─, scope 'wow', small pct near 0 * ACUTE → glyph 🔥, scope 'h1', last 6 buckets vs prior 6 buckets * NEW → glyph 🆕, scope 'age', floor(firstSeenAgeSeconds / 86400) days * * Edge cases: * - empty or single-point trend array → value: 0, label: '—' * - null firstSeenAgeSeconds for NEW → value: 0, label: '—' */ import type { Badge } from './top-patterns-extras.js'; export type TrendDelta = { glyph: '↗' | '↘' | '─' | '🔥' | '🆕'; value: number; unit: 'pct' | 'days'; scope: 'wow' | 'h1' | 'age'; /** Pre-rendered short label, e.g. '+45% WoW', '+220% 1h', '3d'. */ label: string; }; /** * How the age was sourced for a NEW pattern's display string. * 'engine_metric' — firstSeenAgeSeconds came from the first-seen TSDB query * 'derived_from_trend_window' — age derived from trend array length × step seconds * 'unknown' — neither source was available */ export type FirstSeenAgeSource = 'engine_metric' | 'derived_from_trend_window' | 'unknown'; /** * Build the pre-composed display string for a TrendDelta row. * * For GROWING/SHRINKING/STABLE/ACUTE: "${glyph} ${label}" * For NEW with known engine age: "🆕 Xh ago" / "🆕 Xd ago" * For NEW with unknown engine age but trend window available: * derives max age from trendLength × stepSec and emits "🆕