@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.
@@ -0,0 +1,620 @@
1
+ import { BuilderState } from "gscdump/query";
2
+ import { GoogleSearchConsoleClient } from "gscdump";
3
+ import { createEngine as createBrowserQuerySource } from "@gscdump/engine-wasm";
4
+ import { AnalysisQuerySource, AnalysisQuerySource as AnalysisQuerySource$1, FileSet, QueryRow, QueryRow as QueryRow$1, RowQuerySource, SourceCapabilities, SqlQuerySource, isSqlQuerySource } from "@gscdump/engine/resolver";
5
+ import { PlannerCapabilities } from "gscdump/query/plan";
6
+ import { createEngine as createSqliteQuerySource } from "@gscdump/engine-sqlite";
7
+ import { Row, StorageEngine, TenantCtx } from "@gscdump/engine/contracts";
8
+ import { AnalysisParams, AnalysisResult, AnalysisTool } from "gscdump/contracts";
9
+ type SortOrder = 'asc' | 'desc';
10
+ /** Base search metrics */
11
+ interface BaseMetrics {
12
+ clicks: number;
13
+ impressions: number;
14
+ ctr: number;
15
+ position: number;
16
+ }
17
+ /** Keyword row from query */
18
+ interface KeywordRow extends BaseMetrics {
19
+ query: string;
20
+ page?: string;
21
+ }
22
+ /** Page row from query */
23
+ interface PageRow extends BaseMetrics {
24
+ page: string;
25
+ }
26
+ /** Row with both query and page dimensions, both required */
27
+ interface QueryPageRow extends BaseMetrics {
28
+ query: string;
29
+ page: string;
30
+ }
31
+ /** Date row from query */
32
+ interface DateRow extends BaseMetrics {
33
+ date: string;
34
+ }
35
+ /** Coerce arbitrary value (number, bigint, string, null) to number, defaulting to 0. */
36
+ declare function num(v: unknown): number;
37
+ /** Create a generic sorter for any metric type */
38
+ declare function createSorter<T, M extends string>(getValue: (item: T, metric: M) => number, defaultMetric: M, defaultOrder?: SortOrder): (items: T[], sortBy?: M, sortOrder?: SortOrder) => T[];
39
+ /**
40
+ * Capabilities a Plan may require of its host. A dispatcher matches these
41
+ * against a source's declared capabilities and rejects mismatches.
42
+ */
43
+ type Capability = 'executeSql' | 'partitionedParquet' | 'attachedTables' | 'regex' | 'windowTotals' | 'comparisonJoin';
44
+ interface SqlExtraQuery {
45
+ name: string;
46
+ sql: string;
47
+ params: unknown[];
48
+ }
49
+ /**
50
+ * SQL-native plan: SQL string + placeholders, with optional extra file sets
51
+ * and follow-up queries. Mirrors the existing `AnalyzerSpec` shape but
52
+ * renamed for clarity under the unified contract.
53
+ */
54
+ interface SqlPlan {
55
+ kind: 'sql';
56
+ sql: string;
57
+ params: unknown[];
58
+ current: FileSet;
59
+ previous?: FileSet;
60
+ extraFiles?: Record<string, FileSet>;
61
+ extraQueries?: SqlExtraQuery[];
62
+ /** Emits direct table refs (browser-only). Dispatcher rejects for manifest path. */
63
+ requiresAttachedTables?: boolean;
64
+ }
65
+ interface TypedRowQuery<T extends Row = Row> {
66
+ state: BuilderState;
67
+ /** Optional type tag for downstream narrowing. */
68
+ rowType?: (row: Row) => T;
69
+ }
70
+ /**
71
+ * Row-queries plan: a named set of typed `BuilderState` queries. A portable
72
+ * dispatcher runs each against a source's `queryRows` and hands the row
73
+ * collection to `reduce`.
74
+ */
75
+ interface RowQueriesPlan {
76
+ kind: 'rows';
77
+ queries: Record<string, TypedRowQuery>;
78
+ }
79
+ type Plan = SqlPlan | RowQueriesPlan;
80
+ interface ReduceContext<TRow extends Row = Row> {
81
+ params: AnalysisParams;
82
+ /** Extra SQL-query results keyed by `SqlExtraQuery.name`. */
83
+ extras?: Record<string, TRow[]>;
84
+ }
85
+ /**
86
+ * Unified analyzer contract. `TRow` lets authors narrow from the default
87
+ * `Row = Record<string, unknown>` to a typed row shape (e.g. `KeywordRow`)
88
+ * when their reducer assumes specific columns exist — catches drift between
89
+ * `build` (SELECT list) and `reduce` (column access) at compile time.
90
+ */
91
+ interface Analyzer<P extends AnalysisParams = AnalysisParams, R = unknown, TRow extends Row = Row> {
92
+ /** Stable tool id (e.g. `striking-distance`, `opportunity`). */
93
+ id: string;
94
+ /** Capabilities a host source must provide. */
95
+ requires: readonly Capability[];
96
+ /** Pure: params → plan. Snapshot-testable. */
97
+ build: (params: P) => Plan;
98
+ /** Pure: rows + context → typed result + meta. */
99
+ reduce: (rows: TRow[] | Record<string, TRow[]>, ctx: ReduceContext<TRow>) => {
100
+ results: R;
101
+ meta?: Record<string, unknown>;
102
+ };
103
+ }
104
+ interface AnalyzerVariants {
105
+ sql?: Analyzer;
106
+ rows?: Analyzer;
107
+ }
108
+ interface AnalyzerRegistryInit {
109
+ rows?: readonly Analyzer[];
110
+ sql?: readonly Analyzer[];
111
+ }
112
+ interface AnalyzerRegistry {
113
+ listAnalyzerIds: () => readonly string[];
114
+ getAnalyzerVariants: (id: string) => AnalyzerVariants | undefined;
115
+ resolveAnalyzer: (id: string, sourceSupportsSql: boolean) => Analyzer | undefined;
116
+ listAnalyzersFor: (sourceSupportsSql: boolean) => readonly Analyzer[];
117
+ listAnalyzerIdsFor: (source: {
118
+ executeSql?: unknown;
119
+ }) => readonly string[];
120
+ }
121
+ /**
122
+ * Build an immutable registry from collections of row / SQL analyzers.
123
+ * No global state; call this once per logical use (typically at startup
124
+ * or per-request in a worker).
125
+ */
126
+ declare function createAnalyzerRegistry(init?: AnalyzerRegistryInit): AnalyzerRegistry;
127
+ interface TypedQuery<TRow> {
128
+ state: BuilderState;
129
+ readonly __row?: TRow;
130
+ }
131
+ declare function queryRows<TRow = QueryRow$1>(source: AnalysisQuerySource$1, query: BuilderState | TypedQuery<TRow>): Promise<TRow[]>;
132
+ declare function queryComparisonRows<TRow = QueryRow$1>(source: AnalysisQuerySource$1, current: BuilderState | TypedQuery<TRow>, previous: BuilderState | TypedQuery<TRow>): Promise<{
133
+ current: TRow[];
134
+ previous: TRow[];
135
+ }>;
136
+ type ActionSource = 'cannibalization' | 'striking-distance' | 'ctr-anomaly' | 'change-point' | 'opportunity';
137
+ type Effort = 'low' | 'medium' | 'high';
138
+ interface PriorityAction {
139
+ id: string;
140
+ title: string;
141
+ keyword: string;
142
+ page: string;
143
+ sources: ActionSource[];
144
+ severity: number;
145
+ impressions: number;
146
+ impact: number;
147
+ why: string;
148
+ effort: Effort;
149
+ priorityScore: number;
150
+ data: Partial<Record<ActionSource, Record<string, unknown>>>;
151
+ }
152
+ type ActionPrioritySourceStatus = 'pending' | 'running' | 'done' | 'skipped' | 'error';
153
+ interface ActionPrioritySourceState {
154
+ source: ActionSource;
155
+ status: ActionPrioritySourceStatus;
156
+ count: number;
157
+ error?: string;
158
+ }
159
+ interface ActionPriorityResult {
160
+ actions: PriorityAction[];
161
+ totalSignals: number;
162
+ sources: ActionPrioritySourceState[];
163
+ }
164
+ interface ActionPriorityRunOptions {
165
+ sources?: ActionSource[];
166
+ limit?: number;
167
+ continueOnError?: boolean;
168
+ paramsBySource?: Partial<Record<ActionSource, Omit<AnalysisParams, 'type'>>>;
169
+ onSourceStatus?: (state: ActionPrioritySourceState) => void;
170
+ }
171
+ interface ActionPriorityAnalyzer {
172
+ analyze: (params: AnalysisParams) => Promise<AnalysisResult>;
173
+ }
174
+ declare function normalizePriorityActions(source: ActionSource, result: AnalysisResult): PriorityAction[];
175
+ declare function mergePriorityActions(all: PriorityAction[]): PriorityAction[];
176
+ declare function scorePriorityActions(actions: PriorityAction[]): PriorityAction[];
177
+ declare function analyzeActionPriority(analyzer: ActionPriorityAnalyzer, options?: ActionPriorityRunOptions): Promise<ActionPriorityResult>;
178
+ /**
179
+ * Convenience wrapper: build the analyzer callback from an
180
+ * {@link AnalysisQuerySource} so consumers don't hand-roll the adapter.
181
+ * SQL-only tools (e.g. `cannibalization`, `ctr-anomaly`, `change-point`) will
182
+ * surface as `status: 'error'` on their source state when the source lacks a
183
+ * row-based implementation — `continueOnError` defaults to true, so the
184
+ * overall run still produces whatever the other analyzers found.
185
+ */
186
+ declare function analyzeActionPriorityFromSource(source: AnalysisQuerySource, registry: AnalyzerRegistry, options?: ActionPriorityRunOptions): Promise<ActionPriorityResult>;
187
+ declare class AnalyzerCapabilityError extends Error {
188
+ readonly tool: string;
189
+ readonly missing: readonly Capability[];
190
+ constructor(tool: string, missing: readonly Capability[]);
191
+ }
192
+ /**
193
+ * Run an analyzer against a generic `AnalysisQuerySource`. The registry is
194
+ * an explicit parameter — callers build one via `createAnalyzerRegistry` (or
195
+ * reuse `defaultAnalyzerRegistry` from the main barrel).
196
+ */
197
+ declare function runAnalyzerFromSource(source: AnalysisQuerySource, params: AnalysisParams, registry: AnalyzerRegistry): Promise<AnalysisResult>;
198
+ declare const ROW_ANALYZERS: readonly Analyzer[];
199
+ interface BrandSegmentationOptions {
200
+ /** Brand terms to match against keywords (case-insensitive) */
201
+ brandTerms: string[];
202
+ /** Minimum impressions for a keyword to be included. Default: 10 */
203
+ minImpressions?: number;
204
+ }
205
+ interface BrandSummary {
206
+ brandClicks: number;
207
+ nonBrandClicks: number;
208
+ brandShare: number;
209
+ brandImpressions: number;
210
+ nonBrandImpressions: number;
211
+ }
212
+ interface BrandSegmentationResult {
213
+ brand: KeywordRow[];
214
+ nonBrand: KeywordRow[];
215
+ summary: BrandSummary;
216
+ }
217
+ /**
218
+ * Pure helper: segment keywords into brand and non-brand based on provided
219
+ * brand terms. Re-exported from `@gscdump/analysis` for portable callers.
220
+ */
221
+ declare function analyzeBrandSegmentation(keywords: KeywordRow[], options: BrandSegmentationOptions): BrandSegmentationResult;
222
+ type CannibalizationSortMetric = 'clicks' | 'impressions' | 'positionSpread' | 'pageCount';
223
+ interface CannibalizationOptions {
224
+ /** Minimum impressions for a query to be considered. Default: 10 */
225
+ minImpressions?: number;
226
+ /** Maximum position spread to flag as cannibalization. Default: 10 */
227
+ maxPositionSpread?: number;
228
+ /** Minimum number of pages ranking for same query. Default: 2 */
229
+ minPages?: number;
230
+ /** Sort metric. Default: clicks */
231
+ sortBy?: CannibalizationSortMetric;
232
+ /** Sort order. Default: desc */
233
+ sortOrder?: SortOrder;
234
+ }
235
+ interface CannibalizationPage {
236
+ page: string;
237
+ clicks: number;
238
+ impressions: number;
239
+ ctr: number;
240
+ position: number;
241
+ }
242
+ interface CannibalizationResult {
243
+ query: string;
244
+ pages: CannibalizationPage[];
245
+ totalClicks: number;
246
+ totalImpressions: number;
247
+ positionSpread: number;
248
+ }
249
+ interface CannibalizationCompetitor {
250
+ url: string;
251
+ clicks: number;
252
+ impressions: number;
253
+ ctr: number;
254
+ position: number;
255
+ share: number;
256
+ rank: number;
257
+ }
258
+ interface CannibalizationEvent {
259
+ keyword: string;
260
+ totalImpressions: number;
261
+ totalClicks: number;
262
+ competitorCount: number;
263
+ leaderUrl: string;
264
+ leaderCtr: number;
265
+ leaderPosition: number;
266
+ hhi: number;
267
+ fragmentation: number;
268
+ stolenClicks: number;
269
+ severity: number;
270
+ competitors: CannibalizationCompetitor[];
271
+ }
272
+ /**
273
+ * Pure helper: detects keyword cannibalization from row data — queries
274
+ * ranking for multiple pages within a small position spread. Re-exported
275
+ * from `@gscdump/analysis` for portable callers.
276
+ */
277
+ declare function analyzeCannibalization(rows: QueryPageRow[], options?: CannibalizationOptions): CannibalizationResult[];
278
+ type ClusterType = 'prefix' | 'intent' | 'both';
279
+ interface ClusteringOptions {
280
+ /** Minimum keywords for a cluster to be reported. Default: 2 */
281
+ minClusterSize?: number;
282
+ /** Minimum impressions for a keyword to be included. Default: 10 */
283
+ minImpressions?: number;
284
+ /** Clustering method. Default: 'both' */
285
+ clusterBy?: ClusterType;
286
+ }
287
+ interface KeywordCluster {
288
+ clusterName: string;
289
+ clusterType: 'prefix' | 'intent';
290
+ keywords: KeywordRow[];
291
+ totalClicks: number;
292
+ totalImpressions: number;
293
+ avgPosition: number;
294
+ keywordCount: number;
295
+ }
296
+ interface ClusteringResult {
297
+ clusters: KeywordCluster[];
298
+ unclustered: KeywordRow[];
299
+ }
300
+ /**
301
+ * Pure helper: clusters keywords by intent prefix or common word prefix.
302
+ * Re-exported from `@gscdump/analysis` for portable callers.
303
+ */
304
+ declare function analyzeClustering(keywords: KeywordRow[], options?: ClusteringOptions): ClusteringResult;
305
+ type ConcentrationRiskLevel = 'low' | 'medium' | 'high';
306
+ interface ConcentrationOptions {
307
+ /** Number of top items to report. Default: 10 */
308
+ topN?: number;
309
+ }
310
+ interface ConcentrationItem {
311
+ key: string;
312
+ clicks: number;
313
+ share: number;
314
+ }
315
+ interface ConcentrationInput {
316
+ key: string;
317
+ clicks: number;
318
+ }
319
+ interface ConcentrationResult {
320
+ /** Gini coefficient: 0 = equal distribution, 1 = fully concentrated */
321
+ giniCoefficient: number;
322
+ /** Herfindahl-Hirschman Index: 0-10000, >2500 = highly concentrated */
323
+ hhi: number;
324
+ /** Percentage of total clicks from top N items */
325
+ topNConcentration: number;
326
+ topNItems: ConcentrationItem[];
327
+ totalItems: number;
328
+ totalClicks: number;
329
+ /** Risk level derived from HHI: <1500 low, 1500-2500 medium, >2500 high */
330
+ riskLevel: ConcentrationRiskLevel;
331
+ }
332
+ /**
333
+ * Pure helper: analyze traffic concentration across items (pages or keywords).
334
+ * Re-exported from `@gscdump/analysis` for portable callers.
335
+ */
336
+ declare function analyzeConcentration(items: ConcentrationInput[], options?: ConcentrationOptions): ConcentrationResult;
337
+ /**
338
+ * Page concentration analysis.
339
+ */
340
+ declare function analyzePageConcentration(pages: PageRow[], options?: ConcentrationOptions): ConcentrationResult;
341
+ /**
342
+ * Keyword concentration analysis.
343
+ */
344
+ declare function analyzeKeywordConcentration(keywords: KeywordRow[], options?: ConcentrationOptions): ConcentrationResult;
345
+ type DecaySortMetric = 'lostClicks' | 'declinePercent' | 'currentClicks';
346
+ interface DecayOptions {
347
+ /** Minimum clicks in previous period to consider. Default: 50 */
348
+ minPreviousClicks?: number;
349
+ /** Minimum decline percentage (0-1). Default: 0.2 (20%) */
350
+ threshold?: number;
351
+ /** Metric to sort results by. Default: lostClicks */
352
+ sortBy?: DecaySortMetric;
353
+ }
354
+ interface DecayInput {
355
+ current: PageRow[];
356
+ previous: PageRow[];
357
+ }
358
+ interface DecaySeriesPoint {
359
+ week: string;
360
+ clicks: number;
361
+ impressions: number;
362
+ }
363
+ interface DecayResult {
364
+ page: string;
365
+ currentClicks: number;
366
+ previousClicks: number;
367
+ lostClicks: number;
368
+ declinePercent: number;
369
+ currentPosition: number;
370
+ previousPosition: number;
371
+ positionDrop: number;
372
+ series?: DecaySeriesPoint[];
373
+ }
374
+ /**
375
+ * Pure helper: identify "decaying" content — pages that have lost
376
+ * significant traffic between two periods.
377
+ */
378
+ declare function analyzeDecay(input: DecayInput, options?: DecayOptions): DecayResult[];
379
+ type MoversSortMetric = 'clicks' | 'impressions' | 'clicksChange' | 'impressionsChange' | 'positionChange';
380
+ interface MoversOptions {
381
+ /** Minimum change threshold to flag. Default: 0.2 (20%) */
382
+ changeThreshold?: number;
383
+ /** Minimum impressions in recent period. Default: 50 */
384
+ minImpressions?: number;
385
+ /** Metric to sort results by. Default: clicksChange */
386
+ sortBy?: MoversSortMetric;
387
+ }
388
+ interface MoversInput {
389
+ current: KeywordRow[];
390
+ previous: KeywordRow[];
391
+ /** If periods have different lengths, provide normalization factor (previous/current) */
392
+ normalizationFactor?: number;
393
+ }
394
+ interface MoverData {
395
+ keyword: string;
396
+ page: string | null;
397
+ recentClicks: number;
398
+ recentImpressions: number;
399
+ recentPosition: number;
400
+ baselineClicks: number;
401
+ baselineImpressions: number;
402
+ baselinePosition: number;
403
+ clicksChange: number;
404
+ clicksChangePercent: number;
405
+ impressionsChangePercent: number;
406
+ positionChange: number;
407
+ }
408
+ interface MoversResult {
409
+ rising: MoverData[];
410
+ declining: MoverData[];
411
+ stable: MoverData[];
412
+ }
413
+ /**
414
+ * Pure helper: identify "movers and shakers" — keywords with significant
415
+ * recent changes between two periods.
416
+ */
417
+ declare function analyzeMovers(input: MoversInput, options?: MoversOptions): MoversResult;
418
+ interface OpportunityFactors {
419
+ positionScore: number;
420
+ impressionScore: number;
421
+ ctrGapScore: number;
422
+ }
423
+ interface OpportunityResult {
424
+ keyword: string;
425
+ page: string | null;
426
+ clicks: number;
427
+ impressions: number;
428
+ ctr: number;
429
+ position: number;
430
+ opportunityScore: number;
431
+ potentialClicks: number;
432
+ factors: OpportunityFactors;
433
+ }
434
+ type SeasonalityMetric = 'clicks' | 'impressions';
435
+ interface SeasonalityOptions {
436
+ /** Metric to analyze for seasonality. Default: clicks */
437
+ metric?: SeasonalityMetric;
438
+ }
439
+ interface MonthlyData {
440
+ month: string;
441
+ value: number;
442
+ vsAverage: number;
443
+ isPeak: boolean;
444
+ isTrough: boolean;
445
+ }
446
+ interface SeasonalityResult {
447
+ hasSeasonality: boolean;
448
+ /** Coefficient of variation: std dev / mean. Higher = more seasonal. */
449
+ strength: number;
450
+ peakMonths: string[];
451
+ troughMonths: string[];
452
+ monthlyBreakdown: MonthlyData[];
453
+ insufficientData: boolean;
454
+ }
455
+ /**
456
+ * Pure helper: detects seasonality patterns by analyzing monthly traffic
457
+ * variation. Re-exported from `@gscdump/analysis` for portable callers.
458
+ */
459
+ declare function analyzeSeasonality(dates: DateRow[], options?: SeasonalityOptions): SeasonalityResult;
460
+ /**
461
+ * `zero-click` — high impressions, low CTR, good ranking. SQL groups by
462
+ * (query, url) and applies HAVING/WHERE pushdown; row reducer dedupes to the
463
+ * best-positioned page per query (different semantics, kept for parity with
464
+ * the existing live-GSC behavior).
465
+ */
466
+ interface ZeroClickResult {
467
+ query: string;
468
+ page: string;
469
+ clicks: number;
470
+ impressions: number;
471
+ ctr: number;
472
+ position: number;
473
+ }
474
+ type WindowPreset = 'last-7d' | 'last-28d' | 'last-30d' | 'last-90d' | 'last-180d' | 'last-365d' | 'mtd' | 'ytd' | 'custom';
475
+ type ComparisonMode = 'none' | 'prev-period' | 'yoy';
476
+ interface ResolveWindowOptions {
477
+ preset: WindowPreset;
478
+ comparison?: ComparisonMode;
479
+ anchor?: string;
480
+ start?: string;
481
+ end?: string;
482
+ }
483
+ interface ResolvedWindow {
484
+ start: string;
485
+ end: string;
486
+ days: number;
487
+ comparison?: {
488
+ start: string;
489
+ end: string;
490
+ };
491
+ }
492
+ interface AnalysisPeriod {
493
+ startDate: string;
494
+ endDate: string;
495
+ }
496
+ interface ComparisonPeriod {
497
+ current: AnalysisPeriod;
498
+ previous: AnalysisPeriod;
499
+ }
500
+ declare function periodOf(params: AnalysisParams): AnalysisPeriod;
501
+ declare function comparisonOf(params: AnalysisParams): ComparisonPeriod;
502
+ declare function resolveWindow(opts: ResolveWindowOptions): ResolvedWindow;
503
+ /** Convert a ResolvedWindow into the AnalysisPeriod / ComparisonPeriod shape. */
504
+ declare function windowToPeriod(w: ResolvedWindow): AnalysisPeriod;
505
+ declare function windowToComparisonPeriod(w: ResolvedWindow): ComparisonPeriod | undefined;
506
+ interface PadTimeseriesOptions<T> {
507
+ /** ISO date (YYYY-MM-DD), inclusive lower bound. */
508
+ startDate: string;
509
+ /** ISO date (YYYY-MM-DD), inclusive upper bound. */
510
+ endDate: string;
511
+ /**
512
+ * Row to insert for missing dates. Defaults to `{ clicks: 0, impressions: 0, ctr: 0, position: 0 }`.
513
+ * The `date` field is set automatically.
514
+ */
515
+ fill?: Omit<T, 'date'>;
516
+ /** Row-field that carries the ISO date. Defaults to `date`. */
517
+ dateKey?: string;
518
+ }
519
+ type DateRowShape = Record<string, unknown> & {
520
+ date?: unknown;
521
+ };
522
+ /**
523
+ * Pad rows so every calendar day in `[startDate, endDate]` appears at least
524
+ * once. Existing dates keep all their rows (grouped timeseries safe).
525
+ */
526
+ declare function padTimeseries<T extends DateRowShape = DateRowShape>(rows: readonly T[], options: PadTimeseriesOptions<T>): T[];
527
+ /**
528
+ * Produce a canonical form of a search query for grouping near-duplicates.
529
+ * Idempotent: `normalizeQuery(normalizeQuery(q)) === normalizeQuery(q)`.
530
+ */
531
+ declare function normalizeQuery(query: string): string;
532
+ declare function analyzeFromSource(source: AnalysisQuerySource, params: AnalysisParams, registry: AnalyzerRegistry): Promise<AnalysisResult>;
533
+ interface EngineQuerySourceOptions {
534
+ engine: StorageEngine;
535
+ ctx: TenantCtx;
536
+ }
537
+ /**
538
+ * Wraps a storage engine as a {@link SqlQuerySource}. `queryRows` runs typed
539
+ * builder-state queries; `executeSql` delegates to `engine.runSQL` and
540
+ * requires `opts.fileSets` (with a `FILES` entry so the target table can be
541
+ * resolved for partition lookup).
542
+ */
543
+ declare function createEngineQuerySource(options: EngineQuerySourceOptions): SqlQuerySource;
544
+ /**
545
+ * Convenience: wrap a storage engine + tenant ctx in a source and dispatch.
546
+ * Equivalent to
547
+ * `runAnalyzerFromSource(createEngineQuerySource({ engine, ctx }), params, registry)`.
548
+ */
549
+ declare function runAnalyzerWithEngine(deps: {
550
+ engine: StorageEngine;
551
+ }, ctx: TenantCtx, params: AnalysisParams, registry: AnalyzerRegistry): Promise<AnalysisResult>;
552
+ interface GscApiQuerySourceOptions {
553
+ client: GoogleSearchConsoleClient;
554
+ siteUrl: string;
555
+ }
556
+ declare function createGscApiQuerySource(options: GscApiQuerySourceOptions): RowQuerySource;
557
+ interface InMemoryQuerySourceOptions {
558
+ queryRows: (state: BuilderState) => Promise<QueryRow[]> | QueryRow[];
559
+ capabilities?: PlannerCapabilities;
560
+ }
561
+ declare function createInMemoryQuerySource(options: InMemoryQuerySourceOptions): RowQuerySource;
562
+ interface StrikingDistanceResult {
563
+ keyword: string;
564
+ page: string | null;
565
+ clicks: number;
566
+ impressions: number;
567
+ ctr: number;
568
+ position: number;
569
+ /** Estimated clicks at ~15% CTR (the average for positions 1–3). */
570
+ potentialClicks: number;
571
+ }
572
+ type StrikingDistanceSortMetric = 'clicks' | 'impressions' | 'ctr' | 'position' | 'potentialClicks';
573
+ interface StrikingDistanceOptions {
574
+ /** Minimum position (inclusive). Default: 4 */
575
+ minPosition?: number;
576
+ /** Maximum position (inclusive). Default: 20 */
577
+ maxPosition?: number;
578
+ /** Minimum impressions. Default: 100 */
579
+ minImpressions?: number;
580
+ /** Maximum CTR (queries with low CTR have more potential). Default: 0.05 (5%) */
581
+ maxCtr?: number;
582
+ /** Sort metric. Default: potentialClicks */
583
+ sortBy?: StrikingDistanceSortMetric;
584
+ /** Sort order. Default: desc */
585
+ sortOrder?: SortOrder;
586
+ }
587
+ /**
588
+ * Finds striking distance keywords - high impressions, low CTR, position 4-20.
589
+ * These are "quick wins" that could gain significant traffic with small ranking improvements.
590
+ */
591
+ declare function analyzeStrikingDistance(keywords: KeywordRow[], options?: StrikingDistanceOptions): StrikingDistanceResult[];
592
+ type QueryDimension = 'keywords' | 'pages' | 'dates';
593
+ interface QueryOptions {
594
+ dimension?: QueryDimension;
595
+ limit?: number;
596
+ }
597
+ interface QueryResult {
598
+ keywords: KeywordRow[];
599
+ pages: PageRow[];
600
+ dates: DateRow[];
601
+ }
602
+ interface ComparisonQueryResult {
603
+ current: QueryResult;
604
+ previous: QueryResult;
605
+ }
606
+ interface OpportunityOptions {
607
+ minImpressions?: number;
608
+ }
609
+ declare function queryAnalyticsFromSource(source: AnalysisQuerySource, period: AnalysisPeriod, options?: QueryOptions): Promise<QueryResult>;
610
+ declare function queryComparisonFromSource(source: AnalysisQuerySource, periods: ComparisonPeriod, options?: QueryOptions): Promise<ComparisonQueryResult>;
611
+ declare function analyzeStrikingDistanceFromSource(source: AnalysisQuerySource, period: AnalysisPeriod, options?: StrikingDistanceOptions): Promise<StrikingDistanceResult[]>;
612
+ declare function analyzeOpportunityFromSource(source: AnalysisQuerySource, period: AnalysisPeriod, options?: OpportunityOptions): Promise<OpportunityResult[]>;
613
+ declare function analyzeBrandSegmentationFromSource(source: AnalysisQuerySource, period: AnalysisPeriod, options: BrandSegmentationOptions): Promise<BrandSegmentationResult>;
614
+ declare function analyzePageConcentrationFromSource(source: AnalysisQuerySource, period: AnalysisPeriod, options?: ConcentrationOptions): Promise<ConcentrationResult>;
615
+ declare function analyzeKeywordConcentrationFromSource(source: AnalysisQuerySource, period: AnalysisPeriod, options?: ConcentrationOptions): Promise<ConcentrationResult>;
616
+ declare function analyzeClusteringFromSource(source: AnalysisQuerySource, period: AnalysisPeriod, options?: ClusteringOptions): Promise<ClusteringResult>;
617
+ declare function analyzeSeasonalityFromSource(source: AnalysisQuerySource, period: AnalysisPeriod, options?: SeasonalityOptions): Promise<SeasonalityResult>;
618
+ declare function analyzeDecayFromSource(source: AnalysisQuerySource, periods: ComparisonPeriod, options?: DecayOptions): Promise<DecayResult[]>;
619
+ declare function analyzeMoversFromSource(source: AnalysisQuerySource, periods: ComparisonPeriod, options?: MoversOptions): Promise<MoversResult>;
620
+ export { type ActionPriorityAnalyzer, type ActionPriorityResult, type ActionPriorityRunOptions, type ActionPrioritySourceState, type ActionPrioritySourceStatus, type ActionSource, type AnalysisParams, type AnalysisPeriod, type AnalysisQuerySource, type AnalysisResult, type AnalysisTool, AnalyzerCapabilityError, type AnalyzerRegistry, type AnalyzerRegistryInit, type AnalyzerVariants, type BaseMetrics, type BrandSegmentationOptions, type BrandSegmentationResult, type BrandSummary, type CannibalizationCompetitor, type CannibalizationEvent, type CannibalizationOptions, type CannibalizationPage, type CannibalizationResult, type CannibalizationSortMetric, type ClusterType, type ClusteringOptions, type ClusteringResult, type ComparisonMode, type ComparisonPeriod, type ComparisonQueryResult, type ConcentrationInput, type ConcentrationItem, type ConcentrationOptions, type ConcentrationResult, type ConcentrationRiskLevel, type DateRow, type DecayInput, type DecayOptions, type DecayResult, type DecaySeriesPoint, type DecaySortMetric, type Effort, type KeywordCluster, type KeywordRow, type MonthlyData, type MoverData, type MoversInput, type MoversOptions, type MoversResult, type MoversSortMetric, type OpportunityResult, type PadTimeseriesOptions, type PageRow, type PriorityAction, type QueryDimension, type QueryOptions, type QueryPageRow, type QueryResult, type QueryRow, ROW_ANALYZERS, type ResolveWindowOptions, type ResolvedWindow, type RowQuerySource, type SeasonalityMetric, type SeasonalityOptions, type SeasonalityResult, type SortOrder, type SourceCapabilities, type SqlQuerySource, type StrikingDistanceOptions, type StrikingDistanceResult, type StrikingDistanceSortMetric, type WindowPreset, type ZeroClickResult, analyzeActionPriority, analyzeActionPriorityFromSource, analyzeBrandSegmentation, analyzeBrandSegmentationFromSource, analyzeCannibalization, analyzeClustering, analyzeClusteringFromSource, analyzeConcentration, analyzeDecay, analyzeDecayFromSource, analyzeFromSource, analyzeKeywordConcentration, analyzeKeywordConcentrationFromSource, analyzeMovers, analyzeMoversFromSource, analyzeOpportunityFromSource, analyzePageConcentration, analyzePageConcentrationFromSource, analyzeSeasonality, analyzeSeasonalityFromSource, analyzeStrikingDistance, analyzeStrikingDistanceFromSource, comparisonOf, createAnalyzerRegistry, createBrowserQuerySource, createEngineQuerySource, createGscApiQuerySource, createInMemoryQuerySource, createSorter, createSqliteQuerySource, isSqlQuerySource, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryAnalyticsFromSource, queryComparisonFromSource, queryComparisonRows, queryRows, resolveWindow, runAnalyzerFromSource, runAnalyzerWithEngine, scorePriorityActions, windowToComparisonPeriod, windowToPeriod };