@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.
- package/README.md +14 -11
- package/dist/analyzer/all.mjs +9 -9
- package/dist/analyzers/bipartite-pagerank.mjs +1 -1
- package/dist/analyzers/brand.mjs +2 -2
- package/dist/analyzers/cannibalization.mjs +2 -2
- package/dist/analyzers/clustering.mjs +2 -2
- package/dist/analyzers/concentration.mjs +2 -2
- package/dist/analyzers/dark-traffic.mjs +1 -1
- package/dist/analyzers/decay.mjs +2 -2
- package/dist/analyzers/device-gap.mjs +1 -1
- package/dist/analyzers/intent-atlas.mjs +1 -1
- package/dist/analyzers/keyword-breadth.mjs +1 -1
- package/dist/analyzers/long-tail.mjs +1 -1
- package/dist/analyzers/movers.mjs +3 -3
- package/dist/analyzers/opportunity.mjs +1 -1
- package/dist/analyzers/position-distribution.mjs +1 -1
- package/dist/analyzers/position-volatility.mjs +1 -1
- package/dist/analyzers/query-migration.mjs +1 -1
- package/dist/analyzers/seasonality.mjs +2 -2
- package/dist/analyzers/stl-decompose.mjs +1 -1
- package/dist/analyzers/striking-distance.mjs +1 -1
- package/dist/analyzers/survival.mjs +1 -1
- package/dist/analyzers/trends.mjs +1 -1
- package/dist/analyzers/zero-click.mjs +1 -1
- package/dist/browser.d.mts +1 -1
- package/dist/index.d.mts +3 -15
- package/dist/index.mjs +4 -16
- package/dist/query/index.mjs +1 -1
- package/dist/query-dimension.d.mts +3 -0
- package/dist/query-dimension.mjs +3 -0
- package/dist/report/format.mjs +0 -5
- package/dist/report/registry.mjs +0 -2
- package/dist/report/reports/brand.mjs +0 -2
- package/dist/report/reports/growth.mjs +0 -4
- package/dist/report/reports/health.mjs +0 -3
- package/dist/report/reports/movers.mjs +1 -13
- package/dist/report/reports/opportunities.mjs +0 -14
- package/dist/report/reports/pre-publish.mjs +0 -5
- package/dist/report/reports/risks.mjs +0 -12
- package/dist/report/reports/triage.mjs +0 -3
- package/dist/report/runtime.mjs +1 -2
- package/dist/scoring.mjs +1 -11
- package/package.json +9 -4
- package/dist/action-priority.d.mts +0 -34
- package/dist/action-priority.mjs +0 -201
- 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` |
|
|
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
|
|
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('
|
|
55
|
-
const window = resolveWindow({ preset: 'last-28d', comparison: '
|
|
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 —
|
|
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
|
|
178
|
+
| Source factories under `@gscdump/analysis/source` | Public |
|
|
176
179
|
| Per-analyzer modules under `analysis/src/analyzers/<name>` | Private |
|
|
177
180
|
|
|
178
181
|
## Related
|
package/dist/analyzer/all.mjs
CHANGED
|
@@ -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({
|
package/dist/analyzers/brand.mjs
CHANGED
|
@@ -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) {
|
package/dist/analyzers/decay.mjs
CHANGED
|
@@ -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 = {}) {
|
package/dist/browser.d.mts
CHANGED
|
@@ -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 {
|
|
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,
|
|
33
|
-
export { type
|
|
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 {
|
|
19
|
-
import {
|
|
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 {
|
|
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,
|
|
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 };
|
package/dist/query/index.mjs
CHANGED
|
@@ -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 };
|
package/dist/report/format.mjs
CHANGED
|
@@ -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
|
}
|
package/dist/report/registry.mjs
CHANGED
|
@@ -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
|
}
|
package/dist/report/runtime.mjs
CHANGED
|
@@ -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 {
|
|
5
|
+
export { percentDifference };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/analysis",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
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": "^
|
|
61
|
-
"gscdump": "^
|
|
62
|
-
"@gscdump/engine-gsc-api": "^
|
|
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 };
|
package/dist/action-priority.mjs
DELETED
|
@@ -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 };
|