@houtini/seo-audit-console 0.3.0 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/dist/audit/recon.d.ts +124 -0
- package/dist/audit/recon.d.ts.map +1 -0
- package/dist/audit/recon.js +257 -0
- package/dist/audit/recon.js.map +1 -0
- package/dist/core/AuditDatabase.d.ts.map +1 -1
- package/dist/core/AuditDatabase.js +39 -0
- package/dist/core/AuditDatabase.js.map +1 -1
- package/dist/core/DataForSeoClient.d.ts +9 -2
- package/dist/core/DataForSeoClient.d.ts.map +1 -1
- package/dist/core/DataForSeoClient.js +11 -3
- package/dist/core/DataForSeoClient.js.map +1 -1
- package/dist/core/FirecrawlClient.d.ts +39 -0
- package/dist/core/FirecrawlClient.d.ts.map +1 -0
- package/dist/core/FirecrawlClient.js +86 -0
- package/dist/core/FirecrawlClient.js.map +1 -0
- package/dist/core/SupadataClient.d.ts +29 -0
- package/dist/core/SupadataClient.d.ts.map +1 -0
- package/dist/core/SupadataClient.js +63 -0
- package/dist/core/SupadataClient.js.map +1 -0
- package/dist/core/passageScore.d.ts +40 -9
- package/dist/core/passageScore.d.ts.map +1 -1
- package/dist/core/passageScore.js +29 -3
- package/dist/core/passageScore.js.map +1 -1
- package/dist/core/reconFetch.d.ts +29 -0
- package/dist/core/reconFetch.d.ts.map +1 -0
- package/dist/core/reconFetch.js +49 -0
- package/dist/core/reconFetch.js.map +1 -0
- package/dist/core/reconResearch.d.ts +50 -0
- package/dist/core/reconResearch.d.ts.map +1 -0
- package/dist/core/reconResearch.js +122 -0
- package/dist/core/reconResearch.js.map +1 -0
- package/dist/core/serpRecon.d.ts +52 -0
- package/dist/core/serpRecon.d.ts.map +1 -0
- package/dist/core/serpRecon.js +99 -0
- package/dist/core/serpRecon.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +334 -6
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/server.json +18 -4
package/README.md
CHANGED
|
@@ -123,6 +123,7 @@ Each of these is a real procedure I use, and each is one prompt. The expanded ve
|
|
|
123
123
|
9. **Reporting.** *"Show me the dashboard"* in the chat, *"serve the dashboard"* for the live version in your browser, or *"export the report"* as one self-contained HTML file you can send a client. → [dashboard.md](manual/dashboard.md)
|
|
124
124
|
10. **The content opportunity report.** If you write for a living, this is your page one: *"Content opportunities for mysite.com"* returns what to WRITE next (demand you already earn impressions for with no winning page), what to REFRESH (pages bleeding clicks), which SNIPPETS to rewrite (ranking well, under-clicked), and which clusters to STRENGTHEN (position 4-20, one push from the money). Every row traces to your own Search Console data - no invented keyword ideas - and *"draft the brief for row 1"* turns any of them into a grounded outline. → [competitive.md](manual/competitive.md)
|
|
125
125
|
11. **Market Sizing and Prioritisation.** *"Size the market: mysite.com vs rival1.com and rival2.com"* - the keyword universe, total monthly demand, and share of voice per topic cluster, from one cached call per domain. The engagement-opening read that used to need a Semrush subscription. → [competitive.md](manual/competitive.md)
|
|
126
|
+
12. **Content recon - why is this page losing?** *"Run content recon on mysite.com"* takes your worst declining pages, pulls the live Google SERP for each, and reads whether the **AI Overview** cites you. The verdict is the honest bit: *rank but not cited* is a data-accuracy or freshness problem, not a rewrite. It transcribes the ranking videos (Supadata), reads the reachable competitors (Firecrawl, plus a free browser-profile fetch that even gets Reddit), and writes the gaps into a tracked to-do board you can annotate and re-measure. → [competitive.md](manual/competitive.md)
|
|
126
127
|
|
|
127
128
|

|
|
128
129
|
|
|
@@ -132,7 +133,9 @@ Each of these is a real procedure I use, and each is one prompt. The expanded ve
|
|
|
132
133
|
|
|
133
134
|
Everything above works with just your Search Console data. But GSC can only describe searches where you already appear. The moment your question is "how big is this market?" or "what do competitors rank for that I don't?", you need third-party data - and that's [DataForSEO](https://dataforseo.com/?aff=213701): a pay-as-you-go API for volumes, live rankings, competitor data and Lighthouse runs. No subscription; calls cost fractions of a cent to a few cents, cached for 20 days, and only ever run when you ask. My own usage runs to a few dollars a month.
|
|
134
135
|
|
|
135
|
-
It unlocks the Semrush-replacement layer: the organic visibility overview for any domain, any site's top pages and ranked keywords (including which keywords cite a site in **AI Overviews**), the content gap, topic gaps, search intent, lab Core Web Vitals and backlinks. The full workflows and setup: [competitive.md](manual/competitive.md).
|
|
136
|
+
It unlocks the Semrush-replacement layer: the organic visibility overview for any domain, any site's top pages and ranked keywords (including which keywords cite a site in **AI Overviews**), the content gap, topic gaps, search intent, lab Core Web Vitals and backlinks. It also drives **content recon** - `recon_targets` pulls the live Google SERP for a losing page, reads whether the AI Overview cites you, and tells you *why* you're behind. The full workflows and setup: [competitive.md](manual/competitive.md).
|
|
137
|
+
|
|
138
|
+
**Two more optional keys, for content recon only.** When you ask recon to fetch the competitors it finds, it routes each by source: videos to [Supadata](https://supadata.ai) (`SUPADATA_API_KEY`) for transcripts, and competitor pages to [Firecrawl](https://firecrawl.link/2d1PLD8) (`FIRECRAWL_API_KEY`) for clean markdown, with a free browser-profile fetch behind it. Both are pay-as-you-go and entirely optional - recon still classifies the page and lists the competitors without them. Grab a Firecrawl key here: **https://firecrawl.link/2d1PLD8**.
|
|
136
139
|
|
|
137
140
|
---
|
|
138
141
|
|
|
@@ -169,7 +172,10 @@ The one-line version - full descriptions, inputs and example prompts for every t
|
|
|
169
172
|
| `competitors_domain` · `page_intersection` · `topic_gaps` | Competitors · content gap · topic gaps |
|
|
170
173
|
| `domain_visibility` · `top_pages` · `ranked_keywords` | The Semrush-style views, any domain (+ `aioOnly` for AI Overview citations) |
|
|
171
174
|
| `page_lighthouse` · `pull_backlinks` | Lab CWV · backlink profile with live status |
|
|
172
|
-
| `
|
|
175
|
+
| `market_sizing` · `serp_features` | Share of voice vs named competitors · AI-Overview / snippet / video exposure by volume |
|
|
176
|
+
| `keyword_list` · `content_opportunities` | Cluster a keyword list (own / weak / absent) · the content marketer's report |
|
|
177
|
+
| `recon_targets` · `save_recon_todo` · `recon_todos` | **Content recon:** why a page is losing (live SERP + AI Overview) → a tracked, annotatable to-do board |
|
|
178
|
+
| `get_dashboard` · `serve_dashboard` · `export_report` | In-chat dashboard · the same in a local browser tab · shareable HTML |
|
|
173
179
|
| `composition_cookbook` | The data-surface map and recipes for bespoke analyses |
|
|
174
180
|
| `data_storage` | Per-property disk usage and row counts, with confirm-gated pruning |
|
|
175
181
|
| `normalize_url` · `data_location` · `list_properties` · `seo_audit_help` | Utilities and the help menu |
|
|
@@ -194,7 +200,6 @@ Your Search Console data and the crawl live in local SQLite files under `SAC_DAT
|
|
|
194
200
|
|
|
195
201
|
A few things I'm building next, in rough order:
|
|
196
202
|
|
|
197
|
-
- **List mode.** Paste any list of URLs - a migration map, old ranking pages, a PPC export - and have it status-checked and audited. The migration-verification workflow, basically.
|
|
198
203
|
- **Structured-data opportunities, by template.** Not "you have no schema" (most modern stores have plenty), but "this template could earn review stars or an FAQ rich result and doesn't."
|
|
199
204
|
- **A per-page content scorecard.** A dedicated table of *every* ranking phrase your copy misses, not just the top query.
|
|
200
205
|
- **More agent readiness.** A WebMCP advisory (which tool actions your site could expose to agents) and the agent-commerce protocols.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
import type { OwnPageView } from '../core/reconFetch.js';
|
|
3
|
+
import type { SerpReconResult, ReconVerdict } from '../core/serpRecon.js';
|
|
4
|
+
/**
|
|
5
|
+
* Content recon core: pick the pages worth a deep competitive look, and turn what we can
|
|
6
|
+
* determine WITHOUT judgement (schema gaps, freshness, cannibalisation, SERP format) into
|
|
7
|
+
* a trackable to-do ledger. The judgement to-dos (what competitors cover that we don't)
|
|
8
|
+
* are written back by the research session via save_recon_todo.
|
|
9
|
+
*/
|
|
10
|
+
export interface ReconTarget {
|
|
11
|
+
urlKey: string;
|
|
12
|
+
query: string;
|
|
13
|
+
impressions: number;
|
|
14
|
+
clicks: number;
|
|
15
|
+
position: number;
|
|
16
|
+
priorPosition: number | null;
|
|
17
|
+
slipped: boolean;
|
|
18
|
+
competingUrls: number;
|
|
19
|
+
}
|
|
20
|
+
/** Pick declining / striking-distance pages: high impressions, currently pos 3-15, top query per page. */
|
|
21
|
+
export declare function selectReconTargets(db: Database.Database, opts: {
|
|
22
|
+
limit?: number;
|
|
23
|
+
minImpressions?: number;
|
|
24
|
+
maxDate: string;
|
|
25
|
+
}): ReconTarget[];
|
|
26
|
+
/** What the CRAWL says about this URL — the reality check the SERP + GSC read has to survive.
|
|
27
|
+
* GSC history is legacy: it keeps reporting impressions for a URL that has since been
|
|
28
|
+
* canonicalised away or set noindex, and classifying off that alone produced a
|
|
29
|
+
* "defend-and-deepen, produce a companion video" verdict for a non-indexable duplicate. */
|
|
30
|
+
export interface PageState {
|
|
31
|
+
urlKey: string;
|
|
32
|
+
crawled: boolean;
|
|
33
|
+
indexable: boolean | null;
|
|
34
|
+
indexableReason: string | null;
|
|
35
|
+
canonicalKey: string | null;
|
|
36
|
+
canonicalisedAway: boolean;
|
|
37
|
+
statusCode: number | null;
|
|
38
|
+
inlinkCount: number | null;
|
|
39
|
+
ipr: number | null;
|
|
40
|
+
cannotRank: boolean;
|
|
41
|
+
}
|
|
42
|
+
export declare function pageState(db: Database.Database, urlKey: string): PageState;
|
|
43
|
+
/** A crawl row that contradicts the SERP/GSC read WINS. Returns null when there's no conflict. */
|
|
44
|
+
export declare function crawlRealityOverride(v: {
|
|
45
|
+
verdict: ReconVerdict;
|
|
46
|
+
note: string;
|
|
47
|
+
}, state: PageState): {
|
|
48
|
+
verdict: ReconVerdict;
|
|
49
|
+
note: string;
|
|
50
|
+
} | null;
|
|
51
|
+
export interface DraftTodo {
|
|
52
|
+
action: string;
|
|
53
|
+
type: string;
|
|
54
|
+
rationale: string;
|
|
55
|
+
evidence: Record<string, unknown>;
|
|
56
|
+
priority: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Priority = OPPORTUNITY, not impressions. Raw impressions ranked a #4 defend-and-deepen page
|
|
60
|
+
* above the genuinely broken one, i.e. roughly the wrong working order. This prices the clicks
|
|
61
|
+
* actually available: impressions x (CTR at a realistic target position - CTR where we are now),
|
|
62
|
+
* damped by the verdict's headroom, then weighted by to-do type.
|
|
63
|
+
*/
|
|
64
|
+
export interface PriorityBasis {
|
|
65
|
+
model: string;
|
|
66
|
+
impressions: number;
|
|
67
|
+
position: number;
|
|
68
|
+
positionSource: 'live-serp' | 'gsc-average';
|
|
69
|
+
targetPosition: number;
|
|
70
|
+
opportunityClicks: number;
|
|
71
|
+
verdictFactor: number;
|
|
72
|
+
}
|
|
73
|
+
export declare function opportunityBasis(impressions: number, organicRank: number | null, gscPosition: number, verdict: string): PriorityBasis;
|
|
74
|
+
/** The entity-ish terms of a query: the tokens a sibling page would have to share to be competing.
|
|
75
|
+
* Minimum length 2 so short but load-bearing terms ("vr", "ai") survive. */
|
|
76
|
+
export declare function queryCore(query: string): string[];
|
|
77
|
+
export interface ClusterCannibalisation {
|
|
78
|
+
core: string[];
|
|
79
|
+
clusterQueries: number;
|
|
80
|
+
collidingQueries: number;
|
|
81
|
+
urls: {
|
|
82
|
+
urlKey: string;
|
|
83
|
+
impressions: number;
|
|
84
|
+
position: number;
|
|
85
|
+
queries: number;
|
|
86
|
+
}[];
|
|
87
|
+
cannibalising: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Competition is judged PER QUERY and then unioned across the cluster. Judging it on cluster
|
|
91
|
+
* TOTALS is what hid the overlap: the leader accumulates across every query in the topic, so a
|
|
92
|
+
* genuine rival page falls under the 10%-of-leader guard and disappears. Here a URL qualifies if
|
|
93
|
+
* it competes on any single cluster query (>=10 impressions, >=10% of that query's leader, avg
|
|
94
|
+
* position inside 20) — and we separately count the queries where two or more of them collide,
|
|
95
|
+
* which is what distinguishes cannibalisation from ordinary topical coverage.
|
|
96
|
+
*/
|
|
97
|
+
export declare function clusterCannibalisation(db: Database.Database, query: string, maxDate: string, minImpressions?: number): ClusterCannibalisation;
|
|
98
|
+
/** The to-dos we can generate deterministically from our page + the SERP (no judgement). */
|
|
99
|
+
export declare function deterministicTodos(target: ReconTarget, own: OwnPageView, serp: SerpReconResult, verdict: {
|
|
100
|
+
verdict: ReconVerdict;
|
|
101
|
+
note: string;
|
|
102
|
+
}, todayIso: string, ctx?: {
|
|
103
|
+
state?: PageState;
|
|
104
|
+
cluster?: ClusterCannibalisation;
|
|
105
|
+
}): DraftTodo[];
|
|
106
|
+
export declare function persistReconPage(db: Database.Database, p: {
|
|
107
|
+
urlKey: string;
|
|
108
|
+
query: string;
|
|
109
|
+
verdict: string;
|
|
110
|
+
verdictNote: string;
|
|
111
|
+
organicRank: number | null;
|
|
112
|
+
gscPosition: number;
|
|
113
|
+
gscImpressions: number;
|
|
114
|
+
aioPresent: boolean;
|
|
115
|
+
aioCitesUs: boolean | null;
|
|
116
|
+
videoPresent: boolean;
|
|
117
|
+
hasProductSchema: boolean;
|
|
118
|
+
schemaTypes: string[];
|
|
119
|
+
competitors: unknown;
|
|
120
|
+
}): void;
|
|
121
|
+
/** Insert to-dos for a page. `baseline` snapshots the rank/citation state so outcome is measurable.
|
|
122
|
+
* De-dupes on (url_key, action) so a re-run doesn't pile up identical open items. */
|
|
123
|
+
export declare function insertTodos(db: Database.Database, urlKey: string, query: string, todos: DraftTodo[], baseline: Record<string, unknown>, source: 'auto' | 'research'): number;
|
|
124
|
+
//# sourceMappingURL=recon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recon.d.ts","sourceRoot":"","sources":["../../src/audit/recon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { expectedCtr } from '../core/ctrModel.js';
|
|
2
|
+
/** Pick declining / striking-distance pages: high impressions, currently pos 3-15, top query per page. */
|
|
3
|
+
export function selectReconTargets(db, opts) {
|
|
4
|
+
const { limit = 5, minImpressions = 300, maxDate } = opts;
|
|
5
|
+
const rows = db.prepare(`
|
|
6
|
+
WITH cur AS (
|
|
7
|
+
SELECT page_key, query, SUM(impressions) imp, SUM(clicks) clk,
|
|
8
|
+
SUM(position*impressions)*1.0/NULLIF(SUM(impressions),0) pos
|
|
9
|
+
FROM search_analytics
|
|
10
|
+
WHERE page_key IS NOT NULL AND query IS NOT NULL
|
|
11
|
+
AND date > date(?, '-28 days') AND date <= ?
|
|
12
|
+
GROUP BY page_key, query
|
|
13
|
+
),
|
|
14
|
+
ranked AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY page_key ORDER BY imp DESC) rn FROM cur),
|
|
15
|
+
prior AS (
|
|
16
|
+
SELECT page_key, query, SUM(position*impressions)*1.0/NULLIF(SUM(impressions),0) pos
|
|
17
|
+
FROM search_analytics
|
|
18
|
+
WHERE page_key IS NOT NULL AND query IS NOT NULL
|
|
19
|
+
AND date > date(?, '-56 days') AND date <= date(?, '-28 days')
|
|
20
|
+
GROUP BY page_key, query
|
|
21
|
+
)
|
|
22
|
+
SELECT r.page_key, r.query, r.imp, r.clk, r.pos, p.pos AS prior_pos
|
|
23
|
+
FROM ranked r
|
|
24
|
+
LEFT JOIN prior p ON p.page_key = r.page_key AND p.query = r.query
|
|
25
|
+
WHERE r.rn = 1 AND r.pos BETWEEN 3 AND 15 AND r.imp >= ?
|
|
26
|
+
ORDER BY r.imp DESC
|
|
27
|
+
LIMIT ?`).all(maxDate, maxDate, maxDate, maxDate, minImpressions, limit);
|
|
28
|
+
const cannStmt = db.prepare(`
|
|
29
|
+
SELECT COUNT(*) n FROM (
|
|
30
|
+
SELECT page_key FROM search_analytics
|
|
31
|
+
WHERE query = ? AND page_key IS NOT NULL AND date > date(?, '-28 days') AND date <= ?
|
|
32
|
+
GROUP BY page_key HAVING SUM(impressions) >= 10
|
|
33
|
+
)`);
|
|
34
|
+
return rows.map(r => {
|
|
35
|
+
const pos = Math.round(r.pos * 10) / 10;
|
|
36
|
+
const priorPosition = r.prior_pos != null ? Math.round(r.prior_pos * 10) / 10 : null;
|
|
37
|
+
const cann = cannStmt.get(r.query, maxDate, maxDate);
|
|
38
|
+
return {
|
|
39
|
+
urlKey: r.page_key, query: r.query, impressions: r.imp, clicks: r.clk, position: pos,
|
|
40
|
+
priorPosition,
|
|
41
|
+
slipped: priorPosition != null && pos > priorPosition + 0.5,
|
|
42
|
+
competingUrls: cann.n,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function pageState(db, urlKey) {
|
|
47
|
+
const row = db.prepare(`SELECT status_code, indexable, indexable_reason, canonical_key, inlink_count, ipr FROM pages WHERE url_key = ?`).get(urlKey);
|
|
48
|
+
if (!row) {
|
|
49
|
+
return { urlKey, crawled: false, indexable: null, indexableReason: null, canonicalKey: null, canonicalisedAway: false, statusCode: null, inlinkCount: null, ipr: null, cannotRank: false };
|
|
50
|
+
}
|
|
51
|
+
const canonicalisedAway = !!row.canonical_key && row.canonical_key !== urlKey;
|
|
52
|
+
const indexable = row.indexable == null ? null : !!row.indexable;
|
|
53
|
+
return {
|
|
54
|
+
urlKey, crawled: true, indexable, indexableReason: row.indexable_reason,
|
|
55
|
+
canonicalKey: row.canonical_key, canonicalisedAway,
|
|
56
|
+
statusCode: row.status_code, inlinkCount: row.inlink_count, ipr: row.ipr,
|
|
57
|
+
cannotRank: indexable === false || canonicalisedAway,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** A crawl row that contradicts the SERP/GSC read WINS. Returns null when there's no conflict. */
|
|
61
|
+
export function crawlRealityOverride(v, state) {
|
|
62
|
+
if (!state.cannotRank)
|
|
63
|
+
return null;
|
|
64
|
+
const why = state.indexable === false
|
|
65
|
+
? `the crawl marks it not indexable (${state.indexableReason ?? 'reason not recorded'})`
|
|
66
|
+
: `it declares a canonical pointing at ${state.canonicalKey}`;
|
|
67
|
+
const equity = `${state.inlinkCount ?? 0} internal link(s), iPR ${state.ipr ?? 0}`;
|
|
68
|
+
return {
|
|
69
|
+
verdict: 'page-cannot-rank',
|
|
70
|
+
note: `This URL cannot rank as it stands: ${why} (${equity}). Its Search Console history is legacy — the SERP read ("${v.verdict}") describes whoever Google actually ranks, not this URL. Resolve the duplicate first; content work here is wasted.`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const TYPE_WEIGHT = {
|
|
74
|
+
'content-gap': 1.0, schema: 0.9, research: 0.8, cannibalisation: 0.7, format: 0.6, freshness: 0.3,
|
|
75
|
+
};
|
|
76
|
+
/** How much headroom the verdict implies. A page already sitting at #4 with nobody editorial
|
|
77
|
+
* above it has far less to gain than one losing a competitive gap, even at higher impressions. */
|
|
78
|
+
const VERDICT_FACTOR = {
|
|
79
|
+
'competitive-gap': 1.0, 'accuracy-or-freshness': 1.0, 'consolidate-weak-page': 0.9,
|
|
80
|
+
'page-cannot-rank': 0.8, 'defend-and-deepen': 0.4,
|
|
81
|
+
};
|
|
82
|
+
const looksCommercial = (q) => /\b(best|top|vs|review|compare|comparison|cheap|budget|buy)\b/i.test(q);
|
|
83
|
+
export function opportunityBasis(impressions, organicRank, gscPosition, verdict) {
|
|
84
|
+
const position = Math.max(1, organicRank ?? gscPosition);
|
|
85
|
+
const targetPosition = position <= 3 ? 1 : position <= 10 ? 3 : 8;
|
|
86
|
+
const opportunityClicks = Math.max(0, impressions * (expectedCtr(targetPosition) - expectedCtr(position)));
|
|
87
|
+
return {
|
|
88
|
+
model: 'impressions x (expectedCtr(target) - expectedCtr(current)) x verdictFactor x typeWeight',
|
|
89
|
+
impressions, position: Math.round(position * 10) / 10,
|
|
90
|
+
positionSource: organicRank != null ? 'live-serp' : 'gsc-average',
|
|
91
|
+
targetPosition,
|
|
92
|
+
opportunityClicks: Math.round(opportunityClicks),
|
|
93
|
+
verdictFactor: VERDICT_FACTOR[verdict] ?? 1,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Cannibalisation at CLUSTER level, not one query at a time. Keying off a single query missed a
|
|
97
|
+
* four-URL overlap that only showed up across the whole "<entity>" query family. */
|
|
98
|
+
const CLUSTER_STOP = new Set(['the', 'and', 'for', 'with', 'best', 'top', 'vs', 'versus', 'review', 'reviews', 'guide', 'how', 'what', 'which', 'why', 'you', 'your', 'cheap', 'budget', 'buy', 'compare', 'comparison', '2024', '2025', '2026', '2027']);
|
|
99
|
+
/** The entity-ish terms of a query: the tokens a sibling page would have to share to be competing.
|
|
100
|
+
* Minimum length 2 so short but load-bearing terms ("vr", "ai") survive. */
|
|
101
|
+
export function queryCore(query) {
|
|
102
|
+
return [...new Set(query.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length >= 2 && !CLUSTER_STOP.has(t)))].slice(0, 4);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Competition is judged PER QUERY and then unioned across the cluster. Judging it on cluster
|
|
106
|
+
* TOTALS is what hid the overlap: the leader accumulates across every query in the topic, so a
|
|
107
|
+
* genuine rival page falls under the 10%-of-leader guard and disappears. Here a URL qualifies if
|
|
108
|
+
* it competes on any single cluster query (>=10 impressions, >=10% of that query's leader, avg
|
|
109
|
+
* position inside 20) — and we separately count the queries where two or more of them collide,
|
|
110
|
+
* which is what distinguishes cannibalisation from ordinary topical coverage.
|
|
111
|
+
*/
|
|
112
|
+
export function clusterCannibalisation(db, query, maxDate, minImpressions = 10) {
|
|
113
|
+
const core = queryCore(query);
|
|
114
|
+
const none = { core, clusterQueries: 0, collidingQueries: 0, urls: [], cannibalising: false };
|
|
115
|
+
if (!core.length)
|
|
116
|
+
return none;
|
|
117
|
+
const like = core.map(() => 'query LIKE ?').join(' AND ');
|
|
118
|
+
const terms = core.map(t => `%${t}%`);
|
|
119
|
+
const win = `date > date(?, '-28 days') AND date <= ?`;
|
|
120
|
+
const cte = `WITH q AS (
|
|
121
|
+
SELECT query, page_key, SUM(impressions) impr,
|
|
122
|
+
SUM(position*impressions)*1.0/NULLIF(SUM(impressions),0) pos
|
|
123
|
+
FROM search_analytics
|
|
124
|
+
WHERE page_key IS NOT NULL AND query IS NOT NULL AND ${win} AND ${like}
|
|
125
|
+
GROUP BY query, page_key
|
|
126
|
+
),
|
|
127
|
+
lead AS (SELECT query, MAX(impr) top FROM q GROUP BY query),
|
|
128
|
+
comp AS (
|
|
129
|
+
SELECT q.* FROM q JOIN lead l ON l.query = q.query
|
|
130
|
+
WHERE q.impr >= ? AND q.impr >= l.top * 0.1 AND q.pos < 20
|
|
131
|
+
)`;
|
|
132
|
+
const args = [maxDate, maxDate, ...terms, minImpressions];
|
|
133
|
+
const urls = db.prepare(`${cte}
|
|
134
|
+
SELECT page_key urlKey, SUM(impr) impressions,
|
|
135
|
+
SUM(pos*impr)*1.0/NULLIF(SUM(impr),0) position,
|
|
136
|
+
COUNT(DISTINCT query) queries
|
|
137
|
+
FROM comp GROUP BY page_key ORDER BY impressions DESC`).all(...args);
|
|
138
|
+
const { colliding } = db.prepare(`${cte} SELECT COUNT(*) colliding FROM (SELECT query FROM comp GROUP BY query HAVING COUNT(DISTINCT page_key) >= 2)`).get(...args);
|
|
139
|
+
const { n } = db.prepare(`SELECT COUNT(DISTINCT query) n FROM search_analytics WHERE query IS NOT NULL AND ${win} AND ${like}`).get(maxDate, maxDate, ...terms);
|
|
140
|
+
return {
|
|
141
|
+
core, clusterQueries: n, collidingQueries: colliding,
|
|
142
|
+
urls: urls.map(r => ({ ...r, position: Math.round(r.position * 10) / 10 })),
|
|
143
|
+
cannibalising: urls.length >= 2 && colliding >= 1,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** The to-dos we can generate deterministically from our page + the SERP (no judgement). */
|
|
147
|
+
export function deterministicTodos(target, own, serp, verdict, todayIso, ctx = {}) {
|
|
148
|
+
const out = [];
|
|
149
|
+
const basis = opportunityBasis(target.impressions, serp.ourOrganicRank, target.position, verdict.verdict);
|
|
150
|
+
const w = (type) => Math.max(1, Math.round(basis.opportunityClicks * basis.verdictFactor * (TYPE_WEIGHT[type] ?? 0.5)));
|
|
151
|
+
// A URL that cannot rank gets ONE to-do: resolve it. Everything downstream (schema, freshness,
|
|
152
|
+
// a companion video) is work against a page Google will not serve.
|
|
153
|
+
const state = ctx.state;
|
|
154
|
+
if (state?.cannotRank) {
|
|
155
|
+
return [{
|
|
156
|
+
action: state.canonicalisedAway
|
|
157
|
+
? `Resolve the duplicate — this URL canonicalises to ${state.canonicalKey}; merge or de-duplicate before any content work`
|
|
158
|
+
: `Restore indexability — the crawl marks this URL not indexable (${state.indexableReason ?? 'reason not recorded'})`,
|
|
159
|
+
type: 'cannibalisation',
|
|
160
|
+
rationale: `${verdict.note} Its GSC impressions are historical, so they are not evidence the page can win today.`,
|
|
161
|
+
evidence: {
|
|
162
|
+
indexable: state.indexable, indexableReason: state.indexableReason, canonicalKey: state.canonicalKey,
|
|
163
|
+
statusCode: state.statusCode, inlinkCount: state.inlinkCount, ipr: state.ipr, source: 'crawl (pages table)',
|
|
164
|
+
},
|
|
165
|
+
priority: w('cannibalisation'),
|
|
166
|
+
}];
|
|
167
|
+
}
|
|
168
|
+
// Verdict-driven headline action.
|
|
169
|
+
if (verdict.verdict === 'accuracy-or-freshness') {
|
|
170
|
+
out.push({ action: 'Update the facts to current and mark them up so they are machine-liftable', type: 'schema',
|
|
171
|
+
rationale: `You rank organic #${serp.ourOrganicRank} but the AI Overview does not cite you — a data-accuracy/freshness signal.`,
|
|
172
|
+
evidence: { organicRank: serp.ourOrganicRank, aioCitesUs: false, aioReferencesChecked: serp.aioReferences.length }, priority: w('schema') + 1 });
|
|
173
|
+
}
|
|
174
|
+
// Schema: commercial/listicle page without Product/Review markup.
|
|
175
|
+
if (!own.hasProductOrReview && looksCommercial(target.query)) {
|
|
176
|
+
out.push({ action: 'Add Product + Review/AggregateRating schema per item, with current specs and price',
|
|
177
|
+
type: 'schema', rationale: 'A comparison/review page with no item-level markup cannot be lifted into rich results or an AI Overview citation cleanly.',
|
|
178
|
+
evidence: { schemaTypes: own.jsonLdTypes, hasProductOrReview: false }, priority: w('schema') });
|
|
179
|
+
}
|
|
180
|
+
// Freshness: stale dateModified (older than ~90 days from today).
|
|
181
|
+
if (own.dateModified) {
|
|
182
|
+
const ageDays = Math.round((Date.parse(todayIso) - Date.parse(own.dateModified)) / 86400000);
|
|
183
|
+
if (ageDays > 90) {
|
|
184
|
+
out.push({ action: `Refresh the content (dateModified is ${ageDays} days old) — genuinely update, don't just restamp`,
|
|
185
|
+
type: 'freshness', rationale: 'Stale content loses ground on freshness-sensitive queries; a real update (not a date bump) is the lever.',
|
|
186
|
+
evidence: { dateModified: own.dateModified, ageDays }, priority: w('freshness') });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Cannibalisation — cluster first (the real overlap), falling back to the single query.
|
|
190
|
+
const cluster = ctx.cluster;
|
|
191
|
+
if (cluster?.cannibalising) {
|
|
192
|
+
out.push({ action: `Consolidate cannibalisation — ${cluster.urls.length} of your URLs compete across the "${cluster.core.join(' ')}" cluster, colliding on ${cluster.collidingQueries} quer${cluster.collidingQueries === 1 ? 'y' : 'ies'}`,
|
|
193
|
+
type: 'cannibalisation',
|
|
194
|
+
rationale: 'Multiple own URLs competing across one topic cluster split signals; pick a canonical winner and point the others at it. Judged across the cluster, not one query at a time — a single-query count misses siblings that share the topic under different phrasings.',
|
|
195
|
+
evidence: { core: cluster.core, clusterQueries: cluster.clusterQueries, collidingQueries: cluster.collidingQueries, urls: cluster.urls, seedQuery: target.query },
|
|
196
|
+
priority: w('cannibalisation') });
|
|
197
|
+
}
|
|
198
|
+
else if (target.competingUrls >= 2) {
|
|
199
|
+
out.push({ action: `Consolidate cannibalisation — ${target.competingUrls} of your URLs rank for "${target.query}"`,
|
|
200
|
+
type: 'cannibalisation', rationale: 'Multiple own URLs competing for one query split signals; pick a canonical winner.',
|
|
201
|
+
evidence: { competingUrls: target.competingUrls, query: target.query }, priority: w('cannibalisation') });
|
|
202
|
+
}
|
|
203
|
+
// Format: AIO/SERP leans on video.
|
|
204
|
+
const ytRefs = serp.aioReferences.filter(r => /youtube|youtu\.be/.test(r.domain)).length;
|
|
205
|
+
if ((serp.videoPresent || ytRefs >= 2)) {
|
|
206
|
+
out.push({ action: 'Produce a companion video',
|
|
207
|
+
type: 'format', rationale: `The SERP favours video${ytRefs ? ` and ${ytRefs} of the AI Overview's references are YouTube` : ''}.`,
|
|
208
|
+
evidence: { videoPresent: serp.videoPresent, youtubeAioRefs: ytRefs }, priority: w('format') });
|
|
209
|
+
}
|
|
210
|
+
// Research pointer: hand the session the competitor set to diff (coverage + originality).
|
|
211
|
+
const competitors = [
|
|
212
|
+
...serp.organicAbove.map(o => ({ rank: o.rank, url: o.url, kind: 'organic-above' })),
|
|
213
|
+
...serp.aioReferences.slice(0, 6).map(r => ({ rank: r.rank, url: r.url, kind: 'aio-reference' })),
|
|
214
|
+
...serp.videoItems.slice(0, 3).map(v => ({ url: v.url, kind: 'video' })),
|
|
215
|
+
];
|
|
216
|
+
out.push({ action: 'Diff our coverage against the competitors Google rewards — add table-stakes we lack, amplify what only we have',
|
|
217
|
+
type: 'research', rationale: 'Scrape the ranking competitors and transcribe the ranking videos, then compare to our sections.',
|
|
218
|
+
evidence: { competitors }, priority: w('research') });
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
export function persistReconPage(db, p) {
|
|
222
|
+
db.prepare(`INSERT INTO recon_page
|
|
223
|
+
(url_key, query, verdict, verdict_note, organic_rank, gsc_position, gsc_impressions,
|
|
224
|
+
aio_present, aio_cites_us, video_present, has_product_schema, schema_types, competitors, researched_at)
|
|
225
|
+
VALUES (@urlKey,@query,@verdict,@verdictNote,@organicRank,@gscPosition,@gscImpressions,
|
|
226
|
+
@aioPresent,@aioCitesUs,@videoPresent,@hasProductSchema,@schemaTypes,@competitors, datetime('now'))
|
|
227
|
+
ON CONFLICT(url_key) DO UPDATE SET query=excluded.query, verdict=excluded.verdict, verdict_note=excluded.verdict_note,
|
|
228
|
+
organic_rank=excluded.organic_rank, gsc_position=excluded.gsc_position, gsc_impressions=excluded.gsc_impressions,
|
|
229
|
+
aio_present=excluded.aio_present, aio_cites_us=excluded.aio_cites_us, video_present=excluded.video_present,
|
|
230
|
+
has_product_schema=excluded.has_product_schema, schema_types=excluded.schema_types,
|
|
231
|
+
competitors=excluded.competitors, researched_at=datetime('now')`).run({
|
|
232
|
+
urlKey: p.urlKey, query: p.query, verdict: p.verdict, verdictNote: p.verdictNote,
|
|
233
|
+
organicRank: p.organicRank, gscPosition: p.gscPosition, gscImpressions: p.gscImpressions,
|
|
234
|
+
aioPresent: p.aioPresent ? 1 : 0, aioCitesUs: p.aioCitesUs == null ? null : (p.aioCitesUs ? 1 : 0), videoPresent: p.videoPresent ? 1 : 0,
|
|
235
|
+
hasProductSchema: p.hasProductSchema ? 1 : 0, schemaTypes: JSON.stringify(p.schemaTypes),
|
|
236
|
+
competitors: JSON.stringify(p.competitors),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/** Insert to-dos for a page. `baseline` snapshots the rank/citation state so outcome is measurable.
|
|
240
|
+
* De-dupes on (url_key, action) so a re-run doesn't pile up identical open items. */
|
|
241
|
+
export function insertTodos(db, urlKey, query, todos, baseline, source) {
|
|
242
|
+
const exists = db.prepare(`SELECT 1 FROM recon_todo WHERE url_key=? AND action=? AND status NOT IN ('shipped','dismissed')`);
|
|
243
|
+
const ins = db.prepare(`INSERT INTO recon_todo (url_key, query, action, type, rationale, evidence, priority, source, baseline)
|
|
244
|
+
VALUES (?,?,?,?,?,?,?,?,?)`);
|
|
245
|
+
let n = 0;
|
|
246
|
+
const tx = db.transaction(() => {
|
|
247
|
+
for (const t of todos) {
|
|
248
|
+
if (exists.get(urlKey, t.action))
|
|
249
|
+
continue;
|
|
250
|
+
ins.run(urlKey, query, t.action, t.type, t.rationale, JSON.stringify(t.evidence), t.priority, source, JSON.stringify(baseline));
|
|
251
|
+
n++;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
tx();
|
|
255
|
+
return n;
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=recon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recon.js","sourceRoot":"","sources":["../../src/audit/recon.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuditDatabase.d.ts","sourceRoot":"","sources":["../../src/core/AuditDatabase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAItC,MAAM,MAAM,YAAY,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AACvD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAEhE,qBAAa,aAAa;IACxB,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;gBAEnB,MAAM,EAAE,MAAM;IAgB1B,OAAO,CAAC,gBAAgB;
|
|
1
|
+
{"version":3,"file":"AuditDatabase.d.ts","sourceRoot":"","sources":["../../src/core/AuditDatabase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAItC,MAAM,MAAM,YAAY,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AACvD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAEhE,qBAAa,aAAa;IACxB,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;gBAEnB,MAAM,EAAE,MAAM;IAgB1B,OAAO,CAAC,gBAAgB;IAwaxB,2EAA2E;IAC3E,OAAO,CAAC,OAAO;IAyDf,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI;IAWjF,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,SAAS;IAMhH,2FAA2F;IAC3F,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAMhE,iFAAiF;IACjF,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS;IAO5D,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -392,6 +392,45 @@ export class AuditDatabase {
|
|
|
392
392
|
PRIMARY KEY (qid, related_qid, relation)
|
|
393
393
|
);
|
|
394
394
|
CREATE INDEX IF NOT EXISTS idx_entity_edge_related ON entity_edge(related_qid);
|
|
395
|
+
`);
|
|
396
|
+
// ── Content recon (recon_targets) — per-page SERP/AIO classification + a trackable
|
|
397
|
+
// to-do ledger with baseline→outcome so a fix's effect on rank/citation is measurable.
|
|
398
|
+
this.db.exec(`
|
|
399
|
+
CREATE TABLE IF NOT EXISTS recon_page (
|
|
400
|
+
url_key TEXT PRIMARY KEY, -- OUR page under recon
|
|
401
|
+
query TEXT, -- the primary money query analysed
|
|
402
|
+
verdict TEXT, -- defend-and-deepen | accuracy-or-freshness | consolidate-weak-page | competitive-gap
|
|
403
|
+
verdict_note TEXT,
|
|
404
|
+
organic_rank INTEGER, -- our live organic rank (DataForSEO), distinct from GSC avg
|
|
405
|
+
gsc_position REAL, -- GSC blended average position (for contrast)
|
|
406
|
+
gsc_impressions INTEGER,
|
|
407
|
+
aio_present INTEGER DEFAULT 0,
|
|
408
|
+
aio_cites_us INTEGER DEFAULT 0,
|
|
409
|
+
video_present INTEGER DEFAULT 0,
|
|
410
|
+
has_product_schema INTEGER DEFAULT 0,
|
|
411
|
+
schema_types TEXT, -- JSON string[]
|
|
412
|
+
competitors TEXT, -- JSON { organicAbove[], aioReferences[], videoItems[] }
|
|
413
|
+
researched_at TEXT DEFAULT (datetime('now'))
|
|
414
|
+
);
|
|
415
|
+
CREATE TABLE IF NOT EXISTS recon_todo (
|
|
416
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
417
|
+
url_key TEXT NOT NULL,
|
|
418
|
+
query TEXT,
|
|
419
|
+
action TEXT NOT NULL, -- the to-do ("Add a Meta Quest 3S section")
|
|
420
|
+
type TEXT, -- content-gap | schema | freshness | format | cannibalisation | originality
|
|
421
|
+
rationale TEXT,
|
|
422
|
+
evidence TEXT, -- JSON datapoint backing the action
|
|
423
|
+
priority REAL DEFAULT 0,
|
|
424
|
+
status TEXT DEFAULT 'open', -- open | researching | drafted | shipped | dismissed
|
|
425
|
+
source TEXT DEFAULT 'auto', -- auto (deterministic) | research (session-discovered)
|
|
426
|
+
baseline TEXT, -- JSON snapshot at creation (rank/citation/position)
|
|
427
|
+
outcome TEXT, -- JSON snapshot after 'shipped' (re-measured)
|
|
428
|
+
notes TEXT, -- append-only annotation log (timestamped lines)
|
|
429
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
430
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
431
|
+
);
|
|
432
|
+
CREATE INDEX IF NOT EXISTS idx_recon_todo_url ON recon_todo(url_key);
|
|
433
|
+
CREATE INDEX IF NOT EXISTS idx_recon_todo_status ON recon_todo(status);
|
|
395
434
|
`);
|
|
396
435
|
this.migrate();
|
|
397
436
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuditDatabase.js","sourceRoot":"","sources":["../../src/core/AuditDatabase.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AuditDatabase.js","sourceRoot":"","sources":["../../src/core/AuditDatabase.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,8 +30,15 @@ export declare class DataForSeoClient {
|
|
|
30
30
|
private loc;
|
|
31
31
|
/** KEYWORDS_DATA — true monthly search volume + CPC + competition (cheap). */
|
|
32
32
|
searchVolume(keywords: string[], location?: string | number, languageCode?: string): Promise<DfsResponse>;
|
|
33
|
-
/** SERP — live Google organic results (per-request cost; use sparingly).
|
|
34
|
-
|
|
33
|
+
/** SERP — live Google organic results (per-request cost; use sparingly).
|
|
34
|
+
* `depth` = how many ORGANIC results to collect (not total SERP elements), so a domain absent
|
|
35
|
+
* at depth=20 is absent from the top 20 organic — nothing is known about its position beyond that.
|
|
36
|
+
* `loadAsyncAiOverview` makes DataForSEO wait for a client-side-loaded AI Overview and return its
|
|
37
|
+
* references; without it those SERPs come back with an empty AIO (asynchronous_ai_overview:true)
|
|
38
|
+
* and citation is unknowable. Small surcharge, refunded when no async AIO is found. */
|
|
39
|
+
serpOrganic(keyword: string, location?: string | number, languageCode?: string, depth?: number, opts?: {
|
|
40
|
+
loadAsyncAiOverview?: boolean;
|
|
41
|
+
}): Promise<DfsResponse>;
|
|
35
42
|
/**
|
|
36
43
|
* Related terms for a keyword — People Also Ask questions + related searches,
|
|
37
44
|
* parsed from the SERP advanced response. Powers the click-through "related
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataForSeoClient.d.ts","sourceRoot":"","sources":["../../src/core/DataForSeoClient.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAID,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,KAAK,CAAuC;gBAExC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,SAAK;IAmB9E,sFAAsF;IACtF,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,MAAM;IAUd,8EAA8E;IACxE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,SAAS,SAAQ,GAAG,OAAO,CAAC,WAAW,CAAC;IA0DtF,mGAAmG;IACnG,OAAO,CAAC,GAAG;IAMX,8EAA8E;IACxE,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,YAAY,SAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAM7G
|
|
1
|
+
{"version":3,"file":"DataForSeoClient.d.ts","sourceRoot":"","sources":["../../src/core/DataForSeoClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -105,10 +105,18 @@ export class DataForSeoClient {
|
|
|
105
105
|
{ keywords, ...this.loc(location), language_code: languageCode },
|
|
106
106
|
]);
|
|
107
107
|
}
|
|
108
|
-
/** SERP — live Google organic results (per-request cost; use sparingly).
|
|
109
|
-
|
|
108
|
+
/** SERP — live Google organic results (per-request cost; use sparingly).
|
|
109
|
+
* `depth` = how many ORGANIC results to collect (not total SERP elements), so a domain absent
|
|
110
|
+
* at depth=20 is absent from the top 20 organic — nothing is known about its position beyond that.
|
|
111
|
+
* `loadAsyncAiOverview` makes DataForSEO wait for a client-side-loaded AI Overview and return its
|
|
112
|
+
* references; without it those SERPs come back with an empty AIO (asynchronous_ai_overview:true)
|
|
113
|
+
* and citation is unknowable. Small surcharge, refunded when no async AIO is found. */
|
|
114
|
+
async serpOrganic(keyword, location, languageCode = 'en', depth = 20, opts = {}) {
|
|
110
115
|
return this.call('/v3/serp/google/organic/live/advanced', [
|
|
111
|
-
{
|
|
116
|
+
{
|
|
117
|
+
keyword, ...this.loc(location), language_code: languageCode, depth,
|
|
118
|
+
...(opts.loadAsyncAiOverview ? { load_async_ai_overview: true } : {}),
|
|
119
|
+
},
|
|
112
120
|
]);
|
|
113
121
|
}
|
|
114
122
|
/**
|