АДРЕСНЫЙ РЕЖИМ -ADDRESS:Шаг 1 - ЛЛМ ФЕРСТ + feat(address): стабилизация wave1 dynamic resolver контрагентов, follow-up carryover и актуализация docs/tests

This commit is contained in:
2026-04-02 23:11:49 +03:00
parent 53716e548e
commit 88094c09f8
243 changed files with 94296 additions and 181 deletions
@@ -162,6 +162,123 @@ const BANK_OPERATION_CORE_HINTS = [
"wire"
];
const PERIOD_COVERAGE_PROFILE_HINTS = [
"за какие годы",
"за какие года",
"в базе есть данные",
"покрытие периодов",
"диапазон лет",
"профиль данных",
"самый активный год",
"самый активный месяц",
"самый пассивный год",
"самый пассивный месяц",
"наименее активный год",
"наименее активный месяц",
"минимум документов по году",
"минимум операций по месяцу",
"год с минимальным количеством документов",
"месяц с минимальным количеством операций",
"активный год по количеству документов",
"активный месяц по количеству операций",
"most active year",
"most active month",
"least active year",
"least active month",
"year coverage",
"data coverage"
];
const DOCUMENT_TYPE_AND_ACCOUNT_SECTION_PROFILE_HINTS = [
"типы документов",
"типы доков",
"документы чаще всего",
"документы реже всего",
"редкие типы документов",
"наименее используемые типы документов",
"частые типы документов",
"сводка по типам документов",
"доля типов документов",
"разделы учета",
"разделы учёта",
"наиболее заполнены",
"наименее заполнены",
"почти не используются",
"account section",
"document types usage",
"document type profile"
];
const COUNTERPARTY_POPULATION_AND_ROLES_HINTS = [
"сколько всего контрагентов",
"сколько уникальных контрагентов",
"сколько контрагентов в базе",
"сколько заказчиков",
"сколько поставщиков",
"сколько клиентов",
"сколько покупателей",
"скока всего контрагентов",
"скока уникальных контрагентов",
"скока контрагентов в базе",
"скока заказчиков",
"скока поставщиков",
"скока клиентов",
"скока покупателей",
"скок контрагентов",
"скок контрагентов в базе",
"скок заказчиков",
"скок поставщиков",
"скок клиентов",
"скок покупателей",
"сколько смешанных контрагентов",
"типы контрагентов",
"разбей контрагентов",
"раздели контрагентов",
"counterparty population",
"counterparty roles",
"customer supplier split"
];
const COUNTERPARTY_ACTIVITY_LIFECYCLE_HINTS = [
"какие заказчики работали",
"какие заказчики активны",
"какие клиенты работали",
"какие клиенты активны",
"список заказчиков",
"список клиентов",
"список заказчиков за все время",
"список клиентов за все время",
"список активных заказчиков",
"список активных клиентов",
"active customers",
"customer activity list",
"counterparty lifecycle"
];
const CONTRACT_USAGE_OVERVIEW_HINTS = [
"сколько всего договоров",
"сколько договоров заведено",
"сколько договоров в базе",
"сколько договоров использовались",
"сколько договоров использовалось",
"договоры total vs used",
"обзор договорной базы",
"договорная база total used",
"contracts total used",
"contract usage overview"
];
const CONTRACT_LIST_BY_COUNTERPARTY_HINTS = [
"договоры по",
"договора по",
"список договоров по",
"покажи договоры по",
"выведи договоры по",
"contracts by counterparty",
"list contracts by counterparty",
"show contracts by counterparty"
];
function hasAny(text: string, patterns: string[]): boolean {
return patterns.some((item) => text.includes(item));
}
@@ -231,6 +348,162 @@ function hasAccountBalanceSignal(text: string): boolean {
return hasAccountLexeme && hasAsOfStyleDate && hasFollowupBalanceVerb;
}
function hasPeriodCoverageProfileSignal(text: string): boolean {
if (hasAny(text, PERIOD_COVERAGE_PROFILE_HINTS)) {
return true;
}
if (/(?:за\s+какие\s+год[а-яё]*\s+в\s+баз[еы]\s+есть\s+данн)/iu.test(text)) {
return true;
}
if (
/(?:какой\s+год[а-яё]*\s+сам(?:ый|ая|ое)\s+(?:актив|пассив)|какой\s+год[а-яё]*\s+наименее\s+актив|год\s+с\s+минимальн)/iu.test(text) &&
/(?:документ|doc)/iu.test(text)
) {
return true;
}
if (
/(?:какой\s+месяц[а-яё]*\s+сам(?:ый|ая|ое)\s+(?:актив|пассив)|какой\s+месяц[а-яё]*\s+наименее\s+актив|месяц\s+с\s+минимальн)/iu.test(text) &&
/(?:операц|operation|ops?)/iu.test(text)
) {
return true;
}
if (/(?:профил[ья]\s+данн|покрыт(?:ие|ия)\s+период|диапазон\s+лет)/iu.test(text)) {
return true;
}
return false;
}
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+тип[аы]\s+док(?:умент|ов|и)?\s+(?:реже|редк|наименее|миним))/iu.test(text)) {
return true;
}
if (/(?:типы?\s+док(?:умент|ов|и)?\s+и\s+их\s+дол[ья])/iu.test(text)) {
return true;
}
if (/(?:какие\s+раздел[ыа]\s+уч[её]та\s+(?:наибол|наимен|заполн|почти\s+не))/iu.test(text)) {
return true;
}
if (/(?:раздел[ыа]\s+уч[её]та).*(?:жирн|мертв|пуст|использ)/iu.test(text)) {
return true;
}
return false;
}
function hasCounterpartyPopulationAndRolesSignal(text: string): boolean {
if (hasAny(text, COUNTERPARTY_POPULATION_AND_ROLES_HINTS)) {
return true;
}
if (
/(?:(?:сколько|скока|скок)\s+(?:всего\s+)?уникальн(?:ых|ые|ого)?\s+контрагент|(?:сколько|скока|скок)\s+(?:всего\s+)?контрагент(?:ов|а)?(?:\s+в\s+баз[еы])?)/iu.test(
text
)
) {
return true;
}
if (
/(?:(?:сколько|скока|скок)\s+(?:у\s+нас\s+)?заказчик(?:ов|а)?|(?:сколько|скока|скок)\s+(?:у\s+нас\s+)?поставщик(?:ов|а)?|(?:сколько|скока|скок)\s+(?:у\s+нас\s+)?клиент(?:ов|а)?|(?:сколько|скока|скок)\s+(?:у\s+нас\s+)?покупател(?:ей|я)|(?:сколько|скока|скок)\s+(?:у\s+нас\s+)?смешан(?:ных|ые)\s+контрагент(?:ов|а)?|заказчик(?:и|ов)\s*,?\s*поставщик(?:и|ов))/iu.test(
text
)
) {
return true;
}
if (/(?:разбей|раздели|сформируй\s+сводк).*(?:контрагент|заказчик|поставщик|клиент|покупател)/iu.test(text)) {
return true;
}
return false;
}
function hasCounterpartyActivityLifecycleSignal(text: string): boolean {
if (hasDocumentSignal(text) || hasBankOperationSignal(text)) {
return false;
}
if (hasAny(text, COUNTERPARTY_ACTIVITY_LIFECYCLE_HINTS)) {
return true;
}
if (/(?:сколько|скока|скок)\s+/iu.test(text)) {
return false;
}
const hasCustomerLexeme = /(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?|customer(?:s)?|client(?:s)?)/iu.test(
text
);
const hasActivityLexeme = /(?:работал(?:и)?|активн(?:ые|ых|а|о)?|сотрудничал(?:и)?|были\s+в\s+работе|active)/iu.test(text);
const hasTimeWindowLexeme =
/(?:за\s+вс[её]\s+время|all\s+time|\b(?:19|20)\d{2}\b|(?:^|[^\d])\d{2}\s*(?:г(?:од|ода)?|г)(?:[^\p{L}\p{N}]|$)|в\s+конкретн(?:ом|ый)\s+год|за\s+год|в\s+году)/iu.test(
text
);
const hasListVerb = /(?:какие|кто|покажи|выведи|список|list|show)/iu.test(text);
const hasRosterQualifier = /(?:у\s+нас|вообще|в\s+баз[еы]|какие\s+есть|кто\s+есть|who\s+are)/iu.test(text);
const hasListWithWindow = hasCustomerLexeme && hasListVerb && hasTimeWindowLexeme;
if (hasListWithWindow) {
return true;
}
if (hasCustomerLexeme && hasListVerb && hasRosterQualifier) {
return true;
}
return hasCustomerLexeme && hasActivityLexeme && (hasTimeWindowLexeme || hasListVerb);
}
function hasContractUsageOverviewSignal(text: string): boolean {
if (hasAny(text, CONTRACT_USAGE_OVERVIEW_HINTS)) {
return true;
}
if (
/(?:сколько\s+(?:всего\s+)?договор(?:ов|а)?(?:\s+заведен[оы])?|договорн(?:ая|ой)\s+баз[аы]).*(?:сколько|used|использ)/iu.test(
text
)
) {
return true;
}
if (
/(?:сколько\s+из\s+договор(?:ов|а)?\s+(?:реально\s+)?использ(?:ован[оы]|овал(?:и|ось)?))/iu.test(text)
) {
return true;
}
if (/(?:total\s+vs\s+used|used\s+vs\s+total).*(?:договор|contract)?/iu.test(text)) {
return true;
}
return false;
}
function hasContractListByCounterpartySignal(text: string): boolean {
const hasContractLexeme = /(?:договор(?:а|у|ом|е|ы)?|contracts?|contract)/iu.test(text);
if (!hasContractLexeme) {
return false;
}
if (hasContractUsageOverviewSignal(text) || hasOpenContractsListSignal(text)) {
return false;
}
if (hasContractNumberLikeToken(text)) {
return false;
}
if (hasBankOperationSignal(text)) {
return false;
}
const hasListVerb = /(?:покажи|выведи|список|какие|show|list)/iu.test(text);
const hasAllQualifier = /(?:\ball\b|\bвсе\b|всё)/iu.test(text);
const hasCounterpartyAnchor =
hasPartyAnchorMention(text) ||
hasLooseByAnchorMention(text) ||
hasHeuristicCounterpartyAnchor(text);
if (!hasCounterpartyAnchor) {
return false;
}
return hasListVerb || hasAllQualifier || hasAny(text, CONTRACT_LIST_BY_COUNTERPARTY_HINTS);
}
function hasDocumentsByAccountDrilldownSignal(text: string): boolean {
const hasAccountLexeme = hasAccountNumberAnchor(text) || hasCompactAccountCodeToken(text);
const hasDocLexeme = /(?:документ|док(?:и|ам|ах|ов|а)?|docs?|documents?)/iu.test(text);
@@ -287,6 +560,21 @@ function isLikelyCounterpartyToken(rawToken: string): boolean {
"документах",
"докам",
"доками",
"количество",
"количеству",
"количества",
"количеством",
"активный",
"активного",
"активности",
"пассивный",
"пассивного",
"пассивности",
"наименее",
"минимальный",
"минимум",
"реже",
"редкий",
"банк",
"банковские",
"операции",
@@ -450,7 +738,15 @@ function hasLooseByAnchorMention(text: string): boolean {
"периоду",
"период",
"документам",
"докам"
"докам",
"количество",
"количеству",
"количества",
"количеством",
"активности",
"пассивности",
"наименее",
"минимум"
]);
return !stopWords.has(token);
}
@@ -589,6 +885,76 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
};
}
if (
hasPeriodCoverageProfileSignal(text) &&
!hasPartyAnchorMention(text) &&
!hasContractAnchorSignal(text) &&
!hasAccountBalanceSignal(text)
) {
return {
intent: "period_coverage_profile",
confidence: "high",
reasons: ["period_coverage_profile_signal_detected"]
};
}
if (
hasDocumentTypeAndAccountSectionProfileSignal(text) &&
!hasPartyAnchorMention(text) &&
!hasContractAnchorSignal(text) &&
!hasAccountBalanceSignal(text)
) {
return {
intent: "document_type_and_account_section_profile",
confidence: "high",
reasons: ["document_type_and_account_section_profile_signal_detected"]
};
}
if (
hasCounterpartyPopulationAndRolesSignal(text) &&
!hasContractAnchorSignal(text) &&
!hasAccountBalanceSignal(text)
) {
return {
intent: "counterparty_population_and_roles",
confidence: "high",
reasons: ["counterparty_population_and_roles_signal_detected"]
};
}
if (
hasCounterpartyActivityLifecycleSignal(text) &&
!hasContractAnchorSignal(text) &&
!hasAccountBalanceSignal(text)
) {
return {
intent: "counterparty_activity_lifecycle",
confidence: "high",
reasons: ["counterparty_activity_lifecycle_signal_detected"]
};
}
if (
hasContractUsageOverviewSignal(text) &&
!hasAccountBalanceSignal(text) &&
!hasOpenContractsListSignal(text)
) {
return {
intent: "contract_usage_overview",
confidence: "high",
reasons: ["contract_usage_overview_signal_detected"]
};
}
if (hasContractListByCounterpartySignal(text)) {
return {
intent: "list_contracts_by_counterparty",
confidence: "medium",
reasons: ["contracts_by_counterparty_signal_detected"]
};
}
if (
hasContractAnchorSignal(text) &&
hasBankOperationSignal(text)