АДРЕСНЫЙ РЕЖИМ -ADDRESS:Шаг 1 - ЛЛМ ФЕРСТ + feat(address): стабилизация wave1 dynamic resolver контрагентов, follow-up carryover и актуализация docs/tests
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user