@gscdump/analysis 0.32.12 → 0.33.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzer/index.mjs +153 -384
- package/dist/default-registry.mjs +153 -384
- package/dist/index.mjs +267 -609
- package/dist/report/index.mjs +114 -225
- package/dist/semantic/index.d.mts +8 -8
- package/dist/semantic/index.mjs +77 -77
- package/package.json +4 -4
package/dist/report/index.mjs
CHANGED
|
@@ -111,6 +111,27 @@ function requireComparisonWindowResult(report, window, message) {
|
|
|
111
111
|
function requireComparisonWindow(report, window, message) {
|
|
112
112
|
return unwrapResult(requireComparisonWindowResult(report, window, message), analysisErrorToException);
|
|
113
113
|
}
|
|
114
|
+
function reportRows(result) {
|
|
115
|
+
return result?.results ?? [];
|
|
116
|
+
}
|
|
117
|
+
function sectionCoverage(result) {
|
|
118
|
+
return result ? "full" : "partial";
|
|
119
|
+
}
|
|
120
|
+
function sectionArtifact(result, analyzer, params = {}) {
|
|
121
|
+
return result ? {
|
|
122
|
+
analyzer,
|
|
123
|
+
params: {
|
|
124
|
+
type: analyzer,
|
|
125
|
+
...params
|
|
126
|
+
}
|
|
127
|
+
} : void 0;
|
|
128
|
+
}
|
|
129
|
+
function truncation(total, kept) {
|
|
130
|
+
return total > kept ? {
|
|
131
|
+
kept,
|
|
132
|
+
total
|
|
133
|
+
} : void 0;
|
|
134
|
+
}
|
|
114
135
|
const DEFAULT_MAX$7 = 5;
|
|
115
136
|
const brandReport = defineReport({
|
|
116
137
|
id: "brand",
|
|
@@ -160,7 +181,7 @@ const brandReport = defineReport({
|
|
|
160
181
|
}
|
|
161
182
|
});
|
|
162
183
|
function buildBrandSplitSection(res, max) {
|
|
163
|
-
const rows = res
|
|
184
|
+
const rows = reportRows(res);
|
|
164
185
|
const summary = res?.meta?.summary;
|
|
165
186
|
const findings = rows.filter((r) => r.segment === "brand").sort((a, b) => b.clicks - a.clicks).slice(0, max).map((r) => ({
|
|
166
187
|
entity: {
|
|
@@ -182,16 +203,13 @@ function buildBrandSplitSection(res, max) {
|
|
|
182
203
|
severity: "info",
|
|
183
204
|
summary: { magnitudeLabel: summary ? `brand share ${(brandShare * 100).toFixed(1)}% (${summary.brandClicks} brand vs ${summary.nonBrandClicks} non-brand clicks)` : "no summary available" },
|
|
184
205
|
findings,
|
|
185
|
-
coverage: res
|
|
206
|
+
coverage: sectionCoverage(res),
|
|
186
207
|
actions: [],
|
|
187
|
-
artifact: res
|
|
188
|
-
analyzer: "brand",
|
|
189
|
-
params: { type: "brand" }
|
|
190
|
-
} : void 0
|
|
208
|
+
artifact: sectionArtifact(res, "brand")
|
|
191
209
|
};
|
|
192
210
|
}
|
|
193
211
|
function buildConcentrationSection(res, max) {
|
|
194
|
-
const head = (res
|
|
212
|
+
const head = reportRows(res)[0];
|
|
195
213
|
const findings = (head?.topNItems ?? []).slice(0, max).map((it) => ({
|
|
196
214
|
entity: {
|
|
197
215
|
kind: "query",
|
|
@@ -209,15 +227,9 @@ function buildConcentrationSection(res, max) {
|
|
|
209
227
|
severity: head ? severity : "info",
|
|
210
228
|
summary: head ? { magnitudeLabel: `HHI ${head.hhi.toFixed(0)} (${head.riskLevel}); top-N share ${(head.topNConcentration * 100).toFixed(1)}%` } : {},
|
|
211
229
|
findings,
|
|
212
|
-
coverage: res
|
|
230
|
+
coverage: sectionCoverage(res),
|
|
213
231
|
actions: [],
|
|
214
|
-
artifact: res
|
|
215
|
-
analyzer: "concentration",
|
|
216
|
-
params: {
|
|
217
|
-
type: "concentration",
|
|
218
|
-
dimension: "keywords"
|
|
219
|
-
}
|
|
220
|
-
} : void 0
|
|
232
|
+
artifact: sectionArtifact(res, "concentration", { dimension: "keywords" })
|
|
221
233
|
};
|
|
222
234
|
}
|
|
223
235
|
const DEFAULT_MAX$6 = 5;
|
|
@@ -283,7 +295,7 @@ const growthReport = defineReport({
|
|
|
283
295
|
}
|
|
284
296
|
});
|
|
285
297
|
function buildContentVelocity(res) {
|
|
286
|
-
const rows = res
|
|
298
|
+
const rows = reportRows(res);
|
|
287
299
|
const totalNew = rows.reduce((s, r) => s + r.newKeywords, 0);
|
|
288
300
|
const avgPerWeek = rows.length > 0 ? totalNew / rows.length : 0;
|
|
289
301
|
return {
|
|
@@ -292,16 +304,13 @@ function buildContentVelocity(res) {
|
|
|
292
304
|
severity: "info",
|
|
293
305
|
summary: { magnitudeLabel: `${totalNew} new keywords across ${rows.length} weeks (avg ${avgPerWeek.toFixed(1)}/wk)` },
|
|
294
306
|
findings: [],
|
|
295
|
-
coverage: res
|
|
307
|
+
coverage: sectionCoverage(res),
|
|
296
308
|
actions: [],
|
|
297
|
-
artifact: res
|
|
298
|
-
analyzer: "content-velocity",
|
|
299
|
-
params: { type: "content-velocity" }
|
|
300
|
-
} : void 0
|
|
309
|
+
artifact: sectionArtifact(res, "content-velocity")
|
|
301
310
|
};
|
|
302
311
|
}
|
|
303
312
|
function buildKeywordBreadth(res) {
|
|
304
|
-
const rows = res
|
|
313
|
+
const rows = reportRows(res);
|
|
305
314
|
const totalPages = rows.reduce((s, r) => s + r.pageCount, 0);
|
|
306
315
|
const top = rows[0];
|
|
307
316
|
return {
|
|
@@ -310,32 +319,26 @@ function buildKeywordBreadth(res) {
|
|
|
310
319
|
severity: "info",
|
|
311
320
|
summary: { magnitudeLabel: top ? `${totalPages} pages; modal bucket "${top.bucket}" (${top.pageCount} pages)` : "no data" },
|
|
312
321
|
findings: [],
|
|
313
|
-
coverage: res
|
|
322
|
+
coverage: sectionCoverage(res),
|
|
314
323
|
actions: [],
|
|
315
|
-
artifact: res
|
|
316
|
-
analyzer: "keyword-breadth",
|
|
317
|
-
params: { type: "keyword-breadth" }
|
|
318
|
-
} : void 0
|
|
324
|
+
artifact: sectionArtifact(res, "keyword-breadth")
|
|
319
325
|
};
|
|
320
326
|
}
|
|
321
327
|
function buildIntentAtlas(res) {
|
|
322
|
-
const rows = res
|
|
328
|
+
const rows = reportRows(res);
|
|
323
329
|
return {
|
|
324
330
|
id: "intent-atlas",
|
|
325
331
|
title: "Intent atlas",
|
|
326
332
|
severity: "info",
|
|
327
333
|
summary: { magnitudeLabel: `${rows.length} clusters covering ${rows.reduce((s, r) => s + r.keywordCount, 0)} keywords` },
|
|
328
334
|
findings: [],
|
|
329
|
-
coverage: res
|
|
335
|
+
coverage: sectionCoverage(res),
|
|
330
336
|
actions: [],
|
|
331
|
-
artifact: res
|
|
332
|
-
analyzer: "intent-atlas",
|
|
333
|
-
params: { type: "intent-atlas" }
|
|
334
|
-
} : void 0
|
|
337
|
+
artifact: sectionArtifact(res, "intent-atlas")
|
|
335
338
|
};
|
|
336
339
|
}
|
|
337
340
|
function buildLongTail(res, max) {
|
|
338
|
-
const rows = (res
|
|
341
|
+
const rows = reportRows(res).sort((a, b) => {
|
|
339
342
|
const rank = {
|
|
340
343
|
"head-heavy": 0,
|
|
341
344
|
"balanced": 1,
|
|
@@ -364,16 +367,10 @@ function buildLongTail(res, max) {
|
|
|
364
367
|
severity: headHeavy > rows.length / 3 ? "medium" : "info",
|
|
365
368
|
summary: { magnitudeLabel: `${rows.length} pages analysed; ${headHeavy} head-heavy` },
|
|
366
369
|
findings,
|
|
367
|
-
truncated: rows.length
|
|
368
|
-
|
|
369
|
-
total: rows.length
|
|
370
|
-
} : void 0,
|
|
371
|
-
coverage: res ? "full" : "partial",
|
|
370
|
+
truncated: truncation(rows.length, kept.length),
|
|
371
|
+
coverage: sectionCoverage(res),
|
|
372
372
|
actions: [],
|
|
373
|
-
artifact: res
|
|
374
|
-
analyzer: "long-tail",
|
|
375
|
-
params: { type: "long-tail" }
|
|
376
|
-
} : void 0
|
|
373
|
+
artifact: sectionArtifact(res, "long-tail")
|
|
377
374
|
};
|
|
378
375
|
}
|
|
379
376
|
const DEFAULT_MAX$5 = 5;
|
|
@@ -430,7 +427,7 @@ const healthReport = defineReport({
|
|
|
430
427
|
}
|
|
431
428
|
});
|
|
432
429
|
function buildCtrAnomalySection(res, max) {
|
|
433
|
-
const rows = (res
|
|
430
|
+
const rows = reportRows(res).filter((r) => r.breachDaysDown > 0).sort((a, b) => b.clicksLost - a.clicksLost);
|
|
434
431
|
const kept = rows.slice(0, max);
|
|
435
432
|
const totalLost = kept.reduce((s, r) => s + r.clicksLost, 0);
|
|
436
433
|
const findings = kept.map((r) => ({
|
|
@@ -456,20 +453,14 @@ function buildCtrAnomalySection(res, max) {
|
|
|
456
453
|
magnitudeLabel: `${Math.round(totalLost)} clicks lost vs baseline`
|
|
457
454
|
},
|
|
458
455
|
findings,
|
|
459
|
-
truncated: rows.length
|
|
460
|
-
|
|
461
|
-
total: rows.length
|
|
462
|
-
} : void 0,
|
|
463
|
-
coverage: res ? "full" : "partial",
|
|
456
|
+
truncated: truncation(rows.length, kept.length),
|
|
457
|
+
coverage: sectionCoverage(res),
|
|
464
458
|
actions: [],
|
|
465
|
-
artifact: res
|
|
466
|
-
analyzer: "ctr-anomaly",
|
|
467
|
-
params: { type: "ctr-anomaly" }
|
|
468
|
-
} : void 0
|
|
459
|
+
artifact: sectionArtifact(res, "ctr-anomaly")
|
|
469
460
|
};
|
|
470
461
|
}
|
|
471
462
|
function buildChangePointSection$1(res, max) {
|
|
472
|
-
const rows = (res
|
|
463
|
+
const rows = reportRows(res).filter((r) => r.direction === "worsened").sort((a, b) => b.llr - a.llr);
|
|
473
464
|
const kept = rows.slice(0, max);
|
|
474
465
|
const findings = kept.map((r) => ({
|
|
475
466
|
entity: {
|
|
@@ -488,20 +479,14 @@ function buildChangePointSection$1(res, max) {
|
|
|
488
479
|
severity: kept.length ? "medium" : "info",
|
|
489
480
|
summary: { magnitudeLabel: `${kept.length} worsening segments` },
|
|
490
481
|
findings,
|
|
491
|
-
truncated: rows.length
|
|
492
|
-
|
|
493
|
-
total: rows.length
|
|
494
|
-
} : void 0,
|
|
495
|
-
coverage: res ? "full" : "partial",
|
|
482
|
+
truncated: truncation(rows.length, kept.length),
|
|
483
|
+
coverage: sectionCoverage(res),
|
|
496
484
|
actions: [],
|
|
497
|
-
artifact: res
|
|
498
|
-
analyzer: "change-point",
|
|
499
|
-
params: { type: "change-point" }
|
|
500
|
-
} : void 0
|
|
485
|
+
artifact: sectionArtifact(res, "change-point")
|
|
501
486
|
};
|
|
502
487
|
}
|
|
503
488
|
function buildPositionVolatilitySection(res, max) {
|
|
504
|
-
const rows = (res
|
|
489
|
+
const rows = reportRows(res).sort((a, b) => b.peakVolatility - a.peakVolatility);
|
|
505
490
|
const kept = rows.slice(0, max);
|
|
506
491
|
const findings = kept.map((r) => ({
|
|
507
492
|
entity: {
|
|
@@ -520,16 +505,10 @@ function buildPositionVolatilitySection(res, max) {
|
|
|
520
505
|
severity: kept.length ? "low" : "info",
|
|
521
506
|
summary: {},
|
|
522
507
|
findings,
|
|
523
|
-
truncated: rows.length
|
|
524
|
-
|
|
525
|
-
total: rows.length
|
|
526
|
-
} : void 0,
|
|
527
|
-
coverage: res ? "full" : "partial",
|
|
508
|
+
truncated: truncation(rows.length, kept.length),
|
|
509
|
+
coverage: sectionCoverage(res),
|
|
528
510
|
actions: [],
|
|
529
|
-
artifact: res
|
|
530
|
-
analyzer: "position-volatility",
|
|
531
|
-
params: { type: "position-volatility" }
|
|
532
|
-
} : void 0
|
|
511
|
+
artifact: sectionArtifact(res, "position-volatility")
|
|
533
512
|
};
|
|
534
513
|
}
|
|
535
514
|
const DEFAULT_MAX$4 = 5;
|
|
@@ -605,7 +584,7 @@ const moversReport = defineReport({
|
|
|
605
584
|
}
|
|
606
585
|
});
|
|
607
586
|
function buildMoversSection(res, direction, max, minChange) {
|
|
608
|
-
const rows = (res
|
|
587
|
+
const rows = reportRows(res).filter((r) => r.direction === direction && Math.abs(r.clicksChange) >= minChange).sort((a, b) => Math.abs(b.clicksChange) - Math.abs(a.clicksChange));
|
|
609
588
|
const total = rows.length;
|
|
610
589
|
const kept = rows.slice(0, max);
|
|
611
590
|
const findings = kept.map((r) => ({
|
|
@@ -637,21 +616,15 @@ function buildMoversSection(res, direction, max, minChange) {
|
|
|
637
616
|
direction: totalDelta > 0 ? "up" : totalDelta < 0 ? "down" : "flat"
|
|
638
617
|
},
|
|
639
618
|
findings,
|
|
640
|
-
truncated: total
|
|
641
|
-
|
|
642
|
-
total
|
|
643
|
-
} : void 0,
|
|
644
|
-
coverage: res ? "full" : "partial",
|
|
619
|
+
truncated: truncation(total, kept.length),
|
|
620
|
+
coverage: sectionCoverage(res),
|
|
645
621
|
actions: [],
|
|
646
|
-
artifact: res
|
|
647
|
-
analyzer: "movers",
|
|
648
|
-
params: { type: "movers" }
|
|
649
|
-
} : void 0
|
|
622
|
+
artifact: sectionArtifact(res, "movers")
|
|
650
623
|
};
|
|
651
624
|
}
|
|
652
625
|
function buildDeclinersSection(moversRes, decayRes, max, minChange) {
|
|
653
|
-
const decliningQueries = (moversRes
|
|
654
|
-
const lostPages = (decayRes
|
|
626
|
+
const decliningQueries = reportRows(moversRes).filter((r) => r.direction === "declining" && Math.abs(r.clicksChange) >= minChange).slice(0, max);
|
|
627
|
+
const lostPages = reportRows(decayRes).sort((a, b) => b.lostClicks - a.lostClicks).slice(0, max);
|
|
655
628
|
const findings = [...decliningQueries.map((r) => ({
|
|
656
629
|
entity: {
|
|
657
630
|
kind: "query",
|
|
@@ -710,7 +683,7 @@ function buildDeclinersSection(moversRes, decayRes, max, minChange) {
|
|
|
710
683
|
};
|
|
711
684
|
}
|
|
712
685
|
function buildStrikingSection$1(res, max) {
|
|
713
|
-
const rows = (res
|
|
686
|
+
const rows = reportRows(res).sort((a, b) => b.potentialClicks - a.potentialClicks);
|
|
714
687
|
const kept = rows.slice(0, max);
|
|
715
688
|
const findings = kept.map((r) => ({
|
|
716
689
|
entity: {
|
|
@@ -731,16 +704,10 @@ function buildStrikingSection$1(res, max) {
|
|
|
731
704
|
severity: "low",
|
|
732
705
|
summary: { magnitudeLabel: `${kept.reduce((s, r) => s + r.potentialClicks, 0)} potential clicks` },
|
|
733
706
|
findings,
|
|
734
|
-
truncated: rows.length
|
|
735
|
-
|
|
736
|
-
total: rows.length
|
|
737
|
-
} : void 0,
|
|
738
|
-
coverage: res ? "full" : "partial",
|
|
707
|
+
truncated: truncation(rows.length, kept.length),
|
|
708
|
+
coverage: sectionCoverage(res),
|
|
739
709
|
actions: [],
|
|
740
|
-
artifact: res
|
|
741
|
-
analyzer: "striking-distance",
|
|
742
|
-
params: { type: "striking-distance" }
|
|
743
|
-
} : void 0
|
|
710
|
+
artifact: sectionArtifact(res, "striking-distance")
|
|
744
711
|
};
|
|
745
712
|
}
|
|
746
713
|
const DEFAULT_MAX$3 = 5;
|
|
@@ -806,7 +773,7 @@ const opportunitiesReport = defineReport({
|
|
|
806
773
|
}
|
|
807
774
|
});
|
|
808
775
|
function buildStrikingSection(res, max) {
|
|
809
|
-
const rows = (res
|
|
776
|
+
const rows = reportRows(res).sort((a, b) => b.potentialClicks - a.potentialClicks);
|
|
810
777
|
const kept = rows.slice(0, max);
|
|
811
778
|
const findings = kept.map((r) => ({
|
|
812
779
|
entity: {
|
|
@@ -828,20 +795,14 @@ function buildStrikingSection(res, max) {
|
|
|
828
795
|
severity: "low",
|
|
829
796
|
summary: { magnitudeLabel: `${Math.round(totalPotential)} potential clicks` },
|
|
830
797
|
findings,
|
|
831
|
-
truncated: rows.length
|
|
832
|
-
|
|
833
|
-
total: rows.length
|
|
834
|
-
} : void 0,
|
|
835
|
-
coverage: res ? "full" : "partial",
|
|
798
|
+
truncated: truncation(rows.length, kept.length),
|
|
799
|
+
coverage: sectionCoverage(res),
|
|
836
800
|
actions: [],
|
|
837
|
-
artifact: res
|
|
838
|
-
analyzer: "striking-distance",
|
|
839
|
-
params: { type: "striking-distance" }
|
|
840
|
-
} : void 0
|
|
801
|
+
artifact: sectionArtifact(res, "striking-distance")
|
|
841
802
|
};
|
|
842
803
|
}
|
|
843
804
|
function buildOpportunitySection(res, max) {
|
|
844
|
-
const rows = (res
|
|
805
|
+
const rows = reportRows(res).sort((a, b) => b.opportunityScore - a.opportunityScore);
|
|
845
806
|
const kept = rows.slice(0, max);
|
|
846
807
|
return {
|
|
847
808
|
id: "low-ctr",
|
|
@@ -862,11 +823,8 @@ function buildOpportunitySection(res, max) {
|
|
|
862
823
|
},
|
|
863
824
|
why: r.page ? `low CTR on ${r.page}` : "low CTR vs position"
|
|
864
825
|
})),
|
|
865
|
-
truncated: rows.length
|
|
866
|
-
|
|
867
|
-
total: rows.length
|
|
868
|
-
} : void 0,
|
|
869
|
-
coverage: res ? "full" : "partial",
|
|
826
|
+
truncated: truncation(rows.length, kept.length),
|
|
827
|
+
coverage: sectionCoverage(res),
|
|
870
828
|
actions: kept.slice(0, 1).map((r) => ({
|
|
871
829
|
kind: "fix",
|
|
872
830
|
target: r.page ? {
|
|
@@ -878,14 +836,11 @@ function buildOpportunitySection(res, max) {
|
|
|
878
836
|
},
|
|
879
837
|
rationale: "Rewrite title/description to lift CTR at this position"
|
|
880
838
|
})),
|
|
881
|
-
artifact: res
|
|
882
|
-
analyzer: "opportunity",
|
|
883
|
-
params: { type: "opportunity" }
|
|
884
|
-
} : void 0
|
|
839
|
+
artifact: sectionArtifact(res, "opportunity")
|
|
885
840
|
};
|
|
886
841
|
}
|
|
887
842
|
function buildZeroClickSection(res, max) {
|
|
888
|
-
const rows = (res
|
|
843
|
+
const rows = reportRows(res).sort((a, b) => b.impressions - a.impressions);
|
|
889
844
|
const kept = rows.slice(0, max);
|
|
890
845
|
const findings = kept.map((r) => ({
|
|
891
846
|
entity: {
|
|
@@ -905,20 +860,14 @@ function buildZeroClickSection(res, max) {
|
|
|
905
860
|
severity: "info",
|
|
906
861
|
summary: { magnitudeLabel: `${kept.reduce((s, r) => s + r.impressions, 0)} impressions wasted` },
|
|
907
862
|
findings,
|
|
908
|
-
truncated: rows.length
|
|
909
|
-
|
|
910
|
-
total: rows.length
|
|
911
|
-
} : void 0,
|
|
912
|
-
coverage: res ? "full" : "partial",
|
|
863
|
+
truncated: truncation(rows.length, kept.length),
|
|
864
|
+
coverage: sectionCoverage(res),
|
|
913
865
|
actions: [],
|
|
914
|
-
artifact: res
|
|
915
|
-
analyzer: "zero-click",
|
|
916
|
-
params: { type: "zero-click" }
|
|
917
|
-
} : void 0
|
|
866
|
+
artifact: sectionArtifact(res, "zero-click")
|
|
918
867
|
};
|
|
919
868
|
}
|
|
920
869
|
function buildMigrationSection$1(res, max) {
|
|
921
|
-
const rows = (res
|
|
870
|
+
const rows = reportRows(res).sort((a, b) => b.weight - a.weight);
|
|
922
871
|
const kept = rows.slice(0, max);
|
|
923
872
|
return {
|
|
924
873
|
id: "query-migration",
|
|
@@ -936,16 +885,10 @@ function buildMigrationSection$1(res, max) {
|
|
|
936
885
|
},
|
|
937
886
|
why: `migrating from ${r.sourcePage}`
|
|
938
887
|
})),
|
|
939
|
-
truncated: rows.length
|
|
940
|
-
|
|
941
|
-
total: rows.length
|
|
942
|
-
} : void 0,
|
|
943
|
-
coverage: res ? "full" : "partial",
|
|
888
|
+
truncated: truncation(rows.length, kept.length),
|
|
889
|
+
coverage: sectionCoverage(res),
|
|
944
890
|
actions: [],
|
|
945
|
-
artifact: res
|
|
946
|
-
analyzer: "query-migration",
|
|
947
|
-
params: { type: "query-migration" }
|
|
948
|
-
} : void 0
|
|
891
|
+
artifact: sectionArtifact(res, "query-migration")
|
|
949
892
|
};
|
|
950
893
|
}
|
|
951
894
|
const DEFAULT_MAX$2 = 10;
|
|
@@ -996,7 +939,7 @@ const prePublishReport = defineReport({
|
|
|
996
939
|
}
|
|
997
940
|
});
|
|
998
941
|
function buildCannibalizationSection$1(res, matches, max) {
|
|
999
|
-
const rows = (res
|
|
942
|
+
const rows = reportRows(res).filter((r) => matches(r.keyword) || (r.competitors ?? []).some((p) => matches(p.url))).sort((a, b) => b.totalClicks - a.totalClicks);
|
|
1000
943
|
const kept = rows.slice(0, max);
|
|
1001
944
|
const findings = kept.map((r) => {
|
|
1002
945
|
const pageCount = r.competitorCount ?? r.competitors?.length ?? 0;
|
|
@@ -1019,23 +962,17 @@ function buildCannibalizationSection$1(res, matches, max) {
|
|
|
1019
962
|
severity: kept.length ? "high" : "info",
|
|
1020
963
|
summary: { magnitudeLabel: kept.length ? `${kept.length} existing competition` : "no existing competition" },
|
|
1021
964
|
findings,
|
|
1022
|
-
truncated: rows.length
|
|
1023
|
-
|
|
1024
|
-
total: rows.length
|
|
1025
|
-
} : void 0,
|
|
1026
|
-
coverage: res ? "full" : "partial",
|
|
965
|
+
truncated: truncation(rows.length, kept.length),
|
|
966
|
+
coverage: sectionCoverage(res),
|
|
1027
967
|
actions: kept.slice(0, 1).map(() => ({
|
|
1028
968
|
kind: "fix",
|
|
1029
969
|
rationale: "Decide before publishing: redirect existing page, target a different angle, or accept overlap."
|
|
1030
970
|
})),
|
|
1031
|
-
artifact: res
|
|
1032
|
-
analyzer: "cannibalization",
|
|
1033
|
-
params: { type: "cannibalization" }
|
|
1034
|
-
} : void 0
|
|
971
|
+
artifact: sectionArtifact(res, "cannibalization")
|
|
1035
972
|
};
|
|
1036
973
|
}
|
|
1037
974
|
function buildStrikingPeersSection(res, matches, max, topic) {
|
|
1038
|
-
const rows = (res
|
|
975
|
+
const rows = reportRows(res).filter((r) => matches(r.keyword) || matches(r.page)).sort((a, b) => b.potentialClicks - a.potentialClicks);
|
|
1039
976
|
const kept = rows.slice(0, max);
|
|
1040
977
|
const findings = kept.map((r) => ({
|
|
1041
978
|
entity: {
|
|
@@ -1055,16 +992,10 @@ function buildStrikingPeersSection(res, matches, max, topic) {
|
|
|
1055
992
|
severity: kept.length ? "low" : "info",
|
|
1056
993
|
summary: { magnitudeLabel: kept.length ? `${kept.length} adjacent rankings for "${topic}"` : "no adjacent rankings" },
|
|
1057
994
|
findings,
|
|
1058
|
-
truncated: rows.length
|
|
1059
|
-
|
|
1060
|
-
total: rows.length
|
|
1061
|
-
} : void 0,
|
|
1062
|
-
coverage: res ? "full" : "partial",
|
|
995
|
+
truncated: truncation(rows.length, kept.length),
|
|
996
|
+
coverage: sectionCoverage(res),
|
|
1063
997
|
actions: [],
|
|
1064
|
-
artifact: res
|
|
1065
|
-
analyzer: "striking-distance",
|
|
1066
|
-
params: { type: "striking-distance" }
|
|
1067
|
-
} : void 0
|
|
998
|
+
artifact: sectionArtifact(res, "striking-distance")
|
|
1068
999
|
};
|
|
1069
1000
|
}
|
|
1070
1001
|
function clamp01(value) {
|
|
@@ -1439,7 +1370,7 @@ const risksReport = defineReport({
|
|
|
1439
1370
|
}
|
|
1440
1371
|
});
|
|
1441
1372
|
function buildDecaySection(res, max) {
|
|
1442
|
-
const rows = (res
|
|
1373
|
+
const rows = reportRows(res).sort((a, b) => b.lostClicks - a.lostClicks);
|
|
1443
1374
|
const kept = rows.slice(0, max);
|
|
1444
1375
|
const totalLost = kept.reduce((s, r) => s + r.lostClicks, 0);
|
|
1445
1376
|
const findings = kept.map((r) => ({
|
|
@@ -1469,11 +1400,8 @@ function buildDecaySection(res, max) {
|
|
|
1469
1400
|
magnitudeLabel: `${Math.round(totalLost)} clicks lost`
|
|
1470
1401
|
},
|
|
1471
1402
|
findings,
|
|
1472
|
-
truncated: rows.length
|
|
1473
|
-
|
|
1474
|
-
total: rows.length
|
|
1475
|
-
} : void 0,
|
|
1476
|
-
coverage: res ? "full" : "partial",
|
|
1403
|
+
truncated: truncation(rows.length, kept.length),
|
|
1404
|
+
coverage: sectionCoverage(res),
|
|
1477
1405
|
actions: kept.slice(0, 1).map((r) => ({
|
|
1478
1406
|
kind: "analyzer",
|
|
1479
1407
|
target: {
|
|
@@ -1483,14 +1411,11 @@ function buildDecaySection(res, max) {
|
|
|
1483
1411
|
params: { type: "change-point" },
|
|
1484
1412
|
rationale: "Investigate change-point on the worst-affected page"
|
|
1485
1413
|
})),
|
|
1486
|
-
artifact: res
|
|
1487
|
-
analyzer: "decay",
|
|
1488
|
-
params: { type: "decay" }
|
|
1489
|
-
} : void 0
|
|
1414
|
+
artifact: sectionArtifact(res, "decay")
|
|
1490
1415
|
};
|
|
1491
1416
|
}
|
|
1492
1417
|
function buildCannibalizationSection(res, max) {
|
|
1493
|
-
const rows = (res
|
|
1418
|
+
const rows = reportRows(res).sort((a, b) => b.totalClicks - a.totalClicks);
|
|
1494
1419
|
const kept = rows.slice(0, max);
|
|
1495
1420
|
const findings = kept.map((r) => {
|
|
1496
1421
|
const pageCount = r.competitorCount ?? r.competitors?.length ?? 0;
|
|
@@ -1513,20 +1438,14 @@ function buildCannibalizationSection(res, max) {
|
|
|
1513
1438
|
severity: kept.length ? "medium" : "info",
|
|
1514
1439
|
summary: {},
|
|
1515
1440
|
findings,
|
|
1516
|
-
truncated: rows.length
|
|
1517
|
-
|
|
1518
|
-
total: rows.length
|
|
1519
|
-
} : void 0,
|
|
1520
|
-
coverage: res ? "full" : "partial",
|
|
1441
|
+
truncated: truncation(rows.length, kept.length),
|
|
1442
|
+
coverage: sectionCoverage(res),
|
|
1521
1443
|
actions: [],
|
|
1522
|
-
artifact: res
|
|
1523
|
-
analyzer: "cannibalization",
|
|
1524
|
-
params: { type: "cannibalization" }
|
|
1525
|
-
} : void 0
|
|
1444
|
+
artifact: sectionArtifact(res, "cannibalization")
|
|
1526
1445
|
};
|
|
1527
1446
|
}
|
|
1528
1447
|
function buildDarkTrafficSection(res, max) {
|
|
1529
|
-
const rows = (res
|
|
1448
|
+
const rows = reportRows(res).sort((a, b) => b.darkClicks - a.darkClicks);
|
|
1530
1449
|
const kept = rows.slice(0, max);
|
|
1531
1450
|
const totalDark = kept.reduce((s, r) => s + r.darkClicks, 0);
|
|
1532
1451
|
const findings = kept.map((r) => ({
|
|
@@ -1546,20 +1465,14 @@ function buildDarkTrafficSection(res, max) {
|
|
|
1546
1465
|
severity: kept.length ? "low" : "info",
|
|
1547
1466
|
summary: { magnitudeLabel: `${Math.round(totalDark)} unattributed clicks` },
|
|
1548
1467
|
findings,
|
|
1549
|
-
truncated: rows.length
|
|
1550
|
-
|
|
1551
|
-
total: rows.length
|
|
1552
|
-
} : void 0,
|
|
1553
|
-
coverage: res ? "full" : "partial",
|
|
1468
|
+
truncated: truncation(rows.length, kept.length),
|
|
1469
|
+
coverage: sectionCoverage(res),
|
|
1554
1470
|
actions: [],
|
|
1555
|
-
artifact: res
|
|
1556
|
-
analyzer: "dark-traffic",
|
|
1557
|
-
params: { type: "dark-traffic" }
|
|
1558
|
-
} : void 0
|
|
1471
|
+
artifact: sectionArtifact(res, "dark-traffic")
|
|
1559
1472
|
};
|
|
1560
1473
|
}
|
|
1561
1474
|
function buildDeviceGapSection(res, max) {
|
|
1562
|
-
const rows = (res
|
|
1475
|
+
const rows = reportRows(res).sort((a, b) => Math.abs(b.gaps.positionGap) - Math.abs(a.gaps.positionGap));
|
|
1563
1476
|
const kept = rows.slice(0, max);
|
|
1564
1477
|
return {
|
|
1565
1478
|
id: "device-gap",
|
|
@@ -1579,16 +1492,10 @@ function buildDeviceGapSection(res, max) {
|
|
|
1579
1492
|
},
|
|
1580
1493
|
why: `desktop vs mobile delta`
|
|
1581
1494
|
})),
|
|
1582
|
-
truncated: rows.length
|
|
1583
|
-
|
|
1584
|
-
total: rows.length
|
|
1585
|
-
} : void 0,
|
|
1586
|
-
coverage: res ? "full" : "partial",
|
|
1495
|
+
truncated: truncation(rows.length, kept.length),
|
|
1496
|
+
coverage: sectionCoverage(res),
|
|
1587
1497
|
actions: [],
|
|
1588
|
-
artifact: res
|
|
1589
|
-
analyzer: "device-gap",
|
|
1590
|
-
params: { type: "device-gap" }
|
|
1591
|
-
} : void 0
|
|
1498
|
+
artifact: sectionArtifact(res, "device-gap")
|
|
1592
1499
|
};
|
|
1593
1500
|
}
|
|
1594
1501
|
function resolveTarget(opts) {
|
|
@@ -1695,7 +1602,7 @@ const triageReport = defineReport({
|
|
|
1695
1602
|
}
|
|
1696
1603
|
});
|
|
1697
1604
|
function buildChangePointSection(res, kind, matches, max) {
|
|
1698
|
-
const rows = (res
|
|
1605
|
+
const rows = reportRows(res).filter((r) => matches(kind === "page" ? r.page : r.keyword)).sort((a, b) => b.llr - a.llr);
|
|
1699
1606
|
const kept = rows.slice(0, max);
|
|
1700
1607
|
const findings = kept.map((r) => ({
|
|
1701
1608
|
entity: {
|
|
@@ -1714,20 +1621,14 @@ function buildChangePointSection(res, kind, matches, max) {
|
|
|
1714
1621
|
severity: kept.length ? "medium" : "info",
|
|
1715
1622
|
summary: { magnitudeLabel: `${kept.length} change-point${kept.length === 1 ? "" : "s"}` },
|
|
1716
1623
|
findings,
|
|
1717
|
-
truncated: rows.length
|
|
1718
|
-
|
|
1719
|
-
total: rows.length
|
|
1720
|
-
} : void 0,
|
|
1721
|
-
coverage: res ? "full" : "partial",
|
|
1624
|
+
truncated: truncation(rows.length, kept.length),
|
|
1625
|
+
coverage: sectionCoverage(res),
|
|
1722
1626
|
actions: [],
|
|
1723
|
-
artifact: res
|
|
1724
|
-
analyzer: "change-point",
|
|
1725
|
-
params: { type: "change-point" }
|
|
1726
|
-
} : void 0
|
|
1627
|
+
artifact: sectionArtifact(res, "change-point")
|
|
1727
1628
|
};
|
|
1728
1629
|
}
|
|
1729
1630
|
function buildMigrationSection(res, kind, matches, max, _rawTarget) {
|
|
1730
|
-
const rows = (res
|
|
1631
|
+
const rows = reportRows(res).filter((r) => kind === "page" ? matches(r.sourcePage) || matches(r.targetPage) : false).sort((a, b) => b.weight - a.weight);
|
|
1731
1632
|
const kept = rows.slice(0, max);
|
|
1732
1633
|
const findings = kept.map((r) => ({
|
|
1733
1634
|
entity: {
|
|
@@ -1746,20 +1647,14 @@ function buildMigrationSection(res, kind, matches, max, _rawTarget) {
|
|
|
1746
1647
|
severity: "info",
|
|
1747
1648
|
summary: { magnitudeLabel: kind === "page" ? `${kept.length} migration edges touching target` : "N/A for query target" },
|
|
1748
1649
|
findings,
|
|
1749
|
-
truncated: rows.length
|
|
1750
|
-
|
|
1751
|
-
total: rows.length
|
|
1752
|
-
} : void 0,
|
|
1753
|
-
coverage: res ? "full" : "partial",
|
|
1650
|
+
truncated: truncation(rows.length, kept.length),
|
|
1651
|
+
coverage: sectionCoverage(res),
|
|
1754
1652
|
actions: [],
|
|
1755
|
-
artifact: res
|
|
1756
|
-
analyzer: "query-migration",
|
|
1757
|
-
params: { type: "query-migration" }
|
|
1758
|
-
} : void 0
|
|
1653
|
+
artifact: sectionArtifact(res, "query-migration")
|
|
1759
1654
|
};
|
|
1760
1655
|
}
|
|
1761
1656
|
function buildVolatilitySection(res, kind, matches, max) {
|
|
1762
|
-
const rows = kind === "page" ? (res
|
|
1657
|
+
const rows = kind === "page" ? reportRows(res).filter((r) => matches(r.page)) : [];
|
|
1763
1658
|
const kept = rows.sort((a, b) => b.peakVolatility - a.peakVolatility).slice(0, max);
|
|
1764
1659
|
const findings = kept.map((r) => ({
|
|
1765
1660
|
entity: {
|
|
@@ -1778,16 +1673,10 @@ function buildVolatilitySection(res, kind, matches, max) {
|
|
|
1778
1673
|
severity: kept.length ? "low" : "info",
|
|
1779
1674
|
summary: { magnitudeLabel: kind === "page" ? `${kept.length} volatile day${kept.length === 1 ? "" : "s"}` : "N/A for query target" },
|
|
1780
1675
|
findings,
|
|
1781
|
-
truncated: rows.length
|
|
1782
|
-
|
|
1783
|
-
total: rows.length
|
|
1784
|
-
} : void 0,
|
|
1785
|
-
coverage: res ? "full" : "partial",
|
|
1676
|
+
truncated: truncation(rows.length, kept.length),
|
|
1677
|
+
coverage: sectionCoverage(res),
|
|
1786
1678
|
actions: [],
|
|
1787
|
-
artifact: res
|
|
1788
|
-
analyzer: "position-volatility",
|
|
1789
|
-
params: { type: "position-volatility" }
|
|
1790
|
-
} : void 0
|
|
1679
|
+
artifact: sectionArtifact(res, "position-volatility")
|
|
1791
1680
|
};
|
|
1792
1681
|
}
|
|
1793
1682
|
const REPORTS = [
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { AnalysisQuerySource } from "@gscdump/engine/source";
|
|
2
|
+
interface ContentGapQueryCandidate {
|
|
3
|
+
query: string;
|
|
4
|
+
impressions: number;
|
|
5
|
+
clicks: number;
|
|
6
|
+
avgPosition: number;
|
|
7
|
+
currentUrl: string;
|
|
8
|
+
}
|
|
2
9
|
interface ContentGapResult {
|
|
3
10
|
query: string;
|
|
4
11
|
impressions: number;
|
|
@@ -55,16 +62,9 @@ interface ContentGapAnalysis {
|
|
|
55
62
|
modelId: string;
|
|
56
63
|
};
|
|
57
64
|
}
|
|
58
|
-
interface QueryCandidate {
|
|
59
|
-
query: string;
|
|
60
|
-
impressions: number;
|
|
61
|
-
clicks: number;
|
|
62
|
-
avgPosition: number;
|
|
63
|
-
currentUrl: string;
|
|
64
|
-
}
|
|
65
65
|
declare function normalizeUrl(u: string): string;
|
|
66
66
|
declare function deriveUrlText(url: string): string;
|
|
67
67
|
declare function cosineNormalized(a: Float32Array, b: Float32Array): number;
|
|
68
|
-
declare function rankContentGaps(queries:
|
|
68
|
+
declare function rankContentGaps(queries: ContentGapQueryCandidate[], urls: string[], queryEmbeddings: Float32Array[], urlEmbeddings: Float32Array[], minDivergence: number): ContentGapResult[];
|
|
69
69
|
declare function analyzeContentGap(source: AnalysisQuerySource, opts?: ContentGapOptions): Promise<ContentGapAnalysis>;
|
|
70
70
|
export { type ContentGapAnalysis, type ContentGapOptions, type ContentGapProgress, type ContentGapResult, ContentGapSourceUnsupportedError, analyzeContentGap, cosineNormalized, deriveUrlText, normalizeUrl, rankContentGaps };
|