@canonry/canonry 5.18.0 → 5.19.1
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/aero/SKILL.md +13 -8
- package/assets/agent-workspace/skills/aero/references/portfolio-analysis.md +27 -10
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +4 -1
- package/assets/assets/{AuditHistoryPanel-Di5c6xSa.js → AuditHistoryPanel-ddq5tdlB.js} +1 -1
- package/assets/assets/{BacklinksPage-CZi3sZ-7.js → BacklinksPage-g57_xzTX.js} +1 -1
- package/assets/assets/{HistoryPage-BY7HiZWU.js → HistoryPage-C-TdCUQ2.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-D3AdBHpI.js → MeasurementPropertyPage-CAADmNfw.js} +1 -1
- package/assets/assets/{ProjectPage-NyUl1dkF.js → ProjectPage-CoBwpHmA.js} +1 -1
- package/assets/assets/{RunRow-cRjiHQbt.js → RunRow-BpG0Zy-x.js} +1 -1
- package/assets/assets/{RunsPage-DZP_kqJs.js → RunsPage-DK3pb1LH.js} +1 -1
- package/assets/assets/{SettingsPage-BqJXYCDc.js → SettingsPage-BruiY8Zh.js} +1 -1
- package/assets/assets/{SiteHealthSection-Brr0C56X.js → SiteHealthSection-De4U11Gv.js} +3 -3
- package/assets/assets/{TrafficPage-k5NPSDur.js → TrafficPage-BVolLXT2.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BKNzWnxA.js → TrafficSourceDetailPage-CFDb6lPD.js} +1 -1
- package/assets/assets/{extract-error-message-Cnvelll0.js → extract-error-message-Bk1RTjzo.js} +1 -1
- package/assets/assets/index-B--AA1mG.js +86 -0
- package/assets/assets/{react-sigma_core.esm.min-CthGjQKl.js → react-sigma_core.esm.min--QXVK1JS.js} +1 -1
- package/assets/assets/{v2-overview-adapter-q2o2MzmG.js → v2-overview-adapter-CpjJPZsC.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-Y5IDGH5B.js → chunk-3IAIW5KH.js} +534 -136
- package/dist/chunk-4WROKFN6.js +3991 -0
- package/dist/{chunk-TMTO2JHU.js → chunk-EJXDABGU.js} +21 -3970
- package/dist/{chunk-HIFBIDOW.js → chunk-EJZFTNZG.js} +444 -185
- package/dist/{chunk-X3GFCKUJ.js → chunk-J3JXJIST.js} +3 -3
- package/dist/{chunk-HAABFHXB.js → chunk-PGOID3LE.js} +2 -2
- package/dist/{chunk-77VHT3JD.js → chunk-SOCYTKTF.js} +223 -23
- package/dist/cli.js +84 -27
- package/dist/{demo-server-JBGYZKAI.js → demo-server-BLDYKCFS.js} +1071 -187
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-EORTVFDF.js → intelligence-service-3MHYLKMS.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +9 -9
- package/assets/assets/index-Cd_1GknE.js +0 -86
- package/dist/chunk-AOJU5LKX.js +0 -8
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
AppError,
|
|
33
33
|
BacklinkSources,
|
|
34
34
|
COMPETITOR_LANDSCAPE_MODEL_GROUP_LIMIT,
|
|
35
|
+
COMPETITOR_LANDSCAPE_OBSERVED_NAME_LIMIT,
|
|
35
36
|
CcReleaseSyncStatuses,
|
|
36
37
|
CheckCategories,
|
|
37
38
|
CheckScopes,
|
|
@@ -79,6 +80,10 @@ import {
|
|
|
79
80
|
MEASUREMENT_PAGE_DEFAULT_LIMIT,
|
|
80
81
|
MEASUREMENT_PAGE_MAX_LIMIT,
|
|
81
82
|
MEASUREMENT_PLAN_V2_SCHEMA_VERSION,
|
|
83
|
+
MEASUREMENT_PORTFOLIO_ANSWER_SOURCES_LIMIT,
|
|
84
|
+
MEASUREMENT_PORTFOLIO_DEFAULT_LIMIT,
|
|
85
|
+
MEASUREMENT_PORTFOLIO_ROW_EVIDENCE_LIMIT,
|
|
86
|
+
MEASUREMENT_PORTFOLIO_TIE_NOTE,
|
|
82
87
|
MIN_DOMAIN_BRAND_KEY_LENGTH,
|
|
83
88
|
MIN_TREND_POINTS,
|
|
84
89
|
MODEL_ATTRIBUTION_EVENT_LIMIT,
|
|
@@ -733,6 +738,7 @@ import {
|
|
|
733
738
|
snapshotRequestInputSchema,
|
|
734
739
|
snapshotRequestSchema,
|
|
735
740
|
sourceBreakdownDtoSchema,
|
|
741
|
+
sourceBreakdownQuerySchema,
|
|
736
742
|
splitList,
|
|
737
743
|
sumInfraHits,
|
|
738
744
|
summarizeCheckResults,
|
|
@@ -785,7 +791,7 @@ import {
|
|
|
785
791
|
wordpressSchemaDeployResultDtoSchema,
|
|
786
792
|
wordpressSchemaStatusResultDtoSchema,
|
|
787
793
|
wordpressStatusDtoSchema
|
|
788
|
-
} from "./chunk-
|
|
794
|
+
} from "./chunk-SOCYTKTF.js";
|
|
789
795
|
|
|
790
796
|
// src/intelligence-service.ts
|
|
791
797
|
import { eq as eq71, desc as desc32, asc as asc12, and as and60, ne as ne11, or as or18, inArray as inArray27, gte as gte18, lte as lte15, isNull as isNull13, sql as sql28, exists } from "drizzle-orm";
|
|
@@ -9155,7 +9161,7 @@ function compileCompetitiveSignalResolver(competitorDomains) {
|
|
|
9155
9161
|
...evidence.citedDomains ?? [],
|
|
9156
9162
|
...(evidence.groundingSources ?? []).map((source) => source.uri)
|
|
9157
9163
|
];
|
|
9158
|
-
const
|
|
9164
|
+
const answerDomains2 = evidence.answerDomains ?? extractDomainsFromText(evidence.answerText);
|
|
9159
9165
|
const mentionedBrandKeys = matchedAliasKeys(domainBrandMatcher, evidence.answerText);
|
|
9160
9166
|
const citedCompetitorDomains = [];
|
|
9161
9167
|
const mentionedCompetitorDomains = [];
|
|
@@ -9163,7 +9169,7 @@ function compileCompetitiveSignalResolver(competitorDomains) {
|
|
|
9163
9169
|
if (citationCandidates.some((candidate) => hostMatchesDomain(candidate, identity.domain))) {
|
|
9164
9170
|
citedCompetitorDomains.push(identity.domain);
|
|
9165
9171
|
}
|
|
9166
|
-
if (
|
|
9172
|
+
if (answerDomains2.some((candidate) => hostMatchesDomain(candidate, identity.domain)) || identity.domainBrandKey !== null && mentionedBrandKeys.has(identity.domainBrandKey)) {
|
|
9167
9173
|
mentionedCompetitorDomains.push(identity.domain);
|
|
9168
9174
|
}
|
|
9169
9175
|
}
|
|
@@ -16052,7 +16058,8 @@ function readCompetitorLandscape(app, projectName, query, selection) {
|
|
|
16052
16058
|
throw validationError("Invalid competitor landscape query", { issues: parsed.error.issues });
|
|
16053
16059
|
}
|
|
16054
16060
|
const filters = parsed.data;
|
|
16055
|
-
|
|
16061
|
+
const classSplitByDefault = filters.scope === void 0 && filters.groupKey === void 0 && filters.queryClass !== void 0 && filters.queryClass !== "all";
|
|
16062
|
+
if ((selection?.autoAdvanced || classSplitByDefault) && activeMeasurementPlan(app.db, project.id)?.plan.schemaVersion === 2) filters.scope = "all-markets";
|
|
16056
16063
|
const window = parseWindow(filters.window);
|
|
16057
16064
|
const cutoff = windowCutoff(window);
|
|
16058
16065
|
const candidateRuns = app.db.select().from(runs).where(and10(
|
|
@@ -16125,8 +16132,10 @@ function readCompetitorLandscape(app, projectName, query, selection) {
|
|
|
16125
16132
|
const pinned = mergePins(advanced?.pendingPins ?? [], advanced?.activePinned ?? [], projectPins);
|
|
16126
16133
|
const buildHistory = (selectedSnapshots) => {
|
|
16127
16134
|
const inputs = buildMentionShareInputs({ project, competitorDomains: [], snapshots: selectedSnapshots, queryTextById });
|
|
16135
|
+
const allObservedNames = observedCompetitorNames(selectedSnapshots);
|
|
16128
16136
|
return {
|
|
16129
|
-
observedNames:
|
|
16137
|
+
observedNames: allObservedNames.slice(0, COMPETITOR_LANDSCAPE_OBSERVED_NAME_LIMIT),
|
|
16138
|
+
observedNamesTotal: allObservedNames.length,
|
|
16130
16139
|
...buildCompetitorLandscapeHistory({
|
|
16131
16140
|
project: {
|
|
16132
16141
|
domain: project.canonicalDomain,
|
|
@@ -16242,21 +16251,7 @@ function resolveAdvancedScope(app, projectId, kind, groupKey, candidateRuns) {
|
|
|
16242
16251
|
if (kind === "group" && (!groupKey || !active.plan.groups.some((group) => group.stableKey === groupKey))) {
|
|
16243
16252
|
throw validationError(`Unknown Advanced Measurement group "${groupKey ?? ""}".`);
|
|
16244
16253
|
}
|
|
16245
|
-
const
|
|
16246
|
-
const historicalVersions = planVersionIds.length === 0 ? [] : app.db.select().from(measurementPlanVersions).where(and10(
|
|
16247
|
-
eq15(measurementPlanVersions.projectId, projectId),
|
|
16248
|
-
inArray4(measurementPlanVersions.id, planVersionIds)
|
|
16249
|
-
)).all();
|
|
16250
|
-
const frozenByVersionId = new Map(historicalVersions.map((version) => [
|
|
16251
|
-
version.id,
|
|
16252
|
-
scopeForFrozenPlan(parseStoredMeasurementPlanAnyVersion(version.canonicalJson), kind, groupKey)
|
|
16253
|
-
]));
|
|
16254
|
-
const runScopes = /* @__PURE__ */ new Map();
|
|
16255
|
-
for (const run of candidateRuns) {
|
|
16256
|
-
if (!run.measurementPlanVersionId) continue;
|
|
16257
|
-
const frozen = frozenByVersionId.get(run.measurementPlanVersionId);
|
|
16258
|
-
if (frozen) runScopes.set(run.id, frozen);
|
|
16259
|
-
}
|
|
16254
|
+
const runScopes = frozenRunScopes(app.db, projectId, kind, groupKey, candidateRuns);
|
|
16260
16255
|
const activeScope = scopeForFrozenPlan(active.plan, kind, groupKey);
|
|
16261
16256
|
if (!activeScope) throw validationError("The active Advanced Measurement plan cannot resolve this market scope.");
|
|
16262
16257
|
const draft = draftRow(app.db, projectId);
|
|
@@ -16274,25 +16269,58 @@ function resolveAdvancedScope(app, projectId, kind, groupKey, candidateRuns) {
|
|
|
16274
16269
|
runScopes
|
|
16275
16270
|
};
|
|
16276
16271
|
}
|
|
16272
|
+
function frozenRunScopes(db, projectId, kind, groupKey, candidateRuns) {
|
|
16273
|
+
const planVersionIds = [...new Set(candidateRuns.map((run) => run.measurementPlanVersionId).filter((id) => typeof id === "string" && id.length > 0))];
|
|
16274
|
+
const historicalVersions = planVersionIds.length === 0 ? [] : db.select().from(measurementPlanVersions).where(and10(
|
|
16275
|
+
eq15(measurementPlanVersions.projectId, projectId),
|
|
16276
|
+
inArray4(measurementPlanVersions.id, planVersionIds)
|
|
16277
|
+
)).all();
|
|
16278
|
+
const frozenByVersionId = new Map(historicalVersions.map((version) => [
|
|
16279
|
+
version.id,
|
|
16280
|
+
scopeForFrozenPlan(parseStoredMeasurementPlanAnyVersion(version.canonicalJson), kind, groupKey)
|
|
16281
|
+
]));
|
|
16282
|
+
const runScopes = /* @__PURE__ */ new Map();
|
|
16283
|
+
for (const run of candidateRuns) {
|
|
16284
|
+
if (!run.measurementPlanVersionId) continue;
|
|
16285
|
+
const frozen = frozenByVersionId.get(run.measurementPlanVersionId);
|
|
16286
|
+
if (frozen) runScopes.set(run.id, frozen);
|
|
16287
|
+
}
|
|
16288
|
+
return runScopes;
|
|
16289
|
+
}
|
|
16290
|
+
function planQueryClassesByRun(db, projectId, candidateRuns) {
|
|
16291
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
16292
|
+
for (const [runId, scope] of frozenRunScopes(db, projectId, "all-markets", void 0, candidateRuns)) {
|
|
16293
|
+
byRun.set(runId, scope.queryClassesByExecution);
|
|
16294
|
+
}
|
|
16295
|
+
return byRun;
|
|
16296
|
+
}
|
|
16277
16297
|
function scopeForFrozenPlan(plan, kind, groupKey) {
|
|
16278
16298
|
if (plan.schemaVersion !== 2) return null;
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16284
|
-
|
|
16285
|
-
|
|
16286
|
-
edge.targetKey
|
|
16287
|
-
edge.
|
|
16288
|
-
|
|
16289
|
-
|
|
16290
|
-
|
|
16291
|
-
|
|
16299
|
+
let groups = plan.groups;
|
|
16300
|
+
let scopedUsageEdges = plan.usageEdges;
|
|
16301
|
+
let scopedAssignments = plan.assignments;
|
|
16302
|
+
if (kind === "group") {
|
|
16303
|
+
groups = plan.groups.filter((group) => group.stableKey === groupKey);
|
|
16304
|
+
if (groups.length === 0) return null;
|
|
16305
|
+
const targetKeys = new Set(groups.flatMap((group) => group.targetKeys));
|
|
16306
|
+
scopedUsageEdges = plan.usageEdges.filter((edge) => targetKeys.has(edge.targetKey));
|
|
16307
|
+
const scopedUsages = new Set(scopedUsageEdges.map((edge) => JSON.stringify([
|
|
16308
|
+
edge.executionNodeKey,
|
|
16309
|
+
edge.targetKey,
|
|
16310
|
+
edge.queryId
|
|
16311
|
+
])));
|
|
16312
|
+
scopedAssignments = plan.assignments.filter((assignment) => scopedUsages.has(JSON.stringify([
|
|
16292
16313
|
assignment.executionNodeKey,
|
|
16293
16314
|
assignment.targetKey,
|
|
16294
16315
|
assignment.queryId
|
|
16295
|
-
])))
|
|
16316
|
+
])));
|
|
16317
|
+
}
|
|
16318
|
+
const executionNodeKeys = /* @__PURE__ */ new Set([
|
|
16319
|
+
...scopedUsageEdges.map((edge) => edge.executionNodeKey),
|
|
16320
|
+
...scopedAssignments.map((assignment) => assignment.executionNodeKey)
|
|
16321
|
+
]);
|
|
16322
|
+
const queryClassesByExecution = /* @__PURE__ */ new Map();
|
|
16323
|
+
for (const assignment of scopedAssignments) {
|
|
16296
16324
|
const classes = queryClassesByExecution.get(assignment.executionNodeKey) ?? /* @__PURE__ */ new Set();
|
|
16297
16325
|
classes.add(assignment.queryClass);
|
|
16298
16326
|
queryClassesByExecution.set(assignment.executionNodeKey, classes);
|
|
@@ -22807,34 +22835,155 @@ function filteredOverviewWithDb(db, active, plan, run, filters, targetKeys) {
|
|
|
22807
22835
|
overview: evaluator.evaluate(targetKeys)
|
|
22808
22836
|
};
|
|
22809
22837
|
}
|
|
22810
|
-
function
|
|
22811
|
-
const
|
|
22812
|
-
|
|
22813
|
-
|
|
22814
|
-
|
|
22815
|
-
|
|
22838
|
+
function pushTo(map, key, value) {
|
|
22839
|
+
const existing = map.get(key);
|
|
22840
|
+
if (existing) existing.push(value);
|
|
22841
|
+
else map.set(key, [value]);
|
|
22842
|
+
}
|
|
22843
|
+
function innerMap(outer, key) {
|
|
22844
|
+
let inner = outer.get(key);
|
|
22845
|
+
if (!inner) {
|
|
22846
|
+
inner = /* @__PURE__ */ new Map();
|
|
22847
|
+
outer.set(key, inner);
|
|
22848
|
+
}
|
|
22849
|
+
return inner;
|
|
22850
|
+
}
|
|
22851
|
+
function createTargetAnswerReader(plan, materialized, filters) {
|
|
22852
|
+
const assignmentsByTarget = /* @__PURE__ */ new Map();
|
|
22853
|
+
for (const assignment of plan.assignments) {
|
|
22854
|
+
if (filters.queryClass !== "all" && assignment.queryClass !== filters.queryClass) continue;
|
|
22855
|
+
innerMap(assignmentsByTarget, assignment.targetKey).set(assignment.executionNodeKey, assignment);
|
|
22856
|
+
}
|
|
22857
|
+
const edgesByTarget = /* @__PURE__ */ new Map();
|
|
22858
|
+
for (const edge of materialized.input.usageEdges) {
|
|
22859
|
+
if (edge.type !== "target") continue;
|
|
22860
|
+
innerMap(edgesByTarget, edge.targetId).set(edge.executionId, edge);
|
|
22861
|
+
}
|
|
22862
|
+
const answersByEdge = /* @__PURE__ */ new Map();
|
|
22863
|
+
for (const answer of materialized.evidence.answers) {
|
|
22864
|
+
innerMap(answersByEdge, answer.usageEdgeId).set(answer.expectedSlotId, answer);
|
|
22865
|
+
}
|
|
22866
|
+
const citedSlotsByEdge = /* @__PURE__ */ new Map();
|
|
22867
|
+
for (const row of materialized.evidence.evidence) {
|
|
22868
|
+
if (row.classification !== "assigned") continue;
|
|
22869
|
+
const slots = citedSlotsByEdge.get(row.usageEdgeId);
|
|
22870
|
+
if (slots) slots.add(row.expectedSlotId);
|
|
22871
|
+
else citedSlotsByEdge.set(row.usageEdgeId, /* @__PURE__ */ new Set([row.expectedSlotId]));
|
|
22872
|
+
}
|
|
22816
22873
|
const incompleteObservationIds = new Set(materialized.evidence.diagnostics.evidenceIncompleteObservationIds);
|
|
22817
22874
|
const questionsById = new Map(plan.querySnapshots.map((question) => [question.queryId, question.queryText]));
|
|
22818
22875
|
const provider = filters.provider === void 0 ? void 0 : normalizeText2(filters.provider);
|
|
22819
22876
|
const location = filters.location === void 0 ? void 0 : normalizeMeasurementLocation(filters.location);
|
|
22820
|
-
const
|
|
22821
|
-
const
|
|
22822
|
-
|
|
22823
|
-
|
|
22824
|
-
|
|
22825
|
-
|
|
22826
|
-
|
|
22827
|
-
|
|
22828
|
-
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
|
|
22832
|
-
|
|
22833
|
-
|
|
22834
|
-
|
|
22835
|
-
|
|
22877
|
+
const slotsByExecution = /* @__PURE__ */ new Map();
|
|
22878
|
+
const slotOrder = /* @__PURE__ */ new Map();
|
|
22879
|
+
materialized.input.expectedSlots.forEach((slot, index2) => {
|
|
22880
|
+
if (provider !== void 0 && normalizeText2(slot.provider) !== provider) return;
|
|
22881
|
+
if (location !== void 0 && normalizeMeasurementLocation(slot.location) !== location) return;
|
|
22882
|
+
pushTo(slotsByExecution, slot.executionId, slot);
|
|
22883
|
+
slotOrder.set(slot.id, index2);
|
|
22884
|
+
});
|
|
22885
|
+
return (target) => {
|
|
22886
|
+
const assignments = assignmentsByTarget.get(target.stableKey);
|
|
22887
|
+
const edges = edgesByTarget.get(target.stableKey);
|
|
22888
|
+
const answers = [];
|
|
22889
|
+
const sourceAnswers = [];
|
|
22890
|
+
const queries2 = /* @__PURE__ */ new Set();
|
|
22891
|
+
const providers = /* @__PURE__ */ new Set();
|
|
22892
|
+
let expected = 0;
|
|
22893
|
+
if (!assignments || !edges) return { expected, queries: 0, providers, answers, sourceAnswers };
|
|
22894
|
+
for (const [executionId, assignment] of assignments) {
|
|
22895
|
+
const edge = edges.get(executionId);
|
|
22896
|
+
const slots = slotsByExecution.get(executionId);
|
|
22897
|
+
if (!edge || !slots) continue;
|
|
22898
|
+
queries2.add(assignment.queryId);
|
|
22899
|
+
const answerBySlot = answersByEdge.get(edge.id);
|
|
22900
|
+
const citedSlotIds = citedSlotsByEdge.get(edge.id);
|
|
22901
|
+
for (const slot of slots) {
|
|
22902
|
+
expected++;
|
|
22903
|
+
providers.add(slot.provider);
|
|
22904
|
+
const observation = materialized.observationsBySlot.get(slot.id);
|
|
22905
|
+
const snapshot = observation === void 0 ? void 0 : materialized.snapshotsById.get(observation.id);
|
|
22906
|
+
if (!observation || !snapshot) continue;
|
|
22907
|
+
const sourced = {
|
|
22908
|
+
slotId: slot.id,
|
|
22909
|
+
snapshot,
|
|
22910
|
+
citedUrls: observation.citedUrls ?? observation.historicalCitedUrls ?? []
|
|
22911
|
+
};
|
|
22912
|
+
sourceAnswers.push(sourced);
|
|
22913
|
+
if (snapshot.answerText === null) continue;
|
|
22914
|
+
answers.push({
|
|
22915
|
+
...sourced,
|
|
22916
|
+
provider: slot.provider,
|
|
22917
|
+
question: questionsById.get(assignment.queryId) ?? slot.queryText,
|
|
22918
|
+
mentioned: target.mentionNotApplicable ? null : answerBySlot?.get(slot.id)?.mentioned ?? null,
|
|
22919
|
+
cited: citedSlotIds?.has(slot.id) ? true : incompleteObservationIds.has(snapshot.id) ? null : false
|
|
22920
|
+
});
|
|
22921
|
+
}
|
|
22922
|
+
}
|
|
22923
|
+
const inRunOrder = (left, right) => slotOrder.get(left.slotId) - slotOrder.get(right.slotId);
|
|
22924
|
+
answers.sort(inRunOrder);
|
|
22925
|
+
sourceAnswers.sort(inRunOrder);
|
|
22926
|
+
return { expected, queries: queries2.size, providers, answers, sourceAnswers };
|
|
22927
|
+
};
|
|
22928
|
+
}
|
|
22929
|
+
function targetAnswers(plan, target, materialized, filters) {
|
|
22930
|
+
return createTargetAnswerReader(plan, materialized, filters)(target);
|
|
22931
|
+
}
|
|
22932
|
+
function plannedQueryCount(plan, targetKey, queryClass) {
|
|
22933
|
+
return new Set(plan.assignments.filter((assignment) => assignment.targetKey === targetKey).filter((assignment) => queryClass === "all" || assignment.queryClass === queryClass).map((assignment) => assignment.queryId)).size;
|
|
22934
|
+
}
|
|
22935
|
+
function propertyLocations(plan) {
|
|
22936
|
+
const groupsByKey = new Map(plan.groups.map((group) => [group.stableKey, group]));
|
|
22937
|
+
const depth = (group) => {
|
|
22938
|
+
let levels = 0;
|
|
22939
|
+
const seen2 = /* @__PURE__ */ new Set([group.stableKey]);
|
|
22940
|
+
let parentKey = group.parentGroupKey;
|
|
22941
|
+
while (parentKey !== void 0 && !seen2.has(parentKey)) {
|
|
22942
|
+
const parent = groupsByKey.get(parentKey);
|
|
22943
|
+
if (!parent) break;
|
|
22944
|
+
seen2.add(parentKey);
|
|
22945
|
+
levels++;
|
|
22946
|
+
parentKey = parent.parentGroupKey;
|
|
22947
|
+
}
|
|
22948
|
+
return levels;
|
|
22949
|
+
};
|
|
22950
|
+
const byLabel = (left, right) => compareText13(left.label, right.label) || compareText13(left.stableKey, right.stableKey);
|
|
22951
|
+
const metros = /* @__PURE__ */ new Map();
|
|
22952
|
+
const submarkets = /* @__PURE__ */ new Map();
|
|
22953
|
+
for (const group of plan.groups) {
|
|
22954
|
+
const level = depth(group);
|
|
22955
|
+
for (const targetKey of new Set(group.targetKeys)) {
|
|
22956
|
+
if (level === 0) pushTo(metros, targetKey, group);
|
|
22957
|
+
else pushTo(submarkets, targetKey, { group, depth: level });
|
|
22958
|
+
}
|
|
22959
|
+
}
|
|
22960
|
+
const metroDto = (group) => ({ groupKey: group.stableKey, label: group.label });
|
|
22961
|
+
return (targetKey) => {
|
|
22962
|
+
const own = [...metros.get(targetKey) ?? []].sort(byLabel);
|
|
22963
|
+
const nested = [...submarkets.get(targetKey) ?? []].sort((left, right) => left.depth - right.depth || byLabel(left.group, right.group));
|
|
22964
|
+
return {
|
|
22965
|
+
metro: own.length === 0 ? null : metroDto(own[0]),
|
|
22966
|
+
...own.length > 1 ? { otherMetros: own.slice(1).map(metroDto) } : {},
|
|
22967
|
+
submarkets: nested.map((row) => row.group.label)
|
|
22968
|
+
};
|
|
22969
|
+
};
|
|
22970
|
+
}
|
|
22971
|
+
function answerDomains(answer) {
|
|
22972
|
+
const domains = /* @__PURE__ */ new Set();
|
|
22973
|
+
for (const raw of [...answer.snapshot.citedDomains, ...answer.citedUrls]) {
|
|
22974
|
+
const host = hostOf(raw);
|
|
22975
|
+
if (!host || hostMatchesAnyDomain(host, AI_PROVIDER_INFRA_DOMAINS)) continue;
|
|
22976
|
+
domains.add(host);
|
|
22977
|
+
}
|
|
22978
|
+
return domains;
|
|
22979
|
+
}
|
|
22980
|
+
function domainRows(answers, limit) {
|
|
22981
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
22982
|
+
for (const answer of answers) {
|
|
22983
|
+
for (const domain of answerDomains(answer)) counts2.set(domain, (counts2.get(domain) ?? 0) + 1);
|
|
22836
22984
|
}
|
|
22837
|
-
|
|
22985
|
+
const rows = [...counts2].map(([domain, count4]) => ({ domain, answers: count4 })).sort((left, right) => right.answers - left.answers || compareText13(left.domain, right.domain));
|
|
22986
|
+
return { rows: rows.slice(0, limit), total: rows.length };
|
|
22838
22987
|
}
|
|
22839
22988
|
function targetAliasKeys(target) {
|
|
22840
22989
|
return new Set([target.label, ...target.aliases].map(brandKeyFromText).filter(Boolean));
|
|
@@ -22844,16 +22993,18 @@ function recommendationRows(target, population) {
|
|
|
22844
22993
|
const grouped = /* @__PURE__ */ new Map();
|
|
22845
22994
|
for (const answer of population.answers) {
|
|
22846
22995
|
if (answer.mentioned !== false || answer.cited !== false) continue;
|
|
22996
|
+
const seenInAnswer = /* @__PURE__ */ new Set();
|
|
22847
22997
|
for (const rawName of answer.snapshot.recommendedCompetitors) {
|
|
22848
22998
|
const name = rawName.normalize("NFKC").trim().replace(/\s+/g, " ");
|
|
22849
22999
|
const key = brandKeyFromText(name);
|
|
22850
23000
|
if (!key || aliases.has(key)) continue;
|
|
22851
23001
|
const existing = grouped.get(key);
|
|
22852
23002
|
if (existing) {
|
|
23003
|
+
if (compareText13(name, existing.name) < 0) existing.name = name;
|
|
23004
|
+
if (seenInAnswer.has(key)) continue;
|
|
22853
23005
|
existing.occurrences++;
|
|
22854
23006
|
existing.providers.add(answer.provider);
|
|
22855
23007
|
existing.questions.add(answer.question);
|
|
22856
|
-
if (compareText13(name, existing.name) < 0) existing.name = name;
|
|
22857
23008
|
} else {
|
|
22858
23009
|
grouped.set(key, {
|
|
22859
23010
|
name,
|
|
@@ -22862,6 +23013,7 @@ function recommendationRows(target, population) {
|
|
|
22862
23013
|
questions: /* @__PURE__ */ new Set([answer.question])
|
|
22863
23014
|
});
|
|
22864
23015
|
}
|
|
23016
|
+
seenInAnswer.add(key);
|
|
22865
23017
|
}
|
|
22866
23018
|
}
|
|
22867
23019
|
return [...grouped.values()].map((row) => {
|
|
@@ -22879,6 +23031,16 @@ function recommendationRows(target, population) {
|
|
|
22879
23031
|
};
|
|
22880
23032
|
}).sort((left, right) => right.occurrences - left.occurrences || compareText13(left.name, right.name));
|
|
22881
23033
|
}
|
|
23034
|
+
function namedInsteadFields(names) {
|
|
23035
|
+
const returned = names.slice(0, MEASUREMENT_PORTFOLIO_ROW_EVIDENCE_LIMIT);
|
|
23036
|
+
return {
|
|
23037
|
+
namedInsteadInAnswerText: returned.map(({ name, occurrences }) => ({ name, answers: occurrences })),
|
|
23038
|
+
namedInsteadInAnswerTextTotal: names.length,
|
|
23039
|
+
recommendedInstead: returned.map(({ name, occurrences }) => ({ name, occurrences })),
|
|
23040
|
+
recommendedInsteadTotal: names.length,
|
|
23041
|
+
recommendedInsteadTruncated: names.length > returned.length
|
|
23042
|
+
};
|
|
23043
|
+
}
|
|
22882
23044
|
function propertyDto2(target) {
|
|
22883
23045
|
return { targetKey: target.stableKey, label: target.label };
|
|
22884
23046
|
}
|
|
@@ -22925,20 +23087,60 @@ function targetKeysForRun(plan, run, requested, requireEntireScope) {
|
|
|
22925
23087
|
}
|
|
22926
23088
|
return selected;
|
|
22927
23089
|
}
|
|
22928
|
-
function marketRollup(plan,
|
|
22929
|
-
|
|
22930
|
-
|
|
22931
|
-
|
|
22932
|
-
|
|
22933
|
-
|
|
23090
|
+
function marketRollup(plan, measured, scope, includeNested, limit) {
|
|
23091
|
+
const childCounts = /* @__PURE__ */ new Map();
|
|
23092
|
+
for (const group of plan.groups) {
|
|
23093
|
+
if (group.parentGroupKey !== void 0) childCounts.set(group.parentGroupKey, (childCounts.get(group.parentGroupKey) ?? 0) + 1);
|
|
23094
|
+
}
|
|
23095
|
+
const groupKeys = new Set(plan.groups.map((group) => group.stableKey));
|
|
23096
|
+
const isTopLevel = (group) => group.parentGroupKey === void 0 || !groupKeys.has(group.parentGroupKey);
|
|
23097
|
+
let candidates;
|
|
23098
|
+
if (scope === void 0) {
|
|
23099
|
+
candidates = includeNested ? plan.groups : plan.groups.filter(isTopLevel);
|
|
23100
|
+
} else if (!includeNested) {
|
|
23101
|
+
candidates = plan.groups.filter((group) => group.parentGroupKey === scope.stableKey);
|
|
23102
|
+
} else {
|
|
23103
|
+
const descendants = /* @__PURE__ */ new Set([scope.stableKey]);
|
|
23104
|
+
let grew = true;
|
|
23105
|
+
while (grew) {
|
|
23106
|
+
grew = false;
|
|
23107
|
+
for (const group of plan.groups) {
|
|
23108
|
+
if (group.parentGroupKey !== void 0 && descendants.has(group.parentGroupKey) && !descendants.has(group.stableKey)) {
|
|
23109
|
+
descendants.add(group.stableKey);
|
|
23110
|
+
grew = true;
|
|
23111
|
+
}
|
|
23112
|
+
}
|
|
23113
|
+
}
|
|
23114
|
+
candidates = plan.groups.filter((group) => group.stableKey !== scope.stableKey && descendants.has(group.stableKey));
|
|
23115
|
+
}
|
|
23116
|
+
const markets = candidates.map((group) => {
|
|
23117
|
+
const identity = {
|
|
22934
23118
|
groupKey: group.stableKey,
|
|
22935
23119
|
label: group.label,
|
|
23120
|
+
parentGroupKey: isTopLevel(group) ? null : group.parentGroupKey,
|
|
23121
|
+
childMarketCount: childCounts.get(group.stableKey) ?? 0
|
|
23122
|
+
};
|
|
23123
|
+
if (measured === void 0) {
|
|
23124
|
+
return {
|
|
23125
|
+
...identity,
|
|
23126
|
+
propertyCount: group.targetKeys.length,
|
|
23127
|
+
propertiesMentioned: unavailable3("no_completed_run"),
|
|
23128
|
+
mentionCoverage: unavailable3("no_completed_run"),
|
|
23129
|
+
citationCoverage: unavailable3("no_completed_run")
|
|
23130
|
+
};
|
|
23131
|
+
}
|
|
23132
|
+
const targetKeys = targetKeysForRun(plan, measured.run, group.targetKeys, false);
|
|
23133
|
+
const overview = measured.evaluator.evaluate(targetKeys);
|
|
23134
|
+
return {
|
|
23135
|
+
...identity,
|
|
22936
23136
|
propertyCount: targetKeys.length,
|
|
22937
23137
|
propertiesMentioned: countMetric2(overview.propertiesMentioned),
|
|
22938
23138
|
mentionCoverage: coverageMetric2(overview.mentionCoverage),
|
|
22939
23139
|
citationCoverage: coverageMetric2(overview.citationCoverage)
|
|
22940
23140
|
};
|
|
22941
23141
|
}).sort(compareWeakestMarket);
|
|
23142
|
+
const returned = includeNested ? markets : markets.slice(0, limit);
|
|
23143
|
+
return { markets: returned, totalMarkets: markets.length, marketsTruncated: returned.length < markets.length };
|
|
22942
23144
|
}
|
|
22943
23145
|
function compareWeakestMarket(a, b) {
|
|
22944
23146
|
const aUnavailable = a.mentionCoverage.state === "unavailable" || a.citationCoverage.state === "unavailable";
|
|
@@ -22956,11 +23158,20 @@ function compareWeakestMarket(a, b) {
|
|
|
22956
23158
|
function mentionRanking(rows, limit) {
|
|
22957
23159
|
const eligible = [];
|
|
22958
23160
|
const excluded = [];
|
|
22959
|
-
for (const { targetKey, label, mentionCoverage, citationCoverage } of rows) {
|
|
23161
|
+
for (const { targetKey, label, mentionCoverage, citationCoverage, metro, otherMetros, submarkets, queries: queries2 } of rows) {
|
|
22960
23162
|
if (mentionCoverage.state === "unavailable") {
|
|
22961
23163
|
excluded.push({ targetKey, label, reason: mentionCoverage.reason });
|
|
22962
23164
|
} else {
|
|
22963
|
-
eligible.push({
|
|
23165
|
+
eligible.push({
|
|
23166
|
+
targetKey,
|
|
23167
|
+
label,
|
|
23168
|
+
metro,
|
|
23169
|
+
...otherMetros === void 0 ? {} : { otherMetros },
|
|
23170
|
+
submarkets,
|
|
23171
|
+
queries: queries2,
|
|
23172
|
+
mentionCoverage,
|
|
23173
|
+
citationCoverage
|
|
23174
|
+
});
|
|
22964
23175
|
}
|
|
22965
23176
|
}
|
|
22966
23177
|
const byLabel = (a, b) => compareText13(a.label, b.label) || compareText13(a.targetKey, b.targetKey);
|
|
@@ -22985,65 +23196,74 @@ function portfolioResponse(db, active, plan, query) {
|
|
|
22985
23196
|
provider: query.provider,
|
|
22986
23197
|
location: query.location
|
|
22987
23198
|
};
|
|
23199
|
+
const limit = query.limit ?? MEASUREMENT_PORTFOLIO_DEFAULT_LIMIT;
|
|
23200
|
+
const includeNestedMarkets = query.includeNestedMarkets ?? false;
|
|
23201
|
+
const locate = propertyLocations(plan);
|
|
22988
23202
|
if (!run) {
|
|
22989
23203
|
const rows2 = targets.map((target) => ({
|
|
22990
23204
|
...propertyDto2(target),
|
|
23205
|
+
...locate(target.stableKey),
|
|
23206
|
+
queries: plannedQueryCount(plan, target.stableKey, query.queryClass),
|
|
22991
23207
|
mentionCoverage: unavailable3("no_completed_run"),
|
|
22992
23208
|
citationCoverage: unavailable3("no_completed_run"),
|
|
22993
23209
|
flags: 0,
|
|
22994
|
-
|
|
22995
|
-
|
|
22996
|
-
|
|
23210
|
+
...namedInsteadFields([]),
|
|
23211
|
+
citedDomains: [],
|
|
23212
|
+
citedDomainsTotal: 0
|
|
22997
23213
|
})).sort(compareWeakest);
|
|
22998
|
-
const limit2 = query.limit ?? DEFAULT_LIMIT3;
|
|
22999
23214
|
return measurementPortfolioSummaryResponseSchema.parse({
|
|
23000
23215
|
portfolio: { groupKey: group?.stableKey ?? null, label: group?.label ?? null, measurementScope: null },
|
|
23001
23216
|
measurement,
|
|
23002
23217
|
queryClass: query.queryClass,
|
|
23218
|
+
engines: [],
|
|
23003
23219
|
metrics: {
|
|
23004
23220
|
propertiesMentioned: unavailable3("no_completed_run"),
|
|
23005
23221
|
mentionCoverage: unavailable3("no_completed_run"),
|
|
23006
23222
|
citationCoverage: unavailable3("no_completed_run")
|
|
23007
23223
|
},
|
|
23008
|
-
weakestProperties: rows2.slice(0,
|
|
23009
|
-
|
|
23224
|
+
weakestProperties: rows2.slice(0, limit),
|
|
23225
|
+
tiedAtWeakest: null,
|
|
23226
|
+
weakestAnswerSources: null,
|
|
23227
|
+
mentionRanking: mentionRanking(rows2, limit),
|
|
23010
23228
|
// Sorted through the same comparator as the measured branch. Plan order
|
|
23011
23229
|
// is `stableKey`, so emitting it raw put markets in an order the schema
|
|
23012
23230
|
// documents as worst-first and that changes the moment a run lands.
|
|
23013
|
-
|
|
23014
|
-
groupKey: g.stableKey,
|
|
23015
|
-
label: g.label,
|
|
23016
|
-
propertyCount: g.targetKeys.length,
|
|
23017
|
-
propertiesMentioned: unavailable3("no_completed_run"),
|
|
23018
|
-
mentionCoverage: unavailable3("no_completed_run"),
|
|
23019
|
-
citationCoverage: unavailable3("no_completed_run")
|
|
23020
|
-
})).sort(compareWeakestMarket),
|
|
23231
|
+
...marketRollup(plan, void 0, group, includeNestedMarkets, limit),
|
|
23021
23232
|
totalProperties: rows2.length,
|
|
23022
|
-
truncated: rows2.length >
|
|
23233
|
+
truncated: rows2.length > limit
|
|
23023
23234
|
});
|
|
23024
23235
|
}
|
|
23025
23236
|
const { materialized, overview, evaluator } = filteredOverviewWithDb(db, active, plan, run, filters, targetKeys);
|
|
23237
|
+
const readTarget2 = createTargetAnswerReader(plan, materialized, filters);
|
|
23238
|
+
const populations = new Map(targets.map((target) => [target.stableKey, readTarget2(target)]));
|
|
23026
23239
|
const measured = new Map(overview.properties.map((row) => [row.targetId, row]));
|
|
23027
23240
|
const ranked = targets.map((target) => {
|
|
23028
23241
|
const row = measured.get(target.stableKey);
|
|
23242
|
+
const population = populations.get(target.stableKey);
|
|
23029
23243
|
return {
|
|
23030
23244
|
target,
|
|
23245
|
+
population,
|
|
23031
23246
|
...propertyDto2(target),
|
|
23247
|
+
...locate(target.stableKey),
|
|
23248
|
+
queries: population.queries,
|
|
23032
23249
|
mentionCoverage: row ? coverageMetric2(row.mentionCoverage) : unavailable3("no_population"),
|
|
23033
23250
|
citationCoverage: row ? coverageMetric2(row.citationCoverage) : unavailable3("no_population"),
|
|
23034
23251
|
flags: row?.flags ?? 0
|
|
23035
23252
|
};
|
|
23036
23253
|
}).sort(compareWeakest);
|
|
23037
|
-
const
|
|
23038
|
-
const rows =
|
|
23039
|
-
const
|
|
23254
|
+
const displayed = ranked.slice(0, limit);
|
|
23255
|
+
const rows = displayed.map(({ target, population, ...row }) => {
|
|
23256
|
+
const domains = domainRows(population.sourceAnswers, MEASUREMENT_PORTFOLIO_ROW_EVIDENCE_LIMIT);
|
|
23040
23257
|
return {
|
|
23041
23258
|
...row,
|
|
23042
|
-
|
|
23043
|
-
|
|
23044
|
-
|
|
23259
|
+
...namedInsteadFields(recommendationRows(target, population)),
|
|
23260
|
+
citedDomains: domains.rows,
|
|
23261
|
+
citedDomainsTotal: domains.total
|
|
23045
23262
|
};
|
|
23046
23263
|
});
|
|
23264
|
+
const tie = weakestTie(ranked);
|
|
23265
|
+
const engines = /* @__PURE__ */ new Set();
|
|
23266
|
+
for (const population of populations.values()) for (const provider of population.providers) engines.add(provider);
|
|
23047
23267
|
return measurementPortfolioSummaryResponseSchema.parse({
|
|
23048
23268
|
portfolio: {
|
|
23049
23269
|
groupKey: group?.stableKey ?? null,
|
|
@@ -23052,18 +23272,46 @@ function portfolioResponse(db, active, plan, query) {
|
|
|
23052
23272
|
},
|
|
23053
23273
|
measurement,
|
|
23054
23274
|
queryClass: query.queryClass,
|
|
23275
|
+
engines: [...engines].sort(compareText13),
|
|
23055
23276
|
metrics: {
|
|
23056
23277
|
propertiesMentioned: countMetric2(overview.propertiesMentioned),
|
|
23057
23278
|
mentionCoverage: coverageMetric2(overview.mentionCoverage),
|
|
23058
23279
|
citationCoverage: coverageMetric2(overview.citationCoverage)
|
|
23059
23280
|
},
|
|
23060
|
-
weakestProperties: rows
|
|
23281
|
+
weakestProperties: rows,
|
|
23282
|
+
tiedAtWeakest: tie.summary,
|
|
23283
|
+
weakestAnswerSources: weakestAnswerSources([...displayed, ...tie.rows]),
|
|
23061
23284
|
mentionRanking: mentionRanking(ranked, limit),
|
|
23062
|
-
|
|
23285
|
+
...marketRollup(plan, { run, evaluator }, group, includeNestedMarkets, limit),
|
|
23063
23286
|
totalProperties: ranked.length,
|
|
23064
23287
|
truncated: ranked.length > limit
|
|
23065
23288
|
});
|
|
23066
23289
|
}
|
|
23290
|
+
function weakestTie(ranked) {
|
|
23291
|
+
if (ranked.length === 0) return { summary: null, rows: [] };
|
|
23292
|
+
const first = ranked[0];
|
|
23293
|
+
if (first.mentionCoverage.state !== "available" || first.citationCoverage.state !== "available") {
|
|
23294
|
+
return { summary: null, rows: [] };
|
|
23295
|
+
}
|
|
23296
|
+
const mentionRate3 = first.mentionCoverage.value;
|
|
23297
|
+
const citationRate = first.citationCoverage.value;
|
|
23298
|
+
const rows = ranked.filter((row) => row.mentionCoverage.state === "available" && row.mentionCoverage.value === mentionRate3 && row.citationCoverage.state === "available" && row.citationCoverage.value === citationRate);
|
|
23299
|
+
if (rows.length < 2) return { summary: null, rows: [] };
|
|
23300
|
+
return {
|
|
23301
|
+
summary: { count: rows.length, mentionRate: mentionRate3, citationRate, note: MEASUREMENT_PORTFOLIO_TIE_NOTE },
|
|
23302
|
+
rows
|
|
23303
|
+
};
|
|
23304
|
+
}
|
|
23305
|
+
function weakestAnswerSources(rows) {
|
|
23306
|
+
const properties = /* @__PURE__ */ new Set();
|
|
23307
|
+
const answers = /* @__PURE__ */ new Map();
|
|
23308
|
+
for (const row of rows) {
|
|
23309
|
+
properties.add(row.targetKey);
|
|
23310
|
+
for (const answer of row.population.sourceAnswers) answers.set(answer.snapshot.id, answer);
|
|
23311
|
+
}
|
|
23312
|
+
const domains = domainRows([...answers.values()], MEASUREMENT_PORTFOLIO_ANSWER_SOURCES_LIMIT);
|
|
23313
|
+
return { properties: properties.size, answers: answers.size, domains: domains.rows, domainTotal: domains.total };
|
|
23314
|
+
}
|
|
23067
23315
|
function propertyCompetitorsResponse(db, active, plan, query) {
|
|
23068
23316
|
const target = requireTarget3(plan, query.targetKey);
|
|
23069
23317
|
const run = selectMeasurementQuestionRun(db, active.version.projectId, active, query.runId);
|
|
@@ -23398,7 +23646,9 @@ async function measurementPortfolioReadRoutes(app) {
|
|
|
23398
23646
|
"/projects/:name/measurement-portfolio-summary",
|
|
23399
23647
|
async (request) => {
|
|
23400
23648
|
const project = resolveProject(app.db, request.params.name);
|
|
23401
|
-
const
|
|
23649
|
+
const nested = request.query.includeNestedMarkets;
|
|
23650
|
+
const raw = nested === "true" || nested === "false" ? { ...request.query, includeNestedMarkets: nested === "true" } : request.query;
|
|
23651
|
+
const query = parseLimitQuery(raw, measurementPortfolioSummaryQuerySchema, "Invalid measurement portfolio summary query");
|
|
23402
23652
|
const { active, plan } = activeV2Plan2(app.db, project.id, "Portfolio summary");
|
|
23403
23653
|
return portfolioResponse(app.db, active, plan, query);
|
|
23404
23654
|
}
|
|
@@ -27866,6 +28116,31 @@ async function analyticsRoutes(app) {
|
|
|
27866
28116
|
if (!Number.isInteger(n) || n <= 0) throw validationError('"limit" must be a positive integer');
|
|
27867
28117
|
limit = n;
|
|
27868
28118
|
}
|
|
28119
|
+
const parsedFilters = sourceBreakdownQuerySchema.safeParse({
|
|
28120
|
+
runId: request.query.runId,
|
|
28121
|
+
queryClass: request.query.queryClass,
|
|
28122
|
+
includeByQuery: request.query.includeByQuery
|
|
28123
|
+
});
|
|
28124
|
+
if (!parsedFilters.success) {
|
|
28125
|
+
throw validationError("Invalid source breakdown query", { issues: parsedFilters.error.issues });
|
|
28126
|
+
}
|
|
28127
|
+
const requestedRunId = parsedFilters.data.runId ?? null;
|
|
28128
|
+
const queryClass = parsedFilters.data.queryClass ?? "all";
|
|
28129
|
+
const includeByQuery = parsedFilters.data.includeByQuery !== "false" && parsedFilters.data.includeByQuery !== "0";
|
|
28130
|
+
const splitByClass = queryClass !== "all";
|
|
28131
|
+
const planClassified = splitByClass && activeMeasurementPlan(app.db, project.id)?.plan.schemaVersion === 2;
|
|
28132
|
+
const classifyQueryText = splitByClass && !planClassified ? projectQueryClassifier(project) : null;
|
|
28133
|
+
if (splitByClass && !planClassified && !classifyQueryText) {
|
|
28134
|
+
throw validationError(
|
|
28135
|
+
`Query class "${queryClass}" needs a brand name or alias on this project. Add one, or omit queryClass for a pooled source list.`
|
|
28136
|
+
);
|
|
28137
|
+
}
|
|
28138
|
+
const filters = {
|
|
28139
|
+
runId: requestedRunId,
|
|
28140
|
+
queryClass,
|
|
28141
|
+
queryClassBasis: !splitByClass ? null : planClassified ? "measurement-plan" : "query-text",
|
|
28142
|
+
includeByQuery
|
|
28143
|
+
};
|
|
27869
28144
|
const classifyCtx = {
|
|
27870
28145
|
projectDomains: effectiveDomains(project),
|
|
27871
28146
|
competitorDomains: app.db.select({ domain: competitors.domain }).from(competitors).where(eq30(competitors.projectId, project.id)).all().map((r) => r.domain)
|
|
@@ -27875,48 +28150,114 @@ async function analyticsRoutes(app) {
|
|
|
27875
28150
|
const mapped = surfaceClassFromCompetitorType(row.competitorType);
|
|
27876
28151
|
if (mapped) storedSurfaceClasses.set(normalizeProjectDomain(row.domain), mapped);
|
|
27877
28152
|
}
|
|
27878
|
-
|
|
28153
|
+
if (requestedRunId) {
|
|
28154
|
+
const requested = app.db.select().from(runs).where(and25(
|
|
28155
|
+
eq30(runs.id, requestedRunId),
|
|
28156
|
+
eq30(runs.projectId, project.id),
|
|
28157
|
+
eq30(runs.kind, RunKinds["answer-visibility"])
|
|
28158
|
+
)).get();
|
|
28159
|
+
if (!requested) throw notFound("Answer-visibility run", requestedRunId);
|
|
28160
|
+
if (requested.trigger === RunTriggers.probe) {
|
|
28161
|
+
throw validationError(`Run "${requestedRunId}" is a probe run; probes never enter source analytics.`);
|
|
28162
|
+
}
|
|
28163
|
+
if (requested.status !== RunStatuses.completed && requested.status !== RunStatuses.partial) {
|
|
28164
|
+
throw validationError(`Run "${requestedRunId}" is ${requested.status}; only completed or partial runs have source analytics.`);
|
|
28165
|
+
}
|
|
28166
|
+
if (!measuredWhole(app.db, requested)) {
|
|
28167
|
+
throw validationError(`Run "${requestedRunId}" did not fill every slot its measurement plan promised, so it is excluded from source analytics.`);
|
|
28168
|
+
}
|
|
28169
|
+
if (cutoff && requested.createdAt < cutoff) {
|
|
28170
|
+
throw validationError(`Run "${requestedRunId}" is older than the ${window} window. Omit window, or widen it, to read this run.`);
|
|
28171
|
+
}
|
|
28172
|
+
}
|
|
28173
|
+
const windowRuns = app.db.select().from(runs).where(and25(
|
|
28174
|
+
eq30(runs.projectId, project.id),
|
|
28175
|
+
eq30(runs.kind, RunKinds["answer-visibility"]),
|
|
28176
|
+
notProbeRun(),
|
|
28177
|
+
requestedRunId ? eq30(runs.id, requestedRunId) : void 0
|
|
28178
|
+
)).orderBy(desc12(runs.createdAt), desc12(runs.id)).all().filter((r) => r.status === "completed" || r.status === "partial").filter((r) => measuredWhole(app.db, r)).filter((r) => !cutoff || r.createdAt >= cutoff);
|
|
27879
28179
|
if (windowRuns.length === 0) {
|
|
27880
28180
|
return reply.send({
|
|
27881
|
-
|
|
27882
|
-
byQuery: {},
|
|
27883
|
-
ranked: buildRankedList(/* @__PURE__ */ new Map(), limit),
|
|
28181
|
+
ranked: buildRankedList(/* @__PURE__ */ new Map(), limit, 0, 0),
|
|
27884
28182
|
byProvider: {},
|
|
28183
|
+
providersWithoutSources: [],
|
|
28184
|
+
answerTotal: 0,
|
|
28185
|
+
runCount: 0,
|
|
28186
|
+
unclassifiedAnswers: 0,
|
|
28187
|
+
filters,
|
|
27885
28188
|
runId: "",
|
|
27886
28189
|
window,
|
|
27887
|
-
limit
|
|
28190
|
+
limit,
|
|
28191
|
+
overall: [],
|
|
28192
|
+
...includeByQuery ? { byQuery: {} } : {}
|
|
27888
28193
|
});
|
|
27889
28194
|
}
|
|
27890
28195
|
const latestGroup = groupRunsByCreatedAt(windowRuns)[0] ?? [];
|
|
27891
28196
|
const latestRunId = pickGroupRepresentative(latestGroup)?.id ?? windowRuns[0].id;
|
|
27892
28197
|
const windowRunIds = windowRuns.map((r) => r.id);
|
|
27893
|
-
const
|
|
28198
|
+
const loadedSnapshots = app.db.select({
|
|
28199
|
+
runId: querySnapshots.runId,
|
|
27894
28200
|
queryId: querySnapshots.queryId,
|
|
28201
|
+
queryText: querySnapshots.queryText,
|
|
27895
28202
|
query: queries.query,
|
|
27896
28203
|
provider: querySnapshots.provider,
|
|
27897
|
-
|
|
28204
|
+
citedDomains: querySnapshots.citedDomains,
|
|
28205
|
+
citedUrls: querySnapshots.citedUrls,
|
|
28206
|
+
measurementExecutionId: querySnapshots.measurementExecutionId
|
|
27898
28207
|
}).from(querySnapshots).leftJoin(queries, eq30(querySnapshots.queryId, queries.id)).where(inArray13(querySnapshots.runId, windowRunIds)).all();
|
|
28208
|
+
let unclassifiedAnswers = 0;
|
|
28209
|
+
let snapshots = loadedSnapshots;
|
|
28210
|
+
if (splitByClass) {
|
|
28211
|
+
const planClasses = planClassified ? planQueryClassesByRun(app.db, project.id, windowRuns) : null;
|
|
28212
|
+
snapshots = loadedSnapshots.filter((snap) => {
|
|
28213
|
+
let classes;
|
|
28214
|
+
if (planClasses) {
|
|
28215
|
+
classes = snap.measurementExecutionId ? planClasses.get(snap.runId)?.get(snap.measurementExecutionId) : void 0;
|
|
28216
|
+
} else {
|
|
28217
|
+
const text2 = snap.query ?? snap.queryText;
|
|
28218
|
+
classes = text2?.trim() ? /* @__PURE__ */ new Set([classifyQueryText(text2)]) : void 0;
|
|
28219
|
+
}
|
|
28220
|
+
if (!classes || classes.size === 0) {
|
|
28221
|
+
unclassifiedAnswers += 1;
|
|
28222
|
+
return false;
|
|
28223
|
+
}
|
|
28224
|
+
return classes.has(queryClass);
|
|
28225
|
+
});
|
|
28226
|
+
}
|
|
27899
28227
|
const overallCounts = /* @__PURE__ */ new Map();
|
|
27900
|
-
const
|
|
28228
|
+
const queryCounts = /* @__PURE__ */ new Map();
|
|
27901
28229
|
const overallDomains = /* @__PURE__ */ new Map();
|
|
27902
28230
|
const providerDomains = /* @__PURE__ */ new Map();
|
|
28231
|
+
const answersByProvider = /* @__PURE__ */ new Map();
|
|
28232
|
+
let answersWithSources = 0;
|
|
27903
28233
|
for (const snap of snapshots) {
|
|
27904
|
-
const
|
|
27905
|
-
const
|
|
27906
|
-
|
|
27907
|
-
|
|
28234
|
+
const domains = sourceDomainsOf(snap);
|
|
28235
|
+
const providerAnswers = answersByProvider.get(snap.provider) ?? { total: 0, withSources: 0 };
|
|
28236
|
+
providerAnswers.total += 1;
|
|
28237
|
+
if (domains.length > 0) {
|
|
28238
|
+
providerAnswers.withSources += 1;
|
|
28239
|
+
answersWithSources += 1;
|
|
28240
|
+
}
|
|
28241
|
+
answersByProvider.set(snap.provider, providerAnswers);
|
|
28242
|
+
const queryKey = includeByQuery && domains.length > 0 ? snap.query : null;
|
|
28243
|
+
let qCounts;
|
|
28244
|
+
if (queryKey) {
|
|
28245
|
+
qCounts = queryCounts.get(queryKey);
|
|
28246
|
+
if (!qCounts) {
|
|
28247
|
+
qCounts = /* @__PURE__ */ new Map();
|
|
28248
|
+
queryCounts.set(queryKey, qCounts);
|
|
28249
|
+
}
|
|
28250
|
+
}
|
|
28251
|
+
for (const host of domains) {
|
|
28252
|
+
const { category, label, domain } = categorizeSource(host);
|
|
27908
28253
|
const surfaceClass = classifySurfaceFromCategory(
|
|
27909
28254
|
domain,
|
|
27910
28255
|
category,
|
|
27911
28256
|
classifyCtx,
|
|
27912
28257
|
storedSurfaceClasses.get(normalizeProjectDomain(domain))
|
|
27913
28258
|
);
|
|
27914
|
-
|
|
27915
|
-
|
|
27916
|
-
oDomains.set(domain, (oDomains.get(domain) ?? 0) + 1);
|
|
27917
|
-
if (!qCounts.has(category)) qCounts.set(category, /* @__PURE__ */ new Map());
|
|
27918
|
-
const qDomains = qCounts.get(category);
|
|
27919
|
-
qDomains.set(domain, (qDomains.get(domain) ?? 0) + 1);
|
|
28259
|
+
bumpCategory(overallCounts, category, domain);
|
|
28260
|
+
if (qCounts) bumpCategory(qCounts, category, domain);
|
|
27920
28261
|
bumpDomain(overallDomains, domain, category, label, surfaceClass);
|
|
27921
28262
|
let pm = providerDomains.get(snap.provider);
|
|
27922
28263
|
if (!pm) {
|
|
@@ -27925,30 +28266,56 @@ async function analyticsRoutes(app) {
|
|
|
27925
28266
|
}
|
|
27926
28267
|
bumpDomain(pm, domain, category, label, surfaceClass);
|
|
27927
28268
|
}
|
|
27928
|
-
if (sources.length > 0 && snap.query) {
|
|
27929
|
-
byQuery[snap.query] = buildCategoryCounts(qCounts);
|
|
27930
|
-
}
|
|
27931
28269
|
}
|
|
27932
|
-
const
|
|
27933
|
-
const ranked = buildRankedList(overallDomains, limit);
|
|
28270
|
+
const ranked = buildRankedList(overallDomains, limit, snapshots.length, answersWithSources);
|
|
27934
28271
|
const byProvider = {};
|
|
27935
28272
|
for (const [provider, domains] of providerDomains) {
|
|
27936
|
-
|
|
28273
|
+
const answers = answersByProvider.get(provider);
|
|
28274
|
+
byProvider[provider] = buildRankedList(domains, limit, answers.total, answers.withSources);
|
|
28275
|
+
}
|
|
28276
|
+
const providersWithoutSources = [...answersByProvider].filter(([, answers]) => answers.withSources === 0).map(([provider]) => provider).sort();
|
|
28277
|
+
let byQuery;
|
|
28278
|
+
if (includeByQuery) {
|
|
28279
|
+
byQuery = {};
|
|
28280
|
+
for (const [query, counts2] of queryCounts) byQuery[query] = buildCategoryCounts(counts2);
|
|
27937
28281
|
}
|
|
27938
|
-
return reply.send({
|
|
28282
|
+
return reply.send({
|
|
28283
|
+
ranked,
|
|
28284
|
+
byProvider,
|
|
28285
|
+
providersWithoutSources,
|
|
28286
|
+
answerTotal: snapshots.length,
|
|
28287
|
+
runCount: windowRuns.length,
|
|
28288
|
+
unclassifiedAnswers,
|
|
28289
|
+
filters,
|
|
28290
|
+
runId: requestedRunId ?? latestRunId,
|
|
28291
|
+
window,
|
|
28292
|
+
limit,
|
|
28293
|
+
overall: buildCategoryCounts(overallCounts),
|
|
28294
|
+
...byQuery ? { byQuery } : {}
|
|
28295
|
+
});
|
|
27939
28296
|
});
|
|
27940
28297
|
}
|
|
27941
28298
|
var ANCHOR_SWEEP_SCAN_LIMIT = 60;
|
|
27942
28299
|
function isProviderInfraDomain(uri) {
|
|
27943
28300
|
return hostMatchesAnyDomain(uri, AI_PROVIDER_INFRA_DOMAINS);
|
|
27944
28301
|
}
|
|
27945
|
-
function
|
|
27946
|
-
const
|
|
27947
|
-
const
|
|
27948
|
-
|
|
27949
|
-
|
|
27950
|
-
(
|
|
27951
|
-
|
|
28302
|
+
function sourceDomainsOf(snapshot) {
|
|
28303
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
28304
|
+
for (const value of [...snapshot.citedDomains ?? [], ...snapshot.citedUrls ?? []]) {
|
|
28305
|
+
if (typeof value !== "string") continue;
|
|
28306
|
+
const host = hostOf(value);
|
|
28307
|
+
if (!host || isProviderInfraDomain(host)) continue;
|
|
28308
|
+
hosts.add(host);
|
|
28309
|
+
}
|
|
28310
|
+
return [...hosts];
|
|
28311
|
+
}
|
|
28312
|
+
function bumpCategory(counts2, category, domain) {
|
|
28313
|
+
let domains = counts2.get(category);
|
|
28314
|
+
if (!domains) {
|
|
28315
|
+
domains = /* @__PURE__ */ new Map();
|
|
28316
|
+
counts2.set(category, domains);
|
|
28317
|
+
}
|
|
28318
|
+
domains.set(domain, (domains.get(domain) ?? 0) + 1);
|
|
27952
28319
|
}
|
|
27953
28320
|
function bucketSizeForSpan(spanDays) {
|
|
27954
28321
|
if (spanDays <= 14) return 1;
|
|
@@ -28103,7 +28470,7 @@ function bumpDomain(map, domain, category, label, surfaceClass) {
|
|
|
28103
28470
|
if (existing) existing.count += 1;
|
|
28104
28471
|
else map.set(domain, { domain, count: 1, category, label, surfaceClass });
|
|
28105
28472
|
}
|
|
28106
|
-
function buildRankedList(domains, limit) {
|
|
28473
|
+
function buildRankedList(domains, limit, answerTotal, answersWithSources) {
|
|
28107
28474
|
const all = [...domains.values()];
|
|
28108
28475
|
const totalCitedSlots = all.reduce((sum, d) => sum + d.count, 0);
|
|
28109
28476
|
const domainTotal = all.length;
|
|
@@ -28113,6 +28480,7 @@ function buildRankedList(domains, limit) {
|
|
|
28113
28480
|
domain: d.domain,
|
|
28114
28481
|
count: d.count,
|
|
28115
28482
|
percentage: totalCitedSlots > 0 ? round4(d.count / totalCitedSlots) : 0,
|
|
28483
|
+
answerShare: answerTotal > 0 ? round4(d.count / answerTotal) : 0,
|
|
28116
28484
|
category: d.category,
|
|
28117
28485
|
label: d.label,
|
|
28118
28486
|
surfaceClass: d.surfaceClass
|
|
@@ -28134,6 +28502,8 @@ function buildRankedList(domains, limit) {
|
|
|
28134
28502
|
})).sort((a, b) => b.count - a.count || a.surfaceClass.localeCompare(b.surfaceClass));
|
|
28135
28503
|
return {
|
|
28136
28504
|
totalCitedSlots,
|
|
28505
|
+
answerTotal,
|
|
28506
|
+
answersWithSources,
|
|
28137
28507
|
domainTotal,
|
|
28138
28508
|
entries,
|
|
28139
28509
|
truncatedDomainCount: domainTotal - shownEntries.length,
|
|
@@ -36589,7 +36959,7 @@ var competitorLandscapeGroupKeyParameter = {
|
|
|
36589
36959
|
var competitorLandscapeScopeParameter = {
|
|
36590
36960
|
name: "scope",
|
|
36591
36961
|
in: "query",
|
|
36592
|
-
description: 'Set to "all-markets" to aggregate raw stored evidence across every Advanced Measurement market. It cannot be combined with groupKey.',
|
|
36962
|
+
description: 'Set to "all-markets" to aggregate raw stored evidence across every Property in the Advanced Measurement plan, including Properties in no market. It cannot be combined with groupKey.',
|
|
36593
36963
|
schema: { type: "string", enum: ["project", "all-markets"] }
|
|
36594
36964
|
};
|
|
36595
36965
|
var competitorLandscapeProviderParameter = {
|
|
@@ -36613,7 +36983,7 @@ var competitorLandscapeGroupByParameter = {
|
|
|
36613
36983
|
var competitorLandscapeQueryClassParameter = {
|
|
36614
36984
|
name: "queryClass",
|
|
36615
36985
|
in: "query",
|
|
36616
|
-
description: "Restrict evidence to a
|
|
36986
|
+
description: "Restrict evidence to a query class. Advanced groups use their frozen assignment classes; simple projects classify stored query text. On a project with an active v2 measurement plan, branded or non-brand without scope or groupKey defaults to scope=all-markets, so the class comes from the plan rather than the text classifier.",
|
|
36617
36987
|
schema: { type: "string", enum: ["all", "branded", "non-brand"] }
|
|
36618
36988
|
};
|
|
36619
36989
|
var competitorLandscapeLocationParameter = {
|
|
@@ -36628,6 +36998,24 @@ var competitorLandscapeRunIdParameter = {
|
|
|
36628
36998
|
description: "Restrict evidence to one stored answer-visibility run.",
|
|
36629
36999
|
schema: stringSchema
|
|
36630
37000
|
};
|
|
37001
|
+
var sourcesRunIdParameter = {
|
|
37002
|
+
name: "runId",
|
|
37003
|
+
in: "query",
|
|
37004
|
+
description: "Read one stored answer-visibility run instead of pooling every run in the window. An unknown id is 404; a probe, unfinished, partially measured, or out-of-window run is 400.",
|
|
37005
|
+
schema: stringSchema
|
|
37006
|
+
};
|
|
37007
|
+
var sourcesQueryClassParameter = {
|
|
37008
|
+
name: "queryClass",
|
|
37009
|
+
in: "query",
|
|
37010
|
+
description: "Restrict to branded or non-brand answers. With an active v2 measurement plan the class comes from each run's frozen plan assignments (the same answers as the competitor landscape at scope=all-markets); otherwise from the project's brand-name classifier over the query text. Answers neither basis can place are excluded and counted in unclassifiedAnswers. Defaults to all, which pools both classes.",
|
|
37011
|
+
schema: { type: "string", enum: ["all", "branded", "non-brand"] }
|
|
37012
|
+
};
|
|
37013
|
+
var sourcesIncludeByQueryParameter = {
|
|
37014
|
+
name: "includeByQuery",
|
|
37015
|
+
in: "query",
|
|
37016
|
+
description: "Set to false to omit the per-query breakdown (byQuery), which grows with every tracked query. Defaults to true.",
|
|
37017
|
+
schema: { type: "string", enum: ["true", "false", "1", "0"] }
|
|
37018
|
+
};
|
|
36631
37019
|
var analyticsStartDateParameter = {
|
|
36632
37020
|
name: "startDate",
|
|
36633
37021
|
in: "query",
|
|
@@ -37435,7 +37823,7 @@ var routeCatalog = [
|
|
|
37435
37823
|
method: "get",
|
|
37436
37824
|
path: "/api/v1/projects/{name}/measurement-portfolio-summary",
|
|
37437
37825
|
summary: "Get the weakest measured Properties",
|
|
37438
|
-
description: "Returns a compact, revision-pinned portfolio ranking from stored results, plus a worst-first roll-up
|
|
37826
|
+
description: "Returns a compact, revision-pinned portfolio ranking from stored results, plus a worst-first market roll-up. It defaults to non-brand queries, ranks measured mention coverage before citation coverage, and keeps unavailable rows separate from measured weakness. Every Property row carries its metro, submarkets and query count; weakest rows also carry namedInsteadInAnswerText (names written in the answer text of answers that neither named nor cited the Property, counted by answer) and citedDomains (domains cited by the Property's answers, counted by answer, every engine included). tiedAtWeakest reports Properties sharing the weakest rates, and weakestAnswerSources ranks the domains cited across the weakest and tied Properties, each answer once. markets holds one level by default (top-level markets, or the selected group's direct children), worst-first and capped at limit; includeNestedMarkets returns every level. Every market is scoped to the displayed run, so a market row matches that market read with groupKey; markets may share Properties and never sum to the portfolio totals. It never starts provider work.",
|
|
37439
37827
|
tags: ["measurement-plans"],
|
|
37440
37828
|
parameters: [
|
|
37441
37829
|
nameParameter,
|
|
@@ -37444,7 +37832,8 @@ var routeCatalog = [
|
|
|
37444
37832
|
{ name: "provider", in: "query", description: "Restrict to one answer provider.", schema: stringSchema },
|
|
37445
37833
|
{ name: "location", in: "query", description: "Restrict to one execution location label.", schema: stringSchema },
|
|
37446
37834
|
{ name: "runId", in: "query", description: "Read this completed or partial active-revision run, including a named spot check.", schema: stringSchema },
|
|
37447
|
-
{ name: "limit", in: "query", description: "
|
|
37835
|
+
{ name: "limit", in: "query", description: "Rows per list (weakest Properties, both mention rankings, markets). Defaults to 4, maximum 50.", schema: { type: "integer", minimum: 1, maximum: 50 } },
|
|
37836
|
+
{ name: "includeNestedMarkets", in: "query", description: "Return every market in scope at every level, uncapped. Defaults to false: one level (top-level markets, or the selected group's direct children), capped at limit.", schema: { type: "boolean" } }
|
|
37448
37837
|
],
|
|
37449
37838
|
responses: {
|
|
37450
37839
|
200: jsonResponse("Compact portfolio summary returned.", "MeasurementPortfolioSummaryResponse"),
|
|
@@ -38591,11 +38980,20 @@ var routeCatalog = [
|
|
|
38591
38980
|
method: "get",
|
|
38592
38981
|
path: "/api/v1/projects/{name}/analytics/sources",
|
|
38593
38982
|
summary: "Get source origin analytics",
|
|
38983
|
+
description: "Cited domains ranked by how many answers cite them, read from each answer's stored source list (citedDomains plus citedUrls), so every provider is counted, Gemini included. A domain counts at most once per answer. Without runId the response pools every run in the window (runCount says how many); without queryClass it pools branded and non-brand answers.",
|
|
38594
38984
|
tags: ["analytics"],
|
|
38595
|
-
parameters: [
|
|
38985
|
+
parameters: [
|
|
38986
|
+
nameParameter,
|
|
38987
|
+
analyticsWindowParameter,
|
|
38988
|
+
limitQueryParameter,
|
|
38989
|
+
sourcesRunIdParameter,
|
|
38990
|
+
sourcesQueryClassParameter,
|
|
38991
|
+
sourcesIncludeByQueryParameter
|
|
38992
|
+
],
|
|
38596
38993
|
responses: {
|
|
38597
38994
|
200: jsonResponse("Source breakdown returned.", "SourceBreakdownDto"),
|
|
38598
|
-
|
|
38995
|
+
400: errorResponse("Invalid query parameters."),
|
|
38996
|
+
404: errorResponse("Project or run not found.")
|
|
38599
38997
|
}
|
|
38600
38998
|
},
|
|
38601
38999
|
{
|
|
@@ -55513,7 +55911,7 @@ async function cdpRoutes(app, opts) {
|
|
|
55513
55911
|
browserOnlyCited++;
|
|
55514
55912
|
disagreed++;
|
|
55515
55913
|
}
|
|
55516
|
-
const
|
|
55914
|
+
const parseGroundingSources = (snap) => {
|
|
55517
55915
|
if (!snap?.rawResponse) return [];
|
|
55518
55916
|
try {
|
|
55519
55917
|
const raw = JSON.parse(snap.rawResponse);
|
|
@@ -55528,13 +55926,13 @@ async function cdpRoutes(app, opts) {
|
|
|
55528
55926
|
provider: api.provider,
|
|
55529
55927
|
citationState: api.citationState,
|
|
55530
55928
|
citedDomains: api.citedDomains,
|
|
55531
|
-
groundingSources:
|
|
55929
|
+
groundingSources: parseGroundingSources(api)
|
|
55532
55930
|
} : null,
|
|
55533
55931
|
browser: browser ? {
|
|
55534
55932
|
provider: browser.provider,
|
|
55535
55933
|
citationState: browser.citationState,
|
|
55536
55934
|
citedDomains: browser.citedDomains,
|
|
55537
|
-
groundingSources:
|
|
55935
|
+
groundingSources: parseGroundingSources(browser),
|
|
55538
55936
|
screenshotUrl: browser.screenshotPath ? `${opts.routePrefix ?? "/api/v1"}/screenshots/${browser.id}` : void 0
|
|
55539
55937
|
} : null,
|
|
55540
55938
|
agreement
|
|
@@ -87546,6 +87944,10 @@ export {
|
|
|
87546
87944
|
migrate,
|
|
87547
87945
|
OperationalLogStore,
|
|
87548
87946
|
groupRunsByCreatedAt,
|
|
87947
|
+
determineCitationState,
|
|
87948
|
+
computeCompetitorOverlap,
|
|
87949
|
+
computeCitedCompetitorDomains,
|
|
87950
|
+
extractRecommendedCompetitors,
|
|
87549
87951
|
resolveTrustProxy,
|
|
87550
87952
|
USER_SESSION_COOKIE_NAME,
|
|
87551
87953
|
anyUsersExist,
|
|
@@ -87634,10 +88036,6 @@ export {
|
|
|
87634
88036
|
addLogListener,
|
|
87635
88037
|
createLogger,
|
|
87636
88038
|
createFastifyLogger,
|
|
87637
|
-
determineCitationState,
|
|
87638
|
-
computeCompetitorOverlap,
|
|
87639
|
-
computeCitedCompetitorDomains,
|
|
87640
|
-
extractRecommendedCompetitors,
|
|
87641
88039
|
HISTORY_WINDOW_RUNS,
|
|
87642
88040
|
IntelligenceService
|
|
87643
88041
|
};
|