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