@uptimizr/metrics 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,150 @@
1
+ /**
2
+ * The **session narrative** contract (ADR 0051 §7, design sketch §G.2).
3
+ *
4
+ * A narrative is an ordered, compacted account of what one session did — scene
5
+ * changes, mesh dwell, interactions, performance dips, errors and the reason it
6
+ * ended — with every timestamp relative to the session's first event. It exists
7
+ * because the two things an agent could otherwise ask for are both useless to
8
+ * it: the aggregate metrics say nothing about *one* session, and the raw NDJSON
9
+ * stream is tens of thousands of lines of sampled telemetry.
10
+ *
11
+ * Only the **shapes and bounds** live here, in the dependency-free registry
12
+ * package, because three packages need them and none of them may depend on the
13
+ * others:
14
+ *
15
+ * - `@uptimizr/db` computes a narrative from `AnyEvent[]` (`src/narrative/`);
16
+ * - the collector serves it on `GET /api/v1/sessions/:id/narrative` and bounds
17
+ * its querystring with the same numbers;
18
+ * - the registry entry below it (`session_narrative`) advertises the row schema
19
+ * to OpenAPI, the agent tool catalog and the docs tables.
20
+ *
21
+ * **Privacy (ADR 0003).** A narrative is derived from raw per-session events, so
22
+ * it is reachable only on a collector with `ENABLE_RAW_SESSION_RETENTION` and
23
+ * only with a key holding `query:raw`. Even then it is a *projection*: an entry
24
+ * carries a relative timestamp, a templated one-line summary and at most a mesh
25
+ * name, a scene id and a custom-event name. It never carries the visitor hash,
26
+ * the page URL, `pageMeta`, positions, rays, or anything from the `device` block
27
+ * beyond the engine/graphics API. Custom-event **property values** are excluded
28
+ * unless the caller explicitly asks for them; the property *keys* are always
29
+ * safe to show because they are developer-chosen field names.
30
+ */
31
+ import { z } from "zod";
32
+ /**
33
+ * What one narrative entry is about.
34
+ *
35
+ * - `scene` — the session entered a scene (`session_start` or `scene_change`).
36
+ * - `dwell` — a mesh held the viewer's attention for at least `minDwellMs`,
37
+ * aggregated across the session from `mesh_visibility` / `hover_dwell`.
38
+ * - `interaction` — a discrete act: a mesh interaction, a click that hit a mesh,
39
+ * a named input action, or a custom event.
40
+ * - `perf_dip` — a run of consecutive `frame_perf` samples below `fpsThreshold`,
41
+ * collapsed into one entry with the run's duration and sample count.
42
+ * - `error` — a `runtime_error`, message truncated.
43
+ * - `diagnostic` — a `graphics_diagnostic`, by category and severity.
44
+ * - `capability` — a `capability_change` (quality drop, tracking loss, backend
45
+ * fallback).
46
+ * - `xr` — entering/leaving immersive input, and a boundary-proximity summary.
47
+ * - `end` — how the session finished (`session_end` reason), when it reported.
48
+ * - `summary` — the single closing entry carrying the session's totals.
49
+ */
50
+ export const NARRATIVE_ENTRY_KINDS = [
51
+ "scene",
52
+ "dwell",
53
+ "interaction",
54
+ "perf_dip",
55
+ "error",
56
+ "diagnostic",
57
+ "capability",
58
+ "xr",
59
+ "end",
60
+ "summary",
61
+ ];
62
+ /** {@link NARRATIVE_ENTRY_KINDS} as a Zod enum. */
63
+ export const narrativeEntryKindSchema = z.enum(NARRATIVE_ENTRY_KINDS);
64
+ /**
65
+ * The named things an entry points at. Deliberately only three, and all three
66
+ * are developer-assigned identifiers rather than anything about a person: the
67
+ * mesh/object name, the scene id, and the name of a custom event or input
68
+ * action.
69
+ */
70
+ export const narrativeRefsSchema = z.object({
71
+ mesh: z.string().optional(),
72
+ scene: z.string().optional(),
73
+ name: z.string().optional(),
74
+ });
75
+ /**
76
+ * Session-wide totals, carried on the closing `summary` entry so a reader that
77
+ * only keeps the last line still knows the shape of what it just read.
78
+ */
79
+ export const sessionNarrativeTotalsSchema = z.object({
80
+ /** Events the narrative was compacted from (every type, before filtering). */
81
+ events: z.number().int().nonnegative(),
82
+ /** Wall-clock span of the session, first to last event. */
83
+ durationMs: z.number().int().nonnegative(),
84
+ /** Distinct scene ids visited. */
85
+ scenes: z.number().int().nonnegative(),
86
+ /** Distinct mesh names the session touched or dwelled on. */
87
+ meshes: z.number().int().nonnegative(),
88
+ /** Interaction entries (mesh interactions, clicks, input actions, customs). */
89
+ interactions: z.number().int().nonnegative(),
90
+ /** Performance dips detected. */
91
+ dips: z.number().int().nonnegative(),
92
+ /** Runtime errors plus `error`/`fatal` graphics diagnostics. */
93
+ errors: z.number().int().nonnegative(),
94
+ });
95
+ /**
96
+ * One line of the narrative.
97
+ *
98
+ * `summary` is a **templated** one-line sentence composed from the fields
99
+ * already on the entry — never free text copied out of an event payload — so
100
+ * that what an LLM reads cannot contain anything the structured fields do not.
101
+ */
102
+ export const sessionNarrativeEntrySchema = z.object({
103
+ /** Milliseconds since the session's first event. Never a wall-clock time. */
104
+ tMs: z.number().int().nonnegative(),
105
+ kind: narrativeEntryKindSchema,
106
+ /** One templated line of prose describing the entry. */
107
+ summary: z.string(),
108
+ refs: narrativeRefsSchema,
109
+ /** How long the entry spans, for the kinds that cover a stretch of time. */
110
+ durationMs: z.number().int().nonnegative().optional(),
111
+ /** How many source events the entry collapses (dwell samples, dip frames, …). */
112
+ count: z.number().int().nonnegative().optional(),
113
+ /** Session totals. Present on the closing `summary` entry only. */
114
+ totals: sessionNarrativeTotalsSchema.optional(),
115
+ /**
116
+ * Whether entries were dropped to honour `maxEntries`. Present on the closing
117
+ * `summary` entry only, so the bound is always visible to whoever reads the
118
+ * last line.
119
+ */
120
+ truncated: z.boolean().optional(),
121
+ });
122
+ /**
123
+ * The bounds and defaults every consumer shares. The collector's querystring
124
+ * schema, the generated tool's input schema and the compaction function all read
125
+ * them from here, so "the default dwell floor" is one number rather than three.
126
+ */
127
+ export const NARRATIVE_LIMITS = {
128
+ /** Default dwell floor: below this a mesh is a glance, not attention. */
129
+ defaultMinDwellMs: 1_000,
130
+ /** Largest dwell floor a caller may ask for (one hour). */
131
+ maxMinDwellMs: 3_600_000,
132
+ /** Default FPS floor below which a frame sample counts towards a dip. */
133
+ defaultFpsThreshold: 30,
134
+ /** Largest FPS floor a caller may ask for. */
135
+ maxFpsThreshold: 240,
136
+ /**
137
+ * Consecutive sub-threshold `frame_perf` samples before a dip is reported.
138
+ * Two, so a single unlucky sample (a tab switch, a GC pause) is not a story.
139
+ */
140
+ dipMinSamples: 2,
141
+ /** Default entry cap — the "under 200 lines" bound issue #314 asks for. */
142
+ defaultMaxEntries: 200,
143
+ /** Hard cap: no caller can ask for an unbounded narrative (ADR 0051 §9). */
144
+ maxMaxEntries: 1_000,
145
+ /** Error/diagnostic messages are truncated to this many characters. */
146
+ maxMessageLength: 200,
147
+ /** At most this many custom-event property **keys** are listed per entry. */
148
+ maxCustomPropKeys: 12,
149
+ };
150
+ //# sourceMappingURL=narrative.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"narrative.js","sourceRoot":"","sources":["../src/narrative.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * **Registry validation for declarative panel specs** (ADR 0051 §7, sketch §G.3).
3
+ *
4
+ * The same split the query DSL makes, one level up. `@uptimizr/schema`'s
5
+ * `panelSpecV1Schema` answers "is this a well-formed spec?" — a known chart
6
+ * name, bounded strings, a closed query grammar. It cannot answer the question
7
+ * that actually decides whether the panel will *draw anything*: does this chart
8
+ * suit this metric's grain, and do these encoding columns exist in its result.
9
+ * Those are questions about the vocabulary, and the vocabulary is this package.
10
+ *
11
+ * So {@link validatePanelSpec} runs `validateQuery` over the spec's query and
12
+ * then adds the two checks only the registry can make. It returns objections as
13
+ * data in exactly the {@link QueryIssue} shape the DSL already uses, so the
14
+ * collector turns a bad spec into one `400` with the same body a bad query
15
+ * gets, and a test can assert on codes rather than prose.
16
+ *
17
+ * ## Why the chart is checked at all
18
+ *
19
+ * Because a pinned panel is read much later than it is written. A `line` over
20
+ * `top_meshes` is not a crash — it is a chart with no axis to walk along, which
21
+ * renders as *something*, and a week later somebody reads that something as a
22
+ * trend. Refusing it at pin time is the only moment anybody is paying attention.
23
+ *
24
+ * ## The compatibility rules, in words
25
+ *
26
+ * - `table` — every metric. Rows are rows; this is the honest fallback and the
27
+ * reason no metric is unpinnable.
28
+ * - `stat` — a metric whose result *is* one row: `project` grain with nothing
29
+ * keying the rows. One number, big. A `stat` over a ranked list would show
30
+ * the first row and silently hide the rest.
31
+ * - `bar` — a label to name each bar and a measure to size it, over a grain
32
+ * that is a *list* rather than a grid: the ranked grains (`mesh`, `scene`,
33
+ * `session`, `row`) plus `bucket`, which is what a histogram or a funnel
34
+ * already is. A spatial grid's key is a coordinate, and bars of coordinates
35
+ * say nothing.
36
+ * - `line` / `area` — an **ordered axis** to walk along: a column the registry
37
+ * marks `axis`, which it guarantees is exactly the `bucket`-grain metrics
38
+ * (`registry.test.ts` pins that). Everything else has an order chosen by the
39
+ * query, and a line drawn through a ranking is a lie about continuity.
40
+ * - `heatmap2d` — a `bin` grain: the metric already bins its input into a grid,
41
+ * which is the only thing the 2D heatmap canvas can paint.
42
+ * - `world3d` — a `voxel` grain, for the same reason in three dimensions.
43
+ */
44
+ import type { PanelChartKind, PanelSpecV1, QueryV1 } from "@uptimizr/schema";
45
+ import { type MetricDefinition, type MetricId } from "./registry.js";
46
+ import { type QueryIssue, type QueryTier } from "./query.js";
47
+ /** Why a panel spec was rejected. Extends the DSL's codes rather than replacing them. */
48
+ export type PanelSpecIssueCode =
49
+ /** The `chart` cannot draw this metric's grain. */
50
+ "chart_grain_mismatch"
51
+ /** An `encoding` column the metric's result does not contain. */
52
+ | "unknown_encoding_column";
53
+ /** One objection to a panel spec: a DSL issue, or one of the two above. */
54
+ export type PanelSpecIssue = Omit<QueryIssue, "code"> & {
55
+ code: QueryIssue["code"] | PanelSpecIssueCode;
56
+ };
57
+ /** The outcome of validating a panel spec against the registry. */
58
+ export interface PanelSpecValidation {
59
+ /** Empty when the spec can be stored and rendered. */
60
+ issues: readonly PanelSpecIssue[];
61
+ /** The resolved metric, when the spec's query named one it could compile. */
62
+ metric?: MetricDefinition;
63
+ /** The tier the spec's query runs on, when it resolved. */
64
+ tier?: QueryTier;
65
+ }
66
+ /**
67
+ * One row of the chart/grain compatibility table: which chart, what it needs,
68
+ * and the predicate that decides it.
69
+ *
70
+ * Declared as data rather than as a `switch` so the docs table, the validator
71
+ * and the `suggestChart` fallback all read from one place. `docs/` renders
72
+ * {@link PANEL_CHART_RULES} and `src/__tests__/panelSpec.test.ts` pins the
73
+ * rendering, so the published table cannot drift from the code that enforces it.
74
+ */
75
+ export interface PanelChartRule {
76
+ chart: PanelChartKind;
77
+ /** What the chart needs from the metric, in words, for the error and the docs. */
78
+ requires: string;
79
+ /** Whether this metric can be drawn this way. */
80
+ accepts: (metric: MetricDefinition) => boolean;
81
+ }
82
+ /** The metric's ordered axis column, when it has one (only `bucket` grains do). */
83
+ export declare function axisColumn(metric: MetricDefinition): string | undefined;
84
+ /** The metric's row-naming column, when it has one. */
85
+ export declare function labelColumn(metric: MetricDefinition): string | undefined;
86
+ /** The metric's headline measure column, when it has one. */
87
+ export declare function measureColumn(metric: MetricDefinition): string | undefined;
88
+ /**
89
+ * The chart/grain compatibility table. The single source of truth for the
90
+ * validator, the docs and the `suggestChart` fallback.
91
+ */
92
+ export declare const PANEL_CHART_RULES: readonly PanelChartRule[];
93
+ /** Whether `chart` can draw `metric`. The predicate behind the table above. */
94
+ export declare function chartSuitsMetric(chart: PanelChartKind, metric: MetricDefinition): boolean;
95
+ /** Every chart that can draw this metric, in the table's order. */
96
+ export declare function chartsForMetric(metric: MetricDefinition): readonly PanelChartKind[];
97
+ /**
98
+ * The columns one row of this query's result carries.
99
+ *
100
+ * On the delegated tier that is the metric's declared `row` shape, unchanged —
101
+ * the canned builder runs and returns exactly what it always did. On the
102
+ * generic tier the builder projects the grouped dimensions plus the metric's
103
+ * declared measures instead, so the encoding has to be checked against *those*:
104
+ * a regrouped `top_meshes` has no `mesh` column when it was grouped by `source`.
105
+ */
106
+ export declare function resultColumns(metric: MetricDefinition, query: QueryV1): readonly string[];
107
+ /**
108
+ * Check a structurally-valid panel spec against the registry.
109
+ *
110
+ * Runs the DSL's own validation first — a spec whose query cannot be answered
111
+ * is not a panel, whatever chart it asks for — and stops there if the metric
112
+ * did not resolve, because every remaining check is about that metric. Chart
113
+ * and encoding objections are then collected together, so an agent that got
114
+ * both wrong learns both in one round trip.
115
+ */
116
+ export declare function validatePanelSpec(spec: PanelSpecV1): PanelSpecValidation;
117
+ /**
118
+ * The chart to pre-fill when an agent pins an answer it just computed.
119
+ *
120
+ * Pure, registry-only and deliberately unambitious: it reads the grain and
121
+ * picks the drawing that grain *is*. A voxelised metric is a world heatmap, a
122
+ * binned one is a 2D heatmap, a bucketed one is a line, a single record is a
123
+ * stat, a ranked list is bars — and anything left over is a table, which can
124
+ * always be drawn. The result always satisfies {@link chartSuitsMetric}, so the
125
+ * assistant's "Pin as panel" never proposes a spec the collector would refuse.
126
+ *
127
+ * `query` is taken so a *regrouped* metric is suggested on the shape it will
128
+ * actually return: `top_meshes` grouped by `source` is still a ranked list, but
129
+ * a generic-tier query that drops the metric's own label column should not be
130
+ * offered a bar it cannot name.
131
+ */
132
+ export declare function suggestChart(metric: MetricDefinition | MetricId, query: QueryV1): PanelChartKind;
133
+ /**
134
+ * The default encoding for a chart over a metric: the metric's own axis or
135
+ * label on `x`, its headline measure on `y`.
136
+ *
137
+ * Shared by the assistant's pre-fill and by the renderer's fallback when a spec
138
+ * carries no `encoding` at all, so "what does this panel draw when nobody said"
139
+ * has exactly one answer.
140
+ */
141
+ export declare function defaultEncoding(metric: MetricDefinition, chart: PanelChartKind): {
142
+ x?: string;
143
+ y?: string;
144
+ };
145
+ //# sourceMappingURL=panelSpec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panelSpec.d.ts","sourceRoot":"","sources":["../src/panelSpec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAGL,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AAiBpB,yFAAyF;AACzF,MAAM,MAAM,kBAAkB;AAC5B,mDAAmD;AACjD,sBAAsB;AACxB,iEAAiE;GAC/D,yBAAyB,CAAC;AAE9B,2EAA2E;AAC3E,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG;IACtD,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,kBAAkB,CAAC;CAC/C,CAAC;AAEF,mEAAmE;AACnE,MAAM,WAAW,mBAAmB;IAClC,sDAAsD;IACtD,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC,6EAA6E;IAC7E,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,2DAA2D;IAC3D,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,cAAc,CAAC;IACtB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,OAAO,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,CAAC;CAChD;AAOD,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAEvE;AAED,uDAAuD;AACvD,wBAAgB,WAAW,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAExE;AAED,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAE1E;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,cAAc,EAuCtD,CAAC;AAMF,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAEzF;AAED,mEAAmE;AACnE,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,cAAc,EAAE,CAEnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,MAAM,EAAE,CAYzF;AA2BD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,mBAAmB,CAuCxE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,QAAQ,EAAE,KAAK,EAAE,OAAO,GAAG,cAAc,CA8BhG;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,cAAc,GACpB;IAAE,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAU5B"}
@@ -0,0 +1,286 @@
1
+ /**
2
+ * **Registry validation for declarative panel specs** (ADR 0051 §7, sketch §G.3).
3
+ *
4
+ * The same split the query DSL makes, one level up. `@uptimizr/schema`'s
5
+ * `panelSpecV1Schema` answers "is this a well-formed spec?" — a known chart
6
+ * name, bounded strings, a closed query grammar. It cannot answer the question
7
+ * that actually decides whether the panel will *draw anything*: does this chart
8
+ * suit this metric's grain, and do these encoding columns exist in its result.
9
+ * Those are questions about the vocabulary, and the vocabulary is this package.
10
+ *
11
+ * So {@link validatePanelSpec} runs `validateQuery` over the spec's query and
12
+ * then adds the two checks only the registry can make. It returns objections as
13
+ * data in exactly the {@link QueryIssue} shape the DSL already uses, so the
14
+ * collector turns a bad spec into one `400` with the same body a bad query
15
+ * gets, and a test can assert on codes rather than prose.
16
+ *
17
+ * ## Why the chart is checked at all
18
+ *
19
+ * Because a pinned panel is read much later than it is written. A `line` over
20
+ * `top_meshes` is not a crash — it is a chart with no axis to walk along, which
21
+ * renders as *something*, and a week later somebody reads that something as a
22
+ * trend. Refusing it at pin time is the only moment anybody is paying attention.
23
+ *
24
+ * ## The compatibility rules, in words
25
+ *
26
+ * - `table` — every metric. Rows are rows; this is the honest fallback and the
27
+ * reason no metric is unpinnable.
28
+ * - `stat` — a metric whose result *is* one row: `project` grain with nothing
29
+ * keying the rows. One number, big. A `stat` over a ranked list would show
30
+ * the first row and silently hide the rest.
31
+ * - `bar` — a label to name each bar and a measure to size it, over a grain
32
+ * that is a *list* rather than a grid: the ranked grains (`mesh`, `scene`,
33
+ * `session`, `row`) plus `bucket`, which is what a histogram or a funnel
34
+ * already is. A spatial grid's key is a coordinate, and bars of coordinates
35
+ * say nothing.
36
+ * - `line` / `area` — an **ordered axis** to walk along: a column the registry
37
+ * marks `axis`, which it guarantees is exactly the `bucket`-grain metrics
38
+ * (`registry.test.ts` pins that). Everything else has an order chosen by the
39
+ * query, and a line drawn through a ranking is a lie about continuity.
40
+ * - `heatmap2d` — a `bin` grain: the metric already bins its input into a grid,
41
+ * which is the only thing the 2D heatmap canvas can paint.
42
+ * - `world3d` — a `voxel` grain, for the same reason in three dimensions.
43
+ */
44
+ import { getMetric, } from "./registry.js";
45
+ import { dimensionColumn, nativeDimensions, queryTier, validateQuery, } from "./query.js";
46
+ /**
47
+ * Grains whose result is a **list of named things** — the shapes a bar chart
48
+ * can rank. Deliberately the same set the DSL calls orderable, plus `bucket`:
49
+ * a histogram, a funnel and a daily count are all drawn as bars, and the DSL
50
+ * excludes `bucket` from *reordering* for a different reason (its order is the
51
+ * axis, and re-sorting it would destroy the thing being shown).
52
+ */
53
+ const BAR_GRAINS = new Set([
54
+ "mesh",
55
+ "scene",
56
+ "session",
57
+ "row",
58
+ "bucket",
59
+ ]);
60
+ /** Whether the metric's result is a single record rather than a list of rows. */
61
+ function isSingleRecord(metric) {
62
+ return metric.grain === "project" && nativeDimensions(metric).length === 0;
63
+ }
64
+ /** The metric's ordered axis column, when it has one (only `bucket` grains do). */
65
+ export function axisColumn(metric) {
66
+ return Object.entries(metric.columns).find(([, semantics]) => semantics.axis === true)?.[0];
67
+ }
68
+ /** The metric's row-naming column, when it has one. */
69
+ export function labelColumn(metric) {
70
+ return Object.entries(metric.columns).find(([, semantics]) => semantics.label === true)?.[0];
71
+ }
72
+ /** The metric's headline measure column, when it has one. */
73
+ export function measureColumn(metric) {
74
+ return Object.entries(metric.columns).find(([, semantics]) => semantics.measure === true)?.[0];
75
+ }
76
+ /**
77
+ * The chart/grain compatibility table. The single source of truth for the
78
+ * validator, the docs and the `suggestChart` fallback.
79
+ */
80
+ export const PANEL_CHART_RULES = [
81
+ {
82
+ chart: "table",
83
+ requires: "nothing — any metric's rows can be listed",
84
+ accepts: () => true,
85
+ },
86
+ {
87
+ chart: "stat",
88
+ requires: "a single-record result (a `project`-grain metric with no grain dimensions)",
89
+ accepts: isSingleRecord,
90
+ },
91
+ {
92
+ chart: "bar",
93
+ requires: "a label column, a measure column, and a ranked or bucketed grain " +
94
+ "(`mesh`, `scene`, `session`, `row`, `bucket`)",
95
+ accepts: (metric) => BAR_GRAINS.has(metric.grain) && labelColumn(metric) != null && measureColumn(metric) != null,
96
+ },
97
+ {
98
+ chart: "line",
99
+ requires: "an ordered axis column — in practice a `bucket`-grain metric",
100
+ accepts: (metric) => axisColumn(metric) != null,
101
+ },
102
+ {
103
+ chart: "area",
104
+ requires: "an ordered axis column — in practice a `bucket`-grain metric",
105
+ accepts: (metric) => axisColumn(metric) != null,
106
+ },
107
+ {
108
+ chart: "heatmap2d",
109
+ requires: "a `bin` grain (the metric already bins its input into a grid)",
110
+ accepts: (metric) => metric.grain === "bin",
111
+ },
112
+ {
113
+ chart: "world3d",
114
+ requires: "a `voxel` grain (the metric already bins its input into world-space cells)",
115
+ accepts: (metric) => metric.grain === "voxel",
116
+ },
117
+ ];
118
+ const RULE_BY_CHART = new Map(PANEL_CHART_RULES.map((rule) => [rule.chart, rule]));
119
+ /** Whether `chart` can draw `metric`. The predicate behind the table above. */
120
+ export function chartSuitsMetric(chart, metric) {
121
+ return RULE_BY_CHART.get(chart)?.accepts(metric) ?? false;
122
+ }
123
+ /** Every chart that can draw this metric, in the table's order. */
124
+ export function chartsForMetric(metric) {
125
+ return PANEL_CHART_RULES.filter((rule) => rule.accepts(metric)).map((rule) => rule.chart);
126
+ }
127
+ /**
128
+ * The columns one row of this query's result carries.
129
+ *
130
+ * On the delegated tier that is the metric's declared `row` shape, unchanged —
131
+ * the canned builder runs and returns exactly what it always did. On the
132
+ * generic tier the builder projects the grouped dimensions plus the metric's
133
+ * declared measures instead, so the encoding has to be checked against *those*:
134
+ * a regrouped `top_meshes` has no `mesh` column when it was grouped by `source`.
135
+ */
136
+ export function resultColumns(metric, query) {
137
+ const tier = queryTier(metric, query);
138
+ if (tier === "delegated")
139
+ return Object.keys(metric.row.shape);
140
+ // `query.dimensions` is bounded-identifier-typed by the schema (the grammar
141
+ // cannot know the registry's union); `validateQuery` has already rejected any
142
+ // that is not one of the metric's declared dimensions by the time a caller
143
+ // reaches here through `validatePanelSpec`.
144
+ const dimensions = query.dimensions ?? nativeDimensions(metric);
145
+ const grouped = dimensions.map((dimension) => dimensionColumn(metric, dimension));
146
+ const measures = (metric.genericGroupBy?.measures ?? []).map((measure) => measure.column);
147
+ return [...new Set([...grouped, ...measures])];
148
+ }
149
+ /** `` `a`, `b` `` — for a message listing what would have been accepted. */
150
+ function list(values) {
151
+ return values.length === 0 ? "none" : values.map((value) => `\`${value}\``).join(", ");
152
+ }
153
+ /**
154
+ * The query a spec runs, as a `queryV1` the DSL validator understands.
155
+ *
156
+ * `range: "inherit"` is the host's business, not the registry's: whichever
157
+ * window is substituted, it is a window, and nothing `validateQuery` checks
158
+ * depends on which. Substituting a placeholder here keeps the spec validator
159
+ * from needing its own copy of the DSL's rules.
160
+ */
161
+ function asQuery(spec) {
162
+ const { range, ...rest } = spec.query;
163
+ return {
164
+ ...rest,
165
+ range: range === "inherit" ? { since: 0, until: 1 } : range,
166
+ // The two keys the spec grammar drops, restored at their DSL defaults so the
167
+ // document `validateQuery` sees is a complete one.
168
+ format: "full",
169
+ explain: false,
170
+ };
171
+ }
172
+ /**
173
+ * Check a structurally-valid panel spec against the registry.
174
+ *
175
+ * Runs the DSL's own validation first — a spec whose query cannot be answered
176
+ * is not a panel, whatever chart it asks for — and stops there if the metric
177
+ * did not resolve, because every remaining check is about that metric. Chart
178
+ * and encoding objections are then collected together, so an agent that got
179
+ * both wrong learns both in one round trip.
180
+ */
181
+ export function validatePanelSpec(spec) {
182
+ const query = asQuery(spec);
183
+ const { issues: queryIssues, metric, tier } = validateQuery(query);
184
+ const issues = queryIssues.map((issue) => ({
185
+ ...issue,
186
+ path: `query.${issue.path}`,
187
+ }));
188
+ if (!metric || tier == null)
189
+ return { issues };
190
+ // --- chart vs grain -----------------------------------------------------
191
+ if (!chartSuitsMetric(spec.chart, metric)) {
192
+ const rule = RULE_BY_CHART.get(spec.chart);
193
+ const alternatives = chartsForMetric(metric);
194
+ issues.push({
195
+ code: "chart_grain_mismatch",
196
+ path: "chart",
197
+ message: `"${metric.id}" cannot be drawn as a ${spec.chart}: that chart needs ${rule?.requires ?? "something this metric does not have"}, ` +
198
+ `and this metric's rows are one per ${metric.grain}. Draw it as ${list([...alternatives])}.`,
199
+ accepted: [...alternatives],
200
+ });
201
+ }
202
+ // --- encoding vs the result's columns -----------------------------------
203
+ const columns = resultColumns(metric, query);
204
+ const columnSet = new Set(columns);
205
+ for (const [channel, column] of Object.entries(spec.encoding ?? {})) {
206
+ if (column == null || columnSet.has(column))
207
+ continue;
208
+ issues.push({
209
+ code: "unknown_encoding_column",
210
+ path: `encoding.${channel}`,
211
+ message: `"${metric.id}" returns no column "${column}"${tier === "generic" ? " when grouped this way" : ""}. Its result columns are ${list(columns)}.`,
212
+ accepted: columns,
213
+ });
214
+ }
215
+ return { issues, metric, tier };
216
+ }
217
+ /**
218
+ * The chart to pre-fill when an agent pins an answer it just computed.
219
+ *
220
+ * Pure, registry-only and deliberately unambitious: it reads the grain and
221
+ * picks the drawing that grain *is*. A voxelised metric is a world heatmap, a
222
+ * binned one is a 2D heatmap, a bucketed one is a line, a single record is a
223
+ * stat, a ranked list is bars — and anything left over is a table, which can
224
+ * always be drawn. The result always satisfies {@link chartSuitsMetric}, so the
225
+ * assistant's "Pin as panel" never proposes a spec the collector would refuse.
226
+ *
227
+ * `query` is taken so a *regrouped* metric is suggested on the shape it will
228
+ * actually return: `top_meshes` grouped by `source` is still a ranked list, but
229
+ * a generic-tier query that drops the metric's own label column should not be
230
+ * offered a bar it cannot name.
231
+ */
232
+ export function suggestChart(metric, query) {
233
+ const resolved = typeof metric === "string" ? getMetric(metric) : metric;
234
+ if (resolved == null)
235
+ return "table";
236
+ const columns = new Set(resultColumns(resolved, query));
237
+ const preference = [
238
+ "world3d",
239
+ "heatmap2d",
240
+ "stat",
241
+ "line",
242
+ "bar",
243
+ "table",
244
+ ];
245
+ for (const chart of preference) {
246
+ if (!chartSuitsMetric(chart, resolved))
247
+ continue;
248
+ // A generic-tier regrouping can project away the very column the chart
249
+ // would have been drawn from, so the suggestion is checked against the
250
+ // columns the query really returns rather than against the metric's own.
251
+ if (chart === "bar") {
252
+ const label = labelColumn(resolved);
253
+ const measure = measureColumn(resolved);
254
+ if (label == null || measure == null)
255
+ continue;
256
+ if (!columns.has(label) || !columns.has(measure))
257
+ continue;
258
+ }
259
+ if (chart === "line") {
260
+ const axis = axisColumn(resolved);
261
+ if (axis == null || !columns.has(axis))
262
+ continue;
263
+ }
264
+ return chart;
265
+ }
266
+ return "table";
267
+ }
268
+ /**
269
+ * The default encoding for a chart over a metric: the metric's own axis or
270
+ * label on `x`, its headline measure on `y`.
271
+ *
272
+ * Shared by the assistant's pre-fill and by the renderer's fallback when a spec
273
+ * carries no `encoding` at all, so "what does this panel draw when nobody said"
274
+ * has exactly one answer.
275
+ */
276
+ export function defaultEncoding(metric, chart) {
277
+ const measure = measureColumn(metric);
278
+ const x = chart === "line" || chart === "area"
279
+ ? (axisColumn(metric) ?? labelColumn(metric))
280
+ : labelColumn(metric);
281
+ return {
282
+ ...(x != null ? { x } : {}),
283
+ ...(measure != null ? { y: measure } : {}),
284
+ };
285
+ }
286
+ //# sourceMappingURL=panelSpec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panelSpec.js","sourceRoot":"","sources":["../src/panelSpec.ts"],"names":[],"mappings":"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"}