@pagebridge/core 0.0.3 → 0.2.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/LICENSE +21 -21
- package/dist/cannibalization-analyzer.d.ts +38 -0
- package/dist/cannibalization-analyzer.d.ts.map +1 -0
- package/dist/cannibalization-analyzer.js +109 -0
- package/dist/ctr-anomaly-analyzer.d.ts +46 -0
- package/dist/ctr-anomaly-analyzer.d.ts.map +1 -0
- package/dist/ctr-anomaly-analyzer.js +118 -0
- package/dist/daily-metrics-collector.d.ts +12 -0
- package/dist/daily-metrics-collector.d.ts.map +1 -0
- package/dist/daily-metrics-collector.js +47 -0
- package/dist/decay-detector.d.ts.map +1 -1
- package/dist/decay-detector.js +1 -13
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/insight-generator.d.ts +100 -0
- package/dist/insight-generator.d.ts.map +1 -0
- package/dist/insight-generator.js +286 -0
- package/dist/insight-writer.d.ts +26 -0
- package/dist/insight-writer.d.ts.map +1 -0
- package/dist/insight-writer.js +87 -0
- package/dist/publishing-impact-analyzer.d.ts +23 -0
- package/dist/publishing-impact-analyzer.d.ts.map +1 -0
- package/dist/publishing-impact-analyzer.js +79 -0
- package/dist/quick-win-analyzer.d.ts +30 -0
- package/dist/quick-win-analyzer.d.ts.map +1 -0
- package/dist/quick-win-analyzer.js +66 -0
- package/dist/site-insight-analyzer.d.ts +45 -0
- package/dist/site-insight-analyzer.d.ts.map +1 -0
- package/dist/site-insight-analyzer.js +144 -0
- package/dist/sync-engine.d.ts +36 -1
- package/dist/sync-engine.d.ts.map +1 -1
- package/dist/sync-engine.js +194 -82
- package/dist/utils/date-utils.d.ts +4 -0
- package/dist/utils/date-utils.d.ts.map +1 -0
- package/dist/utils/date-utils.js +13 -0
- package/dist/utils/sanity-key.d.ts +6 -0
- package/dist/utils/sanity-key.d.ts.map +1 -0
- package/dist/utils/sanity-key.js +8 -0
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Soma Somorjai
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Soma Somorjai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { DrizzleClient } from "@pagebridge/db";
|
|
2
|
+
import type { CannibalizationTarget } from "./sync-engine.js";
|
|
3
|
+
export interface CannibalizationGroup {
|
|
4
|
+
query: string;
|
|
5
|
+
pages: {
|
|
6
|
+
page: string;
|
|
7
|
+
clicks: number;
|
|
8
|
+
impressions: number;
|
|
9
|
+
position: number;
|
|
10
|
+
}[];
|
|
11
|
+
}
|
|
12
|
+
export interface CannibalizationConfig {
|
|
13
|
+
/** Minimum impressions per query to consider (default: 100) */
|
|
14
|
+
minImpressions: number;
|
|
15
|
+
/** Lookback window in days (default: 28) */
|
|
16
|
+
windowDays: number;
|
|
17
|
+
/** Days of data lag to skip (default: 3) */
|
|
18
|
+
lagDays: number;
|
|
19
|
+
}
|
|
20
|
+
export declare class CannibalizationAnalyzer {
|
|
21
|
+
private config;
|
|
22
|
+
constructor(db: DrizzleClient, config?: Partial<CannibalizationConfig>);
|
|
23
|
+
private db;
|
|
24
|
+
/**
|
|
25
|
+
* Finds queries where 2+ pages rank, grouped by query.
|
|
26
|
+
* Returns site-wide cannibalization groups.
|
|
27
|
+
*/
|
|
28
|
+
analyzeSiteWide(siteId: string): Promise<CannibalizationGroup[]>;
|
|
29
|
+
/**
|
|
30
|
+
* For each matched page, finds competing pages and shared queries.
|
|
31
|
+
* Returns a Map keyed by page URL.
|
|
32
|
+
*/
|
|
33
|
+
analyzeForPages(siteId: string, matches: {
|
|
34
|
+
gscUrl: string;
|
|
35
|
+
sanityId: string | undefined;
|
|
36
|
+
}[]): Promise<Map<string, CannibalizationTarget[]>>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=cannibalization-analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cannibalization-analyzer.d.ts","sourceRoot":"","sources":["../src/cannibalization-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAG9D,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;KAClB,EAAE,CAAC;CACL;AAED,MAAM,WAAW,qBAAqB;IACpC,+DAA+D;IAC/D,cAAc,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;CACjB;AAQD,qBAAa,uBAAuB;IAClC,OAAO,CAAC,MAAM,CAAwB;gBAE1B,EAAE,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC;IAKtE,OAAO,CAAC,EAAE,CAAgB;IAE1B;;;OAGG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IA6DtE;;;OAGG;IACG,eAAe,CACnB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,EAAE,GAC1D,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC,CAAC;CAgDjD"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { queryAnalytics } from "@pagebridge/db";
|
|
2
|
+
import { and, eq, gte, lte, sql } from "drizzle-orm";
|
|
3
|
+
import { daysAgo, formatDate } from "./utils/date-utils.js";
|
|
4
|
+
const defaultConfig = {
|
|
5
|
+
minImpressions: 100,
|
|
6
|
+
windowDays: 28,
|
|
7
|
+
lagDays: 3,
|
|
8
|
+
};
|
|
9
|
+
export class CannibalizationAnalyzer {
|
|
10
|
+
config;
|
|
11
|
+
constructor(db, config) {
|
|
12
|
+
this.db = db;
|
|
13
|
+
this.config = { ...defaultConfig, ...config };
|
|
14
|
+
}
|
|
15
|
+
db;
|
|
16
|
+
/**
|
|
17
|
+
* Finds queries where 2+ pages rank, grouped by query.
|
|
18
|
+
* Returns site-wide cannibalization groups.
|
|
19
|
+
*/
|
|
20
|
+
async analyzeSiteWide(siteId) {
|
|
21
|
+
const startDate = daysAgo(this.config.windowDays);
|
|
22
|
+
const endDate = daysAgo(this.config.lagDays);
|
|
23
|
+
// Get all query+page combos with significant impressions
|
|
24
|
+
const results = await this.db
|
|
25
|
+
.select({
|
|
26
|
+
page: queryAnalytics.page,
|
|
27
|
+
query: queryAnalytics.query,
|
|
28
|
+
totalClicks: sql `sum(${queryAnalytics.clicks})`,
|
|
29
|
+
totalImpressions: sql `sum(${queryAnalytics.impressions})`,
|
|
30
|
+
avgPosition: sql `avg(${queryAnalytics.position})`,
|
|
31
|
+
})
|
|
32
|
+
.from(queryAnalytics)
|
|
33
|
+
.where(and(eq(queryAnalytics.siteId, siteId), gte(queryAnalytics.date, formatDate(startDate)), lte(queryAnalytics.date, formatDate(endDate))))
|
|
34
|
+
.groupBy(queryAnalytics.page, queryAnalytics.query);
|
|
35
|
+
// Group by query and filter for queries with 2+ pages
|
|
36
|
+
const queryMap = new Map();
|
|
37
|
+
for (const row of results) {
|
|
38
|
+
const impressions = Number(row.totalImpressions) || 0;
|
|
39
|
+
if (impressions < this.config.minImpressions)
|
|
40
|
+
continue;
|
|
41
|
+
const existing = queryMap.get(row.query) ?? [];
|
|
42
|
+
existing.push({
|
|
43
|
+
page: row.page,
|
|
44
|
+
clicks: Number(row.totalClicks) || 0,
|
|
45
|
+
impressions,
|
|
46
|
+
position: Number(row.avgPosition) || 0,
|
|
47
|
+
});
|
|
48
|
+
queryMap.set(row.query, existing);
|
|
49
|
+
}
|
|
50
|
+
const groups = [];
|
|
51
|
+
for (const [query, pages] of queryMap) {
|
|
52
|
+
if (pages.length >= 2) {
|
|
53
|
+
pages.sort((a, b) => a.position - b.position);
|
|
54
|
+
groups.push({ query, pages });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Sort by total impressions across competing pages
|
|
58
|
+
groups.sort((a, b) => {
|
|
59
|
+
const aImpr = a.pages.reduce((s, p) => s + p.impressions, 0);
|
|
60
|
+
const bImpr = b.pages.reduce((s, p) => s + p.impressions, 0);
|
|
61
|
+
return bImpr - aImpr;
|
|
62
|
+
});
|
|
63
|
+
return groups;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* For each matched page, finds competing pages and shared queries.
|
|
67
|
+
* Returns a Map keyed by page URL.
|
|
68
|
+
*/
|
|
69
|
+
async analyzeForPages(siteId, matches) {
|
|
70
|
+
const groups = await this.analyzeSiteWide(siteId);
|
|
71
|
+
const matchedUrls = new Set(matches.map((m) => m.gscUrl));
|
|
72
|
+
const sanityIdByUrl = new Map();
|
|
73
|
+
for (const m of matches) {
|
|
74
|
+
if (m.sanityId)
|
|
75
|
+
sanityIdByUrl.set(m.gscUrl, m.sanityId);
|
|
76
|
+
}
|
|
77
|
+
const result = new Map();
|
|
78
|
+
for (const group of groups) {
|
|
79
|
+
const pagesInGroup = group.pages.map((p) => p.page);
|
|
80
|
+
const matchedPagesInGroup = pagesInGroup.filter((p) => matchedUrls.has(p));
|
|
81
|
+
if (matchedPagesInGroup.length === 0)
|
|
82
|
+
continue;
|
|
83
|
+
for (const page of matchedPagesInGroup) {
|
|
84
|
+
const competitors = pagesInGroup.filter((p) => p !== page);
|
|
85
|
+
if (competitors.length === 0)
|
|
86
|
+
continue;
|
|
87
|
+
const existing = result.get(page) ?? [];
|
|
88
|
+
for (const competitor of competitors) {
|
|
89
|
+
// Check if we already have this competitor for this page
|
|
90
|
+
const existingTarget = existing.find((t) => t.competingPage === competitor);
|
|
91
|
+
if (existingTarget) {
|
|
92
|
+
if (!existingTarget.sharedQueries.includes(group.query)) {
|
|
93
|
+
existingTarget.sharedQueries.push(group.query);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
existing.push({
|
|
98
|
+
competingPage: competitor,
|
|
99
|
+
competingDocumentId: sanityIdByUrl.get(competitor) ?? "",
|
|
100
|
+
sharedQueries: [group.query],
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
result.set(page, existing);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { DrizzleClient } from "@pagebridge/db";
|
|
2
|
+
/** Industry-average CTR by position (positions 1-10) */
|
|
3
|
+
export declare const EXPECTED_CTR_BY_POSITION: Record<number, number>;
|
|
4
|
+
export type CtrAnomalySeverity = "low" | "medium" | "high";
|
|
5
|
+
export interface CtrAnomaly {
|
|
6
|
+
page: string;
|
|
7
|
+
detected: boolean;
|
|
8
|
+
actualCtr: number;
|
|
9
|
+
expectedCtr: number;
|
|
10
|
+
positionBucket: number;
|
|
11
|
+
severity: CtrAnomalySeverity;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
export interface InsightAlert {
|
|
15
|
+
type: "ctr_anomaly" | "quick_win_available" | "position_decay" | "stale_content" | "cannibalization";
|
|
16
|
+
severity: "low" | "medium" | "high";
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
export interface CtrAnomalyConfig {
|
|
20
|
+
/** Minimum impressions over the period (default: 100) */
|
|
21
|
+
minImpressions: number;
|
|
22
|
+
/** Maximum average position to analyze (default: 10) */
|
|
23
|
+
maxPosition: number;
|
|
24
|
+
/** Ratio thresholds: actual/expected below this = high severity (default: 0.25) */
|
|
25
|
+
highThreshold: number;
|
|
26
|
+
/** Ratio thresholds: actual/expected below this = medium severity (default: 0.5) */
|
|
27
|
+
mediumThreshold: number;
|
|
28
|
+
/** Ratio thresholds: actual/expected below this = low severity (default: 0.75) */
|
|
29
|
+
lowThreshold: number;
|
|
30
|
+
}
|
|
31
|
+
export declare class CtrAnomalyAnalyzer {
|
|
32
|
+
private db;
|
|
33
|
+
private config;
|
|
34
|
+
constructor(db: DrizzleClient, config?: Partial<CtrAnomalyConfig>);
|
|
35
|
+
/**
|
|
36
|
+
* Analyzes pages for CTR anomalies — pages ranking in top 10 positions
|
|
37
|
+
* with CTR significantly below industry averages.
|
|
38
|
+
*/
|
|
39
|
+
analyze(siteId: string): Promise<Map<string, CtrAnomaly>>;
|
|
40
|
+
/**
|
|
41
|
+
* Builds alert objects from anomaly results + other insight data
|
|
42
|
+
* for writing to snapshot documents.
|
|
43
|
+
*/
|
|
44
|
+
static buildAlerts(ctrAnomaly?: CtrAnomaly, hasQuickWins?: boolean, hasDecay?: boolean, hasCannibalization?: boolean): InsightAlert[];
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=ctr-anomaly-analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ctr-anomaly-analyzer.d.ts","sourceRoot":"","sources":["../src/ctr-anomaly-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAKpD,wDAAwD;AACxD,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAW3D,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE3D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EACA,aAAa,GACb,qBAAqB,GACrB,gBAAgB,GAChB,eAAe,GACf,iBAAiB,CAAC;IACtB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,aAAa,EAAE,MAAM,CAAC;IACtB,oFAAoF;IACpF,eAAe,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,YAAY,EAAE,MAAM,CAAC;CACtB;AAUD,qBAAa,kBAAkB;IAI3B,OAAO,CAAC,EAAE;IAHZ,OAAO,CAAC,MAAM,CAAmB;gBAGvB,EAAE,EAAE,aAAa,EACzB,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC;IAKpC;;;OAGG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAkE/D;;;OAGG;IACH,MAAM,CAAC,WAAW,CAChB,UAAU,CAAC,EAAE,UAAU,EACvB,YAAY,CAAC,EAAE,OAAO,EACtB,QAAQ,CAAC,EAAE,OAAO,EAClB,kBAAkB,CAAC,EAAE,OAAO,GAC3B,YAAY,EAAE;CAqClB"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { searchAnalytics } from "@pagebridge/db";
|
|
2
|
+
import { and, eq, gte, lte, sql } from "drizzle-orm";
|
|
3
|
+
import { daysAgo, formatDate } from "./utils/date-utils.js";
|
|
4
|
+
/** Industry-average CTR by position (positions 1-10) */
|
|
5
|
+
export const EXPECTED_CTR_BY_POSITION = {
|
|
6
|
+
1: 0.319,
|
|
7
|
+
2: 0.246,
|
|
8
|
+
3: 0.185,
|
|
9
|
+
4: 0.133,
|
|
10
|
+
5: 0.095,
|
|
11
|
+
6: 0.069,
|
|
12
|
+
7: 0.052,
|
|
13
|
+
8: 0.041,
|
|
14
|
+
9: 0.033,
|
|
15
|
+
10: 0.028,
|
|
16
|
+
};
|
|
17
|
+
const defaultConfig = {
|
|
18
|
+
minImpressions: 100,
|
|
19
|
+
maxPosition: 10,
|
|
20
|
+
highThreshold: 0.25,
|
|
21
|
+
mediumThreshold: 0.5,
|
|
22
|
+
lowThreshold: 0.75,
|
|
23
|
+
};
|
|
24
|
+
export class CtrAnomalyAnalyzer {
|
|
25
|
+
db;
|
|
26
|
+
config;
|
|
27
|
+
constructor(db, config) {
|
|
28
|
+
this.db = db;
|
|
29
|
+
this.config = { ...defaultConfig, ...config };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Analyzes pages for CTR anomalies — pages ranking in top 10 positions
|
|
33
|
+
* with CTR significantly below industry averages.
|
|
34
|
+
*/
|
|
35
|
+
async analyze(siteId) {
|
|
36
|
+
const startDate = daysAgo(28);
|
|
37
|
+
const endDate = daysAgo(3);
|
|
38
|
+
const results = await this.db
|
|
39
|
+
.select({
|
|
40
|
+
page: searchAnalytics.page,
|
|
41
|
+
totalClicks: sql `sum(${searchAnalytics.clicks})`,
|
|
42
|
+
totalImpressions: sql `sum(${searchAnalytics.impressions})`,
|
|
43
|
+
avgPosition: sql `avg(${searchAnalytics.position})`,
|
|
44
|
+
})
|
|
45
|
+
.from(searchAnalytics)
|
|
46
|
+
.where(and(eq(searchAnalytics.siteId, siteId), gte(searchAnalytics.date, formatDate(startDate)), lte(searchAnalytics.date, formatDate(endDate))))
|
|
47
|
+
.groupBy(searchAnalytics.page);
|
|
48
|
+
const anomalies = new Map();
|
|
49
|
+
for (const row of results) {
|
|
50
|
+
const position = Number(row.avgPosition) || 0;
|
|
51
|
+
const impressions = Number(row.totalImpressions) || 0;
|
|
52
|
+
const clicks = Number(row.totalClicks) || 0;
|
|
53
|
+
if (position > this.config.maxPosition || position < 1)
|
|
54
|
+
continue;
|
|
55
|
+
if (impressions < this.config.minImpressions)
|
|
56
|
+
continue;
|
|
57
|
+
const positionBucket = Math.round(position);
|
|
58
|
+
const expectedCtr = EXPECTED_CTR_BY_POSITION[Math.min(positionBucket, 10)] ?? 0.028;
|
|
59
|
+
const actualCtr = impressions > 0 ? clicks / impressions : 0;
|
|
60
|
+
const ratio = expectedCtr > 0 ? actualCtr / expectedCtr : 1;
|
|
61
|
+
if (ratio >= this.config.lowThreshold)
|
|
62
|
+
continue;
|
|
63
|
+
const severity = ratio < this.config.highThreshold
|
|
64
|
+
? "high"
|
|
65
|
+
: ratio < this.config.mediumThreshold
|
|
66
|
+
? "medium"
|
|
67
|
+
: "low";
|
|
68
|
+
const expectedPct = (expectedCtr * 100).toFixed(1);
|
|
69
|
+
const actualPct = (actualCtr * 100).toFixed(1);
|
|
70
|
+
anomalies.set(row.page, {
|
|
71
|
+
page: row.page,
|
|
72
|
+
detected: true,
|
|
73
|
+
actualCtr,
|
|
74
|
+
expectedCtr,
|
|
75
|
+
positionBucket,
|
|
76
|
+
severity,
|
|
77
|
+
message: `CTR is ${actualPct}% vs ${expectedPct}% expected for position ${positionBucket}`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return anomalies;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Builds alert objects from anomaly results + other insight data
|
|
84
|
+
* for writing to snapshot documents.
|
|
85
|
+
*/
|
|
86
|
+
static buildAlerts(ctrAnomaly, hasQuickWins, hasDecay, hasCannibalization) {
|
|
87
|
+
const alerts = [];
|
|
88
|
+
if (ctrAnomaly?.detected) {
|
|
89
|
+
alerts.push({
|
|
90
|
+
type: "ctr_anomaly",
|
|
91
|
+
severity: ctrAnomaly.severity,
|
|
92
|
+
message: ctrAnomaly.message,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (hasQuickWins) {
|
|
96
|
+
alerts.push({
|
|
97
|
+
type: "quick_win_available",
|
|
98
|
+
severity: "low",
|
|
99
|
+
message: "Page 1 opportunities found — queries at positions 8-20",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (hasDecay) {
|
|
103
|
+
alerts.push({
|
|
104
|
+
type: "position_decay",
|
|
105
|
+
severity: "medium",
|
|
106
|
+
message: "Content decay detected — performance declining",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (hasCannibalization) {
|
|
110
|
+
alerts.push({
|
|
111
|
+
type: "cannibalization",
|
|
112
|
+
severity: "medium",
|
|
113
|
+
message: "Query cannibalization detected — multiple pages competing",
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return alerts;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DrizzleClient } from "@pagebridge/db";
|
|
2
|
+
import type { DailyMetricPoint } from "./sync-engine.js";
|
|
3
|
+
export declare class DailyMetricsCollector {
|
|
4
|
+
private db;
|
|
5
|
+
constructor(db: DrizzleClient);
|
|
6
|
+
/**
|
|
7
|
+
* Collects 28 days of daily metrics per page for sparkline charts.
|
|
8
|
+
* Returns a Map keyed by page URL.
|
|
9
|
+
*/
|
|
10
|
+
collect(siteId: string): Promise<Map<string, DailyMetricPoint[]>>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=daily-metrics-collector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daily-metrics-collector.d.ts","sourceRoot":"","sources":["../src/daily-metrics-collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGzD,qBAAa,qBAAqB;IACpB,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,aAAa;IAErC;;;OAGG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;CA6CxE"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { searchAnalytics } from "@pagebridge/db";
|
|
2
|
+
import { and, eq, gte, lte } from "drizzle-orm";
|
|
3
|
+
import { daysAgo, formatDate } from "./utils/date-utils.js";
|
|
4
|
+
export class DailyMetricsCollector {
|
|
5
|
+
db;
|
|
6
|
+
constructor(db) {
|
|
7
|
+
this.db = db;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Collects 28 days of daily metrics per page for sparkline charts.
|
|
11
|
+
* Returns a Map keyed by page URL.
|
|
12
|
+
*/
|
|
13
|
+
async collect(siteId) {
|
|
14
|
+
const startDate = daysAgo(31); // extra days for buffer
|
|
15
|
+
const endDate = daysAgo(3);
|
|
16
|
+
const results = await this.db
|
|
17
|
+
.select({
|
|
18
|
+
page: searchAnalytics.page,
|
|
19
|
+
date: searchAnalytics.date,
|
|
20
|
+
clicks: searchAnalytics.clicks,
|
|
21
|
+
impressions: searchAnalytics.impressions,
|
|
22
|
+
position: searchAnalytics.position,
|
|
23
|
+
})
|
|
24
|
+
.from(searchAnalytics)
|
|
25
|
+
.where(and(eq(searchAnalytics.siteId, siteId), gte(searchAnalytics.date, formatDate(startDate)), lte(searchAnalytics.date, formatDate(endDate))));
|
|
26
|
+
const map = new Map();
|
|
27
|
+
for (const row of results) {
|
|
28
|
+
const existing = map.get(row.page) ?? [];
|
|
29
|
+
existing.push({
|
|
30
|
+
date: row.date,
|
|
31
|
+
clicks: row.clicks ?? 0,
|
|
32
|
+
impressions: row.impressions ?? 0,
|
|
33
|
+
position: row.position ?? 0,
|
|
34
|
+
});
|
|
35
|
+
map.set(row.page, existing);
|
|
36
|
+
}
|
|
37
|
+
// Sort each page's points by date
|
|
38
|
+
for (const [page, points] of map) {
|
|
39
|
+
points.sort((a, b) => a.date.localeCompare(b.date));
|
|
40
|
+
// Keep last 28 points
|
|
41
|
+
if (points.length > 28) {
|
|
42
|
+
map.set(page, points.slice(-28));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return map;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decay-detector.d.ts","sourceRoot":"","sources":["../src/decay-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"decay-detector.d.ts","sourceRoot":"","sources":["../src/decay-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAKpD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,gBAAgB,GAAG,SAAS,GAAG,kBAAkB,CAAC;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,gBAAgB,GAAG,SAAS,GAAG,kBAAkB,CAAC;IAC1D,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,OAAO,EAAE;QACP,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AASD,eAAO,MAAM,YAAY,EAAE,SAAS,EAsBnC,CAAC;AAEF,qBAAa,aAAa;IAEtB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,KAAK;gBADL,EAAE,EAAE,aAAa,EACjB,KAAK,GAAE,SAAS,EAAiB;IAGrC,WAAW,CACf,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,EACjC,WAAW,GAAE,iBAA+C,GAC3D,OAAO,CAAC,WAAW,EAAE,CAAC;YAsCX,iBAAiB;IA8B/B,OAAO,CAAC,YAAY;IAwEpB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,kBAAkB;CAgB3B"}
|
package/dist/decay-detector.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { searchAnalytics } from "@pagebridge/db";
|
|
2
2
|
import { and, avg, gte, lte, sql, eq } from "drizzle-orm";
|
|
3
|
+
import { daysAgo, daysSince, formatDate } from "./utils/date-utils.js";
|
|
3
4
|
export const defaultRules = [
|
|
4
5
|
{
|
|
5
6
|
type: "position_decay",
|
|
@@ -160,16 +161,3 @@ export class DecayDetector {
|
|
|
160
161
|
return Array.from(byPage.values());
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
|
-
function daysAgo(days) {
|
|
164
|
-
const date = new Date();
|
|
165
|
-
date.setDate(date.getDate() - days);
|
|
166
|
-
return date;
|
|
167
|
-
}
|
|
168
|
-
function daysSince(date) {
|
|
169
|
-
const now = new Date();
|
|
170
|
-
const diff = now.getTime() - date.getTime();
|
|
171
|
-
return Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
172
|
-
}
|
|
173
|
-
function formatDate(date) {
|
|
174
|
-
return date.toISOString().split("T")[0];
|
|
175
|
-
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export { GSCClient, type GSCClientOptions, type IndexStatusResult, type IndexVerdict, } from "./gsc-client.js";
|
|
2
|
-
export { SyncEngine, type SyncOptions, type SyncResult, type IndexStatusSyncResult, } from "./sync-engine.js";
|
|
2
|
+
export { SyncEngine, type SyncOptions, type SyncResult, type IndexStatusSyncResult, type SnapshotInsights, type PublishingImpact, type CannibalizationTarget, type DailyMetricPoint, } from "./sync-engine.js";
|
|
3
3
|
export { DecayDetector, defaultRules, type DecayRule, type DecaySignal, type QuietPeriodConfig, } from "./decay-detector.js";
|
|
4
4
|
export { URLMatcher, type MatchResult, type URLMatcherConfig, type UnmatchReason, type MatchDiagnostics, } from "./url-matcher.js";
|
|
5
5
|
export { TaskGenerator, type TaskGeneratorOptions, type QueryContext, } from "./task-generator.js";
|
|
6
|
+
export { QuickWinAnalyzer, type QuickWinQuery, type QuickWinConfig, } from "./quick-win-analyzer.js";
|
|
7
|
+
export { CtrAnomalyAnalyzer, EXPECTED_CTR_BY_POSITION, type CtrAnomaly, type CtrAnomalySeverity, type CtrAnomalyConfig, type InsightAlert, } from "./ctr-anomaly-analyzer.js";
|
|
8
|
+
export { DailyMetricsCollector } from "./daily-metrics-collector.js";
|
|
9
|
+
export { PublishingImpactAnalyzer, type EditDateInfo, } from "./publishing-impact-analyzer.js";
|
|
10
|
+
export { CannibalizationAnalyzer, type CannibalizationGroup, type CannibalizationConfig, } from "./cannibalization-analyzer.js";
|
|
11
|
+
export { SiteInsightAnalyzer, type SiteInsightData, type TopPerformer, type ZeroClickPage, type OrphanPage, type NewKeywordOpportunity, } from "./site-insight-analyzer.js";
|
|
12
|
+
export { InsightWriter } from "./insight-writer.js";
|
|
13
|
+
export { daysAgo, formatDate, daysSince } from "./utils/date-utils.js";
|
|
6
14
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,YAAY,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,qBAAqB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,YAAY,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,YAAY,EACZ,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,iBAAiB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,YAAY,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,YAAY,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EACL,wBAAwB,EACxB,KAAK,YAAY,GAClB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,uBAAuB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,qBAAqB,GAC3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,3 +3,11 @@ export { SyncEngine, } from "./sync-engine.js";
|
|
|
3
3
|
export { DecayDetector, defaultRules, } from "./decay-detector.js";
|
|
4
4
|
export { URLMatcher, } from "./url-matcher.js";
|
|
5
5
|
export { TaskGenerator, } from "./task-generator.js";
|
|
6
|
+
export { QuickWinAnalyzer, } from "./quick-win-analyzer.js";
|
|
7
|
+
export { CtrAnomalyAnalyzer, EXPECTED_CTR_BY_POSITION, } from "./ctr-anomaly-analyzer.js";
|
|
8
|
+
export { DailyMetricsCollector } from "./daily-metrics-collector.js";
|
|
9
|
+
export { PublishingImpactAnalyzer, } from "./publishing-impact-analyzer.js";
|
|
10
|
+
export { CannibalizationAnalyzer, } from "./cannibalization-analyzer.js";
|
|
11
|
+
export { SiteInsightAnalyzer, } from "./site-insight-analyzer.js";
|
|
12
|
+
export { InsightWriter } from "./insight-writer.js";
|
|
13
|
+
export { daysAgo, formatDate, daysSince } from "./utils/date-utils.js";
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type CtrAnomaly } from "./ctr-anomaly-analyzer.js";
|
|
2
|
+
import type { DecaySignal } from "./decay-detector.js";
|
|
3
|
+
import type { QuickWinQuery } from "./quick-win-analyzer.js";
|
|
4
|
+
/**
|
|
5
|
+
* Represents a single insight with unified structure
|
|
6
|
+
*/
|
|
7
|
+
export interface SeoInsightData {
|
|
8
|
+
insightId: string;
|
|
9
|
+
insightType: "quick_win" | "ctr_anomaly" | "position_decay" | "low_ctr" | "impressions_drop" | "cannibalization" | "zero_click" | "new_keyword" | "publishing_impact";
|
|
10
|
+
status: "open";
|
|
11
|
+
priority: "low" | "medium" | "high" | "critical";
|
|
12
|
+
opportunityScore: number;
|
|
13
|
+
reason: string;
|
|
14
|
+
recommendation: string;
|
|
15
|
+
url: string;
|
|
16
|
+
pathScope: string;
|
|
17
|
+
property?: string;
|
|
18
|
+
linkedDocumentId?: string;
|
|
19
|
+
metrics: Record<string, number>;
|
|
20
|
+
queries?: Array<{
|
|
21
|
+
query: string;
|
|
22
|
+
impressions: number;
|
|
23
|
+
clicks: number;
|
|
24
|
+
position: number;
|
|
25
|
+
}>;
|
|
26
|
+
dateRange: {
|
|
27
|
+
start: string;
|
|
28
|
+
end: string;
|
|
29
|
+
};
|
|
30
|
+
detectedAt: string;
|
|
31
|
+
}
|
|
32
|
+
export interface CannibalizationTarget {
|
|
33
|
+
competingPage: string;
|
|
34
|
+
competingDocumentId?: string;
|
|
35
|
+
sharedQueries: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface MatchResult {
|
|
38
|
+
gscUrl: string;
|
|
39
|
+
sanityId?: string;
|
|
40
|
+
documentTitle?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ZeroClickPage {
|
|
43
|
+
page: string;
|
|
44
|
+
documentId?: string;
|
|
45
|
+
documentTitle?: string;
|
|
46
|
+
impressions: number;
|
|
47
|
+
clicks: number;
|
|
48
|
+
}
|
|
49
|
+
export interface NewKeywordOpportunity {
|
|
50
|
+
query: string;
|
|
51
|
+
page: string;
|
|
52
|
+
documentId?: string;
|
|
53
|
+
impressions: number;
|
|
54
|
+
position: number;
|
|
55
|
+
}
|
|
56
|
+
export interface PublishingImpact {
|
|
57
|
+
lastEditedAt: string;
|
|
58
|
+
daysSinceEdit: number;
|
|
59
|
+
positionBefore?: number;
|
|
60
|
+
positionAfter?: number;
|
|
61
|
+
positionDelta?: number;
|
|
62
|
+
clicksBefore?: number;
|
|
63
|
+
clicksAfter?: number;
|
|
64
|
+
impressionsBefore?: number;
|
|
65
|
+
impressionsAfter?: number;
|
|
66
|
+
ctrBefore?: number;
|
|
67
|
+
ctrAfter?: number;
|
|
68
|
+
}
|
|
69
|
+
export interface InsightGeneratorOptions {
|
|
70
|
+
quickWins?: Map<string, QuickWinQuery[]>;
|
|
71
|
+
ctrAnomalies?: Map<string, CtrAnomaly>;
|
|
72
|
+
decaySignals?: DecaySignal[];
|
|
73
|
+
cannibalizationTargets?: Map<string, CannibalizationTarget[]>;
|
|
74
|
+
publishingImpact?: Map<string, PublishingImpact>;
|
|
75
|
+
zeroClickPages?: ZeroClickPage[];
|
|
76
|
+
newKeywords?: NewKeywordOpportunity[];
|
|
77
|
+
matches?: MatchResult[];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Converts all analyzer outputs into unified insight documents
|
|
81
|
+
*/
|
|
82
|
+
export declare class InsightGenerator {
|
|
83
|
+
private siteId;
|
|
84
|
+
private property;
|
|
85
|
+
constructor(siteId: string, property: string);
|
|
86
|
+
/**
|
|
87
|
+
* Generate unified insights from all analyzer results
|
|
88
|
+
* Returns a Map keyed by insightId for idempotency
|
|
89
|
+
*/
|
|
90
|
+
generate(options: InsightGeneratorOptions): Map<string, SeoInsightData>;
|
|
91
|
+
private makeId;
|
|
92
|
+
private prioritizeQuickWin;
|
|
93
|
+
private scoreQuickWin;
|
|
94
|
+
private scoreCtrAnomaly;
|
|
95
|
+
private scoreDecay;
|
|
96
|
+
private estimateCtrFromPosition;
|
|
97
|
+
private formatDecayReason;
|
|
98
|
+
private formatDecayRecommendation;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=insight-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insight-generator.d.ts","sourceRoot":"","sources":["../src/insight-generator.ts"],"names":[],"mappings":"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"}
|