@canonry/canonry 4.180.3 → 4.180.5

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-6LSP7ODK.js} +4 -4
  18. package/dist/{chunk-CKGWOYPO.js → chunk-DK23XZ6C.js} +2 -2
  19. package/dist/{chunk-OJDH2F6I.js → chunk-EC2O3LHD.js} +519 -440
  20. package/dist/{chunk-YPDKZ73I.js → chunk-JJIESVNL.js} +1 -1
  21. package/dist/{chunk-JWPN6RU7.js → chunk-UESVOQRS.js} +1 -1
  22. package/dist/cli.js +17 -6
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-DSYYBDR2.js → intelligence-service-NJUFIWAO.js} +2 -2
  25. package/dist/mcp.js +3 -3
  26. package/package.json +8 -8
@@ -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,37 @@ function buildCandidateQueries(opts) {
22804
22953
  });
22805
22954
  });
22806
22955
  }
22807
- function aggregateGscByQuery(rows) {
22956
+ var DEFAULT_CONTENT_GSC_WINDOW_DAYS = 30;
22957
+ function windowEndingOn(endDate, windowDays2) {
22958
+ const end = /* @__PURE__ */ new Date(`${endDate}T00:00:00Z`);
22959
+ end.setUTCDate(end.getUTCDate() - (Math.max(1, windowDays2) - 1));
22960
+ return { startDate: end.toISOString().slice(0, 10), endDate };
22961
+ }
22962
+ function resolveContentGscWindow(db, projectId, windowDays2) {
22963
+ const endDate = readLatestGscDataDate(db, projectId);
22964
+ return endDate ? windowEndingOn(endDate, windowDays2) : null;
22965
+ }
22966
+ function pageDayFallback(rows) {
22967
+ const byDay = /* @__PURE__ */ new Map();
22968
+ for (const r of rows) {
22969
+ if (!r.query) continue;
22970
+ const key = `${r.date}\0${r.query}`;
22971
+ const acc = byDay.get(key) ?? { date: r.date, query: r.query, clicks: 0, impressions: 0, weighted: 0 };
22972
+ acc.clicks += r.clicks;
22973
+ acc.impressions += r.impressions;
22974
+ acc.weighted += (Number(r.position) || 0) * r.impressions;
22975
+ byDay.set(key, acc);
22976
+ }
22977
+ return [...byDay.values()].map((a) => ({
22978
+ date: a.date,
22979
+ query: a.query,
22980
+ clicks: a.clicks,
22981
+ impressions: a.impressions,
22982
+ position: a.impressions > 0 ? a.weighted / a.impressions : 0
22983
+ }));
22984
+ }
22985
+ function aggregateGscByQuery(rows, queryTotals = []) {
22986
+ const accurateByQuery = new Map(queryTotals.map((t) => [t.query, t]));
22808
22987
  const accumulators = /* @__PURE__ */ new Map();
22809
22988
  for (const r of rows) {
22810
22989
  const page2 = extractPath2(r.page);
@@ -22830,12 +23009,16 @@ function aggregateGscByQuery(rows) {
22830
23009
  }
22831
23010
  const byQuery = /* @__PURE__ */ new Map();
22832
23011
  for (const [query, acc] of accumulators) {
23012
+ const accurate = accurateByQuery.get(query);
23013
+ const impressions = accurate ? accurate.impressions : acc.totalImpressions;
23014
+ const clicks = accurate ? accurate.clicks : acc.totalClicks;
23015
+ const position = accurate ? accurate.position : acc.totalImpressions > 0 ? acc.weightedPositionSum / acc.totalImpressions : 0;
22833
23016
  byQuery.set(query, {
22834
23017
  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
23018
+ position,
23019
+ impressions,
23020
+ clicks,
23021
+ ctr: impressions > 0 ? clicks / impressions : 0
22839
23022
  });
22840
23023
  }
22841
23024
  return byQuery;
@@ -22987,7 +23170,7 @@ function extractPath2(url) {
22987
23170
 
22988
23171
  // ../api-routes/src/content.ts
22989
23172
  function loadDismissedTargetRefs(db, projectId) {
22990
- const rows = db.select({ targetRef: contentTargetDismissals.targetRef }).from(contentTargetDismissals).where(eq27(contentTargetDismissals.projectId, projectId)).all();
23173
+ const rows = db.select({ targetRef: contentTargetDismissals.targetRef }).from(contentTargetDismissals).where(eq28(contentTargetDismissals.projectId, projectId)).all();
22991
23174
  return new Set(rows.map((r) => r.targetRef));
22992
23175
  }
22993
23176
  function formatDismissalRow(row) {
@@ -23082,7 +23265,7 @@ async function contentRoutes(app, opts = {}) {
23082
23265
  });
23083
23266
  app.get("/projects/:name/content/dismissals", async (request) => {
23084
23267
  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();
23268
+ const rows = app.db.select().from(contentTargetDismissals).where(eq28(contentTargetDismissals.projectId, project.id)).orderBy(contentTargetDismissals.dismissedAt).all();
23086
23269
  const response = {
23087
23270
  dismissals: rows.map(formatDismissalRow)
23088
23271
  };
@@ -23111,9 +23294,9 @@ async function contentRoutes(app, opts = {}) {
23111
23294
  dismissedAt: now
23112
23295
  }
23113
23296
  }).run();
23114
- const row = app.db.select().from(contentTargetDismissals).where(and22(
23115
- eq27(contentTargetDismissals.projectId, project.id),
23116
- eq27(contentTargetDismissals.targetRef, targetRef)
23297
+ const row = app.db.select().from(contentTargetDismissals).where(and23(
23298
+ eq28(contentTargetDismissals.projectId, project.id),
23299
+ eq28(contentTargetDismissals.targetRef, targetRef)
23117
23300
  )).get();
23118
23301
  if (!row) throw notFound("contentTargetDismissal", targetRef);
23119
23302
  return formatDismissalRow(row);
@@ -23121,9 +23304,9 @@ async function contentRoutes(app, opts = {}) {
23121
23304
  app.delete("/projects/:name/content/dismissals/:targetRef", async (request, reply) => {
23122
23305
  const project = resolveProject(app.db, request.params.name);
23123
23306
  const { targetRef } = request.params;
23124
- const result = app.db.delete(contentTargetDismissals).where(and22(
23125
- eq27(contentTargetDismissals.projectId, project.id),
23126
- eq27(contentTargetDismissals.targetRef, targetRef)
23307
+ const result = app.db.delete(contentTargetDismissals).where(and23(
23308
+ eq28(contentTargetDismissals.projectId, project.id),
23309
+ eq28(contentTargetDismissals.targetRef, targetRef)
23127
23310
  )).run();
23128
23311
  if (result.changes === 0) {
23129
23312
  throw notFound("contentTargetDismissal", targetRef);
@@ -23132,9 +23315,9 @@ async function contentRoutes(app, opts = {}) {
23132
23315
  });
23133
23316
  app.get("/projects/:name/content/recommendations/:targetRef/analysis", async (request, reply) => {
23134
23317
  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)
23318
+ const row = app.db.select().from(recommendationExplanations).where(and23(
23319
+ eq28(recommendationExplanations.projectId, project.id),
23320
+ eq28(recommendationExplanations.targetRef, request.params.targetRef)
23138
23321
  )).orderBy(desc11(recommendationExplanations.generatedAt)).limit(1).get();
23139
23322
  if (!row) {
23140
23323
  return reply.status(404).send({ error: { code: "NOT_FOUND", message: "No cached explanation for this targetRef." } });
@@ -23160,9 +23343,9 @@ async function contentRoutes(app, opts = {}) {
23160
23343
  throw notFound("contentRecommendation", targetRef);
23161
23344
  }
23162
23345
  if (!body.forceRefresh) {
23163
- const cached = app.db.select().from(recommendationExplanations).where(and22(
23164
- eq27(recommendationExplanations.projectId, project.id),
23165
- eq27(recommendationExplanations.targetRef, targetRef)
23346
+ const cached = app.db.select().from(recommendationExplanations).where(and23(
23347
+ eq28(recommendationExplanations.projectId, project.id),
23348
+ eq28(recommendationExplanations.targetRef, targetRef)
23166
23349
  )).orderBy(desc11(recommendationExplanations.generatedAt)).limit(1).get();
23167
23350
  if (cached) return reply.send(formatExplanationRow(cached));
23168
23351
  }
@@ -23199,10 +23382,10 @@ async function contentRoutes(app, opts = {}) {
23199
23382
  generatedAt: now
23200
23383
  }
23201
23384
  }).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)
23385
+ const row = app.db.select().from(recommendationExplanations).where(and23(
23386
+ eq28(recommendationExplanations.projectId, project.id),
23387
+ eq28(recommendationExplanations.targetRef, targetRef),
23388
+ eq28(recommendationExplanations.promptVersion, result.promptVersion)
23206
23389
  )).get();
23207
23390
  if (!row) throw notFound("recommendationExplanation", targetRef);
23208
23391
  return reply.send(formatExplanationRow(row));
@@ -23283,17 +23466,17 @@ async function contentRoutes(app, opts = {}) {
23283
23466
  generatedAt: now
23284
23467
  }
23285
23468
  }).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)
23469
+ const row = app.db.select().from(recommendationBriefs).where(and23(
23470
+ eq28(recommendationBriefs.projectId, project.id),
23471
+ eq28(recommendationBriefs.targetRef, targetRef),
23472
+ eq28(recommendationBriefs.promptVersion, result.promptVersion)
23290
23473
  )).get();
23291
23474
  if (!row) throw notFound("recommendationBrief", targetRef);
23292
23475
  return reply.send(formatBriefRow(row));
23293
23476
  });
23294
23477
  app.get("/projects/:name/content/domain-classifications", async (request) => {
23295
23478
  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();
23479
+ const rows = app.db.select().from(domainClassifications).where(eq28(domainClassifications.projectId, project.id)).orderBy(desc11(domainClassifications.hits)).all();
23297
23480
  const response = {
23298
23481
  classifications: rows.map((r) => ({
23299
23482
  domain: r.domain,
@@ -23307,13 +23490,13 @@ async function contentRoutes(app, opts = {}) {
23307
23490
  }
23308
23491
  function lookupCachedBrief(db, projectId, targetRef, promptVersion) {
23309
23492
  const conditions = [
23310
- eq27(recommendationBriefs.projectId, projectId),
23311
- eq27(recommendationBriefs.targetRef, targetRef)
23493
+ eq28(recommendationBriefs.projectId, projectId),
23494
+ eq28(recommendationBriefs.targetRef, targetRef)
23312
23495
  ];
23313
23496
  if (promptVersion !== void 0) {
23314
- conditions.push(eq27(recommendationBriefs.promptVersion, promptVersion));
23497
+ conditions.push(eq28(recommendationBriefs.promptVersion, promptVersion));
23315
23498
  }
23316
- return db.select().from(recommendationBriefs).where(and22(...conditions)).orderBy(desc11(recommendationBriefs.generatedAt)).limit(1).get();
23499
+ return db.select().from(recommendationBriefs).where(and23(...conditions)).orderBy(desc11(recommendationBriefs.generatedAt)).limit(1).get();
23317
23500
  }
23318
23501
  function parseLimitParam(raw) {
23319
23502
  if (raw === void 0) return void 0;
@@ -23338,141 +23521,6 @@ function winnabilityClassRank(winnabilityClass) {
23338
23521
  return winnabilityClass === WinnabilityClasses.ownable ? 0 : 1;
23339
23522
  }
23340
23523
 
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
23524
  // ../api-routes/src/report-renderer.ts
23477
23525
  var COLORS = {
23478
23526
  bg: "#09090b",
@@ -25764,6 +25812,149 @@ function renderReportHtml(report, opts = {}) {
25764
25812
  </html>`;
25765
25813
  }
25766
25814
 
25815
+ // ../api-routes/src/ga-ai-referral-aggregation.ts
25816
+ function normalizeAiTrafficClass(value) {
25817
+ return value === AiReferralTrafficClasses.paid ? AiReferralTrafficClasses.paid : AiReferralTrafficClasses.organic;
25818
+ }
25819
+ function emptyAiCounts() {
25820
+ return { sessions: 0 };
25821
+ }
25822
+ var AI_ATTRIBUTION_PREFERRED_DIMENSION = "session";
25823
+ function pickWinningAttributionDimension(sessionsByDimension) {
25824
+ let winner;
25825
+ let winnerSessions = -1;
25826
+ for (const [dimension, sessions] of sessionsByDimension) {
25827
+ const beatsOnCount = sessions > winnerSessions;
25828
+ const winsTie = sessions === winnerSessions && dimension === AI_ATTRIBUTION_PREFERRED_DIMENSION;
25829
+ if (beatsOnCount || winsTie) {
25830
+ winnerSessions = sessions;
25831
+ winner = dimension;
25832
+ }
25833
+ }
25834
+ return winner;
25835
+ }
25836
+ function resolveWinningDimensions(rows) {
25837
+ const groups = /* @__PURE__ */ new Map();
25838
+ for (const row of rows) {
25839
+ const key = `${row.date}\0${row.source}`;
25840
+ let group = groups.get(key);
25841
+ if (!group) {
25842
+ group = { date: row.date, source: row.source, byDimension: /* @__PURE__ */ new Map(), mediumSessions: /* @__PURE__ */ new Map() };
25843
+ groups.set(key, group);
25844
+ }
25845
+ let dim = group.byDimension.get(row.sourceDimension);
25846
+ if (!dim) {
25847
+ dim = { paidSessions: 0, organicSessions: 0 };
25848
+ group.byDimension.set(row.sourceDimension, dim);
25849
+ }
25850
+ const sessions = row.sessions ?? 0;
25851
+ if (normalizeAiTrafficClass(row.trafficClass) === AiReferralTrafficClasses.paid) {
25852
+ dim.paidSessions += sessions;
25853
+ } else {
25854
+ dim.organicSessions += sessions;
25855
+ }
25856
+ let mediums = group.mediumSessions.get(row.sourceDimension);
25857
+ if (!mediums) {
25858
+ mediums = /* @__PURE__ */ new Map();
25859
+ group.mediumSessions.set(row.sourceDimension, mediums);
25860
+ }
25861
+ mediums.set(row.medium, (mediums.get(row.medium) ?? 0) + sessions);
25862
+ }
25863
+ const winners = [];
25864
+ for (const group of groups.values()) {
25865
+ const winningDimension = pickWinningAttributionDimension(
25866
+ new Map([...group.byDimension].map(([d, c]) => [d, c.paidSessions + c.organicSessions]))
25867
+ );
25868
+ if (winningDimension === void 0) continue;
25869
+ const best = group.byDimension.get(winningDimension);
25870
+ let medium = "";
25871
+ let mediumBest = -1;
25872
+ for (const [candidate, sessions] of group.mediumSessions.get(winningDimension) ?? []) {
25873
+ if (sessions > mediumBest) {
25874
+ mediumBest = sessions;
25875
+ medium = candidate;
25876
+ }
25877
+ }
25878
+ winners.push({
25879
+ date: group.date,
25880
+ source: group.source,
25881
+ medium,
25882
+ paidSessions: best.paidSessions,
25883
+ organicSessions: best.organicSessions
25884
+ });
25885
+ }
25886
+ return winners;
25887
+ }
25888
+ function summarizeAiReferralCounts(rows) {
25889
+ const paidDeduped = emptyAiCounts();
25890
+ const organicDeduped = emptyAiCounts();
25891
+ const paidBySession = emptyAiCounts();
25892
+ const organicBySession = emptyAiCounts();
25893
+ const bySessionChannelGroup = /* @__PURE__ */ new Map();
25894
+ for (const winner of resolveWinningDimensions(rows)) {
25895
+ paidDeduped.sessions += winner.paidSessions;
25896
+ organicDeduped.sessions += winner.organicSessions;
25897
+ }
25898
+ for (const row of rows) {
25899
+ if (row.sourceDimension !== "session") continue;
25900
+ const sessions = row.sessions ?? 0;
25901
+ bySessionChannelGroup.set(
25902
+ row.channelGroup,
25903
+ (bySessionChannelGroup.get(row.channelGroup) ?? 0) + sessions
25904
+ );
25905
+ const bucket = normalizeAiTrafficClass(row.trafficClass) === AiReferralTrafficClasses.paid ? paidBySession : organicBySession;
25906
+ bucket.sessions += sessions;
25907
+ }
25908
+ return {
25909
+ paidDeduped,
25910
+ organicDeduped,
25911
+ paidBySession,
25912
+ organicBySession,
25913
+ bySessionChannelGroup,
25914
+ deduped: { sessions: paidDeduped.sessions + organicDeduped.sessions },
25915
+ bySession: { sessions: paidBySession.sessions + organicBySession.sessions }
25916
+ };
25917
+ }
25918
+ function buildAiReferralDailySeries(rows) {
25919
+ const byDate = /* @__PURE__ */ new Map();
25920
+ const sourceTotals = /* @__PURE__ */ new Map();
25921
+ let totalSessions = 0;
25922
+ let totalPaidSessions = 0;
25923
+ let totalOrganicSessions = 0;
25924
+ for (const winner of resolveWinningDimensions(rows)) {
25925
+ const sessions = winner.paidSessions + winner.organicSessions;
25926
+ let bySource = byDate.get(winner.date);
25927
+ if (!bySource) {
25928
+ bySource = /* @__PURE__ */ new Map();
25929
+ byDate.set(winner.date, bySource);
25930
+ }
25931
+ let bucket = bySource.get(winner.source);
25932
+ if (!bucket) {
25933
+ bucket = { source: winner.source, sessions: 0, paidSessions: 0, organicSessions: 0 };
25934
+ bySource.set(winner.source, bucket);
25935
+ }
25936
+ bucket.sessions += sessions;
25937
+ bucket.paidSessions += winner.paidSessions;
25938
+ bucket.organicSessions += winner.organicSessions;
25939
+ sourceTotals.set(winner.source, (sourceTotals.get(winner.source) ?? 0) + sessions);
25940
+ totalSessions += sessions;
25941
+ totalPaidSessions += winner.paidSessions;
25942
+ totalOrganicSessions += winner.organicSessions;
25943
+ }
25944
+ const days = [...byDate.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([date, bySource]) => {
25945
+ const sources2 = [...bySource.values()].sort((a, b) => b.sessions - a.sessions || a.source.localeCompare(b.source));
25946
+ return {
25947
+ date,
25948
+ sessions: sources2.reduce((sum, s) => sum + s.sessions, 0),
25949
+ paidSessions: sources2.reduce((sum, s) => sum + s.paidSessions, 0),
25950
+ organicSessions: sources2.reduce((sum, s) => sum + s.organicSessions, 0),
25951
+ bySource: sources2
25952
+ };
25953
+ });
25954
+ const sources = [...sourceTotals.entries()].sort(([aSource, aSessions], [bSource, bSessions]) => bSessions - aSessions || aSource.localeCompare(bSource)).map(([source]) => source);
25955
+ return { days, sources, totalSessions, totalPaidSessions, totalOrganicSessions };
25956
+ }
25957
+
25767
25958
  // ../api-routes/src/report.ts
25768
25959
  var TOP_QUERIES_LIMIT = 20;
25769
25960
  var TOP_LANDING_PAGES_LIMIT = 20;
@@ -25829,8 +26020,8 @@ function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trac
25829
26020
  const rows = db.select().from(gscSearchData).where(
25830
26021
  and24(
25831
26022
  eq29(gscSearchData.projectId, projectId),
25832
- gte5(gscSearchData.date, startDate),
25833
- lte3(gscSearchData.date, maxDate)
26023
+ gte6(gscSearchData.date, startDate),
26024
+ lte4(gscSearchData.date, maxDate)
25834
26025
  )
25835
26026
  ).all();
25836
26027
  const dailyTotals = readGscDailyTotals(db, projectId, startDate, maxDate);
@@ -25942,8 +26133,8 @@ function buildGaSection(db, projectId, windowDays2) {
25942
26133
  const snapshotRows = snapshotStartDate ? db.select().from(gaTrafficSnapshots).where(
25943
26134
  and24(
25944
26135
  eq29(gaTrafficSnapshots.projectId, projectId),
25945
- gte5(gaTrafficSnapshots.date, snapshotStartDate),
25946
- lte3(gaTrafficSnapshots.date, snapshotMaxInWindow)
26136
+ gte6(gaTrafficSnapshots.date, snapshotStartDate),
26137
+ lte4(gaTrafficSnapshots.date, snapshotMaxInWindow)
25947
26138
  )
25948
26139
  ).all() : db.select().from(gaTrafficSnapshots).where(eq29(gaTrafficSnapshots.projectId, projectId)).all();
25949
26140
  const totalSessions = windowSummary?.totalSessions ?? matchingFallbackSummary?.totalSessions ?? snapshotRows.reduce((s, r) => s + r.sessions, 0);
@@ -25971,8 +26162,8 @@ function buildGaSection(db, projectId, windowDays2) {
25971
26162
  eq29(gaAiReferrals.sourceDimension, "session")
25972
26163
  ];
25973
26164
  if (snapshotStartDate && snapshotMaxInWindow) {
25974
- aiConditions.push(gte5(gaAiReferrals.date, snapshotStartDate));
25975
- aiConditions.push(lte3(gaAiReferrals.date, snapshotMaxInWindow));
26165
+ aiConditions.push(gte6(gaAiReferrals.date, snapshotStartDate));
26166
+ aiConditions.push(lte4(gaAiReferrals.date, snapshotMaxInWindow));
25976
26167
  }
25977
26168
  const aiSessionRows = db.select({
25978
26169
  trafficClass: gaAiReferrals.trafficClass,
@@ -26077,7 +26268,7 @@ function buildAiReferrals(db, projectId, windowDays2) {
26077
26268
  if (rows.length === 0) return null;
26078
26269
  const dimSessionsByTuple = /* @__PURE__ */ new Map();
26079
26270
  for (const r of rows) {
26080
- const tupleKey = `${r.date}::${r.source}::${r.medium}`;
26271
+ const tupleKey = `${r.date}::${r.source}`;
26081
26272
  let dimMap = dimSessionsByTuple.get(tupleKey);
26082
26273
  if (!dimMap) {
26083
26274
  dimMap = /* @__PURE__ */ new Map();
@@ -26087,18 +26278,11 @@ function buildAiReferrals(db, projectId, windowDays2) {
26087
26278
  }
26088
26279
  const winningDimension = /* @__PURE__ */ new Map();
26089
26280
  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
- }
26281
+ const bestDim = pickWinningAttributionDimension(dimMap);
26098
26282
  if (bestDim) winningDimension.set(tupleKey, bestDim);
26099
26283
  }
26100
26284
  const dedupedRows = rows.filter(
26101
- (r) => winningDimension.get(`${r.date}::${r.source}::${r.medium}`) === r.sourceDimension
26285
+ (r) => winningDimension.get(`${r.date}::${r.source}`) === r.sourceDimension
26102
26286
  );
26103
26287
  let total = 0;
26104
26288
  let paidSessions = 0;
@@ -26168,8 +26352,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26168
26352
  and24(
26169
26353
  eq29(crawlerEventsHourly.projectId, projectId),
26170
26354
  eq29(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26171
- gte5(crawlerEventsHourly.tsHour, windowStartIso),
26172
- exclusiveEnd ? lt6(crawlerEventsHourly.tsHour, windowEndIso) : lte3(crawlerEventsHourly.tsHour, windowEndIso)
26355
+ gte6(crawlerEventsHourly.tsHour, windowStartIso),
26356
+ exclusiveEnd ? lt6(crawlerEventsHourly.tsHour, windowEndIso) : lte4(crawlerEventsHourly.tsHour, windowEndIso)
26173
26357
  )
26174
26358
  ).get()?.total ?? 0
26175
26359
  );
@@ -26178,8 +26362,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26178
26362
  and24(
26179
26363
  eq29(crawlerEventsHourly.projectId, projectId),
26180
26364
  ne3(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26181
- gte5(crawlerEventsHourly.tsHour, windowStartIso),
26182
- exclusiveEnd ? lt6(crawlerEventsHourly.tsHour, windowEndIso) : lte3(crawlerEventsHourly.tsHour, windowEndIso)
26365
+ gte6(crawlerEventsHourly.tsHour, windowStartIso),
26366
+ exclusiveEnd ? lt6(crawlerEventsHourly.tsHour, windowEndIso) : lte4(crawlerEventsHourly.tsHour, windowEndIso)
26183
26367
  )
26184
26368
  ).get()?.total ?? 0
26185
26369
  );
@@ -26192,8 +26376,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26192
26376
  and24(
26193
26377
  eq29(aiReferralEventsHourly.projectId, projectId),
26194
26378
  countableReferralCondition(),
26195
- gte5(aiReferralEventsHourly.tsHour, windowStartIso),
26196
- exclusiveEnd ? lt6(aiReferralEventsHourly.tsHour, windowEndIso) : lte3(aiReferralEventsHourly.tsHour, windowEndIso)
26379
+ gte6(aiReferralEventsHourly.tsHour, windowStartIso),
26380
+ exclusiveEnd ? lt6(aiReferralEventsHourly.tsHour, windowEndIso) : lte4(aiReferralEventsHourly.tsHour, windowEndIso)
26197
26381
  )
26198
26382
  ).get();
26199
26383
  return aiReferralClassCounts(Number(row?.total ?? 0), Number(row?.paid ?? 0), Number(row?.organic ?? 0));
@@ -26202,8 +26386,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26202
26386
  db.select({ total: sql10`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
26203
26387
  and24(
26204
26388
  eq29(aiUserFetchEventsHourly.projectId, projectId),
26205
- gte5(aiUserFetchEventsHourly.tsHour, windowStartIso),
26206
- exclusiveEnd ? lt6(aiUserFetchEventsHourly.tsHour, windowEndIso) : lte3(aiUserFetchEventsHourly.tsHour, windowEndIso)
26389
+ gte6(aiUserFetchEventsHourly.tsHour, windowStartIso),
26390
+ exclusiveEnd ? lt6(aiUserFetchEventsHourly.tsHour, windowEndIso) : lte4(aiUserFetchEventsHourly.tsHour, windowEndIso)
26207
26391
  )
26208
26392
  ).get()?.total ?? 0
26209
26393
  );
@@ -26219,8 +26403,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26219
26403
  and24(
26220
26404
  eq29(aiReferralEventsHourly.projectId, projectId),
26221
26405
  nonSubresourceReferralPathCondition(),
26222
- gte5(aiReferralEventsHourly.tsHour, headlineStart),
26223
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26406
+ gte6(aiReferralEventsHourly.tsHour, headlineStart),
26407
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26224
26408
  )
26225
26409
  ).get()?.total ?? 0
26226
26410
  );
@@ -26233,8 +26417,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26233
26417
  }).from(crawlerEventsHourly).where(
26234
26418
  and24(
26235
26419
  eq29(crawlerEventsHourly.projectId, projectId),
26236
- gte5(crawlerEventsHourly.tsHour, headlineStart),
26237
- lte3(crawlerEventsHourly.tsHour, headlineEnd)
26420
+ gte6(crawlerEventsHourly.tsHour, headlineStart),
26421
+ lte4(crawlerEventsHourly.tsHour, headlineEnd)
26238
26422
  )
26239
26423
  ).groupBy(crawlerEventsHourly.operator, crawlerEventsHourly.verificationStatus).all();
26240
26424
  const crawlerByOperatorPriorRows = db.select({
@@ -26244,7 +26428,7 @@ function buildServerActivity(db, projectId, windowDays2) {
26244
26428
  and24(
26245
26429
  eq29(crawlerEventsHourly.projectId, projectId),
26246
26430
  eq29(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26247
- gte5(crawlerEventsHourly.tsHour, priorStart),
26431
+ gte6(crawlerEventsHourly.tsHour, priorStart),
26248
26432
  lt6(crawlerEventsHourly.tsHour, headlineStart)
26249
26433
  )
26250
26434
  ).groupBy(crawlerEventsHourly.operator).all();
@@ -26255,8 +26439,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26255
26439
  and24(
26256
26440
  eq29(aiReferralEventsHourly.projectId, projectId),
26257
26441
  countableReferralCondition(),
26258
- gte5(aiReferralEventsHourly.tsHour, headlineStart),
26259
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26442
+ gte6(aiReferralEventsHourly.tsHour, headlineStart),
26443
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26260
26444
  )
26261
26445
  ).groupBy(aiReferralEventsHourly.operator).all();
26262
26446
  const userFetchByOperatorRows = db.select({
@@ -26265,8 +26449,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26265
26449
  }).from(aiUserFetchEventsHourly).where(
26266
26450
  and24(
26267
26451
  eq29(aiUserFetchEventsHourly.projectId, projectId),
26268
- gte5(aiUserFetchEventsHourly.tsHour, headlineStart),
26269
- lte3(aiUserFetchEventsHourly.tsHour, headlineEnd)
26452
+ gte6(aiUserFetchEventsHourly.tsHour, headlineStart),
26453
+ lte4(aiUserFetchEventsHourly.tsHour, headlineEnd)
26270
26454
  )
26271
26455
  ).groupBy(aiUserFetchEventsHourly.operator).all();
26272
26456
  const operatorAgg = /* @__PURE__ */ new Map();
@@ -26310,8 +26494,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26310
26494
  and24(
26311
26495
  eq29(crawlerEventsHourly.projectId, projectId),
26312
26496
  eq29(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26313
- gte5(crawlerEventsHourly.tsHour, headlineStart),
26314
- lte3(crawlerEventsHourly.tsHour, headlineEnd)
26497
+ gte6(crawlerEventsHourly.tsHour, headlineStart),
26498
+ lte4(crawlerEventsHourly.tsHour, headlineEnd)
26315
26499
  )
26316
26500
  ).groupBy(crawlerEventsHourly.pathNormalized).orderBy(desc12(sql10`SUM(${crawlerEventsHourly.hits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
26317
26501
  const topCrawledPaths = topPathsRows.map((r) => ({
@@ -26327,8 +26511,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26327
26511
  and24(
26328
26512
  eq29(aiReferralEventsHourly.projectId, projectId),
26329
26513
  countableReferralCondition(),
26330
- gte5(aiReferralEventsHourly.tsHour, headlineStart),
26331
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26514
+ gte6(aiReferralEventsHourly.tsHour, headlineStart),
26515
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26332
26516
  )
26333
26517
  ).groupBy(aiReferralEventsHourly.product).orderBy(desc12(sql10`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).all();
26334
26518
  const referralProducts = referralProductsRows.map((r) => ({
@@ -26344,8 +26528,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26344
26528
  and24(
26345
26529
  eq29(aiReferralEventsHourly.projectId, projectId),
26346
26530
  countableReferralCondition(),
26347
- gte5(aiReferralEventsHourly.tsHour, headlineStart),
26348
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26531
+ gte6(aiReferralEventsHourly.tsHour, headlineStart),
26532
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26349
26533
  )
26350
26534
  ).groupBy(aiReferralEventsHourly.landingPathNormalized).orderBy(desc12(sql10`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
26351
26535
  const topReferralLandingPaths = topReferralRows.map((r) => ({
@@ -26360,8 +26544,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26360
26544
  and24(
26361
26545
  eq29(crawlerEventsHourly.projectId, projectId),
26362
26546
  eq29(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
26363
- gte5(crawlerEventsHourly.tsHour, trendStart),
26364
- lte3(crawlerEventsHourly.tsHour, headlineEnd)
26547
+ gte6(crawlerEventsHourly.tsHour, trendStart),
26548
+ lte4(crawlerEventsHourly.tsHour, headlineEnd)
26365
26549
  )
26366
26550
  ).groupBy(sql10`SUBSTR(${crawlerEventsHourly.tsHour}, 1, 10)`).all();
26367
26551
  const referralTrendRows = db.select({
@@ -26371,8 +26555,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26371
26555
  and24(
26372
26556
  eq29(aiReferralEventsHourly.projectId, projectId),
26373
26557
  countableReferralCondition(),
26374
- gte5(aiReferralEventsHourly.tsHour, trendStart),
26375
- lte3(aiReferralEventsHourly.tsHour, headlineEnd)
26558
+ gte6(aiReferralEventsHourly.tsHour, trendStart),
26559
+ lte4(aiReferralEventsHourly.tsHour, headlineEnd)
26376
26560
  )
26377
26561
  ).groupBy(sql10`SUBSTR(${aiReferralEventsHourly.tsHour}, 1, 10)`).all();
26378
26562
  const userFetchTrendRows = db.select({
@@ -26381,8 +26565,8 @@ function buildServerActivity(db, projectId, windowDays2) {
26381
26565
  }).from(aiUserFetchEventsHourly).where(
26382
26566
  and24(
26383
26567
  eq29(aiUserFetchEventsHourly.projectId, projectId),
26384
- gte5(aiUserFetchEventsHourly.tsHour, trendStart),
26385
- lte3(aiUserFetchEventsHourly.tsHour, headlineEnd)
26568
+ gte6(aiUserFetchEventsHourly.tsHour, trendStart),
26569
+ lte4(aiUserFetchEventsHourly.tsHour, headlineEnd)
26386
26570
  )
26387
26571
  ).groupBy(sql10`SUBSTR(${aiUserFetchEventsHourly.tsHour}, 1, 10)`).all();
26388
26572
  const emptyTrendEntry = () => ({ verifiedCrawlerHits: 0, userFetchHits: 0, referralArrivals: 0 });
@@ -27332,10 +27516,10 @@ async function reportRoutes(app) {
27332
27516
  }
27333
27517
 
27334
27518
  // ../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";
27519
+ 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
27520
 
27337
27521
  // ../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";
27522
+ import { and as and25, eq as eq30, gte as gte7, lte as lte5, or as or5, sql as sql11 } from "drizzle-orm";
27339
27523
  var UNKNOWN_LANDING_PAGE = "(not set)";
27340
27524
  function addDays(date, offset) {
27341
27525
  const value = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
@@ -27531,15 +27715,15 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
27531
27715
  const gaAnchor = [acquisitionAnchor, leadAnchor].filter((date) => date !== null).sort().at(-1) ?? null;
27532
27716
  const gaPeriods = gaAnchor ? buildPeriods(gaAnchor, days) : [];
27533
27717
  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() : [];
27718
+ const acquisitionRows = gaStartDate ? db.select().from(gaAcquisitionDaily).where(and25(eq30(gaAcquisitionDaily.projectId, project.id), gte7(gaAcquisitionDaily.date, gaStartDate), lte5(gaAcquisitionDaily.date, gaAnchor))).all() : [];
27719
+ const leadRows = gaStartDate ? db.select().from(gaLeadEventsDaily).where(and25(eq30(gaLeadEventsDaily.projectId, project.id), gte7(gaLeadEventsDaily.date, gaStartDate), lte5(gaLeadEventsDaily.date, gaAnchor))).all() : [];
27536
27720
  const engagementAnchor = db.select({ date: sql11`max(${gaDailyTotals.date})` }).from(gaDailyTotals).where(eq30(gaDailyTotals.projectId, project.id)).get()?.date ?? null;
27537
27721
  const engagementPeriodWindow = engagementAnchor ? buildPeriods(engagementAnchor, days) : [];
27538
27722
  const engagementStartDate = engagementPeriodWindow[0]?.startDate;
27539
27723
  const engagementRows = engagementStartDate ? db.select().from(gaDailyTotals).where(and25(
27540
27724
  eq30(gaDailyTotals.projectId, project.id),
27541
- gte6(gaDailyTotals.date, engagementStartDate),
27542
- lte4(gaDailyTotals.date, engagementAnchor)
27725
+ gte7(gaDailyTotals.date, engagementStartDate),
27726
+ lte5(gaDailyTotals.date, engagementAnchor)
27543
27727
  )).all() : [];
27544
27728
  const engagementAvailableFrom = db.select({
27545
27729
  date: sql11`min(${gaDailyTotals.date})`
@@ -27553,9 +27737,9 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
27553
27737
  const gscAnchor = db.select({ date: sql11`max(${gscDailyTotals.date})` }).from(gscDailyTotals).where(eq30(gscDailyTotals.projectId, project.id)).get()?.date ?? null;
27554
27738
  const gscPeriods = gscAnchor ? buildPeriods(gscAnchor, days) : [];
27555
27739
  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() : [];
27740
+ const propertyRows = gscStartDate ? db.select().from(gscDailyTotals).where(and25(eq30(gscDailyTotals.projectId, project.id), gte7(gscDailyTotals.date, gscStartDate), lte5(gscDailyTotals.date, gscAnchor))).all() : [];
27741
+ const queryRows = gscStartDate ? db.select().from(gscQueryDailyTotals).where(and25(eq30(gscQueryDailyTotals.projectId, project.id), gte7(gscQueryDailyTotals.date, gscStartDate), lte5(gscQueryDailyTotals.date, gscAnchor))).all() : [];
27742
+ const rawPageRows = gscStartDate ? db.select().from(gscSearchData).where(and25(eq30(gscSearchData.projectId, project.id), gte7(gscSearchData.date, gscStartDate), lte5(gscSearchData.date, gscAnchor))).all() : [];
27559
27743
  const state = db.select().from(gaMeasurementSyncStates).where(eq30(gaMeasurementSyncStates.projectId, project.id)).get();
27560
27744
  const acquisition = acquisitionRows.filter((row) => {
27561
27745
  const landingPage = normalizeLandingPage(row.landingPageNormalized ?? row.landingPage);
@@ -27907,18 +28091,18 @@ function buildOrganicEvidence(db, projectName, periodDays) {
27907
28091
  const isInWindow = (date) => inRange(date, startDate, endDate);
27908
28092
  const gscRows = gscStartDate && gscEndDate ? db.select().from(gscDailyTotals).where(and26(
27909
28093
  eq31(gscDailyTotals.projectId, project.id),
27910
- gte7(gscDailyTotals.date, gscStartDate),
27911
- lte5(gscDailyTotals.date, gscEndDate)
28094
+ gte8(gscDailyTotals.date, gscStartDate),
28095
+ lte6(gscDailyTotals.date, gscEndDate)
27912
28096
  )).all() : [];
27913
28097
  const gaRows = gaStartDate && gaEndDate ? db.select().from(gaTrafficSnapshots).where(and26(
27914
28098
  eq31(gaTrafficSnapshots.projectId, project.id),
27915
- gte7(gaTrafficSnapshots.date, gaStartDate),
27916
- lte5(gaTrafficSnapshots.date, gaEndDate)
28099
+ gte8(gaTrafficSnapshots.date, gaStartDate),
28100
+ lte6(gaTrafficSnapshots.date, gaEndDate)
27917
28101
  )).all() : [];
27918
28102
  const nativeAcquisitionRows = nativeAcquisitionActive && nativeStartDate && nativeEndDate ? db.select().from(gaAcquisitionDaily).where(and26(
27919
28103
  eq31(gaAcquisitionDaily.projectId, project.id),
27920
- gte7(gaAcquisitionDaily.date, nativeStartDate),
27921
- lte5(gaAcquisitionDaily.date, nativeEndDate)
28104
+ gte8(gaAcquisitionDaily.date, nativeStartDate),
28105
+ lte6(gaAcquisitionDaily.date, nativeEndDate)
27922
28106
  )).all().filter((row) => matchesMarketingHost(row.hostName, marketingHosts)) : [];
27923
28107
  const nativeOrganicRows = nativeAcquisitionRows.filter((row) => row.channelGroup === "Organic Search");
27924
28108
  const nativeWindowOrganicRows = nativeOrganicRows;
@@ -27956,8 +28140,8 @@ function buildOrganicEvidence(db, projectName, periodDays) {
27956
28140
  );
27957
28141
  const namedRows = db.select().from(gscQueryDailyTotals).where(and26(
27958
28142
  eq31(gscQueryDailyTotals.projectId, project.id),
27959
- gte7(gscQueryDailyTotals.date, startDate),
27960
- lte5(gscQueryDailyTotals.date, endDate)
28143
+ gte8(gscQueryDailyTotals.date, startDate),
28144
+ lte6(gscQueryDailyTotals.date, endDate)
27961
28145
  )).all();
27962
28146
  for (const row of namedRows) {
27963
28147
  const isBrand = categorizeQueryByIntent(row.query, brandTokens) === "brand";
@@ -27983,7 +28167,7 @@ function buildOrganicEvidence(db, projectName, periodDays) {
27983
28167
  totals: sumSearchRows(gscRows.filter((row) => inRange(row.date, cohort.startDate, cohort.endDate)))
27984
28168
  }))
27985
28169
  } : 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));
28170
+ 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
28171
  const gaWindow = gaStartDate && gaEndDate ? gaRows.filter((row) => inRange(row.date, gaStartDate, gaEndDate)) : [];
27988
28172
  const legacyGaCohorts = gaCohorts.map((cohort) => ({
27989
28173
  ...cohort,
@@ -28010,7 +28194,7 @@ function buildOrganicEvidence(db, projectName, periodDays) {
28010
28194
  }))
28011
28195
  };
28012
28196
  }
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");
28197
+ 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
28198
  const aiRows = gaStartDate && gaEndDate ? allGaAiRows.filter((row) => inRange(row.date, gaStartDate, gaEndDate)) : [];
28015
28199
  const gaAiReferralSummary = allGaAiRows.length ? {
28016
28200
  paidSessions: aiRows.filter((row) => row.trafficClass === AiReferralTrafficClasses.paid).reduce((count2, row) => count2 + row.sessions, 0),
@@ -28019,9 +28203,9 @@ function buildOrganicEvidence(db, projectName, periodDays) {
28019
28203
  const serverSources = db.select().from(trafficSources).where(eq31(trafficSources.projectId, project.id)).all();
28020
28204
  const serverStart = `${startDate}T00:00:00`;
28021
28205
  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();
28206
+ const allCrawlers = db.select().from(crawlerEventsHourly).where(and26(eq31(crawlerEventsHourly.projectId, project.id), gte8(crawlerEventsHourly.tsHour, serverStart), lte6(crawlerEventsHourly.tsHour, serverEnd))).all();
28207
+ const allFetches = db.select().from(aiUserFetchEventsHourly).where(and26(eq31(aiUserFetchEventsHourly.projectId, project.id), gte8(aiUserFetchEventsHourly.tsHour, serverStart), lte6(aiUserFetchEventsHourly.tsHour, serverEnd))).all();
28208
+ const allReferrals = db.select().from(aiReferralEventsHourly).where(and26(eq31(aiReferralEventsHourly.projectId, project.id), countableReferralCondition(), gte8(aiReferralEventsHourly.tsHour, serverStart), lte6(aiReferralEventsHourly.tsHour, serverEnd))).all();
28025
28209
  const [serverCoverageRow] = db.all(sql12`
28026
28210
  select
28027
28211
  min(day) as startDate,
@@ -29015,7 +29199,7 @@ async function visibilityStatsRoutes(app) {
29015
29199
  }
29016
29200
 
29017
29201
  // ../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";
29202
+ import { and as and29, asc as asc5, eq as eq34, gte as gte9, lte as lte7 } from "drizzle-orm";
29019
29203
  var CSV_COLUMNS = [
29020
29204
  "export_schema_version",
29021
29205
  "project_id",
@@ -29155,8 +29339,8 @@ async function resultsExportRoutes(app) {
29155
29339
  eq34(runs.projectId, project.id),
29156
29340
  eq34(runs.kind, RunKinds["answer-visibility"]),
29157
29341
  ...includeProbes ? [] : [notProbeRun()],
29158
- ...sinceIso ? [gte8(runs.createdAt, sinceIso)] : [],
29159
- ...untilIso ? [lte6(runs.createdAt, untilIso)] : []
29342
+ ...sinceIso ? [gte9(runs.createdAt, sinceIso)] : [],
29343
+ ...untilIso ? [lte7(runs.createdAt, untilIso)] : []
29160
29344
  ];
29161
29345
  const rows = app.db.select({
29162
29346
  runId: runs.id,
@@ -43267,7 +43451,7 @@ async function googleMarketingRoutes(app, opts) {
43267
43451
 
43268
43452
  // ../api-routes/src/ads.ts
43269
43453
  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";
43454
+ 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
43455
 
43272
43456
  // ../api-routes/src/ads-activation.ts
43273
43457
  import crypto33 from "crypto";
@@ -44653,7 +44837,7 @@ async function executeApprovedAdsActivation(dependencies, request) {
44653
44837
 
44654
44838
  // ../api-routes/src/ads-activation-routes.ts
44655
44839
  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";
44840
+ 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
44841
  var MAX_ACTIVATION_GRANT_LIFETIME_MS = 24 * 60 * 60 * 1e3;
44658
44842
  var ACTIVATION_LEASE_MS = 5 * 6e4;
44659
44843
  var ACTIVATION_REVOCATION_SETTLEMENT_MS = ACTIVATION_LEASE_MS;
@@ -44783,7 +44967,7 @@ function createActivationStore(app, request) {
44783
44967
  isNull4(adsOperations.leaseOwner),
44784
44968
  eq42(adsOperations.leaseOwner, input.leaseOwner),
44785
44969
  isNull4(adsOperations.leaseExpiresAt),
44786
- lte7(adsOperations.leaseExpiresAt, input.now)
44970
+ lte8(adsOperations.leaseExpiresAt, input.now)
44787
44971
  )
44788
44972
  )).run();
44789
44973
  const resumed = loadCanonicalActivation(tx, grant.id, existing.id);
@@ -45200,7 +45384,7 @@ function selectWatchdogOperations(app, nowIso, batchSize, allowLiveRevocationLea
45200
45384
  const leaseAvailable = or9(
45201
45385
  isNull4(adsOperations.leaseOwner),
45202
45386
  isNull4(adsOperations.leaseExpiresAt),
45203
- lte7(adsOperations.leaseExpiresAt, nowIso)
45387
+ lte8(adsOperations.leaseExpiresAt, nowIso)
45204
45388
  );
45205
45389
  const campaignHasMutationEvidence = or9(
45206
45390
  eq42(adsOperationSteps.state, AdsOperationStepStates.executing),
@@ -45396,7 +45580,7 @@ async function sweepStaleAdsActivationsOnce(app, opts) {
45396
45580
  or9(
45397
45581
  isNull4(adsOperations.leaseOwner),
45398
45582
  isNull4(adsOperations.leaseExpiresAt),
45399
- lte7(adsOperations.leaseExpiresAt, nowIso)
45583
+ lte8(adsOperations.leaseExpiresAt, nowIso)
45400
45584
  )
45401
45585
  )
45402
45586
  )
@@ -45433,7 +45617,7 @@ async function sweepStaleAdsActivationsOnce(app, opts) {
45433
45617
  or9(
45434
45618
  isNull4(adsOperations.leaseOwner),
45435
45619
  isNull4(adsOperations.leaseExpiresAt),
45436
- lte7(adsOperations.leaseExpiresAt, failedAt)
45620
+ lte8(adsOperations.leaseExpiresAt, failedAt)
45437
45621
  )
45438
45622
  )
45439
45623
  )
@@ -46610,13 +46794,13 @@ function claimOperationForReconciliation(app, row, leaseOwner, now, leaseMs, pol
46610
46794
  const unknownCutoff = new Date(now.getTime() - backoffMs).toISOString();
46611
46795
  const claimableState = row.state === AdsOperationStates.pending ? and35(
46612
46796
  eq43(adsOperations.state, AdsOperationStates.pending),
46613
- lte8(adsOperations.updatedAt, pendingCutoff)
46797
+ lte9(adsOperations.updatedAt, pendingCutoff)
46614
46798
  ) : row.state === AdsOperationStates.unknown ? and35(
46615
46799
  eq43(adsOperations.state, AdsOperationStates.unknown),
46616
- enforceBackoff ? lte8(adsOperations.updatedAt, unknownCutoff) : void 0
46800
+ enforceBackoff ? lte9(adsOperations.updatedAt, unknownCutoff) : void 0
46617
46801
  ) : and35(
46618
46802
  eq43(adsOperations.state, AdsOperationStates.reconciling),
46619
- or10(isNull5(adsOperations.leaseExpiresAt), lte8(adsOperations.leaseExpiresAt, nowIso))
46803
+ or10(isNull5(adsOperations.leaseExpiresAt), lte9(adsOperations.leaseExpiresAt, nowIso))
46620
46804
  );
46621
46805
  const claimed = app.db.update(adsOperations).set({
46622
46806
  state: AdsOperationStates.reconciling,
@@ -46736,7 +46920,7 @@ function quarantineExhaustedReconciliation(app, row, now, policy, context) {
46736
46920
  }).where(and35(
46737
46921
  eq43(adsOperations.id, row.id),
46738
46922
  eq43(adsOperations.projectId, row.projectId),
46739
- gte9(adsOperations.reconcileAttempts, policy.maxAttempts),
46923
+ gte10(adsOperations.reconcileAttempts, policy.maxAttempts),
46740
46924
  inArray17(adsOperations.state, [
46741
46925
  AdsOperationStates.pending,
46742
46926
  AdsOperationStates.unknown,
@@ -46934,14 +47118,14 @@ async function sweepAdsOperationsOnce(app, opts, config) {
46934
47118
  const backoffMs = config.policy.backoffBaseMs * 2 ** Math.max(0, reconcileAttempts - 1);
46935
47119
  return and35(
46936
47120
  eq43(adsOperations.reconcileAttempts, reconcileAttempts),
46937
- lte8(adsOperations.updatedAt, new Date(now.getTime() - backoffMs).toISOString())
47121
+ lte9(adsOperations.updatedAt, new Date(now.getTime() - backoffMs).toISOString())
46938
47122
  );
46939
47123
  }
46940
47124
  );
46941
47125
  const retryableStateWhere = or10(
46942
47126
  and35(
46943
47127
  eq43(adsOperations.state, AdsOperationStates.pending),
46944
- lte8(adsOperations.updatedAt, pendingCutoff)
47128
+ lte9(adsOperations.updatedAt, pendingCutoff)
46945
47129
  ),
46946
47130
  and35(
46947
47131
  eq43(adsOperations.state, AdsOperationStates.unknown),
@@ -46949,7 +47133,7 @@ async function sweepAdsOperationsOnce(app, opts, config) {
46949
47133
  ),
46950
47134
  and35(
46951
47135
  eq43(adsOperations.state, AdsOperationStates.reconciling),
46952
- or10(isNull5(adsOperations.leaseExpiresAt), lte8(adsOperations.leaseExpiresAt, nowIso))
47136
+ or10(isNull5(adsOperations.leaseExpiresAt), lte9(adsOperations.leaseExpiresAt, nowIso))
46953
47137
  )
46954
47138
  );
46955
47139
  const staleReconcileWhere = and35(
@@ -46972,16 +47156,16 @@ async function sweepAdsOperationsOnce(app, opts, config) {
46972
47156
  );
46973
47157
  const exhaustedRows = app.db.select().from(adsOperations).where(and35(
46974
47158
  ne5(adsOperations.kind, AdsOperationKinds.campaign_tree_activate),
46975
- gte9(adsOperations.reconcileAttempts, config.policy.maxAttempts),
47159
+ gte10(adsOperations.reconcileAttempts, config.policy.maxAttempts),
46976
47160
  or10(
46977
47161
  and35(
46978
47162
  eq43(adsOperations.state, AdsOperationStates.pending),
46979
- lte8(adsOperations.updatedAt, pendingCutoff)
47163
+ lte9(adsOperations.updatedAt, pendingCutoff)
46980
47164
  ),
46981
47165
  eq43(adsOperations.state, AdsOperationStates.unknown),
46982
47166
  and35(
46983
47167
  eq43(adsOperations.state, AdsOperationStates.reconciling),
46984
- or10(isNull5(adsOperations.leaseExpiresAt), lte8(adsOperations.leaseExpiresAt, nowIso))
47168
+ or10(isNull5(adsOperations.leaseExpiresAt), lte9(adsOperations.leaseExpiresAt, nowIso))
46985
47169
  )
46986
47170
  ),
46987
47171
  or10(
@@ -47857,8 +48041,8 @@ async function adsRoutes(app, opts) {
47857
48041
  const conditions = [eq43(adsInsightsDaily.projectId, project.id)];
47858
48042
  if (parsedLevel) conditions.push(eq43(adsInsightsDaily.level, parsedLevel));
47859
48043
  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));
48044
+ if (from) conditions.push(gte10(adsInsightsDaily.date, from));
48045
+ if (to) conditions.push(lte9(adsInsightsDaily.date, to));
47862
48046
  const rows = app.db.select().from(adsInsightsDaily).where(and35(...conditions)).orderBy(asc8(adsInsightsDaily.date)).all();
47863
48047
  const conn = app.db.select().from(adsConnections).where(eq43(adsConnections.projectId, project.id)).get();
47864
48048
  const accountToday = adsAccountToday(conn?.timezone, (/* @__PURE__ */ new Date()).toISOString());
@@ -49238,118 +49422,6 @@ function buildSessionHistory(landingPageDays, dailyTotals) {
49238
49422
  });
49239
49423
  }
49240
49424
 
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
49425
  // ../api-routes/src/ga.ts
49354
49426
  function gaLog(level, action, ctx) {
49355
49427
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
@@ -49401,16 +49473,26 @@ function buildChannelBreakdown(input) {
49401
49473
  }
49402
49474
  };
49403
49475
  }
49404
- function pickWinningDimension(rows, tupleKey) {
49405
- const winners = /* @__PURE__ */ new Map();
49476
+ function keepWinningDimensionRows(rows, groupKey) {
49477
+ const sessionsByGroupAndDimension = /* @__PURE__ */ new Map();
49406
49478
  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
- }
49479
+ const key = groupKey(row);
49480
+ let byDimension = sessionsByGroupAndDimension.get(key);
49481
+ if (!byDimension) {
49482
+ byDimension = /* @__PURE__ */ new Map();
49483
+ sessionsByGroupAndDimension.set(key, byDimension);
49484
+ }
49485
+ byDimension.set(
49486
+ row.sourceDimension,
49487
+ (byDimension.get(row.sourceDimension) ?? 0) + (row.sessions ?? 0)
49488
+ );
49412
49489
  }
49413
- return [...winners.values()].sort((a, b) => (b.sessions ?? 0) - (a.sessions ?? 0));
49490
+ const winningDimension = /* @__PURE__ */ new Map();
49491
+ for (const [key, byDimension] of sessionsByGroupAndDimension) {
49492
+ const winner = pickWinningAttributionDimension(byDimension);
49493
+ if (winner) winningDimension.set(key, winner);
49494
+ }
49495
+ return rows.filter((row) => winningDimension.get(groupKey(row)) === row.sourceDimension).sort((a, b) => (b.sessions ?? 0) - (a.sessions ?? 0));
49414
49496
  }
49415
49497
  function persistAcquisitionMeasurement(db, input) {
49416
49498
  const { startDate, endDate } = input.report;
@@ -50169,13 +50251,10 @@ async function ga4Routes(app, opts) {
50169
50251
  channelGroup: gaAiReferrals.channelGroup,
50170
50252
  sessions: gaAiReferrals.sessions
50171
50253
  }).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(
50254
+ const aiReferrals = keepWinningDimensionRows(aiReferralRows, (r) => r.source);
50255
+ const aiReferralLandingPages = keepWinningDimensionRows(
50177
50256
  aiReferralLandingPageRows,
50178
- (r) => `${r.source}\0${r.medium}\0${r.trafficClass}\0${r.landingPage}`
50257
+ (r) => `${r.source}\0${r.landingPage}`
50179
50258
  );
50180
50259
  const aiSummary = summarizeAiReferralCounts(aiRowsForTotals);
50181
50260
  const socialReferrals = app.db.select({
@@ -52948,7 +53027,7 @@ import crypto43 from "crypto";
52948
53027
  import { isIP } from "net";
52949
53028
  import { isDeepStrictEqual } from "util";
52950
53029
  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";
53030
+ 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
53031
 
52953
53032
  // ../api-routes/src/cloudflare-worker-version.ts
52954
53033
  var CURRENT_CLOUDFLARE_WORKER_VERSION = "1.0.1";
@@ -69559,7 +69638,7 @@ async function ackCloudflareQueueMessages(client, options) {
69559
69638
 
69560
69639
  // ../api-routes/src/traffic-event-ingest.ts
69561
69640
  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";
69641
+ import { and as and42, eq as eq49, gte as gte11, lt as lt10, lte as lte10, sql as sql21 } from "drizzle-orm";
69563
69642
 
69564
69643
  // ../api-routes/src/raw-event-sample-retention.ts
69565
69644
  import { and as and41, eq as eq48, lt as lt9, sql as sql20 } from "drizzle-orm";
@@ -69638,7 +69717,7 @@ function writeTrafficEventBatch(opts) {
69638
69717
  );
69639
69718
  tx.delete(trafficEventReceipts).where(and42(
69640
69719
  eq49(trafficEventReceipts.sourceId, opts.sourceId),
69641
- lte9(trafficEventReceipts.expiresAt, opts.receivedAt)
69720
+ lte10(trafficEventReceipts.expiresAt, opts.receivedAt)
69642
69721
  )).run();
69643
69722
  const claimedEvents = [];
69644
69723
  for (const event of opts.events) {
@@ -69838,7 +69917,7 @@ function writeTrafficEventBatch(opts) {
69838
69917
  if (remaining === void 0) {
69839
69918
  const existing = tx.select({ total: sql21`COUNT(*)` }).from(rawEventSamples).where(and42(
69840
69919
  eq49(rawEventSamples.sourceId, opts.sourceId),
69841
- gte10(rawEventSamples.ts, hour.start),
69920
+ gte11(rawEventSamples.ts, hour.start),
69842
69921
  lt10(rawEventSamples.ts, hour.end)
69843
69922
  )).get();
69844
69923
  remaining = Math.max(0, hourlySampleLimit - Number(existing?.total ?? 0));
@@ -69896,7 +69975,7 @@ function writeTrafficEventBatch(opts) {
69896
69975
  }
69897
69976
 
69898
69977
  // ../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";
69978
+ import { and as and43, eq as eq50, isNull as isNull6, lte as lte11, or as or11 } from "drizzle-orm";
69900
69979
  function leaseExpiresAt(now, ttlMs) {
69901
69980
  const nowMs = Date.parse(now);
69902
69981
  if (!Number.isFinite(nowMs)) throw new RangeError("Traffic sync lease now must be an ISO instant");
@@ -69913,7 +69992,7 @@ function tryClaimTrafficSyncLease(input) {
69913
69992
  eq50(trafficSources.id, input.sourceId),
69914
69993
  or11(
69915
69994
  isNull6(trafficSources.syncLeaseOwner),
69916
- lte10(trafficSources.syncLeaseExpiresAt, input.now),
69995
+ lte11(trafficSources.syncLeaseExpiresAt, input.now),
69917
69996
  eq50(trafficSources.syncLeaseOwner, input.owner)
69918
69997
  )
69919
69998
  )).run();
@@ -70409,29 +70488,29 @@ async function runBackfillTask(options) {
70409
70488
  tx.delete(crawlerEventsHourly).where(
70410
70489
  and44(
70411
70490
  eq51(crawlerEventsHourly.sourceId, sourceRow2.id),
70412
- gte11(crawlerEventsHourly.tsHour, windowStartIso),
70413
- lte11(crawlerEventsHourly.tsHour, windowEndIso)
70491
+ gte12(crawlerEventsHourly.tsHour, windowStartIso),
70492
+ lte12(crawlerEventsHourly.tsHour, windowEndIso)
70414
70493
  )
70415
70494
  ).run();
70416
70495
  tx.delete(aiUserFetchEventsHourly).where(
70417
70496
  and44(
70418
70497
  eq51(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
70419
- gte11(aiUserFetchEventsHourly.tsHour, windowStartIso),
70420
- lte11(aiUserFetchEventsHourly.tsHour, windowEndIso)
70498
+ gte12(aiUserFetchEventsHourly.tsHour, windowStartIso),
70499
+ lte12(aiUserFetchEventsHourly.tsHour, windowEndIso)
70421
70500
  )
70422
70501
  ).run();
70423
70502
  tx.delete(aiReferralEventsHourly).where(
70424
70503
  and44(
70425
70504
  eq51(aiReferralEventsHourly.sourceId, sourceRow2.id),
70426
- gte11(aiReferralEventsHourly.tsHour, windowStartIso),
70427
- lte11(aiReferralEventsHourly.tsHour, windowEndIso)
70505
+ gte12(aiReferralEventsHourly.tsHour, windowStartIso),
70506
+ lte12(aiReferralEventsHourly.tsHour, windowEndIso)
70428
70507
  )
70429
70508
  ).run();
70430
70509
  tx.delete(rawEventSamples).where(
70431
70510
  and44(
70432
70511
  eq51(rawEventSamples.sourceId, sourceRow2.id),
70433
- gte11(rawEventSamples.ts, windowStartIso),
70434
- lte11(rawEventSamples.ts, windowEndIso)
70512
+ gte12(rawEventSamples.ts, windowStartIso),
70513
+ lte12(rawEventSamples.ts, windowEndIso)
70435
70514
  )
70436
70515
  ).run();
70437
70516
  for (const bucket of report.crawlerEventsHourly) {
@@ -72595,7 +72674,7 @@ async function trafficRoutes(app, opts) {
72595
72674
  }).from(crawlerEventsHourly).where(
72596
72675
  and44(
72597
72676
  eq51(crawlerEventsHourly.sourceId, row.id),
72598
- gte11(crawlerEventsHourly.tsHour, since)
72677
+ gte12(crawlerEventsHourly.tsHour, since)
72599
72678
  )
72600
72679
  ).groupBy(crawlerEventsHourly.pathNormalized).all();
72601
72680
  const crawlerSegments = segmentCrawlerHits(
@@ -72605,7 +72684,7 @@ async function trafficRoutes(app, opts) {
72605
72684
  const aiUserFetchTotals = app.db.select({ total: sql22`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
72606
72685
  and44(
72607
72686
  eq51(aiUserFetchEventsHourly.sourceId, row.id),
72608
- gte11(aiUserFetchEventsHourly.tsHour, since)
72687
+ gte12(aiUserFetchEventsHourly.tsHour, since)
72609
72688
  )
72610
72689
  ).get();
72611
72690
  const aiTotals = app.db.select({
@@ -72615,13 +72694,13 @@ async function trafficRoutes(app, opts) {
72615
72694
  }).from(aiReferralEventsHourly).where(
72616
72695
  and44(
72617
72696
  eq51(aiReferralEventsHourly.sourceId, row.id),
72618
- gte11(aiReferralEventsHourly.tsHour, since)
72697
+ gte12(aiReferralEventsHourly.tsHour, since)
72619
72698
  )
72620
72699
  ).get();
72621
72700
  const sampleTotals = app.db.select({ total: sql22`COUNT(*)` }).from(rawEventSamples).where(
72622
72701
  and44(
72623
72702
  eq51(rawEventSamples.sourceId, row.id),
72624
- gte11(rawEventSamples.ts, since)
72703
+ gte12(rawEventSamples.ts, since)
72625
72704
  )
72626
72705
  ).get();
72627
72706
  const latestRun = app.db.select().from(runs).where(
@@ -72800,8 +72879,8 @@ async function trafficRoutes(app, opts) {
72800
72879
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
72801
72880
  const crawlerFilters = [
72802
72881
  eq51(crawlerEventsHourly.projectId, project.id),
72803
- gte11(crawlerEventsHourly.tsHour, sinceIso),
72804
- lte11(crawlerEventsHourly.tsHour, untilIso)
72882
+ gte12(crawlerEventsHourly.tsHour, sinceIso),
72883
+ lte12(crawlerEventsHourly.tsHour, untilIso)
72805
72884
  ];
72806
72885
  if (sourceIdParam) crawlerFilters.push(eq51(crawlerEventsHourly.sourceId, sourceIdParam));
72807
72886
  const crawlerWhere = and44(...crawlerFilters);
@@ -72875,8 +72954,8 @@ async function trafficRoutes(app, opts) {
72875
72954
  if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
72876
72955
  const userFetchFilters = [
72877
72956
  eq51(aiUserFetchEventsHourly.projectId, project.id),
72878
- gte11(aiUserFetchEventsHourly.tsHour, sinceIso),
72879
- lte11(aiUserFetchEventsHourly.tsHour, untilIso)
72957
+ gte12(aiUserFetchEventsHourly.tsHour, sinceIso),
72958
+ lte12(aiUserFetchEventsHourly.tsHour, untilIso)
72880
72959
  ];
72881
72960
  if (sourceIdParam) userFetchFilters.push(eq51(aiUserFetchEventsHourly.sourceId, sourceIdParam));
72882
72961
  const userFetchWhere = and44(...userFetchFilters);
@@ -72914,8 +72993,8 @@ async function trafficRoutes(app, opts) {
72914
72993
  if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
72915
72994
  const aiFilters = [
72916
72995
  eq51(aiReferralEventsHourly.projectId, project.id),
72917
- gte11(aiReferralEventsHourly.tsHour, sinceIso),
72918
- lte11(aiReferralEventsHourly.tsHour, untilIso)
72996
+ gte12(aiReferralEventsHourly.tsHour, sinceIso),
72997
+ lte12(aiReferralEventsHourly.tsHour, untilIso)
72919
72998
  ];
72920
72999
  if (sourceIdParam) aiFilters.push(eq51(aiReferralEventsHourly.sourceId, sourceIdParam));
72921
73000
  const aiWhere = and44(...aiFilters);
@@ -74803,7 +74882,7 @@ var RUNTIME_STATE_CHECKS = [
74803
74882
  ];
74804
74883
 
74805
74884
  // ../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";
74885
+ import { and as and47, eq as eq57, gte as gte13, inArray as inArray19, ne as ne7, sql as sql23 } from "drizzle-orm";
74807
74886
  var RECENT_DATA_WARN_DAYS = 7;
74808
74887
  var RECENT_DATA_FAIL_DAYS = 30;
74809
74888
  function isCloudflareDirectPush(source) {
@@ -74952,7 +75031,7 @@ var recentDataCheck = {
74952
75031
  and47(
74953
75032
  eq57(crawlerEventsHourly.projectId, ctx.project.id),
74954
75033
  inArray19(crawlerEventsHourly.sourceId, activeSourceIds),
74955
- gte12(crawlerEventsHourly.tsHour, warnCutoff)
75034
+ gte13(crawlerEventsHourly.tsHour, warnCutoff)
74956
75035
  )
74957
75036
  ).get()?.total ?? 0
74958
75037
  );
@@ -74961,7 +75040,7 @@ var recentDataCheck = {
74961
75040
  and47(
74962
75041
  eq57(aiReferralEventsHourly.projectId, ctx.project.id),
74963
75042
  inArray19(aiReferralEventsHourly.sourceId, activeSourceIds),
74964
- gte12(aiReferralEventsHourly.tsHour, warnCutoff)
75043
+ gte13(aiReferralEventsHourly.tsHour, warnCutoff)
74965
75044
  )
74966
75045
  ).get()?.total ?? 0
74967
75046
  );
@@ -74970,7 +75049,7 @@ var recentDataCheck = {
74970
75049
  and47(
74971
75050
  eq57(aiUserFetchEventsHourly.projectId, ctx.project.id),
74972
75051
  inArray19(aiUserFetchEventsHourly.sourceId, activeSourceIds),
74973
- gte12(aiUserFetchEventsHourly.tsHour, warnCutoff)
75052
+ gte13(aiUserFetchEventsHourly.tsHour, warnCutoff)
74974
75053
  )
74975
75054
  ).get()?.total ?? 0
74976
75055
  );
@@ -74996,7 +75075,7 @@ var recentDataCheck = {
74996
75075
  and47(
74997
75076
  eq57(crawlerEventsHourly.projectId, ctx.project.id),
74998
75077
  inArray19(crawlerEventsHourly.sourceId, activeSourceIds),
74999
- gte12(crawlerEventsHourly.tsHour, failCutoff)
75078
+ gte13(crawlerEventsHourly.tsHour, failCutoff)
75000
75079
  )
75001
75080
  ).get()?.total ?? 0
75002
75081
  );
@@ -75005,7 +75084,7 @@ var recentDataCheck = {
75005
75084
  and47(
75006
75085
  eq57(aiReferralEventsHourly.projectId, ctx.project.id),
75007
75086
  inArray19(aiReferralEventsHourly.sourceId, activeSourceIds),
75008
- gte12(aiReferralEventsHourly.tsHour, failCutoff)
75087
+ gte13(aiReferralEventsHourly.tsHour, failCutoff)
75009
75088
  )
75010
75089
  ).get()?.total ?? 0
75011
75090
  );
@@ -75014,7 +75093,7 @@ var recentDataCheck = {
75014
75093
  and47(
75015
75094
  eq57(aiUserFetchEventsHourly.projectId, ctx.project.id),
75016
75095
  inArray19(aiUserFetchEventsHourly.sourceId, activeSourceIds),
75017
- gte12(aiUserFetchEventsHourly.tsHour, failCutoff)
75096
+ gte13(aiUserFetchEventsHourly.tsHour, failCutoff)
75018
75097
  )
75019
75098
  ).get()?.total ?? 0
75020
75099
  );
@@ -75929,7 +76008,7 @@ async function doctorRoutes(app, opts) {
75929
76008
 
75930
76009
  // ../api-routes/src/discovery/routes.ts
75931
76010
  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";
76011
+ 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
76012
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
75934
76013
  async function discoveryRoutes(app, opts) {
75935
76014
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -75989,7 +76068,7 @@ async function discoveryRoutes(app, opts) {
75989
76068
  DiscoverySessionStatuses.seeding,
75990
76069
  DiscoverySessionStatuses.probing
75991
76070
  ]),
75992
- gte13(discoverySessions.createdAt, ageFloorIso)
76071
+ gte14(discoverySessions.createdAt, ageFloorIso)
75993
76072
  )).orderBy(desc24(discoverySessions.createdAt)).get();
75994
76073
  if (existing && existing.runId) {
75995
76074
  return { reused: true, sessionId: existing.id, runId: existing.runId };
@@ -79333,7 +79412,7 @@ var IntelligenceService = class {
79333
79412
  // Never look forward. The window is this run's own past, so
79334
79413
  // re-analyzing a historical run compares it against its true
79335
79414
  // predecessor rather than against sweeps that came after it.
79336
- lte12(runChronologyKey, currentRunRecord.finishedAt ?? currentRunRecord.createdAt),
79415
+ lte13(runChronologyKey, currentRunRecord.finishedAt ?? currentRunRecord.createdAt),
79337
79416
  // MEASUREMENT: only runs that actually wrote snapshots — see above.
79338
79417
  // Index seek on idx_snapshots_run, short-circuited on the first row.
79339
79418
  exists(
@@ -79423,8 +79502,8 @@ var IntelligenceService = class {
79423
79502
  const selected = this.db.select().from(gbpLocations).where(and51(
79424
79503
  eq62(gbpLocations.projectId, projectId),
79425
79504
  eq62(gbpLocations.selected, true),
79426
- gte14(gbpLocations.syncedAt, windowStart),
79427
- lte12(gbpLocations.syncedAt, windowEnd)
79505
+ gte15(gbpLocations.syncedAt, windowStart),
79506
+ lte13(gbpLocations.syncedAt, windowEnd)
79428
79507
  )).all();
79429
79508
  if (selected.length === 0) {
79430
79509
  log.info("gbp-intelligence.skip", { runId, reason: "no locations synced during run" });