@canonry/canonry 4.180.3 → 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-CRtw07lK.js → AuditHistoryPanel-CdM_9qX1.js} +1 -1
  3. package/assets/assets/{BacklinksPage-DURChJhG.js → BacklinksPage-DWWMPSPn.js} +1 -1
  4. package/assets/assets/{HistoryPage-DsSSJmOs.js → HistoryPage-m7TtGFBQ.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-DalkH9IT.js → MeasurementPropertyPage-CYBfBtEz.js} +1 -1
  6. package/assets/assets/{ProjectPage-CXLK_E8n.js → ProjectPage-CNPcNrIR.js} +1 -1
  7. package/assets/assets/{RunRow-DDwwyIxg.js → RunRow-O4u505Fc.js} +1 -1
  8. package/assets/assets/{RunsPage-DCgaTijO.js → RunsPage-CCzFT4X0.js} +1 -1
  9. package/assets/assets/{SettingsPage-DwQqOC3_.js → SettingsPage-DOmE3JbU.js} +1 -1
  10. package/assets/assets/{SiteHealthSection-Fg2-UL0h.js → SiteHealthSection-BZFVE2h9.js} +3 -3
  11. package/assets/assets/{TrafficPage-CefISwhk.js → TrafficPage-DYxOEVbW.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-Rotzu2tN.js → TrafficSourceDetailPage-Qd5Zpx_8.js} +1 -1
  13. package/assets/assets/{extract-error-message-D6lzT2hr.js → extract-error-message-B0bdIDVc.js} +1 -1
  14. package/assets/assets/{index-BuZ_xmnc.js → index-HmdbSL4h.js} +2 -2
  15. package/assets/assets/{react-sigma_core.esm.min-D-IM5qRE.js → react-sigma_core.esm.min-DXCrX1Ie.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-W3YSGMYK.js → chunk-2J65ALIR.js} +4 -4
  18. package/dist/{chunk-CKGWOYPO.js → chunk-DK23XZ6C.js} +2 -2
  19. package/dist/{chunk-YPDKZ73I.js → chunk-JJIESVNL.js} +1 -1
  20. package/dist/{chunk-JWPN6RU7.js → chunk-UESVOQRS.js} +1 -1
  21. package/dist/{chunk-OJDH2F6I.js → chunk-V4QGOJNH.js} +518 -441
  22. package/dist/cli.js +17 -6
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-DSYYBDR2.js → intelligence-service-XNAKDGDQ.js} +2 -2
  25. package/dist/mcp.js +3 -3
  26. package/package.json +9 -9
@@ -643,10 +643,10 @@ import {
643
643
  wordpressSchemaDeployResultDtoSchema,
644
644
  wordpressSchemaStatusResultDtoSchema,
645
645
  wordpressStatusDtoSchema
646
- } from "./chunk-JWPN6RU7.js";
646
+ } from "./chunk-UESVOQRS.js";
647
647
 
648
648
  // src/intelligence-service.ts
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 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";
650
650
 
651
651
  // ../db/src/client.ts
652
652
  import { mkdirSync } from "fs";
@@ -22588,16 +22588,153 @@ function nonSubresourceReferralPathCondition() {
22588
22588
  }
22589
22589
 
22590
22590
  // ../api-routes/src/report.ts
22591
- 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";
22592
22592
 
22593
22593
  // ../api-routes/src/content.ts
22594
22594
  import crypto22 from "crypto";
22595
- 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
+ }
22596
22734
 
22597
22735
  // ../api-routes/src/content-data.ts
22598
- import { and as and21, eq as eq26, desc as desc10, inArray as inArray11 } from "drizzle-orm";
22599
22736
  var RECENT_RUNS_WINDOW = 5;
22600
- function loadOrchestratorInput(db, project, locationFilter = void 0) {
22737
+ function loadOrchestratorInput(db, project, locationFilter = void 0, gscWindowDays = DEFAULT_CONTENT_GSC_WINDOW_DAYS) {
22601
22738
  const projectId = project.id;
22602
22739
  const ownDomain = hostOf(project.canonicalDomain) ?? "";
22603
22740
  const ownedDomains = project.ownedDomains ?? [];
@@ -22616,7 +22753,8 @@ function loadOrchestratorInput(db, project, locationFilter = void 0) {
22616
22753
  recentRunIds,
22617
22754
  latestRunId,
22618
22755
  ourDomains,
22619
- competitorSet
22756
+ competitorSet,
22757
+ gscWindowDays
22620
22758
  });
22621
22759
  const inventory = buildInventory({
22622
22760
  gscPages: listGscPagesForProject(db, projectId),
@@ -22644,7 +22782,7 @@ function loadOrchestratorInput(db, project, locationFilter = void 0) {
22644
22782
  };
22645
22783
  }
22646
22784
  function loadDomainClasses(db, projectId) {
22647
- 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();
22648
22786
  return new Map(rows.map((r) => [hostOf(r.domain) ?? "", r.competitorType]));
22649
22787
  }
22650
22788
  function buildQueryIntentModifiers(project, locationFilter) {
@@ -22716,18 +22854,18 @@ var US_REGION_ABBREVIATIONS = {
22716
22854
  wyoming: "wy"
22717
22855
  };
22718
22856
  function listQueries(db, projectId) {
22719
- 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();
22720
22858
  return rows.map((r) => r.text);
22721
22859
  }
22722
22860
  function listCompetitorDomains(db, projectId) {
22723
- 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();
22724
22862
  return rows.map((r) => r.domain);
22725
22863
  }
22726
22864
  function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter) {
22727
22865
  const rows = db.select({ id: runs.id, location: runs.location }).from(runs).where(
22728
- and21(
22729
- eq26(runs.projectId, projectId),
22730
- eq26(runs.kind, RunKinds["answer-visibility"]),
22866
+ and22(
22867
+ eq27(runs.projectId, projectId),
22868
+ eq27(runs.kind, RunKinds["answer-visibility"]),
22731
22869
  // Queued/running/failed/cancelled runs may have partial or no
22732
22870
  // snapshots; including them risks pointing latestRunId at a run with
22733
22871
  // no usable evidence.
@@ -22741,22 +22879,22 @@ function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter)
22741
22879
  return filtered.slice(0, limit).map((r) => r.id);
22742
22880
  }
22743
22881
  function lookupRunTimestamp(db, runId) {
22744
- 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();
22745
22883
  return row?.createdAt ?? "";
22746
22884
  }
22747
22885
  function listGscPagesForProject(db, projectId) {
22748
- 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();
22749
22887
  return rows.map((r) => r.page);
22750
22888
  }
22751
22889
  function listGa4LandingPagesForProject(db, projectId) {
22752
- 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();
22753
22891
  return rows.map((r) => r.landingPage);
22754
22892
  }
22755
22893
  function buildGaTrafficByPage(db, projectId) {
22756
22894
  const rows = db.select({
22757
22895
  landingPage: gaTrafficSnapshots.landingPage,
22758
22896
  sessions: gaTrafficSnapshots.sessions
22759
- }).from(gaTrafficSnapshots).where(eq26(gaTrafficSnapshots.projectId, projectId)).all();
22897
+ }).from(gaTrafficSnapshots).where(eq27(gaTrafficSnapshots.projectId, projectId)).all();
22760
22898
  const map = /* @__PURE__ */ new Map();
22761
22899
  for (const row of rows) {
22762
22900
  const path8 = extractPath2(row.landingPage);
@@ -22767,9 +22905,9 @@ function buildGaTrafficByPage(db, projectId) {
22767
22905
  }
22768
22906
  function sumAiReferralSessions(db, projectId) {
22769
22907
  const rows = db.select({ sessions: gaAiReferrals.sessions }).from(gaAiReferrals).where(
22770
- and21(
22771
- eq26(gaAiReferrals.projectId, projectId),
22772
- eq26(gaAiReferrals.sourceDimension, "session")
22908
+ and22(
22909
+ eq27(gaAiReferrals.projectId, projectId),
22910
+ eq27(gaAiReferrals.sourceDimension, "session")
22773
22911
  )
22774
22912
  ).all();
22775
22913
  return rows.reduce((acc, r) => acc + (r.sessions ?? 0), 0);
@@ -22778,7 +22916,7 @@ function buildCandidateQueries(opts) {
22778
22916
  if (opts.candidateQueryStrings.length === 0 || opts.recentRunIds.length === 0) {
22779
22917
  return opts.candidateQueryStrings.map((query) => emptyCandidate(query));
22780
22918
  }
22781
- 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();
22782
22920
  const queryIdByText = new Map(queryRows.map((r) => [r.text, r.id]));
22783
22921
  const candidateQueryIds = opts.candidateQueryStrings.map((q) => queryIdByText.get(q)).filter((id) => Boolean(id));
22784
22922
  const snapshotRows = filterTrackedSnapshots(opts.db.select().from(querySnapshots).where(inArray11(querySnapshots.runId, opts.recentRunIds)).all()).filter((r) => candidateQueryIds.includes(r.queryId));
@@ -22788,8 +22926,19 @@ function buildCandidateQueries(opts) {
22788
22926
  list.push(row);
22789
22927
  snapshotsByQuery.set(row.queryId, list);
22790
22928
  }
22791
- const gscRows = opts.db.select().from(gscSearchData).where(eq26(gscSearchData.projectId, opts.projectId)).all();
22792
- 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
+ );
22793
22942
  return opts.candidateQueryStrings.map((query) => {
22794
22943
  const queryId = queryIdByText.get(query);
22795
22944
  const snaps = queryId ? snapshotsByQuery.get(queryId) ?? [] : [];
@@ -22804,7 +22953,35 @@ function buildCandidateQueries(opts) {
22804
22953
  });
22805
22954
  });
22806
22955
  }
22807
- 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]));
22808
22985
  const accumulators = /* @__PURE__ */ new Map();
22809
22986
  for (const r of rows) {
22810
22987
  const page2 = extractPath2(r.page);
@@ -22830,12 +23007,16 @@ function aggregateGscByQuery(rows) {
22830
23007
  }
22831
23008
  const byQuery = /* @__PURE__ */ new Map();
22832
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;
22833
23014
  byQuery.set(query, {
22834
23015
  page: acc.bestPage,
22835
- position: acc.totalImpressions > 0 ? acc.weightedPositionSum / acc.totalImpressions : 0,
22836
- impressions: acc.totalImpressions,
22837
- clicks: acc.totalClicks,
22838
- ctr: acc.totalImpressions > 0 ? acc.totalClicks / acc.totalImpressions : 0
23016
+ position,
23017
+ impressions,
23018
+ clicks,
23019
+ ctr: impressions > 0 ? clicks / impressions : 0
22839
23020
  });
22840
23021
  }
22841
23022
  return byQuery;
@@ -22987,7 +23168,7 @@ function extractPath2(url) {
22987
23168
 
22988
23169
  // ../api-routes/src/content.ts
22989
23170
  function loadDismissedTargetRefs(db, projectId) {
22990
- 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();
22991
23172
  return new Set(rows.map((r) => r.targetRef));
22992
23173
  }
22993
23174
  function formatDismissalRow(row) {
@@ -23082,7 +23263,7 @@ async function contentRoutes(app, opts = {}) {
23082
23263
  });
23083
23264
  app.get("/projects/:name/content/dismissals", async (request) => {
23084
23265
  const project = resolveProject(app.db, request.params.name);
23085
- 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();
23086
23267
  const response = {
23087
23268
  dismissals: rows.map(formatDismissalRow)
23088
23269
  };
@@ -23111,9 +23292,9 @@ async function contentRoutes(app, opts = {}) {
23111
23292
  dismissedAt: now
23112
23293
  }
23113
23294
  }).run();
23114
- const row = app.db.select().from(contentTargetDismissals).where(and22(
23115
- eq27(contentTargetDismissals.projectId, project.id),
23116
- 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)
23117
23298
  )).get();
23118
23299
  if (!row) throw notFound("contentTargetDismissal", targetRef);
23119
23300
  return formatDismissalRow(row);
@@ -23121,9 +23302,9 @@ async function contentRoutes(app, opts = {}) {
23121
23302
  app.delete("/projects/:name/content/dismissals/:targetRef", async (request, reply) => {
23122
23303
  const project = resolveProject(app.db, request.params.name);
23123
23304
  const { targetRef } = request.params;
23124
- const result = app.db.delete(contentTargetDismissals).where(and22(
23125
- eq27(contentTargetDismissals.projectId, project.id),
23126
- eq27(contentTargetDismissals.targetRef, targetRef)
23305
+ const result = app.db.delete(contentTargetDismissals).where(and23(
23306
+ eq28(contentTargetDismissals.projectId, project.id),
23307
+ eq28(contentTargetDismissals.targetRef, targetRef)
23127
23308
  )).run();
23128
23309
  if (result.changes === 0) {
23129
23310
  throw notFound("contentTargetDismissal", targetRef);
@@ -23132,9 +23313,9 @@ async function contentRoutes(app, opts = {}) {
23132
23313
  });
23133
23314
  app.get("/projects/:name/content/recommendations/:targetRef/analysis", async (request, reply) => {
23134
23315
  const project = resolveProject(app.db, request.params.name);
23135
- const row = app.db.select().from(recommendationExplanations).where(and22(
23136
- eq27(recommendationExplanations.projectId, project.id),
23137
- 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)
23138
23319
  )).orderBy(desc11(recommendationExplanations.generatedAt)).limit(1).get();
23139
23320
  if (!row) {
23140
23321
  return reply.status(404).send({ error: { code: "NOT_FOUND", message: "No cached explanation for this targetRef." } });
@@ -23160,9 +23341,9 @@ async function contentRoutes(app, opts = {}) {
23160
23341
  throw notFound("contentRecommendation", targetRef);
23161
23342
  }
23162
23343
  if (!body.forceRefresh) {
23163
- const cached = app.db.select().from(recommendationExplanations).where(and22(
23164
- eq27(recommendationExplanations.projectId, project.id),
23165
- 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)
23166
23347
  )).orderBy(desc11(recommendationExplanations.generatedAt)).limit(1).get();
23167
23348
  if (cached) return reply.send(formatExplanationRow(cached));
23168
23349
  }
@@ -23199,10 +23380,10 @@ async function contentRoutes(app, opts = {}) {
23199
23380
  generatedAt: now
23200
23381
  }
23201
23382
  }).run();
23202
- const row = app.db.select().from(recommendationExplanations).where(and22(
23203
- eq27(recommendationExplanations.projectId, project.id),
23204
- eq27(recommendationExplanations.targetRef, targetRef),
23205
- 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)
23206
23387
  )).get();
23207
23388
  if (!row) throw notFound("recommendationExplanation", targetRef);
23208
23389
  return reply.send(formatExplanationRow(row));
@@ -23283,17 +23464,17 @@ async function contentRoutes(app, opts = {}) {
23283
23464
  generatedAt: now
23284
23465
  }
23285
23466
  }).run();
23286
- const row = app.db.select().from(recommendationBriefs).where(and22(
23287
- eq27(recommendationBriefs.projectId, project.id),
23288
- eq27(recommendationBriefs.targetRef, targetRef),
23289
- 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)
23290
23471
  )).get();
23291
23472
  if (!row) throw notFound("recommendationBrief", targetRef);
23292
23473
  return reply.send(formatBriefRow(row));
23293
23474
  });
23294
23475
  app.get("/projects/:name/content/domain-classifications", async (request) => {
23295
23476
  const project = resolveProject(app.db, request.params.name);
23296
- 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();
23297
23478
  const response = {
23298
23479
  classifications: rows.map((r) => ({
23299
23480
  domain: r.domain,
@@ -23307,13 +23488,13 @@ async function contentRoutes(app, opts = {}) {
23307
23488
  }
23308
23489
  function lookupCachedBrief(db, projectId, targetRef, promptVersion) {
23309
23490
  const conditions = [
23310
- eq27(recommendationBriefs.projectId, projectId),
23311
- eq27(recommendationBriefs.targetRef, targetRef)
23491
+ eq28(recommendationBriefs.projectId, projectId),
23492
+ eq28(recommendationBriefs.targetRef, targetRef)
23312
23493
  ];
23313
23494
  if (promptVersion !== void 0) {
23314
- conditions.push(eq27(recommendationBriefs.promptVersion, promptVersion));
23495
+ conditions.push(eq28(recommendationBriefs.promptVersion, promptVersion));
23315
23496
  }
23316
- 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();
23317
23498
  }
23318
23499
  function parseLimitParam(raw) {
23319
23500
  if (raw === void 0) return void 0;
@@ -23338,141 +23519,6 @@ function winnabilityClassRank(winnabilityClass) {
23338
23519
  return winnabilityClass === WinnabilityClasses.ownable ? 0 : 1;
23339
23520
  }
23340
23521
 
23341
- // ../api-routes/src/gsc-totals.ts
23342
- import { and as and23, asc as asc4, eq as eq28, sql as sql9, max, min } from "drizzle-orm";
23343
- var WINDOW_DAYS = { "7d": 7, "30d": 30, "90d": 90 };
23344
- function resolveGscWindowRange(window, latestDataDate, today) {
23345
- if (window === "all") {
23346
- return {
23347
- startDate: null,
23348
- endDate: latestDataDate,
23349
- latestDataDate,
23350
- daysSinceLatestData: gscDaysSinceLatestData(latestDataDate, today)
23351
- };
23352
- }
23353
- return resolveGscWindowDays(WINDOW_DAYS[window], latestDataDate, today);
23354
- }
23355
- function resolveGscWindowDays(days, latestDataDate, today) {
23356
- if (latestDataDate === null) {
23357
- return {
23358
- startDate: shiftIsoCalendarDate(today, -days),
23359
- endDate: null,
23360
- latestDataDate: null,
23361
- daysSinceLatestData: null
23362
- };
23363
- }
23364
- return {
23365
- startDate: shiftIsoCalendarDate(latestDataDate, -(days - 1)),
23366
- endDate: latestDataDate,
23367
- latestDataDate,
23368
- daysSinceLatestData: gscDaysSinceLatestData(latestDataDate, today)
23369
- };
23370
- }
23371
- function gscDaysSinceLatestData(latestDataDate, today) {
23372
- if (latestDataDate === null) return null;
23373
- return Math.max(0, Math.round(
23374
- (Date.parse(`${today}T00:00:00Z`) - Date.parse(`${latestDataDate}T00:00:00Z`)) / 864e5
23375
- ));
23376
- }
23377
- function readLatestGscDataDate(db, projectId) {
23378
- const watermark = db.select({ through: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq28(gscDataWatermarks.projectId, projectId)).get()?.through ?? null;
23379
- const property = db.select({ latest: max(gscDailyTotals.date) }).from(gscDailyTotals).where(eq28(gscDailyTotals.projectId, projectId)).get()?.latest ?? null;
23380
- const dimensioned = db.select({ latest: max(gscSearchData.date) }).from(gscSearchData).where(eq28(gscSearchData.projectId, projectId)).get()?.latest ?? null;
23381
- return [watermark, property, dimensioned].filter((d) => d !== null).reduce((maxDate, d) => maxDate === null || d > maxDate ? d : maxDate, null);
23382
- }
23383
- function readEarliestGscDataDate(db, projectId) {
23384
- const property = db.select({ earliest: min(gscDailyTotals.date) }).from(gscDailyTotals).where(eq28(gscDailyTotals.projectId, projectId)).get()?.earliest ?? null;
23385
- const dimensioned = db.select({ earliest: min(gscSearchData.date) }).from(gscSearchData).where(eq28(gscSearchData.projectId, projectId)).get()?.earliest ?? null;
23386
- return [property, dimensioned].filter((d) => d !== null).reduce((earliest, d) => earliest === null || d < earliest ? d : earliest, null);
23387
- }
23388
- function readGscDailyTotals(db, projectId, startDate, endDate) {
23389
- const rows = db.select({
23390
- date: gscDailyTotals.date,
23391
- clicks: gscDailyTotals.clicks,
23392
- impressions: gscDailyTotals.impressions,
23393
- position: gscDailyTotals.position
23394
- }).from(gscDailyTotals).where(
23395
- and23(
23396
- eq28(gscDailyTotals.projectId, projectId),
23397
- sql9`${gscDailyTotals.date} >= ${startDate}`,
23398
- sql9`${gscDailyTotals.date} <= ${endDate}`
23399
- )
23400
- ).orderBy(asc4(gscDailyTotals.date)).all();
23401
- return rows.map((r) => {
23402
- const position = Number(r.position);
23403
- return {
23404
- date: r.date,
23405
- clicks: r.clicks,
23406
- impressions: r.impressions,
23407
- position: Number.isFinite(position) ? position : 0
23408
- };
23409
- });
23410
- }
23411
- function mergeGscDailyTotalsWithFallback(propertyTotals, dimensionedFallback) {
23412
- const byDate = /* @__PURE__ */ new Map();
23413
- for (const row of dimensionedFallback) byDate.set(row.date, row);
23414
- for (const row of propertyTotals) byDate.set(row.date, row);
23415
- return [...byDate.values()].sort((a, b) => a.date.localeCompare(b.date));
23416
- }
23417
- function readGscQueryDailyRows(db, projectId, startDate, endDate) {
23418
- const rows = db.select({
23419
- date: gscQueryDailyTotals.date,
23420
- query: gscQueryDailyTotals.query,
23421
- clicks: gscQueryDailyTotals.clicks,
23422
- impressions: gscQueryDailyTotals.impressions,
23423
- position: gscQueryDailyTotals.position
23424
- }).from(gscQueryDailyTotals).where(
23425
- and23(
23426
- eq28(gscQueryDailyTotals.projectId, projectId),
23427
- sql9`${gscQueryDailyTotals.date} >= ${startDate}`,
23428
- sql9`${gscQueryDailyTotals.date} <= ${endDate}`
23429
- )
23430
- ).all();
23431
- return rows.map((r) => {
23432
- const position = Number(r.position);
23433
- return {
23434
- date: r.date,
23435
- query: r.query,
23436
- clicks: r.clicks,
23437
- impressions: r.impressions,
23438
- position: Number.isFinite(position) ? position : 0
23439
- };
23440
- });
23441
- }
23442
- function mergeGscQueryTotalsWithFallback(accurateDays, fallbackDays) {
23443
- const dayKey = (r) => `${r.date}\0${r.query}`;
23444
- const byDay = /* @__PURE__ */ new Map();
23445
- for (const row of fallbackDays) byDay.set(dayKey(row), { row, accurate: false });
23446
- for (const row of accurateDays) byDay.set(dayKey(row), { row, accurate: true });
23447
- const byQuery = /* @__PURE__ */ new Map();
23448
- for (const { row, accurate } of byDay.values()) {
23449
- const acc = byQuery.get(row.query) ?? {
23450
- clicks: 0,
23451
- impressions: 0,
23452
- weightedPositionSum: 0,
23453
- positionSum: 0,
23454
- positionDays: 0,
23455
- sawAccurate: false,
23456
- sawLegacy: false
23457
- };
23458
- acc.clicks += row.clicks;
23459
- acc.impressions += row.impressions;
23460
- acc.weightedPositionSum += row.position * row.impressions;
23461
- acc.positionSum += row.position;
23462
- acc.positionDays += 1;
23463
- if (accurate) acc.sawAccurate = true;
23464
- else acc.sawLegacy = true;
23465
- byQuery.set(row.query, acc);
23466
- }
23467
- return [...byQuery.entries()].map(([query, acc]) => ({
23468
- query,
23469
- clicks: acc.clicks,
23470
- impressions: acc.impressions,
23471
- position: acc.impressions > 0 ? acc.weightedPositionSum / acc.impressions : acc.positionDays > 0 ? acc.positionSum / acc.positionDays : 0,
23472
- source: acc.sawAccurate && acc.sawLegacy ? "mixed" : acc.sawAccurate ? "google" : "page-summed"
23473
- }));
23474
- }
23475
-
23476
23522
  // ../api-routes/src/report-renderer.ts
23477
23523
  var COLORS = {
23478
23524
  bg: "#09090b",
@@ -25764,6 +25810,149 @@ function renderReportHtml(report, opts = {}) {
25764
25810
  </html>`;
25765
25811
  }
25766
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
+
25767
25956
  // ../api-routes/src/report.ts
25768
25957
  var TOP_QUERIES_LIMIT = 20;
25769
25958
  var TOP_LANDING_PAGES_LIMIT = 20;
@@ -25829,8 +26018,8 @@ function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trac
25829
26018
  const rows = db.select().from(gscSearchData).where(
25830
26019
  and24(
25831
26020
  eq29(gscSearchData.projectId, projectId),
25832
- gte5(gscSearchData.date, startDate),
25833
- lte3(gscSearchData.date, maxDate)
26021
+ gte6(gscSearchData.date, startDate),
26022
+ lte4(gscSearchData.date, maxDate)
25834
26023
  )
25835
26024
  ).all();
25836
26025
  const dailyTotals = readGscDailyTotals(db, projectId, startDate, maxDate);
@@ -25942,8 +26131,8 @@ function buildGaSection(db, projectId, windowDays2) {
25942
26131
  const snapshotRows = snapshotStartDate ? db.select().from(gaTrafficSnapshots).where(
25943
26132
  and24(
25944
26133
  eq29(gaTrafficSnapshots.projectId, projectId),
25945
- gte5(gaTrafficSnapshots.date, snapshotStartDate),
25946
- lte3(gaTrafficSnapshots.date, snapshotMaxInWindow)
26134
+ gte6(gaTrafficSnapshots.date, snapshotStartDate),
26135
+ lte4(gaTrafficSnapshots.date, snapshotMaxInWindow)
25947
26136
  )
25948
26137
  ).all() : db.select().from(gaTrafficSnapshots).where(eq29(gaTrafficSnapshots.projectId, projectId)).all();
25949
26138
  const totalSessions = windowSummary?.totalSessions ?? matchingFallbackSummary?.totalSessions ?? snapshotRows.reduce((s, r) => s + r.sessions, 0);
@@ -25971,8 +26160,8 @@ function buildGaSection(db, projectId, windowDays2) {
25971
26160
  eq29(gaAiReferrals.sourceDimension, "session")
25972
26161
  ];
25973
26162
  if (snapshotStartDate && snapshotMaxInWindow) {
25974
- aiConditions.push(gte5(gaAiReferrals.date, snapshotStartDate));
25975
- aiConditions.push(lte3(gaAiReferrals.date, snapshotMaxInWindow));
26163
+ aiConditions.push(gte6(gaAiReferrals.date, snapshotStartDate));
26164
+ aiConditions.push(lte4(gaAiReferrals.date, snapshotMaxInWindow));
25976
26165
  }
25977
26166
  const aiSessionRows = db.select({
25978
26167
  trafficClass: gaAiReferrals.trafficClass,
@@ -26077,7 +26266,7 @@ function buildAiReferrals(db, projectId, windowDays2) {
26077
26266
  if (rows.length === 0) return null;
26078
26267
  const dimSessionsByTuple = /* @__PURE__ */ new Map();
26079
26268
  for (const r of rows) {
26080
- const tupleKey = `${r.date}::${r.source}::${r.medium}`;
26269
+ const tupleKey = `${r.date}::${r.source}`;
26081
26270
  let dimMap = dimSessionsByTuple.get(tupleKey);
26082
26271
  if (!dimMap) {
26083
26272
  dimMap = /* @__PURE__ */ new Map();
@@ -26087,18 +26276,11 @@ function buildAiReferrals(db, projectId, windowDays2) {
26087
26276
  }
26088
26277
  const winningDimension = /* @__PURE__ */ new Map();
26089
26278
  for (const [tupleKey, dimMap] of dimSessionsByTuple) {
26090
- let bestDim;
26091
- let bestSessions = -1;
26092
- for (const [dim, sessions] of dimMap) {
26093
- if (sessions > bestSessions) {
26094
- bestSessions = sessions;
26095
- bestDim = dim;
26096
- }
26097
- }
26279
+ const bestDim = pickWinningAttributionDimension(dimMap);
26098
26280
  if (bestDim) winningDimension.set(tupleKey, bestDim);
26099
26281
  }
26100
26282
  const dedupedRows = rows.filter(
26101
- (r) => winningDimension.get(`${r.date}::${r.source}::${r.medium}`) === r.sourceDimension
26283
+ (r) => winningDimension.get(`${r.date}::${r.source}`) === r.sourceDimension
26102
26284
  );
26103
26285
  let total = 0;
26104
26286
  let paidSessions = 0;
@@ -26168,8 +26350,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26168
26350
  and24(
26169
26351
  eq29(crawlerEventsHourly.projectId, projectId),
26170
26352
  eq29(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26171
- gte5(crawlerEventsHourly.tsHour, windowStartIso),
26172
- exclusiveEnd ? lt6(crawlerEventsHourly.tsHour, windowEndIso) : lte3(crawlerEventsHourly.tsHour, windowEndIso)
26353
+ gte6(crawlerEventsHourly.tsHour, windowStartIso),
26354
+ exclusiveEnd ? lt6(crawlerEventsHourly.tsHour, windowEndIso) : lte4(crawlerEventsHourly.tsHour, windowEndIso)
26173
26355
  )
26174
26356
  ).get()?.total ?? 0
26175
26357
  );
@@ -26178,8 +26360,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26178
26360
  and24(
26179
26361
  eq29(crawlerEventsHourly.projectId, projectId),
26180
26362
  ne3(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26181
- gte5(crawlerEventsHourly.tsHour, windowStartIso),
26182
- exclusiveEnd ? lt6(crawlerEventsHourly.tsHour, windowEndIso) : lte3(crawlerEventsHourly.tsHour, windowEndIso)
26363
+ gte6(crawlerEventsHourly.tsHour, windowStartIso),
26364
+ exclusiveEnd ? lt6(crawlerEventsHourly.tsHour, windowEndIso) : lte4(crawlerEventsHourly.tsHour, windowEndIso)
26183
26365
  )
26184
26366
  ).get()?.total ?? 0
26185
26367
  );
@@ -26192,8 +26374,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26192
26374
  and24(
26193
26375
  eq29(aiReferralEventsHourly.projectId, projectId),
26194
26376
  countableReferralCondition(),
26195
- gte5(aiReferralEventsHourly.tsHour, windowStartIso),
26196
- exclusiveEnd ? lt6(aiReferralEventsHourly.tsHour, windowEndIso) : lte3(aiReferralEventsHourly.tsHour, windowEndIso)
26377
+ gte6(aiReferralEventsHourly.tsHour, windowStartIso),
26378
+ exclusiveEnd ? lt6(aiReferralEventsHourly.tsHour, windowEndIso) : lte4(aiReferralEventsHourly.tsHour, windowEndIso)
26197
26379
  )
26198
26380
  ).get();
26199
26381
  return aiReferralClassCounts(Number(row?.total ?? 0), Number(row?.paid ?? 0), Number(row?.organic ?? 0));
@@ -26202,8 +26384,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26202
26384
  db.select({ total: sql10`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
26203
26385
  and24(
26204
26386
  eq29(aiUserFetchEventsHourly.projectId, projectId),
26205
- gte5(aiUserFetchEventsHourly.tsHour, windowStartIso),
26206
- exclusiveEnd ? lt6(aiUserFetchEventsHourly.tsHour, windowEndIso) : lte3(aiUserFetchEventsHourly.tsHour, windowEndIso)
26387
+ gte6(aiUserFetchEventsHourly.tsHour, windowStartIso),
26388
+ exclusiveEnd ? lt6(aiUserFetchEventsHourly.tsHour, windowEndIso) : lte4(aiUserFetchEventsHourly.tsHour, windowEndIso)
26207
26389
  )
26208
26390
  ).get()?.total ?? 0
26209
26391
  );
@@ -26219,8 +26401,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26219
26401
  and24(
26220
26402
  eq29(aiReferralEventsHourly.projectId, projectId),
26221
26403
  nonSubresourceReferralPathCondition(),
26222
- gte5(aiReferralEventsHourly.tsHour, headlineStart),
26223
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26404
+ gte6(aiReferralEventsHourly.tsHour, headlineStart),
26405
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26224
26406
  )
26225
26407
  ).get()?.total ?? 0
26226
26408
  );
@@ -26233,8 +26415,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26233
26415
  }).from(crawlerEventsHourly).where(
26234
26416
  and24(
26235
26417
  eq29(crawlerEventsHourly.projectId, projectId),
26236
- gte5(crawlerEventsHourly.tsHour, headlineStart),
26237
- lte3(crawlerEventsHourly.tsHour, headlineEnd)
26418
+ gte6(crawlerEventsHourly.tsHour, headlineStart),
26419
+ lte4(crawlerEventsHourly.tsHour, headlineEnd)
26238
26420
  )
26239
26421
  ).groupBy(crawlerEventsHourly.operator, crawlerEventsHourly.verificationStatus).all();
26240
26422
  const crawlerByOperatorPriorRows = db.select({
@@ -26244,7 +26426,7 @@ function buildServerActivity(db, projectId, windowDays2) {
26244
26426
  and24(
26245
26427
  eq29(crawlerEventsHourly.projectId, projectId),
26246
26428
  eq29(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26247
- gte5(crawlerEventsHourly.tsHour, priorStart),
26429
+ gte6(crawlerEventsHourly.tsHour, priorStart),
26248
26430
  lt6(crawlerEventsHourly.tsHour, headlineStart)
26249
26431
  )
26250
26432
  ).groupBy(crawlerEventsHourly.operator).all();
@@ -26255,8 +26437,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26255
26437
  and24(
26256
26438
  eq29(aiReferralEventsHourly.projectId, projectId),
26257
26439
  countableReferralCondition(),
26258
- gte5(aiReferralEventsHourly.tsHour, headlineStart),
26259
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26440
+ gte6(aiReferralEventsHourly.tsHour, headlineStart),
26441
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26260
26442
  )
26261
26443
  ).groupBy(aiReferralEventsHourly.operator).all();
26262
26444
  const userFetchByOperatorRows = db.select({
@@ -26265,8 +26447,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26265
26447
  }).from(aiUserFetchEventsHourly).where(
26266
26448
  and24(
26267
26449
  eq29(aiUserFetchEventsHourly.projectId, projectId),
26268
- gte5(aiUserFetchEventsHourly.tsHour, headlineStart),
26269
- lte3(aiUserFetchEventsHourly.tsHour, headlineEnd)
26450
+ gte6(aiUserFetchEventsHourly.tsHour, headlineStart),
26451
+ lte4(aiUserFetchEventsHourly.tsHour, headlineEnd)
26270
26452
  )
26271
26453
  ).groupBy(aiUserFetchEventsHourly.operator).all();
26272
26454
  const operatorAgg = /* @__PURE__ */ new Map();
@@ -26310,8 +26492,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26310
26492
  and24(
26311
26493
  eq29(crawlerEventsHourly.projectId, projectId),
26312
26494
  eq29(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26313
- gte5(crawlerEventsHourly.tsHour, headlineStart),
26314
- lte3(crawlerEventsHourly.tsHour, headlineEnd)
26495
+ gte6(crawlerEventsHourly.tsHour, headlineStart),
26496
+ lte4(crawlerEventsHourly.tsHour, headlineEnd)
26315
26497
  )
26316
26498
  ).groupBy(crawlerEventsHourly.pathNormalized).orderBy(desc12(sql10`SUM(${crawlerEventsHourly.hits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
26317
26499
  const topCrawledPaths = topPathsRows.map((r) => ({
@@ -26327,8 +26509,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26327
26509
  and24(
26328
26510
  eq29(aiReferralEventsHourly.projectId, projectId),
26329
26511
  countableReferralCondition(),
26330
- gte5(aiReferralEventsHourly.tsHour, headlineStart),
26331
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26512
+ gte6(aiReferralEventsHourly.tsHour, headlineStart),
26513
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26332
26514
  )
26333
26515
  ).groupBy(aiReferralEventsHourly.product).orderBy(desc12(sql10`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).all();
26334
26516
  const referralProducts = referralProductsRows.map((r) => ({
@@ -26344,8 +26526,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26344
26526
  and24(
26345
26527
  eq29(aiReferralEventsHourly.projectId, projectId),
26346
26528
  countableReferralCondition(),
26347
- gte5(aiReferralEventsHourly.tsHour, headlineStart),
26348
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26529
+ gte6(aiReferralEventsHourly.tsHour, headlineStart),
26530
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26349
26531
  )
26350
26532
  ).groupBy(aiReferralEventsHourly.landingPathNormalized).orderBy(desc12(sql10`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
26351
26533
  const topReferralLandingPaths = topReferralRows.map((r) => ({
@@ -26360,8 +26542,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26360
26542
  and24(
26361
26543
  eq29(crawlerEventsHourly.projectId, projectId),
26362
26544
  eq29(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26363
- gte5(crawlerEventsHourly.tsHour, trendStart),
26364
- lte3(crawlerEventsHourly.tsHour, headlineEnd)
26545
+ gte6(crawlerEventsHourly.tsHour, trendStart),
26546
+ lte4(crawlerEventsHourly.tsHour, headlineEnd)
26365
26547
  )
26366
26548
  ).groupBy(sql10`SUBSTR(${crawlerEventsHourly.tsHour}, 1, 10)`).all();
26367
26549
  const referralTrendRows = db.select({
@@ -26371,8 +26553,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26371
26553
  and24(
26372
26554
  eq29(aiReferralEventsHourly.projectId, projectId),
26373
26555
  countableReferralCondition(),
26374
- gte5(aiReferralEventsHourly.tsHour, trendStart),
26375
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26556
+ gte6(aiReferralEventsHourly.tsHour, trendStart),
26557
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26376
26558
  )
26377
26559
  ).groupBy(sql10`SUBSTR(${aiReferralEventsHourly.tsHour}, 1, 10)`).all();
26378
26560
  const userFetchTrendRows = db.select({
@@ -26381,8 +26563,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26381
26563
  }).from(aiUserFetchEventsHourly).where(
26382
26564
  and24(
26383
26565
  eq29(aiUserFetchEventsHourly.projectId, projectId),
26384
- gte5(aiUserFetchEventsHourly.tsHour, trendStart),
26385
- lte3(aiUserFetchEventsHourly.tsHour, headlineEnd)
26566
+ gte6(aiUserFetchEventsHourly.tsHour, trendStart),
26567
+ lte4(aiUserFetchEventsHourly.tsHour, headlineEnd)
26386
26568
  )
26387
26569
  ).groupBy(sql10`SUBSTR(${aiUserFetchEventsHourly.tsHour}, 1, 10)`).all();
26388
26570
  const emptyTrendEntry = () => ({ verifiedCrawlerHits: 0, userFetchHits: 0, referralArrivals: 0 });
@@ -27145,7 +27327,7 @@ function buildProjectReport(db, projectName, periodDays) {
27145
27327
  const indexingHealthSection = buildIndexingHealth(db, project.id);
27146
27328
  const citationsTrend = buildCitationsTrend(db, project.id, queryLookup, latestRunLocation);
27147
27329
  const insightList = buildInsightList(db, project.id, latestRunLocation);
27148
- const orchestratorInput = loadOrchestratorInput(db, project, latestRunLocation);
27330
+ const orchestratorInput = loadOrchestratorInput(db, project, latestRunLocation, periodDays);
27149
27331
  const dismissedTargetRefs = loadDismissedTargetRefs(db, project.id);
27150
27332
  const rawContentOpportunities = buildContentTargetRows(orchestratorInput);
27151
27333
  const contentOpportunities = dismissedTargetRefs.size > 0 ? rawContentOpportunities.filter((r) => !dismissedTargetRefs.has(r.targetRef)) : rawContentOpportunities;
@@ -27332,10 +27514,10 @@ async function reportRoutes(app) {
27332
27514
  }
27333
27515
 
27334
27516
  // ../api-routes/src/organic-evidence.ts
27335
- 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";
27336
27518
 
27337
27519
  // ../api-routes/src/ga-measurement-analysis.ts
27338
- 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";
27339
27521
  var UNKNOWN_LANDING_PAGE = "(not set)";
27340
27522
  function addDays(date, offset) {
27341
27523
  const value = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
@@ -27531,15 +27713,15 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
27531
27713
  const gaAnchor = [acquisitionAnchor, leadAnchor].filter((date) => date !== null).sort().at(-1) ?? null;
27532
27714
  const gaPeriods = gaAnchor ? buildPeriods(gaAnchor, days) : [];
27533
27715
  const gaStartDate = gaPeriods[0]?.startDate;
27534
- const acquisitionRows = gaStartDate ? db.select().from(gaAcquisitionDaily).where(and25(eq30(gaAcquisitionDaily.projectId, project.id), gte6(gaAcquisitionDaily.date, gaStartDate), lte4(gaAcquisitionDaily.date, gaAnchor))).all() : [];
27535
- 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() : [];
27536
27718
  const engagementAnchor = db.select({ date: sql11`max(${gaDailyTotals.date})` }).from(gaDailyTotals).where(eq30(gaDailyTotals.projectId, project.id)).get()?.date ?? null;
27537
27719
  const engagementPeriodWindow = engagementAnchor ? buildPeriods(engagementAnchor, days) : [];
27538
27720
  const engagementStartDate = engagementPeriodWindow[0]?.startDate;
27539
27721
  const engagementRows = engagementStartDate ? db.select().from(gaDailyTotals).where(and25(
27540
27722
  eq30(gaDailyTotals.projectId, project.id),
27541
- gte6(gaDailyTotals.date, engagementStartDate),
27542
- lte4(gaDailyTotals.date, engagementAnchor)
27723
+ gte7(gaDailyTotals.date, engagementStartDate),
27724
+ lte5(gaDailyTotals.date, engagementAnchor)
27543
27725
  )).all() : [];
27544
27726
  const engagementAvailableFrom = db.select({
27545
27727
  date: sql11`min(${gaDailyTotals.date})`
@@ -27553,9 +27735,9 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
27553
27735
  const gscAnchor = db.select({ date: sql11`max(${gscDailyTotals.date})` }).from(gscDailyTotals).where(eq30(gscDailyTotals.projectId, project.id)).get()?.date ?? null;
27554
27736
  const gscPeriods = gscAnchor ? buildPeriods(gscAnchor, days) : [];
27555
27737
  const gscStartDate = gscPeriods[0]?.startDate;
27556
- const propertyRows = gscStartDate ? db.select().from(gscDailyTotals).where(and25(eq30(gscDailyTotals.projectId, project.id), gte6(gscDailyTotals.date, gscStartDate), lte4(gscDailyTotals.date, gscAnchor))).all() : [];
27557
- const queryRows = gscStartDate ? db.select().from(gscQueryDailyTotals).where(and25(eq30(gscQueryDailyTotals.projectId, project.id), gte6(gscQueryDailyTotals.date, gscStartDate), lte4(gscQueryDailyTotals.date, gscAnchor))).all() : [];
27558
- 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() : [];
27559
27741
  const state = db.select().from(gaMeasurementSyncStates).where(eq30(gaMeasurementSyncStates.projectId, project.id)).get();
27560
27742
  const acquisition = acquisitionRows.filter((row) => {
27561
27743
  const landingPage = normalizeLandingPage(row.landingPageNormalized ?? row.landingPage);
@@ -27907,18 +28089,18 @@ function buildOrganicEvidence(db, projectName, periodDays) {
27907
28089
  const isInWindow = (date) => inRange(date, startDate, endDate);
27908
28090
  const gscRows = gscStartDate && gscEndDate ? db.select().from(gscDailyTotals).where(and26(
27909
28091
  eq31(gscDailyTotals.projectId, project.id),
27910
- gte7(gscDailyTotals.date, gscStartDate),
27911
- lte5(gscDailyTotals.date, gscEndDate)
28092
+ gte8(gscDailyTotals.date, gscStartDate),
28093
+ lte6(gscDailyTotals.date, gscEndDate)
27912
28094
  )).all() : [];
27913
28095
  const gaRows = gaStartDate && gaEndDate ? db.select().from(gaTrafficSnapshots).where(and26(
27914
28096
  eq31(gaTrafficSnapshots.projectId, project.id),
27915
- gte7(gaTrafficSnapshots.date, gaStartDate),
27916
- lte5(gaTrafficSnapshots.date, gaEndDate)
28097
+ gte8(gaTrafficSnapshots.date, gaStartDate),
28098
+ lte6(gaTrafficSnapshots.date, gaEndDate)
27917
28099
  )).all() : [];
27918
28100
  const nativeAcquisitionRows = nativeAcquisitionActive && nativeStartDate && nativeEndDate ? db.select().from(gaAcquisitionDaily).where(and26(
27919
28101
  eq31(gaAcquisitionDaily.projectId, project.id),
27920
- gte7(gaAcquisitionDaily.date, nativeStartDate),
27921
- lte5(gaAcquisitionDaily.date, nativeEndDate)
28102
+ gte8(gaAcquisitionDaily.date, nativeStartDate),
28103
+ lte6(gaAcquisitionDaily.date, nativeEndDate)
27922
28104
  )).all().filter((row) => matchesMarketingHost(row.hostName, marketingHosts)) : [];
27923
28105
  const nativeOrganicRows = nativeAcquisitionRows.filter((row) => row.channelGroup === "Organic Search");
27924
28106
  const nativeWindowOrganicRows = nativeOrganicRows;
@@ -27956,8 +28138,8 @@ function buildOrganicEvidence(db, projectName, periodDays) {
27956
28138
  );
27957
28139
  const namedRows = db.select().from(gscQueryDailyTotals).where(and26(
27958
28140
  eq31(gscQueryDailyTotals.projectId, project.id),
27959
- gte7(gscQueryDailyTotals.date, startDate),
27960
- lte5(gscQueryDailyTotals.date, endDate)
28141
+ gte8(gscQueryDailyTotals.date, startDate),
28142
+ lte6(gscQueryDailyTotals.date, endDate)
27961
28143
  )).all();
27962
28144
  for (const row of namedRows) {
27963
28145
  const isBrand = categorizeQueryByIntent(row.query, brandTokens) === "brand";
@@ -27983,7 +28165,7 @@ function buildOrganicEvidence(db, projectName, periodDays) {
27983
28165
  totals: sumSearchRows(gscRows.filter((row) => inRange(row.date, cohort.startDate, cohort.endDate)))
27984
28166
  }))
27985
28167
  } : null;
27986
- 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));
27987
28169
  const gaWindow = gaStartDate && gaEndDate ? gaRows.filter((row) => inRange(row.date, gaStartDate, gaEndDate)) : [];
27988
28170
  const legacyGaCohorts = gaCohorts.map((cohort) => ({
27989
28171
  ...cohort,
@@ -28010,7 +28192,7 @@ function buildOrganicEvidence(db, projectName, periodDays) {
28010
28192
  }))
28011
28193
  };
28012
28194
  }
28013
- 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");
28014
28196
  const aiRows = gaStartDate && gaEndDate ? allGaAiRows.filter((row) => inRange(row.date, gaStartDate, gaEndDate)) : [];
28015
28197
  const gaAiReferralSummary = allGaAiRows.length ? {
28016
28198
  paidSessions: aiRows.filter((row) => row.trafficClass === AiReferralTrafficClasses.paid).reduce((count2, row) => count2 + row.sessions, 0),
@@ -28019,9 +28201,9 @@ function buildOrganicEvidence(db, projectName, periodDays) {
28019
28201
  const serverSources = db.select().from(trafficSources).where(eq31(trafficSources.projectId, project.id)).all();
28020
28202
  const serverStart = `${startDate}T00:00:00`;
28021
28203
  const serverEnd = `${endDate}T23:59:59.999Z`;
28022
- const allCrawlers = db.select().from(crawlerEventsHourly).where(and26(eq31(crawlerEventsHourly.projectId, project.id), gte7(crawlerEventsHourly.tsHour, serverStart), lte5(crawlerEventsHourly.tsHour, serverEnd))).all();
28023
- const allFetches = db.select().from(aiUserFetchEventsHourly).where(and26(eq31(aiUserFetchEventsHourly.projectId, project.id), gte7(aiUserFetchEventsHourly.tsHour, serverStart), lte5(aiUserFetchEventsHourly.tsHour, serverEnd))).all();
28024
- 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();
28025
28207
  const [serverCoverageRow] = db.all(sql12`
28026
28208
  select
28027
28209
  min(day) as startDate,
@@ -29015,7 +29197,7 @@ async function visibilityStatsRoutes(app) {
29015
29197
  }
29016
29198
 
29017
29199
  // ../api-routes/src/results-export.ts
29018
- 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";
29019
29201
  var CSV_COLUMNS = [
29020
29202
  "export_schema_version",
29021
29203
  "project_id",
@@ -29155,8 +29337,8 @@ async function resultsExportRoutes(app) {
29155
29337
  eq34(runs.projectId, project.id),
29156
29338
  eq34(runs.kind, RunKinds["answer-visibility"]),
29157
29339
  ...includeProbes ? [] : [notProbeRun()],
29158
- ...sinceIso ? [gte8(runs.createdAt, sinceIso)] : [],
29159
- ...untilIso ? [lte6(runs.createdAt, untilIso)] : []
29340
+ ...sinceIso ? [gte9(runs.createdAt, sinceIso)] : [],
29341
+ ...untilIso ? [lte7(runs.createdAt, untilIso)] : []
29160
29342
  ];
29161
29343
  const rows = app.db.select({
29162
29344
  runId: runs.id,
@@ -43267,7 +43449,7 @@ async function googleMarketingRoutes(app, opts) {
43267
43449
 
43268
43450
  // ../api-routes/src/ads.ts
43269
43451
  import crypto35 from "crypto";
43270
- 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";
43271
43453
 
43272
43454
  // ../api-routes/src/ads-activation.ts
43273
43455
  import crypto33 from "crypto";
@@ -44653,7 +44835,7 @@ async function executeApprovedAdsActivation(dependencies, request) {
44653
44835
 
44654
44836
  // ../api-routes/src/ads-activation-routes.ts
44655
44837
  import crypto34 from "crypto";
44656
- 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";
44657
44839
  var MAX_ACTIVATION_GRANT_LIFETIME_MS = 24 * 60 * 60 * 1e3;
44658
44840
  var ACTIVATION_LEASE_MS = 5 * 6e4;
44659
44841
  var ACTIVATION_REVOCATION_SETTLEMENT_MS = ACTIVATION_LEASE_MS;
@@ -44783,7 +44965,7 @@ function createActivationStore(app, request) {
44783
44965
  isNull4(adsOperations.leaseOwner),
44784
44966
  eq42(adsOperations.leaseOwner, input.leaseOwner),
44785
44967
  isNull4(adsOperations.leaseExpiresAt),
44786
- lte7(adsOperations.leaseExpiresAt, input.now)
44968
+ lte8(adsOperations.leaseExpiresAt, input.now)
44787
44969
  )
44788
44970
  )).run();
44789
44971
  const resumed = loadCanonicalActivation(tx, grant.id, existing.id);
@@ -45200,7 +45382,7 @@ function selectWatchdogOperations(app, nowIso, batchSize, allowLiveRevocationLea
45200
45382
  const leaseAvailable = or9(
45201
45383
  isNull4(adsOperations.leaseOwner),
45202
45384
  isNull4(adsOperations.leaseExpiresAt),
45203
- lte7(adsOperations.leaseExpiresAt, nowIso)
45385
+ lte8(adsOperations.leaseExpiresAt, nowIso)
45204
45386
  );
45205
45387
  const campaignHasMutationEvidence = or9(
45206
45388
  eq42(adsOperationSteps.state, AdsOperationStepStates.executing),
@@ -45396,7 +45578,7 @@ async function sweepStaleAdsActivationsOnce(app, opts) {
45396
45578
  or9(
45397
45579
  isNull4(adsOperations.leaseOwner),
45398
45580
  isNull4(adsOperations.leaseExpiresAt),
45399
- lte7(adsOperations.leaseExpiresAt, nowIso)
45581
+ lte8(adsOperations.leaseExpiresAt, nowIso)
45400
45582
  )
45401
45583
  )
45402
45584
  )
@@ -45433,7 +45615,7 @@ async function sweepStaleAdsActivationsOnce(app, opts) {
45433
45615
  or9(
45434
45616
  isNull4(adsOperations.leaseOwner),
45435
45617
  isNull4(adsOperations.leaseExpiresAt),
45436
- lte7(adsOperations.leaseExpiresAt, failedAt)
45618
+ lte8(adsOperations.leaseExpiresAt, failedAt)
45437
45619
  )
45438
45620
  )
45439
45621
  )
@@ -46610,13 +46792,13 @@ function claimOperationForReconciliation(app, row, leaseOwner, now, leaseMs, pol
46610
46792
  const unknownCutoff = new Date(now.getTime() - backoffMs).toISOString();
46611
46793
  const claimableState = row.state === AdsOperationStates.pending ? and35(
46612
46794
  eq43(adsOperations.state, AdsOperationStates.pending),
46613
- lte8(adsOperations.updatedAt, pendingCutoff)
46795
+ lte9(adsOperations.updatedAt, pendingCutoff)
46614
46796
  ) : row.state === AdsOperationStates.unknown ? and35(
46615
46797
  eq43(adsOperations.state, AdsOperationStates.unknown),
46616
- enforceBackoff ? lte8(adsOperations.updatedAt, unknownCutoff) : void 0
46798
+ enforceBackoff ? lte9(adsOperations.updatedAt, unknownCutoff) : void 0
46617
46799
  ) : and35(
46618
46800
  eq43(adsOperations.state, AdsOperationStates.reconciling),
46619
- or10(isNull5(adsOperations.leaseExpiresAt), lte8(adsOperations.leaseExpiresAt, nowIso))
46801
+ or10(isNull5(adsOperations.leaseExpiresAt), lte9(adsOperations.leaseExpiresAt, nowIso))
46620
46802
  );
46621
46803
  const claimed = app.db.update(adsOperations).set({
46622
46804
  state: AdsOperationStates.reconciling,
@@ -46736,7 +46918,7 @@ function quarantineExhaustedReconciliation(app, row, now, policy, context) {
46736
46918
  }).where(and35(
46737
46919
  eq43(adsOperations.id, row.id),
46738
46920
  eq43(adsOperations.projectId, row.projectId),
46739
- gte9(adsOperations.reconcileAttempts, policy.maxAttempts),
46921
+ gte10(adsOperations.reconcileAttempts, policy.maxAttempts),
46740
46922
  inArray17(adsOperations.state, [
46741
46923
  AdsOperationStates.pending,
46742
46924
  AdsOperationStates.unknown,
@@ -46934,14 +47116,14 @@ async function sweepAdsOperationsOnce(app, opts, config) {
46934
47116
  const backoffMs = config.policy.backoffBaseMs * 2 ** Math.max(0, reconcileAttempts - 1);
46935
47117
  return and35(
46936
47118
  eq43(adsOperations.reconcileAttempts, reconcileAttempts),
46937
- lte8(adsOperations.updatedAt, new Date(now.getTime() - backoffMs).toISOString())
47119
+ lte9(adsOperations.updatedAt, new Date(now.getTime() - backoffMs).toISOString())
46938
47120
  );
46939
47121
  }
46940
47122
  );
46941
47123
  const retryableStateWhere = or10(
46942
47124
  and35(
46943
47125
  eq43(adsOperations.state, AdsOperationStates.pending),
46944
- lte8(adsOperations.updatedAt, pendingCutoff)
47126
+ lte9(adsOperations.updatedAt, pendingCutoff)
46945
47127
  ),
46946
47128
  and35(
46947
47129
  eq43(adsOperations.state, AdsOperationStates.unknown),
@@ -46949,7 +47131,7 @@ async function sweepAdsOperationsOnce(app, opts, config) {
46949
47131
  ),
46950
47132
  and35(
46951
47133
  eq43(adsOperations.state, AdsOperationStates.reconciling),
46952
- or10(isNull5(adsOperations.leaseExpiresAt), lte8(adsOperations.leaseExpiresAt, nowIso))
47134
+ or10(isNull5(adsOperations.leaseExpiresAt), lte9(adsOperations.leaseExpiresAt, nowIso))
46953
47135
  )
46954
47136
  );
46955
47137
  const staleReconcileWhere = and35(
@@ -46972,16 +47154,16 @@ async function sweepAdsOperationsOnce(app, opts, config) {
46972
47154
  );
46973
47155
  const exhaustedRows = app.db.select().from(adsOperations).where(and35(
46974
47156
  ne5(adsOperations.kind, AdsOperationKinds.campaign_tree_activate),
46975
- gte9(adsOperations.reconcileAttempts, config.policy.maxAttempts),
47157
+ gte10(adsOperations.reconcileAttempts, config.policy.maxAttempts),
46976
47158
  or10(
46977
47159
  and35(
46978
47160
  eq43(adsOperations.state, AdsOperationStates.pending),
46979
- lte8(adsOperations.updatedAt, pendingCutoff)
47161
+ lte9(adsOperations.updatedAt, pendingCutoff)
46980
47162
  ),
46981
47163
  eq43(adsOperations.state, AdsOperationStates.unknown),
46982
47164
  and35(
46983
47165
  eq43(adsOperations.state, AdsOperationStates.reconciling),
46984
- or10(isNull5(adsOperations.leaseExpiresAt), lte8(adsOperations.leaseExpiresAt, nowIso))
47166
+ or10(isNull5(adsOperations.leaseExpiresAt), lte9(adsOperations.leaseExpiresAt, nowIso))
46985
47167
  )
46986
47168
  ),
46987
47169
  or10(
@@ -47857,8 +48039,8 @@ async function adsRoutes(app, opts) {
47857
48039
  const conditions = [eq43(adsInsightsDaily.projectId, project.id)];
47858
48040
  if (parsedLevel) conditions.push(eq43(adsInsightsDaily.level, parsedLevel));
47859
48041
  if (entityId) conditions.push(eq43(adsInsightsDaily.entityId, entityId));
47860
- if (from) conditions.push(gte9(adsInsightsDaily.date, from));
47861
- 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));
47862
48044
  const rows = app.db.select().from(adsInsightsDaily).where(and35(...conditions)).orderBy(asc8(adsInsightsDaily.date)).all();
47863
48045
  const conn = app.db.select().from(adsConnections).where(eq43(adsConnections.projectId, project.id)).get();
47864
48046
  const accountToday = adsAccountToday(conn?.timezone, (/* @__PURE__ */ new Date()).toISOString());
@@ -49238,118 +49420,6 @@ function buildSessionHistory(landingPageDays, dailyTotals) {
49238
49420
  });
49239
49421
  }
49240
49422
 
49241
- // ../api-routes/src/ga-ai-referral-aggregation.ts
49242
- function normalizeAiTrafficClass(value) {
49243
- return value === AiReferralTrafficClasses.paid ? AiReferralTrafficClasses.paid : AiReferralTrafficClasses.organic;
49244
- }
49245
- function emptyAiCounts() {
49246
- return { sessions: 0 };
49247
- }
49248
- function resolveWinningDimensions(rows) {
49249
- const groups = /* @__PURE__ */ new Map();
49250
- for (const row of rows) {
49251
- const key = `${row.date}\0${row.source}\0${row.medium}`;
49252
- let group = groups.get(key);
49253
- if (!group) {
49254
- group = { date: row.date, source: row.source, medium: row.medium, byDimension: /* @__PURE__ */ new Map() };
49255
- groups.set(key, group);
49256
- }
49257
- let dim = group.byDimension.get(row.sourceDimension);
49258
- if (!dim) {
49259
- dim = { paidSessions: 0, organicSessions: 0 };
49260
- group.byDimension.set(row.sourceDimension, dim);
49261
- }
49262
- const sessions = row.sessions ?? 0;
49263
- if (normalizeAiTrafficClass(row.trafficClass) === AiReferralTrafficClasses.paid) {
49264
- dim.paidSessions += sessions;
49265
- } else {
49266
- dim.organicSessions += sessions;
49267
- }
49268
- }
49269
- const winners = [];
49270
- for (const group of groups.values()) {
49271
- const dims = [...group.byDimension.values()];
49272
- const bestSessions = dims.reduce((best, d) => d.paidSessions + d.organicSessions > best.paidSessions + best.organicSessions ? d : best);
49273
- winners.push({
49274
- date: group.date,
49275
- source: group.source,
49276
- medium: group.medium,
49277
- paidSessions: bestSessions.paidSessions,
49278
- organicSessions: bestSessions.organicSessions
49279
- });
49280
- }
49281
- return winners;
49282
- }
49283
- function summarizeAiReferralCounts(rows) {
49284
- const paidDeduped = emptyAiCounts();
49285
- const organicDeduped = emptyAiCounts();
49286
- const paidBySession = emptyAiCounts();
49287
- const organicBySession = emptyAiCounts();
49288
- const bySessionChannelGroup = /* @__PURE__ */ new Map();
49289
- for (const winner of resolveWinningDimensions(rows)) {
49290
- paidDeduped.sessions += winner.paidSessions;
49291
- organicDeduped.sessions += winner.organicSessions;
49292
- }
49293
- for (const row of rows) {
49294
- if (row.sourceDimension !== "session") continue;
49295
- const sessions = row.sessions ?? 0;
49296
- bySessionChannelGroup.set(
49297
- row.channelGroup,
49298
- (bySessionChannelGroup.get(row.channelGroup) ?? 0) + sessions
49299
- );
49300
- const bucket = normalizeAiTrafficClass(row.trafficClass) === AiReferralTrafficClasses.paid ? paidBySession : organicBySession;
49301
- bucket.sessions += sessions;
49302
- }
49303
- return {
49304
- paidDeduped,
49305
- organicDeduped,
49306
- paidBySession,
49307
- organicBySession,
49308
- bySessionChannelGroup,
49309
- deduped: { sessions: paidDeduped.sessions + organicDeduped.sessions },
49310
- bySession: { sessions: paidBySession.sessions + organicBySession.sessions }
49311
- };
49312
- }
49313
- function buildAiReferralDailySeries(rows) {
49314
- const byDate = /* @__PURE__ */ new Map();
49315
- const sourceTotals = /* @__PURE__ */ new Map();
49316
- let totalSessions = 0;
49317
- let totalPaidSessions = 0;
49318
- let totalOrganicSessions = 0;
49319
- for (const winner of resolveWinningDimensions(rows)) {
49320
- const sessions = winner.paidSessions + winner.organicSessions;
49321
- let bySource = byDate.get(winner.date);
49322
- if (!bySource) {
49323
- bySource = /* @__PURE__ */ new Map();
49324
- byDate.set(winner.date, bySource);
49325
- }
49326
- let bucket = bySource.get(winner.source);
49327
- if (!bucket) {
49328
- bucket = { source: winner.source, sessions: 0, paidSessions: 0, organicSessions: 0 };
49329
- bySource.set(winner.source, bucket);
49330
- }
49331
- bucket.sessions += sessions;
49332
- bucket.paidSessions += winner.paidSessions;
49333
- bucket.organicSessions += winner.organicSessions;
49334
- sourceTotals.set(winner.source, (sourceTotals.get(winner.source) ?? 0) + sessions);
49335
- totalSessions += sessions;
49336
- totalPaidSessions += winner.paidSessions;
49337
- totalOrganicSessions += winner.organicSessions;
49338
- }
49339
- const days = [...byDate.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([date, bySource]) => {
49340
- const sources2 = [...bySource.values()].sort((a, b) => b.sessions - a.sessions || a.source.localeCompare(b.source));
49341
- return {
49342
- date,
49343
- sessions: sources2.reduce((sum, s) => sum + s.sessions, 0),
49344
- paidSessions: sources2.reduce((sum, s) => sum + s.paidSessions, 0),
49345
- organicSessions: sources2.reduce((sum, s) => sum + s.organicSessions, 0),
49346
- bySource: sources2
49347
- };
49348
- });
49349
- const sources = [...sourceTotals.entries()].sort(([aSource, aSessions], [bSource, bSessions]) => bSessions - aSessions || aSource.localeCompare(bSource)).map(([source]) => source);
49350
- return { days, sources, totalSessions, totalPaidSessions, totalOrganicSessions };
49351
- }
49352
-
49353
49423
  // ../api-routes/src/ga.ts
49354
49424
  function gaLog(level, action, ctx) {
49355
49425
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
@@ -49401,16 +49471,26 @@ function buildChannelBreakdown(input) {
49401
49471
  }
49402
49472
  };
49403
49473
  }
49404
- function pickWinningDimension(rows, tupleKey) {
49405
- const winners = /* @__PURE__ */ new Map();
49474
+ function keepWinningDimensionRows(rows, groupKey) {
49475
+ const sessionsByGroupAndDimension = /* @__PURE__ */ new Map();
49406
49476
  for (const row of rows) {
49407
- const key = tupleKey(row);
49408
- const existing = winners.get(key);
49409
- if (!existing || (row.sessions ?? 0) > (existing.sessions ?? 0)) {
49410
- winners.set(key, row);
49411
- }
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
+ );
49412
49487
  }
49413
- 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));
49414
49494
  }
49415
49495
  function persistAcquisitionMeasurement(db, input) {
49416
49496
  const { startDate, endDate } = input.report;
@@ -50169,13 +50249,10 @@ async function ga4Routes(app, opts) {
50169
50249
  channelGroup: gaAiReferrals.channelGroup,
50170
50250
  sessions: gaAiReferrals.sessions
50171
50251
  }).from(gaAiReferrals).where(and38(...aiConditions)).all();
50172
- const aiReferrals = pickWinningDimension(
50173
- aiReferralRows,
50174
- (r) => `${r.source}\0${r.medium}\0${r.trafficClass}`
50175
- );
50176
- const aiReferralLandingPages = pickWinningDimension(
50252
+ const aiReferrals = keepWinningDimensionRows(aiReferralRows, (r) => r.source);
50253
+ const aiReferralLandingPages = keepWinningDimensionRows(
50177
50254
  aiReferralLandingPageRows,
50178
- (r) => `${r.source}\0${r.medium}\0${r.trafficClass}\0${r.landingPage}`
50255
+ (r) => `${r.source}\0${r.landingPage}`
50179
50256
  );
50180
50257
  const aiSummary = summarizeAiReferralCounts(aiRowsForTotals);
50181
50258
  const socialReferrals = app.db.select({
@@ -52948,7 +53025,7 @@ import crypto43 from "crypto";
52948
53025
  import { isIP } from "net";
52949
53026
  import { isDeepStrictEqual } from "util";
52950
53027
  import { Agent as UndiciAgent } from "undici";
52951
- 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";
52952
53029
 
52953
53030
  // ../api-routes/src/cloudflare-worker-version.ts
52954
53031
  var CURRENT_CLOUDFLARE_WORKER_VERSION = "1.0.1";
@@ -69559,7 +69636,7 @@ async function ackCloudflareQueueMessages(client, options) {
69559
69636
 
69560
69637
  // ../api-routes/src/traffic-event-ingest.ts
69561
69638
  import crypto42 from "crypto";
69562
- 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";
69563
69640
 
69564
69641
  // ../api-routes/src/raw-event-sample-retention.ts
69565
69642
  import { and as and41, eq as eq48, lt as lt9, sql as sql20 } from "drizzle-orm";
@@ -69638,7 +69715,7 @@ function writeTrafficEventBatch(opts) {
69638
69715
  );
69639
69716
  tx.delete(trafficEventReceipts).where(and42(
69640
69717
  eq49(trafficEventReceipts.sourceId, opts.sourceId),
69641
- lte9(trafficEventReceipts.expiresAt, opts.receivedAt)
69718
+ lte10(trafficEventReceipts.expiresAt, opts.receivedAt)
69642
69719
  )).run();
69643
69720
  const claimedEvents = [];
69644
69721
  for (const event of opts.events) {
@@ -69838,7 +69915,7 @@ function writeTrafficEventBatch(opts) {
69838
69915
  if (remaining === void 0) {
69839
69916
  const existing = tx.select({ total: sql21`COUNT(*)` }).from(rawEventSamples).where(and42(
69840
69917
  eq49(rawEventSamples.sourceId, opts.sourceId),
69841
- gte10(rawEventSamples.ts, hour.start),
69918
+ gte11(rawEventSamples.ts, hour.start),
69842
69919
  lt10(rawEventSamples.ts, hour.end)
69843
69920
  )).get();
69844
69921
  remaining = Math.max(0, hourlySampleLimit - Number(existing?.total ?? 0));
@@ -69896,7 +69973,7 @@ function writeTrafficEventBatch(opts) {
69896
69973
  }
69897
69974
 
69898
69975
  // ../api-routes/src/traffic-sync-lease.ts
69899
- 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";
69900
69977
  function leaseExpiresAt(now, ttlMs) {
69901
69978
  const nowMs = Date.parse(now);
69902
69979
  if (!Number.isFinite(nowMs)) throw new RangeError("Traffic sync lease now must be an ISO instant");
@@ -69913,7 +69990,7 @@ function tryClaimTrafficSyncLease(input) {
69913
69990
  eq50(trafficSources.id, input.sourceId),
69914
69991
  or11(
69915
69992
  isNull6(trafficSources.syncLeaseOwner),
69916
- lte10(trafficSources.syncLeaseExpiresAt, input.now),
69993
+ lte11(trafficSources.syncLeaseExpiresAt, input.now),
69917
69994
  eq50(trafficSources.syncLeaseOwner, input.owner)
69918
69995
  )
69919
69996
  )).run();
@@ -70409,29 +70486,29 @@ async function runBackfillTask(options) {
70409
70486
  tx.delete(crawlerEventsHourly).where(
70410
70487
  and44(
70411
70488
  eq51(crawlerEventsHourly.sourceId, sourceRow2.id),
70412
- gte11(crawlerEventsHourly.tsHour, windowStartIso),
70413
- lte11(crawlerEventsHourly.tsHour, windowEndIso)
70489
+ gte12(crawlerEventsHourly.tsHour, windowStartIso),
70490
+ lte12(crawlerEventsHourly.tsHour, windowEndIso)
70414
70491
  )
70415
70492
  ).run();
70416
70493
  tx.delete(aiUserFetchEventsHourly).where(
70417
70494
  and44(
70418
70495
  eq51(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
70419
- gte11(aiUserFetchEventsHourly.tsHour, windowStartIso),
70420
- lte11(aiUserFetchEventsHourly.tsHour, windowEndIso)
70496
+ gte12(aiUserFetchEventsHourly.tsHour, windowStartIso),
70497
+ lte12(aiUserFetchEventsHourly.tsHour, windowEndIso)
70421
70498
  )
70422
70499
  ).run();
70423
70500
  tx.delete(aiReferralEventsHourly).where(
70424
70501
  and44(
70425
70502
  eq51(aiReferralEventsHourly.sourceId, sourceRow2.id),
70426
- gte11(aiReferralEventsHourly.tsHour, windowStartIso),
70427
- lte11(aiReferralEventsHourly.tsHour, windowEndIso)
70503
+ gte12(aiReferralEventsHourly.tsHour, windowStartIso),
70504
+ lte12(aiReferralEventsHourly.tsHour, windowEndIso)
70428
70505
  )
70429
70506
  ).run();
70430
70507
  tx.delete(rawEventSamples).where(
70431
70508
  and44(
70432
70509
  eq51(rawEventSamples.sourceId, sourceRow2.id),
70433
- gte11(rawEventSamples.ts, windowStartIso),
70434
- lte11(rawEventSamples.ts, windowEndIso)
70510
+ gte12(rawEventSamples.ts, windowStartIso),
70511
+ lte12(rawEventSamples.ts, windowEndIso)
70435
70512
  )
70436
70513
  ).run();
70437
70514
  for (const bucket of report.crawlerEventsHourly) {
@@ -72595,7 +72672,7 @@ async function trafficRoutes(app, opts) {
72595
72672
  }).from(crawlerEventsHourly).where(
72596
72673
  and44(
72597
72674
  eq51(crawlerEventsHourly.sourceId, row.id),
72598
- gte11(crawlerEventsHourly.tsHour, since)
72675
+ gte12(crawlerEventsHourly.tsHour, since)
72599
72676
  )
72600
72677
  ).groupBy(crawlerEventsHourly.pathNormalized).all();
72601
72678
  const crawlerSegments = segmentCrawlerHits(
@@ -72605,7 +72682,7 @@ async function trafficRoutes(app, opts) {
72605
72682
  const aiUserFetchTotals = app.db.select({ total: sql22`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
72606
72683
  and44(
72607
72684
  eq51(aiUserFetchEventsHourly.sourceId, row.id),
72608
- gte11(aiUserFetchEventsHourly.tsHour, since)
72685
+ gte12(aiUserFetchEventsHourly.tsHour, since)
72609
72686
  )
72610
72687
  ).get();
72611
72688
  const aiTotals = app.db.select({
@@ -72615,13 +72692,13 @@ async function trafficRoutes(app, opts) {
72615
72692
  }).from(aiReferralEventsHourly).where(
72616
72693
  and44(
72617
72694
  eq51(aiReferralEventsHourly.sourceId, row.id),
72618
- gte11(aiReferralEventsHourly.tsHour, since)
72695
+ gte12(aiReferralEventsHourly.tsHour, since)
72619
72696
  )
72620
72697
  ).get();
72621
72698
  const sampleTotals = app.db.select({ total: sql22`COUNT(*)` }).from(rawEventSamples).where(
72622
72699
  and44(
72623
72700
  eq51(rawEventSamples.sourceId, row.id),
72624
- gte11(rawEventSamples.ts, since)
72701
+ gte12(rawEventSamples.ts, since)
72625
72702
  )
72626
72703
  ).get();
72627
72704
  const latestRun = app.db.select().from(runs).where(
@@ -72800,8 +72877,8 @@ async function trafficRoutes(app, opts) {
72800
72877
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
72801
72878
  const crawlerFilters = [
72802
72879
  eq51(crawlerEventsHourly.projectId, project.id),
72803
- gte11(crawlerEventsHourly.tsHour, sinceIso),
72804
- lte11(crawlerEventsHourly.tsHour, untilIso)
72880
+ gte12(crawlerEventsHourly.tsHour, sinceIso),
72881
+ lte12(crawlerEventsHourly.tsHour, untilIso)
72805
72882
  ];
72806
72883
  if (sourceIdParam) crawlerFilters.push(eq51(crawlerEventsHourly.sourceId, sourceIdParam));
72807
72884
  const crawlerWhere = and44(...crawlerFilters);
@@ -72875,8 +72952,8 @@ async function trafficRoutes(app, opts) {
72875
72952
  if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
72876
72953
  const userFetchFilters = [
72877
72954
  eq51(aiUserFetchEventsHourly.projectId, project.id),
72878
- gte11(aiUserFetchEventsHourly.tsHour, sinceIso),
72879
- lte11(aiUserFetchEventsHourly.tsHour, untilIso)
72955
+ gte12(aiUserFetchEventsHourly.tsHour, sinceIso),
72956
+ lte12(aiUserFetchEventsHourly.tsHour, untilIso)
72880
72957
  ];
72881
72958
  if (sourceIdParam) userFetchFilters.push(eq51(aiUserFetchEventsHourly.sourceId, sourceIdParam));
72882
72959
  const userFetchWhere = and44(...userFetchFilters);
@@ -72914,8 +72991,8 @@ async function trafficRoutes(app, opts) {
72914
72991
  if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
72915
72992
  const aiFilters = [
72916
72993
  eq51(aiReferralEventsHourly.projectId, project.id),
72917
- gte11(aiReferralEventsHourly.tsHour, sinceIso),
72918
- lte11(aiReferralEventsHourly.tsHour, untilIso)
72994
+ gte12(aiReferralEventsHourly.tsHour, sinceIso),
72995
+ lte12(aiReferralEventsHourly.tsHour, untilIso)
72919
72996
  ];
72920
72997
  if (sourceIdParam) aiFilters.push(eq51(aiReferralEventsHourly.sourceId, sourceIdParam));
72921
72998
  const aiWhere = and44(...aiFilters);
@@ -74803,7 +74880,7 @@ var RUNTIME_STATE_CHECKS = [
74803
74880
  ];
74804
74881
 
74805
74882
  // ../api-routes/src/doctor/checks/traffic-source.ts
74806
- 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";
74807
74884
  var RECENT_DATA_WARN_DAYS = 7;
74808
74885
  var RECENT_DATA_FAIL_DAYS = 30;
74809
74886
  function isCloudflareDirectPush(source) {
@@ -74952,7 +75029,7 @@ var recentDataCheck = {
74952
75029
  and47(
74953
75030
  eq57(crawlerEventsHourly.projectId, ctx.project.id),
74954
75031
  inArray19(crawlerEventsHourly.sourceId, activeSourceIds),
74955
- gte12(crawlerEventsHourly.tsHour, warnCutoff)
75032
+ gte13(crawlerEventsHourly.tsHour, warnCutoff)
74956
75033
  )
74957
75034
  ).get()?.total ?? 0
74958
75035
  );
@@ -74961,7 +75038,7 @@ var recentDataCheck = {
74961
75038
  and47(
74962
75039
  eq57(aiReferralEventsHourly.projectId, ctx.project.id),
74963
75040
  inArray19(aiReferralEventsHourly.sourceId, activeSourceIds),
74964
- gte12(aiReferralEventsHourly.tsHour, warnCutoff)
75041
+ gte13(aiReferralEventsHourly.tsHour, warnCutoff)
74965
75042
  )
74966
75043
  ).get()?.total ?? 0
74967
75044
  );
@@ -74970,7 +75047,7 @@ var recentDataCheck = {
74970
75047
  and47(
74971
75048
  eq57(aiUserFetchEventsHourly.projectId, ctx.project.id),
74972
75049
  inArray19(aiUserFetchEventsHourly.sourceId, activeSourceIds),
74973
- gte12(aiUserFetchEventsHourly.tsHour, warnCutoff)
75050
+ gte13(aiUserFetchEventsHourly.tsHour, warnCutoff)
74974
75051
  )
74975
75052
  ).get()?.total ?? 0
74976
75053
  );
@@ -74996,7 +75073,7 @@ var recentDataCheck = {
74996
75073
  and47(
74997
75074
  eq57(crawlerEventsHourly.projectId, ctx.project.id),
74998
75075
  inArray19(crawlerEventsHourly.sourceId, activeSourceIds),
74999
- gte12(crawlerEventsHourly.tsHour, failCutoff)
75076
+ gte13(crawlerEventsHourly.tsHour, failCutoff)
75000
75077
  )
75001
75078
  ).get()?.total ?? 0
75002
75079
  );
@@ -75005,7 +75082,7 @@ var recentDataCheck = {
75005
75082
  and47(
75006
75083
  eq57(aiReferralEventsHourly.projectId, ctx.project.id),
75007
75084
  inArray19(aiReferralEventsHourly.sourceId, activeSourceIds),
75008
- gte12(aiReferralEventsHourly.tsHour, failCutoff)
75085
+ gte13(aiReferralEventsHourly.tsHour, failCutoff)
75009
75086
  )
75010
75087
  ).get()?.total ?? 0
75011
75088
  );
@@ -75014,7 +75091,7 @@ var recentDataCheck = {
75014
75091
  and47(
75015
75092
  eq57(aiUserFetchEventsHourly.projectId, ctx.project.id),
75016
75093
  inArray19(aiUserFetchEventsHourly.sourceId, activeSourceIds),
75017
- gte12(aiUserFetchEventsHourly.tsHour, failCutoff)
75094
+ gte13(aiUserFetchEventsHourly.tsHour, failCutoff)
75018
75095
  )
75019
75096
  ).get()?.total ?? 0
75020
75097
  );
@@ -75929,7 +76006,7 @@ async function doctorRoutes(app, opts) {
75929
76006
 
75930
76007
  // ../api-routes/src/discovery/routes.ts
75931
76008
  import crypto45 from "crypto";
75932
- 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";
75933
76010
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
75934
76011
  async function discoveryRoutes(app, opts) {
75935
76012
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -75989,7 +76066,7 @@ async function discoveryRoutes(app, opts) {
75989
76066
  DiscoverySessionStatuses.seeding,
75990
76067
  DiscoverySessionStatuses.probing
75991
76068
  ]),
75992
- gte13(discoverySessions.createdAt, ageFloorIso)
76069
+ gte14(discoverySessions.createdAt, ageFloorIso)
75993
76070
  )).orderBy(desc24(discoverySessions.createdAt)).get();
75994
76071
  if (existing && existing.runId) {
75995
76072
  return { reused: true, sessionId: existing.id, runId: existing.runId };
@@ -79333,7 +79410,7 @@ var IntelligenceService = class {
79333
79410
  // Never look forward. The window is this run's own past, so
79334
79411
  // re-analyzing a historical run compares it against its true
79335
79412
  // predecessor rather than against sweeps that came after it.
79336
- lte12(runChronologyKey, currentRunRecord.finishedAt ?? currentRunRecord.createdAt),
79413
+ lte13(runChronologyKey, currentRunRecord.finishedAt ?? currentRunRecord.createdAt),
79337
79414
  // MEASUREMENT: only runs that actually wrote snapshots — see above.
79338
79415
  // Index seek on idx_snapshots_run, short-circuited on the first row.
79339
79416
  exists(
@@ -79423,8 +79500,8 @@ var IntelligenceService = class {
79423
79500
  const selected = this.db.select().from(gbpLocations).where(and51(
79424
79501
  eq62(gbpLocations.projectId, projectId),
79425
79502
  eq62(gbpLocations.selected, true),
79426
- gte14(gbpLocations.syncedAt, windowStart),
79427
- lte12(gbpLocations.syncedAt, windowEnd)
79503
+ gte15(gbpLocations.syncedAt, windowStart),
79504
+ lte13(gbpLocations.syncedAt, windowEnd)
79428
79505
  )).all();
79429
79506
  if (selected.length === 0) {
79430
79507
  log.info("gbp-intelligence.skip", { runId, reason: "no locations synced during run" });