@ainyc/canonry 4.36.0 → 4.40.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.
@@ -5,7 +5,7 @@ import {
5
5
  loadConfig,
6
6
  loadConfigRaw,
7
7
  saveConfigPatch
8
- } from "./chunk-O7S623DL.js";
8
+ } from "./chunk-JS6KRBBZ.js";
9
9
  import {
10
10
  DEFAULT_RUN_HISTORY_LIMIT,
11
11
  IntelligenceService,
@@ -30,11 +30,14 @@ import {
30
30
  buildGapQueryScore,
31
31
  buildInventory,
32
32
  buildMentionCoverage,
33
+ buildMentionGapScore,
33
34
  buildMentionLandscape,
34
35
  buildMovementSummary,
35
36
  buildOverviewCompetitors,
36
37
  buildProviderScores,
38
+ buildProviderTrends,
37
39
  buildRunHistory,
40
+ buildShareOfVoice,
38
41
  buildVisibilityScore,
39
42
  categorizeQueryByIntent,
40
43
  ccReleaseSyncs,
@@ -67,6 +70,7 @@ import {
67
70
  parseJsonColumn,
68
71
  pickGroupRepresentative,
69
72
  projects,
73
+ providerKey,
70
74
  queries,
71
75
  querySnapshots,
72
76
  rawEventSamples,
@@ -74,7 +78,7 @@ import {
74
78
  schedules,
75
79
  trafficSources,
76
80
  usageCounters
77
- } from "./chunk-JQQXMCQ7.js";
81
+ } from "./chunk-EWYUJ2DG.js";
78
82
  import {
79
83
  AGENT_MEMORY_VALUE_MAX_BYTES,
80
84
  AGENT_PROVIDER_IDS,
@@ -926,7 +930,7 @@ function aliasArraysEqual(a, b) {
926
930
 
927
931
  // ../api-routes/src/queries.ts
928
932
  import crypto5 from "crypto";
929
- import { eq as eq4 } from "drizzle-orm";
933
+ import { eq as eq4, inArray, sql as sql3 } from "drizzle-orm";
930
934
  async function queryRoutes(app, opts) {
931
935
  app.get("/projects/:name/queries", async (request, reply) => {
932
936
  const project = resolveProject(app.db, request.params.name);
@@ -962,6 +966,36 @@ async function queryRoutes(app, opts) {
962
966
  const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
963
967
  return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
964
968
  });
969
+ app.post("/projects/:name/queries/replace-preview", async (request, reply) => {
970
+ const project = resolveProject(app.db, request.params.name);
971
+ const body = request.body;
972
+ if (!body || !Array.isArray(body.queries)) {
973
+ throw validationError('Body must contain a "queries" array');
974
+ }
975
+ const currentRows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
976
+ const currentTexts = currentRows.map((r) => r.query);
977
+ const currentSet = new Set(currentTexts);
978
+ const proposedSet = new Set(body.queries);
979
+ const removed = currentTexts.filter((q) => !proposedSet.has(q));
980
+ const added = body.queries.filter((q) => !currentSet.has(q));
981
+ const unchanged = currentTexts.filter((q) => proposedSet.has(q));
982
+ const currentIds = currentRows.map((r) => r.id);
983
+ let snapshotsDetached = 0;
984
+ let affectedQueries = 0;
985
+ if (currentIds.length > 0) {
986
+ const snapshotCount = app.db.select({ n: sql3`count(*)` }).from(querySnapshots).where(inArray(querySnapshots.queryId, currentIds)).get();
987
+ snapshotsDetached = snapshotCount?.n ?? 0;
988
+ const distinctAffected = app.db.select({ n: sql3`count(distinct ${querySnapshots.queryId})` }).from(querySnapshots).where(inArray(querySnapshots.queryId, currentIds)).get();
989
+ affectedQueries = distinctAffected?.n ?? 0;
990
+ }
991
+ return reply.send({
992
+ project: { id: project.id, name: project.name },
993
+ current: currentTexts,
994
+ proposed: body.queries,
995
+ diff: { added, removed, unchanged },
996
+ snapshotImpact: { affectedQueries, snapshotsDetached }
997
+ });
998
+ });
965
999
  app.delete("/projects/:name/queries", async (request, reply) => {
966
1000
  const project = resolveProject(app.db, request.params.name);
967
1001
  const body = request.body;
@@ -1325,7 +1359,7 @@ function parseCompetitorBatch(value) {
1325
1359
 
1326
1360
  // ../api-routes/src/runs.ts
1327
1361
  import crypto8 from "crypto";
1328
- import { and as and2, eq as eq7, asc, desc, or as or2, sql as sql3 } from "drizzle-orm";
1362
+ import { and as and2, eq as eq7, asc, desc, or as or2, sql as sql4 } from "drizzle-orm";
1329
1363
 
1330
1364
  // ../api-routes/src/run-queue.ts
1331
1365
  import crypto7 from "crypto";
@@ -1497,7 +1531,7 @@ async function runRoutes(app, opts) {
1497
1531
  });
1498
1532
  app.get("/projects/:name/runs/latest", async (request, reply) => {
1499
1533
  const project = resolveProject(app.db, request.params.name);
1500
- const countRow = app.db.select({ count: sql3`count(*)` }).from(runs).where(eq7(runs.projectId, project.id)).get();
1534
+ const countRow = app.db.select({ count: sql4`count(*)` }).from(runs).where(eq7(runs.projectId, project.id)).get();
1501
1535
  const totalRuns = countRow?.count ?? 0;
1502
1536
  const latestRun = app.db.select().from(runs).where(eq7(runs.projectId, project.id)).orderBy(desc(runs.createdAt), desc(runs.id)).limit(1).get();
1503
1537
  if (!latestRun) {
@@ -2226,7 +2260,7 @@ function normalizeCompetitorList2(domains) {
2226
2260
  }
2227
2261
 
2228
2262
  // ../api-routes/src/history.ts
2229
- import { eq as eq9, desc as desc2, inArray } from "drizzle-orm";
2263
+ import { eq as eq9, desc as desc2, inArray as inArray2 } from "drizzle-orm";
2230
2264
 
2231
2265
  // ../api-routes/src/notification-redaction.ts
2232
2266
  var REDACTED_URL = {
@@ -2302,7 +2336,7 @@ async function historyRoutes(app) {
2302
2336
  recommendedCompetitors: querySnapshots.recommendedCompetitors,
2303
2337
  location: querySnapshots.location,
2304
2338
  createdAt: querySnapshots.createdAt
2305
- }).from(querySnapshots).leftJoin(queries, eq9(querySnapshots.queryId, queries.id)).where(inArray(querySnapshots.runId, projectRuns.map((r) => r.id))).orderBy(desc2(querySnapshots.createdAt)).all();
2339
+ }).from(querySnapshots).leftJoin(queries, eq9(querySnapshots.queryId, queries.id)).where(inArray2(querySnapshots.runId, projectRuns.map((r) => r.id))).orderBy(desc2(querySnapshots.createdAt)).all();
2306
2340
  const locationFilter = request.query.location;
2307
2341
  const filtered = locationFilter !== void 0 ? allSnapshots.filter((s) => s.location === (locationFilter || null)) : allSnapshots;
2308
2342
  const total = filtered.length;
@@ -2337,7 +2371,7 @@ async function historyRoutes(app) {
2337
2371
  return reply.send([]);
2338
2372
  }
2339
2373
  const runIds = new Set(projectRuns.map((r) => r.id));
2340
- const rawSnapshots = app.db.select().from(querySnapshots).where(inArray(querySnapshots.runId, [...runIds])).all();
2374
+ const rawSnapshots = app.db.select().from(querySnapshots).where(inArray2(querySnapshots.runId, [...runIds])).all();
2341
2375
  const timelineLocationFilter = request.query.location;
2342
2376
  const filteredSnapshots = timelineLocationFilter !== void 0 ? rawSnapshots.filter((s) => s.location === (timelineLocationFilter || null)) : rawSnapshots;
2343
2377
  const allSnapshots = filteredSnapshots.map((snapshot) => ({
@@ -2517,7 +2551,7 @@ function formatAuditEntry(row) {
2517
2551
  }
2518
2552
 
2519
2553
  // ../api-routes/src/analytics.ts
2520
- import { eq as eq10, desc as desc3, inArray as inArray2 } from "drizzle-orm";
2554
+ import { eq as eq10, desc as desc3, inArray as inArray3 } from "drizzle-orm";
2521
2555
  async function analyticsRoutes(app) {
2522
2556
  app.get("/projects/:name/analytics/metrics", async (request, reply) => {
2523
2557
  const project = resolveProject(app.db, request.params.name);
@@ -2544,7 +2578,7 @@ async function analyticsRoutes(app) {
2544
2578
  answerMentioned: querySnapshots.answerMentioned,
2545
2579
  answerText: querySnapshots.answerText,
2546
2580
  createdAt: querySnapshots.createdAt
2547
- }).from(querySnapshots).where(inArray2(querySnapshots.runId, runIds)).all());
2581
+ }).from(querySnapshots).where(inArray3(querySnapshots.runId, runIds)).all());
2548
2582
  const allSnapshots = rawSnapshots.map((s) => ({
2549
2583
  ...s,
2550
2584
  resolvedMentioned: resolveSnapshotAnswerMentioned(s, project)
@@ -2589,7 +2623,7 @@ async function analyticsRoutes(app) {
2589
2623
  citationState: querySnapshots.citationState,
2590
2624
  answerMentioned: querySnapshots.answerMentioned,
2591
2625
  answerText: querySnapshots.answerText
2592
- }).from(querySnapshots).where(inArray2(querySnapshots.runId, windowRunIds)).all());
2626
+ }).from(querySnapshots).where(inArray3(querySnapshots.runId, windowRunIds)).all());
2593
2627
  for (const s of allWindowSnaps) {
2594
2628
  const timePoint = runIdToCreatedAt.get(s.runId) ?? s.runId;
2595
2629
  let entry = consistencyMap.get(s.queryId);
@@ -2610,7 +2644,7 @@ async function analyticsRoutes(app) {
2610
2644
  answerMentioned: querySnapshots.answerMentioned,
2611
2645
  answerText: querySnapshots.answerText,
2612
2646
  competitorOverlap: querySnapshots.competitorOverlap
2613
- }).from(querySnapshots).leftJoin(queries, eq10(querySnapshots.queryId, queries.id)).where(inArray2(querySnapshots.runId, latestGroupRunIds)).all());
2647
+ }).from(querySnapshots).leftJoin(queries, eq10(querySnapshots.queryId, queries.id)).where(inArray3(querySnapshots.runId, latestGroupRunIds)).all());
2614
2648
  const snapshots = rawSnapshots.map((s) => ({
2615
2649
  ...s,
2616
2650
  resolvedMentioned: resolveSnapshotAnswerMentioned(s, project)
@@ -2705,7 +2739,7 @@ async function analyticsRoutes(app) {
2705
2739
  queryId: querySnapshots.queryId,
2706
2740
  query: queries.query,
2707
2741
  rawResponse: querySnapshots.rawResponse
2708
- }).from(querySnapshots).leftJoin(queries, eq10(querySnapshots.queryId, queries.id)).where(inArray2(querySnapshots.runId, windowRunIds)).all();
2742
+ }).from(querySnapshots).leftJoin(queries, eq10(querySnapshots.queryId, queries.id)).where(inArray3(querySnapshots.runId, windowRunIds)).all();
2709
2743
  const overallCounts = /* @__PURE__ */ new Map();
2710
2744
  const byQuery = {};
2711
2745
  for (const snap of snapshots) {
@@ -2864,7 +2898,7 @@ function buildCategoryCounts(counts) {
2864
2898
  }
2865
2899
 
2866
2900
  // ../api-routes/src/intelligence.ts
2867
- import { eq as eq11, desc as desc4, and as and4, inArray as inArray3 } from "drizzle-orm";
2901
+ import { eq as eq11, desc as desc4, and as and4, inArray as inArray4 } from "drizzle-orm";
2868
2902
  function emptyHealthSnapshot(projectId) {
2869
2903
  return {
2870
2904
  id: `no-data:${projectId}`,
@@ -2980,14 +3014,14 @@ async function intelligenceRoutes(app) {
2980
3014
  const projectVisRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and4(
2981
3015
  eq11(runs.projectId, project.id),
2982
3016
  eq11(runs.kind, RunKinds["answer-visibility"]),
2983
- inArray3(runs.status, [RunStatuses.completed, RunStatuses.partial])
3017
+ inArray4(runs.status, [RunStatuses.completed, RunStatuses.partial])
2984
3018
  )).orderBy(desc4(runs.createdAt), desc4(runs.id)).all();
2985
3019
  const latestGroup = groupRunsByCreatedAt(projectVisRuns)[0] ?? [];
2986
3020
  const latestGroupRunIds = latestGroup.map((r) => r.id);
2987
3021
  if (latestGroupRunIds.length > 0) {
2988
3022
  const groupRows = app.db.select().from(healthSnapshots).where(and4(
2989
3023
  eq11(healthSnapshots.projectId, project.id),
2990
- inArray3(healthSnapshots.runId, latestGroupRunIds)
3024
+ inArray4(healthSnapshots.runId, latestGroupRunIds)
2991
3025
  )).all();
2992
3026
  if (groupRows.length > 0) {
2993
3027
  return reply.send(aggregateHealthSnapshots(project.id, groupRows));
@@ -3009,7 +3043,7 @@ async function intelligenceRoutes(app) {
3009
3043
  }
3010
3044
 
3011
3045
  // ../api-routes/src/report.ts
3012
- import { and as and6, desc as desc6, eq as eq13, gte, inArray as inArray5, lt, lte, ne, or as or3, sql as sql4 } from "drizzle-orm";
3046
+ import { and as and6, desc as desc6, eq as eq13, gte, inArray as inArray6, lt, lte, ne, or as or3, sql as sql5 } from "drizzle-orm";
3013
3047
 
3014
3048
  // ../api-routes/src/report-renderer.ts
3015
3049
  var COLORS = {
@@ -5245,7 +5279,7 @@ function renderReportHtml(report, opts = {}) {
5245
5279
  }
5246
5280
 
5247
5281
  // ../api-routes/src/content-data.ts
5248
- import { and as and5, eq as eq12, desc as desc5, inArray as inArray4 } from "drizzle-orm";
5282
+ import { and as and5, eq as eq12, desc as desc5, inArray as inArray5 } from "drizzle-orm";
5249
5283
  var RECENT_RUNS_WINDOW = 5;
5250
5284
  function loadOrchestratorInput(db, project, locationFilter = void 0) {
5251
5285
  const projectId = project.id;
@@ -5375,7 +5409,7 @@ function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter)
5375
5409
  // Queued/running/failed/cancelled runs may have partial or no
5376
5410
  // snapshots; including them risks pointing latestRunId at a run with
5377
5411
  // no usable evidence.
5378
- inArray4(runs.status, [RunStatuses.completed, RunStatuses.partial])
5412
+ inArray5(runs.status, [RunStatuses.completed, RunStatuses.partial])
5379
5413
  )
5380
5414
  ).orderBy(desc5(runs.createdAt)).all();
5381
5415
  const filtered = locationFilter === void 0 ? rows : rows.filter((r) => (r.location ?? null) === locationFilter);
@@ -5417,7 +5451,7 @@ function buildCandidateQueries(opts) {
5417
5451
  const queryRows = opts.db.select({ id: queries.id, text: queries.query }).from(queries).where(eq12(queries.projectId, opts.projectId)).all();
5418
5452
  const queryIdByText = new Map(queryRows.map((r) => [r.text, r.id]));
5419
5453
  const candidateQueryIds = opts.candidateQueryStrings.map((q) => queryIdByText.get(q)).filter((id) => Boolean(id));
5420
- const snapshotRows = filterTrackedSnapshots(opts.db.select().from(querySnapshots).where(inArray4(querySnapshots.runId, opts.recentRunIds)).all()).filter((r) => candidateQueryIds.includes(r.queryId));
5454
+ const snapshotRows = filterTrackedSnapshots(opts.db.select().from(querySnapshots).where(inArray5(querySnapshots.runId, opts.recentRunIds)).all()).filter((r) => candidateQueryIds.includes(r.queryId));
5421
5455
  const snapshotsByQuery = /* @__PURE__ */ new Map();
5422
5456
  for (const row of snapshotRows) {
5423
5457
  const list = snapshotsByQuery.get(row.queryId) ?? [];
@@ -5635,7 +5669,7 @@ function loadSnapshotsForRun(db, runId) {
5635
5669
  }
5636
5670
  function loadSnapshotsForRunIds(db, runIds) {
5637
5671
  if (runIds.length === 0) return [];
5638
- const rows = db.select().from(querySnapshots).where(inArray5(querySnapshots.runId, [...runIds])).all();
5672
+ const rows = db.select().from(querySnapshots).where(inArray6(querySnapshots.runId, [...runIds])).all();
5639
5673
  return rows.filter((r) => r.queryId !== null).map((r) => ({
5640
5674
  id: r.id,
5641
5675
  runId: r.runId,
@@ -5885,7 +5919,7 @@ function buildAiReferrals(db, projectId) {
5885
5919
  return { totalSessions: total, totalUsers, bySource, trend, topLandingPages };
5886
5920
  }
5887
5921
  function nonSubresourceReferralPathCondition() {
5888
- return sql4`
5922
+ return sql5`
5889
5923
  LOWER(${aiReferralEventsHourly.landingPathNormalized}) NOT LIKE '/_next/static/%'
5890
5924
  AND LOWER(${aiReferralEventsHourly.landingPathNormalized}) NOT LIKE '/assets/%'
5891
5925
  AND LOWER(${aiReferralEventsHourly.landingPathNormalized}) NOT LIKE '/static/%'
@@ -5924,7 +5958,7 @@ function buildServerActivity(db, projectId) {
5924
5958
  const priorStart = new Date(priorStartMs).toISOString();
5925
5959
  const trendStart = new Date(trendStartMs).toISOString();
5926
5960
  const sumVerifiedCrawlers = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
5927
- db.select({ total: sql4`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
5961
+ db.select({ total: sql5`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
5928
5962
  and6(
5929
5963
  eq13(crawlerEventsHourly.projectId, projectId),
5930
5964
  eq13(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
@@ -5934,7 +5968,7 @@ function buildServerActivity(db, projectId) {
5934
5968
  ).get()?.total ?? 0
5935
5969
  );
5936
5970
  const sumUnverifiedCrawlers = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
5937
- db.select({ total: sql4`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
5971
+ db.select({ total: sql5`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
5938
5972
  and6(
5939
5973
  eq13(crawlerEventsHourly.projectId, projectId),
5940
5974
  ne(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
@@ -5944,7 +5978,7 @@ function buildServerActivity(db, projectId) {
5944
5978
  ).get()?.total ?? 0
5945
5979
  );
5946
5980
  const sumReferrals = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
5947
- db.select({ total: sql4`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
5981
+ db.select({ total: sql5`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
5948
5982
  and6(
5949
5983
  eq13(aiReferralEventsHourly.projectId, projectId),
5950
5984
  nonSubresourceReferralPathCondition(),
@@ -5962,7 +5996,7 @@ function buildServerActivity(db, projectId) {
5962
5996
  const crawlerByOperatorRows = db.select({
5963
5997
  operator: crawlerEventsHourly.operator,
5964
5998
  verificationStatus: crawlerEventsHourly.verificationStatus,
5965
- hits: sql4`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
5999
+ hits: sql5`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
5966
6000
  }).from(crawlerEventsHourly).where(
5967
6001
  and6(
5968
6002
  eq13(crawlerEventsHourly.projectId, projectId),
@@ -5972,7 +6006,7 @@ function buildServerActivity(db, projectId) {
5972
6006
  ).groupBy(crawlerEventsHourly.operator, crawlerEventsHourly.verificationStatus).all();
5973
6007
  const crawlerByOperatorPriorRows = db.select({
5974
6008
  operator: crawlerEventsHourly.operator,
5975
- hits: sql4`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
6009
+ hits: sql5`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
5976
6010
  }).from(crawlerEventsHourly).where(
5977
6011
  and6(
5978
6012
  eq13(crawlerEventsHourly.projectId, projectId),
@@ -5983,7 +6017,7 @@ function buildServerActivity(db, projectId) {
5983
6017
  ).groupBy(crawlerEventsHourly.operator).all();
5984
6018
  const referralByOperatorRows = db.select({
5985
6019
  operator: aiReferralEventsHourly.operator,
5986
- hits: sql4`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
6020
+ hits: sql5`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
5987
6021
  }).from(aiReferralEventsHourly).where(
5988
6022
  and6(
5989
6023
  eq13(aiReferralEventsHourly.projectId, projectId),
@@ -6023,8 +6057,8 @@ function buildServerActivity(db, projectId) {
6023
6057
  );
6024
6058
  const topPathsRows = db.select({
6025
6059
  path: crawlerEventsHourly.pathNormalized,
6026
- hits: sql4`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
6027
- operators: sql4`COUNT(DISTINCT ${crawlerEventsHourly.operator})`
6060
+ hits: sql5`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
6061
+ operators: sql5`COUNT(DISTINCT ${crawlerEventsHourly.operator})`
6028
6062
  }).from(crawlerEventsHourly).where(
6029
6063
  and6(
6030
6064
  eq13(crawlerEventsHourly.projectId, projectId),
@@ -6032,7 +6066,7 @@ function buildServerActivity(db, projectId) {
6032
6066
  gte(crawlerEventsHourly.tsHour, headlineStart),
6033
6067
  lte(crawlerEventsHourly.tsHour, headlineEnd)
6034
6068
  )
6035
- ).groupBy(crawlerEventsHourly.pathNormalized).orderBy(desc6(sql4`SUM(${crawlerEventsHourly.hits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
6069
+ ).groupBy(crawlerEventsHourly.pathNormalized).orderBy(desc6(sql5`SUM(${crawlerEventsHourly.hits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
6036
6070
  const topCrawledPaths = topPathsRows.map((r) => ({
6037
6071
  path: r.path,
6038
6072
  verifiedHits: Number(r.hits),
@@ -6040,8 +6074,8 @@ function buildServerActivity(db, projectId) {
6040
6074
  }));
6041
6075
  const referralProductsRows = db.select({
6042
6076
  product: aiReferralEventsHourly.product,
6043
- arrivals: sql4`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
6044
- landingPaths: sql4`COUNT(DISTINCT ${aiReferralEventsHourly.landingPathNormalized})`
6077
+ arrivals: sql5`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
6078
+ landingPaths: sql5`COUNT(DISTINCT ${aiReferralEventsHourly.landingPathNormalized})`
6045
6079
  }).from(aiReferralEventsHourly).where(
6046
6080
  and6(
6047
6081
  eq13(aiReferralEventsHourly.projectId, projectId),
@@ -6049,7 +6083,7 @@ function buildServerActivity(db, projectId) {
6049
6083
  gte(aiReferralEventsHourly.tsHour, headlineStart),
6050
6084
  lte(aiReferralEventsHourly.tsHour, headlineEnd)
6051
6085
  )
6052
- ).groupBy(aiReferralEventsHourly.product).orderBy(desc6(sql4`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).all();
6086
+ ).groupBy(aiReferralEventsHourly.product).orderBy(desc6(sql5`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).all();
6053
6087
  const referralProducts = referralProductsRows.map((r) => ({
6054
6088
  product: r.product,
6055
6089
  arrivals: Number(r.arrivals),
@@ -6057,8 +6091,8 @@ function buildServerActivity(db, projectId) {
6057
6091
  }));
6058
6092
  const topReferralRows = db.select({
6059
6093
  path: aiReferralEventsHourly.landingPathNormalized,
6060
- arrivals: sql4`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
6061
- products: sql4`COUNT(DISTINCT ${aiReferralEventsHourly.product})`
6094
+ arrivals: sql5`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
6095
+ products: sql5`COUNT(DISTINCT ${aiReferralEventsHourly.product})`
6062
6096
  }).from(aiReferralEventsHourly).where(
6063
6097
  and6(
6064
6098
  eq13(aiReferralEventsHourly.projectId, projectId),
@@ -6066,15 +6100,15 @@ function buildServerActivity(db, projectId) {
6066
6100
  gte(aiReferralEventsHourly.tsHour, headlineStart),
6067
6101
  lte(aiReferralEventsHourly.tsHour, headlineEnd)
6068
6102
  )
6069
- ).groupBy(aiReferralEventsHourly.landingPathNormalized).orderBy(desc6(sql4`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
6103
+ ).groupBy(aiReferralEventsHourly.landingPathNormalized).orderBy(desc6(sql5`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
6070
6104
  const topReferralLandingPaths = topReferralRows.map((r) => ({
6071
6105
  path: r.path,
6072
6106
  arrivals: Number(r.arrivals),
6073
6107
  distinctProducts: Number(r.products)
6074
6108
  }));
6075
6109
  const crawlerTrendRows = db.select({
6076
- date: sql4`SUBSTR(${crawlerEventsHourly.tsHour}, 1, 10)`,
6077
- hits: sql4`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
6110
+ date: sql5`SUBSTR(${crawlerEventsHourly.tsHour}, 1, 10)`,
6111
+ hits: sql5`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
6078
6112
  }).from(crawlerEventsHourly).where(
6079
6113
  and6(
6080
6114
  eq13(crawlerEventsHourly.projectId, projectId),
@@ -6082,10 +6116,10 @@ function buildServerActivity(db, projectId) {
6082
6116
  gte(crawlerEventsHourly.tsHour, trendStart),
6083
6117
  lte(crawlerEventsHourly.tsHour, headlineEnd)
6084
6118
  )
6085
- ).groupBy(sql4`SUBSTR(${crawlerEventsHourly.tsHour}, 1, 10)`).all();
6119
+ ).groupBy(sql5`SUBSTR(${crawlerEventsHourly.tsHour}, 1, 10)`).all();
6086
6120
  const referralTrendRows = db.select({
6087
- date: sql4`SUBSTR(${aiReferralEventsHourly.tsHour}, 1, 10)`,
6088
- hits: sql4`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
6121
+ date: sql5`SUBSTR(${aiReferralEventsHourly.tsHour}, 1, 10)`,
6122
+ hits: sql5`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
6089
6123
  }).from(aiReferralEventsHourly).where(
6090
6124
  and6(
6091
6125
  eq13(aiReferralEventsHourly.projectId, projectId),
@@ -6093,7 +6127,7 @@ function buildServerActivity(db, projectId) {
6093
6127
  gte(aiReferralEventsHourly.tsHour, trendStart),
6094
6128
  lte(aiReferralEventsHourly.tsHour, headlineEnd)
6095
6129
  )
6096
- ).groupBy(sql4`SUBSTR(${aiReferralEventsHourly.tsHour}, 1, 10)`).all();
6130
+ ).groupBy(sql5`SUBSTR(${aiReferralEventsHourly.tsHour}, 1, 10)`).all();
6097
6131
  const dailyTrendMap = /* @__PURE__ */ new Map();
6098
6132
  for (const r of crawlerTrendRows) {
6099
6133
  const e = dailyTrendMap.get(r.date) ?? { verifiedCrawlerHits: 0, referralArrivals: 0 };
@@ -6215,7 +6249,7 @@ function buildInsightList(db, projectId, locationFilter) {
6215
6249
  )
6216
6250
  ).orderBy(desc6(runs.createdAt)).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter).slice(0, INSIGHT_LOOKBACK_RUNS).map((r) => r.id);
6217
6251
  if (recentRunIds.length === 0) return [];
6218
- const rows = db.select().from(insights).where(and6(eq13(insights.projectId, projectId), inArray5(insights.runId, recentRunIds))).orderBy(desc6(insights.createdAt)).all();
6252
+ const rows = db.select().from(insights).where(and6(eq13(insights.projectId, projectId), inArray6(insights.runId, recentRunIds))).orderBy(desc6(insights.createdAt)).all();
6219
6253
  const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
6220
6254
  const flat = rows.filter((r) => !r.dismissed).map((r) => {
6221
6255
  const recommendation = parseJsonColumn(r.recommendation, null);
@@ -6988,7 +7022,7 @@ async function reportRoutes(app) {
6988
7022
  }
6989
7023
 
6990
7024
  // ../api-routes/src/citations.ts
6991
- import { eq as eq14, inArray as inArray6 } from "drizzle-orm";
7025
+ import { eq as eq14, inArray as inArray7 } from "drizzle-orm";
6992
7026
  async function citationRoutes(app) {
6993
7027
  app.get("/projects/:name/citations/visibility", async (request, reply) => {
6994
7028
  const project = resolveProject(app.db, request.params.name);
@@ -7012,7 +7046,7 @@ async function citationRoutes(app) {
7012
7046
  competitorOverlap: querySnapshots.competitorOverlap,
7013
7047
  answerMentioned: querySnapshots.answerMentioned,
7014
7048
  createdAt: querySnapshots.createdAt
7015
- }).from(querySnapshots).where(inArray6(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
7049
+ }).from(querySnapshots).where(inArray7(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
7016
7050
  if (rawSnapshots.length === 0) {
7017
7051
  return reply.send(emptyCitationVisibility("no-runs-yet"));
7018
7052
  }
@@ -7153,7 +7187,7 @@ function normalizeDomain2(domain) {
7153
7187
  }
7154
7188
 
7155
7189
  // ../api-routes/src/composites.ts
7156
- import { eq as eq15, and as and7, desc as desc7, sql as sql5, like, or as or4, inArray as inArray7 } from "drizzle-orm";
7190
+ import { eq as eq15, and as and7, desc as desc7, sql as sql6, like, or as or4, inArray as inArray8 } from "drizzle-orm";
7157
7191
  var TOP_INSIGHT_LIMIT = 5;
7158
7192
  var SEARCH_HIT_HARD_LIMIT = 50;
7159
7193
  var SEARCH_SNIPPET_RADIUS = 80;
@@ -7205,10 +7239,19 @@ async function compositeRoutes(app) {
7205
7239
  const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq15(queries.projectId, project.id)).all();
7206
7240
  const queryLookup = { byId: new Map(projectQueries.map((q) => [q.id, q.query])) };
7207
7241
  const configuredApiProviders = parseJsonColumn(project.providers, []).filter((p) => !p.startsWith("cdp:"));
7242
+ const projectDomains = effectiveDomains({
7243
+ canonicalDomain: project.canonicalDomain,
7244
+ ownedDomains: parseJsonColumn(project.ownedDomains, [])
7245
+ });
7208
7246
  const scores = {
7209
7247
  mention: buildMentionCoverage(latestSnapshots, { configuredApiProviders }),
7210
7248
  visibility: buildVisibilityScore(latestSnapshots, { configuredApiProviders }),
7249
+ shareOfVoice: buildShareOfVoice(latestSnapshots, {
7250
+ projectDomains,
7251
+ competitorDomains: competitorRows.map((c) => c.domain)
7252
+ }),
7211
7253
  gapQueries: buildGapQueryScore(latestSnapshots),
7254
+ mentionGaps: buildMentionGapScore(latestSnapshots),
7212
7255
  indexCoverage: buildIndexCoverageScore(app, project.id),
7213
7256
  competitorPressure: buildCompetitorPressureScore(
7214
7257
  latestSnapshots,
@@ -7217,8 +7260,19 @@ async function compositeRoutes(app) {
7217
7260
  ),
7218
7261
  runStatus: buildRunStatusScore(allRuns)
7219
7262
  };
7220
- const movementSummary = buildMovementSummary(latestSnapshots, previousSnapshots);
7221
- const providerScores = buildProviderScores(latestSnapshots);
7263
+ const movementSummary = buildMovementSummary(latestSnapshots, previousSnapshots, {
7264
+ queryLookup: queryLookup.byId
7265
+ });
7266
+ const providerScoresBase = buildProviderScores(latestSnapshots);
7267
+ const providerTrends = buildProviderTrends(
7268
+ visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => ({ id: r.id, createdAt: r.createdAt })),
7269
+ snapshotsByRun,
7270
+ DEFAULT_RUN_HISTORY_LIMIT
7271
+ );
7272
+ const providerScores = providerScoresBase.map((score) => {
7273
+ const trend = providerTrends.get(providerKey(score.provider, score.model)) ?? [];
7274
+ return trend.length > 1 ? { ...score, trend: trend.map((p) => p.rate) } : score;
7275
+ });
7222
7276
  const overviewCompetitors = buildOverviewCompetitors(
7223
7277
  latestSnapshots,
7224
7278
  competitorRows.map((c) => ({ id: c.id, domain: c.domain })),
@@ -7271,9 +7325,9 @@ async function compositeRoutes(app) {
7271
7325
  and7(
7272
7326
  eq15(queries.projectId, project.id),
7273
7327
  or4(
7274
- sql5`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
7275
- sql5`${querySnapshots.citedDomains} LIKE ${pattern} ESCAPE '\\'`,
7276
- sql5`${querySnapshots.rawResponse} LIKE ${pattern} ESCAPE '\\'`,
7328
+ sql6`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
7329
+ sql6`${querySnapshots.citedDomains} LIKE ${pattern} ESCAPE '\\'`,
7330
+ sql6`${querySnapshots.rawResponse} LIKE ${pattern} ESCAPE '\\'`,
7277
7331
  like(queries.query, pattern)
7278
7332
  )
7279
7333
  )
@@ -7284,8 +7338,8 @@ async function compositeRoutes(app) {
7284
7338
  or4(
7285
7339
  like(insights.title, pattern),
7286
7340
  like(insights.query, pattern),
7287
- sql5`${insights.recommendation} LIKE ${pattern} ESCAPE '\\'`,
7288
- sql5`${insights.cause} LIKE ${pattern} ESCAPE '\\'`
7341
+ sql6`${insights.recommendation} LIKE ${pattern} ESCAPE '\\'`,
7342
+ sql6`${insights.cause} LIKE ${pattern} ESCAPE '\\'`
7289
7343
  )
7290
7344
  )
7291
7345
  ).orderBy(desc7(insights.createdAt)).limit(limit + 1).all();
@@ -7360,7 +7414,7 @@ function loadSnapshotsByRunIds(app, runIds) {
7360
7414
  answerMentioned: querySnapshots.answerMentioned,
7361
7415
  competitorOverlap: querySnapshots.competitorOverlap,
7362
7416
  citedDomains: querySnapshots.citedDomains
7363
- }).from(querySnapshots).where(inArray7(querySnapshots.runId, [...runIds])).all());
7417
+ }).from(querySnapshots).where(inArray8(querySnapshots.runId, [...runIds])).all());
7364
7418
  for (const row of rows) {
7365
7419
  const list = result.get(row.runId) ?? [];
7366
7420
  list.push({
@@ -8139,6 +8193,32 @@ var routeCatalog = [
8139
8193
  200: { description: "Queries appended." }
8140
8194
  }
8141
8195
  },
8196
+ {
8197
+ method: "post",
8198
+ path: "/api/v1/projects/{name}/queries/replace-preview",
8199
+ summary: "Preview the impact of replacing tracked queries",
8200
+ description: "Read-only impact summary backing `canonry query replace --dry-run`. Returns current vs proposed query sets, the added/removed/unchanged diff, and the count of snapshots that would detach (queryId \u2192 NULL; queryText preserved).",
8201
+ tags: ["queries"],
8202
+ parameters: [nameParameter],
8203
+ requestBody: {
8204
+ required: true,
8205
+ content: {
8206
+ "application/json": {
8207
+ schema: {
8208
+ type: "object",
8209
+ required: ["queries"],
8210
+ properties: {
8211
+ queries: stringArraySchema
8212
+ }
8213
+ }
8214
+ }
8215
+ }
8216
+ },
8217
+ responses: {
8218
+ 200: { description: "Replace preview returned." },
8219
+ 404: { description: "Project not found." }
8220
+ }
8221
+ },
8142
8222
  {
8143
8223
  method: "post",
8144
8224
  path: "/api/v1/projects/{name}/queries/generate",
@@ -11418,7 +11498,7 @@ function formatNotification(row) {
11418
11498
 
11419
11499
  // ../api-routes/src/google.ts
11420
11500
  import crypto14 from "crypto";
11421
- import { eq as eq18, and as and9, desc as desc8, sql as sql6 } from "drizzle-orm";
11501
+ import { eq as eq18, and as and9, desc as desc8, sql as sql7 } from "drizzle-orm";
11422
11502
 
11423
11503
  // ../integration-google/src/constants.ts
11424
11504
  var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
@@ -12644,11 +12724,11 @@ async function googleRoutes(app, opts) {
12644
12724
  const { startDate, endDate, query, page, limit, offset } = request.query;
12645
12725
  const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
12646
12726
  const conditions = [eq18(gscSearchData.projectId, project.id)];
12647
- if (startDate) conditions.push(sql6`${gscSearchData.date} >= ${startDate}`);
12648
- else if (cutoffDate) conditions.push(sql6`${gscSearchData.date} >= ${cutoffDate}`);
12649
- if (endDate) conditions.push(sql6`${gscSearchData.date} <= ${endDate}`);
12650
- if (query) conditions.push(sql6`${gscSearchData.query} LIKE ${"%" + query + "%"}`);
12651
- if (page) conditions.push(sql6`${gscSearchData.page} LIKE ${"%" + page + "%"}`);
12727
+ if (startDate) conditions.push(sql7`${gscSearchData.date} >= ${startDate}`);
12728
+ else if (cutoffDate) conditions.push(sql7`${gscSearchData.date} >= ${cutoffDate}`);
12729
+ if (endDate) conditions.push(sql7`${gscSearchData.date} <= ${endDate}`);
12730
+ if (query) conditions.push(sql7`${gscSearchData.query} LIKE ${"%" + query + "%"}`);
12731
+ if (page) conditions.push(sql7`${gscSearchData.page} LIKE ${"%" + page + "%"}`);
12652
12732
  const limitVal = Math.max(parseInt(limit ?? "500", 10) || 0, 1);
12653
12733
  const offsetVal = Math.max(parseInt(offset ?? "0", 10) || 0, 0);
12654
12734
  const rows = app.db.select().from(gscSearchData).where(and9(...conditions)).orderBy(desc8(gscSearchData.date)).limit(limitVal).offset(offsetVal).all();
@@ -13893,7 +13973,7 @@ async function cdpRoutes(app, opts) {
13893
13973
 
13894
13974
  // ../api-routes/src/ga.ts
13895
13975
  import crypto16 from "crypto";
13896
- import { eq as eq21, desc as desc10, and as and12, sql as sql7 } from "drizzle-orm";
13976
+ import { eq as eq21, desc as desc10, and as and12, sql as sql8 } from "drizzle-orm";
13897
13977
  function gaLog(level, action, ctx) {
13898
13978
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
13899
13979
  const stream = level === "error" ? process.stderr : process.stdout;
@@ -14190,8 +14270,8 @@ async function ga4Routes(app, opts) {
14190
14270
  tx.delete(gaTrafficSnapshots).where(
14191
14271
  and12(
14192
14272
  eq21(gaTrafficSnapshots.projectId, project.id),
14193
- sql7`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
14194
- sql7`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
14273
+ sql8`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
14274
+ sql8`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
14195
14275
  )
14196
14276
  ).run();
14197
14277
  for (const row of rows) {
@@ -14214,8 +14294,8 @@ async function ga4Routes(app, opts) {
14214
14294
  tx.delete(gaAiReferrals).where(
14215
14295
  and12(
14216
14296
  eq21(gaAiReferrals.projectId, project.id),
14217
- sql7`${gaAiReferrals.date} >= ${summary.periodStart}`,
14218
- sql7`${gaAiReferrals.date} <= ${summary.periodEnd}`
14297
+ sql8`${gaAiReferrals.date} >= ${summary.periodStart}`,
14298
+ sql8`${gaAiReferrals.date} <= ${summary.periodEnd}`
14219
14299
  )
14220
14300
  ).run();
14221
14301
  for (const row of aiReferrals) {
@@ -14240,8 +14320,8 @@ async function ga4Routes(app, opts) {
14240
14320
  tx.delete(gaSocialReferrals).where(
14241
14321
  and12(
14242
14322
  eq21(gaSocialReferrals.projectId, project.id),
14243
- sql7`${gaSocialReferrals.date} >= ${summary.periodStart}`,
14244
- sql7`${gaSocialReferrals.date} <= ${summary.periodEnd}`
14323
+ sql8`${gaSocialReferrals.date} >= ${summary.periodStart}`,
14324
+ sql8`${gaSocialReferrals.date} <= ${summary.periodEnd}`
14245
14325
  )
14246
14326
  ).run();
14247
14327
  for (const row of socialReferrals) {
@@ -14331,11 +14411,11 @@ async function ga4Routes(app, opts) {
14331
14411
  const cutoff = windowCutoff(window);
14332
14412
  const cutoffDate = cutoff?.slice(0, 10) ?? null;
14333
14413
  const snapshotConditions = [eq21(gaTrafficSnapshots.projectId, project.id)];
14334
- if (cutoffDate) snapshotConditions.push(sql7`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
14414
+ if (cutoffDate) snapshotConditions.push(sql8`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
14335
14415
  const aiConditions = [eq21(gaAiReferrals.projectId, project.id)];
14336
- if (cutoffDate) aiConditions.push(sql7`${gaAiReferrals.date} >= ${cutoffDate}`);
14416
+ if (cutoffDate) aiConditions.push(sql8`${gaAiReferrals.date} >= ${cutoffDate}`);
14337
14417
  const socialConditions = [eq21(gaSocialReferrals.projectId, project.id)];
14338
- if (cutoffDate) socialConditions.push(sql7`${gaSocialReferrals.date} >= ${cutoffDate}`);
14418
+ if (cutoffDate) socialConditions.push(sql8`${gaSocialReferrals.date} >= ${cutoffDate}`);
14339
14419
  const windowSummaryRow = cutoffDate ? app.db.select({
14340
14420
  totalSessions: gaTrafficWindowSummaries.totalSessions,
14341
14421
  totalOrganicSessions: gaTrafficWindowSummaries.totalOrganicSessions,
@@ -14348,9 +14428,9 @@ async function ga4Routes(app, opts) {
14348
14428
  )
14349
14429
  ).get() : null;
14350
14430
  const snapshotTotalsRow = cutoffDate && !windowSummaryRow ? app.db.select({
14351
- totalSessions: sql7`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
14352
- totalOrganicSessions: sql7`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
14353
- totalUsers: sql7`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
14431
+ totalSessions: sql8`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
14432
+ totalOrganicSessions: sql8`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
14433
+ totalUsers: sql8`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
14354
14434
  }).from(gaTrafficSnapshots).where(and12(...snapshotConditions)).get() : null;
14355
14435
  const summaryRow = cutoffDate ? windowSummaryRow ?? snapshotTotalsRow : app.db.select({
14356
14436
  totalSessions: gaTrafficSummaries.totalSessions,
@@ -14358,38 +14438,38 @@ async function ga4Routes(app, opts) {
14358
14438
  totalUsers: gaTrafficSummaries.totalUsers
14359
14439
  }).from(gaTrafficSummaries).where(eq21(gaTrafficSummaries.projectId, project.id)).get();
14360
14440
  const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
14361
- totalDirectSessions: sql7`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
14441
+ totalDirectSessions: sql8`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
14362
14442
  }).from(gaTrafficSnapshots).where(and12(...snapshotConditions)).get();
14363
14443
  const summaryMeta = app.db.select({
14364
14444
  periodStart: gaTrafficSummaries.periodStart,
14365
14445
  periodEnd: gaTrafficSummaries.periodEnd
14366
14446
  }).from(gaTrafficSummaries).where(eq21(gaTrafficSummaries.projectId, project.id)).get();
14367
14447
  const rows = app.db.select({
14368
- landingPage: sql7`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
14369
- sessions: sql7`SUM(${gaTrafficSnapshots.sessions})`,
14370
- organicSessions: sql7`SUM(${gaTrafficSnapshots.organicSessions})`,
14371
- directSessions: sql7`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
14372
- users: sql7`SUM(${gaTrafficSnapshots.users})`
14373
- }).from(gaTrafficSnapshots).where(and12(...snapshotConditions)).groupBy(sql7`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql7`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
14448
+ landingPage: sql8`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
14449
+ sessions: sql8`SUM(${gaTrafficSnapshots.sessions})`,
14450
+ organicSessions: sql8`SUM(${gaTrafficSnapshots.organicSessions})`,
14451
+ directSessions: sql8`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
14452
+ users: sql8`SUM(${gaTrafficSnapshots.users})`
14453
+ }).from(gaTrafficSnapshots).where(and12(...snapshotConditions)).groupBy(sql8`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql8`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
14374
14454
  const aiReferralRows = app.db.select({
14375
14455
  source: gaAiReferrals.source,
14376
14456
  medium: gaAiReferrals.medium,
14377
14457
  sourceDimension: gaAiReferrals.sourceDimension,
14378
- sessions: sql7`SUM(${gaAiReferrals.sessions})`,
14379
- users: sql7`SUM(${gaAiReferrals.users})`
14458
+ sessions: sql8`SUM(${gaAiReferrals.sessions})`,
14459
+ users: sql8`SUM(${gaAiReferrals.users})`
14380
14460
  }).from(gaAiReferrals).where(and12(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).all();
14381
14461
  const aiReferralLandingPageRows = app.db.select({
14382
14462
  source: gaAiReferrals.source,
14383
14463
  medium: gaAiReferrals.medium,
14384
14464
  sourceDimension: gaAiReferrals.sourceDimension,
14385
- landingPage: sql7`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
14386
- sessions: sql7`SUM(${gaAiReferrals.sessions})`,
14387
- users: sql7`SUM(${gaAiReferrals.users})`
14465
+ landingPage: sql8`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
14466
+ sessions: sql8`SUM(${gaAiReferrals.sessions})`,
14467
+ users: sql8`SUM(${gaAiReferrals.users})`
14388
14468
  }).from(gaAiReferrals).where(and12(...aiConditions)).groupBy(
14389
14469
  gaAiReferrals.source,
14390
14470
  gaAiReferrals.medium,
14391
14471
  gaAiReferrals.sourceDimension,
14392
- sql7`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
14472
+ sql8`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
14393
14473
  ).all();
14394
14474
  const aiReferrals = pickWinningDimension(
14395
14475
  aiReferralRows,
@@ -14400,10 +14480,10 @@ async function ga4Routes(app, opts) {
14400
14480
  (r) => `${r.source}\0${r.medium}\0${r.landingPage}`
14401
14481
  );
14402
14482
  const aiDeduped = app.db.select({
14403
- sessions: sql7`COALESCE(SUM(max_sessions), 0)`,
14404
- users: sql7`COALESCE(SUM(max_users), 0)`
14483
+ sessions: sql8`COALESCE(SUM(max_sessions), 0)`,
14484
+ users: sql8`COALESCE(SUM(max_users), 0)`
14405
14485
  }).from(
14406
- sql7`(
14486
+ sql8`(
14407
14487
  SELECT date, source, medium,
14408
14488
  MAX(dimension_sessions) AS max_sessions,
14409
14489
  MAX(dimension_users) AS max_users
@@ -14412,7 +14492,7 @@ async function ga4Routes(app, opts) {
14412
14492
  SUM(sessions) AS dimension_sessions,
14413
14493
  SUM(users) AS dimension_users
14414
14494
  FROM ga_ai_referrals
14415
- WHERE project_id = ${project.id}${cutoffDate ? sql7` AND date >= ${cutoffDate}` : sql7``}
14495
+ WHERE project_id = ${project.id}${cutoffDate ? sql8` AND date >= ${cutoffDate}` : sql8``}
14416
14496
  GROUP BY date, source, medium, source_dimension
14417
14497
  )
14418
14498
  GROUP BY date, source, medium
@@ -14420,8 +14500,8 @@ async function ga4Routes(app, opts) {
14420
14500
  ).get();
14421
14501
  const aiBySessionRows = app.db.select({
14422
14502
  channelGroup: gaAiReferrals.channelGroup,
14423
- sessions: sql7`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
14424
- users: sql7`COALESCE(SUM(${gaAiReferrals.users}), 0)`
14503
+ sessions: sql8`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
14504
+ users: sql8`COALESCE(SUM(${gaAiReferrals.users}), 0)`
14425
14505
  }).from(gaAiReferrals).where(and12(...aiConditions, eq21(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
14426
14506
  const aiSessionsByChannelGroup = /* @__PURE__ */ new Map();
14427
14507
  let aiBySessionUsers = 0;
@@ -14434,12 +14514,12 @@ async function ga4Routes(app, opts) {
14434
14514
  source: gaSocialReferrals.source,
14435
14515
  medium: gaSocialReferrals.medium,
14436
14516
  channelGroup: gaSocialReferrals.channelGroup,
14437
- sessions: sql7`SUM(${gaSocialReferrals.sessions})`,
14438
- users: sql7`SUM(${gaSocialReferrals.users})`
14439
- }).from(gaSocialReferrals).where(and12(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql7`SUM(${gaSocialReferrals.sessions}) DESC`).all();
14517
+ sessions: sql8`SUM(${gaSocialReferrals.sessions})`,
14518
+ users: sql8`SUM(${gaSocialReferrals.users})`
14519
+ }).from(gaSocialReferrals).where(and12(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql8`SUM(${gaSocialReferrals.sessions}) DESC`).all();
14440
14520
  const socialTotals = app.db.select({
14441
- sessions: sql7`SUM(${gaSocialReferrals.sessions})`,
14442
- users: sql7`SUM(${gaSocialReferrals.users})`
14521
+ sessions: sql8`SUM(${gaSocialReferrals.sessions})`,
14522
+ users: sql8`SUM(${gaSocialReferrals.users})`
14443
14523
  }).from(gaSocialReferrals).where(and12(...socialConditions)).get();
14444
14524
  const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq21(gaTrafficSummaries.projectId, project.id)).orderBy(desc10(gaTrafficSummaries.syncedAt)).limit(1).get();
14445
14525
  const total = summaryRow?.totalSessions ?? 0;
@@ -14522,21 +14602,21 @@ async function ga4Routes(app, opts) {
14522
14602
  requireGa4Connection(opts, project.name, project.canonicalDomain);
14523
14603
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
14524
14604
  const conditions = [eq21(gaAiReferrals.projectId, project.id)];
14525
- if (cutoffDate) conditions.push(sql7`${gaAiReferrals.date} >= ${cutoffDate}`);
14605
+ if (cutoffDate) conditions.push(sql8`${gaAiReferrals.date} >= ${cutoffDate}`);
14526
14606
  const rows = app.db.select({
14527
14607
  date: gaAiReferrals.date,
14528
14608
  source: gaAiReferrals.source,
14529
14609
  medium: gaAiReferrals.medium,
14530
- landingPage: sql7`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
14610
+ landingPage: sql8`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
14531
14611
  sourceDimension: gaAiReferrals.sourceDimension,
14532
- sessions: sql7`SUM(${gaAiReferrals.sessions})`,
14533
- users: sql7`SUM(${gaAiReferrals.users})`
14612
+ sessions: sql8`SUM(${gaAiReferrals.sessions})`,
14613
+ users: sql8`SUM(${gaAiReferrals.users})`
14534
14614
  }).from(gaAiReferrals).where(and12(...conditions)).groupBy(
14535
14615
  gaAiReferrals.date,
14536
14616
  gaAiReferrals.source,
14537
14617
  gaAiReferrals.medium,
14538
14618
  gaAiReferrals.sourceDimension,
14539
- sql7`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
14619
+ sql8`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
14540
14620
  ).orderBy(gaAiReferrals.date).all();
14541
14621
  return rows;
14542
14622
  });
@@ -14545,7 +14625,7 @@ async function ga4Routes(app, opts) {
14545
14625
  requireGa4Connection(opts, project.name, project.canonicalDomain);
14546
14626
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
14547
14627
  const conditions = [eq21(gaSocialReferrals.projectId, project.id)];
14548
- if (cutoffDate) conditions.push(sql7`${gaSocialReferrals.date} >= ${cutoffDate}`);
14628
+ if (cutoffDate) conditions.push(sql8`${gaSocialReferrals.date} >= ${cutoffDate}`);
14549
14629
  const rows = app.db.select({
14550
14630
  date: gaSocialReferrals.date,
14551
14631
  source: gaSocialReferrals.source,
@@ -14566,10 +14646,10 @@ async function ga4Routes(app, opts) {
14566
14646
  d.setDate(d.getDate() - n);
14567
14647
  return fmt(d);
14568
14648
  };
14569
- const sumSocial = (from, to) => app.db.select({ sessions: sql7`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and12(
14649
+ const sumSocial = (from, to) => app.db.select({ sessions: sql8`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and12(
14570
14650
  eq21(gaSocialReferrals.projectId, project.id),
14571
- sql7`${gaSocialReferrals.date} >= ${from}`,
14572
- sql7`${gaSocialReferrals.date} < ${to}`
14651
+ sql8`${gaSocialReferrals.date} >= ${from}`,
14652
+ sql8`${gaSocialReferrals.date} < ${to}`
14573
14653
  )).get();
14574
14654
  const current7d = sumSocial(daysAgo2(7), fmt(today));
14575
14655
  const prev7d = sumSocial(daysAgo2(14), daysAgo2(7));
@@ -14578,19 +14658,19 @@ async function ga4Routes(app, opts) {
14578
14658
  const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
14579
14659
  const sourceCurrent = app.db.select({
14580
14660
  source: gaSocialReferrals.source,
14581
- sessions: sql7`SUM(${gaSocialReferrals.sessions})`
14661
+ sessions: sql8`SUM(${gaSocialReferrals.sessions})`
14582
14662
  }).from(gaSocialReferrals).where(and12(
14583
14663
  eq21(gaSocialReferrals.projectId, project.id),
14584
- sql7`${gaSocialReferrals.date} >= ${daysAgo2(7)}`,
14585
- sql7`${gaSocialReferrals.date} < ${fmt(today)}`
14664
+ sql8`${gaSocialReferrals.date} >= ${daysAgo2(7)}`,
14665
+ sql8`${gaSocialReferrals.date} < ${fmt(today)}`
14586
14666
  )).groupBy(gaSocialReferrals.source).all();
14587
14667
  const sourcePrev = app.db.select({
14588
14668
  source: gaSocialReferrals.source,
14589
- sessions: sql7`SUM(${gaSocialReferrals.sessions})`
14669
+ sessions: sql8`SUM(${gaSocialReferrals.sessions})`
14590
14670
  }).from(gaSocialReferrals).where(and12(
14591
14671
  eq21(gaSocialReferrals.projectId, project.id),
14592
- sql7`${gaSocialReferrals.date} >= ${daysAgo2(14)}`,
14593
- sql7`${gaSocialReferrals.date} < ${daysAgo2(7)}`
14672
+ sql8`${gaSocialReferrals.date} >= ${daysAgo2(14)}`,
14673
+ sql8`${gaSocialReferrals.date} < ${daysAgo2(7)}`
14594
14674
  )).groupBy(gaSocialReferrals.source).all();
14595
14675
  const prevMap = new Map(sourcePrev.map((r) => [r.source, r.sessions]));
14596
14676
  let biggestMover = null;
@@ -14629,16 +14709,16 @@ async function ga4Routes(app, opts) {
14629
14709
  return fmt(d);
14630
14710
  };
14631
14711
  const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
14632
- const sumTotal = (from, to) => app.db.select({ sessions: sql7`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and12(eq21(gaTrafficSnapshots.projectId, project.id), sql7`${gaTrafficSnapshots.date} >= ${from}`, sql7`${gaTrafficSnapshots.date} < ${to}`)).get();
14633
- const sumOrganic = (from, to) => app.db.select({ sessions: sql7`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and12(eq21(gaTrafficSnapshots.projectId, project.id), sql7`${gaTrafficSnapshots.date} >= ${from}`, sql7`${gaTrafficSnapshots.date} < ${to}`)).get();
14634
- const sumDirect = (from, to) => app.db.select({ sessions: sql7`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and12(eq21(gaTrafficSnapshots.projectId, project.id), sql7`${gaTrafficSnapshots.date} >= ${from}`, sql7`${gaTrafficSnapshots.date} < ${to}`)).get();
14635
- const sumAi = (from, to) => app.db.select({ sessions: sql7`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and12(
14712
+ const sumTotal = (from, to) => app.db.select({ sessions: sql8`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and12(eq21(gaTrafficSnapshots.projectId, project.id), sql8`${gaTrafficSnapshots.date} >= ${from}`, sql8`${gaTrafficSnapshots.date} < ${to}`)).get();
14713
+ const sumOrganic = (from, to) => app.db.select({ sessions: sql8`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and12(eq21(gaTrafficSnapshots.projectId, project.id), sql8`${gaTrafficSnapshots.date} >= ${from}`, sql8`${gaTrafficSnapshots.date} < ${to}`)).get();
14714
+ const sumDirect = (from, to) => app.db.select({ sessions: sql8`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and12(eq21(gaTrafficSnapshots.projectId, project.id), sql8`${gaTrafficSnapshots.date} >= ${from}`, sql8`${gaTrafficSnapshots.date} < ${to}`)).get();
14715
+ const sumAi = (from, to) => app.db.select({ sessions: sql8`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and12(
14636
14716
  eq21(gaAiReferrals.projectId, project.id),
14637
- sql7`${gaAiReferrals.date} >= ${from}`,
14638
- sql7`${gaAiReferrals.date} < ${to}`,
14717
+ sql8`${gaAiReferrals.date} >= ${from}`,
14718
+ sql8`${gaAiReferrals.date} < ${to}`,
14639
14719
  eq21(gaAiReferrals.sourceDimension, "session")
14640
14720
  )).get();
14641
- const sumSocial = (from, to) => app.db.select({ sessions: sql7`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and12(eq21(gaSocialReferrals.projectId, project.id), sql7`${gaSocialReferrals.date} >= ${from}`, sql7`${gaSocialReferrals.date} < ${to}`)).get();
14721
+ const sumSocial = (from, to) => app.db.select({ sessions: sql8`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and12(eq21(gaSocialReferrals.projectId, project.id), sql8`${gaSocialReferrals.date} >= ${from}`, sql8`${gaSocialReferrals.date} < ${to}`)).get();
14642
14722
  const todayStr = fmt(today);
14643
14723
  const buildTrend = (sum) => {
14644
14724
  const c7 = sum(daysAgo2(7), todayStr)?.sessions ?? 0;
@@ -14647,16 +14727,16 @@ async function ga4Routes(app, opts) {
14647
14727
  const p30 = sum(daysAgo2(60), daysAgo2(30))?.sessions ?? 0;
14648
14728
  return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
14649
14729
  };
14650
- const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql7`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and12(
14730
+ const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql8`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and12(
14651
14731
  eq21(gaAiReferrals.projectId, project.id),
14652
- sql7`${gaAiReferrals.date} >= ${daysAgo2(7)}`,
14653
- sql7`${gaAiReferrals.date} < ${todayStr}`,
14732
+ sql8`${gaAiReferrals.date} >= ${daysAgo2(7)}`,
14733
+ sql8`${gaAiReferrals.date} < ${todayStr}`,
14654
14734
  eq21(gaAiReferrals.sourceDimension, "session")
14655
14735
  )).groupBy(gaAiReferrals.source).all();
14656
- const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql7`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and12(
14736
+ const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql8`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and12(
14657
14737
  eq21(gaAiReferrals.projectId, project.id),
14658
- sql7`${gaAiReferrals.date} >= ${daysAgo2(14)}`,
14659
- sql7`${gaAiReferrals.date} < ${daysAgo2(7)}`,
14738
+ sql8`${gaAiReferrals.date} >= ${daysAgo2(14)}`,
14739
+ sql8`${gaAiReferrals.date} < ${daysAgo2(7)}`,
14660
14740
  eq21(gaAiReferrals.sourceDimension, "session")
14661
14741
  )).groupBy(gaAiReferrals.source).all();
14662
14742
  const findBiggestMover = (current, prev) => {
@@ -14673,8 +14753,8 @@ async function ga4Routes(app, opts) {
14673
14753
  }
14674
14754
  return mover;
14675
14755
  };
14676
- const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql7`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and12(eq21(gaSocialReferrals.projectId, project.id), sql7`${gaSocialReferrals.date} >= ${daysAgo2(7)}`, sql7`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
14677
- const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql7`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and12(eq21(gaSocialReferrals.projectId, project.id), sql7`${gaSocialReferrals.date} >= ${daysAgo2(14)}`, sql7`${gaSocialReferrals.date} < ${daysAgo2(7)}`)).groupBy(gaSocialReferrals.source).all();
14756
+ const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql8`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and12(eq21(gaSocialReferrals.projectId, project.id), sql8`${gaSocialReferrals.date} >= ${daysAgo2(7)}`, sql8`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
14757
+ const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql8`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and12(eq21(gaSocialReferrals.projectId, project.id), sql8`${gaSocialReferrals.date} >= ${daysAgo2(14)}`, sql8`${gaSocialReferrals.date} < ${daysAgo2(7)}`)).groupBy(gaSocialReferrals.source).all();
14678
14758
  return {
14679
14759
  total: buildTrend(sumTotal),
14680
14760
  organic: buildTrend(sumOrganic),
@@ -14690,12 +14770,12 @@ async function ga4Routes(app, opts) {
14690
14770
  requireGa4Connection(opts, project.name, project.canonicalDomain);
14691
14771
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
14692
14772
  const conditions = [eq21(gaTrafficSnapshots.projectId, project.id)];
14693
- if (cutoffDate) conditions.push(sql7`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
14773
+ if (cutoffDate) conditions.push(sql8`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
14694
14774
  const rows = app.db.select({
14695
14775
  date: gaTrafficSnapshots.date,
14696
- sessions: sql7`SUM(${gaTrafficSnapshots.sessions})`,
14697
- organicSessions: sql7`SUM(${gaTrafficSnapshots.organicSessions})`,
14698
- users: sql7`SUM(${gaTrafficSnapshots.users})`
14776
+ sessions: sql8`SUM(${gaTrafficSnapshots.sessions})`,
14777
+ organicSessions: sql8`SUM(${gaTrafficSnapshots.organicSessions})`,
14778
+ users: sql8`SUM(${gaTrafficSnapshots.users})`
14699
14779
  }).from(gaTrafficSnapshots).where(and12(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
14700
14780
  return rows.map((r) => ({
14701
14781
  date: r.date,
@@ -14708,11 +14788,11 @@ async function ga4Routes(app, opts) {
14708
14788
  const project = resolveProject(app.db, request.params.name);
14709
14789
  requireGa4Connection(opts, project.name, project.canonicalDomain);
14710
14790
  const trafficPages = app.db.select({
14711
- landingPage: sql7`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
14712
- sessions: sql7`SUM(${gaTrafficSnapshots.sessions})`,
14713
- organicSessions: sql7`SUM(${gaTrafficSnapshots.organicSessions})`,
14714
- users: sql7`SUM(${gaTrafficSnapshots.users})`
14715
- }).from(gaTrafficSnapshots).where(eq21(gaTrafficSnapshots.projectId, project.id)).groupBy(sql7`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql7`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
14791
+ landingPage: sql8`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
14792
+ sessions: sql8`SUM(${gaTrafficSnapshots.sessions})`,
14793
+ organicSessions: sql8`SUM(${gaTrafficSnapshots.organicSessions})`,
14794
+ users: sql8`SUM(${gaTrafficSnapshots.users})`
14795
+ }).from(gaTrafficSnapshots).where(eq21(gaTrafficSnapshots.projectId, project.id)).groupBy(sql8`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql8`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
14716
14796
  return {
14717
14797
  pages: trafficPages.map((r) => ({
14718
14798
  landingPage: r.landingPage,
@@ -16349,7 +16429,7 @@ async function wordpressRoutes(app, opts) {
16349
16429
 
16350
16430
  // ../api-routes/src/backlinks.ts
16351
16431
  import crypto18 from "crypto";
16352
- import { and as and14, asc as asc2, desc as desc11, eq as eq22, sql as sql8 } from "drizzle-orm";
16432
+ import { and as and14, asc as asc2, desc as desc11, eq as eq22, sql as sql9 } from "drizzle-orm";
16353
16433
 
16354
16434
  // ../integration-commoncrawl/src/constants.ts
16355
16435
  import os3 from "os";
@@ -16633,7 +16713,7 @@ async function queryBacklinks(opts) {
16633
16713
  const reversed = opts.targets.map(reverseDomain);
16634
16714
  const targetList = reversed.map(quote).join(", ");
16635
16715
  const limitClause = opts.limitPerTarget ? `QUALIFY row_number() OVER (PARTITION BY t.target_rev_domain ORDER BY v.num_hosts DESC) <= ${Math.floor(opts.limitPerTarget)}` : "";
16636
- const sql15 = `
16716
+ const sql16 = `
16637
16717
  WITH vertices AS (
16638
16718
  SELECT * FROM read_csv(
16639
16719
  ${quote(opts.vertexPath)},
@@ -16669,7 +16749,7 @@ async function queryBacklinks(opts) {
16669
16749
  const conn = await instance.connect();
16670
16750
  let rows;
16671
16751
  try {
16672
- const reader = await conn.runAndReadAll(sql15);
16752
+ const reader = await conn.runAndReadAll(sql16);
16673
16753
  rows = reader.getRowObjects();
16674
16754
  } finally {
16675
16755
  conn.disconnectSync?.();
@@ -16845,12 +16925,12 @@ function computeFilteredSummary(db, base) {
16845
16925
  );
16846
16926
  const filteredCondition = and14(baseDomainCondition, backlinkCrawlerExclusionClause());
16847
16927
  const unfilteredAgg = db.select({
16848
- count: sql8`count(*)`,
16849
- total: sql8`coalesce(sum(${backlinkDomains.numHosts}), 0)`
16928
+ count: sql9`count(*)`,
16929
+ total: sql9`coalesce(sum(${backlinkDomains.numHosts}), 0)`
16850
16930
  }).from(backlinkDomains).where(baseDomainCondition).get();
16851
16931
  const filteredAgg = db.select({
16852
- count: sql8`count(*)`,
16853
- total: sql8`coalesce(sum(${backlinkDomains.numHosts}), 0)`
16932
+ count: sql9`count(*)`,
16933
+ total: sql9`coalesce(sum(${backlinkDomains.numHosts}), 0)`
16854
16934
  }).from(backlinkDomains).where(filteredCondition).get();
16855
16935
  const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(desc11(backlinkDomains.numHosts)).limit(10).all();
16856
16936
  const totalLinkingDomains = Number(filteredAgg?.count ?? 0);
@@ -17024,7 +17104,7 @@ async function backlinksRoutes(app, opts) {
17024
17104
  eq22(backlinkDomains.release, targetRelease)
17025
17105
  );
17026
17106
  const domainCondition = excludeCrawlers ? and14(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
17027
- const totalRow = app.db.select({ count: sql8`count(*)` }).from(backlinkDomains).where(domainCondition).get();
17107
+ const totalRow = app.db.select({ count: sql9`count(*)` }).from(backlinkDomains).where(domainCondition).get();
17028
17108
  const rows = app.db.select({
17029
17109
  linkingDomain: backlinkDomains.linkingDomain,
17030
17110
  numHosts: backlinkDomains.numHosts
@@ -17059,7 +17139,7 @@ async function backlinksRoutes(app, opts) {
17059
17139
 
17060
17140
  // ../api-routes/src/traffic.ts
17061
17141
  import crypto20 from "crypto";
17062
- import { and as and15, desc as desc12, eq as eq23, gte as gte2, lte as lte2, sql as sql9 } from "drizzle-orm";
17142
+ import { and as and15, desc as desc12, eq as eq23, gte as gte2, lte as lte2, sql as sql10 } from "drizzle-orm";
17063
17143
 
17064
17144
  // ../integration-cloud-run/src/auth.ts
17065
17145
  import crypto19 from "crypto";
@@ -18797,7 +18877,7 @@ async function trafficRoutes(app, opts) {
18797
18877
  crawlerEventsHourly.status
18798
18878
  ],
18799
18879
  set: {
18800
- hits: sql9`${crawlerEventsHourly.hits} + ${bucket.hits}`,
18880
+ hits: sql10`${crawlerEventsHourly.hits} + ${bucket.hits}`,
18801
18881
  sampledUserAgent: bucket.sampledUserAgent,
18802
18882
  updatedAt: finishedAt
18803
18883
  }
@@ -18832,7 +18912,7 @@ async function trafficRoutes(app, opts) {
18832
18912
  aiReferralEventsHourly.status
18833
18913
  ],
18834
18914
  set: {
18835
- sessionsOrHits: sql9`${aiReferralEventsHourly.sessionsOrHits} + ${bucket.hits}`,
18915
+ sessionsOrHits: sql10`${aiReferralEventsHourly.sessionsOrHits} + ${bucket.hits}`,
18836
18916
  updatedAt: finishedAt
18837
18917
  }
18838
18918
  }).run();
@@ -19084,19 +19164,19 @@ async function trafficRoutes(app, opts) {
19084
19164
  return response;
19085
19165
  });
19086
19166
  function buildSourceDetail(projectId, row, since) {
19087
- const crawlerTotals = app.db.select({ total: sql9`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
19167
+ const crawlerTotals = app.db.select({ total: sql10`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
19088
19168
  and15(
19089
19169
  eq23(crawlerEventsHourly.sourceId, row.id),
19090
19170
  gte2(crawlerEventsHourly.tsHour, since)
19091
19171
  )
19092
19172
  ).get();
19093
- const aiTotals = app.db.select({ total: sql9`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
19173
+ const aiTotals = app.db.select({ total: sql10`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
19094
19174
  and15(
19095
19175
  eq23(aiReferralEventsHourly.sourceId, row.id),
19096
19176
  gte2(aiReferralEventsHourly.tsHour, since)
19097
19177
  )
19098
19178
  ).get();
19099
- const sampleTotals = app.db.select({ total: sql9`COUNT(*)` }).from(rawEventSamples).where(
19179
+ const sampleTotals = app.db.select({ total: sql10`COUNT(*)` }).from(rawEventSamples).where(
19100
19180
  and15(
19101
19181
  eq23(rawEventSamples.sourceId, row.id),
19102
19182
  gte2(rawEventSamples.ts, since)
@@ -19198,7 +19278,7 @@ async function trafficRoutes(app, opts) {
19198
19278
  ];
19199
19279
  if (sourceIdParam) crawlerFilters.push(eq23(crawlerEventsHourly.sourceId, sourceIdParam));
19200
19280
  const crawlerWhere = and15(...crawlerFilters);
19201
- const total = app.db.select({ total: sql9`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(crawlerWhere).get();
19281
+ const total = app.db.select({ total: sql10`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(crawlerWhere).get();
19202
19282
  crawlerTotal = Number(total?.total ?? 0);
19203
19283
  const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc12(crawlerEventsHourly.tsHour)).limit(limit).all();
19204
19284
  for (const r of rows) {
@@ -19223,7 +19303,7 @@ async function trafficRoutes(app, opts) {
19223
19303
  ];
19224
19304
  if (sourceIdParam) aiFilters.push(eq23(aiReferralEventsHourly.sourceId, sourceIdParam));
19225
19305
  const aiWhere = and15(...aiFilters);
19226
- const total = app.db.select({ total: sql9`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
19306
+ const total = app.db.select({ total: sql10`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
19227
19307
  aiReferralTotal = Number(total?.total ?? 0);
19228
19308
  const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc12(aiReferralEventsHourly.tsHour)).limit(limit).all();
19229
19309
  for (const r of rows) {
@@ -19946,7 +20026,7 @@ var providersConfiguredCheck = {
19946
20026
  var PROVIDERS_CHECKS = [providersConfiguredCheck];
19947
20027
 
19948
20028
  // ../api-routes/src/doctor/checks/traffic-source.ts
19949
- import { and as and16, eq as eq24, gte as gte3, ne as ne3, sql as sql10 } from "drizzle-orm";
20029
+ import { and as and16, eq as eq24, gte as gte3, ne as ne3, sql as sql11 } from "drizzle-orm";
19950
20030
  var RECENT_DATA_WARN_DAYS = 7;
19951
20031
  var RECENT_DATA_FAIL_DAYS = 30;
19952
20032
  function skippedNoProject2() {
@@ -20040,7 +20120,7 @@ var recentDataCheck = {
20040
20120
  const warnCutoff = new Date(now.getTime() - RECENT_DATA_WARN_DAYS * 24 * 60 * 6e4).toISOString();
20041
20121
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
20042
20122
  const recentCrawlers = Number(
20043
- ctx.db.select({ total: sql10`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
20123
+ ctx.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
20044
20124
  and16(
20045
20125
  eq24(crawlerEventsHourly.projectId, ctx.project.id),
20046
20126
  gte3(crawlerEventsHourly.tsHour, warnCutoff)
@@ -20048,7 +20128,7 @@ var recentDataCheck = {
20048
20128
  ).get()?.total ?? 0
20049
20129
  );
20050
20130
  const recentReferrals = Number(
20051
- ctx.db.select({ total: sql10`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
20131
+ ctx.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
20052
20132
  and16(
20053
20133
  eq24(aiReferralEventsHourly.projectId, ctx.project.id),
20054
20134
  gte3(aiReferralEventsHourly.tsHour, warnCutoff)
@@ -20064,7 +20144,7 @@ var recentDataCheck = {
20064
20144
  };
20065
20145
  }
20066
20146
  const olderCrawlers = Number(
20067
- ctx.db.select({ total: sql10`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
20147
+ ctx.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
20068
20148
  and16(
20069
20149
  eq24(crawlerEventsHourly.projectId, ctx.project.id),
20070
20150
  gte3(crawlerEventsHourly.tsHour, failCutoff)
@@ -20072,7 +20152,7 @@ var recentDataCheck = {
20072
20152
  ).get()?.total ?? 0
20073
20153
  );
20074
20154
  const olderReferrals = Number(
20075
- ctx.db.select({ total: sql10`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
20155
+ ctx.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
20076
20156
  and16(
20077
20157
  eq24(aiReferralEventsHourly.projectId, ctx.project.id),
20078
20158
  gte3(aiReferralEventsHourly.tsHour, failCutoff)
@@ -20372,7 +20452,7 @@ async function doctorRoutes(app, opts) {
20372
20452
 
20373
20453
  // ../api-routes/src/discovery/routes.ts
20374
20454
  import crypto21 from "crypto";
20375
- import { and as and17, desc as desc13, eq as eq25, gte as gte4, inArray as inArray8 } from "drizzle-orm";
20455
+ import { and as and17, desc as desc13, eq as eq25, gte as gte4, inArray as inArray9 } from "drizzle-orm";
20376
20456
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
20377
20457
  async function discoveryRoutes(app, opts) {
20378
20458
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -20407,7 +20487,7 @@ async function discoveryRoutes(app, opts) {
20407
20487
  const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and17(
20408
20488
  eq25(discoverySessions.projectId, project.id),
20409
20489
  eq25(discoverySessions.icpDescription, icpDescription),
20410
- inArray8(discoverySessions.status, [
20490
+ inArray9(discoverySessions.status, [
20411
20491
  DiscoverySessionStatuses.queued,
20412
20492
  DiscoverySessionStatuses.seeding,
20413
20493
  DiscoverySessionStatuses.probing
@@ -23725,7 +23805,7 @@ import crypto24 from "crypto";
23725
23805
  import fs8 from "fs";
23726
23806
  import path10 from "path";
23727
23807
  import os5 from "os";
23728
- import { and as and18, eq as eq27, inArray as inArray9, sql as sql11 } from "drizzle-orm";
23808
+ import { and as and18, eq as eq27, inArray as inArray10, sql as sql12 } from "drizzle-orm";
23729
23809
 
23730
23810
  // src/run-telemetry.ts
23731
23811
  import crypto23 from "crypto";
@@ -24070,7 +24150,7 @@ var JobRunner = class {
24070
24150
  this.registry = registry;
24071
24151
  }
24072
24152
  recoverStaleRuns() {
24073
- const stale = this.db.select({ id: runs.id, status: runs.status }).from(runs).where(inArray9(runs.status, ["running", "queued"])).all();
24153
+ const stale = this.db.select({ id: runs.id, status: runs.status }).from(runs).where(inArray10(runs.status, ["running", "queued"])).all();
24074
24154
  if (stale.length === 0) return;
24075
24155
  const now = (/* @__PURE__ */ new Date()).toISOString();
24076
24156
  for (const run of stale) {
@@ -24133,7 +24213,7 @@ var JobRunner = class {
24133
24213
  }
24134
24214
  log.info("run.dispatch", { runId, providerCount: activeProviders.length, providers: activeProviders.map((p) => p.adapter.name) });
24135
24215
  const scopedQueryNames = parseJsonColumn(existingRun.queries, null);
24136
- projectQueries = scopedQueryNames ? this.db.select().from(queries).where(and18(eq27(queries.projectId, projectId), inArray9(queries.query, scopedQueryNames))).all() : this.db.select().from(queries).where(eq27(queries.projectId, projectId)).all();
24216
+ projectQueries = scopedQueryNames ? this.db.select().from(queries).where(and18(eq27(queries.projectId, projectId), inArray10(queries.query, scopedQueryNames))).all() : this.db.select().from(queries).where(eq27(queries.projectId, projectId)).all();
24137
24217
  const projectCompetitors = this.db.select().from(competitors).where(eq27(competitors.projectId, projectId)).all();
24138
24218
  const competitorDomains = projectCompetitors.map((c) => c.domain);
24139
24219
  const allDomains = effectiveDomains({
@@ -24406,7 +24486,7 @@ var JobRunner = class {
24406
24486
  updatedAt: now
24407
24487
  }).onConflictDoUpdate({
24408
24488
  target: [usageCounters.scope, usageCounters.period, usageCounters.metric],
24409
- set: { count: sql11`${usageCounters.count} + ${count}`, updatedAt: now }
24489
+ set: { count: sql12`${usageCounters.count} + ${count}`, updatedAt: now }
24410
24490
  }).run();
24411
24491
  }
24412
24492
  flushProviderUsage(projectId, providerDispatchCounts) {
@@ -24476,7 +24556,7 @@ function buildPhases(input) {
24476
24556
 
24477
24557
  // src/gsc-sync.ts
24478
24558
  import crypto25 from "crypto";
24479
- import { eq as eq28, and as and19, sql as sql12 } from "drizzle-orm";
24559
+ import { eq as eq28, and as and19, sql as sql13 } from "drizzle-orm";
24480
24560
  var log2 = createLogger("GscSync");
24481
24561
  function formatDate3(d) {
24482
24562
  return d.toISOString().split("T")[0];
@@ -24530,8 +24610,8 @@ async function executeGscSync(db, runId, projectId, opts) {
24530
24610
  db.delete(gscSearchData).where(
24531
24611
  and19(
24532
24612
  eq28(gscSearchData.projectId, projectId),
24533
- sql12`${gscSearchData.date} >= ${startDate}`,
24534
- sql12`${gscSearchData.date} <= ${endDate}`
24613
+ sql13`${gscSearchData.date} >= ${startDate}`,
24614
+ sql13`${gscSearchData.date} <= ${endDate}`
24535
24615
  )
24536
24616
  ).run();
24537
24617
  const batchSize = 500;
@@ -25067,7 +25147,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
25067
25147
  // src/commoncrawl-sync.ts
25068
25148
  import crypto28 from "crypto";
25069
25149
  import path11 from "path";
25070
- import { and as and21, eq as eq31, sql as sql13 } from "drizzle-orm";
25150
+ import { and as and21, eq as eq31, sql as sql14 } from "drizzle-orm";
25071
25151
  var log6 = createLogger("CommonCrawlSync");
25072
25152
  var INSERT_CHUNK_SIZE = 1e4;
25073
25153
  function defaultDeps() {
@@ -25679,23 +25759,25 @@ function buildDiscoveryInsightTitle(input) {
25679
25759
  }
25680
25760
 
25681
25761
  // src/commands/backfill.ts
25682
- import { and as and24, eq as eq34, inArray as inArray10 } from "drizzle-orm";
25762
+ import { and as and24, eq as eq34, inArray as inArray11 } from "drizzle-orm";
25683
25763
  var SNAPSHOT_BATCH_SIZE = 500;
25684
25764
  async function backfillAnswerVisibilityCommand(opts) {
25685
25765
  const config = loadConfig();
25686
25766
  const db = createClient(config.database);
25687
25767
  migrate(db);
25688
25768
  const projectFilter = opts?.project?.trim();
25769
+ const isDryRun = opts?.dryRun === true;
25689
25770
  const scopedProjects = projectFilter ? db.select().from(projects).where(eq34(projects.name, projectFilter)).all() : db.select().from(projects).all();
25690
25771
  let examined = 0;
25691
25772
  let updated = 0;
25773
+ let wouldUpdate = 0;
25692
25774
  let mentioned = 0;
25693
25775
  let reparsed = 0;
25694
25776
  let providerErrors = 0;
25695
25777
  if (scopedProjects.length > 0) {
25696
25778
  const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and24(
25697
25779
  eq34(runs.kind, RunKinds["answer-visibility"]),
25698
- inArray10(runs.projectId, scopedProjects.map((project) => project.id))
25780
+ inArray11(runs.projectId, scopedProjects.map((project) => project.id))
25699
25781
  )).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq34(runs.kind, RunKinds["answer-visibility"])).all();
25700
25782
  const runIdsByProject = /* @__PURE__ */ new Map();
25701
25783
  for (const run of runRows) {
@@ -25727,7 +25809,7 @@ async function backfillAnswerVisibilityCommand(opts) {
25727
25809
  competitorOverlap: querySnapshots.competitorOverlap,
25728
25810
  recommendedCompetitors: querySnapshots.recommendedCompetitors,
25729
25811
  rawResponse: querySnapshots.rawResponse
25730
- }).from(querySnapshots).where(inArray10(querySnapshots.runId, batchRunIds)).all();
25812
+ }).from(querySnapshots).where(inArray11(querySnapshots.runId, batchRunIds)).all();
25731
25813
  const pendingUpdates = [];
25732
25814
  for (const snapshot of snapshotRows) {
25733
25815
  examined++;
@@ -25791,12 +25873,16 @@ async function backfillAnswerVisibilityCommand(opts) {
25791
25873
  }
25792
25874
  }
25793
25875
  if (pendingUpdates.length > 0) {
25794
- db.transaction((tx) => {
25795
- for (const update of pendingUpdates) {
25796
- tx.update(querySnapshots).set(update.patch).where(eq34(querySnapshots.id, update.id)).run();
25797
- }
25798
- });
25799
- updated += pendingUpdates.length;
25876
+ if (isDryRun) {
25877
+ wouldUpdate += pendingUpdates.length;
25878
+ } else {
25879
+ db.transaction((tx) => {
25880
+ for (const update of pendingUpdates) {
25881
+ tx.update(querySnapshots).set(update.patch).where(eq34(querySnapshots.id, update.id)).run();
25882
+ }
25883
+ });
25884
+ updated += pendingUpdates.length;
25885
+ }
25800
25886
  }
25801
25887
  }
25802
25888
  }
@@ -25810,20 +25896,33 @@ async function backfillAnswerVisibilityCommand(opts) {
25810
25896
  reparsed,
25811
25897
  providerErrors
25812
25898
  };
25899
+ if (isDryRun) {
25900
+ result.dryRun = true;
25901
+ result.wouldUpdate = wouldUpdate;
25902
+ }
25813
25903
  if (opts?.format === "json") {
25814
25904
  console.log(JSON.stringify(result, null, 2));
25815
25905
  return;
25816
25906
  }
25817
- console.log("Answer visibility backfill complete.\n");
25907
+ console.log(`Answer visibility backfill ${isDryRun ? "preview" : "complete"}.
25908
+ `);
25818
25909
  if (projectFilter) {
25819
25910
  console.log(` Project: ${projectFilter}`);
25820
25911
  }
25821
25912
  console.log(` Projects: ${scopedProjects.length}`);
25822
- console.log(` Examined: ${examined}`);
25823
- console.log(` Updated: ${updated}`);
25824
- console.log(` Mentioned: ${mentioned}`);
25825
- console.log(` Reparsed: ${reparsed}`);
25826
- console.log(` Errors: ${providerErrors}`);
25913
+ console.log(` Examined: ${examined}`);
25914
+ if (isDryRun) {
25915
+ console.log(` Would update: ${wouldUpdate}`);
25916
+ } else {
25917
+ console.log(` Updated: ${updated}`);
25918
+ }
25919
+ console.log(` Mentioned: ${mentioned}`);
25920
+ console.log(` Reparsed: ${reparsed}`);
25921
+ console.log(` Errors: ${providerErrors}`);
25922
+ if (isDryRun) {
25923
+ console.log(`
25924
+ No DB writes performed. Re-run without --dry-run to apply.`);
25925
+ }
25827
25926
  }
25828
25927
  function backfillNormalizedPaths(db, opts) {
25829
25928
  const baseConditions = [];
@@ -25969,7 +26068,8 @@ async function backfillAiReferralPathsCommand(opts) {
25969
26068
  console.log(` Updated: ${updated}`);
25970
26069
  console.log(` Unchanged: ${unchanged}`);
25971
26070
  }
25972
- function backfillProjectAnswerMentions(db, projectId) {
26071
+ function backfillProjectAnswerMentions(db, projectId, opts) {
26072
+ const isDryRun = opts?.dryRun === true;
25973
26073
  const project = db.select().from(projects).where(eq34(projects.id, projectId)).get();
25974
26074
  if (!project) return { examined: 0, updated: 0, mentioned: 0 };
25975
26075
  const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq34(competitors.projectId, projectId)).all().map((row) => row.domain);
@@ -25977,8 +26077,11 @@ function backfillProjectAnswerMentions(db, projectId) {
25977
26077
  const runIds = runRows.map((r) => r.id);
25978
26078
  let examined = 0;
25979
26079
  let updated = 0;
26080
+ let wouldUpdate = 0;
25980
26081
  let mentioned = 0;
25981
- if (runIds.length === 0) return { examined, updated, mentioned };
26082
+ if (runIds.length === 0) {
26083
+ return isDryRun ? { examined, updated, wouldUpdate, mentioned } : { examined, updated, mentioned };
26084
+ }
25982
26085
  const projectDomains = effectiveDomains({
25983
26086
  canonicalDomain: project.canonicalDomain,
25984
26087
  ownedDomains: parseJsonColumn(project.ownedDomains, [])
@@ -25998,7 +26101,7 @@ function backfillProjectAnswerMentions(db, projectId) {
25998
26101
  competitorOverlap: querySnapshots.competitorOverlap,
25999
26102
  recommendedCompetitors: querySnapshots.recommendedCompetitors,
26000
26103
  rawResponse: querySnapshots.rawResponse
26001
- }).from(querySnapshots).where(inArray10(querySnapshots.runId, batchRunIds)).all();
26104
+ }).from(querySnapshots).where(inArray11(querySnapshots.runId, batchRunIds)).all();
26002
26105
  const pendingUpdates = [];
26003
26106
  for (const snapshot of snapshotRows) {
26004
26107
  examined++;
@@ -26041,29 +26144,36 @@ function backfillProjectAnswerMentions(db, projectId) {
26041
26144
  }
26042
26145
  }
26043
26146
  if (pendingUpdates.length > 0) {
26044
- db.transaction((tx) => {
26045
- for (const update of pendingUpdates) {
26046
- tx.update(querySnapshots).set(update.patch).where(eq34(querySnapshots.id, update.id)).run();
26047
- }
26048
- });
26049
- updated += pendingUpdates.length;
26147
+ if (isDryRun) {
26148
+ wouldUpdate += pendingUpdates.length;
26149
+ } else {
26150
+ db.transaction((tx) => {
26151
+ for (const update of pendingUpdates) {
26152
+ tx.update(querySnapshots).set(update.patch).where(eq34(querySnapshots.id, update.id)).run();
26153
+ }
26154
+ });
26155
+ updated += pendingUpdates.length;
26156
+ }
26050
26157
  }
26051
26158
  }
26052
- return { examined, updated, mentioned };
26159
+ return isDryRun ? { examined, updated, wouldUpdate, mentioned } : { examined, updated, mentioned };
26053
26160
  }
26054
26161
  async function backfillAnswerMentionsCommand(opts) {
26055
26162
  const config = loadConfig();
26056
26163
  const db = createClient(config.database);
26057
26164
  migrate(db);
26058
26165
  const projectFilter = opts?.project?.trim();
26166
+ const isDryRun = opts?.dryRun === true;
26059
26167
  const scopedProjects = projectFilter ? db.select().from(projects).where(eq34(projects.name, projectFilter)).all() : db.select().from(projects).all();
26060
26168
  let examined = 0;
26061
26169
  let updated = 0;
26170
+ let wouldUpdate = 0;
26062
26171
  let mentioned = 0;
26063
26172
  for (const project of scopedProjects) {
26064
- const result2 = backfillProjectAnswerMentions(db, project.id);
26173
+ const result2 = backfillProjectAnswerMentions(db, project.id, { dryRun: isDryRun });
26065
26174
  examined += result2.examined;
26066
26175
  updated += result2.updated;
26176
+ wouldUpdate += result2.wouldUpdate ?? 0;
26067
26177
  mentioned += result2.mentioned;
26068
26178
  }
26069
26179
  const result = {
@@ -26073,16 +26183,29 @@ async function backfillAnswerMentionsCommand(opts) {
26073
26183
  updated,
26074
26184
  mentioned
26075
26185
  };
26186
+ if (isDryRun) {
26187
+ result.dryRun = true;
26188
+ result.wouldUpdate = wouldUpdate;
26189
+ }
26076
26190
  if (opts?.format === "json") {
26077
26191
  console.log(JSON.stringify(result, null, 2));
26078
26192
  return;
26079
26193
  }
26080
- console.log("Answer mentions backfill complete.\n");
26081
- if (projectFilter) console.log(` Project: ${projectFilter}`);
26082
- console.log(` Projects: ${scopedProjects.length}`);
26083
- console.log(` Examined: ${examined}`);
26084
- console.log(` Updated: ${updated}`);
26085
- console.log(` Mentioned: ${mentioned}`);
26194
+ console.log(`Answer mentions backfill ${isDryRun ? "preview" : "complete"}.
26195
+ `);
26196
+ if (projectFilter) console.log(` Project: ${projectFilter}`);
26197
+ console.log(` Projects: ${scopedProjects.length}`);
26198
+ console.log(` Examined: ${examined}`);
26199
+ if (isDryRun) {
26200
+ console.log(` Would update: ${wouldUpdate}`);
26201
+ } else {
26202
+ console.log(` Updated: ${updated}`);
26203
+ }
26204
+ console.log(` Mentioned: ${mentioned}`);
26205
+ if (isDryRun) {
26206
+ console.log(`
26207
+ No DB writes performed. Re-run without --dry-run to apply.`);
26208
+ }
26086
26209
  }
26087
26210
  function readStoredGroundingSources(rawResponse) {
26088
26211
  const envelope = parseJsonColumn(rawResponse, {});
@@ -26101,7 +26224,7 @@ function readStoredGroundingSources(rawResponse) {
26101
26224
  return result;
26102
26225
  }
26103
26226
  async function backfillInsightsCommand(project, opts) {
26104
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-7AWRUNI2.js");
26227
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-ZZP3TVZZ.js");
26105
26228
  const config = loadConfig();
26106
26229
  const db = createClient(config.database);
26107
26230
  migrate(db);
@@ -26419,7 +26542,7 @@ var Scheduler = class {
26419
26542
  };
26420
26543
 
26421
26544
  // src/notifier.ts
26422
- import { eq as eq36, desc as desc16, and as and26, inArray as inArray11, or as or5 } from "drizzle-orm";
26545
+ import { eq as eq36, desc as desc16, and as and26, inArray as inArray12, or as or5 } from "drizzle-orm";
26423
26546
  import crypto31 from "crypto";
26424
26547
  var log10 = createLogger("Notifier");
26425
26548
  var Notifier = class {
@@ -26572,13 +26695,13 @@ var Notifier = class {
26572
26695
  provider: querySnapshots.provider,
26573
26696
  location: querySnapshots.location,
26574
26697
  citationState: querySnapshots.citationState
26575
- }).from(querySnapshots).leftJoin(queries, eq36(querySnapshots.queryId, queries.id)).where(inArray11(querySnapshots.runId, currentRunIds)).all();
26698
+ }).from(querySnapshots).leftJoin(queries, eq36(querySnapshots.queryId, queries.id)).where(inArray12(querySnapshots.runId, currentRunIds)).all();
26576
26699
  const previousSnapshots = this.db.select({
26577
26700
  queryId: querySnapshots.queryId,
26578
26701
  provider: querySnapshots.provider,
26579
26702
  location: querySnapshots.location,
26580
26703
  citationState: querySnapshots.citationState
26581
- }).from(querySnapshots).where(inArray11(querySnapshots.runId, previousRunIds)).all();
26704
+ }).from(querySnapshots).where(inArray12(querySnapshots.runId, previousRunIds)).all();
26582
26705
  const prevMap = /* @__PURE__ */ new Map();
26583
26706
  for (const s of previousSnapshots) {
26584
26707
  if (s.queryId == null) continue;
@@ -27091,7 +27214,7 @@ function resolveSessionProviderAndModel(config, opts) {
27091
27214
 
27092
27215
  // src/agent/memory-store.ts
27093
27216
  import crypto32 from "crypto";
27094
- import { and as and27, desc as desc17, eq as eq38, like as like2, sql as sql14 } from "drizzle-orm";
27217
+ import { and as and27, desc as desc17, eq as eq38, like as like2, sql as sql15 } from "drizzle-orm";
27095
27218
  var COMPACTION_KEY_PREFIX = "compaction:";
27096
27219
  var COMPACTION_NOTES_PER_SESSION = 3;
27097
27220
  function rowToDto2(row) {
@@ -27177,7 +27300,7 @@ function writeCompactionNote(db, args) {
27177
27300
  ).orderBy(desc17(agentMemory.updatedAt)).all();
27178
27301
  const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
27179
27302
  if (stale.length > 0) {
27180
- tx.delete(agentMemory).where(sql14`${agentMemory.id} IN (${sql14.join(stale.map((s) => sql14`${s}`), sql14`, `)})`).run();
27303
+ tx.delete(agentMemory).where(sql15`${agentMemory.id} IN (${sql15.join(stale.map((s) => sql15`${s}`), sql15`, `)})`).run();
27181
27304
  }
27182
27305
  const row = tx.select().from(agentMemory).where(and27(eq38(agentMemory.projectId, args.projectId), eq38(agentMemory.key, key))).get();
27183
27306
  if (row) inserted = rowToDto2(row);