4958 lines
236 KiB
JavaScript
4958 lines
236 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION = void 0;
|
||
exports.executeAssistantMcpDiscoveryPilot = executeAssistantMcpDiscoveryPilot;
|
||
const addressMcpClient_1 = require("./addressMcpClient");
|
||
const assistantMcpDiscoveryRuntimeAdapter_1 = require("./assistantMcpDiscoveryRuntimeAdapter");
|
||
const assistantMcpDiscoveryPolicy_1 = require("./assistantMcpDiscoveryPolicy");
|
||
const addressRecipeCatalog_1 = require("./addressRecipeCatalog");
|
||
exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION = "assistant_mcp_discovery_pilot_executor_v1";
|
||
const DEFAULT_DEPS = {
|
||
executeAddressMcpQuery: addressMcpClient_1.executeAddressMcpQuery,
|
||
executeAddressMcpMetadata: addressMcpClient_1.executeAddressMcpMetadata
|
||
};
|
||
const ENTITY_RESOLUTION_COUNTERPARTY_LOOKUP_LIMIT = 1000;
|
||
const ENTITY_RESOLUTION_COUNTERPARTY_QUERY_TEMPLATE = `
|
||
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||
ПРЕДСТАВЛЕНИЕ(Контрагенты.Ссылка) КАК Контрагент,
|
||
ПРЕДСТАВЛЕНИЕ(Контрагенты.Ссылка) КАК Counterparty,
|
||
Контрагенты.Ссылка КАК КонтрагентСсылка,
|
||
Контрагенты.Ссылка КАК CounterpartyRef,
|
||
Контрагенты.Наименование КАК Наименование
|
||
ИЗ
|
||
Справочник.Контрагенты КАК Контрагенты
|
||
`;
|
||
const ENTITY_RESOLUTION_STOPWORDS = new Set([
|
||
"ооо",
|
||
"ао",
|
||
"зао",
|
||
"ип",
|
||
"llc",
|
||
"ltd",
|
||
"company",
|
||
"контрагент",
|
||
"counterparty",
|
||
"поставщик",
|
||
"supplier",
|
||
"клиент",
|
||
"customer",
|
||
"в",
|
||
"1с",
|
||
"1c",
|
||
"найди",
|
||
"найти",
|
||
"поищи",
|
||
"search",
|
||
"find"
|
||
]);
|
||
function toNonEmptyString(value) {
|
||
if (value === null || value === undefined) {
|
||
return null;
|
||
}
|
||
const text = String(value).trim();
|
||
return text.length > 0 ? text : null;
|
||
}
|
||
function normalizeReasonCode(value) {
|
||
const normalized = value
|
||
.trim()
|
||
.replace(/[^\p{L}\p{N}_.:-]+/gu, "_")
|
||
.replace(/^_+|_+$/g, "")
|
||
.toLowerCase();
|
||
return normalized.length > 0 ? normalized.slice(0, 120) : null;
|
||
}
|
||
function pushReason(target, value) {
|
||
const normalized = normalizeReasonCode(value);
|
||
if (normalized && !target.includes(normalized)) {
|
||
target.push(normalized);
|
||
}
|
||
}
|
||
function pushUnique(target, value) {
|
||
const text = value.trim();
|
||
if (text && !target.includes(text)) {
|
||
target.push(text);
|
||
}
|
||
}
|
||
function aggregationAxisForPlanner(planner) {
|
||
const axis = toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.asked_aggregation_axis)?.toLowerCase();
|
||
return axis === "month" ? "month" : null;
|
||
}
|
||
function firstEntityCandidate(planner) {
|
||
const candidates = planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
|
||
for (const candidate of candidates) {
|
||
const text = toNonEmptyString(candidate);
|
||
if (text) {
|
||
return text;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
function dateScopeToFilters(dateScope) {
|
||
if (!dateScope) {
|
||
return {};
|
||
}
|
||
const yearMatch = dateScope.match(/^(\d{4})$/);
|
||
if (yearMatch) {
|
||
return {
|
||
period_from: `${yearMatch[1]}-01-01`,
|
||
period_to: `${yearMatch[1]}-12-31`
|
||
};
|
||
}
|
||
const rangeMatch = dateScope.match(/^(\d{4}-\d{2}-\d{2})\.\.(\d{4}-\d{2}-\d{2})$/);
|
||
if (rangeMatch) {
|
||
return {
|
||
period_from: rangeMatch[1],
|
||
period_to: rangeMatch[2]
|
||
};
|
||
}
|
||
const dateMatch = dateScope.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||
if (dateMatch) {
|
||
const date = `${dateMatch[1]}-${dateMatch[2]}-${dateMatch[3]}`;
|
||
return {
|
||
period_from: date,
|
||
period_to: date
|
||
};
|
||
}
|
||
return {};
|
||
}
|
||
function asOfDateFromDateScope(dateScope) {
|
||
if (!dateScope) {
|
||
return null;
|
||
}
|
||
const dateMatch = dateScope.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
||
if (dateMatch) {
|
||
return `${dateMatch[1]}-${dateMatch[2]}-${dateMatch[3]}`;
|
||
}
|
||
const monthMatch = dateScope.match(/^(\d{4})-(\d{2})$/);
|
||
if (monthMatch) {
|
||
const year = Number(monthMatch[1]);
|
||
const month = Number(monthMatch[2]);
|
||
if (Number.isFinite(year) && Number.isFinite(month) && month >= 1 && month <= 12) {
|
||
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
|
||
return `${monthMatch[1]}-${monthMatch[2]}-${String(lastDay).padStart(2, "0")}`;
|
||
}
|
||
}
|
||
const yearMatch = dateScope.match(/^(\d{4})$/);
|
||
return yearMatch ? `${yearMatch[1]}-12-31` : null;
|
||
}
|
||
function buildLifecycleFilters(planner) {
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const counterparty = firstEntityCandidate(planner);
|
||
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
|
||
const dateScope = toNonEmptyString(meaning?.explicit_date_scope);
|
||
return {
|
||
...dateScopeToFilters(dateScope),
|
||
...(counterparty ? { counterparty } : {}),
|
||
...(organization ? { organization } : {}),
|
||
limit: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||
sort: "period_asc"
|
||
};
|
||
}
|
||
function buildValueFlowFilters(planner) {
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const counterparty = firstEntityCandidate(planner);
|
||
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
|
||
const dateScope = toNonEmptyString(meaning?.explicit_date_scope);
|
||
return {
|
||
...dateScopeToFilters(dateScope),
|
||
...(counterparty ? { counterparty } : {}),
|
||
...(organization ? { organization } : {}),
|
||
limit: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||
sort: "period_asc"
|
||
};
|
||
}
|
||
function buildBusinessOverviewProfileFilters(planner) {
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
|
||
const dateScope = toNonEmptyString(meaning?.explicit_date_scope);
|
||
return {
|
||
...dateScopeToFilters(dateScope),
|
||
...(organization ? { organization } : {}),
|
||
limit: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||
sort: "period_asc"
|
||
};
|
||
}
|
||
function buildBusinessOverviewTaxFilters(planner) {
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
|
||
const dateScope = toNonEmptyString(meaning?.explicit_date_scope);
|
||
const periodFilters = dateScopeToFilters(dateScope);
|
||
if (!periodFilters.period_from || !periodFilters.period_to) {
|
||
return null;
|
||
}
|
||
return {
|
||
...periodFilters,
|
||
...(organization ? { organization } : {})
|
||
};
|
||
}
|
||
function buildBusinessOverviewDebtFilters(planner) {
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
|
||
const dateScope = toNonEmptyString(meaning?.explicit_date_scope);
|
||
const asOfDate = asOfDateFromDateScope(dateScope);
|
||
if (!asOfDate) {
|
||
return null;
|
||
}
|
||
return {
|
||
...dateScopeToFilters(dateScope),
|
||
as_of_date: asOfDate,
|
||
...(organization ? { organization } : {}),
|
||
limit: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||
sort: "period_asc"
|
||
};
|
||
}
|
||
function buildBusinessOverviewInventoryFilters(planner) {
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
|
||
const dateScope = toNonEmptyString(meaning?.explicit_date_scope);
|
||
const asOfDate = asOfDateFromDateScope(dateScope);
|
||
if (!asOfDate) {
|
||
return null;
|
||
}
|
||
return {
|
||
...dateScopeToFilters(dateScope),
|
||
as_of_date: asOfDate,
|
||
...(organization ? { organization } : {}),
|
||
limit: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||
sort: "period_asc"
|
||
};
|
||
}
|
||
function buildBusinessOverviewTradingMarginFilters(planner) {
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
|
||
const dateScope = toNonEmptyString(meaning?.explicit_date_scope);
|
||
const periodFilters = dateScopeToFilters(dateScope);
|
||
if (!periodFilters.period_from || !periodFilters.period_to) {
|
||
return null;
|
||
}
|
||
return {
|
||
...periodFilters,
|
||
...(organization ? { organization } : {}),
|
||
limit: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||
sort: "period_asc"
|
||
};
|
||
}
|
||
function buildInventoryExactFilters(planner) {
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const subject = firstEntityCandidate(planner);
|
||
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
|
||
const dateScope = toNonEmptyString(meaning?.explicit_date_scope);
|
||
const asOfDate = asOfDateFromDateScope(dateScope);
|
||
const filters = {
|
||
...dateScopeToFilters(dateScope),
|
||
...(asOfDate ? { as_of_date: asOfDate } : {}),
|
||
...(organization ? { organization } : {}),
|
||
limit: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||
sort: "period_asc"
|
||
};
|
||
if (planner.selected_chain_id === "inventory_purchase_provenance" ||
|
||
planner.selected_chain_id === "inventory_sale_trace") {
|
||
return {
|
||
...filters,
|
||
...(subject ? { item: subject } : {})
|
||
};
|
||
}
|
||
if (planner.selected_chain_id === "inventory_supplier_overlap") {
|
||
return {
|
||
...filters,
|
||
...(subject ? { counterparty: subject } : {})
|
||
};
|
||
}
|
||
return filters;
|
||
}
|
||
function organizationScopeForPlanner(planner) {
|
||
return toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.explicit_organization_scope);
|
||
}
|
||
function rankingNeedForPlanner(planner) {
|
||
const rankingNeed = toNonEmptyString(planner.data_need_graph?.ranking_need)?.toLowerCase();
|
||
if (rankingNeed === "top_desc" || rankingNeed === "bottom_asc") {
|
||
return rankingNeed;
|
||
}
|
||
return null;
|
||
}
|
||
function normalizeEntityResolutionText(value) {
|
||
return String(value ?? "")
|
||
.toLowerCase()
|
||
.replace(/ё/g, "е")
|
||
.replace(/[«»"'`]/g, " ")
|
||
.replace(/[^\p{L}\p{N}\s-]+/gu, " ")
|
||
.replace(/\s+/g, " ")
|
||
.trim();
|
||
}
|
||
function tokenizeEntityResolutionText(value) {
|
||
return normalizeEntityResolutionText(value)
|
||
.split(" ")
|
||
.map((token) => token.trim())
|
||
.filter((token) => token.length >= 2 && !ENTITY_RESOLUTION_STOPWORDS.has(token));
|
||
}
|
||
function isLowQualityEntityResolutionAnchor(value) {
|
||
return tokenizeEntityResolutionText(value).length <= 0;
|
||
}
|
||
function entityResolutionCandidateName(row) {
|
||
const candidates = [
|
||
row["Контрагент"],
|
||
row["Counterparty"],
|
||
row["Наименование"],
|
||
row["name"],
|
||
row["Name"],
|
||
row["registrator"],
|
||
row["Registrator"]
|
||
];
|
||
for (const candidate of candidates) {
|
||
const text = toNonEmptyString(candidate);
|
||
if (text) {
|
||
return text;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
function entityResolutionCandidateRef(row) {
|
||
const candidates = [row["КонтрагентСсылка"], row["CounterpartyRef"], row["ref"], row["Ref"]];
|
||
for (const candidate of candidates) {
|
||
const text = toNonEmptyString(candidate);
|
||
if (text) {
|
||
return text;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
function scoreEntityResolutionCandidate(name, requested) {
|
||
const normalizedName = normalizeEntityResolutionText(name);
|
||
const normalizedRequested = normalizeEntityResolutionText(requested);
|
||
const requestedTokens = tokenizeEntityResolutionText(requested);
|
||
if (!normalizedName || !normalizedRequested || requestedTokens.length <= 0) {
|
||
return null;
|
||
}
|
||
let score = 0;
|
||
if (normalizedName === normalizedRequested) {
|
||
score += 10_000;
|
||
}
|
||
else if (normalizedName.includes(normalizedRequested)) {
|
||
score += 5_000;
|
||
}
|
||
else if (normalizedRequested.includes(normalizedName) && normalizedName.length >= 4) {
|
||
score += 2_000;
|
||
}
|
||
for (const token of requestedTokens) {
|
||
if (!normalizedName.includes(token)) {
|
||
return null;
|
||
}
|
||
score += Math.max(40, token.length * 20);
|
||
}
|
||
score -= Math.abs(normalizedName.length - normalizedRequested.length);
|
||
return score;
|
||
}
|
||
function deriveEntityResolution(result, requestedEntity) {
|
||
if (!result || result.error || !requestedEntity) {
|
||
return null;
|
||
}
|
||
const checkedCandidates = uniqueCandidateStrings(result.raw_rows
|
||
.map((row) => entityResolutionCandidateName(row))
|
||
.filter((value) => Boolean(value)));
|
||
const scoredCandidates = checkedCandidates
|
||
.map((name) => {
|
||
const score = scoreEntityResolutionCandidate(name, requestedEntity);
|
||
return score === null ? null : { name, score };
|
||
})
|
||
.filter((value) => Boolean(value))
|
||
.sort((left, right) => right.score - left.score || left.name.length - right.name.length || left.name.localeCompare(right.name, "ru"));
|
||
if (scoredCandidates.length <= 0) {
|
||
return {
|
||
requested_entity: requestedEntity,
|
||
resolution_status: "not_found",
|
||
resolved_entity: null,
|
||
resolved_reference: null,
|
||
matched_rows: result.rows.length,
|
||
checked_candidates: checkedCandidates.slice(0, 12),
|
||
ambiguity_candidates: [],
|
||
confidence: null,
|
||
inference_basis: "catalog_counterparty_search_rows"
|
||
};
|
||
}
|
||
const bestCandidate = scoredCandidates[0];
|
||
const bestNormalized = normalizeEntityResolutionText(bestCandidate.name);
|
||
const requestedNormalized = normalizeEntityResolutionText(requestedEntity);
|
||
const requestedTokens = tokenizeEntityResolutionText(requestedEntity);
|
||
const exactMatch = bestNormalized === requestedNormalized;
|
||
const strongContains = requestedTokens.length > 1 && bestNormalized.includes(requestedNormalized);
|
||
const topCandidates = scoredCandidates.filter((candidate) => candidate.score === bestCandidate.score);
|
||
if (topCandidates.length > 1 && !exactMatch && !strongContains) {
|
||
return {
|
||
requested_entity: requestedEntity,
|
||
resolution_status: "ambiguous",
|
||
resolved_entity: null,
|
||
resolved_reference: null,
|
||
matched_rows: result.rows.length,
|
||
checked_candidates: checkedCandidates.slice(0, 12),
|
||
ambiguity_candidates: topCandidates.map((candidate) => candidate.name).slice(0, 6),
|
||
confidence: "low",
|
||
inference_basis: "catalog_counterparty_search_rows"
|
||
};
|
||
}
|
||
const matchedRow = result.raw_rows.find((row) => normalizeEntityResolutionText(entityResolutionCandidateName(row)) === bestNormalized) ?? null;
|
||
return {
|
||
requested_entity: requestedEntity,
|
||
resolution_status: "resolved",
|
||
resolved_entity: bestCandidate.name,
|
||
resolved_reference: matchedRow ? entityResolutionCandidateRef(matchedRow) : null,
|
||
matched_rows: result.rows.length,
|
||
checked_candidates: checkedCandidates.slice(0, 12),
|
||
ambiguity_candidates: [],
|
||
confidence: exactMatch ? "high" : strongContains ? "medium" : "low",
|
||
inference_basis: "catalog_counterparty_search_rows"
|
||
};
|
||
}
|
||
function uniqueCandidateStrings(values) {
|
||
const result = [];
|
||
for (const value of values) {
|
||
pushUnique(result, value);
|
||
}
|
||
return result;
|
||
}
|
||
function isLifecyclePilotEligible(planner) {
|
||
if (planner.selected_chain_id === "lifecycle") {
|
||
return true;
|
||
}
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const domain = String(meaning?.asked_domain_family ?? "").toLowerCase();
|
||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||
const combined = `${domain} ${action}`;
|
||
return (planner.proposed_primitives.includes("query_documents") &&
|
||
(combined.includes("lifecycle") || combined.includes("activity") || combined.includes("duration") || combined.includes("age")));
|
||
}
|
||
function isDocumentEvidencePilotEligible(planner) {
|
||
if (planner.selected_chain_id === "document_evidence") {
|
||
return true;
|
||
}
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const domain = String(meaning?.asked_domain_family ?? "").toLowerCase();
|
||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
|
||
const combined = `${domain} ${action} ${unsupported}`;
|
||
return (planner.proposed_primitives.includes("query_documents") &&
|
||
(combined.includes("document") || combined.includes("list_documents")));
|
||
}
|
||
function isMovementEvidencePilotEligible(planner) {
|
||
if (planner.selected_chain_id === "movement_evidence") {
|
||
return true;
|
||
}
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const domain = String(meaning?.asked_domain_family ?? "").toLowerCase();
|
||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
|
||
const semanticNeed = String(planner.semantic_data_need ?? "").toLowerCase();
|
||
const combined = `${domain} ${action} ${unsupported} ${semanticNeed}`;
|
||
return (planner.proposed_primitives.includes("query_movements") &&
|
||
(combined.includes("movement") ||
|
||
combined.includes("movements") ||
|
||
combined.includes("bank_operations") ||
|
||
combined.includes("movement_evidence") ||
|
||
combined.includes("list_movements")));
|
||
}
|
||
function isValueFlowPilotEligible(planner) {
|
||
if (planner.selected_chain_id === "value_flow" ||
|
||
planner.selected_chain_id === "value_flow_ranking" ||
|
||
planner.selected_chain_id === "value_flow_comparison") {
|
||
return true;
|
||
}
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const domain = String(meaning?.asked_domain_family ?? "").toLowerCase();
|
||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
|
||
const combined = `${domain} ${action} ${unsupported}`;
|
||
return (planner.proposed_primitives.includes("query_movements") &&
|
||
(combined.includes("turnover") ||
|
||
combined.includes("revenue") ||
|
||
combined.includes("payment") ||
|
||
combined.includes("payout") ||
|
||
combined.includes("value")));
|
||
}
|
||
function isBusinessOverviewPilotEligible(planner) {
|
||
return planner.selected_chain_id === "business_overview";
|
||
}
|
||
function isInventoryPilotEligible(planner) {
|
||
return (planner.selected_chain_id === "inventory_stock_snapshot" ||
|
||
planner.selected_chain_id === "inventory_supplier_overlap" ||
|
||
planner.selected_chain_id === "inventory_purchase_provenance" ||
|
||
planner.selected_chain_id === "inventory_sale_trace");
|
||
}
|
||
function isMetadataPilotEligible(planner) {
|
||
if (planner.selected_chain_id === "metadata_inspection" ||
|
||
planner.selected_chain_id === "metadata_lane_clarification" ||
|
||
planner.selected_chain_id === "catalog_drilldown") {
|
||
return true;
|
||
}
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const domain = String(meaning?.asked_domain_family ?? "").toLowerCase();
|
||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
|
||
const semanticNeed = String(planner.semantic_data_need ?? "").toLowerCase();
|
||
const combined = `${domain} ${action} ${unsupported} ${semanticNeed}`;
|
||
return (planner.proposed_primitives.includes("inspect_1c_metadata") &&
|
||
(combined.includes("metadata") ||
|
||
combined.includes("schema") ||
|
||
combined.includes("catalog") ||
|
||
combined.includes("inspect_documents") ||
|
||
combined.includes("inspect_registers") ||
|
||
combined.includes("inspect_fields")));
|
||
}
|
||
function isEntityResolutionPilotEligible(planner) {
|
||
if (planner.selected_chain_id === "entity_resolution") {
|
||
return true;
|
||
}
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const domain = String(meaning?.asked_domain_family ?? "").toLowerCase();
|
||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
|
||
const semanticNeed = String(planner.semantic_data_need ?? "").toLowerCase();
|
||
const combined = `${domain} ${action} ${unsupported} ${semanticNeed}`;
|
||
return (planner.proposed_primitives.includes("search_business_entity") &&
|
||
(combined.includes("entity_resolution") ||
|
||
combined.includes("search_business_entity") ||
|
||
combined.includes("entity discovery") ||
|
||
combined.includes("counterparty search")));
|
||
}
|
||
function metadataScopeForPlanner(planner) {
|
||
const entityCandidate = firstEntityCandidate(planner);
|
||
if (entityCandidate) {
|
||
return entityCandidate;
|
||
}
|
||
if (planner.selected_chain_id === "catalog_drilldown") {
|
||
const surface = planner.metadata_surface_ref;
|
||
const scopeCandidate = [
|
||
...(surface?.selected_surface_objects ?? []),
|
||
surface?.selected_entity_set ?? ""
|
||
]
|
||
.map((value) => toNonEmptyString(value))
|
||
.filter((value) => Boolean(value))
|
||
.map((value) => {
|
||
const parts = value.split(".").map((item) => item.trim()).filter((item) => item.length > 0);
|
||
return parts.length > 0 ? parts[parts.length - 1] ?? value : value;
|
||
})
|
||
.find((value) => value.length > 0);
|
||
if (scopeCandidate) {
|
||
return scopeCandidate;
|
||
}
|
||
}
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const combined = `${meaning?.asked_domain_family ?? ""} ${meaning?.asked_action_family ?? ""} ${meaning?.unsupported_but_understood_family ?? ""}`
|
||
.toLowerCase()
|
||
.trim();
|
||
if (combined.includes("vat")) {
|
||
return "НДС";
|
||
}
|
||
if (combined.includes("inventory")) {
|
||
return "склад";
|
||
}
|
||
if (combined.includes("counterparty")) {
|
||
return "контрагент";
|
||
}
|
||
return null;
|
||
}
|
||
function metadataTypesForPlanner(planner) {
|
||
if (planner.selected_chain_id === "catalog_drilldown") {
|
||
const selectedEntitySet = toNonEmptyString(planner.metadata_surface_ref?.selected_entity_set);
|
||
if (selectedEntitySet) {
|
||
return [selectedEntitySet];
|
||
}
|
||
}
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||
if (action === "inspect_registers") {
|
||
return ["РегистрНакопления", "РегистрСведений"];
|
||
}
|
||
if (action === "inspect_documents") {
|
||
return ["Документ"];
|
||
}
|
||
if (action === "inspect_catalog") {
|
||
return ["Справочник"];
|
||
}
|
||
return ["Документ", "РегистрНакопления", "РегистрСведений", "Справочник"];
|
||
}
|
||
function metadataScopeRankingAllowedForPlanner(planner) {
|
||
const action = String(planner.discovery_plan.turn_meaning_ref?.asked_action_family ?? "").toLowerCase().trim();
|
||
return action === "inspect_surface";
|
||
}
|
||
function valueFlowPilotProfile(planner) {
|
||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
|
||
const combined = `${action} ${unsupported}`;
|
||
if (combined.includes("net_value_flow") ||
|
||
combined.includes("bidirectional") ||
|
||
combined.includes("netting") ||
|
||
combined.includes("net")) {
|
||
return {
|
||
scope: "counterparty_bidirectional_value_flow_query_movements_v1",
|
||
recipe_intent: null,
|
||
direction: "bidirectional_net_value_flow"
|
||
};
|
||
}
|
||
if (combined.includes("payout") ||
|
||
combined.includes("outflow") ||
|
||
combined.includes("supplier") ||
|
||
combined.includes("paid")) {
|
||
return {
|
||
scope: "counterparty_supplier_payout_query_movements_v1",
|
||
recipe_intent: "supplier_payouts_profile",
|
||
direction: "outgoing_supplier_payout"
|
||
};
|
||
}
|
||
return {
|
||
scope: "counterparty_value_flow_query_movements_v1",
|
||
recipe_intent: "customer_revenue_and_payments",
|
||
direction: "incoming_customer_revenue"
|
||
};
|
||
}
|
||
function inventoryIntentForPlanner(planner) {
|
||
switch (planner.selected_chain_id) {
|
||
case "inventory_stock_snapshot":
|
||
return "inventory_on_hand_as_of_date";
|
||
case "inventory_supplier_overlap":
|
||
return "inventory_supplier_stock_overlap_as_of_date";
|
||
case "inventory_purchase_provenance":
|
||
return "inventory_purchase_provenance_for_item";
|
||
case "inventory_sale_trace":
|
||
return "inventory_sale_trace_for_item";
|
||
default:
|
||
return null;
|
||
}
|
||
}
|
||
function inventoryExecutablePrimitiveForPlanner(planner) {
|
||
switch (planner.selected_chain_id) {
|
||
case "inventory_stock_snapshot":
|
||
case "inventory_supplier_overlap":
|
||
return "query_movements";
|
||
case "inventory_purchase_provenance":
|
||
case "inventory_sale_trace":
|
||
return "query_documents";
|
||
default:
|
||
return null;
|
||
}
|
||
}
|
||
function skippedProbeResult(step, limitation) {
|
||
return {
|
||
primitive_id: step.primitive_id,
|
||
status: "skipped",
|
||
rows_received: 0,
|
||
rows_matched: 0,
|
||
limitation
|
||
};
|
||
}
|
||
function queryResultToProbeResult(primitiveId, result) {
|
||
return {
|
||
primitive_id: primitiveId,
|
||
status: result.error ? "error" : "ok",
|
||
rows_received: result.fetched_rows,
|
||
rows_matched: result.matched_rows,
|
||
limitation: result.error
|
||
};
|
||
}
|
||
function metadataResultToProbeResult(primitiveId, result) {
|
||
return {
|
||
primitive_id: primitiveId,
|
||
status: result.error ? "error" : "ok",
|
||
rows_received: result.fetched_rows,
|
||
rows_matched: result.error ? 0 : result.rows.length,
|
||
limitation: result.error
|
||
};
|
||
}
|
||
function toCoverageAwareQueryResult(result, options = {}) {
|
||
if (!result) {
|
||
return null;
|
||
}
|
||
return {
|
||
...result,
|
||
coverage_limited_by_probe_limit: options.coverageLimitedByProbeLimit ?? false,
|
||
coverage_recovered_by_period_chunking: options.coverageRecoveredByPeriodChunking ?? false,
|
||
period_chunking_granularity: options.periodChunkingGranularity ?? null,
|
||
period_chunk_count: options.periodChunkCount ?? 0
|
||
};
|
||
}
|
||
function monthWindowsForYear(year) {
|
||
const result = [];
|
||
for (let month = 0; month < 12; month += 1) {
|
||
const start = new Date(Date.UTC(Number(year), month, 1));
|
||
const end = new Date(Date.UTC(Number(year), month + 1, 0));
|
||
result.push({
|
||
period_from: `${start.getUTCFullYear()}-${String(start.getUTCMonth() + 1).padStart(2, "0")}-${String(start.getUTCDate()).padStart(2, "0")}`,
|
||
period_to: `${end.getUTCFullYear()}-${String(end.getUTCMonth() + 1).padStart(2, "0")}-${String(end.getUTCDate()).padStart(2, "0")}`
|
||
});
|
||
}
|
||
return result;
|
||
}
|
||
function periodWindowsForDateScope(dateScope) {
|
||
const yearMatch = dateScope?.match(/^(\d{4})$/);
|
||
if (yearMatch) {
|
||
return monthWindowsForYear(yearMatch[1]);
|
||
}
|
||
return [];
|
||
}
|
||
function mergeCoverageAwareQueryResults(results, options) {
|
||
const rawRows = results.flatMap((item) => item.raw_rows);
|
||
const rows = results.flatMap((item) => item.rows);
|
||
const errors = results.map((item) => toNonEmptyString(item.error)).filter((item) => Boolean(item));
|
||
return {
|
||
fetched_rows: results.reduce((sum, item) => sum + item.fetched_rows, 0),
|
||
matched_rows: results.reduce((sum, item) => sum + item.matched_rows, 0),
|
||
raw_rows: rawRows,
|
||
rows,
|
||
error: errors[0] ?? null,
|
||
coverage_limited_by_probe_limit: options.coverageLimitedByProbeLimit,
|
||
coverage_recovered_by_period_chunking: options.coverageRecoveredByPeriodChunking,
|
||
period_chunking_granularity: options.periodChunkingGranularity,
|
||
period_chunk_count: options.periodChunkCount
|
||
};
|
||
}
|
||
async function executeCoverageAwareValueFlowQuery(input) {
|
||
const queryLimitations = [];
|
||
const probeResults = [];
|
||
let executedProbeCount = 0;
|
||
const broadRecipePlan = input.recipePlanBuilder(input.baseFilters);
|
||
const broadResult = await input.deps.executeAddressMcpQuery({
|
||
query: broadRecipePlan.query,
|
||
limit: broadRecipePlan.limit,
|
||
account_scope: broadRecipePlan.account_scope
|
||
});
|
||
executedProbeCount += 1;
|
||
probeResults.push(queryResultToProbeResult(input.primitiveId, broadResult));
|
||
const broadCoverageLimited = !broadResult.error && broadResult.matched_rows >= input.maxRowsPerProbe;
|
||
if (broadResult.error) {
|
||
pushUnique(queryLimitations, broadResult.error);
|
||
return {
|
||
result: toCoverageAwareQueryResult(broadResult, {
|
||
coverageLimitedByProbeLimit: false
|
||
}),
|
||
probe_results: probeResults,
|
||
query_limitations: queryLimitations,
|
||
executed_probe_count: executedProbeCount
|
||
};
|
||
}
|
||
const periodWindows = periodWindowsForDateScope(input.dateScope);
|
||
if (!broadCoverageLimited || periodWindows.length === 0) {
|
||
return {
|
||
result: toCoverageAwareQueryResult(broadResult, {
|
||
coverageLimitedByProbeLimit: broadCoverageLimited
|
||
}),
|
||
probe_results: probeResults,
|
||
query_limitations: queryLimitations,
|
||
executed_probe_count: executedProbeCount
|
||
};
|
||
}
|
||
const requiredChunkProbeCount = periodWindows.length;
|
||
if (executedProbeCount + requiredChunkProbeCount > input.maxProbeCount) {
|
||
pushUnique(queryLimitations, "Requested period hit the MCP row limit, but the approved monthly recovery probe budget is smaller than the required subperiod count");
|
||
return {
|
||
result: toCoverageAwareQueryResult(broadResult, {
|
||
coverageLimitedByProbeLimit: true
|
||
}),
|
||
probe_results: probeResults,
|
||
query_limitations: queryLimitations,
|
||
executed_probe_count: executedProbeCount
|
||
};
|
||
}
|
||
const chunkResults = [];
|
||
let anyChunkLimited = false;
|
||
let anyChunkError = false;
|
||
for (const window of periodWindows) {
|
||
const chunkFilters = {
|
||
...input.baseFilters,
|
||
period_from: window.period_from,
|
||
period_to: window.period_to
|
||
};
|
||
const chunkPlan = input.recipePlanBuilder(chunkFilters);
|
||
const chunkResult = await input.deps.executeAddressMcpQuery({
|
||
query: chunkPlan.query,
|
||
limit: chunkPlan.limit,
|
||
account_scope: chunkPlan.account_scope
|
||
});
|
||
executedProbeCount += 1;
|
||
probeResults.push(queryResultToProbeResult(input.primitiveId, chunkResult));
|
||
if (chunkResult.error) {
|
||
anyChunkError = true;
|
||
pushUnique(queryLimitations, chunkResult.error);
|
||
continue;
|
||
}
|
||
if (chunkResult.matched_rows >= input.maxRowsPerProbe) {
|
||
anyChunkLimited = true;
|
||
}
|
||
chunkResults.push(chunkResult);
|
||
}
|
||
if (chunkResults.length === 0 && anyChunkError) {
|
||
return {
|
||
result: toCoverageAwareQueryResult(broadResult, {
|
||
coverageLimitedByProbeLimit: true
|
||
}),
|
||
probe_results: probeResults,
|
||
query_limitations: queryLimitations,
|
||
executed_probe_count: executedProbeCount
|
||
};
|
||
}
|
||
return {
|
||
result: mergeCoverageAwareQueryResults(chunkResults, {
|
||
coverageLimitedByProbeLimit: anyChunkLimited || anyChunkError,
|
||
coverageRecoveredByPeriodChunking: true,
|
||
periodChunkingGranularity: "month",
|
||
periodChunkCount: periodWindows.length
|
||
}),
|
||
probe_results: probeResults,
|
||
query_limitations: queryLimitations,
|
||
executed_probe_count: executedProbeCount
|
||
};
|
||
}
|
||
function summarizeLifecycleRows(result) {
|
||
if (result.error) {
|
||
return null;
|
||
}
|
||
if (result.fetched_rows <= 0) {
|
||
return "0 MCP document rows fetched";
|
||
}
|
||
return `${result.fetched_rows} MCP document rows fetched, ${result.matched_rows} matched lifecycle scope`;
|
||
}
|
||
function summarizeDocumentRows(result) {
|
||
if (result.error) {
|
||
return null;
|
||
}
|
||
if (result.fetched_rows <= 0) {
|
||
return "0 MCP document rows fetched";
|
||
}
|
||
return `${result.fetched_rows} MCP document rows fetched, ${result.matched_rows} matched document scope`;
|
||
}
|
||
function summarizeMovementRows(result) {
|
||
if (result.error) {
|
||
return null;
|
||
}
|
||
if (result.fetched_rows <= 0) {
|
||
return "0 MCP movement rows fetched";
|
||
}
|
||
return `${result.fetched_rows} MCP movement rows fetched, ${result.matched_rows} matched movement scope`;
|
||
}
|
||
function summarizeValueFlowRows(result) {
|
||
if (result.error) {
|
||
return null;
|
||
}
|
||
if (result.fetched_rows <= 0) {
|
||
return "0 MCP value-flow rows fetched";
|
||
}
|
||
if (result.coverage_recovered_by_period_chunking && result.period_chunking_granularity === "month") {
|
||
return `${result.period_chunk_count} monthly MCP value-flow probes fetched ${result.fetched_rows} rows total, ${result.matched_rows} matched value-flow scope after the broad probe hit the row limit`;
|
||
}
|
||
return `${result.fetched_rows} MCP value-flow rows fetched, ${result.matched_rows} matched value-flow scope`;
|
||
}
|
||
function summarizeInventoryRows(result) {
|
||
if (result.error) {
|
||
return null;
|
||
}
|
||
if (result.fetched_rows <= 0) {
|
||
return "0 MCP inventory exact rows fetched";
|
||
}
|
||
return `${result.fetched_rows} MCP inventory exact rows fetched, ${result.matched_rows} matched inventory scope`;
|
||
}
|
||
function summarizeMetadataRows(result) {
|
||
if (result.error) {
|
||
return null;
|
||
}
|
||
if (result.fetched_rows <= 0) {
|
||
return "0 MCP metadata rows fetched";
|
||
}
|
||
return `${result.fetched_rows} MCP metadata rows fetched`;
|
||
}
|
||
function summarizeEntityResolutionRows(result) {
|
||
if (result.error) {
|
||
return null;
|
||
}
|
||
if (result.fetched_rows <= 0) {
|
||
return "0 MCP catalog rows fetched";
|
||
}
|
||
return `${result.fetched_rows} MCP catalog rows fetched for entity search`;
|
||
}
|
||
function entityResolutionFollowupStepLimitation() {
|
||
return "Entity-resolution could not continue because the checked catalog search step did not return a confirmed slice";
|
||
}
|
||
function buildEntityResolutionResolveProbeResult(input) {
|
||
if (!input.resolution) {
|
||
return {
|
||
primitive_id: "resolve_entity_reference",
|
||
status: "ok",
|
||
rows_received: input.queryResult.fetched_rows,
|
||
rows_matched: 0,
|
||
limitation: null
|
||
};
|
||
}
|
||
if (input.resolution.resolution_status === "resolved") {
|
||
return {
|
||
primitive_id: "resolve_entity_reference",
|
||
status: "ok",
|
||
rows_received: input.queryResult.fetched_rows,
|
||
rows_matched: 1,
|
||
limitation: null
|
||
};
|
||
}
|
||
return {
|
||
primitive_id: "resolve_entity_reference",
|
||
status: "ok",
|
||
rows_received: input.queryResult.fetched_rows,
|
||
rows_matched: 0,
|
||
limitation: null
|
||
};
|
||
}
|
||
function buildEntityResolutionCoverageProbeResult(input) {
|
||
const resolved = input.resolution?.resolution_status === "resolved";
|
||
return {
|
||
primitive_id: "probe_coverage",
|
||
status: "ok",
|
||
rows_received: 1,
|
||
rows_matched: resolved ? 1 : 0,
|
||
limitation: null
|
||
};
|
||
}
|
||
function metadataRowText(row, keys) {
|
||
for (const key of keys) {
|
||
const text = toNonEmptyString(row[key]);
|
||
if (text) {
|
||
return text;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
function metadataObjectName(row) {
|
||
return metadataRowText(row, [
|
||
"ПолноеИмя",
|
||
"full_name",
|
||
"FullName",
|
||
"Имя",
|
||
"name",
|
||
"Name",
|
||
"presentation",
|
||
"Представление",
|
||
"synonym",
|
||
"Synonym"
|
||
]);
|
||
}
|
||
function metadataEntitySet(row) {
|
||
return metadataRowText(row, [
|
||
"ТипМетаданных",
|
||
"type",
|
||
"Type",
|
||
"meta_type",
|
||
"MetaType",
|
||
"ВидМетаданных",
|
||
"kind"
|
||
]);
|
||
}
|
||
function inferMetadataEntitySetFromObjectName(objectName) {
|
||
const text = String(objectName ?? "").trim();
|
||
if (!text) {
|
||
return null;
|
||
}
|
||
const dotIndex = text.indexOf(".");
|
||
if (dotIndex <= 0) {
|
||
return null;
|
||
}
|
||
const entitySet = text.slice(0, dotIndex).trim();
|
||
return entitySet.length > 0 ? entitySet : null;
|
||
}
|
||
function metadataChildNames(value) {
|
||
if (!Array.isArray(value)) {
|
||
return [];
|
||
}
|
||
const result = [];
|
||
for (const item of value) {
|
||
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
||
continue;
|
||
}
|
||
const record = item;
|
||
const fieldName = metadataRowText(record, ["Имя", "name", "Name", "full_name", "FullName"]);
|
||
if (fieldName) {
|
||
pushUnique(result, fieldName);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
function metadataAvailableFields(rows) {
|
||
const result = [];
|
||
for (const row of rows) {
|
||
for (const field of metadataChildNames(row["Реквизиты"])) {
|
||
pushUnique(result, field);
|
||
}
|
||
for (const field of metadataChildNames(row["attributes"])) {
|
||
pushUnique(result, field);
|
||
}
|
||
for (const field of metadataChildNames(row["Attributes"])) {
|
||
pushUnique(result, field);
|
||
}
|
||
for (const field of metadataChildNames(row["Измерения"])) {
|
||
pushUnique(result, field);
|
||
}
|
||
for (const field of metadataChildNames(row["dimensions"])) {
|
||
pushUnique(result, field);
|
||
}
|
||
for (const field of metadataChildNames(row["Ресурсы"])) {
|
||
pushUnique(result, field);
|
||
}
|
||
for (const field of metadataChildNames(row["resources"])) {
|
||
pushUnique(result, field);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
function normalizeMetadataEntitySetToken(value) {
|
||
return String(value ?? "")
|
||
.toLowerCase()
|
||
.replace(/[\s_.-]+/g, "");
|
||
}
|
||
function metadataMatchesRequestedType(entitySet, requestedMetaType) {
|
||
const entityToken = normalizeMetadataEntitySetToken(entitySet);
|
||
const requestedToken = normalizeMetadataEntitySetToken(requestedMetaType);
|
||
return entityToken.includes(requestedToken) || requestedToken.includes(entityToken);
|
||
}
|
||
function metadataRouteFamilyForEntitySet(entitySet) {
|
||
const token = normalizeMetadataEntitySetToken(entitySet);
|
||
if (token.includes("документ") || token.includes("document")) {
|
||
return "document_evidence";
|
||
}
|
||
if (token.includes("регистрнакопления") ||
|
||
token.includes("регистсведений") ||
|
||
token.includes("регистрсведений") ||
|
||
token.includes("accumulationregister") ||
|
||
token.includes("informationregister")) {
|
||
return "movement_evidence";
|
||
}
|
||
if (token.includes("справочник") || token.includes("catalog") || token.includes("directory")) {
|
||
return "catalog_drilldown";
|
||
}
|
||
return null;
|
||
}
|
||
function metadataRouteFamilyForEntitySetRelaxed(entitySet) {
|
||
const strict = metadataRouteFamilyForEntitySet(entitySet);
|
||
if (strict) {
|
||
return strict;
|
||
}
|
||
const raw = String(entitySet ?? "").trim();
|
||
if (!raw) {
|
||
return null;
|
||
}
|
||
if (raw.includes("Документ") || raw.includes("Документ")) {
|
||
return "document_evidence";
|
||
}
|
||
if (raw.includes("РегистрНакопления") ||
|
||
raw.includes("РегистрСведений") ||
|
||
raw.includes("РегистрНакопления") ||
|
||
raw.includes("РегистрСведений")) {
|
||
return "movement_evidence";
|
||
}
|
||
if (raw.includes("Справочник") || raw.includes("Справочник")) {
|
||
return "catalog_drilldown";
|
||
}
|
||
return null;
|
||
}
|
||
function metadataNextPrimitiveForRouteFamily(routeFamily) {
|
||
if (routeFamily === "document_evidence") {
|
||
return "query_documents";
|
||
}
|
||
if (routeFamily === "movement_evidence") {
|
||
return "query_movements";
|
||
}
|
||
if (routeFamily === "catalog_drilldown") {
|
||
return "drilldown_related_objects";
|
||
}
|
||
return null;
|
||
}
|
||
function selectMetadataEntityGrounding(availableEntitySets, requestedMetaTypes) {
|
||
const requestedMatches = availableEntitySets.filter((entitySet) => requestedMetaTypes.some((requestedMetaType) => metadataMatchesRequestedType(entitySet, requestedMetaType)));
|
||
if (requestedMatches.length === 1) {
|
||
return {
|
||
selectedEntitySet: requestedMatches[0] ?? null,
|
||
ambiguityDetected: false,
|
||
ambiguityEntitySets: []
|
||
};
|
||
}
|
||
if (requestedMatches.length > 1) {
|
||
return {
|
||
selectedEntitySet: null,
|
||
ambiguityDetected: true,
|
||
ambiguityEntitySets: requestedMatches
|
||
};
|
||
}
|
||
if (availableEntitySets.length === 1) {
|
||
return {
|
||
selectedEntitySet: availableEntitySets[0] ?? null,
|
||
ambiguityDetected: false,
|
||
ambiguityEntitySets: []
|
||
};
|
||
}
|
||
return {
|
||
selectedEntitySet: null,
|
||
ambiguityDetected: availableEntitySets.length > 1,
|
||
ambiguityEntitySets: availableEntitySets
|
||
};
|
||
}
|
||
function metadataObjectsForEntitySet(entitySet, matchedObjects) {
|
||
if (!entitySet) {
|
||
return [];
|
||
}
|
||
return matchedObjects.filter((item) => item.startsWith(`${entitySet}.`) || item.includes(entitySet));
|
||
}
|
||
function emptyMetadataSurfaceFamilyScores() {
|
||
return {
|
||
document_evidence: 0,
|
||
movement_evidence: 0,
|
||
catalog_drilldown: 0
|
||
};
|
||
}
|
||
function metadataSurfaceFamilyScores(matchedObjects) {
|
||
const scores = emptyMetadataSurfaceFamilyScores();
|
||
for (const objectName of matchedObjects) {
|
||
const entitySet = inferMetadataEntitySetFromObjectName(objectName);
|
||
const routeFamily = entitySet ? metadataRouteFamilyForEntitySetRelaxed(entitySet) : null;
|
||
if (routeFamily) {
|
||
scores[routeFamily] += 1;
|
||
}
|
||
}
|
||
return scores;
|
||
}
|
||
function normalizeMetadataObjectRankingToken(value) {
|
||
return String(value ?? "")
|
||
.toLowerCase()
|
||
.replace(/[^\p{L}\p{N}]+/gu, "");
|
||
}
|
||
function metadataScopeRankingTokens(metadataScope) {
|
||
const scope = String(metadataScope ?? "").trim();
|
||
if (!scope) {
|
||
return [];
|
||
}
|
||
const condensed = normalizeMetadataObjectRankingToken(scope);
|
||
const result = [];
|
||
if (condensed.length >= 2) {
|
||
pushUnique(result, condensed);
|
||
}
|
||
for (const token of scope.toLowerCase().split(/[^\p{L}\p{N}]+/gu)) {
|
||
const normalized = normalizeMetadataObjectRankingToken(token);
|
||
if (normalized.length >= 2) {
|
||
pushUnique(result, normalized);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
function metadataObjectRelevanceScore(metadataScope, objectName) {
|
||
const objectToken = normalizeMetadataObjectRankingToken(objectName);
|
||
if (!objectToken) {
|
||
return 1;
|
||
}
|
||
let score = 1;
|
||
for (const token of metadataScopeRankingTokens(metadataScope)) {
|
||
if (objectToken.includes(token)) {
|
||
score += token.length >= 6 ? 4 : 3;
|
||
}
|
||
}
|
||
return score;
|
||
}
|
||
function metadataWeightedSurfaceFamilyScores(matchedObjects, metadataScope) {
|
||
const scores = emptyMetadataSurfaceFamilyScores();
|
||
for (const objectName of matchedObjects) {
|
||
const entitySet = inferMetadataEntitySetFromObjectName(objectName);
|
||
const routeFamily = entitySet ? metadataRouteFamilyForEntitySetRelaxed(entitySet) : null;
|
||
if (routeFamily) {
|
||
scores[routeFamily] += metadataObjectRelevanceScore(metadataScope, objectName);
|
||
}
|
||
}
|
||
return scores;
|
||
}
|
||
function sortMetadataObjectsByRelevance(matchedObjects, metadataScope) {
|
||
return [...matchedObjects].sort((left, right) => {
|
||
const scoreDelta = metadataObjectRelevanceScore(metadataScope, right) - metadataObjectRelevanceScore(metadataScope, left);
|
||
if (scoreDelta !== 0) {
|
||
return scoreDelta;
|
||
}
|
||
return left.localeCompare(right, "ru");
|
||
});
|
||
}
|
||
function metadataObjectsForRouteFamily(routeFamily, matchedObjects, metadataScope) {
|
||
if (!routeFamily) {
|
||
return [];
|
||
}
|
||
const filtered = matchedObjects.filter((objectName) => {
|
||
const entitySet = inferMetadataEntitySetFromObjectName(objectName);
|
||
return entitySet ? metadataRouteFamilyForEntitySetRelaxed(entitySet) === routeFamily : false;
|
||
});
|
||
return sortMetadataObjectsByRelevance(filtered, metadataScope);
|
||
}
|
||
function selectDominantMetadataRouteFamilyFromScores(scores) {
|
||
const ranked = Object.entries(scores)
|
||
.filter(([, score]) => score > 0)
|
||
.sort((left, right) => right[1] - left[1]);
|
||
const top = ranked[0];
|
||
const second = ranked[1];
|
||
if (!top) {
|
||
return null;
|
||
}
|
||
if (!second) {
|
||
return top[0];
|
||
}
|
||
const absoluteMargin = top[1] - second[1];
|
||
const relativeRatio = second[1] > 0 ? top[1] / second[1] : Number.POSITIVE_INFINITY;
|
||
const clearlyDominant = absoluteMargin >= 2 || relativeRatio >= 1.5;
|
||
return clearlyDominant ? top[0] : null;
|
||
}
|
||
function selectMetadataRouteFamilyFromSurfaceScores(input) {
|
||
const countDominant = selectDominantMetadataRouteFamilyFromScores(input.countScores);
|
||
if (countDominant) {
|
||
return {
|
||
routeFamily: countDominant,
|
||
rankingApplied: false
|
||
};
|
||
}
|
||
if (!input.allowScopeRanking) {
|
||
return {
|
||
routeFamily: null,
|
||
rankingApplied: false
|
||
};
|
||
}
|
||
const rankedCounts = Object.entries(input.countScores)
|
||
.filter(([, score]) => score > 0)
|
||
.sort((left, right) => right[1] - left[1]);
|
||
const topCount = rankedCounts[0]?.[1] ?? 0;
|
||
const secondCount = rankedCounts[1]?.[1] ?? 0;
|
||
if (topCount <= 0 || topCount !== secondCount) {
|
||
return {
|
||
routeFamily: null,
|
||
rankingApplied: false
|
||
};
|
||
}
|
||
const weightedScores = metadataWeightedSurfaceFamilyScores(input.matchedObjects, input.metadataScope);
|
||
const weightedDominant = selectDominantMetadataRouteFamilyFromScores(weightedScores);
|
||
return {
|
||
routeFamily: weightedDominant,
|
||
rankingApplied: Boolean(weightedDominant)
|
||
};
|
||
}
|
||
function deriveMetadataSurface(result, metadataScope, requestedMetaTypes, allowScopeRanking) {
|
||
if (!result || result.error || result.rows.length <= 0) {
|
||
return null;
|
||
}
|
||
const matchedObjects = [];
|
||
const availableEntitySets = [];
|
||
for (const row of result.rows) {
|
||
const objectName = metadataObjectName(row);
|
||
if (objectName) {
|
||
pushUnique(matchedObjects, objectName);
|
||
}
|
||
const entitySet = metadataEntitySet(row) ?? inferMetadataEntitySetFromObjectName(objectName);
|
||
if (entitySet) {
|
||
pushUnique(availableEntitySets, entitySet);
|
||
}
|
||
}
|
||
const grounding = selectMetadataEntityGrounding(availableEntitySets, requestedMetaTypes);
|
||
const surfaceFamilyScores = metadataSurfaceFamilyScores(matchedObjects);
|
||
const selectedEntitySetRouteFamily = grounding.selectedEntitySet
|
||
? metadataRouteFamilyForEntitySetRelaxed(grounding.selectedEntitySet)
|
||
: null;
|
||
const scoredRouteSelection = selectedEntitySetRouteFamily === null
|
||
? selectMetadataRouteFamilyFromSurfaceScores({
|
||
matchedObjects,
|
||
metadataScope,
|
||
countScores: surfaceFamilyScores,
|
||
allowScopeRanking
|
||
})
|
||
: { routeFamily: null, rankingApplied: false };
|
||
const scoredRouteFamily = scoredRouteSelection.routeFamily;
|
||
const downstreamRouteFamily = selectedEntitySetRouteFamily ?? scoredRouteFamily;
|
||
const routeFamilySelectionBasis = selectedEntitySetRouteFamily
|
||
? "selected_entity_set"
|
||
: scoredRouteFamily
|
||
? "dominant_surface_objects"
|
||
: null;
|
||
const selectedSurfaceObjects = grounding.selectedEntitySet !== null
|
||
? sortMetadataObjectsByRelevance(metadataObjectsForEntitySet(grounding.selectedEntitySet, matchedObjects), metadataScope)
|
||
: metadataObjectsForRouteFamily(downstreamRouteFamily, matchedObjects, metadataScope);
|
||
const knownLimitations = [];
|
||
const ambiguityRemainsUnresolved = grounding.ambiguityDetected && !downstreamRouteFamily;
|
||
if (ambiguityRemainsUnresolved && grounding.ambiguityEntitySets.length > 0) {
|
||
knownLimitations.push(`Exact downstream metadata surface remains ambiguous across: ${grounding.ambiguityEntitySets.join(", ")}`);
|
||
}
|
||
if (grounding.ambiguityDetected && downstreamRouteFamily && routeFamilySelectionBasis === "dominant_surface_objects") {
|
||
knownLimitations.push(`Metadata surface spans multiple object sets, but dominant confirmed objects point to ${downstreamRouteFamily}`);
|
||
}
|
||
return {
|
||
metadata_scope: metadataScope,
|
||
requested_meta_types: requestedMetaTypes,
|
||
matched_rows: result.rows.length,
|
||
available_entity_sets: availableEntitySets,
|
||
matched_objects: matchedObjects,
|
||
selected_entity_set: grounding.selectedEntitySet,
|
||
selected_surface_objects: selectedSurfaceObjects,
|
||
surface_family_scores: surfaceFamilyScores,
|
||
downstream_route_family: downstreamRouteFamily,
|
||
route_family_selection_basis: routeFamilySelectionBasis,
|
||
recommended_next_primitive: metadataNextPrimitiveForRouteFamily(downstreamRouteFamily),
|
||
ambiguity_detected: ambiguityRemainsUnresolved,
|
||
ambiguity_entity_sets: ambiguityRemainsUnresolved ? grounding.ambiguityEntitySets : [],
|
||
surface_object_ranking_applied: scoredRouteSelection.rankingApplied,
|
||
available_fields: metadataAvailableFields(result.rows),
|
||
known_limitations: knownLimitations,
|
||
inference_basis: "confirmed_1c_metadata_surface_rows"
|
||
};
|
||
}
|
||
function buildMetadataConfirmedFacts(surface) {
|
||
if (!surface) {
|
||
return [];
|
||
}
|
||
const facts = [];
|
||
const scopeSuffix = surface.metadata_scope ? ` for ${surface.metadata_scope}` : "";
|
||
facts.push(`Confirmed 1C metadata surface${scopeSuffix}: ${surface.matched_rows} rows and ${surface.matched_objects.length} matching objects`);
|
||
if (surface.available_entity_sets.length > 0) {
|
||
facts.push(`Available metadata object sets: ${surface.available_entity_sets.join(", ")}`);
|
||
}
|
||
if (surface.selected_entity_set) {
|
||
facts.push(`Selected metadata entity set: ${surface.selected_entity_set}`);
|
||
}
|
||
if (surface.selected_surface_objects.length > 0) {
|
||
facts.push(`Selected metadata objects: ${surface.selected_surface_objects.slice(0, 8).join(", ")}`);
|
||
}
|
||
if (surface.surface_family_scores.document_evidence > 0 ||
|
||
surface.surface_family_scores.movement_evidence > 0 ||
|
||
surface.surface_family_scores.catalog_drilldown > 0) {
|
||
facts.push(`Metadata surface family scores: document=${surface.surface_family_scores.document_evidence}, movement=${surface.surface_family_scores.movement_evidence}, catalog=${surface.surface_family_scores.catalog_drilldown}`);
|
||
}
|
||
if (surface.available_fields.length > 0) {
|
||
facts.push(`Available metadata fields/sections: ${surface.available_fields.slice(0, 12).join(", ")}`);
|
||
}
|
||
return facts;
|
||
}
|
||
function buildMetadataInferredFacts(surface) {
|
||
if (!surface || !surface.downstream_route_family || !surface.recommended_next_primitive) {
|
||
return [];
|
||
}
|
||
return [
|
||
`A likely next checked lane may be inferred as ${surface.downstream_route_family} from the confirmed metadata surface`
|
||
];
|
||
}
|
||
function buildMetadataUnknownFacts(surface, metadataScope) {
|
||
if (surface) {
|
||
if (surface.ambiguity_detected && surface.ambiguity_entity_sets.length > 0) {
|
||
return [...surface.known_limitations];
|
||
}
|
||
if (surface.available_fields.length > 0) {
|
||
return [];
|
||
}
|
||
return ["Detailed metadata fields were not returned by this MCP metadata probe"];
|
||
}
|
||
if (metadataScope) {
|
||
return [`No matching 1C metadata objects were confirmed for scope "${metadataScope}"`];
|
||
}
|
||
return ["No matching 1C metadata objects were confirmed by this MCP metadata probe"];
|
||
}
|
||
function buildEntityResolutionConfirmedFacts(resolution) {
|
||
if (!resolution || resolution.resolution_status !== "resolved" || !resolution.resolved_entity) {
|
||
return [];
|
||
}
|
||
if (resolution.requested_entity && normalizeEntityResolutionText(resolution.requested_entity) === normalizeEntityResolutionText(resolution.resolved_entity)) {
|
||
return [`В проверенном каталожном срезе 1С найден контрагент: ${resolution.resolved_entity}`];
|
||
}
|
||
return [
|
||
`В проверенном каталожном срезе 1С найден наиболее вероятный контрагент: ${resolution.resolved_entity}`
|
||
];
|
||
}
|
||
function buildEntityResolutionInferredFacts(resolution) {
|
||
if (!resolution) {
|
||
return [];
|
||
}
|
||
if (resolution.resolution_status === "resolved") {
|
||
const facts = ["Пока проверено только заземление сущности по каталогу 1С; документы, движения и денежные показатели еще не проверялись"];
|
||
if (resolution.requested_entity && resolution.resolved_entity) {
|
||
const requestedNormalized = normalizeEntityResolutionText(resolution.requested_entity);
|
||
const resolvedNormalized = normalizeEntityResolutionText(resolution.resolved_entity);
|
||
if (requestedNormalized !== resolvedNormalized) {
|
||
facts.push("Контрагент выбран как ближайшее подтвержденное совпадение имени в проверенном каталоге 1С");
|
||
}
|
||
}
|
||
return facts;
|
||
}
|
||
if (resolution.resolution_status === "ambiguous") {
|
||
return ["В проверенном каталожном срезе осталось несколько близких кандидатов, поэтому точного контрагента в 1С еще нужно уточнить"];
|
||
}
|
||
return [];
|
||
}
|
||
function buildEntityResolutionUnknownFacts(resolution, requestedEntity) {
|
||
if (!resolution) {
|
||
return ["По проверенному каталожному поиску 1С не удалось заземлить сущность контрагента"];
|
||
}
|
||
const unknownFacts = ["Документы, движения и денежные показатели по этому контрагенту еще не проверялись; пока был только каталожный поиск"];
|
||
if (resolution.resolution_status === "ambiguous" && resolution.ambiguity_candidates.length > 0) {
|
||
unknownFacts.unshift(`Точное заземление контрагента в 1С остается неоднозначным между вариантами: ${resolution.ambiguity_candidates.join(", ")}`);
|
||
return unknownFacts;
|
||
}
|
||
if (resolution.resolution_status === "not_found") {
|
||
unknownFacts.unshift(requestedEntity
|
||
? `В проверенном каталожном срезе 1С не подтвержден контрагент с именем "${requestedEntity}"`
|
||
: "В проверенном каталожном срезе 1С не подтвержден подходящий контрагент");
|
||
}
|
||
return unknownFacts;
|
||
}
|
||
function rowDateValue(row) {
|
||
const candidates = [
|
||
row["Период"],
|
||
row["Period"],
|
||
row["period"],
|
||
row["Дата"],
|
||
row["Date"],
|
||
row["date"]
|
||
];
|
||
for (const candidate of candidates) {
|
||
const text = toNonEmptyString(candidate);
|
||
const match = text?.match(/(\d{4})-(\d{2})-(\d{2})/);
|
||
if (match) {
|
||
return `${match[1]}-${match[2]}-${match[3]}`;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
function rowAmountValue(row) {
|
||
const candidates = [
|
||
row["Сумма"],
|
||
row["РЎСѓРјРјР°"],
|
||
row["СуммаДокумента"],
|
||
row["СуммаДокумента"],
|
||
row["Amount"],
|
||
row["amount"],
|
||
row["Total"],
|
||
row["total"]
|
||
];
|
||
for (const candidate of candidates) {
|
||
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
||
return candidate;
|
||
}
|
||
const text = toNonEmptyString(candidate);
|
||
if (!text) {
|
||
continue;
|
||
}
|
||
const normalized = text
|
||
.replace(/\s+/g, "")
|
||
.replace(/\u00a0/g, "")
|
||
.replace(",", ".")
|
||
.replace(/[^\d.-]/g, "");
|
||
const parsed = Number(normalized);
|
||
if (Number.isFinite(parsed)) {
|
||
return parsed;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
function rowNumberValue(row, keys) {
|
||
for (const key of keys) {
|
||
const candidate = row[key];
|
||
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
||
return candidate;
|
||
}
|
||
const text = toNonEmptyString(candidate);
|
||
if (!text) {
|
||
continue;
|
||
}
|
||
const normalized = text
|
||
.replace(/\s+/g, "")
|
||
.replace(/\u00a0/g, "")
|
||
.replace(",", ".")
|
||
.replace(/[^\d.-]/g, "");
|
||
const parsed = Number(normalized);
|
||
if (Number.isFinite(parsed)) {
|
||
return parsed;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
function rowTextValue(row, keys) {
|
||
for (const key of keys) {
|
||
const text = toNonEmptyString(row[key]);
|
||
if (text) {
|
||
return text;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
function rowInventoryItemValue(row) {
|
||
return rowTextValue(row, ["Номенклатура", "Item", "item", "Товар", "Product", "product"]);
|
||
}
|
||
function rowWarehouseValue(row) {
|
||
return rowTextValue(row, ["Склад", "Warehouse", "warehouse"]);
|
||
}
|
||
function rowDocumentValue(row) {
|
||
return rowTextValue(row, ["Регистратор", "Registrator", "registrator", "Документ", "Document", "document"]);
|
||
}
|
||
function rowAccountValue(row) {
|
||
return rowTextValue(row, ["СчетДт", "AccountDt", "account_dt", "Счет", "Account", "account"]);
|
||
}
|
||
function rowDebitAccountValue(row) {
|
||
return rowTextValue(row, ["СчетДт", "AccountDt", "account_dt", "DebitAccount", "debit_account"]);
|
||
}
|
||
function rowCreditAccountValue(row) {
|
||
return rowTextValue(row, ["СчетКт", "AccountKt", "account_kt", "CreditAccount", "credit_account"]);
|
||
}
|
||
function accountTextMatchesPrefix(account, prefixes) {
|
||
const normalized = String(account ?? "").replace(/\s+/g, "");
|
||
return prefixes.some((prefix) => normalized.startsWith(prefix));
|
||
}
|
||
function rowQuantityValue(row) {
|
||
return rowNumberValue(row, ["Количество", "Quantity", "quantity", "Qty", "qty", "Остаток", "Balance", "balance"]);
|
||
}
|
||
function rowAnalyticsTextValues(row) {
|
||
const values = [];
|
||
const analytics = row["analytics"];
|
||
if (Array.isArray(analytics)) {
|
||
for (const item of analytics) {
|
||
const text = toNonEmptyString(item);
|
||
if (text && !values.includes(text)) {
|
||
values.push(text);
|
||
}
|
||
}
|
||
}
|
||
for (const key of [
|
||
"СубконтоДт1",
|
||
"СубконтоДт2",
|
||
"СубконтоДт3",
|
||
"СубконтоКт1",
|
||
"СубконтоКт2",
|
||
"СубконтоКт3",
|
||
"SubcontoDt1",
|
||
"SubcontoDt2",
|
||
"SubcontoDt3",
|
||
"SubcontoKt1",
|
||
"SubcontoKt2",
|
||
"SubcontoKt3"
|
||
]) {
|
||
const text = toNonEmptyString(row[key]);
|
||
if (text && !values.includes(text)) {
|
||
values.push(text);
|
||
}
|
||
}
|
||
return values;
|
||
}
|
||
function isEmptyAnalyticToken(value) {
|
||
return /^(?:0|<пусто>|пустая ссылка)$/iu.test(value.trim());
|
||
}
|
||
function isLikelyContractToken(value) {
|
||
const normalized = value.trim();
|
||
if (!normalized || isEmptyAnalyticToken(normalized)) {
|
||
return false;
|
||
}
|
||
if (/(?:договор|contract|дог\.)/iu.test(normalized)) {
|
||
return true;
|
||
}
|
||
if (/^\d{4}-\d{2}-\d{2}/.test(normalized)) {
|
||
return false;
|
||
}
|
||
return normalized.length >= 3 && /[\\/]/.test(normalized);
|
||
}
|
||
function isLikelyCounterpartyToken(value) {
|
||
const normalized = value.trim();
|
||
if (!normalized || isEmptyAnalyticToken(normalized)) {
|
||
return false;
|
||
}
|
||
if (/^\d{4}-\d{2}-\d{2}/.test(normalized)) {
|
||
return false;
|
||
}
|
||
if (/^\d+(?:[./-]\d+)*$/.test(normalized)) {
|
||
return false;
|
||
}
|
||
if (!/[a-zа-я]/iu.test(normalized)) {
|
||
return false;
|
||
}
|
||
if (/(?:договор|contract|дог\.|документ|операц|счет[-\s]?фактур|накладн|акт|поступлен|списани|плат[её]ж|банк|касса|movement|invoice|payment)/iu.test(normalized)) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
function rowContractValue(row) {
|
||
const explicit = rowTextValue(row, ["Договор", "Contract", "contract"]);
|
||
if (explicit && !isEmptyAnalyticToken(explicit)) {
|
||
return explicit;
|
||
}
|
||
return rowAnalyticsTextValues(row).find(isLikelyContractToken) ?? null;
|
||
}
|
||
function rowCounterpartyValue(row) {
|
||
const candidates = [row["Контрагент"], row["Counterparty"], row["counterparty"], row["Наименование"], row["name"]];
|
||
for (const candidate of candidates) {
|
||
const text = toNonEmptyString(candidate);
|
||
if (text) {
|
||
return text;
|
||
}
|
||
}
|
||
return rowAnalyticsTextValues(row).find(isLikelyCounterpartyToken) ?? null;
|
||
}
|
||
function normalizeDateParts(yearText, monthText, dayText) {
|
||
const year = Number(yearText);
|
||
const month = Number(monthText);
|
||
const day = Number(dayText);
|
||
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) {
|
||
return null;
|
||
}
|
||
if (year < 1900 || year > 2100 || month < 1 || month > 12 || day < 1 || day > 31) {
|
||
return null;
|
||
}
|
||
const date = new Date(Date.UTC(year, month - 1, day));
|
||
if (date.getUTCFullYear() !== year ||
|
||
date.getUTCMonth() !== month - 1 ||
|
||
date.getUTCDate() !== day) {
|
||
return null;
|
||
}
|
||
return [
|
||
String(year).padStart(4, "0"),
|
||
String(month).padStart(2, "0"),
|
||
String(day).padStart(2, "0")
|
||
].join("-");
|
||
}
|
||
function extractContractDateFromText(value) {
|
||
const text = toNonEmptyString(value);
|
||
if (!text || !/(?:договор|contract|дог\.)/iu.test(text)) {
|
||
return null;
|
||
}
|
||
const isoLikeMatch = text.match(/(\d{4})[-./](\d{1,2})[-./](\d{1,2})/);
|
||
if (isoLikeMatch) {
|
||
return normalizeDateParts(isoLikeMatch[1], isoLikeMatch[2], isoLikeMatch[3]);
|
||
}
|
||
const ruDateMatch = text.match(/(\d{1,2})[./-](\d{1,2})[./-](\d{4})/);
|
||
if (ruDateMatch) {
|
||
return normalizeDateParts(ruDateMatch[3], ruDateMatch[2], ruDateMatch[1]);
|
||
}
|
||
return null;
|
||
}
|
||
function earlierIsoDate(left, right) {
|
||
if (!left) {
|
||
return right;
|
||
}
|
||
if (!right) {
|
||
return left;
|
||
}
|
||
return right < left ? right : left;
|
||
}
|
||
function rowOpenSettlementContractStartDateValue(row) {
|
||
const candidates = [
|
||
rowContractValue(row),
|
||
rowDocumentValue(row),
|
||
...rowAnalyticsTextValues(row)
|
||
];
|
||
for (const candidate of candidates) {
|
||
const contractDate = extractContractDateFromText(candidate);
|
||
if (contractDate) {
|
||
return contractDate;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
function monthBucketFromIsoDate(isoDate) {
|
||
const match = isoDate?.match(/^(\d{4})-(\d{2})-\d{2}$/);
|
||
return match ? `${match[1]}-${match[2]}` : null;
|
||
}
|
||
function yearBucketFromIsoDate(isoDate) {
|
||
const match = isoDate?.match(/^(\d{4})-\d{2}-\d{2}$/);
|
||
return match ? match[1] : null;
|
||
}
|
||
function netDirectionFromAmount(amount) {
|
||
if (amount > 0) {
|
||
return "net_incoming";
|
||
}
|
||
if (amount < 0) {
|
||
return "net_outgoing";
|
||
}
|
||
return "balanced";
|
||
}
|
||
function monthDiff(firstIsoDate, latestIsoDate) {
|
||
const first = new Date(`${firstIsoDate}T00:00:00.000Z`);
|
||
const latest = new Date(`${latestIsoDate}T00:00:00.000Z`);
|
||
if (Number.isNaN(first.getTime()) || Number.isNaN(latest.getTime()) || latest < first) {
|
||
return 0;
|
||
}
|
||
let months = (latest.getUTCFullYear() - first.getUTCFullYear()) * 12;
|
||
months += latest.getUTCMonth() - first.getUTCMonth();
|
||
if (latest.getUTCDate() < first.getUTCDate()) {
|
||
months -= 1;
|
||
}
|
||
return Math.max(0, months);
|
||
}
|
||
function formatDurationHumanRu(totalMonths) {
|
||
const years = Math.floor(totalMonths / 12);
|
||
const months = totalMonths % 12;
|
||
const parts = [];
|
||
if (years > 0) {
|
||
parts.push(`${years} ${years === 1 ? "год" : years >= 2 && years <= 4 ? "года" : "лет"}`);
|
||
}
|
||
if (months > 0) {
|
||
parts.push(`${months} ${months === 1 ? "месяц" : months >= 2 && months <= 4 ? "месяца" : "месяцев"}`);
|
||
}
|
||
return parts.length > 0 ? parts.join(" ") : "меньше месяца";
|
||
}
|
||
function deriveActivityPeriod(result) {
|
||
if (!result || result.error || result.matched_rows <= 0) {
|
||
return null;
|
||
}
|
||
const dates = result.rows
|
||
.map((row) => rowDateValue(row))
|
||
.filter((value) => Boolean(value))
|
||
.sort();
|
||
if (dates.length === 0) {
|
||
return null;
|
||
}
|
||
const first = dates[0];
|
||
const latest = dates[dates.length - 1];
|
||
const totalMonths = monthDiff(first, latest);
|
||
return {
|
||
first_activity_date: first,
|
||
latest_activity_date: latest,
|
||
matched_rows: result.matched_rows,
|
||
duration_total_months: totalMonths,
|
||
duration_years: Math.floor(totalMonths / 12),
|
||
duration_months_remainder: totalMonths % 12,
|
||
duration_human_ru: formatDurationHumanRu(totalMonths),
|
||
inference_basis: "first_and_latest_confirmed_1c_activity_rows"
|
||
};
|
||
}
|
||
function formatAmountHumanRu(amount) {
|
||
const formatted = new Intl.NumberFormat("ru-RU", {
|
||
maximumFractionDigits: 2,
|
||
minimumFractionDigits: Number.isInteger(amount) ? 0 : 2
|
||
})
|
||
.format(amount)
|
||
.replace(/\u00a0/g, " ");
|
||
return `${formatted} руб.`;
|
||
}
|
||
function yearCountHumanRu(count) {
|
||
const abs = Math.abs(count) % 100;
|
||
const last = abs % 10;
|
||
const noun = abs >= 11 && abs <= 14
|
||
? "лет"
|
||
: last === 1
|
||
? "год"
|
||
: last >= 2 && last <= 4
|
||
? "года"
|
||
: "лет";
|
||
return `${count} ${noun}`;
|
||
}
|
||
function deriveValueFlowMonthBreakdown(result, aggregationAxis) {
|
||
if (!result || result.error || aggregationAxis !== "month") {
|
||
return [];
|
||
}
|
||
const buckets = new Map();
|
||
for (const row of result.rows) {
|
||
const isoDate = rowDateValue(row);
|
||
const monthBucket = monthBucketFromIsoDate(isoDate);
|
||
const amount = rowAmountValue(row);
|
||
if (!monthBucket || amount === null) {
|
||
continue;
|
||
}
|
||
const current = buckets.get(monthBucket) ?? { rows_with_amount: 0, total_amount: 0 };
|
||
current.rows_with_amount += 1;
|
||
current.total_amount += amount;
|
||
buckets.set(monthBucket, current);
|
||
}
|
||
return Array.from(buckets.entries())
|
||
.sort(([left], [right]) => left.localeCompare(right))
|
||
.map(([monthBucket, bucket]) => ({
|
||
month_bucket: monthBucket,
|
||
rows_with_amount: bucket.rows_with_amount,
|
||
total_amount: bucket.total_amount,
|
||
total_amount_human_ru: formatAmountHumanRu(bucket.total_amount)
|
||
}));
|
||
}
|
||
function deriveBidirectionalValueFlowMonthBreakdown(input) {
|
||
if (input.aggregationAxis !== "month") {
|
||
return [];
|
||
}
|
||
const incomingBuckets = deriveValueFlowMonthBreakdown(input.incomingResult, "month");
|
||
const outgoingBuckets = deriveValueFlowMonthBreakdown(input.outgoingResult, "month");
|
||
const allMonthBuckets = new Set();
|
||
for (const bucket of incomingBuckets) {
|
||
allMonthBuckets.add(bucket.month_bucket);
|
||
}
|
||
for (const bucket of outgoingBuckets) {
|
||
allMonthBuckets.add(bucket.month_bucket);
|
||
}
|
||
const incomingByMonth = new Map(incomingBuckets.map((bucket) => [bucket.month_bucket, bucket]));
|
||
const outgoingByMonth = new Map(outgoingBuckets.map((bucket) => [bucket.month_bucket, bucket]));
|
||
return Array.from(allMonthBuckets)
|
||
.sort((left, right) => left.localeCompare(right))
|
||
.map((monthBucket) => {
|
||
const incoming = incomingByMonth.get(monthBucket);
|
||
const outgoing = outgoingByMonth.get(monthBucket);
|
||
const incomingAmount = incoming?.total_amount ?? 0;
|
||
const outgoingAmount = outgoing?.total_amount ?? 0;
|
||
const netAmount = incomingAmount - outgoingAmount;
|
||
return {
|
||
month_bucket: monthBucket,
|
||
incoming_total_amount: incomingAmount,
|
||
incoming_total_amount_human_ru: formatAmountHumanRu(incomingAmount),
|
||
incoming_rows_with_amount: incoming?.rows_with_amount ?? 0,
|
||
outgoing_total_amount: outgoingAmount,
|
||
outgoing_total_amount_human_ru: formatAmountHumanRu(outgoingAmount),
|
||
outgoing_rows_with_amount: outgoing?.rows_with_amount ?? 0,
|
||
net_amount: netAmount,
|
||
net_amount_human_ru: formatAmountHumanRu(Math.abs(netAmount)),
|
||
net_direction: netDirectionFromAmount(netAmount)
|
||
};
|
||
});
|
||
}
|
||
function deriveBusinessOverviewSideYearBreakdown(result) {
|
||
if (!result || result.error) {
|
||
return [];
|
||
}
|
||
const buckets = new Map();
|
||
for (const row of result.rows) {
|
||
const yearBucket = yearBucketFromIsoDate(rowDateValue(row));
|
||
const amount = rowAmountValue(row);
|
||
if (!yearBucket || amount === null) {
|
||
continue;
|
||
}
|
||
const current = buckets.get(yearBucket) ?? { rows_with_amount: 0, total_amount: 0 };
|
||
current.rows_with_amount += 1;
|
||
current.total_amount += amount;
|
||
buckets.set(yearBucket, current);
|
||
}
|
||
return Array.from(buckets.entries())
|
||
.sort(([left], [right]) => left.localeCompare(right))
|
||
.map(([yearBucket, bucket]) => ({
|
||
year_bucket: yearBucket,
|
||
rows_with_amount: bucket.rows_with_amount,
|
||
total_amount: bucket.total_amount,
|
||
total_amount_human_ru: formatAmountHumanRu(bucket.total_amount)
|
||
}));
|
||
}
|
||
function deriveBusinessOverviewYearlyBreakdown(input) {
|
||
const incomingBuckets = deriveBusinessOverviewSideYearBreakdown(input.incomingResult);
|
||
const outgoingBuckets = deriveBusinessOverviewSideYearBreakdown(input.outgoingResult);
|
||
const allYearBuckets = new Set();
|
||
for (const bucket of incomingBuckets) {
|
||
allYearBuckets.add(bucket.year_bucket);
|
||
}
|
||
for (const bucket of outgoingBuckets) {
|
||
allYearBuckets.add(bucket.year_bucket);
|
||
}
|
||
const incomingByYear = new Map(incomingBuckets.map((bucket) => [bucket.year_bucket, bucket]));
|
||
const outgoingByYear = new Map(outgoingBuckets.map((bucket) => [bucket.year_bucket, bucket]));
|
||
return Array.from(allYearBuckets)
|
||
.sort((left, right) => left.localeCompare(right))
|
||
.map((yearBucket) => {
|
||
const incoming = incomingByYear.get(yearBucket);
|
||
const outgoing = outgoingByYear.get(yearBucket);
|
||
const incomingAmount = incoming?.total_amount ?? 0;
|
||
const outgoingAmount = outgoing?.total_amount ?? 0;
|
||
const netAmount = incomingAmount - outgoingAmount;
|
||
return {
|
||
year_bucket: yearBucket,
|
||
incoming_total_amount: incomingAmount,
|
||
incoming_total_amount_human_ru: formatAmountHumanRu(incomingAmount),
|
||
incoming_rows_with_amount: incoming?.rows_with_amount ?? 0,
|
||
outgoing_total_amount: outgoingAmount,
|
||
outgoing_total_amount_human_ru: formatAmountHumanRu(outgoingAmount),
|
||
outgoing_rows_with_amount: outgoing?.rows_with_amount ?? 0,
|
||
net_amount: netAmount,
|
||
net_amount_human_ru: formatAmountHumanRu(Math.abs(netAmount)),
|
||
net_direction: netDirectionFromAmount(netAmount)
|
||
};
|
||
});
|
||
}
|
||
function normalizeBusinessOverviewActivityMarker(row) {
|
||
const marker = toNonEmptyString(rowDocumentValue(row));
|
||
return marker ? marker.trim().toUpperCase() : null;
|
||
}
|
||
function normalizeBusinessOverviewAccountSection(value) {
|
||
const normalized = String(value ?? "").replace(/\s+/g, "").trim();
|
||
if (!normalized) {
|
||
return null;
|
||
}
|
||
const sectionMatch = normalized.match(/^(\d{2})(?:[.\-_/]|$)/);
|
||
return sectionMatch?.[1] ?? normalized;
|
||
}
|
||
function deriveBusinessOverviewDocumentActivityProfile(result, periodScope) {
|
||
if (!result || result.error || result.matched_rows <= 0) {
|
||
return null;
|
||
}
|
||
const documentTypeBuckets = new Map();
|
||
const accountSectionBuckets = new Map();
|
||
for (const row of result.rows) {
|
||
const marker = normalizeBusinessOverviewActivityMarker(row);
|
||
const count = rowAmountValue(row);
|
||
if (!marker || count === null || count <= 0) {
|
||
continue;
|
||
}
|
||
if (marker === "DOC_TYPE_DOCS") {
|
||
const documentType = rowDebitAccountValue(row) ?? rowAccountValue(row);
|
||
if (documentType) {
|
||
documentTypeBuckets.set(documentType, (documentTypeBuckets.get(documentType) ?? 0) + count);
|
||
}
|
||
continue;
|
||
}
|
||
if (marker === "SECTION_DT_OPS" || marker === "SECTION_KT_OPS") {
|
||
const section = normalizeBusinessOverviewAccountSection(rowDebitAccountValue(row) ?? rowAccountValue(row));
|
||
if (section) {
|
||
accountSectionBuckets.set(section, (accountSectionBuckets.get(section) ?? 0) + count);
|
||
}
|
||
}
|
||
}
|
||
const totalDocumentTypeCount = Array.from(documentTypeBuckets.values()).reduce((sum, count) => sum + count, 0);
|
||
const totalAccountSectionOperations = Array.from(accountSectionBuckets.values()).reduce((sum, count) => sum + count, 0);
|
||
const topDocumentTypes = Array.from(documentTypeBuckets.entries())
|
||
.map(([documentType, count]) => ({
|
||
document_type: documentType,
|
||
count,
|
||
share_pct: percentageOfTotal(count, totalDocumentTypeCount)
|
||
}))
|
||
.sort((left, right) => right.count - left.count || left.document_type.localeCompare(right.document_type, "ru"))
|
||
.slice(0, 5);
|
||
const topAccountSections = Array.from(accountSectionBuckets.entries())
|
||
.map(([accountSection, operationCount]) => ({
|
||
account_section: accountSection,
|
||
operation_count: operationCount,
|
||
share_pct: percentageOfTotal(operationCount, totalAccountSectionOperations)
|
||
}))
|
||
.sort((left, right) => right.operation_count - left.operation_count || left.account_section.localeCompare(right.account_section, "ru"))
|
||
.slice(0, 5);
|
||
if (topDocumentTypes.length <= 0 && topAccountSections.length <= 0) {
|
||
return null;
|
||
}
|
||
return {
|
||
period_scope: periodScope,
|
||
rows_matched: result.matched_rows,
|
||
total_document_type_count: totalDocumentTypeCount,
|
||
total_account_section_operations: totalAccountSectionOperations,
|
||
top_document_types: topDocumentTypes,
|
||
top_account_sections: topAccountSections,
|
||
inference_basis: "document_type_and_account_section_profile_confirmed_1c_rows"
|
||
};
|
||
}
|
||
function sumBusinessOverviewMarker(result, marker) {
|
||
return result.rows.reduce((sum, row) => {
|
||
const currentMarker = normalizeBusinessOverviewActivityMarker(row);
|
||
if (currentMarker !== marker) {
|
||
return sum;
|
||
}
|
||
const amount = rowAmountValue(row);
|
||
return amount === null ? sum : sum + amount;
|
||
}, 0);
|
||
}
|
||
function deriveBusinessOverviewCounterpartyProfile(result, periodScope) {
|
||
if (!result || result.error || result.matched_rows <= 0) {
|
||
return null;
|
||
}
|
||
const totalCounterparties = sumBusinessOverviewMarker(result, "CP_TOTAL");
|
||
const customerActive = sumBusinessOverviewMarker(result, "CP_CUSTOMER_ACTIVE");
|
||
const supplierActive = sumBusinessOverviewMarker(result, "CP_SUPPLIER_ACTIVE");
|
||
const mixedActive = sumBusinessOverviewMarker(result, "CP_MIXED_ACTIVE");
|
||
const activeUnion = sumBusinessOverviewMarker(result, "CP_ACTIVE_UNION");
|
||
const customerOnly = Math.max(0, customerActive - mixedActive);
|
||
const supplierOnly = Math.max(0, supplierActive - mixedActive);
|
||
const resolvedActive = customerOnly + supplierOnly + mixedActive;
|
||
const activeCounterparties = Math.max(activeUnion, resolvedActive);
|
||
if (totalCounterparties <= 0 && activeCounterparties <= 0) {
|
||
return null;
|
||
}
|
||
return {
|
||
period_scope: periodScope,
|
||
rows_matched: result.matched_rows,
|
||
total_counterparties: totalCounterparties > 0 ? totalCounterparties : null,
|
||
active_counterparties: activeCounterparties,
|
||
customer_only_count: customerOnly,
|
||
supplier_only_count: supplierOnly,
|
||
mixed_role_count: mixedActive,
|
||
other_or_inactive_count: totalCounterparties > 0 ? Math.max(0, totalCounterparties - resolvedActive) : null,
|
||
inference_basis: "counterparty_population_roles_confirmed_1c_rows"
|
||
};
|
||
}
|
||
function deriveBusinessOverviewContractUsageProfile(result, periodScope) {
|
||
if (!result || result.error || result.matched_rows <= 0) {
|
||
return null;
|
||
}
|
||
const totalContracts = sumBusinessOverviewMarker(result, "CT_TOTAL");
|
||
const usedContracts = sumBusinessOverviewMarker(result, "CT_USED");
|
||
if (totalContracts <= 0 && usedContracts <= 0) {
|
||
return null;
|
||
}
|
||
const cappedUsedContracts = totalContracts > 0 ? Math.min(usedContracts, totalContracts) : usedContracts;
|
||
return {
|
||
period_scope: periodScope,
|
||
rows_matched: result.matched_rows,
|
||
total_contracts: totalContracts > 0 ? totalContracts : null,
|
||
used_contracts: usedContracts,
|
||
unused_contracts: totalContracts > 0 ? Math.max(0, totalContracts - cappedUsedContracts) : null,
|
||
used_contract_share_pct: totalContracts > 0 ? percentageOfTotal(cappedUsedContracts, totalContracts) : null,
|
||
inference_basis: "contract_usage_overview_confirmed_1c_rows"
|
||
};
|
||
}
|
||
function deriveValueFlow(result, counterparty, periodScope, direction, aggregationAxis) {
|
||
if (!result || result.error || result.matched_rows <= 0) {
|
||
return null;
|
||
}
|
||
let totalAmount = 0;
|
||
let rowsWithAmount = 0;
|
||
for (const row of result.rows) {
|
||
const amount = rowAmountValue(row);
|
||
if (amount !== null) {
|
||
totalAmount += amount;
|
||
rowsWithAmount += 1;
|
||
}
|
||
}
|
||
if (rowsWithAmount <= 0) {
|
||
return null;
|
||
}
|
||
const dates = result.rows
|
||
.map((row) => rowDateValue(row))
|
||
.filter((value) => Boolean(value))
|
||
.sort();
|
||
return {
|
||
value_flow_direction: direction,
|
||
counterparty,
|
||
period_scope: periodScope,
|
||
aggregation_axis: aggregationAxis,
|
||
rows_matched: result.matched_rows,
|
||
rows_with_amount: rowsWithAmount,
|
||
total_amount: totalAmount,
|
||
total_amount_human_ru: formatAmountHumanRu(totalAmount),
|
||
first_movement_date: dates[0] ?? null,
|
||
latest_movement_date: dates[dates.length - 1] ?? null,
|
||
coverage_limited_by_probe_limit: result.coverage_limited_by_probe_limit,
|
||
coverage_recovered_by_period_chunking: result.coverage_recovered_by_period_chunking,
|
||
period_chunking_granularity: result.period_chunking_granularity,
|
||
monthly_breakdown: deriveValueFlowMonthBreakdown(result, aggregationAxis),
|
||
inference_basis: "sum_of_confirmed_1c_value_flow_rows"
|
||
};
|
||
}
|
||
function deriveRankedValueFlow(result, input) {
|
||
if (!result || result.error || result.matched_rows <= 0) {
|
||
return null;
|
||
}
|
||
const buckets = new Map();
|
||
let rowsWithAmount = 0;
|
||
for (const row of result.rows) {
|
||
const axisValue = rowCounterpartyValue(row);
|
||
const amount = rowAmountValue(row);
|
||
if (!axisValue || amount === null) {
|
||
continue;
|
||
}
|
||
rowsWithAmount += 1;
|
||
const current = buckets.get(axisValue) ?? { rows_with_amount: 0, total_amount: 0 };
|
||
current.rows_with_amount += 1;
|
||
current.total_amount += amount;
|
||
buckets.set(axisValue, current);
|
||
}
|
||
if (rowsWithAmount <= 0 || buckets.size <= 0) {
|
||
return null;
|
||
}
|
||
const rankedValues = Array.from(buckets.entries())
|
||
.map(([axisValue, bucket]) => ({
|
||
axis_value: axisValue,
|
||
rows_with_amount: bucket.rows_with_amount,
|
||
total_amount: bucket.total_amount,
|
||
total_amount_human_ru: formatAmountHumanRu(bucket.total_amount)
|
||
}))
|
||
.sort((left, right) => {
|
||
const amountDelta = right.total_amount - left.total_amount;
|
||
if (input.rankingNeed === "bottom_asc") {
|
||
if (amountDelta !== 0) {
|
||
return -amountDelta;
|
||
}
|
||
}
|
||
else if (amountDelta !== 0) {
|
||
return amountDelta;
|
||
}
|
||
return left.axis_value.localeCompare(right.axis_value, "ru");
|
||
})
|
||
.slice(0, 5);
|
||
return {
|
||
value_flow_direction: input.direction,
|
||
ranking_need: input.rankingNeed,
|
||
ranking_axis: "counterparty",
|
||
organization_scope: input.organizationScope,
|
||
period_scope: input.periodScope,
|
||
rows_matched: result.matched_rows,
|
||
rows_with_amount: rowsWithAmount,
|
||
ranked_values: rankedValues,
|
||
coverage_limited_by_probe_limit: result.coverage_limited_by_probe_limit,
|
||
coverage_recovered_by_period_chunking: result.coverage_recovered_by_period_chunking,
|
||
period_chunking_granularity: result.period_chunking_granularity,
|
||
inference_basis: "ranked_counterparty_totals_from_confirmed_1c_value_flow_rows"
|
||
};
|
||
}
|
||
function deriveValueFlowSideSummary(result) {
|
||
if (!result || result.error || result.matched_rows <= 0) {
|
||
return {
|
||
rows_matched: 0,
|
||
rows_with_amount: 0,
|
||
total_amount: 0,
|
||
total_amount_human_ru: formatAmountHumanRu(0),
|
||
first_movement_date: null,
|
||
latest_movement_date: null,
|
||
coverage_limited_by_probe_limit: false,
|
||
coverage_recovered_by_period_chunking: false,
|
||
period_chunking_granularity: null
|
||
};
|
||
}
|
||
let totalAmount = 0;
|
||
let rowsWithAmount = 0;
|
||
for (const row of result.rows) {
|
||
const amount = rowAmountValue(row);
|
||
if (amount !== null) {
|
||
totalAmount += amount;
|
||
rowsWithAmount += 1;
|
||
}
|
||
}
|
||
const dates = result.rows
|
||
.map((row) => rowDateValue(row))
|
||
.filter((value) => Boolean(value))
|
||
.sort();
|
||
return {
|
||
rows_matched: result.matched_rows,
|
||
rows_with_amount: rowsWithAmount,
|
||
total_amount: totalAmount,
|
||
total_amount_human_ru: formatAmountHumanRu(totalAmount),
|
||
first_movement_date: dates[0] ?? null,
|
||
latest_movement_date: dates[dates.length - 1] ?? null,
|
||
coverage_limited_by_probe_limit: result.coverage_limited_by_probe_limit,
|
||
coverage_recovered_by_period_chunking: result.coverage_recovered_by_period_chunking,
|
||
period_chunking_granularity: result.period_chunking_granularity
|
||
};
|
||
}
|
||
function deriveBidirectionalValueFlow(input) {
|
||
const incoming = deriveValueFlowSideSummary(input.incomingResult);
|
||
const outgoing = deriveValueFlowSideSummary(input.outgoingResult);
|
||
if (incoming.rows_with_amount <= 0 && outgoing.rows_with_amount <= 0) {
|
||
return null;
|
||
}
|
||
const netAmount = incoming.total_amount - outgoing.total_amount;
|
||
return {
|
||
counterparty: input.counterparty,
|
||
period_scope: input.periodScope,
|
||
aggregation_axis: input.aggregationAxis,
|
||
incoming_customer_revenue: incoming,
|
||
outgoing_supplier_payout: outgoing,
|
||
net_amount: netAmount,
|
||
net_amount_human_ru: formatAmountHumanRu(Math.abs(netAmount)),
|
||
net_direction: netDirectionFromAmount(netAmount),
|
||
coverage_limited_by_probe_limit: incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
|
||
coverage_recovered_by_period_chunking: incoming.coverage_recovered_by_period_chunking || outgoing.coverage_recovered_by_period_chunking,
|
||
period_chunking_granularity: incoming.period_chunking_granularity ?? outgoing.period_chunking_granularity ?? null,
|
||
monthly_breakdown: deriveBidirectionalValueFlowMonthBreakdown({
|
||
incomingResult: input.incomingResult,
|
||
outgoingResult: input.outgoingResult,
|
||
aggregationAxis: input.aggregationAxis
|
||
}),
|
||
inference_basis: "incoming_minus_outgoing_confirmed_1c_value_flow_rows"
|
||
};
|
||
}
|
||
function summarizeBidirectionalValueFlowRows(input) {
|
||
const incoming = input.incomingResult;
|
||
const outgoing = input.outgoingResult;
|
||
if (!incoming && !outgoing) {
|
||
return null;
|
||
}
|
||
const incomingSummary = incoming?.error
|
||
? "incoming value-flow query failed"
|
||
: incoming?.coverage_recovered_by_period_chunking && incoming.period_chunking_granularity === "month"
|
||
? `${incoming.period_chunk_count} monthly incoming value-flow probes fetched ${incoming.fetched_rows} rows total, ${incoming.matched_rows} matched`
|
||
: `${incoming?.fetched_rows ?? 0} incoming value-flow rows fetched, ${incoming?.matched_rows ?? 0} matched`;
|
||
const outgoingSummary = outgoing?.error
|
||
? "outgoing supplier-payout query failed"
|
||
: outgoing?.coverage_recovered_by_period_chunking && outgoing.period_chunking_granularity === "month"
|
||
? `${outgoing.period_chunk_count} monthly outgoing supplier-payout probes fetched ${outgoing.fetched_rows} rows total, ${outgoing.matched_rows} matched`
|
||
: `${outgoing?.fetched_rows ?? 0} outgoing supplier-payout rows fetched, ${outgoing?.matched_rows ?? 0} matched`;
|
||
return `${incomingSummary}; ${outgoingSummary}`;
|
||
}
|
||
function deriveBusinessOverviewTaxPosition(result, periodScope) {
|
||
if (!result || result.error || result.matched_rows <= 0 || !periodScope) {
|
||
return null;
|
||
}
|
||
let salesVatAmount = 0;
|
||
let purchaseVatAmount = 0;
|
||
let rowsWithAmount = 0;
|
||
for (const row of result.rows) {
|
||
const amount = rowAmountValue(row);
|
||
if (amount === null) {
|
||
continue;
|
||
}
|
||
const marker = String(rowDocumentValue(row) ?? "").toLowerCase();
|
||
const account = String(rowAccountValue(row) ?? "").toLowerCase();
|
||
if (marker.includes("sales") || marker.includes("продаж") || account.startsWith("68")) {
|
||
salesVatAmount += amount;
|
||
rowsWithAmount += 1;
|
||
continue;
|
||
}
|
||
if (marker.includes("purchase") || marker.includes("покуп") || account.startsWith("19")) {
|
||
purchaseVatAmount += amount;
|
||
rowsWithAmount += 1;
|
||
}
|
||
}
|
||
if (rowsWithAmount <= 0) {
|
||
return null;
|
||
}
|
||
const netVatAmount = salesVatAmount - purchaseVatAmount;
|
||
const netVatDirection = netVatAmount > 0
|
||
? "vat_to_pay"
|
||
: netVatAmount < 0
|
||
? "vat_to_recover_or_offset"
|
||
: "balanced";
|
||
return {
|
||
period_scope: periodScope,
|
||
rows_matched: result.matched_rows,
|
||
rows_with_amount: rowsWithAmount,
|
||
sales_vat_amount: salesVatAmount,
|
||
sales_vat_amount_human_ru: formatAmountHumanRu(salesVatAmount),
|
||
purchase_vat_amount: purchaseVatAmount,
|
||
purchase_vat_amount_human_ru: formatAmountHumanRu(purchaseVatAmount),
|
||
net_vat_amount: netVatAmount,
|
||
net_vat_amount_human_ru: formatAmountHumanRu(Math.abs(netVatAmount)),
|
||
net_vat_direction: netVatDirection,
|
||
inference_basis: "sales_book_minus_purchase_book_confirmed_1c_vat_rows"
|
||
};
|
||
}
|
||
function deriveBusinessOverviewTradingMarginProxy(result, periodScope) {
|
||
if (!result || result.error || result.matched_rows <= 0 || !periodScope) {
|
||
return null;
|
||
}
|
||
const itemBuckets = new Map();
|
||
let salesRowsWithAmount = 0;
|
||
let purchaseRowsWithAmount = 0;
|
||
let salesRevenue = 0;
|
||
let purchaseCostProxy = 0;
|
||
for (const row of result.rows) {
|
||
const amount = rowAmountValue(row);
|
||
if (amount === null || amount <= 0) {
|
||
continue;
|
||
}
|
||
const isSale = accountTextMatchesPrefix(rowCreditAccountValue(row), ["41.01"]);
|
||
const isPurchase = accountTextMatchesPrefix(rowDebitAccountValue(row), ["41.01"]);
|
||
if (!isSale && !isPurchase) {
|
||
continue;
|
||
}
|
||
const item = rowInventoryItemValue(row) ?? "unknown_item";
|
||
const quantity = rowQuantityValue(row) ?? 0;
|
||
const bucket = itemBuckets.get(item) ?? {
|
||
sales_revenue: 0,
|
||
purchase_cost_proxy: 0,
|
||
sales_quantity: 0,
|
||
purchase_quantity: 0
|
||
};
|
||
if (isSale) {
|
||
salesRowsWithAmount += 1;
|
||
salesRevenue += amount;
|
||
bucket.sales_revenue += amount;
|
||
bucket.sales_quantity += quantity;
|
||
}
|
||
if (isPurchase) {
|
||
purchaseRowsWithAmount += 1;
|
||
purchaseCostProxy += amount;
|
||
bucket.purchase_cost_proxy += amount;
|
||
bucket.purchase_quantity += quantity;
|
||
}
|
||
itemBuckets.set(item, bucket);
|
||
}
|
||
if (salesRowsWithAmount <= 0 && purchaseRowsWithAmount <= 0) {
|
||
return null;
|
||
}
|
||
const grossSpreadProxy = salesRevenue - purchaseCostProxy;
|
||
const marginToRevenuePct = salesRevenue > 0 ? percentageOfTotal(grossSpreadProxy, salesRevenue) : null;
|
||
const markupToPurchasePct = purchaseCostProxy > 0 ? percentageOfTotal(grossSpreadProxy, purchaseCostProxy) : null;
|
||
const topItemsBySales = Array.from(itemBuckets.entries())
|
||
.map(([item, bucket]) => ({
|
||
item,
|
||
sales_revenue: bucket.sales_revenue,
|
||
sales_revenue_human_ru: formatAmountHumanRu(bucket.sales_revenue),
|
||
purchase_cost_proxy: bucket.purchase_cost_proxy,
|
||
purchase_cost_proxy_human_ru: formatAmountHumanRu(bucket.purchase_cost_proxy),
|
||
gross_spread_proxy: bucket.sales_revenue - bucket.purchase_cost_proxy,
|
||
gross_spread_proxy_human_ru: formatAmountHumanRu(bucket.sales_revenue - bucket.purchase_cost_proxy),
|
||
sales_quantity: bucket.sales_quantity,
|
||
purchase_quantity: bucket.purchase_quantity
|
||
}))
|
||
.sort((left, right) => {
|
||
const salesDelta = right.sales_revenue - left.sales_revenue;
|
||
return salesDelta !== 0 ? salesDelta : left.item.localeCompare(right.item, "ru");
|
||
})
|
||
.slice(0, 5);
|
||
return {
|
||
period_scope: periodScope,
|
||
rows_matched: result.matched_rows,
|
||
sales_rows_with_amount: salesRowsWithAmount,
|
||
purchase_rows_with_amount: purchaseRowsWithAmount,
|
||
sales_revenue: salesRevenue,
|
||
sales_revenue_human_ru: formatAmountHumanRu(salesRevenue),
|
||
purchase_cost_proxy: purchaseCostProxy,
|
||
purchase_cost_proxy_human_ru: formatAmountHumanRu(purchaseCostProxy),
|
||
gross_spread_proxy: grossSpreadProxy,
|
||
gross_spread_proxy_human_ru: formatAmountHumanRu(grossSpreadProxy),
|
||
margin_to_revenue_pct: marginToRevenuePct,
|
||
markup_to_purchase_pct: markupToPurchasePct,
|
||
top_items_by_sales: topItemsBySales,
|
||
inference_basis: "sales_documents_minus_purchase_documents_confirmed_1c_rows"
|
||
};
|
||
}
|
||
function deriveBusinessOverviewDebtSide(result) {
|
||
if (!result || result.error || result.matched_rows <= 0) {
|
||
return {
|
||
rows_matched: 0,
|
||
rows_with_amount: 0,
|
||
total_amount: 0,
|
||
total_amount_human_ru: formatAmountHumanRu(0),
|
||
top_counterparties: []
|
||
};
|
||
}
|
||
const buckets = new Map();
|
||
let rowsWithAmount = 0;
|
||
let totalAmount = 0;
|
||
for (const row of result.rows) {
|
||
const amount = rowAmountValue(row);
|
||
if (amount === null) {
|
||
continue;
|
||
}
|
||
rowsWithAmount += 1;
|
||
totalAmount += amount;
|
||
const counterparty = rowCounterpartyValue(row) ?? "unknown_counterparty";
|
||
const current = buckets.get(counterparty) ?? { rows_with_amount: 0, total_amount: 0 };
|
||
current.rows_with_amount += 1;
|
||
current.total_amount += amount;
|
||
buckets.set(counterparty, current);
|
||
}
|
||
const topCounterparties = Array.from(buckets.entries())
|
||
.map(([axisValue, bucket]) => ({
|
||
axis_value: axisValue,
|
||
rows_with_amount: bucket.rows_with_amount,
|
||
total_amount: bucket.total_amount,
|
||
total_amount_human_ru: formatAmountHumanRu(bucket.total_amount)
|
||
}))
|
||
.sort((left, right) => {
|
||
const amountDelta = right.total_amount - left.total_amount;
|
||
return amountDelta !== 0 ? amountDelta : left.axis_value.localeCompare(right.axis_value, "ru");
|
||
})
|
||
.slice(0, 5);
|
||
return {
|
||
rows_matched: result.matched_rows,
|
||
rows_with_amount: rowsWithAmount,
|
||
total_amount: totalAmount,
|
||
total_amount_human_ru: formatAmountHumanRu(totalAmount),
|
||
top_counterparties: topCounterparties
|
||
};
|
||
}
|
||
function deriveBusinessOverviewDebtPosition(input) {
|
||
if (!input.debtAsOfDate) {
|
||
return null;
|
||
}
|
||
const receivables = deriveBusinessOverviewDebtSide(input.receivablesResult);
|
||
const payables = deriveBusinessOverviewDebtSide(input.payablesResult);
|
||
if (receivables.rows_with_amount <= 0 && payables.rows_with_amount <= 0) {
|
||
return null;
|
||
}
|
||
const netDebtPositionAmount = receivables.total_amount - payables.total_amount;
|
||
const netDebtPositionDirection = netDebtPositionAmount > 0
|
||
? "net_receivable"
|
||
: netDebtPositionAmount < 0
|
||
? "net_payable"
|
||
: "balanced";
|
||
return {
|
||
as_of_date: input.debtAsOfDate,
|
||
receivables,
|
||
payables,
|
||
net_debt_position_amount: netDebtPositionAmount,
|
||
net_debt_position_amount_human_ru: formatAmountHumanRu(Math.abs(netDebtPositionAmount)),
|
||
net_debt_position_direction: netDebtPositionDirection,
|
||
inference_basis: "receivables_minus_payables_confirmed_1c_balance_rows"
|
||
};
|
||
}
|
||
function percentageOfTotal(part, total) {
|
||
if (!Number.isFinite(part) || !Number.isFinite(total) || total <= 0) {
|
||
return null;
|
||
}
|
||
return Math.round((part / total) * 10_000) / 100;
|
||
}
|
||
function ratioOfTotal(part, total) {
|
||
if (!Number.isFinite(part) || !Number.isFinite(total) || total <= 0) {
|
||
return null;
|
||
}
|
||
return Math.round((part / total) * 100) / 100;
|
||
}
|
||
function deriveBusinessOverviewDebtOpenSettlementQuality(input) {
|
||
if (!input.debtAsOfDate || !input.openContractsResult || input.openContractsResult.error || input.openContractsResult.matched_rows <= 0) {
|
||
return null;
|
||
}
|
||
const debtAsOfDate = input.debtAsOfDate;
|
||
const counterpartyBuckets = new Map();
|
||
const contractBuckets = new Map();
|
||
const counterparties = new Set();
|
||
const contracts = new Set();
|
||
let rowsWithAmount = 0;
|
||
let grossOpenAmount = 0;
|
||
let rowsWithoutCounterparty = 0;
|
||
let rowsWithoutContract = 0;
|
||
for (const row of input.openContractsResult.rows) {
|
||
const amount = rowAmountValue(row);
|
||
if (amount === null) {
|
||
continue;
|
||
}
|
||
const absAmount = Math.abs(amount);
|
||
if (absAmount <= 0) {
|
||
continue;
|
||
}
|
||
rowsWithAmount += 1;
|
||
grossOpenAmount += absAmount;
|
||
const counterparty = rowCounterpartyValue(row);
|
||
const contract = rowContractValue(row);
|
||
if (counterparty) {
|
||
counterparties.add(counterparty);
|
||
const current = counterpartyBuckets.get(counterparty) ?? { rows_with_amount: 0, total_amount: 0 };
|
||
current.rows_with_amount += 1;
|
||
current.total_amount += absAmount;
|
||
counterpartyBuckets.set(counterparty, current);
|
||
}
|
||
else {
|
||
rowsWithoutCounterparty += 1;
|
||
}
|
||
if (contract) {
|
||
const contractStartDate = rowOpenSettlementContractStartDateValue(row);
|
||
contracts.add(contract);
|
||
const current = contractBuckets.get(contract) ?? {
|
||
counterparty,
|
||
contract_start_date: contractStartDate,
|
||
rows_with_amount: 0,
|
||
total_amount: 0
|
||
};
|
||
if (!current.counterparty && counterparty) {
|
||
current.counterparty = counterparty;
|
||
}
|
||
current.contract_start_date = earlierIsoDate(current.contract_start_date, contractStartDate);
|
||
current.rows_with_amount += 1;
|
||
current.total_amount += absAmount;
|
||
contractBuckets.set(contract, current);
|
||
}
|
||
else {
|
||
rowsWithoutContract += 1;
|
||
}
|
||
}
|
||
if (rowsWithAmount <= 0) {
|
||
return null;
|
||
}
|
||
const topCounterparties = Array.from(counterpartyBuckets.entries())
|
||
.map(([axisValue, bucket]) => ({
|
||
axis_value: axisValue,
|
||
rows_with_amount: bucket.rows_with_amount,
|
||
total_amount: bucket.total_amount,
|
||
total_amount_human_ru: formatAmountHumanRu(bucket.total_amount)
|
||
}))
|
||
.sort((left, right) => {
|
||
const amountDelta = right.total_amount - left.total_amount;
|
||
return amountDelta !== 0 ? amountDelta : left.axis_value.localeCompare(right.axis_value, "ru");
|
||
})
|
||
.slice(0, 5);
|
||
const topContracts = Array.from(contractBuckets.entries())
|
||
.map(([contract, bucket]) => ({
|
||
contract,
|
||
counterparty: bucket.counterparty,
|
||
contract_start_date: bucket.contract_start_date,
|
||
rows_with_amount: bucket.rows_with_amount,
|
||
total_amount: bucket.total_amount,
|
||
total_amount_human_ru: formatAmountHumanRu(bucket.total_amount),
|
||
share_of_gross_open_amount_pct: percentageOfTotal(bucket.total_amount, grossOpenAmount)
|
||
}))
|
||
.sort((left, right) => {
|
||
const amountDelta = right.total_amount - left.total_amount;
|
||
return amountDelta !== 0 ? amountDelta : left.contract.localeCompare(right.contract, "ru");
|
||
})
|
||
.slice(0, 5);
|
||
const agedContractBuckets = Array.from(contractBuckets.entries())
|
||
.map(([contract, bucket]) => {
|
||
if (!bucket.contract_start_date) {
|
||
return null;
|
||
}
|
||
return {
|
||
contract,
|
||
counterparty: bucket.counterparty,
|
||
start_date: bucket.contract_start_date,
|
||
age_days: daysBetweenIsoDates(bucket.contract_start_date, debtAsOfDate),
|
||
total_amount: bucket.total_amount,
|
||
total_amount_human_ru: formatAmountHumanRu(bucket.total_amount),
|
||
share_of_gross_open_amount_pct: percentageOfTotal(bucket.total_amount, grossOpenAmount)
|
||
};
|
||
})
|
||
.filter((item) => Boolean(item))
|
||
.sort((left, right) => {
|
||
const leftAge = left.age_days ?? -1;
|
||
const rightAge = right.age_days ?? -1;
|
||
const ageDelta = rightAge - leftAge;
|
||
if (ageDelta !== 0) {
|
||
return ageDelta;
|
||
}
|
||
const amountDelta = right.total_amount - left.total_amount;
|
||
return amountDelta !== 0 ? amountDelta : left.contract.localeCompare(right.contract, "ru");
|
||
});
|
||
const debtAgeDates = agedContractBuckets.map((bucket) => bucket.start_date).sort();
|
||
const maxAgeDays = agedContractBuckets.reduce((max, bucket) => {
|
||
if (bucket.age_days === null) {
|
||
return max;
|
||
}
|
||
return max === null ? bucket.age_days : Math.max(max, bucket.age_days);
|
||
}, null);
|
||
const ageSignal = agedContractBuckets.length > 0
|
||
? {
|
||
contracts_with_start_date: agedContractBuckets.length,
|
||
oldest_start_date: debtAgeDates[0] ?? null,
|
||
latest_start_date: debtAgeDates[debtAgeDates.length - 1] ?? null,
|
||
max_age_days: maxAgeDays,
|
||
top_aged_contracts: agedContractBuckets.slice(0, 5),
|
||
inference_basis: "contract_dates_from_open_settlement_rows"
|
||
}
|
||
: null;
|
||
return {
|
||
as_of_date: debtAsOfDate,
|
||
rows_matched: input.openContractsResult.matched_rows,
|
||
rows_with_amount: rowsWithAmount,
|
||
gross_open_amount: grossOpenAmount,
|
||
gross_open_amount_human_ru: formatAmountHumanRu(grossOpenAmount),
|
||
unique_counterparties: counterparties.size,
|
||
unique_contracts: contracts.size,
|
||
rows_without_counterparty: rowsWithoutCounterparty,
|
||
rows_without_contract: rowsWithoutContract,
|
||
top_counterparties: topCounterparties,
|
||
top_contracts: topContracts,
|
||
concentration_top_counterparty_pct: percentageOfTotal(topCounterparties[0]?.total_amount ?? 0, grossOpenAmount),
|
||
concentration_top_contract_pct: percentageOfTotal(topContracts[0]?.total_amount ?? 0, grossOpenAmount),
|
||
age_signal: ageSignal,
|
||
inference_basis: "open_contracts_confirmed_1c_balance_rows"
|
||
};
|
||
}
|
||
function debtStalenessRiskBand(input) {
|
||
if (input.maxContractAgeDays >= 365 && input.topContractSharePct >= 50) {
|
||
return "high";
|
||
}
|
||
if (input.maxContractAgeDays >= 365 || input.topContractSharePct >= 50) {
|
||
return "elevated";
|
||
}
|
||
if (input.maxContractAgeDays >= 180 || input.topContractSharePct >= 35) {
|
||
return "watch";
|
||
}
|
||
return "lower_visible_risk";
|
||
}
|
||
function deriveBusinessOverviewDebtStalenessRiskProxy(quality) {
|
||
const ageSignal = quality?.age_signal;
|
||
const topAgedContract = ageSignal?.top_aged_contracts[0];
|
||
const topContractSharePct = topAgedContract?.share_of_gross_open_amount_pct;
|
||
if (!quality ||
|
||
!ageSignal?.oldest_start_date ||
|
||
ageSignal.max_age_days === null ||
|
||
ageSignal.max_age_days === undefined ||
|
||
!topAgedContract ||
|
||
topContractSharePct === null ||
|
||
topContractSharePct === undefined) {
|
||
return null;
|
||
}
|
||
return {
|
||
as_of_date: quality.as_of_date,
|
||
gross_open_amount: quality.gross_open_amount,
|
||
gross_open_amount_human_ru: quality.gross_open_amount_human_ru,
|
||
oldest_contract_start_date: ageSignal.oldest_start_date,
|
||
max_contract_age_days: ageSignal.max_age_days,
|
||
top_contract: topAgedContract.contract,
|
||
top_contract_counterparty: topAgedContract.counterparty,
|
||
top_contract_amount: topAgedContract.total_amount,
|
||
top_contract_amount_human_ru: topAgedContract.total_amount_human_ru,
|
||
top_contract_share_pct: topContractSharePct,
|
||
risk_band: debtStalenessRiskBand({
|
||
maxContractAgeDays: ageSignal.max_age_days,
|
||
topContractSharePct
|
||
}),
|
||
inference_basis: "contract_date_age_and_open_balance_concentration_confirmed_1c_rows"
|
||
};
|
||
}
|
||
function debtStalenessRiskBandRu(riskBand) {
|
||
if (riskBand === "high") {
|
||
return "высокая зона внимания";
|
||
}
|
||
if (riskBand === "elevated") {
|
||
return "повышенная зона внимания";
|
||
}
|
||
if (riskBand === "watch") {
|
||
return "зона наблюдения";
|
||
}
|
||
return "низкий видимый риск";
|
||
}
|
||
function daysBetweenIsoDates(leftIsoDate, rightIsoDate) {
|
||
const leftMatch = leftIsoDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
||
const rightMatch = rightIsoDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
||
if (!leftMatch || !rightMatch) {
|
||
return null;
|
||
}
|
||
const left = Date.UTC(Number(leftMatch[1]), Number(leftMatch[2]) - 1, Number(leftMatch[3]));
|
||
const right = Date.UTC(Number(rightMatch[1]), Number(rightMatch[2]) - 1, Number(rightMatch[3]));
|
||
if (!Number.isFinite(left) || !Number.isFinite(right)) {
|
||
return null;
|
||
}
|
||
return Math.max(0, Math.floor((right - left) / 86_400_000));
|
||
}
|
||
function deriveBusinessOverviewInventoryAgingSignal(result, inventoryAsOfDate) {
|
||
if (!result || result.error || result.matched_rows <= 0) {
|
||
return null;
|
||
}
|
||
const dates = result.rows
|
||
.map((row) => rowDateValue(row))
|
||
.filter((value) => Boolean(value))
|
||
.sort((left, right) => left.localeCompare(right));
|
||
if (dates.length <= 0) {
|
||
return null;
|
||
}
|
||
const oldestPurchaseDate = dates[0] ?? null;
|
||
const latestPurchaseDate = dates[dates.length - 1] ?? null;
|
||
return {
|
||
rows_matched: result.matched_rows,
|
||
rows_with_purchase_date: dates.length,
|
||
oldest_purchase_date: oldestPurchaseDate,
|
||
latest_purchase_date: latestPurchaseDate,
|
||
max_age_days: oldestPurchaseDate ? daysBetweenIsoDates(oldestPurchaseDate, inventoryAsOfDate) : null,
|
||
inference_basis: "inventory_purchase_dates_from_confirmed_1c_rows"
|
||
};
|
||
}
|
||
function deriveBusinessOverviewInventoryPosition(input) {
|
||
const { inventoryAsOfDate, inventoryOnHandResult } = input;
|
||
if (!inventoryAsOfDate || !inventoryOnHandResult || inventoryOnHandResult.error || inventoryOnHandResult.matched_rows <= 0) {
|
||
return null;
|
||
}
|
||
const buckets = new Map();
|
||
let rowsWithAmount = 0;
|
||
let rowsWithQuantity = 0;
|
||
let totalAmount = 0;
|
||
let totalQuantity = 0;
|
||
for (const row of inventoryOnHandResult.rows) {
|
||
const amount = rowAmountValue(row);
|
||
const quantity = rowQuantityValue(row);
|
||
if (amount === null && quantity === null) {
|
||
continue;
|
||
}
|
||
const item = rowInventoryItemValue(row) ?? "unknown_item";
|
||
const current = buckets.get(item) ?? {
|
||
rows_with_amount: 0,
|
||
rows_with_quantity: 0,
|
||
total_amount: 0,
|
||
total_quantity: 0
|
||
};
|
||
if (amount !== null) {
|
||
rowsWithAmount += 1;
|
||
totalAmount += amount;
|
||
current.rows_with_amount += 1;
|
||
current.total_amount += amount;
|
||
}
|
||
if (quantity !== null) {
|
||
rowsWithQuantity += 1;
|
||
totalQuantity += quantity;
|
||
current.rows_with_quantity += 1;
|
||
current.total_quantity += quantity;
|
||
}
|
||
buckets.set(item, current);
|
||
}
|
||
if (rowsWithAmount <= 0 && rowsWithQuantity <= 0) {
|
||
return null;
|
||
}
|
||
const topItems = Array.from(buckets.entries())
|
||
.map(([item, bucket]) => ({
|
||
item,
|
||
rows_with_amount: bucket.rows_with_amount,
|
||
rows_with_quantity: bucket.rows_with_quantity,
|
||
total_amount: bucket.total_amount,
|
||
total_amount_human_ru: formatAmountHumanRu(bucket.total_amount),
|
||
total_quantity: bucket.total_quantity
|
||
}))
|
||
.sort((left, right) => {
|
||
const amountDelta = right.total_amount - left.total_amount;
|
||
if (amountDelta !== 0) {
|
||
return amountDelta;
|
||
}
|
||
const quantityDelta = right.total_quantity - left.total_quantity;
|
||
return quantityDelta !== 0 ? quantityDelta : left.item.localeCompare(right.item, "ru");
|
||
})
|
||
.slice(0, 5);
|
||
return {
|
||
as_of_date: inventoryAsOfDate,
|
||
rows_matched: inventoryOnHandResult.matched_rows,
|
||
rows_with_amount: rowsWithAmount,
|
||
rows_with_quantity: rowsWithQuantity,
|
||
total_amount: totalAmount,
|
||
total_amount_human_ru: formatAmountHumanRu(totalAmount),
|
||
total_quantity: totalQuantity,
|
||
top_items: topItems,
|
||
aging_signal: deriveBusinessOverviewInventoryAgingSignal(input.inventoryAgingResult, inventoryAsOfDate),
|
||
inference_basis: "inventory_on_hand_confirmed_1c_balance_rows"
|
||
};
|
||
}
|
||
function deriveBusinessOverviewInventoryTurnoverProxy(input) {
|
||
const { inventoryPosition, tradingMarginProxy } = input;
|
||
if (!inventoryPosition || !tradingMarginProxy) {
|
||
return null;
|
||
}
|
||
if (inventoryPosition.total_amount <= 0 || tradingMarginProxy.sales_revenue <= 0) {
|
||
return null;
|
||
}
|
||
return {
|
||
period_scope: tradingMarginProxy.period_scope,
|
||
as_of_date: inventoryPosition.as_of_date,
|
||
sales_revenue: tradingMarginProxy.sales_revenue,
|
||
sales_revenue_human_ru: tradingMarginProxy.sales_revenue_human_ru,
|
||
inventory_amount: inventoryPosition.total_amount,
|
||
inventory_amount_human_ru: inventoryPosition.total_amount_human_ru,
|
||
sales_to_stock_amount_ratio: ratioOfTotal(tradingMarginProxy.sales_revenue, inventoryPosition.total_amount),
|
||
stock_to_sales_revenue_pct: percentageOfTotal(inventoryPosition.total_amount, tradingMarginProxy.sales_revenue),
|
||
inference_basis: "sales_document_revenue_vs_inventory_balance_confirmed_1c_rows"
|
||
};
|
||
}
|
||
function inventoryStalenessRiskBand(input) {
|
||
if (input.maxPurchaseAgeDays >= 365 && input.salesToStockAmountRatio < 1) {
|
||
return "high";
|
||
}
|
||
if (input.maxPurchaseAgeDays >= 365 || input.salesToStockAmountRatio < 1) {
|
||
return "elevated";
|
||
}
|
||
if (input.maxPurchaseAgeDays >= 180 || input.salesToStockAmountRatio < 2) {
|
||
return "watch";
|
||
}
|
||
return "lower_visible_risk";
|
||
}
|
||
function deriveBusinessOverviewInventoryStalenessRiskProxy(input) {
|
||
const { inventoryPosition, inventoryTurnoverProxy } = input;
|
||
const maxPurchaseAgeDays = inventoryPosition?.aging_signal?.max_age_days;
|
||
const oldestPurchaseDate = inventoryPosition?.aging_signal?.oldest_purchase_date;
|
||
const salesToStockAmountRatio = inventoryTurnoverProxy?.sales_to_stock_amount_ratio;
|
||
if (!inventoryPosition ||
|
||
!inventoryTurnoverProxy ||
|
||
!oldestPurchaseDate ||
|
||
maxPurchaseAgeDays === null ||
|
||
maxPurchaseAgeDays === undefined ||
|
||
salesToStockAmountRatio === null ||
|
||
salesToStockAmountRatio === undefined) {
|
||
return null;
|
||
}
|
||
return {
|
||
as_of_date: inventoryPosition.as_of_date,
|
||
period_scope: inventoryTurnoverProxy.period_scope,
|
||
oldest_purchase_date: oldestPurchaseDate,
|
||
max_purchase_age_days: maxPurchaseAgeDays,
|
||
sales_to_stock_amount_ratio: salesToStockAmountRatio,
|
||
risk_band: inventoryStalenessRiskBand({ maxPurchaseAgeDays, salesToStockAmountRatio }),
|
||
inference_basis: "purchase_date_age_and_sales_to_stock_proxy_confirmed_1c_rows"
|
||
};
|
||
}
|
||
function inventoryStalenessRiskBandRu(riskBand) {
|
||
if (riskBand === "high") {
|
||
return "высокая зона внимания";
|
||
}
|
||
if (riskBand === "elevated") {
|
||
return "повышенная зона внимания";
|
||
}
|
||
if (riskBand === "watch") {
|
||
return "зона наблюдения";
|
||
}
|
||
return "низкий видимый риск";
|
||
}
|
||
function deriveBusinessOverview(input) {
|
||
const incoming = deriveValueFlowSideSummary(input.incomingResult);
|
||
const outgoing = deriveValueFlowSideSummary(input.outgoingResult);
|
||
const rankedIncoming = deriveRankedValueFlow(input.incomingResult, {
|
||
organizationScope: input.organizationScope,
|
||
periodScope: input.periodScope,
|
||
direction: "incoming_customer_revenue",
|
||
rankingNeed: "top_desc"
|
||
});
|
||
const rankedOutgoing = deriveRankedValueFlow(input.outgoingResult, {
|
||
organizationScope: input.organizationScope,
|
||
periodScope: input.periodScope,
|
||
direction: "outgoing_supplier_payout",
|
||
rankingNeed: "top_desc"
|
||
});
|
||
const yearlyBreakdown = deriveBusinessOverviewYearlyBreakdown({
|
||
incomingResult: input.incomingResult,
|
||
outgoingResult: input.outgoingResult
|
||
});
|
||
const activityPeriod = deriveActivityPeriod(input.lifecycleResult);
|
||
const taxPosition = deriveBusinessOverviewTaxPosition(input.taxResult, input.periodScope);
|
||
const tradingMarginProxy = deriveBusinessOverviewTradingMarginProxy(input.tradingMarginResult, input.periodScope);
|
||
const debtPosition = deriveBusinessOverviewDebtPosition({
|
||
receivablesResult: input.receivablesResult,
|
||
payablesResult: input.payablesResult,
|
||
debtAsOfDate: input.debtAsOfDate
|
||
});
|
||
const debtOpenSettlementQuality = deriveBusinessOverviewDebtOpenSettlementQuality({
|
||
openContractsResult: input.openContractsResult,
|
||
debtAsOfDate: input.debtAsOfDate
|
||
});
|
||
const documentActivityProfile = deriveBusinessOverviewDocumentActivityProfile(input.documentActivityProfileResult, input.periodScope);
|
||
const counterpartyProfile = deriveBusinessOverviewCounterpartyProfile(input.counterpartyProfileResult, input.periodScope);
|
||
const contractUsageProfile = deriveBusinessOverviewContractUsageProfile(input.contractUsageProfileResult, input.periodScope);
|
||
const debtStalenessRiskProxy = deriveBusinessOverviewDebtStalenessRiskProxy(debtOpenSettlementQuality);
|
||
const inventoryPosition = deriveBusinessOverviewInventoryPosition({
|
||
inventoryOnHandResult: input.inventoryOnHandResult,
|
||
inventoryAgingResult: input.inventoryAgingResult,
|
||
inventoryAsOfDate: input.inventoryAsOfDate
|
||
});
|
||
const inventoryTurnoverProxy = deriveBusinessOverviewInventoryTurnoverProxy({
|
||
inventoryPosition,
|
||
tradingMarginProxy
|
||
});
|
||
const inventoryStalenessRiskProxy = deriveBusinessOverviewInventoryStalenessRiskProxy({
|
||
inventoryPosition,
|
||
inventoryTurnoverProxy
|
||
});
|
||
const checkedSignalCount = [
|
||
incoming.rows_with_amount > 0,
|
||
outgoing.rows_with_amount > 0,
|
||
Boolean(activityPeriod),
|
||
Boolean(taxPosition),
|
||
Boolean(tradingMarginProxy),
|
||
Boolean(debtPosition),
|
||
Boolean(debtOpenSettlementQuality),
|
||
Boolean(debtStalenessRiskProxy),
|
||
Boolean(documentActivityProfile),
|
||
Boolean(counterpartyProfile),
|
||
Boolean(contractUsageProfile),
|
||
Boolean(inventoryPosition),
|
||
Boolean(inventoryTurnoverProxy),
|
||
Boolean(inventoryStalenessRiskProxy)
|
||
].filter(Boolean).length;
|
||
if (checkedSignalCount <= 0) {
|
||
return null;
|
||
}
|
||
const netAmount = incoming.total_amount - outgoing.total_amount;
|
||
const hasBusinessOverviewProfileSignal = Boolean(documentActivityProfile || counterpartyProfile || contractUsageProfile);
|
||
return {
|
||
organization_scope: input.organizationScope,
|
||
period_scope: input.periodScope,
|
||
incoming_customer_revenue: incoming,
|
||
outgoing_supplier_payout: outgoing,
|
||
net_amount: netAmount,
|
||
net_amount_human_ru: formatAmountHumanRu(Math.abs(netAmount)),
|
||
net_direction: netDirectionFromAmount(netAmount),
|
||
top_customers: rankedIncoming?.ranked_values ?? [],
|
||
top_suppliers: rankedOutgoing?.ranked_values ?? [],
|
||
yearly_breakdown: yearlyBreakdown,
|
||
activity_period: activityPeriod,
|
||
tax_position: taxPosition,
|
||
trading_margin_proxy: tradingMarginProxy,
|
||
debt_position: debtPosition,
|
||
debt_open_settlement_quality: debtOpenSettlementQuality,
|
||
debt_staleness_risk_proxy: debtStalenessRiskProxy,
|
||
inventory_position: inventoryPosition,
|
||
inventory_turnover_proxy: inventoryTurnoverProxy,
|
||
inventory_staleness_risk_proxy: inventoryStalenessRiskProxy,
|
||
document_activity_profile: documentActivityProfile,
|
||
counterparty_profile: counterpartyProfile,
|
||
contract_usage_profile: contractUsageProfile,
|
||
coverage_limited_by_probe_limit: incoming.coverage_limited_by_probe_limit || outgoing.coverage_limited_by_probe_limit,
|
||
checked_signal_count: checkedSignalCount,
|
||
missing_signal_families: [
|
||
tradingMarginProxy ? "accounting_profit_margin" : "profit_margin",
|
||
debtPosition ? null : "debt_position",
|
||
debtOpenSettlementQuality ? "debt_due_date_aging_quality" : "debt_open_settlement_quality",
|
||
taxPosition ? null : "tax_position",
|
||
inventoryPosition
|
||
? inventoryStalenessRiskProxy
|
||
? "inventory_reserve_liquidation_quality"
|
||
: inventoryTurnoverProxy
|
||
? "inventory_liquidity_quality"
|
||
: "inventory_turnover_quality"
|
||
: "inventory_position",
|
||
inventoryPosition?.aging_signal ? null : "inventory_aging_quality"
|
||
].filter((item) => Boolean(item)),
|
||
inference_basis: hasBusinessOverviewProfileSignal || inventoryPosition
|
||
? "business_overview_from_confirmed_1c_multi_family_rows"
|
||
: debtOpenSettlementQuality
|
||
? "business_overview_from_confirmed_1c_multi_family_rows"
|
||
: taxPosition && debtPosition
|
||
? "business_overview_from_confirmed_1c_money_activity_tax_and_debt_rows"
|
||
: taxPosition
|
||
? "business_overview_from_confirmed_1c_money_activity_and_tax_rows"
|
||
: debtPosition
|
||
? "business_overview_from_confirmed_1c_money_activity_and_debt_rows"
|
||
: "business_overview_from_confirmed_1c_money_and_activity_rows"
|
||
};
|
||
}
|
||
function summarizeBusinessOverviewRows(input) {
|
||
const parts = [];
|
||
if (input.incomingResult && !input.incomingResult.error) {
|
||
parts.push(`${input.incomingResult.fetched_rows} incoming rows fetched, ${input.incomingResult.matched_rows} matched`);
|
||
}
|
||
if (input.outgoingResult && !input.outgoingResult.error) {
|
||
parts.push(`${input.outgoingResult.fetched_rows} outgoing rows fetched, ${input.outgoingResult.matched_rows} matched`);
|
||
}
|
||
if (input.lifecycleResult && !input.lifecycleResult.error) {
|
||
parts.push(`${input.lifecycleResult.fetched_rows} activity/document rows fetched, ${input.lifecycleResult.matched_rows} matched`);
|
||
}
|
||
if (input.taxResult && !input.taxResult.error) {
|
||
parts.push(`${input.taxResult.fetched_rows} VAT/tax rows fetched, ${input.taxResult.matched_rows} matched`);
|
||
}
|
||
if (input.tradingMarginResult && !input.tradingMarginResult.error) {
|
||
parts.push(`${input.tradingMarginResult.fetched_rows} trading-margin document rows fetched, ${input.tradingMarginResult.matched_rows} matched`);
|
||
}
|
||
if (input.receivablesResult && !input.receivablesResult.error) {
|
||
parts.push(`${input.receivablesResult.fetched_rows} receivables rows fetched, ${input.receivablesResult.matched_rows} matched`);
|
||
}
|
||
if (input.payablesResult && !input.payablesResult.error) {
|
||
parts.push(`${input.payablesResult.fetched_rows} payables rows fetched, ${input.payablesResult.matched_rows} matched`);
|
||
}
|
||
if (input.openContractsResult && !input.openContractsResult.error) {
|
||
parts.push(`${input.openContractsResult.fetched_rows} open-contract rows fetched, ${input.openContractsResult.matched_rows} matched`);
|
||
}
|
||
if (input.documentActivityProfileResult && !input.documentActivityProfileResult.error) {
|
||
parts.push(`${input.documentActivityProfileResult.fetched_rows} document/account-section profile rows fetched, ${input.documentActivityProfileResult.matched_rows} matched`);
|
||
}
|
||
if (input.counterpartyProfileResult && !input.counterpartyProfileResult.error) {
|
||
parts.push(`${input.counterpartyProfileResult.fetched_rows} counterparty role-profile rows fetched, ${input.counterpartyProfileResult.matched_rows} matched`);
|
||
}
|
||
if (input.contractUsageProfileResult && !input.contractUsageProfileResult.error) {
|
||
parts.push(`${input.contractUsageProfileResult.fetched_rows} contract-usage profile rows fetched, ${input.contractUsageProfileResult.matched_rows} matched`);
|
||
}
|
||
if (input.inventoryOnHandResult && !input.inventoryOnHandResult.error) {
|
||
parts.push(`${input.inventoryOnHandResult.fetched_rows} inventory on-hand rows fetched, ${input.inventoryOnHandResult.matched_rows} matched`);
|
||
}
|
||
if (input.inventoryAgingResult && !input.inventoryAgingResult.error) {
|
||
parts.push(`${input.inventoryAgingResult.fetched_rows} inventory aging rows fetched, ${input.inventoryAgingResult.matched_rows} matched`);
|
||
}
|
||
return parts.length > 0 ? parts.join("; ") : null;
|
||
}
|
||
function buildBusinessOverviewConfirmedFacts(derived) {
|
||
if (!derived) {
|
||
return [];
|
||
}
|
||
const facts = [];
|
||
const organization = derived.organization_scope ? ` по организации ${derived.organization_scope}` : "";
|
||
const period = derived.period_scope ? ` за ${derived.period_scope}` : " за все доступное проверенное окно";
|
||
if (derived.incoming_customer_revenue.rows_with_amount > 0) {
|
||
facts.push(`В 1С подтверждены входящие поступления${organization}${period}: ${derived.incoming_customer_revenue.total_amount_human_ru} по ${derived.incoming_customer_revenue.rows_with_amount} строкам с суммой.`);
|
||
}
|
||
if (derived.outgoing_supplier_payout.rows_with_amount > 0) {
|
||
facts.push(`В 1С подтверждены исходящие платежи/списания${organization}${period}: ${derived.outgoing_supplier_payout.total_amount_human_ru} по ${derived.outgoing_supplier_payout.rows_with_amount} строкам с суммой.`);
|
||
}
|
||
if (derived.top_customers.length > 0) {
|
||
const leader = derived.top_customers[0];
|
||
facts.push(`Самый крупный подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`);
|
||
}
|
||
if (derived.top_suppliers.length > 0) {
|
||
const leader = derived.top_suppliers[0];
|
||
facts.push(`Самый крупный подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`);
|
||
}
|
||
if (derived.yearly_breakdown.length > 0) {
|
||
facts.push(`Годовая раскладка операционного денежного потока построена по подтвержденным строкам 1С за ${yearCountHumanRu(derived.yearly_breakdown.length)}.`);
|
||
}
|
||
if (derived.activity_period) {
|
||
facts.push(`Подтвержденное окно активности в 1С: ${derived.activity_period.first_activity_date} — ${derived.activity_period.latest_activity_date}.`);
|
||
}
|
||
if (derived.document_activity_profile) {
|
||
const profile = derived.document_activity_profile;
|
||
const topDocument = profile.top_document_types[0];
|
||
const topSection = profile.top_account_sections[0];
|
||
const parts = [];
|
||
if (topDocument) {
|
||
const shareText = topDocument.share_pct === null ? "" : ` (${topDocument.share_pct}%)`;
|
||
parts.push(`ведущий тип документов ${topDocument.document_type} — ${topDocument.count} документов${shareText}`);
|
||
}
|
||
if (topSection) {
|
||
const shareText = topSection.share_pct === null ? "" : ` (${topSection.share_pct}%)`;
|
||
parts.push(`ведущий раздел учета ${topSection.account_section} — ${topSection.operation_count} операций${shareText}`);
|
||
}
|
||
if (parts.length > 0) {
|
||
facts.push(`Профиль операционной активности${organization}${period} подтвержден по типам документов и разделам учета 1С: ${parts.join("; ")}. Это activity mix, а не аудит качества учета или полноты бизнес-процессов.`);
|
||
}
|
||
}
|
||
if (derived.counterparty_profile) {
|
||
const profile = derived.counterparty_profile;
|
||
const totalText = profile.total_counterparties === null
|
||
? `${profile.active_counterparties} активных контрагентов`
|
||
: `${profile.total_counterparties} контрагентов в базе, ${profile.active_counterparties} активных по документальной активности`;
|
||
facts.push(`Профиль контрагентской базы${organization}${period} подтвержден по 1С: ${totalText}; заказчики ${profile.customer_only_count}, поставщики ${profile.supplier_only_count}, смешанная роль ${profile.mixed_role_count}. Это не CRM-аудит, не юридическая проверка контрагентов и не оценка качества клиентской базы.`);
|
||
}
|
||
if (derived.contract_usage_profile) {
|
||
const profile = derived.contract_usage_profile;
|
||
const totalText = profile.total_contracts === null
|
||
? `${profile.used_contracts} договоров с подтвержденной связью с операциями`
|
||
: `${profile.used_contracts} из ${profile.total_contracts} договоров используются${profile.used_contract_share_pct === null ? "" : ` (${profile.used_contract_share_pct}%)`}`;
|
||
const unusedText = profile.unused_contracts === null ? "" : `, неиспользуемых ${profile.unused_contracts}`;
|
||
facts.push(`Договорной профиль${organization}${period} подтвержден по 1С: ${totalText}${unusedText}. Это не contract-risk аудит, не проверка актуальности условий и не доказательство качества договорной базы.`);
|
||
}
|
||
if (derived.tax_position) {
|
||
const taxDirection = derived.tax_position.net_vat_direction === "vat_to_pay"
|
||
? "к уплате"
|
||
: derived.tax_position.net_vat_direction === "vat_to_recover_or_offset"
|
||
? "к вычету/зачету"
|
||
: "сбалансирован";
|
||
facts.push(`НДС-позиция за ${derived.tax_position.period_scope} подтверждена по книгам продаж/покупок: продажи ${derived.tax_position.sales_vat_amount_human_ru}, покупки/вычеты ${derived.tax_position.purchase_vat_amount_human_ru}, нетто ${taxDirection} ${derived.tax_position.net_vat_amount_human_ru}.`);
|
||
}
|
||
if (derived.trading_margin_proxy) {
|
||
const proxy = derived.trading_margin_proxy;
|
||
const marginText = proxy.margin_to_revenue_pct === null ? "не рассчитана" : `${proxy.margin_to_revenue_pct}%`;
|
||
facts.push(`Торговый margin proxy за ${proxy.period_scope} подтвержден по товарным документам продаж/поступлений: выручка продаж ${proxy.sales_revenue_human_ru}, закупочный документный след ${proxy.purchase_cost_proxy_human_ru}, валовый спред proxy ${proxy.gross_spread_proxy_human_ru}, маржинальность к выручке ${marginText}. Это не чистая прибыль и не бухгалтерский финрезультат.`);
|
||
}
|
||
if (derived.debt_position) {
|
||
const debtDirection = derived.debt_position.net_debt_position_direction === "net_receivable"
|
||
? "в пользу дебиторки"
|
||
: derived.debt_position.net_debt_position_direction === "net_payable"
|
||
? "в сторону кредиторки"
|
||
: "сбалансировано";
|
||
facts.push(`Долговая позиция на ${derived.debt_position.as_of_date} подтверждена по срезам дебиторки/кредиторки 1С: дебиторка ${derived.debt_position.receivables.total_amount_human_ru}, кредиторка ${derived.debt_position.payables.total_amount_human_ru}, нетто ${debtDirection} ${derived.debt_position.net_debt_position_amount_human_ru}.`);
|
||
}
|
||
if (derived.debt_open_settlement_quality) {
|
||
const quality = derived.debt_open_settlement_quality;
|
||
const leader = quality.top_contracts[0];
|
||
const leaderShareText = leader?.share_of_gross_open_amount_pct === null || leader?.share_of_gross_open_amount_pct === undefined
|
||
? ""
|
||
: ` (${leader.share_of_gross_open_amount_pct}%)`;
|
||
const leaderText = leader
|
||
? ` Крупнейший открытый договор: ${leader.contract}${leader.counterparty ? ` / ${leader.counterparty}` : ""} — ${leader.total_amount_human_ru}${leaderShareText}.`
|
||
: "";
|
||
facts.push(`Качество открытых расчетов на ${quality.as_of_date} проверено по договорным остаткам 60/62/76: брутто ${quality.gross_open_amount_human_ru}, договоров ${quality.unique_contracts}, контрагентов ${quality.unique_counterparties}.${leaderText}`);
|
||
if (quality.age_signal?.oldest_start_date) {
|
||
const ageText = quality.age_signal.max_age_days === null
|
||
? ""
|
||
: `, максимальный возраст сигнала ${quality.age_signal.max_age_days} дн.`;
|
||
facts.push(`Возрастной сигнал открытых расчетов подтвержден по датам договоров: самая ранняя дата договора ${quality.age_signal.oldest_start_date}${ageText}. Это не договорная просрочка и не due-date анализ.`);
|
||
}
|
||
}
|
||
if (derived.debt_staleness_risk_proxy) {
|
||
const proxy = derived.debt_staleness_risk_proxy;
|
||
const counterpartyText = proxy.top_contract_counterparty ? ` / ${proxy.top_contract_counterparty}` : "";
|
||
facts.push(`Staleness risk proxy открытых расчетов на ${proxy.as_of_date}: самый старый договорный сигнал ${proxy.oldest_contract_start_date}, возраст ${proxy.max_contract_age_days} дн.; старейший крупный договор ${proxy.top_contract}${counterpartyText} держит ${proxy.top_contract_amount_human_ru} (${proxy.top_contract_share_pct}% брутто открытых остатков); оценка ${debtStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная просрочка, не кредитный риск и не due-date aging.`);
|
||
}
|
||
if (derived.inventory_position) {
|
||
const leader = derived.inventory_position.top_items[0];
|
||
const leaderText = leader
|
||
? ` Крупнейшая подтвержденная позиция: ${leader.item} — ${leader.total_amount_human_ru}.`
|
||
: "";
|
||
facts.push(`Складской срез на ${derived.inventory_position.as_of_date} подтвержден по 1С: остаток ${derived.inventory_position.total_amount_human_ru} по ${derived.inventory_position.rows_with_amount} строкам с суммой и ${derived.inventory_position.rows_with_quantity} строкам с количеством.${leaderText}`);
|
||
if (derived.inventory_position.aging_signal?.oldest_purchase_date) {
|
||
const ageText = derived.inventory_position.aging_signal.max_age_days === null
|
||
? ""
|
||
: `, максимальный возраст сигнала ${derived.inventory_position.aging_signal.max_age_days} дн.`;
|
||
facts.push(`Возрастной сигнал склада подтвержден по найденным строкам закупок: самая ранняя дата ${derived.inventory_position.aging_signal.oldest_purchase_date}${ageText}.`);
|
||
}
|
||
}
|
||
if (derived.inventory_turnover_proxy) {
|
||
const proxy = derived.inventory_turnover_proxy;
|
||
const ratioText = proxy.sales_to_stock_amount_ratio === null
|
||
? "не рассчитано"
|
||
: `${proxy.sales_to_stock_amount_ratio}x`;
|
||
const stockShareText = proxy.stock_to_sales_revenue_pct === null
|
||
? "не рассчитана"
|
||
: `${proxy.stock_to_sales_revenue_pct}%`;
|
||
facts.push(`Оборотный proxy склада за ${proxy.period_scope} подтвержден по продажным документам и складскому остатку: продажи ${proxy.sales_revenue_human_ru}, остаток на ${proxy.as_of_date} ${proxy.inventory_amount_human_ru}, sales-to-stock ratio ${ratioText}, остаток к продажам ${stockShareText}. Это не полноценная складская ликвидность, не FIFO-оборачиваемость и не анализ устаревания.`);
|
||
}
|
||
if (derived.inventory_staleness_risk_proxy) {
|
||
const proxy = derived.inventory_staleness_risk_proxy;
|
||
facts.push(`Staleness risk proxy склада на ${proxy.as_of_date}: самая ранняя дата закупочного сигнала ${proxy.oldest_purchase_date}, возраст ${proxy.max_purchase_age_days} дн., sales-to-stock ${proxy.sales_to_stock_amount_ratio}x, оценка ${inventoryStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная неликвидность, не резерв и не ликвидационная стоимость.`);
|
||
}
|
||
return facts;
|
||
}
|
||
function buildBusinessOverviewInferredFacts(derived) {
|
||
if (!derived) {
|
||
return [];
|
||
}
|
||
if (derived.incoming_customer_revenue.rows_with_amount <= 0 &&
|
||
derived.outgoing_supplier_payout.rows_with_amount <= 0) {
|
||
return [];
|
||
}
|
||
const direction = derived.net_direction === "net_incoming"
|
||
? "денежный поток в проверенном срезе больше входящий, чем исходящий"
|
||
: derived.net_direction === "net_outgoing"
|
||
? "денежный поток в проверенном срезе больше исходящий, чем входящий"
|
||
: "входящий и исходящий денежный поток в проверенном срезе примерно сбалансированы";
|
||
const supplierLeader = derived.top_suppliers[0];
|
||
const supplierSharePct = supplierLeader
|
||
? percentageOfTotal(supplierLeader.total_amount, derived.outgoing_supplier_payout.total_amount)
|
||
: null;
|
||
const strongestIncomingYear = [...derived.yearly_breakdown]
|
||
.filter((bucket) => bucket.incoming_total_amount > 0)
|
||
.sort((left, right) => right.incoming_total_amount - left.incoming_total_amount || left.year_bucket.localeCompare(right.year_bucket))[0];
|
||
const strongestNetYear = [...derived.yearly_breakdown]
|
||
.filter((bucket) => bucket.net_amount !== 0)
|
||
.sort((left, right) => right.net_amount - left.net_amount || left.year_bucket.localeCompare(right.year_bucket))[0];
|
||
return [
|
||
`Расчетное нетто по найденным строкам: ${derived.net_amount_human_ru}; ${direction}.`,
|
||
supplierLeader
|
||
? supplierSharePct !== null
|
||
? `Крупнейший подтвержденный поставщик/получатель исходящих платежей ${supplierLeader.axis_value} держит около ${supplierSharePct}% проверенного исходящего потока (${supplierLeader.total_amount_human_ru}). Это procurement concentration proxy по найденным строкам, а не полный vendor-risk аудит.`
|
||
: `Крупнейший подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${supplierLeader.axis_value} — ${supplierLeader.total_amount_human_ru}.`
|
||
: null,
|
||
strongestIncomingYear
|
||
? `Самый сильный год по подтвержденным входящим поступлениям: ${strongestIncomingYear.year_bucket} (${strongestIncomingYear.incoming_total_amount_human_ru}).`
|
||
: null,
|
||
strongestNetYear
|
||
? `Лучший год по расчетному операционному нетто найденных строк: ${strongestNetYear.year_bucket} (${netDirectionFromAmount(strongestNetYear.net_amount) === "net_outgoing" ? "нетто исходящее" : "нетто в плюс"} ${strongestNetYear.net_amount_human_ru}). Это не бухгалтерская прибыль.`
|
||
: null,
|
||
"Это операционный денежный сигнал по найденным строкам 1С, а не прибыль, маржа или бухгалтерское заключение о здоровье бизнеса."
|
||
].filter((fact) => Boolean(fact));
|
||
}
|
||
function buildBusinessOverviewUnknownFacts(derived) {
|
||
const missing = new Set(derived?.missing_signal_families ?? [
|
||
"profit_margin",
|
||
"debt_position",
|
||
"tax_position",
|
||
"inventory_position"
|
||
]);
|
||
const unknowns = [
|
||
missing.has("profit_margin")
|
||
? "Прибыль и маржа этим бизнес-обзором не подтверждены: нужны себестоимость, расходы и закрывающие документы."
|
||
: null,
|
||
missing.has("accounting_profit_margin")
|
||
? "Чистая прибыль, бухгалтерская маржа и финрезультат этим бизнес-обзором не подтверждены: торговый proxy показывает только документную разницу продаж и поступлений без расходов, закрытия периода и точной себестоимости продаж."
|
||
: null,
|
||
missing.has("debt_quality")
|
||
? "Качество дебиторки/кредиторки этим бизнес-обзором не подтверждено: нужен отдельный долговой срез."
|
||
: null,
|
||
missing.has("debt_position")
|
||
? "Дебиторка/кредиторка этим бизнес-обзором не подтверждены: нужен отдельный долговой срез на явную дату."
|
||
: null,
|
||
missing.has("debt_aging_quality")
|
||
? "Качество долга и просрочка этим бизнес-обзором не подтверждены: текущий долговой срез показывает только суммы на дату, без aging/due-date анализа."
|
||
: null,
|
||
missing.has("debt_open_settlement_quality")
|
||
? "Качество открытых расчетов этим бизнес-обзором не подтверждено: нужен срез открытых договоров на явную дату."
|
||
: null,
|
||
missing.has("debt_due_date_aging_quality")
|
||
? "Просрочка и due-date aging этим бизнес-обзором не подтверждены: открытые договоры показывают концентрацию остатков, но не договорные сроки оплаты."
|
||
: null,
|
||
missing.has("tax_position")
|
||
? "Налоговая/VAT-позиция этим бизнес-обзором не подтверждена: нужен отдельный налоговый контур или явный проверяемый период."
|
||
: null,
|
||
missing.has("inventory_health")
|
||
? "Складская ликвидность и товарные остатки этим бизнес-обзором не подтверждены: нужен отдельный inventory-срез."
|
||
: null,
|
||
missing.has("inventory_position")
|
||
? "Складской остаток этим бизнес-обзором не подтвержден: нужен отдельный inventory-срез на явную дату."
|
||
: null,
|
||
missing.has("inventory_aging_quality")
|
||
? "Возраст, залежалость и ликвидность склада этим бизнес-обзором не подтверждены: текущий складской срез показывает только остаток на дату без полноценной оборачиваемости."
|
||
: null,
|
||
missing.has("inventory_turnover_quality")
|
||
? "Скорость продаж, оборачиваемость и ликвидность склада этим бизнес-обзором не подтверждены: нужен отдельный inventory/продажный анализ, а не только остаток на дату."
|
||
: null,
|
||
missing.has("inventory_liquidity_quality")
|
||
? "Полная складская ликвидность этим бизнес-обзором не подтверждена: sales-to-stock proxy показывает только соотношение продажных документов и остатка на дату, без FIFO-оборачиваемости, устаревания, резервов и ликвидационной стоимости."
|
||
: null,
|
||
missing.has("inventory_reserve_liquidation_quality")
|
||
? "Резервы, списания, подтвержденная неликвидность и ликвидационная стоимость склада этим бизнес-обзором не подтверждены: staleness proxy показывает только возраст закупочного сигнала и sales-to-stock, без управленческого решения о запасах."
|
||
: null
|
||
].filter((item) => Boolean(item));
|
||
if (derived?.coverage_limited_by_probe_limit) {
|
||
unknowns.unshift("Полное покрытие бизнес-обзора не подтверждено: хотя бы один денежный probe достиг лимита строк.");
|
||
}
|
||
return unknowns;
|
||
}
|
||
function buildLifecycleConfirmedFacts(result, counterparty) {
|
||
if (result.error || result.matched_rows <= 0) {
|
||
return [];
|
||
}
|
||
return [
|
||
counterparty
|
||
? `1C activity rows were found for counterparty ${counterparty}; matched_rows=${result.matched_rows}`
|
||
: `1C activity rows were found for the requested counterparty scope; matched_rows=${result.matched_rows}`
|
||
];
|
||
}
|
||
function checkedCounterpartySuffixRu(counterparty) {
|
||
return counterparty ? ` по контрагенту ${counterparty}` : "";
|
||
}
|
||
function checkedPeriodSuffixRu(periodScope) {
|
||
return periodScope ? ` за ${periodScope}` : " в проверенном окне";
|
||
}
|
||
function uncheckedPeriodBoundaryRu(periodScope) {
|
||
return periodScope ? ` вне периода ${periodScope}` : " без явно проверенного периода";
|
||
}
|
||
function buildDocumentConfirmedFacts(result, counterparty, periodScope) {
|
||
if (result.error || result.matched_rows <= 0) {
|
||
return [];
|
||
}
|
||
return [`В 1С найдены строки документов${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)}.`];
|
||
}
|
||
function buildMovementConfirmedFacts(result, counterparty, periodScope) {
|
||
if (result.error || result.matched_rows <= 0) {
|
||
return [];
|
||
}
|
||
return [`В 1С найдены строки движений${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)}.`];
|
||
}
|
||
function buildValueFlowConfirmedFacts(result, counterparty, direction) {
|
||
if (result.error || result.matched_rows <= 0) {
|
||
return [];
|
||
}
|
||
if (direction === "outgoing_supplier_payout") {
|
||
return [
|
||
counterparty
|
||
? `1C supplier-payout rows were found for counterparty ${counterparty}`
|
||
: "1C supplier-payout rows were found for the requested counterparty scope"
|
||
];
|
||
}
|
||
return [
|
||
counterparty
|
||
? `1C value-flow rows were found for counterparty ${counterparty}`
|
||
: "1C value-flow rows were found for the requested counterparty scope"
|
||
];
|
||
}
|
||
function buildRankedValueFlowConfirmedFacts(derived) {
|
||
if (!derived || derived.ranked_values.length <= 0) {
|
||
return [];
|
||
}
|
||
const leader = derived.ranked_values[0];
|
||
const directionLabel = derived.value_flow_direction === "outgoing_supplier_payout" ? "supplier-payout" : "incoming value-flow";
|
||
return [
|
||
`1C ${directionLabel} rows were ranked by counterparty for the checked scope; leader=${leader.axis_value}, rows_with_amount=${leader.rows_with_amount}`
|
||
];
|
||
}
|
||
function buildBidirectionalValueFlowConfirmedFacts(derived) {
|
||
if (!derived) {
|
||
return [];
|
||
}
|
||
const hasIncoming = derived.incoming_customer_revenue.rows_matched > 0;
|
||
const hasOutgoing = derived.outgoing_supplier_payout.rows_matched > 0;
|
||
if (derived.counterparty) {
|
||
return [
|
||
`1C bidirectional value-flow rows were checked for counterparty ${derived.counterparty}: incoming=${hasIncoming ? "found" : "not_found"}, outgoing=${hasOutgoing ? "found" : "not_found"}`
|
||
];
|
||
}
|
||
return [
|
||
`1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=${hasIncoming ? "found" : "not_found"}, outgoing=${hasOutgoing ? "found" : "not_found"}`
|
||
];
|
||
}
|
||
function inventoryLabelRu(intent) {
|
||
if (intent === "inventory_supplier_stock_overlap_as_of_date") {
|
||
return "связи поставщиков с товарным остатком";
|
||
}
|
||
if (intent === "inventory_purchase_provenance_for_item") {
|
||
return "закупочной истории позиции";
|
||
}
|
||
if (intent === "inventory_sale_trace_for_item") {
|
||
return "продаж по позиции";
|
||
}
|
||
return "складского среза";
|
||
}
|
||
function inventoryScopeSuffixRu(input) {
|
||
const parts = [];
|
||
if (input.organization) {
|
||
parts.push(`по организации ${input.organization}`);
|
||
}
|
||
if (input.item) {
|
||
parts.push(`по позиции ${input.item}`);
|
||
}
|
||
if (input.intent === "inventory_supplier_stock_overlap_as_of_date" && input.counterparty) {
|
||
parts.push(`по поставщику/контрагенту ${input.counterparty}`);
|
||
}
|
||
if (input.asOfDate) {
|
||
parts.push(`на ${input.asOfDate}`);
|
||
}
|
||
else if (input.dateScope) {
|
||
parts.push(`за ${input.dateScope}`);
|
||
}
|
||
return parts.length > 0 ? ` ${parts.join(", ")}` : "";
|
||
}
|
||
function inventoryRowSample(row) {
|
||
const item = rowInventoryItemValue(row);
|
||
const quantity = rowQuantityValue(row);
|
||
const warehouse = rowWarehouseValue(row);
|
||
const counterparty = rowCounterpartyValue(row);
|
||
const document = rowDocumentValue(row);
|
||
const parts = [];
|
||
if (item) {
|
||
parts.push(item);
|
||
}
|
||
if (quantity !== null) {
|
||
parts.push(`${quantity} шт.`);
|
||
}
|
||
if (warehouse) {
|
||
parts.push(`склад ${warehouse}`);
|
||
}
|
||
if (counterparty) {
|
||
parts.push(`контрагент ${counterparty}`);
|
||
}
|
||
if (document) {
|
||
parts.push(`документ ${document}`);
|
||
}
|
||
return parts.length > 0 ? parts.join(", ") : null;
|
||
}
|
||
function buildInventoryConfirmedFacts(result, planner, intent) {
|
||
if (result.error || result.matched_rows <= 0) {
|
||
return [];
|
||
}
|
||
const dateScope = toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.explicit_date_scope);
|
||
const item = intent === "inventory_purchase_provenance_for_item" || intent === "inventory_sale_trace_for_item"
|
||
? firstEntityCandidate(planner)
|
||
: null;
|
||
const counterparty = intent === "inventory_supplier_stock_overlap_as_of_date" ? firstEntityCandidate(planner) : null;
|
||
const scope = inventoryScopeSuffixRu({
|
||
intent,
|
||
item,
|
||
counterparty,
|
||
organization: organizationScopeForPlanner(planner),
|
||
asOfDate: asOfDateFromDateScope(dateScope),
|
||
dateScope
|
||
});
|
||
const samples = result.rows
|
||
.slice(0, 3)
|
||
.map((row) => inventoryRowSample(row))
|
||
.filter((value) => Boolean(value));
|
||
const sampleSuffix = samples.length > 0 ? ` Примеры строк: ${samples.join("; ")}.` : "";
|
||
return [`В 1С найдены подтвержденные строки ${inventoryLabelRu(intent)}${scope}: ${result.matched_rows}.${sampleSuffix}`];
|
||
}
|
||
function buildInventoryInferredFacts(result, intent) {
|
||
if (result.error || result.fetched_rows <= 0) {
|
||
return [];
|
||
}
|
||
if (result.matched_rows <= 0) {
|
||
return [
|
||
`По ${inventoryLabelRu(intent)} удалось проверить только ограниченный срез 1С; подтвержденных строк этим поиском не найдено.`
|
||
];
|
||
}
|
||
return [
|
||
`Вывод по ${inventoryLabelRu(intent)} ограничен найденными строками 1С и указанными датой, организацией, позицией или поставщиком.`
|
||
];
|
||
}
|
||
function buildInventoryUnknownFacts(result, intent, dateScope) {
|
||
const facts = [
|
||
dateScope
|
||
? `Полный товарный контур вне проверенного среза ${dateScope} не подтвержден.`
|
||
: "Полный товарный контур без явного проверенного периода или даты не подтвержден."
|
||
];
|
||
if (!result || result.error || result.matched_rows <= 0) {
|
||
facts.unshift(`Подтвержденный факт по ${inventoryLabelRu(intent)} в проверенных строках 1С не найден.`);
|
||
}
|
||
return facts;
|
||
}
|
||
function buildLifecycleInferredFacts(result) {
|
||
if (result.error || result.fetched_rows <= 0) {
|
||
return [];
|
||
}
|
||
const period = deriveActivityPeriod(result);
|
||
if (!period) {
|
||
return ["Business activity duration may be inferred only when confirmed 1C activity row dates are available"];
|
||
}
|
||
return [
|
||
"Business activity duration may be inferred from first and latest confirmed 1C activity rows",
|
||
`Activity window is bounded by first=${period.first_activity_date}, latest=${period.latest_activity_date}, matched_rows=${period.matched_rows}`,
|
||
"Activity-window inference is not legal registration age"
|
||
];
|
||
}
|
||
function buildDocumentInferredFacts(result, counterparty, periodScope) {
|
||
if (result.error || result.fetched_rows <= 0) {
|
||
return [];
|
||
}
|
||
if (result.matched_rows <= 0) {
|
||
return [
|
||
`По документам${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)} удалось проверить только ограниченный срез 1С; подтвержденных строк документов этим поиском не найдено.`
|
||
];
|
||
}
|
||
return [
|
||
`Срез документов${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)} ограничен только подтвержденными строками документов, найденными этим поиском.`
|
||
];
|
||
}
|
||
function buildMovementInferredFacts(result, counterparty, periodScope) {
|
||
if (result.error || result.fetched_rows <= 0) {
|
||
return [];
|
||
}
|
||
if (result.matched_rows <= 0) {
|
||
return [
|
||
`По движениям${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)} удалось проверить только ограниченный срез 1С; подтвержденных строк движений этим поиском не найдено.`
|
||
];
|
||
}
|
||
return [
|
||
`Срез движений${checkedCounterpartySuffixRu(counterparty)}${checkedPeriodSuffixRu(periodScope)} ограничен только подтвержденными строками движений, найденными этим поиском.`
|
||
];
|
||
}
|
||
function buildValueFlowInferredFacts(derived) {
|
||
if (!derived) {
|
||
return [];
|
||
}
|
||
const facts = [];
|
||
if (derived.value_flow_direction === "outgoing_supplier_payout") {
|
||
facts.push("Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows");
|
||
}
|
||
else {
|
||
facts.push("Counterparty value-flow total was calculated from confirmed 1C movement rows");
|
||
}
|
||
if (derived.coverage_recovered_by_period_chunking && derived.period_chunking_granularity === "month") {
|
||
facts.push("Requested period coverage was recovered through monthly 1C value-flow probes");
|
||
}
|
||
if (derived.aggregation_axis === "month" && derived.monthly_breakdown.length > 0) {
|
||
facts.push("Counterparty monthly value-flow breakdown was grouped by month over confirmed 1C movement rows");
|
||
}
|
||
return facts;
|
||
}
|
||
function buildRankedValueFlowInferredFacts(derived) {
|
||
if (!derived) {
|
||
return [];
|
||
}
|
||
const facts = ["Counterparty ranking was calculated from confirmed 1C movement rows grouped by counterparty"];
|
||
if (derived.coverage_recovered_by_period_chunking && derived.period_chunking_granularity === "month") {
|
||
facts.push("Requested period coverage for counterparty ranking was recovered through monthly 1C probes");
|
||
}
|
||
return facts;
|
||
}
|
||
function buildBidirectionalValueFlowInferredFacts(derived) {
|
||
if (!derived) {
|
||
return [];
|
||
}
|
||
const facts = ["Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows"];
|
||
if (derived.coverage_recovered_by_period_chunking && derived.period_chunking_granularity === "month") {
|
||
facts.push("Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes");
|
||
}
|
||
if (derived.aggregation_axis === "month" && derived.monthly_breakdown.length > 0) {
|
||
facts.push("Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows");
|
||
}
|
||
return facts;
|
||
}
|
||
function buildLifecycleUnknownFacts() {
|
||
return [
|
||
"Legal registration date is not proven by this MCP discovery pilot",
|
||
"Business activity before the first confirmed 1C activity row is not proven by this MCP discovery pilot"
|
||
];
|
||
}
|
||
function buildDocumentUnknownFacts(periodScope, counterparty) {
|
||
return [
|
||
`Полный исторический срез документов${checkedCounterpartySuffixRu(counterparty)}${uncheckedPeriodBoundaryRu(periodScope)} этим поиском не подтвержден.`
|
||
];
|
||
}
|
||
function buildMovementUnknownFacts(periodScope, counterparty) {
|
||
return [
|
||
`Полный исторический срез движений${checkedCounterpartySuffixRu(counterparty)}${uncheckedPeriodBoundaryRu(periodScope)} этим поиском не подтвержден.`
|
||
];
|
||
}
|
||
function buildValueFlowUnknownFacts(periodScope, direction, derived) {
|
||
const unknownFacts = [];
|
||
if (derived?.coverage_limited_by_probe_limit) {
|
||
unknownFacts.push("Complete requested-period coverage is not proven by the available checked rows");
|
||
}
|
||
if (direction === "outgoing_supplier_payout") {
|
||
unknownFacts.push(periodScope
|
||
? "Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot"
|
||
: "Full all-time supplier-payout amount is not proven without an explicit checked period");
|
||
return unknownFacts;
|
||
}
|
||
unknownFacts.push(periodScope
|
||
? "Full turnover outside the checked period is not proven by this MCP discovery pilot"
|
||
: "Full all-time turnover is not proven without an explicit checked period");
|
||
return unknownFacts;
|
||
}
|
||
function buildRankedValueFlowUnknownFacts(periodScope, derived) {
|
||
const unknownFacts = [];
|
||
if (derived?.coverage_limited_by_probe_limit) {
|
||
unknownFacts.push("Complete requested-period ranking coverage is not proven by the available checked rows");
|
||
}
|
||
unknownFacts.push(periodScope
|
||
? "Full ranking outside the checked period is not proven by this MCP discovery pilot"
|
||
: "Full all-time counterparty ranking is not proven without an explicit checked period");
|
||
return unknownFacts;
|
||
}
|
||
function buildBidirectionalValueFlowUnknownFacts(periodScope, derived) {
|
||
const unknownFacts = [];
|
||
if (derived?.coverage_limited_by_probe_limit) {
|
||
unknownFacts.push("Complete requested-period coverage for bidirectional value-flow is not proven by the available checked rows");
|
||
}
|
||
unknownFacts.push(periodScope
|
||
? "Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot"
|
||
: "Full all-time bidirectional value-flow is not proven without an explicit checked period");
|
||
return unknownFacts;
|
||
}
|
||
function buildEmptyEvidence(planner, dryRun, probeResults, reason) {
|
||
return (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||
plan: planner.discovery_plan,
|
||
probeResults,
|
||
unknownFacts: [reason],
|
||
queryLimitations: [reason],
|
||
recommendedNextProbe: dryRun.user_facing_fallback
|
||
});
|
||
}
|
||
function pilotScopeForPlanner(planner) {
|
||
switch (planner.selected_chain_id) {
|
||
case "catalog_drilldown":
|
||
case "metadata_lane_clarification":
|
||
case "metadata_inspection":
|
||
return "metadata_inspection_v1";
|
||
case "inventory_stock_snapshot":
|
||
case "inventory_supplier_overlap":
|
||
case "inventory_purchase_provenance":
|
||
case "inventory_sale_trace":
|
||
return "inventory_route_template_v1";
|
||
case "movement_evidence":
|
||
return "counterparty_movement_evidence_query_movements_v1";
|
||
case "value_flow_comparison":
|
||
case "value_flow_ranking":
|
||
case "value_flow":
|
||
return valueFlowPilotProfile(planner).scope;
|
||
case "business_overview":
|
||
return "business_overview_route_template_v1";
|
||
case "document_evidence":
|
||
return "counterparty_document_evidence_query_documents_v1";
|
||
case "lifecycle":
|
||
return "counterparty_lifecycle_query_documents_v1";
|
||
case "entity_resolution":
|
||
return "entity_resolution_search_v1";
|
||
}
|
||
}
|
||
async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) {
|
||
const runtimeDeps = {
|
||
...DEFAULT_DEPS,
|
||
...deps
|
||
};
|
||
const dryRun = (0, assistantMcpDiscoveryRuntimeAdapter_1.buildAssistantMcpDiscoveryRuntimeDryRun)(planner);
|
||
const reasonCodes = [...dryRun.reason_codes];
|
||
const executedPrimitives = [];
|
||
const skippedPrimitives = [];
|
||
const probeResults = [];
|
||
const queryLimitations = [];
|
||
const pilotScope = pilotScopeForPlanner(planner);
|
||
if (dryRun.adapter_status === "blocked") {
|
||
pushReason(reasonCodes, "pilot_blocked_before_mcp_execution");
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "MCP discovery pilot was blocked before execution");
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "blocked",
|
||
pilot_scope: pilotScope,
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: ["MCP discovery pilot was blocked before execution"],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
if (dryRun.adapter_status !== "dry_run_ready") {
|
||
pushReason(reasonCodes, "pilot_needs_clarification_before_mcp_execution");
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "MCP discovery pilot needs more scope before execution");
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "skipped_needs_clarification",
|
||
pilot_scope: pilotScope,
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: ["MCP discovery pilot needs more scope before execution"],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
const metadataPilotEligible = isMetadataPilotEligible(planner);
|
||
const documentPilotEligible = isDocumentEvidencePilotEligible(planner);
|
||
const movementPilotEligible = isMovementEvidencePilotEligible(planner);
|
||
const lifecyclePilotEligible = isLifecyclePilotEligible(planner);
|
||
const valueFlowPilotEligible = isValueFlowPilotEligible(planner);
|
||
const businessOverviewPilotEligible = isBusinessOverviewPilotEligible(planner);
|
||
const entityResolutionPilotEligible = isEntityResolutionPilotEligible(planner);
|
||
const inventoryPilotEligible = isInventoryPilotEligible(planner);
|
||
if (!metadataPilotEligible &&
|
||
!documentPilotEligible &&
|
||
!movementPilotEligible &&
|
||
!lifecyclePilotEligible &&
|
||
!valueFlowPilotEligible &&
|
||
!businessOverviewPilotEligible &&
|
||
!entityResolutionPilotEligible &&
|
||
!inventoryPilotEligible) {
|
||
pushReason(reasonCodes, "pilot_scope_unsupported_for_live_execution");
|
||
for (const step of dryRun.execution_steps) {
|
||
skippedPrimitives.push(step.primitive_id);
|
||
probeResults.push(skippedProbeResult(step, "pilot_scope_unsupported_for_live_execution"));
|
||
}
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "MCP discovery pilot scope is not implemented yet");
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "unsupported",
|
||
pilot_scope: pilotScope,
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: ["MCP discovery pilot scope is not implemented yet"],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
const counterparty = firstEntityCandidate(planner);
|
||
const dateScope = toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.explicit_date_scope);
|
||
const organizationScope = organizationScopeForPlanner(planner);
|
||
const aggregationAxis = aggregationAxisForPlanner(planner);
|
||
const rankingNeed = rankingNeedForPlanner(planner);
|
||
if (inventoryPilotEligible) {
|
||
let queryResult = null;
|
||
const inventoryIntent = inventoryIntentForPlanner(planner);
|
||
const executablePrimitive = inventoryExecutablePrimitiveForPlanner(planner);
|
||
if (!inventoryIntent || !executablePrimitive) {
|
||
pushReason(reasonCodes, "pilot_inventory_exact_recipe_not_mapped");
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Inventory exact recipe is not mapped");
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "unsupported",
|
||
pilot_scope: "inventory_route_template_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: ["Inventory exact recipe is not mapped"],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
const filters = buildInventoryExactFilters(planner);
|
||
const selection = (0, addressRecipeCatalog_1.selectAddressRecipe)(inventoryIntent, filters);
|
||
if (selection.missing_required_filters.length > 0) {
|
||
pushReason(reasonCodes, "pilot_inventory_exact_recipe_needs_required_filters");
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, `Inventory exact recipe needs required filters: ${selection.missing_required_filters.join(", ")}`);
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "skipped_needs_clarification",
|
||
pilot_scope: "inventory_route_template_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: [`Inventory exact recipe needs required filters: ${selection.missing_required_filters.join(", ")}`],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
if (!selection.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_inventory_exact_recipe_not_available");
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Inventory exact recipe is not available");
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "unsupported",
|
||
pilot_scope: "inventory_route_template_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: ["Inventory exact recipe is not available"],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
pushReason(reasonCodes, "pilot_inventory_exact_recipe_selected");
|
||
const recipePlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(selection.selected_recipe, filters);
|
||
for (const step of dryRun.execution_steps) {
|
||
if (step.primitive_id !== executablePrimitive) {
|
||
skippedPrimitives.push(step.primitive_id);
|
||
probeResults.push(skippedProbeResult(step, `pilot_inventory_exact_bridge_executes_${executablePrimitive}`));
|
||
continue;
|
||
}
|
||
queryResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: recipePlan.query,
|
||
limit: recipePlan.limit,
|
||
account_scope: recipePlan.account_scope
|
||
});
|
||
pushUnique(executedPrimitives, step.primitive_id);
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, queryResult));
|
||
if (queryResult.error) {
|
||
pushUnique(queryLimitations, queryResult.error);
|
||
pushReason(reasonCodes, "pilot_inventory_exact_mcp_error");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_inventory_exact_mcp_executed");
|
||
}
|
||
}
|
||
const sourceRowsSummary = queryResult ? summarizeInventoryRows(queryResult) : null;
|
||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||
plan: planner.discovery_plan,
|
||
probeResults,
|
||
confirmedFacts: queryResult ? buildInventoryConfirmedFacts(queryResult, planner, inventoryIntent) : [],
|
||
inferredFacts: queryResult ? buildInventoryInferredFacts(queryResult, inventoryIntent) : [],
|
||
unknownFacts: buildInventoryUnknownFacts(queryResult, inventoryIntent, toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.explicit_date_scope)),
|
||
sourceRowsSummary,
|
||
queryLimitations,
|
||
recommendedNextProbe: "explain_evidence_basis"
|
||
});
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "executed",
|
||
pilot_scope: "inventory_route_template_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: executedPrimitives.length > 0,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: sourceRowsSummary,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: queryLimitations,
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
if (businessOverviewPilotEligible) {
|
||
let incomingResult = null;
|
||
let outgoingResult = null;
|
||
let lifecycleResult = null;
|
||
let taxResult = null;
|
||
let tradingMarginResult = null;
|
||
let receivablesResult = null;
|
||
let payablesResult = null;
|
||
let openContractsResult = null;
|
||
let documentActivityProfileResult = null;
|
||
let counterpartyProfileResult = null;
|
||
let contractUsageProfileResult = null;
|
||
let inventoryOnHandResult = null;
|
||
let inventoryAgingResult = null;
|
||
const valueFilters = buildValueFlowFilters(planner);
|
||
const lifecycleFilters = buildLifecycleFilters(planner);
|
||
const profileFilters = buildBusinessOverviewProfileFilters(planner);
|
||
const taxFilters = buildBusinessOverviewTaxFilters(planner);
|
||
const tradingMarginFilters = buildBusinessOverviewTradingMarginFilters(planner);
|
||
const debtFilters = buildBusinessOverviewDebtFilters(planner);
|
||
const inventoryFilters = buildBusinessOverviewInventoryFilters(planner);
|
||
const debtAsOfDate = toNonEmptyString(debtFilters?.as_of_date);
|
||
const inventoryAsOfDate = toNonEmptyString(inventoryFilters?.as_of_date);
|
||
const incomingSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("customer_revenue_and_payments", valueFilters);
|
||
const outgoingSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("supplier_payouts_profile", valueFilters);
|
||
const lifecycleSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("counterparty_activity_lifecycle", lifecycleFilters);
|
||
const documentActivitySelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("document_type_and_account_section_profile", profileFilters);
|
||
const counterpartyProfileSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("counterparty_population_and_roles", profileFilters);
|
||
const contractUsageSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("contract_usage_overview", profileFilters);
|
||
const taxSelection = taxFilters
|
||
? (0, addressRecipeCatalog_1.selectAddressRecipe)("vat_liability_confirmed_for_tax_period", taxFilters)
|
||
: null;
|
||
const tradingMarginSelection = tradingMarginFilters
|
||
? (0, addressRecipeCatalog_1.selectAddressRecipe)("inventory_trading_margin_proxy_for_organization", tradingMarginFilters)
|
||
: null;
|
||
const receivablesSelection = debtFilters
|
||
? (0, addressRecipeCatalog_1.selectAddressRecipe)("receivables_confirmed_as_of_date", debtFilters)
|
||
: null;
|
||
const payablesSelection = debtFilters
|
||
? (0, addressRecipeCatalog_1.selectAddressRecipe)("payables_confirmed_as_of_date", debtFilters)
|
||
: null;
|
||
const openContractsSelection = debtFilters
|
||
? (0, addressRecipeCatalog_1.selectAddressRecipe)("open_contracts_confirmed_as_of_date", debtFilters)
|
||
: null;
|
||
const inventoryOnHandSelection = inventoryFilters
|
||
? (0, addressRecipeCatalog_1.selectAddressRecipe)("inventory_on_hand_as_of_date", inventoryFilters)
|
||
: null;
|
||
const inventoryAgingSelection = inventoryFilters
|
||
? (0, addressRecipeCatalog_1.selectAddressRecipe)("inventory_aging_by_purchase_date", inventoryFilters)
|
||
: null;
|
||
if (!incomingSelection.selected_recipe || !outgoingSelection.selected_recipe || !lifecycleSelection.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_business_overview_recipe_not_available");
|
||
const missing = [
|
||
incomingSelection.selected_recipe ? null : "customer_revenue_and_payments",
|
||
outgoingSelection.selected_recipe ? null : "supplier_payouts_profile",
|
||
lifecycleSelection.selected_recipe ? null : "counterparty_activity_lifecycle"
|
||
].filter((item) => Boolean(item));
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Business overview recipe is not available");
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "unsupported",
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: [`Business overview recipe is not available: ${missing.join(", ")}`],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
pushReason(reasonCodes, "pilot_business_overview_recipes_selected");
|
||
if (documentActivitySelection.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_business_overview_document_activity_profile_recipe_selected");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_business_overview_document_activity_profile_recipe_not_available");
|
||
pushUnique(queryLimitations, "Business overview document/account-section profile requires an executable document-section profile recipe");
|
||
}
|
||
if (counterpartyProfileSelection.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_business_overview_counterparty_profile_recipe_selected");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_business_overview_counterparty_profile_recipe_not_available");
|
||
pushUnique(queryLimitations, "Business overview counterparty profile requires an executable counterparty population/roles recipe");
|
||
}
|
||
if (contractUsageSelection.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_business_overview_contract_usage_profile_recipe_selected");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_business_overview_contract_usage_profile_recipe_not_available");
|
||
pushUnique(queryLimitations, "Business overview contract usage profile requires an executable contract usage recipe");
|
||
}
|
||
if (taxSelection?.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_business_overview_tax_recipe_selected");
|
||
}
|
||
else if (!taxFilters) {
|
||
pushReason(reasonCodes, "pilot_business_overview_tax_probe_skipped_without_explicit_period");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_business_overview_tax_recipe_not_available");
|
||
pushUnique(queryLimitations, "Business overview VAT/tax probe requires an executable tax-period recipe");
|
||
}
|
||
if (tradingMarginSelection?.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_business_overview_trading_margin_recipe_selected");
|
||
}
|
||
else if (!tradingMarginFilters) {
|
||
pushReason(reasonCodes, "pilot_business_overview_trading_margin_probe_skipped_without_explicit_period");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_business_overview_trading_margin_recipe_not_available");
|
||
pushUnique(queryLimitations, "Business overview trading-margin proxy requires an executable explicit-period purchase/sale document recipe");
|
||
}
|
||
if (receivablesSelection?.selected_recipe && payablesSelection?.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_business_overview_debt_recipes_selected");
|
||
}
|
||
else if (!debtFilters) {
|
||
pushReason(reasonCodes, "pilot_business_overview_debt_probe_skipped_without_explicit_as_of_date");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_business_overview_debt_recipe_not_available");
|
||
pushUnique(queryLimitations, "Business overview debt-position probe requires executable receivables/payables as-of-date recipes");
|
||
}
|
||
if (openContractsSelection?.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_business_overview_open_contracts_recipe_selected");
|
||
}
|
||
else if (!debtFilters) {
|
||
pushReason(reasonCodes, "pilot_business_overview_open_contracts_probe_skipped_without_explicit_as_of_date");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_business_overview_open_contracts_recipe_not_available");
|
||
pushUnique(queryLimitations, "Business overview open-settlement quality probe requires executable open-contracts as-of-date recipe");
|
||
}
|
||
if (inventoryOnHandSelection?.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_business_overview_inventory_on_hand_recipe_selected");
|
||
if (inventoryAgingSelection?.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_business_overview_inventory_aging_recipe_selected");
|
||
}
|
||
}
|
||
else if (!inventoryFilters) {
|
||
pushReason(reasonCodes, "pilot_business_overview_inventory_probe_skipped_without_explicit_as_of_date");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_business_overview_inventory_recipe_not_available");
|
||
pushUnique(queryLimitations, "Business overview inventory-position probe requires an executable inventory on-hand as-of-date recipe");
|
||
}
|
||
for (const step of dryRun.execution_steps) {
|
||
if (step.primitive_id === "query_movements") {
|
||
const incomingExecution = await executeCoverageAwareValueFlowQuery({
|
||
primitiveId: step.primitive_id,
|
||
recipePlanBuilder: (scopedFilters) => (0, addressRecipeCatalog_1.buildAddressRecipePlan)(incomingSelection.selected_recipe, scopedFilters),
|
||
baseFilters: valueFilters,
|
||
dateScope,
|
||
maxProbeCount: planner.discovery_plan.execution_budget.max_probe_count,
|
||
maxRowsPerProbe: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||
deps: runtimeDeps
|
||
});
|
||
const outgoingExecution = await executeCoverageAwareValueFlowQuery({
|
||
primitiveId: step.primitive_id,
|
||
recipePlanBuilder: (scopedFilters) => (0, addressRecipeCatalog_1.buildAddressRecipePlan)(outgoingSelection.selected_recipe, scopedFilters),
|
||
baseFilters: valueFilters,
|
||
dateScope,
|
||
maxProbeCount: planner.discovery_plan.execution_budget.max_probe_count,
|
||
maxRowsPerProbe: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||
deps: runtimeDeps
|
||
});
|
||
incomingResult = incomingExecution.result;
|
||
outgoingResult = outgoingExecution.result;
|
||
if (taxSelection?.selected_recipe) {
|
||
const taxPlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(taxSelection.selected_recipe, taxFilters);
|
||
taxResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: taxPlan.query,
|
||
limit: taxPlan.limit,
|
||
account_scope: taxPlan.account_scope
|
||
});
|
||
}
|
||
if (receivablesSelection?.selected_recipe && payablesSelection?.selected_recipe) {
|
||
const receivablesPlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(receivablesSelection.selected_recipe, debtFilters);
|
||
receivablesResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: receivablesPlan.query,
|
||
limit: receivablesPlan.limit,
|
||
account_scope: receivablesPlan.account_scope
|
||
});
|
||
const payablesPlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(payablesSelection.selected_recipe, debtFilters);
|
||
payablesResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: payablesPlan.query,
|
||
limit: payablesPlan.limit,
|
||
account_scope: payablesPlan.account_scope
|
||
});
|
||
}
|
||
if (openContractsSelection?.selected_recipe) {
|
||
const openContractsPlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(openContractsSelection.selected_recipe, debtFilters);
|
||
openContractsResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: openContractsPlan.query,
|
||
limit: openContractsPlan.limit,
|
||
account_scope: openContractsPlan.account_scope
|
||
});
|
||
}
|
||
if (inventoryOnHandSelection?.selected_recipe) {
|
||
const inventoryOnHandPlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(inventoryOnHandSelection.selected_recipe, inventoryFilters);
|
||
inventoryOnHandResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: inventoryOnHandPlan.query,
|
||
limit: inventoryOnHandPlan.limit,
|
||
account_scope: inventoryOnHandPlan.account_scope
|
||
});
|
||
if (inventoryAgingSelection?.selected_recipe) {
|
||
const inventoryAgingPlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(inventoryAgingSelection.selected_recipe, inventoryFilters);
|
||
inventoryAgingResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: inventoryAgingPlan.query,
|
||
limit: inventoryAgingPlan.limit,
|
||
account_scope: inventoryAgingPlan.account_scope
|
||
});
|
||
}
|
||
}
|
||
pushUnique(executedPrimitives, step.primitive_id);
|
||
probeResults.push(...incomingExecution.probe_results, ...outgoingExecution.probe_results);
|
||
if (taxResult) {
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, taxResult));
|
||
}
|
||
if (receivablesResult) {
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, receivablesResult));
|
||
}
|
||
if (payablesResult) {
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, payablesResult));
|
||
}
|
||
if (openContractsResult) {
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, openContractsResult));
|
||
}
|
||
if (inventoryOnHandResult) {
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, inventoryOnHandResult));
|
||
}
|
||
if (inventoryAgingResult) {
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, inventoryAgingResult));
|
||
}
|
||
for (const limitation of [...incomingExecution.query_limitations, ...outgoingExecution.query_limitations]) {
|
||
pushUnique(queryLimitations, limitation);
|
||
}
|
||
if (incomingResult?.error) {
|
||
pushReason(reasonCodes, "pilot_business_overview_incoming_query_mcp_error");
|
||
}
|
||
if (outgoingResult?.error) {
|
||
pushReason(reasonCodes, "pilot_business_overview_outgoing_query_mcp_error");
|
||
}
|
||
if (!incomingResult?.error || !outgoingResult?.error) {
|
||
pushReason(reasonCodes, "pilot_business_overview_query_movements_mcp_executed");
|
||
}
|
||
if (taxResult?.error) {
|
||
pushUnique(queryLimitations, taxResult.error);
|
||
pushReason(reasonCodes, "pilot_business_overview_tax_query_mcp_error");
|
||
}
|
||
else if (taxResult) {
|
||
pushReason(reasonCodes, "pilot_business_overview_tax_query_mcp_executed");
|
||
}
|
||
if (receivablesResult?.error) {
|
||
pushUnique(queryLimitations, receivablesResult.error);
|
||
pushReason(reasonCodes, "pilot_business_overview_receivables_query_mcp_error");
|
||
}
|
||
else if (receivablesResult) {
|
||
pushReason(reasonCodes, "pilot_business_overview_receivables_query_mcp_executed");
|
||
}
|
||
if (payablesResult?.error) {
|
||
pushUnique(queryLimitations, payablesResult.error);
|
||
pushReason(reasonCodes, "pilot_business_overview_payables_query_mcp_error");
|
||
}
|
||
else if (payablesResult) {
|
||
pushReason(reasonCodes, "pilot_business_overview_payables_query_mcp_executed");
|
||
}
|
||
if ((receivablesResult && !receivablesResult.error) ||
|
||
(payablesResult && !payablesResult.error)) {
|
||
pushReason(reasonCodes, "pilot_business_overview_debt_query_mcp_executed");
|
||
}
|
||
if (openContractsResult?.error) {
|
||
pushUnique(queryLimitations, openContractsResult.error);
|
||
pushReason(reasonCodes, "pilot_business_overview_open_contracts_query_mcp_error");
|
||
}
|
||
else if (openContractsResult) {
|
||
pushReason(reasonCodes, "pilot_business_overview_open_contracts_query_mcp_executed");
|
||
}
|
||
if (inventoryOnHandResult?.error) {
|
||
pushUnique(queryLimitations, inventoryOnHandResult.error);
|
||
pushReason(reasonCodes, "pilot_business_overview_inventory_on_hand_query_mcp_error");
|
||
}
|
||
else if (inventoryOnHandResult) {
|
||
pushReason(reasonCodes, "pilot_business_overview_inventory_on_hand_query_mcp_executed");
|
||
}
|
||
if (inventoryAgingResult?.error) {
|
||
pushUnique(queryLimitations, inventoryAgingResult.error);
|
||
pushReason(reasonCodes, "pilot_business_overview_inventory_aging_query_mcp_error");
|
||
}
|
||
else if (inventoryAgingResult) {
|
||
pushReason(reasonCodes, "pilot_business_overview_inventory_aging_query_mcp_executed");
|
||
}
|
||
if ((inventoryOnHandResult && !inventoryOnHandResult.error) ||
|
||
(inventoryAgingResult && !inventoryAgingResult.error)) {
|
||
pushReason(reasonCodes, "pilot_business_overview_inventory_query_mcp_executed");
|
||
}
|
||
continue;
|
||
}
|
||
if (step.primitive_id === "query_documents") {
|
||
const lifecyclePlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(lifecycleSelection.selected_recipe, lifecycleFilters);
|
||
lifecycleResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: lifecyclePlan.query,
|
||
limit: lifecyclePlan.limit,
|
||
account_scope: lifecyclePlan.account_scope
|
||
});
|
||
pushUnique(executedPrimitives, step.primitive_id);
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, lifecycleResult));
|
||
if (tradingMarginSelection?.selected_recipe) {
|
||
const tradingMarginPlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(tradingMarginSelection.selected_recipe, tradingMarginFilters);
|
||
tradingMarginResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: tradingMarginPlan.query,
|
||
limit: tradingMarginPlan.limit,
|
||
account_scope: tradingMarginPlan.account_scope
|
||
});
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, tradingMarginResult));
|
||
}
|
||
if (documentActivitySelection.selected_recipe) {
|
||
const documentActivityPlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(documentActivitySelection.selected_recipe, profileFilters);
|
||
documentActivityProfileResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: documentActivityPlan.query,
|
||
limit: documentActivityPlan.limit,
|
||
account_scope: documentActivityPlan.account_scope
|
||
});
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, documentActivityProfileResult));
|
||
}
|
||
if (counterpartyProfileSelection.selected_recipe) {
|
||
const counterpartyProfilePlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(counterpartyProfileSelection.selected_recipe, profileFilters);
|
||
counterpartyProfileResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: counterpartyProfilePlan.query,
|
||
limit: counterpartyProfilePlan.limit,
|
||
account_scope: counterpartyProfilePlan.account_scope
|
||
});
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, counterpartyProfileResult));
|
||
}
|
||
if (contractUsageSelection.selected_recipe) {
|
||
const contractUsagePlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(contractUsageSelection.selected_recipe, profileFilters);
|
||
contractUsageProfileResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: contractUsagePlan.query,
|
||
limit: contractUsagePlan.limit,
|
||
account_scope: contractUsagePlan.account_scope
|
||
});
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, contractUsageProfileResult));
|
||
}
|
||
if (lifecycleResult.error) {
|
||
pushUnique(queryLimitations, lifecycleResult.error);
|
||
pushReason(reasonCodes, "pilot_business_overview_query_documents_mcp_error");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_business_overview_query_documents_mcp_executed");
|
||
}
|
||
if (tradingMarginResult?.error) {
|
||
pushUnique(queryLimitations, tradingMarginResult.error);
|
||
pushReason(reasonCodes, "pilot_business_overview_trading_margin_query_mcp_error");
|
||
}
|
||
else if (tradingMarginResult) {
|
||
pushReason(reasonCodes, "pilot_business_overview_trading_margin_query_mcp_executed");
|
||
}
|
||
if (documentActivityProfileResult?.error) {
|
||
pushUnique(queryLimitations, documentActivityProfileResult.error);
|
||
pushReason(reasonCodes, "pilot_business_overview_document_activity_profile_query_mcp_error");
|
||
}
|
||
else if (documentActivityProfileResult) {
|
||
pushReason(reasonCodes, "pilot_business_overview_document_activity_profile_query_mcp_executed");
|
||
}
|
||
if (counterpartyProfileResult?.error) {
|
||
pushUnique(queryLimitations, counterpartyProfileResult.error);
|
||
pushReason(reasonCodes, "pilot_business_overview_counterparty_profile_query_mcp_error");
|
||
}
|
||
else if (counterpartyProfileResult) {
|
||
pushReason(reasonCodes, "pilot_business_overview_counterparty_profile_query_mcp_executed");
|
||
}
|
||
if (contractUsageProfileResult?.error) {
|
||
pushUnique(queryLimitations, contractUsageProfileResult.error);
|
||
pushReason(reasonCodes, "pilot_business_overview_contract_usage_profile_query_mcp_error");
|
||
}
|
||
else if (contractUsageProfileResult) {
|
||
pushReason(reasonCodes, "pilot_business_overview_contract_usage_profile_query_mcp_executed");
|
||
}
|
||
continue;
|
||
}
|
||
skippedPrimitives.push(step.primitive_id);
|
||
probeResults.push(skippedProbeResult(step, "pilot_business_overview_derives_aggregate_coverage_and_explanation"));
|
||
}
|
||
const derivedBusinessOverview = deriveBusinessOverview({
|
||
incomingResult,
|
||
outgoingResult,
|
||
lifecycleResult,
|
||
taxResult,
|
||
tradingMarginResult,
|
||
receivablesResult,
|
||
payablesResult,
|
||
openContractsResult,
|
||
documentActivityProfileResult,
|
||
counterpartyProfileResult,
|
||
contractUsageProfileResult,
|
||
debtAsOfDate,
|
||
inventoryOnHandResult,
|
||
inventoryAgingResult,
|
||
inventoryAsOfDate,
|
||
organizationScope,
|
||
periodScope: dateScope
|
||
});
|
||
if (derivedBusinessOverview) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_from_confirmed_rows");
|
||
if (derivedBusinessOverview.top_customers.length > 0) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_top_customers_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.top_suppliers.length > 0) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_top_suppliers_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.yearly_breakdown.length > 0) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_yearly_operating_breakdown_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.activity_period) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_activity_window_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.document_activity_profile) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_document_activity_profile_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.counterparty_profile) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_counterparty_profile_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.contract_usage_profile) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_contract_usage_profile_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.tax_position) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_tax_position_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.trading_margin_proxy) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_trading_margin_proxy_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.debt_position) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_debt_position_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.debt_open_settlement_quality) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_open_settlement_quality_from_confirmed_rows");
|
||
if (derivedBusinessOverview.debt_open_settlement_quality.age_signal) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_debt_age_signal_from_contract_dates");
|
||
}
|
||
}
|
||
if (derivedBusinessOverview.debt_staleness_risk_proxy) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_debt_staleness_risk_proxy_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.inventory_position) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_inventory_position_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.inventory_turnover_proxy) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_inventory_turnover_proxy_from_confirmed_rows");
|
||
}
|
||
if (derivedBusinessOverview.inventory_staleness_risk_proxy) {
|
||
pushReason(reasonCodes, "pilot_derived_business_overview_inventory_staleness_risk_proxy_from_confirmed_rows");
|
||
}
|
||
}
|
||
const sourceRowsSummary = summarizeBusinessOverviewRows({
|
||
incomingResult,
|
||
outgoingResult,
|
||
lifecycleResult,
|
||
taxResult,
|
||
tradingMarginResult,
|
||
receivablesResult,
|
||
payablesResult,
|
||
openContractsResult,
|
||
documentActivityProfileResult,
|
||
counterpartyProfileResult,
|
||
contractUsageProfileResult,
|
||
inventoryOnHandResult,
|
||
inventoryAgingResult
|
||
});
|
||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||
plan: planner.discovery_plan,
|
||
probeResults,
|
||
confirmedFacts: buildBusinessOverviewConfirmedFacts(derivedBusinessOverview),
|
||
inferredFacts: buildBusinessOverviewInferredFacts(derivedBusinessOverview),
|
||
unknownFacts: buildBusinessOverviewUnknownFacts(derivedBusinessOverview),
|
||
sourceRowsSummary,
|
||
queryLimitations,
|
||
recommendedNextProbe: "explain_evidence_basis"
|
||
});
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "executed",
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: executedPrimitives.length > 0,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: sourceRowsSummary,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
derived_business_overview: derivedBusinessOverview,
|
||
query_limitations: queryLimitations,
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
if (metadataPilotEligible) {
|
||
let metadataResult = null;
|
||
const metadataScope = metadataScopeForPlanner(planner);
|
||
const requestedMetaTypes = metadataTypesForPlanner(planner);
|
||
if (planner.selected_chain_id === "catalog_drilldown" && metadataScope) {
|
||
pushReason(reasonCodes, "pilot_catalog_drilldown_metadata_scope_seeded_from_surface_ref");
|
||
}
|
||
for (const step of dryRun.execution_steps) {
|
||
if (step.primitive_id !== "inspect_1c_metadata") {
|
||
skippedPrimitives.push(step.primitive_id);
|
||
probeResults.push(skippedProbeResult(step, "pilot_metadata_uses_only_inspect_1c_metadata"));
|
||
continue;
|
||
}
|
||
metadataResult = await runtimeDeps.executeAddressMcpMetadata({
|
||
meta_type: requestedMetaTypes,
|
||
name_mask: metadataScope ?? undefined,
|
||
limit: planner.discovery_plan.execution_budget.max_rows_per_probe
|
||
});
|
||
pushUnique(executedPrimitives, step.primitive_id);
|
||
probeResults.push(metadataResultToProbeResult(step.primitive_id, metadataResult));
|
||
if (metadataResult.error) {
|
||
pushUnique(queryLimitations, metadataResult.error);
|
||
pushReason(reasonCodes, "pilot_inspect_1c_metadata_mcp_error");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_inspect_1c_metadata_mcp_executed");
|
||
}
|
||
}
|
||
const sourceRowsSummary = metadataResult ? summarizeMetadataRows(metadataResult) : null;
|
||
const derivedMetadataSurface = deriveMetadataSurface(metadataResult, metadataScope, requestedMetaTypes, metadataScopeRankingAllowedForPlanner(planner));
|
||
if (derivedMetadataSurface) {
|
||
pushReason(reasonCodes, "pilot_derived_metadata_surface_from_confirmed_rows");
|
||
if (derivedMetadataSurface.route_family_selection_basis === "dominant_surface_objects") {
|
||
pushReason(reasonCodes, "pilot_selected_metadata_route_family_from_dominant_surface_objects");
|
||
}
|
||
if (derivedMetadataSurface.surface_object_ranking_applied) {
|
||
pushReason(reasonCodes, "pilot_selected_metadata_route_family_from_surface_object_ranking");
|
||
}
|
||
}
|
||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||
plan: planner.discovery_plan,
|
||
probeResults,
|
||
confirmedFacts: buildMetadataConfirmedFacts(derivedMetadataSurface),
|
||
inferredFacts: buildMetadataInferredFacts(derivedMetadataSurface),
|
||
unknownFacts: buildMetadataUnknownFacts(derivedMetadataSurface, metadataScope),
|
||
sourceRowsSummary,
|
||
queryLimitations,
|
||
recommendedNextProbe: "inspect_1c_metadata"
|
||
});
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "executed",
|
||
pilot_scope: "metadata_inspection_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: executedPrimitives.length > 0,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: sourceRowsSummary,
|
||
derived_metadata_surface: derivedMetadataSurface,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: queryLimitations,
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
if (entityResolutionPilotEligible) {
|
||
let queryResult = null;
|
||
const requestedEntity = counterparty;
|
||
if (isLowQualityEntityResolutionAnchor(requestedEntity)) {
|
||
pushReason(reasonCodes, "pilot_entity_resolution_anchor_missing_or_low_quality");
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Entity-resolution needs a clearer counterparty name");
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "skipped_needs_clarification",
|
||
pilot_scope: "entity_resolution_search_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: ["Entity-resolution needs a clearer counterparty name"],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
let derivedEntityResolution = null;
|
||
for (const step of dryRun.execution_steps) {
|
||
if (step.primitive_id === "search_business_entity") {
|
||
queryResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: ENTITY_RESOLUTION_COUNTERPARTY_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(ENTITY_RESOLUTION_COUNTERPARTY_LOOKUP_LIMIT)),
|
||
limit: ENTITY_RESOLUTION_COUNTERPARTY_LOOKUP_LIMIT
|
||
});
|
||
pushUnique(executedPrimitives, step.primitive_id);
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, queryResult));
|
||
if (queryResult.error) {
|
||
pushUnique(queryLimitations, queryResult.error);
|
||
pushReason(reasonCodes, "pilot_search_business_entity_mcp_error");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_search_business_entity_mcp_executed");
|
||
derivedEntityResolution = deriveEntityResolution(queryResult, requestedEntity);
|
||
}
|
||
continue;
|
||
}
|
||
if (step.primitive_id === "resolve_entity_reference") {
|
||
if (!queryResult || queryResult.error) {
|
||
skippedPrimitives.push(step.primitive_id);
|
||
probeResults.push(skippedProbeResult(step, entityResolutionFollowupStepLimitation()));
|
||
continue;
|
||
}
|
||
if (!derivedEntityResolution) {
|
||
derivedEntityResolution = deriveEntityResolution(queryResult, requestedEntity);
|
||
}
|
||
pushUnique(executedPrimitives, step.primitive_id);
|
||
probeResults.push(buildEntityResolutionResolveProbeResult({
|
||
queryResult,
|
||
resolution: derivedEntityResolution
|
||
}));
|
||
if (derivedEntityResolution?.resolution_status === "resolved") {
|
||
pushReason(reasonCodes, "pilot_resolve_entity_reference_from_catalog_rows");
|
||
}
|
||
else if (derivedEntityResolution?.resolution_status === "ambiguous") {
|
||
pushReason(reasonCodes, "pilot_resolve_entity_reference_requires_clarification");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_resolve_entity_reference_not_confirmed");
|
||
}
|
||
continue;
|
||
}
|
||
if (step.primitive_id === "probe_coverage") {
|
||
if (!queryResult || queryResult.error) {
|
||
skippedPrimitives.push(step.primitive_id);
|
||
probeResults.push(skippedProbeResult(step, entityResolutionFollowupStepLimitation()));
|
||
continue;
|
||
}
|
||
if (!derivedEntityResolution) {
|
||
derivedEntityResolution = deriveEntityResolution(queryResult, requestedEntity);
|
||
}
|
||
pushUnique(executedPrimitives, step.primitive_id);
|
||
probeResults.push(buildEntityResolutionCoverageProbeResult({
|
||
resolution: derivedEntityResolution
|
||
}));
|
||
pushReason(reasonCodes, "pilot_probe_coverage_executed_for_entity_resolution");
|
||
if (derivedEntityResolution?.resolution_status === "resolved") {
|
||
pushReason(reasonCodes, "pilot_entity_resolution_grounding_stable_for_downstream_probe");
|
||
}
|
||
else if (derivedEntityResolution?.resolution_status === "ambiguous") {
|
||
pushReason(reasonCodes, "pilot_entity_resolution_coverage_requires_clarification");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_entity_resolution_coverage_not_confirmed");
|
||
}
|
||
continue;
|
||
}
|
||
skippedPrimitives.push(step.primitive_id);
|
||
probeResults.push(skippedProbeResult(step, "pilot_entity_resolution_step_not_implemented"));
|
||
}
|
||
const sourceRowsSummary = queryResult ? summarizeEntityResolutionRows(queryResult) : null;
|
||
if (!derivedEntityResolution && queryResult && !queryResult.error) {
|
||
derivedEntityResolution = deriveEntityResolution(queryResult, requestedEntity);
|
||
}
|
||
if (derivedEntityResolution?.resolution_status === "resolved") {
|
||
pushReason(reasonCodes, "pilot_derived_entity_resolution_from_catalog_rows");
|
||
}
|
||
if (derivedEntityResolution?.resolution_status === "ambiguous") {
|
||
pushReason(reasonCodes, "pilot_entity_resolution_ambiguity_requires_clarification");
|
||
}
|
||
if (derivedEntityResolution?.resolution_status === "not_found") {
|
||
pushReason(reasonCodes, "pilot_entity_resolution_not_found_in_checked_catalog");
|
||
}
|
||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||
plan: planner.discovery_plan,
|
||
probeResults,
|
||
confirmedFacts: buildEntityResolutionConfirmedFacts(derivedEntityResolution),
|
||
inferredFacts: buildEntityResolutionInferredFacts(derivedEntityResolution),
|
||
unknownFacts: buildEntityResolutionUnknownFacts(derivedEntityResolution, requestedEntity),
|
||
sourceRowsSummary,
|
||
queryLimitations,
|
||
recommendedNextProbe: null
|
||
});
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "executed",
|
||
pilot_scope: "entity_resolution_search_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: executedPrimitives.length > 0,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: sourceRowsSummary,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: derivedEntityResolution,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: queryLimitations,
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
if (documentPilotEligible) {
|
||
let queryResult = null;
|
||
const filters = buildLifecycleFilters(planner);
|
||
const selection = (0, addressRecipeCatalog_1.selectAddressRecipe)("list_documents_by_counterparty", filters);
|
||
if (!selection.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_document_recipe_not_available");
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Document-evidence recipe is not available");
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "unsupported",
|
||
pilot_scope: "counterparty_document_evidence_query_documents_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: ["Document-evidence recipe is not available"],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
const recipePlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(selection.selected_recipe, filters);
|
||
for (const step of dryRun.execution_steps) {
|
||
if (step.primitive_id !== "query_documents") {
|
||
skippedPrimitives.push(step.primitive_id);
|
||
probeResults.push(skippedProbeResult(step, "pilot_only_executes_query_documents"));
|
||
continue;
|
||
}
|
||
queryResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: recipePlan.query,
|
||
limit: recipePlan.limit,
|
||
account_scope: recipePlan.account_scope
|
||
});
|
||
executedPrimitives.push(step.primitive_id);
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, queryResult));
|
||
if (queryResult.error) {
|
||
pushUnique(queryLimitations, queryResult.error);
|
||
pushReason(reasonCodes, "pilot_query_documents_mcp_error");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_query_documents_mcp_executed");
|
||
}
|
||
}
|
||
const sourceRowsSummary = queryResult ? summarizeDocumentRows(queryResult) : null;
|
||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||
plan: planner.discovery_plan,
|
||
probeResults,
|
||
confirmedFacts: queryResult ? buildDocumentConfirmedFacts(queryResult, counterparty, dateScope) : [],
|
||
inferredFacts: queryResult ? buildDocumentInferredFacts(queryResult, counterparty, dateScope) : [],
|
||
unknownFacts: buildDocumentUnknownFacts(dateScope, counterparty),
|
||
sourceRowsSummary,
|
||
queryLimitations,
|
||
recommendedNextProbe: "explain_evidence_basis"
|
||
});
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "executed",
|
||
pilot_scope: "counterparty_document_evidence_query_documents_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: executedPrimitives.length > 0,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: sourceRowsSummary,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: queryLimitations,
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
if (movementPilotEligible) {
|
||
let queryResult = null;
|
||
const filters = buildValueFlowFilters(planner);
|
||
const selection = (0, addressRecipeCatalog_1.selectAddressRecipe)("bank_operations_by_counterparty", filters);
|
||
if (!selection.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_movement_recipe_not_available");
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Movement-evidence recipe is not available");
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "unsupported",
|
||
pilot_scope: "counterparty_movement_evidence_query_movements_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: ["Movement-evidence recipe is not available"],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
const recipePlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(selection.selected_recipe, filters);
|
||
for (const step of dryRun.execution_steps) {
|
||
if (step.primitive_id !== "query_movements") {
|
||
skippedPrimitives.push(step.primitive_id);
|
||
probeResults.push(skippedProbeResult(step, "pilot_only_executes_query_movements"));
|
||
continue;
|
||
}
|
||
queryResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: recipePlan.query,
|
||
limit: recipePlan.limit,
|
||
account_scope: recipePlan.account_scope
|
||
});
|
||
executedPrimitives.push(step.primitive_id);
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, queryResult));
|
||
if (queryResult.error) {
|
||
pushUnique(queryLimitations, queryResult.error);
|
||
pushReason(reasonCodes, "pilot_query_movements_mcp_error");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_query_movements_mcp_executed");
|
||
}
|
||
}
|
||
const sourceRowsSummary = queryResult ? summarizeMovementRows(queryResult) : null;
|
||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||
plan: planner.discovery_plan,
|
||
probeResults,
|
||
confirmedFacts: queryResult ? buildMovementConfirmedFacts(queryResult, counterparty, dateScope) : [],
|
||
inferredFacts: queryResult ? buildMovementInferredFacts(queryResult, counterparty, dateScope) : [],
|
||
unknownFacts: buildMovementUnknownFacts(dateScope, counterparty),
|
||
sourceRowsSummary,
|
||
queryLimitations,
|
||
recommendedNextProbe: "explain_evidence_basis"
|
||
});
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "executed",
|
||
pilot_scope: "counterparty_movement_evidence_query_movements_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: executedPrimitives.length > 0,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: sourceRowsSummary,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: queryLimitations,
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
if (valueFlowPilotEligible) {
|
||
let queryResult = null;
|
||
const filters = buildValueFlowFilters(planner);
|
||
const valueFlowProfile = valueFlowPilotProfile(planner);
|
||
if (valueFlowProfile.direction === "bidirectional_net_value_flow") {
|
||
let incomingResult = null;
|
||
let outgoingResult = null;
|
||
const incomingSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("customer_revenue_and_payments", filters);
|
||
const outgoingSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("supplier_payouts_profile", filters);
|
||
if (!incomingSelection.selected_recipe || !outgoingSelection.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_bidirectional_value_flow_recipe_not_available");
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Bidirectional value-flow recipes are not available");
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "unsupported",
|
||
pilot_scope: valueFlowProfile.scope,
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: ["Bidirectional value-flow recipes are not available"],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
pushReason(reasonCodes, "pilot_bidirectional_value_flow_recipes_selected");
|
||
for (const step of dryRun.execution_steps) {
|
||
if (step.primitive_id !== "query_movements") {
|
||
skippedPrimitives.push(step.primitive_id);
|
||
probeResults.push(skippedProbeResult(step, "pilot_bidirectional_value_flow_uses_two_query_movements_and_derives_net"));
|
||
continue;
|
||
}
|
||
const incomingExecution = await executeCoverageAwareValueFlowQuery({
|
||
primitiveId: step.primitive_id,
|
||
recipePlanBuilder: (scopedFilters) => (0, addressRecipeCatalog_1.buildAddressRecipePlan)(incomingSelection.selected_recipe, scopedFilters),
|
||
baseFilters: filters,
|
||
dateScope,
|
||
maxProbeCount: planner.discovery_plan.execution_budget.max_probe_count,
|
||
maxRowsPerProbe: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||
deps: runtimeDeps
|
||
});
|
||
const outgoingExecution = await executeCoverageAwareValueFlowQuery({
|
||
primitiveId: step.primitive_id,
|
||
recipePlanBuilder: (scopedFilters) => (0, addressRecipeCatalog_1.buildAddressRecipePlan)(outgoingSelection.selected_recipe, scopedFilters),
|
||
baseFilters: filters,
|
||
dateScope,
|
||
maxProbeCount: planner.discovery_plan.execution_budget.max_probe_count,
|
||
maxRowsPerProbe: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||
deps: runtimeDeps
|
||
});
|
||
incomingResult = incomingExecution.result;
|
||
outgoingResult = outgoingExecution.result;
|
||
pushUnique(executedPrimitives, step.primitive_id);
|
||
probeResults.push(...incomingExecution.probe_results, ...outgoingExecution.probe_results);
|
||
for (const limitation of [...incomingExecution.query_limitations, ...outgoingExecution.query_limitations]) {
|
||
pushUnique(queryLimitations, limitation);
|
||
}
|
||
if (incomingResult?.error) {
|
||
pushReason(reasonCodes, "pilot_bidirectional_incoming_query_movements_mcp_error");
|
||
}
|
||
if (outgoingResult?.error) {
|
||
pushReason(reasonCodes, "pilot_bidirectional_outgoing_query_movements_mcp_error");
|
||
}
|
||
if (incomingResult?.coverage_recovered_by_period_chunking) {
|
||
pushReason(reasonCodes, "pilot_bidirectional_incoming_monthly_period_chunking_recovered_coverage");
|
||
}
|
||
if (outgoingResult?.coverage_recovered_by_period_chunking) {
|
||
pushReason(reasonCodes, "pilot_bidirectional_outgoing_monthly_period_chunking_recovered_coverage");
|
||
}
|
||
if (!incomingResult?.error || !outgoingResult?.error) {
|
||
pushReason(reasonCodes, "pilot_bidirectional_query_movements_mcp_executed");
|
||
}
|
||
}
|
||
const sourceRowsSummary = summarizeBidirectionalValueFlowRows({ incomingResult, outgoingResult });
|
||
const derivedBidirectionalValueFlow = deriveBidirectionalValueFlow({
|
||
incomingResult,
|
||
outgoingResult,
|
||
counterparty,
|
||
periodScope: dateScope,
|
||
aggregationAxis
|
||
});
|
||
if (derivedBidirectionalValueFlow) {
|
||
pushReason(reasonCodes, "pilot_derived_bidirectional_value_flow_from_confirmed_rows");
|
||
if (aggregationAxis === "month" && derivedBidirectionalValueFlow.monthly_breakdown.length > 0) {
|
||
pushReason(reasonCodes, "pilot_derived_bidirectional_monthly_breakdown_from_confirmed_rows");
|
||
}
|
||
}
|
||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||
plan: planner.discovery_plan,
|
||
probeResults,
|
||
confirmedFacts: buildBidirectionalValueFlowConfirmedFacts(derivedBidirectionalValueFlow),
|
||
inferredFacts: buildBidirectionalValueFlowInferredFacts(derivedBidirectionalValueFlow),
|
||
unknownFacts: buildBidirectionalValueFlowUnknownFacts(dateScope, derivedBidirectionalValueFlow),
|
||
sourceRowsSummary,
|
||
queryLimitations,
|
||
recommendedNextProbe: "explain_evidence_basis"
|
||
});
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "executed",
|
||
pilot_scope: valueFlowProfile.scope,
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: executedPrimitives.length > 0,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: sourceRowsSummary,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: derivedBidirectionalValueFlow,
|
||
query_limitations: queryLimitations,
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
const recipeIntent = valueFlowProfile.recipe_intent;
|
||
const selection = recipeIntent ? (0, addressRecipeCatalog_1.selectAddressRecipe)(recipeIntent, filters) : { selected_recipe: null };
|
||
if (!selection.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_value_flow_recipe_not_available");
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Value-flow recipe is not available");
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "unsupported",
|
||
pilot_scope: valueFlowProfile.scope,
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: ["Value-flow recipe is not available"],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
pushReason(reasonCodes, valueFlowProfile.direction === "outgoing_supplier_payout"
|
||
? "pilot_supplier_payout_recipe_selected"
|
||
: "pilot_customer_revenue_recipe_selected");
|
||
for (const step of dryRun.execution_steps) {
|
||
if (step.primitive_id !== "query_movements") {
|
||
skippedPrimitives.push(step.primitive_id);
|
||
probeResults.push(skippedProbeResult(step, "pilot_value_flow_uses_query_movements_and_derives_aggregate"));
|
||
continue;
|
||
}
|
||
const execution = await executeCoverageAwareValueFlowQuery({
|
||
primitiveId: step.primitive_id,
|
||
recipePlanBuilder: (scopedFilters) => (0, addressRecipeCatalog_1.buildAddressRecipePlan)(selection.selected_recipe, scopedFilters),
|
||
baseFilters: filters,
|
||
dateScope,
|
||
maxProbeCount: planner.discovery_plan.execution_budget.max_probe_count,
|
||
maxRowsPerProbe: planner.discovery_plan.execution_budget.max_rows_per_probe,
|
||
deps: runtimeDeps
|
||
});
|
||
queryResult = execution.result;
|
||
pushUnique(executedPrimitives, step.primitive_id);
|
||
probeResults.push(...execution.probe_results);
|
||
for (const limitation of execution.query_limitations) {
|
||
pushUnique(queryLimitations, limitation);
|
||
}
|
||
if (queryResult?.error) {
|
||
pushReason(reasonCodes, "pilot_query_movements_mcp_error");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_query_movements_mcp_executed");
|
||
}
|
||
if (queryResult?.coverage_recovered_by_period_chunking) {
|
||
pushReason(reasonCodes, "pilot_monthly_period_chunking_recovered_coverage");
|
||
}
|
||
}
|
||
const sourceRowsSummary = queryResult ? summarizeValueFlowRows(queryResult) : null;
|
||
if (planner.selected_chain_id === "value_flow_ranking" && rankingNeed) {
|
||
const derivedRankedValueFlow = deriveRankedValueFlow(queryResult, {
|
||
organizationScope,
|
||
periodScope: dateScope,
|
||
direction: valueFlowProfile.direction,
|
||
rankingNeed
|
||
});
|
||
if (derivedRankedValueFlow) {
|
||
pushReason(reasonCodes, "pilot_derived_ranked_value_flow_from_confirmed_rows");
|
||
}
|
||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||
plan: planner.discovery_plan,
|
||
probeResults,
|
||
confirmedFacts: buildRankedValueFlowConfirmedFacts(derivedRankedValueFlow),
|
||
inferredFacts: buildRankedValueFlowInferredFacts(derivedRankedValueFlow),
|
||
unknownFacts: buildRankedValueFlowUnknownFacts(dateScope, derivedRankedValueFlow),
|
||
sourceRowsSummary,
|
||
queryLimitations,
|
||
recommendedNextProbe: "explain_evidence_basis"
|
||
});
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "executed",
|
||
pilot_scope: valueFlowProfile.scope,
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: executedPrimitives.length > 0,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: sourceRowsSummary,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_ranked_value_flow: derivedRankedValueFlow,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: queryLimitations,
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
const derivedValueFlow = deriveValueFlow(queryResult, counterparty, dateScope, valueFlowProfile.direction, aggregationAxis);
|
||
if (derivedValueFlow) {
|
||
pushReason(reasonCodes, "pilot_derived_value_flow_from_confirmed_rows");
|
||
if (aggregationAxis === "month" && derivedValueFlow.monthly_breakdown.length > 0) {
|
||
pushReason(reasonCodes, "pilot_derived_value_flow_monthly_breakdown_from_confirmed_rows");
|
||
}
|
||
}
|
||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||
plan: planner.discovery_plan,
|
||
probeResults,
|
||
confirmedFacts: queryResult ? buildValueFlowConfirmedFacts(queryResult, counterparty, valueFlowProfile.direction) : [],
|
||
inferredFacts: buildValueFlowInferredFacts(derivedValueFlow),
|
||
unknownFacts: buildValueFlowUnknownFacts(dateScope, valueFlowProfile.direction, derivedValueFlow),
|
||
sourceRowsSummary,
|
||
queryLimitations,
|
||
recommendedNextProbe: "explain_evidence_basis"
|
||
});
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "executed",
|
||
pilot_scope: valueFlowProfile.scope,
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: executedPrimitives.length > 0,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: sourceRowsSummary,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_ranked_value_flow: null,
|
||
derived_value_flow: derivedValueFlow,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: queryLimitations,
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
let queryResult = null;
|
||
const filters = buildLifecycleFilters(planner);
|
||
const selection = (0, addressRecipeCatalog_1.selectAddressRecipe)("counterparty_activity_lifecycle", filters);
|
||
if (!selection.selected_recipe) {
|
||
pushReason(reasonCodes, "pilot_lifecycle_recipe_not_available");
|
||
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Lifecycle recipe is not available");
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "unsupported",
|
||
pilot_scope: "counterparty_lifecycle_query_documents_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: false,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: null,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: null,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: ["Lifecycle recipe is not available"],
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|
||
const recipePlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(selection.selected_recipe, filters);
|
||
for (const step of dryRun.execution_steps) {
|
||
if (step.primitive_id !== "query_documents") {
|
||
skippedPrimitives.push(step.primitive_id);
|
||
probeResults.push(skippedProbeResult(step, "pilot_only_executes_query_documents"));
|
||
continue;
|
||
}
|
||
queryResult = await runtimeDeps.executeAddressMcpQuery({
|
||
query: recipePlan.query,
|
||
limit: recipePlan.limit,
|
||
account_scope: recipePlan.account_scope
|
||
});
|
||
executedPrimitives.push(step.primitive_id);
|
||
probeResults.push(queryResultToProbeResult(step.primitive_id, queryResult));
|
||
if (queryResult.error) {
|
||
pushUnique(queryLimitations, queryResult.error);
|
||
pushReason(reasonCodes, "pilot_query_documents_mcp_error");
|
||
}
|
||
else {
|
||
pushReason(reasonCodes, "pilot_query_documents_mcp_executed");
|
||
}
|
||
}
|
||
const sourceRowsSummary = queryResult ? summarizeLifecycleRows(queryResult) : null;
|
||
const derivedActivityPeriod = deriveActivityPeriod(queryResult);
|
||
if (derivedActivityPeriod) {
|
||
pushReason(reasonCodes, "pilot_derived_activity_period_from_confirmed_rows");
|
||
}
|
||
const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({
|
||
plan: planner.discovery_plan,
|
||
probeResults,
|
||
confirmedFacts: queryResult ? buildLifecycleConfirmedFacts(queryResult, counterparty) : [],
|
||
inferredFacts: queryResult ? buildLifecycleInferredFacts(queryResult) : [],
|
||
unknownFacts: buildLifecycleUnknownFacts(),
|
||
sourceRowsSummary,
|
||
queryLimitations,
|
||
recommendedNextProbe: "explain_evidence_basis"
|
||
});
|
||
return {
|
||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
|
||
policy_owner: "assistantMcpDiscoveryPilotExecutor",
|
||
pilot_status: "executed",
|
||
pilot_scope: "counterparty_lifecycle_query_documents_v1",
|
||
dry_run: dryRun,
|
||
mcp_execution_performed: executedPrimitives.length > 0,
|
||
executed_primitives: executedPrimitives,
|
||
skipped_primitives: skippedPrimitives,
|
||
probe_results: probeResults,
|
||
evidence,
|
||
source_rows_summary: sourceRowsSummary,
|
||
derived_metadata_surface: null,
|
||
derived_entity_resolution: null,
|
||
derived_activity_period: derivedActivityPeriod,
|
||
derived_value_flow: null,
|
||
derived_bidirectional_value_flow: null,
|
||
query_limitations: queryLimitations,
|
||
reason_codes: reasonCodes
|
||
};
|
||
}
|