/** * Renderer for the v2 `log10x_top_patterns` layout. Keep this separate * from `pattern-render.ts` (which `whats_changing`, `event_lookup`, * `services`, `trend` still rely on); only `top_patterns` uses this. * * Output is **GitHub-flavored markdown** designed to render inside an AI * IDE chat panel. Section headers use **bold**, multi-row ASCII (table, * chart, snippets, queries) lives inside ```fenced blocks so monospace * spacing survives VS Code's proportional-font renderer, and sub-items * use bullet lists. The previous "ASCII-art-everywhere" layout rendered * as a wall of proportional text once VS Code's markdown engine got * hold of it. * * Layout, top to bottom: * 1. Orientation header (window, sort key, forwarder + analyzer detected, totals) * 2. Compact table (one row per pattern, identity-first columns, fenced) * 3. Per-pattern cards, each with: * - ### row header + bold cost line ($/h, $/mo, MB, events, first seen) * - **Volume trend** + fenced chart (top-3 / NEW only; y-axis is * MB/h — volume parses faster than $/h since the row header * already names the cost) * - **Example event** *(sample from namespace X)* + fenced semantic * excerpt (namespace lives in the header so we don't need a * separate "where this comes from" section) * - **What varies across these events** (markdown table when fields * vary; one-line verdict when not — replaces the legacy * varying/noise/constant tri-bucket) * - **To find these events in ** + fenced query * - **To drop at the forwarder** + fenced snippet + * bullets (placement / sample variant / "using a different * forwarder? ask: …" — action-shaped, not just a list / and a * final "analyzer-side drop: unavailable" bullet when the * analyzer can't drop at ingest) * - **To drop at (analyzer-side)** — only when the * analyzer actually supports drop-at-ingest (splunk, datadog) * - **To apply this to the running ** — kubectl * commands when sample has k8s metadata, host-level otherwise; * closes the loop from "here's the snippet" to "production is * now dropping these events" */ import type { DisplayToken } from './pattern-df.js'; import { type ForwarderId } from './forwarder-snippets.js'; import type { FieldVariation } from './field-variation.js'; import type { ParsedSiemEvent } from './siem/sample.js'; import { type Badge, type BadgeInfo } from './top-patterns-extras.js'; import type { TrendDelta } from './trend-delta.js'; import type { DepCheckResult } from './siem/deps/index.js'; export interface TopPatternRow { rank: number; hash: string; pattern: string; service: string; severity: string; bytes: number; costPerHour: number; costPerMonth: number; events: number; /** Age in seconds, or null when unknown. */ firstSeenAgeSeconds: number | null; /** Byte-rate values from the 24h trend Prom query, or empty. */ trendBytesPerSec: number[]; /** One parsed SIEM event used as the sample. */ sample?: ParsedSiemEvent; /** Per-field variation across N sampled events. */ fieldVar?: FieldVariation; /** Trajectory classification (v3 — see top-patterns-extras.ts). */ state: Badge; /** Full BadgeInfo with ratio + first-seen-age for richer rendering * ("+85% vs baseline" instead of just "ACUTE"). Optional for * backward compat; renderers should prefer this when present. */ badgeInfo?: BadgeInfo; /** Pre-computed trend delta for this row. Carries glyph, signed * percent change (WoW or 1h), or age-in-days for NEW rows. */ trendDelta?: TrendDelta; /** Distinct services emitting this hash. >1 surfaces the breakdown CTA. */ serviceCount?: number; /** Per-hash dependency-check result (token-AND match against the * detected analyzer's saved searches / alerts / dashboards). */ deps?: DepCheckResult; /** Datadog ingest-exclusion query for this hash. Set when the env's * analyzer is Datadog; folded inline as a pre-meter drop option. */ datadogAnalyzerQuery?: string; /** RENDER-ONLY (Layer 2): discriminator-first display name derived from * `pattern` (symbolMessage) over the shared env df-map. Identity fields * (`pattern`, `hash`) are untouched. Absent => renderer falls back to the * sample/descriptor path. */ display_name?: string; /** RENDER-ONLY: per-token {text, distinctive} classification of `pattern`, * for the homepage widget to emphasize discriminators. */ display_tokens?: DisplayToken[]; } export interface TopPatternsRenderOpts { windowLabel: string; totalBytesInScope: number; totalCostPerHour: number; totalCostMonthly: number; patternCountShown: number; patternCountTotal?: number; /** Detected forwarder for this env. `null` when detection fails. */ forwarder: ForwarderId | null; /** Detected analyzer name (e.g. "cloudwatch", "splunk"). Lowercase. */ analyzer: string | null; /** Engine's symbolMessageHashField value. Defaults to "tenx_hash". */ hashField?: string; /** Log group / index / scope to surface in the analyzer query line. */ analyzerScope?: string; /** Optional degraded-state banner from healthBanner(). */ healthBanner?: string | null; /** Cost-by-service rollup (sorted desc) — the "where is the money" * headline. Each entry is a service, its bytes, and its share of the * total. Rendered as a compact block above the pattern list. */ costByService?: Array<{ service: string; bytes: number; pct: number; }>; /** $/GB rate for converting rollup bytes to cost. Default 1.0. */ costPerGb?: number; /** Verbose mode — when true, every card carries the full forwarder * snippet inline, all CTAs render unconditionally, and the volume * trend chart shows on every top-3 card instead of only ACUTE/NEW. * Default false (compact, badge-driven, gated CTAs). */ verbose?: boolean; } export declare function renderTopPatterns(rows: TopPatternRow[], opts: TopPatternsRenderOpts): string;