Open-World: удержать бизнес-обзор от мусорных anchors

This commit is contained in:
2026-05-05 15:43:47 +03:00
parent ee69c7bc8d
commit d797b3fa99
13 changed files with 424 additions and 19 deletions
@@ -103,7 +103,17 @@ function hasUnicodeLikelyCounterpartyAfterBy(text: string): boolean {
"\u0431\u0430\u043d\u043a\u0435",
"\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c",
"\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b",
"\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0443"
"\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0443",
"\u0434\u0430\u043d\u043d\u044b\u043c",
"\u044d\u0442\u0438\u043c",
"\u044d\u0442\u0438\u043c\u0438",
"\u0438\u0442\u043e\u0433\u0443",
"\u0438\u0442\u043e\u0433\u0430\u043c",
"\u0432\u0441\u0435\u043c\u0443",
"\u0432\u0441\u0435\u0439",
"\u0432\u0441\u0435\u043c",
"\u0432\u044b\u0432\u043e\u0434\u0443",
"\u0432\u044b\u0432\u043e\u0434\u0430\u043c"
]);
return !stopWords.has(token);
}
@@ -1379,6 +1379,30 @@ function isLikelyCounterpartyToken(rawToken: string): boolean {
"этомуже",
"томуже"
]);
for (const semanticStopWord of [
"данным",
"этим",
"этими",
"итогу",
"итогам",
"всему",
"всей",
"всем",
"выводу",
"выводам",
"аудиту",
"прокси",
"покажи",
"показать",
"выведи",
"вывести",
"дай",
"дать",
"какие",
"список"
]) {
stopWords.add(semanticStopWord);
}
return !stopWords.has(token);
}
@@ -2145,6 +2169,18 @@ function resolveUnicodeAddressIntentBridge(text: string): AddressIntentResolutio
"сальдо",
"дате",
"периоду",
"данным",
"этим",
"этими",
"итогу",
"итогам",
"всему",
"всей",
"всем",
"выводу",
"выводам",
"аудиту",
"прокси",
"складу",
"товару",
"этому",
@@ -144,12 +144,56 @@ function isActionVerbEntityCandidate(value: string | null): boolean {
);
}
function isGarbageSemanticAnchorCandidate(value: string | null): boolean {
if (!value) {
return false;
}
const text = compactLower(repairAddressMojibakeText(value));
if (!text || text.length < 2) {
return true;
}
const compact = normalizeRussianComparableText(text);
if (
new Set([
"данным",
"этим",
"этими",
"итогу",
"итогам",
"всему",
"всей",
"всем",
"выводу",
"выводам",
"аудиту",
"прокси",
"proxy",
"summary",
"overall"
]).has(compact)
) {
return true;
}
if (
/^(?:по\s+)?(?:этим|этими)\s+данн\p{L}*$/iu.test(text) ||
/^(?:и\s+)?кто\s+(?:главн\p{L}*|основн\p{L}*|крупн\p{L}*)\s+(?:клиент|покупател|поставщик|контрагент)(?:\s+в)?$/iu.test(
text
) ||
/^(?:что|чего)\s+(?:подтвержден\p{L}*|не\s+хватает)/iu.test(text) ||
/^(?:можно\s+ли|если\s+нет|дай\s+proxy|дай\s+прокси)/iu.test(text)
) {
return true;
}
return false;
}
function isInvalidEntityCandidate(value: string | null): boolean {
return Boolean(
value &&
(isReferentialEntityPlaceholder(value) ||
isValueFlowPredicateEntityCandidate(value) ||
isActionVerbEntityCandidate(value))
isActionVerbEntityCandidate(value) ||
isGarbageSemanticAnchorCandidate(value))
);
}
@@ -373,6 +417,13 @@ function mapPilotScopeToFollowupMeaning(
unsupported: "1c_metadata_surface"
};
}
if (pilotScope === "business_overview_route_template_v1") {
return {
domain: "business_overview",
action: "broad_evaluation",
unsupported: "broad_business_evaluation"
};
}
return {
domain: null,
action: null,
@@ -798,6 +849,36 @@ function hasBusinessOverviewSignal(text: string): boolean {
);
}
function hasBusinessOverviewContinuationSignal(text: string): boolean {
if (!text) {
return false;
}
const normalized = compactLower(text);
const hasEvidenceContinuationCue =
/(?:по\s+этим\s+данн|на\s+базе\s+этих\s+данн|из\s+этого|по\s+итогам|что\s+подтвержден|чего\s+не\s+хватает|что\s+пока\s+нельзя|что\s+можно\s+сказать|какой\s+вывод|итогов|резюм|вывод)/iu.test(
normalized
);
const hasAnalystContinuationCue =
/(?:можно\s+ли|если\s+нет|proxy|прокси|аудит|оцен|что\s+думаешь|нормальн\p{L}*\s+прибыл|прибыл|марж|рентаб|ликвидн|просроч|качество\s+долг|риск|налогов\p{L}*\s+вывод)/iu.test(
normalized
);
const hasTaxContinuationCue =
/(?:ндс|vat)[\s\S]{0,120}(?:позици|период|основан|не\s+хватает|налогов\p{L}*\s+вывод)|(?:позици|налогов\p{L}*\s+вывод)[\s\S]{0,80}(?:ндс|vat)/iu.test(
normalized
);
return hasEvidenceContinuationCue || hasAnalystContinuationCue || hasTaxContinuationCue;
}
function hasBusinessOverviewFollowupSeed(followupSeed: ReturnType<typeof collectFollowupDiscoverySeed>): boolean {
return (
followupSeed.pilotScope === "business_overview_route_template_v1" ||
followupSeed.domain === "business_overview" ||
followupSeed.action === "broad_evaluation" ||
followupSeed.unsupported === "broad_business_evaluation" ||
followupSeed.loopSelectedChainId === "business_overview"
);
}
function hasValueFlowSignal(text: string): boolean {
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|(?<!\p{L})заработ(?:ал|али|ало|аем|ает|ать|ано|ок)(?!\p{L})|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(
text
@@ -1275,7 +1356,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
const rawReferentialDocumentExclusionSignal = hasReferentialDocumentExclusionFollowupSignal(
repairedUserText ?? rawUserText ?? ""
);
const rawBusinessOverviewSignal = hasBusinessOverviewSignal(rawText);
const businessOverviewContinuationSignal =
hasBusinessOverviewFollowupSeed(followupSeed) && hasBusinessOverviewContinuationSignal(rawText);
const rawBusinessOverviewSignal = hasBusinessOverviewSignal(rawText) || businessOverviewContinuationSignal;
const rawLifecycleSignal = !rawBusinessOverviewSignal && hasLifecycleSignal(rawText);
const rawBidirectionalValueFlowSignal =
!rawBusinessOverviewSignal && !rawLifecycleSignal && hasBidirectionalValueFlowSignal(rawText);
@@ -2244,6 +2327,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
if (businessOverviewSignal) {
pushReason(reasonCodes, "mcp_discovery_broad_business_evaluation_route_candidate");
}
if (businessOverviewContinuationSignal) {
pushReason(reasonCodes, "mcp_discovery_business_overview_continuation_from_followup_context");
}
if (
!(valueFlowOrganizationStaysScope && normalizedPredecomposeCounterparty === explicitOrganizationScope) &&
normalizedPredecomposeCounterparty