@gscdump/analysis 0.25.14 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzer/index.mjs +65 -4
- package/dist/default-registry.mjs +65 -4
- package/dist/errors.d.mts +52 -0
- package/dist/errors.mjs +69 -0
- package/dist/index.d.mts +75 -6
- package/dist/index.mjs +119 -27
- package/dist/report/index.d.mts +53 -6
- package/dist/report/index.mjs +95 -23
- package/package.json +9 -4
package/dist/analyzer/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { comparisonOf, defaultEndDate, padTimeseries, periodOf } from "@gscdump/
|
|
|
3
3
|
import { enumeratePartitions } from "@gscdump/engine/planner";
|
|
4
4
|
import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
|
|
5
5
|
import { num } from "@gscdump/engine/analysis-types";
|
|
6
|
+
import { err, ok, unwrapResult } from "gscdump/result";
|
|
6
7
|
import { between, date, extractDateRange, gsc, page, query } from "gscdump/query";
|
|
7
8
|
import { MS_PER_DAY, daysAgo, toIsoDate } from "gscdump";
|
|
8
9
|
import { buildExtrasQueries, buildTotalsSql, mergeExtras, resolveComparisonSQL, resolveToSQL, resolveToSQLOptimized } from "@gscdump/engine/resolver";
|
|
@@ -445,12 +446,72 @@ function pagesQueryState(period, limit = DEFAULT_LIMIT$1) {
|
|
|
445
446
|
function datesQueryState(period, limit = DEFAULT_LIMIT$1) {
|
|
446
447
|
return gsc.select(date).where(between(date, period.startDate, period.endDate)).limit(limit).getState();
|
|
447
448
|
}
|
|
449
|
+
const analysisErrors = {
|
|
450
|
+
missingReportParam(report, param, message) {
|
|
451
|
+
return {
|
|
452
|
+
kind: "missing-report-param",
|
|
453
|
+
report,
|
|
454
|
+
param,
|
|
455
|
+
message
|
|
456
|
+
};
|
|
457
|
+
},
|
|
458
|
+
missingComparisonWindow(report, message) {
|
|
459
|
+
return {
|
|
460
|
+
kind: "missing-comparison-window",
|
|
461
|
+
report,
|
|
462
|
+
message
|
|
463
|
+
};
|
|
464
|
+
},
|
|
465
|
+
missingBrandTerms() {
|
|
466
|
+
return {
|
|
467
|
+
kind: "missing-brand-terms",
|
|
468
|
+
message: "Brand analysis requires brandTerms"
|
|
469
|
+
};
|
|
470
|
+
},
|
|
471
|
+
unknownReport(report, available) {
|
|
472
|
+
return {
|
|
473
|
+
kind: "unknown-report",
|
|
474
|
+
report,
|
|
475
|
+
available,
|
|
476
|
+
message: `unknown report "${report}"; available: ${available.join(", ")}`
|
|
477
|
+
};
|
|
478
|
+
},
|
|
479
|
+
unknownAnalyzer(analyzer) {
|
|
480
|
+
return {
|
|
481
|
+
kind: "unknown-analyzer",
|
|
482
|
+
analyzer,
|
|
483
|
+
message: `unknown analyzer "${analyzer}"`
|
|
484
|
+
};
|
|
485
|
+
},
|
|
486
|
+
requiredStepFailed(report, stepKey, stepError, cause) {
|
|
487
|
+
return {
|
|
488
|
+
kind: "required-step-failed",
|
|
489
|
+
report,
|
|
490
|
+
stepKey,
|
|
491
|
+
stepError,
|
|
492
|
+
cause,
|
|
493
|
+
message: `runReport(${report}): required step "${stepKey}" failed: ${stepError}`
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
function analysisErrorToException(error) {
|
|
498
|
+
const exception = new Error(error.message);
|
|
499
|
+
if ("cause" in error && error.cause !== void 0) exception.cause = error.cause;
|
|
500
|
+
exception.analysisError = error;
|
|
501
|
+
return exception;
|
|
502
|
+
}
|
|
448
503
|
function escapeRegexAlt(s) {
|
|
449
504
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
450
505
|
}
|
|
451
506
|
function str$21(v) {
|
|
452
507
|
return v == null ? "" : String(v);
|
|
453
508
|
}
|
|
509
|
+
function requireBrandTermsResult(brandTerms) {
|
|
510
|
+
return brandTerms?.length ? ok(brandTerms) : err(analysisErrors.missingBrandTerms());
|
|
511
|
+
}
|
|
512
|
+
function requireBrandTerms(brandTerms) {
|
|
513
|
+
return unwrapResult(requireBrandTermsResult(brandTerms), analysisErrorToException);
|
|
514
|
+
}
|
|
454
515
|
function analyzeBrandSegmentation(keywords, options) {
|
|
455
516
|
const { brandTerms, minImpressions = 10 } = options;
|
|
456
517
|
const lowerBrandTerms = brandTerms.map((t) => t.toLowerCase());
|
|
@@ -479,11 +540,11 @@ function analyzeBrandSegmentation(keywords, options) {
|
|
|
479
540
|
const brandAnalyzer = defineAnalyzer({
|
|
480
541
|
id: "brand",
|
|
481
542
|
buildSql(params) {
|
|
482
|
-
|
|
543
|
+
const brandTerms = requireBrandTerms(params.brandTerms);
|
|
483
544
|
const { startDate, endDate } = periodOf(params);
|
|
484
545
|
const minImpressions = params.minImpressions ?? 10;
|
|
485
546
|
const limit = params.limit ?? 1e4;
|
|
486
|
-
const regex = `(${
|
|
547
|
+
const regex = `(${brandTerms.map((t) => escapeRegexAlt(t.toLowerCase())).join("|")})`;
|
|
487
548
|
return {
|
|
488
549
|
sql: `
|
|
489
550
|
WITH agg AS (
|
|
@@ -558,9 +619,9 @@ const brandAnalyzer = defineAnalyzer({
|
|
|
558
619
|
return { queries: queriesQueryState(periodOf(params), params.limit) };
|
|
559
620
|
},
|
|
560
621
|
reduceRows(rows, params) {
|
|
561
|
-
|
|
622
|
+
const brandTerms = requireBrandTerms(params.brandTerms);
|
|
562
623
|
const result = analyzeBrandSegmentation(Array.isArray(rows) ? rows : [], {
|
|
563
|
-
brandTerms
|
|
624
|
+
brandTerms,
|
|
564
625
|
minImpressions: params.minImpressions
|
|
565
626
|
});
|
|
566
627
|
return {
|
|
@@ -3,6 +3,7 @@ import { comparisonOf, defaultEndDate, padTimeseries, periodOf } from "@gscdump/
|
|
|
3
3
|
import { enumeratePartitions } from "@gscdump/engine/planner";
|
|
4
4
|
import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
|
|
5
5
|
import { num } from "@gscdump/engine/analysis-types";
|
|
6
|
+
import { err, ok, unwrapResult } from "gscdump/result";
|
|
6
7
|
import { between, date, extractDateRange, gsc, page, query } from "gscdump/query";
|
|
7
8
|
import { MS_PER_DAY, daysAgo, toIsoDate } from "gscdump";
|
|
8
9
|
import { buildExtrasQueries, buildTotalsSql, mergeExtras, resolveComparisonSQL, resolveToSQL, resolveToSQLOptimized } from "@gscdump/engine/resolver";
|
|
@@ -445,12 +446,72 @@ function pagesQueryState(period, limit = DEFAULT_LIMIT$1) {
|
|
|
445
446
|
function datesQueryState(period, limit = DEFAULT_LIMIT$1) {
|
|
446
447
|
return gsc.select(date).where(between(date, period.startDate, period.endDate)).limit(limit).getState();
|
|
447
448
|
}
|
|
449
|
+
const analysisErrors = {
|
|
450
|
+
missingReportParam(report, param, message) {
|
|
451
|
+
return {
|
|
452
|
+
kind: "missing-report-param",
|
|
453
|
+
report,
|
|
454
|
+
param,
|
|
455
|
+
message
|
|
456
|
+
};
|
|
457
|
+
},
|
|
458
|
+
missingComparisonWindow(report, message) {
|
|
459
|
+
return {
|
|
460
|
+
kind: "missing-comparison-window",
|
|
461
|
+
report,
|
|
462
|
+
message
|
|
463
|
+
};
|
|
464
|
+
},
|
|
465
|
+
missingBrandTerms() {
|
|
466
|
+
return {
|
|
467
|
+
kind: "missing-brand-terms",
|
|
468
|
+
message: "Brand analysis requires brandTerms"
|
|
469
|
+
};
|
|
470
|
+
},
|
|
471
|
+
unknownReport(report, available) {
|
|
472
|
+
return {
|
|
473
|
+
kind: "unknown-report",
|
|
474
|
+
report,
|
|
475
|
+
available,
|
|
476
|
+
message: `unknown report "${report}"; available: ${available.join(", ")}`
|
|
477
|
+
};
|
|
478
|
+
},
|
|
479
|
+
unknownAnalyzer(analyzer) {
|
|
480
|
+
return {
|
|
481
|
+
kind: "unknown-analyzer",
|
|
482
|
+
analyzer,
|
|
483
|
+
message: `unknown analyzer "${analyzer}"`
|
|
484
|
+
};
|
|
485
|
+
},
|
|
486
|
+
requiredStepFailed(report, stepKey, stepError, cause) {
|
|
487
|
+
return {
|
|
488
|
+
kind: "required-step-failed",
|
|
489
|
+
report,
|
|
490
|
+
stepKey,
|
|
491
|
+
stepError,
|
|
492
|
+
cause,
|
|
493
|
+
message: `runReport(${report}): required step "${stepKey}" failed: ${stepError}`
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
function analysisErrorToException(error) {
|
|
498
|
+
const exception = new Error(error.message);
|
|
499
|
+
if ("cause" in error && error.cause !== void 0) exception.cause = error.cause;
|
|
500
|
+
exception.analysisError = error;
|
|
501
|
+
return exception;
|
|
502
|
+
}
|
|
448
503
|
function escapeRegexAlt(s) {
|
|
449
504
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
450
505
|
}
|
|
451
506
|
function str$21(v) {
|
|
452
507
|
return v == null ? "" : String(v);
|
|
453
508
|
}
|
|
509
|
+
function requireBrandTermsResult(brandTerms) {
|
|
510
|
+
return brandTerms?.length ? ok(brandTerms) : err(analysisErrors.missingBrandTerms());
|
|
511
|
+
}
|
|
512
|
+
function requireBrandTerms(brandTerms) {
|
|
513
|
+
return unwrapResult(requireBrandTermsResult(brandTerms), analysisErrorToException);
|
|
514
|
+
}
|
|
454
515
|
function analyzeBrandSegmentation(keywords, options) {
|
|
455
516
|
const { brandTerms, minImpressions = 10 } = options;
|
|
456
517
|
const lowerBrandTerms = brandTerms.map((t) => t.toLowerCase());
|
|
@@ -479,11 +540,11 @@ function analyzeBrandSegmentation(keywords, options) {
|
|
|
479
540
|
const brandAnalyzer = defineAnalyzer({
|
|
480
541
|
id: "brand",
|
|
481
542
|
buildSql(params) {
|
|
482
|
-
|
|
543
|
+
const brandTerms = requireBrandTerms(params.brandTerms);
|
|
483
544
|
const { startDate, endDate } = periodOf(params);
|
|
484
545
|
const minImpressions = params.minImpressions ?? 10;
|
|
485
546
|
const limit = params.limit ?? 1e4;
|
|
486
|
-
const regex = `(${
|
|
547
|
+
const regex = `(${brandTerms.map((t) => escapeRegexAlt(t.toLowerCase())).join("|")})`;
|
|
487
548
|
return {
|
|
488
549
|
sql: `
|
|
489
550
|
WITH agg AS (
|
|
@@ -558,9 +619,9 @@ const brandAnalyzer = defineAnalyzer({
|
|
|
558
619
|
return { queries: queriesQueryState(periodOf(params), params.limit) };
|
|
559
620
|
},
|
|
560
621
|
reduceRows(rows, params) {
|
|
561
|
-
|
|
622
|
+
const brandTerms = requireBrandTerms(params.brandTerms);
|
|
562
623
|
const result = analyzeBrandSegmentation(Array.isArray(rows) ? rows : [], {
|
|
563
|
-
brandTerms
|
|
624
|
+
brandTerms,
|
|
564
625
|
minImpressions: params.minImpressions
|
|
565
626
|
});
|
|
566
627
|
return {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
type AnalysisErrorKind = 'missing-report-param' | 'missing-comparison-window' | 'missing-brand-terms' | 'unknown-report' | 'unknown-analyzer' | 'required-step-failed';
|
|
2
|
+
type AnalysisError = {
|
|
3
|
+
kind: 'missing-report-param';
|
|
4
|
+
report: string;
|
|
5
|
+
param: string;
|
|
6
|
+
message: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'missing-comparison-window';
|
|
9
|
+
report: string;
|
|
10
|
+
message: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'missing-brand-terms';
|
|
13
|
+
message: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'unknown-report';
|
|
16
|
+
report: string;
|
|
17
|
+
available: readonly string[];
|
|
18
|
+
message: string;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'unknown-analyzer';
|
|
21
|
+
analyzer: string;
|
|
22
|
+
message: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'required-step-failed';
|
|
25
|
+
report: string;
|
|
26
|
+
stepKey: string;
|
|
27
|
+
stepError: string;
|
|
28
|
+
message: string;
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
};
|
|
31
|
+
declare const analysisErrors: {
|
|
32
|
+
readonly missingReportParam: (report: string, param: string, message: string) => AnalysisError;
|
|
33
|
+
readonly missingComparisonWindow: (report: string, message: string) => AnalysisError;
|
|
34
|
+
readonly missingBrandTerms: () => AnalysisError;
|
|
35
|
+
readonly unknownReport: (report: string, available: readonly string[]) => AnalysisError;
|
|
36
|
+
readonly unknownAnalyzer: (analyzer: string) => AnalysisError;
|
|
37
|
+
readonly requiredStepFailed: (report: string, stepKey: string, stepError: string, cause?: unknown) => AnalysisError;
|
|
38
|
+
};
|
|
39
|
+
declare function isAnalysisError(value: unknown): value is AnalysisError;
|
|
40
|
+
/** The human-readable rendering of an `AnalysisError`, for logs and string sinks. */
|
|
41
|
+
declare function formatAnalysisError(error: AnalysisError): string;
|
|
42
|
+
/**
|
|
43
|
+
* Re-raises an `AnalysisError` value as a generic `Error`, stashing the union
|
|
44
|
+
* under `.analysisError` for stack-walking and preserving the original `cause`
|
|
45
|
+
* (so a `required-step-failed` keeps the underlying thrown value reachable).
|
|
46
|
+
* Used by the throwing wrappers over the `Result`-returning cores. The thrown
|
|
47
|
+
* `.message` is kept verbatim from the union, so existing message-regex
|
|
48
|
+
* assertions (`/--target/`, `/comparison window/`, `/required step "k"/`)
|
|
49
|
+
* continue to match.
|
|
50
|
+
*/
|
|
51
|
+
declare function analysisErrorToException(error: AnalysisError): Error;
|
|
52
|
+
export { AnalysisError, AnalysisErrorKind, analysisErrorToException, analysisErrors, formatAnalysisError, isAnalysisError };
|
package/dist/errors.mjs
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const analysisErrors = {
|
|
2
|
+
missingReportParam(report, param, message) {
|
|
3
|
+
return {
|
|
4
|
+
kind: "missing-report-param",
|
|
5
|
+
report,
|
|
6
|
+
param,
|
|
7
|
+
message
|
|
8
|
+
};
|
|
9
|
+
},
|
|
10
|
+
missingComparisonWindow(report, message) {
|
|
11
|
+
return {
|
|
12
|
+
kind: "missing-comparison-window",
|
|
13
|
+
report,
|
|
14
|
+
message
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
missingBrandTerms() {
|
|
18
|
+
return {
|
|
19
|
+
kind: "missing-brand-terms",
|
|
20
|
+
message: "Brand analysis requires brandTerms"
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
unknownReport(report, available) {
|
|
24
|
+
return {
|
|
25
|
+
kind: "unknown-report",
|
|
26
|
+
report,
|
|
27
|
+
available,
|
|
28
|
+
message: `unknown report "${report}"; available: ${available.join(", ")}`
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
unknownAnalyzer(analyzer) {
|
|
32
|
+
return {
|
|
33
|
+
kind: "unknown-analyzer",
|
|
34
|
+
analyzer,
|
|
35
|
+
message: `unknown analyzer "${analyzer}"`
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
requiredStepFailed(report, stepKey, stepError, cause) {
|
|
39
|
+
return {
|
|
40
|
+
kind: "required-step-failed",
|
|
41
|
+
report,
|
|
42
|
+
stepKey,
|
|
43
|
+
stepError,
|
|
44
|
+
cause,
|
|
45
|
+
message: `runReport(${report}): required step "${stepKey}" failed: ${stepError}`
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const ANALYSIS_ERROR_KINDS = new Set([
|
|
50
|
+
"missing-report-param",
|
|
51
|
+
"missing-comparison-window",
|
|
52
|
+
"missing-brand-terms",
|
|
53
|
+
"unknown-report",
|
|
54
|
+
"unknown-analyzer",
|
|
55
|
+
"required-step-failed"
|
|
56
|
+
]);
|
|
57
|
+
function isAnalysisError(value) {
|
|
58
|
+
return typeof value === "object" && value !== null && ANALYSIS_ERROR_KINDS.has(value.kind) && typeof value.message === "string";
|
|
59
|
+
}
|
|
60
|
+
function formatAnalysisError(error) {
|
|
61
|
+
return error.message;
|
|
62
|
+
}
|
|
63
|
+
function analysisErrorToException(error) {
|
|
64
|
+
const exception = new Error(error.message);
|
|
65
|
+
if ("cause" in error && error.cause !== void 0) exception.cause = error.cause;
|
|
66
|
+
exception.analysisError = error;
|
|
67
|
+
return exception;
|
|
68
|
+
}
|
|
69
|
+
export { analysisErrorToException, analysisErrors, formatAnalysisError, isAnalysisError };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Analyzer, Analyzer as Analyzer$1, AnalyzerCapabilityError, AnalyzerRegistry, AnalyzerRegistry as AnalyzerRegistry$1, AnalyzerRegistryInit, AnalyzerVariants, DefineAnalyzerOptions, DefinedAnalyzer, Plan, ReduceContext, ReduceCtx, Reducer, RequiredCapability, RowQueriesPlan, SqlExtraQuery, SqlPlan, SqlPlanSpec, TypedRowQuery, createAnalyzerRegistry, defineAnalyzer, runAnalyzerFromSource } from "@gscdump/engine/analyzer";
|
|
2
2
|
import { AnalysisPeriod, ComparisonMode, ComparisonPeriod, PadTimeseriesOptions, ResolveWindowOptions, ResolvedWindow, WindowPreset, comparisonOf, padTimeseries, periodOf, resolveWindow, windowToComparisonPeriod, windowToPeriod } from "@gscdump/engine/period";
|
|
3
3
|
import { AnalysisParams, AnalysisParams as AnalysisParams$1, AnalysisResult, AnalysisResult as AnalysisResult$1, AnalysisTool, num } from "@gscdump/engine/analysis-types";
|
|
4
|
+
import { Result } from "gscdump/result";
|
|
4
5
|
import { BuilderState } from "gscdump/query";
|
|
5
6
|
import { AnalysisQuerySource, AnalysisQuerySource as AnalysisQuerySource$1, AnalysisSourceKind, AttachedTableRunner, AttachedTableRunner as AnalyzerRunner, AttachedTableSourceOptions, AttachedTableSourceOptions as BrowserAnalyzeOptions, ENGINE_QUERY_CAPABILITIES, EngineQuerySourceOptions, ExecuteSqlOptions, FileSet, QueryRow, QueryRow as QueryRow$1, SourceCapabilities, TypedQuery, createEngineQuerySource, queryComparisonRows, queryRows, rewriteForTableSource, runAnalyzerWithEngine, typedQuery } from "@gscdump/engine/source";
|
|
6
7
|
import { DefineReportOptions, DefinedReport, DefinedReport as DefinedReport$1, ReportAction, ReportContext, ReportContext as ReportContext$1, ReportFinding, ReportParams, ReportPlanStep, ReportResult, ReportResult as ReportResult$1, ReportSection } from "@gscdump/engine/report";
|
|
@@ -388,6 +389,57 @@ declare function analyzeInBrowser(runner: AttachedTableRunner, opts: AttachedTab
|
|
|
388
389
|
* with the flat `ROW_ANALYZERS` / `SQL_ANALYZERS` arrays.
|
|
389
390
|
*/
|
|
390
391
|
declare const defaultAnalyzerRegistry: import("@gscdump/engine/analyzer").AnalyzerRegistry;
|
|
392
|
+
type AnalysisErrorKind = 'missing-report-param' | 'missing-comparison-window' | 'missing-brand-terms' | 'unknown-report' | 'unknown-analyzer' | 'required-step-failed';
|
|
393
|
+
type AnalysisError = {
|
|
394
|
+
kind: 'missing-report-param';
|
|
395
|
+
report: string;
|
|
396
|
+
param: string;
|
|
397
|
+
message: string;
|
|
398
|
+
} | {
|
|
399
|
+
kind: 'missing-comparison-window';
|
|
400
|
+
report: string;
|
|
401
|
+
message: string;
|
|
402
|
+
} | {
|
|
403
|
+
kind: 'missing-brand-terms';
|
|
404
|
+
message: string;
|
|
405
|
+
} | {
|
|
406
|
+
kind: 'unknown-report';
|
|
407
|
+
report: string;
|
|
408
|
+
available: readonly string[];
|
|
409
|
+
message: string;
|
|
410
|
+
} | {
|
|
411
|
+
kind: 'unknown-analyzer';
|
|
412
|
+
analyzer: string;
|
|
413
|
+
message: string;
|
|
414
|
+
} | {
|
|
415
|
+
kind: 'required-step-failed';
|
|
416
|
+
report: string;
|
|
417
|
+
stepKey: string;
|
|
418
|
+
stepError: string;
|
|
419
|
+
message: string;
|
|
420
|
+
cause?: unknown;
|
|
421
|
+
};
|
|
422
|
+
declare const analysisErrors: {
|
|
423
|
+
readonly missingReportParam: (report: string, param: string, message: string) => AnalysisError;
|
|
424
|
+
readonly missingComparisonWindow: (report: string, message: string) => AnalysisError;
|
|
425
|
+
readonly missingBrandTerms: () => AnalysisError;
|
|
426
|
+
readonly unknownReport: (report: string, available: readonly string[]) => AnalysisError;
|
|
427
|
+
readonly unknownAnalyzer: (analyzer: string) => AnalysisError;
|
|
428
|
+
readonly requiredStepFailed: (report: string, stepKey: string, stepError: string, cause?: unknown) => AnalysisError;
|
|
429
|
+
};
|
|
430
|
+
declare function isAnalysisError(value: unknown): value is AnalysisError;
|
|
431
|
+
/** The human-readable rendering of an `AnalysisError`, for logs and string sinks. */
|
|
432
|
+
declare function formatAnalysisError(error: AnalysisError): string;
|
|
433
|
+
/**
|
|
434
|
+
* Re-raises an `AnalysisError` value as a generic `Error`, stashing the union
|
|
435
|
+
* under `.analysisError` for stack-walking and preserving the original `cause`
|
|
436
|
+
* (so a `required-step-failed` keeps the underlying thrown value reachable).
|
|
437
|
+
* Used by the throwing wrappers over the `Result`-returning cores. The thrown
|
|
438
|
+
* `.message` is kept verbatim from the union, so existing message-regex
|
|
439
|
+
* assertions (`/--target/`, `/comparison window/`, `/required step "k"/`)
|
|
440
|
+
* continue to match.
|
|
441
|
+
*/
|
|
442
|
+
declare function analysisErrorToException(error: AnalysisError): Error;
|
|
391
443
|
/**
|
|
392
444
|
* Produce a canonical form of a search query for grouping near-duplicates.
|
|
393
445
|
* Idempotent: `normalizeQuery(normalizeQuery(q)) === normalizeQuery(q)`.
|
|
@@ -406,11 +458,28 @@ interface RunReportOptions<P extends ReportParams = ReportParams> {
|
|
|
406
458
|
ctx: ReportContext$1<P>;
|
|
407
459
|
}
|
|
408
460
|
/**
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
461
|
+
* `Result`-returning core for {@link runReport}. Models the one
|
|
462
|
+
* caller-actionable failure of a structurally-valid report run: a required
|
|
463
|
+
* step's analyzer threw (`required-step-failed`, with the underlying error as
|
|
464
|
+
* `cause`). Hosts can map that to a 4xx/partial response instead of catching an
|
|
465
|
+
* untyped `Error`.
|
|
466
|
+
*
|
|
467
|
+
* Steps execute in parallel via `Promise.all`. The report's `reduce` is invoked
|
|
468
|
+
* with a results bag that only contains successful steps — sections that
|
|
469
|
+
* depended on a failed step should set their own `coverage: 'partial'` (the
|
|
470
|
+
* runtime additionally marks `meta.degraded` when any step errored).
|
|
471
|
+
*
|
|
472
|
+
* The report's own `plan()` param-validation throws (`--target` etc.) are
|
|
473
|
+
* defects from this core's perspective and still propagate; those are modelled
|
|
474
|
+
* at the report-definition boundary, not here.
|
|
475
|
+
*/
|
|
476
|
+
declare function runReportResult<P extends ReportParams = ReportParams>(report: DefinedReport$1<P>, opts: RunReportOptions<P>): Promise<Result<ReportResult$1, AnalysisError>>;
|
|
477
|
+
/**
|
|
478
|
+
* Throwing wrapper over {@link runReportResult}, preserving the historical
|
|
479
|
+
* call-site ergonomics (a required-step failure rejects). The thrown message is
|
|
480
|
+
* kept verbatim (`runReport(id): required step "k" failed: ...`) so existing
|
|
481
|
+
* assertions hold; the typed `AnalysisError` is reachable via `.analysisError`
|
|
482
|
+
* and the original failure via `.cause`.
|
|
414
483
|
*/
|
|
415
484
|
declare function runReport<P extends ReportParams = ReportParams>(report: DefinedReport$1<P>, opts: RunReportOptions<P>): Promise<ReportResult$1>;
|
|
416
485
|
interface DryRunReportResult {
|
|
@@ -498,4 +567,4 @@ interface InMemoryQuerySourceOptions {
|
|
|
498
567
|
}
|
|
499
568
|
declare function createInMemoryQuerySource(options: InMemoryQuerySourceOptions): AnalysisQuerySource$1;
|
|
500
569
|
declare const SQL_ANALYZERS: readonly Analyzer$1[];
|
|
501
|
-
export { type ActionPriorityResult, type ActionPrioritySourceState, type ActionPrioritySourceStatus, type ActionSource, 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 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, 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, IN_MEMORY_DEFAULT_CAPABILITIES, type InMemoryQuerySourceOptions, type KeywordCluster, type MonthlyData, type MoverData, type MoversInput, type MoversOptions, type MoversResult, type MoversSortMetric, type OpportunityResult, 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, SQL_ANALYZERS, type SeasonalityMetric, type SeasonalityOptions, type SeasonalityResult, type SitemapDelta, type SitemapHealthDiff, type SitemapHealthInput, type SitemapHealthRow, type SitemapHealthTotals, type SortOrder, type SourceCapabilities, type SqlExtraQuery, type SqlPlan, type SqlPlanSpec, type StrikingDistanceInputRow, type StrikingDistanceResult, type TypedQuery, type TypedRowQuery, type WindowPreset, type ZeroClickResult, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzePageConcentration, analyzeSeasonality, comparisonOf, createAnalyzerRegistry, createCompositeSource, createEngineQuerySource, createInMemoryQuerySource, createSorter, defaultAnalyzerRegistry, defaultReportRegistry, defineAnalyzer, diffSitemapHealth, dryRunReport, formatReport, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryComparisonRows, queryRows, resolveWindow, rewriteForTableSource, runAnalyzerFromSource, runAnalyzerWithEngine, runReport, scorePriorityActions, typedQuery, windowToComparisonPeriod, windowToPeriod };
|
|
570
|
+
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 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, 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, IN_MEMORY_DEFAULT_CAPABILITIES, type InMemoryQuerySourceOptions, type KeywordCluster, type MonthlyData, type MoverData, type MoversInput, type MoversOptions, type MoversResult, type MoversSortMetric, type OpportunityResult, 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, SQL_ANALYZERS, type SeasonalityMetric, type SeasonalityOptions, type SeasonalityResult, type SitemapDelta, type SitemapHealthDiff, type SitemapHealthInput, type SitemapHealthRow, type SitemapHealthTotals, type SortOrder, type SourceCapabilities, type SqlExtraQuery, type SqlPlan, type SqlPlanSpec, type StrikingDistanceInputRow, type StrikingDistanceResult, type TypedQuery, type TypedRowQuery, type WindowPreset, type ZeroClickResult, analysisErrorToException, analysisErrors, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzePageConcentration, analyzeSeasonality, comparisonOf, createAnalyzerRegistry, createCompositeSource, createEngineQuerySource, createInMemoryQuerySource, createSorter, defaultAnalyzerRegistry, defaultReportRegistry, defineAnalyzer, diffSitemapHealth, dryRunReport, formatAnalysisError, formatReport, isAnalysisError, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryComparisonRows, queryRows, resolveWindow, rewriteForTableSource, runAnalyzerFromSource, runAnalyzerWithEngine, runReport, runReportResult, scorePriorityActions, typedQuery, windowToComparisonPeriod, windowToPeriod };
|
package/dist/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { comparisonOf, comparisonOf as comparisonOf$1, defaultEndDate, padTimese
|
|
|
3
3
|
import { enumeratePartitions } from "@gscdump/engine/planner";
|
|
4
4
|
import { METRIC_EXPR } from "@gscdump/engine/sql-fragments";
|
|
5
5
|
import { num, num as num$1 } from "@gscdump/engine/analysis-types";
|
|
6
|
+
import { err, ok, unwrapResult } from "gscdump/result";
|
|
6
7
|
import { between, date, extractDateRange, gsc, page, query } from "gscdump/query";
|
|
7
8
|
import { MS_PER_DAY, daysAgo, toIsoDate } from "gscdump";
|
|
8
9
|
import { buildExtrasQueries, buildTotalsSql, mergeExtras, pgResolverAdapter, resolveComparisonSQL, resolveToSQL, resolveToSQLOptimized } from "@gscdump/engine/resolver";
|
|
@@ -662,12 +663,86 @@ function pagesQueryState(period, limit = DEFAULT_LIMIT$2) {
|
|
|
662
663
|
function datesQueryState(period, limit = DEFAULT_LIMIT$2) {
|
|
663
664
|
return gsc.select(date).where(between(date, period.startDate, period.endDate)).limit(limit).getState();
|
|
664
665
|
}
|
|
666
|
+
const analysisErrors = {
|
|
667
|
+
missingReportParam(report, param, message) {
|
|
668
|
+
return {
|
|
669
|
+
kind: "missing-report-param",
|
|
670
|
+
report,
|
|
671
|
+
param,
|
|
672
|
+
message
|
|
673
|
+
};
|
|
674
|
+
},
|
|
675
|
+
missingComparisonWindow(report, message) {
|
|
676
|
+
return {
|
|
677
|
+
kind: "missing-comparison-window",
|
|
678
|
+
report,
|
|
679
|
+
message
|
|
680
|
+
};
|
|
681
|
+
},
|
|
682
|
+
missingBrandTerms() {
|
|
683
|
+
return {
|
|
684
|
+
kind: "missing-brand-terms",
|
|
685
|
+
message: "Brand analysis requires brandTerms"
|
|
686
|
+
};
|
|
687
|
+
},
|
|
688
|
+
unknownReport(report, available) {
|
|
689
|
+
return {
|
|
690
|
+
kind: "unknown-report",
|
|
691
|
+
report,
|
|
692
|
+
available,
|
|
693
|
+
message: `unknown report "${report}"; available: ${available.join(", ")}`
|
|
694
|
+
};
|
|
695
|
+
},
|
|
696
|
+
unknownAnalyzer(analyzer) {
|
|
697
|
+
return {
|
|
698
|
+
kind: "unknown-analyzer",
|
|
699
|
+
analyzer,
|
|
700
|
+
message: `unknown analyzer "${analyzer}"`
|
|
701
|
+
};
|
|
702
|
+
},
|
|
703
|
+
requiredStepFailed(report, stepKey, stepError, cause) {
|
|
704
|
+
return {
|
|
705
|
+
kind: "required-step-failed",
|
|
706
|
+
report,
|
|
707
|
+
stepKey,
|
|
708
|
+
stepError,
|
|
709
|
+
cause,
|
|
710
|
+
message: `runReport(${report}): required step "${stepKey}" failed: ${stepError}`
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
const ANALYSIS_ERROR_KINDS = new Set([
|
|
715
|
+
"missing-report-param",
|
|
716
|
+
"missing-comparison-window",
|
|
717
|
+
"missing-brand-terms",
|
|
718
|
+
"unknown-report",
|
|
719
|
+
"unknown-analyzer",
|
|
720
|
+
"required-step-failed"
|
|
721
|
+
]);
|
|
722
|
+
function isAnalysisError(value) {
|
|
723
|
+
return typeof value === "object" && value !== null && ANALYSIS_ERROR_KINDS.has(value.kind) && typeof value.message === "string";
|
|
724
|
+
}
|
|
725
|
+
function formatAnalysisError(error) {
|
|
726
|
+
return error.message;
|
|
727
|
+
}
|
|
728
|
+
function analysisErrorToException(error) {
|
|
729
|
+
const exception = new Error(error.message);
|
|
730
|
+
if ("cause" in error && error.cause !== void 0) exception.cause = error.cause;
|
|
731
|
+
exception.analysisError = error;
|
|
732
|
+
return exception;
|
|
733
|
+
}
|
|
665
734
|
function escapeRegexAlt(s) {
|
|
666
735
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
667
736
|
}
|
|
668
737
|
function str$21(v) {
|
|
669
738
|
return v == null ? "" : String(v);
|
|
670
739
|
}
|
|
740
|
+
function requireBrandTermsResult(brandTerms) {
|
|
741
|
+
return brandTerms?.length ? ok(brandTerms) : err(analysisErrors.missingBrandTerms());
|
|
742
|
+
}
|
|
743
|
+
function requireBrandTerms(brandTerms) {
|
|
744
|
+
return unwrapResult(requireBrandTermsResult(brandTerms), analysisErrorToException);
|
|
745
|
+
}
|
|
671
746
|
function analyzeBrandSegmentation(keywords, options) {
|
|
672
747
|
const { brandTerms, minImpressions = 10 } = options;
|
|
673
748
|
const lowerBrandTerms = brandTerms.map((t) => t.toLowerCase());
|
|
@@ -696,11 +771,11 @@ function analyzeBrandSegmentation(keywords, options) {
|
|
|
696
771
|
const brandAnalyzer = defineAnalyzer$1({
|
|
697
772
|
id: "brand",
|
|
698
773
|
buildSql(params) {
|
|
699
|
-
|
|
774
|
+
const brandTerms = requireBrandTerms(params.brandTerms);
|
|
700
775
|
const { startDate, endDate } = periodOf$1(params);
|
|
701
776
|
const minImpressions = params.minImpressions ?? 10;
|
|
702
777
|
const limit = params.limit ?? 1e4;
|
|
703
|
-
const regex = `(${
|
|
778
|
+
const regex = `(${brandTerms.map((t) => escapeRegexAlt(t.toLowerCase())).join("|")})`;
|
|
704
779
|
return {
|
|
705
780
|
sql: `
|
|
706
781
|
WITH agg AS (
|
|
@@ -775,9 +850,9 @@ const brandAnalyzer = defineAnalyzer$1({
|
|
|
775
850
|
return { queries: queriesQueryState(periodOf$1(params), params.limit) };
|
|
776
851
|
},
|
|
777
852
|
reduceRows(rows, params) {
|
|
778
|
-
|
|
853
|
+
const brandTerms = requireBrandTerms(params.brandTerms);
|
|
779
854
|
const result = analyzeBrandSegmentation(Array.isArray(rows) ? rows : [], {
|
|
780
|
-
brandTerms
|
|
855
|
+
brandTerms,
|
|
781
856
|
minImpressions: params.minImpressions
|
|
782
857
|
});
|
|
783
858
|
return {
|
|
@@ -5347,6 +5422,18 @@ function formatNumber(n) {
|
|
|
5347
5422
|
if (Number.isInteger(n)) return String(n);
|
|
5348
5423
|
return n.toFixed(2);
|
|
5349
5424
|
}
|
|
5425
|
+
function requireReportParamResult(report, param, value, message) {
|
|
5426
|
+
return value && value.trim() ? ok(value) : err(analysisErrors.missingReportParam(report, param, message));
|
|
5427
|
+
}
|
|
5428
|
+
function requireReportParam(report, param, value, message) {
|
|
5429
|
+
return unwrapResult(requireReportParamResult(report, param, value, message), analysisErrorToException);
|
|
5430
|
+
}
|
|
5431
|
+
function requireComparisonWindowResult(report, window, message) {
|
|
5432
|
+
return window.comparison ? ok(window.comparison) : err(analysisErrors.missingComparisonWindow(report, message));
|
|
5433
|
+
}
|
|
5434
|
+
function requireComparisonWindow(report, window, message) {
|
|
5435
|
+
return unwrapResult(requireComparisonWindowResult(report, window, message), analysisErrorToException);
|
|
5436
|
+
}
|
|
5350
5437
|
const DEFAULT_MAX$7 = 5;
|
|
5351
5438
|
const brandReport = defineReport({
|
|
5352
5439
|
id: "brand",
|
|
@@ -5366,8 +5453,7 @@ const brandReport = defineReport({
|
|
|
5366
5453
|
}
|
|
5367
5454
|
},
|
|
5368
5455
|
plan: (params, window) => {
|
|
5369
|
-
|
|
5370
|
-
const brandTerms = params.brandTerms.split(",").map((t) => t.trim()).filter(Boolean);
|
|
5456
|
+
const brandTerms = requireReportParam("brand", "brand-terms", params.brandTerms, "brand report requires --brand-terms <comma,separated,list>").split(",").map((t) => t.trim()).filter(Boolean);
|
|
5371
5457
|
const dates = {
|
|
5372
5458
|
startDate: window.start,
|
|
5373
5459
|
endDate: window.end
|
|
@@ -5789,14 +5875,14 @@ const moversReport = defineReport({
|
|
|
5789
5875
|
}
|
|
5790
5876
|
},
|
|
5791
5877
|
plan: (_params, window) => {
|
|
5792
|
-
|
|
5878
|
+
const comparison = requireComparisonWindow("movers", window, "movers report requires a comparison window — pass --vs prev-period");
|
|
5793
5879
|
const cur = {
|
|
5794
5880
|
startDate: window.start,
|
|
5795
5881
|
endDate: window.end
|
|
5796
5882
|
};
|
|
5797
5883
|
const prev = {
|
|
5798
|
-
prevStartDate:
|
|
5799
|
-
prevEndDate:
|
|
5884
|
+
prevStartDate: comparison.start,
|
|
5885
|
+
prevEndDate: comparison.end
|
|
5800
5886
|
};
|
|
5801
5887
|
return [
|
|
5802
5888
|
{
|
|
@@ -6204,7 +6290,7 @@ const prePublishReport = defineReport({
|
|
|
6204
6290
|
}
|
|
6205
6291
|
},
|
|
6206
6292
|
plan: (params, window) => {
|
|
6207
|
-
|
|
6293
|
+
requireReportParam("pre-publish", "topic", params.topic, "pre-publish report requires --topic <topic-or-url>");
|
|
6208
6294
|
const dates = {
|
|
6209
6295
|
startDate: window.start,
|
|
6210
6296
|
endDate: window.end
|
|
@@ -6410,14 +6496,14 @@ const risksReport = defineReport({
|
|
|
6410
6496
|
default: DEFAULT_MAX$1
|
|
6411
6497
|
} },
|
|
6412
6498
|
plan: (_params, window) => {
|
|
6413
|
-
|
|
6499
|
+
const comparison = requireComparisonWindow("risks", window, "risks report requires a comparison window — pass --vs prev-period");
|
|
6414
6500
|
const dates = {
|
|
6415
6501
|
startDate: window.start,
|
|
6416
6502
|
endDate: window.end
|
|
6417
6503
|
};
|
|
6418
6504
|
const prev = {
|
|
6419
|
-
prevStartDate:
|
|
6420
|
-
prevEndDate:
|
|
6505
|
+
prevStartDate: comparison.start,
|
|
6506
|
+
prevEndDate: comparison.end
|
|
6421
6507
|
};
|
|
6422
6508
|
return [
|
|
6423
6509
|
{
|
|
@@ -6674,7 +6760,7 @@ const triageReport = defineReport({
|
|
|
6674
6760
|
}
|
|
6675
6761
|
},
|
|
6676
6762
|
plan: (params, window) => {
|
|
6677
|
-
|
|
6763
|
+
requireReportParam("triage", "target", params.target, "triage report requires --target <page-or-query>");
|
|
6678
6764
|
const dates = {
|
|
6679
6765
|
startDate: window.start,
|
|
6680
6766
|
endDate: window.end
|
|
@@ -6844,17 +6930,20 @@ async function executeStep(source, analyzers, step) {
|
|
|
6844
6930
|
status: "done"
|
|
6845
6931
|
},
|
|
6846
6932
|
result
|
|
6847
|
-
})).catch((
|
|
6848
|
-
const message =
|
|
6849
|
-
return {
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6933
|
+
})).catch((thrown) => {
|
|
6934
|
+
const message = thrown?.message ?? String(thrown);
|
|
6935
|
+
return {
|
|
6936
|
+
state: {
|
|
6937
|
+
key: step.key,
|
|
6938
|
+
type: step.type,
|
|
6939
|
+
status: "error",
|
|
6940
|
+
error: message
|
|
6941
|
+
},
|
|
6942
|
+
cause: thrown
|
|
6943
|
+
};
|
|
6855
6944
|
});
|
|
6856
6945
|
}
|
|
6857
|
-
async function
|
|
6946
|
+
async function runReportResult(report, opts) {
|
|
6858
6947
|
const startedAt = Date.now();
|
|
6859
6948
|
const generatedAt = new Date(startedAt).toISOString();
|
|
6860
6949
|
const inputHash = await computeInputHash({
|
|
@@ -6868,13 +6957,13 @@ async function runReport(report, opts) {
|
|
|
6868
6957
|
const outcomes = await Promise.all(steps.map((s) => executeStep(opts.source, opts.analyzers, s)));
|
|
6869
6958
|
const required = new Map(steps.filter((s) => s.required).map((s) => [s.key, s]));
|
|
6870
6959
|
const errored = outcomes.filter((o) => o.state.status === "error");
|
|
6871
|
-
for (const o of errored) if (required.has(o.state.key))
|
|
6960
|
+
for (const o of errored) if (required.has(o.state.key)) return err(analysisErrors.requiredStepFailed(report.id, o.state.key, o.state.error ?? "unknown error", o.cause));
|
|
6872
6961
|
const resultsByKey = {};
|
|
6873
6962
|
for (const o of outcomes) if (o.result) resultsByKey[o.state.key] = o.result;
|
|
6874
6963
|
const sections = report.reduce(resultsByKey, opts.ctx).sections;
|
|
6875
6964
|
const degraded = errored.length > 0;
|
|
6876
6965
|
const stepStates = outcomes.map((o) => o.state);
|
|
6877
|
-
return {
|
|
6966
|
+
return ok({
|
|
6878
6967
|
id: report.id,
|
|
6879
6968
|
site: opts.ctx.site,
|
|
6880
6969
|
inputHash,
|
|
@@ -6887,7 +6976,10 @@ async function runReport(report, opts) {
|
|
|
6887
6976
|
degraded,
|
|
6888
6977
|
steps: stepStates
|
|
6889
6978
|
}
|
|
6890
|
-
};
|
|
6979
|
+
});
|
|
6980
|
+
}
|
|
6981
|
+
async function runReport(report, opts) {
|
|
6982
|
+
return unwrapResult(await runReportResult(report, opts), analysisErrorToException);
|
|
6891
6983
|
}
|
|
6892
6984
|
async function dryRunReport(report, ctx) {
|
|
6893
6985
|
return {
|
|
@@ -6995,4 +7087,4 @@ function createInMemoryQuerySource(options) {
|
|
|
6995
7087
|
};
|
|
6996
7088
|
}
|
|
6997
7089
|
const SQL_ANALYZERS = ALL_ANALYZERS.flatMap((d) => d.sql ? [d.sql] : []);
|
|
6998
|
-
export { AnalyzerCapabilityError, DEFAULT_PRIORITY_SOURCES, ENGINE_QUERY_CAPABILITIES, IN_MEMORY_DEFAULT_CAPABILITIES, REPORTS, ROW_ANALYZERS, SQL_ANALYZERS, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzePageConcentration, analyzeSeasonality, comparisonOf, createAnalyzerRegistry, createCompositeSource, createEngineQuerySource, createInMemoryQuerySource, createSorter, defaultAnalyzerRegistry, defaultReportRegistry, defineAnalyzer, diffSitemapHealth, dryRunReport, formatReport, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryComparisonRows, queryRows, resolveWindow, rewriteForTableSource, runAnalyzerFromSource, runAnalyzerWithEngine, runReport, scorePriorityActions, typedQuery, windowToComparisonPeriod, windowToPeriod };
|
|
7090
|
+
export { AnalyzerCapabilityError, DEFAULT_PRIORITY_SOURCES, ENGINE_QUERY_CAPABILITIES, IN_MEMORY_DEFAULT_CAPABILITIES, REPORTS, ROW_ANALYZERS, SQL_ANALYZERS, analysisErrorToException, analysisErrors, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzePageConcentration, analyzeSeasonality, comparisonOf, createAnalyzerRegistry, createCompositeSource, createEngineQuerySource, createInMemoryQuerySource, createSorter, defaultAnalyzerRegistry, defaultReportRegistry, defineAnalyzer, diffSitemapHealth, dryRunReport, formatAnalysisError, formatReport, isAnalysisError, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryComparisonRows, queryRows, resolveWindow, rewriteForTableSource, runAnalyzerFromSource, runAnalyzerWithEngine, runReport, runReportResult, scorePriorityActions, typedQuery, windowToComparisonPeriod, windowToPeriod };
|
package/dist/report/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DefinedReport, ReportContext, ReportParams, ReportResult } from "@gscdump/engine/report";
|
|
2
|
+
import { Result } from "gscdump/result";
|
|
2
3
|
import { AnalyzerRegistry } from "@gscdump/engine/analyzer";
|
|
3
4
|
import { AnalysisQuerySource } from "@gscdump/engine/source";
|
|
4
5
|
interface FormatReportOptions {
|
|
@@ -36,17 +37,63 @@ interface ResolveTargetResult {
|
|
|
36
37
|
unresolved: boolean;
|
|
37
38
|
}
|
|
38
39
|
declare function resolveTarget(opts: ResolveTargetInput): ResolveTargetResult;
|
|
40
|
+
type AnalysisError = {
|
|
41
|
+
kind: 'missing-report-param';
|
|
42
|
+
report: string;
|
|
43
|
+
param: string;
|
|
44
|
+
message: string;
|
|
45
|
+
} | {
|
|
46
|
+
kind: 'missing-comparison-window';
|
|
47
|
+
report: string;
|
|
48
|
+
message: string;
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'missing-brand-terms';
|
|
51
|
+
message: string;
|
|
52
|
+
} | {
|
|
53
|
+
kind: 'unknown-report';
|
|
54
|
+
report: string;
|
|
55
|
+
available: readonly string[];
|
|
56
|
+
message: string;
|
|
57
|
+
} | {
|
|
58
|
+
kind: 'unknown-analyzer';
|
|
59
|
+
analyzer: string;
|
|
60
|
+
message: string;
|
|
61
|
+
} | {
|
|
62
|
+
kind: 'required-step-failed';
|
|
63
|
+
report: string;
|
|
64
|
+
stepKey: string;
|
|
65
|
+
stepError: string;
|
|
66
|
+
message: string;
|
|
67
|
+
cause?: unknown;
|
|
68
|
+
};
|
|
39
69
|
interface RunReportOptions<P extends ReportParams = ReportParams> {
|
|
40
70
|
source: AnalysisQuerySource;
|
|
41
71
|
analyzers: AnalyzerRegistry;
|
|
42
72
|
ctx: ReportContext<P>;
|
|
43
73
|
}
|
|
44
74
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
75
|
+
* `Result`-returning core for {@link runReport}. Models the one
|
|
76
|
+
* caller-actionable failure of a structurally-valid report run: a required
|
|
77
|
+
* step's analyzer threw (`required-step-failed`, with the underlying error as
|
|
78
|
+
* `cause`). Hosts can map that to a 4xx/partial response instead of catching an
|
|
79
|
+
* untyped `Error`.
|
|
80
|
+
*
|
|
81
|
+
* Steps execute in parallel via `Promise.all`. The report's `reduce` is invoked
|
|
82
|
+
* with a results bag that only contains successful steps — sections that
|
|
83
|
+
* depended on a failed step should set their own `coverage: 'partial'` (the
|
|
84
|
+
* runtime additionally marks `meta.degraded` when any step errored).
|
|
85
|
+
*
|
|
86
|
+
* The report's own `plan()` param-validation throws (`--target` etc.) are
|
|
87
|
+
* defects from this core's perspective and still propagate; those are modelled
|
|
88
|
+
* at the report-definition boundary, not here.
|
|
89
|
+
*/
|
|
90
|
+
declare function runReportResult<P extends ReportParams = ReportParams>(report: DefinedReport<P>, opts: RunReportOptions<P>): Promise<Result<ReportResult, AnalysisError>>;
|
|
91
|
+
/**
|
|
92
|
+
* Throwing wrapper over {@link runReportResult}, preserving the historical
|
|
93
|
+
* call-site ergonomics (a required-step failure rejects). The thrown message is
|
|
94
|
+
* kept verbatim (`runReport(id): required step "k" failed: ...`) so existing
|
|
95
|
+
* assertions hold; the typed `AnalysisError` is reachable via `.analysisError`
|
|
96
|
+
* and the original failure via `.cause`.
|
|
50
97
|
*/
|
|
51
98
|
declare function runReport<P extends ReportParams = ReportParams>(report: DefinedReport<P>, opts: RunReportOptions<P>): Promise<ReportResult>;
|
|
52
99
|
interface DryRunReportResult {
|
|
@@ -67,4 +114,4 @@ interface DryRunReportResult {
|
|
|
67
114
|
* Useful right now only as an "is this report wired up correctly?" check.
|
|
68
115
|
*/
|
|
69
116
|
declare function dryRunReport<P extends ReportParams = ReportParams>(report: DefinedReport<P>, ctx: ReportContext<P>): Promise<DryRunReportResult>;
|
|
70
|
-
export { type DryRunReportResult, type FormatReportOptions, REPORTS, type ResolveTargetInput, type ResolveTargetKind, type ResolveTargetResult, type RunReportOptions, defaultReportRegistry, dryRunReport, formatReport, resolveTarget, runReport };
|
|
117
|
+
export { type DryRunReportResult, type FormatReportOptions, REPORTS, type ResolveTargetInput, type ResolveTargetKind, type ResolveTargetResult, type RunReportOptions, defaultReportRegistry, dryRunReport, formatReport, resolveTarget, runReport, runReportResult };
|
package/dist/report/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { computeInputHash, createReportRegistry, defineReport } from "@gscdump/engine/report";
|
|
2
|
+
import { err, ok, unwrapResult } from "gscdump/result";
|
|
2
3
|
import { runAnalyzerFromSource } from "@gscdump/engine/analyzer";
|
|
3
4
|
const SEVERITY_GLYPH = {
|
|
4
5
|
info: "i",
|
|
@@ -44,6 +45,72 @@ function formatNumber(n) {
|
|
|
44
45
|
if (Number.isInteger(n)) return String(n);
|
|
45
46
|
return n.toFixed(2);
|
|
46
47
|
}
|
|
48
|
+
const analysisErrors = {
|
|
49
|
+
missingReportParam(report, param, message) {
|
|
50
|
+
return {
|
|
51
|
+
kind: "missing-report-param",
|
|
52
|
+
report,
|
|
53
|
+
param,
|
|
54
|
+
message
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
missingComparisonWindow(report, message) {
|
|
58
|
+
return {
|
|
59
|
+
kind: "missing-comparison-window",
|
|
60
|
+
report,
|
|
61
|
+
message
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
missingBrandTerms() {
|
|
65
|
+
return {
|
|
66
|
+
kind: "missing-brand-terms",
|
|
67
|
+
message: "Brand analysis requires brandTerms"
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
unknownReport(report, available) {
|
|
71
|
+
return {
|
|
72
|
+
kind: "unknown-report",
|
|
73
|
+
report,
|
|
74
|
+
available,
|
|
75
|
+
message: `unknown report "${report}"; available: ${available.join(", ")}`
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
unknownAnalyzer(analyzer) {
|
|
79
|
+
return {
|
|
80
|
+
kind: "unknown-analyzer",
|
|
81
|
+
analyzer,
|
|
82
|
+
message: `unknown analyzer "${analyzer}"`
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
requiredStepFailed(report, stepKey, stepError, cause) {
|
|
86
|
+
return {
|
|
87
|
+
kind: "required-step-failed",
|
|
88
|
+
report,
|
|
89
|
+
stepKey,
|
|
90
|
+
stepError,
|
|
91
|
+
cause,
|
|
92
|
+
message: `runReport(${report}): required step "${stepKey}" failed: ${stepError}`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
function analysisErrorToException(error) {
|
|
97
|
+
const exception = new Error(error.message);
|
|
98
|
+
if ("cause" in error && error.cause !== void 0) exception.cause = error.cause;
|
|
99
|
+
exception.analysisError = error;
|
|
100
|
+
return exception;
|
|
101
|
+
}
|
|
102
|
+
function requireReportParamResult(report, param, value, message) {
|
|
103
|
+
return value && value.trim() ? ok(value) : err(analysisErrors.missingReportParam(report, param, message));
|
|
104
|
+
}
|
|
105
|
+
function requireReportParam(report, param, value, message) {
|
|
106
|
+
return unwrapResult(requireReportParamResult(report, param, value, message), analysisErrorToException);
|
|
107
|
+
}
|
|
108
|
+
function requireComparisonWindowResult(report, window, message) {
|
|
109
|
+
return window.comparison ? ok(window.comparison) : err(analysisErrors.missingComparisonWindow(report, message));
|
|
110
|
+
}
|
|
111
|
+
function requireComparisonWindow(report, window, message) {
|
|
112
|
+
return unwrapResult(requireComparisonWindowResult(report, window, message), analysisErrorToException);
|
|
113
|
+
}
|
|
47
114
|
const DEFAULT_MAX$7 = 5;
|
|
48
115
|
const brandReport = defineReport({
|
|
49
116
|
id: "brand",
|
|
@@ -63,8 +130,7 @@ const brandReport = defineReport({
|
|
|
63
130
|
}
|
|
64
131
|
},
|
|
65
132
|
plan: (params, window) => {
|
|
66
|
-
|
|
67
|
-
const brandTerms = params.brandTerms.split(",").map((t) => t.trim()).filter(Boolean);
|
|
133
|
+
const brandTerms = requireReportParam("brand", "brand-terms", params.brandTerms, "brand report requires --brand-terms <comma,separated,list>").split(",").map((t) => t.trim()).filter(Boolean);
|
|
68
134
|
const dates = {
|
|
69
135
|
startDate: window.start,
|
|
70
136
|
endDate: window.end
|
|
@@ -486,14 +552,14 @@ const moversReport = defineReport({
|
|
|
486
552
|
}
|
|
487
553
|
},
|
|
488
554
|
plan: (_params, window) => {
|
|
489
|
-
|
|
555
|
+
const comparison = requireComparisonWindow("movers", window, "movers report requires a comparison window — pass --vs prev-period");
|
|
490
556
|
const cur = {
|
|
491
557
|
startDate: window.start,
|
|
492
558
|
endDate: window.end
|
|
493
559
|
};
|
|
494
560
|
const prev = {
|
|
495
|
-
prevStartDate:
|
|
496
|
-
prevEndDate:
|
|
561
|
+
prevStartDate: comparison.start,
|
|
562
|
+
prevEndDate: comparison.end
|
|
497
563
|
};
|
|
498
564
|
return [
|
|
499
565
|
{
|
|
@@ -901,7 +967,7 @@ const prePublishReport = defineReport({
|
|
|
901
967
|
}
|
|
902
968
|
},
|
|
903
969
|
plan: (params, window) => {
|
|
904
|
-
|
|
970
|
+
requireReportParam("pre-publish", "topic", params.topic, "pre-publish report requires --topic <topic-or-url>");
|
|
905
971
|
const dates = {
|
|
906
972
|
startDate: window.start,
|
|
907
973
|
endDate: window.end
|
|
@@ -1316,14 +1382,14 @@ const risksReport = defineReport({
|
|
|
1316
1382
|
default: DEFAULT_MAX$1
|
|
1317
1383
|
} },
|
|
1318
1384
|
plan: (_params, window) => {
|
|
1319
|
-
|
|
1385
|
+
const comparison = requireComparisonWindow("risks", window, "risks report requires a comparison window — pass --vs prev-period");
|
|
1320
1386
|
const dates = {
|
|
1321
1387
|
startDate: window.start,
|
|
1322
1388
|
endDate: window.end
|
|
1323
1389
|
};
|
|
1324
1390
|
const prev = {
|
|
1325
|
-
prevStartDate:
|
|
1326
|
-
prevEndDate:
|
|
1391
|
+
prevStartDate: comparison.start,
|
|
1392
|
+
prevEndDate: comparison.end
|
|
1327
1393
|
};
|
|
1328
1394
|
return [
|
|
1329
1395
|
{
|
|
@@ -1580,7 +1646,7 @@ const triageReport = defineReport({
|
|
|
1580
1646
|
}
|
|
1581
1647
|
},
|
|
1582
1648
|
plan: (params, window) => {
|
|
1583
|
-
|
|
1649
|
+
requireReportParam("triage", "target", params.target, "triage report requires --target <page-or-query>");
|
|
1584
1650
|
const dates = {
|
|
1585
1651
|
startDate: window.start,
|
|
1586
1652
|
endDate: window.end
|
|
@@ -1750,17 +1816,20 @@ async function executeStep(source, analyzers, step) {
|
|
|
1750
1816
|
status: "done"
|
|
1751
1817
|
},
|
|
1752
1818
|
result
|
|
1753
|
-
})).catch((
|
|
1754
|
-
const message =
|
|
1755
|
-
return {
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1819
|
+
})).catch((thrown) => {
|
|
1820
|
+
const message = thrown?.message ?? String(thrown);
|
|
1821
|
+
return {
|
|
1822
|
+
state: {
|
|
1823
|
+
key: step.key,
|
|
1824
|
+
type: step.type,
|
|
1825
|
+
status: "error",
|
|
1826
|
+
error: message
|
|
1827
|
+
},
|
|
1828
|
+
cause: thrown
|
|
1829
|
+
};
|
|
1761
1830
|
});
|
|
1762
1831
|
}
|
|
1763
|
-
async function
|
|
1832
|
+
async function runReportResult(report, opts) {
|
|
1764
1833
|
const startedAt = Date.now();
|
|
1765
1834
|
const generatedAt = new Date(startedAt).toISOString();
|
|
1766
1835
|
const inputHash = await computeInputHash({
|
|
@@ -1774,13 +1843,13 @@ async function runReport(report, opts) {
|
|
|
1774
1843
|
const outcomes = await Promise.all(steps.map((s) => executeStep(opts.source, opts.analyzers, s)));
|
|
1775
1844
|
const required = new Map(steps.filter((s) => s.required).map((s) => [s.key, s]));
|
|
1776
1845
|
const errored = outcomes.filter((o) => o.state.status === "error");
|
|
1777
|
-
for (const o of errored) if (required.has(o.state.key))
|
|
1846
|
+
for (const o of errored) if (required.has(o.state.key)) return err(analysisErrors.requiredStepFailed(report.id, o.state.key, o.state.error ?? "unknown error", o.cause));
|
|
1778
1847
|
const resultsByKey = {};
|
|
1779
1848
|
for (const o of outcomes) if (o.result) resultsByKey[o.state.key] = o.result;
|
|
1780
1849
|
const sections = report.reduce(resultsByKey, opts.ctx).sections;
|
|
1781
1850
|
const degraded = errored.length > 0;
|
|
1782
1851
|
const stepStates = outcomes.map((o) => o.state);
|
|
1783
|
-
return {
|
|
1852
|
+
return ok({
|
|
1784
1853
|
id: report.id,
|
|
1785
1854
|
site: opts.ctx.site,
|
|
1786
1855
|
inputHash,
|
|
@@ -1793,7 +1862,10 @@ async function runReport(report, opts) {
|
|
|
1793
1862
|
degraded,
|
|
1794
1863
|
steps: stepStates
|
|
1795
1864
|
}
|
|
1796
|
-
};
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
async function runReport(report, opts) {
|
|
1868
|
+
return unwrapResult(await runReportResult(report, opts), analysisErrorToException);
|
|
1797
1869
|
}
|
|
1798
1870
|
async function dryRunReport(report, ctx) {
|
|
1799
1871
|
return {
|
|
@@ -1808,4 +1880,4 @@ async function dryRunReport(report, ctx) {
|
|
|
1808
1880
|
}
|
|
1809
1881
|
};
|
|
1810
1882
|
}
|
|
1811
|
-
export { REPORTS, defaultReportRegistry, dryRunReport, formatReport, resolveTarget, runReport };
|
|
1883
|
+
export { REPORTS, defaultReportRegistry, dryRunReport, formatReport, resolveTarget, runReport, runReportResult };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/analysis",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.26.1",
|
|
5
5
|
"description": "GSC analyzers — striking-distance, opportunity, movers, decay, brand, clustering, concentration, seasonality. Pure row-based + DuckDB-native.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -41,6 +41,11 @@
|
|
|
41
41
|
"import": "./dist/source/index.mjs",
|
|
42
42
|
"default": "./dist/source/index.mjs"
|
|
43
43
|
},
|
|
44
|
+
"./errors": {
|
|
45
|
+
"types": "./dist/errors.d.mts",
|
|
46
|
+
"import": "./dist/errors.mjs",
|
|
47
|
+
"default": "./dist/errors.mjs"
|
|
48
|
+
},
|
|
44
49
|
"./semantic": {
|
|
45
50
|
"types": "./dist/semantic/index.d.mts",
|
|
46
51
|
"import": "./dist/semantic/index.mjs",
|
|
@@ -70,9 +75,9 @@
|
|
|
70
75
|
},
|
|
71
76
|
"dependencies": {
|
|
72
77
|
"drizzle-orm": "1.0.0-rc.3",
|
|
73
|
-
"@gscdump/engine": "0.
|
|
74
|
-
"gscdump": "0.
|
|
75
|
-
"
|
|
78
|
+
"@gscdump/engine": "0.26.1",
|
|
79
|
+
"@gscdump/engine-gsc-api": "0.26.1",
|
|
80
|
+
"gscdump": "0.26.1"
|
|
76
81
|
},
|
|
77
82
|
"devDependencies": {
|
|
78
83
|
"vitest": "^4.1.8"
|