АДРЕСНЫЙ РЕЖИМ - fix(address): закрыт S019 predecompose-anchor drift; nightly 102/102+25/25 PASS; docs synced

This commit is contained in:
2026-04-08 11:15:33 +03:00
parent a5dee965c2
commit eec5ef4fc0
210 changed files with 325486 additions and 100 deletions
@@ -89,6 +89,9 @@ const DOCUMENTS_BY_COUNTERPARTY_HINTS = [
"доки",
"доки по",
"док по",
"doki",
"docy",
"doci",
"по контрагент"
];
@@ -484,6 +487,21 @@ function hasPeriodCoverageProfileSignal(text: string): boolean {
if (hasAny(text, PERIOD_COVERAGE_PROFILE_HINTS)) {
return true;
}
if (/(?:за\s+какие\s+год[а-яё]*).*(?:баз[аы].*жив|период|данн)/iu.test(text)) {
return true;
}
if (/(?:какой\s+год[а-яё]*).*(?:по\s+док|докам|документам)/iu.test(text)) {
return true;
}
if (/(?:какой\s+месяц[а-яё]*).*(?:пик|по\s+операц)/iu.test(text)) {
return true;
}
if (/(?:месяц[\s-]*пик).*(?:операц|ops?|operation)/iu.test(text)) {
return true;
}
if (/(?:top\s*year|top\s*month|years?\/top\s*year|years?\s*top\s*year)/iu.test(text)) {
return true;
}
if (/(?:за\s+какие\s+год[а-яё]*\s+в\s+баз[еы]\s+есть\s+данн)/iu.test(text)) {
return true;
}
@@ -509,6 +527,12 @@ function hasDocumentTypeAndAccountSectionProfileSignal(text: string): boolean {
if (hasAny(text, DOCUMENT_TYPE_AND_ACCOUNT_SECTION_PROFILE_HINTS)) {
return true;
}
if (/(?:каких?\s+док(?:ов|и)?).*(?:больше\s+всего|чаще\s+всего|крут)/iu.test(text)) {
return true;
}
if (/(?:сводк[ауи].*тип[а-яё]*\s+док(?:умент|ов|и)?).*(?:дол[ья]|объем|объ[её]м)/iu.test(text)) {
return true;
}
if (/(?:какие\s+тип[аы]\s+док(?:умент|ов|и)?\s+(?:использ|чаще|больш))/iu.test(text)) {
return true;
}
@@ -623,6 +647,11 @@ function hasCustomerRevenueAndPaymentsSignal(text: string): boolean {
/(?:кто\s+нам\s+(?:больше|чаще)|кто\s+платит)/iu.test(text);
const asksCounterpartySource = /(?:с\s+каких|от\s+каких|от\s+кого|from\s+which|from\s+who)/iu.test(text);
const asksIncomingFlow = /(?:приход|поступлен|входящ|зачислен|inflow|incoming)/iu.test(text);
const asksDealBudgetRanking =
/(?:сделк|deal|бюджет)/iu.test(text) &&
/(?:топ|top|сам(?:ый|ая|ое|ые)|крупн|мален|жирн|мелк|больше\s+всего|чаще\s+всего|наибольш|максимальн|минимальн)/iu.test(
text
);
const asksValue =
/(?:доходн|выручк|приход|поступлен|входящ|зачислен|оплат|чек|сделк|бюджет|занес|занёс|принес|принёс|revenue|inflow|deal)/iu.test(
text
@@ -646,6 +675,9 @@ function hasCustomerRevenueAndPaymentsSignal(text: string): boolean {
if (!hasFuzzySupplierLexeme && asksIncomingFlow && asksRankOrTop) {
return true;
}
if (!hasFuzzySupplierLexeme && asksDealBudgetRanking) {
return true;
}
return false;
}
@@ -704,6 +736,11 @@ function hasContractListByCounterpartySignal(text: string): boolean {
return false;
}
// If user explicitly asks for documents, keep routing in document-by-contract/counterparty lane.
if (hasDocumentSignal(text)) {
return false;
}
if (hasContractUsageOverviewSignal(text) || hasOpenContractsListSignal(text)) {
return false;
}
@@ -732,7 +769,7 @@ function hasContractListByCounterpartySignal(text: string): boolean {
function hasDocumentsByAccountDrilldownSignal(text: string): boolean {
const hasAccountLexeme = hasAccountNumberAnchor(text) || hasCompactAccountCodeToken(text);
const hasDocLexeme = /(?:документ|док(?:и|ам|ах|ов|а)?|docs?|documents?)/iu.test(text);
const hasDocLexeme = /(?:документ|док(?:и|ам|ах|ов|а)?|docs?|documents?|doki|docy|doci)/iu.test(text);
const hasDrilldownVerb = /(?:раскрой|раскры|разлож|разверн|документами|по\s+документ)/iu.test(text);
const hasSameDate = /(?:на\s+ту\s+же\s+дат[ауеы]|same\s+date|the\s+same\s+date)/iu.test(text);
return hasAccountLexeme && hasDocLexeme && (hasDrilldownVerb || hasSameDate);
@@ -822,6 +859,25 @@ function isLikelyCounterpartyToken(rawToken: string): boolean {
"пж",
"пжлст",
"пожалуйста",
"есть",
"же",
"сводные",
"сводный",
"сводная",
"сводную",
"сводном",
"сводного",
"сводному",
"неуказанному",
"неуказанный",
"неуказанная",
"неуказанное",
"указанному",
"указанный",
"указанная",
"указанное",
"объекту",
"объект",
"бля",
"блять",
"епт",
@@ -834,7 +890,9 @@ function isLikelyCounterpartyToken(rawToken: string): boolean {
"связанных",
"связанному",
"related",
"linked"
"linked",
"этомуже",
"томуже"
]);
return !stopWords.has(token);
}
@@ -979,14 +1037,14 @@ function hasLooseByAnchorMention(text: string): boolean {
function hasImplicitCounterpartyAnchorAroundDocs(text: string): boolean {
const beforeDocsMatch = text.match(
/(?:^|\s)([a-zа-яё][a-zа-яё0-9._-]{1,})\s+(?:док(?:и|ум(?:ент(?:ы|ов|ам|а)?)?)|docs?|documents?)(?=[\s,.;:!?)]|$)/iu
/(?:^|\s)([a-zа-яё][a-zа-яё0-9._-]{1,})\s+(?:док(?:и|ум(?:ент(?:ы|ов|ам|а)?)?)|docs?|documents?|doki|docy|doci)(?=[\s,.;:!?)]|$)/iu
);
if (beforeDocsMatch && isLikelyCounterpartyToken(String(beforeDocsMatch[1] ?? ""))) {
return true;
}
const afterDocsMatch = text.match(
/(?:док(?:и|ум(?:ент(?:ы|ов|ам|а)?)?)|docs?|documents?)\s+(?:по\s+)?([a-zа-яё][a-zа-яё0-9._-]{1,})(?=[\s,.;:!?)]|$)/iu
/(?:док(?:и|ум(?:ент(?:ы|ов|ам|а)?)?)|docs?|documents?|doki|docy|doci)\s+(?:по\s+)?([a-zа-яё][a-zа-яё0-9._-]{1,})(?=[\s,.;:!?)]|$)/iu
);
if (afterDocsMatch && isLikelyCounterpartyToken(String(afterDocsMatch[1] ?? ""))) {
return true;
@@ -996,7 +1054,7 @@ function hasImplicitCounterpartyAnchorAroundDocs(text: string): boolean {
}
function hasDocsOrBankSignal(text: string): boolean {
return /(?:док(?:и|умент|ументы|ументов)|docs?|documents?|банк|выписк|платеж|платёж|оплат|transactions?|bank\s+ops|bank\s+operations?)/iu.test(
return /(?:док(?:и|умент|ументы|ументов)|docs?|documents?|doki|docy|doci|банк|выписк|платеж|платёж|оплат|transactions?|bank\s+ops|bank\s+operations?)/iu.test(
text
);
}
@@ -1010,6 +1068,9 @@ function hasDocumentSignal(text: string): boolean {
text.includes("док") ||
text.includes("доки") ||
text.includes("документ") ||
text.includes("doki") ||
text.includes("docy") ||
text.includes("doci") ||
text.includes("docs") ||
text.includes("documents")
);