/** * The analytics query layer: the read-only, deterministic analog of the control * side's `StatePatch`. An {@link AnalyticsQuery} describes an insight to compute * (scalar metrics, a grouped aggregate, or ranked rows); {@link runAnalytics} * evaluates it over the grid's rows; {@link formatAnalyticsAnswer} renders a * natural-language answer. Pure: no grid, no DOM, no clock. The rule engine builds * a query from an utterance (see entities.ts), and an LLM reasoner can emit the same * shape via structured output, so both share one executor. * * @see plans/analytics-query-spec.md */ import type { GridSchema } from 'apex-grid'; /** The statistics the analytics executor can compute. Superset of the grid's agg funcs. */ export type AnalyticsFunc = 'min' | 'max' | 'avg' | 'median' | 'sum' | 'count' | 'distinct' | 'range' | 'stddev' | 'mode'; /** A scope clause, reusing the grid's filter-operand vocabulary. */ export interface QueryFilter { key: string; operand: string; value: unknown; } /** A read-only analytical question over the grid data. */ export interface AnalyticsQuery { /** `aggregate` = scalar metrics; `group` = metric per dimension; `rank` = ranked rows. */ kind: 'aggregate' | 'group' | 'rank'; /** Aggregate: one or many. Group: the primary metric (first is used). */ metrics: AnalyticsFunc[]; /** The measure column key (optional for a plain row `count`). */ column?: string; /** `group` kind: the dimension column key. */ groupBy?: string; /** Row scope, applied before computing (all kinds). */ where?: QueryFilter[]; /** `rank` (and ranked `group`): which end to take. */ direction?: 'top' | 'bottom'; /** `rank` / ranked `group`: how many. */ limit?: number; /** `rank` kind: the column whose value labels a returned row. */ labelColumn?: string; } /** One computed metric. `value` is a string only for `mode` over a non-numeric column. */ export interface MetricValue { func: AnalyticsFunc; value: number | string | null; } /** The result of {@link runAnalytics}: shape depends on `kind`. */ export interface AnalyticsResult { kind: AnalyticsQuery['kind']; column?: string; groupBy?: string; /** `aggregate`. */ metrics?: MetricValue[]; /** `group`: one row per group. */ groups?: Array<{ group: string; value: number | null; }>; /** `rank`: the ranked rows. */ rows?: Array<{ label: string; value: number; }>; direction?: 'top' | 'bottom'; /** `rank` / ranked `group`: how many were requested. */ limit?: number; /** Rows considered, after `where`. */ rowCount: number; /** Scope clauses echoed back, for the answer. */ where?: QueryFilter[]; notes: string[]; } /** Round to 2 decimals and render raw (no locale separators, for deterministic output). */ export declare function formatNumber(n: number): string; /** Evaluate an {@link AnalyticsQuery} over the grid's rows. Pure. */ export declare function runAnalytics(query: AnalyticsQuery, data: readonly unknown[], _schema: GridSchema): AnalyticsResult; /** Render an {@link AnalyticsResult} as a concise natural-language answer. */ export declare function formatAnalyticsAnswer(result: AnalyticsResult, schema: GridSchema): string;