Стабилизировать денежные ответы и прибыль 1С-ассистента

This commit is contained in:
2026-05-23 13:14:22 +03:00
parent b18613961c
commit 473cdc3a9b
17 changed files with 1184 additions and 27 deletions
@@ -14,6 +14,11 @@ function sessionOrganizationName(sessionOrganizationScope, toNonEmptyString) {
const scope = toRecordObject(sessionOrganizationScope);
return toNonEmptyString(scope?.selectedOrganization) ?? toNonEmptyString(scope?.activeOrganization);
}
function sessionKnownOrganizations(sessionOrganizationScope) {
const scope = toRecordObject(sessionOrganizationScope);
const knownOrganizations = scope?.knownOrganizations;
return Array.isArray(knownOrganizations) ? knownOrganizations : [];
}
function predecomposeOrganizationName(predecomposeContract, toNonEmptyString) {
const entities = toRecordObject(predecomposeContract?.entities);
return (toNonEmptyString(entities?.organization) ??
@@ -223,7 +228,8 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
effectiveMessage: addressInputMessage,
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
predecomposeContract,
followupContext: discoveryFollowupContext
followupContext: discoveryFollowupContext,
knownOrganizations: sessionKnownOrganizations(input.sessionOrganizationScope ?? null)
}));
}
catch (error) {
@@ -115,6 +115,12 @@ function hasBusinessOverviewDirectMoneyAnswerHint(input) {
return true;
}
const text = input.rawUtterance;
if (/(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(text)) {
return true;
}
if (/(?:\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?\b|\u0434\u0435\u043d\p{L}{0,20}\s+\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\u043b\p{L}*[\s\S]{0,80}\u0443\u0448\u043b\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e|\u0432\u0445\u043e\u0434\u044f\u0449\p{L}*[\s\S]{0,80}\u0438\u0441\u0445\u043e\u0434\u044f\u0449\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e)/iu.test(text)) {
return true;
}
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|how\s+much)[\s\S]{0,120}(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447|\u0434\u0435\u043d\p{L}*|\u043f\u043e\u043b\u0443\u0447|\u043f\u043e\u0441\u0442\u0443\u043f\p{L}*)|(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447)[\s\S]{0,120}(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u0432\u0441\u0435\u0433\u043e|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time)|(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f|\u043a\u0430\u043a\u0438\u0435|which|what)[\s\S]{0,80}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,80}(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,40}(?:\u0433\u043e\u0434|year)/iu.test(text);
}
function timeScopeNeedFor(input) {
@@ -225,6 +231,13 @@ function rankingNeedFromRawUtterance(value) {
}
return null;
}
function suppressRankingNeedFromRawUtterance(value) {
const text = lower(value);
if (!text) {
return false;
}
return /(?:\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?\b|\u043d\u0435\s+\u0442\u043e\u043f\b|\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440\b|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0438\u0441\u043a\u043b\u044e\u0447\w*\s+\u0442\u043e\u043f|\u0431\u0435\u0437\s+\u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0430\b|без\s+топ(?:ов|а)?\b|не\s+топ\b|не\s+обзор\b|просто\s+ден\p{L}+|исключ\S*\s+топ|без\s+рейтинга\b)/iu.test(text);
}
function proofExpectationFor(input) {
if (input.clarificationGaps.length > 0) {
return "clarification_required";
@@ -383,6 +396,7 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
const action = lower(turnMeaning?.asked_action_family);
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
const rawUtterance = lower(input.rawUtterance);
const rawQuestionSignal = lower([input.rawUtterance, turnMeaning?.raw_message, turnMeaning?.effective_message].join(" "));
const aggregationAxis = lower(turnMeaning?.asked_aggregation_axis);
const seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
@@ -400,15 +414,18 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
});
const aggregationNeed = aggregationNeedFor(aggregationAxis);
const comparisonNeed = comparisonNeedFor(action);
const rankingNeed = rankingNeedFromRawUtterance(rawUtterance) ?? seededRankingNeed;
const allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
const subjectScopedBidirectionalAllTime = businessFactFamily === "value_flow" &&
comparisonNeed === "incoming_vs_outgoing" &&
subjectCandidates.length > 0 &&
!explicitDateScope;
const suppressRankingNeed = suppressRankingNeedFromRawUtterance(rawQuestionSignal) ||
/(?:\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?|\u043d\u0435\s+\u0442\u043e\u043f|\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0438\u0441\u043a\u043b\u044e\u0447\w*\s+\u0442\u043e\u043f|\u0431\u0435\u0437\s+\u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0430)/iu.test(rawQuestionSignal);
const rawRankingNeed = rankingNeedFromRawUtterance(rawQuestionSignal);
const rankingNeed = suppressRankingNeed ? null : rawRankingNeed ?? seededRankingNeed;
const directBusinessOverviewMoneyAnswerHint = hasBusinessOverviewDirectMoneyAnswerHint({
family: businessFactFamily,
rawUtterance,
rawUtterance: rawQuestionSignal,
rankingNeed
});
const oneSidedOpenScopeTotalHint = hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action);
@@ -34,6 +34,30 @@ function requestsFinancialCounterpartyBoundary(turnMeaning, graph) {
return (/(?:банк|сбербанк|финанс|кредит|депозит)/iu.test(text) &&
/(?:клиент|поставщик|выручк|топ|обычн|роль|поток)/iu.test(text));
}
function requestsCompactCashflowAnswer(turnMeaning, graph) {
const text = normalizeQuestionText([
turnMeaning?.raw_message,
turnMeaning?.effective_message,
graph?.source_message,
graph?.question
].join(" "));
if (!text) {
return false;
}
if (/(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(text)) {
return true;
}
return /(?:\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?\b|\u0434\u0435\u043d\p{L}{0,20}\s+\u043d\u0435\u0442\u0442\u043e|\u043f\u043e\u043b\u0443\u0447\p{L}*[\s\S]{0,80}\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\u043b\p{L}*[\s\S]{0,80}\u0443\u0448\u043b\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e|(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*)[\s\S]{0,120}(?:\u0434\u0435\u043d\p{L}*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u043e\u043b\u0443\u0447|\u043f\u0440\u0438\u0448\u043b))/iu.test(text);
}
function requestsCashflowPolarityAnswer(turnMeaning, graph) {
const text = normalizeQuestionText([
turnMeaning?.raw_message,
turnMeaning?.effective_message,
graph?.source_message,
graph?.question
].join(" "));
return /(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(text);
}
function toStringList(value) {
if (!Array.isArray(value)) {
return [];
@@ -595,6 +619,18 @@ function compactComparable(value) {
.replace(/\s+/g, " ")
.trim();
}
function businessOverviewOrganizationScopeLabel(value) {
const text = toNonEmptyString(value);
if (!text) {
return null;
}
const comparable = compactComparable(text);
if (/^(?:с|без)\s+разбивк/.test(comparable) ||
/\b(?:входящ|исходящ|нетто|топ|контрагент|платеж|поступлен)\b/.test(comparable)) {
return null;
}
return text;
}
function businessOverviewSeparateSubjectLabel(graph, turnMeaning, organizationScope) {
const candidates = uniqueStrings([
...toStringList(turnMeaning?.business_overview_separate_entity_candidates),
@@ -686,7 +722,7 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
const netDirection = overview.net_direction === "net_outgoing" ? "операционное нетто в минус" : "расчетное операционное нетто";
const period = businessOverviewPeriodText(overview);
const limitLine = businessOverviewCoverageLimitLine(overview);
const organizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
const organizationScope = businessOverviewOrganizationScopeLabel(turnMeaning?.explicit_organization_scope);
const separateSubject = businessOverviewSeparateSubjectLabel(graph, turnMeaning, organizationScope);
const previousCounterpartySummary = buildPreviousCounterpartyValueFlowSummary(toRecordObject(turnMeaning?.previous_counterparty_value_flow_bundle), separateSubject, toRecordObject(turnMeaning?.previous_counterparty_document_bundle));
const organizationPrefix = organizationScope ? `по компании ${organizationScope} ` : "";
@@ -730,6 +766,22 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
const debtDueDateBoundary = actionFamily === "debt_due_date_boundary" || unsupportedFamily === "debt_due_date_boundary";
const vendorRiskBoundary = actionFamily === "vendor_risk_procurement_boundary" || unsupportedFamily === "vendor_risk_procurement_boundary";
const inventoryReserveBoundary = actionFamily === "inventory_reserve_boundary" || unsupportedFamily === "inventory_reserve_liquidation_boundary";
const compactCashflowRequested = directMoneyAnswer && requestsCompactCashflowAnswer(turnMeaning, graph);
const cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
if (compactCashflowRequested && !rankingNeed && (incomingAmount || outgoingAmount || netAmount)) {
const netDisplay = sentenceAmount(netAmount) ?? netAmount ?? "0 \u0440\u0443\u0431.";
const signedNetDisplay = cashflowPolarityRequested && netDisplay && !String(netDisplay).trim().startsWith("-")
? `+${netDisplay}`
: netDisplay;
const polarityLead = cashflowPolarityRequested
? `\u041a\u043e\u0440\u043e\u0442\u043a\u043e: \u043f\u043e \u0434\u0435\u043d\u044c\u0433\u0430\u043c \u043f\u043b\u044e\u0441. `
: "\u041a\u043e\u0440\u043e\u0442\u043a\u043e: ";
lines.push(`${polarityLead}${organizationPrefix}${period} \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 ${incomingAmount ?? "0 \u0440\u0443\u0431."}; \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438/\u0441\u043f\u0438\u0441\u0430\u043b\u0438 ${outgoingAmount ?? "0 \u0440\u0443\u0431."}; \u0434\u0435\u043d\u0435\u0436\u043d\u043e\u0435 \u043d\u0435\u0442\u0442\u043e ${signedNetDisplay}.`);
lines.push(cashflowPolarityRequested
? "\u042d\u0442\u043e \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u043f\u043e \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u043c \u0441\u0442\u0440\u043e\u043a\u0430\u043c 1\u0421, \u043d\u0435 \u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0441\u043a\u0438\u0439 \u0444\u0438\u043d\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442."
: "\u042d\u0442\u043e \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u043f\u043e \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u043c \u0441\u0442\u0440\u043e\u043a\u0430\u043c 1\u0421, \u043d\u0435 \u0447\u0438\u0441\u0442\u0430\u044f \u043f\u0440\u0438\u0431\u044b\u043b\u044c \u0438 \u043d\u0435 \u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0441\u043a\u0438\u0439 \u0444\u0438\u043d\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442.");
return joinBusinessReplyLines(lines);
}
if (profitMarginBoundary) {
const accountingFinancialResult = toRecordObject(overview.accounting_financial_result);
if (accountingFinancialResult) {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.ASSISTANT_MCP_DISCOVERY_TURN_INPUT_SCHEMA_VERSION = void 0;
exports.buildAssistantMcpDiscoveryTurnInput = buildAssistantMcpDiscoveryTurnInput;
const assistantMcpDiscoveryDataNeedGraph_1 = require("./assistantMcpDiscoveryDataNeedGraph");
const assistantOrganizationMatcher_1 = require("./assistantOrganizationMatcher");
const addressTextRepair_1 = require("./addressTextRepair");
exports.ASSISTANT_MCP_DISCOVERY_TURN_INPUT_SCHEMA_VERSION = "assistant_mcp_discovery_turn_input_v1";
function toRecordObject(value) {
@@ -734,11 +735,13 @@ function hasBusinessOverviewContinuationSignal(text) {
const hasFinalSummaryCue = /(?:\u0447\u0442\u043e\s+\u043c\u044b\s+\u0437\u043d\u0430\u0435\u043c|\u0447\u0442\u043e\s+\u043f\u043e\u043d\u044f\u0442\u043d\u043e|\u0447\u0442\u043e\s+\u043f\u0440\u043e\u0432\u0435\u0440\w*\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0441\u043b\u0435\u0434\u0443\u044e\u0449\w*\s+\u0448\u0430\u0433|\u0438\u0442\u043e\u0433\w*\s+\u0432\u044b\u0432\u043e\u0434|\u043a\u0430\u043a\u043e\u0439\s+\u0432\u044b\u0432\u043e\u0434|\u0447\u0442\u043e\s+\u0441\s+\u044d\u0442\u0438\u043c\s+\u0434\u0435\u043b\u0430\u0442\u044c|what\s+do\s+we\s+know|what\s+is\s+missing|next\s+step|final\s+summary)/iu.test(normalized);
const hasMoneyBreakdownCue = /(?:\u0440\u0430\u0441\u043a\u0440\u043e\p{L}*\s+\u0434\u0435\u043d\p{L}*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u0432\u0441\u0435\u0433\u043e\s+\u043f\u043e\u043b\u0443\u0447|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:\u0432\u0441\u0435\u0433\u043e\s+)?\u0437\u0430\u043f\u043b\u0430\u0442|\u0447\u0438\u0441\u0442\p{L}*\s+\u0434\u0435\u043d\u0435\u0436\u043d\p{L}*\s+\u043f\u043e\u0442\u043e\u043a|\u0433\u043b\u0430\u0432\u043d\p{L}*\s+(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a)|top\s+(?:customer|supplier)|cash\s+breakdown)/iu.test(normalized) &&
/(?:\u043f\u043e\u043b\u0443\u0447|\u0437\u0430\u043f\u043b\u0430\u0442|\u043d\u0435\u0442\u0442\u043e|\u0434\u0435\u043d\p{L}*|\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|received|paid|net|cash|customer|supplier)/iu.test(normalized);
const hasCashflowPolarityCue = /(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(normalized);
return (hasEvidenceContinuationCue ||
hasAnalystContinuationCue ||
hasTaxContinuationCue ||
hasFinalSummaryCue ||
hasMoneyBreakdownCue);
hasMoneyBreakdownCue ||
hasCashflowPolarityCue);
}
function hasExplicitVatQuestionSignal(text) {
if (!text) {
@@ -785,6 +788,9 @@ function hasBusinessOverviewFollowupSeed(followupSeed) {
followupSeed.loopSelectedChainId === "business_overview");
}
function hasValueFlowSignal(text) {
if (/(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\u043a|\u043e\u043f\u043b\u0430\u0442|\u043f\u043b\u0430\u0442[\u0435\u0451]\u0436|\u0437\u0430\u043f\u043b\u0430\u0442|\u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b|\u0441\u043f\u0438\u0441\u0430\u043d|\u0440\u0430\u0441\u0445\u043e\u0434|\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u0432\u0445\u043e\u0434\u044f\u0449|\u043f\u043e\u043b\u0443\u0447(?:\u0438\u043b|\u0435\u043d\u043e|\u0435\u043d)|\u043f\u043e\u0441\u0442\u0443\u043f\u0438\u043b|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d|\u0434\u0435\u043d\p{L}*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(text)) {
return true;
}
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|(?<!\p{L})заработ(?:ал|али|ало|аем|ает|ать|ано|ок)(?!\p{L})|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(text);
}
function hasValueFlowAggregateQuestionSignal(text) {
@@ -794,6 +800,9 @@ function hasPayoutSignal(text) {
return /(?:\bмы\s+(?:за)?плат|(?:за)?платил|оплатил|перечисл|списан|расход|поставщик|исходящ|supplier|payout|outflow|paid\s+to|payment\s+to)/iu.test(text);
}
function hasBidirectionalValueFlowSignal(text) {
if (/(?:\u043d\u0435\u0442\u0442\u043e|\u0441\u0430\u043b\u044c\u0434\u043e|\u0431\u0430\u043b\u0430\u043d\u0441\s+(?:\u043f\u043b\u0430\u0442|\u0434\u0435\u043d\u0435\u0433|\u0434\u0435\u043d\u0435\u0436)|\u0432\u0437\u0430\u0438\u043c\u043e\u0440\u0430\u0441\u0447[\u0435\u0451]\u0442|\u043f\u043e\u043b\u0443\u0447\p{L}*.*(?:\u0437\u0430)?\u043f\u043b\u0430\u0442\p{L}*|(?:\u0437\u0430)?\u043f\u043b\u0430\u0442\p{L}*.*\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0432\u0445\u043e\u0434\u044f\u0449.*\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u0438\u0441\u0445\u043e\u0434\u044f\u0449.*\u0432\u0445\u043e\u0434\u044f\u0449|\u043f\u0440\u0438\u0448\p{L}*.*\u0443\u0448\p{L}*|\u0443\u0448\p{L}*.*\u043f\u0440\u0438\u0448\p{L}*|\u043f\u043b\u044e\u0441.*\u043c\u0438\u043d\u0443\u0441|\u043c\u0438\u043d\u0443\u0441.*\u043f\u043b\u044e\u0441|net\s+(?:flow|cash|payment)|cash\s+net|incoming\s+and\s+outgoing|received\s+and\s+paid|paid\s+and\s+received)/iu.test(text)) {
return true;
}
return /(?:нетто|сальдо|баланс\s+(?:плат|денег|денеж)|взаиморасч[её]т|получил[иа]?.*(?:за)?платил|(?:за)?платил[иа]?.*получил|входящ.*исходящ|исходящ.*входящ|дебет.*кредит|кредит.*дебет|net\s+(?:flow|cash|payment)|cash\s+net|incoming\s+and\s+outgoing|received\s+and\s+paid|paid\s+and\s+received)/iu.test(text);
}
function hasValueRankingSignal(text) {
@@ -820,6 +829,39 @@ function extractOrganizationScopeFromRawText(value) {
.replace(/\s+(?:\u043f\u043e\s+\u0434\u0430\u043d\u043d\u044b\u043c\s+1\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0437\u0430\s+(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})(?:\s+\u0433(?:\u043e\u0434|\.)?)?|\u043d\u0430\s+(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})|\u0437\u0430\s+\u0432\u0441[\u0435\u0451]\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|\u0434\u0430\u0439\s+\u0431\u0438\u0437\u043d\u0435\u0441-?\u043e\u0431\u0437\u043e\u0440|\u0431\u0438\u0437\u043d\u0435\u0441-?\u043e\u0431\u0437\u043e\u0440|\u043d\u043e\s+\u043d\u0435).*$/iu, "")
.trim());
}
function extractOrganizationScopeFromSemanticText(value) {
const text = toNonEmptyString(value);
if (!text) {
return null;
}
const match = text.match(/(?:^|[\s,;:])(?:\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u0438|\u0438\u044f)|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446(?:\u0438\u0438|\u0438\u044f))\s+(.+?)(?=$|[\n,.;:!?]|\s+\u0437\u0430\s+(?:\d{4}|\d{2}\s*\u0433|\d{2}\s+\u0433\u043e\u0434)|\s+\u043d\u0430\s+(?:\d{4}|\d{2}\s*\u0433|\d{2}\s+\u0433\u043e\u0434))/iu);
return toNonEmptyString(match?.[1]);
}
function normalizeLooseOrganizationAlias(value) {
const text = toNonEmptyString(value);
if (!text) {
return null;
}
const normalized = text
.replace(/^[«"'“”]+|[»"'“”]+$/gu, "")
.replace(/\s+/g, " ")
.trim();
if (!normalized) {
return null;
}
const comparable = (0, addressTextRepair_1.normalizeRussianComparableText)(normalized);
const normalizedTokens = normalized.split(/\s+/u).filter(Boolean);
const hasYearOnlyTimeTail = /\b(?:19|20)\d{2}\b/u.test(normalized) &&
normalizedTokens.length <= 4 &&
!/(?:\u043e\u043e\u043e|\u0438\u043f|\u0430\u043e|\u043f\u0430\u043e|\u0437\u0430\u043e|llc|inc|corp)/iu.test(comparable);
if (hasYearOnlyTimeTail) {
return null;
}
if (/^(?:\u0438|\u0432|\u0432\u043e|\u0437\u0430|\u043d\u0430|\u043f\u043e|\u043a\u0442\u043e|\u0447\u0442\u043e|\u043a\u0430\u043a(?:\u043e\u0439|\u0430\u044f|\u0438\u0435)?|\u0433\u043b\u0430\u0432\u043d\p{L}*)\b/iu.test(comparable)) {
return null;
}
return normalized;
}
function hasMonthlyAggregationSignal(text) {
return /(?:\u043f\u043e\s+\u043c\u0435\u0441\u044f\u0446\u0430\u043c|\u043f\u043e\u043c\u0435\u0441\u044f\u0447\u043d\u043e|\u0435\u0436\u0435\u043c\u0435\u0441\u044f\u0447\u043d\u043e|month\s+by\s+month|by\s+month|monthly)/iu.test(text);
}
@@ -1030,7 +1072,10 @@ function metadataScopeHintFromRawText(text) {
return null;
}
function hasExplicitDateScopeLiteral(text) {
return /(?:\b(?:19|20)\d{2}\b|\b\d{4}-\d{2}-\d{2}\b|\b\d{4}-\d{2}\b)/iu.test(text);
if (/\b\d{2}\s*(?:\u0433(?:\u043e\u0434(?:\u0430|\u0443)?)?\.?)\b/iu.test(text)) {
return true;
}
return /(?:\b(?:19|20)\d{2}\b|\b\d{4}-\d{2}-\d{2}\b|\b\d{4}-\d{2}\b|\b\d{2}\s*(?:г(?:од(?:а|у)?)?\.?))\b/iu.test(text);
}
function stripNegatedTaxDateScopeClauses(text) {
const dateScopeLiteral = String.raw `\b(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})\b`;
@@ -1054,6 +1099,20 @@ function collectDateScopeFromRawText(text) {
if (year?.[1]) {
return year[1];
}
const utf8ShortYear = text.match(/\b(\d{2})\s*(?:\u0433(?:\u043e\u0434(?:\u0430|\u0443)?)?\.?)\b/iu);
if (utf8ShortYear?.[1]) {
const numericYear = Number.parseInt(utf8ShortYear[1], 10);
if (Number.isFinite(numericYear)) {
return String(numericYear <= 30 ? 2000 + numericYear : 1900 + numericYear);
}
}
const shortYear = text.match(/\b(\d{2})\s*(?:г(?:од(?:а|у)?)?\.?)\b/iu);
if (shortYear?.[1]) {
const numericYear = Number.parseInt(shortYear[1], 10);
if (Number.isFinite(numericYear)) {
return String(numericYear <= 30 ? 2000 + numericYear : 1900 + numericYear);
}
}
return null;
}
function currentIsoDate() {
@@ -1137,6 +1196,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
const reasonCodes = [];
const rawUserText = toNonEmptyString(input.userMessage);
const rawEffectiveText = toNonEmptyString(input.effectiveMessage);
const knownOrganizations = Array.isArray(input.knownOrganizations) ? input.knownOrganizations : [];
const repairedUserText = rawUserText ? (0, addressTextRepair_1.repairAddressMojibakeText)(rawUserText) : null;
const repairedEffectiveText = rawEffectiveText ? (0, addressTextRepair_1.repairAddressMojibakeText)(rawEffectiveText) : null;
const rawUserSignalSourceText = repairedUserText ?? rawUserText ?? "";
@@ -1246,9 +1306,28 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
const assistantTurnMeaningOrganizationScope = isReferentialOrganizationPlaceholder(rawAssistantTurnMeaningOrganizationScope)
? null
: rawAssistantTurnMeaningOrganizationScope;
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
const currentTurnFreshOrganizationScope = predecomposeEntities.organization ?? rawOrganizationScope;
const organizationSelectionFromKnown = (0, assistantOrganizationMatcher_1.resolveOrganizationSelectionFromMessage)(rawUserText ?? rawEffectiveText ?? rawSignalSourceText, knownOrganizations) ??
(0, assistantOrganizationMatcher_1.resolveOrganizationSelectionFromMessage)(rawSignalSourceText, knownOrganizations) ??
null;
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText) ??
extractOrganizationScopeFromRawText(rawEffectiveText) ??
extractOrganizationScopeFromRawText(rawSignalSourceText);
const semanticOrganizationScope = normalizeLooseOrganizationAlias(extractOrganizationScopeFromSemanticText(rawEffectiveText) ??
extractOrganizationScopeFromSemanticText(rawUserText) ??
extractOrganizationScopeFromSemanticText(rawSignalSourceText));
const predecomposeCounterpartyAsOrganizationScope = (rawBusinessOverviewSignal || seededBusinessOverviewSignal) &&
semanticOrganizationScope &&
!rawScopedEntityCandidate &&
!predecomposeEntities.organization
? normalizeLooseOrganizationAlias(predecomposeEntities.counterparty)
: null;
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText) ||
Boolean(organizationSelectionFromKnown || rawOrganizationScope || semanticOrganizationScope || predecomposeCounterpartyAsOrganizationScope);
const currentTurnFreshOrganizationScope = organizationSelectionFromKnown ??
predecomposeEntities.organization ??
rawOrganizationScope ??
semanticOrganizationScope ??
predecomposeCounterpartyAsOrganizationScope;
const currentTurnOrganizationScope = currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
const predecomposeOrganizationMirrorsCounterparty = sameScopedName(predecomposeEntities.counterparty, predecomposeEntities.organization);
const organizationMirrorsPredecomposeCounterpartyForPivot = Boolean(sameScopedName(predecomposeEntities.counterparty, assistantTurnMeaningOrganizationScope) ||
@@ -1805,10 +1884,14 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
}
}
const clarificationLoopStillNeedsPeriod = Boolean(followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopPendingAxes.includes("period"));
const rawTwoDigitPredecomposeYearHint = Boolean(predecomposeDateScope &&
/^\d{4}$/.test(predecomposeDateScope) &&
/\b\d{2}\b/u.test(rawText));
const businessOverviewRawWithoutDateScope = Boolean(businessOverviewSignal &&
!rawAllTimeScopeSignal &&
!explicitDateScopeLiteralDetected &&
!rawDateScope &&
!rawTwoDigitPredecomposeYearHint &&
!relativeCurrentDateHintDetected);
const predecomposeDateScopeCountsAsCurrentTurnPeriod = Boolean(predecomposeDateScope &&
!isImplicitCurrentDateScope(predecomposeDateScope) &&
@@ -2236,6 +2319,15 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
if (businessOverviewRawYearOverridesPredecomposeAsOf) {
pushReason(reasonCodes, "mcp_discovery_business_overview_raw_year_overrode_predecompose_as_of_scope");
}
if (organizationSelectionFromKnown) {
pushReason(reasonCodes, "mcp_discovery_organization_scope_from_known_organizations");
}
if (semanticOrganizationScope) {
pushReason(reasonCodes, "mcp_discovery_organization_scope_from_semantic_text");
}
if (predecomposeCounterpartyAsOrganizationScope) {
pushReason(reasonCodes, "mcp_discovery_counterparty_reinterpreted_as_organization_scope");
}
if (!(valueFlowOrganizationStaysScope && normalizedPredecomposeCounterparty === explicitOrganizationScope) &&
normalizedPredecomposeCounterparty) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
@@ -489,8 +489,24 @@ function createAssistantTransitionPolicy(deps) {
llmPreDecomposeMeta
})
: null;
const hasCompactCashflowFollowupCue = (value) => {
const normalized = normalizeFollowupText(value);
if (!normalized) {
return false;
}
const hasCompactCue = /(?:\u043a\u043e\u0440\u043e\u0442\p{L}*|\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u0431\u0435\u0437\s+\u0442\u043e\u043f\p{L}*)/iu.test(normalized);
const hasCashflowCue = /(?:\u0434\u0435\u043d\p{L}*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u043e\u043b\u0443\u0447|\u0437\u0430\u043f\u043b\u0430\u0442|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043d\u0435\u0442\u0442\u043e)/iu.test(normalized);
return hasCompactCue && hasCashflowCue;
};
const compactCashflowFollowupSignal = hasShortValueFlowRetargetCue(userMessage) ||
hasCompactCashflowFollowupCue(userMessage) ||
(deps.toNonEmptyString(alternateMessage)
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
: false);
if (assistantTurnMeaning?.stale_replay_forbidden === true &&
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage)) {
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage) &&
!compactCashflowFollowupSignal) {
return null;
}
const latestAddressItem = deps.findLastAddressAssistantItem(items);
@@ -555,7 +571,8 @@ function createAssistantTransitionPolicy(deps) {
const hasValueFlowCarryoverSourceHint = sourceIntentHint === "customer_revenue_and_payments" ||
sourceDiscoveryPilotScopeHint === "counterparty_value_flow_query_movements_v1" ||
sourceDiscoveryPilotScopeHint === "counterparty_supplier_payout_query_movements_v1" ||
sourceDiscoveryPilotScopeHint === "counterparty_bidirectional_value_flow_query_movements_v1";
sourceDiscoveryPilotScopeHint === "counterparty_bidirectional_value_flow_query_movements_v1" ||
sourceDiscoveryPilotScopeHint === "business_overview_route_template_v1";
const hasBusinessOverviewCarryoverSourceHint = sourceDiscoveryPilotScopeHint === "business_overview_route_template_v1";
const navigationSessionState = (0, assistantContinuityPolicy_1.resolveNavigationSessionContextState)(addressNavigationState, deps.toNonEmptyString, deps.normalizeOrganizationScopeValue);
const navigationFocusObjectHint = navigationSessionState.focusObject;
@@ -579,9 +596,11 @@ function createAssistantTransitionPolicy(deps) {
? deps.resolveDebtRoleSwapFollowupIntent(String(alternateMessage ?? ""), sourceIntentHint)
: null;
const debtRoleSwapIntent = debtRoleSwapPrimary ?? debtRoleSwapAlternate ?? null;
const shortValueFlowRetargetPrimary = hasValueFlowCarryoverSourceHint && hasShortValueFlowRetargetCue(userMessage);
const shortValueFlowRetargetPrimary = hasValueFlowCarryoverSourceHint &&
(hasShortValueFlowRetargetCue(userMessage) || hasCompactCashflowFollowupCue(userMessage));
const shortValueFlowRetargetAlternate = hasValueFlowCarryoverSourceHint && deps.toNonEmptyString(alternateMessage)
? hasShortValueFlowRetargetCue(String(alternateMessage ?? ""))
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
: false;
const businessOverviewBoundaryFollowupPrimary = hasBusinessOverviewCarryoverSourceHint && hasBusinessOverviewBoundaryFollowupCue(userMessage);
const businessOverviewBoundaryFollowupAlternate = hasBusinessOverviewCarryoverSourceHint && deps.toNonEmptyString(alternateMessage)