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