@uptimizr/db 2.0.1 → 2.1.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.
- package/AGENTS.md +228 -3
- package/README.md +13 -0
- package/dist/duckdb/migrations.d.ts.map +1 -1
- package/dist/duckdb/migrations.js +169 -0
- package/dist/duckdb/migrations.js.map +1 -1
- package/dist/duckdb/panelSpecs.d.ts +29 -0
- package/dist/duckdb/panelSpecs.d.ts.map +1 -0
- package/dist/duckdb/panelSpecs.js +104 -0
- package/dist/duckdb/panelSpecs.js.map +1 -0
- package/dist/duckdb/projectMetadata.d.ts +43 -0
- package/dist/duckdb/projectMetadata.d.ts.map +1 -0
- package/dist/duckdb/projectMetadata.js +222 -0
- package/dist/duckdb/projectMetadata.js.map +1 -0
- package/dist/duckdb/subscriptions.d.ts +45 -0
- package/dist/duckdb/subscriptions.d.ts.map +1 -0
- package/dist/duckdb/subscriptions.js +202 -0
- package/dist/duckdb/subscriptions.js.map +1 -0
- package/dist/index.d.ts +18 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -3
- package/dist/index.js.map +1 -1
- package/dist/insights/anomalies.d.ts +213 -0
- package/dist/insights/anomalies.d.ts.map +1 -0
- package/dist/insights/anomalies.js +340 -0
- package/dist/insights/anomalies.js.map +1 -0
- package/dist/insights/baseline.d.ts +51 -0
- package/dist/insights/baseline.d.ts.map +1 -0
- package/dist/insights/baseline.js +48 -0
- package/dist/insights/baseline.js.map +1 -0
- package/dist/insights/buckets.d.ts +114 -0
- package/dist/insights/buckets.d.ts.map +1 -0
- package/dist/insights/buckets.js +220 -0
- package/dist/insights/buckets.js.map +1 -0
- package/dist/insights/changepoint.d.ts +130 -0
- package/dist/insights/changepoint.d.ts.map +1 -0
- package/dist/insights/changepoint.js +209 -0
- package/dist/insights/changepoint.js.map +1 -0
- package/dist/insights/evaluate.d.ts +63 -0
- package/dist/insights/evaluate.d.ts.map +1 -0
- package/dist/insights/evaluate.js +158 -0
- package/dist/insights/evaluate.js.map +1 -0
- package/dist/insights/health.d.ts +219 -0
- package/dist/insights/health.d.ts.map +1 -0
- package/dist/insights/health.js +349 -0
- package/dist/insights/health.js.map +1 -0
- package/dist/insights/index.d.ts +45 -0
- package/dist/insights/index.d.ts.map +1 -0
- package/dist/insights/index.js +40 -0
- package/dist/insights/index.js.map +1 -0
- package/dist/insights/measures.d.ts +222 -0
- package/dist/insights/measures.d.ts.map +1 -0
- package/dist/insights/measures.js +431 -0
- package/dist/insights/measures.js.map +1 -0
- package/dist/insights/movers.d.ts +123 -0
- package/dist/insights/movers.d.ts.map +1 -0
- package/dist/insights/movers.js +166 -0
- package/dist/insights/movers.js.map +1 -0
- package/dist/insights/significance.d.ts +221 -0
- package/dist/insights/significance.d.ts.map +1 -0
- package/dist/insights/significance.js +531 -0
- package/dist/insights/significance.js.map +1 -0
- package/dist/insights/stats.d.ts +104 -0
- package/dist/insights/stats.d.ts.map +1 -0
- package/dist/insights/stats.js +181 -0
- package/dist/insights/stats.js.map +1 -0
- package/dist/insights/windows.d.ts +95 -0
- package/dist/insights/windows.d.ts.map +1 -0
- package/dist/insights/windows.js +97 -0
- package/dist/insights/windows.js.map +1 -0
- package/dist/metadata.d.ts +178 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +80 -0
- package/dist/metadata.js.map +1 -1
- package/dist/narrative/build.d.ts +78 -0
- package/dist/narrative/build.d.ts.map +1 -0
- package/dist/narrative/build.js +437 -0
- package/dist/narrative/build.js.map +1 -0
- package/dist/narrative/index.d.ts +14 -0
- package/dist/narrative/index.d.ts.map +1 -0
- package/dist/narrative/index.js +13 -0
- package/dist/narrative/index.js.map +1 -0
- package/dist/narrative/text.d.ts +25 -0
- package/dist/narrative/text.d.ts.map +1 -0
- package/dist/narrative/text.js +41 -0
- package/dist/narrative/text.js.map +1 -0
- package/dist/parity/cases.d.ts +3 -0
- package/dist/parity/cases.d.ts.map +1 -1
- package/dist/parity/cases.js +387 -7
- package/dist/parity/cases.js.map +1 -1
- package/dist/parity/compare.d.ts +19 -0
- package/dist/parity/compare.d.ts.map +1 -1
- package/dist/parity/compare.js +19 -0
- package/dist/parity/compare.js.map +1 -1
- package/dist/parity/fixtures.d.ts.map +1 -1
- package/dist/parity/fixtures.js +25 -0
- package/dist/parity/fixtures.js.map +1 -1
- package/dist/query/aggregations.d.ts +46 -1
- package/dist/query/aggregations.d.ts.map +1 -1
- package/dist/query/aggregations.js +86 -0
- package/dist/query/aggregations.js.map +1 -1
- package/dist/query/clickhouseDialect.d.ts.map +1 -1
- package/dist/query/clickhouseDialect.js +5 -1
- package/dist/query/clickhouseDialect.js.map +1 -1
- package/dist/query/customEventVocabulary.d.ts +46 -0
- package/dist/query/customEventVocabulary.d.ts.map +1 -0
- package/dist/query/customEventVocabulary.js +127 -0
- package/dist/query/customEventVocabulary.js.map +1 -0
- package/dist/query/dsl/builders.d.ts +38 -0
- package/dist/query/dsl/builders.d.ts.map +1 -0
- package/dist/query/dsl/builders.js +35 -0
- package/dist/query/dsl/builders.js.map +1 -0
- package/dist/query/dsl/compare.d.ts +167 -0
- package/dist/query/dsl/compare.d.ts.map +1 -0
- package/dist/query/dsl/compare.js +358 -0
- package/dist/query/dsl/compare.js.map +1 -0
- package/dist/query/dsl/compile.d.ts +89 -0
- package/dist/query/dsl/compile.d.ts.map +1 -0
- package/dist/query/dsl/compile.js +174 -0
- package/dist/query/dsl/compile.js.map +1 -0
- package/dist/query/dsl/explain.d.ts +115 -0
- package/dist/query/dsl/explain.d.ts.map +1 -0
- package/dist/query/dsl/explain.js +143 -0
- package/dist/query/dsl/explain.js.map +1 -0
- package/dist/query/dsl/generic.d.ts +127 -0
- package/dist/query/dsl/generic.d.ts.map +1 -0
- package/dist/query/dsl/generic.js +323 -0
- package/dist/query/dsl/generic.js.map +1 -0
- package/dist/query/dsl/index.d.ts +30 -0
- package/dist/query/dsl/index.d.ts.map +1 -0
- package/dist/query/dsl/index.js +23 -0
- package/dist/query/dsl/index.js.map +1 -0
- package/dist/query/dsl/order.d.ts +41 -0
- package/dist/query/dsl/order.d.ts.map +1 -0
- package/dist/query/dsl/order.js +55 -0
- package/dist/query/dsl/order.js.map +1 -0
- package/dist/query/dsl/significance.d.ts +130 -0
- package/dist/query/dsl/significance.d.ts.map +1 -0
- package/dist/query/dsl/significance.js +224 -0
- package/dist/query/dsl/significance.js.map +1 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +8 -0
- package/dist/query/index.js.map +1 -1
- package/dist/query/summary/index.d.ts +4 -2
- package/dist/query/summary/index.d.ts.map +1 -1
- package/dist/query/summary/index.js +2 -1
- package/dist/query/summary/index.js.map +1 -1
- package/dist/query/summary/labels.d.ts +101 -0
- package/dist/query/summary/labels.d.ts.map +1 -0
- package/dist/query/summary/labels.js +261 -0
- package/dist/query/summary/labels.js.map +1 -0
- package/dist/query/summary/reading.d.ts.map +1 -1
- package/dist/query/summary/reading.js +21 -2
- package/dist/query/summary/reading.js.map +1 -1
- package/dist/query/summary/schema.d.ts +250 -20
- package/dist/query/summary/schema.d.ts.map +1 -1
- package/dist/query/summary/schema.js +135 -123
- package/dist/query/summary/schema.js.map +1 -1
- package/dist/query/summary/summarize.d.ts.map +1 -1
- package/dist/query/summary/summarize.js +91 -5
- package/dist/query/summary/summarize.js.map +1 -1
- package/dist/query/summary/types.d.ts +77 -2
- package/dist/query/summary/types.d.ts.map +1 -1
- package/dist/query/types.d.ts +47 -1
- package/dist/query/types.d.ts.map +1 -1
- package/dist/subscriptions.d.ts +208 -0
- package/dist/subscriptions.d.ts.map +1 -0
- package/dist/subscriptions.js +153 -0
- package/dist/subscriptions.js.map +1 -0
- package/llms.txt +9 -0
- package/package.json +8 -8
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **The generic group-by compiler** (ADR 0051 §3, design sketch §C.2, tier 2).
|
|
3
|
+
*
|
|
4
|
+
* The delegated tier (`compile.ts`) can run any metric, but only at the grain
|
|
5
|
+
* its builder renders: `top_meshes` is one row per mesh and nothing else. That
|
|
6
|
+
* is right for a spatial binning or a percentile — those measures *are* their
|
|
7
|
+
* grain — but wrong for a count. "How many mesh interactions per input source"
|
|
8
|
+
* and "…per scene" are the same question about the same events, and the only
|
|
9
|
+
* reason they needed two endpoints is that somebody wrote two `GROUP BY`s.
|
|
10
|
+
*
|
|
11
|
+
* So a metric that declares `genericGroupBy` in the registry (a portable
|
|
12
|
+
* `count` / `count(DISTINCT session_id)` / `sum` / `avg` / `max` over promoted
|
|
13
|
+
* columns) gets one shared, dialect-authored builder that renders
|
|
14
|
+
*
|
|
15
|
+
* ```sql
|
|
16
|
+
* SELECT <dimensions>, <measures>
|
|
17
|
+
* FROM events [LEFT JOIN session_attrs …]
|
|
18
|
+
* WHERE project_id = ? AND <event types> AND <scope> AND <range> AND <filters>
|
|
19
|
+
* GROUP BY <dimensions>
|
|
20
|
+
* ORDER BY <measure> DESC, <dimensions>
|
|
21
|
+
* LIMIT ?
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* for **any** subset of the dimensions that metric declares.
|
|
25
|
+
*
|
|
26
|
+
* ## Why this is still only one SQL path
|
|
27
|
+
*
|
|
28
|
+
* Everything variable in the SQL above comes from **registry data**, never from
|
|
29
|
+
* the request: the event types and the scope predicate are the metric's
|
|
30
|
+
* `genericGroupBy`, the measures are its declared columns, and each dimension's
|
|
31
|
+
* expression is chosen from a closed table keyed by `DimensionId`. Every value a
|
|
32
|
+
* caller supplies — a scene id, a mesh name, an event predicate — reaches the
|
|
33
|
+
* SQL as a bound parameter through the same {@link ParamBag} the canned
|
|
34
|
+
* aggregations use. There is no string interpolation of caller input anywhere in
|
|
35
|
+
* this file, which is what makes a grammar with a `GROUP BY` in it still a
|
|
36
|
+
* closed grammar.
|
|
37
|
+
*
|
|
38
|
+
* ## Portability
|
|
39
|
+
*
|
|
40
|
+
* The promoted dimensions (`scene_id`, `session_id`, `mesh`, `name`, `source`,
|
|
41
|
+
* `event_type`) are plain columns on every engine. The session attributes
|
|
42
|
+
* (`cameraMode`, `device.*`) live in the `session_start` payload, so they are
|
|
43
|
+
* read through `Dialect.jsonText` in one CTE and `LEFT JOIN`ed on `session_id`
|
|
44
|
+
* — the same shape `buildPerfByDevice` has always used. `GROUP BY` repeats the
|
|
45
|
+
* expressions rather than naming the output aliases, because T-SQL rejects the
|
|
46
|
+
* latter (the mssql dialect rewrites it, but not relying on the rewrite is
|
|
47
|
+
* cheaper than relying on it).
|
|
48
|
+
*
|
|
49
|
+
* ## Why it is not called `build*`
|
|
50
|
+
*
|
|
51
|
+
* Every `build*` export of this package is a dialect-agnostic aggregation with
|
|
52
|
+
* the signature `(projectId, opts, dialect)`, and the optional stores' suites
|
|
53
|
+
* enumerate them by that prefix to run each one against DuckDB. This one takes a
|
|
54
|
+
* `MetricDefinition` first, because the metric *is* its configuration — so it is
|
|
55
|
+
* a `compile*`, next to `compileMetric` and `compileQuery`, and the prefix keeps
|
|
56
|
+
* meaning what it has always meant.
|
|
57
|
+
*
|
|
58
|
+
* The one session attribute deliberately left out is `device.isMobile`: it is a
|
|
59
|
+
* boolean, and a group-by on it would key rows by `"true"` on one engine and `1`
|
|
60
|
+
* on another. `GENERIC_DIMENSIONS` in the registry says so, and `validateQuery`
|
|
61
|
+
* refuses it by name.
|
|
62
|
+
*/
|
|
63
|
+
import { dimensionColumn, } from "@uptimizr/metrics";
|
|
64
|
+
import { ParamBag, cameraModeClause, rangeClause } from "../dialect.js";
|
|
65
|
+
/** The default row cap when a caller names none. */
|
|
66
|
+
const DEFAULT_GENERIC_LIMIT = 200;
|
|
67
|
+
/** Session attributes read out of the `session_start` payload, by dimension. */
|
|
68
|
+
const SESSION_ATTRIBUTE_PATHS = {
|
|
69
|
+
cameraMode: ["scene", "cameraType"],
|
|
70
|
+
"device.engine": ["device", "engine"],
|
|
71
|
+
"device.renderer": ["device", "renderer"],
|
|
72
|
+
"device.browser": ["device", "browser"],
|
|
73
|
+
"device.os": ["device", "os"],
|
|
74
|
+
};
|
|
75
|
+
/** Promoted `events` columns, by dimension. The rest come from the CTE. */
|
|
76
|
+
const PROMOTED_COLUMNS = {
|
|
77
|
+
scene: "scene_id",
|
|
78
|
+
session: "session_id",
|
|
79
|
+
mesh: "mesh",
|
|
80
|
+
name: "name",
|
|
81
|
+
source: "source",
|
|
82
|
+
event_type: "event_type",
|
|
83
|
+
};
|
|
84
|
+
/** The CTE the session-attribute dimensions are read from. */
|
|
85
|
+
const SESSION_CTE = "session_attrs";
|
|
86
|
+
/** Whether a dimension is read from the joined `session_start` attributes. */
|
|
87
|
+
function isSessionAttribute(dimension) {
|
|
88
|
+
return dimension in SESSION_ATTRIBUTE_PATHS;
|
|
89
|
+
}
|
|
90
|
+
/** Sanitised alias for a session attribute inside the CTE (`device.os` → `sa_os`). */
|
|
91
|
+
function attributeAlias(dimension) {
|
|
92
|
+
return `sa_${dimension
|
|
93
|
+
.replace(/^device\./, "")
|
|
94
|
+
.replace(/[^A-Za-z0-9]/g, "_")
|
|
95
|
+
.toLowerCase()}`;
|
|
96
|
+
}
|
|
97
|
+
/** Resolve every asked-for dimension to its output column and SQL expression. */
|
|
98
|
+
function resolveDimensions(metric, dimensions) {
|
|
99
|
+
return dimensions.map((id) => ({
|
|
100
|
+
id,
|
|
101
|
+
column: dimensionColumn(metric, id),
|
|
102
|
+
expression: isSessionAttribute(id)
|
|
103
|
+
? `${SESSION_CTE}.${attributeAlias(id)}`
|
|
104
|
+
: `events.${PROMOTED_COLUMNS[id] ?? id}`,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
/** Render one measure column. */
|
|
108
|
+
function renderMeasure(measure) {
|
|
109
|
+
switch (measure.kind) {
|
|
110
|
+
case "count":
|
|
111
|
+
return `count(*) AS ${measure.column}`;
|
|
112
|
+
case "sessions":
|
|
113
|
+
return `count(DISTINCT events.session_id) AS ${measure.column}`;
|
|
114
|
+
case "sum":
|
|
115
|
+
return `sum(events.${measure.of}) AS ${measure.column}`;
|
|
116
|
+
case "avg":
|
|
117
|
+
return `avg(events.${measure.of}) AS ${measure.column}`;
|
|
118
|
+
case "max":
|
|
119
|
+
return `max(events.${measure.of}) AS ${measure.column}`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* `event_type IN (…)`, every type a bound parameter. Empty for a metric whose
|
|
124
|
+
* subject is the whole stream (`event_counts`).
|
|
125
|
+
*/
|
|
126
|
+
function eventTypeClause(bag, types) {
|
|
127
|
+
if (types.length === 0)
|
|
128
|
+
return "";
|
|
129
|
+
const bound = types.map((type, index) => bag.add(`gType${index}`, "string", type));
|
|
130
|
+
return ` AND events.event_type IN (${bound.join(", ")})`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The metric's own scope predicate, so a regrouped result counts the same
|
|
134
|
+
* population its builder counts. Closed: each value is one non-empty test on a
|
|
135
|
+
* promoted column, named by the registry, never by the caller.
|
|
136
|
+
*/
|
|
137
|
+
function scopeClause(scope) {
|
|
138
|
+
const parts = (scope ?? []).map((rule) => rule === "hasMesh" ? "events.mesh != ''" : "events.name != ''");
|
|
139
|
+
return parts.length === 0 ? "" : ` AND ${parts.join(" AND ")}`;
|
|
140
|
+
}
|
|
141
|
+
/** An equality predicate on a promoted column, or `""` when the value is absent. */
|
|
142
|
+
function equality(bag, column, param, value) {
|
|
143
|
+
if (value == null || value.length === 0)
|
|
144
|
+
return "";
|
|
145
|
+
return ` AND events.${column} = ${bag.add(param, "string", value)}`;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* `session_id IN (SELECT … FROM session_start WHERE <attribute> = ?)` — the
|
|
149
|
+
* device filter, rendered exactly like the camera-mode filter it sits next to so
|
|
150
|
+
* it composes with every other clause and needs no join.
|
|
151
|
+
*/
|
|
152
|
+
function deviceClause(bag, d, projectId, device) {
|
|
153
|
+
const tests = [];
|
|
154
|
+
if (device?.os != null && device.os.length > 0) {
|
|
155
|
+
tests.push(`${d.jsonText("payload", "device", "os")} = ${bag.add("devOs", "string", device.os)}`);
|
|
156
|
+
}
|
|
157
|
+
if (device?.browser != null && device.browser.length > 0) {
|
|
158
|
+
tests.push(`${d.jsonText("payload", "device", "browser")} = ` +
|
|
159
|
+
`${bag.add("devBrowser", "string", device.browser)}`);
|
|
160
|
+
}
|
|
161
|
+
if (tests.length === 0)
|
|
162
|
+
return "";
|
|
163
|
+
const pid = bag.add("devProjectId", "string", projectId);
|
|
164
|
+
return ` AND events.session_id IN (
|
|
165
|
+
SELECT session_id FROM events
|
|
166
|
+
WHERE project_id = ${pid} AND event_type = 'session_start' AND ${tests.join(" AND ")}
|
|
167
|
+
)`;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* `session_id IN (SELECT … WHERE <the step predicate>)` — the ADR 0038 event
|
|
171
|
+
* filter.
|
|
172
|
+
*
|
|
173
|
+
* It is a **cohort** predicate, not a row predicate: it keeps the sessions in
|
|
174
|
+
* which the event happened at least once, and the metric then counts its own
|
|
175
|
+
* events within them. That is the useful question ("what did the people who
|
|
176
|
+
* reached checkout look at") and the one the funnel grammar already answers;
|
|
177
|
+
* narrowing the counted rows themselves is what `filters.event_type`-style
|
|
178
|
+
* dimensions and `eventTypes` already do.
|
|
179
|
+
*/
|
|
180
|
+
function eventPredicateClause(bag, projectId, event) {
|
|
181
|
+
if (event == null)
|
|
182
|
+
return "";
|
|
183
|
+
const parts = [`event_type = ${bag.add("evType", "string", event.type)}`];
|
|
184
|
+
if (event.name != null && event.name.length > 0) {
|
|
185
|
+
parts.push(`name = ${bag.add("evName", "string", event.name)}`);
|
|
186
|
+
}
|
|
187
|
+
if (event.mesh != null && event.mesh.length > 0) {
|
|
188
|
+
parts.push(`mesh = ${bag.add("evMesh", "string", event.mesh)}`);
|
|
189
|
+
}
|
|
190
|
+
const pid = bag.add("evProjectId", "string", projectId);
|
|
191
|
+
return ` AND events.session_id IN (
|
|
192
|
+
SELECT session_id FROM events
|
|
193
|
+
WHERE project_id = ${pid} AND ${parts.join(" AND ")}
|
|
194
|
+
)`;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Equality predicates for the dimensions a `segment` holds fixed. Only the
|
|
198
|
+
* dimensions the delegated filters cannot express reach here — the rest arrive
|
|
199
|
+
* as ordinary filter options.
|
|
200
|
+
*/
|
|
201
|
+
function segmentClauses(bag, d, projectId, segment) {
|
|
202
|
+
if (segment == null)
|
|
203
|
+
return "";
|
|
204
|
+
const parts = [];
|
|
205
|
+
let index = 0;
|
|
206
|
+
for (const [dimension, value] of Object.entries(segment)) {
|
|
207
|
+
if (value == null || value.length === 0)
|
|
208
|
+
continue;
|
|
209
|
+
const param = `seg${index++}`;
|
|
210
|
+
const path = SESSION_ATTRIBUTE_PATHS[dimension];
|
|
211
|
+
if (path != null) {
|
|
212
|
+
const pid = bag.add(`${param}ProjectId`, "string", projectId);
|
|
213
|
+
parts.push(`events.session_id IN (
|
|
214
|
+
SELECT session_id FROM events
|
|
215
|
+
WHERE project_id = ${pid} AND event_type = 'session_start'
|
|
216
|
+
AND ${d.jsonText("payload", ...path)} = ${bag.add(param, "string", value)}
|
|
217
|
+
)`);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const column = PROMOTED_COLUMNS[dimension];
|
|
221
|
+
if (column == null)
|
|
222
|
+
continue;
|
|
223
|
+
parts.push(`events.${column} = ${bag.add(param, "string", value)}`);
|
|
224
|
+
}
|
|
225
|
+
return parts.length === 0 ? "" : ` AND ${parts.join(" AND ")}`;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The `session_start` attribute CTE and its join, rendered only when a session
|
|
229
|
+
* attribute is actually one of the grouping dimensions. A device or camera-mode
|
|
230
|
+
* *filter* never needs it — those are sub-selects.
|
|
231
|
+
*/
|
|
232
|
+
function sessionAttributes(bag, d, projectId, dimensions) {
|
|
233
|
+
const attributes = dimensions.filter((dimension) => isSessionAttribute(dimension.id));
|
|
234
|
+
if (attributes.length === 0)
|
|
235
|
+
return { cte: "", join: "" };
|
|
236
|
+
const pid = bag.add("saProjectId", "string", projectId);
|
|
237
|
+
const projections = attributes.map((dimension) => `${d.jsonText("payload", ...(SESSION_ATTRIBUTE_PATHS[dimension.id] ?? []))} AS ` +
|
|
238
|
+
`${attributeAlias(dimension.id)}`);
|
|
239
|
+
return {
|
|
240
|
+
cte: `WITH ${SESSION_CTE} AS (
|
|
241
|
+
SELECT session_id AS sa_session, ${projections.join(", ")}
|
|
242
|
+
FROM events
|
|
243
|
+
WHERE project_id = ${pid} AND event_type = 'session_start'
|
|
244
|
+
)
|
|
245
|
+
`,
|
|
246
|
+
join: `
|
|
247
|
+
LEFT JOIN ${SESSION_CTE} ON ${SESSION_CTE}.sa_session = events.session_id`,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Compile a generic group-by query for one metric.
|
|
252
|
+
*
|
|
253
|
+
* `metric` must declare `genericGroupBy` — the caller (`compileMetric`) has
|
|
254
|
+
* already checked that, and `validateQuery` before it, so the throw below guards
|
|
255
|
+
* a programming error rather than a client one.
|
|
256
|
+
*/
|
|
257
|
+
export function compileGenericGroupBy(metric, projectId, opts, d) {
|
|
258
|
+
const spec = metric.genericGroupBy;
|
|
259
|
+
if (spec == null) {
|
|
260
|
+
throw new Error(`metric '${metric.id}' declares no genericGroupBy and cannot be grouped by an arbitrary grain`);
|
|
261
|
+
}
|
|
262
|
+
const bag = new ParamBag(d);
|
|
263
|
+
const dimensions = resolveDimensions(metric, opts.dimensions ?? metric.grainDimensions);
|
|
264
|
+
// The attribute CTE binds its own project id, and must be rendered before the
|
|
265
|
+
// outer clauses so the parameters read in source order.
|
|
266
|
+
const attributes = sessionAttributes(bag, d, projectId, dimensions);
|
|
267
|
+
const pid = bag.add("projectId", "string", projectId);
|
|
268
|
+
const where = [
|
|
269
|
+
eventTypeClause(bag, spec.eventTypes),
|
|
270
|
+
scopeClause(spec.scope),
|
|
271
|
+
rangeClause(bag, opts),
|
|
272
|
+
equality(bag, "scene_id", "scene", opts.scene),
|
|
273
|
+
equality(bag, "session_id", "session", opts.session),
|
|
274
|
+
equality(bag, "source", "source", opts.source),
|
|
275
|
+
equality(bag, "mesh", "mesh", opts.mesh),
|
|
276
|
+
cameraModeClause(bag, d, projectId, opts),
|
|
277
|
+
deviceClause(bag, d, projectId, opts.device),
|
|
278
|
+
eventPredicateClause(bag, projectId, opts.event),
|
|
279
|
+
segmentClauses(bag, d, projectId, opts.segment),
|
|
280
|
+
].join("");
|
|
281
|
+
const measures = spec.measures.map(renderMeasure);
|
|
282
|
+
const selected = [
|
|
283
|
+
...dimensions.map((dimension) => `${dimension.expression} AS ${dimension.column}`),
|
|
284
|
+
...measures,
|
|
285
|
+
];
|
|
286
|
+
// Order by the asked-for measure, then by every dimension, so the row set a
|
|
287
|
+
// `LIMIT` keeps is a function of the data rather than of the engine's
|
|
288
|
+
// scan order — the same reason the summariser breaks ties on the label.
|
|
289
|
+
const primary = opts.order?.by ?? spec.measures[0]?.column ?? "1";
|
|
290
|
+
const direction = opts.order?.dir === "asc" ? "ASC" : "DESC";
|
|
291
|
+
const order = [
|
|
292
|
+
`${primary} ${direction}`,
|
|
293
|
+
...dimensions.map((dimension) => `${dimension.expression} ASC`),
|
|
294
|
+
].join(", ");
|
|
295
|
+
const limit = bag.add("limit", "u32", opts.limit ?? DEFAULT_GENERIC_LIMIT);
|
|
296
|
+
const groupBy = dimensions.length === 0
|
|
297
|
+
? ""
|
|
298
|
+
: `
|
|
299
|
+
GROUP BY ${dimensions.map((dimension) => dimension.expression).join(", ")}`;
|
|
300
|
+
return {
|
|
301
|
+
query: `
|
|
302
|
+
${attributes.cte}SELECT ${selected.join(", ")}
|
|
303
|
+
FROM events${attributes.join}
|
|
304
|
+
WHERE events.project_id = ${pid}${where}${groupBy}
|
|
305
|
+
ORDER BY ${order}
|
|
306
|
+
LIMIT ${limit}
|
|
307
|
+
`,
|
|
308
|
+
metric: metric.id,
|
|
309
|
+
query_params: bag.values,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* The columns a generic group-by result carries, in order: the grouping
|
|
314
|
+
* dimensions, then the metric's measures. Used by the explain plan and by the
|
|
315
|
+
* tests that assert a generic row is self-describing.
|
|
316
|
+
*/
|
|
317
|
+
export function genericResultColumns(metric, dimensions) {
|
|
318
|
+
return [
|
|
319
|
+
...dimensions.map((dimension) => dimensionColumn(metric, dimension)),
|
|
320
|
+
...(metric.genericGroupBy?.measures ?? []).map((measure) => measure.column),
|
|
321
|
+
];
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=generic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic.js","sourceRoot":"","sources":["../../../src/query/dsl/generic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAEH,OAAO,EACL,eAAe,GAKhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAgB,MAAM,eAAe,CAAC;AAkDtF,oDAAoD;AACpD,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAElC,gFAAgF;AAChF,MAAM,uBAAuB,GAAgD;IAC3E,UAAU,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC;IACnC,eAAe,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;IACrC,iBAAiB,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;IACzC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC;IACvC,WAAW,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC;CAC9B,CAAC;AAEF,2EAA2E;AAC3E,MAAM,gBAAgB,GAAqC;IACzD,KAAK,EAAE,UAAU;IACjB,OAAO,EAAE,YAAY;IACrB,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,YAAY;CACzB,CAAC;AAEF,8DAA8D;AAC9D,MAAM,WAAW,GAAG,eAAe,CAAC;AAWpC,8EAA8E;AAC9E,SAAS,kBAAkB,CAAC,SAAiB;IAC3C,OAAO,SAAS,IAAI,uBAAuB,CAAC;AAC9C,CAAC;AAED,sFAAsF;AACtF,SAAS,cAAc,CAAC,SAAiB;IACvC,OAAO,MAAM,SAAS;SACnB,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;SACxB,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC;SAC7B,WAAW,EAAE,EAAE,CAAC;AACrB,CAAC;AAED,iFAAiF;AACjF,SAAS,iBAAiB,CACxB,MAAwB,EACxB,UAAkC;IAElC,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7B,EAAE;QACF,MAAM,EAAE,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC;QACnC,UAAU,EAAE,kBAAkB,CAAC,EAAE,CAAC;YAChC,CAAC,CAAC,GAAG,WAAW,IAAI,cAAc,CAAC,EAAE,CAAC,EAAE;YACxC,CAAC,CAAC,UAAU,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE;KAC3C,CAAC,CAAC,CAAC;AACN,CAAC;AAED,iCAAiC;AACjC,SAAS,aAAa,CAAC,OAAuB;IAC5C,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,OAAO;YACV,OAAO,eAAe,OAAO,CAAC,MAAM,EAAE,CAAC;QACzC,KAAK,UAAU;YACb,OAAO,wCAAwC,OAAO,CAAC,MAAM,EAAE,CAAC;QAClE,KAAK,KAAK;YACR,OAAO,cAAc,OAAO,CAAC,EAAE,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;QAC1D,KAAK,KAAK;YACR,OAAO,cAAc,OAAO,CAAC,EAAE,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;QAC1D,KAAK,KAAK;YACR,OAAO,cAAc,OAAO,CAAC,EAAE,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;IAC5D,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CAAC,GAAa,EAAE,KAAwB;IAC9D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;IACnF,OAAO,8BAA8B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,KAAoC;IACvD,MAAM,KAAK,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACvC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,mBAAmB,CAC/D,CAAC;IACF,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;AACjE,CAAC;AAED,oFAAoF;AACpF,SAAS,QAAQ,CAAC,GAAa,EAAE,MAAc,EAAE,KAAa,EAAE,KAAyB;IACvF,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACnD,OAAO,eAAe,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CACnB,GAAa,EACb,CAAU,EACV,SAAiB,EACjB,MAAuC;IAEvC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,MAAM,EAAE,EAAE,IAAI,IAAI,IAAI,MAAM,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/C,KAAK,CAAC,IAAI,CACR,GAAG,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CACtF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,EAAE,OAAO,IAAI,IAAI,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,KAAK,CAAC,IAAI,CACR,GAAG,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC,KAAK;YAChD,GAAG,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CACvD,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IACzD,OAAO;;6BAEoB,GAAG,yCAAyC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC;QACpF,CAAC;AACT,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,oBAAoB,CAC3B,GAAa,EACb,SAAiB,EACjB,KAAwC;IAExC,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAG,CAAC,gBAAgB,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1E,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IACxD,OAAO;;6BAEoB,GAAG,QAAQ,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC;QACnD,CAAC;AACT,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CACrB,GAAa,EACb,CAAU,EACV,SAAiB,EACjB,OAAqD;IAErD,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IAC/B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACzD,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAClD,MAAM,KAAK,GAAG,MAAM,KAAK,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,uBAAuB,CAAC,SAAS,CAAC,CAAC;QAChD,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,WAAW,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;YAC9D,KAAK,CAAC,IAAI,CACR;;6BAEqB,GAAG;gBAChB,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC;QAC3E,CACD,CAAC;YACF,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,MAAM,IAAI,IAAI;YAAE,SAAS;QAC7B,KAAK,CAAC,IAAI,CAAC,UAAU,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;AACjE,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CACxB,GAAa,EACb,CAAU,EACV,SAAiB,EACjB,UAAwC;IAExC,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;IACtF,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAC1D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,UAAU,CAAC,GAAG,CAChC,CAAC,SAAS,EAAE,EAAE,CACZ,GAAG,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,uBAAuB,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM;QAChF,GAAG,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CACpC,CAAC;IACF,OAAO;QACL,GAAG,EAAE,QAAQ,WAAW;2CACe,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;;6BAEpC,GAAG;;OAEzB;QACH,IAAI,EAAE;kBACQ,WAAW,OAAO,WAAW,iCAAiC;KAC7E,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAwB,EACxB,SAAiB,EACjB,IAAyB,EACzB,CAAU;IAEV,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,CAAC;IACnC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,WAAW,MAAM,CAAC,EAAE,0EAA0E,CAC/F,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,eAAe,CAAC,CAAC;IACxF,8EAA8E;IAC9E,wDAAwD;IACxD,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,EAAE,CAAC,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IAEpE,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG;QACZ,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC;QACrC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC;QACvB,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC;QACtB,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC;QAC9C,QAAQ,CAAC,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC;QACpD,QAAQ,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC;QAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC;QACxC,gBAAgB,CAAC,GAAG,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC;QACzC,YAAY,CAAC,GAAG,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC;QAC5C,oBAAoB,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC;QAChD,cAAc,CAAC,GAAG,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC;KAChD,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEX,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG;QACf,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,UAAU,OAAO,SAAS,CAAC,MAAM,EAAE,CAAC;QAClF,GAAG,QAAQ;KACZ,CAAC;IAEF,4EAA4E;IAC5E,sEAAsE;IACtE,wEAAwE;IACxE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,GAAG,CAAC;IAClE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7D,MAAM,KAAK,GAAG;QACZ,GAAG,OAAO,IAAI,SAAS,EAAE;QACzB,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,UAAU,MAAM,CAAC;KAChE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,qBAAqB,CAAC,CAAC;IAC3E,MAAM,OAAO,GACX,UAAU,CAAC,MAAM,KAAK,CAAC;QACrB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC;iBACS,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAEhF,OAAO;QACL,KAAK,EAAE;QACH,UAAU,CAAC,GAAG,UAAU,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;mBAChC,UAAU,CAAC,IAAI;kCACA,GAAG,GAAG,KAAK,GAAG,OAAO;iBACtC,KAAK;cACR,KAAK;KACd;QACD,MAAM,EAAE,MAAM,CAAC,EAAc;QAC7B,YAAY,EAAE,GAAG,CAAC,MAAM;KACzB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAwB,EACxB,UAAkC;IAElC,OAAO;QACL,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACpE,GAAG,CAAC,MAAM,CAAC,cAAc,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;KAC5E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The query DSL's compilation tiers (ADR 0051 §3, design sketch §C.2), and the
|
|
3
|
+
* four pure layers that sit on top of them.
|
|
4
|
+
*
|
|
5
|
+
* - **delegated** (`compile.ts`) — a validated `queryV1` document is mapped onto
|
|
6
|
+
* the metric's existing aggregation builder, so every metric is reachable at
|
|
7
|
+
* exactly the power its canned endpoint has.
|
|
8
|
+
* - **generic** (`generic.ts`) — a metric that declares `genericGroupBy` is
|
|
9
|
+
* recomputed at any grain it declares, by one shared dialect-authored builder.
|
|
10
|
+
*
|
|
11
|
+
* On top: `compare.ts` joins two runs of the same spec, `significance.ts` says
|
|
12
|
+
* whether the difference is real, `explain.ts` renders the plan and the reasons
|
|
13
|
+
* not to trust it, and `order.ts` re-sorts a delegated result honestly. All four
|
|
14
|
+
* are pure — rows in, rows out — and none of them runs a query.
|
|
15
|
+
*/
|
|
16
|
+
export { builderFor } from "./builders.js";
|
|
17
|
+
export type { AggregationBuilder } from "./builders.js";
|
|
18
|
+
export { cameraTypeForMode, compileMetric, compileQuery, toBuilderOptions } from "./compile.js";
|
|
19
|
+
export type { MetricQueryOptions, QueryResolution } from "./compile.js";
|
|
20
|
+
export { compileGenericGroupBy, genericResultColumns } from "./generic.js";
|
|
21
|
+
export type { GenericDeviceFilter, GenericEventPredicate, GenericQueryOptions } from "./generic.js";
|
|
22
|
+
export { compareRows, comparisonKeys, summarizeComparison } from "./compare.js";
|
|
23
|
+
export type { ComparisonBasis, ComparisonContext, ComparisonMeta, ComparisonResult, ComparisonRow, ComparisonSide, MoversSummary, } from "./compare.js";
|
|
24
|
+
export { DEFAULT_ALPHA, normalCdf, sampleOf, studentTTwoSided, twoProportionZ, welchT, wilsonScoreInterval, } from "./significance.js";
|
|
25
|
+
export type { MeanSignificance, Proportion, ProportionSignificance, Sample, ScoreInterval, Significance, } from "./significance.js";
|
|
26
|
+
export { channelRows, explainQuery, explainSpec, planWarnings, silentChannels } from "./explain.js";
|
|
27
|
+
export type { ExplainParam, ExplainParamType, PlanContext, QueryPlan } from "./explain.js";
|
|
28
|
+
export { ORDER_AFTER_CAP_CAVEAT, applyOrder, reordersCappedResult } from "./order.js";
|
|
29
|
+
export type { ResultOrder } from "./order.js";
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/query/dsl/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChG,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAC3E,YAAY,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAChF,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,aAAa,GACd,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,cAAc,EACd,MAAM,EACN,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,sBAAsB,EACtB,MAAM,EACN,aAAa,EACb,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACpG,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACtF,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The query DSL's compilation tiers (ADR 0051 §3, design sketch §C.2), and the
|
|
3
|
+
* four pure layers that sit on top of them.
|
|
4
|
+
*
|
|
5
|
+
* - **delegated** (`compile.ts`) — a validated `queryV1` document is mapped onto
|
|
6
|
+
* the metric's existing aggregation builder, so every metric is reachable at
|
|
7
|
+
* exactly the power its canned endpoint has.
|
|
8
|
+
* - **generic** (`generic.ts`) — a metric that declares `genericGroupBy` is
|
|
9
|
+
* recomputed at any grain it declares, by one shared dialect-authored builder.
|
|
10
|
+
*
|
|
11
|
+
* On top: `compare.ts` joins two runs of the same spec, `significance.ts` says
|
|
12
|
+
* whether the difference is real, `explain.ts` renders the plan and the reasons
|
|
13
|
+
* not to trust it, and `order.ts` re-sorts a delegated result honestly. All four
|
|
14
|
+
* are pure — rows in, rows out — and none of them runs a query.
|
|
15
|
+
*/
|
|
16
|
+
export { builderFor } from "./builders.js";
|
|
17
|
+
export { cameraTypeForMode, compileMetric, compileQuery, toBuilderOptions } from "./compile.js";
|
|
18
|
+
export { compileGenericGroupBy, genericResultColumns } from "./generic.js";
|
|
19
|
+
export { compareRows, comparisonKeys, summarizeComparison } from "./compare.js";
|
|
20
|
+
export { DEFAULT_ALPHA, normalCdf, sampleOf, studentTTwoSided, twoProportionZ, welchT, wilsonScoreInterval, } from "./significance.js";
|
|
21
|
+
export { channelRows, explainQuery, explainSpec, planWarnings, silentChannels } from "./explain.js";
|
|
22
|
+
export { ORDER_AFTER_CAP_CAVEAT, applyOrder, reordersCappedResult } from "./order.js";
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/query/dsl/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhG,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAE3E,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAUhF,OAAO,EACL,aAAa,EACb,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,cAAc,EACd,MAAM,EACN,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAS3B,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEpG,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **`order`: on the delegated tier** (ADR 0051 §3).
|
|
3
|
+
*
|
|
4
|
+
* The generic builder puts the caller's `order` straight into its `ORDER BY`, so
|
|
5
|
+
* the row cap keeps the rows the caller asked for. A *delegated* metric cannot:
|
|
6
|
+
* its builder has one `ORDER BY` of its own, and it applied the `LIMIT` in that
|
|
7
|
+
* order before these rows existed.
|
|
8
|
+
*
|
|
9
|
+
* So a delegated re-order is exactly what it looks like — a re-sort of the rows
|
|
10
|
+
* that came back, done here rather than in the model's head. What it is **not**
|
|
11
|
+
* is a "bottom N": asking `top_meshes` for `count ASC` returns the *top* rows
|
|
12
|
+
* sorted ascending, because the store already discarded everything below the
|
|
13
|
+
* cap. {@link ORDER_AFTER_CAP_CAVEAT} says so, and the collector attaches it to
|
|
14
|
+
* the envelope whenever this function actually changed the order of a capped
|
|
15
|
+
* result — which is the only honest way to offer the feature at all.
|
|
16
|
+
*/
|
|
17
|
+
/** A row as it leaves a store. */
|
|
18
|
+
type Row = Readonly<Record<string, unknown>>;
|
|
19
|
+
/** What `order` asks for. */
|
|
20
|
+
export interface ResultOrder {
|
|
21
|
+
by: string;
|
|
22
|
+
dir: "asc" | "desc";
|
|
23
|
+
}
|
|
24
|
+
/** Attached to a result whose delegated rows were re-ordered after the cap. */
|
|
25
|
+
export declare const ORDER_AFTER_CAP_CAVEAT: string;
|
|
26
|
+
/**
|
|
27
|
+
* Re-sort rows by a column, missing and non-numeric values last in either
|
|
28
|
+
* direction (a row with no value is not "the smallest"; it is unmeasured).
|
|
29
|
+
* Ties break on the remaining columns' rendered values so the order is a
|
|
30
|
+
* function of the row set rather than of the engine's scan order.
|
|
31
|
+
*
|
|
32
|
+
* Returns a new array; the input is never mutated.
|
|
33
|
+
*/
|
|
34
|
+
export declare function applyOrder<T extends Row>(rows: readonly T[], order: ResultOrder): T[];
|
|
35
|
+
/**
|
|
36
|
+
* Whether re-ordering `rows` could misrepresent a capped result — the condition
|
|
37
|
+
* under which {@link ORDER_AFTER_CAP_CAVEAT} belongs on the envelope.
|
|
38
|
+
*/
|
|
39
|
+
export declare function reordersCappedResult(rows: readonly Row[], limit: number | undefined): boolean;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=order.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order.d.ts","sourceRoot":"","sources":["../../../src/query/dsl/order.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,kCAAkC;AAClC,KAAK,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7C,6BAA6B;AAC7B,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,KAAK,GAAG,MAAM,CAAC;CACrB;AAED,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB,QAGiD,CAAC;AAErF;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,CAAC,EAAE,CAgBrF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE7F"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **`order`: on the delegated tier** (ADR 0051 §3).
|
|
3
|
+
*
|
|
4
|
+
* The generic builder puts the caller's `order` straight into its `ORDER BY`, so
|
|
5
|
+
* the row cap keeps the rows the caller asked for. A *delegated* metric cannot:
|
|
6
|
+
* its builder has one `ORDER BY` of its own, and it applied the `LIMIT` in that
|
|
7
|
+
* order before these rows existed.
|
|
8
|
+
*
|
|
9
|
+
* So a delegated re-order is exactly what it looks like — a re-sort of the rows
|
|
10
|
+
* that came back, done here rather than in the model's head. What it is **not**
|
|
11
|
+
* is a "bottom N": asking `top_meshes` for `count ASC` returns the *top* rows
|
|
12
|
+
* sorted ascending, because the store already discarded everything below the
|
|
13
|
+
* cap. {@link ORDER_AFTER_CAP_CAVEAT} says so, and the collector attaches it to
|
|
14
|
+
* the envelope whenever this function actually changed the order of a capped
|
|
15
|
+
* result — which is the only honest way to offer the feature at all.
|
|
16
|
+
*/
|
|
17
|
+
/** Attached to a result whose delegated rows were re-ordered after the cap. */
|
|
18
|
+
export const ORDER_AFTER_CAP_CAVEAT = "`order` was applied to the rows this metric returned, which its own builder had already " +
|
|
19
|
+
"capped in its own order — so an ascending sort shows the smallest of the top rows, not the " +
|
|
20
|
+
"smallest overall. Raise `limit`, or group by the dimension you want the tail of.";
|
|
21
|
+
/**
|
|
22
|
+
* Re-sort rows by a column, missing and non-numeric values last in either
|
|
23
|
+
* direction (a row with no value is not "the smallest"; it is unmeasured).
|
|
24
|
+
* Ties break on the remaining columns' rendered values so the order is a
|
|
25
|
+
* function of the row set rather than of the engine's scan order.
|
|
26
|
+
*
|
|
27
|
+
* Returns a new array; the input is never mutated.
|
|
28
|
+
*/
|
|
29
|
+
export function applyOrder(rows, order) {
|
|
30
|
+
const sign = order.dir === "asc" ? 1 : -1;
|
|
31
|
+
return [...rows].sort((a, b) => {
|
|
32
|
+
const left = a[order.by];
|
|
33
|
+
const right = b[order.by];
|
|
34
|
+
const leftNumber = typeof left === "number" && Number.isFinite(left) ? left : null;
|
|
35
|
+
const rightNumber = typeof right === "number" && Number.isFinite(right) ? right : null;
|
|
36
|
+
if (leftNumber == null || rightNumber == null) {
|
|
37
|
+
if (leftNumber !== rightNumber)
|
|
38
|
+
return leftNumber == null ? 1 : -1;
|
|
39
|
+
}
|
|
40
|
+
else if (leftNumber !== rightNumber) {
|
|
41
|
+
return sign * (leftNumber - rightNumber);
|
|
42
|
+
}
|
|
43
|
+
const leftKey = JSON.stringify(a);
|
|
44
|
+
const rightKey = JSON.stringify(b);
|
|
45
|
+
return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Whether re-ordering `rows` could misrepresent a capped result — the condition
|
|
50
|
+
* under which {@link ORDER_AFTER_CAP_CAVEAT} belongs on the envelope.
|
|
51
|
+
*/
|
|
52
|
+
export function reordersCappedResult(rows, limit) {
|
|
53
|
+
return limit != null && Number.isFinite(limit) && rows.length >= limit;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order.js","sourceRoot":"","sources":["../../../src/query/dsl/order.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,sBAAsB,GACjC,0FAA0F;IAC1F,6FAA6F;IAC7F,kFAAkF,CAAC;AAErF;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAgB,IAAkB,EAAE,KAAkB;IAC9E,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACzB,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1B,MAAM,UAAU,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;QACnF,MAAM,WAAW,GAAG,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QACvF,IAAI,UAAU,IAAI,IAAI,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;YAC9C,IAAI,UAAU,KAAK,WAAW;gBAAE,OAAO,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrE,CAAC;aAAM,IAAI,UAAU,KAAK,WAAW,EAAE,CAAC;YACtC,OAAO,IAAI,GAAG,CAAC,UAAU,GAAG,WAAW,CAAC,CAAC;QAC3C,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QACnC,OAAO,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAoB,EAAE,KAAyB;IAClF,OAAO,KAAK,IAAI,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC;AACzE,CAAC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **Is this change real?** — the two significance tests `compare` can justify
|
|
3
|
+
* (ADR 0051 §4, design sketch §C.2).
|
|
4
|
+
*
|
|
5
|
+
* Pure functions over numbers: no registry, no store, no I/O. They exist here
|
|
6
|
+
* rather than inline in `compare.ts` so they can be unit-tested against
|
|
7
|
+
* textbook worked examples, which is the only way to be sure a statistic is
|
|
8
|
+
* right — a plausible-looking number is exactly the failure mode this file is
|
|
9
|
+
* meant to prevent, since a model will quote whatever it is handed.
|
|
10
|
+
*
|
|
11
|
+
* Two tests, because analytics produces two shapes of measure:
|
|
12
|
+
*
|
|
13
|
+
* - {@link twoProportionZ} — a **count or rate**. "Was 40/1000 this week
|
|
14
|
+
* different from 25/900 last week?" Pooled two-proportion z, two-sided normal
|
|
15
|
+
* p-value, plus a Wilson score interval on each proportion (which, unlike the
|
|
16
|
+
* Wald interval, stays inside 0..1 at the small counts a long tail produces —
|
|
17
|
+
* the same reason the summariser already uses it).
|
|
18
|
+
* - {@link welchT} — a **mean** over two samples of unequal size and variance.
|
|
19
|
+
* Welch's unequal-variance t-test with the Welch–Satterthwaite degrees of
|
|
20
|
+
* freedom. It applies where the two ranges really do give two *samples*: a
|
|
21
|
+
* `bucket`-grain metric compared range-over-range hands us one value per
|
|
22
|
+
* bucket on each side.
|
|
23
|
+
*
|
|
24
|
+
* Neither is applied speculatively. `compare.ts` computes a significance only
|
|
25
|
+
* when the registry says the measure supports it (`unit: count | sessions`, or a
|
|
26
|
+
* `rateOf` denominator) and the denominator clears the metric's
|
|
27
|
+
* `comparable.minSample`; otherwise the field is simply absent, because
|
|
28
|
+
* "no p-value" is a far better answer than a p-value computed from a
|
|
29
|
+
* denominator that cannot support one.
|
|
30
|
+
*
|
|
31
|
+
* ## Accuracy
|
|
32
|
+
*
|
|
33
|
+
* The normal CDF uses the Abramowitz & Stegun 7.1.26 rational approximation of
|
|
34
|
+
* `erf` (|error| < 1.5e-7). The Student-t CDF uses the regularised incomplete
|
|
35
|
+
* beta function via the Lentz continued fraction (Numerical Recipes §6.4),
|
|
36
|
+
* converged to 3e-16 or 300 iterations. Both are far tighter than the third
|
|
37
|
+
* significant figure anyone reads off a p-value.
|
|
38
|
+
*/
|
|
39
|
+
/** A 95% (by default) score interval on a proportion. */
|
|
40
|
+
export interface ScoreInterval {
|
|
41
|
+
low: number;
|
|
42
|
+
high: number;
|
|
43
|
+
}
|
|
44
|
+
/** One side of a proportion comparison. */
|
|
45
|
+
export interface Proportion {
|
|
46
|
+
/** Events (or sessions) in this group. */
|
|
47
|
+
successes: number;
|
|
48
|
+
/** Events (or sessions) in the population the group is part of. */
|
|
49
|
+
trials: number;
|
|
50
|
+
}
|
|
51
|
+
/** The result of a two-proportion z test. */
|
|
52
|
+
export interface ProportionSignificance {
|
|
53
|
+
test: "two-proportion-z";
|
|
54
|
+
/** Share of the current population. */
|
|
55
|
+
current: number;
|
|
56
|
+
/** Share of the previous population. */
|
|
57
|
+
previous: number;
|
|
58
|
+
/** `current - previous`, in share points. */
|
|
59
|
+
diff: number;
|
|
60
|
+
/** The test statistic. */
|
|
61
|
+
z: number;
|
|
62
|
+
/** Two-sided p-value under the normal approximation. */
|
|
63
|
+
pValue: number;
|
|
64
|
+
/** Whether `pValue` clears the level the caller asked for. */
|
|
65
|
+
significant: boolean;
|
|
66
|
+
/** 95% Wilson score interval on the current share. */
|
|
67
|
+
currentInterval: ScoreInterval;
|
|
68
|
+
/** 95% Wilson score interval on the previous share. */
|
|
69
|
+
previousInterval: ScoreInterval;
|
|
70
|
+
}
|
|
71
|
+
/** The result of Welch's unequal-variance t test. */
|
|
72
|
+
export interface MeanSignificance {
|
|
73
|
+
test: "welch-t";
|
|
74
|
+
current: number;
|
|
75
|
+
previous: number;
|
|
76
|
+
diff: number;
|
|
77
|
+
/** The test statistic. */
|
|
78
|
+
t: number;
|
|
79
|
+
/** Welch–Satterthwaite degrees of freedom. */
|
|
80
|
+
df: number;
|
|
81
|
+
pValue: number;
|
|
82
|
+
significant: boolean;
|
|
83
|
+
/** Samples behind each mean — a t-test on two points is not evidence. */
|
|
84
|
+
currentSamples: number;
|
|
85
|
+
previousSamples: number;
|
|
86
|
+
}
|
|
87
|
+
/** Either shape of significance a comparison row can carry. */
|
|
88
|
+
export type Significance = ProportionSignificance | MeanSignificance;
|
|
89
|
+
/** The default two-sided level. */
|
|
90
|
+
export declare const DEFAULT_ALPHA = 0.05;
|
|
91
|
+
/** The standard normal CDF, Φ(x). */
|
|
92
|
+
export declare function normalCdf(x: number): number;
|
|
93
|
+
/** The two-sided p-value of a Student-t statistic with `df` degrees of freedom. */
|
|
94
|
+
export declare function studentTTwoSided(t: number, df: number): number;
|
|
95
|
+
/**
|
|
96
|
+
* A Wilson score interval on `successes / trials`.
|
|
97
|
+
*
|
|
98
|
+
* The same interval the summary envelope reports, restated here so this module
|
|
99
|
+
* stays free of the summariser (and vice versa): both need it, neither should
|
|
100
|
+
* import the other.
|
|
101
|
+
*/
|
|
102
|
+
export declare function wilsonScoreInterval(successes: number, trials: number, z?: number): ScoreInterval;
|
|
103
|
+
/**
|
|
104
|
+
* Pooled two-proportion z test: is `current.successes / current.trials`
|
|
105
|
+
* different from `previous.successes / previous.trials`?
|
|
106
|
+
*
|
|
107
|
+
* Returns `null` when either side has no trials — a proportion of nothing is not
|
|
108
|
+
* a proportion, and reporting `z = 0` for it would read as "no change" when the
|
|
109
|
+
* truth is "no data".
|
|
110
|
+
*/
|
|
111
|
+
export declare function twoProportionZ(current: Proportion, previous: Proportion, alpha?: number): ProportionSignificance | null;
|
|
112
|
+
/** One sample, described by the three numbers Welch's test needs. */
|
|
113
|
+
export interface Sample {
|
|
114
|
+
mean: number;
|
|
115
|
+
/** Unbiased (n − 1) sample variance. */
|
|
116
|
+
variance: number;
|
|
117
|
+
/** Number of observations. */
|
|
118
|
+
n: number;
|
|
119
|
+
}
|
|
120
|
+
/** Mean and unbiased variance of a list of observations; `null` below two. */
|
|
121
|
+
export declare function sampleOf(values: readonly number[]): Sample | null;
|
|
122
|
+
/**
|
|
123
|
+
* Welch's unequal-variance t test with the Welch–Satterthwaite degrees of
|
|
124
|
+
* freedom.
|
|
125
|
+
*
|
|
126
|
+
* Returns `null` when either sample has fewer than two observations (there is no
|
|
127
|
+
* variance to test) or when both variances are zero and the means are equal.
|
|
128
|
+
*/
|
|
129
|
+
export declare function welchT(current: Sample, previous: Sample, alpha?: number): MeanSignificance | null;
|
|
130
|
+
//# sourceMappingURL=significance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"significance.d.ts","sourceRoot":"","sources":["../../../src/query/dsl/significance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,yDAAyD;AACzD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,6CAA6C;AAC7C,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,kBAAkB,CAAC;IACzB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,WAAW,EAAE,OAAO,CAAC;IACrB,sDAAsD;IACtD,eAAe,EAAE,aAAa,CAAC;IAC/B,uDAAuD;IACvD,gBAAgB,EAAE,aAAa,CAAC;CACjC;AAED,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,yEAAyE;IACzE,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,+DAA+D;AAC/D,MAAM,MAAM,YAAY,GAAG,sBAAsB,GAAG,gBAAgB,CAAC;AAErE,mCAAmC;AACnC,eAAO,MAAM,aAAa,OAAO,CAAC;AAmBlC,qCAAqC;AACrC,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;AAiED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,CAAC,GAAE,MAAa,GACf,aAAa,CAUf;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,UAAU,EACpB,KAAK,GAAE,MAAsB,GAC5B,sBAAsB,GAAG,IAAI,CAwB/B;AAED,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,CAAC,EAAE,MAAM,CAAC;CACX;AAED,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAOjE;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CACpB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAsB,GAC5B,gBAAgB,GAAG,IAAI,CAqBzB"}
|