@gscdump/analysis 2.3.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +14 -11
  2. package/dist/analyzer/all.mjs +9 -9
  3. package/dist/analyzers/bipartite-pagerank.mjs +1 -1
  4. package/dist/analyzers/brand.mjs +2 -2
  5. package/dist/analyzers/cannibalization.mjs +2 -2
  6. package/dist/analyzers/clustering.mjs +2 -2
  7. package/dist/analyzers/concentration.mjs +2 -2
  8. package/dist/analyzers/dark-traffic.mjs +1 -1
  9. package/dist/analyzers/decay.mjs +2 -2
  10. package/dist/analyzers/device-gap.mjs +1 -1
  11. package/dist/analyzers/intent-atlas.mjs +1 -1
  12. package/dist/analyzers/keyword-breadth.mjs +1 -1
  13. package/dist/analyzers/long-tail.mjs +1 -1
  14. package/dist/analyzers/movers.mjs +3 -3
  15. package/dist/analyzers/opportunity.mjs +1 -1
  16. package/dist/analyzers/position-distribution.mjs +1 -1
  17. package/dist/analyzers/position-volatility.mjs +1 -1
  18. package/dist/analyzers/query-migration.mjs +1 -1
  19. package/dist/analyzers/seasonality.mjs +2 -2
  20. package/dist/analyzers/stl-decompose.mjs +1 -1
  21. package/dist/analyzers/striking-distance.mjs +1 -1
  22. package/dist/analyzers/survival.mjs +1 -1
  23. package/dist/analyzers/trends.mjs +1 -1
  24. package/dist/analyzers/zero-click.mjs +1 -1
  25. package/dist/browser.d.mts +1 -1
  26. package/dist/index.d.mts +3 -15
  27. package/dist/index.mjs +4 -16
  28. package/dist/query/index.mjs +1 -1
  29. package/dist/query-dimension.d.mts +3 -0
  30. package/dist/query-dimension.mjs +3 -0
  31. package/dist/report/format.mjs +0 -5
  32. package/dist/report/registry.mjs +0 -2
  33. package/dist/report/reports/brand.mjs +0 -2
  34. package/dist/report/reports/growth.mjs +0 -4
  35. package/dist/report/reports/health.mjs +0 -3
  36. package/dist/report/reports/movers.mjs +1 -13
  37. package/dist/report/reports/opportunities.mjs +0 -14
  38. package/dist/report/reports/pre-publish.mjs +0 -5
  39. package/dist/report/reports/risks.mjs +0 -12
  40. package/dist/report/reports/triage.mjs +0 -3
  41. package/dist/report/runtime.mjs +1 -2
  42. package/dist/scoring.mjs +1 -11
  43. package/package.json +9 -4
  44. package/dist/action-priority.d.mts +0 -34
  45. package/dist/action-priority.mjs +0 -201
  46. package/dist/report/reports/priority.mjs +0 -98
package/README.md CHANGED
@@ -16,8 +16,11 @@ npm install @gscdump/analysis
16
16
 
17
17
  | Subpath | Use when |
18
18
  |---|---|
19
- | `@gscdump/analysis` | Top-level barrel: row analyzers, source-backed analyzers, `SQL_ANALYZERS`, `analyzeInBrowser`, `defaultAnalyzerRegistry`, contract types re-exported from engine. |
19
+ | `@gscdump/analysis` | Pure analyzers, `analyzeInBrowser`, and shared analyzer contracts. |
20
20
  | `@gscdump/analysis/registry` | Pre-built `defaultAnalyzerRegistry` (rows + sql). Convenience for callers who don't care about bundle size. |
21
+ | `@gscdump/analysis/errors` | Typed analysis failures and rendering helpers. |
22
+ | `@gscdump/analysis/report` | Report registry and runtime. |
23
+ | `@gscdump/analysis/source` | Portable source factories. |
21
24
 
22
25
  The contract layer (`Analyzer`, `Plan`, `Capability`, `AnalysisParams`, `AnalysisResult`, `AnalysisQuerySource`, `runAnalyzerFromSource`, `createAnalyzerRegistry`, `defineAnalyzer`, period helpers, `createEngineQuerySource`) lives in `@gscdump/engine` under the `/analyzer`, `/analysis-types`, `/period`, `/source`, and `/resolver` subpaths. Most are re-exported from `@gscdump/analysis` for convenience.
23
26
 
@@ -41,24 +44,24 @@ const movers = analyzeMovers(currentRows, previousRows)
41
44
  const decay = analyzeDecay(currentRows, previousRows)
42
45
  ```
43
46
 
44
- Meta-analyses live in the report layer. Run a composed, ranked priority list via `runReport`:
47
+ Meta-analyses live in the report layer. Run a composed evidence report via `runReport`:
45
48
 
46
49
  ```ts
50
+ import { defaultAnalyzerRegistry } from '@gscdump/analysis/registry'
47
51
  import {
48
- defaultAnalyzerRegistry,
49
52
  defaultReportRegistry,
50
53
  runReport,
51
- } from '@gscdump/analysis'
54
+ } from '@gscdump/analysis/report'
52
55
  import { resolveWindow } from '@gscdump/engine/period'
53
56
 
54
- const report = defaultReportRegistry.getReport('priority')!
55
- const window = resolveWindow({ preset: 'last-28d', comparison: 'prev-period' })
57
+ const report = defaultReportRegistry.getReport('health')!
58
+ const window = resolveWindow({ preset: 'last-28d', comparison: 'none' })
56
59
  const result = await runReport(report, {
57
60
  source,
58
61
  analyzers: defaultAnalyzerRegistry,
59
62
  ctx: { site: siteUrl, window, params: {}, registryVersion: defaultReportRegistry.version },
60
63
  })
61
- // result.sections[0].findings — ranked priority actions, page+query keyed.
64
+ // result.sections[0].findings — bounded evidence, page+query keyed.
62
65
  ```
63
66
 
64
67
  Source adapters compose a GSC client + analyzer in one call:
@@ -76,7 +79,7 @@ const movers = await analyzeMoversFromSource(source, { current, previous })
76
79
  SQL-native path. `SQL_ANALYZERS` dispatch through `runAnalyzerFromSource` against an engine-backed source.
77
80
 
78
81
  ```ts
79
- import { ROW_ANALYZERS, SQL_ANALYZERS } from '@gscdump/analysis'
82
+ import { ROW_ANALYZERS, SQL_ANALYZERS } from '@gscdump/analysis/registry'
80
83
  import { createAnalyzerRegistry, runAnalyzerFromSource } from '@gscdump/engine/analyzer'
81
84
  import { createEngineQuerySource } from '@gscdump/engine/source'
82
85
 
@@ -145,8 +148,8 @@ Available source factories:
145
148
 
146
149
  - `createGscApiQuerySource({ client, siteUrl })` — `@gscdump/engine-gsc-api`
147
150
  - `createLiveGscSource({ accessToken, siteUrl })` — `@gscdump/engine-gsc-api`
148
- - `createCompositeSource({ engine, gsc })` — `@gscdump/analysis`; engine first, GSC fallback
149
- - `createInMemoryQuerySource({ queryRows })` — `@gscdump/analysis`
151
+ - `createCompositeSource({ engine, gsc })` — `@gscdump/analysis/source`; engine first, GSC fallback
152
+ - `createInMemoryQuerySource({ queryRows })` — `@gscdump/analysis/source`
150
153
  - `createEngineQuerySource({ engine, ctx })` — `@gscdump/engine/source`
151
154
  - `createSqliteQuerySource({ ... })` — `@gscdump/engine-sqlite`
152
155
 
@@ -172,7 +175,7 @@ Presets: `last-7d`, `last-28d`, `last-30d`, `last-90d`, `last-180d`, `last-365d`
172
175
  | Row analyzers (`analyzeMovers`, `analyzeDecay`, ...) | Public |
173
176
  | Source factories + `analyzeFromSource` | Public |
174
177
  | `Analyzer<P, R>` contract + `createAnalyzerRegistry` (re-exported from `@gscdump/engine/analyzer`) | Public |
175
- | Source factories on the package root | Public |
178
+ | Source factories under `@gscdump/analysis/source` | Public |
176
179
  | Per-analyzer modules under `analysis/src/analyzers/<name>` | Private |
177
180
 
178
181
  ## Related
@@ -1,32 +1,32 @@
1
- import { bayesianCtrAnalyzer } from "../analyzers/bayesian-ctr.mjs";
2
- import { bipartitePagerankAnalyzer } from "../analyzers/bipartite-pagerank.mjs";
3
1
  import { brandAnalyzer } from "../analyzers/brand.mjs";
4
2
  import { cannibalizationAnalyzer } from "../analyzers/cannibalization.mjs";
5
- import { changePointAnalyzer } from "../analyzers/change-point.mjs";
6
3
  import { clusteringAnalyzer } from "../analyzers/clustering.mjs";
7
4
  import { concentrationAnalyzer } from "../analyzers/concentration.mjs";
5
+ import { decayAnalyzer } from "../analyzers/decay.mjs";
6
+ import { moversAnalyzer } from "../analyzers/movers.mjs";
7
+ import { opportunityAnalyzer } from "../analyzers/opportunity.mjs";
8
+ import { seasonalityAnalyzer } from "../analyzers/seasonality.mjs";
9
+ import { strikingDistanceAnalyzer } from "../analyzers/striking-distance.mjs";
10
+ import { zeroClickAnalyzer } from "../analyzers/zero-click.mjs";
11
+ import { bayesianCtrAnalyzer } from "../analyzers/bayesian-ctr.mjs";
12
+ import { bipartitePagerankAnalyzer } from "../analyzers/bipartite-pagerank.mjs";
13
+ import { changePointAnalyzer } from "../analyzers/change-point.mjs";
8
14
  import { contentVelocityAnalyzer } from "../analyzers/content-velocity.mjs";
9
15
  import { ctrAnomalyAnalyzer } from "../analyzers/ctr-anomaly.mjs";
10
16
  import { ctrCurveAnalyzer } from "../analyzers/ctr-curve.mjs";
11
17
  import { darkTrafficAnalyzer } from "../analyzers/dark-traffic.mjs";
12
18
  import { dataDetailAnalyzer } from "../analyzers/data-detail.mjs";
13
19
  import { dataQueryAnalyzer } from "../analyzers/data-query.mjs";
14
- import { decayAnalyzer } from "../analyzers/decay.mjs";
15
20
  import { deviceGapAnalyzer } from "../analyzers/device-gap.mjs";
16
21
  import { intentAtlasAnalyzer } from "../analyzers/intent-atlas.mjs";
17
22
  import { keywordBreadthAnalyzer } from "../analyzers/keyword-breadth.mjs";
18
23
  import { longTailAnalyzer } from "../analyzers/long-tail.mjs";
19
- import { moversAnalyzer } from "../analyzers/movers.mjs";
20
- import { opportunityAnalyzer } from "../analyzers/opportunity.mjs";
21
24
  import { positionDistributionAnalyzer } from "../analyzers/position-distribution.mjs";
22
25
  import { positionVolatilityAnalyzer } from "../analyzers/position-volatility.mjs";
23
26
  import { queryMigrationAnalyzer } from "../analyzers/query-migration.mjs";
24
- import { seasonalityAnalyzer } from "../analyzers/seasonality.mjs";
25
27
  import { stlDecomposeAnalyzer } from "../analyzers/stl-decompose.mjs";
26
- import { strikingDistanceAnalyzer } from "../analyzers/striking-distance.mjs";
27
28
  import { survivalAnalyzer } from "../analyzers/survival.mjs";
28
29
  import { trendsAnalyzer } from "../analyzers/trends.mjs";
29
- import { zeroClickAnalyzer } from "../analyzers/zero-click.mjs";
30
30
  const ALL_ANALYZERS = [
31
31
  bayesianCtrAnalyzer,
32
32
  bipartitePagerankAnalyzer,
@@ -1,8 +1,8 @@
1
1
  import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { periodOf } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
- import { num } from "@gscdump/engine/analysis-types";
6
6
  const BIPARTITE_PAGERANK_ITERATIONS = 25;
7
7
  const BIPARTITE_PAGERANK_DAMPING = .85;
8
8
  const bipartitePagerankAnalyzer = defineAnalyzer({
@@ -1,11 +1,11 @@
1
- import { rowString } from "../analyzer/row-values.mjs";
2
1
  import { queriesQueryState } from "../analyzer/adapt-rows.mjs";
2
+ import { rowString } from "../analyzer/row-values.mjs";
3
3
  import { analysisErrorToException, analysisErrors } from "../errors.mjs";
4
+ import { num } from "@gscdump/engine/analysis-types";
4
5
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
5
6
  import { periodOf } from "@gscdump/engine/period";
6
7
  import { enumeratePartitions } from "@gscdump/engine/planner";
7
8
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
8
- import { num } from "@gscdump/engine/analysis-types";
9
9
  import { err, ok, unwrapResult } from "gscdump/result";
10
10
  function escapeRegexAlt(s) {
11
11
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -1,11 +1,11 @@
1
- import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
2
1
  import { queriesQueryState } from "../analyzer/adapt-rows.mjs";
2
+ import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
3
3
  import { createSorter } from "../types.mjs";
4
+ import { num } from "@gscdump/engine/analysis-types";
4
5
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
5
6
  import { periodOf } from "@gscdump/engine/period";
6
7
  import { enumeratePartitions } from "@gscdump/engine/planner";
7
8
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
8
- import { num } from "@gscdump/engine/analysis-types";
9
9
  const sortRowResults = createSorter((item, metric) => {
10
10
  switch (metric) {
11
11
  case "clicks": return item.totalClicks;
@@ -1,10 +1,10 @@
1
- import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
2
1
  import { queriesQueryState } from "../analyzer/adapt-rows.mjs";
2
+ import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
3
+ import { num } from "@gscdump/engine/analysis-types";
3
4
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
4
5
  import { periodOf } from "@gscdump/engine/period";
5
6
  import { enumeratePartitions } from "@gscdump/engine/planner";
6
7
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
7
- import { num } from "@gscdump/engine/analysis-types";
8
8
  const INTENT_PREFIXES_REGEX = "^(how to|what is|what are|why is|why do|where to|when to|best|top|vs|versus|compare|review|buy|cheap|free|near me)(\\s|$)";
9
9
  const INTENT_PREFIXES = [
10
10
  "how to",
@@ -1,10 +1,10 @@
1
- import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
2
1
  import { pagesQueryState, queriesQueryState } from "../analyzer/adapt-rows.mjs";
2
+ import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
3
+ import { num } from "@gscdump/engine/analysis-types";
3
4
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
4
5
  import { periodOf } from "@gscdump/engine/period";
5
6
  import { enumeratePartitions } from "@gscdump/engine/planner";
6
7
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
7
- import { num } from "@gscdump/engine/analysis-types";
8
8
  function analyzeConcentration(items, options = {}) {
9
9
  const { topN = 10 } = options;
10
10
  if (items.length === 0) return {
@@ -1,8 +1,8 @@
1
1
  import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { periodOf } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
- import { num } from "@gscdump/engine/analysis-types";
6
6
  const darkTrafficAnalyzer = defineAnalyzer({
7
7
  id: "dark-traffic",
8
8
  buildSql(params) {
@@ -1,12 +1,12 @@
1
- import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
2
1
  import { pagesQueryState } from "../analyzer/adapt-rows.mjs";
2
+ import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
3
3
  import { buildPeriodMap, createMetricSorter } from "../types.mjs";
4
4
  import { paginateInMemory } from "../analyzer/paginate.mjs";
5
+ import { num } from "@gscdump/engine/analysis-types";
5
6
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
6
7
  import { comparisonOf } from "@gscdump/engine/period";
7
8
  import { enumeratePartitions } from "@gscdump/engine/planner";
8
9
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
9
- import { num } from "@gscdump/engine/analysis-types";
10
10
  const sortResults = createMetricSorter("lostClicks", {
11
11
  lostClicks: "desc",
12
12
  declinePercent: "desc",
@@ -1,8 +1,8 @@
1
1
  import { rowString } from "../analyzer/row-values.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { periodOf } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
- import { num } from "@gscdump/engine/analysis-types";
6
6
  const deviceGapAnalyzer = defineAnalyzer({
7
7
  id: "device-gap",
8
8
  buildSql(params) {
@@ -1,9 +1,9 @@
1
1
  import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { defaultEndDate } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
6
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
6
- import { num } from "@gscdump/engine/analysis-types";
7
7
  import { daysAgoUtc } from "gscdump/dates";
8
8
  const INTENT_ATLAS_STOP_WORDS = [
9
9
  "the",
@@ -1,9 +1,9 @@
1
1
  import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { periodOf } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
6
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
6
- import { num } from "@gscdump/engine/analysis-types";
7
7
  const keywordBreadthAnalyzer = defineAnalyzer({
8
8
  id: "keyword-breadth",
9
9
  buildSql(params) {
@@ -1,9 +1,9 @@
1
1
  import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { periodOf } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
6
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
6
- import { num } from "@gscdump/engine/analysis-types";
7
7
  function downsampleLogRank(points) {
8
8
  const toPoint = (p) => ({
9
9
  rank: num(p.rank),
@@ -1,12 +1,12 @@
1
- import { percentDifference } from "../scoring.mjs";
2
- import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
3
1
  import { queriesQueryState } from "../analyzer/adapt-rows.mjs";
2
+ import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
4
3
  import { buildPeriodMap } from "../types.mjs";
4
+ import { percentDifference } from "../scoring.mjs";
5
+ import { num } from "@gscdump/engine/analysis-types";
5
6
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
6
7
  import { comparisonOf } from "@gscdump/engine/period";
7
8
  import { enumeratePartitions } from "@gscdump/engine/planner";
8
9
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
9
- import { num } from "@gscdump/engine/analysis-types";
10
10
  function withCanonicalFields(r) {
11
11
  return {
12
12
  ...r,
@@ -1,10 +1,10 @@
1
1
  import { queriesQueryState } from "../analyzer/adapt-rows.mjs";
2
2
  import { paginateClause, paginateSortedInMemory } from "../analyzer/paginate.mjs";
3
+ import { num } from "@gscdump/engine/analysis-types";
3
4
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
4
5
  import { periodOf } from "@gscdump/engine/period";
5
6
  import { enumeratePartitions } from "@gscdump/engine/planner";
6
7
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
7
- import { num } from "@gscdump/engine/analysis-types";
8
8
  const EXPECTED_CTR_BY_POSITION = {
9
9
  1: .3,
10
10
  2: .15,
@@ -1,8 +1,8 @@
1
1
  import { rowString } from "../analyzer/row-values.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { periodOf } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
- import { num } from "@gscdump/engine/analysis-types";
6
6
  const positionDistributionAnalyzer = defineAnalyzer({
7
7
  id: "position-distribution",
8
8
  buildSql(params) {
@@ -1,9 +1,9 @@
1
1
  import { rowString } from "../analyzer/row-values.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { periodOf } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
6
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
6
- import { num } from "@gscdump/engine/analysis-types";
7
7
  const positionVolatilityAnalyzer = defineAnalyzer({
8
8
  id: "position-volatility",
9
9
  buildSql(params) {
@@ -1,9 +1,9 @@
1
1
  import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { periodOf } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
6
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
6
- import { num } from "@gscdump/engine/analysis-types";
7
7
  import { MS_PER_DAY, toIsoDate } from "gscdump/dates";
8
8
  const queryMigrationAnalyzer = defineAnalyzer({
9
9
  id: "query-migration",
@@ -1,9 +1,9 @@
1
- import { rowBoolean, rowString } from "../analyzer/row-values.mjs";
2
1
  import { datesQueryState } from "../analyzer/adapt-rows.mjs";
2
+ import { rowBoolean, rowString } from "../analyzer/row-values.mjs";
3
+ import { num } from "@gscdump/engine/analysis-types";
3
4
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
4
5
  import { periodOf } from "@gscdump/engine/period";
5
6
  import { enumeratePartitions } from "@gscdump/engine/planner";
6
- import { num } from "@gscdump/engine/analysis-types";
7
7
  function calculateCV(values) {
8
8
  if (values.length === 0) return 0;
9
9
  const mean = values.reduce((a, b) => a + b, 0) / values.length;
@@ -1,9 +1,9 @@
1
1
  import { parseJsonRows, rowBoolean, rowString } from "../analyzer/row-values.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { defaultEndDate } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
6
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
6
- import { num } from "@gscdump/engine/analysis-types";
7
7
  import { daysAgoUtc } from "gscdump/dates";
8
8
  const stlDecomposeAnalyzer = defineAnalyzer({
9
9
  id: "stl-decompose",
@@ -1,9 +1,9 @@
1
1
  import { queriesQueryState } from "../analyzer/adapt-rows.mjs";
2
2
  import { paginateSortedInMemory } from "../analyzer/paginate.mjs";
3
+ import { num } from "@gscdump/engine/analysis-types";
3
4
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
4
5
  import { periodOf } from "@gscdump/engine/period";
5
6
  import { enumeratePartitions } from "@gscdump/engine/planner";
6
- import { num } from "@gscdump/engine/analysis-types";
7
7
  const DEFAULT_ROW_LIMIT = 25e3;
8
8
  function paginateStrikingDistance(results, options) {
9
9
  return paginateSortedInMemory(results, {
@@ -1,9 +1,9 @@
1
1
  import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { defaultEndDate } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
6
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
6
- import { num } from "@gscdump/engine/analysis-types";
7
7
  import { MS_PER_DAY, daysAgoUtc } from "gscdump/dates";
8
8
  const survivalAnalyzer = defineAnalyzer({
9
9
  id: "survival",
@@ -1,9 +1,9 @@
1
1
  import { parseJsonRows, rowString } from "../analyzer/row-values.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { defaultEndDate } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
6
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
6
- import { num } from "@gscdump/engine/analysis-types";
7
7
  import { MS_PER_DAY, toIsoDate } from "gscdump/dates";
8
8
  const trendsAnalyzer = defineAnalyzer({
9
9
  id: "trends",
@@ -1,9 +1,9 @@
1
1
  import { paginateClause, paginateSortedInMemory } from "../analyzer/paginate.mjs";
2
+ import { num } from "@gscdump/engine/analysis-types";
2
3
  import { defineAnalyzer } from "@gscdump/engine/analyzer";
3
4
  import { periodOf } from "@gscdump/engine/period";
4
5
  import { enumeratePartitions } from "@gscdump/engine/planner";
5
6
  import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
6
- import { num } from "@gscdump/engine/analysis-types";
7
7
  import { between, date, gsc, page, query } from "gscdump/query";
8
8
  const DEFAULT_ROW_LIMIT = 25e3;
9
9
  function analyzeZeroClick(rows, options = {}) {
@@ -1,5 +1,5 @@
1
- import { AnalyzerRegistry } from "@gscdump/engine/analyzer";
2
1
  import { AnalysisParams, AnalysisResult } from "@gscdump/engine/analysis-types";
2
+ import { AnalyzerRegistry } from "@gscdump/engine/analyzer";
3
3
  import { AttachedTableRunner, AttachedTableRunner as AnalyzerRunner, AttachedTableSourceOptions, AttachedTableSourceOptions as BrowserAnalyzeOptions } from "@gscdump/engine/source";
4
4
  declare function analyzeInBrowser(runner: AttachedTableRunner, opts: AttachedTableSourceOptions, params: AnalysisParams, registry: AnalyzerRegistry): Promise<AnalysisResult>;
5
5
  export { type AnalyzerRunner, type BrowserAnalyzeOptions, analyzeInBrowser };
package/dist/index.d.mts CHANGED
@@ -1,8 +1,3 @@
1
- import { ROW_ANALYZERS } from "./analyzer/row-analyzers.mjs";
2
- import { SQL_ANALYZERS } from "./sql-analyzers.mjs";
3
- import { defaultAnalyzerRegistry } from "./default-registry.mjs";
4
- import { AnalysisError, AnalysisErrorKind, analysisErrorToException, analysisErrors, formatAnalysisError, isAnalysisError } from "./errors.mjs";
5
- import { ActionPriorityResult, ActionPrioritySourceState, ActionPrioritySourceStatus, ActionSource, DEFAULT_PRIORITY_SOURCES, Effort, PriorityAction, mergePriorityActions, normalizePriorityActions, scorePriorityActions } from "./action-priority.mjs";
6
1
  import { BaseMetrics, DateRow, PageRow, QueriesRow, QueryPageRow, SortOrder, createSorter } from "./types.mjs";
7
2
  import { BrandSegmentationOptions, BrandSegmentationResult, BrandSegmentationRow, BrandSummary, analyzeBrandSegmentation } from "./analyzers/brand.mjs";
8
3
  import { CannibalizationCompetitor, CannibalizationEvent, CannibalizationOptions, CannibalizationPage, CannibalizationResult, CannibalizationSortMetric, analyzeCannibalization } from "./analyzers/cannibalization.mjs";
@@ -17,17 +12,10 @@ import { ZeroClickOptions, ZeroClickResult, analyzeZeroClick } from "./analyzers
17
12
  import { AnalyzerRunner, BrowserAnalyzeOptions, analyzeInBrowser } from "./browser.mjs";
18
13
  import { INTENT_CLASSIFIER_VERSION, IntentClassification, SEARCH_INTENT_CODE, SearchIntent, classifyQueryIntent, encodeIntent } from "./query/intent.mjs";
19
14
  import { NORMALIZER_VERSION, normalizeQuery } from "./query/normalize.mjs";
20
- import { FormatReportOptions, formatReport } from "./report/format.mjs";
21
- import { REPORTS, defaultReportRegistry } from "./report/registry.mjs";
22
- import { DryRunReportResult, RunReportOptions, dryRunReport, runReport, runReportResult } from "./report/runtime.mjs";
23
- import "./report/index.mjs";
24
15
  import { CurrentSitemapScope, SITEMAP_SYNC_COLLAPSE_POLICY, SITEMAP_TRUST_COLLAPSE_POLICY, SitemapCollapsePolicy, SitemapCollapseState, SitemapDelta, SitemapHealthDiff, SitemapHealthInput, SitemapHealthRow, SitemapHealthTotals, classifySitemapCollapse, compareCurrentSitemapScope, diffSitemapHealth, sitemapHistoryHasCollapse } from "./sitemap-health.mjs";
25
- import { CompositeSourceOptions, createCompositeSource } from "./source/composite.mjs";
26
- import { IN_MEMORY_DEFAULT_CAPABILITIES, InMemoryQuerySourceOptions, createInMemoryQuerySource } from "./source/in-memory.mjs";
27
- import "./source/index.mjs";
16
+ import { AnalysisParams, AnalysisResult, AnalysisTool, num } from "@gscdump/engine/analysis-types";
28
17
  import { Analyzer, AnalyzerCapabilityError, AnalyzerRegistry, AnalyzerRegistryInit, AnalyzerVariants, DefineAnalyzerOptions, DefinedAnalyzer, Plan, ReduceContext, ReduceCtx, Reducer, RequiredCapability, RowQueriesPlan, SqlExtraQuery, SqlPlan, SqlPlanSpec, TypedRowQuery, createAnalyzerRegistry, defineAnalyzer, runAnalyzerFromSource } from "@gscdump/engine/analyzer";
29
18
  import { AnalysisPeriod, ComparisonMode, ComparisonPeriod, PadTimeseriesOptions, ResolveWindowOptions, ResolvedWindow, WindowPreset, comparisonOf, padTimeseries, periodOf, resolveWindow } from "@gscdump/engine/period";
30
- import { AnalysisParams, AnalysisResult, AnalysisTool, num } from "@gscdump/engine/analysis-types";
31
19
  import { AnalysisQuerySource, AnalysisSourceKind, ENGINE_QUERY_CAPABILITIES, EngineQuerySourceOptions, ExecuteSqlOptions, FileSet, QueryRow, SourceCapabilities, createEngineQuerySource, queryRows, runAnalyzerWithEngine } from "@gscdump/engine/source";
32
- import { DefineReportOptions, DefinedReport, ReportAction, ReportContext, ReportFinding, ReportPlanStep, ReportResult, ReportSection } from "@gscdump/engine/report";
33
- export { type ActionPriorityResult, type ActionPrioritySourceState, type ActionPrioritySourceStatus, type ActionSource, type AnalysisError, type AnalysisErrorKind, type AnalysisParams, type AnalysisPeriod, type AnalysisQuerySource, type AnalysisResult, type AnalysisSourceKind, type AnalysisTool, type Analyzer, AnalyzerCapabilityError, type AnalyzerRegistry, type AnalyzerRegistryInit, type AnalyzerRunner, type AnalyzerVariants, type BaseMetrics, type BrandSegmentationOptions, type BrandSegmentationResult, type BrandSegmentationRow, type BrandSummary, type BrowserAnalyzeOptions, type CannibalizationCompetitor, type CannibalizationEvent, type CannibalizationOptions, type CannibalizationPage, type CannibalizationResult, type CannibalizationSortMetric, type ClusterType, type ClusteringOptions, type ClusteringResult, type ComparisonMode, type ComparisonPeriod, type CompositeSourceOptions, type ConcentrationInput, type ConcentrationItem, type ConcentrationOptions, type ConcentrationResult, type ConcentrationRiskLevel, type CurrentSitemapScope, DEFAULT_PRIORITY_SOURCES, type DateRow, type DecayInput, type DecayOptions, type DecayResult, type DecaySeriesPoint, type DecaySortMetric, type DefineAnalyzerOptions, type DefineReportOptions, type DefinedAnalyzer, type DefinedReport, type DryRunReportResult, ENGINE_QUERY_CAPABILITIES, type Effort, type EngineQuerySourceOptions, type ExecuteSqlOptions, type FileSet, type FormatReportOptions, INTENT_CLASSIFIER_VERSION, IN_MEMORY_DEFAULT_CAPABILITIES, type InMemoryQuerySourceOptions, type IntentClassification, type KeywordCluster, type MonthlyData, type MoverData, type MoversInput, type MoversOptions, type MoversResult, type MoversSortMetric, NORMALIZER_VERSION, type OpportunityFactors, type OpportunityOptions, type OpportunityResult, type OpportunitySortMetric, type OpportunityWeights, type PadTimeseriesOptions, type PageRow, type Plan, type PriorityAction, type QueriesRow, type QueryPageRow, type QueryRow, REPORTS, ROW_ANALYZERS, type ReduceContext, type ReduceCtx, type Reducer, type ReportAction, type ReportContext, type ReportFinding, type ReportPlanStep, type ReportResult, type ReportSection, type RequiredCapability, type ResolveWindowOptions, type ResolvedWindow, type RowQueriesPlan, type RunReportOptions, SEARCH_INTENT_CODE, SITEMAP_SYNC_COLLAPSE_POLICY, SITEMAP_TRUST_COLLAPSE_POLICY, SQL_ANALYZERS, type SearchIntent, type SeasonalityMetric, type SeasonalityOptions, type SeasonalityResult, type SitemapCollapsePolicy, type SitemapCollapseState, type SitemapDelta, type SitemapHealthDiff, type SitemapHealthInput, type SitemapHealthRow, type SitemapHealthTotals, type SortOrder, type SourceCapabilities, type SqlExtraQuery, type SqlPlan, type SqlPlanSpec, type StrikingDistanceInputRow, type StrikingDistanceOptions, type StrikingDistanceResult, type TypedRowQuery, type WindowPreset, type ZeroClickOptions, type ZeroClickResult, analysisErrorToException, analysisErrors, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzeOpportunity, analyzePageConcentration, analyzeSeasonality, analyzeStrikingDistance, analyzeZeroClick, classifyQueryIntent, classifySitemapCollapse, compareCurrentSitemapScope, comparisonOf, createAnalyzerRegistry, createCompositeSource, createEngineQuerySource, createInMemoryQuerySource, createSorter, defaultAnalyzerRegistry, defaultReportRegistry, defineAnalyzer, diffSitemapHealth, dryRunReport, encodeIntent, formatAnalysisError, formatReport, isAnalysisError, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryRows, resolveWindow, runAnalyzerFromSource, runAnalyzerWithEngine, runReport, runReportResult, scorePriorityActions, sitemapHistoryHasCollapse };
20
+ import { DefineReportOptions, DefinedReport, ReportContext, ReportFinding, ReportPlanStep, ReportResult, ReportSection } from "@gscdump/engine/report";
21
+ export { type AnalysisParams, type AnalysisPeriod, type AnalysisQuerySource, type AnalysisResult, type AnalysisSourceKind, type AnalysisTool, type Analyzer, AnalyzerCapabilityError, type AnalyzerRegistry, type AnalyzerRegistryInit, type AnalyzerRunner, type AnalyzerVariants, type BaseMetrics, type BrandSegmentationOptions, type BrandSegmentationResult, type BrandSegmentationRow, type BrandSummary, type BrowserAnalyzeOptions, type CannibalizationCompetitor, type CannibalizationEvent, type CannibalizationOptions, type CannibalizationPage, type CannibalizationResult, type CannibalizationSortMetric, type ClusterType, type ClusteringOptions, type ClusteringResult, type ComparisonMode, type ComparisonPeriod, type ConcentrationInput, type ConcentrationItem, type ConcentrationOptions, type ConcentrationResult, type ConcentrationRiskLevel, type CurrentSitemapScope, type DateRow, type DecayInput, type DecayOptions, type DecayResult, type DecaySeriesPoint, type DecaySortMetric, type DefineAnalyzerOptions, type DefineReportOptions, type DefinedAnalyzer, type DefinedReport, ENGINE_QUERY_CAPABILITIES, type EngineQuerySourceOptions, type ExecuteSqlOptions, type FileSet, INTENT_CLASSIFIER_VERSION, type IntentClassification, type KeywordCluster, type MonthlyData, type MoverData, type MoversInput, type MoversOptions, type MoversResult, type MoversSortMetric, NORMALIZER_VERSION, type OpportunityFactors, type OpportunityOptions, type OpportunityResult, type OpportunitySortMetric, type OpportunityWeights, type PadTimeseriesOptions, type PageRow, type Plan, type QueriesRow, type QueryPageRow, type QueryRow, type ReduceContext, type ReduceCtx, type Reducer, type ReportContext, type ReportFinding, type ReportPlanStep, type ReportResult, type ReportSection, type RequiredCapability, type ResolveWindowOptions, type ResolvedWindow, type RowQueriesPlan, SEARCH_INTENT_CODE, SITEMAP_SYNC_COLLAPSE_POLICY, SITEMAP_TRUST_COLLAPSE_POLICY, type SearchIntent, type SeasonalityMetric, type SeasonalityOptions, type SeasonalityResult, type SitemapCollapsePolicy, type SitemapCollapseState, type SitemapDelta, type SitemapHealthDiff, type SitemapHealthInput, type SitemapHealthRow, type SitemapHealthTotals, type SortOrder, type SourceCapabilities, type SqlExtraQuery, type SqlPlan, type SqlPlanSpec, type StrikingDistanceInputRow, type StrikingDistanceOptions, type StrikingDistanceResult, type TypedRowQuery, type WindowPreset, type ZeroClickOptions, type ZeroClickResult, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzeOpportunity, analyzePageConcentration, analyzeSeasonality, analyzeStrikingDistance, analyzeZeroClick, classifyQueryIntent, classifySitemapCollapse, compareCurrentSitemapScope, comparisonOf, createAnalyzerRegistry, createEngineQuerySource, createSorter, defineAnalyzer, diffSitemapHealth, encodeIntent, normalizeQuery, num, padTimeseries, periodOf, queryRows, resolveWindow, runAnalyzerFromSource, runAnalyzerWithEngine, sitemapHistoryHasCollapse };
package/dist/index.mjs CHANGED
@@ -1,32 +1,20 @@
1
- import { DEFAULT_PRIORITY_SOURCES, mergePriorityActions, normalizePriorityActions, scorePriorityActions } from "./action-priority.mjs";
2
- import { analysisErrorToException, analysisErrors, formatAnalysisError, isAnalysisError } from "./errors.mjs";
3
1
  import { analyzeBrandSegmentation } from "./analyzers/brand.mjs";
4
2
  import { createSorter } from "./types.mjs";
5
3
  import { analyzeCannibalization } from "./analyzers/cannibalization.mjs";
6
4
  import { analyzeClustering } from "./analyzers/clustering.mjs";
7
5
  import { analyzeConcentration, analyzeKeywordConcentration, analyzePageConcentration } from "./analyzers/concentration.mjs";
8
- import { INTENT_CLASSIFIER_VERSION, SEARCH_INTENT_CODE, classifyQueryIntent, encodeIntent } from "./query/intent.mjs";
9
- import { NORMALIZER_VERSION, normalizeQuery } from "./query/normalize.mjs";
10
6
  import { analyzeDecay } from "./analyzers/decay.mjs";
11
7
  import { analyzeMovers } from "./analyzers/movers.mjs";
12
8
  import { analyzeOpportunity } from "./analyzers/opportunity.mjs";
13
9
  import { analyzeSeasonality } from "./analyzers/seasonality.mjs";
14
10
  import { analyzeStrikingDistance } from "./analyzers/striking-distance.mjs";
15
11
  import { analyzeZeroClick } from "./analyzers/zero-click.mjs";
16
- import { ROW_ANALYZERS } from "./analyzer/row-analyzers.mjs";
17
12
  import { analyzeInBrowser } from "./browser.mjs";
18
- import { SQL_ANALYZERS } from "./sql-analyzers.mjs";
19
- import { defaultAnalyzerRegistry } from "./default-registry.mjs";
20
- import { formatReport } from "./report/format.mjs";
21
- import { REPORTS, defaultReportRegistry } from "./report/registry.mjs";
22
- import { dryRunReport, runReport, runReportResult } from "./report/runtime.mjs";
23
- import "./report/index.mjs";
13
+ import { INTENT_CLASSIFIER_VERSION, SEARCH_INTENT_CODE, classifyQueryIntent, encodeIntent } from "./query/intent.mjs";
14
+ import { NORMALIZER_VERSION, normalizeQuery } from "./query/normalize.mjs";
24
15
  import { SITEMAP_SYNC_COLLAPSE_POLICY, SITEMAP_TRUST_COLLAPSE_POLICY, classifySitemapCollapse, compareCurrentSitemapScope, diffSitemapHealth, sitemapHistoryHasCollapse } from "./sitemap-health.mjs";
25
- import { createCompositeSource } from "./source/composite.mjs";
26
- import { IN_MEMORY_DEFAULT_CAPABILITIES, createInMemoryQuerySource } from "./source/in-memory.mjs";
27
- import "./source/index.mjs";
16
+ import { num } from "@gscdump/engine/analysis-types";
28
17
  import { AnalyzerCapabilityError, createAnalyzerRegistry, defineAnalyzer, runAnalyzerFromSource } from "@gscdump/engine/analyzer";
29
18
  import { comparisonOf, padTimeseries, periodOf, resolveWindow } from "@gscdump/engine/period";
30
- import { num } from "@gscdump/engine/analysis-types";
31
19
  import { ENGINE_QUERY_CAPABILITIES, createEngineQuerySource, queryRows, runAnalyzerWithEngine } from "@gscdump/engine/source";
32
- export { AnalyzerCapabilityError, DEFAULT_PRIORITY_SOURCES, ENGINE_QUERY_CAPABILITIES, INTENT_CLASSIFIER_VERSION, IN_MEMORY_DEFAULT_CAPABILITIES, NORMALIZER_VERSION, REPORTS, ROW_ANALYZERS, SEARCH_INTENT_CODE, SITEMAP_SYNC_COLLAPSE_POLICY, SITEMAP_TRUST_COLLAPSE_POLICY, SQL_ANALYZERS, analysisErrorToException, analysisErrors, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzeOpportunity, analyzePageConcentration, analyzeSeasonality, analyzeStrikingDistance, analyzeZeroClick, classifyQueryIntent, classifySitemapCollapse, compareCurrentSitemapScope, comparisonOf, createAnalyzerRegistry, createCompositeSource, createEngineQuerySource, createInMemoryQuerySource, createSorter, defaultAnalyzerRegistry, defaultReportRegistry, defineAnalyzer, diffSitemapHealth, dryRunReport, encodeIntent, formatAnalysisError, formatReport, isAnalysisError, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryRows, resolveWindow, runAnalyzerFromSource, runAnalyzerWithEngine, runReport, runReportResult, scorePriorityActions, sitemapHistoryHasCollapse };
20
+ export { AnalyzerCapabilityError, ENGINE_QUERY_CAPABILITIES, INTENT_CLASSIFIER_VERSION, NORMALIZER_VERSION, SEARCH_INTENT_CODE, SITEMAP_SYNC_COLLAPSE_POLICY, SITEMAP_TRUST_COLLAPSE_POLICY, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzeOpportunity, analyzePageConcentration, analyzeSeasonality, analyzeStrikingDistance, analyzeZeroClick, classifyQueryIntent, classifySitemapCollapse, compareCurrentSitemapScope, comparisonOf, createAnalyzerRegistry, createEngineQuerySource, createSorter, defineAnalyzer, diffSitemapHealth, encodeIntent, normalizeQuery, num, padTimeseries, periodOf, queryRows, resolveWindow, runAnalyzerFromSource, runAnalyzerWithEngine, sitemapHistoryHasCollapse };
@@ -1,4 +1,4 @@
1
- import { buildDataDetailPlan, buildDataDetailRows, buildDataQueryPlan, buildDataQueryRows, shapeDataDetailRowResults, shapeDataDetailRows, shapeDataQueryRowResults, shapeDataQueryRows } from "./analyzers.mjs";
2
1
  import "./intent.mjs";
3
2
  import "./normalize.mjs";
3
+ import { buildDataDetailPlan, buildDataDetailRows, buildDataQueryPlan, buildDataQueryRows, shapeDataDetailRowResults, shapeDataDetailRows, shapeDataQueryRowResults, shapeDataQueryRows } from "./analyzers.mjs";
4
4
  export { buildDataDetailPlan, buildDataDetailRows, buildDataQueryPlan, buildDataQueryRows, shapeDataDetailRowResults, shapeDataDetailRows, shapeDataQueryRowResults, shapeDataQueryRows };
@@ -0,0 +1,3 @@
1
+ import { INTENT_CLASSIFIER_VERSION, IntentClassification, SEARCH_INTENT_CODE, SearchIntent, classifyQueryIntent, encodeIntent } from "./query/intent.mjs";
2
+ import { NORMALIZER_VERSION, normalizeQuery } from "./query/normalize.mjs";
3
+ export { INTENT_CLASSIFIER_VERSION, type IntentClassification, NORMALIZER_VERSION, SEARCH_INTENT_CODE, type SearchIntent, classifyQueryIntent, encodeIntent, normalizeQuery };
@@ -0,0 +1,3 @@
1
+ import { INTENT_CLASSIFIER_VERSION, SEARCH_INTENT_CODE, classifyQueryIntent, encodeIntent } from "./query/intent.mjs";
2
+ import { NORMALIZER_VERSION, normalizeQuery } from "./query/normalize.mjs";
3
+ export { INTENT_CLASSIFIER_VERSION, NORMALIZER_VERSION, SEARCH_INTENT_CODE, classifyQueryIntent, encodeIntent, normalizeQuery };
@@ -30,11 +30,6 @@ function renderSection(section, cap) {
30
30
  lines.push(` - [${f.entity.kind}] ${f.entity.value} ${metricsStr}${f.why ? ` — ${f.why}` : ""}`);
31
31
  }
32
32
  if (section.truncated && section.truncated.kept < section.truncated.total) lines.push(` … +${section.truncated.total - section.truncated.kept} more`);
33
- for (const a of section.actions) {
34
- const target = a.target ? ` ${a.target.kind}=${a.target.value}` : "";
35
- lines.push(` → ${a.kind}${target}: ${a.rationale}`);
36
- if (a.cliHint) lines.push(` $ ${a.cliHint}`);
37
- }
38
33
  lines.push("");
39
34
  return lines;
40
35
  }
@@ -4,7 +4,6 @@ import { healthReport } from "./reports/health.mjs";
4
4
  import { moversReport } from "./reports/movers.mjs";
5
5
  import { opportunitiesReport } from "./reports/opportunities.mjs";
6
6
  import { prePublishReport } from "./reports/pre-publish.mjs";
7
- import { priorityReport } from "./reports/priority.mjs";
8
7
  import { risksReport } from "./reports/risks.mjs";
9
8
  import { triageReport } from "./reports/triage.mjs";
10
9
  import { createReportRegistry } from "@gscdump/engine/report";
@@ -15,7 +14,6 @@ const REPORTS = [
15
14
  moversReport,
16
15
  opportunitiesReport,
17
16
  prePublishReport,
18
- priorityReport,
19
17
  risksReport,
20
18
  triageReport
21
19
  ];
@@ -74,7 +74,6 @@ function buildBrandSplitSection(res, max) {
74
74
  summary: { magnitudeLabel: summary ? `brand share ${(brandShare * 100).toFixed(1)}% (${summary.brandClicks} brand vs ${summary.nonBrandClicks} non-brand clicks)` : "no summary available" },
75
75
  findings,
76
76
  coverage: sectionCoverage(res),
77
- actions: [],
78
77
  artifact: sectionArtifact(res, "brand")
79
78
  };
80
79
  }
@@ -98,7 +97,6 @@ function buildConcentrationSection(res, max) {
98
97
  summary: head ? { magnitudeLabel: `HHI ${head.hhi.toFixed(0)} (${head.riskLevel}); top-N share ${(head.topNConcentration * 100).toFixed(1)}%` } : {},
99
98
  findings,
100
99
  coverage: sectionCoverage(res),
101
- actions: [],
102
100
  artifact: sectionArtifact(res, "concentration", { dimension: "keywords" })
103
101
  };
104
102
  }
@@ -73,7 +73,6 @@ function buildContentVelocity(res) {
73
73
  summary: { magnitudeLabel: `${totalNew} new keywords across ${rows.length} weeks (avg ${avgPerWeek.toFixed(1)}/wk)` },
74
74
  findings: [],
75
75
  coverage: sectionCoverage(res),
76
- actions: [],
77
76
  artifact: sectionArtifact(res, "content-velocity")
78
77
  };
79
78
  }
@@ -88,7 +87,6 @@ function buildKeywordBreadth(res) {
88
87
  summary: { magnitudeLabel: top ? `${totalPages} pages; modal bucket "${top.bucket}" (${top.pageCount} pages)` : "no data" },
89
88
  findings: [],
90
89
  coverage: sectionCoverage(res),
91
- actions: [],
92
90
  artifact: sectionArtifact(res, "keyword-breadth")
93
91
  };
94
92
  }
@@ -101,7 +99,6 @@ function buildIntentAtlas(res) {
101
99
  summary: { magnitudeLabel: `${rows.length} clusters covering ${rows.reduce((s, r) => s + r.keywordCount, 0)} keywords` },
102
100
  findings: [],
103
101
  coverage: sectionCoverage(res),
104
- actions: [],
105
102
  artifact: sectionArtifact(res, "intent-atlas")
106
103
  };
107
104
  }
@@ -137,7 +134,6 @@ function buildLongTail(res, max) {
137
134
  findings,
138
135
  truncated: truncation(rows.length, kept.length),
139
136
  coverage: sectionCoverage(res),
140
- actions: [],
141
137
  artifact: sectionArtifact(res, "long-tail")
142
138
  };
143
139
  }
@@ -82,7 +82,6 @@ function buildCtrAnomalySection(res, max) {
82
82
  findings,
83
83
  truncated: truncation(rows.length, kept.length),
84
84
  coverage: sectionCoverage(res),
85
- actions: [],
86
85
  artifact: sectionArtifact(res, "ctr-anomaly")
87
86
  };
88
87
  }
@@ -108,7 +107,6 @@ function buildChangePointSection(res, max) {
108
107
  findings,
109
108
  truncated: truncation(rows.length, kept.length),
110
109
  coverage: sectionCoverage(res),
111
- actions: [],
112
110
  artifact: sectionArtifact(res, "change-point")
113
111
  };
114
112
  }
@@ -134,7 +132,6 @@ function buildPositionVolatilitySection(res, max) {
134
132
  findings,
135
133
  truncated: truncation(rows.length, kept.length),
136
134
  coverage: sectionCoverage(res),
137
- actions: [],
138
135
  artifact: sectionArtifact(res, "position-volatility")
139
136
  };
140
137
  }
@@ -111,7 +111,6 @@ function buildMoversSection(res, direction, max, minChange) {
111
111
  findings,
112
112
  truncated: truncation(total, kept.length),
113
113
  coverage: sectionCoverage(res),
114
- actions: [],
115
114
  artifact: sectionArtifact(res, "movers")
116
115
  };
117
116
  }
@@ -162,17 +161,7 @@ function buildDeclinersSection(moversRes, decayRes, max, minChange) {
162
161
  magnitudeLabel: `${Math.round(totalLost)} clicks lost`
163
162
  },
164
163
  findings,
165
- coverage: decayRes && moversRes ? "full" : "partial",
166
- actions: lostPages.slice(0, 1).map((r) => ({
167
- kind: "analyzer",
168
- target: {
169
- kind: "page",
170
- value: r.page
171
- },
172
- params: { type: "change-point" },
173
- rationale: "Investigate the change-point on the worst-affected page",
174
- cliHint: `gscdump analyze change-point --start <date> --end <date>`
175
- }))
164
+ coverage: decayRes && moversRes ? "full" : "partial"
176
165
  };
177
166
  }
178
167
  function buildStrikingSection(res, max) {
@@ -199,7 +188,6 @@ function buildStrikingSection(res, max) {
199
188
  findings,
200
189
  truncated: truncation(rows.length, kept.length),
201
190
  coverage: sectionCoverage(res),
202
- actions: [],
203
191
  artifact: sectionArtifact(res, "striking-distance")
204
192
  };
205
193
  }
@@ -89,7 +89,6 @@ function buildStrikingSection(res, max) {
89
89
  findings,
90
90
  truncated: truncation(rows.length, kept.length),
91
91
  coverage: sectionCoverage(res),
92
- actions: [],
93
92
  artifact: sectionArtifact(res, "striking-distance")
94
93
  };
95
94
  }
@@ -117,17 +116,6 @@ function buildOpportunitySection(res, max) {
117
116
  })),
118
117
  truncated: truncation(rows.length, kept.length),
119
118
  coverage: sectionCoverage(res),
120
- actions: kept.slice(0, 1).map((r) => ({
121
- kind: "fix",
122
- target: r.page ? {
123
- kind: "page",
124
- value: r.page
125
- } : {
126
- kind: "query",
127
- value: r.keyword
128
- },
129
- rationale: "Rewrite title/description to lift CTR at this position"
130
- })),
131
119
  artifact: sectionArtifact(res, "opportunity")
132
120
  };
133
121
  }
@@ -154,7 +142,6 @@ function buildZeroClickSection(res, max) {
154
142
  findings,
155
143
  truncated: truncation(rows.length, kept.length),
156
144
  coverage: sectionCoverage(res),
157
- actions: [],
158
145
  artifact: sectionArtifact(res, "zero-click")
159
146
  };
160
147
  }
@@ -179,7 +166,6 @@ function buildMigrationSection(res, max) {
179
166
  })),
180
167
  truncated: truncation(rows.length, kept.length),
181
168
  coverage: sectionCoverage(res),
182
- actions: [],
183
169
  artifact: sectionArtifact(res, "query-migration")
184
170
  };
185
171
  }
@@ -76,10 +76,6 @@ function buildCannibalizationSection(res, matches, max) {
76
76
  findings,
77
77
  truncated: truncation(rows.length, kept.length),
78
78
  coverage: sectionCoverage(res),
79
- actions: kept.slice(0, 1).map(() => ({
80
- kind: "fix",
81
- rationale: "Decide before publishing: redirect existing page, target a different angle, or accept overlap."
82
- })),
83
79
  artifact: sectionArtifact(res, "cannibalization")
84
80
  };
85
81
  }
@@ -106,7 +102,6 @@ function buildStrikingPeersSection(res, matches, max, topic) {
106
102
  findings,
107
103
  truncated: truncation(rows.length, kept.length),
108
104
  coverage: sectionCoverage(res),
109
- actions: [],
110
105
  artifact: sectionArtifact(res, "striking-distance")
111
106
  };
112
107
  }
@@ -102,15 +102,6 @@ function buildDecaySection(res, max) {
102
102
  findings,
103
103
  truncated: truncation(rows.length, kept.length),
104
104
  coverage: sectionCoverage(res),
105
- actions: kept.slice(0, 1).map((r) => ({
106
- kind: "analyzer",
107
- target: {
108
- kind: "page",
109
- value: r.page
110
- },
111
- params: { type: "change-point" },
112
- rationale: "Investigate change-point on the worst-affected page"
113
- })),
114
105
  artifact: sectionArtifact(res, "decay")
115
106
  };
116
107
  }
@@ -140,7 +131,6 @@ function buildCannibalizationSection(res, max) {
140
131
  findings,
141
132
  truncated: truncation(rows.length, kept.length),
142
133
  coverage: sectionCoverage(res),
143
- actions: [],
144
134
  artifact: sectionArtifact(res, "cannibalization")
145
135
  };
146
136
  }
@@ -167,7 +157,6 @@ function buildDarkTrafficSection(res, max) {
167
157
  findings,
168
158
  truncated: truncation(rows.length, kept.length),
169
159
  coverage: sectionCoverage(res),
170
- actions: [],
171
160
  artifact: sectionArtifact(res, "dark-traffic")
172
161
  };
173
162
  }
@@ -194,7 +183,6 @@ function buildDeviceGapSection(res, max) {
194
183
  })),
195
184
  truncated: truncation(rows.length, kept.length),
196
185
  coverage: sectionCoverage(res),
197
- actions: [],
198
186
  artifact: sectionArtifact(res, "device-gap")
199
187
  };
200
188
  }
@@ -96,7 +96,6 @@ function buildChangePointSection(res, kind, matches, max) {
96
96
  findings,
97
97
  truncated: truncation(rows.length, kept.length),
98
98
  coverage: sectionCoverage(res),
99
- actions: [],
100
99
  artifact: sectionArtifact(res, "change-point")
101
100
  };
102
101
  }
@@ -122,7 +121,6 @@ function buildMigrationSection(res, kind, matches, max, _rawTarget) {
122
121
  findings,
123
122
  truncated: truncation(rows.length, kept.length),
124
123
  coverage: sectionCoverage(res),
125
- actions: [],
126
124
  artifact: sectionArtifact(res, "query-migration")
127
125
  };
128
126
  }
@@ -148,7 +146,6 @@ function buildVolatilitySection(res, kind, matches, max) {
148
146
  findings,
149
147
  truncated: truncation(rows.length, kept.length),
150
148
  coverage: sectionCoverage(res),
151
- actions: [],
152
149
  artifact: sectionArtifact(res, "position-volatility")
153
150
  };
154
151
  }
@@ -34,8 +34,7 @@ function unavailableSection(section, failedKeys) {
34
34
  severity: "unknown",
35
35
  summary: { magnitudeLabel: `unavailable — ${failedKeys.join(", ")} failed` },
36
36
  findings: [],
37
- coverage: "partial",
38
- actions: []
37
+ coverage: "partial"
39
38
  };
40
39
  }
41
40
  function markUnavailableSections(sections, steps, outcomes) {
package/dist/scoring.mjs CHANGED
@@ -1,15 +1,5 @@
1
- function clamp01(value) {
2
- if (value < 0) return 0;
3
- if (value > 1) return 1;
4
- return value;
5
- }
6
- function clamp(value, min, max) {
7
- if (value < min) return min;
8
- if (value > max) return max;
9
- return value;
10
- }
11
1
  function percentDifference(current, previous) {
12
2
  if (previous === 0) return current > 0 ? 100 : 0;
13
3
  return (current - previous) / previous * 100;
14
4
  }
15
- export { clamp, clamp01, percentDifference };
5
+ export { percentDifference };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/analysis",
3
3
  "type": "module",
4
- "version": "2.3.0",
4
+ "version": "3.1.0",
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",
@@ -36,6 +36,11 @@
36
36
  "import": "./dist/errors.mjs",
37
37
  "default": "./dist/errors.mjs"
38
38
  },
39
+ "./query-dimension": {
40
+ "types": "./dist/query-dimension.d.mts",
41
+ "import": "./dist/query-dimension.mjs",
42
+ "default": "./dist/query-dimension.mjs"
43
+ },
39
44
  "./report": {
40
45
  "types": "./dist/report/index.d.mts",
41
46
  "import": "./dist/report/index.mjs",
@@ -57,9 +62,9 @@
57
62
  },
58
63
  "dependencies": {
59
64
  "pluralize": "^8.0.0",
60
- "@gscdump/engine": "^2.3.0",
61
- "gscdump": "^2.3.0",
62
- "@gscdump/engine-gsc-api": "^2.3.0"
65
+ "@gscdump/engine": "^3.1.0",
66
+ "gscdump": "^3.1.0",
67
+ "@gscdump/engine-gsc-api": "^3.1.0"
63
68
  },
64
69
  "devDependencies": {
65
70
  "vitest": "^4.1.10"
@@ -1,34 +0,0 @@
1
- import { AnalysisResult } from "@gscdump/engine/analysis-types";
2
- type ActionSource = 'cannibalization' | 'striking-distance' | 'ctr-anomaly' | 'change-point' | 'opportunity';
3
- type Effort = 'low' | 'medium' | 'high';
4
- interface PriorityAction {
5
- id: string;
6
- title: string;
7
- keyword: string;
8
- page: string;
9
- sources: ActionSource[];
10
- severity: number;
11
- impressions: number;
12
- impact: number;
13
- why: string;
14
- effort: Effort;
15
- priorityScore: number;
16
- data: Partial<Record<ActionSource, Record<string, unknown>>>;
17
- }
18
- type ActionPrioritySourceStatus = 'pending' | 'running' | 'done' | 'skipped' | 'error';
19
- interface ActionPrioritySourceState {
20
- source: ActionSource;
21
- status: ActionPrioritySourceStatus;
22
- count: number;
23
- error?: string;
24
- }
25
- interface ActionPriorityResult {
26
- actions: PriorityAction[];
27
- totalSignals: number;
28
- sources: ActionPrioritySourceState[];
29
- }
30
- declare const DEFAULT_PRIORITY_SOURCES: ActionSource[];
31
- declare function normalizePriorityActions(source: ActionSource, result: AnalysisResult): PriorityAction[];
32
- declare function mergePriorityActions(all: PriorityAction[]): PriorityAction[];
33
- declare function scorePriorityActions(actions: PriorityAction[]): PriorityAction[];
34
- export { ActionPriorityResult, ActionPrioritySourceState, ActionPrioritySourceStatus, ActionSource, DEFAULT_PRIORITY_SOURCES, Effort, PriorityAction, mergePriorityActions, normalizePriorityActions, scorePriorityActions };
@@ -1,201 +0,0 @@
1
- import { clamp, clamp01 } from "./scoring.mjs";
2
- const DEFAULT_PRIORITY_SOURCES = [
3
- "striking-distance",
4
- "opportunity",
5
- "cannibalization",
6
- "ctr-anomaly",
7
- "change-point"
8
- ];
9
- const EFFORT_BY_SOURCE = {
10
- "striking-distance": "low",
11
- "opportunity": "low",
12
- "cannibalization": "medium",
13
- "ctr-anomaly": "high",
14
- "change-point": "high"
15
- };
16
- const EFFORT_MULTIPLIER = {
17
- low: 1.3,
18
- medium: 1,
19
- high: .7
20
- };
21
- const EFFORT_RANK = {
22
- low: 0,
23
- medium: 1,
24
- high: 2
25
- };
26
- function idKey(keyword, page) {
27
- return `${keyword.toLowerCase()}|${page.toLowerCase()}`;
28
- }
29
- function truncate(s, n) {
30
- return s.length <= n ? s : `${s.slice(0, n - 1)}...`;
31
- }
32
- function buildAction(spec) {
33
- return {
34
- id: idKey(spec.keyword, spec.page),
35
- title: spec.title,
36
- keyword: spec.keyword,
37
- page: spec.page,
38
- sources: [spec.source],
39
- severity: spec.severity,
40
- impressions: spec.impressions,
41
- impact: spec.impact,
42
- effort: EFFORT_BY_SOURCE[spec.source],
43
- why: spec.why,
44
- priorityScore: 0,
45
- data: { [spec.source]: spec.data }
46
- };
47
- }
48
- function fromStrikingDistance(rows) {
49
- const out = [];
50
- for (const r of rows) {
51
- if (r.page == null) continue;
52
- const impact = Math.max(0, r.potentialClicks);
53
- if (impact <= 0) continue;
54
- const posScore = clamp01((20 - r.position) / 16);
55
- const imprScore = Math.min(1, r.impressions / 5e3);
56
- out.push(buildAction({
57
- source: "striking-distance",
58
- keyword: r.keyword,
59
- page: r.page,
60
- title: `Push "${truncate(r.keyword, 40)}" onto page 1`,
61
- why: `Ranks #${r.position.toFixed(1)} with ${Math.round(r.impressions)} impressions; small gains unlock page-1 clicks.`,
62
- severity: Math.round(100 * Math.sqrt(posScore * imprScore)),
63
- impressions: r.impressions,
64
- impact,
65
- data: r
66
- }));
67
- }
68
- return out;
69
- }
70
- function fromOpportunity(rows) {
71
- const out = [];
72
- for (const r of rows) {
73
- if (r.page == null) continue;
74
- const impact = Math.max(0, r.potentialClicks);
75
- if (impact <= 0) continue;
76
- out.push(buildAction({
77
- source: "opportunity",
78
- keyword: r.keyword,
79
- page: r.page,
80
- title: `Improve on-page for "${truncate(r.keyword, 40)}"`,
81
- why: `Opportunity score ${Math.round(r.opportunityScore)}; CTR ${(r.ctr * 100).toFixed(1)}% vs expected at pos ${r.position.toFixed(1)}.`,
82
- severity: Math.round(r.opportunityScore),
83
- impressions: r.impressions,
84
- impact,
85
- data: r
86
- }));
87
- }
88
- return out;
89
- }
90
- function fromCannibalization(events) {
91
- const out = [];
92
- for (const ev of events) {
93
- if (ev.severity < 30) continue;
94
- out.push(buildAction({
95
- source: "cannibalization",
96
- keyword: ev.keyword,
97
- page: ev.leaderUrl,
98
- title: `Consolidate cannibalization on "${truncate(ev.keyword, 36)}"`,
99
- why: `${ev.competitorCount} URLs split ${Math.round(ev.totalImpressions)} impressions; leader loses ~${Math.round(ev.stolenClicks)} clicks to siblings.`,
100
- severity: Math.round(ev.severity),
101
- impressions: ev.totalImpressions,
102
- impact: Math.max(0, ev.stolenClicks),
103
- data: ev
104
- }));
105
- }
106
- return out;
107
- }
108
- function fromCtrAnomaly(rows) {
109
- const out = [];
110
- let maxRaw = 0;
111
- for (const r of rows) if (r.severity > maxRaw) maxRaw = r.severity;
112
- for (const r of rows) {
113
- const impact = Math.max(0, r.clicksLost);
114
- if (impact <= 0) continue;
115
- out.push(buildAction({
116
- source: "ctr-anomaly",
117
- keyword: r.keyword,
118
- page: r.page,
119
- title: `Lift CTR on "${truncate(r.keyword, 36)}"`,
120
- why: `CTR collapsed ${r.breachDaysDown} days at flat position; ~${Math.round(r.clicksLost)} clicks lost vs baseline ${(r.baselineCtr * 100).toFixed(1)}%.`,
121
- severity: maxRaw > 0 ? Math.round(r.severity / maxRaw * 100) : 0,
122
- impressions: r.totalImpressions,
123
- impact,
124
- data: r
125
- }));
126
- }
127
- return out;
128
- }
129
- function fromChangePoint(rows) {
130
- const out = [];
131
- for (const r of rows) {
132
- if (r.direction !== "worsened") continue;
133
- const days = Math.max(1, r.totalDays / 2);
134
- const impact = Math.abs(r.leftMean - r.rightMean) * days;
135
- if (impact <= 0) continue;
136
- out.push(buildAction({
137
- source: "change-point",
138
- keyword: r.keyword,
139
- page: r.page,
140
- title: `Diagnose drop on "${truncate(r.keyword, 34)}"`,
141
- why: `Significant regression around ${r.changeDate} (${r.leftMean.toFixed(1)} -> ${r.rightMean.toFixed(1)}, LLR ${r.llr.toFixed(0)}).`,
142
- severity: clamp(Math.round(Math.log10(Math.max(10, r.llr)) / 3 * 100), 0, 100),
143
- impressions: r.totalImpressions,
144
- impact,
145
- data: r
146
- }));
147
- }
148
- return out;
149
- }
150
- function normalizePriorityActions(source, result) {
151
- const rows = result.results;
152
- if (source === "striking-distance") return fromStrikingDistance(rows);
153
- if (source === "opportunity") return fromOpportunity(rows);
154
- if (source === "cannibalization") return fromCannibalization(rows);
155
- if (source === "ctr-anomaly") return fromCtrAnomaly(rows);
156
- return fromChangePoint(rows);
157
- }
158
- function mergePriorityActions(all) {
159
- const byId = /* @__PURE__ */ new Map();
160
- for (const a of all) {
161
- const existing = byId.get(a.id);
162
- if (existing == null) {
163
- byId.set(a.id, {
164
- ...a,
165
- sources: [...a.sources],
166
- data: { ...a.data }
167
- });
168
- continue;
169
- }
170
- const mergedSources = [.../* @__PURE__ */ new Set([...existing.sources, ...a.sources])];
171
- const preferNew = a.severity > existing.severity;
172
- const mergedEffort = EFFORT_RANK[a.effort] < EFFORT_RANK[existing.effort] ? a.effort : existing.effort;
173
- byId.set(a.id, {
174
- id: existing.id,
175
- title: preferNew ? a.title : existing.title,
176
- keyword: existing.keyword,
177
- page: existing.page,
178
- sources: mergedSources,
179
- severity: Math.max(existing.severity, a.severity),
180
- impressions: Math.max(existing.impressions, a.impressions),
181
- impact: existing.impact + a.impact,
182
- why: preferNew ? a.why : existing.why,
183
- effort: mergedEffort,
184
- priorityScore: 0,
185
- data: {
186
- ...existing.data,
187
- ...a.data
188
- }
189
- });
190
- }
191
- return [...byId.values()];
192
- }
193
- function scorePriorityActions(actions) {
194
- for (const a of actions) {
195
- const mult = EFFORT_MULTIPLIER[a.effort];
196
- a.priorityScore = a.impact * (1 + a.severity / 100) * mult;
197
- }
198
- actions.sort((a, b) => b.priorityScore - a.priorityScore);
199
- return actions;
200
- }
201
- export { DEFAULT_PRIORITY_SOURCES, mergePriorityActions, normalizePriorityActions, scorePriorityActions };
@@ -1,98 +0,0 @@
1
- import { DEFAULT_PRIORITY_SOURCES, mergePriorityActions, normalizePriorityActions, scorePriorityActions } from "../../action-priority.mjs";
2
- import { defineReport } from "@gscdump/engine/report";
3
- const DEFAULT_LIMIT = 40;
4
- const DEFAULT_ACTIONS = 10;
5
- const priorityReport = defineReport({
6
- id: "priority",
7
- description: "Ranked priority actions composed from striking-distance, opportunity, cannibalization, ctr-anomaly, and change-point signals.",
8
- defaultPeriod: "last-28d",
9
- defaultComparison: "prev-period",
10
- argsSpec: {
11
- "limit": {
12
- type: "number",
13
- description: "Max actions in the section",
14
- default: DEFAULT_LIMIT
15
- },
16
- "max-actions": {
17
- type: "number",
18
- description: "Top-N rendered as ReportAction",
19
- default: DEFAULT_ACTIONS
20
- }
21
- },
22
- plan: (_params, window) => {
23
- const dates = {
24
- startDate: window.start,
25
- endDate: window.end
26
- };
27
- const cmp = window.comparison ? {
28
- prevStartDate: window.comparison.start,
29
- prevEndDate: window.comparison.end
30
- } : {};
31
- return DEFAULT_PRIORITY_SOURCES.map((source) => ({
32
- key: source,
33
- type: source,
34
- params: {
35
- ...dates,
36
- ...cmp,
37
- limit: 100
38
- },
39
- required: false,
40
- feeds: ["priority"]
41
- }));
42
- },
43
- reduce: (results, ctx) => {
44
- const limit = ctx.params.limit ?? DEFAULT_LIMIT;
45
- const maxActions = ctx.params.maxActions ?? DEFAULT_ACTIONS;
46
- const all = [];
47
- let anyMissing = false;
48
- for (const source of DEFAULT_PRIORITY_SOURCES) {
49
- const r = results[source];
50
- if (!r) {
51
- anyMissing = true;
52
- continue;
53
- }
54
- all.push(...normalizePriorityActions(source, r));
55
- }
56
- const ranked = scorePriorityActions(mergePriorityActions(all)).slice(0, limit);
57
- const findings = ranked.map((a) => ({
58
- entity: {
59
- kind: "query",
60
- value: a.keyword
61
- },
62
- metrics: {
63
- priorityScore: a.priorityScore,
64
- severity: a.severity,
65
- impact: a.impact,
66
- impressions: a.impressions
67
- },
68
- why: `${a.title} — ${a.why} (page: ${a.page})`
69
- }));
70
- const actions = ranked.slice(0, maxActions).map((a) => {
71
- const primarySource = a.sources[0];
72
- return {
73
- kind: primarySource === "cannibalization" ? "fix" : "analyzer",
74
- target: {
75
- kind: "page",
76
- value: a.page
77
- },
78
- params: { type: primarySource },
79
- rationale: a.title
80
- };
81
- });
82
- const topSeverity = ranked[0]?.severity ?? 0;
83
- return { sections: [{
84
- id: "priority",
85
- title: "Priority actions",
86
- severity: topSeverity >= 70 ? "high" : topSeverity >= 40 ? "medium" : ranked.length ? "low" : "info",
87
- summary: { magnitudeLabel: `${ranked.length} actions ranked` },
88
- findings,
89
- truncated: all.length > ranked.length ? {
90
- kept: ranked.length,
91
- total: all.length
92
- } : void 0,
93
- coverage: anyMissing ? "partial" : "full",
94
- actions
95
- }] };
96
- }
97
- });
98
- export { priorityReport };