@ainyc/canonry 4.1.1 → 4.2.2
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/assets/assets/index-DoJfQkim.js +302 -0
- package/assets/index.html +1 -1
- package/dist/{chunk-NCWCPBOT.js → chunk-7YSI4GFA.js} +760 -27
- package/dist/{chunk-BQN6BBHI.js → chunk-HJZY4EOE.js} +289 -247
- package/dist/{chunk-KCETXLDF.js → chunk-SR7TGHHG.js} +18 -6
- package/dist/{chunk-O5JZQUPX.js → chunk-T2I6AO7D.js} +10 -1
- package/dist/cli.js +100 -29
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-EITZP4KG.js → intelligence-service-CQGAXKKN.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +6 -6
- package/assets/assets/index-Dtgn4FDp.js +0 -302
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
configExists,
|
|
5
5
|
loadConfig,
|
|
6
6
|
saveConfigPatch
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SR7TGHHG.js";
|
|
8
8
|
import {
|
|
9
|
+
DEFAULT_RUN_HISTORY_LIMIT,
|
|
9
10
|
IntelligenceService,
|
|
10
11
|
MIN_TREND_POINTS,
|
|
11
12
|
agentMemory,
|
|
@@ -16,11 +17,22 @@ import {
|
|
|
16
17
|
backlinkSummaries,
|
|
17
18
|
bingCoverageSnapshots,
|
|
18
19
|
bingUrlInspections,
|
|
20
|
+
buildAiSourceOrigin,
|
|
19
21
|
buildBrandTokens,
|
|
22
|
+
buildCitationScorecard,
|
|
23
|
+
buildCompetitorLandscape,
|
|
24
|
+
buildCompetitorPressureScore,
|
|
20
25
|
buildContentGapRows,
|
|
21
26
|
buildContentSourceRows,
|
|
22
27
|
buildContentTargetRows,
|
|
28
|
+
buildGapQueryScore,
|
|
23
29
|
buildInventory,
|
|
30
|
+
buildMentionLandscape,
|
|
31
|
+
buildMovementSummary,
|
|
32
|
+
buildOverviewCompetitors,
|
|
33
|
+
buildProviderScores,
|
|
34
|
+
buildRunHistory,
|
|
35
|
+
buildVisibilityScore,
|
|
24
36
|
categorizeQueryByIntent,
|
|
25
37
|
ccReleaseSyncs,
|
|
26
38
|
competitors,
|
|
@@ -49,7 +61,7 @@ import {
|
|
|
49
61
|
runs,
|
|
50
62
|
schedules,
|
|
51
63
|
usageCounters
|
|
52
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-7YSI4GFA.js";
|
|
53
65
|
import {
|
|
54
66
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
55
67
|
AGENT_PROVIDER_IDS,
|
|
@@ -115,7 +127,7 @@ import {
|
|
|
115
127
|
visibilityStateFromAnswerMentioned,
|
|
116
128
|
windowCutoff,
|
|
117
129
|
wordpressEnvSchema
|
|
118
|
-
} from "./chunk-
|
|
130
|
+
} from "./chunk-T2I6AO7D.js";
|
|
119
131
|
|
|
120
132
|
// src/telemetry.ts
|
|
121
133
|
import crypto from "crypto";
|
|
@@ -1946,7 +1958,7 @@ async function historyRoutes(app) {
|
|
|
1946
1958
|
for (const snap of allSnapshots) {
|
|
1947
1959
|
const key = `${snap.runId}:${snap.queryId}`;
|
|
1948
1960
|
const existing = deduped.get(key);
|
|
1949
|
-
if (!existing || !existing.answerMentioned && snap.answerMentioned || existing.answerMentioned === snap.answerMentioned && snap.citationState ===
|
|
1961
|
+
if (!existing || !existing.answerMentioned && snap.answerMentioned || existing.answerMentioned === snap.answerMentioned && snap.citationState === CitationStates.cited) {
|
|
1950
1962
|
deduped.set(key, snap);
|
|
1951
1963
|
}
|
|
1952
1964
|
}
|
|
@@ -1968,16 +1980,16 @@ async function historyRoutes(app) {
|
|
|
1968
1980
|
function computeTransitions(snaps) {
|
|
1969
1981
|
return snaps.map((snap, idx) => {
|
|
1970
1982
|
const run = projectRuns.find((r) => r.id === snap.runId);
|
|
1971
|
-
let transition = snap.citationState ===
|
|
1983
|
+
let transition = snap.citationState === CitationStates.cited ? "cited" : "not-cited";
|
|
1972
1984
|
let visibilityTransition = snap.answerMentioned ? "visible" : "not-visible";
|
|
1973
1985
|
if (idx === 0) {
|
|
1974
1986
|
transition = "new";
|
|
1975
1987
|
visibilityTransition = "new";
|
|
1976
1988
|
} else {
|
|
1977
1989
|
const prev = snaps[idx - 1];
|
|
1978
|
-
if (prev.citationState === "not-cited" && snap.citationState ===
|
|
1990
|
+
if (prev.citationState === CitationStates["not-cited"] && snap.citationState === CitationStates.cited) {
|
|
1979
1991
|
transition = "emerging";
|
|
1980
|
-
} else if (prev.citationState ===
|
|
1992
|
+
} else if (prev.citationState === CitationStates.cited && snap.citationState === CitationStates["not-cited"]) {
|
|
1981
1993
|
transition = "lost";
|
|
1982
1994
|
}
|
|
1983
1995
|
if (!prev.answerMentioned && snap.answerMentioned) {
|
|
@@ -2051,7 +2063,7 @@ async function historyRoutes(app) {
|
|
|
2051
2063
|
resolvedVisibilityState: resolveSnapshotVisibilityState(s, project)
|
|
2052
2064
|
};
|
|
2053
2065
|
const existing = map1.get(s.queryId);
|
|
2054
|
-
if (!existing || !existing.resolvedAnswerMentioned && resolved.resolvedAnswerMentioned || existing.resolvedAnswerMentioned === resolved.resolvedAnswerMentioned && resolved.citationState ===
|
|
2066
|
+
if (!existing || !existing.resolvedAnswerMentioned && resolved.resolvedAnswerMentioned || existing.resolvedAnswerMentioned === resolved.resolvedAnswerMentioned && resolved.citationState === CitationStates.cited) {
|
|
2055
2067
|
map1.set(s.queryId, resolved);
|
|
2056
2068
|
}
|
|
2057
2069
|
}
|
|
@@ -2063,7 +2075,7 @@ async function historyRoutes(app) {
|
|
|
2063
2075
|
resolvedVisibilityState: resolveSnapshotVisibilityState(s, project)
|
|
2064
2076
|
};
|
|
2065
2077
|
const existing = map2.get(s.queryId);
|
|
2066
|
-
if (!existing || !existing.resolvedAnswerMentioned && resolved.resolvedAnswerMentioned || existing.resolvedAnswerMentioned === resolved.resolvedAnswerMentioned && resolved.citationState ===
|
|
2078
|
+
if (!existing || !existing.resolvedAnswerMentioned && resolved.resolvedAnswerMentioned || existing.resolvedAnswerMentioned === resolved.resolvedAnswerMentioned && resolved.citationState === CitationStates.cited) {
|
|
2067
2079
|
map2.set(s.queryId, resolved);
|
|
2068
2080
|
}
|
|
2069
2081
|
}
|
|
@@ -2177,7 +2189,7 @@ async function analyticsRoutes(app) {
|
|
|
2177
2189
|
consistencyMap.set(s.queryId, entry);
|
|
2178
2190
|
}
|
|
2179
2191
|
entry.totalRuns.add(s.runId);
|
|
2180
|
-
if (s.citationState ===
|
|
2192
|
+
if (s.citationState === CitationStates.cited) entry.citedRuns.add(s.runId);
|
|
2181
2193
|
if (resolveSnapshotAnswerMentioned(s, project)) entry.mentionedRuns.add(s.runId);
|
|
2182
2194
|
}
|
|
2183
2195
|
}
|
|
@@ -2209,7 +2221,7 @@ async function analyticsRoutes(app) {
|
|
|
2209
2221
|
const notMentioned = [];
|
|
2210
2222
|
for (const [queryId, qSnapshots] of byQuery) {
|
|
2211
2223
|
const query = qSnapshots[0]?.query ?? "";
|
|
2212
|
-
const citedProviders = qSnapshots.filter((s) => s.citationState ===
|
|
2224
|
+
const citedProviders = qSnapshots.filter((s) => s.citationState === CitationStates.cited).map((s) => s.provider);
|
|
2213
2225
|
const mentionedProviders = qSnapshots.filter((s) => s.resolvedMentioned).map((s) => s.provider);
|
|
2214
2226
|
const competitorsCiting = /* @__PURE__ */ new Set();
|
|
2215
2227
|
for (const s of qSnapshots) {
|
|
@@ -2338,7 +2350,7 @@ function bucketSizeForSpan(spanDays) {
|
|
|
2338
2350
|
}
|
|
2339
2351
|
function computeProviderMetric(snapshots) {
|
|
2340
2352
|
const total = snapshots.length;
|
|
2341
|
-
const cited = snapshots.filter((s) => s.citationState ===
|
|
2353
|
+
const cited = snapshots.filter((s) => s.citationState === CitationStates.cited).length;
|
|
2342
2354
|
const mentionedCount = snapshots.filter((s) => s.resolvedMentioned).length;
|
|
2343
2355
|
return {
|
|
2344
2356
|
citationRate: total > 0 ? Math.round(cited / total * 1e4) / 1e4 : 0,
|
|
@@ -2967,7 +2979,7 @@ function renderCitationMatrix(scorecard) {
|
|
|
2967
2979
|
if (!cell) {
|
|
2968
2980
|
return '<td><span class="cell-pending">\u2014</span></td>';
|
|
2969
2981
|
}
|
|
2970
|
-
if (cell.citationState ===
|
|
2982
|
+
if (cell.citationState === CitationStates.cited) {
|
|
2971
2983
|
return '<td><span class="cell-cited">Cited</span></td>';
|
|
2972
2984
|
}
|
|
2973
2985
|
return '<td><span class="cell-not-cited">Not cited</span></td>';
|
|
@@ -3846,7 +3858,6 @@ function extractPath(url) {
|
|
|
3846
3858
|
var TOP_QUERIES_LIMIT = 20;
|
|
3847
3859
|
var TOP_LANDING_PAGES_LIMIT = 20;
|
|
3848
3860
|
var TOP_AI_REFERRAL_PAGES_LIMIT = 10;
|
|
3849
|
-
var TOP_SOURCE_DOMAINS_LIMIT = 20;
|
|
3850
3861
|
var TOP_CAMPAIGN_LIMIT = 10;
|
|
3851
3862
|
var INSIGHT_LOOKBACK_RUNS = 5;
|
|
3852
3863
|
function safeNum(value) {
|
|
@@ -3857,14 +3868,6 @@ function safeNum(value) {
|
|
|
3857
3868
|
}
|
|
3858
3869
|
return 0;
|
|
3859
3870
|
}
|
|
3860
|
-
function citedDomainBelongsToProject(citedDomain, projectDomains) {
|
|
3861
|
-
const candidate = normalizeProjectDomain(citedDomain);
|
|
3862
|
-
for (const domain of projectDomains) {
|
|
3863
|
-
const normalized = normalizeProjectDomain(domain);
|
|
3864
|
-
if (candidate === normalized || candidate.endsWith(`.${normalized}`)) return true;
|
|
3865
|
-
}
|
|
3866
|
-
return false;
|
|
3867
|
-
}
|
|
3868
3871
|
function categorizeQuery(query, projectDisplayName, canonicalDomain) {
|
|
3869
3872
|
return categorizeQueryByIntent(query, buildBrandTokens(canonicalDomain, projectDisplayName));
|
|
3870
3873
|
}
|
|
@@ -3891,186 +3894,6 @@ function loadQueryLookup(db, projectId) {
|
|
|
3891
3894
|
for (const row of rows) byId.set(row.id, row.query);
|
|
3892
3895
|
return { byId };
|
|
3893
3896
|
}
|
|
3894
|
-
function buildCitationScorecard(snapshots, queryLookup) {
|
|
3895
|
-
if (snapshots.length === 0) {
|
|
3896
|
-
return { queries: [], providers: [], matrix: [], providerRates: [] };
|
|
3897
|
-
}
|
|
3898
|
-
const querySet = /* @__PURE__ */ new Set();
|
|
3899
|
-
const providerSet = /* @__PURE__ */ new Set();
|
|
3900
|
-
for (const snap of snapshots) {
|
|
3901
|
-
const q = queryLookup.byId.get(snap.queryId);
|
|
3902
|
-
if (!q) continue;
|
|
3903
|
-
querySet.add(q);
|
|
3904
|
-
providerSet.add(snap.provider);
|
|
3905
|
-
}
|
|
3906
|
-
const queryList = [...querySet].sort();
|
|
3907
|
-
const providerList = [...providerSet].sort();
|
|
3908
|
-
const matrix = queryList.map(
|
|
3909
|
-
() => providerList.map(() => null)
|
|
3910
|
-
);
|
|
3911
|
-
const providerCounts = /* @__PURE__ */ new Map();
|
|
3912
|
-
for (const snap of snapshots) {
|
|
3913
|
-
const q = queryLookup.byId.get(snap.queryId);
|
|
3914
|
-
if (!q) continue;
|
|
3915
|
-
const qi = queryList.indexOf(q);
|
|
3916
|
-
const pi = providerList.indexOf(snap.provider);
|
|
3917
|
-
if (qi < 0 || pi < 0) continue;
|
|
3918
|
-
matrix[qi][pi] = {
|
|
3919
|
-
citationState: snap.citationState === "cited" ? "cited" : "not-cited",
|
|
3920
|
-
answerMentioned: snap.answerMentioned ?? null,
|
|
3921
|
-
model: snap.model
|
|
3922
|
-
};
|
|
3923
|
-
const counts = providerCounts.get(snap.provider) ?? { cited: 0, total: 0 };
|
|
3924
|
-
counts.total++;
|
|
3925
|
-
if (snap.citationState === "cited") counts.cited++;
|
|
3926
|
-
providerCounts.set(snap.provider, counts);
|
|
3927
|
-
}
|
|
3928
|
-
const providerRates = providerList.map((provider) => {
|
|
3929
|
-
const counts = providerCounts.get(provider) ?? { cited: 0, total: 0 };
|
|
3930
|
-
const citationRate = counts.total > 0 ? Math.round(counts.cited / counts.total * 100) : 0;
|
|
3931
|
-
return {
|
|
3932
|
-
provider,
|
|
3933
|
-
citedCount: counts.cited,
|
|
3934
|
-
totalCount: counts.total,
|
|
3935
|
-
citationRate
|
|
3936
|
-
};
|
|
3937
|
-
});
|
|
3938
|
-
return { queries: queryList, providers: providerList, matrix, providerRates };
|
|
3939
|
-
}
|
|
3940
|
-
function buildCompetitorLandscape(snapshots, competitorDomains, projectDomains, queryLookup) {
|
|
3941
|
-
let projectCitationCount = 0;
|
|
3942
|
-
const competitorMap = /* @__PURE__ */ new Map();
|
|
3943
|
-
for (const c of competitorDomains) {
|
|
3944
|
-
competitorMap.set(c, { count: 0, queries: /* @__PURE__ */ new Set(), pages: /* @__PURE__ */ new Map() });
|
|
3945
|
-
}
|
|
3946
|
-
for (const snap of snapshots) {
|
|
3947
|
-
const q = queryLookup.byId.get(snap.queryId);
|
|
3948
|
-
const allDomains = [...snap.citedDomains, ...snap.competitorOverlap];
|
|
3949
|
-
if (allDomains.some((d) => citedDomainBelongsToProject(d, projectDomains))) {
|
|
3950
|
-
projectCitationCount++;
|
|
3951
|
-
}
|
|
3952
|
-
for (const competitor of competitorDomains) {
|
|
3953
|
-
if (allDomains.some((d) => citedDomainBelongsToProject(d, [competitor]))) {
|
|
3954
|
-
const entry = competitorMap.get(competitor);
|
|
3955
|
-
entry.count++;
|
|
3956
|
-
if (q) entry.queries.add(q);
|
|
3957
|
-
}
|
|
3958
|
-
const competitorNorm = normalizeDomain(competitor);
|
|
3959
|
-
for (const gs of snap.groundingSources) {
|
|
3960
|
-
const host = normalizeDomain(extractHostFromUri(gs.uri));
|
|
3961
|
-
if (!host) continue;
|
|
3962
|
-
if (host === competitorNorm || host.endsWith(`.${competitorNorm}`)) {
|
|
3963
|
-
const entry = competitorMap.get(competitor);
|
|
3964
|
-
const pageQueries = entry.pages.get(gs.uri) ?? /* @__PURE__ */ new Set();
|
|
3965
|
-
if (q) pageQueries.add(q);
|
|
3966
|
-
entry.pages.set(gs.uri, pageQueries);
|
|
3967
|
-
}
|
|
3968
|
-
}
|
|
3969
|
-
}
|
|
3970
|
-
}
|
|
3971
|
-
const totalCitedSlots = projectCitationCount + [...competitorMap.values()].reduce((sum, v) => sum + v.count, 0);
|
|
3972
|
-
const competitorRows = [...competitorMap.entries()].map(([domain, data]) => {
|
|
3973
|
-
const total = snapshots.length;
|
|
3974
|
-
const ratio = total > 0 ? data.count / total : 0;
|
|
3975
|
-
let pressureLabel = "None";
|
|
3976
|
-
if (data.count > 0) {
|
|
3977
|
-
if (ratio >= 0.5) pressureLabel = "High";
|
|
3978
|
-
else if (ratio >= 0.2) pressureLabel = "Moderate";
|
|
3979
|
-
else pressureLabel = "Low";
|
|
3980
|
-
}
|
|
3981
|
-
const sharePct = totalCitedSlots > 0 ? Math.round(data.count / totalCitedSlots * 100) : 0;
|
|
3982
|
-
const theirCitedPages = [...data.pages.entries()].map(([url, qs]) => ({ url, citedFor: [...qs].sort() })).sort((a, b) => b.citedFor.length - a.citedFor.length);
|
|
3983
|
-
return {
|
|
3984
|
-
domain,
|
|
3985
|
-
citationCount: data.count,
|
|
3986
|
-
totalCount: total,
|
|
3987
|
-
pressureLabel,
|
|
3988
|
-
citedQueries: [...data.queries].sort(),
|
|
3989
|
-
sharePct,
|
|
3990
|
-
theirCitedPages
|
|
3991
|
-
};
|
|
3992
|
-
});
|
|
3993
|
-
competitorRows.sort((a, b) => b.citationCount - a.citationCount);
|
|
3994
|
-
return { projectCitationCount, competitors: competitorRows };
|
|
3995
|
-
}
|
|
3996
|
-
function buildMentionLandscape(snapshots, competitorDomains, projectDisplayName, projectDomains, queryLookup) {
|
|
3997
|
-
let projectMentionCount = 0;
|
|
3998
|
-
let totalAnswerSnapshots = 0;
|
|
3999
|
-
const competitorMap = /* @__PURE__ */ new Map();
|
|
4000
|
-
for (const c of competitorDomains) {
|
|
4001
|
-
competitorMap.set(c, { count: 0, queries: /* @__PURE__ */ new Set() });
|
|
4002
|
-
}
|
|
4003
|
-
for (const snap of snapshots) {
|
|
4004
|
-
const text = snap.answerText;
|
|
4005
|
-
if (!text) continue;
|
|
4006
|
-
totalAnswerSnapshots++;
|
|
4007
|
-
const q = queryLookup.byId.get(snap.queryId);
|
|
4008
|
-
const projectMentioned = snap.answerMentioned ?? determineAnswerMentioned(
|
|
4009
|
-
text,
|
|
4010
|
-
projectDisplayName,
|
|
4011
|
-
projectDomains
|
|
4012
|
-
);
|
|
4013
|
-
if (projectMentioned) projectMentionCount++;
|
|
4014
|
-
for (const competitor of competitorDomains) {
|
|
4015
|
-
const brand = brandLabelFromDomain(competitor);
|
|
4016
|
-
const mentioned = determineAnswerMentioned(text, brand, [competitor]);
|
|
4017
|
-
if (mentioned) {
|
|
4018
|
-
const entry = competitorMap.get(competitor);
|
|
4019
|
-
entry.count++;
|
|
4020
|
-
if (q) entry.queries.add(q);
|
|
4021
|
-
}
|
|
4022
|
-
}
|
|
4023
|
-
}
|
|
4024
|
-
const totalMentionedSlots = projectMentionCount + [...competitorMap.values()].reduce((sum, v) => sum + v.count, 0);
|
|
4025
|
-
const competitorRows = [...competitorMap.entries()].map(([domain, data]) => {
|
|
4026
|
-
const ratio = totalAnswerSnapshots > 0 ? data.count / totalAnswerSnapshots : 0;
|
|
4027
|
-
let pressureLabel = "None";
|
|
4028
|
-
if (data.count > 0) {
|
|
4029
|
-
if (ratio >= 0.5) pressureLabel = "High";
|
|
4030
|
-
else if (ratio >= 0.2) pressureLabel = "Moderate";
|
|
4031
|
-
else pressureLabel = "Low";
|
|
4032
|
-
}
|
|
4033
|
-
const sharePct = totalMentionedSlots > 0 ? Math.round(data.count / totalMentionedSlots * 100) : 0;
|
|
4034
|
-
return {
|
|
4035
|
-
domain,
|
|
4036
|
-
mentionCount: data.count,
|
|
4037
|
-
totalCount: totalAnswerSnapshots,
|
|
4038
|
-
pressureLabel,
|
|
4039
|
-
mentionedQueries: [...data.queries].sort(),
|
|
4040
|
-
sharePct
|
|
4041
|
-
};
|
|
4042
|
-
});
|
|
4043
|
-
competitorRows.sort((a, b) => b.mentionCount - a.mentionCount);
|
|
4044
|
-
return { projectMentionCount, totalAnswerSnapshots, competitors: competitorRows };
|
|
4045
|
-
}
|
|
4046
|
-
function buildAiSourceOrigin(snapshots, projectDomains, competitorDomains) {
|
|
4047
|
-
const categoryCounts = /* @__PURE__ */ new Map();
|
|
4048
|
-
const domainCounts = /* @__PURE__ */ new Map();
|
|
4049
|
-
let totalCitations = 0;
|
|
4050
|
-
for (const snap of snapshots) {
|
|
4051
|
-
for (const raw of snap.citedDomains) {
|
|
4052
|
-
if (citedDomainBelongsToProject(raw, projectDomains)) continue;
|
|
4053
|
-
const { category, label, domain } = categorizeSource(raw);
|
|
4054
|
-
const cat = categoryCounts.get(category) ?? { label, count: 0 };
|
|
4055
|
-
cat.count++;
|
|
4056
|
-
categoryCounts.set(category, cat);
|
|
4057
|
-
domainCounts.set(domain, (domainCounts.get(domain) ?? 0) + 1);
|
|
4058
|
-
totalCitations++;
|
|
4059
|
-
}
|
|
4060
|
-
}
|
|
4061
|
-
const categories = [...categoryCounts.entries()].map(([category, { label, count }]) => ({
|
|
4062
|
-
category,
|
|
4063
|
-
label,
|
|
4064
|
-
count,
|
|
4065
|
-
sharePct: totalCitations > 0 ? Math.round(count / totalCitations * 100) : 0
|
|
4066
|
-
})).sort((a, b) => b.count - a.count);
|
|
4067
|
-
const topDomains = [...domainCounts.entries()].map(([domain, count]) => ({
|
|
4068
|
-
domain,
|
|
4069
|
-
count,
|
|
4070
|
-
isCompetitor: citedDomainBelongsToProject(domain, competitorDomains)
|
|
4071
|
-
})).sort((a, b) => b.count - a.count).slice(0, TOP_SOURCE_DOMAINS_LIMIT);
|
|
4072
|
-
return { categories, topDomains };
|
|
4073
|
-
}
|
|
4074
3897
|
function buildGscSection(db, projectId, projectDisplayName, canonicalDomain, trackedQueries) {
|
|
4075
3898
|
const rows = db.select().from(gscSearchData).where(eq13(gscSearchData.projectId, projectId)).all();
|
|
4076
3899
|
if (rows.length === 0) return null;
|
|
@@ -4319,10 +4142,10 @@ function buildCitationsTrend(db, projectId, queryLookup) {
|
|
|
4319
4142
|
for (const snap of snaps) {
|
|
4320
4143
|
if (!queryLookup.byId.has(snap.queryId)) continue;
|
|
4321
4144
|
considered++;
|
|
4322
|
-
if (snap.citationState ===
|
|
4145
|
+
if (snap.citationState === CitationStates.cited) cited++;
|
|
4323
4146
|
const counts = providerCounts.get(snap.provider) ?? { cited: 0, total: 0 };
|
|
4324
4147
|
counts.total++;
|
|
4325
|
-
if (snap.citationState ===
|
|
4148
|
+
if (snap.citationState === CitationStates.cited) counts.cited++;
|
|
4326
4149
|
providerCounts.set(snap.provider, counts);
|
|
4327
4150
|
}
|
|
4328
4151
|
if (considered === 0) continue;
|
|
@@ -4523,7 +4346,7 @@ function buildProjectReport(db, projectName) {
|
|
|
4523
4346
|
for (const snap of latestSnapshots) {
|
|
4524
4347
|
if (!queryLookup.byId.has(snap.queryId)) continue;
|
|
4525
4348
|
latestConsidered++;
|
|
4526
|
-
if (snap.citationState ===
|
|
4349
|
+
if (snap.citationState === CitationStates.cited) latestCited++;
|
|
4527
4350
|
}
|
|
4528
4351
|
const citationRate = latestConsidered > 0 ? Math.round(latestCited / latestConsidered * 100) : 0;
|
|
4529
4352
|
const trendBaseline = isTrendBaseline(citationsTrend);
|
|
@@ -4785,24 +4608,68 @@ function normalizeDomain2(domain) {
|
|
|
4785
4608
|
}
|
|
4786
4609
|
|
|
4787
4610
|
// ../api-routes/src/composites.ts
|
|
4788
|
-
import { eq as eq15, and as and5, desc as desc7, sql as sql3, like, or as or3 } from "drizzle-orm";
|
|
4611
|
+
import { eq as eq15, and as and5, desc as desc7, sql as sql3, like, or as or3, inArray as inArray6 } from "drizzle-orm";
|
|
4789
4612
|
var TOP_INSIGHT_LIMIT = 5;
|
|
4790
4613
|
var SEARCH_HIT_HARD_LIMIT = 50;
|
|
4791
4614
|
var SEARCH_SNIPPET_RADIUS = 80;
|
|
4792
4615
|
async function compositeRoutes(app) {
|
|
4793
4616
|
app.get("/projects/:name/overview", async (request, reply) => {
|
|
4794
4617
|
const project = resolveProject(app.db, request.params.name);
|
|
4795
|
-
const
|
|
4796
|
-
const
|
|
4797
|
-
const
|
|
4798
|
-
const
|
|
4618
|
+
const filterLocation = (request.query.location ?? "").trim() || null;
|
|
4619
|
+
const sinceIso = parseSinceFilter(request.query.since);
|
|
4620
|
+
const allRunsRaw = app.db.select().from(runs).where(eq15(runs.projectId, project.id)).orderBy(desc7(runs.createdAt)).all();
|
|
4621
|
+
const allRuns = allRunsRaw.filter((r) => runMatchesFilters(r, filterLocation, sinceIso));
|
|
4622
|
+
const totalRuns = allRuns.length;
|
|
4623
|
+
const visibilityRuns = allRuns.filter((r) => r.kind === RunKinds["answer-visibility"]);
|
|
4624
|
+
const completedVisRuns = visibilityRuns.filter(
|
|
4625
|
+
(r) => r.status === RunStatuses.completed || r.status === RunStatuses.partial
|
|
4626
|
+
);
|
|
4627
|
+
const latestVisibilityRun = completedVisRuns[0] ?? null;
|
|
4628
|
+
const previousVisibilityRun = completedVisRuns[1] ?? null;
|
|
4629
|
+
const latestRunRow = allRuns[0] ?? null;
|
|
4799
4630
|
const latestRun = latestRunRow ? { totalRuns, run: summarizeRun(latestRunRow) } : { totalRuns: 0, run: null };
|
|
4800
4631
|
const healthRow = app.db.select().from(healthSnapshots).where(eq15(healthSnapshots.projectId, project.id)).orderBy(desc7(healthSnapshots.createdAt)).limit(1).get();
|
|
4801
4632
|
const health = healthRow ? mapHealthRow2(healthRow) : null;
|
|
4802
4633
|
const insightRows = app.db.select().from(insights).where(eq15(insights.projectId, project.id)).orderBy(desc7(insights.createdAt)).all();
|
|
4803
4634
|
const topInsights = insightRows.filter((row) => !row.dismissed).slice(0, TOP_INSIGHT_LIMIT).map(mapInsightRow2);
|
|
4804
|
-
const
|
|
4805
|
-
const
|
|
4635
|
+
const sparklineRunIds = visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => r.id);
|
|
4636
|
+
const snapshotRunIds = new Set(sparklineRunIds);
|
|
4637
|
+
if (latestVisibilityRun) snapshotRunIds.add(latestVisibilityRun.id);
|
|
4638
|
+
if (previousVisibilityRun) snapshotRunIds.add(previousVisibilityRun.id);
|
|
4639
|
+
const snapshotsByRun = loadSnapshotsByRunIds(app, [...snapshotRunIds]);
|
|
4640
|
+
const latestSnapshots = latestVisibilityRun ? snapshotsByRun.get(latestVisibilityRun.id) ?? [] : [];
|
|
4641
|
+
const previousSnapshots = previousVisibilityRun ? snapshotsByRun.get(previousVisibilityRun.id) ?? [] : [];
|
|
4642
|
+
const { queryCounts, providers } = summarizeFromSnapshots(latestSnapshots);
|
|
4643
|
+
const transitions = summarizeTransitionsFromSnapshots(
|
|
4644
|
+
latestSnapshots,
|
|
4645
|
+
previousSnapshots,
|
|
4646
|
+
previousVisibilityRun?.createdAt ?? null
|
|
4647
|
+
);
|
|
4648
|
+
const competitorRows = app.db.select().from(competitors).where(eq15(competitors.projectId, project.id)).all();
|
|
4649
|
+
const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq15(queries.projectId, project.id)).all();
|
|
4650
|
+
const queryLookup = { byId: new Map(projectQueries.map((q) => [q.id, q.query])) };
|
|
4651
|
+
const configuredApiProviders = parseJsonColumn(project.providers, []).filter((p) => !p.startsWith("cdp:"));
|
|
4652
|
+
const scores = {
|
|
4653
|
+
visibility: buildVisibilityScore(latestSnapshots, { configuredApiProviders }),
|
|
4654
|
+
gapQueries: buildGapQueryScore(latestSnapshots),
|
|
4655
|
+
indexCoverage: buildIndexCoverageScore(app, project.id),
|
|
4656
|
+
competitorPressure: buildCompetitorPressureScore(
|
|
4657
|
+
latestSnapshots,
|
|
4658
|
+
competitorRows.map((c) => c.domain),
|
|
4659
|
+
competitorRows.length
|
|
4660
|
+
),
|
|
4661
|
+
runStatus: buildRunStatusScore(allRuns)
|
|
4662
|
+
};
|
|
4663
|
+
const movementSummary = buildMovementSummary(latestSnapshots, previousSnapshots);
|
|
4664
|
+
const providerScores = buildProviderScores(latestSnapshots);
|
|
4665
|
+
const overviewCompetitors = buildOverviewCompetitors(
|
|
4666
|
+
latestSnapshots,
|
|
4667
|
+
competitorRows.map((c) => ({ id: c.id, domain: c.domain })),
|
|
4668
|
+
queryLookup
|
|
4669
|
+
);
|
|
4670
|
+
const attentionItems = buildAttentionItems(insightRows, allRuns);
|
|
4671
|
+
const sparklineRuns = visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => ({ id: r.id, createdAt: r.createdAt, status: r.status }));
|
|
4672
|
+
const runHistory = buildRunHistory(sparklineRuns, snapshotsByRun);
|
|
4806
4673
|
const result = {
|
|
4807
4674
|
project: formatProject2(project),
|
|
4808
4675
|
latestRun,
|
|
@@ -4810,7 +4677,15 @@ async function compositeRoutes(app) {
|
|
|
4810
4677
|
topInsights,
|
|
4811
4678
|
queryCounts,
|
|
4812
4679
|
providers,
|
|
4813
|
-
transitions
|
|
4680
|
+
transitions,
|
|
4681
|
+
scores,
|
|
4682
|
+
movementSummary,
|
|
4683
|
+
competitors: overviewCompetitors,
|
|
4684
|
+
providerScores,
|
|
4685
|
+
attentionItems,
|
|
4686
|
+
runHistory,
|
|
4687
|
+
dateRangeLabel: "All time",
|
|
4688
|
+
contextLabel: `${project.country} / ${project.language.toUpperCase()}`
|
|
4814
4689
|
};
|
|
4815
4690
|
return reply.send(result);
|
|
4816
4691
|
});
|
|
@@ -4876,6 +4751,21 @@ async function compositeRoutes(app) {
|
|
|
4876
4751
|
return reply.send(response);
|
|
4877
4752
|
});
|
|
4878
4753
|
}
|
|
4754
|
+
function parseSinceFilter(raw) {
|
|
4755
|
+
if (!raw) return null;
|
|
4756
|
+
const trimmed = raw.trim();
|
|
4757
|
+
if (!trimmed) return null;
|
|
4758
|
+
const parsed = Date.parse(trimmed);
|
|
4759
|
+
if (Number.isNaN(parsed)) {
|
|
4760
|
+
throw validationError('"since" must be an ISO 8601 datetime');
|
|
4761
|
+
}
|
|
4762
|
+
return new Date(parsed).toISOString();
|
|
4763
|
+
}
|
|
4764
|
+
function runMatchesFilters(run, location, sinceIso) {
|
|
4765
|
+
if (location !== null && (run.location ?? "") !== location) return false;
|
|
4766
|
+
if (sinceIso !== null && run.createdAt < sinceIso) return false;
|
|
4767
|
+
return true;
|
|
4768
|
+
}
|
|
4879
4769
|
function clampSearchLimit(raw) {
|
|
4880
4770
|
if (!raw) return 25;
|
|
4881
4771
|
const parsed = Number.parseInt(raw, 10);
|
|
@@ -4901,29 +4791,49 @@ function summarizeRun(run) {
|
|
|
4901
4791
|
createdAt: run.createdAt
|
|
4902
4792
|
};
|
|
4903
4793
|
}
|
|
4904
|
-
function
|
|
4794
|
+
function loadSnapshotsByRunIds(app, runIds) {
|
|
4795
|
+
const result = /* @__PURE__ */ new Map();
|
|
4796
|
+
if (runIds.length === 0) return result;
|
|
4797
|
+
const rows = app.db.select({
|
|
4798
|
+
runId: querySnapshots.runId,
|
|
4799
|
+
queryId: querySnapshots.queryId,
|
|
4800
|
+
provider: querySnapshots.provider,
|
|
4801
|
+
model: querySnapshots.model,
|
|
4802
|
+
citationState: querySnapshots.citationState,
|
|
4803
|
+
competitorOverlap: querySnapshots.competitorOverlap,
|
|
4804
|
+
citedDomains: querySnapshots.citedDomains
|
|
4805
|
+
}).from(querySnapshots).where(inArray6(querySnapshots.runId, [...runIds])).all();
|
|
4806
|
+
for (const row of rows) {
|
|
4807
|
+
const list = result.get(row.runId) ?? [];
|
|
4808
|
+
list.push({
|
|
4809
|
+
queryId: row.queryId,
|
|
4810
|
+
provider: row.provider,
|
|
4811
|
+
model: row.model,
|
|
4812
|
+
citationState: row.citationState,
|
|
4813
|
+
competitorOverlap: parseJsonColumn(row.competitorOverlap, []),
|
|
4814
|
+
citedDomains: parseJsonColumn(row.citedDomains, [])
|
|
4815
|
+
});
|
|
4816
|
+
result.set(row.runId, list);
|
|
4817
|
+
}
|
|
4818
|
+
return result;
|
|
4819
|
+
}
|
|
4820
|
+
function summarizeFromSnapshots(snapshots) {
|
|
4905
4821
|
const empty = {
|
|
4906
4822
|
queryCounts: { totalQueries: 0, citedQueries: 0, notCitedQueries: 0, citedRate: 0 },
|
|
4907
4823
|
providers: []
|
|
4908
4824
|
};
|
|
4909
|
-
if (
|
|
4910
|
-
const rows = app.db.select({
|
|
4911
|
-
queryId: querySnapshots.queryId,
|
|
4912
|
-
provider: querySnapshots.provider,
|
|
4913
|
-
citationState: querySnapshots.citationState
|
|
4914
|
-
}).from(querySnapshots).where(eq15(querySnapshots.runId, run.id)).all();
|
|
4915
|
-
if (rows.length === 0) return empty;
|
|
4825
|
+
if (snapshots.length === 0) return empty;
|
|
4916
4826
|
const perQuery = /* @__PURE__ */ new Map();
|
|
4917
4827
|
const perProvider = /* @__PURE__ */ new Map();
|
|
4918
|
-
for (const
|
|
4919
|
-
const cited =
|
|
4920
|
-
if (!perQuery.has(
|
|
4921
|
-
perQuery.set(
|
|
4828
|
+
for (const snap of snapshots) {
|
|
4829
|
+
const cited = snap.citationState === CitationStates.cited;
|
|
4830
|
+
if (!perQuery.has(snap.queryId) || cited) {
|
|
4831
|
+
perQuery.set(snap.queryId, cited);
|
|
4922
4832
|
}
|
|
4923
|
-
const bucket = perProvider.get(
|
|
4833
|
+
const bucket = perProvider.get(snap.provider) ?? { cited: 0, total: 0 };
|
|
4924
4834
|
bucket.total += 1;
|
|
4925
4835
|
if (cited) bucket.cited += 1;
|
|
4926
|
-
perProvider.set(
|
|
4836
|
+
perProvider.set(snap.provider, bucket);
|
|
4927
4837
|
}
|
|
4928
4838
|
const totalQueries = perQuery.size;
|
|
4929
4839
|
let citedQueries = 0;
|
|
@@ -4943,23 +4853,20 @@ function summarizeLatestRun(app, run) {
|
|
|
4943
4853
|
providers
|
|
4944
4854
|
};
|
|
4945
4855
|
}
|
|
4946
|
-
function
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
for (const row of rows) {
|
|
4956
|
-
const cited = row.citationState === "cited";
|
|
4957
|
-
if (!map.has(row.queryId) || cited) map.set(row.queryId, cited);
|
|
4856
|
+
function summarizeTransitionsFromSnapshots(latest, previous, since) {
|
|
4857
|
+
if (!since || previous.length === 0) {
|
|
4858
|
+
return { since: null, gained: 0, lost: 0, emerging: 0 };
|
|
4859
|
+
}
|
|
4860
|
+
const buildMap = (snaps) => {
|
|
4861
|
+
const m = /* @__PURE__ */ new Map();
|
|
4862
|
+
for (const s of snaps) {
|
|
4863
|
+
const cited = s.citationState === CitationStates.cited;
|
|
4864
|
+
if (!m.has(s.queryId) || cited) m.set(s.queryId, cited);
|
|
4958
4865
|
}
|
|
4959
|
-
return
|
|
4866
|
+
return m;
|
|
4960
4867
|
};
|
|
4961
|
-
const latestMap =
|
|
4962
|
-
const previousMap =
|
|
4868
|
+
const latestMap = buildMap(latest);
|
|
4869
|
+
const previousMap = buildMap(previous);
|
|
4963
4870
|
let gained = 0;
|
|
4964
4871
|
let lost = 0;
|
|
4965
4872
|
let emerging = 0;
|
|
@@ -4972,7 +4879,142 @@ function summarizeTransitions(app, latest, previous) {
|
|
|
4972
4879
|
if (latestCited && !previousCited) gained += 1;
|
|
4973
4880
|
else if (!latestCited && previousCited) lost += 1;
|
|
4974
4881
|
}
|
|
4975
|
-
return { since
|
|
4882
|
+
return { since, gained, lost, emerging };
|
|
4883
|
+
}
|
|
4884
|
+
function buildIndexCoverageScore(app, projectId) {
|
|
4885
|
+
const tooltip = "Percentage of inspected URLs currently indexed. Google Search Console is preferred when available, otherwise Bing Webmaster Tools is used.";
|
|
4886
|
+
const empty = {
|
|
4887
|
+
label: "Index Coverage",
|
|
4888
|
+
value: "No data",
|
|
4889
|
+
delta: "Connect GSC or Bing",
|
|
4890
|
+
tone: "neutral",
|
|
4891
|
+
description: "Connect Google Search Console or Bing Webmaster Tools and inspect your sitemap to populate coverage.",
|
|
4892
|
+
tooltip,
|
|
4893
|
+
trend: []
|
|
4894
|
+
};
|
|
4895
|
+
const gscRow = app.db.select().from(gscCoverageSnapshots).where(eq15(gscCoverageSnapshots.projectId, projectId)).orderBy(desc7(gscCoverageSnapshots.date)).limit(1).get();
|
|
4896
|
+
const bingRow = app.db.select().from(bingCoverageSnapshots).where(eq15(bingCoverageSnapshots.projectId, projectId)).orderBy(desc7(bingCoverageSnapshots.date)).limit(1).get();
|
|
4897
|
+
const chosen = pickIndexCoverageRow(gscRow, bingRow);
|
|
4898
|
+
if (!chosen) return empty;
|
|
4899
|
+
const total = chosen.indexed + chosen.notIndexed;
|
|
4900
|
+
if (total === 0) return empty;
|
|
4901
|
+
const deindexed = chosen.provider === "Google" ? countGoogleDeindexedUrls(app, projectId) : 0;
|
|
4902
|
+
const percentage = chosen.indexed / total * 100;
|
|
4903
|
+
const tone = deindexed > 0 ? "negative" : percentage >= 90 ? "positive" : percentage >= 70 ? "caution" : "negative";
|
|
4904
|
+
const notIndexedLabel = chosen.notIndexed === 1 ? "URL is" : "URLs are";
|
|
4905
|
+
const deindexedLabel = deindexed === 1 ? "URL" : "URLs";
|
|
4906
|
+
return {
|
|
4907
|
+
label: "Index Coverage",
|
|
4908
|
+
value: `${Math.round(percentage)}`,
|
|
4909
|
+
delta: `${chosen.provider} \xB7 ${chosen.indexed} of ${total} indexed`,
|
|
4910
|
+
tone,
|
|
4911
|
+
description: deindexed > 0 ? `${deindexed} deindexed ${deindexedLabel} detected in the latest Google Search Console inspection.` : `${chosen.notIndexed} ${notIndexedLabel} not indexed in ${chosen.provider === "Google" ? "Google Search Console" : "Bing Webmaster Tools"}.`,
|
|
4912
|
+
tooltip,
|
|
4913
|
+
trend: [],
|
|
4914
|
+
progress: Math.round(percentage)
|
|
4915
|
+
};
|
|
4916
|
+
}
|
|
4917
|
+
function countGoogleDeindexedUrls(app, projectId) {
|
|
4918
|
+
const rows = app.db.select({
|
|
4919
|
+
url: gscUrlInspections.url,
|
|
4920
|
+
indexingState: gscUrlInspections.indexingState,
|
|
4921
|
+
inspectedAt: gscUrlInspections.inspectedAt
|
|
4922
|
+
}).from(gscUrlInspections).where(eq15(gscUrlInspections.projectId, projectId)).orderBy(desc7(gscUrlInspections.inspectedAt)).all();
|
|
4923
|
+
if (rows.length === 0) return 0;
|
|
4924
|
+
const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
|
|
4925
|
+
const historyByUrl = /* @__PURE__ */ new Map();
|
|
4926
|
+
for (const row of rows) {
|
|
4927
|
+
const key = canonicalUrl(row.url);
|
|
4928
|
+
const list = historyByUrl.get(key);
|
|
4929
|
+
if (list) list.push(row);
|
|
4930
|
+
else historyByUrl.set(key, [row]);
|
|
4931
|
+
}
|
|
4932
|
+
let deindexed = 0;
|
|
4933
|
+
for (const history of historyByUrl.values()) {
|
|
4934
|
+
if (history.length < 2) continue;
|
|
4935
|
+
const latest = history[0];
|
|
4936
|
+
const previous = history[1];
|
|
4937
|
+
if (previous.indexingState === "INDEXING_ALLOWED" && latest.indexingState !== "INDEXING_ALLOWED") {
|
|
4938
|
+
deindexed++;
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
return deindexed;
|
|
4942
|
+
}
|
|
4943
|
+
function pickIndexCoverageRow(gsc, bing) {
|
|
4944
|
+
if (gsc && gsc.indexed + gsc.notIndexed > 0) {
|
|
4945
|
+
return { provider: "Google", indexed: gsc.indexed, notIndexed: gsc.notIndexed };
|
|
4946
|
+
}
|
|
4947
|
+
if (bing && bing.indexed + bing.notIndexed > 0) {
|
|
4948
|
+
return { provider: "Bing", indexed: bing.indexed, notIndexed: bing.notIndexed };
|
|
4949
|
+
}
|
|
4950
|
+
if (gsc) return { provider: "Google", indexed: gsc.indexed, notIndexed: gsc.notIndexed };
|
|
4951
|
+
if (bing) return { provider: "Bing", indexed: bing.indexed, notIndexed: bing.notIndexed };
|
|
4952
|
+
return null;
|
|
4953
|
+
}
|
|
4954
|
+
function buildRunStatusScore(allRuns) {
|
|
4955
|
+
const tooltip = "Current execution state of visibility sweeps. Shows the status of the most recent run and total run count.";
|
|
4956
|
+
if (allRuns.length === 0) {
|
|
4957
|
+
return {
|
|
4958
|
+
label: "Run Status",
|
|
4959
|
+
value: "None",
|
|
4960
|
+
delta: "No runs yet",
|
|
4961
|
+
tone: "neutral",
|
|
4962
|
+
description: "Trigger a visibility sweep to start tracking.",
|
|
4963
|
+
tooltip,
|
|
4964
|
+
trend: []
|
|
4965
|
+
};
|
|
4966
|
+
}
|
|
4967
|
+
const latestVisibility = allRuns.find((r) => r.kind === RunKinds["answer-visibility"]);
|
|
4968
|
+
const latest = latestVisibility ?? allRuns[0];
|
|
4969
|
+
const value = latest.status === RunStatuses.completed ? "Healthy" : latest.status === RunStatuses.running ? "Running" : latest.status === RunStatuses.queued ? "Queued" : latest.status === RunStatuses.partial ? "Partial" : "Failed";
|
|
4970
|
+
const tone = latest.status === RunStatuses.completed ? "positive" : latest.status === RunStatuses.failed ? "negative" : latest.status === RunStatuses.partial ? "caution" : "neutral";
|
|
4971
|
+
const visibilityRunCount = allRuns.filter((r) => r.kind === RunKinds["answer-visibility"]).length;
|
|
4972
|
+
const syncRunCount = allRuns.length - visibilityRunCount;
|
|
4973
|
+
const delta = syncRunCount > 0 ? `${visibilityRunCount} visibility \xB7 ${syncRunCount} sync` : `${visibilityRunCount} visibility run${visibilityRunCount === 1 ? "" : "s"}`;
|
|
4974
|
+
return {
|
|
4975
|
+
label: "Run Status",
|
|
4976
|
+
value,
|
|
4977
|
+
delta,
|
|
4978
|
+
tone,
|
|
4979
|
+
description: `Latest run ${value.toLowerCase()}. ${allRuns.length} total run${allRuns.length === 1 ? "" : "s"}.`,
|
|
4980
|
+
tooltip,
|
|
4981
|
+
trend: []
|
|
4982
|
+
};
|
|
4983
|
+
}
|
|
4984
|
+
var ATTENTION_INSIGHT_LIMIT = 5;
|
|
4985
|
+
function buildAttentionItems(insightRows, allRuns) {
|
|
4986
|
+
const items = [];
|
|
4987
|
+
for (const row of insightRows) {
|
|
4988
|
+
if (row.dismissed) continue;
|
|
4989
|
+
if (row.severity !== "critical" && row.severity !== "high") continue;
|
|
4990
|
+
if (items.length >= ATTENTION_INSIGHT_LIMIT) break;
|
|
4991
|
+
items.push({
|
|
4992
|
+
id: `insight_${row.id}`,
|
|
4993
|
+
tone: row.severity === "critical" ? "negative" : "caution",
|
|
4994
|
+
title: row.title,
|
|
4995
|
+
detail: row.query ? `On query: ${row.query}` : "",
|
|
4996
|
+
actionLabel: row.severity === "critical" ? "Critical" : "High",
|
|
4997
|
+
href: `#insight-${row.id}`
|
|
4998
|
+
});
|
|
4999
|
+
}
|
|
5000
|
+
const sortedRuns = [...allRuns].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
5001
|
+
const latestVisRun = sortedRuns.find((r) => r.kind === RunKinds["answer-visibility"]);
|
|
5002
|
+
const latestSyncRun = sortedRuns.find((r) => r.kind !== RunKinds["answer-visibility"]);
|
|
5003
|
+
if (latestVisRun && latestSyncRun) {
|
|
5004
|
+
const visibilityAge = new Date(latestSyncRun.createdAt).getTime() - new Date(latestVisRun.createdAt).getTime();
|
|
5005
|
+
const ONE_DAY = 24 * 60 * 60 * 1e3;
|
|
5006
|
+
if (visibilityAge > ONE_DAY) {
|
|
5007
|
+
items.push({
|
|
5008
|
+
id: "stale_visibility",
|
|
5009
|
+
tone: "caution",
|
|
5010
|
+
title: "Stale visibility data",
|
|
5011
|
+
detail: `Last visibility sweep was ${latestVisRun.createdAt}; integration syncs have run since.`,
|
|
5012
|
+
actionLabel: "Stale",
|
|
5013
|
+
href: "#runs"
|
|
5014
|
+
});
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
5017
|
+
return items;
|
|
4976
5018
|
}
|
|
4977
5019
|
function mapInsightRow2(r) {
|
|
4978
5020
|
return {
|
|
@@ -10814,8 +10856,8 @@ async function cdpRoutes(app, opts) {
|
|
|
10814
10856
|
let total = 0;
|
|
10815
10857
|
const queryResults = [...byQuery.values()].map(({ query, api, browser }) => {
|
|
10816
10858
|
total++;
|
|
10817
|
-
const apiCited = api?.citationState ===
|
|
10818
|
-
const browserCited = browser?.citationState ===
|
|
10859
|
+
const apiCited = api?.citationState === CitationStates.cited;
|
|
10860
|
+
const browserCited = browser?.citationState === CitationStates.cited;
|
|
10819
10861
|
let agreement;
|
|
10820
10862
|
if (!api && !browser) {
|
|
10821
10863
|
agreement = "no-data";
|
|
@@ -17299,7 +17341,7 @@ import crypto19 from "crypto";
|
|
|
17299
17341
|
import fs7 from "fs";
|
|
17300
17342
|
import path9 from "path";
|
|
17301
17343
|
import os4 from "os";
|
|
17302
|
-
import { and as and12, eq as eq23, inArray as
|
|
17344
|
+
import { and as and12, eq as eq23, inArray as inArray7, sql as sql7 } from "drizzle-orm";
|
|
17303
17345
|
|
|
17304
17346
|
// src/citation-utils.ts
|
|
17305
17347
|
function domainMatches(domain, canonicalDomain) {
|
|
@@ -17556,7 +17598,7 @@ var JobRunner = class {
|
|
|
17556
17598
|
this.registry = registry;
|
|
17557
17599
|
}
|
|
17558
17600
|
recoverStaleRuns() {
|
|
17559
|
-
const stale = this.db.select({ id: runs.id, status: runs.status }).from(runs).where(
|
|
17601
|
+
const stale = this.db.select({ id: runs.id, status: runs.status }).from(runs).where(inArray7(runs.status, ["running", "queued"])).all();
|
|
17560
17602
|
if (stale.length === 0) return;
|
|
17561
17603
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
17562
17604
|
for (const run of stale) {
|