@canonry/canonry 4.133.2 → 4.134.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -13
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +25 -0
- package/assets/assets/{AuditHistoryPanel-CVDg3GXg.js → AuditHistoryPanel-DBUWbfNk.js} +1 -1
- package/assets/assets/{BacklinksPage-Dh4RIvS7.js → BacklinksPage-BFm0VdUO.js} +1 -1
- package/assets/assets/{ChartPrimitives-BK89G5H_.js → ChartPrimitives-oV_nduTH.js} +1 -1
- package/assets/assets/{HistoryPage-D8rMu_Js.js → HistoryPage-IF2FMem3.js} +1 -1
- package/assets/assets/{ProjectPage-CiTDveNK.js → ProjectPage-ClwxX9Ga.js} +2 -2
- package/assets/assets/{RunRow-DVOwbmTp.js → RunRow-De5zdch5.js} +1 -1
- package/assets/assets/{RunsPage-tz3Oil7N.js → RunsPage-xtRF78r8.js} +1 -1
- package/assets/assets/{SettingsPage-C698YidM.js → SettingsPage-B_GAxaWu.js} +1 -1
- package/assets/assets/{TrafficPage-XDXWMtey.js → TrafficPage-E8I5ineJ.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-Clz8kOR8.js → TrafficSourceDetailPage-DGk2MqSp.js} +1 -1
- package/assets/assets/{arrow-left-GWLbh0pE.js → arrow-left-D-4QMxi9.js} +1 -1
- package/assets/assets/{extract-error-message-Dk8w-sb1.js → extract-error-message-DuqIFU8Z.js} +1 -1
- package/assets/assets/{index-jTshRRh0.js → index-De1MKbc7.js} +90 -90
- package/assets/assets/{trash-2-DwnzsuH1.js → trash-2-BVh__r6G.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-IEZTETUZ.js → chunk-LIXWDIDR.js} +787 -42
- package/dist/{chunk-ZPQX5ZQ2.js → chunk-PC7AYXND.js} +325 -2
- package/dist/{chunk-V2YR3YK6.js → chunk-TKXWTDV6.js} +73 -7
- package/dist/{chunk-JTJQZ4UC.js → chunk-ZUQPHGEN.js} +73 -2
- package/dist/cli.js +109 -4
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-WZNE23TV.js → intelligence-service-4XOFUN5E.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +10 -10
|
@@ -8,11 +8,20 @@ import {
|
|
|
8
8
|
AdsActivationEntityTypes,
|
|
9
9
|
AdsActivationGrantStates,
|
|
10
10
|
AdsActivationVersionPolicies,
|
|
11
|
+
AdsActivityAssessmentStates,
|
|
11
12
|
AdsAdGroupBillingEventTypes,
|
|
12
13
|
AdsCampaignBiddingTypes,
|
|
14
|
+
AdsDeliveryConfigurationBases,
|
|
15
|
+
AdsDeliverySnapshotIssues,
|
|
16
|
+
AdsDeliverySnapshotStatuses,
|
|
13
17
|
AdsEntityStatuses,
|
|
14
18
|
AdsEntityTypes,
|
|
19
|
+
AdsHistoricalCampaignRollupStatuses,
|
|
20
|
+
AdsInsightLevels,
|
|
15
21
|
AdsIntegrityDecisions,
|
|
22
|
+
AdsLiveDeliveryBases,
|
|
23
|
+
AdsLiveEntityTypes,
|
|
24
|
+
AdsLivePresences,
|
|
16
25
|
AdsOperationKinds,
|
|
17
26
|
AdsOperationStates,
|
|
18
27
|
AdsOperationStepStates,
|
|
@@ -83,12 +92,15 @@ import {
|
|
|
83
92
|
adsConversionPixelListResponseSchema,
|
|
84
93
|
adsCpcMicros,
|
|
85
94
|
adsCtr,
|
|
95
|
+
adsDeliveryDiagnosticsDtoSchema,
|
|
86
96
|
adsDisconnectResponseSchema,
|
|
87
97
|
adsGeoSearchQuerySchema,
|
|
88
98
|
adsGeoSearchResponseSchema,
|
|
89
99
|
adsImageUploadRequestSchema,
|
|
90
100
|
adsInsightLevelSchema,
|
|
91
101
|
adsInsightsResponseSchema,
|
|
102
|
+
adsLiveDeliveryDtoSchema,
|
|
103
|
+
adsLiveDeliveryQuerySchema,
|
|
92
104
|
adsOperationReconcileResponseSchema,
|
|
93
105
|
adsOperationResponseSchema,
|
|
94
106
|
adsOperationStepDtoSchema,
|
|
@@ -176,6 +188,7 @@ import {
|
|
|
176
188
|
discoverySessionDetailDtoSchema,
|
|
177
189
|
discoverySessionDtoSchema,
|
|
178
190
|
doctorReportSchema,
|
|
191
|
+
dollarsToMicros,
|
|
179
192
|
domainClassificationsResponseDtoSchema,
|
|
180
193
|
effectiveBrandNames,
|
|
181
194
|
effectiveDomains,
|
|
@@ -192,6 +205,7 @@ import {
|
|
|
192
205
|
formatDateRange,
|
|
193
206
|
formatDeltaCopy,
|
|
194
207
|
formatIsoDate,
|
|
208
|
+
formatIsoDateInTimeZone,
|
|
195
209
|
formatNumber,
|
|
196
210
|
formatRatio,
|
|
197
211
|
formatWindowCountDelta,
|
|
@@ -235,6 +249,7 @@ import {
|
|
|
235
249
|
internalError,
|
|
236
250
|
isBrowserProvider,
|
|
237
251
|
isReadOnlyKey,
|
|
252
|
+
isoDateDaysBeforeInTimeZone,
|
|
238
253
|
keywordDtoSchema,
|
|
239
254
|
keywordGenerateRequestSchema,
|
|
240
255
|
latestProjectRunDtoSchema,
|
|
@@ -349,10 +364,10 @@ import {
|
|
|
349
364
|
wordpressSchemaDeployResultDtoSchema,
|
|
350
365
|
wordpressSchemaStatusResultDtoSchema,
|
|
351
366
|
wordpressStatusDtoSchema
|
|
352
|
-
} from "./chunk-
|
|
367
|
+
} from "./chunk-PC7AYXND.js";
|
|
353
368
|
|
|
354
369
|
// src/intelligence-service.ts
|
|
355
|
-
import { eq as eq44, desc as
|
|
370
|
+
import { eq as eq44, desc as desc21, asc as asc9, and as and35, ne as ne6, or as or10, inArray as inArray15, gte as gte12, lte as lte8 } from "drizzle-orm";
|
|
356
371
|
|
|
357
372
|
// ../db/src/client.ts
|
|
358
373
|
import { mkdirSync } from "fs";
|
|
@@ -1455,6 +1470,8 @@ var researchRunQueries = sqliteTable("research_run_queries", {
|
|
|
1455
1470
|
groundingSources: text("grounding_sources", { mode: "json" }).$type().notNull().default([]),
|
|
1456
1471
|
citedDomains: text("cited_domains", { mode: "json" }).$type().notNull().default([]),
|
|
1457
1472
|
searchQueries: text("search_queries", { mode: "json" }).$type().notNull().default([]),
|
|
1473
|
+
namedCompetitors: text("named_competitors", { mode: "json" }).$type().notNull().default([]),
|
|
1474
|
+
citedCompetitorDomains: text("cited_competitor_domains", { mode: "json" }).$type().notNull().default([]),
|
|
1458
1475
|
answerMentioned: integer("answer_mentioned", { mode: "boolean" }),
|
|
1459
1476
|
citationState: text("citation_state"),
|
|
1460
1477
|
rawResponse: text("raw_response", { mode: "json" }).$type(),
|
|
@@ -4259,6 +4276,16 @@ var MIGRATION_VERSIONS = [
|
|
|
4259
4276
|
updated_at TEXT NOT NULL
|
|
4260
4277
|
)`
|
|
4261
4278
|
]
|
|
4279
|
+
},
|
|
4280
|
+
{
|
|
4281
|
+
// Keep research batches useful as a competitor-comparison workspace while
|
|
4282
|
+
// retaining their strict isolation from tracked visibility snapshots.
|
|
4283
|
+
version: 110,
|
|
4284
|
+
name: "research-query-named-and-cited-competitors",
|
|
4285
|
+
statements: [
|
|
4286
|
+
`ALTER TABLE research_run_queries ADD COLUMN named_competitors TEXT NOT NULL DEFAULT '[]'`,
|
|
4287
|
+
`ALTER TABLE research_run_queries ADD COLUMN cited_competitor_domains TEXT NOT NULL DEFAULT '[]'`
|
|
4288
|
+
]
|
|
4262
4289
|
}
|
|
4263
4290
|
];
|
|
4264
4291
|
function rebuildBacklinkTableWithSource(tx, table) {
|
|
@@ -17379,11 +17406,13 @@ var SCHEMA_TABLE = {
|
|
|
17379
17406
|
AdsActivationGrantResponse: adsActivationGrantResponseSchema,
|
|
17380
17407
|
AdsCampaignListResponse: adsCampaignListResponseSchema,
|
|
17381
17408
|
AdsConnectionStatusDto: adsConnectionStatusDtoSchema,
|
|
17409
|
+
AdsDeliveryDiagnosticsDto: adsDeliveryDiagnosticsDtoSchema,
|
|
17382
17410
|
AdsConversionEventSettingListResponse: adsConversionEventSettingListResponseSchema,
|
|
17383
17411
|
AdsConversionPixelListResponse: adsConversionPixelListResponseSchema,
|
|
17384
17412
|
AdsDisconnectResponse: adsDisconnectResponseSchema,
|
|
17385
17413
|
AdsGeoSearchResponse: adsGeoSearchResponseSchema,
|
|
17386
17414
|
AdsInsightsResponse: adsInsightsResponseSchema,
|
|
17415
|
+
AdsLiveDeliveryDto: adsLiveDeliveryDtoSchema,
|
|
17387
17416
|
AdsOperationReconcileResponse: adsOperationReconcileResponseSchema,
|
|
17388
17417
|
AdsOperationResponse: adsOperationResponseSchema,
|
|
17389
17418
|
AdsSummaryDto: adsSummaryDtoSchema,
|
|
@@ -20375,6 +20404,47 @@ var routeCatalog = [
|
|
|
20375
20404
|
404: errorResponse("Project not found.")
|
|
20376
20405
|
}
|
|
20377
20406
|
},
|
|
20407
|
+
{
|
|
20408
|
+
method: "get",
|
|
20409
|
+
path: "/api/v1/projects/{name}/ads/delivery-diagnostics",
|
|
20410
|
+
summary: "Stored ads snapshot provenance, configuration facts, and historical campaign activity",
|
|
20411
|
+
description: "Read-only local evidence. It does not call OpenAI and never determines provider eligibility or serving.",
|
|
20412
|
+
tags: ["ads"],
|
|
20413
|
+
parameters: [nameParameter],
|
|
20414
|
+
responses: {
|
|
20415
|
+
200: jsonResponse("Stored diagnostics returned.", "AdsDeliveryDiagnosticsDto"),
|
|
20416
|
+
404: errorResponse("Project not found.")
|
|
20417
|
+
}
|
|
20418
|
+
},
|
|
20419
|
+
{
|
|
20420
|
+
method: "get",
|
|
20421
|
+
path: "/api/v1/projects/{name}/ads/live-delivery",
|
|
20422
|
+
summary: "Live provider read of ads status and metrics, with the local snapshot delta",
|
|
20423
|
+
description: "Calls the OpenAI Ads API right now with the stored connection and returns the provider's current status and metrics unaggregated, alongside the corresponding local snapshot values and an explicit per-entity delta. Dates on both sides are the ad account's local calendar dates. The first day of the window is measured from 00:00 in the account timezone on both sides, so it is a whole day either way and a difference on it is real; the current local day is in progress on both sides, so a difference there is snapshot staleness. Read-only: it never creates, updates, pauses, or activates anything, and it never waits for a sync run. The window counts calendar days in the account timezone, not fixed 24-hour periods, so a daylight-saving transition cannot move the first day. The walk is bounded by per-level caps and a budget of 40 reader calls (see bounds). Admission has two rules: a project may have only one live read running at a time (a second request is refused for as long as the first is actually walking, which can exceed any interval), and consecutive reads are spaced by at least one minute. The interval is counted as soon as the provider is called, so a read that fails upstream still holds it; only a read rejected before the provider was called leaves the next one free. A reader call is not one HTTP request: each list or insight read auto-paginates up to 100 pages, so the worst-case upstream cost of a single request is about 4000 HTTP requests, reported as bounds.maxUpstreamHttpRequests.",
|
|
20424
|
+
tags: ["ads"],
|
|
20425
|
+
parameters: [
|
|
20426
|
+
nameParameter,
|
|
20427
|
+
{
|
|
20428
|
+
name: "campaignId",
|
|
20429
|
+
in: "query",
|
|
20430
|
+
description: "Scope the walk to a single campaign.",
|
|
20431
|
+
schema: stringSchema
|
|
20432
|
+
},
|
|
20433
|
+
{
|
|
20434
|
+
name: "lookbackDays",
|
|
20435
|
+
in: "query",
|
|
20436
|
+
description: "Metrics window in calendar days of the ad account timezone (1-30, default 7).",
|
|
20437
|
+
schema: integerSchema
|
|
20438
|
+
}
|
|
20439
|
+
],
|
|
20440
|
+
responses: {
|
|
20441
|
+
200: jsonResponse("Live provider state and snapshot delta returned.", "AdsLiveDeliveryDto"),
|
|
20442
|
+
400: errorResponse("Invalid query, or no ads connection for this project."),
|
|
20443
|
+
404: errorResponse("Project not found."),
|
|
20444
|
+
429: errorResponse("Another live read for this project is still running, or the minimum interval since the last provider read has not elapsed. error.details.reason says which."),
|
|
20445
|
+
502: errorResponse("The OpenAI Ads API read failed.")
|
|
20446
|
+
}
|
|
20447
|
+
},
|
|
20378
20448
|
{
|
|
20379
20449
|
method: "post",
|
|
20380
20450
|
path: "/api/v1/projects/{name}/bing/connect",
|
|
@@ -26008,7 +26078,7 @@ async function googleRoutes(app, opts) {
|
|
|
26008
26078
|
|
|
26009
26079
|
// ../api-routes/src/ads.ts
|
|
26010
26080
|
import crypto22 from "crypto";
|
|
26011
|
-
import { eq as eq28, and as and22, asc as asc6, gt as gt2, gte as gte8, lt as lt3, lte as lte6, ne as ne3, inArray as inArray12, or as or8, isNull as isNull2, isNotNull as isNotNull2, sql as sql14 } from "drizzle-orm";
|
|
26081
|
+
import { eq as eq28, and as and22, asc as asc6, desc as desc13, gt as gt2, gte as gte8, lt as lt3, lte as lte6, ne as ne3, inArray as inArray12, or as or8, isNull as isNull2, isNotNull as isNotNull2, sql as sql14 } from "drizzle-orm";
|
|
26012
26082
|
|
|
26013
26083
|
// ../api-routes/src/ads-activation-routes.ts
|
|
26014
26084
|
import crypto21 from "crypto";
|
|
@@ -28445,7 +28515,256 @@ function registerAdsActivationRoutes(app, opts) {
|
|
|
28445
28515
|
});
|
|
28446
28516
|
}
|
|
28447
28517
|
|
|
28518
|
+
// ../api-routes/src/ads-live-delivery.ts
|
|
28519
|
+
function liveComparisonWindow(fetchedAtMs, lookbackDays, timezone) {
|
|
28520
|
+
const fetchedAtIso = new Date(fetchedAtMs).toISOString();
|
|
28521
|
+
return {
|
|
28522
|
+
startDate: isoDateDaysBeforeInTimeZone(fetchedAtIso, lookbackDays, timezone),
|
|
28523
|
+
endDate: formatIsoDateInTimeZone(fetchedAtIso, timezone)
|
|
28524
|
+
};
|
|
28525
|
+
}
|
|
28526
|
+
var EMPTY_METRIC_VALUES = {
|
|
28527
|
+
impressions: 0,
|
|
28528
|
+
clicks: 0,
|
|
28529
|
+
spendMicros: 0,
|
|
28530
|
+
conversions: 0
|
|
28531
|
+
};
|
|
28532
|
+
function presenceOf(live, stored) {
|
|
28533
|
+
if (live && stored) return AdsLivePresences.both;
|
|
28534
|
+
if (live) return AdsLivePresences["live-only"];
|
|
28535
|
+
return AdsLivePresences["stored-only"];
|
|
28536
|
+
}
|
|
28537
|
+
function fieldDelta(field, live, stored) {
|
|
28538
|
+
return live === stored ? null : { field, live, stored };
|
|
28539
|
+
}
|
|
28540
|
+
function buildFieldDeltas(live, stored) {
|
|
28541
|
+
if (!live || !stored) return [];
|
|
28542
|
+
const deltas = [
|
|
28543
|
+
fieldDelta("status", live.status, stored.status),
|
|
28544
|
+
fieldDelta("reviewStatus", live.reviewStatus, stored.reviewStatus),
|
|
28545
|
+
fieldDelta("name", live.name, stored.name)
|
|
28546
|
+
];
|
|
28547
|
+
return deltas.filter((delta) => delta !== null);
|
|
28548
|
+
}
|
|
28549
|
+
function foldLiveRows(rows) {
|
|
28550
|
+
let values = EMPTY_METRIC_VALUES;
|
|
28551
|
+
for (const row of rows) {
|
|
28552
|
+
values = {
|
|
28553
|
+
impressions: values.impressions + Math.round(row.impressions ?? 0),
|
|
28554
|
+
clicks: values.clicks + Math.round(row.clicks ?? 0),
|
|
28555
|
+
spendMicros: values.spendMicros + dollarsToMicros(row.spend ?? 0),
|
|
28556
|
+
conversions: values.conversions + Math.round(row.conversions ?? 0)
|
|
28557
|
+
};
|
|
28558
|
+
}
|
|
28559
|
+
return values;
|
|
28560
|
+
}
|
|
28561
|
+
function sameMetrics(a, b) {
|
|
28562
|
+
return a.impressions === b.impressions && a.clicks === b.clicks && a.spendMicros === b.spendMicros && a.conversions === b.conversions;
|
|
28563
|
+
}
|
|
28564
|
+
function buildMetricDeltas(liveRows, storedRows, window) {
|
|
28565
|
+
if (liveRows === null && storedRows === null) return null;
|
|
28566
|
+
const liveByDate = /* @__PURE__ */ new Map();
|
|
28567
|
+
for (const row of liveRows ?? []) {
|
|
28568
|
+
if (row.date === null) continue;
|
|
28569
|
+
const bucket = liveByDate.get(row.date) ?? [];
|
|
28570
|
+
bucket.push(row);
|
|
28571
|
+
liveByDate.set(row.date, bucket);
|
|
28572
|
+
}
|
|
28573
|
+
const storedByDate = /* @__PURE__ */ new Map();
|
|
28574
|
+
for (const row of storedRows ?? []) {
|
|
28575
|
+
if (row.date < window.startDate || row.date > window.endDate) continue;
|
|
28576
|
+
storedByDate.set(row.date, row);
|
|
28577
|
+
}
|
|
28578
|
+
const dates = [.../* @__PURE__ */ new Set([...liveByDate.keys(), ...storedByDate.keys()])].sort();
|
|
28579
|
+
return dates.map((date) => {
|
|
28580
|
+
const liveBucket = liveByDate.get(date);
|
|
28581
|
+
const storedRow = storedByDate.get(date);
|
|
28582
|
+
const live = liveBucket ? foldLiveRows(liveBucket) : null;
|
|
28583
|
+
const stored = storedRow ? {
|
|
28584
|
+
impressions: storedRow.impressions,
|
|
28585
|
+
clicks: storedRow.clicks,
|
|
28586
|
+
spendMicros: storedRow.spendMicros,
|
|
28587
|
+
conversions: storedRow.conversions
|
|
28588
|
+
} : null;
|
|
28589
|
+
const drifted = live === null || stored === null || !sameMetrics(live, stored);
|
|
28590
|
+
return { date, live, stored, drifted };
|
|
28591
|
+
});
|
|
28592
|
+
}
|
|
28593
|
+
function buildLiveEntityComparison(observation, window) {
|
|
28594
|
+
const presence = presenceOf(observation.live, observation.stored);
|
|
28595
|
+
const fieldDeltas = buildFieldDeltas(observation.live, observation.stored);
|
|
28596
|
+
const metricDeltas = buildMetricDeltas(observation.liveMetrics, observation.storedMetrics, window);
|
|
28597
|
+
const drifted = presence !== AdsLivePresences.both || fieldDeltas.length > 0 || (metricDeltas?.some((delta) => delta.drifted) ?? false);
|
|
28598
|
+
return {
|
|
28599
|
+
entityType: observation.entityType,
|
|
28600
|
+
id: observation.id,
|
|
28601
|
+
parentId: observation.parentId,
|
|
28602
|
+
presence,
|
|
28603
|
+
live: observation.live,
|
|
28604
|
+
stored: observation.stored,
|
|
28605
|
+
fieldDeltas,
|
|
28606
|
+
liveMetrics: observation.liveMetrics,
|
|
28607
|
+
metricDeltas,
|
|
28608
|
+
drifted
|
|
28609
|
+
};
|
|
28610
|
+
}
|
|
28611
|
+
function summarizeLiveDrift(entities) {
|
|
28612
|
+
let driftedEntities = 0;
|
|
28613
|
+
let statusDrifted = 0;
|
|
28614
|
+
let metricsDrifted = 0;
|
|
28615
|
+
for (const entity of entities) {
|
|
28616
|
+
if (entity.drifted) driftedEntities += 1;
|
|
28617
|
+
if (entity.fieldDeltas.some((delta) => delta.field === "status")) statusDrifted += 1;
|
|
28618
|
+
if (entity.metricDeltas?.some((delta) => delta.drifted)) metricsDrifted += 1;
|
|
28619
|
+
}
|
|
28620
|
+
return {
|
|
28621
|
+
entitiesCompared: entities.length,
|
|
28622
|
+
driftedEntities,
|
|
28623
|
+
statusDrifted,
|
|
28624
|
+
metricsDrifted
|
|
28625
|
+
};
|
|
28626
|
+
}
|
|
28627
|
+
|
|
28448
28628
|
// ../api-routes/src/ads.ts
|
|
28629
|
+
function runSourceDto(row) {
|
|
28630
|
+
const parsed = runStatusSchema.safeParse(row.status);
|
|
28631
|
+
return parsed.success ? { runId: row.id, status: parsed.data } : null;
|
|
28632
|
+
}
|
|
28633
|
+
function storedSnapshotProvenance(input) {
|
|
28634
|
+
if (!input.connected) {
|
|
28635
|
+
return {
|
|
28636
|
+
status: AdsDeliverySnapshotStatuses.unavailable,
|
|
28637
|
+
issue: AdsDeliverySnapshotIssues.no_ads_connection,
|
|
28638
|
+
sourceSync: null
|
|
28639
|
+
};
|
|
28640
|
+
}
|
|
28641
|
+
if (input.connectionLastSyncedAt === null) {
|
|
28642
|
+
return {
|
|
28643
|
+
status: input.hasEntityRows ? AdsDeliverySnapshotStatuses.partial : AdsDeliverySnapshotStatuses.unavailable,
|
|
28644
|
+
issue: AdsDeliverySnapshotIssues.connection_not_synced,
|
|
28645
|
+
sourceSync: null
|
|
28646
|
+
};
|
|
28647
|
+
}
|
|
28648
|
+
if (!input.hasEntityRows) {
|
|
28649
|
+
if (!input.latestAdsSync) {
|
|
28650
|
+
return {
|
|
28651
|
+
status: AdsDeliverySnapshotStatuses.unavailable,
|
|
28652
|
+
issue: AdsDeliverySnapshotIssues.no_ads_sync,
|
|
28653
|
+
sourceSync: null
|
|
28654
|
+
};
|
|
28655
|
+
}
|
|
28656
|
+
const sourceSync2 = runSourceDto(input.latestAdsSync);
|
|
28657
|
+
if (!sourceSync2 || sourceSync2.status !== RunStatuses.completed) {
|
|
28658
|
+
return {
|
|
28659
|
+
status: AdsDeliverySnapshotStatuses.partial,
|
|
28660
|
+
issue: AdsDeliverySnapshotIssues.source_sync_not_completed,
|
|
28661
|
+
sourceSync: sourceSync2
|
|
28662
|
+
};
|
|
28663
|
+
}
|
|
28664
|
+
return {
|
|
28665
|
+
status: AdsDeliverySnapshotStatuses.complete,
|
|
28666
|
+
issue: null,
|
|
28667
|
+
sourceSync: sourceSync2
|
|
28668
|
+
};
|
|
28669
|
+
}
|
|
28670
|
+
if (input.hasEntityWithoutSyncRunId) {
|
|
28671
|
+
return {
|
|
28672
|
+
status: AdsDeliverySnapshotStatuses.partial,
|
|
28673
|
+
issue: AdsDeliverySnapshotIssues.entity_rows_missing_sync_run_id,
|
|
28674
|
+
sourceSync: null
|
|
28675
|
+
};
|
|
28676
|
+
}
|
|
28677
|
+
if (input.entitySyncRunIds.size !== 1) {
|
|
28678
|
+
return {
|
|
28679
|
+
status: AdsDeliverySnapshotStatuses.partial,
|
|
28680
|
+
issue: AdsDeliverySnapshotIssues.entity_rows_span_multiple_sync_runs,
|
|
28681
|
+
sourceSync: null
|
|
28682
|
+
};
|
|
28683
|
+
}
|
|
28684
|
+
if (!input.sourceSync) {
|
|
28685
|
+
return {
|
|
28686
|
+
status: AdsDeliverySnapshotStatuses.partial,
|
|
28687
|
+
issue: AdsDeliverySnapshotIssues.source_sync_missing,
|
|
28688
|
+
sourceSync: null
|
|
28689
|
+
};
|
|
28690
|
+
}
|
|
28691
|
+
const sourceSync = runSourceDto(input.sourceSync);
|
|
28692
|
+
if (input.sourceSync.kind !== RunKinds["ads-sync"]) {
|
|
28693
|
+
return {
|
|
28694
|
+
status: AdsDeliverySnapshotStatuses.partial,
|
|
28695
|
+
issue: AdsDeliverySnapshotIssues.source_sync_not_ads_sync,
|
|
28696
|
+
sourceSync
|
|
28697
|
+
};
|
|
28698
|
+
}
|
|
28699
|
+
if (!sourceSync || sourceSync.status !== RunStatuses.completed) {
|
|
28700
|
+
return {
|
|
28701
|
+
status: AdsDeliverySnapshotStatuses.partial,
|
|
28702
|
+
issue: AdsDeliverySnapshotIssues.source_sync_not_completed,
|
|
28703
|
+
sourceSync
|
|
28704
|
+
};
|
|
28705
|
+
}
|
|
28706
|
+
return {
|
|
28707
|
+
status: AdsDeliverySnapshotStatuses.complete,
|
|
28708
|
+
issue: null,
|
|
28709
|
+
sourceSync
|
|
28710
|
+
};
|
|
28711
|
+
}
|
|
28712
|
+
function historicalCampaignRollups(rows) {
|
|
28713
|
+
if (rows.length === 0) {
|
|
28714
|
+
return {
|
|
28715
|
+
status: AdsHistoricalCampaignRollupStatuses.unavailable,
|
|
28716
|
+
window: { from: null, to: null },
|
|
28717
|
+
totals: null
|
|
28718
|
+
};
|
|
28719
|
+
}
|
|
28720
|
+
let impressions = 0;
|
|
28721
|
+
let clicks = 0;
|
|
28722
|
+
let spendMicros = 0;
|
|
28723
|
+
let conversions = 0;
|
|
28724
|
+
let fromDate = null;
|
|
28725
|
+
let toDate = null;
|
|
28726
|
+
for (const row of rows) {
|
|
28727
|
+
impressions += row.impressions;
|
|
28728
|
+
clicks += row.clicks;
|
|
28729
|
+
spendMicros += row.spendMicros;
|
|
28730
|
+
conversions += row.conversions;
|
|
28731
|
+
if (fromDate === null || row.date < fromDate) fromDate = row.date;
|
|
28732
|
+
if (toDate === null || row.date > toDate) toDate = row.date;
|
|
28733
|
+
}
|
|
28734
|
+
return {
|
|
28735
|
+
status: AdsHistoricalCampaignRollupStatuses.reported,
|
|
28736
|
+
window: { from: fromDate, to: toDate },
|
|
28737
|
+
totals: {
|
|
28738
|
+
impressions,
|
|
28739
|
+
clicks,
|
|
28740
|
+
spendMicros,
|
|
28741
|
+
conversions,
|
|
28742
|
+
ctr: adsCtr(clicks, impressions),
|
|
28743
|
+
cpcMicros: adsCpcMicros(spendMicros, clicks)
|
|
28744
|
+
}
|
|
28745
|
+
};
|
|
28746
|
+
}
|
|
28747
|
+
function storedActivityAssessment(snapshotStatus, rollupStatus, rollupImpressions) {
|
|
28748
|
+
switch (snapshotStatus) {
|
|
28749
|
+
case AdsDeliverySnapshotStatuses.unavailable:
|
|
28750
|
+
return AdsActivityAssessmentStates.unavailable;
|
|
28751
|
+
case AdsDeliverySnapshotStatuses.partial:
|
|
28752
|
+
return AdsActivityAssessmentStates.partial_snapshot;
|
|
28753
|
+
case AdsDeliverySnapshotStatuses.complete:
|
|
28754
|
+
switch (rollupStatus) {
|
|
28755
|
+
case AdsHistoricalCampaignRollupStatuses.unavailable:
|
|
28756
|
+
return AdsActivityAssessmentStates.metrics_unavailable;
|
|
28757
|
+
case AdsHistoricalCampaignRollupStatuses.reported:
|
|
28758
|
+
return (rollupImpressions ?? 0) > 0 ? AdsActivityAssessmentStates.observed_activity : AdsActivityAssessmentStates.no_observed_activity;
|
|
28759
|
+
}
|
|
28760
|
+
}
|
|
28761
|
+
}
|
|
28762
|
+
var ADS_LIVE_MAX_CAMPAIGNS = 5;
|
|
28763
|
+
var ADS_LIVE_MAX_AD_GROUPS_PER_CAMPAIGN = 10;
|
|
28764
|
+
var ADS_LIVE_MAX_ADS_PER_AD_GROUP = 20;
|
|
28765
|
+
var ADS_LIVE_MAX_READER_CALLS = 40;
|
|
28766
|
+
var DEFAULT_ADS_LIVE_MAX_PAGES_PER_READER_CALL = 100;
|
|
28767
|
+
var DEFAULT_ADS_LIVE_DELIVERY_MIN_INTERVAL_MS = 6e4;
|
|
28449
28768
|
var DEFAULT_RECONCILE_SWEEP_INTERVAL_MS = 6e4;
|
|
28450
28769
|
var DEFAULT_RECONCILE_PENDING_STALE_MS = 5 * 6e4;
|
|
28451
28770
|
var DEFAULT_RECONCILE_BACKOFF_BASE_MS = 5 * 6e4;
|
|
@@ -28808,6 +29127,47 @@ async function executeAdsRead(surface, read) {
|
|
|
28808
29127
|
});
|
|
28809
29128
|
}
|
|
28810
29129
|
}
|
|
29130
|
+
function resolveAdsLiveDeliveryReader(opts, projectName) {
|
|
29131
|
+
const apiKey = opts.adsCredentialStore?.getConnection(projectName)?.apiKey;
|
|
29132
|
+
if (!apiKey) {
|
|
29133
|
+
throw validationError("No OpenAI Ads API key configured for this project");
|
|
29134
|
+
}
|
|
29135
|
+
if (!opts.adsLiveDeliveryReader) {
|
|
29136
|
+
throw validationError("Ads live delivery reads are not configured for this deployment");
|
|
29137
|
+
}
|
|
29138
|
+
return { apiKey, reader: opts.adsLiveDeliveryReader };
|
|
29139
|
+
}
|
|
29140
|
+
function upstreamStatusOf(error) {
|
|
29141
|
+
return typeof error === "object" && error !== null && "status" in error && typeof error.status === "number" && Number.isInteger(error.status) ? error.status : null;
|
|
29142
|
+
}
|
|
29143
|
+
function liveReadFailure(surface, entityId, error) {
|
|
29144
|
+
return { surface, entityId, upstreamStatus: upstreamStatusOf(error) };
|
|
29145
|
+
}
|
|
29146
|
+
function liveReadProviderError(surface, error) {
|
|
29147
|
+
const upstreamStatus = upstreamStatusOf(error);
|
|
29148
|
+
return providerError(
|
|
29149
|
+
`OpenAI Ads API ${surface} read failed`,
|
|
29150
|
+
upstreamStatus === null ? void 0 : { upstreamStatus }
|
|
29151
|
+
);
|
|
29152
|
+
}
|
|
29153
|
+
function liveEntityState(entity) {
|
|
29154
|
+
return {
|
|
29155
|
+
name: entity.name,
|
|
29156
|
+
status: entity.status,
|
|
29157
|
+
reviewStatus: entity.reviewStatus,
|
|
29158
|
+
mode: entity.mode,
|
|
29159
|
+
updatedAt: entity.updatedAt
|
|
29160
|
+
};
|
|
29161
|
+
}
|
|
29162
|
+
function storedEntityState(row) {
|
|
29163
|
+
return {
|
|
29164
|
+
name: row.name,
|
|
29165
|
+
status: row.status,
|
|
29166
|
+
reviewStatus: row.reviewStatus ?? null,
|
|
29167
|
+
upstreamUpdatedAt: row.upstreamUpdatedAt,
|
|
29168
|
+
syncedAt: row.syncedAt
|
|
29169
|
+
};
|
|
29170
|
+
}
|
|
28811
29171
|
async function executeAdsOperation(app, request, input) {
|
|
28812
29172
|
const hash = requestHash({ kind: input.kind, entityType: input.entityType, payload: input.payload });
|
|
28813
29173
|
const existing = readOperationByKey(app, input.projectId, input.operationKey);
|
|
@@ -29446,6 +29806,21 @@ async function adsRoutes(app, opts) {
|
|
|
29446
29806
|
opts.adsAccountVerificationCacheTtlMs ?? DEFAULT_ADS_ACCOUNT_VERIFICATION_CACHE_TTL_MS
|
|
29447
29807
|
);
|
|
29448
29808
|
const verificationCache = /* @__PURE__ */ new Map();
|
|
29809
|
+
const liveDeliveryMinIntervalMs = Math.max(
|
|
29810
|
+
0,
|
|
29811
|
+
opts.adsLiveDeliveryMinIntervalMs ?? DEFAULT_ADS_LIVE_DELIVERY_MIN_INTERVAL_MS
|
|
29812
|
+
);
|
|
29813
|
+
const liveDeliveryMaxPagesPerReaderCall = Math.max(
|
|
29814
|
+
1,
|
|
29815
|
+
opts.adsLiveDeliveryMaxPagesPerReaderCall ?? DEFAULT_ADS_LIVE_MAX_PAGES_PER_READER_CALL
|
|
29816
|
+
);
|
|
29817
|
+
const liveDeliveryInFlight = /* @__PURE__ */ new Set();
|
|
29818
|
+
const liveDeliveryLastAttemptAtMs = /* @__PURE__ */ new Map();
|
|
29819
|
+
const pruneLiveDeliveryAttempts = (nowMs) => {
|
|
29820
|
+
for (const [projectId, lastAtMs] of liveDeliveryLastAttemptAtMs) {
|
|
29821
|
+
if (nowMs - lastAtMs >= liveDeliveryMinIntervalMs) liveDeliveryLastAttemptAtMs.delete(projectId);
|
|
29822
|
+
}
|
|
29823
|
+
};
|
|
29449
29824
|
const reconcilePolicy = {
|
|
29450
29825
|
pendingMinIdleMs: pendingStaleMs,
|
|
29451
29826
|
backoffBaseMs,
|
|
@@ -29571,6 +29946,10 @@ async function adsRoutes(app, opts) {
|
|
|
29571
29946
|
reviewStatus: account.reviewStatus,
|
|
29572
29947
|
integrityReviewStatus: account.integrityReviewStatus,
|
|
29573
29948
|
integrityDecision: account.integrityDecision,
|
|
29949
|
+
// A credential rotation for the same account preserves its
|
|
29950
|
+
// snapshot watermark. A different account invalidates all stored
|
|
29951
|
+
// entity/insight provenance until the new binding is synced.
|
|
29952
|
+
lastSyncedAt: existingRow.adAccountId === account.id ? existingRow.lastSyncedAt : null,
|
|
29574
29953
|
updatedAt: now
|
|
29575
29954
|
}).where(eq28(adsConnections.id, existingRow.id)).run();
|
|
29576
29955
|
} else {
|
|
@@ -30126,6 +30505,359 @@ async function adsRoutes(app, opts) {
|
|
|
30126
30505
|
const response = { rows: dtoRows, currencyCode: conn?.currencyCode ?? null };
|
|
30127
30506
|
return response;
|
|
30128
30507
|
});
|
|
30508
|
+
app.get("/projects/:name/ads/delivery-diagnostics", async (request) => {
|
|
30509
|
+
const project = resolveProject(app.db, request.params.name);
|
|
30510
|
+
const connection = app.db.select().from(adsConnections).where(eq28(adsConnections.projectId, project.id)).get();
|
|
30511
|
+
const campaignRows = app.db.select().from(adsCampaigns).where(eq28(adsCampaigns.projectId, project.id)).all();
|
|
30512
|
+
const adGroupRows = app.db.select().from(adsAdGroups).where(eq28(adsAdGroups.projectId, project.id)).all();
|
|
30513
|
+
const adRows = app.db.select().from(adsAds).where(eq28(adsAds.projectId, project.id)).all();
|
|
30514
|
+
const entityRows = [...campaignRows, ...adGroupRows, ...adRows];
|
|
30515
|
+
const hasEntityWithoutSyncRunId = entityRows.some((row) => row.syncRunId === null);
|
|
30516
|
+
const entitySyncRunIds = new Set(entityRows.flatMap((row) => row.syncRunId === null ? [] : [row.syncRunId]));
|
|
30517
|
+
const sourceSync = entitySyncRunIds.size === 1 ? app.db.select({ id: runs.id, kind: runs.kind, status: runs.status }).from(runs).where(and22(
|
|
30518
|
+
eq28(runs.id, [...entitySyncRunIds][0]),
|
|
30519
|
+
eq28(runs.projectId, project.id)
|
|
30520
|
+
)).get() : void 0;
|
|
30521
|
+
const latestAdsSync = app.db.select({ id: runs.id, kind: runs.kind, status: runs.status }).from(runs).where(and22(
|
|
30522
|
+
eq28(runs.projectId, project.id),
|
|
30523
|
+
eq28(runs.kind, RunKinds["ads-sync"])
|
|
30524
|
+
)).orderBy(desc13(runs.createdAt)).get();
|
|
30525
|
+
const snapshot = storedSnapshotProvenance({
|
|
30526
|
+
connected: Boolean(connection),
|
|
30527
|
+
connectionLastSyncedAt: connection?.lastSyncedAt ?? null,
|
|
30528
|
+
hasEntityRows: entityRows.length > 0,
|
|
30529
|
+
hasEntityWithoutSyncRunId,
|
|
30530
|
+
entitySyncRunIds,
|
|
30531
|
+
sourceSync,
|
|
30532
|
+
latestAdsSync
|
|
30533
|
+
});
|
|
30534
|
+
const rollups = historicalCampaignRollups(app.db.select({
|
|
30535
|
+
date: adsInsightsDaily.date,
|
|
30536
|
+
impressions: adsInsightsDaily.impressions,
|
|
30537
|
+
clicks: adsInsightsDaily.clicks,
|
|
30538
|
+
spendMicros: adsInsightsDaily.spendMicros,
|
|
30539
|
+
conversions: adsInsightsDaily.conversions
|
|
30540
|
+
}).from(adsInsightsDaily).where(and22(
|
|
30541
|
+
eq28(adsInsightsDaily.projectId, project.id),
|
|
30542
|
+
eq28(adsInsightsDaily.level, AdsInsightLevels.campaign)
|
|
30543
|
+
)).all());
|
|
30544
|
+
const adsByGroup = /* @__PURE__ */ new Map();
|
|
30545
|
+
for (const ad of adRows) {
|
|
30546
|
+
const list = adsByGroup.get(ad.adGroupId) ?? [];
|
|
30547
|
+
list.push({ id: ad.id, name: ad.name, status: ad.status, reviewStatus: ad.reviewStatus });
|
|
30548
|
+
adsByGroup.set(ad.adGroupId, list);
|
|
30549
|
+
}
|
|
30550
|
+
const groupsByCampaign = /* @__PURE__ */ new Map();
|
|
30551
|
+
for (const adGroup of adGroupRows) {
|
|
30552
|
+
const list = groupsByCampaign.get(adGroup.campaignId) ?? [];
|
|
30553
|
+
list.push({
|
|
30554
|
+
id: adGroup.id,
|
|
30555
|
+
name: adGroup.name,
|
|
30556
|
+
status: adGroup.status,
|
|
30557
|
+
billingEventType: adGroupBillingEventTypeDto(adGroup.billingEventType),
|
|
30558
|
+
maxBidMicros: adGroup.maxBidMicros,
|
|
30559
|
+
contextHints: adGroup.contextHints,
|
|
30560
|
+
ads: adsByGroup.get(adGroup.id) ?? []
|
|
30561
|
+
});
|
|
30562
|
+
groupsByCampaign.set(adGroup.campaignId, list);
|
|
30563
|
+
}
|
|
30564
|
+
const response = {
|
|
30565
|
+
snapshot: {
|
|
30566
|
+
...snapshot,
|
|
30567
|
+
lastSyncedAt: connection?.lastSyncedAt ?? null,
|
|
30568
|
+
campaignCount: campaignRows.length,
|
|
30569
|
+
adGroupCount: adGroupRows.length,
|
|
30570
|
+
adCount: adRows.length
|
|
30571
|
+
},
|
|
30572
|
+
historicalCampaignRollups: rollups,
|
|
30573
|
+
storedConfiguration: {
|
|
30574
|
+
basis: AdsDeliveryConfigurationBases.storedSnapshot,
|
|
30575
|
+
connection: connection === void 0 ? null : {
|
|
30576
|
+
status: connection.status,
|
|
30577
|
+
reviewStatus: connection.reviewStatus,
|
|
30578
|
+
integrityReviewStatus: connection.integrityReviewStatus,
|
|
30579
|
+
integrityDecision: connection.integrityDecision,
|
|
30580
|
+
conversionTrackingConfigured: connection.conversionTrackingConfigured
|
|
30581
|
+
},
|
|
30582
|
+
campaigns: campaignRows.map((campaign) => ({
|
|
30583
|
+
id: campaign.id,
|
|
30584
|
+
name: campaign.name,
|
|
30585
|
+
status: campaign.status,
|
|
30586
|
+
biddingType: campaignBiddingTypeDto(campaign.biddingType),
|
|
30587
|
+
dailySpendLimitMicros: campaign.dailySpendLimitMicros,
|
|
30588
|
+
lifetimeSpendLimitMicros: campaign.lifetimeSpendLimitMicros,
|
|
30589
|
+
conversionEventSettingIds: campaign.conversionEventSettingIds,
|
|
30590
|
+
adGroups: groupsByCampaign.get(campaign.id) ?? []
|
|
30591
|
+
}))
|
|
30592
|
+
},
|
|
30593
|
+
assessment: {
|
|
30594
|
+
state: storedActivityAssessment(
|
|
30595
|
+
snapshot.status,
|
|
30596
|
+
rollups.status,
|
|
30597
|
+
rollups.totals === null ? null : rollups.totals.impressions
|
|
30598
|
+
)
|
|
30599
|
+
}
|
|
30600
|
+
};
|
|
30601
|
+
return response;
|
|
30602
|
+
});
|
|
30603
|
+
app.get("/projects/:name/ads/live-delivery", async (request) => {
|
|
30604
|
+
const project = resolveProject(app.db, request.params.name);
|
|
30605
|
+
const parsed = adsLiveDeliveryQuerySchema.safeParse(request.query);
|
|
30606
|
+
if (!parsed.success) {
|
|
30607
|
+
throw validationError("Invalid ads live delivery query", { issues: parsed.error.issues });
|
|
30608
|
+
}
|
|
30609
|
+
const { campaignId, lookbackDays } = parsed.data;
|
|
30610
|
+
const connection = app.db.select().from(adsConnections).where(eq28(adsConnections.projectId, project.id)).get();
|
|
30611
|
+
if (!connection) {
|
|
30612
|
+
throw validationError('No ads connection for this project. Run "canonry ads connect" first.');
|
|
30613
|
+
}
|
|
30614
|
+
const { apiKey, reader } = resolveAdsLiveDeliveryReader(opts, project.name);
|
|
30615
|
+
const verifyAdsAccount = opts.verifyAdsAccount;
|
|
30616
|
+
if (!verifyAdsAccount) {
|
|
30617
|
+
throw validationError("Ads account verification is not configured for this deployment");
|
|
30618
|
+
}
|
|
30619
|
+
const fetchedAtMs = Date.now();
|
|
30620
|
+
const lastAttemptAtMs = liveDeliveryLastAttemptAtMs.get(project.id);
|
|
30621
|
+
const remainingIntervalMs = liveDeliveryMinIntervalMs > 0 && lastAttemptAtMs !== void 0 ? lastAttemptAtMs + liveDeliveryMinIntervalMs - fetchedAtMs : 0;
|
|
30622
|
+
if (liveDeliveryInFlight.has(project.id)) {
|
|
30623
|
+
throw quotaExceeded("OpenAI Ads live delivery reads", {
|
|
30624
|
+
reason: "read-in-flight",
|
|
30625
|
+
minIntervalMs: liveDeliveryMinIntervalMs,
|
|
30626
|
+
// A LOWER bound, and only when the interval itself still blocks. How
|
|
30627
|
+
// much longer the running walk needs is unknowable from here, so once
|
|
30628
|
+
// it has outlived the interval there is no honest number to give and
|
|
30629
|
+
// the field is absent rather than guessed.
|
|
30630
|
+
...remainingIntervalMs > 0 ? { retryAfterMs: remainingIntervalMs } : {}
|
|
30631
|
+
});
|
|
30632
|
+
}
|
|
30633
|
+
if (remainingIntervalMs > 0) {
|
|
30634
|
+
throw quotaExceeded("OpenAI Ads live delivery reads", {
|
|
30635
|
+
reason: "min-interval",
|
|
30636
|
+
minIntervalMs: liveDeliveryMinIntervalMs,
|
|
30637
|
+
retryAfterMs: remainingIntervalMs
|
|
30638
|
+
});
|
|
30639
|
+
}
|
|
30640
|
+
liveDeliveryInFlight.add(project.id);
|
|
30641
|
+
const spendLiveDeliveryAttempt = () => {
|
|
30642
|
+
if (liveDeliveryMinIntervalMs <= 0) return;
|
|
30643
|
+
pruneLiveDeliveryAttempts(fetchedAtMs);
|
|
30644
|
+
liveDeliveryLastAttemptAtMs.set(project.id, fetchedAtMs);
|
|
30645
|
+
};
|
|
30646
|
+
try {
|
|
30647
|
+
let readerCalls = 0;
|
|
30648
|
+
let truncated = false;
|
|
30649
|
+
const errors = [];
|
|
30650
|
+
const callProvider = async (surface, entityId, run) => {
|
|
30651
|
+
if (readerCalls >= ADS_LIVE_MAX_READER_CALLS) {
|
|
30652
|
+
truncated = true;
|
|
30653
|
+
return null;
|
|
30654
|
+
}
|
|
30655
|
+
readerCalls += 1;
|
|
30656
|
+
try {
|
|
30657
|
+
return await run();
|
|
30658
|
+
} catch (error) {
|
|
30659
|
+
errors.push(liveReadFailure(surface, entityId, error));
|
|
30660
|
+
return null;
|
|
30661
|
+
}
|
|
30662
|
+
};
|
|
30663
|
+
readerCalls += 1;
|
|
30664
|
+
let account;
|
|
30665
|
+
spendLiveDeliveryAttempt();
|
|
30666
|
+
try {
|
|
30667
|
+
account = await verifyAdsAccount(apiKey);
|
|
30668
|
+
} catch (error) {
|
|
30669
|
+
throw liveReadProviderError("account identity", error);
|
|
30670
|
+
}
|
|
30671
|
+
if (account.id !== connection.adAccountId) {
|
|
30672
|
+
throw validationError("The configured key belongs to a different OpenAI ad account; reconnect this project");
|
|
30673
|
+
}
|
|
30674
|
+
readerCalls += 1;
|
|
30675
|
+
let liveCampaigns;
|
|
30676
|
+
try {
|
|
30677
|
+
liveCampaigns = await reader.listCampaigns(apiKey);
|
|
30678
|
+
} catch (error) {
|
|
30679
|
+
throw liveReadProviderError("campaign list", error);
|
|
30680
|
+
}
|
|
30681
|
+
const scopedCampaigns = campaignId === void 0 ? liveCampaigns : liveCampaigns.filter((campaign) => campaign.id === campaignId);
|
|
30682
|
+
if (scopedCampaigns.length > ADS_LIVE_MAX_CAMPAIGNS) truncated = true;
|
|
30683
|
+
const walkedCampaigns = scopedCampaigns.slice(0, ADS_LIVE_MAX_CAMPAIGNS);
|
|
30684
|
+
const storedCampaignRows = app.db.select().from(adsCampaigns).where(and22(
|
|
30685
|
+
eq28(adsCampaigns.projectId, project.id),
|
|
30686
|
+
campaignId === void 0 ? void 0 : eq28(adsCampaigns.id, campaignId)
|
|
30687
|
+
)).all();
|
|
30688
|
+
const storedAdGroupRows = app.db.select().from(adsAdGroups).where(and22(
|
|
30689
|
+
eq28(adsAdGroups.projectId, project.id),
|
|
30690
|
+
campaignId === void 0 ? void 0 : eq28(adsAdGroups.campaignId, campaignId)
|
|
30691
|
+
)).all();
|
|
30692
|
+
const storedAdGroupIds = new Set(storedAdGroupRows.map((row) => row.id));
|
|
30693
|
+
const storedAdRows = app.db.select().from(adsAds).where(eq28(adsAds.projectId, project.id)).all().filter((row) => campaignId === void 0 || storedAdGroupIds.has(row.adGroupId));
|
|
30694
|
+
const storedInsightRows = app.db.select({
|
|
30695
|
+
level: adsInsightsDaily.level,
|
|
30696
|
+
entityId: adsInsightsDaily.entityId,
|
|
30697
|
+
date: adsInsightsDaily.date,
|
|
30698
|
+
impressions: adsInsightsDaily.impressions,
|
|
30699
|
+
clicks: adsInsightsDaily.clicks,
|
|
30700
|
+
spendMicros: adsInsightsDaily.spendMicros,
|
|
30701
|
+
conversions: adsInsightsDaily.conversions
|
|
30702
|
+
}).from(adsInsightsDaily).where(eq28(adsInsightsDaily.projectId, project.id)).all();
|
|
30703
|
+
const storedCampaignById = new Map(storedCampaignRows.map((row) => [row.id, row]));
|
|
30704
|
+
const storedAdGroupById = new Map(storedAdGroupRows.map((row) => [row.id, row]));
|
|
30705
|
+
const storedAdById = new Map(storedAdRows.map((row) => [row.id, row]));
|
|
30706
|
+
const storedMetricsByEntity = /* @__PURE__ */ new Map();
|
|
30707
|
+
for (const row of storedInsightRows) {
|
|
30708
|
+
const key = `${row.level}:${row.entityId}`;
|
|
30709
|
+
const bucket = storedMetricsByEntity.get(key) ?? [];
|
|
30710
|
+
bucket.push({
|
|
30711
|
+
date: row.date,
|
|
30712
|
+
impressions: row.impressions,
|
|
30713
|
+
clicks: row.clicks,
|
|
30714
|
+
spendMicros: row.spendMicros,
|
|
30715
|
+
conversions: row.conversions
|
|
30716
|
+
});
|
|
30717
|
+
storedMetricsByEntity.set(key, bucket);
|
|
30718
|
+
}
|
|
30719
|
+
const storedMetricsFor = (level, entityId) => storedMetricsByEntity.get(`${level}:${entityId}`) ?? [];
|
|
30720
|
+
const accountTimezone = account.timezone ?? "UTC";
|
|
30721
|
+
const window = liveComparisonWindow(fetchedAtMs, lookbackDays, accountTimezone);
|
|
30722
|
+
const insightsRequest = {
|
|
30723
|
+
startDate: window.startDate,
|
|
30724
|
+
fetchedAtMs,
|
|
30725
|
+
timezone: accountTimezone
|
|
30726
|
+
};
|
|
30727
|
+
const observations = [];
|
|
30728
|
+
const walkedCampaignIds = /* @__PURE__ */ new Set();
|
|
30729
|
+
const walkedAdGroupIds = /* @__PURE__ */ new Set();
|
|
30730
|
+
const walkedAdIds = /* @__PURE__ */ new Set();
|
|
30731
|
+
for (const campaign of walkedCampaigns) {
|
|
30732
|
+
walkedCampaignIds.add(campaign.id);
|
|
30733
|
+
const campaignMetrics = await callProvider(
|
|
30734
|
+
"campaign insights",
|
|
30735
|
+
campaign.id,
|
|
30736
|
+
() => reader.campaignInsights(apiKey, campaign.id, insightsRequest)
|
|
30737
|
+
);
|
|
30738
|
+
observations.push({
|
|
30739
|
+
entityType: AdsLiveEntityTypes.campaign,
|
|
30740
|
+
id: campaign.id,
|
|
30741
|
+
parentId: null,
|
|
30742
|
+
live: liveEntityState(campaign),
|
|
30743
|
+
stored: storedCampaignById.has(campaign.id) ? storedEntityState(storedCampaignById.get(campaign.id)) : null,
|
|
30744
|
+
// A metrics call that did not happen is not evidence about metrics, so
|
|
30745
|
+
// both sides stay null and no delta is claimed.
|
|
30746
|
+
liveMetrics: campaignMetrics,
|
|
30747
|
+
storedMetrics: campaignMetrics === null ? null : storedMetricsFor(AdsInsightLevels.campaign, campaign.id)
|
|
30748
|
+
});
|
|
30749
|
+
const liveAdGroups = await callProvider(
|
|
30750
|
+
"ad group list",
|
|
30751
|
+
campaign.id,
|
|
30752
|
+
() => reader.listAdGroups(apiKey, campaign.id)
|
|
30753
|
+
);
|
|
30754
|
+
if (liveAdGroups === null) continue;
|
|
30755
|
+
if (liveAdGroups.length > ADS_LIVE_MAX_AD_GROUPS_PER_CAMPAIGN) truncated = true;
|
|
30756
|
+
for (const adGroup of liveAdGroups.slice(0, ADS_LIVE_MAX_AD_GROUPS_PER_CAMPAIGN)) {
|
|
30757
|
+
walkedAdGroupIds.add(adGroup.id);
|
|
30758
|
+
const adGroupMetrics = await callProvider(
|
|
30759
|
+
"ad group insights",
|
|
30760
|
+
adGroup.id,
|
|
30761
|
+
() => reader.adGroupInsights(apiKey, adGroup.id, insightsRequest)
|
|
30762
|
+
);
|
|
30763
|
+
observations.push({
|
|
30764
|
+
entityType: AdsLiveEntityTypes.ad_group,
|
|
30765
|
+
id: adGroup.id,
|
|
30766
|
+
parentId: campaign.id,
|
|
30767
|
+
live: liveEntityState(adGroup),
|
|
30768
|
+
stored: storedAdGroupById.has(adGroup.id) ? storedEntityState(storedAdGroupById.get(adGroup.id)) : null,
|
|
30769
|
+
liveMetrics: adGroupMetrics,
|
|
30770
|
+
storedMetrics: adGroupMetrics === null ? null : storedMetricsFor(AdsInsightLevels.ad_group, adGroup.id)
|
|
30771
|
+
});
|
|
30772
|
+
const liveAds = await callProvider(
|
|
30773
|
+
"ad list",
|
|
30774
|
+
adGroup.id,
|
|
30775
|
+
() => reader.listAds(apiKey, adGroup.id)
|
|
30776
|
+
);
|
|
30777
|
+
if (liveAds === null) continue;
|
|
30778
|
+
if (liveAds.length > ADS_LIVE_MAX_ADS_PER_AD_GROUP) truncated = true;
|
|
30779
|
+
for (const ad of liveAds.slice(0, ADS_LIVE_MAX_ADS_PER_AD_GROUP)) {
|
|
30780
|
+
walkedAdIds.add(ad.id);
|
|
30781
|
+
observations.push({
|
|
30782
|
+
entityType: AdsLiveEntityTypes.ad,
|
|
30783
|
+
id: ad.id,
|
|
30784
|
+
parentId: adGroup.id,
|
|
30785
|
+
live: liveEntityState(ad),
|
|
30786
|
+
stored: storedAdById.has(ad.id) ? storedEntityState(storedAdById.get(ad.id)) : null,
|
|
30787
|
+
// The provider has no per-ad insights surface (same reason ads-sync
|
|
30788
|
+
// stores no ad-level rollups), so ads carry status evidence only.
|
|
30789
|
+
liveMetrics: null,
|
|
30790
|
+
storedMetrics: null
|
|
30791
|
+
});
|
|
30792
|
+
}
|
|
30793
|
+
}
|
|
30794
|
+
}
|
|
30795
|
+
if (!truncated && errors.length === 0) {
|
|
30796
|
+
for (const row of storedCampaignRows) {
|
|
30797
|
+
if (walkedCampaignIds.has(row.id)) continue;
|
|
30798
|
+
observations.push({
|
|
30799
|
+
entityType: AdsLiveEntityTypes.campaign,
|
|
30800
|
+
id: row.id,
|
|
30801
|
+
parentId: null,
|
|
30802
|
+
live: null,
|
|
30803
|
+
stored: storedEntityState(row),
|
|
30804
|
+
liveMetrics: null,
|
|
30805
|
+
storedMetrics: null
|
|
30806
|
+
});
|
|
30807
|
+
}
|
|
30808
|
+
for (const row of storedAdGroupRows) {
|
|
30809
|
+
if (walkedAdGroupIds.has(row.id)) continue;
|
|
30810
|
+
observations.push({
|
|
30811
|
+
entityType: AdsLiveEntityTypes.ad_group,
|
|
30812
|
+
id: row.id,
|
|
30813
|
+
parentId: row.campaignId,
|
|
30814
|
+
live: null,
|
|
30815
|
+
stored: storedEntityState(row),
|
|
30816
|
+
liveMetrics: null,
|
|
30817
|
+
storedMetrics: null
|
|
30818
|
+
});
|
|
30819
|
+
}
|
|
30820
|
+
for (const row of storedAdRows) {
|
|
30821
|
+
if (walkedAdIds.has(row.id)) continue;
|
|
30822
|
+
observations.push({
|
|
30823
|
+
entityType: AdsLiveEntityTypes.ad,
|
|
30824
|
+
id: row.id,
|
|
30825
|
+
parentId: row.adGroupId,
|
|
30826
|
+
live: null,
|
|
30827
|
+
stored: storedEntityState(row),
|
|
30828
|
+
liveMetrics: null,
|
|
30829
|
+
storedMetrics: null
|
|
30830
|
+
});
|
|
30831
|
+
}
|
|
30832
|
+
}
|
|
30833
|
+
const entities = observations.map(
|
|
30834
|
+
(observation) => buildLiveEntityComparison(observation, window)
|
|
30835
|
+
);
|
|
30836
|
+
const response = {
|
|
30837
|
+
basis: AdsLiveDeliveryBases.liveProviderRead,
|
|
30838
|
+
fetchedAt: new Date(fetchedAtMs).toISOString(),
|
|
30839
|
+
adAccountId: account.id,
|
|
30840
|
+
storedSnapshotSyncedAt: connection.lastSyncedAt ?? null,
|
|
30841
|
+
metricsWindow: { lookbackDays },
|
|
30842
|
+
bounds: {
|
|
30843
|
+
maxCampaigns: ADS_LIVE_MAX_CAMPAIGNS,
|
|
30844
|
+
maxAdGroupsPerCampaign: ADS_LIVE_MAX_AD_GROUPS_PER_CAMPAIGN,
|
|
30845
|
+
maxAdsPerAdGroup: ADS_LIVE_MAX_ADS_PER_AD_GROUP,
|
|
30846
|
+
maxReaderCalls: ADS_LIVE_MAX_READER_CALLS,
|
|
30847
|
+
readerCalls,
|
|
30848
|
+
maxPagesPerReaderCall: liveDeliveryMaxPagesPerReaderCall,
|
|
30849
|
+
maxUpstreamHttpRequests: ADS_LIVE_MAX_READER_CALLS * liveDeliveryMaxPagesPerReaderCall,
|
|
30850
|
+
truncated
|
|
30851
|
+
},
|
|
30852
|
+
entities,
|
|
30853
|
+
drift: summarizeLiveDrift(entities),
|
|
30854
|
+
errors
|
|
30855
|
+
};
|
|
30856
|
+
return response;
|
|
30857
|
+
} finally {
|
|
30858
|
+
liveDeliveryInFlight.delete(project.id);
|
|
30859
|
+
}
|
|
30860
|
+
});
|
|
30129
30861
|
app.get("/projects/:name/ads/summary", async (request) => {
|
|
30130
30862
|
const project = resolveProject(app.db, request.params.name);
|
|
30131
30863
|
const row = app.db.select().from(adsConnections).where(eq28(adsConnections.projectId, project.id)).get();
|
|
@@ -30174,7 +30906,7 @@ async function adsRoutes(app, opts) {
|
|
|
30174
30906
|
|
|
30175
30907
|
// ../api-routes/src/bing.ts
|
|
30176
30908
|
import crypto23 from "crypto";
|
|
30177
|
-
import { eq as eq29, and as and23, desc as
|
|
30909
|
+
import { eq as eq29, and as and23, desc as desc14 } from "drizzle-orm";
|
|
30178
30910
|
|
|
30179
30911
|
// ../integration-bing/src/constants.ts
|
|
30180
30912
|
var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
@@ -30548,7 +31280,7 @@ async function bingRoutes(app, opts) {
|
|
|
30548
31280
|
const store = requireConnectionStore();
|
|
30549
31281
|
const project = resolveProject(app.db, request.params.name);
|
|
30550
31282
|
requireConnection(store, project.canonicalDomain);
|
|
30551
|
-
const allInspections = app.db.select().from(bingUrlInspections).where(eq29(bingUrlInspections.projectId, project.id)).orderBy(
|
|
31283
|
+
const allInspections = app.db.select().from(bingUrlInspections).where(eq29(bingUrlInspections.projectId, project.id)).orderBy(desc14(bingUrlInspections.inspectedAt)).all();
|
|
30552
31284
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
30553
31285
|
const definitiveByUrl = /* @__PURE__ */ new Map();
|
|
30554
31286
|
for (const row of allInspections) {
|
|
@@ -30637,7 +31369,7 @@ async function bingRoutes(app, opts) {
|
|
|
30637
31369
|
const project = resolveProject(app.db, request.params.name);
|
|
30638
31370
|
const parsed = parseInt(request.query.limit ?? "90", 10);
|
|
30639
31371
|
const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
|
|
30640
|
-
const rows = app.db.select().from(bingCoverageSnapshots).where(eq29(bingCoverageSnapshots.projectId, project.id)).orderBy(
|
|
31372
|
+
const rows = app.db.select().from(bingCoverageSnapshots).where(eq29(bingCoverageSnapshots.projectId, project.id)).orderBy(desc14(bingCoverageSnapshots.date)).limit(limit).all();
|
|
30641
31373
|
return rows.map((r) => ({
|
|
30642
31374
|
date: r.date,
|
|
30643
31375
|
indexed: r.indexed,
|
|
@@ -30650,7 +31382,7 @@ async function bingRoutes(app, opts) {
|
|
|
30650
31382
|
const project = resolveProject(app.db, request.params.name);
|
|
30651
31383
|
const { url, limit } = request.query;
|
|
30652
31384
|
const whereClause = url ? and23(eq29(bingUrlInspections.projectId, project.id), eq29(bingUrlInspections.url, url)) : eq29(bingUrlInspections.projectId, project.id);
|
|
30653
|
-
const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(
|
|
31385
|
+
const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc14(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
|
|
30654
31386
|
return filtered.map((r) => ({
|
|
30655
31387
|
id: r.id,
|
|
30656
31388
|
url: r.url,
|
|
@@ -30794,7 +31526,7 @@ async function bingRoutes(app, opts) {
|
|
|
30794
31526
|
}
|
|
30795
31527
|
let urlsToSubmit = request.body?.urls ?? [];
|
|
30796
31528
|
if (request.body?.allUnindexed) {
|
|
30797
|
-
const allInspections = app.db.select().from(bingUrlInspections).where(eq29(bingUrlInspections.projectId, project.id)).orderBy(
|
|
31529
|
+
const allInspections = app.db.select().from(bingUrlInspections).where(eq29(bingUrlInspections.projectId, project.id)).orderBy(desc14(bingUrlInspections.inspectedAt)).all();
|
|
30798
31530
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
30799
31531
|
for (const row of allInspections) {
|
|
30800
31532
|
if (!latestByUrl.has(row.url)) {
|
|
@@ -31053,7 +31785,7 @@ async function cdpRoutes(app, opts) {
|
|
|
31053
31785
|
|
|
31054
31786
|
// ../api-routes/src/ga.ts
|
|
31055
31787
|
import crypto24 from "crypto";
|
|
31056
|
-
import { eq as eq31, desc as
|
|
31788
|
+
import { eq as eq31, desc as desc15, and as and25, sql as sql15 } from "drizzle-orm";
|
|
31057
31789
|
|
|
31058
31790
|
// ../api-routes/src/ga-session-history.ts
|
|
31059
31791
|
function buildSessionHistory(landingPageDays, dailyTotals) {
|
|
@@ -31500,7 +32232,7 @@ async function ga4Routes(app, opts) {
|
|
|
31500
32232
|
if (!connected) {
|
|
31501
32233
|
return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
|
|
31502
32234
|
}
|
|
31503
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq31(gaTrafficSummaries.projectId, project.id)).orderBy(
|
|
32235
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq31(gaTrafficSummaries.projectId, project.id)).orderBy(desc15(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
31504
32236
|
return {
|
|
31505
32237
|
connected: true,
|
|
31506
32238
|
propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
|
|
@@ -31906,7 +32638,7 @@ async function ga4Routes(app, opts) {
|
|
|
31906
32638
|
sessions: sql15`SUM(${gaSocialReferrals.sessions})`,
|
|
31907
32639
|
users: sql15`SUM(${gaSocialReferrals.users})`
|
|
31908
32640
|
}).from(gaSocialReferrals).where(and25(...socialConditions)).get();
|
|
31909
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq31(gaTrafficSummaries.projectId, project.id)).orderBy(
|
|
32641
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq31(gaTrafficSummaries.projectId, project.id)).orderBy(desc15(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
31910
32642
|
const total = summaryRow?.totalSessions ?? 0;
|
|
31911
32643
|
const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
|
|
31912
32644
|
const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
|
|
@@ -33851,7 +34583,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
33851
34583
|
|
|
33852
34584
|
// ../api-routes/src/backlinks.ts
|
|
33853
34585
|
import crypto26 from "crypto";
|
|
33854
|
-
import { and as and27, asc as asc7, desc as
|
|
34586
|
+
import { and as and27, asc as asc7, desc as desc16, eq as eq32, sql as sql16 } from "drizzle-orm";
|
|
33855
34587
|
|
|
33856
34588
|
// ../integration-commoncrawl/src/constants.ts
|
|
33857
34589
|
import os2 from "os";
|
|
@@ -34352,7 +35084,7 @@ function latestSummaryForProject(db, projectId, source, release) {
|
|
|
34352
35084
|
eq32(backlinkSummaries.source, source)
|
|
34353
35085
|
];
|
|
34354
35086
|
if (release) conditions.push(eq32(backlinkSummaries.release, release));
|
|
34355
|
-
return db.select().from(backlinkSummaries).where(and27(...conditions)).orderBy(
|
|
35087
|
+
return db.select().from(backlinkSummaries).where(and27(...conditions)).orderBy(desc16(backlinkSummaries.queriedAt)).limit(1).get();
|
|
34356
35088
|
}
|
|
34357
35089
|
function parseExcludeCrawlers(value) {
|
|
34358
35090
|
if (!value) return false;
|
|
@@ -34374,7 +35106,7 @@ function computeFilteredSummary(db, base) {
|
|
|
34374
35106
|
count: sql16`count(*)`,
|
|
34375
35107
|
total: sql16`coalesce(sum(${backlinkDomains.numHosts}), 0)`
|
|
34376
35108
|
}).from(backlinkDomains).where(filteredCondition).get();
|
|
34377
|
-
const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(
|
|
35109
|
+
const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(desc16(backlinkDomains.numHosts)).limit(10).all();
|
|
34378
35110
|
const totalLinkingDomains = Number(filteredAgg?.count ?? 0);
|
|
34379
35111
|
const totalHosts = Number(filteredAgg?.total ?? 0);
|
|
34380
35112
|
const unfilteredLinkingDomains = Number(unfilteredAgg?.count ?? 0);
|
|
@@ -34395,7 +35127,7 @@ function computeFilteredSummary(db, base) {
|
|
|
34395
35127
|
};
|
|
34396
35128
|
}
|
|
34397
35129
|
function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
|
|
34398
|
-
const summary = db.select().from(backlinkSummaries).where(and27(eq32(backlinkSummaries.projectId, projectId), eq32(backlinkSummaries.source, source))).orderBy(
|
|
35130
|
+
const summary = db.select().from(backlinkSummaries).where(and27(eq32(backlinkSummaries.projectId, projectId), eq32(backlinkSummaries.source, source))).orderBy(desc16(backlinkSummaries.queriedAt)).limit(1).get();
|
|
34399
35131
|
let totalLinkingDomains = summary?.totalLinkingDomains ?? 0;
|
|
34400
35132
|
if (summary && excludeCrawlers) {
|
|
34401
35133
|
const filtered = db.select({ count: sql16`count(*)` }).from(backlinkDomains).where(and27(
|
|
@@ -34501,11 +35233,11 @@ async function backlinksRoutes(app, opts) {
|
|
|
34501
35233
|
return reply.status(201).send(mapSyncRow(inserted));
|
|
34502
35234
|
});
|
|
34503
35235
|
app.get("/backlinks/syncs/latest", async (_request, reply) => {
|
|
34504
|
-
const row = app.db.select().from(ccReleaseSyncs).orderBy(
|
|
35236
|
+
const row = app.db.select().from(ccReleaseSyncs).orderBy(desc16(ccReleaseSyncs.updatedAt)).limit(1).get();
|
|
34505
35237
|
return reply.send(row ? mapSyncRow(row) : null);
|
|
34506
35238
|
});
|
|
34507
35239
|
app.get("/backlinks/syncs", async (_request, reply) => {
|
|
34508
|
-
const rows = app.db.select().from(ccReleaseSyncs).orderBy(
|
|
35240
|
+
const rows = app.db.select().from(ccReleaseSyncs).orderBy(desc16(ccReleaseSyncs.updatedAt)).all();
|
|
34509
35241
|
return reply.send(rows.map(mapSyncRow));
|
|
34510
35242
|
});
|
|
34511
35243
|
app.get("/backlinks/releases", async (_request, reply) => {
|
|
@@ -34592,7 +35324,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
34592
35324
|
linkingDomain: backlinkDomains.linkingDomain,
|
|
34593
35325
|
numHosts: backlinkDomains.numHosts,
|
|
34594
35326
|
source: backlinkDomains.source
|
|
34595
|
-
}).from(backlinkDomains).where(domainCondition).orderBy(
|
|
35327
|
+
}).from(backlinkDomains).where(domainCondition).orderBy(desc16(backlinkDomains.numHosts)).limit(limit).offset(offset).all();
|
|
34596
35328
|
let summary = null;
|
|
34597
35329
|
if (summaryRow) {
|
|
34598
35330
|
summary = excludeCrawlers ? computeFilteredSummary(app.db, summaryRow) : mapSummaryRow(summaryRow);
|
|
@@ -34666,7 +35398,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
34666
35398
|
// ../api-routes/src/traffic.ts
|
|
34667
35399
|
import crypto28 from "crypto";
|
|
34668
35400
|
import { Agent as UndiciAgent } from "undici";
|
|
34669
|
-
import { and as and28, desc as
|
|
35401
|
+
import { and as and28, desc as desc17, eq as eq33, gte as gte9, lte as lte7, sql as sql17 } from "drizzle-orm";
|
|
34670
35402
|
|
|
34671
35403
|
// ../integration-cloud-run/src/auth.ts
|
|
34672
35404
|
import crypto27 from "crypto";
|
|
@@ -39768,7 +40500,7 @@ async function trafficRoutes(app, opts) {
|
|
|
39768
40500
|
eq33(runs.kind, RunKinds["traffic-sync"]),
|
|
39769
40501
|
eq33(runs.sourceId, row.id)
|
|
39770
40502
|
)
|
|
39771
|
-
).orderBy(
|
|
40503
|
+
).orderBy(desc17(runs.startedAt)).limit(1).get();
|
|
39772
40504
|
return {
|
|
39773
40505
|
...rowToDto(row),
|
|
39774
40506
|
totals24h: {
|
|
@@ -39828,14 +40560,14 @@ async function trafficRoutes(app, opts) {
|
|
|
39828
40560
|
});
|
|
39829
40561
|
app.get("/projects/:name/traffic/sources", async (request) => {
|
|
39830
40562
|
const project = resolveProject(app.db, request.params.name);
|
|
39831
|
-
const rows = app.db.select().from(trafficSources).where(eq33(trafficSources.projectId, project.id)).orderBy(
|
|
40563
|
+
const rows = app.db.select().from(trafficSources).where(eq33(trafficSources.projectId, project.id)).orderBy(desc17(trafficSources.createdAt)).all();
|
|
39832
40564
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
|
|
39833
40565
|
const response = { sources };
|
|
39834
40566
|
return response;
|
|
39835
40567
|
});
|
|
39836
40568
|
app.get("/projects/:name/traffic/status", async (request) => {
|
|
39837
40569
|
const project = resolveProject(app.db, request.params.name);
|
|
39838
|
-
const rows = app.db.select().from(trafficSources).where(eq33(trafficSources.projectId, project.id)).orderBy(
|
|
40570
|
+
const rows = app.db.select().from(trafficSources).where(eq33(trafficSources.projectId, project.id)).orderBy(desc17(trafficSources.createdAt)).all();
|
|
39839
40571
|
const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
|
|
39840
40572
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
|
|
39841
40573
|
const response = { sources };
|
|
@@ -39934,7 +40666,7 @@ async function trafficRoutes(app, opts) {
|
|
|
39934
40666
|
for (const row of crawlerSeries) {
|
|
39935
40667
|
trafficSeriesPoint(seriesByBucket, row.bucket).crawlerHits = Number(row.hits);
|
|
39936
40668
|
}
|
|
39937
|
-
const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(
|
|
40669
|
+
const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc17(crawlerEventsHourly.tsHour)).limit(limit).all();
|
|
39938
40670
|
for (const r of rows) {
|
|
39939
40671
|
events.push({
|
|
39940
40672
|
kind: TrafficEventKinds.crawler,
|
|
@@ -39972,7 +40704,7 @@ async function trafficRoutes(app, opts) {
|
|
|
39972
40704
|
for (const row of userFetchSeries) {
|
|
39973
40705
|
trafficSeriesPoint(seriesByBucket, row.bucket).aiUserFetchHits = Number(row.hits);
|
|
39974
40706
|
}
|
|
39975
|
-
const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(
|
|
40707
|
+
const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc17(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
|
|
39976
40708
|
for (const r of rows) {
|
|
39977
40709
|
events.push({
|
|
39978
40710
|
kind: TrafficEventKinds["ai-user-fetch"],
|
|
@@ -40015,7 +40747,7 @@ async function trafficRoutes(app, opts) {
|
|
|
40015
40747
|
for (const row of referralSeries) {
|
|
40016
40748
|
trafficSeriesPoint(seriesByBucket, row.bucket).aiReferralHits = Number(row.hits);
|
|
40017
40749
|
}
|
|
40018
|
-
const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(
|
|
40750
|
+
const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc17(aiReferralEventsHourly.tsHour)).limit(limit).all();
|
|
40019
40751
|
for (const r of rows) {
|
|
40020
40752
|
const counts2 = aiReferralClassCounts(r.sessionsOrHits, r.paidSessionsOrHits, r.organicSessionsOrHits);
|
|
40021
40753
|
events.push({
|
|
@@ -42251,7 +42983,7 @@ async function doctorRoutes(app, opts) {
|
|
|
42251
42983
|
|
|
42252
42984
|
// ../api-routes/src/discovery/routes.ts
|
|
42253
42985
|
import crypto30 from "crypto";
|
|
42254
|
-
import { and as and32, desc as
|
|
42986
|
+
import { and as and32, desc as desc18, eq as eq40, gte as gte11, inArray as inArray13, isNull as isNull3, or as or9 } from "drizzle-orm";
|
|
42255
42987
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
42256
42988
|
async function discoveryRoutes(app, opts) {
|
|
42257
42989
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -42312,7 +43044,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
42312
43044
|
DiscoverySessionStatuses.probing
|
|
42313
43045
|
]),
|
|
42314
43046
|
gte11(discoverySessions.createdAt, ageFloorIso)
|
|
42315
|
-
)).orderBy(
|
|
43047
|
+
)).orderBy(desc18(discoverySessions.createdAt)).get();
|
|
42316
43048
|
if (existing && existing.runId) {
|
|
42317
43049
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
42318
43050
|
}
|
|
@@ -42381,7 +43113,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
42381
43113
|
const project = resolveProject(app.db, request.params.name);
|
|
42382
43114
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
42383
43115
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
42384
|
-
const rows = app.db.select().from(discoverySessions).where(eq40(discoverySessions.projectId, project.id)).orderBy(
|
|
43116
|
+
const rows = app.db.select().from(discoverySessions).where(eq40(discoverySessions.projectId, project.id)).orderBy(desc18(discoverySessions.createdAt)).limit(limit).all();
|
|
42385
43117
|
return reply.send(rows.map(serializeSession));
|
|
42386
43118
|
}
|
|
42387
43119
|
);
|
|
@@ -42931,7 +43663,7 @@ function dedupeStrings(input) {
|
|
|
42931
43663
|
|
|
42932
43664
|
// ../api-routes/src/technical-aeo.ts
|
|
42933
43665
|
import crypto32 from "crypto";
|
|
42934
|
-
import { and as and33, asc as asc8, count, desc as
|
|
43666
|
+
import { and as and33, asc as asc8, count, desc as desc19, eq as eq42, inArray as inArray14, lt as lt4 } from "drizzle-orm";
|
|
42935
43667
|
var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
|
|
42936
43668
|
function emptyScore(projectName) {
|
|
42937
43669
|
return {
|
|
@@ -42970,13 +43702,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
42970
43702
|
notProbeRun()
|
|
42971
43703
|
];
|
|
42972
43704
|
const targetFilters = request.query.runId ? [...baseFilters, eq42(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
|
|
42973
|
-
const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq42(siteAuditSnapshots.runId, runs.id)).where(and33(...targetFilters)).orderBy(
|
|
43705
|
+
const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq42(siteAuditSnapshots.runId, runs.id)).where(and33(...targetFilters)).orderBy(desc19(siteAuditSnapshots.createdAt)).limit(1).get();
|
|
42974
43706
|
if (!latest) {
|
|
42975
43707
|
if (request.query.runId) throw notFound("Site audit run", request.query.runId);
|
|
42976
43708
|
return emptyScore(project.name);
|
|
42977
43709
|
}
|
|
42978
43710
|
const snap = latest.snap;
|
|
42979
|
-
const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs, eq42(siteAuditSnapshots.runId, runs.id)).where(and33(...baseFilters, lt4(siteAuditSnapshots.createdAt, snap.createdAt))).orderBy(
|
|
43711
|
+
const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs, eq42(siteAuditSnapshots.runId, runs.id)).where(and33(...baseFilters, lt4(siteAuditSnapshots.createdAt, snap.createdAt))).orderBy(desc19(siteAuditSnapshots.createdAt)).limit(1).get()?.snap ?? null;
|
|
42980
43712
|
const deltaScore = previous ? snap.aggregateScore - previous.aggregateScore : null;
|
|
42981
43713
|
const trend = deltaScore == null ? null : deltaScore > 0 ? SiteAuditTrendDirections.up : deltaScore < 0 ? SiteAuditTrendDirections.down : SiteAuditTrendDirections.flat;
|
|
42982
43714
|
return {
|
|
@@ -43009,7 +43741,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
43009
43741
|
notProbeRun()
|
|
43010
43742
|
];
|
|
43011
43743
|
if (request.query.runId) targetFilters.push(eq42(siteAuditSnapshots.runId, request.query.runId));
|
|
43012
|
-
const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq42(siteAuditSnapshots.runId, runs.id)).where(and33(...targetFilters)).orderBy(
|
|
43744
|
+
const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq42(siteAuditSnapshots.runId, runs.id)).where(and33(...targetFilters)).orderBy(desc19(siteAuditSnapshots.createdAt)).limit(1).get();
|
|
43013
43745
|
if (!latest && request.query.runId) {
|
|
43014
43746
|
throw notFound("Site audit run", request.query.runId);
|
|
43015
43747
|
}
|
|
@@ -43024,7 +43756,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
43024
43756
|
const total = totalRow?.value ?? 0;
|
|
43025
43757
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
43026
43758
|
const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
|
|
43027
|
-
const orderBy = request.query.sort === "score-desc" ?
|
|
43759
|
+
const orderBy = request.query.sort === "score-desc" ? desc19(siteAuditPages.overallScore) : request.query.sort === "url" ? asc8(siteAuditPages.url) : asc8(siteAuditPages.overallScore);
|
|
43028
43760
|
const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
|
|
43029
43761
|
const pages = rows.map((row) => ({
|
|
43030
43762
|
url: row.url,
|
|
@@ -43048,7 +43780,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
43048
43780
|
eq42(runs.kind, RunKinds["site-audit"]),
|
|
43049
43781
|
inArray14(runs.status, SURFACEABLE_STATUSES),
|
|
43050
43782
|
notProbeRun()
|
|
43051
|
-
)).orderBy(
|
|
43783
|
+
)).orderBy(desc19(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
43052
43784
|
return { project: project.name, points: rows.reverse() };
|
|
43053
43785
|
});
|
|
43054
43786
|
app.post("/projects/:name/technical-aeo/runs", async (request) => {
|
|
@@ -43085,7 +43817,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
43085
43817
|
|
|
43086
43818
|
// ../api-routes/src/research.ts
|
|
43087
43819
|
import crypto33 from "crypto";
|
|
43088
|
-
import { and as and34, desc as
|
|
43820
|
+
import { and as and34, desc as desc20, eq as eq43 } from "drizzle-orm";
|
|
43089
43821
|
var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
|
|
43090
43822
|
async function researchRoutes(app, opts) {
|
|
43091
43823
|
app.post("/projects/:name/research/runs", async (request, reply) => {
|
|
@@ -43136,7 +43868,7 @@ async function researchRoutes(app, opts) {
|
|
|
43136
43868
|
const project = resolveProject(app.db, request.params.name);
|
|
43137
43869
|
const requested = Number.parseInt(request.query.limit ?? "", 10);
|
|
43138
43870
|
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
|
|
43139
|
-
const runs2 = app.db.select().from(researchRuns).where(eq43(researchRuns.projectId, project.id)).orderBy(
|
|
43871
|
+
const runs2 = app.db.select().from(researchRuns).where(eq43(researchRuns.projectId, project.id)).orderBy(desc20(researchRuns.createdAt)).limit(limit).all().map(serializeRun);
|
|
43140
43872
|
return { runs: runs2 };
|
|
43141
43873
|
});
|
|
43142
43874
|
app.get("/projects/:name/research/runs/:runId", async (request) => {
|
|
@@ -43154,7 +43886,7 @@ function serializeRun(row) {
|
|
|
43154
43886
|
return { id: row.id, projectId: row.projectId, status: row.status, provider: row.provider, requestedModel: row.requestedModel, resolvedModel: row.resolvedModel, location: row.location ?? null, totalQueries: row.totalQueries, completedQueries: row.completedQueries, failedQueries: row.failedQueries, error: row.error, startedAt: row.startedAt, finishedAt: row.finishedAt, createdAt: row.createdAt };
|
|
43155
43887
|
}
|
|
43156
43888
|
function serializeQuery(row) {
|
|
43157
|
-
return { id: row.id, position: row.position, query: row.queryText, status: row.status, requestedModel: row.requestedModel, resolvedModel: row.resolvedModel, servedModel: row.servedModel, answerText: row.answerText, groundingSources: row.groundingSources, citedDomains: row.citedDomains, searchQueries: row.searchQueries, answerMentioned: row.answerMentioned, citationState: row.citationState, error: row.error, startedAt: row.startedAt, finishedAt: row.finishedAt, createdAt: row.createdAt };
|
|
43889
|
+
return { id: row.id, position: row.position, query: row.queryText, status: row.status, requestedModel: row.requestedModel, resolvedModel: row.resolvedModel, servedModel: row.servedModel, answerText: row.answerText, groundingSources: row.groundingSources, citedDomains: row.citedDomains, searchQueries: row.searchQueries, namedCompetitors: row.namedCompetitors, citedCompetitorDomains: row.citedCompetitorDomains, answerMentioned: row.answerMentioned, citationState: row.citationState, error: row.error, startedAt: row.startedAt, finishedAt: row.finishedAt, createdAt: row.createdAt };
|
|
43158
43890
|
}
|
|
43159
43891
|
|
|
43160
43892
|
// ../api-routes/src/index.ts
|
|
@@ -43275,6 +44007,9 @@ async function apiRoutes(app, opts) {
|
|
|
43275
44007
|
adsCredentialStore: opts.adsCredentialStore,
|
|
43276
44008
|
verifyAdsAccount: opts.verifyAdsAccount,
|
|
43277
44009
|
adsReader: opts.adsReader,
|
|
44010
|
+
adsLiveDeliveryReader: opts.adsLiveDeliveryReader,
|
|
44011
|
+
adsLiveDeliveryMinIntervalMs: opts.adsLiveDeliveryMinIntervalMs,
|
|
44012
|
+
adsLiveDeliveryMaxPagesPerReaderCall: opts.adsLiveDeliveryMaxPagesPerReaderCall,
|
|
43278
44013
|
onAdsSyncRequested: opts.onAdsSyncRequested,
|
|
43279
44014
|
adsOperator: opts.adsOperator,
|
|
43280
44015
|
adsReconcileSweepIntervalMs: opts.adsReconcileSweepIntervalMs,
|
|
@@ -43618,6 +44353,15 @@ function computeCompetitorOverlap(normalized, competitorDomains) {
|
|
|
43618
44353
|
}
|
|
43619
44354
|
return [...overlapSet];
|
|
43620
44355
|
}
|
|
44356
|
+
function computeCitedCompetitorDomains(citedDomains, competitorDomains) {
|
|
44357
|
+
const citedCompetitors = /* @__PURE__ */ new Set();
|
|
44358
|
+
for (const citedDomain of citedDomains) {
|
|
44359
|
+
for (const competitorDomain of competitorDomains) {
|
|
44360
|
+
if (domainMatches(citedDomain, competitorDomain)) citedCompetitors.add(competitorDomain);
|
|
44361
|
+
}
|
|
44362
|
+
}
|
|
44363
|
+
return [...citedCompetitors];
|
|
44364
|
+
}
|
|
43621
44365
|
function escapeRegExp5(value) {
|
|
43622
44366
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
43623
44367
|
}
|
|
@@ -43678,7 +44422,7 @@ function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, com
|
|
|
43678
44422
|
for (const pattern of candidatePatterns) {
|
|
43679
44423
|
let match;
|
|
43680
44424
|
while ((match = pattern.exec(answerText)) !== null) {
|
|
43681
|
-
const candidate = cleanCandidateName(match[1]
|
|
44425
|
+
const candidate = cleanCandidateName(match[1]);
|
|
43682
44426
|
const candidateKey = brandKeyFromText(candidate);
|
|
43683
44427
|
if (!candidateKey) continue;
|
|
43684
44428
|
if (genericKeys.has(candidateKey)) continue;
|
|
@@ -43752,7 +44496,7 @@ var IntelligenceService = class {
|
|
|
43752
44496
|
// intelligence window.
|
|
43753
44497
|
ne6(runs.trigger, RunTriggers.probe)
|
|
43754
44498
|
)
|
|
43755
|
-
).orderBy(
|
|
44499
|
+
).orderBy(desc21(runs.finishedAt), desc21(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
|
|
43756
44500
|
if (recentRuns.length === 0) {
|
|
43757
44501
|
log.info("intelligence.skip", { runId, reason: "no completed runs" });
|
|
43758
44502
|
return null;
|
|
@@ -43874,10 +44618,10 @@ var IntelligenceService = class {
|
|
|
43874
44618
|
buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
|
|
43875
44619
|
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and35(eq44(gbpDailyMetrics.projectId, projectId), eq44(gbpDailyMetrics.locationName, locationName))).all();
|
|
43876
44620
|
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and35(eq44(gbpPlaceActions.projectId, projectId), eq44(gbpPlaceActions.locationName, locationName))).all();
|
|
43877
|
-
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and35(eq44(gbpLodgingSnapshots.projectId, projectId), eq44(gbpLodgingSnapshots.locationName, locationName))).orderBy(
|
|
44621
|
+
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and35(eq44(gbpLodgingSnapshots.projectId, projectId), eq44(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc21(gbpLodgingSnapshots.syncedAt)).limit(1).get();
|
|
43878
44622
|
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and35(eq44(gbpLocations.projectId, projectId), eq44(gbpLocations.locationName, locationName))).get();
|
|
43879
44623
|
const descriptionMissing = !(ownerRow?.description ?? "").trim();
|
|
43880
|
-
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and35(eq44(gbpPlaceDetails.projectId, projectId), eq44(gbpPlaceDetails.locationName, locationName))).orderBy(
|
|
44624
|
+
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and35(eq44(gbpPlaceDetails.projectId, projectId), eq44(gbpPlaceDetails.locationName, locationName))).orderBy(desc21(gbpPlaceDetails.syncedAt)).limit(1).get();
|
|
43881
44625
|
const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
|
|
43882
44626
|
const summary = buildGbpSummary({
|
|
43883
44627
|
locationName,
|
|
@@ -44230,7 +44974,7 @@ var IntelligenceService = class {
|
|
|
44230
44974
|
// Defensive — see top of file.
|
|
44231
44975
|
ne6(runs.trigger, RunTriggers.probe)
|
|
44232
44976
|
)
|
|
44233
|
-
).orderBy(
|
|
44977
|
+
).orderBy(desc21(runs.createdAt), desc21(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
|
|
44234
44978
|
const recentGroups = groupRunsByCreatedAt(recentRunRows);
|
|
44235
44979
|
const recentRunIds = [];
|
|
44236
44980
|
const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
|
|
@@ -44393,6 +45137,7 @@ export {
|
|
|
44393
45137
|
classifyAiUserFetch,
|
|
44394
45138
|
determineCitationState,
|
|
44395
45139
|
computeCompetitorOverlap,
|
|
45140
|
+
computeCitedCompetitorDomains,
|
|
44396
45141
|
extractRecommendedCompetitors,
|
|
44397
45142
|
getPlaceDetails,
|
|
44398
45143
|
hashPlaceDetails,
|