@houtini/seo-audit-console 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.
Files changed (166) hide show
  1. package/LICENSE +92 -0
  2. package/README.md +211 -0
  3. package/dist/audit/checks.d.ts +35 -0
  4. package/dist/audit/checks.d.ts.map +1 -0
  5. package/dist/audit/checks.js +1475 -0
  6. package/dist/audit/checks.js.map +1 -0
  7. package/dist/audit/drift.d.ts +40 -0
  8. package/dist/audit/drift.d.ts.map +1 -0
  9. package/dist/audit/drift.js +148 -0
  10. package/dist/audit/drift.js.map +1 -0
  11. package/dist/audit/engine.d.ts +33 -0
  12. package/dist/audit/engine.d.ts.map +1 -0
  13. package/dist/audit/engine.js +186 -0
  14. package/dist/audit/engine.js.map +1 -0
  15. package/dist/audit/opportunities.d.ts +23 -0
  16. package/dist/audit/opportunities.d.ts.map +1 -0
  17. package/dist/audit/opportunities.js +149 -0
  18. package/dist/audit/opportunities.js.map +1 -0
  19. package/dist/audit/report.d.ts +11 -0
  20. package/dist/audit/report.d.ts.map +1 -0
  21. package/dist/audit/report.js +59 -0
  22. package/dist/audit/report.js.map +1 -0
  23. package/dist/audit/schema-validate.d.ts +35 -0
  24. package/dist/audit/schema-validate.d.ts.map +1 -0
  25. package/dist/audit/schema-validate.js +293 -0
  26. package/dist/audit/schema-validate.js.map +1 -0
  27. package/dist/audit/templates.d.ts +43 -0
  28. package/dist/audit/templates.d.ts.map +1 -0
  29. package/dist/audit/templates.js +129 -0
  30. package/dist/audit/templates.js.map +1 -0
  31. package/dist/audit/topicGaps.d.ts +42 -0
  32. package/dist/audit/topicGaps.d.ts.map +1 -0
  33. package/dist/audit/topicGaps.js +181 -0
  34. package/dist/audit/topicGaps.js.map +1 -0
  35. package/dist/core/AuditDatabase.d.ts +33 -0
  36. package/dist/core/AuditDatabase.d.ts.map +1 -0
  37. package/dist/core/AuditDatabase.js +481 -0
  38. package/dist/core/AuditDatabase.js.map +1 -0
  39. package/dist/core/Backlinks.d.ts +33 -0
  40. package/dist/core/Backlinks.d.ts.map +1 -0
  41. package/dist/core/Backlinks.js +110 -0
  42. package/dist/core/Backlinks.js.map +1 -0
  43. package/dist/core/Crawler.d.ts +23 -0
  44. package/dist/core/Crawler.d.ts.map +1 -0
  45. package/dist/core/Crawler.js +588 -0
  46. package/dist/core/Crawler.js.map +1 -0
  47. package/dist/core/DataForSeoClient.d.ts +86 -0
  48. package/dist/core/DataForSeoClient.d.ts.map +1 -0
  49. package/dist/core/DataForSeoClient.js +232 -0
  50. package/dist/core/DataForSeoClient.js.map +1 -0
  51. package/dist/core/Entities.d.ts +23 -0
  52. package/dist/core/Entities.d.ts.map +1 -0
  53. package/dist/core/Entities.js +62 -0
  54. package/dist/core/Entities.js.map +1 -0
  55. package/dist/core/GscClient.d.ts +22 -0
  56. package/dist/core/GscClient.d.ts.map +1 -0
  57. package/dist/core/GscClient.js +93 -0
  58. package/dist/core/GscClient.js.map +1 -0
  59. package/dist/core/GscSync.d.ts +20 -0
  60. package/dist/core/GscSync.d.ts.map +1 -0
  61. package/dist/core/GscSync.js +133 -0
  62. package/dist/core/GscSync.js.map +1 -0
  63. package/dist/core/JobManager.d.ts +30 -0
  64. package/dist/core/JobManager.d.ts.map +1 -0
  65. package/dist/core/JobManager.js +68 -0
  66. package/dist/core/JobManager.js.map +1 -0
  67. package/dist/core/RankTracker.d.ts +25 -0
  68. package/dist/core/RankTracker.d.ts.map +1 -0
  69. package/dist/core/RankTracker.js +78 -0
  70. package/dist/core/RankTracker.js.map +1 -0
  71. package/dist/core/Refresh.d.ts +32 -0
  72. package/dist/core/Refresh.d.ts.map +1 -0
  73. package/dist/core/Refresh.js +73 -0
  74. package/dist/core/Refresh.js.map +1 -0
  75. package/dist/core/UrlInspector.d.ts +22 -0
  76. package/dist/core/UrlInspector.d.ts.map +1 -0
  77. package/dist/core/UrlInspector.js +92 -0
  78. package/dist/core/UrlInspector.js.map +1 -0
  79. package/dist/core/WikidataClient.d.ts +19 -0
  80. package/dist/core/WikidataClient.d.ts.map +1 -0
  81. package/dist/core/WikidataClient.js +59 -0
  82. package/dist/core/WikidataClient.js.map +1 -0
  83. package/dist/core/agentReadiness.d.ts +34 -0
  84. package/dist/core/agentReadiness.d.ts.map +1 -0
  85. package/dist/core/agentReadiness.js +119 -0
  86. package/dist/core/agentReadiness.js.map +1 -0
  87. package/dist/core/ctrModel.d.ts +2 -0
  88. package/dist/core/ctrModel.d.ts.map +1 -0
  89. package/dist/core/ctrModel.js +6 -0
  90. package/dist/core/ctrModel.js.map +1 -0
  91. package/dist/core/dashboardData.d.ts +312 -0
  92. package/dist/core/dashboardData.d.ts.map +1 -0
  93. package/dist/core/dashboardData.js +550 -0
  94. package/dist/core/dashboardData.js.map +1 -0
  95. package/dist/core/dataStorage.d.ts +42 -0
  96. package/dist/core/dataStorage.d.ts.map +1 -0
  97. package/dist/core/dataStorage.js +193 -0
  98. package/dist/core/dataStorage.js.map +1 -0
  99. package/dist/core/draftBrief.d.ts +27 -0
  100. package/dist/core/draftBrief.d.ts.map +1 -0
  101. package/dist/core/draftBrief.js +69 -0
  102. package/dist/core/draftBrief.js.map +1 -0
  103. package/dist/core/extract.d.ts +67 -0
  104. package/dist/core/extract.d.ts.map +1 -0
  105. package/dist/core/extract.js +262 -0
  106. package/dist/core/extract.js.map +1 -0
  107. package/dist/core/gscFreshness.d.ts +18 -0
  108. package/dist/core/gscFreshness.d.ts.map +1 -0
  109. package/dist/core/gscFreshness.js +32 -0
  110. package/dist/core/gscFreshness.js.map +1 -0
  111. package/dist/core/linkGraph.d.ts +19 -0
  112. package/dist/core/linkGraph.d.ts.map +1 -0
  113. package/dist/core/linkGraph.js +125 -0
  114. package/dist/core/linkGraph.js.map +1 -0
  115. package/dist/core/passageScore.d.ts +19 -0
  116. package/dist/core/passageScore.d.ts.map +1 -0
  117. package/dist/core/passageScore.js +59 -0
  118. package/dist/core/passageScore.js.map +1 -0
  119. package/dist/core/paths.d.ts +5 -0
  120. package/dist/core/paths.d.ts.map +1 -0
  121. package/dist/core/paths.js +15 -0
  122. package/dist/core/paths.js.map +1 -0
  123. package/dist/core/queryData.d.ts +35 -0
  124. package/dist/core/queryData.d.ts.map +1 -0
  125. package/dist/core/queryData.js +200 -0
  126. package/dist/core/queryData.js.map +1 -0
  127. package/dist/core/reranker.d.ts +8 -0
  128. package/dist/core/reranker.d.ts.map +1 -0
  129. package/dist/core/reranker.js +68 -0
  130. package/dist/core/reranker.js.map +1 -0
  131. package/dist/core/robots.d.ts +11 -0
  132. package/dist/core/robots.d.ts.map +1 -0
  133. package/dist/core/robots.js +76 -0
  134. package/dist/core/robots.js.map +1 -0
  135. package/dist/core/sitemap.d.ts +15 -0
  136. package/dist/core/sitemap.d.ts.map +1 -0
  137. package/dist/core/sitemap.js +100 -0
  138. package/dist/core/sitemap.js.map +1 -0
  139. package/dist/core/sql.d.ts +6 -0
  140. package/dist/core/sql.d.ts.map +1 -0
  141. package/dist/core/sql.js +6 -0
  142. package/dist/core/sql.js.map +1 -0
  143. package/dist/core/types.d.ts +22 -0
  144. package/dist/core/types.d.ts.map +1 -0
  145. package/dist/core/types.js +2 -0
  146. package/dist/core/types.js.map +1 -0
  147. package/dist/core/url-key.d.ts +39 -0
  148. package/dist/core/url-key.d.ts.map +1 -0
  149. package/dist/core/url-key.js +106 -0
  150. package/dist/core/url-key.js.map +1 -0
  151. package/dist/generators/index.d.ts +45 -0
  152. package/dist/generators/index.d.ts.map +1 -0
  153. package/dist/generators/index.js +184 -0
  154. package/dist/generators/index.js.map +1 -0
  155. package/dist/index.d.ts +3 -0
  156. package/dist/index.d.ts.map +1 -0
  157. package/dist/index.js +10 -0
  158. package/dist/index.js.map +1 -0
  159. package/dist/server.d.ts +7 -0
  160. package/dist/server.d.ts.map +1 -0
  161. package/dist/server.js +1387 -0
  162. package/dist/server.js.map +1 -0
  163. package/dist/src/ui/dashboard.html +347 -0
  164. package/dist/src/ui/sync-progress.html +104 -0
  165. package/package.json +101 -0
  166. package/server.json +57 -0
@@ -0,0 +1,550 @@
1
+ import { AuditDatabase } from './AuditDatabase.js';
2
+ import { dbPathFor } from './paths.js';
3
+ import { gscFreshness } from './gscFreshness.js';
4
+ import { expectedCtr } from './ctrModel.js';
5
+ import { brandToken } from './url-key.js';
6
+ import { HTML_CT } from './sql.js';
7
+ // Bump when the dashboard payload SHAPE/content changes, so cached entries from older code are
8
+ // invalidated even if the underlying GSC/crawl data hasn't changed. Part of the cache version key.
9
+ const PAYLOAD_VERSION = '5';
10
+ /** Build the dashboard payload for a property from its synced GSC history. */
11
+ export function getDashboardData(dataDir, siteUrl) {
12
+ const db = new AuditDatabase(dbPathFor(dataDir, siteUrl));
13
+ try {
14
+ // Cache: the payload is ~20 GROUP-BY scans over search_analytics (~6s at 1.8M rows) but only
15
+ // changes on sync/audit. Probe a cheap data-version; serve the cached JSON on a hit.
16
+ // Probe every table the payload reads from — search_analytics + pages + the latest audit run,
17
+ // PLUS agent_readiness and rank_history (written by check_agent_readiness / track_ranks via their
18
+ // own paths). Omitting the latter two would serve a stale dashboard after those tools run.
19
+ const ver = db.db.prepare(`SELECT (SELECT MAX(date)||':'||COALESCE(MAX(rowid),0) FROM search_analytics) sa,
20
+ (SELECT run_id FROM audit_runs ORDER BY started_at DESC LIMIT 1) run,
21
+ (SELECT COALESCE(MAX(rowid),0) FROM pages) pg,
22
+ (SELECT COALESCE(MAX(checked_at),'') FROM agent_readiness) ar,
23
+ (SELECT COUNT(*)||':'||COALESCE(MAX(period),'') FROM rank_history) rh`).get();
24
+ const version = `${PAYLOAD_VERSION}|${ver.sa ?? 'none'}|${ver.run ?? 'none'}|${ver.pg}|${ver.ar ?? ''}|${ver.rh ?? ''}`;
25
+ const hit = db.db.prepare('SELECT payload FROM dashboard_cache WHERE id=1 AND version=?').get(version);
26
+ // A corrupt/truncated cache row must fall through to a rebuild, not throw forever.
27
+ if (hit) {
28
+ try {
29
+ return JSON.parse(hit.payload);
30
+ }
31
+ catch { /* rebuild below */ }
32
+ }
33
+ // Use the finalised date (trailing partial GSC days trimmed) for ALL windows + charts, so the
34
+ // dashboard never shows the "traffic tanking" cliff of unfinalised data.
35
+ const fresh = gscFreshness(db.db);
36
+ const maxDate = fresh.effectiveMax;
37
+ if (!maxDate)
38
+ return { siteUrl, empty: true };
39
+ // Upper bound: never include unfinalised days past effectiveMax. Windows anchored on maxDate
40
+ // only set a lower bound (date > date(maxDate,'-Nd')), so without this the partial trailing
41
+ // days would still slip into current-period sums and the daily charts. (Prior windows already
42
+ // cap below maxDate, so they don't need it.) maxDate is our own ISO date — safe to interpolate.
43
+ const UB = `date <= '${maxDate}'`;
44
+ const totals = (start, end) => {
45
+ const where = end
46
+ ? `date > date(?, '${start}') AND date <= date(?, '${end}')`
47
+ : `date > date(?, '${start}') AND ${UB}`;
48
+ const args = end ? [maxDate, maxDate] : [maxDate];
49
+ const r = db.db
50
+ .prepare(`SELECT COALESCE(SUM(clicks),0) clicks, COALESCE(SUM(impressions),0) impressions, COALESCE(AVG(position),0) position FROM search_analytics WHERE ${where}`)
51
+ .get(...args);
52
+ return r;
53
+ };
54
+ const cur = totals('-28 days');
55
+ const prior = totals('-56 days', '-28 days');
56
+ const ctr = (t) => (t.impressions ? t.clicks / t.impressions : 0);
57
+ // Branded vs non-branded — brand derived from the property's registrable label, matched against
58
+ // the space-stripped query (so "sim racing cockpit" matches brand "simracingcockpit"). Done in
59
+ // one SQL pass. The detected brand is surfaced in the UI so the split is verifiable, not a
60
+ // black box — for descriptive domains where the brand equals a generic term, the user can see it.
61
+ const brandKey = brandToken(siteUrl); // shared helper — same derivation the audit's brandExcl uses
62
+ let brandedSplit;
63
+ if (brandKey) {
64
+ // Whole-token match (brand surrounded by word boundaries), NOT substring — so a 3-letter brand
65
+ // like "ehi" matches the query "ehi inspections" but NOT "vehicle". Strict: this under-detects
66
+ // for descriptive concatenated domains (where the brand is typed spaced = the category) rather
67
+ // than over-detecting — a wrong split is worse than a conservative one.
68
+ const tok = `% ${brandKey} %`;
69
+ const splitRow = db.db.prepare(`SELECT COALESCE(SUM(CASE WHEN (' '||LOWER(query)||' ') LIKE ? THEN clicks ELSE 0 END),0) bClk,
70
+ COALESCE(SUM(CASE WHEN (' '||LOWER(query)||' ') LIKE ? THEN impressions ELSE 0 END),0) bImp,
71
+ COALESCE(SUM(clicks),0) tClk, COALESCE(SUM(impressions),0) tImp
72
+ FROM search_analytics WHERE query IS NOT NULL AND ${UB} AND date > date(?, '-28 days')`)
73
+ .get(tok, tok, maxDate);
74
+ const priorRow = db.db.prepare(`SELECT COALESCE(SUM(CASE WHEN (' '||LOWER(query)||' ') LIKE ? THEN clicks ELSE 0 END),0) bClk, COALESCE(SUM(clicks),0) tClk
75
+ FROM search_analytics WHERE query IS NOT NULL AND date > date(?, '-56 days') AND date <= date(?, '-28 days')`)
76
+ .get(tok, maxDate, maxDate);
77
+ brandedSplit = {
78
+ brand: brandKey,
79
+ branded: { clicks: splitRow.bClk, impressions: splitRow.bImp },
80
+ nonBranded: { clicks: splitRow.tClk - splitRow.bClk, impressions: splitRow.tImp - splitRow.bImp },
81
+ priorBrandedClicks: priorRow.bClk,
82
+ priorNonBrandedClicks: priorRow.tClk - priorRow.bClk,
83
+ };
84
+ }
85
+ const rankTrend = db.db
86
+ .prepare(`SELECT date, COALESCE(SUM(clicks),0) clicks, COALESCE(SUM(impressions),0) impressions, COALESCE(AVG(position),0) position
87
+ FROM search_analytics WHERE ${UB} AND date > date(?, '-90 days') GROUP BY date ORDER BY date`)
88
+ .all(maxDate);
89
+ const curKw = db.db
90
+ .prepare(`SELECT query, SUM(clicks) clicks, AVG(position) position
91
+ FROM search_analytics WHERE query IS NOT NULL AND ${UB} AND date > date(?, '-28 days')
92
+ GROUP BY query ORDER BY clicks DESC LIMIT 15`)
93
+ .all(maxDate);
94
+ const priorKw = db.db
95
+ .prepare(`SELECT query, SUM(clicks) clicks, AVG(position) position
96
+ FROM search_analytics WHERE query IS NOT NULL AND date > date(?, '-56 days') AND date <= date(?, '-28 days')
97
+ GROUP BY query`)
98
+ .all(maxDate, maxDate);
99
+ const priorMap = new Map(priorKw.map(k => [k.query, k]));
100
+ const topKeywords = curKw.map(k => {
101
+ const p = priorMap.get(k.query);
102
+ return {
103
+ query: k.query,
104
+ clicks: k.clicks,
105
+ prevClicks: p?.clicks ?? 0,
106
+ clicksChange: k.clicks - (p?.clicks ?? 0),
107
+ position: Math.round(k.position * 10) / 10,
108
+ prevPosition: p ? Math.round(p.position * 10) / 10 : 0,
109
+ };
110
+ });
111
+ // Ranking distribution over time (impressions by position bucket) — flagship #1
112
+ const rankingDistribution = db.db
113
+ .prepare(`SELECT date,
114
+ COALESCE(SUM(CASE WHEN position<=3 THEN impressions ELSE 0 END),0) b1,
115
+ COALESCE(SUM(CASE WHEN position>3 AND position<=10 THEN impressions ELSE 0 END),0) b2,
116
+ COALESCE(SUM(CASE WHEN position>10 AND position<=20 THEN impressions ELSE 0 END),0) b3,
117
+ COALESCE(SUM(CASE WHEN position>20 THEN impressions ELSE 0 END),0) b4
118
+ FROM search_analytics WHERE ${UB} AND date > date(?, '-90 days') GROUP BY date ORDER BY date`)
119
+ .all(maxDate);
120
+ // Striking-distance queries (avg position 11–20, current period) — flagship #2
121
+ const strikingDistance = db.db
122
+ .prepare(`SELECT query, AVG(position) position, SUM(impressions) impressions, SUM(clicks) clicks
123
+ FROM search_analytics WHERE query IS NOT NULL AND ${UB} AND date > date(?, '-28 days')
124
+ GROUP BY query HAVING AVG(position) > 10 AND AVG(position) <= 20 AND SUM(impressions) > 0
125
+ ORDER BY SUM(impressions) DESC LIMIT 40`)
126
+ .all(maxDate)
127
+ .map(r => ({ ...r, position: Math.round(r.position * 10) / 10 }));
128
+ // Quick-wins matrix — every query with real impressions (≤ pos 20), plotted CTR vs position
129
+ // against the expected-CTR curve. Two opportunity types: 'striking' (page 2 → push to page 1)
130
+ // and 'snippet' (ranks page-1 but under-clicked vs expected). potential = recoverable clicks,
131
+ // grounded in the same CTR model the audit uses. 'ok' queries are context dots.
132
+ const quickWins = db.db.prepare(`SELECT query, SUM(impressions) impr, SUM(clicks) clicks, SUM(position*impressions)*1.0/NULLIF(SUM(impressions),0) pos
133
+ FROM search_analytics WHERE query IS NOT NULL AND ${UB} AND date > date(?, '-28 days')
134
+ GROUP BY query HAVING SUM(impressions) >= 50 AND pos <= 20
135
+ ORDER BY SUM(impressions) DESC LIMIT 300`).all(maxDate)
136
+ .map(r => {
137
+ const ctr = r.impr ? r.clicks / r.impr : 0;
138
+ const exp = expectedCtr(r.pos);
139
+ // STRICT: near-zero CTR at a page-1 position is NOT a snippet-rewrite win — a SERP feature
140
+ // (AI overview / video / pack), cannibalisation, or tracking gap is eating the clicks. Bucket
141
+ // it as 'serp' (verify, no recoverable promise) so we never over-state the opportunity.
142
+ const type = r.pos > 10 ? 'striking'
143
+ : (r.pos <= 10 && ctr < exp * 0.1) ? 'serp'
144
+ : (ctr < exp * 0.6 ? 'snippet' : 'ok');
145
+ const potential = type === 'striking' ? Math.max(0, r.impr * expectedCtr(8) - r.clicks)
146
+ : type === 'snippet' ? Math.max(0, r.impr * exp - r.clicks) : 0; // serp/ok carry no recoverable estimate
147
+ return { query: r.query, position: Math.round(r.pos * 10) / 10, impressions: r.impr, clicks: r.clicks, ctr: Math.round(ctr * 1000) / 10, expectedCtr: Math.round(exp * 1000) / 10, type, potential: Math.round(potential) };
148
+ })
149
+ .sort((a, b) => b.potential - a.potential);
150
+ // DataForSEO over-time sequence + GSC↔DataForSEO date-range reconciliation
151
+ const rankHistory = db.db
152
+ .prepare('SELECT period, pos_1_3, pos_4_10, pos_11_20, pos_21_100, etv, keyword_count FROM rank_history ORDER BY period')
153
+ .all();
154
+ const gscMin = db.db.prepare('SELECT MIN(date) d FROM search_analytics').get().d;
155
+ const dateAlignment = reconcileRanges(gscMin, maxDate, rankHistory ?? []);
156
+ // Device breakdown (current vs prior 28d)
157
+ const devCur = db.db.prepare(`SELECT device, SUM(clicks) clicks, SUM(impressions) impressions, AVG(position) position FROM search_analytics WHERE device <> '' AND ${UB} AND date > date(?, '-28 days') GROUP BY device`).all(maxDate);
158
+ const devPrior = new Map(db.db.prepare(`SELECT device, SUM(clicks) clicks FROM search_analytics WHERE device <> '' AND date > date(?, '-56 days') AND date <= date(?, '-28 days') GROUP BY device`).all(maxDate, maxDate).map(d => [d.device, d.clicks]));
159
+ const deviceBreakdown = devCur.map(d => ({ device: d.device, clicks: d.clicks, prevClicks: devPrior.get(d.device) ?? 0, impressions: d.impressions, ctr: d.impressions ? d.clicks / d.impressions : 0, position: Math.round(d.position * 10) / 10 }));
160
+ // Country breakdown (top 10 current, with prior clicks)
161
+ const ctyPrior = new Map(db.db.prepare(`SELECT country, SUM(clicks) clicks FROM search_analytics WHERE country <> '' AND date > date(?, '-56 days') AND date <= date(?, '-28 days') GROUP BY country`).all(maxDate, maxDate).map(c => [c.country, c.clicks]));
162
+ const countryBreakdown = db.db.prepare(`SELECT country, SUM(clicks) clicks, SUM(impressions) impressions FROM search_analytics WHERE country <> '' AND ${UB} AND date > date(?, '-28 days') GROUP BY country ORDER BY clicks DESC LIMIT 10`).all(maxDate).map(c => ({ country: c.country, clicks: c.clicks, prevClicks: ctyPrior.get(c.country) ?? 0, impressions: c.impressions }));
163
+ // Page performance + categorisation (current vs prior 28d)
164
+ const pagePrior = new Map(db.db.prepare(`SELECT page_key, SUM(clicks) clicks, SUM(impressions) impressions FROM search_analytics WHERE page_key IS NOT NULL AND date > date(?, '-56 days') AND date <= date(?, '-28 days') GROUP BY page_key`).all(maxDate, maxDate).map(p => [p.page_key, p]));
165
+ const pagePerformance = db.db.prepare(`SELECT page_key, SUM(clicks) clicks, SUM(impressions) impressions, AVG(position) position FROM search_analytics WHERE page_key IS NOT NULL AND ${UB} AND date > date(?, '-28 days') GROUP BY page_key ORDER BY clicks DESC LIMIT 40`).all(maxDate).map(p => {
166
+ const prev = pagePrior.get(p.page_key) ?? { clicks: 0, impressions: 0 };
167
+ const pct = prev.clicks ? ((p.clicks - prev.clicks) / prev.clicks) * 100 : (p.clicks > 0 ? 100 : 0);
168
+ return { urlKey: p.page_key, clicks: p.clicks, prevClicks: prev.clicks, clicksChangePct: Math.round(pct), impressions: p.impressions, position: Math.round(p.position * 10) / 10, category: pageCategory(p, prev) };
169
+ });
170
+ // Content decay & refresh ROI — pages that earned ≥10 clicks last period and have since fallen
171
+ // ≥20%, ranked by clicks lost (the recoverable upside from a content refresh). Fact-based: every
172
+ // row is a real page with a measured before/after over matched 28-day windows.
173
+ const contentDecay = db.db.prepare(`WITH cur AS (SELECT page_key, SUM(clicks) c, SUM(impressions) i, SUM(position*impressions)*1.0/NULLIF(SUM(impressions),0) pos
174
+ FROM search_analytics WHERE page_key IS NOT NULL AND ${UB} AND date > date(?, '-28 days') GROUP BY page_key),
175
+ prev AS (SELECT page_key, SUM(clicks) c FROM search_analytics WHERE page_key IS NOT NULL AND date > date(?, '-56 days') AND date <= date(?, '-28 days') GROUP BY page_key)
176
+ SELECT prev.page_key url, prev.c prevC, COALESCE(cur.c,0) curC, COALESCE(cur.i,0) curI, COALESCE(cur.pos,0) pos
177
+ FROM prev LEFT JOIN cur ON cur.page_key=prev.page_key
178
+ WHERE prev.c >= 10 AND COALESCE(cur.c,0) < prev.c * 0.8
179
+ ORDER BY (prev.c - COALESCE(cur.c,0)) DESC LIMIT 30`).all(maxDate, maxDate, maxDate)
180
+ .map(r => ({ urlKey: r.url, prevClicks: r.prevC, clicks: r.curC, lost: r.prevC - r.curC, dropPct: Math.round((1 - r.curC / r.prevC) * 100), impressions: r.curI, position: Math.round(r.pos * 10) / 10 }));
181
+ // Keyword ranking movement (first-seen vs last-seen position over 90d)
182
+ const keywordMovement = db.db.prepare(`WITH q AS (SELECT query, MIN(date) fd, MAX(date) ld FROM search_analytics WHERE query IS NOT NULL AND ${UB} AND date > date(?, '-90 days') GROUP BY query HAVING SUM(impressions) >= 50 AND MIN(date) < MAX(date))
183
+ SELECT q.query,
184
+ (SELECT AVG(position) FROM search_analytics s WHERE s.query=q.query AND s.date=q.fd AND s.impressions>0) firstPos,
185
+ (SELECT AVG(position) FROM search_analytics s WHERE s.query=q.query AND s.date=q.ld AND s.impressions>0) lastPos,
186
+ q.fd firstDate, q.ld lastDate
187
+ FROM q`).all(maxDate)
188
+ .filter(r => r.firstPos != null && r.lastPos != null)
189
+ .map(r => ({ query: r.query, firstPos: Math.round(r.firstPos * 10) / 10, lastPos: Math.round(r.lastPos * 10) / 10, delta: Math.round((r.firstPos - r.lastPos) * 10) / 10, firstDate: r.firstDate, lastDate: r.lastDate, category: movementCategory(r.firstPos, r.lastPos) }))
190
+ .filter(r => r.category !== 'stable')
191
+ .sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta))
192
+ .slice(0, 40);
193
+ // Latest audit findings (if run_audit has run for this property)
194
+ const lastRun = db.db.prepare('SELECT run_id, finding_count, finished_at FROM audit_runs ORDER BY started_at DESC LIMIT 1').get();
195
+ let findings = null;
196
+ if (lastRun) {
197
+ const byCheck = db.db.prepare('SELECT check_id, category, severity, COUNT(*) count, AVG(priority) priority FROM findings WHERE run_id=? GROUP BY check_id ORDER BY count DESC').all(lastRun.run_id);
198
+ const topRaw = db.db.prepare('SELECT check_id, category, severity, url_key, evidence, traffic_at_risk, effort, priority, recommendation FROM findings WHERE run_id=? ORDER BY priority DESC LIMIT 50').all(lastRun.run_id);
199
+ // Impact = priority normalised to the run's top finding (preserves magnitude), shown as a
200
+ // 0–100 index + S/M/L/XL size instead of a clicks "forecast".
201
+ const maxPrio = Math.max(1, ...topRaw.map(f => f.priority || 0));
202
+ const top = topRaw.map(f => { const impact = Math.round((f.priority || 0) / maxPrio * 100); return { ...f, impact, size: impact >= 50 ? 'XL' : impact >= 20 ? 'L' : impact >= 5 ? 'M' : 'S' }; });
203
+ // One representative (highest-priority) example per check → the audit-report view.
204
+ const exRows = db.db.prepare('SELECT check_id, url_key, evidence, traffic_at_risk, recommendation, priority FROM findings WHERE run_id=? ORDER BY priority DESC').all(lastRun.run_id);
205
+ const parse = (s) => { try {
206
+ return JSON.parse(s || '{}');
207
+ }
208
+ catch {
209
+ return {};
210
+ } };
211
+ // Up to 3 representative examples per check (highest-priority first) → richer recs view.
212
+ const exByCheck = new Map();
213
+ for (const r of exRows) {
214
+ const arr = exByCheck.get(r.check_id) ?? [];
215
+ if (arr.length < 3) {
216
+ arr.push(r);
217
+ exByCheck.set(r.check_id, arr);
218
+ }
219
+ }
220
+ const SEV = { crit: 0, high: 1, med: 2, low: 3, info: 4 };
221
+ const sevRank = (s) => SEV[s] ?? 5; // unknown severity sorts last, not NaN
222
+ const catMap = new Map();
223
+ const mkEx = (r) => {
224
+ const t = parse(r.traffic_at_risk);
225
+ return { urlKey: r.url_key, evidence: parse(r.evidence), clicks: t.clicks || 0, impressions: t.impressions || 0 };
226
+ };
227
+ for (const c of byCheck) {
228
+ const exs = exByCheck.get(c.check_id) ?? [];
229
+ const rec = exs[0] ? parse(exs[0].recommendation) : {};
230
+ const list = catMap.get(c.category) ?? [];
231
+ const examples = exs.map(mkEx);
232
+ list.push({
233
+ checkId: c.check_id,
234
+ title: rec.title || c.check_id,
235
+ fix: rec.text || '',
236
+ severity: c.severity,
237
+ count: c.count,
238
+ example: examples[0] ?? null,
239
+ examples,
240
+ });
241
+ catMap.set(c.category, list);
242
+ }
243
+ const recommendations = [...catMap.entries()]
244
+ .map(([category, checks]) => ({ category, checks: checks.sort((a, b) => (sevRank(a.severity) - sevRank(b.severity)) || (b.count - a.count)) }))
245
+ .sort((a, b) => Math.min(...a.checks.map(c => sevRank(c.severity))) - Math.min(...b.checks.map(c => sevRank(c.severity))));
246
+ findings = { runId: lastRun.run_id, total: lastRun.finding_count, finishedAt: lastRun.finished_at, byCheck, top, recommendations };
247
+ }
248
+ // Equity vs reality: join per-page internal PageRank with 28d GSC traffic, bucketed by template.
249
+ // (Needs a crawl — pages.ipr is populated post-crawl. Empty if crawl-free.)
250
+ let equityScatter;
251
+ let templateMismatch;
252
+ const pageRows = db.db.prepare(`SELECT url_key, COALESCE(ipr,0) ipr FROM pages WHERE status_code=200 AND indexable=1`).all();
253
+ if (pageRows.length) {
254
+ const g28 = new Map();
255
+ for (const r of db.db.prepare(`SELECT page_key, SUM(clicks) c, SUM(impressions) i FROM search_analytics WHERE page_key IS NOT NULL AND ${UB} AND date > date(?, '-28 days') GROUP BY page_key`).all(maxDate))
256
+ g28.set(r.page_key, { clicks: r.c, impr: r.i });
257
+ const scatter = [];
258
+ const agg = new Map();
259
+ for (const p of pageRows) {
260
+ const t = templateBucket(p.url_key);
261
+ const gg = g28.get(p.url_key) ?? { clicks: 0, impr: 0 };
262
+ const a = agg.get(t) ?? { ipr: 0, clicks: 0, n: 0 };
263
+ a.ipr += p.ipr;
264
+ a.clicks += gg.clicks;
265
+ a.n += 1;
266
+ agg.set(t, a);
267
+ if (p.ipr >= 15 || gg.impr >= 5)
268
+ scatter.push({ x: Math.round(p.ipr), y: gg.impr, t: ['content', 'category', 'homepage'].includes(t) ? t : 'other' });
269
+ }
270
+ equityScatter = scatter.sort((a, b) => b.y - a.y).slice(0, 500);
271
+ const tIpr = [...agg.values()].reduce((s, a) => s + a.ipr, 0) || 1;
272
+ const tClk = [...agg.values()].reduce((s, a) => s + a.clicks, 0) || 1;
273
+ templateMismatch = [...agg.entries()].map(([template, a]) => ({ template, pages: a.n, iprPct: Math.round(a.ipr / tIpr * 1000) / 10, trafficPct: Math.round(a.clicks / tClk * 1000) / 10 }))
274
+ .sort((x, y) => y.iprPct - x.iprPct).slice(0, 8);
275
+ }
276
+ // Top internally-linked pages with their current status code (most-linked first). A 404/redirect
277
+ // high in this list is a major leak — lots of internal links pointing at a dead/redirected URL.
278
+ const topLinkedPages = db.db.prepare(`SELECT url_key, COALESCE(inlink_count,0) inl, status_code, indexable, indexable_reason FROM pages WHERE is_internal=1 ORDER BY inlink_count DESC, status_code LIMIT 30`).all()
279
+ .map(p => ({ url: p.url_key, inlinks: p.inl, status: p.status_code, indexable: !!p.indexable, reason: p.indexable_reason }));
280
+ // Site health — Screaming-Frog-style crawl diagnostics. One conditional-aggregation
281
+ // pass per table region (not a COUNT(*) scan per bucket); tones are assigned HERE, where
282
+ // the meaning of each bucket is known, so the UI never has to infer colour from label text.
283
+ let crawlHealth;
284
+ {
285
+ const one = (sql) => db.db.prepare(sql).get().n;
286
+ const totalPages = one(`SELECT COUNT(*) n FROM pages WHERE is_internal=1`);
287
+ if (totalPages > 0) {
288
+ const stats = (rows) => rows.filter(r => r.count > 0);
289
+ const num = (v) => Number(v) || 0;
290
+ // Estimated transfer size: pages.bytes is the DECODED HTML size (see Crawler), so
291
+ // compressed responses ship roughly 22% of it over the wire.
292
+ const EST = `CAST(CASE WHEN content_encoding IS NOT NULL AND content_encoding <> '' THEN bytes*0.22 ELSE bytes END AS INTEGER)`;
293
+ const IDX = `status_code=200 AND indexable=1 AND ${HTML_CT}`;
294
+ const g1 = db.db.prepare(`SELECT
295
+ SUM(CASE WHEN status_code=200 THEN 1 ELSE 0 END) ok,
296
+ SUM(CASE WHEN status_code BETWEEN 300 AND 399 THEN 1 ELSE 0 END) r3,
297
+ SUM(CASE WHEN status_code BETWEEN 400 AND 499 THEN 1 ELSE 0 END) r4,
298
+ SUM(CASE WHEN status_code BETWEEN 500 AND 599 THEN 1 ELSE 0 END) r5,
299
+ SUM(CASE WHEN indexable_reason='robots-disallowed' THEN 1 ELSE 0 END) rb,
300
+ SUM(CASE WHEN ${HTML_CT} AND response_time_ms < 200 THEN 1 ELSE 0 END) s1,
301
+ SUM(CASE WHEN ${HTML_CT} AND response_time_ms BETWEEN 200 AND 499 THEN 1 ELSE 0 END) s2,
302
+ SUM(CASE WHEN ${HTML_CT} AND response_time_ms BETWEEN 500 AND 999 THEN 1 ELSE 0 END) s3,
303
+ SUM(CASE WHEN ${HTML_CT} AND response_time_ms >= 1000 THEN 1 ELSE 0 END) s4,
304
+ SUM(CASE WHEN ${HTML_CT} AND status_code=200 AND ${EST} < 30000 THEN 1 ELSE 0 END) z1,
305
+ SUM(CASE WHEN ${HTML_CT} AND status_code=200 AND ${EST} BETWEEN 30000 AND 59999 THEN 1 ELSE 0 END) z2,
306
+ SUM(CASE WHEN ${HTML_CT} AND status_code=200 AND ${EST} BETWEEN 60000 AND 99999 THEN 1 ELSE 0 END) z3,
307
+ SUM(CASE WHEN ${HTML_CT} AND status_code=200 AND ${EST} >= 100000 THEN 1 ELSE 0 END) z4
308
+ FROM pages`).get();
309
+ const g2 = db.db.prepare(`SELECT
310
+ SUM(CASE WHEN title IS NULL OR TRIM(title)='' THEN 1 ELSE 0 END) tMissing,
311
+ SUM(CASE WHEN title_length > 60 THEN 1 ELSE 0 END) tLong,
312
+ SUM(CASE WHEN title_length BETWEEN 1 AND 29 THEN 1 ELSE 0 END) tShort,
313
+ SUM(CASE WHEN meta_description IS NULL OR TRIM(meta_description)='' THEN 1 ELSE 0 END) mMissing,
314
+ SUM(CASE WHEN meta_description_length > 155 THEN 1 ELSE 0 END) mLong,
315
+ SUM(CASE WHEN meta_description_length BETWEEN 1 AND 69 THEN 1 ELSE 0 END) mShort,
316
+ SUM(CASE WHEN h1 IS NULL OR TRIM(h1)='' THEN 1 ELSE 0 END) h1Missing,
317
+ SUM(CASE WHEN h1_count > 1 THEN 1 ELSE 0 END) h1Multi,
318
+ SUM(CASE WHEN images_without_alt > 0 THEN 1 ELSE 0 END) noAlt,
319
+ SUM(CASE WHEN images_missing_dimensions > 0 THEN 1 ELSE 0 END) noDims,
320
+ SUM(CASE WHEN mixed_content_count > 0 THEN 1 ELSE 0 END) mixed,
321
+ SUM(CASE WHEN heading_skips > 0 THEN 1 ELSE 0 END) hSkips
322
+ FROM pages WHERE ${IDX}`).get();
323
+ // Duplicate title/meta: one GROUP BY pass each (sum of members of every duplicated group).
324
+ const dupCount = (col) => db.db.prepare(`SELECT COALESCE(SUM(c),0) n FROM (SELECT COUNT(*) c FROM pages WHERE ${IDX} AND ${col} IS NOT NULL AND TRIM(${col})<>'' GROUP BY LOWER(TRIM(${col})) HAVING COUNT(*)>1)`).get().n;
325
+ const responseCodes = stats([
326
+ { label: '200 OK', count: num(g1.ok), tone: 'ok' },
327
+ { label: '3xx redirect', count: num(g1.r3), tone: 'muted' },
328
+ { label: '4xx client error', count: num(g1.r4), tone: 'warn' },
329
+ { label: '5xx server error', count: num(g1.r5), tone: 'bad' },
330
+ { label: 'Blocked by robots', count: num(g1.rb), tone: 'warn' },
331
+ { label: 'Fetch failed', count: one(`SELECT COUNT(*) n FROM errors WHERE error_type='fetch'`), tone: 'bad' },
332
+ ]);
333
+ const indexability = stats(db.db.prepare(`SELECT COALESCE(CASE WHEN indexable=1 THEN 'Indexable' ELSE indexable_reason END,'unknown') label, COUNT(*) count
334
+ FROM pages WHERE status_code IS NOT NULL GROUP BY label ORDER BY count DESC`).all()
335
+ .map(r => ({ ...r, tone: r.label === 'Indexable' ? 'ok' : /canonicalised|redirect/.test(r.label) ? 'muted' : 'warn' })));
336
+ const speed = stats([
337
+ { label: 'Under 200 ms', count: num(g1.s1), tone: 'ok' },
338
+ { label: '200–500 ms', count: num(g1.s2), tone: 'ok' },
339
+ { label: '500 ms–1 s', count: num(g1.s3), tone: 'warn' },
340
+ { label: 'Over 1 s', count: num(g1.s4), tone: 'bad' },
341
+ ]);
342
+ const htmlSize = stats([
343
+ { label: 'Under 30 KB', count: num(g1.z1), tone: 'ok' },
344
+ { label: '30–60 KB', count: num(g1.z2), tone: 'muted' },
345
+ { label: '60–100 KB', count: num(g1.z3) },
346
+ { label: 'Over 100 KB', count: num(g1.z4), tone: 'bad' },
347
+ ]);
348
+ const depth = stats(db.db.prepare(`SELECT CASE WHEN depth >= 6 THEN '6+' ELSE CAST(depth AS TEXT) END label, COUNT(*) count
349
+ FROM pages WHERE depth IS NOT NULL GROUP BY label ORDER BY MIN(depth)`).all()
350
+ .map(r => ({ label: `Depth ${r.label}`, count: r.count })));
351
+ const titles = stats([
352
+ { label: 'Missing', count: num(g2.tMissing), tone: 'warn' },
353
+ { label: 'Duplicate', count: dupCount('title'), tone: 'warn' },
354
+ { label: 'Over 60 characters', count: num(g2.tLong), tone: 'warn' },
355
+ { label: 'Under 30 characters', count: num(g2.tShort), tone: 'warn' },
356
+ ]);
357
+ const metas = stats([
358
+ { label: 'Missing', count: num(g2.mMissing), tone: 'warn' },
359
+ { label: 'Duplicate', count: dupCount('meta_description'), tone: 'warn' },
360
+ { label: 'Over 155 characters', count: num(g2.mLong), tone: 'warn' },
361
+ { label: 'Under 70 characters', count: num(g2.mShort), tone: 'warn' },
362
+ ]);
363
+ const onPage = stats([
364
+ { label: 'Missing H1', count: num(g2.h1Missing), tone: 'warn' },
365
+ { label: 'Multiple H1s', count: num(g2.h1Multi), tone: 'warn' },
366
+ { label: 'Images missing alt text', count: num(g2.noAlt), tone: 'warn' },
367
+ { label: 'Images without dimensions (CLS)', count: num(g2.noDims), tone: 'warn' },
368
+ { label: 'Mixed content (http on https)', count: num(g2.mixed), tone: 'warn' },
369
+ { label: 'Heading order skips', count: num(g2.hSkips), tone: 'warn' },
370
+ ]);
371
+ const serverErrors = db.db.prepare(`SELECT url_key url, status_code status FROM pages WHERE status_code BETWEEN 500 AND 599 ORDER BY inlink_count DESC LIMIT 15`).all();
372
+ const slowPages = db.db.prepare(`SELECT url_key url, response_time_ms ms FROM pages WHERE ${HTML_CT} AND status_code=200 AND response_time_ms >= 1000 ORDER BY response_time_ms DESC LIMIT 15`).all();
373
+ const imgSampled = one(`SELECT COUNT(*) n FROM image_assets WHERE bytes IS NOT NULL`);
374
+ const largeImages = db.db.prepare(`SELECT url, bytes, used_on FROM image_assets WHERE bytes >= 100000 ORDER BY bytes DESC LIMIT 15`).all()
375
+ .map(r => ({ url: r.url, kb: Math.round(r.bytes / 1024), usedOn: r.used_on }));
376
+ crawlHealth = {
377
+ responseCodes, indexability, speed, htmlSize, depth, titles, metas, onPage, serverErrors, slowPages, largeImages,
378
+ imageStats: imgSampled > 0 ? {
379
+ sampled: imgSampled,
380
+ over100kb: one(`SELECT COUNT(*) n FROM image_assets WHERE bytes >= 100000`),
381
+ over300kb: one(`SELECT COUNT(*) n FROM image_assets WHERE bytes >= 300000`),
382
+ } : null,
383
+ totalPages,
384
+ };
385
+ }
386
+ }
387
+ // Agent readiness — latest result persisted by check_agent_readiness (one property per DB).
388
+ const arRow = db.db.prepare(`SELECT score, level, checks, by_category, checked_at FROM agent_readiness ORDER BY checked_at DESC LIMIT 1`).get();
389
+ const parseJ = (s) => { try {
390
+ return JSON.parse(s || '[]');
391
+ }
392
+ catch {
393
+ return [];
394
+ } };
395
+ const agentReadiness = arRow ? { score: arRow.score, level: arRow.level, checkedAt: arRow.checked_at, byCategory: parseJ(arRow.by_category), checks: parseJ(arRow.checks) } : undefined;
396
+ // Cannibalisation braids — queries where multiple URLs compete, each URL's weekly avg position.
397
+ // The "braid" of crossing lines over time is Google thrashing between URLs (severity = tangle).
398
+ const cannQueries = db.db.prepare(`WITH pp AS (SELECT query, page_key, SUM(impressions) imp, SUM(position*impressions)*1.0/NULLIF(SUM(impressions),0) pos
399
+ FROM search_analytics WHERE query IS NOT NULL AND page_key IS NOT NULL AND ${UB} AND date > date(?, '-90 days')
400
+ GROUP BY query, page_key HAVING pos < 20 AND imp >= 30)
401
+ SELECT query, COUNT(*) urls, SUM(imp) imp FROM pp GROUP BY query HAVING COUNT(*) >= 2 ORDER BY imp DESC LIMIT 6`).all(maxDate);
402
+ const cannibalisation = cannQueries.map(q => {
403
+ const urls = db.db.prepare(`SELECT page_key, SUM(impressions) imp FROM search_analytics WHERE query=? AND page_key IS NOT NULL AND ${UB} AND date > date(?, '-90 days') GROUP BY page_key ORDER BY imp DESC LIMIT 4`).all(q.query, maxDate);
404
+ return {
405
+ query: q.query,
406
+ urls: urls.map(u => ({
407
+ url: u.page_key,
408
+ points: db.db.prepare(`SELECT strftime('%Y-%W', date) week, SUM(position*impressions)*1.0/NULLIF(SUM(impressions),0) pos FROM search_analytics WHERE query=? AND page_key=? AND ${UB} AND date > date(?, '-90 days') AND impressions > 0 GROUP BY week ORDER BY week`).all(q.query, u.page_key, maxDate)
409
+ .map(w => ({ week: w.week, position: Math.round(w.pos * 10) / 10 })),
410
+ })),
411
+ };
412
+ });
413
+ // Cannibalisation table — the full list (not just the 6 braided). One query for every
414
+ // (query,page_key) pair that competes, grouped client-side. Verdict by IMPRESSION share (robust:
415
+ // impressions are always present, so a single noise click can't flip it): 'dominant' = Google
416
+ // overwhelmingly shows one URL (≥70%, low urgency); 'split' = it rotates between URLs
417
+ // (consolidate). crossType = the competing URLs are DIFFERENT page types (e.g. a category page
418
+ // and an article) — the clearest intent confusion and the most actionable to resolve.
419
+ const cannRows = db.db.prepare(`WITH pp AS (SELECT query, page_key, SUM(impressions) imp, SUM(clicks) clk, SUM(position*impressions)*1.0/NULLIF(SUM(impressions),0) pos
420
+ FROM search_analytics WHERE query IS NOT NULL AND page_key IS NOT NULL AND ${UB} AND date > date(?, '-90 days')
421
+ GROUP BY query, page_key HAVING pos < 20 AND imp >= 30),
422
+ multi AS (SELECT query FROM pp GROUP BY query HAVING COUNT(*) >= 2)
423
+ SELECT pp.query, pp.page_key, pp.imp, pp.clk, pp.pos FROM pp JOIN multi ON multi.query = pp.query
424
+ ORDER BY pp.query, pp.imp DESC`).all(maxDate);
425
+ const cannMap = new Map();
426
+ for (const r of cannRows) {
427
+ const arr = cannMap.get(r.query) ?? [];
428
+ arr.push({ url: r.page_key, impressions: r.imp, clicks: r.clk, position: Math.round(r.pos * 10) / 10, template: templateBucket(r.page_key) });
429
+ cannMap.set(r.query, arr);
430
+ }
431
+ const cannibalisationTable = [...cannMap.entries()].map(([query, urls]) => {
432
+ const totalImpressions = urls.reduce((s, u) => s + u.impressions, 0);
433
+ const totalClicks = urls.reduce((s, u) => s + u.clicks, 0);
434
+ // Impression share of the most-shown URL — robust to click noise (the old click-share verdict
435
+ // could flip 'dominant' on a single click in the 5–10 total-clicks band).
436
+ const imprShare = totalImpressions > 0 ? Math.max(...urls.map(u => u.impressions)) / totalImpressions : 0;
437
+ const verdict = imprShare >= 0.7 ? 'dominant' : 'split';
438
+ // Cross-type = the competing URLs span 2+ page templates (e.g. a /category page vs an article).
439
+ const crossType = new Set(urls.map(u => u.template)).size >= 2;
440
+ return { query, urlCount: urls.length, totalImpressions, totalClicks, verdict, crossType, urls: urls.slice(0, 6) };
441
+ }).sort((a, b) => (Number(b.crossType) - Number(a.crossType)) || // cross-type first (most actionable)
442
+ ((a.verdict === 'split' ? 0 : 1) - (b.verdict === 'split' ? 0 : 1)) || // then split before dominant
443
+ (b.totalImpressions - a.totalImpressions)).slice(0, 40);
444
+ const payload = {
445
+ siteUrl,
446
+ dateRange: { current: `last 28d to ${maxDate}`, prior: 'prior 28d', maxDate, rawMaxDate: fresh.rawMax ?? maxDate, trimmedDays: fresh.trimmedDays },
447
+ equityScatter,
448
+ templateMismatch,
449
+ crawlHealth,
450
+ cannibalisation,
451
+ cannibalisationTable,
452
+ brandedSplit,
453
+ topLinkedPages,
454
+ agentReadiness,
455
+ rankHistory,
456
+ dateAlignment,
457
+ rankingDistribution,
458
+ strikingDistance,
459
+ quickWins,
460
+ contentDecay,
461
+ summary: {
462
+ current: { ...cur, ctr: ctr(cur) },
463
+ prior: { ...prior, ctr: ctr(prior) },
464
+ },
465
+ rankTrend,
466
+ topKeywords,
467
+ deviceBreakdown,
468
+ countryBreakdown,
469
+ pagePerformance,
470
+ keywordMovement,
471
+ findings,
472
+ };
473
+ // Best-effort cache write (skip silently if the DB is read-only).
474
+ try {
475
+ db.db.prepare(`INSERT INTO dashboard_cache (id, version, payload, created_at) VALUES (1, ?, ?, datetime('now'))
476
+ ON CONFLICT(id) DO UPDATE SET version=excluded.version, payload=excluded.payload, created_at=excluded.created_at`)
477
+ .run(version, JSON.stringify(payload));
478
+ }
479
+ catch { /* cache is an optimisation, never block the dashboard on it */ }
480
+ return payload;
481
+ }
482
+ finally {
483
+ db.close();
484
+ }
485
+ }
486
+ /** Template proxy: first URL path segment (homepage / content-root collapse to named buckets). */
487
+ function templateBucket(urlKey) {
488
+ try {
489
+ const p = new URL(urlKey).pathname.replace(/^\/+|\/+$/g, '');
490
+ if (!p)
491
+ return 'homepage';
492
+ return p.includes('/') ? p.split('/')[0] : 'content';
493
+ }
494
+ catch {
495
+ return 'other';
496
+ }
497
+ }
498
+ /** Categorise a page by period-over-period performance (per the agency-report idiom). */
499
+ function pageCategory(cur, prev) {
500
+ const pct = prev.clicks ? ((cur.clicks - prev.clicks) / prev.clicks) * 100 : (cur.clicks > 0 ? 100 : 0);
501
+ if (pct >= 15)
502
+ return 'top performer';
503
+ if (pct <= -30)
504
+ return 'low performer';
505
+ if (prev.impressions && ((cur.impressions - prev.impressions) / prev.impressions) * 100 <= -20)
506
+ return 'declining visibility';
507
+ const curCtr = cur.impressions ? cur.clicks / cur.impressions : 0;
508
+ const prevCtr = prev.impressions ? prev.clicks / prev.impressions : 0;
509
+ if (prevCtr && curCtr < prevCtr * 0.85)
510
+ return 'improve CTR';
511
+ return 'stable';
512
+ }
513
+ /** Categorise a query's rank movement (first-seen vs last-seen position). */
514
+ function movementCategory(first, last) {
515
+ if (first > 3 && last <= 3)
516
+ return 'entered top 3';
517
+ if (first <= 3 && last > 3)
518
+ return 'dropped from top 3';
519
+ const delta = first - last; // positive = improved (lower position number)
520
+ if (delta >= 3)
521
+ return 'gained';
522
+ if (delta <= -3)
523
+ return 'lost';
524
+ return 'stable';
525
+ }
526
+ /** Reconcile GSC (daily) and DataForSEO (monthly) date ranges before charting. */
527
+ function reconcileRanges(gscMin, gscMax, rankHistory) {
528
+ const gsc = gscMin && gscMax ? { start: gscMin, end: gscMax } : null;
529
+ const dfs = rankHistory.length
530
+ ? { start: rankHistory[0].period, end: rankHistory[rankHistory.length - 1].period }
531
+ : null;
532
+ if (!dfs)
533
+ return { gsc, dataforseo: null, overlap: null, note: 'No DataForSEO rank history yet — run track_ranks to add the over-time sequence.' };
534
+ if (!gsc)
535
+ return { gsc: null, dataforseo: dfs, overlap: null, note: 'No GSC data — run sync_gsc.' };
536
+ const gsM = gsc.start.slice(0, 7);
537
+ const geM = gsc.end.slice(0, 7);
538
+ const oStart = gsM > dfs.start ? gsM : dfs.start;
539
+ const oEnd = geM < dfs.end ? geM : dfs.end;
540
+ const aligned = oStart <= oEnd;
541
+ return {
542
+ gsc,
543
+ dataforseo: dfs,
544
+ overlap: aligned ? { start: oStart, end: oEnd } : null,
545
+ note: aligned
546
+ ? `GSC ${gsc.start}–${gsc.end} (daily) overlaps DataForSEO ${dfs.start}–${dfs.end} (monthly) at ${oStart}–${oEnd}; time charts share that window.`
547
+ : `No overlap: GSC ends ${geM}, DataForSEO covers ${dfs.start}–${dfs.end}. Render on separate axes.`,
548
+ };
549
+ }
550
+ //# sourceMappingURL=dashboardData.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboardData.js","sourceRoot":"","sources":["../../src/core/dashboardData.ts"],"names":[],"mappings":"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"}