@canonry/canonry 4.180.2 → 4.180.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -1
  2. package/assets/assets/{AuditHistoryPanel-Cf-jEEKZ.js → AuditHistoryPanel-CdM_9qX1.js} +1 -1
  3. package/assets/assets/{BacklinksPage-oMcS6Hc_.js → BacklinksPage-DWWMPSPn.js} +1 -1
  4. package/assets/assets/{HistoryPage-CDQOpxi0.js → HistoryPage-m7TtGFBQ.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-MoKFTmrS.js → MeasurementPropertyPage-CYBfBtEz.js} +1 -1
  6. package/assets/assets/{ProjectPage-CtD1AwXE.js → ProjectPage-CNPcNrIR.js} +1 -1
  7. package/assets/assets/{RunRow-CPekiscK.js → RunRow-O4u505Fc.js} +1 -1
  8. package/assets/assets/{RunsPage-hn05ImCv.js → RunsPage-CCzFT4X0.js} +1 -1
  9. package/assets/assets/{SettingsPage-BOTYXtfF.js → SettingsPage-DOmE3JbU.js} +1 -1
  10. package/assets/assets/{SiteHealthSection-FwMZ11F8.js → SiteHealthSection-BZFVE2h9.js} +3 -3
  11. package/assets/assets/{TrafficPage-f2v6A_uk.js → TrafficPage-DYxOEVbW.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-nhWRLHiu.js → TrafficSourceDetailPage-Qd5Zpx_8.js} +1 -1
  13. package/assets/assets/{extract-error-message-Cp1YeVEr.js → extract-error-message-B0bdIDVc.js} +1 -1
  14. package/assets/assets/{index-VmxEHABq.js → index-HmdbSL4h.js} +17 -17
  15. package/assets/assets/{react-sigma_core.esm.min-fnm2MkMi.js → react-sigma_core.esm.min-DXCrX1Ie.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-665YENL2.js → chunk-2J65ALIR.js} +23 -5
  18. package/dist/{chunk-4XHQ2GCN.js → chunk-DK23XZ6C.js} +2 -2
  19. package/dist/{chunk-DA25LOTW.js → chunk-JJIESVNL.js} +1 -1
  20. package/dist/{chunk-6IR32MIH.js → chunk-UESVOQRS.js} +10 -2
  21. package/dist/{chunk-44LVINXQ.js → chunk-V4QGOJNH.js} +681 -452
  22. package/dist/cli.js +17 -6
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-RNBODYMZ.js → intelligence-service-XNAKDGDQ.js} +2 -2
  25. package/dist/mcp.js +3 -3
  26. package/package.json +11 -11
@@ -141,6 +141,7 @@ import {
141
141
  adsAdGroupCreateRequestSchema,
142
142
  adsAdGroupUpdateRequestSchema,
143
143
  adsAdUpdateRequestSchema,
144
+ adsArchiveRequestSchema,
144
145
  adsCampaignBiddingTypeSchema,
145
146
  adsCampaignCreateRequestSchema,
146
147
  adsCampaignListResponseSchema,
@@ -642,10 +643,10 @@ import {
642
643
  wordpressSchemaDeployResultDtoSchema,
643
644
  wordpressSchemaStatusResultDtoSchema,
644
645
  wordpressStatusDtoSchema
645
- } from "./chunk-6IR32MIH.js";
646
+ } from "./chunk-UESVOQRS.js";
646
647
 
647
648
  // src/intelligence-service.ts
648
- import { eq as eq62, desc as desc27, asc as asc11, and as and51, ne as ne8, or as or14, inArray as inArray22, gte as gte14, lte as lte12, isNull as isNull9, sql as sql25, exists } from "drizzle-orm";
649
+ import { eq as eq62, desc as desc27, asc as asc11, and as and51, ne as ne8, or as or14, inArray as inArray22, gte as gte15, lte as lte13, isNull as isNull9, sql as sql25, exists } from "drizzle-orm";
649
650
 
650
651
  // ../db/src/client.ts
651
652
  import { mkdirSync } from "fs";
@@ -22587,16 +22588,153 @@ function nonSubresourceReferralPathCondition() {
22587
22588
  }
22588
22589
 
22589
22590
  // ../api-routes/src/report.ts
22590
- import { and as and24, desc as desc12, eq as eq29, gte as gte5, inArray as inArray12, lt as lt6, lte as lte3, ne as ne3, or as or4, sql as sql10 } from "drizzle-orm";
22591
+ import { and as and24, desc as desc12, eq as eq29, gte as gte6, inArray as inArray12, lt as lt6, lte as lte4, ne as ne3, or as or4, sql as sql10 } from "drizzle-orm";
22591
22592
 
22592
22593
  // ../api-routes/src/content.ts
22593
22594
  import crypto22 from "crypto";
22594
- import { and as and22, desc as desc11, eq as eq27 } from "drizzle-orm";
22595
+ import { and as and23, desc as desc11, eq as eq28 } from "drizzle-orm";
22596
+
22597
+ // ../api-routes/src/content-data.ts
22598
+ import { and as and22, eq as eq27, desc as desc10, gte as gte5, inArray as inArray11, lte as lte3 } from "drizzle-orm";
22599
+
22600
+ // ../api-routes/src/gsc-totals.ts
22601
+ import { and as and21, asc as asc4, eq as eq26, sql as sql9, max, min } from "drizzle-orm";
22602
+ var WINDOW_DAYS = { "7d": 7, "30d": 30, "90d": 90 };
22603
+ function resolveGscWindowRange(window, latestDataDate, today) {
22604
+ if (window === "all") {
22605
+ return {
22606
+ startDate: null,
22607
+ endDate: latestDataDate,
22608
+ latestDataDate,
22609
+ daysSinceLatestData: gscDaysSinceLatestData(latestDataDate, today)
22610
+ };
22611
+ }
22612
+ return resolveGscWindowDays(WINDOW_DAYS[window], latestDataDate, today);
22613
+ }
22614
+ function resolveGscWindowDays(days, latestDataDate, today) {
22615
+ if (latestDataDate === null) {
22616
+ return {
22617
+ startDate: shiftIsoCalendarDate(today, -days),
22618
+ endDate: null,
22619
+ latestDataDate: null,
22620
+ daysSinceLatestData: null
22621
+ };
22622
+ }
22623
+ return {
22624
+ startDate: shiftIsoCalendarDate(latestDataDate, -(days - 1)),
22625
+ endDate: latestDataDate,
22626
+ latestDataDate,
22627
+ daysSinceLatestData: gscDaysSinceLatestData(latestDataDate, today)
22628
+ };
22629
+ }
22630
+ function gscDaysSinceLatestData(latestDataDate, today) {
22631
+ if (latestDataDate === null) return null;
22632
+ return Math.max(0, Math.round(
22633
+ (Date.parse(`${today}T00:00:00Z`) - Date.parse(`${latestDataDate}T00:00:00Z`)) / 864e5
22634
+ ));
22635
+ }
22636
+ function readLatestGscDataDate(db, projectId) {
22637
+ const watermark = db.select({ through: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq26(gscDataWatermarks.projectId, projectId)).get()?.through ?? null;
22638
+ const property = db.select({ latest: max(gscDailyTotals.date) }).from(gscDailyTotals).where(eq26(gscDailyTotals.projectId, projectId)).get()?.latest ?? null;
22639
+ const dimensioned = db.select({ latest: max(gscSearchData.date) }).from(gscSearchData).where(eq26(gscSearchData.projectId, projectId)).get()?.latest ?? null;
22640
+ return [watermark, property, dimensioned].filter((d) => d !== null).reduce((maxDate, d) => maxDate === null || d > maxDate ? d : maxDate, null);
22641
+ }
22642
+ function readEarliestGscDataDate(db, projectId) {
22643
+ const property = db.select({ earliest: min(gscDailyTotals.date) }).from(gscDailyTotals).where(eq26(gscDailyTotals.projectId, projectId)).get()?.earliest ?? null;
22644
+ const dimensioned = db.select({ earliest: min(gscSearchData.date) }).from(gscSearchData).where(eq26(gscSearchData.projectId, projectId)).get()?.earliest ?? null;
22645
+ return [property, dimensioned].filter((d) => d !== null).reduce((earliest, d) => earliest === null || d < earliest ? d : earliest, null);
22646
+ }
22647
+ function readGscDailyTotals(db, projectId, startDate, endDate) {
22648
+ const rows = db.select({
22649
+ date: gscDailyTotals.date,
22650
+ clicks: gscDailyTotals.clicks,
22651
+ impressions: gscDailyTotals.impressions,
22652
+ position: gscDailyTotals.position
22653
+ }).from(gscDailyTotals).where(
22654
+ and21(
22655
+ eq26(gscDailyTotals.projectId, projectId),
22656
+ sql9`${gscDailyTotals.date} >= ${startDate}`,
22657
+ sql9`${gscDailyTotals.date} <= ${endDate}`
22658
+ )
22659
+ ).orderBy(asc4(gscDailyTotals.date)).all();
22660
+ return rows.map((r) => {
22661
+ const position = Number(r.position);
22662
+ return {
22663
+ date: r.date,
22664
+ clicks: r.clicks,
22665
+ impressions: r.impressions,
22666
+ position: Number.isFinite(position) ? position : 0
22667
+ };
22668
+ });
22669
+ }
22670
+ function mergeGscDailyTotalsWithFallback(propertyTotals, dimensionedFallback) {
22671
+ const byDate = /* @__PURE__ */ new Map();
22672
+ for (const row of dimensionedFallback) byDate.set(row.date, row);
22673
+ for (const row of propertyTotals) byDate.set(row.date, row);
22674
+ return [...byDate.values()].sort((a, b) => a.date.localeCompare(b.date));
22675
+ }
22676
+ function readGscQueryDailyRows(db, projectId, startDate, endDate) {
22677
+ const rows = db.select({
22678
+ date: gscQueryDailyTotals.date,
22679
+ query: gscQueryDailyTotals.query,
22680
+ clicks: gscQueryDailyTotals.clicks,
22681
+ impressions: gscQueryDailyTotals.impressions,
22682
+ position: gscQueryDailyTotals.position
22683
+ }).from(gscQueryDailyTotals).where(
22684
+ and21(
22685
+ eq26(gscQueryDailyTotals.projectId, projectId),
22686
+ sql9`${gscQueryDailyTotals.date} >= ${startDate}`,
22687
+ sql9`${gscQueryDailyTotals.date} <= ${endDate}`
22688
+ )
22689
+ ).all();
22690
+ return rows.map((r) => {
22691
+ const position = Number(r.position);
22692
+ return {
22693
+ date: r.date,
22694
+ query: r.query,
22695
+ clicks: r.clicks,
22696
+ impressions: r.impressions,
22697
+ position: Number.isFinite(position) ? position : 0
22698
+ };
22699
+ });
22700
+ }
22701
+ function mergeGscQueryTotalsWithFallback(accurateDays, fallbackDays) {
22702
+ const dayKey = (r) => `${r.date}\0${r.query}`;
22703
+ const byDay = /* @__PURE__ */ new Map();
22704
+ for (const row of fallbackDays) byDay.set(dayKey(row), { row, accurate: false });
22705
+ for (const row of accurateDays) byDay.set(dayKey(row), { row, accurate: true });
22706
+ const byQuery = /* @__PURE__ */ new Map();
22707
+ for (const { row, accurate } of byDay.values()) {
22708
+ const acc = byQuery.get(row.query) ?? {
22709
+ clicks: 0,
22710
+ impressions: 0,
22711
+ weightedPositionSum: 0,
22712
+ positionSum: 0,
22713
+ positionDays: 0,
22714
+ sawAccurate: false,
22715
+ sawLegacy: false
22716
+ };
22717
+ acc.clicks += row.clicks;
22718
+ acc.impressions += row.impressions;
22719
+ acc.weightedPositionSum += row.position * row.impressions;
22720
+ acc.positionSum += row.position;
22721
+ acc.positionDays += 1;
22722
+ if (accurate) acc.sawAccurate = true;
22723
+ else acc.sawLegacy = true;
22724
+ byQuery.set(row.query, acc);
22725
+ }
22726
+ return [...byQuery.entries()].map(([query, acc]) => ({
22727
+ query,
22728
+ clicks: acc.clicks,
22729
+ impressions: acc.impressions,
22730
+ position: acc.impressions > 0 ? acc.weightedPositionSum / acc.impressions : acc.positionDays > 0 ? acc.positionSum / acc.positionDays : 0,
22731
+ source: acc.sawAccurate && acc.sawLegacy ? "mixed" : acc.sawAccurate ? "google" : "page-summed"
22732
+ }));
22733
+ }
22595
22734
 
22596
22735
  // ../api-routes/src/content-data.ts
22597
- import { and as and21, eq as eq26, desc as desc10, inArray as inArray11 } from "drizzle-orm";
22598
22736
  var RECENT_RUNS_WINDOW = 5;
22599
- function loadOrchestratorInput(db, project, locationFilter = void 0) {
22737
+ function loadOrchestratorInput(db, project, locationFilter = void 0, gscWindowDays = DEFAULT_CONTENT_GSC_WINDOW_DAYS) {
22600
22738
  const projectId = project.id;
22601
22739
  const ownDomain = hostOf(project.canonicalDomain) ?? "";
22602
22740
  const ownedDomains = project.ownedDomains ?? [];
@@ -22615,7 +22753,8 @@ function loadOrchestratorInput(db, project, locationFilter = void 0) {
22615
22753
  recentRunIds,
22616
22754
  latestRunId,
22617
22755
  ourDomains,
22618
- competitorSet
22756
+ competitorSet,
22757
+ gscWindowDays
22619
22758
  });
22620
22759
  const inventory = buildInventory({
22621
22760
  gscPages: listGscPagesForProject(db, projectId),
@@ -22643,7 +22782,7 @@ function loadOrchestratorInput(db, project, locationFilter = void 0) {
22643
22782
  };
22644
22783
  }
22645
22784
  function loadDomainClasses(db, projectId) {
22646
- const rows = db.select({ domain: domainClassifications.domain, competitorType: domainClassifications.competitorType }).from(domainClassifications).where(eq26(domainClassifications.projectId, projectId)).all();
22785
+ const rows = db.select({ domain: domainClassifications.domain, competitorType: domainClassifications.competitorType }).from(domainClassifications).where(eq27(domainClassifications.projectId, projectId)).all();
22647
22786
  return new Map(rows.map((r) => [hostOf(r.domain) ?? "", r.competitorType]));
22648
22787
  }
22649
22788
  function buildQueryIntentModifiers(project, locationFilter) {
@@ -22715,18 +22854,18 @@ var US_REGION_ABBREVIATIONS = {
22715
22854
  wyoming: "wy"
22716
22855
  };
22717
22856
  function listQueries(db, projectId) {
22718
- const rows = db.select({ text: queries.query }).from(queries).where(eq26(queries.projectId, projectId)).all();
22857
+ const rows = db.select({ text: queries.query }).from(queries).where(eq27(queries.projectId, projectId)).all();
22719
22858
  return rows.map((r) => r.text);
22720
22859
  }
22721
22860
  function listCompetitorDomains(db, projectId) {
22722
- const rows = db.select({ domain: competitors.domain }).from(competitors).where(eq26(competitors.projectId, projectId)).all();
22861
+ const rows = db.select({ domain: competitors.domain }).from(competitors).where(eq27(competitors.projectId, projectId)).all();
22723
22862
  return rows.map((r) => r.domain);
22724
22863
  }
22725
22864
  function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter) {
22726
22865
  const rows = db.select({ id: runs.id, location: runs.location }).from(runs).where(
22727
- and21(
22728
- eq26(runs.projectId, projectId),
22729
- eq26(runs.kind, RunKinds["answer-visibility"]),
22866
+ and22(
22867
+ eq27(runs.projectId, projectId),
22868
+ eq27(runs.kind, RunKinds["answer-visibility"]),
22730
22869
  // Queued/running/failed/cancelled runs may have partial or no
22731
22870
  // snapshots; including them risks pointing latestRunId at a run with
22732
22871
  // no usable evidence.
@@ -22740,22 +22879,22 @@ function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter)
22740
22879
  return filtered.slice(0, limit).map((r) => r.id);
22741
22880
  }
22742
22881
  function lookupRunTimestamp(db, runId) {
22743
- const row = db.select({ createdAt: runs.createdAt }).from(runs).where(eq26(runs.id, runId)).get();
22882
+ const row = db.select({ createdAt: runs.createdAt }).from(runs).where(eq27(runs.id, runId)).get();
22744
22883
  return row?.createdAt ?? "";
22745
22884
  }
22746
22885
  function listGscPagesForProject(db, projectId) {
22747
- const rows = db.selectDistinct({ page: gscSearchData.page }).from(gscSearchData).where(eq26(gscSearchData.projectId, projectId)).all();
22886
+ const rows = db.selectDistinct({ page: gscSearchData.page }).from(gscSearchData).where(eq27(gscSearchData.projectId, projectId)).all();
22748
22887
  return rows.map((r) => r.page);
22749
22888
  }
22750
22889
  function listGa4LandingPagesForProject(db, projectId) {
22751
- const rows = db.selectDistinct({ landingPage: gaTrafficSnapshots.landingPage }).from(gaTrafficSnapshots).where(eq26(gaTrafficSnapshots.projectId, projectId)).all();
22890
+ const rows = db.selectDistinct({ landingPage: gaTrafficSnapshots.landingPage }).from(gaTrafficSnapshots).where(eq27(gaTrafficSnapshots.projectId, projectId)).all();
22752
22891
  return rows.map((r) => r.landingPage);
22753
22892
  }
22754
22893
  function buildGaTrafficByPage(db, projectId) {
22755
22894
  const rows = db.select({
22756
22895
  landingPage: gaTrafficSnapshots.landingPage,
22757
22896
  sessions: gaTrafficSnapshots.sessions
22758
- }).from(gaTrafficSnapshots).where(eq26(gaTrafficSnapshots.projectId, projectId)).all();
22897
+ }).from(gaTrafficSnapshots).where(eq27(gaTrafficSnapshots.projectId, projectId)).all();
22759
22898
  const map = /* @__PURE__ */ new Map();
22760
22899
  for (const row of rows) {
22761
22900
  const path8 = extractPath2(row.landingPage);
@@ -22766,9 +22905,9 @@ function buildGaTrafficByPage(db, projectId) {
22766
22905
  }
22767
22906
  function sumAiReferralSessions(db, projectId) {
22768
22907
  const rows = db.select({ sessions: gaAiReferrals.sessions }).from(gaAiReferrals).where(
22769
- and21(
22770
- eq26(gaAiReferrals.projectId, projectId),
22771
- eq26(gaAiReferrals.sourceDimension, "session")
22908
+ and22(
22909
+ eq27(gaAiReferrals.projectId, projectId),
22910
+ eq27(gaAiReferrals.sourceDimension, "session")
22772
22911
  )
22773
22912
  ).all();
22774
22913
  return rows.reduce((acc, r) => acc + (r.sessions ?? 0), 0);
@@ -22777,7 +22916,7 @@ function buildCandidateQueries(opts) {
22777
22916
  if (opts.candidateQueryStrings.length === 0 || opts.recentRunIds.length === 0) {
22778
22917
  return opts.candidateQueryStrings.map((query) => emptyCandidate(query));
22779
22918
  }
22780
- const queryRows = opts.db.select({ id: queries.id, text: queries.query }).from(queries).where(eq26(queries.projectId, opts.projectId)).all();
22919
+ const queryRows = opts.db.select({ id: queries.id, text: queries.query }).from(queries).where(eq27(queries.projectId, opts.projectId)).all();
22781
22920
  const queryIdByText = new Map(queryRows.map((r) => [r.text, r.id]));
22782
22921
  const candidateQueryIds = opts.candidateQueryStrings.map((q) => queryIdByText.get(q)).filter((id) => Boolean(id));
22783
22922
  const snapshotRows = filterTrackedSnapshots(opts.db.select().from(querySnapshots).where(inArray11(querySnapshots.runId, opts.recentRunIds)).all()).filter((r) => candidateQueryIds.includes(r.queryId));
@@ -22787,8 +22926,19 @@ function buildCandidateQueries(opts) {
22787
22926
  list.push(row);
22788
22927
  snapshotsByQuery.set(row.queryId, list);
22789
22928
  }
22790
- const gscRows = opts.db.select().from(gscSearchData).where(eq26(gscSearchData.projectId, opts.projectId)).all();
22791
- const gscByQuery = aggregateGscByQuery(gscRows);
22929
+ const gscWindow = resolveContentGscWindow(opts.db, opts.projectId, opts.gscWindowDays);
22930
+ const pageRows = gscWindow ? opts.db.select().from(gscSearchData).where(and22(
22931
+ eq27(gscSearchData.projectId, opts.projectId),
22932
+ gte5(gscSearchData.date, gscWindow.startDate),
22933
+ lte3(gscSearchData.date, gscWindow.endDate)
22934
+ )).all() : [];
22935
+ const gscByQuery = aggregateGscByQuery(
22936
+ pageRows,
22937
+ gscWindow ? mergeGscQueryTotalsWithFallback(
22938
+ readGscQueryDailyRows(opts.db, opts.projectId, gscWindow.startDate, gscWindow.endDate),
22939
+ pageDayFallback(pageRows)
22940
+ ) : []
22941
+ );
22792
22942
  return opts.candidateQueryStrings.map((query) => {
22793
22943
  const queryId = queryIdByText.get(query);
22794
22944
  const snaps = queryId ? snapshotsByQuery.get(queryId) ?? [] : [];
@@ -22803,7 +22953,35 @@ function buildCandidateQueries(opts) {
22803
22953
  });
22804
22954
  });
22805
22955
  }
22806
- function aggregateGscByQuery(rows) {
22956
+ var DEFAULT_CONTENT_GSC_WINDOW_DAYS = 30;
22957
+ function resolveContentGscWindow(db, projectId, windowDays2) {
22958
+ const endDate = readLatestGscDataDate(db, projectId);
22959
+ if (!endDate) return null;
22960
+ const end = /* @__PURE__ */ new Date(`${endDate}T00:00:00Z`);
22961
+ end.setUTCDate(end.getUTCDate() - (Math.max(1, windowDays2) - 1));
22962
+ return { startDate: end.toISOString().slice(0, 10), endDate };
22963
+ }
22964
+ function pageDayFallback(rows) {
22965
+ const byDay = /* @__PURE__ */ new Map();
22966
+ for (const r of rows) {
22967
+ if (!r.query) continue;
22968
+ const key = `${r.date}\0${r.query}`;
22969
+ const acc = byDay.get(key) ?? { date: r.date, query: r.query, clicks: 0, impressions: 0, weighted: 0 };
22970
+ acc.clicks += r.clicks;
22971
+ acc.impressions += r.impressions;
22972
+ acc.weighted += (Number(r.position) || 0) * r.impressions;
22973
+ byDay.set(key, acc);
22974
+ }
22975
+ return [...byDay.values()].map((a) => ({
22976
+ date: a.date,
22977
+ query: a.query,
22978
+ clicks: a.clicks,
22979
+ impressions: a.impressions,
22980
+ position: a.impressions > 0 ? a.weighted / a.impressions : 0
22981
+ }));
22982
+ }
22983
+ function aggregateGscByQuery(rows, queryTotals = []) {
22984
+ const accurateByQuery = new Map(queryTotals.map((t) => [t.query, t]));
22807
22985
  const accumulators = /* @__PURE__ */ new Map();
22808
22986
  for (const r of rows) {
22809
22987
  const page2 = extractPath2(r.page);
@@ -22829,12 +23007,16 @@ function aggregateGscByQuery(rows) {
22829
23007
  }
22830
23008
  const byQuery = /* @__PURE__ */ new Map();
22831
23009
  for (const [query, acc] of accumulators) {
23010
+ const accurate = accurateByQuery.get(query);
23011
+ const impressions = accurate ? accurate.impressions : acc.totalImpressions;
23012
+ const clicks = accurate ? accurate.clicks : acc.totalClicks;
23013
+ const position = accurate ? accurate.position : acc.totalImpressions > 0 ? acc.weightedPositionSum / acc.totalImpressions : 0;
22832
23014
  byQuery.set(query, {
22833
23015
  page: acc.bestPage,
22834
- position: acc.totalImpressions > 0 ? acc.weightedPositionSum / acc.totalImpressions : 0,
22835
- impressions: acc.totalImpressions,
22836
- clicks: acc.totalClicks,
22837
- ctr: acc.totalImpressions > 0 ? acc.totalClicks / acc.totalImpressions : 0
23016
+ position,
23017
+ impressions,
23018
+ clicks,
23019
+ ctr: impressions > 0 ? clicks / impressions : 0
22838
23020
  });
22839
23021
  }
22840
23022
  return byQuery;
@@ -22986,7 +23168,7 @@ function extractPath2(url) {
22986
23168
 
22987
23169
  // ../api-routes/src/content.ts
22988
23170
  function loadDismissedTargetRefs(db, projectId) {
22989
- const rows = db.select({ targetRef: contentTargetDismissals.targetRef }).from(contentTargetDismissals).where(eq27(contentTargetDismissals.projectId, projectId)).all();
23171
+ const rows = db.select({ targetRef: contentTargetDismissals.targetRef }).from(contentTargetDismissals).where(eq28(contentTargetDismissals.projectId, projectId)).all();
22990
23172
  return new Set(rows.map((r) => r.targetRef));
22991
23173
  }
22992
23174
  function formatDismissalRow(row) {
@@ -23081,7 +23263,7 @@ async function contentRoutes(app, opts = {}) {
23081
23263
  });
23082
23264
  app.get("/projects/:name/content/dismissals", async (request) => {
23083
23265
  const project = resolveProject(app.db, request.params.name);
23084
- const rows = app.db.select().from(contentTargetDismissals).where(eq27(contentTargetDismissals.projectId, project.id)).orderBy(contentTargetDismissals.dismissedAt).all();
23266
+ const rows = app.db.select().from(contentTargetDismissals).where(eq28(contentTargetDismissals.projectId, project.id)).orderBy(contentTargetDismissals.dismissedAt).all();
23085
23267
  const response = {
23086
23268
  dismissals: rows.map(formatDismissalRow)
23087
23269
  };
@@ -23110,9 +23292,9 @@ async function contentRoutes(app, opts = {}) {
23110
23292
  dismissedAt: now
23111
23293
  }
23112
23294
  }).run();
23113
- const row = app.db.select().from(contentTargetDismissals).where(and22(
23114
- eq27(contentTargetDismissals.projectId, project.id),
23115
- eq27(contentTargetDismissals.targetRef, targetRef)
23295
+ const row = app.db.select().from(contentTargetDismissals).where(and23(
23296
+ eq28(contentTargetDismissals.projectId, project.id),
23297
+ eq28(contentTargetDismissals.targetRef, targetRef)
23116
23298
  )).get();
23117
23299
  if (!row) throw notFound("contentTargetDismissal", targetRef);
23118
23300
  return formatDismissalRow(row);
@@ -23120,9 +23302,9 @@ async function contentRoutes(app, opts = {}) {
23120
23302
  app.delete("/projects/:name/content/dismissals/:targetRef", async (request, reply) => {
23121
23303
  const project = resolveProject(app.db, request.params.name);
23122
23304
  const { targetRef } = request.params;
23123
- const result = app.db.delete(contentTargetDismissals).where(and22(
23124
- eq27(contentTargetDismissals.projectId, project.id),
23125
- eq27(contentTargetDismissals.targetRef, targetRef)
23305
+ const result = app.db.delete(contentTargetDismissals).where(and23(
23306
+ eq28(contentTargetDismissals.projectId, project.id),
23307
+ eq28(contentTargetDismissals.targetRef, targetRef)
23126
23308
  )).run();
23127
23309
  if (result.changes === 0) {
23128
23310
  throw notFound("contentTargetDismissal", targetRef);
@@ -23131,9 +23313,9 @@ async function contentRoutes(app, opts = {}) {
23131
23313
  });
23132
23314
  app.get("/projects/:name/content/recommendations/:targetRef/analysis", async (request, reply) => {
23133
23315
  const project = resolveProject(app.db, request.params.name);
23134
- const row = app.db.select().from(recommendationExplanations).where(and22(
23135
- eq27(recommendationExplanations.projectId, project.id),
23136
- eq27(recommendationExplanations.targetRef, request.params.targetRef)
23316
+ const row = app.db.select().from(recommendationExplanations).where(and23(
23317
+ eq28(recommendationExplanations.projectId, project.id),
23318
+ eq28(recommendationExplanations.targetRef, request.params.targetRef)
23137
23319
  )).orderBy(desc11(recommendationExplanations.generatedAt)).limit(1).get();
23138
23320
  if (!row) {
23139
23321
  return reply.status(404).send({ error: { code: "NOT_FOUND", message: "No cached explanation for this targetRef." } });
@@ -23159,9 +23341,9 @@ async function contentRoutes(app, opts = {}) {
23159
23341
  throw notFound("contentRecommendation", targetRef);
23160
23342
  }
23161
23343
  if (!body.forceRefresh) {
23162
- const cached = app.db.select().from(recommendationExplanations).where(and22(
23163
- eq27(recommendationExplanations.projectId, project.id),
23164
- eq27(recommendationExplanations.targetRef, targetRef)
23344
+ const cached = app.db.select().from(recommendationExplanations).where(and23(
23345
+ eq28(recommendationExplanations.projectId, project.id),
23346
+ eq28(recommendationExplanations.targetRef, targetRef)
23165
23347
  )).orderBy(desc11(recommendationExplanations.generatedAt)).limit(1).get();
23166
23348
  if (cached) return reply.send(formatExplanationRow(cached));
23167
23349
  }
@@ -23198,10 +23380,10 @@ async function contentRoutes(app, opts = {}) {
23198
23380
  generatedAt: now
23199
23381
  }
23200
23382
  }).run();
23201
- const row = app.db.select().from(recommendationExplanations).where(and22(
23202
- eq27(recommendationExplanations.projectId, project.id),
23203
- eq27(recommendationExplanations.targetRef, targetRef),
23204
- eq27(recommendationExplanations.promptVersion, result.promptVersion)
23383
+ const row = app.db.select().from(recommendationExplanations).where(and23(
23384
+ eq28(recommendationExplanations.projectId, project.id),
23385
+ eq28(recommendationExplanations.targetRef, targetRef),
23386
+ eq28(recommendationExplanations.promptVersion, result.promptVersion)
23205
23387
  )).get();
23206
23388
  if (!row) throw notFound("recommendationExplanation", targetRef);
23207
23389
  return reply.send(formatExplanationRow(row));
@@ -23282,17 +23464,17 @@ async function contentRoutes(app, opts = {}) {
23282
23464
  generatedAt: now
23283
23465
  }
23284
23466
  }).run();
23285
- const row = app.db.select().from(recommendationBriefs).where(and22(
23286
- eq27(recommendationBriefs.projectId, project.id),
23287
- eq27(recommendationBriefs.targetRef, targetRef),
23288
- eq27(recommendationBriefs.promptVersion, result.promptVersion)
23467
+ const row = app.db.select().from(recommendationBriefs).where(and23(
23468
+ eq28(recommendationBriefs.projectId, project.id),
23469
+ eq28(recommendationBriefs.targetRef, targetRef),
23470
+ eq28(recommendationBriefs.promptVersion, result.promptVersion)
23289
23471
  )).get();
23290
23472
  if (!row) throw notFound("recommendationBrief", targetRef);
23291
23473
  return reply.send(formatBriefRow(row));
23292
23474
  });
23293
23475
  app.get("/projects/:name/content/domain-classifications", async (request) => {
23294
23476
  const project = resolveProject(app.db, request.params.name);
23295
- const rows = app.db.select().from(domainClassifications).where(eq27(domainClassifications.projectId, project.id)).orderBy(desc11(domainClassifications.hits)).all();
23477
+ const rows = app.db.select().from(domainClassifications).where(eq28(domainClassifications.projectId, project.id)).orderBy(desc11(domainClassifications.hits)).all();
23296
23478
  const response = {
23297
23479
  classifications: rows.map((r) => ({
23298
23480
  domain: r.domain,
@@ -23306,13 +23488,13 @@ async function contentRoutes(app, opts = {}) {
23306
23488
  }
23307
23489
  function lookupCachedBrief(db, projectId, targetRef, promptVersion) {
23308
23490
  const conditions = [
23309
- eq27(recommendationBriefs.projectId, projectId),
23310
- eq27(recommendationBriefs.targetRef, targetRef)
23491
+ eq28(recommendationBriefs.projectId, projectId),
23492
+ eq28(recommendationBriefs.targetRef, targetRef)
23311
23493
  ];
23312
23494
  if (promptVersion !== void 0) {
23313
- conditions.push(eq27(recommendationBriefs.promptVersion, promptVersion));
23495
+ conditions.push(eq28(recommendationBriefs.promptVersion, promptVersion));
23314
23496
  }
23315
- return db.select().from(recommendationBriefs).where(and22(...conditions)).orderBy(desc11(recommendationBriefs.generatedAt)).limit(1).get();
23497
+ return db.select().from(recommendationBriefs).where(and23(...conditions)).orderBy(desc11(recommendationBriefs.generatedAt)).limit(1).get();
23316
23498
  }
23317
23499
  function parseLimitParam(raw) {
23318
23500
  if (raw === void 0) return void 0;
@@ -23337,141 +23519,6 @@ function winnabilityClassRank(winnabilityClass) {
23337
23519
  return winnabilityClass === WinnabilityClasses.ownable ? 0 : 1;
23338
23520
  }
23339
23521
 
23340
- // ../api-routes/src/gsc-totals.ts
23341
- import { and as and23, asc as asc4, eq as eq28, sql as sql9, max, min } from "drizzle-orm";
23342
- var WINDOW_DAYS = { "7d": 7, "30d": 30, "90d": 90 };
23343
- function resolveGscWindowRange(window, latestDataDate, today) {
23344
- if (window === "all") {
23345
- return {
23346
- startDate: null,
23347
- endDate: latestDataDate,
23348
- latestDataDate,
23349
- daysSinceLatestData: gscDaysSinceLatestData(latestDataDate, today)
23350
- };
23351
- }
23352
- return resolveGscWindowDays(WINDOW_DAYS[window], latestDataDate, today);
23353
- }
23354
- function resolveGscWindowDays(days, latestDataDate, today) {
23355
- if (latestDataDate === null) {
23356
- return {
23357
- startDate: shiftIsoCalendarDate(today, -days),
23358
- endDate: null,
23359
- latestDataDate: null,
23360
- daysSinceLatestData: null
23361
- };
23362
- }
23363
- return {
23364
- startDate: shiftIsoCalendarDate(latestDataDate, -(days - 1)),
23365
- endDate: latestDataDate,
23366
- latestDataDate,
23367
- daysSinceLatestData: gscDaysSinceLatestData(latestDataDate, today)
23368
- };
23369
- }
23370
- function gscDaysSinceLatestData(latestDataDate, today) {
23371
- if (latestDataDate === null) return null;
23372
- return Math.max(0, Math.round(
23373
- (Date.parse(`${today}T00:00:00Z`) - Date.parse(`${latestDataDate}T00:00:00Z`)) / 864e5
23374
- ));
23375
- }
23376
- function readLatestGscDataDate(db, projectId) {
23377
- const watermark = db.select({ through: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq28(gscDataWatermarks.projectId, projectId)).get()?.through ?? null;
23378
- const property = db.select({ latest: max(gscDailyTotals.date) }).from(gscDailyTotals).where(eq28(gscDailyTotals.projectId, projectId)).get()?.latest ?? null;
23379
- const dimensioned = db.select({ latest: max(gscSearchData.date) }).from(gscSearchData).where(eq28(gscSearchData.projectId, projectId)).get()?.latest ?? null;
23380
- return [watermark, property, dimensioned].filter((d) => d !== null).reduce((maxDate, d) => maxDate === null || d > maxDate ? d : maxDate, null);
23381
- }
23382
- function readEarliestGscDataDate(db, projectId) {
23383
- const property = db.select({ earliest: min(gscDailyTotals.date) }).from(gscDailyTotals).where(eq28(gscDailyTotals.projectId, projectId)).get()?.earliest ?? null;
23384
- const dimensioned = db.select({ earliest: min(gscSearchData.date) }).from(gscSearchData).where(eq28(gscSearchData.projectId, projectId)).get()?.earliest ?? null;
23385
- return [property, dimensioned].filter((d) => d !== null).reduce((earliest, d) => earliest === null || d < earliest ? d : earliest, null);
23386
- }
23387
- function readGscDailyTotals(db, projectId, startDate, endDate) {
23388
- const rows = db.select({
23389
- date: gscDailyTotals.date,
23390
- clicks: gscDailyTotals.clicks,
23391
- impressions: gscDailyTotals.impressions,
23392
- position: gscDailyTotals.position
23393
- }).from(gscDailyTotals).where(
23394
- and23(
23395
- eq28(gscDailyTotals.projectId, projectId),
23396
- sql9`${gscDailyTotals.date} >= ${startDate}`,
23397
- sql9`${gscDailyTotals.date} <= ${endDate}`
23398
- )
23399
- ).orderBy(asc4(gscDailyTotals.date)).all();
23400
- return rows.map((r) => {
23401
- const position = Number(r.position);
23402
- return {
23403
- date: r.date,
23404
- clicks: r.clicks,
23405
- impressions: r.impressions,
23406
- position: Number.isFinite(position) ? position : 0
23407
- };
23408
- });
23409
- }
23410
- function mergeGscDailyTotalsWithFallback(propertyTotals, dimensionedFallback) {
23411
- const byDate = /* @__PURE__ */ new Map();
23412
- for (const row of dimensionedFallback) byDate.set(row.date, row);
23413
- for (const row of propertyTotals) byDate.set(row.date, row);
23414
- return [...byDate.values()].sort((a, b) => a.date.localeCompare(b.date));
23415
- }
23416
- function readGscQueryDailyRows(db, projectId, startDate, endDate) {
23417
- const rows = db.select({
23418
- date: gscQueryDailyTotals.date,
23419
- query: gscQueryDailyTotals.query,
23420
- clicks: gscQueryDailyTotals.clicks,
23421
- impressions: gscQueryDailyTotals.impressions,
23422
- position: gscQueryDailyTotals.position
23423
- }).from(gscQueryDailyTotals).where(
23424
- and23(
23425
- eq28(gscQueryDailyTotals.projectId, projectId),
23426
- sql9`${gscQueryDailyTotals.date} >= ${startDate}`,
23427
- sql9`${gscQueryDailyTotals.date} <= ${endDate}`
23428
- )
23429
- ).all();
23430
- return rows.map((r) => {
23431
- const position = Number(r.position);
23432
- return {
23433
- date: r.date,
23434
- query: r.query,
23435
- clicks: r.clicks,
23436
- impressions: r.impressions,
23437
- position: Number.isFinite(position) ? position : 0
23438
- };
23439
- });
23440
- }
23441
- function mergeGscQueryTotalsWithFallback(accurateDays, fallbackDays) {
23442
- const dayKey = (r) => `${r.date}\0${r.query}`;
23443
- const byDay = /* @__PURE__ */ new Map();
23444
- for (const row of fallbackDays) byDay.set(dayKey(row), { row, accurate: false });
23445
- for (const row of accurateDays) byDay.set(dayKey(row), { row, accurate: true });
23446
- const byQuery = /* @__PURE__ */ new Map();
23447
- for (const { row, accurate } of byDay.values()) {
23448
- const acc = byQuery.get(row.query) ?? {
23449
- clicks: 0,
23450
- impressions: 0,
23451
- weightedPositionSum: 0,
23452
- positionSum: 0,
23453
- positionDays: 0,
23454
- sawAccurate: false,
23455
- sawLegacy: false
23456
- };
23457
- acc.clicks += row.clicks;
23458
- acc.impressions += row.impressions;
23459
- acc.weightedPositionSum += row.position * row.impressions;
23460
- acc.positionSum += row.position;
23461
- acc.positionDays += 1;
23462
- if (accurate) acc.sawAccurate = true;
23463
- else acc.sawLegacy = true;
23464
- byQuery.set(row.query, acc);
23465
- }
23466
- return [...byQuery.entries()].map(([query, acc]) => ({
23467
- query,
23468
- clicks: acc.clicks,
23469
- impressions: acc.impressions,
23470
- position: acc.impressions > 0 ? acc.weightedPositionSum / acc.impressions : acc.positionDays > 0 ? acc.positionSum / acc.positionDays : 0,
23471
- source: acc.sawAccurate && acc.sawLegacy ? "mixed" : acc.sawAccurate ? "google" : "page-summed"
23472
- }));
23473
- }
23474
-
23475
23522
  // ../api-routes/src/report-renderer.ts
23476
23523
  var COLORS = {
23477
23524
  bg: "#09090b",
@@ -25763,6 +25810,149 @@ function renderReportHtml(report, opts = {}) {
25763
25810
  </html>`;
25764
25811
  }
25765
25812
 
25813
+ // ../api-routes/src/ga-ai-referral-aggregation.ts
25814
+ function normalizeAiTrafficClass(value) {
25815
+ return value === AiReferralTrafficClasses.paid ? AiReferralTrafficClasses.paid : AiReferralTrafficClasses.organic;
25816
+ }
25817
+ function emptyAiCounts() {
25818
+ return { sessions: 0 };
25819
+ }
25820
+ var AI_ATTRIBUTION_PREFERRED_DIMENSION = "session";
25821
+ function pickWinningAttributionDimension(sessionsByDimension) {
25822
+ let winner;
25823
+ let winnerSessions = -1;
25824
+ for (const [dimension, sessions] of sessionsByDimension) {
25825
+ const beatsOnCount = sessions > winnerSessions;
25826
+ const winsTie = sessions === winnerSessions && dimension === AI_ATTRIBUTION_PREFERRED_DIMENSION;
25827
+ if (beatsOnCount || winsTie) {
25828
+ winnerSessions = sessions;
25829
+ winner = dimension;
25830
+ }
25831
+ }
25832
+ return winner;
25833
+ }
25834
+ function resolveWinningDimensions(rows) {
25835
+ const groups = /* @__PURE__ */ new Map();
25836
+ for (const row of rows) {
25837
+ const key = `${row.date}\0${row.source}`;
25838
+ let group = groups.get(key);
25839
+ if (!group) {
25840
+ group = { date: row.date, source: row.source, byDimension: /* @__PURE__ */ new Map(), mediumSessions: /* @__PURE__ */ new Map() };
25841
+ groups.set(key, group);
25842
+ }
25843
+ let dim = group.byDimension.get(row.sourceDimension);
25844
+ if (!dim) {
25845
+ dim = { paidSessions: 0, organicSessions: 0 };
25846
+ group.byDimension.set(row.sourceDimension, dim);
25847
+ }
25848
+ const sessions = row.sessions ?? 0;
25849
+ if (normalizeAiTrafficClass(row.trafficClass) === AiReferralTrafficClasses.paid) {
25850
+ dim.paidSessions += sessions;
25851
+ } else {
25852
+ dim.organicSessions += sessions;
25853
+ }
25854
+ let mediums = group.mediumSessions.get(row.sourceDimension);
25855
+ if (!mediums) {
25856
+ mediums = /* @__PURE__ */ new Map();
25857
+ group.mediumSessions.set(row.sourceDimension, mediums);
25858
+ }
25859
+ mediums.set(row.medium, (mediums.get(row.medium) ?? 0) + sessions);
25860
+ }
25861
+ const winners = [];
25862
+ for (const group of groups.values()) {
25863
+ const winningDimension = pickWinningAttributionDimension(
25864
+ new Map([...group.byDimension].map(([d, c]) => [d, c.paidSessions + c.organicSessions]))
25865
+ );
25866
+ if (winningDimension === void 0) continue;
25867
+ const best = group.byDimension.get(winningDimension);
25868
+ let medium = "";
25869
+ let mediumBest = -1;
25870
+ for (const [candidate, sessions] of group.mediumSessions.get(winningDimension) ?? []) {
25871
+ if (sessions > mediumBest) {
25872
+ mediumBest = sessions;
25873
+ medium = candidate;
25874
+ }
25875
+ }
25876
+ winners.push({
25877
+ date: group.date,
25878
+ source: group.source,
25879
+ medium,
25880
+ paidSessions: best.paidSessions,
25881
+ organicSessions: best.organicSessions
25882
+ });
25883
+ }
25884
+ return winners;
25885
+ }
25886
+ function summarizeAiReferralCounts(rows) {
25887
+ const paidDeduped = emptyAiCounts();
25888
+ const organicDeduped = emptyAiCounts();
25889
+ const paidBySession = emptyAiCounts();
25890
+ const organicBySession = emptyAiCounts();
25891
+ const bySessionChannelGroup = /* @__PURE__ */ new Map();
25892
+ for (const winner of resolveWinningDimensions(rows)) {
25893
+ paidDeduped.sessions += winner.paidSessions;
25894
+ organicDeduped.sessions += winner.organicSessions;
25895
+ }
25896
+ for (const row of rows) {
25897
+ if (row.sourceDimension !== "session") continue;
25898
+ const sessions = row.sessions ?? 0;
25899
+ bySessionChannelGroup.set(
25900
+ row.channelGroup,
25901
+ (bySessionChannelGroup.get(row.channelGroup) ?? 0) + sessions
25902
+ );
25903
+ const bucket = normalizeAiTrafficClass(row.trafficClass) === AiReferralTrafficClasses.paid ? paidBySession : organicBySession;
25904
+ bucket.sessions += sessions;
25905
+ }
25906
+ return {
25907
+ paidDeduped,
25908
+ organicDeduped,
25909
+ paidBySession,
25910
+ organicBySession,
25911
+ bySessionChannelGroup,
25912
+ deduped: { sessions: paidDeduped.sessions + organicDeduped.sessions },
25913
+ bySession: { sessions: paidBySession.sessions + organicBySession.sessions }
25914
+ };
25915
+ }
25916
+ function buildAiReferralDailySeries(rows) {
25917
+ const byDate = /* @__PURE__ */ new Map();
25918
+ const sourceTotals = /* @__PURE__ */ new Map();
25919
+ let totalSessions = 0;
25920
+ let totalPaidSessions = 0;
25921
+ let totalOrganicSessions = 0;
25922
+ for (const winner of resolveWinningDimensions(rows)) {
25923
+ const sessions = winner.paidSessions + winner.organicSessions;
25924
+ let bySource = byDate.get(winner.date);
25925
+ if (!bySource) {
25926
+ bySource = /* @__PURE__ */ new Map();
25927
+ byDate.set(winner.date, bySource);
25928
+ }
25929
+ let bucket = bySource.get(winner.source);
25930
+ if (!bucket) {
25931
+ bucket = { source: winner.source, sessions: 0, paidSessions: 0, organicSessions: 0 };
25932
+ bySource.set(winner.source, bucket);
25933
+ }
25934
+ bucket.sessions += sessions;
25935
+ bucket.paidSessions += winner.paidSessions;
25936
+ bucket.organicSessions += winner.organicSessions;
25937
+ sourceTotals.set(winner.source, (sourceTotals.get(winner.source) ?? 0) + sessions);
25938
+ totalSessions += sessions;
25939
+ totalPaidSessions += winner.paidSessions;
25940
+ totalOrganicSessions += winner.organicSessions;
25941
+ }
25942
+ const days = [...byDate.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([date, bySource]) => {
25943
+ const sources2 = [...bySource.values()].sort((a, b) => b.sessions - a.sessions || a.source.localeCompare(b.source));
25944
+ return {
25945
+ date,
25946
+ sessions: sources2.reduce((sum, s) => sum + s.sessions, 0),
25947
+ paidSessions: sources2.reduce((sum, s) => sum + s.paidSessions, 0),
25948
+ organicSessions: sources2.reduce((sum, s) => sum + s.organicSessions, 0),
25949
+ bySource: sources2
25950
+ };
25951
+ });
25952
+ const sources = [...sourceTotals.entries()].sort(([aSource, aSessions], [bSource, bSessions]) => bSessions - aSessions || aSource.localeCompare(bSource)).map(([source]) => source);
25953
+ return { days, sources, totalSessions, totalPaidSessions, totalOrganicSessions };
25954
+ }
25955
+
25766
25956
  // ../api-routes/src/report.ts
25767
25957
  var TOP_QUERIES_LIMIT = 20;
25768
25958
  var TOP_LANDING_PAGES_LIMIT = 20;
@@ -25828,8 +26018,8 @@ function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trac
25828
26018
  const rows = db.select().from(gscSearchData).where(
25829
26019
  and24(
25830
26020
  eq29(gscSearchData.projectId, projectId),
25831
- gte5(gscSearchData.date, startDate),
25832
- lte3(gscSearchData.date, maxDate)
26021
+ gte6(gscSearchData.date, startDate),
26022
+ lte4(gscSearchData.date, maxDate)
25833
26023
  )
25834
26024
  ).all();
25835
26025
  const dailyTotals = readGscDailyTotals(db, projectId, startDate, maxDate);
@@ -25941,8 +26131,8 @@ function buildGaSection(db, projectId, windowDays2) {
25941
26131
  const snapshotRows = snapshotStartDate ? db.select().from(gaTrafficSnapshots).where(
25942
26132
  and24(
25943
26133
  eq29(gaTrafficSnapshots.projectId, projectId),
25944
- gte5(gaTrafficSnapshots.date, snapshotStartDate),
25945
- lte3(gaTrafficSnapshots.date, snapshotMaxInWindow)
26134
+ gte6(gaTrafficSnapshots.date, snapshotStartDate),
26135
+ lte4(gaTrafficSnapshots.date, snapshotMaxInWindow)
25946
26136
  )
25947
26137
  ).all() : db.select().from(gaTrafficSnapshots).where(eq29(gaTrafficSnapshots.projectId, projectId)).all();
25948
26138
  const totalSessions = windowSummary?.totalSessions ?? matchingFallbackSummary?.totalSessions ?? snapshotRows.reduce((s, r) => s + r.sessions, 0);
@@ -25970,8 +26160,8 @@ function buildGaSection(db, projectId, windowDays2) {
25970
26160
  eq29(gaAiReferrals.sourceDimension, "session")
25971
26161
  ];
25972
26162
  if (snapshotStartDate && snapshotMaxInWindow) {
25973
- aiConditions.push(gte5(gaAiReferrals.date, snapshotStartDate));
25974
- aiConditions.push(lte3(gaAiReferrals.date, snapshotMaxInWindow));
26163
+ aiConditions.push(gte6(gaAiReferrals.date, snapshotStartDate));
26164
+ aiConditions.push(lte4(gaAiReferrals.date, snapshotMaxInWindow));
25975
26165
  }
25976
26166
  const aiSessionRows = db.select({
25977
26167
  trafficClass: gaAiReferrals.trafficClass,
@@ -26076,7 +26266,7 @@ function buildAiReferrals(db, projectId, windowDays2) {
26076
26266
  if (rows.length === 0) return null;
26077
26267
  const dimSessionsByTuple = /* @__PURE__ */ new Map();
26078
26268
  for (const r of rows) {
26079
- const tupleKey = `${r.date}::${r.source}::${r.medium}`;
26269
+ const tupleKey = `${r.date}::${r.source}`;
26080
26270
  let dimMap = dimSessionsByTuple.get(tupleKey);
26081
26271
  if (!dimMap) {
26082
26272
  dimMap = /* @__PURE__ */ new Map();
@@ -26086,18 +26276,11 @@ function buildAiReferrals(db, projectId, windowDays2) {
26086
26276
  }
26087
26277
  const winningDimension = /* @__PURE__ */ new Map();
26088
26278
  for (const [tupleKey, dimMap] of dimSessionsByTuple) {
26089
- let bestDim;
26090
- let bestSessions = -1;
26091
- for (const [dim, sessions] of dimMap) {
26092
- if (sessions > bestSessions) {
26093
- bestSessions = sessions;
26094
- bestDim = dim;
26095
- }
26096
- }
26279
+ const bestDim = pickWinningAttributionDimension(dimMap);
26097
26280
  if (bestDim) winningDimension.set(tupleKey, bestDim);
26098
26281
  }
26099
26282
  const dedupedRows = rows.filter(
26100
- (r) => winningDimension.get(`${r.date}::${r.source}::${r.medium}`) === r.sourceDimension
26283
+ (r) => winningDimension.get(`${r.date}::${r.source}`) === r.sourceDimension
26101
26284
  );
26102
26285
  let total = 0;
26103
26286
  let paidSessions = 0;
@@ -26167,8 +26350,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26167
26350
  and24(
26168
26351
  eq29(crawlerEventsHourly.projectId, projectId),
26169
26352
  eq29(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26170
- gte5(crawlerEventsHourly.tsHour, windowStartIso),
26171
- exclusiveEnd ? lt6(crawlerEventsHourly.tsHour, windowEndIso) : lte3(crawlerEventsHourly.tsHour, windowEndIso)
26353
+ gte6(crawlerEventsHourly.tsHour, windowStartIso),
26354
+ exclusiveEnd ? lt6(crawlerEventsHourly.tsHour, windowEndIso) : lte4(crawlerEventsHourly.tsHour, windowEndIso)
26172
26355
  )
26173
26356
  ).get()?.total ?? 0
26174
26357
  );
@@ -26177,8 +26360,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26177
26360
  and24(
26178
26361
  eq29(crawlerEventsHourly.projectId, projectId),
26179
26362
  ne3(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26180
- gte5(crawlerEventsHourly.tsHour, windowStartIso),
26181
- exclusiveEnd ? lt6(crawlerEventsHourly.tsHour, windowEndIso) : lte3(crawlerEventsHourly.tsHour, windowEndIso)
26363
+ gte6(crawlerEventsHourly.tsHour, windowStartIso),
26364
+ exclusiveEnd ? lt6(crawlerEventsHourly.tsHour, windowEndIso) : lte4(crawlerEventsHourly.tsHour, windowEndIso)
26182
26365
  )
26183
26366
  ).get()?.total ?? 0
26184
26367
  );
@@ -26191,8 +26374,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26191
26374
  and24(
26192
26375
  eq29(aiReferralEventsHourly.projectId, projectId),
26193
26376
  countableReferralCondition(),
26194
- gte5(aiReferralEventsHourly.tsHour, windowStartIso),
26195
- exclusiveEnd ? lt6(aiReferralEventsHourly.tsHour, windowEndIso) : lte3(aiReferralEventsHourly.tsHour, windowEndIso)
26377
+ gte6(aiReferralEventsHourly.tsHour, windowStartIso),
26378
+ exclusiveEnd ? lt6(aiReferralEventsHourly.tsHour, windowEndIso) : lte4(aiReferralEventsHourly.tsHour, windowEndIso)
26196
26379
  )
26197
26380
  ).get();
26198
26381
  return aiReferralClassCounts(Number(row?.total ?? 0), Number(row?.paid ?? 0), Number(row?.organic ?? 0));
@@ -26201,8 +26384,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26201
26384
  db.select({ total: sql10`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
26202
26385
  and24(
26203
26386
  eq29(aiUserFetchEventsHourly.projectId, projectId),
26204
- gte5(aiUserFetchEventsHourly.tsHour, windowStartIso),
26205
- exclusiveEnd ? lt6(aiUserFetchEventsHourly.tsHour, windowEndIso) : lte3(aiUserFetchEventsHourly.tsHour, windowEndIso)
26387
+ gte6(aiUserFetchEventsHourly.tsHour, windowStartIso),
26388
+ exclusiveEnd ? lt6(aiUserFetchEventsHourly.tsHour, windowEndIso) : lte4(aiUserFetchEventsHourly.tsHour, windowEndIso)
26206
26389
  )
26207
26390
  ).get()?.total ?? 0
26208
26391
  );
@@ -26218,8 +26401,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26218
26401
  and24(
26219
26402
  eq29(aiReferralEventsHourly.projectId, projectId),
26220
26403
  nonSubresourceReferralPathCondition(),
26221
- gte5(aiReferralEventsHourly.tsHour, headlineStart),
26222
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26404
+ gte6(aiReferralEventsHourly.tsHour, headlineStart),
26405
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26223
26406
  )
26224
26407
  ).get()?.total ?? 0
26225
26408
  );
@@ -26232,8 +26415,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26232
26415
  }).from(crawlerEventsHourly).where(
26233
26416
  and24(
26234
26417
  eq29(crawlerEventsHourly.projectId, projectId),
26235
- gte5(crawlerEventsHourly.tsHour, headlineStart),
26236
- lte3(crawlerEventsHourly.tsHour, headlineEnd)
26418
+ gte6(crawlerEventsHourly.tsHour, headlineStart),
26419
+ lte4(crawlerEventsHourly.tsHour, headlineEnd)
26237
26420
  )
26238
26421
  ).groupBy(crawlerEventsHourly.operator, crawlerEventsHourly.verificationStatus).all();
26239
26422
  const crawlerByOperatorPriorRows = db.select({
@@ -26243,7 +26426,7 @@ function buildServerActivity(db, projectId, windowDays2) {
26243
26426
  and24(
26244
26427
  eq29(crawlerEventsHourly.projectId, projectId),
26245
26428
  eq29(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26246
- gte5(crawlerEventsHourly.tsHour, priorStart),
26429
+ gte6(crawlerEventsHourly.tsHour, priorStart),
26247
26430
  lt6(crawlerEventsHourly.tsHour, headlineStart)
26248
26431
  )
26249
26432
  ).groupBy(crawlerEventsHourly.operator).all();
@@ -26254,8 +26437,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26254
26437
  and24(
26255
26438
  eq29(aiReferralEventsHourly.projectId, projectId),
26256
26439
  countableReferralCondition(),
26257
- gte5(aiReferralEventsHourly.tsHour, headlineStart),
26258
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26440
+ gte6(aiReferralEventsHourly.tsHour, headlineStart),
26441
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26259
26442
  )
26260
26443
  ).groupBy(aiReferralEventsHourly.operator).all();
26261
26444
  const userFetchByOperatorRows = db.select({
@@ -26264,8 +26447,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26264
26447
  }).from(aiUserFetchEventsHourly).where(
26265
26448
  and24(
26266
26449
  eq29(aiUserFetchEventsHourly.projectId, projectId),
26267
- gte5(aiUserFetchEventsHourly.tsHour, headlineStart),
26268
- lte3(aiUserFetchEventsHourly.tsHour, headlineEnd)
26450
+ gte6(aiUserFetchEventsHourly.tsHour, headlineStart),
26451
+ lte4(aiUserFetchEventsHourly.tsHour, headlineEnd)
26269
26452
  )
26270
26453
  ).groupBy(aiUserFetchEventsHourly.operator).all();
26271
26454
  const operatorAgg = /* @__PURE__ */ new Map();
@@ -26309,8 +26492,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26309
26492
  and24(
26310
26493
  eq29(crawlerEventsHourly.projectId, projectId),
26311
26494
  eq29(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26312
- gte5(crawlerEventsHourly.tsHour, headlineStart),
26313
- lte3(crawlerEventsHourly.tsHour, headlineEnd)
26495
+ gte6(crawlerEventsHourly.tsHour, headlineStart),
26496
+ lte4(crawlerEventsHourly.tsHour, headlineEnd)
26314
26497
  )
26315
26498
  ).groupBy(crawlerEventsHourly.pathNormalized).orderBy(desc12(sql10`SUM(${crawlerEventsHourly.hits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
26316
26499
  const topCrawledPaths = topPathsRows.map((r) => ({
@@ -26326,8 +26509,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26326
26509
  and24(
26327
26510
  eq29(aiReferralEventsHourly.projectId, projectId),
26328
26511
  countableReferralCondition(),
26329
- gte5(aiReferralEventsHourly.tsHour, headlineStart),
26330
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26512
+ gte6(aiReferralEventsHourly.tsHour, headlineStart),
26513
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26331
26514
  )
26332
26515
  ).groupBy(aiReferralEventsHourly.product).orderBy(desc12(sql10`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).all();
26333
26516
  const referralProducts = referralProductsRows.map((r) => ({
@@ -26343,8 +26526,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26343
26526
  and24(
26344
26527
  eq29(aiReferralEventsHourly.projectId, projectId),
26345
26528
  countableReferralCondition(),
26346
- gte5(aiReferralEventsHourly.tsHour, headlineStart),
26347
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26529
+ gte6(aiReferralEventsHourly.tsHour, headlineStart),
26530
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26348
26531
  )
26349
26532
  ).groupBy(aiReferralEventsHourly.landingPathNormalized).orderBy(desc12(sql10`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
26350
26533
  const topReferralLandingPaths = topReferralRows.map((r) => ({
@@ -26359,8 +26542,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26359
26542
  and24(
26360
26543
  eq29(crawlerEventsHourly.projectId, projectId),
26361
26544
  eq29(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26362
- gte5(crawlerEventsHourly.tsHour, trendStart),
26363
- lte3(crawlerEventsHourly.tsHour, headlineEnd)
26545
+ gte6(crawlerEventsHourly.tsHour, trendStart),
26546
+ lte4(crawlerEventsHourly.tsHour, headlineEnd)
26364
26547
  )
26365
26548
  ).groupBy(sql10`SUBSTR(${crawlerEventsHourly.tsHour}, 1, 10)`).all();
26366
26549
  const referralTrendRows = db.select({
@@ -26370,8 +26553,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26370
26553
  and24(
26371
26554
  eq29(aiReferralEventsHourly.projectId, projectId),
26372
26555
  countableReferralCondition(),
26373
- gte5(aiReferralEventsHourly.tsHour, trendStart),
26374
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26556
+ gte6(aiReferralEventsHourly.tsHour, trendStart),
26557
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26375
26558
  )
26376
26559
  ).groupBy(sql10`SUBSTR(${aiReferralEventsHourly.tsHour}, 1, 10)`).all();
26377
26560
  const userFetchTrendRows = db.select({
@@ -26380,8 +26563,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26380
26563
  }).from(aiUserFetchEventsHourly).where(
26381
26564
  and24(
26382
26565
  eq29(aiUserFetchEventsHourly.projectId, projectId),
26383
- gte5(aiUserFetchEventsHourly.tsHour, trendStart),
26384
- lte3(aiUserFetchEventsHourly.tsHour, headlineEnd)
26566
+ gte6(aiUserFetchEventsHourly.tsHour, trendStart),
26567
+ lte4(aiUserFetchEventsHourly.tsHour, headlineEnd)
26385
26568
  )
26386
26569
  ).groupBy(sql10`SUBSTR(${aiUserFetchEventsHourly.tsHour}, 1, 10)`).all();
26387
26570
  const emptyTrendEntry = () => ({ verifiedCrawlerHits: 0, userFetchHits: 0, referralArrivals: 0 });
@@ -27144,7 +27327,7 @@ function buildProjectReport(db, projectName, periodDays) {
27144
27327
  const indexingHealthSection = buildIndexingHealth(db, project.id);
27145
27328
  const citationsTrend = buildCitationsTrend(db, project.id, queryLookup, latestRunLocation);
27146
27329
  const insightList = buildInsightList(db, project.id, latestRunLocation);
27147
- const orchestratorInput = loadOrchestratorInput(db, project, latestRunLocation);
27330
+ const orchestratorInput = loadOrchestratorInput(db, project, latestRunLocation, periodDays);
27148
27331
  const dismissedTargetRefs = loadDismissedTargetRefs(db, project.id);
27149
27332
  const rawContentOpportunities = buildContentTargetRows(orchestratorInput);
27150
27333
  const contentOpportunities = dismissedTargetRefs.size > 0 ? rawContentOpportunities.filter((r) => !dismissedTargetRefs.has(r.targetRef)) : rawContentOpportunities;
@@ -27331,10 +27514,10 @@ async function reportRoutes(app) {
27331
27514
  }
27332
27515
 
27333
27516
  // ../api-routes/src/organic-evidence.ts
27334
- import { and as and26, desc as desc13, eq as eq31, gte as gte7, lte as lte5, or as or6, sql as sql12 } from "drizzle-orm";
27517
+ import { and as and26, desc as desc13, eq as eq31, gte as gte8, lte as lte6, or as or6, sql as sql12 } from "drizzle-orm";
27335
27518
 
27336
27519
  // ../api-routes/src/ga-measurement-analysis.ts
27337
- import { and as and25, eq as eq30, gte as gte6, lte as lte4, or as or5, sql as sql11 } from "drizzle-orm";
27520
+ import { and as and25, eq as eq30, gte as gte7, lte as lte5, or as or5, sql as sql11 } from "drizzle-orm";
27338
27521
  var UNKNOWN_LANDING_PAGE = "(not set)";
27339
27522
  function addDays(date, offset) {
27340
27523
  const value = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
@@ -27530,15 +27713,15 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
27530
27713
  const gaAnchor = [acquisitionAnchor, leadAnchor].filter((date) => date !== null).sort().at(-1) ?? null;
27531
27714
  const gaPeriods = gaAnchor ? buildPeriods(gaAnchor, days) : [];
27532
27715
  const gaStartDate = gaPeriods[0]?.startDate;
27533
- const acquisitionRows = gaStartDate ? db.select().from(gaAcquisitionDaily).where(and25(eq30(gaAcquisitionDaily.projectId, project.id), gte6(gaAcquisitionDaily.date, gaStartDate), lte4(gaAcquisitionDaily.date, gaAnchor))).all() : [];
27534
- const leadRows = gaStartDate ? db.select().from(gaLeadEventsDaily).where(and25(eq30(gaLeadEventsDaily.projectId, project.id), gte6(gaLeadEventsDaily.date, gaStartDate), lte4(gaLeadEventsDaily.date, gaAnchor))).all() : [];
27716
+ const acquisitionRows = gaStartDate ? db.select().from(gaAcquisitionDaily).where(and25(eq30(gaAcquisitionDaily.projectId, project.id), gte7(gaAcquisitionDaily.date, gaStartDate), lte5(gaAcquisitionDaily.date, gaAnchor))).all() : [];
27717
+ const leadRows = gaStartDate ? db.select().from(gaLeadEventsDaily).where(and25(eq30(gaLeadEventsDaily.projectId, project.id), gte7(gaLeadEventsDaily.date, gaStartDate), lte5(gaLeadEventsDaily.date, gaAnchor))).all() : [];
27535
27718
  const engagementAnchor = db.select({ date: sql11`max(${gaDailyTotals.date})` }).from(gaDailyTotals).where(eq30(gaDailyTotals.projectId, project.id)).get()?.date ?? null;
27536
27719
  const engagementPeriodWindow = engagementAnchor ? buildPeriods(engagementAnchor, days) : [];
27537
27720
  const engagementStartDate = engagementPeriodWindow[0]?.startDate;
27538
27721
  const engagementRows = engagementStartDate ? db.select().from(gaDailyTotals).where(and25(
27539
27722
  eq30(gaDailyTotals.projectId, project.id),
27540
- gte6(gaDailyTotals.date, engagementStartDate),
27541
- lte4(gaDailyTotals.date, engagementAnchor)
27723
+ gte7(gaDailyTotals.date, engagementStartDate),
27724
+ lte5(gaDailyTotals.date, engagementAnchor)
27542
27725
  )).all() : [];
27543
27726
  const engagementAvailableFrom = db.select({
27544
27727
  date: sql11`min(${gaDailyTotals.date})`
@@ -27552,9 +27735,9 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
27552
27735
  const gscAnchor = db.select({ date: sql11`max(${gscDailyTotals.date})` }).from(gscDailyTotals).where(eq30(gscDailyTotals.projectId, project.id)).get()?.date ?? null;
27553
27736
  const gscPeriods = gscAnchor ? buildPeriods(gscAnchor, days) : [];
27554
27737
  const gscStartDate = gscPeriods[0]?.startDate;
27555
- const propertyRows = gscStartDate ? db.select().from(gscDailyTotals).where(and25(eq30(gscDailyTotals.projectId, project.id), gte6(gscDailyTotals.date, gscStartDate), lte4(gscDailyTotals.date, gscAnchor))).all() : [];
27556
- const queryRows = gscStartDate ? db.select().from(gscQueryDailyTotals).where(and25(eq30(gscQueryDailyTotals.projectId, project.id), gte6(gscQueryDailyTotals.date, gscStartDate), lte4(gscQueryDailyTotals.date, gscAnchor))).all() : [];
27557
- const rawPageRows = gscStartDate ? db.select().from(gscSearchData).where(and25(eq30(gscSearchData.projectId, project.id), gte6(gscSearchData.date, gscStartDate), lte4(gscSearchData.date, gscAnchor))).all() : [];
27738
+ const propertyRows = gscStartDate ? db.select().from(gscDailyTotals).where(and25(eq30(gscDailyTotals.projectId, project.id), gte7(gscDailyTotals.date, gscStartDate), lte5(gscDailyTotals.date, gscAnchor))).all() : [];
27739
+ const queryRows = gscStartDate ? db.select().from(gscQueryDailyTotals).where(and25(eq30(gscQueryDailyTotals.projectId, project.id), gte7(gscQueryDailyTotals.date, gscStartDate), lte5(gscQueryDailyTotals.date, gscAnchor))).all() : [];
27740
+ const rawPageRows = gscStartDate ? db.select().from(gscSearchData).where(and25(eq30(gscSearchData.projectId, project.id), gte7(gscSearchData.date, gscStartDate), lte5(gscSearchData.date, gscAnchor))).all() : [];
27558
27741
  const state = db.select().from(gaMeasurementSyncStates).where(eq30(gaMeasurementSyncStates.projectId, project.id)).get();
27559
27742
  const acquisition = acquisitionRows.filter((row) => {
27560
27743
  const landingPage = normalizeLandingPage(row.landingPageNormalized ?? row.landingPage);
@@ -27906,18 +28089,18 @@ function buildOrganicEvidence(db, projectName, periodDays) {
27906
28089
  const isInWindow = (date) => inRange(date, startDate, endDate);
27907
28090
  const gscRows = gscStartDate && gscEndDate ? db.select().from(gscDailyTotals).where(and26(
27908
28091
  eq31(gscDailyTotals.projectId, project.id),
27909
- gte7(gscDailyTotals.date, gscStartDate),
27910
- lte5(gscDailyTotals.date, gscEndDate)
28092
+ gte8(gscDailyTotals.date, gscStartDate),
28093
+ lte6(gscDailyTotals.date, gscEndDate)
27911
28094
  )).all() : [];
27912
28095
  const gaRows = gaStartDate && gaEndDate ? db.select().from(gaTrafficSnapshots).where(and26(
27913
28096
  eq31(gaTrafficSnapshots.projectId, project.id),
27914
- gte7(gaTrafficSnapshots.date, gaStartDate),
27915
- lte5(gaTrafficSnapshots.date, gaEndDate)
28097
+ gte8(gaTrafficSnapshots.date, gaStartDate),
28098
+ lte6(gaTrafficSnapshots.date, gaEndDate)
27916
28099
  )).all() : [];
27917
28100
  const nativeAcquisitionRows = nativeAcquisitionActive && nativeStartDate && nativeEndDate ? db.select().from(gaAcquisitionDaily).where(and26(
27918
28101
  eq31(gaAcquisitionDaily.projectId, project.id),
27919
- gte7(gaAcquisitionDaily.date, nativeStartDate),
27920
- lte5(gaAcquisitionDaily.date, nativeEndDate)
28102
+ gte8(gaAcquisitionDaily.date, nativeStartDate),
28103
+ lte6(gaAcquisitionDaily.date, nativeEndDate)
27921
28104
  )).all().filter((row) => matchesMarketingHost(row.hostName, marketingHosts)) : [];
27922
28105
  const nativeOrganicRows = nativeAcquisitionRows.filter((row) => row.channelGroup === "Organic Search");
27923
28106
  const nativeWindowOrganicRows = nativeOrganicRows;
@@ -27955,8 +28138,8 @@ function buildOrganicEvidence(db, projectName, periodDays) {
27955
28138
  );
27956
28139
  const namedRows = db.select().from(gscQueryDailyTotals).where(and26(
27957
28140
  eq31(gscQueryDailyTotals.projectId, project.id),
27958
- gte7(gscQueryDailyTotals.date, startDate),
27959
- lte5(gscQueryDailyTotals.date, endDate)
28141
+ gte8(gscQueryDailyTotals.date, startDate),
28142
+ lte6(gscQueryDailyTotals.date, endDate)
27960
28143
  )).all();
27961
28144
  for (const row of namedRows) {
27962
28145
  const isBrand = categorizeQueryByIntent(row.query, brandTokens) === "brand";
@@ -27982,7 +28165,7 @@ function buildOrganicEvidence(db, projectName, periodDays) {
27982
28165
  totals: sumSearchRows(gscRows.filter((row) => inRange(row.date, cohort.startDate, cohort.endDate)))
27983
28166
  }))
27984
28167
  } : null;
27985
- const pageSearchRows = db.select().from(gscSearchData).where(and26(eq31(gscSearchData.projectId, project.id), gte7(gscSearchData.date, startDate), lte5(gscSearchData.date, endDate))).all().filter((row) => pageMatchesMarketingHost(row.page, marketingHosts));
28168
+ const pageSearchRows = db.select().from(gscSearchData).where(and26(eq31(gscSearchData.projectId, project.id), gte8(gscSearchData.date, startDate), lte6(gscSearchData.date, endDate))).all().filter((row) => pageMatchesMarketingHost(row.page, marketingHosts));
27986
28169
  const gaWindow = gaStartDate && gaEndDate ? gaRows.filter((row) => inRange(row.date, gaStartDate, gaEndDate)) : [];
27987
28170
  const legacyGaCohorts = gaCohorts.map((cohort) => ({
27988
28171
  ...cohort,
@@ -28009,7 +28192,7 @@ function buildOrganicEvidence(db, projectName, periodDays) {
28009
28192
  }))
28010
28193
  };
28011
28194
  }
28012
- const allGaAiRows = db.select().from(gaAiReferrals).where(and26(eq31(gaAiReferrals.projectId, project.id), gte7(gaAiReferrals.date, gaStartDate ?? startDate), lte5(gaAiReferrals.date, gaEndDate ?? endDate))).all().filter((row) => row.sourceDimension === "session");
28195
+ const allGaAiRows = db.select().from(gaAiReferrals).where(and26(eq31(gaAiReferrals.projectId, project.id), gte8(gaAiReferrals.date, gaStartDate ?? startDate), lte6(gaAiReferrals.date, gaEndDate ?? endDate))).all().filter((row) => row.sourceDimension === "session");
28013
28196
  const aiRows = gaStartDate && gaEndDate ? allGaAiRows.filter((row) => inRange(row.date, gaStartDate, gaEndDate)) : [];
28014
28197
  const gaAiReferralSummary = allGaAiRows.length ? {
28015
28198
  paidSessions: aiRows.filter((row) => row.trafficClass === AiReferralTrafficClasses.paid).reduce((count2, row) => count2 + row.sessions, 0),
@@ -28018,9 +28201,9 @@ function buildOrganicEvidence(db, projectName, periodDays) {
28018
28201
  const serverSources = db.select().from(trafficSources).where(eq31(trafficSources.projectId, project.id)).all();
28019
28202
  const serverStart = `${startDate}T00:00:00`;
28020
28203
  const serverEnd = `${endDate}T23:59:59.999Z`;
28021
- const allCrawlers = db.select().from(crawlerEventsHourly).where(and26(eq31(crawlerEventsHourly.projectId, project.id), gte7(crawlerEventsHourly.tsHour, serverStart), lte5(crawlerEventsHourly.tsHour, serverEnd))).all();
28022
- const allFetches = db.select().from(aiUserFetchEventsHourly).where(and26(eq31(aiUserFetchEventsHourly.projectId, project.id), gte7(aiUserFetchEventsHourly.tsHour, serverStart), lte5(aiUserFetchEventsHourly.tsHour, serverEnd))).all();
28023
- const allReferrals = db.select().from(aiReferralEventsHourly).where(and26(eq31(aiReferralEventsHourly.projectId, project.id), countableReferralCondition(), gte7(aiReferralEventsHourly.tsHour, serverStart), lte5(aiReferralEventsHourly.tsHour, serverEnd))).all();
28204
+ const allCrawlers = db.select().from(crawlerEventsHourly).where(and26(eq31(crawlerEventsHourly.projectId, project.id), gte8(crawlerEventsHourly.tsHour, serverStart), lte6(crawlerEventsHourly.tsHour, serverEnd))).all();
28205
+ const allFetches = db.select().from(aiUserFetchEventsHourly).where(and26(eq31(aiUserFetchEventsHourly.projectId, project.id), gte8(aiUserFetchEventsHourly.tsHour, serverStart), lte6(aiUserFetchEventsHourly.tsHour, serverEnd))).all();
28206
+ const allReferrals = db.select().from(aiReferralEventsHourly).where(and26(eq31(aiReferralEventsHourly.projectId, project.id), countableReferralCondition(), gte8(aiReferralEventsHourly.tsHour, serverStart), lte6(aiReferralEventsHourly.tsHour, serverEnd))).all();
28024
28207
  const [serverCoverageRow] = db.all(sql12`
28025
28208
  select
28026
28209
  min(day) as startDate,
@@ -29014,7 +29197,7 @@ async function visibilityStatsRoutes(app) {
29014
29197
  }
29015
29198
 
29016
29199
  // ../api-routes/src/results-export.ts
29017
- import { and as and29, asc as asc5, eq as eq34, gte as gte8, lte as lte6 } from "drizzle-orm";
29200
+ import { and as and29, asc as asc5, eq as eq34, gte as gte9, lte as lte7 } from "drizzle-orm";
29018
29201
  var CSV_COLUMNS = [
29019
29202
  "export_schema_version",
29020
29203
  "project_id",
@@ -29154,8 +29337,8 @@ async function resultsExportRoutes(app) {
29154
29337
  eq34(runs.projectId, project.id),
29155
29338
  eq34(runs.kind, RunKinds["answer-visibility"]),
29156
29339
  ...includeProbes ? [] : [notProbeRun()],
29157
- ...sinceIso ? [gte8(runs.createdAt, sinceIso)] : [],
29158
- ...untilIso ? [lte6(runs.createdAt, untilIso)] : []
29340
+ ...sinceIso ? [gte9(runs.createdAt, sinceIso)] : [],
29341
+ ...untilIso ? [lte7(runs.createdAt, untilIso)] : []
29159
29342
  ];
29160
29343
  const rows = app.db.select({
29161
29344
  runId: runs.id,
@@ -34649,6 +34832,69 @@ var routeCatalog = [
34649
34832
  502: errorResponse("Upstream outcome failed or is unknown.")
34650
34833
  }
34651
34834
  },
34835
+ {
34836
+ method: "post",
34837
+ path: "/api/v1/projects/{name}/ads/campaigns/{id}/archive",
34838
+ summary: "Archive an OpenAI Ads campaign (irreversible)",
34839
+ description: "IRREVERSIBLE. The entity must already be paused, and expectedUpdatedAt must match the live upstream revision, so an active entity is refused and a stale version is refused. There is no remediation: an unconfirmed archived state leaves the receipt unknown for reconciliation. The upstream archive path and its archived status transition were verified live against a Canonry-owned test advertiser account on 2026-09-02. The provider list endpoints are eventually consistent and can still report a freshly archived entity as paused, so the archived state is confirmed by a direct single-entity read, never by a list.",
34840
+ tags: ["ads"],
34841
+ parameters: [nameParameter, { name: "id", in: "path", required: true, description: "Campaign ID.", schema: stringSchema }],
34842
+ requestBody: { required: true, content: { "application/json": { schema: {
34843
+ type: "object",
34844
+ required: ["operationKey", "expectedUpdatedAt"],
34845
+ properties: { operationKey: adsOperationKeySchema, expectedUpdatedAt: integerSchema }
34846
+ } } } },
34847
+ responses: {
34848
+ 200: jsonResponse("Campaign archive receipt.", "AdsOperationResponse"),
34849
+ 400: errorResponse("Invalid request, active entity, stale expectedUpdatedAt, or ads connection unavailable."),
34850
+ 403: errorResponse("The key lacks ads.write."),
34851
+ 404: errorResponse("Project not found."),
34852
+ 409: errorResponse("Operation key was already used for a different request."),
34853
+ 502: errorResponse("Upstream outcome failed or is unknown.")
34854
+ }
34855
+ },
34856
+ {
34857
+ method: "post",
34858
+ path: "/api/v1/projects/{name}/ads/ad-groups/{id}/archive",
34859
+ summary: "Archive an OpenAI Ads ad group (irreversible)",
34860
+ description: "IRREVERSIBLE. The entity must already be paused, and expectedUpdatedAt must match the live upstream revision, so an active entity is refused and a stale version is refused. There is no remediation: an unconfirmed archived state leaves the receipt unknown for reconciliation. The upstream archive path and its archived status transition were verified live against a Canonry-owned test advertiser account on 2026-09-02. The provider list endpoints are eventually consistent and can still report a freshly archived entity as paused, so the archived state is confirmed by a direct single-entity read, never by a list.",
34861
+ tags: ["ads"],
34862
+ parameters: [nameParameter, { name: "id", in: "path", required: true, description: "Ad group ID.", schema: stringSchema }],
34863
+ requestBody: { required: true, content: { "application/json": { schema: {
34864
+ type: "object",
34865
+ required: ["operationKey", "expectedUpdatedAt"],
34866
+ properties: { operationKey: adsOperationKeySchema, expectedUpdatedAt: integerSchema }
34867
+ } } } },
34868
+ responses: {
34869
+ 200: jsonResponse("Ad-group archive receipt.", "AdsOperationResponse"),
34870
+ 400: errorResponse("Invalid request, active entity, stale expectedUpdatedAt, or ads connection unavailable."),
34871
+ 403: errorResponse("The key lacks ads.write."),
34872
+ 404: errorResponse("Project not found."),
34873
+ 409: errorResponse("Operation key was already used for a different request."),
34874
+ 502: errorResponse("Upstream outcome failed or is unknown.")
34875
+ }
34876
+ },
34877
+ {
34878
+ method: "post",
34879
+ path: "/api/v1/projects/{name}/ads/ads/{id}/archive",
34880
+ summary: "Archive an OpenAI Ads ad (irreversible)",
34881
+ description: "IRREVERSIBLE. The entity must already be paused, and expectedUpdatedAt must match the live upstream revision, so an active entity is refused and a stale version is refused. There is no remediation: an unconfirmed archived state leaves the receipt unknown for reconciliation. The upstream archive path and its archived status transition were verified live against a Canonry-owned test advertiser account on 2026-09-02. The provider list endpoints are eventually consistent and can still report a freshly archived entity as paused, so the archived state is confirmed by a direct single-entity read, never by a list.",
34882
+ tags: ["ads"],
34883
+ parameters: [nameParameter, { name: "id", in: "path", required: true, description: "Ad ID.", schema: stringSchema }],
34884
+ requestBody: { required: true, content: { "application/json": { schema: {
34885
+ type: "object",
34886
+ required: ["operationKey", "expectedUpdatedAt"],
34887
+ properties: { operationKey: adsOperationKeySchema, expectedUpdatedAt: integerSchema }
34888
+ } } } },
34889
+ responses: {
34890
+ 200: jsonResponse("Ad archive receipt.", "AdsOperationResponse"),
34891
+ 400: errorResponse("Invalid request, active entity, stale expectedUpdatedAt, or ads connection unavailable."),
34892
+ 403: errorResponse("The key lacks ads.write."),
34893
+ 404: errorResponse("Project not found."),
34894
+ 409: errorResponse("Operation key was already used for a different request."),
34895
+ 502: errorResponse("Upstream outcome failed or is unknown.")
34896
+ }
34897
+ },
34652
34898
  {
34653
34899
  method: "post",
34654
34900
  path: "/api/v1/projects/{name}/ads/sync",
@@ -43203,7 +43449,7 @@ async function googleMarketingRoutes(app, opts) {
43203
43449
 
43204
43450
  // ../api-routes/src/ads.ts
43205
43451
  import crypto35 from "crypto";
43206
- import { eq as eq43, and as and35, asc as asc8, desc as desc19, gt as gt2, gte as gte9, lt as lt8, lte as lte8, ne as ne5, inArray as inArray17, or as or10, isNull as isNull5, isNotNull as isNotNull2, sql as sql17 } from "drizzle-orm";
43452
+ import { eq as eq43, and as and35, asc as asc8, desc as desc19, gt as gt2, gte as gte10, lt as lt8, lte as lte9, ne as ne5, inArray as inArray17, or as or10, isNull as isNull5, isNotNull as isNotNull2, sql as sql17 } from "drizzle-orm";
43207
43453
 
43208
43454
  // ../api-routes/src/ads-activation.ts
43209
43455
  import crypto33 from "crypto";
@@ -44589,7 +44835,7 @@ async function executeApprovedAdsActivation(dependencies, request) {
44589
44835
 
44590
44836
  // ../api-routes/src/ads-activation-routes.ts
44591
44837
  import crypto34 from "crypto";
44592
- import { and as and34, asc as asc7, eq as eq42, gt, isNotNull, isNull as isNull4, lte as lte7, or as or9, sql as sql16 } from "drizzle-orm";
44838
+ import { and as and34, asc as asc7, eq as eq42, gt, isNotNull, isNull as isNull4, lte as lte8, or as or9, sql as sql16 } from "drizzle-orm";
44593
44839
  var MAX_ACTIVATION_GRANT_LIFETIME_MS = 24 * 60 * 60 * 1e3;
44594
44840
  var ACTIVATION_LEASE_MS = 5 * 6e4;
44595
44841
  var ACTIVATION_REVOCATION_SETTLEMENT_MS = ACTIVATION_LEASE_MS;
@@ -44719,7 +44965,7 @@ function createActivationStore(app, request) {
44719
44965
  isNull4(adsOperations.leaseOwner),
44720
44966
  eq42(adsOperations.leaseOwner, input.leaseOwner),
44721
44967
  isNull4(adsOperations.leaseExpiresAt),
44722
- lte7(adsOperations.leaseExpiresAt, input.now)
44968
+ lte8(adsOperations.leaseExpiresAt, input.now)
44723
44969
  )
44724
44970
  )).run();
44725
44971
  const resumed = loadCanonicalActivation(tx, grant.id, existing.id);
@@ -45136,7 +45382,7 @@ function selectWatchdogOperations(app, nowIso, batchSize, allowLiveRevocationLea
45136
45382
  const leaseAvailable = or9(
45137
45383
  isNull4(adsOperations.leaseOwner),
45138
45384
  isNull4(adsOperations.leaseExpiresAt),
45139
- lte7(adsOperations.leaseExpiresAt, nowIso)
45385
+ lte8(adsOperations.leaseExpiresAt, nowIso)
45140
45386
  );
45141
45387
  const campaignHasMutationEvidence = or9(
45142
45388
  eq42(adsOperationSteps.state, AdsOperationStepStates.executing),
@@ -45332,7 +45578,7 @@ async function sweepStaleAdsActivationsOnce(app, opts) {
45332
45578
  or9(
45333
45579
  isNull4(adsOperations.leaseOwner),
45334
45580
  isNull4(adsOperations.leaseExpiresAt),
45335
- lte7(adsOperations.leaseExpiresAt, nowIso)
45581
+ lte8(adsOperations.leaseExpiresAt, nowIso)
45336
45582
  )
45337
45583
  )
45338
45584
  )
@@ -45369,7 +45615,7 @@ async function sweepStaleAdsActivationsOnce(app, opts) {
45369
45615
  or9(
45370
45616
  isNull4(adsOperations.leaseOwner),
45371
45617
  isNull4(adsOperations.leaseExpiresAt),
45372
- lte7(adsOperations.leaseExpiresAt, failedAt)
45618
+ lte8(adsOperations.leaseExpiresAt, failedAt)
45373
45619
  )
45374
45620
  )
45375
45621
  )
@@ -45989,14 +46235,15 @@ var DEFAULT_RECONCILE_BATCH_SIZE = 10;
45989
46235
  var DEFAULT_RECONCILE_LEASE_MS = 3e4;
45990
46236
  var DEFAULT_ADS_ACCOUNT_VERIFICATION_CACHE_TTL_MS = 5 * 6e4;
45991
46237
  var ADS_RECONCILIATION_QUARANTINED = "ADS_RECONCILIATION_QUARANTINED";
45992
- var AdsPausedPostconditionError = class extends Error {
45993
- constructor(entityId) {
45994
- super("OpenAI Ads API did not confirm the required paused state");
46238
+ var AdsStatusPostconditionError = class extends Error {
46239
+ constructor(entityId, expectedStatus) {
46240
+ super(`OpenAI Ads API did not confirm the required ${expectedStatus} state`);
45995
46241
  this.entityId = entityId;
45996
- this.name = "AdsPausedPostconditionError";
46242
+ this.code = `ADS_${expectedStatus.toUpperCase()}_POSTCONDITION_FAILED`;
46243
+ this.name = "AdsStatusPostconditionError";
45997
46244
  }
45998
46245
  entityId;
45999
- code = "ADS_PAUSED_POSTCONDITION_FAILED";
46246
+ code;
46000
46247
  status = 502;
46001
46248
  };
46002
46249
  function statusDto(row) {
@@ -46448,7 +46695,7 @@ async function executeAdsOperation(app, request, input) {
46448
46695
  if (input.expectedStatus !== void 0 && result.status !== input.expectedStatus) {
46449
46696
  if (input.remediateStatus) result = await input.remediateStatus(result);
46450
46697
  if (result.status !== input.expectedStatus) {
46451
- throw new AdsPausedPostconditionError(result.id);
46698
+ throw new AdsStatusPostconditionError(result.id, input.expectedStatus);
46452
46699
  }
46453
46700
  }
46454
46701
  const succeededResult = result;
@@ -46545,13 +46792,13 @@ function claimOperationForReconciliation(app, row, leaseOwner, now, leaseMs, pol
46545
46792
  const unknownCutoff = new Date(now.getTime() - backoffMs).toISOString();
46546
46793
  const claimableState = row.state === AdsOperationStates.pending ? and35(
46547
46794
  eq43(adsOperations.state, AdsOperationStates.pending),
46548
- lte8(adsOperations.updatedAt, pendingCutoff)
46795
+ lte9(adsOperations.updatedAt, pendingCutoff)
46549
46796
  ) : row.state === AdsOperationStates.unknown ? and35(
46550
46797
  eq43(adsOperations.state, AdsOperationStates.unknown),
46551
- enforceBackoff ? lte8(adsOperations.updatedAt, unknownCutoff) : void 0
46798
+ enforceBackoff ? lte9(adsOperations.updatedAt, unknownCutoff) : void 0
46552
46799
  ) : and35(
46553
46800
  eq43(adsOperations.state, AdsOperationStates.reconciling),
46554
- or10(isNull5(adsOperations.leaseExpiresAt), lte8(adsOperations.leaseExpiresAt, nowIso))
46801
+ or10(isNull5(adsOperations.leaseExpiresAt), lte9(adsOperations.leaseExpiresAt, nowIso))
46555
46802
  );
46556
46803
  const claimed = app.db.update(adsOperations).set({
46557
46804
  state: AdsOperationStates.reconciling,
@@ -46671,7 +46918,7 @@ function quarantineExhaustedReconciliation(app, row, now, policy, context) {
46671
46918
  }).where(and35(
46672
46919
  eq43(adsOperations.id, row.id),
46673
46920
  eq43(adsOperations.projectId, row.projectId),
46674
- gte9(adsOperations.reconcileAttempts, policy.maxAttempts),
46921
+ gte10(adsOperations.reconcileAttempts, policy.maxAttempts),
46675
46922
  inArray17(adsOperations.state, [
46676
46923
  AdsOperationStates.pending,
46677
46924
  AdsOperationStates.unknown,
@@ -46869,14 +47116,14 @@ async function sweepAdsOperationsOnce(app, opts, config) {
46869
47116
  const backoffMs = config.policy.backoffBaseMs * 2 ** Math.max(0, reconcileAttempts - 1);
46870
47117
  return and35(
46871
47118
  eq43(adsOperations.reconcileAttempts, reconcileAttempts),
46872
- lte8(adsOperations.updatedAt, new Date(now.getTime() - backoffMs).toISOString())
47119
+ lte9(adsOperations.updatedAt, new Date(now.getTime() - backoffMs).toISOString())
46873
47120
  );
46874
47121
  }
46875
47122
  );
46876
47123
  const retryableStateWhere = or10(
46877
47124
  and35(
46878
47125
  eq43(adsOperations.state, AdsOperationStates.pending),
46879
- lte8(adsOperations.updatedAt, pendingCutoff)
47126
+ lte9(adsOperations.updatedAt, pendingCutoff)
46880
47127
  ),
46881
47128
  and35(
46882
47129
  eq43(adsOperations.state, AdsOperationStates.unknown),
@@ -46884,7 +47131,7 @@ async function sweepAdsOperationsOnce(app, opts, config) {
46884
47131
  ),
46885
47132
  and35(
46886
47133
  eq43(adsOperations.state, AdsOperationStates.reconciling),
46887
- or10(isNull5(adsOperations.leaseExpiresAt), lte8(adsOperations.leaseExpiresAt, nowIso))
47134
+ or10(isNull5(adsOperations.leaseExpiresAt), lte9(adsOperations.leaseExpiresAt, nowIso))
46888
47135
  )
46889
47136
  );
46890
47137
  const staleReconcileWhere = and35(
@@ -46907,16 +47154,16 @@ async function sweepAdsOperationsOnce(app, opts, config) {
46907
47154
  );
46908
47155
  const exhaustedRows = app.db.select().from(adsOperations).where(and35(
46909
47156
  ne5(adsOperations.kind, AdsOperationKinds.campaign_tree_activate),
46910
- gte9(adsOperations.reconcileAttempts, config.policy.maxAttempts),
47157
+ gte10(adsOperations.reconcileAttempts, config.policy.maxAttempts),
46911
47158
  or10(
46912
47159
  and35(
46913
47160
  eq43(adsOperations.state, AdsOperationStates.pending),
46914
- lte8(adsOperations.updatedAt, pendingCutoff)
47161
+ lte9(adsOperations.updatedAt, pendingCutoff)
46915
47162
  ),
46916
47163
  eq43(adsOperations.state, AdsOperationStates.unknown),
46917
47164
  and35(
46918
47165
  eq43(adsOperations.state, AdsOperationStates.reconciling),
46919
- or10(isNull5(adsOperations.leaseExpiresAt), lte8(adsOperations.leaseExpiresAt, nowIso))
47166
+ or10(isNull5(adsOperations.leaseExpiresAt), lte9(adsOperations.leaseExpiresAt, nowIso))
46920
47167
  )
46921
47168
  ),
46922
47169
  or10(
@@ -46979,9 +47226,9 @@ function assertExpectedUpdatedAt(entity, expected) {
46979
47226
  });
46980
47227
  }
46981
47228
  }
46982
- function assertPausedForUpdate(entity) {
47229
+ function assertPausedForWrite(entity, action = "updating") {
46983
47230
  if (entity.status !== AdsEntityStatuses.paused) {
46984
- throw validationError("Pause the upstream ad entity before updating it", {
47231
+ throw validationError(`Pause the upstream ad entity before ${action} it`, {
46985
47232
  actualStatus: entity.status
46986
47233
  });
46987
47234
  }
@@ -47449,7 +47696,7 @@ async function adsRoutes(app, opts) {
47449
47696
  run: async () => {
47450
47697
  const current = await operator.getCampaign(apiKey, request.params.id);
47451
47698
  assertExpectedUpdatedAt(current, expectedUpdatedAt);
47452
- assertPausedForUpdate(current);
47699
+ assertPausedForWrite(current);
47453
47700
  return operator.updateCampaign(apiKey, request.params.id, update);
47454
47701
  }
47455
47702
  });
@@ -47480,7 +47727,7 @@ async function adsRoutes(app, opts) {
47480
47727
  run: async () => {
47481
47728
  const current = await operator.getAdGroup(apiKey, request.params.id);
47482
47729
  assertExpectedUpdatedAt(current, expectedUpdatedAt);
47483
- assertPausedForUpdate(current);
47730
+ assertPausedForWrite(current);
47484
47731
  if (update.maxBidMicros === void 0) {
47485
47732
  return operator.updateAdGroup(apiKey, request.params.id, update);
47486
47733
  }
@@ -47522,7 +47769,7 @@ async function adsRoutes(app, opts) {
47522
47769
  run: async () => {
47523
47770
  const current = await operator.getAd(apiKey, request.params.id);
47524
47771
  assertExpectedUpdatedAt(current, expectedUpdatedAt);
47525
- assertPausedForUpdate(current);
47772
+ assertPausedForWrite(current);
47526
47773
  return operator.updateAd(apiKey, request.params.id, update);
47527
47774
  }
47528
47775
  });
@@ -47603,6 +47850,93 @@ async function adsRoutes(app, opts) {
47603
47850
  });
47604
47851
  }
47605
47852
  );
47853
+ app.post(
47854
+ "/projects/:name/ads/campaigns/:id/archive",
47855
+ async (request) => {
47856
+ requireScope(request, ADS_WRITE_SCOPE);
47857
+ const project = resolveProject(app.db, request.params.name);
47858
+ const body = parseBody4(adsArchiveRequestSchema, request.body);
47859
+ const { apiKey, adAccountId, operator } = await resolveOperatorForProject(project);
47860
+ return executeAdsOperation(app, request, {
47861
+ projectId: project.id,
47862
+ adAccountId,
47863
+ operationKey: body.operationKey,
47864
+ kind: AdsOperationKinds.campaign_archive,
47865
+ entityType: AdsEntityTypes.campaign,
47866
+ payload: { id: request.params.id, expectedUpdatedAt: body.expectedUpdatedAt },
47867
+ knownEntityId: request.params.id,
47868
+ reconciliation: {
47869
+ strategy: AdsReconcileStrategies.known_entity,
47870
+ fields: { status: AdsEntityStatuses.archived }
47871
+ },
47872
+ expectedStatus: AdsEntityStatuses.archived,
47873
+ run: async () => {
47874
+ const current = await operator.getCampaign(apiKey, request.params.id);
47875
+ assertExpectedUpdatedAt(current, body.expectedUpdatedAt);
47876
+ assertPausedForWrite(current, "archiving");
47877
+ return operator.archiveCampaign(apiKey, request.params.id);
47878
+ }
47879
+ });
47880
+ }
47881
+ );
47882
+ app.post(
47883
+ "/projects/:name/ads/ad-groups/:id/archive",
47884
+ async (request) => {
47885
+ requireScope(request, ADS_WRITE_SCOPE);
47886
+ const project = resolveProject(app.db, request.params.name);
47887
+ const body = parseBody4(adsArchiveRequestSchema, request.body);
47888
+ const { apiKey, adAccountId, operator } = await resolveOperatorForProject(project);
47889
+ return executeAdsOperation(app, request, {
47890
+ projectId: project.id,
47891
+ adAccountId,
47892
+ operationKey: body.operationKey,
47893
+ kind: AdsOperationKinds.ad_group_archive,
47894
+ entityType: AdsEntityTypes.ad_group,
47895
+ payload: { id: request.params.id, expectedUpdatedAt: body.expectedUpdatedAt },
47896
+ knownEntityId: request.params.id,
47897
+ reconciliation: {
47898
+ strategy: AdsReconcileStrategies.known_entity,
47899
+ fields: { status: AdsEntityStatuses.archived }
47900
+ },
47901
+ expectedStatus: AdsEntityStatuses.archived,
47902
+ run: async () => {
47903
+ const current = await operator.getAdGroup(apiKey, request.params.id);
47904
+ assertExpectedUpdatedAt(current, body.expectedUpdatedAt);
47905
+ assertPausedForWrite(current, "archiving");
47906
+ return operator.archiveAdGroup(apiKey, request.params.id);
47907
+ }
47908
+ });
47909
+ }
47910
+ );
47911
+ app.post(
47912
+ "/projects/:name/ads/ads/:id/archive",
47913
+ async (request) => {
47914
+ requireScope(request, ADS_WRITE_SCOPE);
47915
+ const project = resolveProject(app.db, request.params.name);
47916
+ const body = parseBody4(adsArchiveRequestSchema, request.body);
47917
+ const { apiKey, adAccountId, operator } = await resolveOperatorForProject(project);
47918
+ return executeAdsOperation(app, request, {
47919
+ projectId: project.id,
47920
+ adAccountId,
47921
+ operationKey: body.operationKey,
47922
+ kind: AdsOperationKinds.ad_archive,
47923
+ entityType: AdsEntityTypes.ad,
47924
+ payload: { id: request.params.id, expectedUpdatedAt: body.expectedUpdatedAt },
47925
+ knownEntityId: request.params.id,
47926
+ reconciliation: {
47927
+ strategy: AdsReconcileStrategies.known_entity,
47928
+ fields: { status: AdsEntityStatuses.archived }
47929
+ },
47930
+ expectedStatus: AdsEntityStatuses.archived,
47931
+ run: async () => {
47932
+ const current = await operator.getAd(apiKey, request.params.id);
47933
+ assertExpectedUpdatedAt(current, body.expectedUpdatedAt);
47934
+ assertPausedForWrite(current, "archiving");
47935
+ return operator.archiveAd(apiKey, request.params.id);
47936
+ }
47937
+ });
47938
+ }
47939
+ );
47606
47940
  app.post("/projects/:name/ads/sync", async (request) => {
47607
47941
  requireScope(request, ADS_WRITE_SCOPE);
47608
47942
  const project = resolveProject(app.db, request.params.name);
@@ -47705,8 +48039,8 @@ async function adsRoutes(app, opts) {
47705
48039
  const conditions = [eq43(adsInsightsDaily.projectId, project.id)];
47706
48040
  if (parsedLevel) conditions.push(eq43(adsInsightsDaily.level, parsedLevel));
47707
48041
  if (entityId) conditions.push(eq43(adsInsightsDaily.entityId, entityId));
47708
- if (from) conditions.push(gte9(adsInsightsDaily.date, from));
47709
- if (to) conditions.push(lte8(adsInsightsDaily.date, to));
48042
+ if (from) conditions.push(gte10(adsInsightsDaily.date, from));
48043
+ if (to) conditions.push(lte9(adsInsightsDaily.date, to));
47710
48044
  const rows = app.db.select().from(adsInsightsDaily).where(and35(...conditions)).orderBy(asc8(adsInsightsDaily.date)).all();
47711
48045
  const conn = app.db.select().from(adsConnections).where(eq43(adsConnections.projectId, project.id)).get();
47712
48046
  const accountToday = adsAccountToday(conn?.timezone, (/* @__PURE__ */ new Date()).toISOString());
@@ -49086,118 +49420,6 @@ function buildSessionHistory(landingPageDays, dailyTotals) {
49086
49420
  });
49087
49421
  }
49088
49422
 
49089
- // ../api-routes/src/ga-ai-referral-aggregation.ts
49090
- function normalizeAiTrafficClass(value) {
49091
- return value === AiReferralTrafficClasses.paid ? AiReferralTrafficClasses.paid : AiReferralTrafficClasses.organic;
49092
- }
49093
- function emptyAiCounts() {
49094
- return { sessions: 0 };
49095
- }
49096
- function resolveWinningDimensions(rows) {
49097
- const groups = /* @__PURE__ */ new Map();
49098
- for (const row of rows) {
49099
- const key = `${row.date}\0${row.source}\0${row.medium}`;
49100
- let group = groups.get(key);
49101
- if (!group) {
49102
- group = { date: row.date, source: row.source, medium: row.medium, byDimension: /* @__PURE__ */ new Map() };
49103
- groups.set(key, group);
49104
- }
49105
- let dim = group.byDimension.get(row.sourceDimension);
49106
- if (!dim) {
49107
- dim = { paidSessions: 0, organicSessions: 0 };
49108
- group.byDimension.set(row.sourceDimension, dim);
49109
- }
49110
- const sessions = row.sessions ?? 0;
49111
- if (normalizeAiTrafficClass(row.trafficClass) === AiReferralTrafficClasses.paid) {
49112
- dim.paidSessions += sessions;
49113
- } else {
49114
- dim.organicSessions += sessions;
49115
- }
49116
- }
49117
- const winners = [];
49118
- for (const group of groups.values()) {
49119
- const dims = [...group.byDimension.values()];
49120
- const bestSessions = dims.reduce((best, d) => d.paidSessions + d.organicSessions > best.paidSessions + best.organicSessions ? d : best);
49121
- winners.push({
49122
- date: group.date,
49123
- source: group.source,
49124
- medium: group.medium,
49125
- paidSessions: bestSessions.paidSessions,
49126
- organicSessions: bestSessions.organicSessions
49127
- });
49128
- }
49129
- return winners;
49130
- }
49131
- function summarizeAiReferralCounts(rows) {
49132
- const paidDeduped = emptyAiCounts();
49133
- const organicDeduped = emptyAiCounts();
49134
- const paidBySession = emptyAiCounts();
49135
- const organicBySession = emptyAiCounts();
49136
- const bySessionChannelGroup = /* @__PURE__ */ new Map();
49137
- for (const winner of resolveWinningDimensions(rows)) {
49138
- paidDeduped.sessions += winner.paidSessions;
49139
- organicDeduped.sessions += winner.organicSessions;
49140
- }
49141
- for (const row of rows) {
49142
- if (row.sourceDimension !== "session") continue;
49143
- const sessions = row.sessions ?? 0;
49144
- bySessionChannelGroup.set(
49145
- row.channelGroup,
49146
- (bySessionChannelGroup.get(row.channelGroup) ?? 0) + sessions
49147
- );
49148
- const bucket = normalizeAiTrafficClass(row.trafficClass) === AiReferralTrafficClasses.paid ? paidBySession : organicBySession;
49149
- bucket.sessions += sessions;
49150
- }
49151
- return {
49152
- paidDeduped,
49153
- organicDeduped,
49154
- paidBySession,
49155
- organicBySession,
49156
- bySessionChannelGroup,
49157
- deduped: { sessions: paidDeduped.sessions + organicDeduped.sessions },
49158
- bySession: { sessions: paidBySession.sessions + organicBySession.sessions }
49159
- };
49160
- }
49161
- function buildAiReferralDailySeries(rows) {
49162
- const byDate = /* @__PURE__ */ new Map();
49163
- const sourceTotals = /* @__PURE__ */ new Map();
49164
- let totalSessions = 0;
49165
- let totalPaidSessions = 0;
49166
- let totalOrganicSessions = 0;
49167
- for (const winner of resolveWinningDimensions(rows)) {
49168
- const sessions = winner.paidSessions + winner.organicSessions;
49169
- let bySource = byDate.get(winner.date);
49170
- if (!bySource) {
49171
- bySource = /* @__PURE__ */ new Map();
49172
- byDate.set(winner.date, bySource);
49173
- }
49174
- let bucket = bySource.get(winner.source);
49175
- if (!bucket) {
49176
- bucket = { source: winner.source, sessions: 0, paidSessions: 0, organicSessions: 0 };
49177
- bySource.set(winner.source, bucket);
49178
- }
49179
- bucket.sessions += sessions;
49180
- bucket.paidSessions += winner.paidSessions;
49181
- bucket.organicSessions += winner.organicSessions;
49182
- sourceTotals.set(winner.source, (sourceTotals.get(winner.source) ?? 0) + sessions);
49183
- totalSessions += sessions;
49184
- totalPaidSessions += winner.paidSessions;
49185
- totalOrganicSessions += winner.organicSessions;
49186
- }
49187
- const days = [...byDate.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([date, bySource]) => {
49188
- const sources2 = [...bySource.values()].sort((a, b) => b.sessions - a.sessions || a.source.localeCompare(b.source));
49189
- return {
49190
- date,
49191
- sessions: sources2.reduce((sum, s) => sum + s.sessions, 0),
49192
- paidSessions: sources2.reduce((sum, s) => sum + s.paidSessions, 0),
49193
- organicSessions: sources2.reduce((sum, s) => sum + s.organicSessions, 0),
49194
- bySource: sources2
49195
- };
49196
- });
49197
- const sources = [...sourceTotals.entries()].sort(([aSource, aSessions], [bSource, bSessions]) => bSessions - aSessions || aSource.localeCompare(bSource)).map(([source]) => source);
49198
- return { days, sources, totalSessions, totalPaidSessions, totalOrganicSessions };
49199
- }
49200
-
49201
49423
  // ../api-routes/src/ga.ts
49202
49424
  function gaLog(level, action, ctx) {
49203
49425
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
@@ -49249,16 +49471,26 @@ function buildChannelBreakdown(input) {
49249
49471
  }
49250
49472
  };
49251
49473
  }
49252
- function pickWinningDimension(rows, tupleKey) {
49253
- const winners = /* @__PURE__ */ new Map();
49474
+ function keepWinningDimensionRows(rows, groupKey) {
49475
+ const sessionsByGroupAndDimension = /* @__PURE__ */ new Map();
49254
49476
  for (const row of rows) {
49255
- const key = tupleKey(row);
49256
- const existing = winners.get(key);
49257
- if (!existing || (row.sessions ?? 0) > (existing.sessions ?? 0)) {
49258
- winners.set(key, row);
49259
- }
49477
+ const key = groupKey(row);
49478
+ let byDimension = sessionsByGroupAndDimension.get(key);
49479
+ if (!byDimension) {
49480
+ byDimension = /* @__PURE__ */ new Map();
49481
+ sessionsByGroupAndDimension.set(key, byDimension);
49482
+ }
49483
+ byDimension.set(
49484
+ row.sourceDimension,
49485
+ (byDimension.get(row.sourceDimension) ?? 0) + (row.sessions ?? 0)
49486
+ );
49260
49487
  }
49261
- return [...winners.values()].sort((a, b) => (b.sessions ?? 0) - (a.sessions ?? 0));
49488
+ const winningDimension = /* @__PURE__ */ new Map();
49489
+ for (const [key, byDimension] of sessionsByGroupAndDimension) {
49490
+ const winner = pickWinningAttributionDimension(byDimension);
49491
+ if (winner) winningDimension.set(key, winner);
49492
+ }
49493
+ return rows.filter((row) => winningDimension.get(groupKey(row)) === row.sourceDimension).sort((a, b) => (b.sessions ?? 0) - (a.sessions ?? 0));
49262
49494
  }
49263
49495
  function persistAcquisitionMeasurement(db, input) {
49264
49496
  const { startDate, endDate } = input.report;
@@ -50017,13 +50249,10 @@ async function ga4Routes(app, opts) {
50017
50249
  channelGroup: gaAiReferrals.channelGroup,
50018
50250
  sessions: gaAiReferrals.sessions
50019
50251
  }).from(gaAiReferrals).where(and38(...aiConditions)).all();
50020
- const aiReferrals = pickWinningDimension(
50021
- aiReferralRows,
50022
- (r) => `${r.source}\0${r.medium}\0${r.trafficClass}`
50023
- );
50024
- const aiReferralLandingPages = pickWinningDimension(
50252
+ const aiReferrals = keepWinningDimensionRows(aiReferralRows, (r) => r.source);
50253
+ const aiReferralLandingPages = keepWinningDimensionRows(
50025
50254
  aiReferralLandingPageRows,
50026
- (r) => `${r.source}\0${r.medium}\0${r.trafficClass}\0${r.landingPage}`
50255
+ (r) => `${r.source}\0${r.landingPage}`
50027
50256
  );
50028
50257
  const aiSummary = summarizeAiReferralCounts(aiRowsForTotals);
50029
50258
  const socialReferrals = app.db.select({
@@ -52796,7 +53025,7 @@ import crypto43 from "crypto";
52796
53025
  import { isIP } from "net";
52797
53026
  import { isDeepStrictEqual } from "util";
52798
53027
  import { Agent as UndiciAgent } from "undici";
52799
- import { and as and44, desc as desc23, eq as eq51, gte as gte11, inArray as inArray18, lte as lte11, sql as sql22 } from "drizzle-orm";
53028
+ import { and as and44, desc as desc23, eq as eq51, gte as gte12, inArray as inArray18, lte as lte12, sql as sql22 } from "drizzle-orm";
52800
53029
 
52801
53030
  // ../api-routes/src/cloudflare-worker-version.ts
52802
53031
  var CURRENT_CLOUDFLARE_WORKER_VERSION = "1.0.1";
@@ -69407,7 +69636,7 @@ async function ackCloudflareQueueMessages(client, options) {
69407
69636
 
69408
69637
  // ../api-routes/src/traffic-event-ingest.ts
69409
69638
  import crypto42 from "crypto";
69410
- import { and as and42, eq as eq49, gte as gte10, lt as lt10, lte as lte9, sql as sql21 } from "drizzle-orm";
69639
+ import { and as and42, eq as eq49, gte as gte11, lt as lt10, lte as lte10, sql as sql21 } from "drizzle-orm";
69411
69640
 
69412
69641
  // ../api-routes/src/raw-event-sample-retention.ts
69413
69642
  import { and as and41, eq as eq48, lt as lt9, sql as sql20 } from "drizzle-orm";
@@ -69486,7 +69715,7 @@ function writeTrafficEventBatch(opts) {
69486
69715
  );
69487
69716
  tx.delete(trafficEventReceipts).where(and42(
69488
69717
  eq49(trafficEventReceipts.sourceId, opts.sourceId),
69489
- lte9(trafficEventReceipts.expiresAt, opts.receivedAt)
69718
+ lte10(trafficEventReceipts.expiresAt, opts.receivedAt)
69490
69719
  )).run();
69491
69720
  const claimedEvents = [];
69492
69721
  for (const event of opts.events) {
@@ -69686,7 +69915,7 @@ function writeTrafficEventBatch(opts) {
69686
69915
  if (remaining === void 0) {
69687
69916
  const existing = tx.select({ total: sql21`COUNT(*)` }).from(rawEventSamples).where(and42(
69688
69917
  eq49(rawEventSamples.sourceId, opts.sourceId),
69689
- gte10(rawEventSamples.ts, hour.start),
69918
+ gte11(rawEventSamples.ts, hour.start),
69690
69919
  lt10(rawEventSamples.ts, hour.end)
69691
69920
  )).get();
69692
69921
  remaining = Math.max(0, hourlySampleLimit - Number(existing?.total ?? 0));
@@ -69744,7 +69973,7 @@ function writeTrafficEventBatch(opts) {
69744
69973
  }
69745
69974
 
69746
69975
  // ../api-routes/src/traffic-sync-lease.ts
69747
- import { and as and43, eq as eq50, isNull as isNull6, lte as lte10, or as or11 } from "drizzle-orm";
69976
+ import { and as and43, eq as eq50, isNull as isNull6, lte as lte11, or as or11 } from "drizzle-orm";
69748
69977
  function leaseExpiresAt(now, ttlMs) {
69749
69978
  const nowMs = Date.parse(now);
69750
69979
  if (!Number.isFinite(nowMs)) throw new RangeError("Traffic sync lease now must be an ISO instant");
@@ -69761,7 +69990,7 @@ function tryClaimTrafficSyncLease(input) {
69761
69990
  eq50(trafficSources.id, input.sourceId),
69762
69991
  or11(
69763
69992
  isNull6(trafficSources.syncLeaseOwner),
69764
- lte10(trafficSources.syncLeaseExpiresAt, input.now),
69993
+ lte11(trafficSources.syncLeaseExpiresAt, input.now),
69765
69994
  eq50(trafficSources.syncLeaseOwner, input.owner)
69766
69995
  )
69767
69996
  )).run();
@@ -70257,29 +70486,29 @@ async function runBackfillTask(options) {
70257
70486
  tx.delete(crawlerEventsHourly).where(
70258
70487
  and44(
70259
70488
  eq51(crawlerEventsHourly.sourceId, sourceRow2.id),
70260
- gte11(crawlerEventsHourly.tsHour, windowStartIso),
70261
- lte11(crawlerEventsHourly.tsHour, windowEndIso)
70489
+ gte12(crawlerEventsHourly.tsHour, windowStartIso),
70490
+ lte12(crawlerEventsHourly.tsHour, windowEndIso)
70262
70491
  )
70263
70492
  ).run();
70264
70493
  tx.delete(aiUserFetchEventsHourly).where(
70265
70494
  and44(
70266
70495
  eq51(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
70267
- gte11(aiUserFetchEventsHourly.tsHour, windowStartIso),
70268
- lte11(aiUserFetchEventsHourly.tsHour, windowEndIso)
70496
+ gte12(aiUserFetchEventsHourly.tsHour, windowStartIso),
70497
+ lte12(aiUserFetchEventsHourly.tsHour, windowEndIso)
70269
70498
  )
70270
70499
  ).run();
70271
70500
  tx.delete(aiReferralEventsHourly).where(
70272
70501
  and44(
70273
70502
  eq51(aiReferralEventsHourly.sourceId, sourceRow2.id),
70274
- gte11(aiReferralEventsHourly.tsHour, windowStartIso),
70275
- lte11(aiReferralEventsHourly.tsHour, windowEndIso)
70503
+ gte12(aiReferralEventsHourly.tsHour, windowStartIso),
70504
+ lte12(aiReferralEventsHourly.tsHour, windowEndIso)
70276
70505
  )
70277
70506
  ).run();
70278
70507
  tx.delete(rawEventSamples).where(
70279
70508
  and44(
70280
70509
  eq51(rawEventSamples.sourceId, sourceRow2.id),
70281
- gte11(rawEventSamples.ts, windowStartIso),
70282
- lte11(rawEventSamples.ts, windowEndIso)
70510
+ gte12(rawEventSamples.ts, windowStartIso),
70511
+ lte12(rawEventSamples.ts, windowEndIso)
70283
70512
  )
70284
70513
  ).run();
70285
70514
  for (const bucket of report.crawlerEventsHourly) {
@@ -72443,7 +72672,7 @@ async function trafficRoutes(app, opts) {
72443
72672
  }).from(crawlerEventsHourly).where(
72444
72673
  and44(
72445
72674
  eq51(crawlerEventsHourly.sourceId, row.id),
72446
- gte11(crawlerEventsHourly.tsHour, since)
72675
+ gte12(crawlerEventsHourly.tsHour, since)
72447
72676
  )
72448
72677
  ).groupBy(crawlerEventsHourly.pathNormalized).all();
72449
72678
  const crawlerSegments = segmentCrawlerHits(
@@ -72453,7 +72682,7 @@ async function trafficRoutes(app, opts) {
72453
72682
  const aiUserFetchTotals = app.db.select({ total: sql22`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
72454
72683
  and44(
72455
72684
  eq51(aiUserFetchEventsHourly.sourceId, row.id),
72456
- gte11(aiUserFetchEventsHourly.tsHour, since)
72685
+ gte12(aiUserFetchEventsHourly.tsHour, since)
72457
72686
  )
72458
72687
  ).get();
72459
72688
  const aiTotals = app.db.select({
@@ -72463,13 +72692,13 @@ async function trafficRoutes(app, opts) {
72463
72692
  }).from(aiReferralEventsHourly).where(
72464
72693
  and44(
72465
72694
  eq51(aiReferralEventsHourly.sourceId, row.id),
72466
- gte11(aiReferralEventsHourly.tsHour, since)
72695
+ gte12(aiReferralEventsHourly.tsHour, since)
72467
72696
  )
72468
72697
  ).get();
72469
72698
  const sampleTotals = app.db.select({ total: sql22`COUNT(*)` }).from(rawEventSamples).where(
72470
72699
  and44(
72471
72700
  eq51(rawEventSamples.sourceId, row.id),
72472
- gte11(rawEventSamples.ts, since)
72701
+ gte12(rawEventSamples.ts, since)
72473
72702
  )
72474
72703
  ).get();
72475
72704
  const latestRun = app.db.select().from(runs).where(
@@ -72648,8 +72877,8 @@ async function trafficRoutes(app, opts) {
72648
72877
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
72649
72878
  const crawlerFilters = [
72650
72879
  eq51(crawlerEventsHourly.projectId, project.id),
72651
- gte11(crawlerEventsHourly.tsHour, sinceIso),
72652
- lte11(crawlerEventsHourly.tsHour, untilIso)
72880
+ gte12(crawlerEventsHourly.tsHour, sinceIso),
72881
+ lte12(crawlerEventsHourly.tsHour, untilIso)
72653
72882
  ];
72654
72883
  if (sourceIdParam) crawlerFilters.push(eq51(crawlerEventsHourly.sourceId, sourceIdParam));
72655
72884
  const crawlerWhere = and44(...crawlerFilters);
@@ -72723,8 +72952,8 @@ async function trafficRoutes(app, opts) {
72723
72952
  if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
72724
72953
  const userFetchFilters = [
72725
72954
  eq51(aiUserFetchEventsHourly.projectId, project.id),
72726
- gte11(aiUserFetchEventsHourly.tsHour, sinceIso),
72727
- lte11(aiUserFetchEventsHourly.tsHour, untilIso)
72955
+ gte12(aiUserFetchEventsHourly.tsHour, sinceIso),
72956
+ lte12(aiUserFetchEventsHourly.tsHour, untilIso)
72728
72957
  ];
72729
72958
  if (sourceIdParam) userFetchFilters.push(eq51(aiUserFetchEventsHourly.sourceId, sourceIdParam));
72730
72959
  const userFetchWhere = and44(...userFetchFilters);
@@ -72762,8 +72991,8 @@ async function trafficRoutes(app, opts) {
72762
72991
  if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
72763
72992
  const aiFilters = [
72764
72993
  eq51(aiReferralEventsHourly.projectId, project.id),
72765
- gte11(aiReferralEventsHourly.tsHour, sinceIso),
72766
- lte11(aiReferralEventsHourly.tsHour, untilIso)
72994
+ gte12(aiReferralEventsHourly.tsHour, sinceIso),
72995
+ lte12(aiReferralEventsHourly.tsHour, untilIso)
72767
72996
  ];
72768
72997
  if (sourceIdParam) aiFilters.push(eq51(aiReferralEventsHourly.sourceId, sourceIdParam));
72769
72998
  const aiWhere = and44(...aiFilters);
@@ -74651,7 +74880,7 @@ var RUNTIME_STATE_CHECKS = [
74651
74880
  ];
74652
74881
 
74653
74882
  // ../api-routes/src/doctor/checks/traffic-source.ts
74654
- import { and as and47, eq as eq57, gte as gte12, inArray as inArray19, ne as ne7, sql as sql23 } from "drizzle-orm";
74883
+ import { and as and47, eq as eq57, gte as gte13, inArray as inArray19, ne as ne7, sql as sql23 } from "drizzle-orm";
74655
74884
  var RECENT_DATA_WARN_DAYS = 7;
74656
74885
  var RECENT_DATA_FAIL_DAYS = 30;
74657
74886
  function isCloudflareDirectPush(source) {
@@ -74800,7 +75029,7 @@ var recentDataCheck = {
74800
75029
  and47(
74801
75030
  eq57(crawlerEventsHourly.projectId, ctx.project.id),
74802
75031
  inArray19(crawlerEventsHourly.sourceId, activeSourceIds),
74803
- gte12(crawlerEventsHourly.tsHour, warnCutoff)
75032
+ gte13(crawlerEventsHourly.tsHour, warnCutoff)
74804
75033
  )
74805
75034
  ).get()?.total ?? 0
74806
75035
  );
@@ -74809,7 +75038,7 @@ var recentDataCheck = {
74809
75038
  and47(
74810
75039
  eq57(aiReferralEventsHourly.projectId, ctx.project.id),
74811
75040
  inArray19(aiReferralEventsHourly.sourceId, activeSourceIds),
74812
- gte12(aiReferralEventsHourly.tsHour, warnCutoff)
75041
+ gte13(aiReferralEventsHourly.tsHour, warnCutoff)
74813
75042
  )
74814
75043
  ).get()?.total ?? 0
74815
75044
  );
@@ -74818,7 +75047,7 @@ var recentDataCheck = {
74818
75047
  and47(
74819
75048
  eq57(aiUserFetchEventsHourly.projectId, ctx.project.id),
74820
75049
  inArray19(aiUserFetchEventsHourly.sourceId, activeSourceIds),
74821
- gte12(aiUserFetchEventsHourly.tsHour, warnCutoff)
75050
+ gte13(aiUserFetchEventsHourly.tsHour, warnCutoff)
74822
75051
  )
74823
75052
  ).get()?.total ?? 0
74824
75053
  );
@@ -74844,7 +75073,7 @@ var recentDataCheck = {
74844
75073
  and47(
74845
75074
  eq57(crawlerEventsHourly.projectId, ctx.project.id),
74846
75075
  inArray19(crawlerEventsHourly.sourceId, activeSourceIds),
74847
- gte12(crawlerEventsHourly.tsHour, failCutoff)
75076
+ gte13(crawlerEventsHourly.tsHour, failCutoff)
74848
75077
  )
74849
75078
  ).get()?.total ?? 0
74850
75079
  );
@@ -74853,7 +75082,7 @@ var recentDataCheck = {
74853
75082
  and47(
74854
75083
  eq57(aiReferralEventsHourly.projectId, ctx.project.id),
74855
75084
  inArray19(aiReferralEventsHourly.sourceId, activeSourceIds),
74856
- gte12(aiReferralEventsHourly.tsHour, failCutoff)
75085
+ gte13(aiReferralEventsHourly.tsHour, failCutoff)
74857
75086
  )
74858
75087
  ).get()?.total ?? 0
74859
75088
  );
@@ -74862,7 +75091,7 @@ var recentDataCheck = {
74862
75091
  and47(
74863
75092
  eq57(aiUserFetchEventsHourly.projectId, ctx.project.id),
74864
75093
  inArray19(aiUserFetchEventsHourly.sourceId, activeSourceIds),
74865
- gte12(aiUserFetchEventsHourly.tsHour, failCutoff)
75094
+ gte13(aiUserFetchEventsHourly.tsHour, failCutoff)
74866
75095
  )
74867
75096
  ).get()?.total ?? 0
74868
75097
  );
@@ -75777,7 +76006,7 @@ async function doctorRoutes(app, opts) {
75777
76006
 
75778
76007
  // ../api-routes/src/discovery/routes.ts
75779
76008
  import crypto45 from "crypto";
75780
- import { and as and48, desc as desc24, eq as eq58, gte as gte13, inArray as inArray20, isNull as isNull7, or as or12 } from "drizzle-orm";
76009
+ import { and as and48, desc as desc24, eq as eq58, gte as gte14, inArray as inArray20, isNull as isNull7, or as or12 } from "drizzle-orm";
75781
76010
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
75782
76011
  async function discoveryRoutes(app, opts) {
75783
76012
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -75837,7 +76066,7 @@ async function discoveryRoutes(app, opts) {
75837
76066
  DiscoverySessionStatuses.seeding,
75838
76067
  DiscoverySessionStatuses.probing
75839
76068
  ]),
75840
- gte13(discoverySessions.createdAt, ageFloorIso)
76069
+ gte14(discoverySessions.createdAt, ageFloorIso)
75841
76070
  )).orderBy(desc24(discoverySessions.createdAt)).get();
75842
76071
  if (existing && existing.runId) {
75843
76072
  return { reused: true, sessionId: existing.id, runId: existing.runId };
@@ -79181,7 +79410,7 @@ var IntelligenceService = class {
79181
79410
  // Never look forward. The window is this run's own past, so
79182
79411
  // re-analyzing a historical run compares it against its true
79183
79412
  // predecessor rather than against sweeps that came after it.
79184
- lte12(runChronologyKey, currentRunRecord.finishedAt ?? currentRunRecord.createdAt),
79413
+ lte13(runChronologyKey, currentRunRecord.finishedAt ?? currentRunRecord.createdAt),
79185
79414
  // MEASUREMENT: only runs that actually wrote snapshots — see above.
79186
79415
  // Index seek on idx_snapshots_run, short-circuited on the first row.
79187
79416
  exists(
@@ -79271,8 +79500,8 @@ var IntelligenceService = class {
79271
79500
  const selected = this.db.select().from(gbpLocations).where(and51(
79272
79501
  eq62(gbpLocations.projectId, projectId),
79273
79502
  eq62(gbpLocations.selected, true),
79274
- gte14(gbpLocations.syncedAt, windowStart),
79275
- lte12(gbpLocations.syncedAt, windowEnd)
79503
+ gte15(gbpLocations.syncedAt, windowStart),
79504
+ lte13(gbpLocations.syncedAt, windowEnd)
79276
79505
  )).all();
79277
79506
  if (selected.length === 0) {
79278
79507
  log.info("gbp-intelligence.skip", { runId, reason: "no locations synced during run" });