@gscdump/analysis 0.32.12 → 0.33.3

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.
@@ -7,15 +7,26 @@ import { err, ok, unwrapResult } from "gscdump/result";
7
7
  import { between, date, extractDateRange, gsc, page, query } from "gscdump/query";
8
8
  import { MS_PER_DAY, daysAgo, toIsoDate } from "gscdump";
9
9
  import { buildExtrasQueries, buildTotalsSql, mergeExtras, resolveComparisonSQL, resolveToSQL, resolveToSQLOptimized } from "@gscdump/engine/resolver";
10
- function num$5(v) {
11
- if (typeof v === "number") return v;
12
- if (typeof v === "bigint") return Number(v);
13
- if (v == null) return 0;
14
- const n = Number(v);
10
+ function rowNumber(value) {
11
+ if (typeof value === "number") return Number.isFinite(value) ? value : 0;
12
+ if (typeof value === "bigint") return Number(value);
13
+ if (value == null) return 0;
14
+ const n = Number(value);
15
15
  return Number.isFinite(n) ? n : 0;
16
16
  }
17
- function str$23(v) {
18
- return v == null ? "" : String(v);
17
+ function rowString(value) {
18
+ return value == null ? "" : String(value);
19
+ }
20
+ function rowBoolean(value) {
21
+ return value === true || value === 1 || value === "true";
22
+ }
23
+ function parseJsonRows(value) {
24
+ if (Array.isArray(value)) return value;
25
+ if (typeof value === "string" && value.length > 0) {
26
+ const parsed = JSON.parse(value);
27
+ return Array.isArray(parsed) ? parsed : [];
28
+ }
29
+ return [];
19
30
  }
20
31
  const bayesianCtrAnalyzer = defineAnalyzer({
21
32
  id: "bayesian-ctr",
@@ -155,24 +166,24 @@ const bayesianCtrAnalyzer = defineAnalyzer({
155
166
  const arr = Array.isArray(rows) ? rows : [];
156
167
  const minImpressions = params.minImpressions ?? 50;
157
168
  const results = arr.map((r) => ({
158
- keyword: str$23(r.keyword),
159
- page: str$23(r.page),
160
- clicks: num$5(r.clicks),
161
- impressions: num$5(r.impressions),
162
- observedCtr: num$5(r.observedCtr),
163
- position: num$5(r.position),
164
- bucket: num$5(r.bucket),
165
- priorAlpha: num$5(r.priorAlpha),
166
- priorBeta: num$5(r.priorBeta),
167
- bucketPriorMean: num$5(r.bucketPriorMean),
168
- posteriorMean: num$5(r.posteriorMean),
169
- posteriorSd: num$5(r.posteriorSd),
170
- ciLow: num$5(r.ciLow),
171
- ciHigh: num$5(r.ciHigh),
172
- shrinkageDelta: num$5(r.shrinkageDelta),
173
- expectedClicksDelta: num$5(r.expectedClicksDelta),
174
- significance: num$5(r.significance),
175
- classification: str$23(r.classification)
169
+ keyword: rowString(r.keyword),
170
+ page: rowString(r.page),
171
+ clicks: rowNumber(r.clicks),
172
+ impressions: rowNumber(r.impressions),
173
+ observedCtr: rowNumber(r.observedCtr),
174
+ position: rowNumber(r.position),
175
+ bucket: rowNumber(r.bucket),
176
+ priorAlpha: rowNumber(r.priorAlpha),
177
+ priorBeta: rowNumber(r.priorBeta),
178
+ bucketPriorMean: rowNumber(r.bucketPriorMean),
179
+ posteriorMean: rowNumber(r.posteriorMean),
180
+ posteriorSd: rowNumber(r.posteriorSd),
181
+ ciLow: rowNumber(r.ciLow),
182
+ ciHigh: rowNumber(r.ciHigh),
183
+ shrinkageDelta: rowNumber(r.shrinkageDelta),
184
+ expectedClicksDelta: rowNumber(r.expectedClicksDelta),
185
+ significance: rowNumber(r.significance),
186
+ classification: rowString(r.classification)
176
187
  }));
177
188
  const under = results.filter((r) => r.classification === "underperforming").length;
178
189
  const over = results.filter((r) => r.classification === "overperforming").length;
@@ -190,17 +201,6 @@ const bayesianCtrAnalyzer = defineAnalyzer({
190
201
  });
191
202
  const BIPARTITE_PAGERANK_ITERATIONS = 25;
192
203
  const BIPARTITE_PAGERANK_DAMPING = .85;
193
- function str$22(v) {
194
- return v == null ? "" : String(v);
195
- }
196
- function parseJsonList$16(v) {
197
- if (Array.isArray(v)) return v;
198
- if (typeof v === "string" && v.length > 0) {
199
- const parsed = JSON.parse(v);
200
- return Array.isArray(parsed) ? parsed : [];
201
- }
202
- return [];
203
- }
204
204
  const bipartitePagerankAnalyzer = defineAnalyzer({
205
205
  id: "bipartite-pagerank",
206
206
  buildSql(params) {
@@ -406,8 +406,8 @@ const bipartitePagerankAnalyzer = defineAnalyzer({
406
406
  const iterations = BIPARTITE_PAGERANK_ITERATIONS;
407
407
  const d = BIPARTITE_PAGERANK_DAMPING;
408
408
  const results = arr.map((r) => ({
409
- kind: str$22(r.kind),
410
- id: str$22(r.id),
409
+ kind: rowString(r.kind),
410
+ id: rowString(r.id),
411
411
  rank: num(r.rank),
412
412
  bridging: num(r.bridging),
413
413
  anchoring: num(r.anchoring),
@@ -417,7 +417,7 @@ const bipartitePagerankAnalyzer = defineAnalyzer({
417
417
  const first = arr[0] ?? {};
418
418
  const queryCount = num(first.queryCount);
419
419
  const urlCount = num(first.urlCount);
420
- const deltas = parseJsonList$16(first.deltasJson).map((e) => ({
420
+ const deltas = parseJsonRows(first.deltasJson).map((e) => ({
421
421
  step: num(e.step),
422
422
  l1: num(e.l1)
423
423
  }));
@@ -503,9 +503,6 @@ function analysisErrorToException(error) {
503
503
  function escapeRegexAlt(s) {
504
504
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
505
505
  }
506
- function str$21(v) {
507
- return v == null ? "" : String(v);
508
- }
509
506
  function requireBrandTermsResult(brandTerms) {
510
507
  return brandTerms?.length ? ok(brandTerms) : err(analysisErrors.missingBrandTerms());
511
508
  }
@@ -581,13 +578,13 @@ const brandAnalyzer = defineAnalyzer({
581
578
  },
582
579
  reduceSql(rows) {
583
580
  const normalized = (Array.isArray(rows) ? rows : []).map((r) => ({
584
- query: str$21(r.query),
585
- page: r.page == null ? void 0 : str$21(r.page),
581
+ query: rowString(r.query),
582
+ page: r.page == null ? void 0 : rowString(r.page),
586
583
  clicks: num(r.clicks),
587
584
  impressions: num(r.impressions),
588
585
  ctr: num(r.ctr),
589
586
  position: num(r.position),
590
- segment: str$21(r.segment)
587
+ segment: rowString(r.segment)
591
588
  }));
592
589
  let brandClicks = 0;
593
590
  let nonBrandClicks = 0;
@@ -664,17 +661,6 @@ const sortRowResults$1 = createSorter((item, metric) => {
664
661
  case "pageCount": return item.pages.length;
665
662
  }
666
663
  }, "clicks");
667
- function str$20(v) {
668
- return v == null ? "" : String(v);
669
- }
670
- function parseJsonList$15(v) {
671
- if (Array.isArray(v)) return v;
672
- if (typeof v === "string" && v.length > 0) {
673
- const parsed = JSON.parse(v);
674
- return Array.isArray(parsed) ? parsed : [];
675
- }
676
- return [];
677
- }
678
664
  function analyzeCannibalization(rows, options = {}) {
679
665
  const { minImpressions = 10, maxPositionSpread = 10, minPages = 2, sortBy = "clicks", sortOrder = "desc" } = options;
680
666
  const queryMap = /* @__PURE__ */ new Map();
@@ -827,19 +813,19 @@ const cannibalizationAnalyzer = defineAnalyzer({
827
813
  },
828
814
  reduceSql(rows) {
829
815
  const events = (Array.isArray(rows) ? rows : []).map((r) => ({
830
- keyword: str$20(r.keyword),
816
+ keyword: rowString(r.keyword),
831
817
  totalImpressions: num(r.totalImpressions),
832
818
  totalClicks: num(r.totalClicks),
833
819
  competitorCount: num(r.competitorCount),
834
- leaderUrl: str$20(r.leaderUrl),
820
+ leaderUrl: rowString(r.leaderUrl),
835
821
  leaderCtr: num(r.leaderCtr),
836
822
  leaderPosition: num(r.leaderPosition),
837
823
  hhi: num(r.hhi),
838
824
  fragmentation: num(r.fragmentation),
839
825
  stolenClicks: num(r.stolenClicks),
840
826
  severity: num(r.severity),
841
- competitors: parseJsonList$15(r.competitors).map((c) => ({
842
- url: str$20(c.url),
827
+ competitors: parseJsonRows(r.competitors).map((c) => ({
828
+ url: rowString(c.url),
843
829
  clicks: num(c.clicks),
844
830
  impressions: num(c.impressions),
845
831
  ctr: num(c.ctr),
@@ -916,24 +902,6 @@ const cannibalizationAnalyzer = defineAnalyzer({
916
902
  };
917
903
  }
918
904
  });
919
- function num$4(v) {
920
- if (typeof v === "number") return v;
921
- if (typeof v === "bigint") return Number(v);
922
- if (v == null) return 0;
923
- const n = Number(v);
924
- return Number.isFinite(n) ? n : 0;
925
- }
926
- function str$19(v) {
927
- return v == null ? "" : String(v);
928
- }
929
- function parseJsonList$14(v) {
930
- if (Array.isArray(v)) return v;
931
- if (typeof v === "string" && v.length > 0) {
932
- const parsed = JSON.parse(v);
933
- return Array.isArray(parsed) ? parsed : [];
934
- }
935
- return [];
936
- }
937
905
  const changePointAnalyzer = defineAnalyzer({
938
906
  id: "change-point",
939
907
  buildSql(params) {
@@ -1086,24 +1054,24 @@ const changePointAnalyzer = defineAnalyzer({
1086
1054
  const metric = params.metric === "clicks" || params.metric === "impressions" ? params.metric : "position";
1087
1055
  const lowerIsBetter = metric === "position";
1088
1056
  const results = arr.map((r) => {
1089
- const delta = num$4(r.delta);
1057
+ const delta = rowNumber(r.delta);
1090
1058
  const improved = lowerIsBetter ? delta < 0 : delta > 0;
1091
1059
  return {
1092
- keyword: str$19(r.keyword),
1093
- page: str$19(r.page),
1094
- totalDays: num$4(r.totalDays),
1095
- totalImpressions: num$4(r.totalImpressions),
1096
- changeDate: str$19(r.changeDate),
1097
- llr: num$4(r.llr),
1098
- leftMean: num$4(r.leftMean),
1099
- rightMean: num$4(r.rightMean),
1060
+ keyword: rowString(r.keyword),
1061
+ page: rowString(r.page),
1062
+ totalDays: rowNumber(r.totalDays),
1063
+ totalImpressions: rowNumber(r.totalImpressions),
1064
+ changeDate: rowString(r.changeDate),
1065
+ llr: rowNumber(r.llr),
1066
+ leftMean: rowNumber(r.leftMean),
1067
+ rightMean: rowNumber(r.rightMean),
1100
1068
  delta,
1101
- leftStddev: num$4(r.leftStddev),
1102
- rightStddev: num$4(r.rightStddev),
1069
+ leftStddev: rowNumber(r.leftStddev),
1070
+ rightStddev: rowNumber(r.rightStddev),
1103
1071
  direction: improved ? "improved" : "worsened",
1104
- series: parseJsonList$14(r.seriesJson).map((s) => ({
1105
- date: str$19(s.date),
1106
- value: num$4(s.value)
1072
+ series: parseJsonRows(r.seriesJson).map((s) => ({
1073
+ date: rowString(s.date),
1074
+ value: rowNumber(s.value)
1107
1075
  }))
1108
1076
  };
1109
1077
  });
@@ -1140,17 +1108,6 @@ const INTENT_PREFIXES = [
1140
1108
  "near me"
1141
1109
  ];
1142
1110
  const WHITESPACE_RE = /\s+/;
1143
- function str$18(v) {
1144
- return v == null ? "" : String(v);
1145
- }
1146
- function parseJsonList$13(v) {
1147
- if (Array.isArray(v)) return v;
1148
- if (typeof v === "string" && v.length > 0) {
1149
- const parsed = JSON.parse(v);
1150
- return Array.isArray(parsed) ? parsed : [];
1151
- }
1152
- return [];
1153
- }
1154
1111
  function extractIntentPrefix(keyword) {
1155
1112
  const lower = keyword.toLowerCase();
1156
1113
  for (const prefix of INTENT_PREFIXES) if (lower.startsWith(`${prefix} `) || lower.startsWith(prefix)) return prefix;
@@ -1288,14 +1245,14 @@ const clusteringAnalyzer = defineAnalyzer({
1288
1245
  },
1289
1246
  reduceSql(rows) {
1290
1247
  const clusters = (Array.isArray(rows) ? rows : []).map((r) => ({
1291
- clusterName: str$18(r.clusterName),
1292
- clusterType: str$18(r.clusterType),
1248
+ clusterName: rowString(r.clusterName),
1249
+ clusterType: rowString(r.clusterType),
1293
1250
  keywordCount: num(r.keywordCount),
1294
1251
  totalClicks: num(r.totalClicks),
1295
1252
  totalImpressions: num(r.totalImpressions),
1296
1253
  avgPosition: num(r.avgPosition),
1297
- keywords: parseJsonList$13(r.keywords).map((k) => ({
1298
- query: str$18(k.query),
1254
+ keywords: parseJsonRows(r.keywords).map((k) => ({
1255
+ query: rowString(k.query),
1299
1256
  clicks: num(k.clicks),
1300
1257
  impressions: num(k.impressions),
1301
1258
  ctr: num(k.ctr),
@@ -1325,17 +1282,6 @@ const clusteringAnalyzer = defineAnalyzer({
1325
1282
  };
1326
1283
  }
1327
1284
  });
1328
- function str$17(v) {
1329
- return v == null ? "" : String(v);
1330
- }
1331
- function parseJsonList$12(v) {
1332
- if (Array.isArray(v)) return v;
1333
- if (typeof v === "string" && v.length > 0) {
1334
- const parsed = JSON.parse(v);
1335
- return Array.isArray(parsed) ? parsed : [];
1336
- }
1337
- return [];
1338
- }
1339
1285
  function calculateGini(values) {
1340
1286
  if (values.length === 0) return 0;
1341
1287
  const sorted = [...values].sort((a, b) => a - b);
@@ -1476,20 +1422,20 @@ const concentrationAnalyzer = defineAnalyzer({
1476
1422
  },
1477
1423
  reduceSql(rows, params) {
1478
1424
  const r = (Array.isArray(rows) ? rows : [])[0] ?? {};
1479
- const topRaw = parseJsonList$12(r.topNItems);
1425
+ const topRaw = parseJsonRows(r.topNItems);
1480
1426
  return {
1481
1427
  results: [{
1482
1428
  giniCoefficient: num(r.giniCoefficient),
1483
1429
  hhi: num(r.hhi),
1484
1430
  topNConcentration: num(r.topNConcentration),
1485
1431
  topNItems: topRaw.map((t) => ({
1486
- key: str$17(t.key),
1432
+ key: rowString(t.key),
1487
1433
  clicks: num(t.clicks),
1488
1434
  share: num(t.share)
1489
1435
  })),
1490
1436
  totalItems: num(r.totalItems),
1491
1437
  totalClicks: num(r.totalClicks),
1492
- riskLevel: str$17(r.riskLevel)
1438
+ riskLevel: rowString(r.riskLevel)
1493
1439
  }],
1494
1440
  meta: {
1495
1441
  total: 1,
@@ -1514,16 +1460,6 @@ const concentrationAnalyzer = defineAnalyzer({
1514
1460
  };
1515
1461
  }
1516
1462
  });
1517
- function num$3(v) {
1518
- if (typeof v === "number") return v;
1519
- if (typeof v === "bigint") return Number(v);
1520
- if (v == null) return 0;
1521
- const n = Number(v);
1522
- return Number.isFinite(n) ? n : 0;
1523
- }
1524
- function str$16(v) {
1525
- return v == null ? "" : String(v);
1526
- }
1527
1463
  const contentVelocityAnalyzer = defineAnalyzer({
1528
1464
  id: "content-velocity",
1529
1465
  buildSql(params) {
@@ -1580,9 +1516,9 @@ const contentVelocityAnalyzer = defineAnalyzer({
1580
1516
  startDateD.setUTCDate(startDateD.getUTCDate() - days);
1581
1517
  const startDate = toIsoDate(startDateD);
1582
1518
  const weekly = arr.map((r) => ({
1583
- week: str$16(r.week),
1584
- newKeywords: num$3(r.newKeywords),
1585
- totalKeywords: num$3(r.totalKeywords)
1519
+ week: rowString(r.week),
1520
+ newKeywords: rowNumber(r.newKeywords),
1521
+ totalKeywords: rowNumber(r.totalKeywords)
1586
1522
  }));
1587
1523
  const total = weekly.reduce((s, w) => s + w.newKeywords, 0);
1588
1524
  const avg = weekly.length > 0 ? total / weekly.length : 0;
@@ -1605,27 +1541,6 @@ const contentVelocityAnalyzer = defineAnalyzer({
1605
1541
  };
1606
1542
  }
1607
1543
  });
1608
- function num$2(v) {
1609
- if (typeof v === "number") return v;
1610
- if (typeof v === "bigint") return Number(v);
1611
- if (v == null) return 0;
1612
- const n = Number(v);
1613
- return Number.isFinite(n) ? n : 0;
1614
- }
1615
- function str$15(v) {
1616
- return v == null ? "" : String(v);
1617
- }
1618
- function bool$2(v) {
1619
- return v === true || v === 1 || v === "true";
1620
- }
1621
- function parseJsonList$11(v) {
1622
- if (Array.isArray(v)) return v;
1623
- if (typeof v === "string" && v.length > 0) {
1624
- const parsed = JSON.parse(v);
1625
- return Array.isArray(parsed) ? parsed : [];
1626
- }
1627
- return [];
1628
- }
1629
1544
  const ctrAnomalyAnalyzer = defineAnalyzer({
1630
1545
  id: "ctr-anomaly",
1631
1546
  buildSql(params) {
@@ -1767,26 +1682,26 @@ const ctrAnomalyAnalyzer = defineAnalyzer({
1767
1682
  const minRollingN = 14;
1768
1683
  const zThreshold = params.threshold ?? 2;
1769
1684
  const anomalies = arr.map((r) => ({
1770
- keyword: str$15(r.keyword),
1771
- page: str$15(r.page),
1772
- breachDaysDown: num$2(r.breachDaysDown),
1773
- breachDaysUp: num$2(r.breachDaysUp),
1774
- clicksLost: num$2(r.clicksLost),
1775
- severity: num$2(r.severityRaw),
1776
- maxZ: num$2(r.maxZ),
1777
- baselineCtr: num$2(r.baselineCtr),
1778
- baselinePosition: num$2(r.baselinePosition),
1779
- totalImpressions: num$2(r.totalImpressions),
1780
- totalClicks: num$2(r.totalClicks),
1781
- series: parseJsonList$11(r.seriesJson).map((s) => ({
1782
- date: str$15(s.date),
1783
- ctr: num$2(s.ctr),
1784
- position: num$2(s.position),
1785
- impressions: num$2(s.impressions),
1786
- rollingCtr: s.rollingCtr == null ? null : num$2(s.rollingCtr),
1787
- rollingStddev: s.rollingStddev == null ? null : num$2(s.rollingStddev),
1788
- z: num$2(s.z),
1789
- breach: bool$2(s.breach)
1685
+ keyword: rowString(r.keyword),
1686
+ page: rowString(r.page),
1687
+ breachDaysDown: rowNumber(r.breachDaysDown),
1688
+ breachDaysUp: rowNumber(r.breachDaysUp),
1689
+ clicksLost: rowNumber(r.clicksLost),
1690
+ severity: rowNumber(r.severityRaw),
1691
+ maxZ: rowNumber(r.maxZ),
1692
+ baselineCtr: rowNumber(r.baselineCtr),
1693
+ baselinePosition: rowNumber(r.baselinePosition),
1694
+ totalImpressions: rowNumber(r.totalImpressions),
1695
+ totalClicks: rowNumber(r.totalClicks),
1696
+ series: parseJsonRows(r.seriesJson).map((s) => ({
1697
+ date: rowString(s.date),
1698
+ ctr: rowNumber(s.ctr),
1699
+ position: rowNumber(s.position),
1700
+ impressions: rowNumber(s.impressions),
1701
+ rollingCtr: s.rollingCtr == null ? null : rowNumber(s.rollingCtr),
1702
+ rollingStddev: s.rollingStddev == null ? null : rowNumber(s.rollingStddev),
1703
+ z: rowNumber(s.z),
1704
+ breach: rowBoolean(s.breach)
1790
1705
  }))
1791
1706
  }));
1792
1707
  const totalClicksLost = anomalies.reduce((s, a) => s + a.clicksLost, 0);
@@ -1803,24 +1718,6 @@ const ctrAnomalyAnalyzer = defineAnalyzer({
1803
1718
  };
1804
1719
  }
1805
1720
  });
1806
- function num$1(v) {
1807
- if (typeof v === "number") return v;
1808
- if (typeof v === "bigint") return Number(v);
1809
- if (v == null) return 0;
1810
- const n = Number(v);
1811
- return Number.isFinite(n) ? n : 0;
1812
- }
1813
- function str$14(v) {
1814
- return v == null ? "" : String(v);
1815
- }
1816
- function parseJsonList$10(v) {
1817
- if (Array.isArray(v)) return v;
1818
- if (typeof v === "string" && v.length > 0) {
1819
- const parsed = JSON.parse(v);
1820
- return Array.isArray(parsed) ? parsed : [];
1821
- }
1822
- return [];
1823
- }
1824
1721
  const ctrCurveAnalyzer = defineAnalyzer({
1825
1722
  id: "ctr-curve",
1826
1723
  buildSql(params) {
@@ -1915,22 +1812,22 @@ const ctrCurveAnalyzer = defineAnalyzer({
1915
1812
  const arr = Array.isArray(rows) ? rows : [];
1916
1813
  const { startDate, endDate } = periodOf(params);
1917
1814
  const row = arr[0] ?? {};
1918
- const curve = parseJsonList$10(row.curve_json).map((r) => ({
1919
- bucket: str$14(r.bucket),
1920
- avgCtr: num$1(r.avgCtr),
1921
- medianPosition: num$1(r.medianPosition),
1922
- keywordCount: num$1(r.keywordCount),
1923
- totalClicks: num$1(r.totalClicks),
1924
- totalImpressions: num$1(r.totalImpressions)
1815
+ const curve = parseJsonRows(row.curve_json).map((r) => ({
1816
+ bucket: rowString(r.bucket),
1817
+ avgCtr: rowNumber(r.avgCtr),
1818
+ medianPosition: rowNumber(r.medianPosition),
1819
+ keywordCount: rowNumber(r.keywordCount),
1820
+ totalClicks: rowNumber(r.totalClicks),
1821
+ totalImpressions: rowNumber(r.totalImpressions)
1925
1822
  }));
1926
- const outliers = parseJsonList$10(row.outliers_json).map((r) => ({
1927
- query: str$14(r.query),
1928
- clicks: num$1(r.clicks),
1929
- impressions: num$1(r.impressions),
1930
- ctr: num$1(r.ctr),
1931
- position: num$1(r.position),
1932
- expectedCtr: num$1(r.expectedCtr),
1933
- ctrDiff: num$1(r.ctrDiff)
1823
+ const outliers = parseJsonRows(row.outliers_json).map((r) => ({
1824
+ query: rowString(r.query),
1825
+ clicks: rowNumber(r.clicks),
1826
+ impressions: rowNumber(r.impressions),
1827
+ ctr: rowNumber(r.ctr),
1828
+ position: rowNumber(r.position),
1829
+ expectedCtr: rowNumber(r.expectedCtr),
1830
+ ctrDiff: rowNumber(r.ctrDiff)
1934
1831
  }));
1935
1832
  return {
1936
1833
  results: curve,
@@ -1943,17 +1840,6 @@ const ctrCurveAnalyzer = defineAnalyzer({
1943
1840
  };
1944
1841
  }
1945
1842
  });
1946
- function str$13(v) {
1947
- return v == null ? "" : String(v);
1948
- }
1949
- function parseJsonList$9(v) {
1950
- if (Array.isArray(v)) return v;
1951
- if (typeof v === "string" && v.length > 0) {
1952
- const parsed = JSON.parse(v);
1953
- return Array.isArray(parsed) ? parsed : [];
1954
- }
1955
- return [];
1956
- }
1957
1843
  const darkTrafficAnalyzer = defineAnalyzer({
1958
1844
  id: "dark-traffic",
1959
1845
  buildSql(params) {
@@ -2055,8 +1941,8 @@ const darkTrafficAnalyzer = defineAnalyzer({
2055
1941
  const darkClicks = Math.max(0, totalClicks - attributedClicks);
2056
1942
  const darkPercent = totalClicks > 0 ? darkClicks / totalClicks : 0;
2057
1943
  return {
2058
- results: parseJsonList$9(row.pages_json).map((r) => ({
2059
- url: str$13(r.url),
1944
+ results: parseJsonRows(row.pages_json).map((r) => ({
1945
+ url: rowString(r.url),
2060
1946
  totalClicks: num(r.totalClicks),
2061
1947
  attributedClicks: num(r.attributedClicks),
2062
1948
  darkClicks: num(r.darkClicks),
@@ -2482,17 +2368,6 @@ const sortResults$1 = createMetricSorter("lostClicks", {
2482
2368
  declinePercent: "desc",
2483
2369
  currentClicks: "asc"
2484
2370
  });
2485
- function str$12(v) {
2486
- return v == null ? "" : String(v);
2487
- }
2488
- function parseJsonList$8(v) {
2489
- if (Array.isArray(v)) return v;
2490
- if (typeof v === "string" && v.length > 0) {
2491
- const parsed = JSON.parse(v);
2492
- return Array.isArray(parsed) ? parsed : [];
2493
- }
2494
- return [];
2495
- }
2496
2371
  function analyzeDecay(input, options = {}) {
2497
2372
  const { minPreviousClicks = 50, threshold = .2, sortBy = "lostClicks" } = options;
2498
2373
  const currentMap = buildPeriodMap(input.current, (r) => r.page, (r) => ({
@@ -2619,7 +2494,7 @@ const decayAnalyzer = defineAnalyzer({
2619
2494
  },
2620
2495
  reduceSql(rows, params) {
2621
2496
  const mapped = (Array.isArray(rows) ? rows : []).map((r) => ({
2622
- page: str$12(r.page),
2497
+ page: rowString(r.page),
2623
2498
  currentClicks: num(r.currentClicks),
2624
2499
  previousClicks: num(r.previousClicks),
2625
2500
  lostClicks: num(r.lostClicks),
@@ -2627,8 +2502,8 @@ const decayAnalyzer = defineAnalyzer({
2627
2502
  currentPosition: num(r.currentPosition),
2628
2503
  previousPosition: num(r.previousPosition),
2629
2504
  positionDrop: num(r.positionDrop),
2630
- series: parseJsonList$8(r.seriesJson).map((s) => ({
2631
- week: str$12(s.week),
2505
+ series: parseJsonRows(r.seriesJson).map((s) => ({
2506
+ week: rowString(s.week),
2632
2507
  clicks: num(s.clicks),
2633
2508
  impressions: num(s.impressions)
2634
2509
  }))
@@ -2666,9 +2541,6 @@ const decayAnalyzer = defineAnalyzer({
2666
2541
  };
2667
2542
  }
2668
2543
  });
2669
- function str$11(v) {
2670
- return v == null ? "" : String(v);
2671
- }
2672
2544
  const deviceGapAnalyzer = defineAnalyzer({
2673
2545
  id: "device-gap",
2674
2546
  buildSql(params) {
@@ -2719,8 +2591,8 @@ const deviceGapAnalyzer = defineAnalyzer({
2719
2591
  const arr = Array.isArray(rows) ? rows : [];
2720
2592
  const { startDate, endDate } = periodOf(params);
2721
2593
  const typed = arr.map((r) => ({
2722
- date: str$11(r.date),
2723
- device: str$11(r.device).toUpperCase(),
2594
+ date: rowString(r.date),
2595
+ device: rowString(r.device).toUpperCase(),
2724
2596
  clicks: num(r.clicks),
2725
2597
  impressions: num(r.impressions),
2726
2598
  ctr: num(r.ctr),
@@ -2798,17 +2670,6 @@ const deviceGapAnalyzer = defineAnalyzer({
2798
2670
  };
2799
2671
  }
2800
2672
  });
2801
- function str$10(v) {
2802
- return v == null ? "" : String(v);
2803
- }
2804
- function parseJsonList$7(v) {
2805
- if (Array.isArray(v)) return v;
2806
- if (typeof v === "string" && v.length > 0) {
2807
- const parsed = JSON.parse(v);
2808
- return Array.isArray(parsed) ? parsed : [];
2809
- }
2810
- return [];
2811
- }
2812
2673
  const INTENT_ATLAS_STOP_WORDS = [
2813
2674
  "the",
2814
2675
  "a",
@@ -2951,14 +2812,14 @@ const intentAtlasAnalyzer = defineAnalyzer({
2951
2812
  },
2952
2813
  reduceSql(rows) {
2953
2814
  const clusters = (Array.isArray(rows) ? rows : []).map((r) => ({
2954
- clusterKey: str$10(r.clusterKey),
2815
+ clusterKey: rowString(r.clusterKey),
2955
2816
  keywordCount: num(r.keywordCount),
2956
2817
  totalImpressions: num(r.totalImpressions),
2957
2818
  totalClicks: num(r.totalClicks),
2958
2819
  ctr: num(r.ctr),
2959
2820
  avgPosition: num(r.avgPosition),
2960
- keywords: parseJsonList$7(r.keywords).slice(0, 25).map((k) => ({
2961
- query: str$10(k.query),
2821
+ keywords: parseJsonRows(r.keywords).slice(0, 25).map((k) => ({
2822
+ query: rowString(k.query),
2962
2823
  impressions: num(k.impressions),
2963
2824
  clicks: num(k.clicks),
2964
2825
  position: num(k.position)
@@ -2976,17 +2837,6 @@ const intentAtlasAnalyzer = defineAnalyzer({
2976
2837
  };
2977
2838
  }
2978
2839
  });
2979
- function str$9(v) {
2980
- return v == null ? "" : String(v);
2981
- }
2982
- function parseJsonList$6(v) {
2983
- if (Array.isArray(v)) return v;
2984
- if (typeof v === "string" && v.length > 0) {
2985
- const parsed = JSON.parse(v);
2986
- return Array.isArray(parsed) ? parsed : [];
2987
- }
2988
- return [];
2989
- }
2990
2840
  const keywordBreadthAnalyzer = defineAnalyzer({
2991
2841
  id: "keyword-breadth",
2992
2842
  buildSql(params) {
@@ -3061,18 +2911,18 @@ const keywordBreadthAnalyzer = defineAnalyzer({
3061
2911
  const arr = Array.isArray(rows) ? rows : [];
3062
2912
  const { startDate, endDate } = periodOf(params);
3063
2913
  const row = arr[0] ?? {};
3064
- const distribution = parseJsonList$6(row.distribution_json).sort((a, b) => num(a.sortKey) - num(b.sortKey)).map((r) => ({
3065
- bucket: str$9(r.bucket),
2914
+ const distribution = parseJsonRows(row.distribution_json).sort((a, b) => num(a.sortKey) - num(b.sortKey)).map((r) => ({
2915
+ bucket: rowString(r.bucket),
3066
2916
  pageCount: num(r.pageCount)
3067
2917
  }));
3068
- const fragile = parseJsonList$6(row.fragile_json).map((r) => ({
3069
- url: str$9(r.url),
2918
+ const fragile = parseJsonRows(row.fragile_json).map((r) => ({
2919
+ url: rowString(r.url),
3070
2920
  keywordCount: num(r.keywordCount),
3071
2921
  clicks: num(r.clicks),
3072
2922
  impressions: num(r.impressions)
3073
2923
  }));
3074
- const authority = parseJsonList$6(row.authority_json).map((r) => ({
3075
- url: str$9(r.url),
2924
+ const authority = parseJsonRows(row.authority_json).map((r) => ({
2925
+ url: rowString(r.url),
3076
2926
  keywordCount: num(r.keywordCount),
3077
2927
  clicks: num(r.clicks),
3078
2928
  impressions: num(r.impressions)
@@ -3095,23 +2945,12 @@ const keywordBreadthAnalyzer = defineAnalyzer({
3095
2945
  };
3096
2946
  }
3097
2947
  });
3098
- function str$8(v) {
3099
- return v == null ? "" : String(v);
3100
- }
3101
- function parseJsonList$5(v) {
3102
- if (Array.isArray(v)) return v;
3103
- if (typeof v === "string" && v.length > 0) {
3104
- const parsed = JSON.parse(v);
3105
- return Array.isArray(parsed) ? parsed : [];
3106
- }
3107
- return [];
3108
- }
3109
2948
  function downsampleLogRank(points) {
3110
2949
  const all = points.map((p) => ({
3111
2950
  rank: num(p.rank),
3112
2951
  impressions: num(p.impressions),
3113
2952
  clicks: num(p.clicks),
3114
- query: str$8(p.query)
2953
+ query: rowString(p.query)
3115
2954
  }));
3116
2955
  if (all.length <= 80) return all;
3117
2956
  const top = all.slice(0, 10);
@@ -3220,7 +3059,7 @@ const longTailAnalyzer = defineAnalyzer({
3220
3059
  },
3221
3060
  reduceSql(rows) {
3222
3061
  const results = (Array.isArray(rows) ? rows : []).map((r) => ({
3223
- page: str$8(r.page),
3062
+ page: rowString(r.page),
3224
3063
  queryCount: num(r.queryCount),
3225
3064
  totalImpressions: num(r.totalImpressions),
3226
3065
  totalClicks: num(r.totalClicks),
@@ -3229,8 +3068,8 @@ const longTailAnalyzer = defineAnalyzer({
3229
3068
  r2: num(r.r2),
3230
3069
  headImpressions: num(r.headImpressions),
3231
3070
  headShare: num(r.headShare),
3232
- fingerprint: str$8(r.fingerprint),
3233
- points: downsampleLogRank(parseJsonList$5(r.pointsJson))
3071
+ fingerprint: rowString(r.fingerprint),
3072
+ points: downsampleLogRank(parseJsonRows(r.pointsJson))
3234
3073
  }));
3235
3074
  const counts = {
3236
3075
  "flat-tail": 0,
@@ -3252,17 +3091,6 @@ function percentDifference(current, previous) {
3252
3091
  if (previous === 0) return current > 0 ? 100 : 0;
3253
3092
  return (current - previous) / previous * 100;
3254
3093
  }
3255
- function str$7(v) {
3256
- return v == null ? "" : String(v);
3257
- }
3258
- function parseJsonList$4(v) {
3259
- if (Array.isArray(v)) return v;
3260
- if (typeof v === "string" && v.length > 0) {
3261
- const parsed = JSON.parse(v);
3262
- return Array.isArray(parsed) ? parsed : [];
3263
- }
3264
- return [];
3265
- }
3266
3094
  function withCanonicalFields(r) {
3267
3095
  return {
3268
3096
  ...r,
@@ -3488,8 +3316,8 @@ const moversAnalyzer = defineAnalyzer({
3488
3316
  const baselineImpressions = Math.round(num(r.baselineImpressions));
3489
3317
  const baselinePosition = num(r.baselinePosition);
3490
3318
  return {
3491
- keyword: str$7(r.keyword),
3492
- page: r.page == null ? null : str$7(r.page),
3319
+ keyword: rowString(r.keyword),
3320
+ page: r.page == null ? null : rowString(r.page),
3493
3321
  recentClicks,
3494
3322
  recentImpressions,
3495
3323
  recentPosition,
@@ -3500,9 +3328,9 @@ const moversAnalyzer = defineAnalyzer({
3500
3328
  clicksChangePercent: num(r.clicksChangePercent),
3501
3329
  impressionsChangePercent: num(r.impressionsChangePercent),
3502
3330
  positionChange: num(r.positionChange),
3503
- direction: str$7(r.direction),
3504
- series: parseJsonList$4(r.seriesJson).map((s) => ({
3505
- week: str$7(s.week),
3331
+ direction: rowString(r.direction),
3332
+ series: parseJsonRows(r.seriesJson).map((s) => ({
3333
+ week: rowString(s.week),
3506
3334
  clicks: num(s.clicks),
3507
3335
  impressions: num(s.impressions)
3508
3336
  }))
@@ -3738,9 +3566,6 @@ const opportunityAnalyzer = defineAnalyzer({
3738
3566
  };
3739
3567
  }
3740
3568
  });
3741
- function str$6(v) {
3742
- return v == null ? "" : String(v);
3743
- }
3744
3569
  const positionDistributionAnalyzer = defineAnalyzer({
3745
3570
  id: "position-distribution",
3746
3571
  buildSql(params) {
@@ -3777,7 +3602,7 @@ const positionDistributionAnalyzer = defineAnalyzer({
3777
3602
  const { startDate, endDate } = periodOf(params);
3778
3603
  return {
3779
3604
  results: arr.map((r) => ({
3780
- date: str$6(r.date),
3605
+ date: rowString(r.date),
3781
3606
  pos_1_3: num(r.pos_1_3),
3782
3607
  pos_4_10: num(r.pos_4_10),
3783
3608
  pos_11_20: num(r.pos_11_20),
@@ -3792,9 +3617,6 @@ const positionDistributionAnalyzer = defineAnalyzer({
3792
3617
  };
3793
3618
  }
3794
3619
  });
3795
- function str$5(v) {
3796
- return v == null ? "" : String(v);
3797
- }
3798
3620
  const positionVolatilityAnalyzer = defineAnalyzer({
3799
3621
  id: "position-volatility",
3800
3622
  buildSql(params) {
@@ -3895,8 +3717,8 @@ const positionVolatilityAnalyzer = defineAnalyzer({
3895
3717
  const byPage = /* @__PURE__ */ new Map();
3896
3718
  const allDates = /* @__PURE__ */ new Set();
3897
3719
  for (const r of arr) {
3898
- const page = str$5(r.page);
3899
- const date = str$5(r.date);
3720
+ const page = rowString(r.page);
3721
+ const date = rowString(r.date);
3900
3722
  allDates.add(date);
3901
3723
  const entry = byPage.get(page) ?? {
3902
3724
  page,
@@ -3931,17 +3753,6 @@ const positionVolatilityAnalyzer = defineAnalyzer({
3931
3753
  };
3932
3754
  }
3933
3755
  });
3934
- function str$4(v) {
3935
- return v == null ? "" : String(v);
3936
- }
3937
- function parseJsonList$3(v) {
3938
- if (Array.isArray(v)) return v;
3939
- if (typeof v === "string" && v.length > 0) {
3940
- const parsed = JSON.parse(v);
3941
- return Array.isArray(parsed) ? parsed : [];
3942
- }
3943
- return [];
3944
- }
3945
3756
  const queryMigrationAnalyzer = defineAnalyzer({
3946
3757
  id: "query-migration",
3947
3758
  buildSql(params) {
@@ -4063,17 +3874,17 @@ const queryMigrationAnalyzer = defineAnalyzer({
4063
3874
  prevStart = toIsoDate(new Date(curStartMs - MS_PER_DAY - span));
4064
3875
  }
4065
3876
  const edges = arr.map((r) => ({
4066
- sourcePage: str$4(r.source_page),
4067
- targetPage: str$4(r.target_page),
3877
+ sourcePage: rowString(r.source_page),
3878
+ targetPage: rowString(r.target_page),
4068
3879
  weight: num(r.weight),
4069
3880
  queryCount: num(r.query_count),
4070
3881
  exactCount: num(r.exact_count),
4071
3882
  fuzzyCount: num(r.query_count) - num(r.exact_count),
4072
- examples: parseJsonList$3(r.examplesJson).slice(0, 8).map((e) => ({
4073
- sourceQuery: str$4(e.sourceQuery),
4074
- targetQuery: str$4(e.targetQuery),
3883
+ examples: parseJsonRows(r.examplesJson).slice(0, 8).map((e) => ({
3884
+ sourceQuery: rowString(e.sourceQuery),
3885
+ targetQuery: rowString(e.targetQuery),
4075
3886
  absorbed: num(e.absorbed),
4076
- matchType: str$4(e.matchType)
3887
+ matchType: rowString(e.matchType)
4077
3888
  }))
4078
3889
  }));
4079
3890
  const nodeAgg = /* @__PURE__ */ new Map();
@@ -4112,12 +3923,6 @@ const queryMigrationAnalyzer = defineAnalyzer({
4112
3923
  };
4113
3924
  }
4114
3925
  });
4115
- function str$3(v) {
4116
- return v == null ? "" : String(v);
4117
- }
4118
- function bool$1(v) {
4119
- return v === true || v === 1 || v === "true";
4120
- }
4121
3926
  function calculateCV(values) {
4122
3927
  if (values.length === 0) return 0;
4123
3928
  const mean = values.reduce((a, b) => a + b, 0) / values.length;
@@ -4211,11 +4016,11 @@ const seasonalityAnalyzer = defineAnalyzer({
4211
4016
  reduceSql(rows) {
4212
4017
  const arr = Array.isArray(rows) ? rows : [];
4213
4018
  const breakdown = arr.map((r) => ({
4214
- month: str$3(r.month),
4019
+ month: rowString(r.month),
4215
4020
  value: num(r.value),
4216
4021
  vsAverage: num(r.vsAverage),
4217
- isPeak: bool$1(r.isPeak),
4218
- isTrough: bool$1(r.isTrough)
4022
+ isPeak: rowBoolean(r.isPeak),
4023
+ isTrough: rowBoolean(r.isTrough)
4219
4024
  }));
4220
4025
  const first = arr[0];
4221
4026
  const strength = first ? num(first.strength) : 0;
@@ -4247,20 +4052,6 @@ const seasonalityAnalyzer = defineAnalyzer({
4247
4052
  };
4248
4053
  }
4249
4054
  });
4250
- function str$2(v) {
4251
- return v == null ? "" : String(v);
4252
- }
4253
- function bool(v) {
4254
- return v === true || v === 1 || v === "true";
4255
- }
4256
- function parseJsonList$2(v) {
4257
- if (Array.isArray(v)) return v;
4258
- if (typeof v === "string" && v.length > 0) {
4259
- const parsed = JSON.parse(v);
4260
- return Array.isArray(parsed) ? parsed : [];
4261
- }
4262
- return [];
4263
- }
4264
4055
  const stlDecomposeAnalyzer = defineAnalyzer({
4265
4056
  id: "stl-decompose",
4266
4057
  buildSql(params) {
@@ -4413,21 +4204,21 @@ const stlDecomposeAnalyzer = defineAnalyzer({
4413
4204
  const arr = Array.isArray(rows) ? rows : [];
4414
4205
  const metric = params.metric === "clicks" ? "clicks" : "impressions";
4415
4206
  const results = arr.map((r) => ({
4416
- keyword: str$2(r.keyword),
4417
- page: str$2(r.page),
4207
+ keyword: rowString(r.keyword),
4208
+ page: rowString(r.page),
4418
4209
  totalImpressions: num(r.totalImpressions),
4419
4210
  days: num(r.days),
4420
4211
  seasonalStrength: num(r.seasonalStrength),
4421
4212
  trendStrength: num(r.trendStrength),
4422
4213
  residualAnomalies: num(r.residualAnomalies),
4423
4214
  trendSlope: num(r.trendSlope),
4424
- series: parseJsonList$2(r.seriesJson).map((s) => ({
4425
- date: str$2(s.date),
4215
+ series: parseJsonRows(r.seriesJson).map((s) => ({
4216
+ date: rowString(s.date),
4426
4217
  observed: num(s.observed),
4427
4218
  trend: s.trend == null ? null : num(s.trend),
4428
4219
  seasonal: s.seasonal == null ? null : num(s.seasonal),
4429
4220
  residual: s.residual == null ? null : num(s.residual),
4430
- anomaly: bool(s.anomaly)
4221
+ anomaly: rowBoolean(s.anomaly)
4431
4222
  }))
4432
4223
  }));
4433
4224
  return {
@@ -4510,17 +4301,6 @@ const strikingDistanceAnalyzer = defineAnalyzer({
4510
4301
  return { queries: queriesQueryState(periodOf(params), params.limit ?? DEFAULT_ROW_LIMIT$1) };
4511
4302
  }
4512
4303
  });
4513
- function str$1(v) {
4514
- return v == null ? "" : String(v);
4515
- }
4516
- function parseJsonList$1(v) {
4517
- if (Array.isArray(v)) return v;
4518
- if (typeof v === "string" && v.length > 0) {
4519
- const parsed = JSON.parse(v);
4520
- return Array.isArray(parsed) ? parsed : [];
4521
- }
4522
- return [];
4523
- }
4524
4304
  const survivalAnalyzer = defineAnalyzer({
4525
4305
  id: "survival",
4526
4306
  buildSql(params) {
@@ -4673,7 +4453,7 @@ const survivalAnalyzer = defineAnalyzer({
4673
4453
  const startDate = params.startDate ?? daysAgo(183);
4674
4454
  const windowDays = Math.round((new Date(endDate).getTime() - new Date(startDate).getTime()) / MS_PER_DAY) + 1;
4675
4455
  const results = arr.map((r) => {
4676
- const curve = parseJsonList$1(r.curveJson).map((p) => ({
4456
+ const curve = parseJsonRows(r.curveJson).map((p) => ({
4677
4457
  tenure: num(p.tenure),
4678
4458
  survival: num(p.survival),
4679
4459
  atRisk: num(p.atRisk),
@@ -4696,7 +4476,7 @@ const survivalAnalyzer = defineAnalyzer({
4696
4476
  const last = curve[curve.length - 1];
4697
4477
  if (medianTenure === 0 && last && last.survival > .5) medianTenure = last.tenure;
4698
4478
  return {
4699
- cohort: str$1(r.cohort),
4479
+ cohort: rowString(r.cohort),
4700
4480
  episodeCount: num(r.episodeCount),
4701
4481
  censoringRate: num(r.censoringRate),
4702
4482
  medianTenure,
@@ -4713,17 +4493,6 @@ const survivalAnalyzer = defineAnalyzer({
4713
4493
  };
4714
4494
  }
4715
4495
  });
4716
- function str(v) {
4717
- return v == null ? "" : String(v);
4718
- }
4719
- function parseJsonList(v) {
4720
- if (Array.isArray(v)) return v;
4721
- if (typeof v === "string" && v.length > 0) {
4722
- const parsed = JSON.parse(v);
4723
- return Array.isArray(parsed) ? parsed : [];
4724
- }
4725
- return [];
4726
- }
4727
4496
  const trendsAnalyzer = defineAnalyzer({
4728
4497
  id: "trends",
4729
4498
  buildSql(params) {
@@ -4834,20 +4603,20 @@ const trendsAnalyzer = defineAnalyzer({
4834
4603
  const startDate = params.startDate || toIsoDate(/* @__PURE__ */ new Date(Date.parse(endDate) - (weeks * 7 - 1) * MS_PER_DAY));
4835
4604
  const dim = params.dimension === "keywords" ? "keywords" : "pages";
4836
4605
  const results = arr.map((r) => {
4837
- const series = parseJsonList(r.seriesJson).map((s) => ({
4838
- week: str(s.week),
4606
+ const series = parseJsonRows(r.seriesJson).map((s) => ({
4607
+ week: rowString(s.week),
4839
4608
  clicks: num(s.clicks),
4840
4609
  impressions: num(s.impressions)
4841
4610
  }));
4842
4611
  return {
4843
- [dim === "keywords" ? "query" : "page"]: str(r.entity),
4612
+ [dim === "keywords" ? "query" : "page"]: rowString(r.entity),
4844
4613
  totalClicks: num(r.totalClicks),
4845
4614
  totalImpressions: num(r.totalImpressions),
4846
4615
  weeksWithData: num(r.weeksWithData),
4847
4616
  slope: num(r.slope),
4848
4617
  growthRatio: num(r.growthRatio),
4849
4618
  avgPosition: num(r.avgPosition),
4850
- trend: str(r.trend),
4619
+ trend: rowString(r.trend),
4851
4620
  series
4852
4621
  };
4853
4622
  });