@gscdump/analysis 0.7.1 → 0.7.5

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,122 +1,11 @@
1
+ import { defineAnalyzer } from "@gscdump/engine/analyzer";
2
+ import { comparisonOf, defaultEndDate, padTimeseries, periodOf } from "@gscdump/engine/period";
1
3
  import { enumeratePartitions } from "@gscdump/engine/planner";
2
4
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
3
- import { MS_PER_DAY, daysAgo, toIsoDate } from "gscdump";
5
+ import { num } from "@gscdump/engine/analysis-types";
4
6
  import { between, date, extractDateRange, gsc, page, query } from "gscdump/query";
7
+ import { MS_PER_DAY, daysAgo, toIsoDate } from "gscdump";
5
8
  import { buildExtrasQueries, buildTotalsSql, mergeExtras, pgResolverAdapter, resolveComparisonSQL, resolveToSQL, resolveToSQLOptimized } from "@gscdump/engine/resolver";
6
- const DEFAULT_SQL_REQUIRES = ["executeSql", "partitionedParquet"];
7
- function defineAnalyzer(opts) {
8
- const { id, reduce, reduceSql, reduceRows, buildSql, buildRows, sqlRequires = DEFAULT_SQL_REQUIRES, rowsRequires = [] } = opts;
9
- const sqlReducer = reduceSql ?? reduce;
10
- const rowsReducer = reduceRows ?? reduce;
11
- if (buildSql && !sqlReducer) throw new Error(`defineAnalyzer(${id}): buildSql requires reduce or reduceSql`);
12
- if (buildRows && !rowsReducer) throw new Error(`defineAnalyzer(${id}): buildRows requires reduce or reduceRows`);
13
- const wrap = (fn) => (rows, params, ctx) => {
14
- return fn(Array.isArray(rows) ? rows : pickSingle(rows) ?? rows, params, ctx);
15
- };
16
- return {
17
- id,
18
- sql: buildSql && sqlReducer ? {
19
- id,
20
- requires: sqlRequires,
21
- build(params) {
22
- const spec = buildSql(params);
23
- return {
24
- kind: "sql",
25
- sql: spec.sql,
26
- params: spec.params,
27
- current: spec.current,
28
- previous: spec.previous,
29
- extraFiles: spec.extraFiles,
30
- extraQueries: spec.extraQueries,
31
- requiresAttachedTables: spec.requiresAttachedTables
32
- };
33
- },
34
- reduce(rows, ctx) {
35
- const { results, meta } = wrap(sqlReducer)(rows, ctx.params, { extras: ctx.extras });
36
- return {
37
- results,
38
- meta
39
- };
40
- }
41
- } : void 0,
42
- rows: buildRows && rowsReducer ? {
43
- id,
44
- requires: rowsRequires,
45
- build(params) {
46
- const queries = buildRows(params);
47
- return {
48
- kind: "rows",
49
- queries: Object.fromEntries(Object.entries(queries).map(([k, state]) => [k, { state }]))
50
- };
51
- },
52
- reduce(rows, ctx) {
53
- const { results, meta } = wrap(rowsReducer)(rows, ctx.params, {});
54
- return {
55
- results,
56
- meta
57
- };
58
- }
59
- } : void 0
60
- };
61
- }
62
- function pickSingle(rows) {
63
- const keys = Object.keys(rows);
64
- return keys.length === 1 ? rows[keys[0]] : void 0;
65
- }
66
- function defaultEndDate() {
67
- return daysAgo(3);
68
- }
69
- function defaultStartDate() {
70
- return daysAgo(31);
71
- }
72
- function periodOf(params) {
73
- return {
74
- startDate: params.startDate || defaultStartDate(),
75
- endDate: params.endDate || defaultEndDate()
76
- };
77
- }
78
- function comparisonOf(params) {
79
- if (!params.prevStartDate || !params.prevEndDate) throw new Error(`${params.type} analysis requires prevStartDate and prevEndDate`);
80
- return {
81
- current: periodOf(params),
82
- previous: {
83
- startDate: params.prevStartDate,
84
- endDate: params.prevEndDate
85
- }
86
- };
87
- }
88
- const DEFAULT_FILL = {
89
- clicks: 0,
90
- impressions: 0,
91
- ctr: 0,
92
- position: 0
93
- };
94
- function padTimeseries(rows, options) {
95
- const { startDate, endDate } = options;
96
- const dateKey = options.dateKey ?? "date";
97
- const fill = options.fill ?? DEFAULT_FILL;
98
- const byDate = /* @__PURE__ */ new Map();
99
- for (const row of rows) {
100
- const d = String(row[dateKey]);
101
- const bucket = byDate.get(d);
102
- if (bucket) bucket.push(row);
103
- else byDate.set(d, [row]);
104
- }
105
- const result = [];
106
- const start = /* @__PURE__ */ new Date(`${startDate}T00:00:00Z`);
107
- const end = /* @__PURE__ */ new Date(`${endDate}T00:00:00Z`);
108
- if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) throw new Error(`padTimeseries: invalid date range ${startDate}..${endDate}`);
109
- for (let cursorMs = start.getTime(), endMs = end.getTime(); cursorMs <= endMs; cursorMs += MS_PER_DAY) {
110
- const dateStr = toIsoDate(new Date(cursorMs));
111
- const existing = byDate.get(dateStr);
112
- if (existing) result.push(...existing);
113
- else result.push({
114
- ...fill,
115
- [dateKey]: dateStr
116
- });
117
- }
118
- return result;
119
- }
120
9
  function num$5(v) {
121
10
  if (typeof v === "number") return v;
122
11
  if (typeof v === "bigint") return Number(v);
@@ -298,32 +187,6 @@ const bayesianCtrAnalyzer = defineAnalyzer({
298
187
  };
299
188
  }
300
189
  });
301
- function num$4(v) {
302
- if (typeof v === "number") return v;
303
- if (typeof v === "bigint") return Number(v);
304
- if (v == null) return 0;
305
- return Number(v);
306
- }
307
- function buildPeriodMap(rows, key, value, filter) {
308
- const out = /* @__PURE__ */ new Map();
309
- for (const row of rows) {
310
- if (filter && !filter(row)) continue;
311
- out.set(key(row), value(row));
312
- }
313
- return out;
314
- }
315
- function createSorter(getValue, defaultMetric, defaultOrder = "desc") {
316
- return (items, sortBy = defaultMetric, sortOrder = defaultOrder) => {
317
- const mult = sortOrder === "desc" ? -1 : 1;
318
- return [...items].sort((a, b) => (getValue(a, sortBy) - getValue(b, sortBy)) * mult);
319
- };
320
- }
321
- function createMetricSorter(defaultMetric, orderByMetric) {
322
- return (items, sortBy = defaultMetric) => {
323
- const mult = orderByMetric[sortBy] === "desc" ? -1 : 1;
324
- return [...items].sort((a, b) => (a[sortBy] - b[sortBy]) * mult);
325
- };
326
- }
327
190
  const BIPARTITE_PAGERANK_ITERATIONS = 25;
328
191
  const BIPARTITE_PAGERANK_DAMPING = .85;
329
192
  function str$22(v) {
@@ -544,18 +407,18 @@ const bipartitePagerankAnalyzer = defineAnalyzer({
544
407
  const results = arr.map((r) => ({
545
408
  kind: str$22(r.kind),
546
409
  id: str$22(r.id),
547
- rank: num$4(r.rank),
548
- bridging: num$4(r.bridging),
549
- anchoring: num$4(r.anchoring),
550
- degree: num$4(r.degree),
551
- impressions: num$4(r.impressions)
410
+ rank: num(r.rank),
411
+ bridging: num(r.bridging),
412
+ anchoring: num(r.anchoring),
413
+ degree: num(r.degree),
414
+ impressions: num(r.impressions)
552
415
  }));
553
416
  const first = arr[0] ?? {};
554
- const queryCount = num$4(first.queryCount);
555
- const urlCount = num$4(first.urlCount);
417
+ const queryCount = num(first.queryCount);
418
+ const urlCount = num(first.urlCount);
556
419
  const deltas = parseJsonList$16(first.deltasJson).map((e) => ({
557
- step: num$4(e.step),
558
- l1: num$4(e.l1)
420
+ step: num(e.step),
421
+ l1: num(e.l1)
559
422
  }));
560
423
  const convergenceDelta = deltas.length > 0 ? deltas[deltas.length - 1].l1 : 0;
561
424
  return {
@@ -594,12 +457,12 @@ function analyzeBrandSegmentation(keywords, options) {
594
457
  const brand = [];
595
458
  const nonBrand = [];
596
459
  for (const row of keywords) {
597
- if (num$4(row.impressions) < minImpressions) continue;
460
+ if (num(row.impressions) < minImpressions) continue;
598
461
  if (lowerBrandTerms.some((term) => row.query.toLowerCase().includes(term))) brand.push(row);
599
462
  else nonBrand.push(row);
600
463
  }
601
- const brandClicks = brand.reduce((sum, k) => sum + num$4(k.clicks), 0);
602
- const nonBrandClicks = nonBrand.reduce((sum, k) => sum + num$4(k.clicks), 0);
464
+ const brandClicks = brand.reduce((sum, k) => sum + num(k.clicks), 0);
465
+ const nonBrandClicks = nonBrand.reduce((sum, k) => sum + num(k.clicks), 0);
603
466
  const totalClicks = brandClicks + nonBrandClicks;
604
467
  return {
605
468
  brand,
@@ -608,8 +471,8 @@ function analyzeBrandSegmentation(keywords, options) {
608
471
  brandClicks,
609
472
  nonBrandClicks,
610
473
  brandShare: totalClicks > 0 ? brandClicks / totalClicks : 0,
611
- brandImpressions: brand.reduce((sum, k) => sum + num$4(k.impressions), 0),
612
- nonBrandImpressions: nonBrand.reduce((sum, k) => sum + num$4(k.impressions), 0)
474
+ brandImpressions: brand.reduce((sum, k) => sum + num(k.impressions), 0),
475
+ nonBrandImpressions: nonBrand.reduce((sum, k) => sum + num(k.impressions), 0)
613
476
  }
614
477
  };
615
478
  }
@@ -659,10 +522,10 @@ const brandAnalyzer = defineAnalyzer({
659
522
  const normalized = (Array.isArray(rows) ? rows : []).map((r) => ({
660
523
  query: str$21(r.query),
661
524
  page: r.page == null ? void 0 : str$21(r.page),
662
- clicks: num$4(r.clicks),
663
- impressions: num$4(r.impressions),
664
- ctr: num$4(r.ctr),
665
- position: num$4(r.position),
525
+ clicks: num(r.clicks),
526
+ impressions: num(r.impressions),
527
+ ctr: num(r.ctr),
528
+ position: num(r.position),
666
529
  segment: str$21(r.segment)
667
530
  }));
668
531
  let brandClicks = 0;
@@ -712,6 +575,26 @@ const brandAnalyzer = defineAnalyzer({
712
575
  };
713
576
  }
714
577
  });
578
+ function buildPeriodMap(rows, key, value, filter) {
579
+ const out = /* @__PURE__ */ new Map();
580
+ for (const row of rows) {
581
+ if (filter && !filter(row)) continue;
582
+ out.set(key(row), value(row));
583
+ }
584
+ return out;
585
+ }
586
+ function createSorter(getValue, defaultMetric, defaultOrder = "desc") {
587
+ return (items, sortBy = defaultMetric, sortOrder = defaultOrder) => {
588
+ const mult = sortOrder === "desc" ? -1 : 1;
589
+ return [...items].sort((a, b) => (getValue(a, sortBy) - getValue(b, sortBy)) * mult);
590
+ };
591
+ }
592
+ function createMetricSorter(defaultMetric, orderByMetric) {
593
+ return (items, sortBy = defaultMetric) => {
594
+ const mult = orderByMetric[sortBy] === "desc" ? -1 : 1;
595
+ return [...items].sort((a, b) => (a[sortBy] - b[sortBy]) * mult);
596
+ };
597
+ }
715
598
  const sortRowResults$1 = createSorter((item, metric) => {
716
599
  switch (metric) {
717
600
  case "clicks": return item.totalClicks;
@@ -884,24 +767,24 @@ const cannibalizationAnalyzer = defineAnalyzer({
884
767
  reduceSql(rows) {
885
768
  const events = (Array.isArray(rows) ? rows : []).map((r) => ({
886
769
  keyword: str$20(r.keyword),
887
- totalImpressions: num$4(r.totalImpressions),
888
- totalClicks: num$4(r.totalClicks),
889
- competitorCount: num$4(r.competitorCount),
770
+ totalImpressions: num(r.totalImpressions),
771
+ totalClicks: num(r.totalClicks),
772
+ competitorCount: num(r.competitorCount),
890
773
  leaderUrl: str$20(r.leaderUrl),
891
- leaderCtr: num$4(r.leaderCtr),
892
- leaderPosition: num$4(r.leaderPosition),
893
- hhi: num$4(r.hhi),
894
- fragmentation: num$4(r.fragmentation),
895
- stolenClicks: num$4(r.stolenClicks),
896
- severity: num$4(r.severity),
774
+ leaderCtr: num(r.leaderCtr),
775
+ leaderPosition: num(r.leaderPosition),
776
+ hhi: num(r.hhi),
777
+ fragmentation: num(r.fragmentation),
778
+ stolenClicks: num(r.stolenClicks),
779
+ severity: num(r.severity),
897
780
  competitors: parseJsonList$15(r.competitors).map((c) => ({
898
781
  url: str$20(c.url),
899
- clicks: num$4(c.clicks),
900
- impressions: num$4(c.impressions),
901
- ctr: num$4(c.ctr),
902
- position: num$4(c.position),
903
- share: num$4(c.share),
904
- rank: num$4(c.rank)
782
+ clicks: num(c.clicks),
783
+ impressions: num(c.impressions),
784
+ ctr: num(c.ctr),
785
+ position: num(c.position),
786
+ share: num(c.share),
787
+ rank: num(c.rank)
905
788
  }))
906
789
  }));
907
790
  const nodeAgg = /* @__PURE__ */ new Map();
@@ -972,7 +855,7 @@ const cannibalizationAnalyzer = defineAnalyzer({
972
855
  };
973
856
  }
974
857
  });
975
- function num$3(v) {
858
+ function num$4(v) {
976
859
  if (typeof v === "number") return v;
977
860
  if (typeof v === "bigint") return Number(v);
978
861
  if (v == null) return 0;
@@ -1139,24 +1022,24 @@ const changePointAnalyzer = defineAnalyzer({
1139
1022
  const metric = params.metric === "clicks" || params.metric === "impressions" ? params.metric : "position";
1140
1023
  const lowerIsBetter = metric === "position";
1141
1024
  const results = arr.map((r) => {
1142
- const delta = num$3(r.delta);
1025
+ const delta = num$4(r.delta);
1143
1026
  const improved = lowerIsBetter ? delta < 0 : delta > 0;
1144
1027
  return {
1145
1028
  keyword: str$19(r.keyword),
1146
1029
  page: str$19(r.page),
1147
- totalDays: num$3(r.totalDays),
1148
- totalImpressions: num$3(r.totalImpressions),
1030
+ totalDays: num$4(r.totalDays),
1031
+ totalImpressions: num$4(r.totalImpressions),
1149
1032
  changeDate: str$19(r.changeDate),
1150
- llr: num$3(r.llr),
1151
- leftMean: num$3(r.leftMean),
1152
- rightMean: num$3(r.rightMean),
1033
+ llr: num$4(r.llr),
1034
+ leftMean: num$4(r.leftMean),
1035
+ rightMean: num$4(r.rightMean),
1153
1036
  delta,
1154
- leftStddev: num$3(r.leftStddev),
1155
- rightStddev: num$3(r.rightStddev),
1037
+ leftStddev: num$4(r.leftStddev),
1038
+ rightStddev: num$4(r.rightStddev),
1156
1039
  direction: improved ? "improved" : "worsened",
1157
1040
  series: parseJsonList$14(r.seriesJson).map((s) => ({
1158
1041
  date: str$19(s.date),
1159
- value: num$3(s.value)
1042
+ value: num$4(s.value)
1160
1043
  }))
1161
1044
  };
1162
1045
  });
@@ -1216,7 +1099,7 @@ function extractWordPrefix(keyword, wordCount = 2) {
1216
1099
  }
1217
1100
  function analyzeClustering(keywords, options = {}) {
1218
1101
  const { minClusterSize = 2, minImpressions = 10, clusterBy = "both" } = options;
1219
- const filtered = keywords.filter((k) => num$4(k.impressions) >= minImpressions);
1102
+ const filtered = keywords.filter((k) => num(k.impressions) >= minImpressions);
1220
1103
  const clusterMap = /* @__PURE__ */ new Map();
1221
1104
  const clusteredKeywords = /* @__PURE__ */ new Set();
1222
1105
  if (clusterBy === "intent" || clusterBy === "both") for (const kw of filtered) {
@@ -1253,9 +1136,9 @@ function analyzeClustering(keywords, options = {}) {
1253
1136
  const clusters = [];
1254
1137
  for (const [name, data] of clusterMap) {
1255
1138
  if (data.keywords.length < minClusterSize) continue;
1256
- const totalClicks = data.keywords.reduce((sum, k) => sum + num$4(k.clicks), 0);
1257
- const totalImpressions = data.keywords.reduce((sum, k) => sum + num$4(k.impressions), 0);
1258
- const avgPosition = data.keywords.reduce((sum, k) => sum + num$4(k.position), 0) / data.keywords.length;
1139
+ const totalClicks = data.keywords.reduce((sum, k) => sum + num(k.clicks), 0);
1140
+ const totalImpressions = data.keywords.reduce((sum, k) => sum + num(k.impressions), 0);
1141
+ const avgPosition = data.keywords.reduce((sum, k) => sum + num(k.position), 0) / data.keywords.length;
1259
1142
  clusters.push({
1260
1143
  clusterName: name,
1261
1144
  clusterType: data.type,
@@ -1343,16 +1226,16 @@ const clusteringAnalyzer = defineAnalyzer({
1343
1226
  const clusters = (Array.isArray(rows) ? rows : []).map((r) => ({
1344
1227
  clusterName: str$18(r.clusterName),
1345
1228
  clusterType: str$18(r.clusterType),
1346
- keywordCount: num$4(r.keywordCount),
1347
- totalClicks: num$4(r.totalClicks),
1348
- totalImpressions: num$4(r.totalImpressions),
1349
- avgPosition: num$4(r.avgPosition),
1229
+ keywordCount: num(r.keywordCount),
1230
+ totalClicks: num(r.totalClicks),
1231
+ totalImpressions: num(r.totalImpressions),
1232
+ avgPosition: num(r.avgPosition),
1350
1233
  keywords: parseJsonList$13(r.keywords).map((k) => ({
1351
1234
  query: str$18(k.query),
1352
- clicks: num$4(k.clicks),
1353
- impressions: num$4(k.impressions),
1354
- ctr: num$4(k.ctr),
1355
- position: num$4(k.position)
1235
+ clicks: num(k.clicks),
1236
+ impressions: num(k.impressions),
1237
+ ctr: num(k.ctr),
1238
+ position: num(k.position)
1356
1239
  }))
1357
1240
  }));
1358
1241
  return {
@@ -1442,13 +1325,13 @@ function analyzeConcentration(items, options = {}) {
1442
1325
  function analyzePageConcentration(pages, options) {
1443
1326
  return analyzeConcentration(pages.map((p) => ({
1444
1327
  key: p.page,
1445
- clicks: num$4(p.clicks)
1328
+ clicks: num(p.clicks)
1446
1329
  })), options);
1447
1330
  }
1448
1331
  function analyzeKeywordConcentration(keywords, options) {
1449
1332
  return analyzeConcentration(keywords.map((k) => ({
1450
1333
  key: k.query,
1451
- clicks: num$4(k.clicks)
1334
+ clicks: num(k.clicks)
1452
1335
  })), options);
1453
1336
  }
1454
1337
  const concentrationAnalyzer = defineAnalyzer({
@@ -1532,16 +1415,16 @@ const concentrationAnalyzer = defineAnalyzer({
1532
1415
  const topRaw = parseJsonList$12(r.topNItems);
1533
1416
  return {
1534
1417
  results: [{
1535
- giniCoefficient: num$4(r.giniCoefficient),
1536
- hhi: num$4(r.hhi),
1537
- topNConcentration: num$4(r.topNConcentration),
1418
+ giniCoefficient: num(r.giniCoefficient),
1419
+ hhi: num(r.hhi),
1420
+ topNConcentration: num(r.topNConcentration),
1538
1421
  topNItems: topRaw.map((t) => ({
1539
1422
  key: str$17(t.key),
1540
- clicks: num$4(t.clicks),
1541
- share: num$4(t.share)
1423
+ clicks: num(t.clicks),
1424
+ share: num(t.share)
1542
1425
  })),
1543
- totalItems: num$4(r.totalItems),
1544
- totalClicks: num$4(r.totalClicks),
1426
+ totalItems: num(r.totalItems),
1427
+ totalClicks: num(r.totalClicks),
1545
1428
  riskLevel: str$17(r.riskLevel)
1546
1429
  }],
1547
1430
  meta: {
@@ -1567,7 +1450,7 @@ const concentrationAnalyzer = defineAnalyzer({
1567
1450
  };
1568
1451
  }
1569
1452
  });
1570
- function num$2(v) {
1453
+ function num$3(v) {
1571
1454
  if (typeof v === "number") return v;
1572
1455
  if (typeof v === "bigint") return Number(v);
1573
1456
  if (v == null) return 0;
@@ -1597,7 +1480,7 @@ const contentVelocityAnalyzer = defineAnalyzer({
1597
1480
  ),
1598
1481
  per_week AS (
1599
1482
  SELECT
1600
- strftime(date, '%G-W%V') AS week,
1483
+ strftime(CAST(date AS DATE), '%G-W%V') AS week,
1601
1484
  MIN(date) AS week_start,
1602
1485
  CAST(COUNT(DISTINCT query) AS DOUBLE) AS totalKeywords
1603
1486
  FROM src
@@ -1605,7 +1488,7 @@ const contentVelocityAnalyzer = defineAnalyzer({
1605
1488
  ),
1606
1489
  new_per_week AS (
1607
1490
  SELECT
1608
- strftime(first_date, '%G-W%V') AS week,
1491
+ strftime(CAST(first_date AS DATE), '%G-W%V') AS week,
1609
1492
  CAST(COUNT(*) AS DOUBLE) AS newKeywords
1610
1493
  FROM first_seen
1611
1494
  GROUP BY week
@@ -1634,8 +1517,8 @@ const contentVelocityAnalyzer = defineAnalyzer({
1634
1517
  const startDate = toIsoDate(startDateD);
1635
1518
  const weekly = arr.map((r) => ({
1636
1519
  week: str$16(r.week),
1637
- newKeywords: num$2(r.newKeywords),
1638
- totalKeywords: num$2(r.totalKeywords)
1520
+ newKeywords: num$3(r.newKeywords),
1521
+ totalKeywords: num$3(r.totalKeywords)
1639
1522
  }));
1640
1523
  const total = weekly.reduce((s, w) => s + w.newKeywords, 0);
1641
1524
  const avg = weekly.length > 0 ? total / weekly.length : 0;
@@ -1658,7 +1541,7 @@ const contentVelocityAnalyzer = defineAnalyzer({
1658
1541
  };
1659
1542
  }
1660
1543
  });
1661
- function num$1(v) {
1544
+ function num$2(v) {
1662
1545
  if (typeof v === "number") return v;
1663
1546
  if (typeof v === "bigint") return Number(v);
1664
1547
  if (v == null) return 0;
@@ -1819,23 +1702,23 @@ const ctrAnomalyAnalyzer = defineAnalyzer({
1819
1702
  const anomalies = arr.map((r) => ({
1820
1703
  keyword: str$15(r.keyword),
1821
1704
  page: str$15(r.page),
1822
- breachDaysDown: num$1(r.breachDaysDown),
1823
- breachDaysUp: num$1(r.breachDaysUp),
1824
- clicksLost: num$1(r.clicksLost),
1825
- severity: num$1(r.severityRaw),
1826
- maxZ: num$1(r.maxZ),
1827
- baselineCtr: num$1(r.baselineCtr),
1828
- baselinePosition: num$1(r.baselinePosition),
1829
- totalImpressions: num$1(r.totalImpressions),
1830
- totalClicks: num$1(r.totalClicks),
1705
+ breachDaysDown: num$2(r.breachDaysDown),
1706
+ breachDaysUp: num$2(r.breachDaysUp),
1707
+ clicksLost: num$2(r.clicksLost),
1708
+ severity: num$2(r.severityRaw),
1709
+ maxZ: num$2(r.maxZ),
1710
+ baselineCtr: num$2(r.baselineCtr),
1711
+ baselinePosition: num$2(r.baselinePosition),
1712
+ totalImpressions: num$2(r.totalImpressions),
1713
+ totalClicks: num$2(r.totalClicks),
1831
1714
  series: parseJsonList$11(r.seriesJson).map((s) => ({
1832
1715
  date: str$15(s.date),
1833
- ctr: num$1(s.ctr),
1834
- position: num$1(s.position),
1835
- impressions: num$1(s.impressions),
1836
- rollingCtr: s.rollingCtr == null ? null : num$1(s.rollingCtr),
1837
- rollingStddev: s.rollingStddev == null ? null : num$1(s.rollingStddev),
1838
- z: num$1(s.z),
1716
+ ctr: num$2(s.ctr),
1717
+ position: num$2(s.position),
1718
+ impressions: num$2(s.impressions),
1719
+ rollingCtr: s.rollingCtr == null ? null : num$2(s.rollingCtr),
1720
+ rollingStddev: s.rollingStddev == null ? null : num$2(s.rollingStddev),
1721
+ z: num$2(s.z),
1839
1722
  breach: bool$2(s.breach)
1840
1723
  }))
1841
1724
  }));
@@ -1853,7 +1736,7 @@ const ctrAnomalyAnalyzer = defineAnalyzer({
1853
1736
  };
1854
1737
  }
1855
1738
  });
1856
- function num(v) {
1739
+ function num$1(v) {
1857
1740
  if (typeof v === "number") return v;
1858
1741
  if (typeof v === "bigint") return Number(v);
1859
1742
  if (v == null) return 0;
@@ -1967,20 +1850,20 @@ const ctrCurveAnalyzer = defineAnalyzer({
1967
1850
  const row = arr[0] ?? {};
1968
1851
  const curve = parseJsonList$10(row.curve_json).map((r) => ({
1969
1852
  bucket: str$14(r.bucket),
1970
- avgCtr: num(r.avgCtr),
1971
- medianPosition: num(r.medianPosition),
1972
- keywordCount: num(r.keywordCount),
1973
- totalClicks: num(r.totalClicks),
1974
- totalImpressions: num(r.totalImpressions)
1853
+ avgCtr: num$1(r.avgCtr),
1854
+ medianPosition: num$1(r.medianPosition),
1855
+ keywordCount: num$1(r.keywordCount),
1856
+ totalClicks: num$1(r.totalClicks),
1857
+ totalImpressions: num$1(r.totalImpressions)
1975
1858
  }));
1976
1859
  const outliers = parseJsonList$10(row.outliers_json).map((r) => ({
1977
1860
  query: str$14(r.query),
1978
- clicks: num(r.clicks),
1979
- impressions: num(r.impressions),
1980
- ctr: num(r.ctr),
1981
- position: num(r.position),
1982
- expectedCtr: num(r.expectedCtr),
1983
- ctrDiff: num(r.ctrDiff)
1861
+ clicks: num$1(r.clicks),
1862
+ impressions: num$1(r.impressions),
1863
+ ctr: num$1(r.ctr),
1864
+ position: num$1(r.position),
1865
+ expectedCtr: num$1(r.expectedCtr),
1866
+ ctrDiff: num$1(r.ctrDiff)
1984
1867
  }));
1985
1868
  return {
1986
1869
  results: curve,
@@ -2098,20 +1981,20 @@ const darkTrafficAnalyzer = defineAnalyzer({
2098
1981
  const row = arr[0] ?? {};
2099
1982
  const pageTotals = typeof row.page_totals_json === "string" ? JSON.parse(row.page_totals_json) : row.page_totals_json ?? {};
2100
1983
  const kwTotals = typeof row.kw_totals_json === "string" ? JSON.parse(row.kw_totals_json) : row.kw_totals_json ?? {};
2101
- const totalClicks = num$4(pageTotals.totalClicks);
2102
- const totalImpressions = num$4(pageTotals.totalImpressions);
2103
- const attributedClicks = num$4(kwTotals.attributedClicks);
2104
- const attributedImpressions = num$4(kwTotals.attributedImpressions);
1984
+ const totalClicks = num(pageTotals.totalClicks);
1985
+ const totalImpressions = num(pageTotals.totalImpressions);
1986
+ const attributedClicks = num(kwTotals.attributedClicks);
1987
+ const attributedImpressions = num(kwTotals.attributedImpressions);
2105
1988
  const darkClicks = Math.max(0, totalClicks - attributedClicks);
2106
1989
  const darkPercent = totalClicks > 0 ? darkClicks / totalClicks : 0;
2107
1990
  return {
2108
1991
  results: parseJsonList$9(row.pages_json).map((r) => ({
2109
1992
  url: str$13(r.url),
2110
- totalClicks: num$4(r.totalClicks),
2111
- attributedClicks: num$4(r.attributedClicks),
2112
- darkClicks: num$4(r.darkClicks),
2113
- darkPercent: num$4(r.darkPercent),
2114
- keywordCount: num$4(r.keywordCount)
1993
+ totalClicks: num(r.totalClicks),
1994
+ attributedClicks: num(r.attributedClicks),
1995
+ darkClicks: num(r.darkClicks),
1996
+ darkPercent: num(r.darkPercent),
1997
+ keywordCount: num(r.keywordCount)
2115
1998
  })),
2116
1999
  meta: {
2117
2000
  summary: {
@@ -2350,13 +2233,13 @@ function parseJsonList$8(v) {
2350
2233
  function analyzeDecay(input, options = {}) {
2351
2234
  const { minPreviousClicks = 50, threshold = .2, sortBy = "lostClicks" } = options;
2352
2235
  const currentMap = buildPeriodMap(input.current, (r) => r.page, (r) => ({
2353
- clicks: num$4(r.clicks),
2354
- position: num$4(r.position)
2236
+ clicks: num(r.clicks),
2237
+ position: num(r.position)
2355
2238
  }));
2356
2239
  const previousMap = buildPeriodMap(input.previous, (r) => r.page, (r) => ({
2357
- clicks: num$4(r.clicks),
2358
- position: num$4(r.position)
2359
- }), (r) => num$4(r.clicks) >= minPreviousClicks);
2240
+ clicks: num(r.clicks),
2241
+ position: num(r.position)
2242
+ }), (r) => num(r.clicks) >= minPreviousClicks);
2360
2243
  const results = [];
2361
2244
  for (const [page, prev] of previousMap) {
2362
2245
  const curr = currentMap.get(page) || {
@@ -2477,17 +2360,17 @@ const decayAnalyzer = defineAnalyzer({
2477
2360
  return {
2478
2361
  results: arr.map((r) => ({
2479
2362
  page: str$12(r.page),
2480
- currentClicks: num$4(r.currentClicks),
2481
- previousClicks: num$4(r.previousClicks),
2482
- lostClicks: num$4(r.lostClicks),
2483
- declinePercent: num$4(r.declinePercent),
2484
- currentPosition: num$4(r.currentPosition),
2485
- previousPosition: num$4(r.previousPosition),
2486
- positionDrop: num$4(r.positionDrop),
2363
+ currentClicks: num(r.currentClicks),
2364
+ previousClicks: num(r.previousClicks),
2365
+ lostClicks: num(r.lostClicks),
2366
+ declinePercent: num(r.declinePercent),
2367
+ currentPosition: num(r.currentPosition),
2368
+ previousPosition: num(r.previousPosition),
2369
+ positionDrop: num(r.positionDrop),
2487
2370
  series: parseJsonList$8(r.seriesJson).map((s) => ({
2488
2371
  week: str$12(s.week),
2489
- clicks: num$4(s.clicks),
2490
- impressions: num$4(s.impressions)
2372
+ clicks: num(s.clicks),
2373
+ impressions: num(s.impressions)
2491
2374
  }))
2492
2375
  })),
2493
2376
  meta: { total: arr.length }
@@ -2552,10 +2435,10 @@ const deviceGapAnalyzer = defineAnalyzer({
2552
2435
  const typed = arr.map((r) => ({
2553
2436
  date: str$11(r.date),
2554
2437
  device: str$11(r.device).toUpperCase(),
2555
- clicks: num$4(r.clicks),
2556
- impressions: num$4(r.impressions),
2557
- ctr: num$4(r.ctr),
2558
- position: num$4(r.position)
2438
+ clicks: num(r.clicks),
2439
+ impressions: num(r.impressions),
2440
+ ctr: num(r.ctr),
2441
+ position: num(r.position)
2559
2442
  }));
2560
2443
  const byDate = /* @__PURE__ */ new Map();
2561
2444
  for (const r of typed) {
@@ -2783,16 +2666,16 @@ const intentAtlasAnalyzer = defineAnalyzer({
2783
2666
  reduceSql(rows) {
2784
2667
  const clusters = (Array.isArray(rows) ? rows : []).map((r) => ({
2785
2668
  clusterKey: str$10(r.clusterKey),
2786
- keywordCount: num$4(r.keywordCount),
2787
- totalImpressions: num$4(r.totalImpressions),
2788
- totalClicks: num$4(r.totalClicks),
2789
- ctr: num$4(r.ctr),
2790
- avgPosition: num$4(r.avgPosition),
2669
+ keywordCount: num(r.keywordCount),
2670
+ totalImpressions: num(r.totalImpressions),
2671
+ totalClicks: num(r.totalClicks),
2672
+ ctr: num(r.ctr),
2673
+ avgPosition: num(r.avgPosition),
2791
2674
  keywords: parseJsonList$7(r.keywords).slice(0, 25).map((k) => ({
2792
2675
  query: str$10(k.query),
2793
- impressions: num$4(k.impressions),
2794
- clicks: num$4(k.clicks),
2795
- position: num$4(k.position)
2676
+ impressions: num(k.impressions),
2677
+ clicks: num(k.clicks),
2678
+ position: num(k.position)
2796
2679
  }))
2797
2680
  }));
2798
2681
  const totalImpressions = clusters.reduce((s, c) => s + c.totalImpressions, 0);
@@ -2893,21 +2776,21 @@ const keywordBreadthAnalyzer = defineAnalyzer({
2893
2776
  const arr = Array.isArray(rows) ? rows : [];
2894
2777
  const { startDate, endDate } = periodOf(params);
2895
2778
  const row = arr[0] ?? {};
2896
- const distribution = parseJsonList$6(row.distribution_json).sort((a, b) => num$4(a.sortKey) - num$4(b.sortKey)).map((r) => ({
2779
+ const distribution = parseJsonList$6(row.distribution_json).sort((a, b) => num(a.sortKey) - num(b.sortKey)).map((r) => ({
2897
2780
  bucket: str$9(r.bucket),
2898
- pageCount: num$4(r.pageCount)
2781
+ pageCount: num(r.pageCount)
2899
2782
  }));
2900
2783
  const fragile = parseJsonList$6(row.fragile_json).map((r) => ({
2901
2784
  url: str$9(r.url),
2902
- keywordCount: num$4(r.keywordCount),
2903
- clicks: num$4(r.clicks),
2904
- impressions: num$4(r.impressions)
2785
+ keywordCount: num(r.keywordCount),
2786
+ clicks: num(r.clicks),
2787
+ impressions: num(r.impressions)
2905
2788
  }));
2906
2789
  const authority = parseJsonList$6(row.authority_json).map((r) => ({
2907
2790
  url: str$9(r.url),
2908
- keywordCount: num$4(r.keywordCount),
2909
- clicks: num$4(r.clicks),
2910
- impressions: num$4(r.impressions)
2791
+ keywordCount: num(r.keywordCount),
2792
+ clicks: num(r.clicks),
2793
+ impressions: num(r.impressions)
2911
2794
  }));
2912
2795
  const stats = typeof row.stats_json === "string" ? JSON.parse(row.stats_json) : row.stats_json ?? {};
2913
2796
  return {
@@ -2916,10 +2799,10 @@ const keywordBreadthAnalyzer = defineAnalyzer({
2916
2799
  fragilePages: fragile,
2917
2800
  authorityPages: authority,
2918
2801
  summary: {
2919
- totalPages: num$4(stats.totalPages),
2920
- avgKeywordsPerPage: num$4(stats.avgKeywordsPerPage),
2921
- fragileCount: num$4(stats.fragileCount),
2922
- authorityCount: num$4(stats.authorityCount)
2802
+ totalPages: num(stats.totalPages),
2803
+ avgKeywordsPerPage: num(stats.avgKeywordsPerPage),
2804
+ fragileCount: num(stats.fragileCount),
2805
+ authorityCount: num(stats.authorityCount)
2923
2806
  },
2924
2807
  startDate,
2925
2808
  endDate
@@ -2940,9 +2823,9 @@ function parseJsonList$5(v) {
2940
2823
  }
2941
2824
  function downsampleLogRank(points) {
2942
2825
  const all = points.map((p) => ({
2943
- rank: num$4(p.rank),
2944
- impressions: num$4(p.impressions),
2945
- clicks: num$4(p.clicks),
2826
+ rank: num(p.rank),
2827
+ impressions: num(p.impressions),
2828
+ clicks: num(p.clicks),
2946
2829
  query: str$8(p.query)
2947
2830
  }));
2948
2831
  if (all.length <= 80) return all;
@@ -3053,14 +2936,14 @@ const longTailAnalyzer = defineAnalyzer({
3053
2936
  reduceSql(rows) {
3054
2937
  const results = (Array.isArray(rows) ? rows : []).map((r) => ({
3055
2938
  page: str$8(r.page),
3056
- queryCount: num$4(r.queryCount),
3057
- totalImpressions: num$4(r.totalImpressions),
3058
- totalClicks: num$4(r.totalClicks),
3059
- slope: num$4(r.slope),
3060
- intercept: num$4(r.intercept),
3061
- r2: num$4(r.r2),
3062
- headImpressions: num$4(r.headImpressions),
3063
- headShare: num$4(r.headShare),
2939
+ queryCount: num(r.queryCount),
2940
+ totalImpressions: num(r.totalImpressions),
2941
+ totalClicks: num(r.totalClicks),
2942
+ slope: num(r.slope),
2943
+ intercept: num(r.intercept),
2944
+ r2: num(r.r2),
2945
+ headImpressions: num(r.headImpressions),
2946
+ headShare: num(r.headShare),
3064
2947
  fingerprint: str$8(r.fingerprint),
3065
2948
  points: downsampleLogRank(parseJsonList$5(r.pointsJson))
3066
2949
  }));
@@ -3099,9 +2982,9 @@ function analyzeMovers(input, options = {}) {
3099
2982
  const { changeThreshold = .2, minImpressions = 50, sortBy = "clicksChange" } = options;
3100
2983
  const normFactor = input.normalizationFactor ?? 1;
3101
2984
  const baselineMap = buildPeriodMap(input.previous, (r) => r.query, (r) => ({
3102
- clicks: num$4(r.clicks) / normFactor,
3103
- impressions: num$4(r.impressions) / normFactor,
3104
- position: num$4(r.position),
2985
+ clicks: num(r.clicks) / normFactor,
2986
+ impressions: num(r.impressions) / normFactor,
2987
+ position: num(r.position),
3105
2988
  page: r.page ?? null
3106
2989
  }));
3107
2990
  const pageMap = /* @__PURE__ */ new Map();
@@ -3111,9 +2994,9 @@ function analyzeMovers(input, options = {}) {
3111
2994
  const declining = [];
3112
2995
  const stable = [];
3113
2996
  for (const row of input.current) {
3114
- const impressions = num$4(row.impressions);
3115
- const clicks = num$4(row.clicks);
3116
- const position = num$4(row.position);
2997
+ const impressions = num(row.impressions);
2998
+ const clicks = num(row.clicks);
2999
+ const position = num(row.position);
3117
3000
  if (impressions < minImpressions) continue;
3118
3001
  const baseline = baselineMap.get(row.query) || {
3119
3002
  clicks: 0,
@@ -3276,21 +3159,21 @@ const moversAnalyzer = defineAnalyzer({
3276
3159
  const normalized = (Array.isArray(rows) ? rows : []).map((r) => ({
3277
3160
  keyword: str$7(r.keyword),
3278
3161
  page: r.page == null ? null : str$7(r.page),
3279
- recentClicks: num$4(r.recentClicks),
3280
- recentImpressions: num$4(r.recentImpressions),
3281
- recentPosition: num$4(r.recentPosition),
3282
- baselineClicks: Math.round(num$4(r.baselineClicks)),
3283
- baselineImpressions: Math.round(num$4(r.baselineImpressions)),
3284
- baselinePosition: num$4(r.baselinePosition),
3285
- clicksChange: num$4(r.clicksChange),
3286
- clicksChangePercent: num$4(r.clicksChangePercent),
3287
- impressionsChangePercent: num$4(r.impressionsChangePercent),
3288
- positionChange: num$4(r.positionChange),
3162
+ recentClicks: num(r.recentClicks),
3163
+ recentImpressions: num(r.recentImpressions),
3164
+ recentPosition: num(r.recentPosition),
3165
+ baselineClicks: Math.round(num(r.baselineClicks)),
3166
+ baselineImpressions: Math.round(num(r.baselineImpressions)),
3167
+ baselinePosition: num(r.baselinePosition),
3168
+ clicksChange: num(r.clicksChange),
3169
+ clicksChangePercent: num(r.clicksChangePercent),
3170
+ impressionsChangePercent: num(r.impressionsChangePercent),
3171
+ positionChange: num(r.positionChange),
3289
3172
  direction: str$7(r.direction),
3290
3173
  series: parseJsonList$4(r.seriesJson).map((s) => ({
3291
3174
  week: str$7(s.week),
3292
- clicks: num$4(s.clicks),
3293
- impressions: num$4(s.impressions)
3175
+ clicks: num(s.clicks),
3176
+ impressions: num(s.impressions)
3294
3177
  }))
3295
3178
  }));
3296
3179
  const rising = normalized.filter((r) => r.direction === "rising");
@@ -3500,16 +3383,16 @@ const opportunityAnalyzer = defineAnalyzer({
3500
3383
  results: arr.map((r) => ({
3501
3384
  keyword: r.keyword == null ? "" : String(r.keyword),
3502
3385
  page: r.page == null ? null : String(r.page),
3503
- clicks: num$4(r.clicks),
3504
- impressions: num$4(r.impressions),
3505
- ctr: num$4(r.ctr),
3506
- position: num$4(r.position),
3507
- opportunityScore: num$4(r.opportunityScore),
3508
- potentialClicks: num$4(r.potentialClicks),
3386
+ clicks: num(r.clicks),
3387
+ impressions: num(r.impressions),
3388
+ ctr: num(r.ctr),
3389
+ position: num(r.position),
3390
+ opportunityScore: num(r.opportunityScore),
3391
+ potentialClicks: num(r.potentialClicks),
3509
3392
  factors: {
3510
- positionScore: num$4(r.positionScore),
3511
- impressionScore: num$4(r.impressionScore),
3512
- ctrGapScore: num$4(r.ctrGapScore)
3393
+ positionScore: num(r.positionScore),
3394
+ impressionScore: num(r.impressionScore),
3395
+ ctrGapScore: num(r.ctrGapScore)
3513
3396
  }
3514
3397
  })),
3515
3398
  meta: { total: arr.length }
@@ -3527,10 +3410,10 @@ const opportunityAnalyzer = defineAnalyzer({
3527
3410
  const sortBy = "opportunityScore";
3528
3411
  const results = [];
3529
3412
  for (const row of keywords) {
3530
- const impressions = num$4(row.impressions);
3531
- const position = num$4(row.position);
3532
- const ctr = num$4(row.ctr);
3533
- const clicks = num$4(row.clicks);
3413
+ const impressions = num(row.impressions);
3414
+ const position = num(row.position);
3415
+ const ctr = num(row.ctr);
3416
+ const clicks = num(row.clicks);
3534
3417
  if (impressions < minImpressions) continue;
3535
3418
  const positionScore = calculatePositionScore(position);
3536
3419
  const impressionScore = calculateImpressionScore(impressions);
@@ -3609,11 +3492,11 @@ const positionDistributionAnalyzer = defineAnalyzer({
3609
3492
  return {
3610
3493
  results: arr.map((r) => ({
3611
3494
  date: str$6(r.date),
3612
- pos_1_3: num$4(r.pos_1_3),
3613
- pos_4_10: num$4(r.pos_4_10),
3614
- pos_11_20: num$4(r.pos_11_20),
3615
- pos_20_plus: num$4(r.pos_20_plus),
3616
- total: num$4(r.total)
3495
+ pos_1_3: num(r.pos_1_3),
3496
+ pos_4_10: num(r.pos_4_10),
3497
+ pos_11_20: num(r.pos_11_20),
3498
+ pos_20_plus: num(r.pos_20_plus),
3499
+ total: num(r.total)
3617
3500
  })),
3618
3501
  meta: {
3619
3502
  total: arr.length,
@@ -3728,21 +3611,21 @@ const positionVolatilityAnalyzer = defineAnalyzer({
3728
3611
  allDates.add(date);
3729
3612
  const entry = byPage.get(page) ?? {
3730
3613
  page,
3731
- avgVolatility: num$4(r.pageAvgVolatility),
3732
- peakVolatility: num$4(r.pagePeakVolatility),
3733
- totalImpressions: num$4(r.pageTotalImpressions),
3614
+ avgVolatility: num(r.pageAvgVolatility),
3615
+ peakVolatility: num(r.pagePeakVolatility),
3616
+ totalImpressions: num(r.pageTotalImpressions),
3734
3617
  days: []
3735
3618
  };
3736
3619
  entry.days.push({
3737
3620
  date,
3738
- queryCount: num$4(r.queryCount),
3739
- dayImpressions: num$4(r.dayImpressions),
3740
- avgPosition: num$4(r.avgPosition),
3741
- posStddev: num$4(r.posStddev),
3742
- bestPosition: num$4(r.bestPosition),
3743
- worstPosition: num$4(r.worstPosition),
3744
- dodShift: num$4(r.dodShift),
3745
- volatility: num$4(r.volatility)
3621
+ queryCount: num(r.queryCount),
3622
+ dayImpressions: num(r.dayImpressions),
3623
+ avgPosition: num(r.avgPosition),
3624
+ posStddev: num(r.posStddev),
3625
+ bestPosition: num(r.bestPosition),
3626
+ worstPosition: num(r.worstPosition),
3627
+ dodShift: num(r.dodShift),
3628
+ volatility: num(r.volatility)
3746
3629
  });
3747
3630
  byPage.set(page, entry);
3748
3631
  }
@@ -3893,14 +3776,14 @@ const queryMigrationAnalyzer = defineAnalyzer({
3893
3776
  const edges = arr.map((r) => ({
3894
3777
  sourcePage: str$4(r.source_page),
3895
3778
  targetPage: str$4(r.target_page),
3896
- weight: num$4(r.weight),
3897
- queryCount: num$4(r.query_count),
3898
- exactCount: num$4(r.exact_count),
3899
- fuzzyCount: num$4(r.query_count) - num$4(r.exact_count),
3779
+ weight: num(r.weight),
3780
+ queryCount: num(r.query_count),
3781
+ exactCount: num(r.exact_count),
3782
+ fuzzyCount: num(r.query_count) - num(r.exact_count),
3900
3783
  examples: parseJsonList$3(r.examplesJson).slice(0, 8).map((e) => ({
3901
3784
  sourceQuery: str$4(e.sourceQuery),
3902
3785
  targetQuery: str$4(e.targetQuery),
3903
- absorbed: num$4(e.absorbed),
3786
+ absorbed: num(e.absorbed),
3904
3787
  matchType: str$4(e.matchType)
3905
3788
  }))
3906
3789
  }));
@@ -4040,14 +3923,14 @@ const seasonalityAnalyzer = defineAnalyzer({
4040
3923
  const arr = Array.isArray(rows) ? rows : [];
4041
3924
  const breakdown = arr.map((r) => ({
4042
3925
  month: str$3(r.month),
4043
- value: num$4(r.value),
4044
- vsAverage: num$4(r.vsAverage),
3926
+ value: num(r.value),
3927
+ vsAverage: num(r.vsAverage),
4045
3928
  isPeak: bool$1(r.isPeak),
4046
3929
  isTrough: bool$1(r.isTrough)
4047
3930
  }));
4048
3931
  const first = arr[0];
4049
- const strength = first ? num$4(first.strength) : 0;
4050
- const monthCount = first ? num$4(first.monthCount) : 0;
3932
+ const strength = first ? num(first.strength) : 0;
3933
+ const monthCount = first ? num(first.monthCount) : 0;
4051
3934
  const peakMonths = [...new Set(breakdown.filter((m) => m.isPeak).map((m) => m.month.substring(5, 7)))];
4052
3935
  const troughMonths = [...new Set(breakdown.filter((m) => m.isTrough).map((m) => m.month.substring(5, 7)))];
4053
3936
  const hasSeasonality = peakMonths.length > 0 || troughMonths.length > 0 || strength > .3;
@@ -4240,18 +4123,18 @@ const stlDecomposeAnalyzer = defineAnalyzer({
4240
4123
  const results = arr.map((r) => ({
4241
4124
  keyword: str$2(r.keyword),
4242
4125
  page: str$2(r.page),
4243
- totalImpressions: num$4(r.totalImpressions),
4244
- days: num$4(r.days),
4245
- seasonalStrength: num$4(r.seasonalStrength),
4246
- trendStrength: num$4(r.trendStrength),
4247
- residualAnomalies: num$4(r.residualAnomalies),
4248
- trendSlope: num$4(r.trendSlope),
4126
+ totalImpressions: num(r.totalImpressions),
4127
+ days: num(r.days),
4128
+ seasonalStrength: num(r.seasonalStrength),
4129
+ trendStrength: num(r.trendStrength),
4130
+ residualAnomalies: num(r.residualAnomalies),
4131
+ trendSlope: num(r.trendSlope),
4249
4132
  series: parseJsonList$2(r.seriesJson).map((s) => ({
4250
4133
  date: str$2(s.date),
4251
- observed: num$4(s.observed),
4252
- trend: s.trend == null ? null : num$4(s.trend),
4253
- seasonal: s.seasonal == null ? null : num$4(s.seasonal),
4254
- residual: s.residual == null ? null : num$4(s.residual),
4134
+ observed: num(s.observed),
4135
+ trend: s.trend == null ? null : num(s.trend),
4136
+ seasonal: s.seasonal == null ? null : num(s.seasonal),
4137
+ residual: s.residual == null ? null : num(s.residual),
4255
4138
  anomaly: bool(s.anomaly)
4256
4139
  }))
4257
4140
  }));
@@ -4277,10 +4160,10 @@ const strikingDistanceAnalyzer = defineAnalyzer({
4277
4160
  const limit = params.limit ?? 1e3;
4278
4161
  const results = [];
4279
4162
  for (const row of arr) {
4280
- const position = num$4(row.position);
4281
- const impressions = num$4(row.impressions);
4282
- const ctr = num$4(row.ctr);
4283
- const clicks = num$4(row.clicks);
4163
+ const position = num(row.position);
4164
+ const impressions = num(row.impressions);
4165
+ const ctr = num(row.ctr);
4166
+ const clicks = num(row.clicks);
4284
4167
  if (position < minPosition || position > maxPosition) continue;
4285
4168
  if (impressions < minImpressions) continue;
4286
4169
  if (ctr > maxCtr) continue;
@@ -4497,10 +4380,10 @@ const survivalAnalyzer = defineAnalyzer({
4497
4380
  const windowDays = Math.round((new Date(endDate).getTime() - new Date(startDate).getTime()) / MS_PER_DAY) + 1;
4498
4381
  const results = arr.map((r) => {
4499
4382
  const curve = parseJsonList$1(r.curveJson).map((p) => ({
4500
- tenure: num$4(p.tenure),
4501
- survival: num$4(p.survival),
4502
- atRisk: num$4(p.atRisk),
4503
- events: num$4(p.events)
4383
+ tenure: num(p.tenure),
4384
+ survival: num(p.survival),
4385
+ atRisk: num(p.atRisk),
4386
+ events: num(p.events)
4504
4387
  }));
4505
4388
  let medianTenure = 0;
4506
4389
  for (let i = 0; i < curve.length; i++) {
@@ -4520,8 +4403,8 @@ const survivalAnalyzer = defineAnalyzer({
4520
4403
  if (medianTenure === 0 && last && last.survival > .5) medianTenure = last.tenure;
4521
4404
  return {
4522
4405
  cohort: str$1(r.cohort),
4523
- episodeCount: num$4(r.episodeCount),
4524
- censoringRate: num$4(r.censoringRate),
4406
+ episodeCount: num(r.episodeCount),
4407
+ censoringRate: num(r.censoringRate),
4525
4408
  medianTenure,
4526
4409
  curve
4527
4410
  };
@@ -4659,17 +4542,17 @@ const trendsAnalyzer = defineAnalyzer({
4659
4542
  const results = arr.map((r) => {
4660
4543
  const series = parseJsonList(r.seriesJson).map((s) => ({
4661
4544
  week: str(s.week),
4662
- clicks: num$4(s.clicks),
4663
- impressions: num$4(s.impressions)
4545
+ clicks: num(s.clicks),
4546
+ impressions: num(s.impressions)
4664
4547
  }));
4665
4548
  return {
4666
4549
  [dim === "keywords" ? "query" : "page"]: str(r.entity),
4667
- totalClicks: num$4(r.totalClicks),
4668
- totalImpressions: num$4(r.totalImpressions),
4669
- weeksWithData: num$4(r.weeksWithData),
4670
- slope: num$4(r.slope),
4671
- growthRatio: num$4(r.growthRatio),
4672
- avgPosition: num$4(r.avgPosition),
4550
+ totalClicks: num(r.totalClicks),
4551
+ totalImpressions: num(r.totalImpressions),
4552
+ weeksWithData: num(r.weeksWithData),
4553
+ slope: num(r.slope),
4554
+ growthRatio: num(r.growthRatio),
4555
+ avgPosition: num(r.avgPosition),
4673
4556
  trend: str(r.trend),
4674
4557
  series
4675
4558
  };
@@ -4760,11 +4643,11 @@ const zeroClickAnalyzer = defineAnalyzer({
4760
4643
  results: arr.map((r) => ({
4761
4644
  query: r.query == null ? "" : String(r.query),
4762
4645
  page: r.page == null ? "" : String(r.page),
4763
- clicks: num$4(r.clicks),
4764
- impressions: num$4(r.impressions),
4765
- ctr: num$4(r.ctr),
4766
- position: num$4(r.position),
4767
- missedClicks: num$4(r.missedClicks)
4646
+ clicks: num(r.clicks),
4647
+ impressions: num(r.impressions),
4648
+ ctr: num(r.ctr),
4649
+ position: num(r.position),
4650
+ missedClicks: num(r.missedClicks)
4768
4651
  })),
4769
4652
  meta: {
4770
4653
  total: arr.length,
@@ -4813,122 +4696,6 @@ const zeroClickAnalyzer = defineAnalyzer({
4813
4696
  };
4814
4697
  }
4815
4698
  });
4816
- var AnalyzerCapabilityError = class extends Error {
4817
- constructor(tool, missing) {
4818
- super(`analyzer "${tool}" requires capabilities [${missing.join(", ")}] not provided by source`);
4819
- this.tool = tool;
4820
- this.missing = missing;
4821
- this.name = "AnalyzerCapabilityError";
4822
- }
4823
- };
4824
- function sourceCapabilities(source) {
4825
- const caps = /* @__PURE__ */ new Set();
4826
- if (source.executeSql) caps.add("executeSql");
4827
- if (source.capabilities.fileSets) caps.add("partitionedParquet");
4828
- if (source.capabilities.regex) caps.add("regex");
4829
- if (source.capabilities.windowTotals) caps.add("windowTotals");
4830
- if (source.capabilities.comparisonJoin) caps.add("comparisonJoin");
4831
- if (source.capabilities.attachedTables) caps.add("attachedTables");
4832
- return caps;
4833
- }
4834
- function assertSatisfies(analyzer, caps) {
4835
- const missing = analyzer.requires.filter((c) => !caps.has(c));
4836
- if (missing.length > 0) throw new AnalyzerCapabilityError(analyzer.id, missing);
4837
- }
4838
- async function runAnalyzerFromSource(source, params, registry) {
4839
- const caps = sourceCapabilities(source);
4840
- const analyzer = registry.resolveAnalyzer(params.type, caps.has("executeSql") || caps.has("attachedTables"));
4841
- if (!analyzer) throw new AnalyzerCapabilityError(params.type, ["executeSql"]);
4842
- assertSatisfies(analyzer, caps);
4843
- const plan = analyzer.build(params);
4844
- if (plan.kind === "rows") return runRowsPlanAgainstSource(source, analyzer, plan, params);
4845
- return runSqlPlanAgainstSource(source, analyzer, plan, params);
4846
- }
4847
- async function runRowsPlanAgainstSource(source, analyzer, plan, params) {
4848
- const entries = Object.entries(plan.queries);
4849
- const resolved = await Promise.all(entries.map(async ([k, q]) => [k, await source.queryRows(q.state)]));
4850
- const rowMap = Object.fromEntries(resolved);
4851
- const { results, meta } = analyzer.reduce(rowMap, { params });
4852
- return {
4853
- results,
4854
- meta: {
4855
- tool: params.type,
4856
- ...meta
4857
- }
4858
- };
4859
- }
4860
- function fileSetsFor(plan) {
4861
- const fileSets = { FILES: plan.current };
4862
- if (plan.previous) fileSets.FILES_PREV = plan.previous;
4863
- if (plan.extraFiles) for (const [key, fs] of Object.entries(plan.extraFiles)) fileSets[`FILES_${key}`] = fs;
4864
- return fileSets;
4865
- }
4866
- async function runSqlPlanAgainstSource(source, analyzer, plan, params) {
4867
- if (!source.executeSql) throw new AnalyzerCapabilityError(analyzer.id, ["executeSql"]);
4868
- if (plan.requiresAttachedTables && !source.capabilities.attachedTables) throw new AnalyzerCapabilityError(analyzer.id, ["attachedTables"]);
4869
- const fileSets = source.capabilities.fileSets ? fileSetsFor(plan) : void 0;
4870
- const rows = await source.executeSql(plan.sql, plan.params, fileSets ? { fileSets } : void 0);
4871
- const extras = {};
4872
- if (plan.extraQueries) for (const q of plan.extraQueries) {
4873
- const extraRows = await source.executeSql(q.sql, q.params, fileSets ? { fileSets } : void 0);
4874
- extras[q.name] = extraRows;
4875
- }
4876
- const { results, meta } = analyzer.reduce(rows, {
4877
- params,
4878
- extras
4879
- });
4880
- const sourceMeta = source.capabilities.localSource ? { source: "local" } : {};
4881
- return {
4882
- results,
4883
- meta: {
4884
- tool: params.type,
4885
- ...sourceMeta,
4886
- ...meta
4887
- }
4888
- };
4889
- }
4890
- function createAnalyzerRegistry(init = {}) {
4891
- const byId = /* @__PURE__ */ new Map();
4892
- for (const a of init.rows ?? []) {
4893
- const entry = byId.get(a.id) ?? {};
4894
- entry.rows = a;
4895
- byId.set(a.id, entry);
4896
- }
4897
- for (const a of init.sql ?? []) {
4898
- const entry = byId.get(a.id) ?? {};
4899
- entry.sql = a;
4900
- byId.set(a.id, entry);
4901
- }
4902
- const listAnalyzerIds = () => [...byId.keys()].sort();
4903
- const getAnalyzerVariants = (id) => byId.get(id);
4904
- const resolveAnalyzer = (id, sourceSupportsSql) => {
4905
- const variants = byId.get(id);
4906
- if (!variants) return void 0;
4907
- if (sourceSupportsSql) return variants.sql ?? variants.rows;
4908
- return variants.rows;
4909
- };
4910
- const listAnalyzersFor = (sourceSupportsSql) => {
4911
- const out = [];
4912
- for (const id of listAnalyzerIds()) {
4913
- const a = resolveAnalyzer(id, sourceSupportsSql);
4914
- if (a) out.push(a);
4915
- }
4916
- return out;
4917
- };
4918
- const listAnalyzerIdsFor = (source) => {
4919
- const sourceSupportsSql = typeof source.executeSql === "function";
4920
- const out = [];
4921
- for (const id of listAnalyzerIds()) if (resolveAnalyzer(id, sourceSupportsSql)) out.push(id);
4922
- return out;
4923
- };
4924
- return {
4925
- listAnalyzerIds,
4926
- getAnalyzerVariants,
4927
- resolveAnalyzer,
4928
- listAnalyzersFor,
4929
- listAnalyzerIdsFor
4930
- };
4931
- }
4932
4699
  const ROW_ANALYZERS = [
4933
4700
  strikingDistanceAnalyzer.rows,
4934
4701
  opportunityAnalyzer.rows,
@@ -4941,4 +4708,4 @@ const ROW_ANALYZERS = [
4941
4708
  cannibalizationAnalyzer.rows,
4942
4709
  zeroClickAnalyzer.rows
4943
4710
  ];
4944
- export { AnalyzerCapabilityError, ROW_ANALYZERS, 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 };
4711
+ export { ROW_ANALYZERS, bayesianCtrAnalyzer, bipartitePagerankAnalyzer, brandAnalyzer, cannibalizationAnalyzer, changePointAnalyzer, clampLimit, clampOffset, clusteringAnalyzer, concentrationAnalyzer, contentVelocityAnalyzer, ctrAnomalyAnalyzer, ctrCurveAnalyzer, darkTrafficAnalyzer, dataDetailAnalyzer, dataQueryAnalyzer, datesQueryState, decayAnalyzer, deviceGapAnalyzer, intentAtlasAnalyzer, keywordBreadthAnalyzer, keywordsQueryState, longTailAnalyzer, moversAnalyzer, opportunityAnalyzer, pagesQueryState, paginateClause, paginateInMemory, positionDistributionAnalyzer, positionVolatilityAnalyzer, queryMigrationAnalyzer, resolveSort, seasonalityAnalyzer, stlDecomposeAnalyzer, strikingDistanceAnalyzer, survivalAnalyzer, trendsAnalyzer, zeroClickAnalyzer };