@gscdump/analysis 0.38.2 → 0.40.1

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.
package/dist/index.mjs CHANGED
@@ -1,14 +1,14 @@
1
1
  import { AnalyzerCapabilityError, createAnalyzerRegistry, createAnalyzerRegistry as createAnalyzerRegistry$1, defineAnalyzer, defineAnalyzer as defineAnalyzer$1, requireAdapter, runAnalyzerFromSource, runAnalyzerFromSource as runAnalyzerFromSource$1 } from "@gscdump/engine/analyzer";
2
- import { comparisonOf, comparisonOf as comparisonOf$1, defaultEndDate, padTimeseries, padTimeseries as padTimeseries$1, periodOf, periodOf as periodOf$1, resolveWindow, windowToComparisonPeriod, windowToPeriod } from "@gscdump/engine/period";
2
+ import { comparisonOf, comparisonOf as comparisonOf$1, defaultEndDate, padTimeseries, padTimeseries as padTimeseries$1, periodOf, periodOf as periodOf$1, resolveWindow } from "@gscdump/engine/period";
3
3
  import { enumeratePartitions } from "@gscdump/engine/planner";
4
4
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
5
5
  import { num, num as num$1 } from "@gscdump/engine/analysis-types";
6
6
  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, daysAgoUtc, toIsoDate } from "gscdump/dates";
9
- import { buildExtrasQueries, buildTotalsSql, mergeExtras, pgResolverAdapter, resolveComparisonSQL, resolveToSQL, resolveToSQLOptimized } from "@gscdump/engine/resolver";
9
+ import { buildExtrasQueries, buildTotalsSql, mergeExtras, pgResolverAdapter, resolveComparisonSQL, resolveToSQLOptimized } from "@gscdump/engine/resolver";
10
10
  import pluralize from "pluralize";
11
- import { ENGINE_QUERY_CAPABILITIES, createAttachedTableSource, createEngineQuerySource, queryComparisonRows, queryRows, rewriteForTableSource, runAnalyzerWithEngine, typedQuery } from "@gscdump/engine/source";
11
+ import { ENGINE_QUERY_CAPABILITIES, createAttachedTableSource, createEngineQuerySource, queryRows, rewriteForTableSource, runAnalyzerWithEngine } from "@gscdump/engine/source";
12
12
  import { computeInputHash, createReportRegistry, defineReport } from "@gscdump/engine/report";
13
13
  import { canProxyToGsc } from "@gscdump/engine-gsc-api";
14
14
  import { isStateResolvable } from "gscdump/query/plan";
@@ -754,8 +754,7 @@ function analyzeBrandSegmentation(keywords, options) {
754
754
  const impressions = num$1(row.impressions);
755
755
  if (impressions < minImpressions) continue;
756
756
  const clicks = num$1(row.clicks);
757
- const query = row.query.toLowerCase();
758
- if (lowerBrandTerms.some((term) => query.includes(term))) {
757
+ if ((row.variants?.length ? row.variants : [row.query ?? row.keyword ?? ""]).some((query) => lowerBrandTerms.some((term) => query.toLowerCase().includes(term)))) {
759
758
  brand.push(row);
760
759
  brandClicks += clicks;
761
760
  brandImpressions += impressions;
@@ -897,7 +896,7 @@ function createMetricSorter(defaultMetric, orderByMetric) {
897
896
  return [...items].sort((a, b) => (a[sortBy] - b[sortBy]) * mult);
898
897
  };
899
898
  }
900
- const sortRowResults$1 = createSorter((item, metric) => {
899
+ const sortRowResults = createSorter((item, metric) => {
901
900
  switch (metric) {
902
901
  case "clicks": return item.totalClicks;
903
902
  case "impressions": return item.totalImpressions;
@@ -944,7 +943,7 @@ function analyzeCannibalization(rows, options = {}) {
944
943
  positionSpread
945
944
  });
946
945
  }
947
- return sortRowResults$1(results, sortBy, sortOrder);
946
+ return sortRowResults(results, sortBy, sortOrder);
948
947
  }
949
948
  const cannibalizationAnalyzer = defineAnalyzer$1({
950
949
  id: "cannibalization",
@@ -2482,14 +2481,9 @@ function shapeDataQueryRows(rows, params, extras) {
2482
2481
  function buildDataDetailPlan(params, options) {
2483
2482
  const state = requireBuilderState(params.q, "data-detail");
2484
2483
  if (!state.dimensions.includes("date")) throw new Error("data-detail: `date` dimension is required");
2485
- const main = resolveToSQL(state, options);
2486
- const totals = buildTotalsSql(state, options);
2484
+ const main = resolveToSQLOptimized(state, options);
2487
2485
  const prev = optionalBuilderState(params.qc, "data-detail", "qc");
2488
- const extraQueries = [{
2489
- name: "totals",
2490
- sql: totals.sql,
2491
- params: totals.params
2492
- }];
2486
+ const extraQueries = [];
2493
2487
  if (prev) {
2494
2488
  const previousTotals = buildTotalsSql(prev, options);
2495
2489
  extraQueries.push({
@@ -2507,18 +2501,22 @@ function buildDataDetailPlan(params, options) {
2507
2501
  }
2508
2502
  function shapeDataDetailRows(rows, params, extras) {
2509
2503
  const { startDate: rangeStart, endDate: rangeEnd } = extractDateRange(requireBuilderState(params.q, "data-detail").filter);
2510
- const coerced = rows.map(coerceNumericCols);
2504
+ const first = rows[0];
2505
+ const totals = {
2506
+ clicks: Number(first?.totalClicks ?? 0),
2507
+ impressions: Number(first?.totalImpressions ?? 0),
2508
+ ctr: Number(first?.totalCtr ?? 0),
2509
+ position: Number(first?.totalPosition ?? 0)
2510
+ };
2511
+ const coerced = rows.map((raw) => {
2512
+ const { totalCount: _tc, totalClicks: _tclk, totalImpressions: _timp, totalCtr: _tctr, totalPosition: _tpos, sum_position: _sp, ...rest } = raw;
2513
+ return coerceNumericCols(rest);
2514
+ });
2511
2515
  const daily = rangeStart && rangeEnd ? padTimeseries$1(coerced, {
2512
2516
  startDate: rangeStart,
2513
2517
  endDate: rangeEnd
2514
2518
  }) : coerced;
2515
- const totalsRow = extras?.totals?.[0] ?? {};
2516
- const meta = { totals: {
2517
- clicks: Number(totalsRow.clicks ?? 0),
2518
- impressions: Number(totalsRow.impressions ?? 0),
2519
- ctr: Number(totalsRow.ctr ?? 0),
2520
- position: Number(totalsRow.position ?? 0)
2521
- } };
2519
+ const meta = { totals };
2522
2520
  if (extras?.prevTotals) {
2523
2521
  const previousTotalsRow = extras.prevTotals[0] ?? {};
2524
2522
  meta.previousTotals = {
@@ -2647,10 +2645,12 @@ const SEPARATOR_RE$1 = /[-_/.@#:+]+/g;
2647
2645
  const WHITESPACE_RE$1 = /\s+/g;
2648
2646
  const DIACRITICS_RE$1 = /\p{Diacritic}/gu;
2649
2647
  function tokenize(query) {
2650
- const text = query.normalize("NFKD").replace(DIACRITICS_RE$1, "").toLowerCase().replace(SEPARATOR_RE$1, " ").replace(WHITESPACE_RE$1, " ").trim();
2648
+ let index = 0;
2649
+ while (index < query.length && query.charCodeAt(index) <= 127) index++;
2650
+ const text = (index === query.length ? query : query.normalize("NFKD").replace(DIACRITICS_RE$1, "")).toLowerCase().replace(SEPARATOR_RE$1, " ").replace(WHITESPACE_RE$1, " ").trim();
2651
2651
  return {
2652
2652
  text,
2653
- tokens: text.split(" ").filter(Boolean)
2653
+ tokens: text.length === 0 ? [] : text.split(" ")
2654
2654
  };
2655
2655
  }
2656
2656
  function classifyQueryIntent(query) {
@@ -2816,15 +2816,27 @@ const NO_STRIP_S = /* @__PURE__ */ new Set([
2816
2816
  "nervous",
2817
2817
  "conscious"
2818
2818
  ]);
2819
+ const DEPLURALIZED_CACHE_MAX = 4096;
2820
+ const depluralizedCache = /* @__PURE__ */ new Map();
2819
2821
  function depluralize(token) {
2820
2822
  if (token.length <= 3 || NO_STRIP_S.has(token) || token.endsWith("sis")) return token;
2821
- return pluralize.singular(token);
2823
+ const last = token.charCodeAt(token.length - 1);
2824
+ if (last < 97 || last > 122) return token;
2825
+ const cached = depluralizedCache.get(token);
2826
+ if (cached !== void 0) return cached;
2827
+ const singular = pluralize.singular(token);
2828
+ if (depluralizedCache.size >= DEPLURALIZED_CACHE_MAX) depluralizedCache.clear();
2829
+ depluralizedCache.set(token, singular);
2830
+ return singular;
2822
2831
  }
2823
2832
  const SEPARATOR_RE = /[-_/.@#:+]+/g;
2824
2833
  const WHITESPACE_RE = /\s+/g;
2825
2834
  const DIACRITICS_RE = /\p{Diacritic}/gu;
2826
2835
  const NORMALIZER_VERSION = 2;
2827
2836
  function foldUnicode(s) {
2837
+ let index = 0;
2838
+ while (index < s.length && s.charCodeAt(index) <= 127) index++;
2839
+ if (index === s.length) return s;
2828
2840
  return s.normalize("NFKD").replace(DIACRITICS_RE, "");
2829
2841
  }
2830
2842
  const DIRECTIONAL_CONNECTORS = /* @__PURE__ */ new Set([
@@ -2860,9 +2872,15 @@ function isOrderSensitive(tokens) {
2860
2872
  return false;
2861
2873
  }
2862
2874
  function normalizeQuery(query) {
2863
- const cleaned = foldUnicode(query).toLowerCase().replace(SEPARATOR_RE, " ").replace(WHITESPACE_RE, " ").trim().split(" ").filter(Boolean);
2864
- const mapped = cleaned.map((token) => SYNONYMS[token] ?? token).filter(Boolean);
2865
- const tokens = (mapped.length > 0 ? mapped : cleaned).map(depluralize);
2875
+ const normalized = foldUnicode(query).toLowerCase().replace(SEPARATOR_RE, " ").replace(WHITESPACE_RE, " ").trim();
2876
+ if (normalized.length === 0) return "";
2877
+ const cleaned = normalized.split(" ");
2878
+ const mapped = [];
2879
+ for (const token of cleaned) {
2880
+ const synonym = SYNONYMS[token] ?? token;
2881
+ if (synonym) mapped.push(depluralize(synonym));
2882
+ }
2883
+ const tokens = mapped.length > 0 ? mapped : cleaned.map(depluralize);
2866
2884
  return (isOrderSensitive(tokens) ? tokens : tokens.sort()).join(" ");
2867
2885
  }
2868
2886
  const dataDetailAnalyzer = defineAnalyzer$1({
@@ -2967,7 +2985,69 @@ function paginateInMemory(rows, input) {
2967
2985
  const o = clampOffset(input.offset);
2968
2986
  return rows.slice(o, o + l);
2969
2987
  }
2970
- const sortResults$1 = createMetricSorter("lostClicks", {
2988
+ function paginateSortedInMemory(rows, input, compare) {
2989
+ const limit = clampLimit(input.limit, rows.length);
2990
+ const offset = clampOffset(input.offset);
2991
+ const selectedCount = Math.min(rows.length, offset + limit);
2992
+ if (limit === 0 || offset >= rows.length || selectedCount === 0) return [];
2993
+ const fullSort = () => [...rows].sort(compare).slice(offset, offset + limit);
2994
+ if (selectedCount >= rows.length / 2) return fullSort();
2995
+ let invalidComparison = false;
2996
+ const compareStable = (left, right) => {
2997
+ const order = compare(left.value, right.value);
2998
+ if (Number.isNaN(order)) {
2999
+ invalidComparison = true;
3000
+ return left.index - right.index;
3001
+ }
3002
+ return order || left.index - right.index;
3003
+ };
3004
+ const isWorse = (left, right) => compareStable(left, right) > 0;
3005
+ const heap = [];
3006
+ const siftUp = (start) => {
3007
+ let index = start;
3008
+ while (index > 0) {
3009
+ const parent = index - 1 >>> 1;
3010
+ if (!isWorse(heap[index], heap[parent])) break;
3011
+ const swap = heap[parent];
3012
+ heap[parent] = heap[index];
3013
+ heap[index] = swap;
3014
+ index = parent;
3015
+ }
3016
+ };
3017
+ const siftDown = () => {
3018
+ let index = 0;
3019
+ for (;;) {
3020
+ const left = index * 2 + 1;
3021
+ if (left >= heap.length) return;
3022
+ const right = left + 1;
3023
+ let worse = left;
3024
+ if (right < heap.length && isWorse(heap[right], heap[left])) worse = right;
3025
+ if (!isWorse(heap[worse], heap[index])) return;
3026
+ const swap = heap[index];
3027
+ heap[index] = heap[worse];
3028
+ heap[worse] = swap;
3029
+ index = worse;
3030
+ }
3031
+ };
3032
+ for (let index = 0; index < rows.length; index++) {
3033
+ const candidate = {
3034
+ value: rows[index],
3035
+ index
3036
+ };
3037
+ if (heap.length < selectedCount) {
3038
+ heap.push(candidate);
3039
+ siftUp(heap.length - 1);
3040
+ } else if (compareStable(candidate, heap[0]) < 0) {
3041
+ heap[0] = candidate;
3042
+ siftDown();
3043
+ }
3044
+ }
3045
+ if (invalidComparison) return fullSort();
3046
+ heap.sort(compareStable);
3047
+ if (invalidComparison) return fullSort();
3048
+ return heap.slice(offset, offset + limit).map((entry) => entry.value);
3049
+ }
3050
+ const sortResults = createMetricSorter("lostClicks", {
2971
3051
  lostClicks: "desc",
2972
3052
  declinePercent: "desc",
2973
3053
  currentClicks: "asc"
@@ -3000,7 +3080,7 @@ function analyzeDecay(input, options = {}) {
3000
3080
  positionDrop: currentPosition != null ? currentPosition - prev.position : null
3001
3081
  });
3002
3082
  }
3003
- return sortResults$1(results, sortBy);
3083
+ return sortResults(results, sortBy);
3004
3084
  }
3005
3085
  const decayAnalyzer = defineAnalyzer$1({
3006
3086
  id: "decay",
@@ -3995,12 +4075,52 @@ function calculateCtrGapScore(actualCtr, position) {
3995
4075
  const gap = expectedCtr - actualCtr;
3996
4076
  return Math.min(gap / expectedCtr, 1);
3997
4077
  }
3998
- const sortResults = createMetricSorter("opportunityScore", {
4078
+ const SORT_DIR = {
3999
4079
  opportunityScore: "desc",
4000
4080
  potentialClicks: "desc",
4001
4081
  impressions: "desc",
4002
4082
  position: "asc"
4003
- });
4083
+ };
4084
+ function analyzeOpportunity(keywords, options = {}) {
4085
+ const minImpressions = options.minImpressions ?? 100;
4086
+ const positionWeight = options.weights?.position ?? 1;
4087
+ const impressionsWeight = options.weights?.impressions ?? 1;
4088
+ const ctrGapWeight = options.weights?.ctrGap ?? 1;
4089
+ const totalWeight = positionWeight + impressionsWeight + ctrGapWeight;
4090
+ const sortBy = options.sortBy ?? "opportunityScore";
4091
+ const results = [];
4092
+ for (const row of keywords) {
4093
+ const impressions = num$1(row.impressions);
4094
+ if (impressions < minImpressions) continue;
4095
+ const position = num$1(row.position);
4096
+ const ctr = num$1(row.ctr);
4097
+ const clicks = num$1(row.clicks);
4098
+ const positionScore = calculatePositionScore(position);
4099
+ const impressionScore = calculateImpressionScore(impressions);
4100
+ const ctrGapScore = calculateCtrGapScore(ctr, position);
4101
+ const weightedProduct = positionScore ** positionWeight * impressionScore ** impressionsWeight * ctrGapScore ** ctrGapWeight;
4102
+ const opportunityScore = Math.round(weightedProduct ** (1 / totalWeight) * 100);
4103
+ const potentialClicks = Math.round(impressions * getExpectedCtr(Math.min(3, position)));
4104
+ results.push({
4105
+ keyword: row.query,
4106
+ page: row.page ?? null,
4107
+ clicks,
4108
+ impressions,
4109
+ ctr,
4110
+ position,
4111
+ opportunityScore,
4112
+ potentialClicks,
4113
+ factors: {
4114
+ positionScore,
4115
+ impressionScore,
4116
+ ctrGapScore
4117
+ }
4118
+ });
4119
+ }
4120
+ const direction = SORT_DIR[sortBy];
4121
+ results.sort((left, right) => direction === "asc" ? left[sortBy] - right[sortBy] : right[sortBy] - left[sortBy]);
4122
+ return options.limit ? results.slice(0, options.limit) : results;
4123
+ }
4004
4124
  const opportunityAnalyzer = defineAnalyzer$1({
4005
4125
  id: "opportunity",
4006
4126
  buildSql(params) {
@@ -4114,51 +4234,15 @@ const opportunityAnalyzer = defineAnalyzer$1({
4114
4234
  return { queries: queriesQueryState(periodOf$1(params), params.limit) };
4115
4235
  },
4116
4236
  reduceRows(rows, params) {
4117
- const keywords = (Array.isArray(rows) ? rows : []) ?? [];
4118
- const minImpressions = params.minImpressions ?? 100;
4119
- const positionWeight = 1;
4120
- const impressionsWeight = 1;
4121
- const ctrGapWeight = 1;
4122
- const sortBy = "opportunityScore";
4123
- const results = [];
4124
- for (const row of keywords) {
4125
- const impressions = num$1(row.impressions);
4126
- const position = num$1(row.position);
4127
- const ctr = num$1(row.ctr);
4128
- const clicks = num$1(row.clicks);
4129
- if (impressions < minImpressions) continue;
4130
- const positionScore = calculatePositionScore(position);
4131
- const impressionScore = calculateImpressionScore(impressions);
4132
- const ctrGapScore = calculateCtrGapScore(ctr, position);
4133
- const geometricMean = (positionScore ** positionWeight * impressionScore ** impressionsWeight * ctrGapScore ** ctrGapWeight) ** (1 / 3);
4134
- const opportunityScore = Math.round(geometricMean * 100);
4135
- const targetCtr = getExpectedCtr(Math.min(3, position));
4136
- const potentialClicks = Math.round(impressions * targetCtr);
4137
- results.push({
4138
- keyword: row.query,
4139
- page: row.page ?? null,
4140
- clicks,
4141
- impressions,
4142
- ctr,
4143
- position,
4144
- opportunityScore,
4145
- potentialClicks,
4146
- factors: {
4147
- positionScore,
4148
- impressionScore,
4149
- ctrGapScore
4150
- }
4151
- });
4152
- }
4153
- const sorted = sortResults(results, sortBy);
4154
- const paged = paginateInMemory(sorted, {
4237
+ const results = analyzeOpportunity((Array.isArray(rows) ? rows : []) ?? [], { minImpressions: params.minImpressions });
4238
+ const paged = paginateSortedInMemory(results, {
4155
4239
  limit: params.limit,
4156
4240
  offset: params.offset
4157
- });
4241
+ }, (left, right) => right.opportunityScore - left.opportunityScore);
4158
4242
  return {
4159
4243
  results: paged,
4160
4244
  meta: {
4161
- total: sorted.length,
4245
+ total: results.length,
4162
4246
  returned: paged.length
4163
4247
  }
4164
4248
  };
@@ -4830,6 +4914,12 @@ const stlDecomposeAnalyzer = defineAnalyzer$1({
4830
4914
  }
4831
4915
  });
4832
4916
  const DEFAULT_ROW_LIMIT$1 = 25e3;
4917
+ function paginateStrikingDistance(results, options) {
4918
+ return paginateSortedInMemory(results, {
4919
+ limit: options.limit ?? 1e3,
4920
+ offset: options.offset
4921
+ }, (left, right) => right.potentialClicks - left.potentialClicks);
4922
+ }
4833
4923
  function filterStrikingDistance(rows, options = {}) {
4834
4924
  const minPosition = options.minPosition ?? 4;
4835
4925
  const maxPosition = options.maxPosition ?? 20;
@@ -4856,15 +4946,14 @@ function filterStrikingDistance(rows, options = {}) {
4856
4946
  }
4857
4947
  return results;
4858
4948
  }
4949
+ function analyzeStrikingDistance(rows, options = {}) {
4950
+ return paginateStrikingDistance(filterStrikingDistance(rows, options), options);
4951
+ }
4859
4952
  const strikingDistanceAnalyzer = defineAnalyzer$1({
4860
4953
  id: "striking-distance",
4861
4954
  reduce(rows, params) {
4862
4955
  const results = filterStrikingDistance(Array.isArray(rows) ? rows : [], params);
4863
- results.sort((a, b) => b.potentialClicks - a.potentialClicks);
4864
- const paged = paginateInMemory(results, {
4865
- limit: params.limit ?? 1e3,
4866
- offset: params.offset
4867
- });
4956
+ const paged = paginateStrikingDistance(results, params);
4868
4957
  return {
4869
4958
  results: paged,
4870
4959
  meta: {
@@ -5240,7 +5329,18 @@ const trendsAnalyzer = defineAnalyzer$1({
5240
5329
  }
5241
5330
  });
5242
5331
  const DEFAULT_ROW_LIMIT = 25e3;
5243
- const sortRowResults = createSorter((item) => item.impressions, "impressions");
5332
+ function analyzeZeroClick(rows, options = {}) {
5333
+ const minImpressions = options.minImpressions ?? 1e3;
5334
+ const maxCtr = options.maxCtr ?? .03;
5335
+ const maxPosition = options.maxPosition ?? 10;
5336
+ const queryMap = /* @__PURE__ */ new Map();
5337
+ for (const row of rows) {
5338
+ if (row.impressions < minImpressions || row.position > maxPosition || row.ctr > maxCtr) continue;
5339
+ const existing = queryMap.get(row.query);
5340
+ if (!existing || row.position < existing.position) queryMap.set(row.query, { ...row });
5341
+ }
5342
+ return [...queryMap.values()].sort((left, right) => right.impressions - left.impressions);
5343
+ }
5244
5344
  const ALL_ANALYZERS = [
5245
5345
  bayesianCtrAnalyzer,
5246
5346
  bipartitePagerankAnalyzer,
@@ -5353,30 +5453,15 @@ const ALL_ANALYZERS = [
5353
5453
  return { rows: gsc.select(query, page).where(between(date, period.startDate, period.endDate)).limit(limit).getState() };
5354
5454
  },
5355
5455
  reduceRows(rows, params) {
5356
- const arr = Array.isArray(rows) ? rows : [];
5357
- const minImpressions = params.minImpressions ?? 1e3;
5358
- const maxCtr = params.maxCtr ?? .03;
5359
- const maxPosition = params.maxPosition ?? 10;
5360
- const queryMap = /* @__PURE__ */ new Map();
5361
- for (const row of arr) {
5362
- if (row.impressions < minImpressions) continue;
5363
- if (row.position > maxPosition) continue;
5364
- if (row.ctr > maxCtr) continue;
5365
- const existing = queryMap.get(row.query);
5366
- if (!existing || row.position < existing.position) queryMap.set(row.query, {
5367
- query: row.query,
5368
- page: row.page,
5369
- clicks: row.clicks,
5370
- impressions: row.impressions,
5371
- ctr: row.ctr,
5372
- position: row.position
5373
- });
5374
- }
5375
- const results = sortRowResults(Array.from(queryMap.values()), "impressions", "desc");
5376
- const paged = paginateInMemory(results, {
5456
+ const results = analyzeZeroClick(Array.isArray(rows) ? rows : [], {
5457
+ minImpressions: params.minImpressions ?? 1e3,
5458
+ maxCtr: params.maxCtr ?? .03,
5459
+ maxPosition: params.maxPosition ?? 10
5460
+ });
5461
+ const paged = paginateSortedInMemory(results, {
5377
5462
  limit: params.limit,
5378
5463
  offset: params.offset
5379
- });
5464
+ }, (left, right) => right.impressions - left.impressions);
5380
5465
  return {
5381
5466
  results: paged,
5382
5467
  meta: {
@@ -6994,4 +7079,4 @@ function createInMemoryQuerySource(options) {
6994
7079
  };
6995
7080
  }
6996
7081
  const SQL_ANALYZERS = ALL_ANALYZERS.flatMap((d) => d.sql ? [d.sql] : []);
6997
- export { AnalyzerCapabilityError, DEFAULT_PRIORITY_SOURCES, ENGINE_QUERY_CAPABILITIES, INTENT_CLASSIFIER_VERSION, IN_MEMORY_DEFAULT_CAPABILITIES, NORMALIZER_VERSION, REPORTS, ROW_ANALYZERS, SEARCH_INTENT_CODE, SQL_ANALYZERS, analysisErrorToException, analysisErrors, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzePageConcentration, analyzeSeasonality, classifyQueryIntent, comparisonOf, createAnalyzerRegistry, createCompositeSource, createEngineQuerySource, createInMemoryQuerySource, createSorter, decodeIntent, defaultAnalyzerRegistry, defaultReportRegistry, defineAnalyzer, diffSitemapHealth, dryRunReport, encodeIntent, formatAnalysisError, formatReport, isAnalysisError, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryComparisonRows, queryRows, resolveWindow, rewriteForTableSource, runAnalyzerFromSource, runAnalyzerWithEngine, runReport, runReportResult, scorePriorityActions, typedQuery, windowToComparisonPeriod, windowToPeriod };
7082
+ export { AnalyzerCapabilityError, DEFAULT_PRIORITY_SOURCES, ENGINE_QUERY_CAPABILITIES, INTENT_CLASSIFIER_VERSION, IN_MEMORY_DEFAULT_CAPABILITIES, NORMALIZER_VERSION, REPORTS, ROW_ANALYZERS, SEARCH_INTENT_CODE, SQL_ANALYZERS, analysisErrorToException, analysisErrors, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzeOpportunity, analyzePageConcentration, analyzeSeasonality, analyzeStrikingDistance, analyzeZeroClick, classifyQueryIntent, comparisonOf, createAnalyzerRegistry, createCompositeSource, createEngineQuerySource, createInMemoryQuerySource, createSorter, decodeIntent, defaultAnalyzerRegistry, defaultReportRegistry, defineAnalyzer, diffSitemapHealth, dryRunReport, encodeIntent, formatAnalysisError, formatReport, isAnalysisError, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryRows, resolveWindow, rewriteForTableSource, runAnalyzerFromSource, runAnalyzerWithEngine, runReport, runReportResult, scorePriorityActions };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/analysis",
3
3
  "type": "module",
4
- "version": "0.38.2",
4
+ "version": "0.40.1",
5
5
  "description": "GSC analyzers — striking-distance, opportunity, movers, decay, brand, clustering, concentration, seasonality. Pure row-based + DuckDB-native.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -74,11 +74,10 @@
74
74
  }
75
75
  },
76
76
  "dependencies": {
77
- "drizzle-orm": "1.0.0-rc.3",
78
77
  "pluralize": "^8.0.0",
79
- "@gscdump/engine": "0.38.2",
80
- "gscdump": "0.38.2",
81
- "@gscdump/engine-gsc-api": "0.38.2"
78
+ "@gscdump/engine": "0.40.1",
79
+ "@gscdump/engine-gsc-api": "0.40.1",
80
+ "gscdump": "0.40.1"
82
81
  },
83
82
  "devDependencies": {
84
83
  "vitest": "^4.1.10"