@gscdump/analysis 0.4.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/LICENSE +21 -0
- package/README.md +251 -0
- package/dist/analyzer/index.d.mts +893 -0
- package/dist/analyzer/index.mjs +4944 -0
- package/dist/default-registry.d.mts +93 -0
- package/dist/default-registry.mjs +1957 -0
- package/dist/index.d.mts +620 -0
- package/dist/index.mjs +2873 -0
- package/dist/period/index.d.mts +57 -0
- package/dist/period/index.mjs +150 -0
- package/dist/query/index.d.mts +26 -0
- package/dist/query/index.mjs +340 -0
- package/dist/semantic/index.d.mts +70 -0
- package/dist/semantic/index.mjs +391 -0
- package/dist/source/index.d.mts +427 -0
- package/dist/source/index.mjs +1865 -0
- package/package.json +86 -0
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
import { BuilderState } from "gscdump/query";
|
|
2
|
+
import { AnalysisQuerySource, FileSet, FileSet as FileSet$1 } from "@gscdump/engine/resolver";
|
|
3
|
+
import { AnalysisParams, AnalysisResult } from "gscdump/contracts";
|
|
4
|
+
import { Row } from "@gscdump/engine/contracts";
|
|
5
|
+
/**
|
|
6
|
+
* `bayesian-ctr` — Empirical-Bayes CTR shrinkage. SQL-only colocation.
|
|
7
|
+
*
|
|
8
|
+
* Migrated from `engine-duckdb-node/src/analyzers/bayesian-ctr.ts`.
|
|
9
|
+
*/
|
|
10
|
+
interface BayesianCtrResult {
|
|
11
|
+
keyword: string;
|
|
12
|
+
page: string;
|
|
13
|
+
clicks: number;
|
|
14
|
+
impressions: number;
|
|
15
|
+
observedCtr: number;
|
|
16
|
+
position: number;
|
|
17
|
+
bucket: number;
|
|
18
|
+
priorAlpha: number;
|
|
19
|
+
priorBeta: number;
|
|
20
|
+
bucketPriorMean: number;
|
|
21
|
+
posteriorMean: number;
|
|
22
|
+
posteriorSd: number;
|
|
23
|
+
ciLow: number;
|
|
24
|
+
ciHigh: number;
|
|
25
|
+
shrinkageDelta: number;
|
|
26
|
+
expectedClicksDelta: number;
|
|
27
|
+
significance: number;
|
|
28
|
+
classification: 'overperforming' | 'underperforming' | 'expected';
|
|
29
|
+
}
|
|
30
|
+
declare const bayesianCtrAnalyzer: DefinedAnalyzer;
|
|
31
|
+
/**
|
|
32
|
+
* bipartite-pagerank
|
|
33
|
+
*
|
|
34
|
+
* Personalized PageRank on the (query <-> url) bipartite graph, edge-weighted
|
|
35
|
+
* by impressions. Each power-iteration "step" is two half-steps: queries
|
|
36
|
+
* receive mass from URLs, then URLs receive mass from queries. We express
|
|
37
|
+
* the fixed-N iteration as a chain of CTEs built programmatically (bounded
|
|
38
|
+
* unroll). This sidesteps DuckDB's recursive-CTE restriction on aggregate
|
|
39
|
+
* functions over the recursive reference; the natural formulation needs
|
|
40
|
+
* SUM(prev.rank * weight) GROUP BY node_id, which recursive CTEs reject.
|
|
41
|
+
*
|
|
42
|
+
* "Hub queries" bridge many URLs with roughly-even mass distribution;
|
|
43
|
+
* "hub URLs" anchor many queries the same way. Rank is eigenvector
|
|
44
|
+
* centrality: a node is important if it links to other important nodes.
|
|
45
|
+
*/
|
|
46
|
+
interface BipartitePagerankNode {
|
|
47
|
+
kind: 'query' | 'url';
|
|
48
|
+
id: string;
|
|
49
|
+
rank: number;
|
|
50
|
+
bridging: number;
|
|
51
|
+
anchoring: number;
|
|
52
|
+
degree: number;
|
|
53
|
+
impressions: number;
|
|
54
|
+
}
|
|
55
|
+
type BipartitePagerankResult = BipartitePagerankNode;
|
|
56
|
+
declare const bipartitePagerankAnalyzer: DefinedAnalyzer;
|
|
57
|
+
type SortOrder = 'asc' | 'desc';
|
|
58
|
+
/** Base search metrics */
|
|
59
|
+
interface BaseMetrics {
|
|
60
|
+
clicks: number;
|
|
61
|
+
impressions: number;
|
|
62
|
+
ctr: number;
|
|
63
|
+
position: number;
|
|
64
|
+
}
|
|
65
|
+
/** Keyword row from query */
|
|
66
|
+
interface KeywordRow extends BaseMetrics {
|
|
67
|
+
query: string;
|
|
68
|
+
page?: string;
|
|
69
|
+
}
|
|
70
|
+
/** Page row from query */
|
|
71
|
+
interface PageRow extends BaseMetrics {
|
|
72
|
+
page: string;
|
|
73
|
+
}
|
|
74
|
+
interface BrandSegmentationOptions {
|
|
75
|
+
/** Brand terms to match against keywords (case-insensitive) */
|
|
76
|
+
brandTerms: string[];
|
|
77
|
+
/** Minimum impressions for a keyword to be included. Default: 10 */
|
|
78
|
+
minImpressions?: number;
|
|
79
|
+
}
|
|
80
|
+
interface BrandSummary {
|
|
81
|
+
brandClicks: number;
|
|
82
|
+
nonBrandClicks: number;
|
|
83
|
+
brandShare: number;
|
|
84
|
+
brandImpressions: number;
|
|
85
|
+
nonBrandImpressions: number;
|
|
86
|
+
}
|
|
87
|
+
interface BrandSegmentationResult {
|
|
88
|
+
brand: KeywordRow[];
|
|
89
|
+
nonBrand: KeywordRow[];
|
|
90
|
+
summary: BrandSummary;
|
|
91
|
+
}
|
|
92
|
+
interface BrandResultRow {
|
|
93
|
+
query: string;
|
|
94
|
+
page?: string;
|
|
95
|
+
clicks: number;
|
|
96
|
+
impressions: number;
|
|
97
|
+
ctr: number;
|
|
98
|
+
position: number;
|
|
99
|
+
segment: 'brand' | 'non-brand';
|
|
100
|
+
}
|
|
101
|
+
declare const brandAnalyzer: DefinedAnalyzer;
|
|
102
|
+
type CannibalizationSortMetric = 'clicks' | 'impressions' | 'positionSpread' | 'pageCount';
|
|
103
|
+
interface CannibalizationOptions {
|
|
104
|
+
/** Minimum impressions for a query to be considered. Default: 10 */
|
|
105
|
+
minImpressions?: number;
|
|
106
|
+
/** Maximum position spread to flag as cannibalization. Default: 10 */
|
|
107
|
+
maxPositionSpread?: number;
|
|
108
|
+
/** Minimum number of pages ranking for same query. Default: 2 */
|
|
109
|
+
minPages?: number;
|
|
110
|
+
/** Sort metric. Default: clicks */
|
|
111
|
+
sortBy?: CannibalizationSortMetric;
|
|
112
|
+
/** Sort order. Default: desc */
|
|
113
|
+
sortOrder?: SortOrder;
|
|
114
|
+
}
|
|
115
|
+
interface CannibalizationPage {
|
|
116
|
+
page: string;
|
|
117
|
+
clicks: number;
|
|
118
|
+
impressions: number;
|
|
119
|
+
ctr: number;
|
|
120
|
+
position: number;
|
|
121
|
+
}
|
|
122
|
+
interface CannibalizationResult {
|
|
123
|
+
query: string;
|
|
124
|
+
pages: CannibalizationPage[];
|
|
125
|
+
totalClicks: number;
|
|
126
|
+
totalImpressions: number;
|
|
127
|
+
positionSpread: number;
|
|
128
|
+
}
|
|
129
|
+
interface CannibalizationCompetitor {
|
|
130
|
+
url: string;
|
|
131
|
+
clicks: number;
|
|
132
|
+
impressions: number;
|
|
133
|
+
ctr: number;
|
|
134
|
+
position: number;
|
|
135
|
+
share: number;
|
|
136
|
+
rank: number;
|
|
137
|
+
}
|
|
138
|
+
interface CannibalizationEvent {
|
|
139
|
+
keyword: string;
|
|
140
|
+
totalImpressions: number;
|
|
141
|
+
totalClicks: number;
|
|
142
|
+
competitorCount: number;
|
|
143
|
+
leaderUrl: string;
|
|
144
|
+
leaderCtr: number;
|
|
145
|
+
leaderPosition: number;
|
|
146
|
+
hhi: number;
|
|
147
|
+
fragmentation: number;
|
|
148
|
+
stolenClicks: number;
|
|
149
|
+
severity: number;
|
|
150
|
+
competitors: CannibalizationCompetitor[];
|
|
151
|
+
}
|
|
152
|
+
declare const cannibalizationAnalyzer: DefinedAnalyzer;
|
|
153
|
+
/**
|
|
154
|
+
* `change-point` — per-(query, url) timeseries change-point detection. SQL-only.
|
|
155
|
+
*
|
|
156
|
+
* Migrated from `engine-duckdb-node/src/analyzers/change-point.ts`.
|
|
157
|
+
*/
|
|
158
|
+
interface ChangePointSeriesPoint {
|
|
159
|
+
date: string;
|
|
160
|
+
value: number;
|
|
161
|
+
}
|
|
162
|
+
interface ChangePointResult {
|
|
163
|
+
keyword: string;
|
|
164
|
+
page: string;
|
|
165
|
+
totalDays: number;
|
|
166
|
+
totalImpressions: number;
|
|
167
|
+
changeDate: string;
|
|
168
|
+
llr: number;
|
|
169
|
+
leftMean: number;
|
|
170
|
+
rightMean: number;
|
|
171
|
+
delta: number;
|
|
172
|
+
leftStddev: number;
|
|
173
|
+
rightStddev: number;
|
|
174
|
+
direction: 'improved' | 'worsened';
|
|
175
|
+
series: ChangePointSeriesPoint[];
|
|
176
|
+
}
|
|
177
|
+
declare const changePointAnalyzer: DefinedAnalyzer;
|
|
178
|
+
type ClusterType = 'prefix' | 'intent' | 'both';
|
|
179
|
+
interface ClusteringOptions {
|
|
180
|
+
/** Minimum keywords for a cluster to be reported. Default: 2 */
|
|
181
|
+
minClusterSize?: number;
|
|
182
|
+
/** Minimum impressions for a keyword to be included. Default: 10 */
|
|
183
|
+
minImpressions?: number;
|
|
184
|
+
/** Clustering method. Default: 'both' */
|
|
185
|
+
clusterBy?: ClusterType;
|
|
186
|
+
}
|
|
187
|
+
interface KeywordCluster {
|
|
188
|
+
clusterName: string;
|
|
189
|
+
clusterType: 'prefix' | 'intent';
|
|
190
|
+
keywords: KeywordRow[];
|
|
191
|
+
totalClicks: number;
|
|
192
|
+
totalImpressions: number;
|
|
193
|
+
avgPosition: number;
|
|
194
|
+
keywordCount: number;
|
|
195
|
+
}
|
|
196
|
+
interface ClusteringResult {
|
|
197
|
+
clusters: KeywordCluster[];
|
|
198
|
+
unclustered: KeywordRow[];
|
|
199
|
+
}
|
|
200
|
+
declare const clusteringAnalyzer: DefinedAnalyzer;
|
|
201
|
+
type ConcentrationRiskLevel = 'low' | 'medium' | 'high';
|
|
202
|
+
interface ConcentrationOptions {
|
|
203
|
+
/** Number of top items to report. Default: 10 */
|
|
204
|
+
topN?: number;
|
|
205
|
+
}
|
|
206
|
+
interface ConcentrationItem {
|
|
207
|
+
key: string;
|
|
208
|
+
clicks: number;
|
|
209
|
+
share: number;
|
|
210
|
+
}
|
|
211
|
+
interface ConcentrationInput {
|
|
212
|
+
key: string;
|
|
213
|
+
clicks: number;
|
|
214
|
+
}
|
|
215
|
+
interface ConcentrationResult {
|
|
216
|
+
/** Gini coefficient: 0 = equal distribution, 1 = fully concentrated */
|
|
217
|
+
giniCoefficient: number;
|
|
218
|
+
/** Herfindahl-Hirschman Index: 0-10000, >2500 = highly concentrated */
|
|
219
|
+
hhi: number;
|
|
220
|
+
/** Percentage of total clicks from top N items */
|
|
221
|
+
topNConcentration: number;
|
|
222
|
+
topNItems: ConcentrationItem[];
|
|
223
|
+
totalItems: number;
|
|
224
|
+
totalClicks: number;
|
|
225
|
+
/** Risk level derived from HHI: <1500 low, 1500-2500 medium, >2500 high */
|
|
226
|
+
riskLevel: ConcentrationRiskLevel;
|
|
227
|
+
}
|
|
228
|
+
declare const concentrationAnalyzer: DefinedAnalyzer;
|
|
229
|
+
/**
|
|
230
|
+
* `content-velocity` — weekly new-keyword cadence. SQL-only colocation.
|
|
231
|
+
*
|
|
232
|
+
* Migrated from `engine-duckdb-node/src/analyzers/content-velocity.ts`.
|
|
233
|
+
*/
|
|
234
|
+
interface ContentVelocityWeek {
|
|
235
|
+
week: string;
|
|
236
|
+
newKeywords: number;
|
|
237
|
+
totalKeywords: number;
|
|
238
|
+
}
|
|
239
|
+
declare const contentVelocityAnalyzer: DefinedAnalyzer;
|
|
240
|
+
/**
|
|
241
|
+
* `ctr-anomaly` — rolling CTR envelope per (query, page). SQL-only colocation.
|
|
242
|
+
*
|
|
243
|
+
* Migrated from `engine-duckdb-node/src/analyzers/ctr-anomaly.ts`.
|
|
244
|
+
*/
|
|
245
|
+
interface CtrAnomalySeriesPoint {
|
|
246
|
+
date: string;
|
|
247
|
+
ctr: number;
|
|
248
|
+
position: number;
|
|
249
|
+
impressions: number;
|
|
250
|
+
rollingCtr: number | null;
|
|
251
|
+
rollingStddev: number | null;
|
|
252
|
+
z: number;
|
|
253
|
+
breach: boolean;
|
|
254
|
+
}
|
|
255
|
+
interface CtrAnomalyResult {
|
|
256
|
+
keyword: string;
|
|
257
|
+
page: string;
|
|
258
|
+
breachDaysDown: number;
|
|
259
|
+
breachDaysUp: number;
|
|
260
|
+
clicksLost: number;
|
|
261
|
+
severity: number;
|
|
262
|
+
maxZ: number;
|
|
263
|
+
baselineCtr: number;
|
|
264
|
+
baselinePosition: number;
|
|
265
|
+
totalImpressions: number;
|
|
266
|
+
totalClicks: number;
|
|
267
|
+
series: CtrAnomalySeriesPoint[];
|
|
268
|
+
}
|
|
269
|
+
declare const ctrAnomalyAnalyzer: DefinedAnalyzer;
|
|
270
|
+
/**
|
|
271
|
+
* `ctr-curve` — CTR by position bucket plus over/under-performing outliers.
|
|
272
|
+
* SQL-only colocation.
|
|
273
|
+
*
|
|
274
|
+
* Migrated from `engine-duckdb-node/src/analyzers/ctr-curve.ts`.
|
|
275
|
+
*/
|
|
276
|
+
interface CtrCurveBucket {
|
|
277
|
+
bucket: string;
|
|
278
|
+
avgCtr: number;
|
|
279
|
+
medianPosition: number;
|
|
280
|
+
keywordCount: number;
|
|
281
|
+
totalClicks: number;
|
|
282
|
+
totalImpressions: number;
|
|
283
|
+
}
|
|
284
|
+
interface CtrCurveOutlier {
|
|
285
|
+
query: string;
|
|
286
|
+
clicks: number;
|
|
287
|
+
impressions: number;
|
|
288
|
+
ctr: number;
|
|
289
|
+
position: number;
|
|
290
|
+
expectedCtr: number;
|
|
291
|
+
ctrDiff: number;
|
|
292
|
+
}
|
|
293
|
+
declare const ctrCurveAnalyzer: DefinedAnalyzer;
|
|
294
|
+
/**
|
|
295
|
+
* dark-traffic — gap between page-level clicks and sum-of-keyword clicks.
|
|
296
|
+
*
|
|
297
|
+
* Reads three tables: pages (primary), keywords (summary aggregate),
|
|
298
|
+
* page_keywords (per-page attribution). Uses `extraFiles` for the latter two.
|
|
299
|
+
*/
|
|
300
|
+
interface DarkTrafficResult {
|
|
301
|
+
url: string;
|
|
302
|
+
totalClicks: number;
|
|
303
|
+
attributedClicks: number;
|
|
304
|
+
darkClicks: number;
|
|
305
|
+
darkPercent: number;
|
|
306
|
+
keywordCount: number;
|
|
307
|
+
}
|
|
308
|
+
declare const darkTrafficAnalyzer: DefinedAnalyzer;
|
|
309
|
+
type DataDetailResult = Row;
|
|
310
|
+
declare const dataDetailAnalyzer: DefinedAnalyzer;
|
|
311
|
+
type DataQueryResult = Row;
|
|
312
|
+
declare const dataQueryAnalyzer: DefinedAnalyzer;
|
|
313
|
+
type DecaySortMetric = 'lostClicks' | 'declinePercent' | 'currentClicks';
|
|
314
|
+
interface DecayOptions {
|
|
315
|
+
/** Minimum clicks in previous period to consider. Default: 50 */
|
|
316
|
+
minPreviousClicks?: number;
|
|
317
|
+
/** Minimum decline percentage (0-1). Default: 0.2 (20%) */
|
|
318
|
+
threshold?: number;
|
|
319
|
+
/** Metric to sort results by. Default: lostClicks */
|
|
320
|
+
sortBy?: DecaySortMetric;
|
|
321
|
+
}
|
|
322
|
+
interface DecayInput {
|
|
323
|
+
current: PageRow[];
|
|
324
|
+
previous: PageRow[];
|
|
325
|
+
}
|
|
326
|
+
interface DecaySeriesPoint {
|
|
327
|
+
week: string;
|
|
328
|
+
clicks: number;
|
|
329
|
+
impressions: number;
|
|
330
|
+
}
|
|
331
|
+
interface DecayResult {
|
|
332
|
+
page: string;
|
|
333
|
+
currentClicks: number;
|
|
334
|
+
previousClicks: number;
|
|
335
|
+
lostClicks: number;
|
|
336
|
+
declinePercent: number;
|
|
337
|
+
currentPosition: number;
|
|
338
|
+
previousPosition: number;
|
|
339
|
+
positionDrop: number;
|
|
340
|
+
series?: DecaySeriesPoint[];
|
|
341
|
+
}
|
|
342
|
+
declare const decayAnalyzer: DefinedAnalyzer;
|
|
343
|
+
/**
|
|
344
|
+
* device-gap — desktop vs mobile CTR/position delta over time.
|
|
345
|
+
*/
|
|
346
|
+
interface DeviceDayMetrics {
|
|
347
|
+
clicks: number;
|
|
348
|
+
impressions: number;
|
|
349
|
+
ctr: number;
|
|
350
|
+
position: number;
|
|
351
|
+
}
|
|
352
|
+
interface DeviceGapResult {
|
|
353
|
+
date: string;
|
|
354
|
+
desktop: DeviceDayMetrics;
|
|
355
|
+
mobile: DeviceDayMetrics;
|
|
356
|
+
gaps: {
|
|
357
|
+
ctrGap: number;
|
|
358
|
+
positionGap: number;
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
declare const deviceGapAnalyzer: DefinedAnalyzer;
|
|
362
|
+
/**
|
|
363
|
+
* intent-atlas
|
|
364
|
+
*
|
|
365
|
+
* Token-cooccurrence clustering. Tokenizes queries via regexp_split_to_array
|
|
366
|
+
* + unnest, drops stop-words and short tokens, weights tokens by impressions,
|
|
367
|
+
* then clusters each query by its top-2 highest-impression tokens (sorted +
|
|
368
|
+
* joined as the cluster key).
|
|
369
|
+
*/
|
|
370
|
+
interface IntentAtlasResult {
|
|
371
|
+
clusterKey: string;
|
|
372
|
+
keywordCount: number;
|
|
373
|
+
totalImpressions: number;
|
|
374
|
+
totalClicks: number;
|
|
375
|
+
ctr: number;
|
|
376
|
+
avgPosition: number;
|
|
377
|
+
keywords: Array<{
|
|
378
|
+
query: string;
|
|
379
|
+
impressions: number;
|
|
380
|
+
clicks: number;
|
|
381
|
+
position: number;
|
|
382
|
+
}>;
|
|
383
|
+
}
|
|
384
|
+
declare const intentAtlasAnalyzer: DefinedAnalyzer;
|
|
385
|
+
/**
|
|
386
|
+
* keyword-breadth — keyword-count histogram across pages + fragile/authority
|
|
387
|
+
* classification.
|
|
388
|
+
*/
|
|
389
|
+
interface KeywordBreadthResult {
|
|
390
|
+
bucket: string;
|
|
391
|
+
pageCount: number;
|
|
392
|
+
}
|
|
393
|
+
declare const keywordBreadthAnalyzer: DefinedAnalyzer;
|
|
394
|
+
/**
|
|
395
|
+
* long-tail
|
|
396
|
+
*
|
|
397
|
+
* Per-page power-law fit on log(rank) vs log(impressions) via DuckDB's
|
|
398
|
+
* REGR_SLOPE / REGR_INTERCEPT / REGR_R2 regression aggregates.
|
|
399
|
+
*/
|
|
400
|
+
interface LongTailResult {
|
|
401
|
+
page: string;
|
|
402
|
+
queryCount: number;
|
|
403
|
+
totalImpressions: number;
|
|
404
|
+
totalClicks: number;
|
|
405
|
+
slope: number;
|
|
406
|
+
intercept: number;
|
|
407
|
+
r2: number;
|
|
408
|
+
headImpressions: number;
|
|
409
|
+
headShare: number;
|
|
410
|
+
fingerprint: 'flat-tail' | 'balanced' | 'head-heavy';
|
|
411
|
+
points: Array<{
|
|
412
|
+
rank: number;
|
|
413
|
+
impressions: number;
|
|
414
|
+
clicks: number;
|
|
415
|
+
query: string;
|
|
416
|
+
}>;
|
|
417
|
+
}
|
|
418
|
+
declare const longTailAnalyzer: DefinedAnalyzer;
|
|
419
|
+
type MoversSortMetric = 'clicks' | 'impressions' | 'clicksChange' | 'impressionsChange' | 'positionChange';
|
|
420
|
+
interface MoversOptions {
|
|
421
|
+
/** Minimum change threshold to flag. Default: 0.2 (20%) */
|
|
422
|
+
changeThreshold?: number;
|
|
423
|
+
/** Minimum impressions in recent period. Default: 50 */
|
|
424
|
+
minImpressions?: number;
|
|
425
|
+
/** Metric to sort results by. Default: clicksChange */
|
|
426
|
+
sortBy?: MoversSortMetric;
|
|
427
|
+
}
|
|
428
|
+
interface MoversInput {
|
|
429
|
+
current: KeywordRow[];
|
|
430
|
+
previous: KeywordRow[];
|
|
431
|
+
/** If periods have different lengths, provide normalization factor (previous/current) */
|
|
432
|
+
normalizationFactor?: number;
|
|
433
|
+
}
|
|
434
|
+
interface MoverData {
|
|
435
|
+
keyword: string;
|
|
436
|
+
page: string | null;
|
|
437
|
+
recentClicks: number;
|
|
438
|
+
recentImpressions: number;
|
|
439
|
+
recentPosition: number;
|
|
440
|
+
baselineClicks: number;
|
|
441
|
+
baselineImpressions: number;
|
|
442
|
+
baselinePosition: number;
|
|
443
|
+
clicksChange: number;
|
|
444
|
+
clicksChangePercent: number;
|
|
445
|
+
impressionsChangePercent: number;
|
|
446
|
+
positionChange: number;
|
|
447
|
+
}
|
|
448
|
+
interface MoversResult {
|
|
449
|
+
rising: MoverData[];
|
|
450
|
+
declining: MoverData[];
|
|
451
|
+
stable: MoverData[];
|
|
452
|
+
}
|
|
453
|
+
interface MoversSeriesPoint {
|
|
454
|
+
week: string;
|
|
455
|
+
clicks: number;
|
|
456
|
+
impressions: number;
|
|
457
|
+
}
|
|
458
|
+
interface MoversResultRow extends MoverData {
|
|
459
|
+
direction: 'rising' | 'declining' | 'stable';
|
|
460
|
+
series?: MoversSeriesPoint[];
|
|
461
|
+
}
|
|
462
|
+
declare const moversAnalyzer: DefinedAnalyzer;
|
|
463
|
+
interface OpportunityFactors {
|
|
464
|
+
positionScore: number;
|
|
465
|
+
impressionScore: number;
|
|
466
|
+
ctrGapScore: number;
|
|
467
|
+
}
|
|
468
|
+
interface OpportunityResult {
|
|
469
|
+
keyword: string;
|
|
470
|
+
page: string | null;
|
|
471
|
+
clicks: number;
|
|
472
|
+
impressions: number;
|
|
473
|
+
ctr: number;
|
|
474
|
+
position: number;
|
|
475
|
+
opportunityScore: number;
|
|
476
|
+
potentialClicks: number;
|
|
477
|
+
factors: OpportunityFactors;
|
|
478
|
+
}
|
|
479
|
+
declare const opportunityAnalyzer: DefinedAnalyzer;
|
|
480
|
+
/**
|
|
481
|
+
* position-distribution — daily count of keywords per position bucket.
|
|
482
|
+
* Matches `/api/sites/[siteId]/position-distribution.get.ts`.
|
|
483
|
+
*/
|
|
484
|
+
interface PositionDistributionResult {
|
|
485
|
+
date: string;
|
|
486
|
+
pos_1_3: number;
|
|
487
|
+
pos_4_10: number;
|
|
488
|
+
pos_11_20: number;
|
|
489
|
+
pos_20_plus: number;
|
|
490
|
+
total: number;
|
|
491
|
+
}
|
|
492
|
+
declare const positionDistributionAnalyzer: DefinedAnalyzer;
|
|
493
|
+
/**
|
|
494
|
+
* position-volatility
|
|
495
|
+
*
|
|
496
|
+
* Per (page, day) compute query-level position STDDEV_POP plus day-over-day
|
|
497
|
+
* shift in weighted avg position via LAG. The resulting "volatility score"
|
|
498
|
+
* (σ + |Δpos|) surfaces pages whose SERP positioning is genuinely noisy,
|
|
499
|
+
* not just pages that rank well. Output is a pages × dates matrix for a
|
|
500
|
+
* calendar-style heatmap.
|
|
501
|
+
*/
|
|
502
|
+
interface PositionVolatilityDay {
|
|
503
|
+
date: string;
|
|
504
|
+
queryCount: number;
|
|
505
|
+
dayImpressions: number;
|
|
506
|
+
avgPosition: number;
|
|
507
|
+
posStddev: number;
|
|
508
|
+
bestPosition: number;
|
|
509
|
+
worstPosition: number;
|
|
510
|
+
dodShift: number;
|
|
511
|
+
volatility: number;
|
|
512
|
+
}
|
|
513
|
+
interface PositionVolatilityResult {
|
|
514
|
+
page: string;
|
|
515
|
+
avgVolatility: number;
|
|
516
|
+
peakVolatility: number;
|
|
517
|
+
totalImpressions: number;
|
|
518
|
+
days: PositionVolatilityDay[];
|
|
519
|
+
}
|
|
520
|
+
declare const positionVolatilityAnalyzer: DefinedAnalyzer;
|
|
521
|
+
/**
|
|
522
|
+
* query-migration
|
|
523
|
+
*
|
|
524
|
+
* Two-period query absorption analysis. For each (page, query) pair lost in
|
|
525
|
+
* the previous period and (page, query) gained in the current period across
|
|
526
|
+
* different* pages, match via either exact string or `levenshtein()` ≤ 2
|
|
527
|
+
* edits. The matched edge represents impressions that *probably* migrated
|
|
528
|
+
* from page-A to page-B as Google reassigned the ranking URL.
|
|
529
|
+
*
|
|
530
|
+
* Output: a sankey-ready edge list grouped by (sourcePage → targetPage),
|
|
531
|
+
* weighted by absorbed impressions, with example query pairs attached.
|
|
532
|
+
*/
|
|
533
|
+
interface QueryMigrationExample {
|
|
534
|
+
sourceQuery: string;
|
|
535
|
+
targetQuery: string;
|
|
536
|
+
absorbed: number;
|
|
537
|
+
matchType: 'exact' | 'fuzzy';
|
|
538
|
+
}
|
|
539
|
+
interface QueryMigrationResult {
|
|
540
|
+
sourcePage: string;
|
|
541
|
+
targetPage: string;
|
|
542
|
+
weight: number;
|
|
543
|
+
queryCount: number;
|
|
544
|
+
exactCount: number;
|
|
545
|
+
fuzzyCount: number;
|
|
546
|
+
examples: QueryMigrationExample[];
|
|
547
|
+
}
|
|
548
|
+
declare const queryMigrationAnalyzer: DefinedAnalyzer;
|
|
549
|
+
type SeasonalityMetric = 'clicks' | 'impressions';
|
|
550
|
+
interface SeasonalityOptions {
|
|
551
|
+
/** Metric to analyze for seasonality. Default: clicks */
|
|
552
|
+
metric?: SeasonalityMetric;
|
|
553
|
+
}
|
|
554
|
+
interface MonthlyData {
|
|
555
|
+
month: string;
|
|
556
|
+
value: number;
|
|
557
|
+
vsAverage: number;
|
|
558
|
+
isPeak: boolean;
|
|
559
|
+
isTrough: boolean;
|
|
560
|
+
}
|
|
561
|
+
interface SeasonalityResult {
|
|
562
|
+
hasSeasonality: boolean;
|
|
563
|
+
/** Coefficient of variation: std dev / mean. Higher = more seasonal. */
|
|
564
|
+
strength: number;
|
|
565
|
+
peakMonths: string[];
|
|
566
|
+
troughMonths: string[];
|
|
567
|
+
monthlyBreakdown: MonthlyData[];
|
|
568
|
+
insufficientData: boolean;
|
|
569
|
+
}
|
|
570
|
+
declare const seasonalityAnalyzer: DefinedAnalyzer;
|
|
571
|
+
/**
|
|
572
|
+
* stl-decompose
|
|
573
|
+
*
|
|
574
|
+
* Classical additive decomposition: observed = trend + seasonal + residual.
|
|
575
|
+
* Trend is a centered 7-day moving average; seasonal is the mean of detrended
|
|
576
|
+
* residuals per-dayofweek per-entity; residual is the leftover. Entities with
|
|
577
|
+
* a large |residual| relative to STDDEV_POP(residual) are flagged anomalous.
|
|
578
|
+
* Not true STL (no iterative LOESS), but faithful where it matters and
|
|
579
|
+
* expressible in pure DuckDB window functions.
|
|
580
|
+
*/
|
|
581
|
+
interface StlDecomposeSeriesPoint {
|
|
582
|
+
date: string;
|
|
583
|
+
observed: number;
|
|
584
|
+
trend: number | null;
|
|
585
|
+
seasonal: number | null;
|
|
586
|
+
residual: number | null;
|
|
587
|
+
anomaly: boolean;
|
|
588
|
+
}
|
|
589
|
+
interface StlDecomposeResult {
|
|
590
|
+
keyword: string;
|
|
591
|
+
page: string;
|
|
592
|
+
totalImpressions: number;
|
|
593
|
+
days: number;
|
|
594
|
+
seasonalStrength: number;
|
|
595
|
+
trendStrength: number;
|
|
596
|
+
residualAnomalies: number;
|
|
597
|
+
trendSlope: number;
|
|
598
|
+
series: StlDecomposeSeriesPoint[];
|
|
599
|
+
}
|
|
600
|
+
declare const stlDecomposeAnalyzer: DefinedAnalyzer;
|
|
601
|
+
/**
|
|
602
|
+
* Unified `striking-distance` analyzer. Spike for the `defineAnalyzer`
|
|
603
|
+
* pattern: one file owns the SQL plan, the row plan, the typed `InputRow`
|
|
604
|
+
* contract, and the shared reducer.
|
|
605
|
+
*
|
|
606
|
+
* SQL and row plans both emit rows matching `StrikingDistanceInputRow`.
|
|
607
|
+
* Filtering (`minPosition`/`maxPosition`/`minImpressions`/`maxCtr`),
|
|
608
|
+
* derivation (`potentialClicks`), and sorting all live in the reducer, so
|
|
609
|
+
* drift between plans cannot silently change results. A SQL-side `LIMIT`
|
|
610
|
+
* push-down can be added later as an optional optimization, but the
|
|
611
|
+
* correctness contract stays with the reducer.
|
|
612
|
+
*/
|
|
613
|
+
/**
|
|
614
|
+
* Row shape both plans produce. Field names match GSC's native columns so
|
|
615
|
+
* the row-source path needs no rename step; the SQL plan aliases `url → page`.
|
|
616
|
+
*/
|
|
617
|
+
interface StrikingDistanceInputRow {
|
|
618
|
+
query: string;
|
|
619
|
+
page: string | null;
|
|
620
|
+
clicks: number;
|
|
621
|
+
impressions: number;
|
|
622
|
+
ctr: number;
|
|
623
|
+
position: number;
|
|
624
|
+
}
|
|
625
|
+
interface StrikingDistanceResult {
|
|
626
|
+
keyword: string;
|
|
627
|
+
page: string | null;
|
|
628
|
+
clicks: number;
|
|
629
|
+
impressions: number;
|
|
630
|
+
ctr: number;
|
|
631
|
+
position: number;
|
|
632
|
+
/** Estimated clicks at ~15% CTR (the average for positions 1–3). */
|
|
633
|
+
potentialClicks: number;
|
|
634
|
+
}
|
|
635
|
+
declare const strikingDistanceAnalyzer: DefinedAnalyzer;
|
|
636
|
+
/**
|
|
637
|
+
* survival (Kaplan-Meier)
|
|
638
|
+
*
|
|
639
|
+
* Models "keyword survival in top-10". An episode starts the first day a
|
|
640
|
+
* (query, url) daily avg position drops to ≤10; it ends the first day
|
|
641
|
+
* position rises above 10 (simpler than the 3-day grace rule — a single bad
|
|
642
|
+
* day is rare enough at the daily-aggregate level to still give a clean
|
|
643
|
+
* tenure signal, and it avoids a windowed state machine in SQL). If the run
|
|
644
|
+
* reaches window_end - 2, the episode is censored (still alive).
|
|
645
|
+
*
|
|
646
|
+
* KM cumulative product is computed via EXP(SUM(LN(...)) OVER ...) — DuckDB
|
|
647
|
+
* has no CUMPROD, but running sums of logs with GREATEST(.., 1e-9) give the
|
|
648
|
+
* same result without any host-side math.
|
|
649
|
+
*/
|
|
650
|
+
interface SurvivalCurvePoint {
|
|
651
|
+
tenure: number;
|
|
652
|
+
survival: number;
|
|
653
|
+
atRisk: number;
|
|
654
|
+
events: number;
|
|
655
|
+
}
|
|
656
|
+
interface SurvivalResult {
|
|
657
|
+
cohort: string;
|
|
658
|
+
episodeCount: number;
|
|
659
|
+
censoringRate: number;
|
|
660
|
+
medianTenure: number;
|
|
661
|
+
curve: SurvivalCurvePoint[];
|
|
662
|
+
}
|
|
663
|
+
declare const survivalAnalyzer: DefinedAnalyzer;
|
|
664
|
+
/**
|
|
665
|
+
* trends — weekly trajectory over a rolling window (default 28 weeks)
|
|
666
|
+
*/
|
|
667
|
+
type TrendCategory = 'accelerating' | 'growing' | 'steady' | 'declining' | 'cratering';
|
|
668
|
+
interface TrendSeriesPoint {
|
|
669
|
+
week: string;
|
|
670
|
+
clicks: number;
|
|
671
|
+
impressions: number;
|
|
672
|
+
}
|
|
673
|
+
interface TrendsResult {
|
|
674
|
+
query?: string;
|
|
675
|
+
page?: string;
|
|
676
|
+
totalClicks: number;
|
|
677
|
+
totalImpressions: number;
|
|
678
|
+
weeksWithData: number;
|
|
679
|
+
slope: number;
|
|
680
|
+
growthRatio: number;
|
|
681
|
+
avgPosition: number;
|
|
682
|
+
trend: TrendCategory;
|
|
683
|
+
series: TrendSeriesPoint[];
|
|
684
|
+
}
|
|
685
|
+
declare const trendsAnalyzer: DefinedAnalyzer;
|
|
686
|
+
/**
|
|
687
|
+
* `zero-click` — high impressions, low CTR, good ranking. SQL groups by
|
|
688
|
+
* (query, url) and applies HAVING/WHERE pushdown; row reducer dedupes to the
|
|
689
|
+
* best-positioned page per query (different semantics, kept for parity with
|
|
690
|
+
* the existing live-GSC behavior).
|
|
691
|
+
*/
|
|
692
|
+
interface ZeroClickResult {
|
|
693
|
+
query: string;
|
|
694
|
+
page: string;
|
|
695
|
+
clicks: number;
|
|
696
|
+
impressions: number;
|
|
697
|
+
ctr: number;
|
|
698
|
+
position: number;
|
|
699
|
+
}
|
|
700
|
+
declare const zeroClickAnalyzer: DefinedAnalyzer;
|
|
701
|
+
interface AnalysisPeriod {
|
|
702
|
+
startDate: string;
|
|
703
|
+
endDate: string;
|
|
704
|
+
}
|
|
705
|
+
declare function keywordsQueryState(period: AnalysisPeriod, limit?: number): BuilderState;
|
|
706
|
+
declare function pagesQueryState(period: AnalysisPeriod, limit?: number): BuilderState;
|
|
707
|
+
declare function datesQueryState(period: AnalysisPeriod, limit?: number): BuilderState;
|
|
708
|
+
/**
|
|
709
|
+
* Capabilities a Plan may require of its host. A dispatcher matches these
|
|
710
|
+
* against a source's declared capabilities and rejects mismatches.
|
|
711
|
+
*/
|
|
712
|
+
type Capability = 'executeSql' | 'partitionedParquet' | 'attachedTables' | 'regex' | 'windowTotals' | 'comparisonJoin';
|
|
713
|
+
interface SqlExtraQuery {
|
|
714
|
+
name: string;
|
|
715
|
+
sql: string;
|
|
716
|
+
params: unknown[];
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* SQL-native plan: SQL string + placeholders, with optional extra file sets
|
|
720
|
+
* and follow-up queries. Mirrors the existing `AnalyzerSpec` shape but
|
|
721
|
+
* renamed for clarity under the unified contract.
|
|
722
|
+
*/
|
|
723
|
+
interface SqlPlan {
|
|
724
|
+
kind: 'sql';
|
|
725
|
+
sql: string;
|
|
726
|
+
params: unknown[];
|
|
727
|
+
current: FileSet$1;
|
|
728
|
+
previous?: FileSet$1;
|
|
729
|
+
extraFiles?: Record<string, FileSet$1>;
|
|
730
|
+
extraQueries?: SqlExtraQuery[];
|
|
731
|
+
/** Emits direct table refs (browser-only). Dispatcher rejects for manifest path. */
|
|
732
|
+
requiresAttachedTables?: boolean;
|
|
733
|
+
}
|
|
734
|
+
interface TypedRowQuery<T extends Row = Row> {
|
|
735
|
+
state: BuilderState;
|
|
736
|
+
/** Optional type tag for downstream narrowing. */
|
|
737
|
+
rowType?: (row: Row) => T;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Row-queries plan: a named set of typed `BuilderState` queries. A portable
|
|
741
|
+
* dispatcher runs each against a source's `queryRows` and hands the row
|
|
742
|
+
* collection to `reduce`.
|
|
743
|
+
*/
|
|
744
|
+
interface RowQueriesPlan {
|
|
745
|
+
kind: 'rows';
|
|
746
|
+
queries: Record<string, TypedRowQuery>;
|
|
747
|
+
}
|
|
748
|
+
type Plan = SqlPlan | RowQueriesPlan;
|
|
749
|
+
interface ReduceContext<TRow extends Row = Row> {
|
|
750
|
+
params: AnalysisParams;
|
|
751
|
+
/** Extra SQL-query results keyed by `SqlExtraQuery.name`. */
|
|
752
|
+
extras?: Record<string, TRow[]>;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Unified analyzer contract. `TRow` lets authors narrow from the default
|
|
756
|
+
* `Row = Record<string, unknown>` to a typed row shape (e.g. `KeywordRow`)
|
|
757
|
+
* when their reducer assumes specific columns exist — catches drift between
|
|
758
|
+
* `build` (SELECT list) and `reduce` (column access) at compile time.
|
|
759
|
+
*/
|
|
760
|
+
interface Analyzer<P extends AnalysisParams = AnalysisParams, R = unknown, TRow extends Row = Row> {
|
|
761
|
+
/** Stable tool id (e.g. `striking-distance`, `opportunity`). */
|
|
762
|
+
id: string;
|
|
763
|
+
/** Capabilities a host source must provide. */
|
|
764
|
+
requires: readonly Capability[];
|
|
765
|
+
/** Pure: params → plan. Snapshot-testable. */
|
|
766
|
+
build: (params: P) => Plan;
|
|
767
|
+
/** Pure: rows + context → typed result + meta. */
|
|
768
|
+
reduce: (rows: TRow[] | Record<string, TRow[]>, ctx: ReduceContext<TRow>) => {
|
|
769
|
+
results: R;
|
|
770
|
+
meta?: Record<string, unknown>;
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
interface SqlPlanSpec {
|
|
774
|
+
sql: string;
|
|
775
|
+
params: unknown[];
|
|
776
|
+
current: FileSet$1;
|
|
777
|
+
previous?: FileSet$1;
|
|
778
|
+
extraFiles?: Record<string, FileSet$1>;
|
|
779
|
+
extraQueries?: SqlExtraQuery[];
|
|
780
|
+
requiresAttachedTables?: boolean;
|
|
781
|
+
}
|
|
782
|
+
interface ReduceCtx<InputRow> {
|
|
783
|
+
/** Extra SQL-query results keyed by `SqlExtraQuery.name` (SQL path only). */
|
|
784
|
+
extras?: Record<string, InputRow[]>;
|
|
785
|
+
}
|
|
786
|
+
type Reducer<Params, InputRow, Result> = (rows: InputRow[] | Record<string, InputRow[]>, params: Params, ctx: ReduceCtx<InputRow>) => {
|
|
787
|
+
results: Result;
|
|
788
|
+
meta?: Record<string, unknown>;
|
|
789
|
+
};
|
|
790
|
+
interface DefineAnalyzerOptions<Params extends AnalysisParams, InputRow, Result> {
|
|
791
|
+
id: string;
|
|
792
|
+
/**
|
|
793
|
+
* Shared reducer used by both SQL and row paths. Use this when the
|
|
794
|
+
* post-aggregation row count is small and filter/sort/derive can live in
|
|
795
|
+
* one place. Mutually exclusive with `reduceSql` / `reduceRows`.
|
|
796
|
+
*/
|
|
797
|
+
reduce?: Reducer<Params, InputRow, Result>;
|
|
798
|
+
/** SQL-only reducer. Required when `buildSql` is set without `reduce`. */
|
|
799
|
+
reduceSql?: Reducer<Params, InputRow, Result>;
|
|
800
|
+
/** Row-only reducer. Required when `buildRows` is set without `reduce`. */
|
|
801
|
+
reduceRows?: Reducer<Params, InputRow, Result>;
|
|
802
|
+
/** SQL plan builder. Omit if the analyzer has no SQL path. */
|
|
803
|
+
buildSql?: (params: Params) => SqlPlanSpec;
|
|
804
|
+
/** Row plan builder. Omit if the analyzer has no row path. */
|
|
805
|
+
buildRows?: (params: Params) => Record<string, BuilderState>;
|
|
806
|
+
/** Capabilities required by the SQL plan. Defaults to `['executeSql', 'partitionedParquet']`. */
|
|
807
|
+
sqlRequires?: readonly Capability[];
|
|
808
|
+
/** Capabilities required by the row plan. Defaults to `[]`. */
|
|
809
|
+
rowsRequires?: readonly Capability[];
|
|
810
|
+
}
|
|
811
|
+
interface DefinedAnalyzer {
|
|
812
|
+
id: string;
|
|
813
|
+
sql?: Analyzer;
|
|
814
|
+
rows?: Analyzer;
|
|
815
|
+
}
|
|
816
|
+
declare function defineAnalyzer<Params extends AnalysisParams, InputRow, Result>(opts: DefineAnalyzerOptions<Params, InputRow, Result>): DefinedAnalyzer;
|
|
817
|
+
interface AnalyzerVariants {
|
|
818
|
+
sql?: Analyzer;
|
|
819
|
+
rows?: Analyzer;
|
|
820
|
+
}
|
|
821
|
+
interface AnalyzerRegistryInit {
|
|
822
|
+
rows?: readonly Analyzer[];
|
|
823
|
+
sql?: readonly Analyzer[];
|
|
824
|
+
}
|
|
825
|
+
interface AnalyzerRegistry {
|
|
826
|
+
listAnalyzerIds: () => readonly string[];
|
|
827
|
+
getAnalyzerVariants: (id: string) => AnalyzerVariants | undefined;
|
|
828
|
+
resolveAnalyzer: (id: string, sourceSupportsSql: boolean) => Analyzer | undefined;
|
|
829
|
+
listAnalyzersFor: (sourceSupportsSql: boolean) => readonly Analyzer[];
|
|
830
|
+
listAnalyzerIdsFor: (source: {
|
|
831
|
+
executeSql?: unknown;
|
|
832
|
+
}) => readonly string[];
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Build an immutable registry from collections of row / SQL analyzers.
|
|
836
|
+
* No global state; call this once per logical use (typically at startup
|
|
837
|
+
* or per-request in a worker).
|
|
838
|
+
*/
|
|
839
|
+
declare function createAnalyzerRegistry(init?: AnalyzerRegistryInit): AnalyzerRegistry;
|
|
840
|
+
declare class AnalyzerCapabilityError extends Error {
|
|
841
|
+
readonly tool: string;
|
|
842
|
+
readonly missing: readonly Capability[];
|
|
843
|
+
constructor(tool: string, missing: readonly Capability[]);
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Run an analyzer against a generic `AnalysisQuerySource`. The registry is
|
|
847
|
+
* an explicit parameter — callers build one via `createAnalyzerRegistry` (or
|
|
848
|
+
* reuse `defaultAnalyzerRegistry` from the main barrel).
|
|
849
|
+
*/
|
|
850
|
+
declare function runAnalyzerFromSource(source: AnalysisQuerySource, params: AnalysisParams, registry: AnalyzerRegistry): Promise<AnalysisResult>;
|
|
851
|
+
/**
|
|
852
|
+
* Pagination helpers shared across analyzers. SQL builders compose
|
|
853
|
+
* `paginateClause` into their tail; row reducers use `paginateInMemory`
|
|
854
|
+
* to slice already-materialized results.
|
|
855
|
+
*
|
|
856
|
+
* Sort whitelisting stays per-analyzer because each result shape has its
|
|
857
|
+
* own valid columns. `resolveSort` provides a tiny safe-cast.
|
|
858
|
+
*/
|
|
859
|
+
interface PaginateInput {
|
|
860
|
+
limit?: number;
|
|
861
|
+
offset?: number;
|
|
862
|
+
}
|
|
863
|
+
declare function clampLimit(limit: number | undefined, fallback?: number): number;
|
|
864
|
+
declare function clampOffset(offset: number | undefined): number;
|
|
865
|
+
/**
|
|
866
|
+
* Emits `LIMIT n` or `LIMIT n OFFSET m` literally (no placeholders) so it
|
|
867
|
+
* can be safely concatenated into the analyzer's SQL tail. Inputs are
|
|
868
|
+
* coerced and clamped — never user-controllable.
|
|
869
|
+
*/
|
|
870
|
+
declare function paginateClause(input: PaginateInput): string;
|
|
871
|
+
/**
|
|
872
|
+
* Slice a materialized result array. Use in row reducers and in SQL
|
|
873
|
+
* reducers when SQL didn't push pagination down.
|
|
874
|
+
*/
|
|
875
|
+
declare function paginateInMemory<T>(rows: readonly T[], input: PaginateInput): T[];
|
|
876
|
+
/**
|
|
877
|
+
* Resolve `sortBy` against an allow-list. Returns a typed key + direction
|
|
878
|
+
* suitable for ORDER BY interpolation or for indexing into a comparator
|
|
879
|
+
* map. Falls back to the analyzer's default when input is missing or
|
|
880
|
+
* unrecognized.
|
|
881
|
+
*/
|
|
882
|
+
declare function resolveSort<K extends string>(input: {
|
|
883
|
+
sortBy?: string;
|
|
884
|
+
sortDir?: 'asc' | 'desc';
|
|
885
|
+
}, allowed: readonly K[], defaults: {
|
|
886
|
+
sortBy: K;
|
|
887
|
+
sortDir: 'asc' | 'desc';
|
|
888
|
+
}): {
|
|
889
|
+
sortBy: K;
|
|
890
|
+
sortDir: 'asc' | 'desc';
|
|
891
|
+
};
|
|
892
|
+
declare const ROW_ANALYZERS: readonly Analyzer[];
|
|
893
|
+
export { type Analyzer, AnalyzerCapabilityError, type AnalyzerRegistry, type AnalyzerRegistryInit, type AnalyzerVariants, type BayesianCtrResult, type BipartitePagerankNode, type BipartitePagerankResult, type BrandResultRow, type BrandSegmentationOptions, type BrandSegmentationResult, type BrandSummary, type CannibalizationCompetitor, type CannibalizationEvent, type CannibalizationOptions, type CannibalizationPage, type CannibalizationResult, type CannibalizationSortMetric, type Capability, type ChangePointResult, type ChangePointSeriesPoint, type ClusterType, type ClusteringOptions, type ClusteringResult, type ConcentrationInput, type ConcentrationItem, type ConcentrationOptions, type ConcentrationResult, type ConcentrationRiskLevel, type ContentVelocityWeek, type CtrAnomalyResult, type CtrAnomalySeriesPoint, type CtrCurveBucket, type CtrCurveOutlier, type DarkTrafficResult, type DataDetailResult, type DataQueryResult, type DecayInput, type DecayOptions, type DecayResult, type DecaySeriesPoint, type DecaySortMetric, type DefineAnalyzerOptions, type DefinedAnalyzer, type DeviceGapResult, type FileSet, type IntentAtlasResult, type KeywordBreadthResult, type KeywordCluster, type LongTailResult, type MonthlyData, type MoverData, type MoversInput, type MoversOptions, type MoversResult, type MoversResultRow, type MoversSeriesPoint, type MoversSortMetric, type OpportunityResult, type PaginateInput, type Plan, type PositionDistributionResult, type PositionVolatilityDay, type PositionVolatilityResult, type QueryMigrationExample, type QueryMigrationResult, ROW_ANALYZERS, type ReduceContext, type RowQueriesPlan, type SeasonalityMetric, type SeasonalityOptions, type SeasonalityResult, type SqlExtraQuery, type SqlPlan, type SqlPlanSpec, type StlDecomposeResult, type StlDecomposeSeriesPoint, type StrikingDistanceInputRow, type StrikingDistanceResult, type SurvivalCurvePoint, type SurvivalResult, type TrendCategory, type TrendSeriesPoint, type TrendsResult, type TypedRowQuery, type ZeroClickResult, bayesianCtrAnalyzer, bipartitePagerankAnalyzer, brandAnalyzer, cannibalizationAnalyzer, changePointAnalyzer, clampLimit, clampOffset, clusteringAnalyzer, concentrationAnalyzer, contentVelocityAnalyzer, createAnalyzerRegistry, ctrAnomalyAnalyzer, ctrCurveAnalyzer, darkTrafficAnalyzer, dataDetailAnalyzer, dataQueryAnalyzer, datesQueryState, decayAnalyzer, defineAnalyzer, deviceGapAnalyzer, intentAtlasAnalyzer, keywordBreadthAnalyzer, keywordsQueryState, longTailAnalyzer, moversAnalyzer, opportunityAnalyzer, pagesQueryState, paginateClause, paginateInMemory, positionDistributionAnalyzer, positionVolatilityAnalyzer, queryMigrationAnalyzer, resolveSort, runAnalyzerFromSource, seasonalityAnalyzer, stlDecomposeAnalyzer, strikingDistanceAnalyzer, survivalAnalyzer, trendsAnalyzer, zeroClickAnalyzer };
|