@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),
@@ -2476,17 +2362,6 @@ const sortResults$1 = createMetricSorter("lostClicks", {
2476
2362
  declinePercent: "desc",
2477
2363
  currentClicks: "asc"
2478
2364
  });
2479
- function str$12(v) {
2480
- return v == null ? "" : String(v);
2481
- }
2482
- function parseJsonList$8(v) {
2483
- if (Array.isArray(v)) return v;
2484
- if (typeof v === "string" && v.length > 0) {
2485
- const parsed = JSON.parse(v);
2486
- return Array.isArray(parsed) ? parsed : [];
2487
- }
2488
- return [];
2489
- }
2490
2365
  function analyzeDecay(input, options = {}) {
2491
2366
  const { minPreviousClicks = 50, threshold = .2, sortBy = "lostClicks" } = options;
2492
2367
  const currentMap = buildPeriodMap(input.current, (r) => r.page, (r) => ({
@@ -2613,7 +2488,7 @@ const decayAnalyzer = defineAnalyzer({
2613
2488
  },
2614
2489
  reduceSql(rows, params) {
2615
2490
  const mapped = (Array.isArray(rows) ? rows : []).map((r) => ({
2616
- page: str$12(r.page),
2491
+ page: rowString(r.page),
2617
2492
  currentClicks: num(r.currentClicks),
2618
2493
  previousClicks: num(r.previousClicks),
2619
2494
  lostClicks: num(r.lostClicks),
@@ -2621,8 +2496,8 @@ const decayAnalyzer = defineAnalyzer({
2621
2496
  currentPosition: num(r.currentPosition),
2622
2497
  previousPosition: num(r.previousPosition),
2623
2498
  positionDrop: num(r.positionDrop),
2624
- series: parseJsonList$8(r.seriesJson).map((s) => ({
2625
- week: str$12(s.week),
2499
+ series: parseJsonRows(r.seriesJson).map((s) => ({
2500
+ week: rowString(s.week),
2626
2501
  clicks: num(s.clicks),
2627
2502
  impressions: num(s.impressions)
2628
2503
  }))
@@ -2660,9 +2535,6 @@ const decayAnalyzer = defineAnalyzer({
2660
2535
  };
2661
2536
  }
2662
2537
  });
2663
- function str$11(v) {
2664
- return v == null ? "" : String(v);
2665
- }
2666
2538
  const deviceGapAnalyzer = defineAnalyzer({
2667
2539
  id: "device-gap",
2668
2540
  buildSql(params) {
@@ -2713,8 +2585,8 @@ const deviceGapAnalyzer = defineAnalyzer({
2713
2585
  const arr = Array.isArray(rows) ? rows : [];
2714
2586
  const { startDate, endDate } = periodOf(params);
2715
2587
  const typed = arr.map((r) => ({
2716
- date: str$11(r.date),
2717
- device: str$11(r.device).toUpperCase(),
2588
+ date: rowString(r.date),
2589
+ device: rowString(r.device).toUpperCase(),
2718
2590
  clicks: num(r.clicks),
2719
2591
  impressions: num(r.impressions),
2720
2592
  ctr: num(r.ctr),
@@ -2792,17 +2664,6 @@ const deviceGapAnalyzer = defineAnalyzer({
2792
2664
  };
2793
2665
  }
2794
2666
  });
2795
- function str$10(v) {
2796
- return v == null ? "" : String(v);
2797
- }
2798
- function parseJsonList$7(v) {
2799
- if (Array.isArray(v)) return v;
2800
- if (typeof v === "string" && v.length > 0) {
2801
- const parsed = JSON.parse(v);
2802
- return Array.isArray(parsed) ? parsed : [];
2803
- }
2804
- return [];
2805
- }
2806
2667
  const INTENT_ATLAS_STOP_WORDS = [
2807
2668
  "the",
2808
2669
  "a",
@@ -2945,14 +2806,14 @@ const intentAtlasAnalyzer = defineAnalyzer({
2945
2806
  },
2946
2807
  reduceSql(rows) {
2947
2808
  const clusters = (Array.isArray(rows) ? rows : []).map((r) => ({
2948
- clusterKey: str$10(r.clusterKey),
2809
+ clusterKey: rowString(r.clusterKey),
2949
2810
  keywordCount: num(r.keywordCount),
2950
2811
  totalImpressions: num(r.totalImpressions),
2951
2812
  totalClicks: num(r.totalClicks),
2952
2813
  ctr: num(r.ctr),
2953
2814
  avgPosition: num(r.avgPosition),
2954
- keywords: parseJsonList$7(r.keywords).slice(0, 25).map((k) => ({
2955
- query: str$10(k.query),
2815
+ keywords: parseJsonRows(r.keywords).slice(0, 25).map((k) => ({
2816
+ query: rowString(k.query),
2956
2817
  impressions: num(k.impressions),
2957
2818
  clicks: num(k.clicks),
2958
2819
  position: num(k.position)
@@ -2970,17 +2831,6 @@ const intentAtlasAnalyzer = defineAnalyzer({
2970
2831
  };
2971
2832
  }
2972
2833
  });
2973
- function str$9(v) {
2974
- return v == null ? "" : String(v);
2975
- }
2976
- function parseJsonList$6(v) {
2977
- if (Array.isArray(v)) return v;
2978
- if (typeof v === "string" && v.length > 0) {
2979
- const parsed = JSON.parse(v);
2980
- return Array.isArray(parsed) ? parsed : [];
2981
- }
2982
- return [];
2983
- }
2984
2834
  const keywordBreadthAnalyzer = defineAnalyzer({
2985
2835
  id: "keyword-breadth",
2986
2836
  buildSql(params) {
@@ -3055,18 +2905,18 @@ const keywordBreadthAnalyzer = defineAnalyzer({
3055
2905
  const arr = Array.isArray(rows) ? rows : [];
3056
2906
  const { startDate, endDate } = periodOf(params);
3057
2907
  const row = arr[0] ?? {};
3058
- const distribution = parseJsonList$6(row.distribution_json).sort((a, b) => num(a.sortKey) - num(b.sortKey)).map((r) => ({
3059
- bucket: str$9(r.bucket),
2908
+ const distribution = parseJsonRows(row.distribution_json).sort((a, b) => num(a.sortKey) - num(b.sortKey)).map((r) => ({
2909
+ bucket: rowString(r.bucket),
3060
2910
  pageCount: num(r.pageCount)
3061
2911
  }));
3062
- const fragile = parseJsonList$6(row.fragile_json).map((r) => ({
3063
- url: str$9(r.url),
2912
+ const fragile = parseJsonRows(row.fragile_json).map((r) => ({
2913
+ url: rowString(r.url),
3064
2914
  keywordCount: num(r.keywordCount),
3065
2915
  clicks: num(r.clicks),
3066
2916
  impressions: num(r.impressions)
3067
2917
  }));
3068
- const authority = parseJsonList$6(row.authority_json).map((r) => ({
3069
- url: str$9(r.url),
2918
+ const authority = parseJsonRows(row.authority_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)
@@ -3089,23 +2939,12 @@ const keywordBreadthAnalyzer = defineAnalyzer({
3089
2939
  };
3090
2940
  }
3091
2941
  });
3092
- function str$8(v) {
3093
- return v == null ? "" : String(v);
3094
- }
3095
- function parseJsonList$5(v) {
3096
- if (Array.isArray(v)) return v;
3097
- if (typeof v === "string" && v.length > 0) {
3098
- const parsed = JSON.parse(v);
3099
- return Array.isArray(parsed) ? parsed : [];
3100
- }
3101
- return [];
3102
- }
3103
2942
  function downsampleLogRank(points) {
3104
2943
  const all = points.map((p) => ({
3105
2944
  rank: num(p.rank),
3106
2945
  impressions: num(p.impressions),
3107
2946
  clicks: num(p.clicks),
3108
- query: str$8(p.query)
2947
+ query: rowString(p.query)
3109
2948
  }));
3110
2949
  if (all.length <= 80) return all;
3111
2950
  const top = all.slice(0, 10);
@@ -3214,7 +3053,7 @@ const longTailAnalyzer = defineAnalyzer({
3214
3053
  },
3215
3054
  reduceSql(rows) {
3216
3055
  const results = (Array.isArray(rows) ? rows : []).map((r) => ({
3217
- page: str$8(r.page),
3056
+ page: rowString(r.page),
3218
3057
  queryCount: num(r.queryCount),
3219
3058
  totalImpressions: num(r.totalImpressions),
3220
3059
  totalClicks: num(r.totalClicks),
@@ -3223,8 +3062,8 @@ const longTailAnalyzer = defineAnalyzer({
3223
3062
  r2: num(r.r2),
3224
3063
  headImpressions: num(r.headImpressions),
3225
3064
  headShare: num(r.headShare),
3226
- fingerprint: str$8(r.fingerprint),
3227
- points: downsampleLogRank(parseJsonList$5(r.pointsJson))
3065
+ fingerprint: rowString(r.fingerprint),
3066
+ points: downsampleLogRank(parseJsonRows(r.pointsJson))
3228
3067
  }));
3229
3068
  const counts = {
3230
3069
  "flat-tail": 0,
@@ -3246,17 +3085,6 @@ function percentDifference(current, previous) {
3246
3085
  if (previous === 0) return current > 0 ? 100 : 0;
3247
3086
  return (current - previous) / previous * 100;
3248
3087
  }
3249
- function str$7(v) {
3250
- return v == null ? "" : String(v);
3251
- }
3252
- function parseJsonList$4(v) {
3253
- if (Array.isArray(v)) return v;
3254
- if (typeof v === "string" && v.length > 0) {
3255
- const parsed = JSON.parse(v);
3256
- return Array.isArray(parsed) ? parsed : [];
3257
- }
3258
- return [];
3259
- }
3260
3088
  function withCanonicalFields(r) {
3261
3089
  return {
3262
3090
  ...r,
@@ -3482,8 +3310,8 @@ const moversAnalyzer = defineAnalyzer({
3482
3310
  const baselineImpressions = Math.round(num(r.baselineImpressions));
3483
3311
  const baselinePosition = num(r.baselinePosition);
3484
3312
  return {
3485
- keyword: str$7(r.keyword),
3486
- page: r.page == null ? null : str$7(r.page),
3313
+ keyword: rowString(r.keyword),
3314
+ page: r.page == null ? null : rowString(r.page),
3487
3315
  recentClicks,
3488
3316
  recentImpressions,
3489
3317
  recentPosition,
@@ -3494,9 +3322,9 @@ const moversAnalyzer = defineAnalyzer({
3494
3322
  clicksChangePercent: num(r.clicksChangePercent),
3495
3323
  impressionsChangePercent: num(r.impressionsChangePercent),
3496
3324
  positionChange: num(r.positionChange),
3497
- direction: str$7(r.direction),
3498
- series: parseJsonList$4(r.seriesJson).map((s) => ({
3499
- week: str$7(s.week),
3325
+ direction: rowString(r.direction),
3326
+ series: parseJsonRows(r.seriesJson).map((s) => ({
3327
+ week: rowString(s.week),
3500
3328
  clicks: num(s.clicks),
3501
3329
  impressions: num(s.impressions)
3502
3330
  }))
@@ -3732,9 +3560,6 @@ const opportunityAnalyzer = defineAnalyzer({
3732
3560
  };
3733
3561
  }
3734
3562
  });
3735
- function str$6(v) {
3736
- return v == null ? "" : String(v);
3737
- }
3738
3563
  const positionDistributionAnalyzer = defineAnalyzer({
3739
3564
  id: "position-distribution",
3740
3565
  buildSql(params) {
@@ -3771,7 +3596,7 @@ const positionDistributionAnalyzer = defineAnalyzer({
3771
3596
  const { startDate, endDate } = periodOf(params);
3772
3597
  return {
3773
3598
  results: arr.map((r) => ({
3774
- date: str$6(r.date),
3599
+ date: rowString(r.date),
3775
3600
  pos_1_3: num(r.pos_1_3),
3776
3601
  pos_4_10: num(r.pos_4_10),
3777
3602
  pos_11_20: num(r.pos_11_20),
@@ -3786,9 +3611,6 @@ const positionDistributionAnalyzer = defineAnalyzer({
3786
3611
  };
3787
3612
  }
3788
3613
  });
3789
- function str$5(v) {
3790
- return v == null ? "" : String(v);
3791
- }
3792
3614
  const positionVolatilityAnalyzer = defineAnalyzer({
3793
3615
  id: "position-volatility",
3794
3616
  buildSql(params) {
@@ -3889,8 +3711,8 @@ const positionVolatilityAnalyzer = defineAnalyzer({
3889
3711
  const byPage = /* @__PURE__ */ new Map();
3890
3712
  const allDates = /* @__PURE__ */ new Set();
3891
3713
  for (const r of arr) {
3892
- const page = str$5(r.page);
3893
- const date = str$5(r.date);
3714
+ const page = rowString(r.page);
3715
+ const date = rowString(r.date);
3894
3716
  allDates.add(date);
3895
3717
  const entry = byPage.get(page) ?? {
3896
3718
  page,
@@ -3925,17 +3747,6 @@ const positionVolatilityAnalyzer = defineAnalyzer({
3925
3747
  };
3926
3748
  }
3927
3749
  });
3928
- function str$4(v) {
3929
- return v == null ? "" : String(v);
3930
- }
3931
- function parseJsonList$3(v) {
3932
- if (Array.isArray(v)) return v;
3933
- if (typeof v === "string" && v.length > 0) {
3934
- const parsed = JSON.parse(v);
3935
- return Array.isArray(parsed) ? parsed : [];
3936
- }
3937
- return [];
3938
- }
3939
3750
  const queryMigrationAnalyzer = defineAnalyzer({
3940
3751
  id: "query-migration",
3941
3752
  buildSql(params) {
@@ -4057,17 +3868,17 @@ const queryMigrationAnalyzer = defineAnalyzer({
4057
3868
  prevStart = toIsoDate(new Date(curStartMs - MS_PER_DAY - span));
4058
3869
  }
4059
3870
  const edges = arr.map((r) => ({
4060
- sourcePage: str$4(r.source_page),
4061
- targetPage: str$4(r.target_page),
3871
+ sourcePage: rowString(r.source_page),
3872
+ targetPage: rowString(r.target_page),
4062
3873
  weight: num(r.weight),
4063
3874
  queryCount: num(r.query_count),
4064
3875
  exactCount: num(r.exact_count),
4065
3876
  fuzzyCount: num(r.query_count) - num(r.exact_count),
4066
- examples: parseJsonList$3(r.examplesJson).slice(0, 8).map((e) => ({
4067
- sourceQuery: str$4(e.sourceQuery),
4068
- targetQuery: str$4(e.targetQuery),
3877
+ examples: parseJsonRows(r.examplesJson).slice(0, 8).map((e) => ({
3878
+ sourceQuery: rowString(e.sourceQuery),
3879
+ targetQuery: rowString(e.targetQuery),
4069
3880
  absorbed: num(e.absorbed),
4070
- matchType: str$4(e.matchType)
3881
+ matchType: rowString(e.matchType)
4071
3882
  }))
4072
3883
  }));
4073
3884
  const nodeAgg = /* @__PURE__ */ new Map();
@@ -4106,12 +3917,6 @@ const queryMigrationAnalyzer = defineAnalyzer({
4106
3917
  };
4107
3918
  }
4108
3919
  });
4109
- function str$3(v) {
4110
- return v == null ? "" : String(v);
4111
- }
4112
- function bool$1(v) {
4113
- return v === true || v === 1 || v === "true";
4114
- }
4115
3920
  function calculateCV(values) {
4116
3921
  if (values.length === 0) return 0;
4117
3922
  const mean = values.reduce((a, b) => a + b, 0) / values.length;
@@ -4205,11 +4010,11 @@ const seasonalityAnalyzer = defineAnalyzer({
4205
4010
  reduceSql(rows) {
4206
4011
  const arr = Array.isArray(rows) ? rows : [];
4207
4012
  const breakdown = arr.map((r) => ({
4208
- month: str$3(r.month),
4013
+ month: rowString(r.month),
4209
4014
  value: num(r.value),
4210
4015
  vsAverage: num(r.vsAverage),
4211
- isPeak: bool$1(r.isPeak),
4212
- isTrough: bool$1(r.isTrough)
4016
+ isPeak: rowBoolean(r.isPeak),
4017
+ isTrough: rowBoolean(r.isTrough)
4213
4018
  }));
4214
4019
  const first = arr[0];
4215
4020
  const strength = first ? num(first.strength) : 0;
@@ -4241,20 +4046,6 @@ const seasonalityAnalyzer = defineAnalyzer({
4241
4046
  };
4242
4047
  }
4243
4048
  });
4244
- function str$2(v) {
4245
- return v == null ? "" : String(v);
4246
- }
4247
- function bool(v) {
4248
- return v === true || v === 1 || v === "true";
4249
- }
4250
- function parseJsonList$2(v) {
4251
- if (Array.isArray(v)) return v;
4252
- if (typeof v === "string" && v.length > 0) {
4253
- const parsed = JSON.parse(v);
4254
- return Array.isArray(parsed) ? parsed : [];
4255
- }
4256
- return [];
4257
- }
4258
4049
  const stlDecomposeAnalyzer = defineAnalyzer({
4259
4050
  id: "stl-decompose",
4260
4051
  buildSql(params) {
@@ -4407,21 +4198,21 @@ const stlDecomposeAnalyzer = defineAnalyzer({
4407
4198
  const arr = Array.isArray(rows) ? rows : [];
4408
4199
  const metric = params.metric === "clicks" ? "clicks" : "impressions";
4409
4200
  const results = arr.map((r) => ({
4410
- keyword: str$2(r.keyword),
4411
- page: str$2(r.page),
4201
+ keyword: rowString(r.keyword),
4202
+ page: rowString(r.page),
4412
4203
  totalImpressions: num(r.totalImpressions),
4413
4204
  days: num(r.days),
4414
4205
  seasonalStrength: num(r.seasonalStrength),
4415
4206
  trendStrength: num(r.trendStrength),
4416
4207
  residualAnomalies: num(r.residualAnomalies),
4417
4208
  trendSlope: num(r.trendSlope),
4418
- series: parseJsonList$2(r.seriesJson).map((s) => ({
4419
- date: str$2(s.date),
4209
+ series: parseJsonRows(r.seriesJson).map((s) => ({
4210
+ date: rowString(s.date),
4420
4211
  observed: num(s.observed),
4421
4212
  trend: s.trend == null ? null : num(s.trend),
4422
4213
  seasonal: s.seasonal == null ? null : num(s.seasonal),
4423
4214
  residual: s.residual == null ? null : num(s.residual),
4424
- anomaly: bool(s.anomaly)
4215
+ anomaly: rowBoolean(s.anomaly)
4425
4216
  }))
4426
4217
  }));
4427
4218
  return {
@@ -4504,17 +4295,6 @@ const strikingDistanceAnalyzer = defineAnalyzer({
4504
4295
  return { queries: queriesQueryState(periodOf(params), params.limit ?? DEFAULT_ROW_LIMIT$1) };
4505
4296
  }
4506
4297
  });
4507
- function str$1(v) {
4508
- return v == null ? "" : String(v);
4509
- }
4510
- function parseJsonList$1(v) {
4511
- if (Array.isArray(v)) return v;
4512
- if (typeof v === "string" && v.length > 0) {
4513
- const parsed = JSON.parse(v);
4514
- return Array.isArray(parsed) ? parsed : [];
4515
- }
4516
- return [];
4517
- }
4518
4298
  const survivalAnalyzer = defineAnalyzer({
4519
4299
  id: "survival",
4520
4300
  buildSql(params) {
@@ -4667,7 +4447,7 @@ const survivalAnalyzer = defineAnalyzer({
4667
4447
  const startDate = params.startDate ?? daysAgo(183);
4668
4448
  const windowDays = Math.round((new Date(endDate).getTime() - new Date(startDate).getTime()) / MS_PER_DAY) + 1;
4669
4449
  const results = arr.map((r) => {
4670
- const curve = parseJsonList$1(r.curveJson).map((p) => ({
4450
+ const curve = parseJsonRows(r.curveJson).map((p) => ({
4671
4451
  tenure: num(p.tenure),
4672
4452
  survival: num(p.survival),
4673
4453
  atRisk: num(p.atRisk),
@@ -4690,7 +4470,7 @@ const survivalAnalyzer = defineAnalyzer({
4690
4470
  const last = curve[curve.length - 1];
4691
4471
  if (medianTenure === 0 && last && last.survival > .5) medianTenure = last.tenure;
4692
4472
  return {
4693
- cohort: str$1(r.cohort),
4473
+ cohort: rowString(r.cohort),
4694
4474
  episodeCount: num(r.episodeCount),
4695
4475
  censoringRate: num(r.censoringRate),
4696
4476
  medianTenure,
@@ -4707,17 +4487,6 @@ const survivalAnalyzer = defineAnalyzer({
4707
4487
  };
4708
4488
  }
4709
4489
  });
4710
- function str(v) {
4711
- return v == null ? "" : String(v);
4712
- }
4713
- function parseJsonList(v) {
4714
- if (Array.isArray(v)) return v;
4715
- if (typeof v === "string" && v.length > 0) {
4716
- const parsed = JSON.parse(v);
4717
- return Array.isArray(parsed) ? parsed : [];
4718
- }
4719
- return [];
4720
- }
4721
4490
  const trendsAnalyzer = defineAnalyzer({
4722
4491
  id: "trends",
4723
4492
  buildSql(params) {
@@ -4828,20 +4597,20 @@ const trendsAnalyzer = defineAnalyzer({
4828
4597
  const startDate = params.startDate || toIsoDate(/* @__PURE__ */ new Date(Date.parse(endDate) - (weeks * 7 - 1) * MS_PER_DAY));
4829
4598
  const dim = params.dimension === "keywords" ? "keywords" : "pages";
4830
4599
  const results = arr.map((r) => {
4831
- const series = parseJsonList(r.seriesJson).map((s) => ({
4832
- week: str(s.week),
4600
+ const series = parseJsonRows(r.seriesJson).map((s) => ({
4601
+ week: rowString(s.week),
4833
4602
  clicks: num(s.clicks),
4834
4603
  impressions: num(s.impressions)
4835
4604
  }));
4836
4605
  return {
4837
- [dim === "keywords" ? "query" : "page"]: str(r.entity),
4606
+ [dim === "keywords" ? "query" : "page"]: rowString(r.entity),
4838
4607
  totalClicks: num(r.totalClicks),
4839
4608
  totalImpressions: num(r.totalImpressions),
4840
4609
  weeksWithData: num(r.weeksWithData),
4841
4610
  slope: num(r.slope),
4842
4611
  growthRatio: num(r.growthRatio),
4843
4612
  avgPosition: num(r.avgPosition),
4844
- trend: str(r.trend),
4613
+ trend: rowString(r.trend),
4845
4614
  series
4846
4615
  };
4847
4616
  });