Стабилизировать маршрутизацию deep/address и chat-boundary

This commit is contained in:
dctouch 2026-05-24 12:07:07 +03:00
parent 801982b66b
commit 08615aaa7c
18 changed files with 272 additions and 57 deletions

View File

@ -1952,6 +1952,10 @@ function resolveUnicodeAddressIntentBridge(text) {
const hasTaxPeriodCue = /(?:налогов|налоговую|бюджет|декларац|квартал|\b[1-4]\s*кв)/iu.test(normalized); const hasTaxPeriodCue = /(?:налогов|налоговую|бюджет|декларац|квартал|\b[1-4]\s*кв)/iu.test(normalized);
const hasVatMonthPeriodCue = /(?:за|на|в)\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?/iu.test(normalized) && const hasVatMonthPeriodCue = /(?:за|на|в)\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?/iu.test(normalized) &&
!/\b\d{1,2}\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?/iu.test(normalized); !/\b\d{1,2}\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?/iu.test(normalized);
if ((hasTaxPeriodCue || hasVatMonthPeriodCue) &&
/(?:сгруз|какой\s+ндс\s+(?:мы\s+)?должн|ндс\s+необходимо)/iu.test(normalized)) {
return unicodeBridgeResolution("vat_liability_confirmed_for_tax_period", "high", "vat_liability_colloquial_bridge_signal_detected");
}
if ((hasTaxPeriodCue || (hasVatMonthPeriodCue && !hasVatDebtCue)) && if ((hasTaxPeriodCue || (hasVatMonthPeriodCue && !hasVatDebtCue)) &&
/(?:скольк|скока|надо|нужно|заплат|уплат|оплат|прикин)/iu.test(normalized)) { /(?:скольк|скока|надо|нужно|заплат|уплат|оплат|прикин)/iu.test(normalized)) {
return unicodeBridgeResolution("vat_liability_confirmed_for_tax_period", "high", "vat_liability_confirmed_tax_period_signal_detected"); return unicodeBridgeResolution("vat_liability_confirmed_for_tax_period", "high", "vat_liability_confirmed_tax_period_signal_detected");

View File

@ -3886,7 +3886,6 @@ function composeFactualReplyBody(intent, rows, options = {}) {
(String(right.period ?? "").localeCompare(String(left.period ?? ""), "ru"))) (String(right.period ?? "").localeCompare(String(left.period ?? ""), "ru")))
.slice(0, Math.min(rows.length, 5)); .slice(0, Math.min(rows.length, 5));
const semanticSummary = summarizeBankOperationSemantics(rows); const semanticSummary = summarizeBankOperationSemantics(rows);
const compactRoleAnswer = /(?:клиент|поставщик|финансов|роль|коротко)/iu.test(String(options.userMessage ?? ""));
const compactEvidenceRows = visibleRows.map((row, index) => { const compactEvidenceRows = visibleRows.map((row, index) => {
const direction = bankOperationDirectionLabel(bankOperationDirection(row)); const direction = bankOperationDirectionLabel(bankOperationDirection(row));
const amount = formatMoneyRub(row.amount ?? 0); const amount = formatMoneyRub(row.amount ?? 0);
@ -3904,11 +3903,9 @@ function composeFactualReplyBody(intent, rows, options = {}) {
roleBoundary ?? "Показываю подтвержденные банковские операции из текущего среза.", roleBoundary ?? "Показываю подтвержденные банковские операции из текущего среза.",
...(semanticSummary ? [semanticSummary] : []) ...(semanticSummary ? [semanticSummary] : [])
]; ];
if (!compactRoleAnswer) {
lines.push(bankOperationEvidenceLine(rows, preferredBankEvidenceDirection(options.userMessage)), "Примеры строк 1С:", ...compactEvidenceRows); lines.push(bankOperationEvidenceLine(rows, preferredBankEvidenceDirection(options.userMessage)), "Примеры строк 1С:", ...compactEvidenceRows);
}
lines.push("Следующий шаг: могу отдельно разложить назначения платежа, договоры или отделить банковский контур от клиентского/поставщицкого."); lines.push("Следующий шаг: могу отдельно разложить назначения платежа, договоры или отделить банковский контур от клиентского/поставщицкого.");
if (!compactRoleAnswer && rows.length > visibleRows.length) { if (rows.length > visibleRows.length) {
lines.push(`Показаны первые ${visibleRows.length} из ${rows.length}; полный список остается в подтвержденном срезе.`); lines.push(`Показаны первые ${visibleRows.length} из ${rows.length}; полный список остается в подтвержденном срезе.`);
} }
return { return {

View File

@ -42,11 +42,17 @@ async function tryHandleAssistantLivingChatRuntime(input) {
if (!runtime.handled || !runtime.debug) { if (!runtime.handled || !runtime.debug) {
return null; return null;
} }
const runtimeReplySource = typeof runtime.debug.living_chat_response_source === "string"
? runtime.debug.living_chat_response_source
: null;
const replyType = runtimeReplySource === "deterministic_unsupported_current_turn_boundary"
? "clarification_required"
: "factual_with_explanation";
const finalization = finalizeLivingChatTurnSafe({ const finalization = finalizeLivingChatTurnSafe({
sessionId: input.sessionId, sessionId: input.sessionId,
userMessage: input.userMessage, userMessage: input.userMessage,
assistantReply: runtime.chatText, assistantReply: runtime.chatText,
replyType: "factual_with_explanation", replyType,
debug: runtime.debug, debug: runtime.debug,
modeDecision: input.modeDecision, modeDecision: input.modeDecision,
appendItem: input.appendItem, appendItem: input.appendItem,

View File

@ -377,6 +377,8 @@ async function runAssistantLivingChatRuntime(input) {
address_llm_predecompose_contract: predecomposeContract, address_llm_predecompose_contract: predecomposeContract,
address_semantic_extraction_contract: semanticExtractionContract, address_semantic_extraction_contract: semanticExtractionContract,
orchestration_contract_v1: addressRuntimeMeta.orchestrationContract ?? null, orchestration_contract_v1: addressRuntimeMeta.orchestrationContract ?? null,
address_tool_gate_decision: addressRuntimeMeta.toolGateDecision ?? null,
address_tool_gate_reason: addressRuntimeMeta.toolGateReason ?? null,
tool_gate_decision: addressRuntimeMeta.toolGateDecision ?? null, tool_gate_decision: addressRuntimeMeta.toolGateDecision ?? null,
tool_gate_reason: addressRuntimeMeta.toolGateReason ?? null, tool_gate_reason: addressRuntimeMeta.toolGateReason ?? null,
normalized: null, normalized: null,

View File

@ -350,6 +350,29 @@ function hasExactBankOperationsAddressReply(input, entryPoint) {
routeMode === "exact" || routeMode === "exact" ||
hasFullConfirmedTruth(input)); hasFullConfirmedTruth(input));
} }
function hasExactDocumentListAddressReply(input, entryPoint) {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
if (!hasEffectivelyFactualAddressReply(input)) {
return false;
}
const source = String(input.currentReplySource ?? input.livingChatSource ?? "").trim().toLowerCase();
if (source !== "address_query_runtime_v1" && source !== "address_exact" && source !== "address_lane") {
return false;
}
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
const isDocumentIntent = detectedIntent === "list_documents_by_counterparty" || detectedIntent === "list_documents_by_contract";
const isDocumentRecipe = selectedRecipe === "address_documents_by_counterparty_v1" ||
selectedRecipe === "address_documents_by_contract_v1";
if (!isDocumentIntent || !isDocumentRecipe) {
return false;
}
const mcpCallStatus = toNonEmptyString(input.addressRuntimeMeta?.mcp_call_status);
const responseType = toNonEmptyString(input.addressRuntimeMeta?.response_type);
return Boolean(mcpCallStatus === "matched_non_empty" || responseType === "FACTUAL_LIST" || hasFullConfirmedTruth(input));
}
function hasInventoryMarginRankingAddressReply(input, entryPoint) { function hasInventoryMarginRankingAddressReply(input, entryPoint) {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) { if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false; return false;
@ -643,6 +666,7 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
const staleMetadataDiscoveryFallbackAgainstExactAddressReply = hasStaleMetadataDiscoveryFallbackAgainstExactAddressReply(input, entryPoint); const staleMetadataDiscoveryFallbackAgainstExactAddressReply = hasStaleMetadataDiscoveryFallbackAgainstExactAddressReply(input, entryPoint);
const exactValueFlowReplyForBusinessOverviewDirectMoneyNeed = hasExactValueFlowReplyForBusinessOverviewDirectMoneyNeed(input, entryPoint); const exactValueFlowReplyForBusinessOverviewDirectMoneyNeed = hasExactValueFlowReplyForBusinessOverviewDirectMoneyNeed(input, entryPoint);
const exactBankOperationsAddressReply = hasExactBankOperationsAddressReply(input, entryPoint); const exactBankOperationsAddressReply = hasExactBankOperationsAddressReply(input, entryPoint);
const exactDocumentListAddressReply = hasExactDocumentListAddressReply(input, entryPoint);
const inventoryMarginRankingAddressReply = hasInventoryMarginRankingAddressReply(input, entryPoint); const inventoryMarginRankingAddressReply = hasInventoryMarginRankingAddressReply(input, entryPoint);
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint); const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint); const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
@ -711,6 +735,9 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
if (exactBankOperationsProtectsCurrent) { if (exactBankOperationsProtectsCurrent) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_bank_operations_address_reply"); pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_bank_operations_address_reply");
} }
if (exactDocumentListAddressReply) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_document_list_address_reply");
}
if (inventoryMarginRankingAddressReply) { if (inventoryMarginRankingAddressReply) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_inventory_margin_ranking_address_reply"); pushReason(reasonCodes, "mcp_discovery_response_policy_keep_inventory_margin_ranking_address_reply");
} }
@ -744,6 +771,7 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
!staleMetadataDiscoveryFallbackAgainstExactAddressReply && !staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
!exactValueFlowReplyForBusinessOverviewDirectMoneyNeed && !exactValueFlowReplyForBusinessOverviewDirectMoneyNeed &&
!exactBankOperationsProtectsCurrent && !exactBankOperationsProtectsCurrent &&
!exactDocumentListAddressReply &&
!inventoryMarginRankingAddressReply && !inventoryMarginRankingAddressReply &&
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") && !(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) && ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&

View File

@ -48,6 +48,21 @@ const ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS = new Set([
function shouldBypassStrictDeepInvestigationCueForAddressIntent(intent) { function shouldBypassStrictDeepInvestigationCueForAddressIntent(intent) {
return Boolean(intent && ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS.has(intent)); return Boolean(intent && ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS.has(intent));
} }
function hasTerseUnsupportedLookupBoundarySignal(text) {
const normalized = String(text ?? "").toLowerCase().replace(/\s+/gu, " ").trim();
if (!normalized) {
return false;
}
if (!/\b(?:gib|give|list|lst)\b/iu.test(normalized)) {
return false;
}
const tokens = normalized.split(/[^\p{L}0-9._-]+/u).filter(Boolean);
if (tokens.length < 2 || tokens.length > 5) {
return false;
}
const commandTokens = new Set(["gib", "give", "list", "lst", "show", "find"]);
return tokens.some((token) => token.length >= 2 && !commandTokens.has(token));
}
function resolveAddressFallbackToDeepArbitration(input) { function resolveAddressFallbackToDeepArbitration(input) {
const { baseToolGateRunAddressLane, llmRuntimeUnavailableDetected, unsupportedIntentOrMode, strongDataSignal, rootContextOnlyFollowup, llmContractMode, strictDeepInvestigationCueDetected, semanticDeepInvestigationHintDetected, aggregateBusinessAnalyticsSignal, preserveAddressLaneSignal, supportedAddressRouteCandidateDetected, followupContext, followupSemanticOverrideToDeepAllowed, deepAnalysisPreferenceDetected, protectAddressLaneFromFallback, dataRetrievalSignal, vatExplainFollowupSignal, semanticAggregateShapeDetected, semanticApplyCanonicalRecommended, standaloneAddressTopicSignal, hasDeepSessionContinuationSignalDetected } = input; const { baseToolGateRunAddressLane, llmRuntimeUnavailableDetected, unsupportedIntentOrMode, strongDataSignal, rootContextOnlyFollowup, llmContractMode, strictDeepInvestigationCueDetected, semanticDeepInvestigationHintDetected, aggregateBusinessAnalyticsSignal, preserveAddressLaneSignal, supportedAddressRouteCandidateDetected, followupContext, followupSemanticOverrideToDeepAllowed, deepAnalysisPreferenceDetected, protectAddressLaneFromFallback, dataRetrievalSignal, vatExplainFollowupSignal, semanticAggregateShapeDetected, semanticApplyCanonicalRecommended, standaloneAddressTopicSignal, hasDeepSessionContinuationSignalDetected } = input;
const unsupportedAddressIntentFallbackToDeep = Boolean(baseToolGateRunAddressLane && const unsupportedAddressIntentFallbackToDeep = Boolean(baseToolGateRunAddressLane &&
@ -79,11 +94,10 @@ function resolveAddressFallbackToDeepArbitration(input) {
semanticAggregateShapeDetected || semanticAggregateShapeDetected ||
!semanticApplyCanonicalRecommended || !semanticApplyCanonicalRecommended ||
standaloneAddressTopicSignal)); standaloneAddressTopicSignal));
const deepSessionContinuationFallbackToDeep = Boolean(!followupContext && const deepSessionContinuationFallbackToDeep = Boolean(baseToolGateRunAddressLane &&
baseToolGateRunAddressLane &&
!llmRuntimeUnavailableDetected && !llmRuntimeUnavailableDetected &&
!protectAddressLaneFromFallback && hasDeepSessionContinuationSignalDetected &&
hasDeepSessionContinuationSignalDetected); (!protectAddressLaneFromFallback || deepAnalysisPreferenceDetected || semanticDeepInvestigationHintDetected));
return { return {
unsupportedAddressIntentFallbackToDeep, unsupportedAddressIntentFallbackToDeep,
deepAnalysisSignalFallbackToDeep, deepAnalysisSignalFallbackToDeep,
@ -444,6 +458,7 @@ function createAssistantRoutePolicy(deps) {
} }
: baseResolvedIntentResolution; : baseResolvedIntentResolution;
const llmPreDecomposeReason = toNonEmptyString(llmPreDecomposeMeta?.reason); const llmPreDecomposeReason = toNonEmptyString(llmPreDecomposeMeta?.reason);
const diagnosticRewriteRejected = llmPreDecomposeReason === "normalized_fragment_rejected_diagnostic_rewrite";
const providerExecution = resolveProviderExecutionState({ const providerExecution = resolveProviderExecutionState({
useMock, useMock,
llmPreDecomposeReason llmPreDecomposeReason
@ -590,11 +605,13 @@ function createAssistantRoutePolicy(deps) {
].includes(String(baseToolGate?.reason ?? ""))) || ].includes(String(baseToolGate?.reason ?? ""))) ||
Boolean(baseToolGate?.runAddressLane && Boolean(baseToolGate?.runAddressLane &&
String(baseToolGate?.reason ?? "") === "followup_context_detected" && String(baseToolGate?.reason ?? "") === "followup_context_detected" &&
effectiveGroundedValueFlowFollowupContextDetected); effectiveGroundedValueFlowFollowupContextDetected) ||
diagnosticRewriteRejected;
const nonDomainQueryIndexed = Boolean(!llmFirstAddressCandidate && const nonDomainQueryIndexed = Boolean(!llmFirstAddressCandidate &&
deterministicNonDomainGuard && deterministicNonDomainGuard &&
(llmFirstUnsupportedCandidate || llmContractMode === null) && (llmFirstUnsupportedCandidate || llmContractMode === null) &&
!baseToolGatePreservesAddressLane && !baseToolGatePreservesAddressLane &&
!strictDeepInvestigationCueDetected &&
!effectiveGroundedValueFlowFollowupContextDetected && !effectiveGroundedValueFlowFollowupContextDetected &&
!protectedInventoryShortFollowup && !protectedInventoryShortFollowup &&
!protectedInventoryMarginRankingFollowup && !protectedInventoryMarginRankingFollowup &&
@ -644,6 +661,13 @@ function createAssistantRoutePolicy(deps) {
!dangerOrCoercionSignal && !dangerOrCoercionSignal &&
!effectiveGroundedValueFlowFollowupContextDetected && !effectiveGroundedValueFlowFollowupContextDetected &&
!organizationClarificationContinuationDetected); !organizationClarificationContinuationDetected);
const nonDomainUnsupportedBoundary = Boolean(assistantTurnMeaning?.unsupported_but_understood_family &&
assistantTurnMeaning?.stale_replay_forbidden === true &&
!turnMeaningIntentCandidate) ||
hasTerseUnsupportedLookupBoundarySignal(rawUserMessage) ||
hasTerseUnsupportedLookupBoundarySignal(repairedRawUserMessage) ||
hasTerseUnsupportedLookupBoundarySignal(effectiveAddressUserMessage) ||
hasTerseUnsupportedLookupBoundarySignal(repairedEffectiveAddressUserMessage);
const hardMetaMode = resolveHardMetaMode({ const hardMetaMode = resolveHardMetaMode({
dataScopeMetaQuery, dataScopeMetaQuery,
capabilityMetaQuery, capabilityMetaQuery,
@ -1044,11 +1068,14 @@ function createAssistantRoutePolicy(deps) {
toolGateDecision: "skip_address_lane", toolGateDecision: "skip_address_lane",
toolGateReason: "non_domain_query_indexed", toolGateReason: "non_domain_query_indexed",
livingMode: "chat", livingMode: "chat",
livingReason: "non_domain_query_indexed", livingReason: nonDomainUnsupportedBoundary
? "unsupported_current_turn_meaning_boundary"
: "non_domain_query_indexed",
orchestrationContract: { orchestrationContract: {
schema_version: "assistant_orchestration_contract_v1", schema_version: "assistant_orchestration_contract_v1",
hard_meta_mode: "non_domain", hard_meta_mode: "non_domain",
provider_execution: providerExecution, provider_execution: providerExecution,
assistant_turn_meaning: assistantTurnMeaning,
address_mode: resolvedModeDetection.mode, address_mode: resolvedModeDetection.mode,
address_mode_confidence: resolvedModeDetection.confidence, address_mode_confidence: resolvedModeDetection.confidence,
address_intent: resolvedIntentResolution.intent, address_intent: resolvedIntentResolution.intent,
@ -1056,13 +1083,19 @@ function createAssistantRoutePolicy(deps) {
strong_data_signal_detected: strongDataSignal, strong_data_signal_detected: strongDataSignal,
data_retrieval_signal_detected: dataRetrievalSignal, data_retrieval_signal_detected: dataRetrievalSignal,
followup_context_detected: Boolean(followupContext), followup_context_detected: Boolean(followupContext),
unsupported_current_turn_meaning_boundary: nonDomainUnsupportedBoundary,
unsupported_current_turn_family: nonDomainUnsupportedBoundary
? assistantTurnMeaning?.unsupported_but_understood_family ?? "terse_unsupported_lookup"
: null,
unsupported_address_intent_fallback_to_deep: false, unsupported_address_intent_fallback_to_deep: false,
final_decision: { final_decision: {
run_address_lane: false, run_address_lane: false,
tool_gate_decision: "skip_address_lane", tool_gate_decision: "skip_address_lane",
tool_gate_reason: "non_domain_query_indexed", tool_gate_reason: "non_domain_query_indexed",
living_mode: "chat", living_mode: "chat",
living_reason: "non_domain_query_indexed" living_reason: nonDomainUnsupportedBoundary
? "unsupported_current_turn_meaning_boundary"
: "non_domain_query_indexed"
} }
} }
}; };
@ -1146,7 +1179,8 @@ function createAssistantRoutePolicy(deps) {
const exactAddressIntentProtectedFromSemanticDeepHint = laneProtectionArbitration.exactAddressIntentProtectedFromSemanticDeepHint; const exactAddressIntentProtectedFromSemanticDeepHint = laneProtectionArbitration.exactAddressIntentProtectedFromSemanticDeepHint;
const protectAddressLaneFromFallback = Boolean(laneProtectionArbitration.protectAddressLaneFromFallback || const protectAddressLaneFromFallback = Boolean(laneProtectionArbitration.protectAddressLaneFromFallback ||
customerValueRankingAddressSignal || customerValueRankingAddressSignal ||
protectedInventoryMarginRankingFollowup); protectedInventoryMarginRankingFollowup ||
diagnosticRewriteRejected);
const vatExplainFollowupSignal = Boolean(followupContext && const vatExplainFollowupSignal = Boolean(followupContext &&
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" && toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
/(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`))); /(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
@ -1226,7 +1260,7 @@ function createAssistantRoutePolicy(deps) {
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane"); let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0"); let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
const semanticAddressLaneRecovery = Boolean(!runAddressLane && const semanticAddressLaneRecovery = Boolean(!runAddressLane &&
(supportedAddressRouteCandidateDetected || protectedInventoryMarginRankingFollowup) && (supportedAddressRouteCandidateDetected || protectedInventoryMarginRankingFollowup || diagnosticRewriteRejected) &&
!deepAnalysisPreferenceDetected && !deepAnalysisPreferenceDetected &&
!unsupportedAddressIntentFallbackToDeep && !unsupportedAddressIntentFallbackToDeep &&
!deepAnalysisSignalFallbackToDeep && !deepAnalysisSignalFallbackToDeep &&

View File

@ -1605,6 +1605,7 @@ const ADDRESS_CONTRACT_SIGNAL_PATTERN = /(?:договор(?:а|у|ом|е)?|(?:
const ADDRESS_BALANCE_SIGNAL_PATTERN = /(?:остат|сальдо|баланс|взаиморасч|долг|saldo|balance)/i; const ADDRESS_BALANCE_SIGNAL_PATTERN = /(?:остат|сальдо|баланс|взаиморасч|долг|saldo|balance)/i;
const ADDRESS_ALL_TIME_PATTERN = /(?:за\s+вс[её]\s+время|за\s+весь\s+период|за\s+всю\s+истори(?:ю|и)|for\s+all\s+time|all\s+time|entire\s+period|full\s+history)/iu; const ADDRESS_ALL_TIME_PATTERN = /(?:за\s+вс[её]\s+время|за\s+весь\s+период|за\s+всю\s+истори(?:ю|и)|for\s+all\s+time|all\s+time|entire\s+period|full\s+history)/iu;
const ADDRESS_MANAGEMENT_PROFILE_PATTERN = /(?:за\s+какие\s+год[а-яё]*|сам(?:ый|ая|ое)\s+(?:актив|пассив)|наименее\s+актив|минимальн|покрыт(?:ие|ия)\s+период|диапазон\s+лет|тип[аы]\s+док(?:умент|ов|и)?|раздел[ыа]\s+уч[её]та|по\s+количеств[аоуе]|редк|реже|(?:сколько|скока|скок)\s+(?:всего\s+)?(?:уникальн(?:ых|ые|ого)?\s+)?контрагент(?:ов|а)?|(?:сколько|скока|скок)\s+(?:у\s+нас\s+)?(?:заказчик(?:ов|а)?|поставщик(?:ов|а)?|клиент(?:ов|а)?|покупател(?:ей|я)|смешан(?:ных|ые)\s+контрагент(?:ов|а)?)|(?:покажи|выведи|список|какие|кто).*(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?).*(?:за\s+вс[её]\s+время|all\s+time|(?:^|[^\d])(19|20)\d{2}(?:[^\d]|$)|(?:^|[^\d])\d{2}\s*(?:г(?:од|ода)?|г)(?:[^\p{L}\p{N}]|$)|за\s+год|в\s+году)|(?:какие|кто|покажи|выведи|список).*(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?).*(?:работал(?:и)?|активн(?:ые|ых|а|о)?).*(?:за\s+вс[её]\s+время|(?:19|20)\d{2}|за\s+год|в\s+году)|договорн(?:ая|ой)\s+баз[аы]|total\s+vs\s+used)/iu; const ADDRESS_MANAGEMENT_PROFILE_PATTERN = /(?:за\s+какие\s+год[а-яё]*|сам(?:ый|ая|ое)\s+(?:актив|пассив)|наименее\s+актив|минимальн|покрыт(?:ие|ия)\s+период|диапазон\s+лет|тип[аы]\s+док(?:умент|ов|и)?|раздел[ыа]\s+уч[её]та|по\s+количеств[аоуе]|редк|реже|(?:сколько|скока|скок)\s+(?:всего\s+)?(?:уникальн(?:ых|ые|ого)?\s+)?контрагент(?:ов|а)?|(?:сколько|скока|скок)\s+(?:у\s+нас\s+)?(?:заказчик(?:ов|а)?|поставщик(?:ов|а)?|клиент(?:ов|а)?|покупател(?:ей|я)|смешан(?:ных|ые)\s+контрагент(?:ов|а)?)|(?:покажи|выведи|список|какие|кто).*(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?).*(?:за\s+вс[её]\s+время|all\s+time|(?:^|[^\d])(19|20)\d{2}(?:[^\d]|$)|(?:^|[^\d])\d{2}\s*(?:г(?:од|ода)?|г)(?:[^\p{L}\p{N}]|$)|за\s+год|в\s+году)|(?:какие|кто|покажи|выведи|список).*(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?).*(?:работал(?:и)?|активн(?:ые|ых|а|о)?).*(?:за\s+вс[её]\s+время|(?:19|20)\d{2}|за\s+год|в\s+году)|договорн(?:ая|ой)\s+баз[аы]|total\s+vs\s+used)/iu;
const ADDRESS_DEEP_ANALYSIS_FALLBACK_BLOCK_PATTERN = /(?:\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\p{L}*\s+\u043a\u0430\u0440\u0442\u0438\u043d|\u0442\u043e\u043f\s+\u0440\u0438\u0441\u043a|\u0447\u0442\u043e\s+\u043d\u0435\s+\u0442\u0430\u043a|\u043e\u0441\u043d\u043e\u0432\p{L}*\s+\u0441\u0440\u0435\u0434\u0441\u0442)/iu;
function normalizeAddressMonthAliasToken(token) { function normalizeAddressMonthAliasToken(token) {
const source = String(token ?? "").trim().toLowerCase(); const source = String(token ?? "").trim().toLowerCase();
if (!source) { if (!source) {
@ -1814,6 +1815,9 @@ function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage)
if (ADDRESS_MANAGEMENT_PROFILE_PATTERN.test(source)) { if (ADDRESS_MANAGEMENT_PROFILE_PATTERN.test(source)) {
return null; return null;
} }
if (ADDRESS_DEEP_ANALYSIS_FALLBACK_BLOCK_PATTERN.test(source)) {
return null;
}
const monthYear = extractAddressFallbackMonthYear(source); const monthYear = extractAddressFallbackMonthYear(source);
const year = extractAddressFallbackYear(source); const year = extractAddressFallbackYear(source);
const allTime = ADDRESS_ALL_TIME_PATTERN.test(source); const allTime = ADDRESS_ALL_TIME_PATTERN.test(source);
@ -3319,6 +3323,13 @@ function hasPredecomposeDiagnosticUncertaintyLead(text) {
} }
return /^(?:неясно|не\s+ясно|непонятно|не\s+понятно|unclear|not\s+clear|ambiguous|unknown)(?=$|[\s,.;:!?])/iu.test(normalized); return /^(?:неясно|не\s+ясно|непонятно|не\s+понятно|unclear|not\s+clear|ambiguous|unknown)(?=$|[\s,.;:!?])/iu.test(normalized);
} }
function hasPredecomposeDebtSnapshotIntentSignal(text) {
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()).replace(/\u0451/gu, "\u0435");
if (!normalized) {
return false;
}
return /(?:\u043a\u0442\u043e\s+\u043d\u0430\u043c(?:\s+\p{L}+){0,4}\s+\u0434\u043e\u043b\u0436|\u043d\u0430\u043c\s+(?:\u043a\u0442\u043e(?:-\u0442\u043e)?\s+)?\u0434\u043e\u043b\u0436|\u0434\u0435\u0431\u0438\u0442\u043e\u0440|receivables?)/iu.test(normalized);
}
function attachAddressPredecomposeContract(meta, sourceMessage) { function attachAddressPredecomposeContract(meta, sourceMessage) {
const sourceMeta = meta && typeof meta === "object" ? meta : {}; const sourceMeta = meta && typeof meta === "object" ? meta : {};
const { providerExecutionInput, providerExecutionContract: providerExecutionContractInput, ...restMeta } = sourceMeta; const { providerExecutionInput, providerExecutionContract: providerExecutionContractInput, ...restMeta } = sourceMeta;
@ -3439,9 +3450,11 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
const repairedSourceMessage = repairAddressMojibake(userMessage); const repairedSourceMessage = repairAddressMojibake(userMessage);
const sourceIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(repairedSourceMessage || userMessage); const sourceIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(repairedSourceMessage || userMessage);
const candidateIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(candidate); const candidateIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(candidate);
const sourceIntentKnown = sourceIntentResolution.intent !== "unknown"; const sourceIntentKnown = sourceIntentResolution.intent !== "unknown" ||
hasPredecomposeDebtSnapshotIntentSignal(repairedSourceMessage || userMessage);
const candidateIntentKnown = candidateIntentResolution.intent !== "unknown"; const candidateIntentKnown = candidateIntentResolution.intent !== "unknown";
const candidateStartsWithDiagnosticUncertainty = hasPredecomposeDiagnosticUncertaintyLead(candidate); const candidateStartsWithDiagnosticUncertainty = hasPredecomposeDiagnosticUncertaintyLead(candidate) ||
/^(?:\u043d\u0435\u044f\u0441\u043d\u043e|\u043d\u0435\s+\u044f\u0441\u043d\u043e|\u043d\u0435\u043f\u043e\u043d\u044f\u0442\u043d\u043e|\u043d\u0435\s+\u043f\u043e\u043d\u044f\u0442\u043d\u043e)(?=$|[\s,.;:!?])/iu.test(compactWhitespace(repairAddressMojibake(String(candidate ?? "")).toLowerCase()));
if (candidateStartsWithDiagnosticUncertainty && sourceIntentKnown) { if (candidateStartsWithDiagnosticUncertainty && sourceIntentKnown) {
return attachAddressPredecomposeContract({ return attachAddressPredecomposeContract({
...baseMeta, ...baseMeta,
@ -3463,6 +3476,13 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
const rejectCandidateForIntentSafety = intentDroppedByCandidate || const rejectCandidateForIntentSafety = intentDroppedByCandidate ||
(intentConflict && (intentConflict &&
(sourceIntentResolution.confidence === "high" || candidateIntentResolution.confidence !== "high")); (sourceIntentResolution.confidence === "high" || candidateIntentResolution.confidence !== "high"));
const sourceAnchorQualityForIntentSafety = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
const candidateAnchorQualityForIntentSafety = evaluateAddressAnchorQuality(candidate);
const counterpartyAnchorSubstitutedDuringIntentDrop = sourceAnchorQualityForIntentSafety.anchorType === "counterparty" &&
sourceAnchorQualityForIntentSafety.quality >= 2 &&
Boolean(sourceAnchorQualityForIntentSafety.anchorValue) &&
candidateAnchorQualityForIntentSafety.quality < sourceAnchorQualityForIntentSafety.quality &&
hasCounterpartyAnchorSubstitution(sourceAnchorQualityForIntentSafety.anchorValue ?? "", candidate);
if (rejectCandidateForIntentSafety) { if (rejectCandidateForIntentSafety) {
return attachAddressPredecomposeContract({ return attachAddressPredecomposeContract({
...baseMeta, ...baseMeta,
@ -3471,7 +3491,9 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
traceId: normalized?.trace_id ?? null, traceId: normalized?.trace_id ?? null,
llmCanonicalCandidateDetected: true, llmCanonicalCandidateDetected: true,
effectiveMessage: userMessage, effectiveMessage: userMessage,
reason: intentDroppedByCandidate reason: counterpartyAnchorSubstitutedDuringIntentDrop
? "normalized_fragment_rejected_anchor_substitution"
: intentDroppedByCandidate
? "normalized_fragment_rejected_intent_drop" ? "normalized_fragment_rejected_intent_drop"
: "normalized_fragment_rejected_intent_conflict", : "normalized_fragment_rejected_intent_conflict",
fallbackRuleHit: null, fallbackRuleHit: null,
@ -3482,7 +3504,8 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
const sourceHasExplicitDrilldownSignal = hasPredecomposeExplicitDrilldownSignal(repairedSourceMessage || userMessage); const sourceHasExplicitDrilldownSignal = hasPredecomposeExplicitDrilldownSignal(repairedSourceMessage || userMessage);
const candidateHasExplicitDrilldownSignal = hasPredecomposeExplicitDrilldownSignal(candidate); const candidateHasExplicitDrilldownSignal = hasPredecomposeExplicitDrilldownSignal(candidate);
const sourceLooksLikeSameDateAccountFollowup = hasSameDateAccountFollowupSignalForPredecompose(repairedSourceMessage || userMessage); const sourceLooksLikeSameDateAccountFollowup = hasSameDateAccountFollowupSignalForPredecompose(repairedSourceMessage || userMessage);
const candidateInjectsDrilldownIntent = candidateIntentResolution.intent === "documents_forming_balance"; const candidateInjectsDrilldownIntent = candidateIntentResolution.intent === "documents_forming_balance" ||
(candidateHasExplicitDrilldownSignal && /(?:document|posting|docs?|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b|\u043f\u0440\u043e\u0432\u043e\u0434\u043a)/iu.test(candidate));
if (sourceLooksLikeSameDateAccountFollowup && if (sourceLooksLikeSameDateAccountFollowup &&
!sourceHasExplicitDrilldownSignal && !sourceHasExplicitDrilldownSignal &&
candidateHasExplicitDrilldownSignal && candidateHasExplicitDrilldownSignal &&
@ -3801,8 +3824,8 @@ function hasDeepSessionContinuationSignal(input) {
return candidateTexts.some((text) => { return candidateTexts.some((text) => {
const hasContinuationCue = /^(?:\u0438|\u0430|\u0442\u0430\u043a\u0436\u0435|\u0435\u0449[\u0435\u0451]|\u0434\u043e\u0431\u0430\u0432\u044c|\u0434\u043e\u043f\u043e\u043b\u043d\u0438|\u0443\u0442\u043e\u0447\u043d\u0438|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438|\u0442\u0435\u043f\u0435\u0440\u044c|then|also|and)\b/iu.test(text) || const hasContinuationCue = /^(?:\u0438|\u0430|\u0442\u0430\u043a\u0436\u0435|\u0435\u0449[\u0435\u0451]|\u0434\u043e\u0431\u0430\u0432\u044c|\u0434\u043e\u043f\u043e\u043b\u043d\u0438|\u0443\u0442\u043e\u0447\u043d\u0438|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438|\u0442\u0435\u043f\u0435\u0440\u044c|then|also|and)\b/iu.test(text) ||
/(?:\u043f\u043e\s+\u0442\u043e\u043c\u0443\s+\u0436\u0435|\u043f\u043e\s+\u044d\u0442\u043e\u043c\u0443|\u0432\s+\u044d\u0442\u043e\u043c\s+\u0436\u0435|\u0438\s+\u043f\u043e\s+\u043f\u0435\u0440\u0438\u043e\u0434\u0443|\u0434\u043e\u0431\u0430\u0432\u044c\s+\u0443\u0442\u043e\u0447\u043d\u0435\u043d\u0438\u0435|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u043c|\u0430\s+\u0435\u0441\u043b\u0438|\u0435\u0441\u043b\u0438\s+\u0442\u043e\u043b\u044c\u043a\u043e|\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e)/iu.test(text); /(?:\u043f\u043e\s+\u0442\u043e\u043c\u0443\s+\u0436\u0435|\u043f\u043e\s+\u044d\u0442\u043e\u043c\u0443|\u0432\s+\u044d\u0442\u043e\u043c\s+\u0436\u0435|\u0438\s+\u043f\u043e\s+\u043f\u0435\u0440\u0438\u043e\u0434\u0443|\u0434\u043e\u0431\u0430\u0432\u044c\s+\u0443\u0442\u043e\u0447\u043d\u0435\u043d\u0438\u0435|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u043c|\u0430\s+\u0435\u0441\u043b\u0438|\u0435\u0441\u043b\u0438\s+\u0442\u043e\u043b\u044c\u043a\u043e|\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e)/iu.test(text);
const hasAccountOrPeriodCue = /(?:\u0441\u0447[\u0435\u0451]\u0442|account|\b\d{2}(?:[.,]\d{1,2})?\b|\b20\d{2}(?:[-/.]\d{1,2})?\b|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446)/iu.test(text); const hasAccountOrPeriodCue = /(?:\u0441\u0447[\u0435\u0451]\u0442|account|\b\d{2}(?:[.,]\d{1,2})?\b|\b20\d{2}(?:[-/.]\d{1,2})?\b|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|contract)/iu.test(text);
const hasDeepRebindCue = /(?:\u0430\u043c\u043e\u0440\u0442\u0438\u0437|fixed\s*asset|\u043e\u0441\b|\u043d\u0434\u0441|vat|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447\u043a|\u0430\u043d\u043e\u043c\u0430\u043b|lifecycle|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447)/iu.test(text); const hasDeepRebindCue = /(?:\u0430\u043c\u043e\u0440\u0442\u0438\u0437|fixed\s*asset|\u043e\u0441\b|\u043d\u0434\u0441|vat|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447\u043a|\u0430\u043d\u043e\u043c\u0430\u043b|lifecycle|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0437\u0430\u043a\u0440\u044b\u0442|\u0445\u0432\u043e\u0441\u0442|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|contract)/iu.test(text);
if (hasContinuationCue && (hasAccountOrPeriodCue || hasDeepRebindCue)) { if (hasContinuationCue && (hasAccountOrPeriodCue || hasDeepRebindCue)) {
return true; return true;
} }
@ -3822,6 +3845,7 @@ function hasDeepAnalysisPreferenceSignal(text) {
if (openContractsListQuestionSignal) { if (openContractsListQuestionSignal) {
return false; return false;
} }
const broadOverviewRiskSignal = /(?:\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\p{L}*\s+\u043a\u0430\u0440\u0442\u0438\u043d|\u0442\u043e\u043f\s+\u0440\u0438\u0441\u043a|\u0447\u0442\u043e\s+\u043d\u0435\s+\u0442\u0430\u043a)/iu.test(lower);
const riskOrAnomalySignal = /(?:\u0440\u0438\u0441\u043a|risk|\u0430\u043d\u043e\u043c\u0430\u043b|anomal|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442|conflict|deviation|\u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d|\u043d\u0435\u0441\u044b\u043a\u043e\u0432\u043a|\u043d\u0435\u0441\u0445\u043e\u0434|\u043e\u0448\u0438\u0431|error|issue|\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower); const riskOrAnomalySignal = /(?:\u0440\u0438\u0441\u043a|risk|\u0430\u043d\u043e\u043c\u0430\u043b|anomal|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442|conflict|deviation|\u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d|\u043d\u0435\u0441\u044b\u043a\u043e\u0432\u043a|\u043d\u0435\u0441\u0445\u043e\u0434|\u043e\u0448\u0438\u0431|error|issue|\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower);
const chainSignal = /(?:\u0446\u0435\u043f\u043e\u0447\u043a|chain|trace\s*chain|lifecycle|\u0436\u0438\u0437\u043d\u0435\u043d\u043d[\u0430-\u044f]+\s+\u0446\u0438\u043a\u043b|state\s+transition|\u0440\u0430\u0437\u0440\u044b\u0432[\u0430-\u044f]*)/iu.test(lower); const chainSignal = /(?:\u0446\u0435\u043f\u043e\u0447\u043a|chain|trace\s*chain|lifecycle|\u0436\u0438\u0437\u043d\u0435\u043d\u043d[\u0430-\u044f]+\s+\u0446\u0438\u043a\u043b|state\s+transition|\u0440\u0430\u0437\u0440\u044b\u0432[\u0430-\u044f]*)/iu.test(lower);
const diagnosticsKeywordSignal = /(?:\u0440\u0430\u0437\u043b\u043e\u0436\u0438|\u0434\u0435\u043a\u043e\u043c\u043f\u043e\u0437|\u0440\u0430\u0437\u0431\u0435\u0440\u0438|\u043f\u043e\u0447\u0435\u043c\u0443|why|audit|scan|\u043a\u043e\u0440\u043d\u0435\u0432[\u0430-\u044f]+\s+\u043f\u0440\u0438\u0447\u0438\u043d|root\s*cause|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c[\u0430-\u044f]*|\u0433\u0434\u0435\s+\u0440\u0430\u0437\u0440\u044b\u0432|\u0447\u0442\u043e\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower); const diagnosticsKeywordSignal = /(?:\u0440\u0430\u0437\u043b\u043e\u0436\u0438|\u0434\u0435\u043a\u043e\u043c\u043f\u043e\u0437|\u0440\u0430\u0437\u0431\u0435\u0440\u0438|\u043f\u043e\u0447\u0435\u043c\u0443|why|audit|scan|\u043a\u043e\u0440\u043d\u0435\u0432[\u0430-\u044f]+\s+\u043f\u0440\u0438\u0447\u0438\u043d|root\s*cause|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c[\u0430-\u044f]*|\u0433\u0434\u0435\s+\u0440\u0430\u0437\u0440\u044b\u0432|\u0447\u0442\u043e\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower);
@ -3829,17 +3853,20 @@ function hasDeepAnalysisPreferenceSignal(text) {
const diagnosticsSignal = diagnosticsKeywordSignal || diagnosticsCheckVerbSignal; const diagnosticsSignal = diagnosticsKeywordSignal || diagnosticsCheckVerbSignal;
const closureSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]\s+\u043f\u0435\u0440\u0438\u043e\u0434|period\s*close|\u043d\u0435\s+\u0437\u0430\u043a\u0440\u044b\u043b[\u0430-\u044f]*|\u0445\u0432\u043e\u0441\u0442[\u0430-\u044f]*)/iu.test(lower); const closureSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]\s+\u043f\u0435\u0440\u0438\u043e\u0434|period\s*close|\u043d\u0435\s+\u0437\u0430\u043a\u0440\u044b\u043b[\u0430-\u044f]*|\u0445\u0432\u043e\u0441\u0442[\u0430-\u044f]*)/iu.test(lower);
const closureIntentSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|period\s*close|close\s+period)/iu.test(lower); const closureIntentSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|period\s*close|close\s+period)/iu.test(lower);
const closureStateQuestionSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|\u0445\u0432\u043e\u0441\u0442[\u0430-\u044f]*)[^\n]{0,80}(?:\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434[\u0430-\u044f]*|\u0436\u0438\u0432[\u0430-\u044f]*|\u043e\u0441\u0442\u0430[\u043b-\u044f]*|\u0435\u0441\u0442\u044c)|(?:\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434[\u0430-\u044f]*|\u0436\u0438\u0432[\u0430-\u044f]*|\u043e\u0441\u0442\u0430[\u043b-\u044f]*|\u0435\u0441\u0442\u044c)[^\n]{0,80}(?:\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|\u0445\u0432\u043e\u0441\u0442[\u0430-\u044f]*)/iu.test(lower);
const closureDiagnosticPhraseSignal = /(?:\u0447\u0442\u043e(?:\s+\S+){0,8}\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower); const closureDiagnosticPhraseSignal = /(?:\u0447\u0442\u043e(?:\s+\S+){0,8}\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower);
const signalVsNoiseDiagnostic = /(?:\u043d\u0435\s+\u043f\u0440\u043e\u0441\u0442\u043e\s+(?:\u043d\u0430\s+)?\u0448\u0443\u043c|\u043f\u043e\u0445\u043e\u0436[\u0438\u0435]\s+(?:\u0438\u043c\u0435\u043d\u043d\u043e\s+)?\u043d\u0430\s+\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower); const signalVsNoiseDiagnostic = /(?:\u043d\u0435\s+\u043f\u0440\u043e\u0441\u0442\u043e\s+(?:\u043d\u0430\s+)?\u0448\u0443\u043c|\u043f\u043e\u0445\u043e\u0436[\u0438\u0435]\s+(?:\u0438\u043c\u0435\u043d\u043d\u043e\s+)?\u043d\u0430\s+\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower);
const lifecycleMismatchSignal = /(?:\u043d\u0435\s+\u0442\u0435\u043c\s+\u0442\u0438\u043f(?:\u043e\u043c)?\s+\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|wrong\s+closing\s+document|expected\s+transition)/iu.test(lower); const lifecycleMismatchSignal = /(?:\u043d\u0435\s+\u0442\u0435\u043c\s+\u0442\u0438\u043f(?:\u043e\u043c)?\s+\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|wrong\s+closing\s+document|expected\s+transition)/iu.test(lower);
const lifecycleTransitionGapSignal = /(?:\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u0441\u0442\u0430\u0434\u0438[\u0438\u044f\u0435]\s+.*\u043f\u0440\u043e\u0439\u0434\u0435\u043d.*\u043f\u0435\u0440\u0435\u0445\u043e\u0434)/iu.test(lower); const lifecycleTransitionGapSignal = /(?:\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u0441\u0442\u0430\u0434\u0438[\u0438\u044f\u0435]\s+.*\u043f\u0440\u043e\u0439\u0434\u0435\u043d.*\u043f\u0435\u0440\u0435\u0445\u043e\u0434)/iu.test(lower);
const expectedActualMismatchSignal = /(?:\u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a[\u0430-\u044f]+\s+\u0441\u043e\u0441\u0442\u043e\u044f\u043d[\u0438\u0435\u044f]+\s+.*\u0440\u0430\u0441\u0445\u043e\u0434[\u0430-\u044f]*\s+\u0441\s+\u043e\u0436\u0438\u0434\u0430\u0435\u043c|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d[\u0430-\u044f]*\s+\u0441\u043f\u0438\u0441\u0430\u043d)/iu.test(lower); const expectedActualMismatchSignal = /(?:\u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a[\u0430-\u044f]+\s+\u0441\u043e\u0441\u0442\u043e\u044f\u043d[\u0438\u0435\u044f]+\s+.*\u0440\u0430\u0441\u0445\u043e\u0434[\u0430-\u044f]*\s+\u0441\s+\u043e\u0436\u0438\u0434\u0430\u0435\u043c|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d[\u0430-\u044f]*\s+\u0441\u043f\u0438\u0441\u0430\u043d)/iu.test(lower);
return lifecycleMismatchSignal || return broadOverviewRiskSignal ||
lifecycleMismatchSignal ||
(chainSignal && lifecycleTransitionGapSignal) || (chainSignal && lifecycleTransitionGapSignal) ||
expectedActualMismatchSignal || expectedActualMismatchSignal ||
(chainSignal && diagnosticsSignal) || (chainSignal && diagnosticsSignal) ||
(riskOrAnomalySignal && (chainSignal || diagnosticsSignal || lifecycleTransitionGapSignal)) || (riskOrAnomalySignal && (chainSignal || diagnosticsSignal || lifecycleTransitionGapSignal)) ||
(diagnosticsSignal && (closureSignal || closureIntentSignal)) || (diagnosticsSignal && (closureSignal || closureIntentSignal)) ||
closureStateQuestionSignal ||
closureDiagnosticPhraseSignal || closureDiagnosticPhraseSignal ||
signalVsNoiseDiagnostic; signalVsNoiseDiagnostic;
} }
@ -3867,7 +3894,7 @@ function hasStrictDeepInvestigationCue(text) {
if (!hasInvestigativeVerb) { if (!hasInvestigativeVerb) {
return false; return false;
} }
return /(?:\u0445\u0432\u043e\u0441\u0442|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447|\u0430\u043d\u043e\u043c\u0430\u043b|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]|\u043e\u0431\u044a\u0435\u043a\u0442(?:\u0443)?\s+\u0440\u0430\u0441\u0447(?:\u0435|\u0451)\u0442|lifecycle|state\s+transition)/iu.test(normalized); return /(?:\u0445\u0432\u043e\u0441\u0442|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447|\u0430\u043d\u043e\u043c\u0430\u043b|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]|\u043e\u0431\u044a\u0435\u043a\u0442(?:\u0443)?\s+\u0440\u0430\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0441\u043d\u043e\u0432\p{L}*\s+\u0441\u0440\u0435\u0434\u0441\u0442|fixed\s*asset|\b\u043e\u0441\b|lifecycle|state\s+transition)/iu.test(normalized);
} }
function hasAggregateBusinessAnalyticsSignal(text) { function hasAggregateBusinessAnalyticsSignal(text) {
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()); const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());

View File

@ -67,7 +67,7 @@ function detectSupportedIntent(text, deps) {
reason: "address_intent_resolver_current_turn_signal" reason: "address_intent_resolver_current_turn_signal"
}; };
} }
if (/(?:\u043a\u0442\u043e\s+\u043d\u0430\u043c\s+\u0434\u043e\u043b\u0436|\u043d\u0430\u043c\s+\u043a\u0442\u043e\s+\u0434\u043e\u043b\u0436|\u0434\u0435\u0431\u0438\u0442\u043e\u0440|\u0434\u0435\u0431\u0438\u0442\u043e\u0440\u0441\u043a|\breceivables?\b)/iu.test(text)) { if (/(?:\u043a\u0442\u043e\s+\u043d\u0430\u043c(?:\s+\p{L}+){0,4}\s+\u0434\u043e\u043b\u0436|\u043d\u0430\u043c\s+\u043a\u0442\u043e(?:\s+\p{L}+){0,4}\s+\u0434\u043e\u043b\u0436|\u0434\u0435\u0431\u0438\u0442\u043e\u0440|\u0434\u0435\u0431\u0438\u0442\u043e\u0440\u0441\u043a|\breceivables?\b)/iu.test(text)) {
return { return {
intent: "receivables_confirmed_as_of_date", intent: "receivables_confirmed_as_of_date",
confidence: "high", confidence: "high",

View File

@ -2811,6 +2811,16 @@ function resolveUnicodeAddressIntentBridge(text: string): AddressIntentResolutio
!/\b\d{1,2}\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?/iu.test( !/\b\d{1,2}\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?/iu.test(
normalized normalized
); );
if (
(hasTaxPeriodCue || hasVatMonthPeriodCue) &&
/(?:сгруз|какой\s+ндс\s+(?:мы\s+)?должн|ндс\s+необходимо)/iu.test(normalized)
) {
return unicodeBridgeResolution(
"vat_liability_confirmed_for_tax_period",
"high",
"vat_liability_colloquial_bridge_signal_detected"
);
}
if ( if (
(hasTaxPeriodCue || (hasVatMonthPeriodCue && !hasVatDebtCue)) && (hasTaxPeriodCue || (hasVatMonthPeriodCue && !hasVatDebtCue)) &&
/(?:скольк|скока|надо|нужно|заплат|уплат|оплат|прикин)/iu.test(normalized) /(?:скольк|скока|надо|нужно|заплат|уплат|оплат|прикин)/iu.test(normalized)

View File

@ -4940,7 +4940,6 @@ function composeFactualReplyBody(
) )
.slice(0, Math.min(rows.length, 5)); .slice(0, Math.min(rows.length, 5));
const semanticSummary = summarizeBankOperationSemantics(rows); const semanticSummary = summarizeBankOperationSemantics(rows);
const compactRoleAnswer = /(?:клиент|поставщик|финансов|роль|коротко)/iu.test(String(options.userMessage ?? ""));
const compactEvidenceRows = visibleRows.map((row, index) => { const compactEvidenceRows = visibleRows.map((row, index) => {
const direction = bankOperationDirectionLabel(bankOperationDirection(row)); const direction = bankOperationDirectionLabel(bankOperationDirection(row));
const amount = formatMoneyRub(row.amount ?? 0); const amount = formatMoneyRub(row.amount ?? 0);
@ -4958,15 +4957,13 @@ function composeFactualReplyBody(
roleBoundary ?? "Показываю подтвержденные банковские операции из текущего среза.", roleBoundary ?? "Показываю подтвержденные банковские операции из текущего среза.",
...(semanticSummary ? [semanticSummary] : []) ...(semanticSummary ? [semanticSummary] : [])
]; ];
if (!compactRoleAnswer) {
lines.push( lines.push(
bankOperationEvidenceLine(rows, preferredBankEvidenceDirection(options.userMessage)), bankOperationEvidenceLine(rows, preferredBankEvidenceDirection(options.userMessage)),
"Примеры строк 1С:", "Примеры строк 1С:",
...compactEvidenceRows ...compactEvidenceRows
); );
}
lines.push("Следующий шаг: могу отдельно разложить назначения платежа, договоры или отделить банковский контур от клиентского/поставщицкого."); lines.push("Следующий шаг: могу отдельно разложить назначения платежа, договоры или отделить банковский контур от клиентского/поставщицкого.");
if (!compactRoleAnswer && rows.length > visibleRows.length) { if (rows.length > visibleRows.length) {
lines.push(`Показаны первые ${visibleRows.length} из ${rows.length}; полный список остается в подтвержденном срезе.`); lines.push(`Показаны первые ${visibleRows.length} из ${rows.length}; полный список остается в подтвержденном срезе.`);
} }
return { return {

View File

@ -98,11 +98,19 @@ export async function tryHandleAssistantLivingChatRuntime<ResponseType = unknown
if (!runtime.handled || !runtime.debug) { if (!runtime.handled || !runtime.debug) {
return null; return null;
} }
const runtimeReplySource =
typeof runtime.debug.living_chat_response_source === "string"
? runtime.debug.living_chat_response_source
: null;
const replyType =
runtimeReplySource === "deterministic_unsupported_current_turn_boundary"
? "clarification_required"
: "factual_with_explanation";
const finalization = finalizeLivingChatTurnSafe({ const finalization = finalizeLivingChatTurnSafe({
sessionId: input.sessionId, sessionId: input.sessionId,
userMessage: input.userMessage, userMessage: input.userMessage,
assistantReply: runtime.chatText, assistantReply: runtime.chatText,
replyType: "factual_with_explanation", replyType,
debug: runtime.debug, debug: runtime.debug,
modeDecision: input.modeDecision, modeDecision: input.modeDecision,
appendItem: input.appendItem, appendItem: input.appendItem,

View File

@ -487,6 +487,8 @@ export async function runAssistantLivingChatRuntime(
address_llm_predecompose_contract: predecomposeContract, address_llm_predecompose_contract: predecomposeContract,
address_semantic_extraction_contract: semanticExtractionContract, address_semantic_extraction_contract: semanticExtractionContract,
orchestration_contract_v1: addressRuntimeMeta.orchestrationContract ?? null, orchestration_contract_v1: addressRuntimeMeta.orchestrationContract ?? null,
address_tool_gate_decision: addressRuntimeMeta.toolGateDecision ?? null,
address_tool_gate_reason: addressRuntimeMeta.toolGateReason ?? null,
tool_gate_decision: addressRuntimeMeta.toolGateDecision ?? null, tool_gate_decision: addressRuntimeMeta.toolGateDecision ?? null,
tool_gate_reason: addressRuntimeMeta.toolGateReason ?? null, tool_gate_reason: addressRuntimeMeta.toolGateReason ?? null,
normalized: null, normalized: null,

View File

@ -496,6 +496,35 @@ function hasExactBankOperationsAddressReply(
); );
} }
function hasExactDocumentListAddressReply(
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
): boolean {
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
if (!hasEffectivelyFactualAddressReply(input)) {
return false;
}
const source = String(input.currentReplySource ?? input.livingChatSource ?? "").trim().toLowerCase();
if (source !== "address_query_runtime_v1" && source !== "address_exact" && source !== "address_lane") {
return false;
}
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
const isDocumentIntent =
detectedIntent === "list_documents_by_counterparty" || detectedIntent === "list_documents_by_contract";
const isDocumentRecipe =
selectedRecipe === "address_documents_by_counterparty_v1" ||
selectedRecipe === "address_documents_by_contract_v1";
if (!isDocumentIntent || !isDocumentRecipe) {
return false;
}
const mcpCallStatus = toNonEmptyString(input.addressRuntimeMeta?.mcp_call_status);
const responseType = toNonEmptyString(input.addressRuntimeMeta?.response_type);
return Boolean(mcpCallStatus === "matched_non_empty" || responseType === "FACTUAL_LIST" || hasFullConfirmedTruth(input));
}
function hasInventoryMarginRankingAddressReply( function hasInventoryMarginRankingAddressReply(
input: ApplyAssistantMcpDiscoveryResponsePolicyInput, input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
@ -863,6 +892,7 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
entryPoint entryPoint
); );
const exactBankOperationsAddressReply = hasExactBankOperationsAddressReply(input, entryPoint); const exactBankOperationsAddressReply = hasExactBankOperationsAddressReply(input, entryPoint);
const exactDocumentListAddressReply = hasExactDocumentListAddressReply(input, entryPoint);
const inventoryMarginRankingAddressReply = hasInventoryMarginRankingAddressReply(input, entryPoint); const inventoryMarginRankingAddressReply = hasInventoryMarginRankingAddressReply(input, entryPoint);
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint); const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint); const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
@ -951,6 +981,9 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
if (exactBankOperationsProtectsCurrent) { if (exactBankOperationsProtectsCurrent) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_bank_operations_address_reply"); pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_bank_operations_address_reply");
} }
if (exactDocumentListAddressReply) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_document_list_address_reply");
}
if (inventoryMarginRankingAddressReply) { if (inventoryMarginRankingAddressReply) {
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_inventory_margin_ranking_address_reply"); pushReason(reasonCodes, "mcp_discovery_response_policy_keep_inventory_margin_ranking_address_reply");
} }
@ -989,6 +1022,7 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
!staleMetadataDiscoveryFallbackAgainstExactAddressReply && !staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
!exactValueFlowReplyForBusinessOverviewDirectMoneyNeed && !exactValueFlowReplyForBusinessOverviewDirectMoneyNeed &&
!exactBankOperationsProtectsCurrent && !exactBankOperationsProtectsCurrent &&
!exactDocumentListAddressReply &&
!inventoryMarginRankingAddressReply && !inventoryMarginRankingAddressReply &&
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") && !(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) && ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&

View File

@ -50,6 +50,22 @@ function shouldBypassStrictDeepInvestigationCueForAddressIntent(intent) {
return Boolean(intent && ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS.has(intent)); return Boolean(intent && ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS.has(intent));
} }
function hasTerseUnsupportedLookupBoundarySignal(text) {
const normalized = String(text ?? "").toLowerCase().replace(/\s+/gu, " ").trim();
if (!normalized) {
return false;
}
if (!/\b(?:gib|give|list|lst)\b/iu.test(normalized)) {
return false;
}
const tokens = normalized.split(/[^\p{L}0-9._-]+/u).filter(Boolean);
if (tokens.length < 2 || tokens.length > 5) {
return false;
}
const commandTokens = new Set(["gib", "give", "list", "lst", "show", "find"]);
return tokens.some((token) => token.length >= 2 && !commandTokens.has(token));
}
function resolveAddressFallbackToDeepArbitration(input) { function resolveAddressFallbackToDeepArbitration(input) {
const { const {
baseToolGateRunAddressLane, baseToolGateRunAddressLane,
@ -103,11 +119,10 @@ function resolveAddressFallbackToDeepArbitration(input) {
semanticAggregateShapeDetected || semanticAggregateShapeDetected ||
!semanticApplyCanonicalRecommended || !semanticApplyCanonicalRecommended ||
standaloneAddressTopicSignal)); standaloneAddressTopicSignal));
const deepSessionContinuationFallbackToDeep = Boolean(!followupContext && const deepSessionContinuationFallbackToDeep = Boolean(baseToolGateRunAddressLane &&
baseToolGateRunAddressLane &&
!llmRuntimeUnavailableDetected && !llmRuntimeUnavailableDetected &&
!protectAddressLaneFromFallback && hasDeepSessionContinuationSignalDetected &&
hasDeepSessionContinuationSignalDetected); (!protectAddressLaneFromFallback || deepAnalysisPreferenceDetected || semanticDeepInvestigationHintDetected));
return { return {
unsupportedAddressIntentFallbackToDeep, unsupportedAddressIntentFallbackToDeep,
deepAnalysisSignalFallbackToDeep, deepAnalysisSignalFallbackToDeep,
@ -531,6 +546,7 @@ export function createAssistantRoutePolicy(deps) {
} }
: baseResolvedIntentResolution; : baseResolvedIntentResolution;
const llmPreDecomposeReason = toNonEmptyString(llmPreDecomposeMeta?.reason); const llmPreDecomposeReason = toNonEmptyString(llmPreDecomposeMeta?.reason);
const diagnosticRewriteRejected = llmPreDecomposeReason === "normalized_fragment_rejected_diagnostic_rewrite";
const providerExecution = resolveProviderExecutionState({ const providerExecution = resolveProviderExecutionState({
useMock, useMock,
llmPreDecomposeReason llmPreDecomposeReason
@ -678,11 +694,13 @@ export function createAssistantRoutePolicy(deps) {
].includes(String(baseToolGate?.reason ?? ""))) || ].includes(String(baseToolGate?.reason ?? ""))) ||
Boolean(baseToolGate?.runAddressLane && Boolean(baseToolGate?.runAddressLane &&
String(baseToolGate?.reason ?? "") === "followup_context_detected" && String(baseToolGate?.reason ?? "") === "followup_context_detected" &&
effectiveGroundedValueFlowFollowupContextDetected); effectiveGroundedValueFlowFollowupContextDetected) ||
diagnosticRewriteRejected;
const nonDomainQueryIndexed = Boolean(!llmFirstAddressCandidate && const nonDomainQueryIndexed = Boolean(!llmFirstAddressCandidate &&
deterministicNonDomainGuard && deterministicNonDomainGuard &&
(llmFirstUnsupportedCandidate || llmContractMode === null) && (llmFirstUnsupportedCandidate || llmContractMode === null) &&
!baseToolGatePreservesAddressLane && !baseToolGatePreservesAddressLane &&
!strictDeepInvestigationCueDetected &&
!effectiveGroundedValueFlowFollowupContextDetected && !effectiveGroundedValueFlowFollowupContextDetected &&
!protectedInventoryShortFollowup && !protectedInventoryShortFollowup &&
!protectedInventoryMarginRankingFollowup && !protectedInventoryMarginRankingFollowup &&
@ -733,6 +751,14 @@ export function createAssistantRoutePolicy(deps) {
!dangerOrCoercionSignal && !dangerOrCoercionSignal &&
!effectiveGroundedValueFlowFollowupContextDetected && !effectiveGroundedValueFlowFollowupContextDetected &&
!organizationClarificationContinuationDetected); !organizationClarificationContinuationDetected);
const nonDomainUnsupportedBoundary = Boolean(
assistantTurnMeaning?.unsupported_but_understood_family &&
assistantTurnMeaning?.stale_replay_forbidden === true &&
!turnMeaningIntentCandidate) ||
hasTerseUnsupportedLookupBoundarySignal(rawUserMessage) ||
hasTerseUnsupportedLookupBoundarySignal(repairedRawUserMessage) ||
hasTerseUnsupportedLookupBoundarySignal(effectiveAddressUserMessage) ||
hasTerseUnsupportedLookupBoundarySignal(repairedEffectiveAddressUserMessage);
const hardMetaMode = resolveHardMetaMode({ const hardMetaMode = resolveHardMetaMode({
dataScopeMetaQuery, dataScopeMetaQuery,
capabilityMetaQuery, capabilityMetaQuery,
@ -1134,11 +1160,14 @@ export function createAssistantRoutePolicy(deps) {
toolGateDecision: "skip_address_lane", toolGateDecision: "skip_address_lane",
toolGateReason: "non_domain_query_indexed", toolGateReason: "non_domain_query_indexed",
livingMode: "chat", livingMode: "chat",
livingReason: "non_domain_query_indexed", livingReason: nonDomainUnsupportedBoundary
? "unsupported_current_turn_meaning_boundary"
: "non_domain_query_indexed",
orchestrationContract: { orchestrationContract: {
schema_version: "assistant_orchestration_contract_v1", schema_version: "assistant_orchestration_contract_v1",
hard_meta_mode: "non_domain", hard_meta_mode: "non_domain",
provider_execution: providerExecution, provider_execution: providerExecution,
assistant_turn_meaning: assistantTurnMeaning,
address_mode: resolvedModeDetection.mode, address_mode: resolvedModeDetection.mode,
address_mode_confidence: resolvedModeDetection.confidence, address_mode_confidence: resolvedModeDetection.confidence,
address_intent: resolvedIntentResolution.intent, address_intent: resolvedIntentResolution.intent,
@ -1146,13 +1175,19 @@ export function createAssistantRoutePolicy(deps) {
strong_data_signal_detected: strongDataSignal, strong_data_signal_detected: strongDataSignal,
data_retrieval_signal_detected: dataRetrievalSignal, data_retrieval_signal_detected: dataRetrievalSignal,
followup_context_detected: Boolean(followupContext), followup_context_detected: Boolean(followupContext),
unsupported_current_turn_meaning_boundary: nonDomainUnsupportedBoundary,
unsupported_current_turn_family: nonDomainUnsupportedBoundary
? assistantTurnMeaning?.unsupported_but_understood_family ?? "terse_unsupported_lookup"
: null,
unsupported_address_intent_fallback_to_deep: false, unsupported_address_intent_fallback_to_deep: false,
final_decision: { final_decision: {
run_address_lane: false, run_address_lane: false,
tool_gate_decision: "skip_address_lane", tool_gate_decision: "skip_address_lane",
tool_gate_reason: "non_domain_query_indexed", tool_gate_reason: "non_domain_query_indexed",
living_mode: "chat", living_mode: "chat",
living_reason: "non_domain_query_indexed" living_reason: nonDomainUnsupportedBoundary
? "unsupported_current_turn_meaning_boundary"
: "non_domain_query_indexed"
} }
} }
}; };
@ -1237,7 +1272,8 @@ export function createAssistantRoutePolicy(deps) {
const protectAddressLaneFromFallback = Boolean( const protectAddressLaneFromFallback = Boolean(
laneProtectionArbitration.protectAddressLaneFromFallback || laneProtectionArbitration.protectAddressLaneFromFallback ||
customerValueRankingAddressSignal || customerValueRankingAddressSignal ||
protectedInventoryMarginRankingFollowup protectedInventoryMarginRankingFollowup ||
diagnosticRewriteRejected
); );
const vatExplainFollowupSignal = Boolean(followupContext && const vatExplainFollowupSignal = Boolean(followupContext &&
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" && toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
@ -1318,7 +1354,7 @@ export function createAssistantRoutePolicy(deps) {
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane"); let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0"); let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
const semanticAddressLaneRecovery = Boolean(!runAddressLane && const semanticAddressLaneRecovery = Boolean(!runAddressLane &&
(supportedAddressRouteCandidateDetected || protectedInventoryMarginRankingFollowup) && (supportedAddressRouteCandidateDetected || protectedInventoryMarginRankingFollowup || diagnosticRewriteRejected) &&
!deepAnalysisPreferenceDetected && !deepAnalysisPreferenceDetected &&
!unsupportedAddressIntentFallbackToDeep && !unsupportedAddressIntentFallbackToDeep &&
!deepAnalysisSignalFallbackToDeep && !deepAnalysisSignalFallbackToDeep &&

View File

@ -1561,6 +1561,8 @@ const ADDRESS_BALANCE_SIGNAL_PATTERN = /(?:остат|сальдо|баланс|
const ADDRESS_ALL_TIME_PATTERN = /(?:за\s+вс[её]\s+время|за\s+весь\s+период|за\s+всю\s+истори(?:ю|и)|for\s+all\s+time|all\s+time|entire\s+period|full\s+history)/iu; const ADDRESS_ALL_TIME_PATTERN = /(?:за\s+вс[её]\s+время|за\s+весь\s+период|за\s+всю\s+истори(?:ю|и)|for\s+all\s+time|all\s+time|entire\s+period|full\s+history)/iu;
const ADDRESS_MANAGEMENT_PROFILE_PATTERN = const ADDRESS_MANAGEMENT_PROFILE_PATTERN =
/(?:за\s+какие\s+год[а-яё]*|сам(?:ый|ая|ое)\s+(?:актив|пассив)|наименее\s+актив|минимальн|покрыт(?:ие|ия)\s+период|диапазон\s+лет|тип[аы]\s+док(?:умент|ов|и)?|раздел[ыа]\s+уч[её]та|по\s+количеств[аоуе]|редк|реже|(?:сколько|скока|скок)\s+(?:всего\s+)?(?:уникальн(?:ых|ые|ого)?\s+)?контрагент(?:ов|а)?|(?:сколько|скока|скок)\s+(?:у\s+нас\s+)?(?:заказчик(?:ов|а)?|поставщик(?:ов|а)?|клиент(?:ов|а)?|покупател(?:ей|я)|смешан(?:ных|ые)\s+контрагент(?:ов|а)?)|(?:покажи|выведи|список|какие|кто).*(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?).*(?:за\s+вс[её]\s+время|all\s+time|(?:^|[^\d])(19|20)\d{2}(?:[^\d]|$)|(?:^|[^\d])\d{2}\s*(?:г(?:од|ода)?|г)(?:[^\p{L}\p{N}]|$)|за\s+год|в\s+году)|(?:какие|кто|покажи|выведи|список).*(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?).*(?:работал(?:и)?|активн(?:ые|ых|а|о)?).*(?:за\s+вс[её]\s+время|(?:19|20)\d{2}|за\s+год|в\s+году)|договорн(?:ая|ой)\s+баз[аы]|total\s+vs\s+used)/iu; /(?:за\s+какие\s+год[а-яё]*|сам(?:ый|ая|ое)\s+(?:актив|пассив)|наименее\s+актив|минимальн|покрыт(?:ие|ия)\s+период|диапазон\s+лет|тип[аы]\s+док(?:умент|ов|и)?|раздел[ыа]\s+уч[её]та|по\s+количеств[аоуе]|редк|реже|(?:сколько|скока|скок)\s+(?:всего\s+)?(?:уникальн(?:ых|ые|ого)?\s+)?контрагент(?:ов|а)?|(?:сколько|скока|скок)\s+(?:у\s+нас\s+)?(?:заказчик(?:ов|а)?|поставщик(?:ов|а)?|клиент(?:ов|а)?|покупател(?:ей|я)|смешан(?:ных|ые)\s+контрагент(?:ов|а)?)|(?:покажи|выведи|список|какие|кто).*(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?).*(?:за\s+вс[её]\s+время|all\s+time|(?:^|[^\d])(19|20)\d{2}(?:[^\d]|$)|(?:^|[^\d])\d{2}\s*(?:г(?:од|ода)?|г)(?:[^\p{L}\p{N}]|$)|за\s+год|в\s+году)|(?:какие|кто|покажи|выведи|список).*(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?).*(?:работал(?:и)?|активн(?:ые|ых|а|о)?).*(?:за\s+вс[её]\s+время|(?:19|20)\d{2}|за\s+год|в\s+году)|договорн(?:ая|ой)\s+баз[аы]|total\s+vs\s+used)/iu;
const ADDRESS_DEEP_ANALYSIS_FALLBACK_BLOCK_PATTERN =
/(?:\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\p{L}*\s+\u043a\u0430\u0440\u0442\u0438\u043d|\u0442\u043e\u043f\s+\u0440\u0438\u0441\u043a|\u0447\u0442\u043e\s+\u043d\u0435\s+\u0442\u0430\u043a|\u043e\u0441\u043d\u043e\u0432\p{L}*\s+\u0441\u0440\u0435\u0434\u0441\u0442)/iu;
function normalizeAddressMonthAliasToken(token) { function normalizeAddressMonthAliasToken(token) {
const source = String(token ?? "").trim().toLowerCase(); const source = String(token ?? "").trim().toLowerCase();
if (!source) { if (!source) {
@ -1770,6 +1772,9 @@ function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage)
if (ADDRESS_MANAGEMENT_PROFILE_PATTERN.test(source)) { if (ADDRESS_MANAGEMENT_PROFILE_PATTERN.test(source)) {
return null; return null;
} }
if (ADDRESS_DEEP_ANALYSIS_FALLBACK_BLOCK_PATTERN.test(source)) {
return null;
}
const monthYear = extractAddressFallbackMonthYear(source); const monthYear = extractAddressFallbackMonthYear(source);
const year = extractAddressFallbackYear(source); const year = extractAddressFallbackYear(source);
const allTime = ADDRESS_ALL_TIME_PATTERN.test(source); const allTime = ADDRESS_ALL_TIME_PATTERN.test(source);
@ -3275,6 +3280,13 @@ function hasPredecomposeDiagnosticUncertaintyLead(text) {
} }
return /^(?:неясно|не\s+ясно|непонятно|не\s+понятно|unclear|not\s+clear|ambiguous|unknown)(?=$|[\s,.;:!?])/iu.test(normalized); return /^(?:неясно|не\s+ясно|непонятно|не\s+понятно|unclear|not\s+clear|ambiguous|unknown)(?=$|[\s,.;:!?])/iu.test(normalized);
} }
function hasPredecomposeDebtSnapshotIntentSignal(text) {
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()).replace(/\u0451/gu, "\u0435");
if (!normalized) {
return false;
}
return /(?:\u043a\u0442\u043e\s+\u043d\u0430\u043c(?:\s+\p{L}+){0,4}\s+\u0434\u043e\u043b\u0436|\u043d\u0430\u043c\s+(?:\u043a\u0442\u043e(?:-\u0442\u043e)?\s+)?\u0434\u043e\u043b\u0436|\u0434\u0435\u0431\u0438\u0442\u043e\u0440|receivables?)/iu.test(normalized);
}
function attachAddressPredecomposeContract(meta, sourceMessage) { function attachAddressPredecomposeContract(meta, sourceMessage) {
const sourceMeta = meta && typeof meta === "object" ? meta : {}; const sourceMeta = meta && typeof meta === "object" ? meta : {};
const { providerExecutionInput, providerExecutionContract: providerExecutionContractInput, ...restMeta } = sourceMeta; const { providerExecutionInput, providerExecutionContract: providerExecutionContractInput, ...restMeta } = sourceMeta;
@ -3395,9 +3407,11 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
const repairedSourceMessage = repairAddressMojibake(userMessage); const repairedSourceMessage = repairAddressMojibake(userMessage);
const sourceIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(repairedSourceMessage || userMessage); const sourceIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(repairedSourceMessage || userMessage);
const candidateIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(candidate); const candidateIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(candidate);
const sourceIntentKnown = sourceIntentResolution.intent !== "unknown"; const sourceIntentKnown = sourceIntentResolution.intent !== "unknown" ||
hasPredecomposeDebtSnapshotIntentSignal(repairedSourceMessage || userMessage);
const candidateIntentKnown = candidateIntentResolution.intent !== "unknown"; const candidateIntentKnown = candidateIntentResolution.intent !== "unknown";
const candidateStartsWithDiagnosticUncertainty = hasPredecomposeDiagnosticUncertaintyLead(candidate); const candidateStartsWithDiagnosticUncertainty = hasPredecomposeDiagnosticUncertaintyLead(candidate) ||
/^(?:\u043d\u0435\u044f\u0441\u043d\u043e|\u043d\u0435\s+\u044f\u0441\u043d\u043e|\u043d\u0435\u043f\u043e\u043d\u044f\u0442\u043d\u043e|\u043d\u0435\s+\u043f\u043e\u043d\u044f\u0442\u043d\u043e)(?=$|[\s,.;:!?])/iu.test(compactWhitespace(repairAddressMojibake(String(candidate ?? "")).toLowerCase()));
if (candidateStartsWithDiagnosticUncertainty && sourceIntentKnown) { if (candidateStartsWithDiagnosticUncertainty && sourceIntentKnown) {
return attachAddressPredecomposeContract({ return attachAddressPredecomposeContract({
...baseMeta, ...baseMeta,
@ -3419,6 +3433,13 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
const rejectCandidateForIntentSafety = intentDroppedByCandidate || const rejectCandidateForIntentSafety = intentDroppedByCandidate ||
(intentConflict && (intentConflict &&
(sourceIntentResolution.confidence === "high" || candidateIntentResolution.confidence !== "high")); (sourceIntentResolution.confidence === "high" || candidateIntentResolution.confidence !== "high"));
const sourceAnchorQualityForIntentSafety = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
const candidateAnchorQualityForIntentSafety = evaluateAddressAnchorQuality(candidate);
const counterpartyAnchorSubstitutedDuringIntentDrop = sourceAnchorQualityForIntentSafety.anchorType === "counterparty" &&
sourceAnchorQualityForIntentSafety.quality >= 2 &&
Boolean(sourceAnchorQualityForIntentSafety.anchorValue) &&
candidateAnchorQualityForIntentSafety.quality < sourceAnchorQualityForIntentSafety.quality &&
hasCounterpartyAnchorSubstitution(sourceAnchorQualityForIntentSafety.anchorValue ?? "", candidate);
if (rejectCandidateForIntentSafety) { if (rejectCandidateForIntentSafety) {
return attachAddressPredecomposeContract({ return attachAddressPredecomposeContract({
...baseMeta, ...baseMeta,
@ -3427,7 +3448,9 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
traceId: normalized?.trace_id ?? null, traceId: normalized?.trace_id ?? null,
llmCanonicalCandidateDetected: true, llmCanonicalCandidateDetected: true,
effectiveMessage: userMessage, effectiveMessage: userMessage,
reason: intentDroppedByCandidate reason: counterpartyAnchorSubstitutedDuringIntentDrop
? "normalized_fragment_rejected_anchor_substitution"
: intentDroppedByCandidate
? "normalized_fragment_rejected_intent_drop" ? "normalized_fragment_rejected_intent_drop"
: "normalized_fragment_rejected_intent_conflict", : "normalized_fragment_rejected_intent_conflict",
fallbackRuleHit: null, fallbackRuleHit: null,
@ -3438,7 +3461,8 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
const sourceHasExplicitDrilldownSignal = hasPredecomposeExplicitDrilldownSignal(repairedSourceMessage || userMessage); const sourceHasExplicitDrilldownSignal = hasPredecomposeExplicitDrilldownSignal(repairedSourceMessage || userMessage);
const candidateHasExplicitDrilldownSignal = hasPredecomposeExplicitDrilldownSignal(candidate); const candidateHasExplicitDrilldownSignal = hasPredecomposeExplicitDrilldownSignal(candidate);
const sourceLooksLikeSameDateAccountFollowup = hasSameDateAccountFollowupSignalForPredecompose(repairedSourceMessage || userMessage); const sourceLooksLikeSameDateAccountFollowup = hasSameDateAccountFollowupSignalForPredecompose(repairedSourceMessage || userMessage);
const candidateInjectsDrilldownIntent = candidateIntentResolution.intent === "documents_forming_balance"; const candidateInjectsDrilldownIntent = candidateIntentResolution.intent === "documents_forming_balance" ||
(candidateHasExplicitDrilldownSignal && /(?:document|posting|docs?|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b|\u043f\u0440\u043e\u0432\u043e\u0434\u043a)/iu.test(candidate));
if (sourceLooksLikeSameDateAccountFollowup && if (sourceLooksLikeSameDateAccountFollowup &&
!sourceHasExplicitDrilldownSignal && !sourceHasExplicitDrilldownSignal &&
candidateHasExplicitDrilldownSignal && candidateHasExplicitDrilldownSignal &&
@ -3757,8 +3781,8 @@ function hasDeepSessionContinuationSignal(input) {
return candidateTexts.some((text) => { return candidateTexts.some((text) => {
const hasContinuationCue = /^(?:\u0438|\u0430|\u0442\u0430\u043a\u0436\u0435|\u0435\u0449[\u0435\u0451]|\u0434\u043e\u0431\u0430\u0432\u044c|\u0434\u043e\u043f\u043e\u043b\u043d\u0438|\u0443\u0442\u043e\u0447\u043d\u0438|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438|\u0442\u0435\u043f\u0435\u0440\u044c|then|also|and)\b/iu.test(text) || const hasContinuationCue = /^(?:\u0438|\u0430|\u0442\u0430\u043a\u0436\u0435|\u0435\u0449[\u0435\u0451]|\u0434\u043e\u0431\u0430\u0432\u044c|\u0434\u043e\u043f\u043e\u043b\u043d\u0438|\u0443\u0442\u043e\u0447\u043d\u0438|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438|\u0442\u0435\u043f\u0435\u0440\u044c|then|also|and)\b/iu.test(text) ||
/(?:\u043f\u043e\s+\u0442\u043e\u043c\u0443\s+\u0436\u0435|\u043f\u043e\s+\u044d\u0442\u043e\u043c\u0443|\u0432\s+\u044d\u0442\u043e\u043c\s+\u0436\u0435|\u0438\s+\u043f\u043e\s+\u043f\u0435\u0440\u0438\u043e\u0434\u0443|\u0434\u043e\u0431\u0430\u0432\u044c\s+\u0443\u0442\u043e\u0447\u043d\u0435\u043d\u0438\u0435|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u043c|\u0430\s+\u0435\u0441\u043b\u0438|\u0435\u0441\u043b\u0438\s+\u0442\u043e\u043b\u044c\u043a\u043e|\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e)/iu.test(text); /(?:\u043f\u043e\s+\u0442\u043e\u043c\u0443\s+\u0436\u0435|\u043f\u043e\s+\u044d\u0442\u043e\u043c\u0443|\u0432\s+\u044d\u0442\u043e\u043c\s+\u0436\u0435|\u0438\s+\u043f\u043e\s+\u043f\u0435\u0440\u0438\u043e\u0434\u0443|\u0434\u043e\u0431\u0430\u0432\u044c\s+\u0443\u0442\u043e\u0447\u043d\u0435\u043d\u0438\u0435|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u043c|\u0430\s+\u0435\u0441\u043b\u0438|\u0435\u0441\u043b\u0438\s+\u0442\u043e\u043b\u044c\u043a\u043e|\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e)/iu.test(text);
const hasAccountOrPeriodCue = /(?:\u0441\u0447[\u0435\u0451]\u0442|account|\b\d{2}(?:[.,]\d{1,2})?\b|\b20\d{2}(?:[-/.]\d{1,2})?\b|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446)/iu.test(text); const hasAccountOrPeriodCue = /(?:\u0441\u0447[\u0435\u0451]\u0442|account|\b\d{2}(?:[.,]\d{1,2})?\b|\b20\d{2}(?:[-/.]\d{1,2})?\b|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|contract)/iu.test(text);
const hasDeepRebindCue = /(?:\u0430\u043c\u043e\u0440\u0442\u0438\u0437|fixed\s*asset|\u043e\u0441\b|\u043d\u0434\u0441|vat|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447\u043a|\u0430\u043d\u043e\u043c\u0430\u043b|lifecycle|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447)/iu.test(text); const hasDeepRebindCue = /(?:\u0430\u043c\u043e\u0440\u0442\u0438\u0437|fixed\s*asset|\u043e\u0441\b|\u043d\u0434\u0441|vat|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447\u043a|\u0430\u043d\u043e\u043c\u0430\u043b|lifecycle|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0437\u0430\u043a\u0440\u044b\u0442|\u0445\u0432\u043e\u0441\u0442|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|contract)/iu.test(text);
if (hasContinuationCue && (hasAccountOrPeriodCue || hasDeepRebindCue)) { if (hasContinuationCue && (hasAccountOrPeriodCue || hasDeepRebindCue)) {
return true; return true;
} }
@ -3779,6 +3803,8 @@ function hasDeepAnalysisPreferenceSignal(text) {
if (openContractsListQuestionSignal) { if (openContractsListQuestionSignal) {
return false; return false;
} }
const broadOverviewRiskSignal =
/(?:\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\p{L}*\s+\u043a\u0430\u0440\u0442\u0438\u043d|\u0442\u043e\u043f\s+\u0440\u0438\u0441\u043a|\u0447\u0442\u043e\s+\u043d\u0435\s+\u0442\u0430\u043a)/iu.test(lower);
const riskOrAnomalySignal = /(?:\u0440\u0438\u0441\u043a|risk|\u0430\u043d\u043e\u043c\u0430\u043b|anomal|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442|conflict|deviation|\u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d|\u043d\u0435\u0441\u044b\u043a\u043e\u0432\u043a|\u043d\u0435\u0441\u0445\u043e\u0434|\u043e\u0448\u0438\u0431|error|issue|\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower); const riskOrAnomalySignal = /(?:\u0440\u0438\u0441\u043a|risk|\u0430\u043d\u043e\u043c\u0430\u043b|anomal|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442|conflict|deviation|\u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d|\u043d\u0435\u0441\u044b\u043a\u043e\u0432\u043a|\u043d\u0435\u0441\u0445\u043e\u0434|\u043e\u0448\u0438\u0431|error|issue|\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower);
const chainSignal = /(?:\u0446\u0435\u043f\u043e\u0447\u043a|chain|trace\s*chain|lifecycle|\u0436\u0438\u0437\u043d\u0435\u043d\u043d[\u0430-\u044f]+\s+\u0446\u0438\u043a\u043b|state\s+transition|\u0440\u0430\u0437\u0440\u044b\u0432[\u0430-\u044f]*)/iu.test(lower); const chainSignal = /(?:\u0446\u0435\u043f\u043e\u0447\u043a|chain|trace\s*chain|lifecycle|\u0436\u0438\u0437\u043d\u0435\u043d\u043d[\u0430-\u044f]+\s+\u0446\u0438\u043a\u043b|state\s+transition|\u0440\u0430\u0437\u0440\u044b\u0432[\u0430-\u044f]*)/iu.test(lower);
const diagnosticsKeywordSignal = /(?:\u0440\u0430\u0437\u043b\u043e\u0436\u0438|\u0434\u0435\u043a\u043e\u043c\u043f\u043e\u0437|\u0440\u0430\u0437\u0431\u0435\u0440\u0438|\u043f\u043e\u0447\u0435\u043c\u0443|why|audit|scan|\u043a\u043e\u0440\u043d\u0435\u0432[\u0430-\u044f]+\s+\u043f\u0440\u0438\u0447\u0438\u043d|root\s*cause|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c[\u0430-\u044f]*|\u0433\u0434\u0435\s+\u0440\u0430\u0437\u0440\u044b\u0432|\u0447\u0442\u043e\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower); const diagnosticsKeywordSignal = /(?:\u0440\u0430\u0437\u043b\u043e\u0436\u0438|\u0434\u0435\u043a\u043e\u043c\u043f\u043e\u0437|\u0440\u0430\u0437\u0431\u0435\u0440\u0438|\u043f\u043e\u0447\u0435\u043c\u0443|why|audit|scan|\u043a\u043e\u0440\u043d\u0435\u0432[\u0430-\u044f]+\s+\u043f\u0440\u0438\u0447\u0438\u043d|root\s*cause|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c[\u0430-\u044f]*|\u0433\u0434\u0435\s+\u0440\u0430\u0437\u0440\u044b\u0432|\u0447\u0442\u043e\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower);
@ -3786,17 +3812,20 @@ function hasDeepAnalysisPreferenceSignal(text) {
const diagnosticsSignal = diagnosticsKeywordSignal || diagnosticsCheckVerbSignal; const diagnosticsSignal = diagnosticsKeywordSignal || diagnosticsCheckVerbSignal;
const closureSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]\s+\u043f\u0435\u0440\u0438\u043e\u0434|period\s*close|\u043d\u0435\s+\u0437\u0430\u043a\u0440\u044b\u043b[\u0430-\u044f]*|\u0445\u0432\u043e\u0441\u0442[\u0430-\u044f]*)/iu.test(lower); const closureSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]\s+\u043f\u0435\u0440\u0438\u043e\u0434|period\s*close|\u043d\u0435\s+\u0437\u0430\u043a\u0440\u044b\u043b[\u0430-\u044f]*|\u0445\u0432\u043e\u0441\u0442[\u0430-\u044f]*)/iu.test(lower);
const closureIntentSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|period\s*close|close\s+period)/iu.test(lower); const closureIntentSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|period\s*close|close\s+period)/iu.test(lower);
const closureStateQuestionSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|\u0445\u0432\u043e\u0441\u0442[\u0430-\u044f]*)[^\n]{0,80}(?:\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434[\u0430-\u044f]*|\u0436\u0438\u0432[\u0430-\u044f]*|\u043e\u0441\u0442\u0430[\u043b-\u044f]*|\u0435\u0441\u0442\u044c)|(?:\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434[\u0430-\u044f]*|\u0436\u0438\u0432[\u0430-\u044f]*|\u043e\u0441\u0442\u0430[\u043b-\u044f]*|\u0435\u0441\u0442\u044c)[^\n]{0,80}(?:\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|\u0445\u0432\u043e\u0441\u0442[\u0430-\u044f]*)/iu.test(lower);
const closureDiagnosticPhraseSignal = /(?:\u0447\u0442\u043e(?:\s+\S+){0,8}\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower); const closureDiagnosticPhraseSignal = /(?:\u0447\u0442\u043e(?:\s+\S+){0,8}\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower);
const signalVsNoiseDiagnostic = /(?:\u043d\u0435\s+\u043f\u0440\u043e\u0441\u0442\u043e\s+(?:\u043d\u0430\s+)?\u0448\u0443\u043c|\u043f\u043e\u0445\u043e\u0436[\u0438\u0435]\s+(?:\u0438\u043c\u0435\u043d\u043d\u043e\s+)?\u043d\u0430\s+\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower); const signalVsNoiseDiagnostic = /(?:\u043d\u0435\s+\u043f\u0440\u043e\u0441\u0442\u043e\s+(?:\u043d\u0430\s+)?\u0448\u0443\u043c|\u043f\u043e\u0445\u043e\u0436[\u0438\u0435]\s+(?:\u0438\u043c\u0435\u043d\u043d\u043e\s+)?\u043d\u0430\s+\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower);
const lifecycleMismatchSignal = /(?:\u043d\u0435\s+\u0442\u0435\u043c\s+\u0442\u0438\u043f(?:\u043e\u043c)?\s+\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|wrong\s+closing\s+document|expected\s+transition)/iu.test(lower); const lifecycleMismatchSignal = /(?:\u043d\u0435\s+\u0442\u0435\u043c\s+\u0442\u0438\u043f(?:\u043e\u043c)?\s+\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|wrong\s+closing\s+document|expected\s+transition)/iu.test(lower);
const lifecycleTransitionGapSignal = /(?:\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u0441\u0442\u0430\u0434\u0438[\u0438\u044f\u0435]\s+.*\u043f\u0440\u043e\u0439\u0434\u0435\u043d.*\u043f\u0435\u0440\u0435\u0445\u043e\u0434)/iu.test(lower); const lifecycleTransitionGapSignal = /(?:\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u0441\u0442\u0430\u0434\u0438[\u0438\u044f\u0435]\s+.*\u043f\u0440\u043e\u0439\u0434\u0435\u043d.*\u043f\u0435\u0440\u0435\u0445\u043e\u0434)/iu.test(lower);
const expectedActualMismatchSignal = /(?:\u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a[\u0430-\u044f]+\s+\u0441\u043e\u0441\u0442\u043e\u044f\u043d[\u0438\u0435\u044f]+\s+.*\u0440\u0430\u0441\u0445\u043e\u0434[\u0430-\u044f]*\s+\u0441\s+\u043e\u0436\u0438\u0434\u0430\u0435\u043c|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d[\u0430-\u044f]*\s+\u0441\u043f\u0438\u0441\u0430\u043d)/iu.test(lower); const expectedActualMismatchSignal = /(?:\u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a[\u0430-\u044f]+\s+\u0441\u043e\u0441\u0442\u043e\u044f\u043d[\u0438\u0435\u044f]+\s+.*\u0440\u0430\u0441\u0445\u043e\u0434[\u0430-\u044f]*\s+\u0441\s+\u043e\u0436\u0438\u0434\u0430\u0435\u043c|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d[\u0430-\u044f]*\s+\u0441\u043f\u0438\u0441\u0430\u043d)/iu.test(lower);
return lifecycleMismatchSignal || return broadOverviewRiskSignal ||
lifecycleMismatchSignal ||
(chainSignal && lifecycleTransitionGapSignal) || (chainSignal && lifecycleTransitionGapSignal) ||
expectedActualMismatchSignal || expectedActualMismatchSignal ||
(chainSignal && diagnosticsSignal) || (chainSignal && diagnosticsSignal) ||
(riskOrAnomalySignal && (chainSignal || diagnosticsSignal || lifecycleTransitionGapSignal)) || (riskOrAnomalySignal && (chainSignal || diagnosticsSignal || lifecycleTransitionGapSignal)) ||
(diagnosticsSignal && (closureSignal || closureIntentSignal)) || (diagnosticsSignal && (closureSignal || closureIntentSignal)) ||
closureStateQuestionSignal ||
closureDiagnosticPhraseSignal || closureDiagnosticPhraseSignal ||
signalVsNoiseDiagnostic; signalVsNoiseDiagnostic;
} }
@ -3827,7 +3856,7 @@ function hasStrictDeepInvestigationCue(text) {
if (!hasInvestigativeVerb) { if (!hasInvestigativeVerb) {
return false; return false;
} }
return /(?:\u0445\u0432\u043e\u0441\u0442|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447|\u0430\u043d\u043e\u043c\u0430\u043b|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]|\u043e\u0431\u044a\u0435\u043a\u0442(?:\u0443)?\s+\u0440\u0430\u0441\u0447(?:\u0435|\u0451)\u0442|lifecycle|state\s+transition)/iu.test(normalized); return /(?:\u0445\u0432\u043e\u0441\u0442|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447|\u0430\u043d\u043e\u043c\u0430\u043b|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]|\u043e\u0431\u044a\u0435\u043a\u0442(?:\u0443)?\s+\u0440\u0430\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0441\u043d\u043e\u0432\p{L}*\s+\u0441\u0440\u0435\u0434\u0441\u0442|fixed\s*asset|\b\u043e\u0441\b|lifecycle|state\s+transition)/iu.test(normalized);
} }
function hasAggregateBusinessAnalyticsSignal(text) { function hasAggregateBusinessAnalyticsSignal(text) {
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()); const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());

View File

@ -70,7 +70,7 @@ function detectSupportedIntent(text, deps) {
reason: "address_intent_resolver_current_turn_signal" reason: "address_intent_resolver_current_turn_signal"
}; };
} }
if (/(?:\u043a\u0442\u043e\s+\u043d\u0430\u043c\s+\u0434\u043e\u043b\u0436|\u043d\u0430\u043c\s+\u043a\u0442\u043e\s+\u0434\u043e\u043b\u0436|\u0434\u0435\u0431\u0438\u0442\u043e\u0440|\u0434\u0435\u0431\u0438\u0442\u043e\u0440\u0441\u043a|\breceivables?\b)/iu.test(text)) { if (/(?:\u043a\u0442\u043e\s+\u043d\u0430\u043c(?:\s+\p{L}+){0,4}\s+\u0434\u043e\u043b\u0436|\u043d\u0430\u043c\s+\u043a\u0442\u043e(?:\s+\p{L}+){0,4}\s+\u0434\u043e\u043b\u0436|\u0434\u0435\u0431\u0438\u0442\u043e\u0440|\u0434\u0435\u0431\u0438\u0442\u043e\u0440\u0441\u043a|\breceivables?\b)/iu.test(text)) {
return { return {
intent: "receivables_confirmed_as_of_date", intent: "receivables_confirmed_as_of_date",
confidence: "high", confidence: "high",

View File

@ -34,7 +34,8 @@ describe("inventory root frame follow-up", () => {
expect(result?.baseReasons).toContain("intent_restored_to_inventory_root_frame"); expect(result?.baseReasons).toContain("intent_restored_to_inventory_root_frame");
expect(result?.filters.extracted_filters.item).toBeUndefined(); expect(result?.filters.extracted_filters.item).toBeUndefined();
expect(result?.filters.extracted_filters.organization).toBe("альтернатива"); expect(result?.filters.extracted_filters.organization).toBe("альтернатива");
expect(result?.filters.extracted_filters.counterparty).toBe("альтернатива"); expect(result?.filters.extracted_filters.counterparty).toBeUndefined();
expect(result?.baseReasons).toContain("counterparty_cleared_as_organization_scope_alias");
expect(result?.filters.extracted_filters.period_from).toBe("2020-05-01"); expect(result?.filters.extracted_filters.period_from).toBe("2020-05-01");
expect(result?.filters.extracted_filters.period_to).toBe("2020-05-31"); expect(result?.filters.extracted_filters.period_to).toBe("2020-05-31");
expect(result?.filters.extracted_filters.as_of_date).toBe("2020-05-31"); expect(result?.filters.extracted_filters.as_of_date).toBe("2020-05-31");

View File

@ -86,5 +86,5 @@ describe("vat payable confirmed as-of route", () => {
expect(result?.debug.requested_result_mode).toBe("confirmed_balance"); expect(result?.debug.requested_result_mode).toBe("confirmed_balance");
expect(result?.debug.route_expectation_status).toBe("matched"); expect(result?.debug.route_expectation_status).toBe("matched");
expect(result?.debug.limited_reason_category).not.toBe("unsupported"); expect(result?.debug.limited_reason_category).not.toBe("unsupported");
}, 15000); }, 30000);
}); });