1838 lines
92 KiB
JavaScript
1838 lines
92 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.resolveAddressIntent = resolveAddressIntent;
|
||
const inventoryLifecycleCueHelpers_1 = require("./inventoryLifecycleCueHelpers");
|
||
const RECEIVABLES_STRONG = [
|
||
"кто должен нам",
|
||
"кто нам должен",
|
||
"кто нам должэн",
|
||
"нам должны",
|
||
"нам должен",
|
||
"нам должэны",
|
||
"who owes us",
|
||
"receivable",
|
||
"receivables",
|
||
"debtor",
|
||
"debtors",
|
||
"дебитор",
|
||
"дебиторск"
|
||
];
|
||
const PAYABLES_STRONG = [
|
||
"кому должны мы",
|
||
"кому должэны мы",
|
||
"кому мы должны",
|
||
"кому мы должэны",
|
||
"мы должны",
|
||
"мы должэны",
|
||
"who we owe",
|
||
"payable",
|
||
"payables",
|
||
"creditor",
|
||
"creditors",
|
||
"кредитор",
|
||
"кредиторск"
|
||
];
|
||
const ACCOUNT_BALANCE_HINTS = [
|
||
"account balance",
|
||
"balance by account",
|
||
"saldo",
|
||
"баланс",
|
||
"остаток по счет",
|
||
"сальдо по счет",
|
||
"по счету",
|
||
"что на счете",
|
||
"что на счёте",
|
||
"на конец"
|
||
];
|
||
const DOCUMENTS_FORMING_BALANCE_HINTS = [
|
||
"documents forming balance",
|
||
"docs forming balance",
|
||
"documents form balance",
|
||
"docs form balance",
|
||
"balance documents",
|
||
"documents for balance",
|
||
"which documents form balance",
|
||
"из чего состоит остаток",
|
||
"какие документы формируют остаток",
|
||
"раскрой остаток по документам",
|
||
"документы под остатком"
|
||
];
|
||
const OPEN_CONTRACTS_HINTS = [
|
||
"open contracts",
|
||
"unclosed contracts",
|
||
"незакрыт",
|
||
"не закрыт",
|
||
"открыт",
|
||
"договор",
|
||
"контракт"
|
||
];
|
||
const OPEN_ITEMS_HINTS = [
|
||
"open items",
|
||
"unclosed items",
|
||
"хвост",
|
||
"висят",
|
||
"незакрыт",
|
||
"открыт",
|
||
"долг",
|
||
"задолж",
|
||
"позици"
|
||
];
|
||
const DOCUMENTS_BY_COUNTERPARTY_HINTS = [
|
||
"documents by counterparty",
|
||
"docs by counterparty",
|
||
"documents by company",
|
||
"documents by supplier",
|
||
"documents by customer",
|
||
"documents by client",
|
||
"documents by partner",
|
||
"show documents by counterparty",
|
||
"list documents by counterparty",
|
||
"документы по",
|
||
"доступные документы",
|
||
"список документов",
|
||
"документ",
|
||
"доки",
|
||
"доки по",
|
||
"док по",
|
||
"doki",
|
||
"docy",
|
||
"doci",
|
||
"по контрагент"
|
||
];
|
||
const BANK_OPERATIONS_BY_COUNTERPARTY_HINTS = [
|
||
"bank operations by counterparty",
|
||
"bank payments by counterparty",
|
||
"payment orders by counterparty",
|
||
"bank operations by company",
|
||
"bank operations by supplier",
|
||
"bank operations by customer",
|
||
"show bank operations by counterparty",
|
||
"bank ops",
|
||
"bank oper",
|
||
"transactions by counterparty",
|
||
"транзак",
|
||
"банк",
|
||
"банков",
|
||
"по банку",
|
||
"опер",
|
||
"выписк",
|
||
"платеж",
|
||
"платёж",
|
||
"оплат",
|
||
"списан",
|
||
"списани",
|
||
"поступлен",
|
||
"поступлени",
|
||
"движени"
|
||
];
|
||
const DOCUMENTS_BY_CONTRACT_HINTS = [
|
||
"documents by contract",
|
||
"docs by contract",
|
||
"show documents by contract",
|
||
"list documents by contract",
|
||
"документы по договору",
|
||
"доки по договору",
|
||
"док по договору",
|
||
"документы договор",
|
||
"договор",
|
||
"документы по контракту",
|
||
"доки по контракту",
|
||
"контракт"
|
||
];
|
||
const BANK_OPERATIONS_BY_CONTRACT_HINTS = [
|
||
"bank operations by contract",
|
||
"bank payments by contract",
|
||
"payment orders by contract",
|
||
"transactions by contract",
|
||
"bank ops by contract",
|
||
"банковские операции по договору",
|
||
"платежи по договору",
|
||
"выписка по договору",
|
||
"банковские операции по контракту",
|
||
"платежи по контракту",
|
||
"выписка по контракту"
|
||
];
|
||
const BANK_OPERATION_CORE_HINTS = [
|
||
"банк",
|
||
"банков",
|
||
"операц",
|
||
"опер",
|
||
"выписк",
|
||
"платеж",
|
||
"платёж",
|
||
"оплат",
|
||
"списан",
|
||
"поступлен",
|
||
"движени",
|
||
"транзак",
|
||
"bank",
|
||
"payment",
|
||
"payments",
|
||
"transaction",
|
||
"transactions",
|
||
"statement",
|
||
"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",
|
||
"сколько лет активности в базе",
|
||
"сколько лет активности в 1с",
|
||
"сколько лет в базе 1с",
|
||
"какой первый платеж",
|
||
"какое первое поступление",
|
||
"когда была первая активность",
|
||
"когда была последняя активность",
|
||
"первая активность в базе",
|
||
"последняя активность в базе",
|
||
"customer activity list",
|
||
"counterparty lifecycle"
|
||
];
|
||
const CONTRACT_USAGE_OVERVIEW_HINTS = [
|
||
"сколько всего договоров",
|
||
"сколько договоров заведено",
|
||
"сколько договоров в базе",
|
||
"сколько договоров использовались",
|
||
"сколько договоров использовалось",
|
||
"договоры total vs used",
|
||
"обзор договорной базы",
|
||
"договорная база total used",
|
||
"неиспользуемые договоры",
|
||
"давно не использовались договоры",
|
||
"мертвые договоры",
|
||
"мёртвые договоры",
|
||
"stale contracts",
|
||
"unused contracts",
|
||
"contracts total used",
|
||
"contract usage overview"
|
||
];
|
||
const CUSTOMER_REVENUE_AND_PAYMENTS_HINTS = [
|
||
"самые доходные клиенты",
|
||
"самые доходные заказчики",
|
||
"самые ликвидные клиенты",
|
||
"самые ликвидные заказчики",
|
||
"самых ликвидних заказчиков",
|
||
"топ клиентов по сумме поступлений",
|
||
"топ заказчиков по сумме поступлений",
|
||
"кто больше всего принес денег",
|
||
"кто больше всего принёс денег",
|
||
"кто принес больше всего денег",
|
||
"кто принёс больше всего денег",
|
||
"кто нам больше денег принес",
|
||
"кто нам больше денег принёс",
|
||
"кто нам принес больше денег",
|
||
"кто нам принёс больше денег",
|
||
"кто нам больше всего занес",
|
||
"кто нам больше всего занёс",
|
||
"кто нам принес больше всего",
|
||
"кто нам принёс больше всего",
|
||
"кто платит чаще всего",
|
||
"средний чек клиентов",
|
||
"средний чек заказчиков",
|
||
"крупные сделки по поступлениям",
|
||
"маленькие сделки по поступлениям",
|
||
"smallest deals by inflow",
|
||
"largest deals by inflow",
|
||
"top customers by inflow",
|
||
"top customers by revenue"
|
||
];
|
||
const SUPPLIER_PAYOUTS_PROFILE_HINTS = [
|
||
"топ поставщиков по сумме выплат",
|
||
"кому мы больше всего заплатили",
|
||
"кому ушло больше всего денег",
|
||
"кому мы больше всего сгрузили денег",
|
||
"поставщики по выплатам",
|
||
"поставщики по исходящим платежам",
|
||
"поставщики с максимальным числом выплат",
|
||
"крупные разовые выплаты поставщикам",
|
||
"top suppliers by payouts",
|
||
"top suppliers by outgoing payments"
|
||
];
|
||
const CONTRACT_USAGE_AND_VALUE_HINTS = [
|
||
"договоры по обороту",
|
||
"договоры по сумме оборота",
|
||
"топ договоров по обороту",
|
||
"контракты по обороту",
|
||
"контракты по сумме оборота",
|
||
"топ контрактов по обороту",
|
||
"договоры с минимальным бюджетом",
|
||
"договоры с самым маленьким бюджетом",
|
||
"контракты с минимальным бюджетом",
|
||
"контракты с самым маленьким бюджетом",
|
||
"активные договоры по бюджету",
|
||
"активные контракты по бюджету",
|
||
"контрагенты с несколькими договорами",
|
||
"несколько договоров у контрагента",
|
||
"мультидоговорные контрагенты",
|
||
"какие договоры активны",
|
||
"какие контракты активны",
|
||
"рабочие договоры",
|
||
"рабочие контракты",
|
||
"contracts by turnover",
|
||
"contracts by budget"
|
||
];
|
||
const CONTRACT_LIST_BY_COUNTERPARTY_HINTS = [
|
||
"договоры по",
|
||
"договора по",
|
||
"список договоров по",
|
||
"покажи договоры по",
|
||
"выведи договоры по",
|
||
"контракты по",
|
||
"список контрактов по",
|
||
"покажи контракты по",
|
||
"выведи контракты по",
|
||
"contracts by counterparty",
|
||
"list contracts by counterparty",
|
||
"show contracts by counterparty"
|
||
];
|
||
function hasAny(text, patterns) {
|
||
return patterns.some((item) => text.includes(item));
|
||
}
|
||
function hasFlexibleReceivablesDebtSignal(text) {
|
||
const normalized = String(text ?? "");
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
const hasFlexibleWhoOwesUs = /(?:\u043a\u0442\u043e(?:\s+\S+){0,4}\s+\u043d\u0430\u043c(?:\s+\S+){0,4}\s+\u0434\u043e\u043b\u0436)/iu.test(normalized) ||
|
||
/(?:\u043d\u0430\u043c(?:\s+\S+){0,4}\s+\u043a\u0442\u043e(?:\s+\S+){0,4}\s+\u0434\u043e\u043b\u0436)/iu.test(normalized);
|
||
const hasTorchatToUsSignal = /(?:\u043d\u0430\u043c(?:\s+\S+){0,3}\s+\u0442\u043e\u0440\u0447(?:\u0430\u0442|\u0438\u0442)|\u0442\u043e\u0440\u0447(?:\u0430\u0442|\u0438\u0442)(?:\s+\S+){0,3}\s+\u043d\u0430\u043c)/iu.test(normalized);
|
||
return hasFlexibleWhoOwesUs || hasTorchatToUsSignal;
|
||
}
|
||
function hasFlexiblePayablesDebtSignal(text) {
|
||
const normalized = String(text ?? "");
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
return (/(?:кому(?:\s+\S+){0,4}\s+мы(?:\s+\S+){0,4}\s+долж)/iu.test(normalized) ||
|
||
/(?:мы(?:\s+\S+){0,4}\s+кому(?:\s+\S+){0,4}\s+долж)/iu.test(normalized));
|
||
}
|
||
function tokenizeText(text) {
|
||
return String(text ?? "")
|
||
.toLowerCase()
|
||
.split(/[^a-zа-яё0-9]+/iu)
|
||
.map((token) => token.trim())
|
||
.filter((token) => token.length > 0);
|
||
}
|
||
function trimRussianEnding(token) {
|
||
return token.replace(/(?:иями|ями|ами|ого|ему|ому|ыми|ими|ией|ей|ий|ый|ой|ях|ах|ов|ев|ам|ям|ом|ем|ы|и|а|я|у|ю|е|о)$/u, "");
|
||
}
|
||
function normalizeLexemeToken(rawToken) {
|
||
const token = String(rawToken ?? "").toLowerCase().replace(/[^a-zа-яё0-9]+/gu, "");
|
||
if (!token) {
|
||
return "";
|
||
}
|
||
if (/^[a-z0-9]+$/u.test(token)) {
|
||
return token;
|
||
}
|
||
return trimRussianEnding(token);
|
||
}
|
||
function levenshteinDistance(a, b) {
|
||
if (a === b) {
|
||
return 0;
|
||
}
|
||
if (!a.length) {
|
||
return b.length;
|
||
}
|
||
if (!b.length) {
|
||
return a.length;
|
||
}
|
||
const prev = new Array(b.length + 1);
|
||
const curr = new Array(b.length + 1);
|
||
for (let j = 0; j <= b.length; j += 1) {
|
||
prev[j] = j;
|
||
}
|
||
for (let i = 1; i <= a.length; i += 1) {
|
||
curr[0] = i;
|
||
for (let j = 1; j <= b.length; j += 1) {
|
||
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
||
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
||
}
|
||
for (let j = 0; j <= b.length; j += 1) {
|
||
prev[j] = curr[j];
|
||
}
|
||
}
|
||
return prev[b.length];
|
||
}
|
||
function hasFuzzyLexeme(text, lexemeRoots) {
|
||
const normalizedRoots = lexemeRoots
|
||
.map((root) => normalizeLexemeToken(root))
|
||
.filter((root) => root.length > 0);
|
||
if (normalizedRoots.length === 0) {
|
||
return false;
|
||
}
|
||
const tokens = tokenizeText(text)
|
||
.map((token) => normalizeLexemeToken(token))
|
||
.filter((token) => token.length >= 4);
|
||
for (const token of tokens) {
|
||
for (const root of normalizedRoots) {
|
||
if (token.includes(root)) {
|
||
return true;
|
||
}
|
||
if (root.includes(token) && token.length >= 5) {
|
||
return true;
|
||
}
|
||
const maxDistance = root.length >= 7 ? 2 : 1;
|
||
if (Math.abs(token.length - root.length) > maxDistance) {
|
||
continue;
|
||
}
|
||
if (levenshteinDistance(token, root) <= maxDistance) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
function hasCompactAccountCodeToken(text) {
|
||
// Match compact account tokens while reducing false positives on short-year literals like "22 год".
|
||
const source = String(text ?? "");
|
||
if (!source) {
|
||
return false;
|
||
}
|
||
// Safe compact form: 60.01 / 62.1
|
||
if (/(?<![\d-])\d{2}[.,]\d{1,2}(?![\d-])/u.test(source)) {
|
||
return true;
|
||
}
|
||
// Plain two-digit code is accepted only in explicit account context.
|
||
if (/(?:сч[её]т|account)\D{0,12}\d{2}(?![\d-])/iu.test(source)) {
|
||
return true;
|
||
}
|
||
if (/(?:^|\s)по\s+\d{2}(?=$|[\s,.;:!?])/iu.test(source)) {
|
||
if (!/(?:^|\s)(?:за|в)\s+\d{2}\s*(?:г(?:од|ода)?|year)\b/iu.test(source)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
function hasDocumentsFormingBalanceSignal(text) {
|
||
if (hasAny(text, DOCUMENTS_FORMING_BALANCE_HINTS)) {
|
||
return true;
|
||
}
|
||
const hasLooseAccountCodeToken = hasCompactAccountCodeToken(text);
|
||
const hasDocLexeme = /(?:документ|док(?:и|ам|ах|ов|а)?)/u.test(text);
|
||
const hasFormingLexeme = text.includes("формир");
|
||
const hasBalanceLexeme = text.includes("остат");
|
||
const hasAccountLexeme = text.includes("счет") || text.includes("счёт") || hasAccountNumberAnchor(text) || hasLooseAccountCodeToken;
|
||
if (hasDocLexeme && hasFormingLexeme && hasBalanceLexeme && hasAccountLexeme) {
|
||
return true;
|
||
}
|
||
if (hasDocLexeme &&
|
||
hasBalanceLexeme &&
|
||
hasAccountLexeme &&
|
||
(text.includes("раскрой") || text.includes("раскид") || text.includes("под остатк"))) {
|
||
return true;
|
||
}
|
||
if (hasBalanceLexeme && hasAccountLexeme && text.includes("из чего состоит")) {
|
||
return true;
|
||
}
|
||
return hasBalanceLexeme && hasAccountLexeme && /из\s+чего\s+остат/u.test(text);
|
||
}
|
||
function hasDocumentsFormingBalanceAccountAnchor(text) {
|
||
if (hasAccountNumberAnchor(text) || text.includes("счет") || text.includes("счёт")) {
|
||
return true;
|
||
}
|
||
// Allow compact account mentions like "60.01" in slang prompts without explicit "счет".
|
||
return hasCompactAccountCodeToken(text);
|
||
}
|
||
function hasAccountBalanceSignal(text) {
|
||
if (hasAny(text, ACCOUNT_BALANCE_HINTS)) {
|
||
return true;
|
||
}
|
||
const hasAccountLexeme = hasAccountNumberAnchor(text) || hasCompactAccountCodeToken(text) || /(?:^|\s)по\s+\d{2}(?:[.,]\d{1,2})?(?=$|[\s,.;:!?])/u.test(text);
|
||
const hasBalanceLexeme = text.includes("баланс") ||
|
||
text.includes("остат") ||
|
||
text.includes("сальд") ||
|
||
text.includes("saldo") ||
|
||
text.includes("balance") ||
|
||
text.includes("скока") ||
|
||
text.includes("сколько") ||
|
||
/на\s+конец/u.test(text);
|
||
if (hasAccountLexeme && hasBalanceLexeme) {
|
||
return true;
|
||
}
|
||
const hasAsOfStyleDate = /\b(19|20)\d{2}[./-](0?[1-9]|1[0-2])(?:[./-](0?[1-9]|[12]\d|3[01]))\b/u.test(text) ||
|
||
/(?:на\s+ту\s+же\s+дат[ауеы]|same\s+date|the\s+same\s+date)/iu.test(text);
|
||
const hasFollowupBalanceVerb = /(?:вернись|вернуться|вернуть|back|return)/iu.test(text);
|
||
return hasAccountLexeme && hasAsOfStyleDate && hasFollowupBalanceVerb;
|
||
}
|
||
function hasForecastTaxSignal(text) {
|
||
const hasForecastLexeme = /(?:прогноз|forecast|план(?:\s+платежа|\s+оплаты)?|прикин(?:уть|ем|у|ь|ул|ули|усь|усь))/iu.test(text);
|
||
const hasTaxLexeme = /(?:ндс|vat|налог)/iu.test(text);
|
||
return hasForecastLexeme && hasTaxLexeme;
|
||
}
|
||
function hasVatLiabilityConfirmedTaxPeriodSignal(text) {
|
||
const hasVatLexeme = /(?:ндс|vat)/iu.test(text);
|
||
if (!hasVatLexeme) {
|
||
return false;
|
||
}
|
||
const hasPaymentCue = /(?:к\s+уплате|надо|нужно|заплатить|уплатить|плат[её]ж|платежку|в\s+налогов|в\s+бюджет|должн[аы]?\s+заплатить|мы\s+должн[аы]?|должн[аы]?\s+мы|сгруз(?:ить|им|ишь|ите|ил|ила|или|ка))/iu.test(text);
|
||
if (!hasPaymentCue) {
|
||
return false;
|
||
}
|
||
const hasAsOfCue = /(?:на\s+дат|по\s+состоянию|на\s+конец|as\s+of)/iu.test(text);
|
||
if (hasAsOfCue) {
|
||
return false;
|
||
}
|
||
const hasTaxAuthorityCue = /(?:в\s+налогов|в\s+бюджет|декларац|налогов(?:ый|ую)\s+период)/iu.test(text);
|
||
const hasQuarterCue = /(?:\b[1-4]\s*(?:квартал|кв\.?)\b|квартал|кв\.?)/iu.test(text);
|
||
const hasZaPeriodCue = /(?:за\s+(?:\d{4}|январ|феврал|март|апрел|май|июн|июл|август|сентябр|октябр|ноябр|декабр|квартал|кв\.?|месяц|год|период))/iu.test(text);
|
||
const hasExplicitDayDate = /\b(?:\d{1,2}[./-]\d{1,2}[./-](?:\d{2}|\d{4})|(?:19|20)\d{2}[./-]\d{1,2}[./-]\d{1,2})\b/u.test(text);
|
||
const hasMonthYearNaCue = /(?:на\s+(?:январ|феврал|март|апрел|май|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*\s+(?:19|20)\d{2})/iu.test(text);
|
||
const hasHowMuchCue = /(?:сколько|скока|скок)/iu.test(text);
|
||
// "На март 2020" и конкретная дата без налогового контекста чаще означают as-of срез.
|
||
if (!hasTaxAuthorityCue && !hasZaPeriodCue && !hasQuarterCue && (hasMonthYearNaCue || hasExplicitDayDate)) {
|
||
return false;
|
||
}
|
||
return hasTaxAuthorityCue || hasZaPeriodCue || hasQuarterCue || (hasHowMuchCue && hasTaxAuthorityCue);
|
||
}
|
||
function hasVatPayableConfirmedSignal(text) {
|
||
const hasVatLexeme = /(?:ндс|vat)/iu.test(text);
|
||
if (!hasVatLexeme) {
|
||
return false;
|
||
}
|
||
const hasPaymentCue = /(?:к\s+уплате|надо|нужно|заплатить|уплатить|плат[её]ж|платежку|в\s+налогов|в\s+бюджет|должн[аы]?\s+заплатить|мы\s+должн[аы]?|должн[аы]?\s+мы|сгруз(?:ить|им|ишь|ите|ил|ила|или|ка))/iu.test(text);
|
||
if (!hasPaymentCue) {
|
||
return false;
|
||
}
|
||
const hasDateOrPeriodCue = /(?:на\s+дат|по\s+состоянию|на\s+конец|за\s+(?:\d{4}|январ|феврал|март|апрел|май|июн|июл|август|сентябр|октябр|ноябр|декабр)|на\s+(?:январ|феврал|март|апрел|май|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?|квартал|месяц|год|период|\b\d{4}[./-]\d{2}[./-]\d{2}\b)/iu.test(text);
|
||
return hasDateOrPeriodCue || /(?:сколько|скока|скок)/iu.test(text);
|
||
}
|
||
function hasPeriodCoverageProfileSignal(text) {
|
||
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;
|
||
}
|
||
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) {
|
||
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;
|
||
}
|
||
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) {
|
||
if (hasLifecycleSegmentationSignal(text)) {
|
||
return false;
|
||
}
|
||
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 hasLifecycleSegmentationSignal(text) {
|
||
return /(?:вперв|нов(?:ые|ых|ые\s+контрагент|ые\s+клиент|ые\s+заказчик)|исчез|ушед|ушл|пропал|отвал|только\s+один\s+раз|ровно\s+один\s+раз|однораз|дольше\s+всех|дольше\s+всего|долгожив|самые\s+старые|старые\s+по\s+сотрудничеству|регуляр|эпизодич|разов(?:ые|ой|ые\s+поставщик)|давно\s+не\s+использ|неиспольз|потом\s+перестал)/iu.test(text);
|
||
}
|
||
function hasCounterpartyDebtLongevitySignal(text) {
|
||
const hasCounterpartyLexeme = /(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?|контрагент(?:ов|а|ы)?|customer(?:s)?|client(?:s)?|counterpart(?:y|ies)|buyer(?:s)?)/iu.test(text);
|
||
const hasDebtLexeme = /(?:долг(?:и|ов|а|у)?|задолж(?:енность|енности|енностям|ал|али)?|просроч|хвост)/iu.test(text);
|
||
const hasLongevityCue = /(?:долгожив|долгожител|несколько\s+месяц|по\s+годам|дольше|лет|год(?:ам|а|у|ы)?|на\s+этот\s+момент|длительн)/iu.test(text);
|
||
return hasCounterpartyLexeme && hasDebtLexeme && hasLongevityCue;
|
||
}
|
||
function hasCounterpartyActivityLifecycleSignal(text) {
|
||
if (hasCustomerRevenueAndPaymentsSignal(text) || hasSupplierPayoutsProfileSignal(text)) {
|
||
return false;
|
||
}
|
||
const hasActivityAgeCue = /(?:сколько\s+лет\s+активности|сколько\s+лет\s+в\s+базе|возраст\s+активности|перв(?:ая|ый|ое)\s+(?:активность|платеж|поступление|документ)|последн(?:яя|ий|ее)\s+активность|с\s+какого\s+года\s+актив)/iu.test(text);
|
||
const hasActivityAgeAnchor = /(?:компан|контрагент|организац|ооо|ао|зао|ип|по\s+[a-zа-я0-9"«»().,_-]{3,}|в\s+базе\s+1с|в\s+1с\s+базе)/iu.test(text);
|
||
if (hasActivityAgeCue && hasActivityAgeAnchor) {
|
||
return true;
|
||
}
|
||
const hasPaymentRiskLexeme = /(?:не\s+плат(?:ит|ят|ил|или)|без\s+оплат|оплат(?:ы|а)?\s+нет|нет\s+оплат|задерж(?:ива|к)|просроч|задолж|\bдолг(?:и|ов|а|у)?\b)/iu.test(text);
|
||
if (hasPaymentRiskLexeme) {
|
||
return false;
|
||
}
|
||
if ((hasDocumentSignal(text) || hasBankOperationSignal(text)) && !hasLifecycleSegmentationSignal(text)) {
|
||
return false;
|
||
}
|
||
if (hasAny(text, COUNTERPARTY_ACTIVITY_LIFECYCLE_HINTS)) {
|
||
return true;
|
||
}
|
||
if (/(?:сколько|скока|скок)\s+/iu.test(text) && !hasLifecycleSegmentationSignal(text)) {
|
||
return false;
|
||
}
|
||
const hasCounterpartyLexeme = /(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?|контрагент(?:ов|а|ы)?|поставщик(?:ов|а|и)?|customer(?:s)?|client(?:s)?|counterpart(?:y|ies)|supplier(?:s)?|vendor(?: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 hasImplicitCounterpartyQuestion = /(?:кто\s+с\s+нами|кто\s+у\s+нас|всех?\s+зак(?:ов|а|и)?|все\s+заки|кто\s+нов(?:ые|ых|ый)\b|кто\s+был\s+активен|самые\s+старые\s+по\s+сотрудничеству)/iu.test(text);
|
||
const hasListWithWindow = hasCounterpartyLexeme && hasListVerb && hasTimeWindowLexeme;
|
||
if (hasListWithWindow) {
|
||
return true;
|
||
}
|
||
if (hasCounterpartyLexeme && hasListVerb && hasRosterQualifier) {
|
||
return true;
|
||
}
|
||
if (hasCounterpartyLexeme && hasLifecycleSegmentationSignal(text)) {
|
||
return true;
|
||
}
|
||
if (hasImplicitCounterpartyQuestion && (hasLifecycleSegmentationSignal(text) || hasTimeWindowLexeme || hasActivityLexeme)) {
|
||
return true;
|
||
}
|
||
if (!hasCounterpartyLexeme && hasListVerb && hasLifecycleSegmentationSignal(text) && /\bкто\b/iu.test(text)) {
|
||
return true;
|
||
}
|
||
return hasCounterpartyLexeme && hasActivityLexeme && (hasTimeWindowLexeme || hasListVerb);
|
||
}
|
||
function hasCounterpartyShipmentItemFlowSignal(text) {
|
||
const hasNamedTailAfterShipmentCue = /(?:отгруж(?:ал|али|ено)|постав(?:лял|ляли|ил|или)|привоз(?:ил|или)|продал)\s+[a-zа-яё][a-zа-яё0-9._-]{2,}/iu.test(text);
|
||
const hasPartySignal = hasPartyAnchorMention(text) ||
|
||
hasLooseByAnchorMention(text) ||
|
||
hasImplicitCounterpartyAnchorAroundDocs(text) ||
|
||
hasHeuristicCounterpartyAnchor(text);
|
||
if (!hasPartySignal && !hasNamedTailAfterShipmentCue) {
|
||
return false;
|
||
}
|
||
const hasInboundShipmentCue = /(?:что\s+нам\s+(?:отгруж(?:ал|али|ено)|постав(?:лял|ляли|ил|или)|привоз(?:ил|или)|продал)|кто\s+нам\s+постав(?:лял|ил)|что\s+постав(?:лял|или)\s+нам|что\s+нам\s+поставили)/iu.test(text);
|
||
const hasItemOrServiceCue = /(?:како(?:й|е|го|му)\s+товар|каки(?:е|х)\s+товар|какую\s+услуг|какие\s+услуг|товар\s+или\s+услуг|позици(?:ю|и|ях)?)/iu.test(text);
|
||
return hasInboundShipmentCue || hasItemOrServiceCue;
|
||
}
|
||
function hasContractUsageOverviewSignal(text) {
|
||
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;
|
||
}
|
||
if (/(?:какие\s+(?:договор|контракт)(?:ы|а)?).*(?:давно\s+не\s+использ|неиспольз|протух|мертв|мёртв|stale|unused)/iu.test(text)) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
function hasCustomerRevenueAndPaymentsSignal(text) {
|
||
if (hasAny(text, CUSTOMER_REVENUE_AND_PAYMENTS_HINTS)) {
|
||
return true;
|
||
}
|
||
if (hasContractAnchorSignal(text)) {
|
||
return false;
|
||
}
|
||
const hasFuzzyCustomerLexeme = hasFuzzyLexeme(text, ["клиент", "заказчик", "покупател", "customer", "client"]);
|
||
const hasFuzzySupplierLexeme = hasFuzzyLexeme(text, ["поставщик", "supplier", "vendor"]);
|
||
const hasCounterpartyLexeme = /(?:контрагент(?:ов|а|ы)?|counterpart(?:y|ies)|компан(?:и|ия|ии|ию)|организац(?:и|ия|ии|ию)|partner(?:s)?)/iu.test(text);
|
||
const hasSpecificCounterpartyAnchor = hasLooseByAnchorMention(text) ||
|
||
hasHeuristicCounterpartyAnchor(text) ||
|
||
/(?:по\s+(?:клиент(?:у|а)?|заказчик(?:у|а)?|покупател(?:ю|я)|customer|client)\s+[a-zа-яё0-9])/iu.test(text);
|
||
const asksWhoPays = /(?:кто\s+(?:нам\s+)?(?:(?:больше|чаще)\s+)?плат(?:ит|ят)?)/iu.test(text);
|
||
const asksCustomerGroup = /(?:клиент(?:ов|а|ы)?|заказчик(?:ов|а|и)?|покупател(?:ей|я|и)?|customer(?:s)?|client(?:s)?)/iu.test(text) ||
|
||
hasFuzzyCustomerLexeme ||
|
||
asksWhoPays;
|
||
const asksCounterpartySource = /(?:с\s+каких|от\s+каких|от\s+кого|from\s+which|from\s+who)/iu.test(text);
|
||
const asksIncomingFlow = /(?:приход|поступлен|входящ|зачислен|inflow|incoming)/iu.test(text);
|
||
const asksWhoBringsMostMoney = /(?:кто\s+(?:нам\s+)?(?:больше\s+всего|сам(?:ый|ая|ое|ые)|наибольш(?:ий|ая|ее|ие))\s+(?:прин[её]с|зан[её]с).*(?:деньг|денег))/iu.test(text);
|
||
const asksWhoBringsMoneyLoose = /(?:кто\s+(?:нам\s+)?(?:больше|больше\s+всех|больше\s+всего).*(?:деньг|денег|доход|выручк).*(?:прин[её]с|зан[её]с))/iu.test(text) ||
|
||
/(?:кто\s+(?:нам\s+)?(?:прин[её]с|зан[её]с).*(?:больше|больше\s+всех|больше\s+всего).*(?:деньг|денег|доход|выручк))/iu.test(text);
|
||
const asksLiquidityRanking = /(?:ликвидн|liquid)/iu.test(text) &&
|
||
(asksCustomerGroup || hasCounterpartyLexeme || /(?:клиент|заказчик|контрагент|customer|client|counterpart)/iu.test(text));
|
||
const asksProfitableYears = /(?:доходн|выручк|оборот|прибыл|revenue|turnover).*(?:год|года|годы|year|years)/iu.test(text) &&
|
||
/(?:сам(?:ый|ая|ое|ые)|топ|луч|max|best|наибольш|больше)/iu.test(text);
|
||
const asksDealBudgetRanking = /(?:сделк|deal|бюджет)/iu.test(text) &&
|
||
/(?:топ|top|сам(?:ый|ая|ое|ые)|крупн|мален|жирн|мелк|больше\s+всего|чаще\s+всего|наибольш|максимальн|минимальн)/iu.test(text);
|
||
const asksRevenueTotal = /(?:сколько|скока|скок).*(?:денег|выручк|доход|заработ|оборот)/iu.test(text);
|
||
const asksOverallTurnover = /(?:общ(?:ий|ие|ая)\s+оборот|общ(?:ая|ий)\s+выручк|total\s+turnover|turnover\s+total)/iu.test(text);
|
||
const asksMajorShare = /(?:основн(?:ую|ая|ые|ой)\s+част|больш(?:ую|ая|ие)\s+част|львин(?:ая|ую)\s+дол[яю]|ключев(?:ую|ая)\s+част)/iu.test(text);
|
||
const asksValue = /(?:доходн|выручк|приход|поступлен|входящ|зачислен|оплат|плат(?:еж|ёж|ежн|ежей|ежа|ит|ят)|деньг|денег|заработ|оборот|чек|сделк|бюджет|занес|занёс|принес|принёс|ликвидн|revenue|inflow|deal|turnover|liquid)/iu.test(text);
|
||
const asksRankOrTop = /(?:топ|top|сам(?:ый|ая|ое|ые)|крупн|мален|жирн|мелк|больше\s+всего|чаще\s+всего|наибольш|максимальн)/iu.test(text);
|
||
const asksCountOnly = /(?:сколько|скока|скок)\s+/iu.test(text) && !asksValue;
|
||
if (asksCountOnly) {
|
||
return false;
|
||
}
|
||
if (hasSpecificCounterpartyAnchor && !asksRankOrTop) {
|
||
return false;
|
||
}
|
||
if (asksCustomerGroup && (asksValue || asksRankOrTop)) {
|
||
return true;
|
||
}
|
||
if (!hasFuzzySupplierLexeme && hasCounterpartyLexeme && asksRankOrTop && (asksValue || asksWhoPays)) {
|
||
return true;
|
||
}
|
||
if (!hasFuzzySupplierLexeme && asksWhoPays && (asksRankOrTop || hasCounterpartyLexeme)) {
|
||
return true;
|
||
}
|
||
if (!hasFuzzySupplierLexeme && asksWhoBringsMostMoney) {
|
||
return true;
|
||
}
|
||
if (!hasFuzzySupplierLexeme && asksWhoBringsMoneyLoose) {
|
||
return true;
|
||
}
|
||
if (!hasFuzzySupplierLexeme && asksLiquidityRanking) {
|
||
return true;
|
||
}
|
||
if (!hasFuzzySupplierLexeme && asksProfitableYears) {
|
||
return true;
|
||
}
|
||
if (!hasFuzzySupplierLexeme && (asksRevenueTotal || asksOverallTurnover)) {
|
||
return true;
|
||
}
|
||
if (asksCounterpartySource && asksValue) {
|
||
return true;
|
||
}
|
||
if (!hasFuzzySupplierLexeme && (asksCustomerGroup || hasCounterpartyLexeme) && asksMajorShare && asksValue) {
|
||
return true;
|
||
}
|
||
if (!hasFuzzySupplierLexeme && asksIncomingFlow && asksRankOrTop) {
|
||
return true;
|
||
}
|
||
if (!hasFuzzySupplierLexeme && asksDealBudgetRanking) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
function hasSupplierPayoutsProfileSignal(text) {
|
||
if (hasAny(text, SUPPLIER_PAYOUTS_PROFILE_HINTS)) {
|
||
return true;
|
||
}
|
||
if (hasContractAnchorSignal(text)) {
|
||
return false;
|
||
}
|
||
const hasFuzzySupplierLexeme = hasFuzzyLexeme(text, ["поставщик", "supplier", "vendor"]);
|
||
const hasSpecificCounterpartyAnchor = hasLooseByAnchorMention(text) ||
|
||
hasHeuristicCounterpartyAnchor(text) ||
|
||
/(?:по\s+(?:поставщик(?:у|а)?|supplier|vendor)\s+[a-zа-яё0-9])/iu.test(text);
|
||
const asksSupplierGroup = /(?:поставщик(?:ов|а|и)?|supplier(?:s)?|vendor(?:s)?|к[ао]му\s+мы)/iu.test(text) ||
|
||
hasFuzzySupplierLexeme ||
|
||
/(?:кому\s+ушло|кому\s+платили|кому\s+заплатили)/iu.test(text);
|
||
const asksPayoutValue = /(?:выплат|исходящ|списан|заплат|ушло|сгрузил|сгрузили|перевел|перевёл|отдали|платеж|платёж|outflow|payout)/iu.test(text);
|
||
const asksRankOrTop = /(?:топ|top|сам(?:ый|ая|ое|ые)|крупн|больше\s+всего|чаще\s+всего|максимальн|наибольш)/iu.test(text);
|
||
const asksCountOnly = /(?:сколько|скока|скок)\s+/iu.test(text) && !asksPayoutValue;
|
||
if (asksCountOnly) {
|
||
return false;
|
||
}
|
||
if (hasSpecificCounterpartyAnchor && !asksRankOrTop) {
|
||
return false;
|
||
}
|
||
return asksSupplierGroup && (asksPayoutValue || asksRankOrTop);
|
||
}
|
||
function hasContractUsageAndValueSignal(text) {
|
||
if (hasAny(text, CONTRACT_USAGE_AND_VALUE_HINTS)) {
|
||
return true;
|
||
}
|
||
if (!/(?:договор(?:ов|а|ы)?|контракт(?:ов|а|ы|у|ом|е)?|contract(?:s)?)/iu.test(text)) {
|
||
return false;
|
||
}
|
||
if (hasContractUsageOverviewSignal(text)) {
|
||
return false;
|
||
}
|
||
const asksStructure = /(?:нескольк(?:ими|их|ие|о)?\s+(?:договор|контракт)|мультидоговор|контрагент(?:ов|ы)?.*нескольк(?:ими|их|ие|о)\s+(?:договор|контракт)|какие\s+(?:договор|контракт)(?:ы|а)?\s+активн|рабоч(?:ие|их)\s+(?:договор|контракт))/iu.test(text);
|
||
const asksValue = /(?:оборот|бюджет|сумм|стоим|value|turnover|amount|revenue|крупн|мелк|миним|максим)/iu.test(text);
|
||
const asksRank = /(?:топ|top|ранк|rank|сам(?:ый|ая|ое|ые))/iu.test(text);
|
||
return asksStructure || asksValue || asksRank;
|
||
}
|
||
function hasContractListByCounterpartySignal(text) {
|
||
const hasContractLexeme = /(?:договор(?:а|у|ом|е|ы)?|контракт(?:а|у|ом|е|ы)?|contracts?|contract)/iu.test(text);
|
||
if (!hasContractLexeme) {
|
||
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;
|
||
}
|
||
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) {
|
||
const hasAccountLexeme = hasAccountNumberAnchor(text) || hasCompactAccountCodeToken(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);
|
||
}
|
||
function hasOpenContractsListSignal(text) {
|
||
const hasContractLexeme = text.includes("договор") || text.includes("контракт") || text.includes("contract") || text.includes("dogovor");
|
||
const hasOpenLexeme = /(?:незакрыт|не\s+закрыт|открыт|open|unclosed)/iu.test(text);
|
||
if (!hasContractLexeme || !hasOpenLexeme) {
|
||
return false;
|
||
}
|
||
// Query about a specific contract should stay in open-items lane.
|
||
if (hasContractNumberLikeToken(text)) {
|
||
return false;
|
||
}
|
||
// Debt/tail wording indicates open-items intent, not contract list.
|
||
if (/(?:долг|задолж|хвост|позиц|open\s+items|unclosed\s+items|взаиморасчет|взаиморасчёт)/iu.test(text)) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
function hasSupplierTailRiskSignal(text) {
|
||
const hasSupplier = /(?:поставщик|supplier|vendor)/iu.test(text);
|
||
const hasTail = /(?:хвост|висят|незакрыт|не\s+закрыв|задолж|долг|просроч|сч[её]т)/iu.test(text);
|
||
const hasRisk = /(?:систематич|регулярн|проблем|тревог|не\s+разов|больше\s+похож)/iu.test(text);
|
||
const hasPeriodCue = /(?:на\s+конец\s+(?:месяц|период)|конец\s+месяц|пару\s+месяц|несколько\s+месяц|больше\s+месяц)/iu.test(text);
|
||
return hasSupplier && hasTail && (hasRisk || hasPeriodCue);
|
||
}
|
||
function hasPayablesDebtLifecycleSignal(text) {
|
||
const hasOweSignal = /(?:кому\s+мы\s+долж(?:ен|ны|эны|эна|эно)?|мы\s+долж(?:ен|ны|эны|эна|эно)?|кому\s+долж(?:ен|ны|эны|эна|эно)?|долж[нэ](?:ы|а|о)?\s+(?:заплат|оплат|перечис)|к\s+оплате|на\s+оплату|who\s+we\s+owe|owe\s+to|payables?|кредитор(?:[а-яё]{0,6})?)/iu.test(text);
|
||
if (!hasOweSignal) {
|
||
return false;
|
||
}
|
||
const hasPastPaymentSignal = /(?:заплатил(?:и)?|платил(?:и)?|кому\s+ушло|выплатил(?:и)?|списан|outflow|payout)/iu.test(text);
|
||
const hasTopRankingSignal = /(?:топ|top|больше\s+всего|сам(?:ый|ая|ое|ые)|наибольш|максимальн)/iu.test(text);
|
||
if (hasPastPaymentSignal && hasTopRankingSignal) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
function hasReceivablesDebtLifecycleSignal(text) {
|
||
const hasOweUsSignal = /(?:кто\s+нам\s+долж(?:ен|ны|эны|эна|эно)?|кто\s+долж(?:ен|ны|эны|эна|эно)?\s+нам|нам\s+долж(?:ен|ны|эны|эна|эно)?|должник(?:[а-яё]{0,6})?|дебитор(?:[а-яё]{0,6})?|дебиторск(?:[а-яё]{0,6})?|задолж|долг(?:и|ов|а|у)?|к\s+получению|на\s+поступление|к\s+взысканию|who\s+owes\s+us|receivables?)/iu.test(text);
|
||
if (!hasOweUsSignal) {
|
||
return false;
|
||
}
|
||
const hasPastInflowSignal = /(?:прин[её]с|зан[её]с|поступил|приход|inflow|paid\s+us|already\s+paid)/iu.test(text);
|
||
const hasTopRankingSignal = /(?:топ|top|больше\s+всего|сам(?:ый|ая|ое|ые)|наибольш|максимальн)/iu.test(text);
|
||
if (hasPastInflowSignal && hasTopRankingSignal) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
function hasReceivablesLatencyRiskSignal(text) {
|
||
const hasBuyer = /(?:покупател|клиент|заказчик|customer|buyer)/iu.test(text);
|
||
const hasCounterparty = /(?:контрагент|counterparty|partner)/iu.test(text);
|
||
const hasPayment = /(?:оплат|платеж|платёж|payment)/iu.test(text);
|
||
const hasShipment = /(?:отправк|отгруз|реализ|shipment|delivery)/iu.test(text);
|
||
const hasDelay = /(?:длинн|долг|просроч|задерж|висят|тревог|too\s+long|late)/iu.test(text);
|
||
const hasOverdueDeadlineCue = /(?:срок(?:и|а)?(?:\s+оплат[ыы]?)?[\s\S]{0,24}(?:прош|выш|истек|истёк)|срок(?:и|а)?\s+давно\s+прошл|давно\s+пора\s+оплат|давно\s+не\s+оплач)/iu.test(text);
|
||
const hasNonPayment = /(?:не\s+плат(?:ит|ят|ил|или)|не\s+оплат|не\s+оплач|без\s+оплат|оплат(?:ы|а)?\s+нет|нет\s+оплат|неоплач)/iu.test(text);
|
||
const hasPaymentShipmentImbalance = /(?:оплач(?:ено|ен[аоы]?|ивать|ивать)?\s+меньше[\s\S]{0,36}отгруж|недоплат[\s\S]{0,36}отгруж|отгруж[\s\S]{0,36}оплач(?:ено|ено\s+меньше))/iu.test(text);
|
||
const hasNegativeSaldoRisk = /(?:сальд[оа]\s+(?:уже\s+)?отрицат|минусов(?:ое|ой)\s+сальдо|сальдо\s+в\s+минус)/iu.test(text);
|
||
const hasPeriodOrRiskCue = /(?:за\s+текущ|на\s+конец|тревог|просроч|задерж|долг|длинн|несколько\s+месяц|больше\s+месяц)/iu.test(text) ||
|
||
hasOverdueDeadlineCue ||
|
||
hasNegativeSaldoRisk;
|
||
const hasBetweenShipmentAndPayment = /между[\s\S]{0,80}(?:отправк|отгруз|реализ)[\s\S]{0,80}(?:оплат|платеж|платёж|payment)/iu.test(text);
|
||
if (hasBuyer &&
|
||
hasPayment &&
|
||
((hasShipment && (hasDelay || hasOverdueDeadlineCue)) || hasBetweenShipmentAndPayment || hasPaymentShipmentImbalance)) {
|
||
return true;
|
||
}
|
||
if ((hasBuyer || hasCounterparty) && hasPaymentShipmentImbalance) {
|
||
return true;
|
||
}
|
||
return (hasBuyer || hasCounterparty) && hasNonPayment && hasPeriodOrRiskCue;
|
||
}
|
||
function hasSettlementGapSignal(text) {
|
||
const hasPayment = /(?:платеж|платёж|оплат|списани|поступлен|payment)/iu.test(text);
|
||
const hasDocument = /(?:док(?:и|умент|ументы|ументов)|docs?|documents?)/iu.test(text);
|
||
const hasShipment = /(?:отгруз|реализ|shipment|delivery|товар|услуг)/iu.test(text);
|
||
const hasAdvance = /(?:аванс|предоплат)/iu.test(text);
|
||
const hasClosureLexeme = /(?:закрыти|взаиморасч|акт|сч[её]т(?:ов|а|ы)?)/iu.test(text);
|
||
const hasNoDocumentForClosing = /(?:нет|без)\s+(?:док(?:и|умент|ументы|ументов)|закрывающ)/iu.test(text) &&
|
||
hasClosureLexeme;
|
||
const hasNoDocumentForClosingReversed = /(?:док(?:и|умент|ументы|ументов)|закрывающ)[\s\S]{0,48}(?:нет|без)/iu.test(text) &&
|
||
hasClosureLexeme;
|
||
const hasNoPayments = /(?:нет|без)\s+(?:оплат|платеж|платёж|payment)/iu.test(text) ||
|
||
/(?:оплат|платеж|платёж|payment)\s+нет/iu.test(text);
|
||
const hasDocsWithoutPayments = hasDocument && hasNoPayments;
|
||
const hasPaymentsWithoutClosingDocs = hasPayment && (hasNoDocumentForClosing || hasNoDocumentForClosingReversed);
|
||
const hasPaymentsWithoutSettlementClosure = hasPayment &&
|
||
/(?:без|нет)\s+закрыти(?:я|й)?(?:\s+взаиморасч[её]тов)?/iu.test(text) &&
|
||
hasClosureLexeme;
|
||
const hasShipmentWithoutClosingDocs = hasShipment &&
|
||
(hasNoDocumentForClosing ||
|
||
hasNoDocumentForClosingReversed ||
|
||
/(?:без|нет)\s+док(?:и|умент(?:ов|ы|а)?)\s+(?:для\s+)?(?:их\s+)?закрыти/u.test(text));
|
||
const hasClosingWithoutSupportingDocs = hasClosureLexeme &&
|
||
/(?:без|нет)\s+подтверждающ(?:их|его|ие)?\s+док(?:и|умент(?:ов|ы|а)?)/iu.test(text);
|
||
const hasAdvanceStuckRisk = /(?:зависш(?:ий|ие|ая|ие\s+аванс)|давно\s+пора\s+закрыть|пора\s+закрывать|перепривяз(?:ать|к)|списыв(?:ать|ани|ан)|нереальн)/iu.test(text);
|
||
const hasUnclosedAdvanceGap = hasAdvance &&
|
||
(/(?:не\s+закрыт|незакрыт|долго\s+не\s+закрыт|давно\s+не\s+закрыт|давно\s+пора\s+закрыть)/iu.test(text) ||
|
||
hasAdvanceStuckRisk ||
|
||
hasNoDocumentForClosing ||
|
||
hasNoDocumentForClosingReversed);
|
||
return (hasPaymentsWithoutClosingDocs ||
|
||
hasPaymentsWithoutSettlementClosure ||
|
||
hasDocsWithoutPayments ||
|
||
hasShipmentWithoutClosingDocs ||
|
||
hasClosingWithoutSupportingDocs ||
|
||
hasUnclosedAdvanceGap);
|
||
}
|
||
function hasReconciliationMismatchSignal(text) {
|
||
const hasCounterparty = /(?:контрагент|поставщик|клиент|покупател|customer|supplier|counterparty)/iu.test(text);
|
||
const hasReconciliationLexeme = /(?:акт(?:а|ом|ах)?\s+свер(?:к|ок)|свер(?:к|ок))/iu.test(text);
|
||
const hasMismatchLexeme = /(?:не\s+совпад|несовпад|расхожд|расход|не\s+сход|несход|разъех|разниц|не\s+бь[её]т)/iu.test(text);
|
||
const hasBalanceLexeme = /(?:сальд|остат|баланс|saldo|balance)/iu.test(text);
|
||
const hasLookupVerb = /(?:покажи|выведи|найд[иь]|show|list)/iu.test(text);
|
||
const hasInterrogativeLookup = /(?:по\s+каким|у\s+кого|какие|какой|кто|где)/iu.test(text);
|
||
return (hasCounterparty &&
|
||
hasReconciliationLexeme &&
|
||
hasMismatchLexeme &&
|
||
hasBalanceLexeme &&
|
||
(hasLookupVerb || hasInterrogativeLookup));
|
||
}
|
||
function isLikelyCounterpartyToken(rawToken) {
|
||
const token = String(rawToken ?? "").trim().toLowerCase();
|
||
if (!token || token.length < 2) {
|
||
return false;
|
||
}
|
||
if (/^\d+$/.test(token)) {
|
||
return false;
|
||
}
|
||
if (/^(?:19|20)\d{2}$/.test(token)) {
|
||
return false;
|
||
}
|
||
const stopWords = new Set([
|
||
"за",
|
||
"с",
|
||
"по",
|
||
"на",
|
||
"и",
|
||
"или",
|
||
"док",
|
||
"доки",
|
||
"доки?",
|
||
"документ",
|
||
"документы",
|
||
"документов",
|
||
"документами",
|
||
"документу",
|
||
"документе",
|
||
"документа",
|
||
"документах",
|
||
"докам",
|
||
"доками",
|
||
"количество",
|
||
"количеству",
|
||
"количества",
|
||
"количеством",
|
||
"активный",
|
||
"активного",
|
||
"активности",
|
||
"пассивный",
|
||
"пассивного",
|
||
"пассивности",
|
||
"наименее",
|
||
"минимальный",
|
||
"минимум",
|
||
"реже",
|
||
"редкий",
|
||
"банк",
|
||
"банковские",
|
||
"операции",
|
||
"платежи",
|
||
"платеж",
|
||
"платёж",
|
||
"контрагент",
|
||
"контрагенту",
|
||
"контрагента",
|
||
"компания",
|
||
"компании",
|
||
"организация",
|
||
"организации",
|
||
"год",
|
||
"года",
|
||
"г",
|
||
"плс",
|
||
"pls",
|
||
"пж",
|
||
"пжлст",
|
||
"пожалуйста",
|
||
"есть",
|
||
"же",
|
||
"сводные",
|
||
"сводный",
|
||
"сводная",
|
||
"сводную",
|
||
"сводном",
|
||
"сводного",
|
||
"сводному",
|
||
"неуказанному",
|
||
"неуказанный",
|
||
"неуказанная",
|
||
"неуказанное",
|
||
"указанному",
|
||
"указанный",
|
||
"указанная",
|
||
"указанное",
|
||
"объекту",
|
||
"объект",
|
||
"бля",
|
||
"блять",
|
||
"епт",
|
||
"ёпт",
|
||
"епта",
|
||
"нах",
|
||
"нахуй",
|
||
"связанным",
|
||
"связанные",
|
||
"связанных",
|
||
"связанному",
|
||
"related",
|
||
"linked",
|
||
"этомуже",
|
||
"томуже"
|
||
]);
|
||
return !stopWords.has(token);
|
||
}
|
||
function hasPartyAnchorMention(text) {
|
||
return (text.includes("контраг") ||
|
||
text.includes("контра") ||
|
||
text.includes("counterparty") ||
|
||
text.includes("компан") ||
|
||
text.includes("company") ||
|
||
text.includes("организац") ||
|
||
text.includes("supplier") ||
|
||
text.includes("vendor") ||
|
||
text.includes("customer") ||
|
||
text.includes("client") ||
|
||
text.includes("partner") ||
|
||
text.includes("поставщик") ||
|
||
text.includes("клиент") ||
|
||
text.includes("покупател") ||
|
||
text.includes("партнер"));
|
||
}
|
||
function hasContractAnchorMention(text) {
|
||
return (text.includes("договор") ||
|
||
text.includes("контракт") ||
|
||
/\bдог\.?\b/iu.test(text) ||
|
||
text.includes("дог.") ||
|
||
text.includes("contract") ||
|
||
text.includes("dogovor"));
|
||
}
|
||
function hasContractNumberLikeToken(text) {
|
||
if (/(?:^|[\s([{])(?:№|#|n)\s*[a-zа-яё0-9][a-zа-яё0-9./_-]{1,}(?=$|[\s,.;:!?)\]}])/iu.test(text)) {
|
||
return true;
|
||
}
|
||
const rawTokens = text
|
||
.split(/[\s,;:!?()[\]{}"«»]+/u)
|
||
.map((token) => token.replace(/^[^\p{L}\p{N}#№]+|[^\p{L}\p{N}./_-]+$/gu, "").trim())
|
||
.filter((token) => token.length > 0);
|
||
for (const rawToken of rawTokens) {
|
||
const token = String(rawToken ?? "").trim();
|
||
if (!/^\d{1,6}[./_-]\d{1,6}(?:[./_-]\d{1,6})?$/u.test(token)) {
|
||
continue;
|
||
}
|
||
if (!token) {
|
||
continue;
|
||
}
|
||
if (/^\d{1,2}\.\d{1,2}$/u.test(token)) {
|
||
// Likely an account code like 60.01/51.00, not a contract number.
|
||
continue;
|
||
}
|
||
const parts = token.split(/[./_-]+/u).map((part) => Number(part));
|
||
if (!parts.every((part) => Number.isFinite(part))) {
|
||
return true;
|
||
}
|
||
if (parts.length === 2) {
|
||
const [a, b] = parts;
|
||
const yearFirst = a >= 1900 && a <= 2099 && b >= 1 && b <= 12;
|
||
const yearSecond = b >= 1900 && b <= 2099 && a >= 1 && a <= 12;
|
||
if (yearFirst || yearSecond) {
|
||
continue;
|
||
}
|
||
return true;
|
||
}
|
||
if (parts.length === 3) {
|
||
const [a, b, c] = parts;
|
||
const ymd = a >= 1900 && a <= 2099 && b >= 1 && b <= 12 && c >= 1 && c <= 31;
|
||
const dmy = c >= 1900 && c <= 2099 && a >= 1 && a <= 31 && b >= 1 && b <= 12;
|
||
if (ymd || dmy) {
|
||
continue;
|
||
}
|
||
return true;
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
function hasContractAnchorSignal(text) {
|
||
if (hasContractAnchorMention(text)) {
|
||
return true;
|
||
}
|
||
// Allow short forms like "19/15" for follow-up prompts if document/bank signal exists.
|
||
return hasContractNumberLikeToken(text) && hasDocsOrBankSignal(text);
|
||
}
|
||
function hasLooseByAnchorMention(text) {
|
||
const match = text.match(/(?:^|\s)по\s+([a-zа-яё][a-zа-яё0-9._-]{1,})(?=[\s,.;:!?)]|$)/iu);
|
||
if (!match) {
|
||
return false;
|
||
}
|
||
const token = String(match[1] ?? "").toLowerCase();
|
||
if (!token) {
|
||
return false;
|
||
}
|
||
const stopWords = new Set([
|
||
"контрагенту",
|
||
"контрагента",
|
||
"контре",
|
||
"компании",
|
||
"компанию",
|
||
"организации",
|
||
"организацию",
|
||
"поставщику",
|
||
"поставщика",
|
||
"клиенту",
|
||
"клиента",
|
||
"покупателю",
|
||
"покупателя",
|
||
"партнеру",
|
||
"партнера",
|
||
"договору",
|
||
"договора",
|
||
"счету",
|
||
"счёту",
|
||
"дате",
|
||
"периоду",
|
||
"период",
|
||
"документам",
|
||
"докам",
|
||
"количество",
|
||
"количеству",
|
||
"количества",
|
||
"количеством",
|
||
"активности",
|
||
"пассивности",
|
||
"наименее",
|
||
"минимум"
|
||
]);
|
||
return !stopWords.has(token);
|
||
}
|
||
function hasImplicitCounterpartyAnchorAroundDocs(text) {
|
||
const beforeDocsMatch = text.match(/(?:^|\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?|doki|docy|doci)\s+(?:по\s+)?([a-zа-яё][a-zа-яё0-9._-]{1,})(?=[\s,.;:!?)]|$)/iu);
|
||
if (afterDocsMatch && isLikelyCounterpartyToken(String(afterDocsMatch[1] ?? ""))) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
function hasDocsOrBankSignal(text) {
|
||
return /(?:док(?:и|умент|ументы|ументов)|docs?|documents?|doki|docy|doci|банк|выписк|платеж|платёж|оплат|transactions?|bank\s+ops|bank\s+operations?)/iu.test(text);
|
||
}
|
||
function hasBankOperationSignal(text) {
|
||
return hasAny(text, BANK_OPERATION_CORE_HINTS) || hasAny(text, BANK_OPERATIONS_BY_COUNTERPARTY_HINTS) || hasAny(text, BANK_OPERATIONS_BY_CONTRACT_HINTS);
|
||
}
|
||
function hasDocumentSignal(text) {
|
||
return (text.includes("док") ||
|
||
text.includes("доки") ||
|
||
text.includes("документ") ||
|
||
text.includes("doki") ||
|
||
text.includes("docy") ||
|
||
text.includes("doci") ||
|
||
text.includes("docs") ||
|
||
text.includes("documents"));
|
||
}
|
||
function hasHeuristicCounterpartyAnchor(text) {
|
||
if (!hasDocsOrBankSignal(text) && !hasBankOperationSignal(text)) {
|
||
return false;
|
||
}
|
||
const tokens = String(text ?? "")
|
||
.split(/[^a-zа-яё0-9._-]+/iu)
|
||
.map((item) => item.trim())
|
||
.filter((item) => item.length > 0);
|
||
for (const token of tokens) {
|
||
const lowered = token.toLowerCase();
|
||
if (!isLikelyCounterpartyToken(lowered)) {
|
||
continue;
|
||
}
|
||
if (/^\d{2}$/.test(lowered) || /^\d{4}$/.test(lowered)) {
|
||
continue;
|
||
}
|
||
if (/(?:^за$|^for$|^from$|^to$|^по$|^с$|^год$|^года$|^г$|^year$)/iu.test(lowered)) {
|
||
continue;
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
function hasGenericAddressLookupSignal(text) {
|
||
return (/\bесть\b/iu.test(text) ||
|
||
/\bпокажи\b/iu.test(text) ||
|
||
/\bвыведи\b/iu.test(text) ||
|
||
/\bкакие\b/iu.test(text) ||
|
||
/\bчто(?:-|\s)?то\b/iu.test(text) ||
|
||
/за\s+любой\s+период/iu.test(text) ||
|
||
/за\s+вс[её]\s+время/iu.test(text) ||
|
||
/for\s+all\s+time/iu.test(text) ||
|
||
/all\s+time/iu.test(text));
|
||
}
|
||
function hasAccountNumberAnchor(text) {
|
||
return /(?:account|сч[её]т|счет)\D{0,12}\d{2}(?:[.,]\d{1,2})?/i.test(text);
|
||
}
|
||
function hasInventoryAccount41Anchor(text) {
|
||
return /(?:сч[её]т(?:а|е|у)?|счет(?:а|е|у)?)\D{0,12}41(?:[.,]0?1)?/iu.test(text) || /41(?:[.,]0?1)?\D{0,12}(?:сч[её]т(?:а|е|у)?|счет(?:а|е|у)?)/iu.test(text);
|
||
}
|
||
function hasInventoryAsOfCue(text) {
|
||
return /(?:сейчас|текущ|на\s+дату|по\s+состоянию|срез|на\s+конец|date|as\s+of|current|now|today)/iu.test(text);
|
||
}
|
||
function hasInventoryOnHandSignal(text) {
|
||
const hasColloquialStockSnapshotCue = /(?:что|ч[еёо])\s+(?:у\s+нас\s+)?на\s+склад(?:е|у|ом|ах)(?=$|[\s,.;:!?])/iu.test(text);
|
||
const hasStockStateCue = /(?:(?:что|ч[еёо])\s+там\s+на\s+склад(?:е|у|ом|ах)|(?:что|ч[еёо]).*происход(?:ит|ило|ящее).*(?:на\s+)?склад(?:е|у|ом|ах)|происход(?:ит|ило|ящее)\s+на\s+склад(?:е|у|ом|ах)|ситуац(?:ия|ии)\s+на\s+склад(?:е|у|ом|ах)|обстановк(?:а|и)\s+на\s+склад(?:е|у|ом|ах)|what(?:'s| is)?\s+(?:there\s+)?(?:on|in)\s+(?:the\s+)?(?:warehouse|stock)|what(?:'s| is)?\s+happening\s+(?:on|in)\s+(?:the\s+)?(?:warehouse|stock))/iu.test(text);
|
||
const hasAccount41Anchor = hasInventoryAccount41Anchor(text);
|
||
const hasStockLexeme = /(?:склад(?:е|у|ом|ы|ов)?|warehouse|stock(?:room)?|inventory|on[\s-]?hand)/iu.test(text);
|
||
if (!hasStockLexeme && !hasAccount41Anchor) {
|
||
return false;
|
||
}
|
||
if (hasInventoryProvenanceSignalV2(text) ||
|
||
hasInventoryPurchaseDocumentsSignalV2(text) ||
|
||
hasInventorySaleTraceSignalV2(text) ||
|
||
hasInventoryAgingSignal(text) ||
|
||
hasInventoryPurchaseToSaleChainSignal(text)) {
|
||
return false;
|
||
}
|
||
const hasGoodsLexeme = /(?:товар(?:ы|ов|ом|а|ные)?|номенклатур|материал(?:ы|ов|а|ам)?|item(?:s)?|sku|product(?:s)?)/iu.test(text);
|
||
const hasBalanceLexeme = /(?:леж(?:ит|ат)|есть|числ(?:ит(?:ся|сь)|ятся)|остат(?:ок|ки)|срез|на\s+дат|по\s+состоянию|на\s+конец|происход(?:ит|ило|ящее)|ситуац(?:ия|ии)|обстановк(?:а|и)|today|now|current|as\s+of)/iu.test(text);
|
||
const hasRequestCue = /(?:покажи|показать|выведи|дай|какие|что|ч[еёо]|какой|сколько|проверь|проверить|чекни|check|show|list|which|what)/iu.test(text);
|
||
if (hasAccount41Anchor && (hasGoodsLexeme || hasBalanceLexeme || hasRequestCue || hasInventoryAsOfCue(text))) {
|
||
return true;
|
||
}
|
||
return (hasGoodsLexeme || hasBalanceLexeme || hasColloquialStockSnapshotCue || hasStockStateCue) &&
|
||
(hasRequestCue || hasBalanceLexeme || hasColloquialStockSnapshotCue || hasStockStateCue);
|
||
}
|
||
function hasInventoryProvenanceSignal(text) {
|
||
return /(?:поставщик|закупк|происхожд|откуда|когда был куплен|активная закупк|purchase provenance|purchase date|supplier provenance|stock overlap)/iu.test(text);
|
||
}
|
||
function hasInventoryPurchaseDocumentsSignal(text) {
|
||
return /(?:по каким документам|документы закупки|purchase documents|documents of purchase|through which documents|chain of documents)/iu.test(text);
|
||
}
|
||
function hasInventorySaleTraceSignal(text) {
|
||
return /(?:продаж|покупател|buyer|sale trace|purchase[\s-]?to[\s-]?sale|purchase -> warehouse -> sale|закупка.*продаж)/iu.test(text);
|
||
}
|
||
function hasSelectedObjectInventoryCue(text) {
|
||
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+нему|по\s+ней|по\s+ним|по\s+нему\s+же|по\s+ней\s+же|selected\s+object)/iu.test(String(text ?? ""));
|
||
}
|
||
function hasSelectedObjectInventoryProvenanceSignal(text) {
|
||
return hasSelectedObjectInventoryCue(text) && (0, inventoryLifecycleCueHelpers_1.hasInventorySupplierCue)(text);
|
||
}
|
||
function hasSelectedObjectInventoryPurchaseDocumentsSignal(text) {
|
||
const hasPurchaseDocumentsCue = /(?:по\s+каким\s+документам\s+(?:это|его|этот\s+товар|эту\s+позицию)\s+купили|по\s+каким\s+документам\s+(?:был\s+)?куплен|какими\s+документами\s+(?:это|его|этот\s+товар|эту\s+позицию)\s+купили|какими\s+документами\s+(?:был\s+)?куплен|purchase\s+documents|documents\s+of\s+purchase|through\s+which\s+documents)/iu.test(text) ||
|
||
/(?:(?:по\s+каким|какими)\s+док[а-яё]*[\s\S]{0,80}(?:купил|куплен)|док(?:и|умент[а-яё]*)[\s\S]{0,80}(?:по\s+(?:ним|ней|нему|этой\s+позиции|этому\s+товару)|операци)|(?:по\s+(?:ним|ней|нему|этой\s+позиции|этому\s+товару))[\s\S]{0,80}док(?:и|умент[а-яё]*))/iu.test(text);
|
||
return (hasSelectedObjectInventoryCue(text) &&
|
||
hasPurchaseDocumentsCue);
|
||
}
|
||
function hasSelectedObjectInventorySaleTraceSignal(text) {
|
||
return hasSelectedObjectInventoryCue(text) && (0, inventoryLifecycleCueHelpers_1.hasInventorySaleCue)(text);
|
||
}
|
||
function hasSelectedObjectInventoryProfitabilitySignal(text) {
|
||
return hasSelectedObjectInventoryCue(text) && (0, inventoryLifecycleCueHelpers_1.hasInventoryProfitabilityCue)(text);
|
||
}
|
||
function hasInventoryProvenanceSignalV2(text) {
|
||
const hasItemCue = /(?:товар|номенклатур|sku|item|product|остат(?:ок|ки)|склад)/iu.test(text);
|
||
const hasSupplierCue = (0, inventoryLifecycleCueHelpers_1.hasInventorySupplierCue)(text) || /кем\s+поставлен/iu.test(text);
|
||
const hasPurchaseCue = /(?:куплен(?:ы|а|о)?|закупк|происхождени|откуда|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|товар|позицию))?|где\s+куплено|когда\s+был\s+куплен|когда\s+куплен|дата\s+закупк|кто\s+(?:нам\s+)?поставил|кем\s+поставлен|поставлен(?:ы|а)?|purchase\s+provenance|purchase\s+date)/iu.test(text) || (0, inventoryLifecycleCueHelpers_1.hasInventoryPurchaseStem)(text);
|
||
return hasItemCue && hasSupplierCue && hasPurchaseCue;
|
||
}
|
||
function hasInventoryPurchaseDateSignal(text) {
|
||
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
|
||
const hasPurchaseDateCue = /(?:когда\s+(?:примерно\s+)?(?:мы\s+)?купили|когда\s+был\s+куплен|когда\s+куплен|дата\s+закупк|purchase\s+date)/iu.test(text);
|
||
return hasItemCue && hasPurchaseDateCue;
|
||
}
|
||
function hasInventoryPurchaseDocumentsSignalV2(text) {
|
||
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
|
||
const hasPurchaseDocCue = /(?:по\s+каким\s+документам\s+был\s+куплен|по\s+каким\s+документам\s+куплен|какими\s+документами\s+был\s+куплен|документ(?:ам|ы)\s+закупк|purchase\s+documents|documents\s+of\s+purchase|through\s+which\s+documents)/iu.test(text);
|
||
return hasItemCue && hasPurchaseDocCue;
|
||
}
|
||
function hasInventorySaleTraceSignalV2(text) {
|
||
const hasItemCue = /(?:товар|номенклатур|sku|item|product|позици(?:я|ю|и)|продукци(?:я|ю|и))/iu.test(text);
|
||
const hasTraceCue = /(?:кому\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали|кому\s+был\s+продан|куда\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали(?:\s+(?:это|его|товар|позицию))?|куда\s+(?:была\s+)?реализована\s+(?:позиция|номенклатура|продукция)|кто\s+купил|buyer|sale\s+trace|trace\s+of\s+sale|через\s+какие\s+документы\s+прош[её]л\s+путь\s+товара|закупк.*склад.*продаж|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*warehouse\s*->\s*sale|purchase\s*->\s*stock\s*->\s*sale)/iu.test(text);
|
||
return hasItemCue && hasTraceCue;
|
||
}
|
||
function hasInventorySupplierStockOverlapSignal(text) {
|
||
const hasDirectSingleItemSupplierQuestion = /(?:от\s+какого\s+поставщика\s+куплен\s+(?:товар|номенклатур(?:а|у|ы)|позици(?:я|ю|и))|от\s+кого\s+куплен\s+(?:товар|номенклатур(?:а|у|ы)|позици(?:я|ю|и)))/iu.test(text);
|
||
if (hasDirectSingleItemSupplierQuestion) {
|
||
return false;
|
||
}
|
||
const hasSupplierCue = /(?:поставщик|supplier|vendor|от\s+поставщика|у\s+поставщика)/iu.test(text);
|
||
const hasStockCue = /(?:склад|остат(?:ок|ке|ков)|лежат|лежит|сейчас\s+еще|сейчас\s+ещ[её]|на\s+дату|по\s+состоянию\s+на\s+дату|current\s+stock|stock\s+overlap|что\s+сейчас\s+лежит)/iu.test(text);
|
||
return hasSupplierCue && hasStockCue;
|
||
}
|
||
function hasInventoryAgingSignal(text) {
|
||
const hasResidueCue = /(?:остат(?:ок|ки)|в\s+остатке|среди\s+текущих\s+остатков|на\s+складе|stock\s+residue|stock\s+balance)/iu.test(text);
|
||
const hasAgingCue = /(?:стар(?:ые|ым|ых)\s+закупк|стары(?:м|х)\s+закупк(?:ам|и|ах)|относит(?:ся|ся\s+ли)?\s+.*\s+к\s+старым\s+закупк|закупал(?:ись|ся)\s+очень\s+давно|очень\s+давно|давно\s+куплен|давно\s+приобретен|куплен\s+задолго\s+до(?:\s+даты)?|закуплен(?:ы|а)?\s+давно|приобретен\s+давно|задолго\s+до(?:\s+даты)?|возраст\s+остатк|возраст\s+закупк|aged?\s+stock|old\s+purchase|old\s+purchases|old\s+stock|bought\s+long\s+ago|purchased\s+long\s+ago|aging\s+by\s+purchase\s+date|very\s+old\s+stock|very\s+old\s+purchase|old\s+procurement|older\s+purchases|aged\s+items|old\s+goods)/iu.test(text);
|
||
return hasAgingCue || (hasResidueCue && /(?:давно\s+куплен|давно\s+приобретен|задолго\s+до)/iu.test(text));
|
||
}
|
||
function hasInventoryPurchaseToSaleChainSignal(text) {
|
||
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
|
||
const hasChainCue = /(?:закупк.*склад.*продаж|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale|закупка\s*->\s*склад\s*->\s*продажа|цепочк[аи]\s+движен|документально\s+подтвержденн\w+\s+цепочк|supplier\s*->\s*item\s*->\s*(?:buyer|customer)|supplier\s+to\s+buyer|supplier\s+to\s+item\s+to\s+buyer)/iu.test(text) || text.includes("->");
|
||
return hasItemCue && hasChainCue;
|
||
}
|
||
function hasInventorySupplierToBuyerChainSignal(text) {
|
||
const hasSupplierCue = /(?:поставщик|supplier|vendor)/iu.test(text);
|
||
const hasBuyerCue = /(?:покупател|buyer|customer|client)/iu.test(text);
|
||
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
|
||
const hasChainCue = /(?:документально\s+подтвержденн\w+\s+цепочк|supplier\s*->\s*item\s*->\s*buyer|supplier\s*->\s*item\s*->\s*customer|supplier\s*->\s*buyer|supplier\s+to\s+buyer|supplier\s+to\s+buyer\s+chain|supplier\s+to\s+item\s+to\s+buyer|поставщик\s*->\s*товар\s*->\s*покупател|поставщик\s*->\s*товар\s*->\s*клиент|поставщик\s*->\s*товар\s*->\s*покупатель|поставщик\s+к\s+покупател|поставщик\s+к\s+клиент|поставщик\s+к\s+товару\s+и\s+покупателю)/iu.test(text) || text.includes("->");
|
||
return hasSupplierCue && hasBuyerCue && hasItemCue && hasChainCue;
|
||
}
|
||
function resolveAddressIntent(userMessage) {
|
||
const text = String(userMessage ?? "").trim().toLowerCase();
|
||
if (hasVatLiabilityConfirmedTaxPeriodSignal(text)) {
|
||
return {
|
||
intent: "vat_liability_confirmed_for_tax_period",
|
||
confidence: "high",
|
||
reasons: ["vat_liability_confirmed_tax_period_signal_detected"]
|
||
};
|
||
}
|
||
if (hasForecastTaxSignal(text)) {
|
||
return {
|
||
intent: "vat_payable_forecast",
|
||
confidence: "high",
|
||
reasons: ["forecast_tax_signal_detected"]
|
||
};
|
||
}
|
||
if (hasVatPayableConfirmedSignal(text)) {
|
||
return {
|
||
intent: "vat_payable_confirmed_as_of_date",
|
||
confidence: "high",
|
||
reasons: ["vat_payable_confirmed_signal_detected"]
|
||
};
|
||
}
|
||
if (hasAny(text, RECEIVABLES_STRONG) || hasFlexibleReceivablesDebtSignal(text)) {
|
||
const receivablesDebtLifecycleSignal = hasReceivablesDebtLifecycleSignal(text) || hasFlexibleReceivablesDebtSignal(text);
|
||
const reasons = ["receivables_signal_detected"];
|
||
if (receivablesDebtLifecycleSignal) {
|
||
reasons.push("receivables_debt_lifecycle_signal_detected");
|
||
if (hasFlexibleReceivablesDebtSignal(text)) {
|
||
reasons.push("receivables_signal_detected_flexible_phrase");
|
||
}
|
||
}
|
||
return {
|
||
intent: receivablesDebtLifecycleSignal ? "receivables_confirmed_as_of_date" : "list_receivables_counterparties",
|
||
confidence: "high",
|
||
reasons
|
||
};
|
||
}
|
||
if (hasAny(text, PAYABLES_STRONG) || hasFlexiblePayablesDebtSignal(text)) {
|
||
const reasons = ["payables_signal_detected"];
|
||
const payablesDebtLifecycleSignal = hasPayablesDebtLifecycleSignal(text) || hasFlexiblePayablesDebtSignal(text);
|
||
if (payablesDebtLifecycleSignal) {
|
||
reasons.push("payables_debt_lifecycle_signal_detected");
|
||
if (hasFlexiblePayablesDebtSignal(text)) {
|
||
reasons.push("payables_signal_detected_flexible_phrase");
|
||
}
|
||
}
|
||
return {
|
||
intent: payablesDebtLifecycleSignal ? "payables_confirmed_as_of_date" : "list_payables_counterparties",
|
||
confidence: "high",
|
||
reasons
|
||
};
|
||
}
|
||
if (hasSettlementGapSignal(text)) {
|
||
return {
|
||
intent: "list_open_contracts",
|
||
confidence: "medium",
|
||
reasons: ["settlement_gap_signal_detected"]
|
||
};
|
||
}
|
||
if (hasReconciliationMismatchSignal(text)) {
|
||
return {
|
||
intent: "list_open_contracts",
|
||
confidence: "medium",
|
||
reasons: ["reconciliation_mismatch_signal_detected"]
|
||
};
|
||
}
|
||
if (hasReceivablesLatencyRiskSignal(text)) {
|
||
return {
|
||
intent: "list_receivables_counterparties",
|
||
confidence: "medium",
|
||
reasons: ["receivables_payment_lag_signal_detected"]
|
||
};
|
||
}
|
||
if (hasCounterpartyDebtLongevitySignal(text)) {
|
||
return {
|
||
intent: "list_receivables_counterparties",
|
||
confidence: "medium",
|
||
reasons: ["receivables_debt_lifecycle_signal_detected"]
|
||
};
|
||
}
|
||
if (hasSupplierTailRiskSignal(text)) {
|
||
return {
|
||
intent: "list_payables_counterparties",
|
||
confidence: "medium",
|
||
reasons: ["supplier_tail_risk_signal_detected", "payables_debt_lifecycle_signal_detected"]
|
||
};
|
||
}
|
||
if (hasDocumentsFormingBalanceSignal(text) && hasDocumentsFormingBalanceAccountAnchor(text)) {
|
||
return {
|
||
intent: "documents_forming_balance",
|
||
confidence: "high",
|
||
reasons: ["documents_forming_balance_signal_detected"]
|
||
};
|
||
}
|
||
if (hasDocumentsByAccountDrilldownSignal(text)) {
|
||
return {
|
||
intent: "documents_forming_balance",
|
||
confidence: "medium",
|
||
reasons: ["documents_by_account_drilldown_signal_detected"]
|
||
};
|
||
}
|
||
if (/(?:старым\s+закупк(?:ам|и|ах)|относится\s+ли\s+.*\s+к\s+старым\s+закупк(?:ам|и|ах)|очень\s+давно|давно\s+куплен|давно\s+приобретен|old\s+stock|old\s+purchase|aging\s+by\s+purchase\s+date)/iu.test(text)) {
|
||
return {
|
||
intent: "inventory_aging_by_purchase_date",
|
||
confidence: "high",
|
||
reasons: ["inventory_aging_signal_detected_strong"]
|
||
};
|
||
}
|
||
if (hasInventoryAccount41Anchor(text) && hasInventoryAsOfCue(text)) {
|
||
return {
|
||
intent: "inventory_on_hand_as_of_date",
|
||
confidence: "high",
|
||
reasons: ["inventory_account_41_as_of_date_signal_detected"]
|
||
};
|
||
}
|
||
if (/(?:без\s+понятн(?:ой|ого)\s+привязк(?:и|а)\s+к\s+поставщик|без\s+привязк(?:и|а)\s+к\s+поставщик|unresolved\s+supplier\s+link)/iu.test(text)) {
|
||
return {
|
||
intent: "inventory_supplier_stock_overlap_as_of_date",
|
||
confidence: "medium",
|
||
reasons: ["inventory_unresolved_provenance_signal_detected"]
|
||
};
|
||
}
|
||
if (hasInventorySupplierStockOverlapSignal(text)) {
|
||
return {
|
||
intent: "inventory_supplier_stock_overlap_as_of_date",
|
||
confidence: "medium",
|
||
reasons: ["inventory_supplier_stock_overlap_signal_detected"]
|
||
};
|
||
}
|
||
if (/(?:supplier\s*->\s*buyer|supplier\s+to\s+buyer|supplier\s+to\s+buyer\s+chain|поставщик\s+к\s+покупателю|поставщик\s*->\s*товар\s*->\s*покупател|документально\s+подтвержденн\w+\s+цепочк)/iu.test(text) &&
|
||
/(?:поставщик|supplier|vendor)/iu.test(text) &&
|
||
/(?:покупател|buyer|customer|client)/iu.test(text) &&
|
||
/(?:товар|номенклатур|sku|item|product)/iu.test(text)) {
|
||
return {
|
||
intent: "inventory_purchase_to_sale_chain",
|
||
confidence: "high",
|
||
reasons: ["inventory_supplier_to_buyer_chain_signal_detected_strong"]
|
||
};
|
||
}
|
||
if (hasInventoryPurchaseToSaleChainSignal(text)) {
|
||
return {
|
||
intent: "inventory_purchase_to_sale_chain",
|
||
confidence: "medium",
|
||
reasons: ["inventory_purchase_to_sale_chain_signal_detected"]
|
||
};
|
||
}
|
||
if (hasInventoryAgingSignal(text)) {
|
||
return {
|
||
intent: "inventory_aging_by_purchase_date",
|
||
confidence: "medium",
|
||
reasons: ["inventory_aging_signal_detected"]
|
||
};
|
||
}
|
||
if (hasSelectedObjectInventoryProvenanceSignal(text)) {
|
||
return {
|
||
intent: "inventory_purchase_provenance_for_item",
|
||
confidence: "medium",
|
||
reasons: ["inventory_selected_object_provenance_signal_detected"]
|
||
};
|
||
}
|
||
if (hasInventoryProvenanceSignalV2(text)) {
|
||
return {
|
||
intent: "inventory_purchase_provenance_for_item",
|
||
confidence: "medium",
|
||
reasons: ["inventory_provenance_signal_detected"]
|
||
};
|
||
}
|
||
if (hasInventoryPurchaseDateSignal(text)) {
|
||
return {
|
||
intent: "inventory_purchase_provenance_for_item",
|
||
confidence: "medium",
|
||
reasons: ["inventory_purchase_date_signal_detected"]
|
||
};
|
||
}
|
||
if (hasSelectedObjectInventoryPurchaseDocumentsSignal(text)) {
|
||
return {
|
||
intent: "inventory_purchase_documents_for_item",
|
||
confidence: "medium",
|
||
reasons: ["inventory_selected_object_purchase_documents_signal_detected"]
|
||
};
|
||
}
|
||
if (hasInventoryPurchaseDocumentsSignalV2(text)) {
|
||
return {
|
||
intent: "inventory_purchase_documents_for_item",
|
||
confidence: "medium",
|
||
reasons: ["inventory_purchase_documents_signal_detected"]
|
||
};
|
||
}
|
||
if (hasSelectedObjectInventoryProfitabilitySignal(text)) {
|
||
return {
|
||
intent: "inventory_profitability_for_item",
|
||
confidence: "medium",
|
||
reasons: ["inventory_selected_object_profitability_signal_detected"]
|
||
};
|
||
}
|
||
if (hasSelectedObjectInventorySaleTraceSignal(text)) {
|
||
return {
|
||
intent: "inventory_sale_trace_for_item",
|
||
confidence: "medium",
|
||
reasons: ["inventory_selected_object_sale_trace_signal_detected"]
|
||
};
|
||
}
|
||
if (/(?:кому\s+(?:мы\s+)?впарили(?:\s+(?:это|его|товар|позицию))?|кому\s+в\s+итоге\s+мы\s+впарили)/iu.test(text) &&
|
||
/(?:товар|номенклатур|sku|item|product|позици(?:я|ю|и)|продукци(?:я|ю|и))/iu.test(text)) {
|
||
return {
|
||
intent: "inventory_sale_trace_for_item",
|
||
confidence: "medium",
|
||
reasons: ["inventory_sale_trace_signal_detected"]
|
||
};
|
||
}
|
||
if (hasInventorySaleTraceSignalV2(text)) {
|
||
return {
|
||
intent: "inventory_sale_trace_for_item",
|
||
confidence: "medium",
|
||
reasons: ["inventory_sale_trace_signal_detected"]
|
||
};
|
||
}
|
||
if (hasInventorySupplierToBuyerChainSignal(text)) {
|
||
return {
|
||
intent: "inventory_purchase_to_sale_chain",
|
||
confidence: "medium",
|
||
reasons: ["inventory_supplier_to_buyer_chain_signal_detected"]
|
||
};
|
||
}
|
||
if (hasInventoryOnHandSignal(text)) {
|
||
return {
|
||
intent: "inventory_on_hand_as_of_date",
|
||
confidence: "high",
|
||
reasons: ["inventory_on_hand_signal_detected"]
|
||
};
|
||
}
|
||
if (hasOpenContractsListSignal(text)) {
|
||
return {
|
||
intent: "open_contracts_confirmed_as_of_date",
|
||
confidence: "medium",
|
||
reasons: ["open_contract_signal_detected"]
|
||
};
|
||
}
|
||
if (hasAny(text, OPEN_ITEMS_HINTS) &&
|
||
!hasCounterpartyDebtLongevitySignal(text) &&
|
||
!hasInventoryAgingSignal(text) &&
|
||
!hasInventoryProvenanceSignalV2(text) &&
|
||
!hasInventoryPurchaseDocumentsSignalV2(text) &&
|
||
!hasInventorySaleTraceSignalV2(text) &&
|
||
(/(?:контраг|договор|контракт|counterparty|contract|покупател|клиент|заказчик|customer|client|buyer|supplier|поставщик)/iu.test(text) ||
|
||
hasAccountNumberAnchor(text) ||
|
||
hasCompactAccountCodeToken(text))) {
|
||
return {
|
||
intent: "open_items_by_counterparty_or_contract",
|
||
confidence: "medium",
|
||
reasons: ["open_items_signal_detected"]
|
||
};
|
||
}
|
||
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 (hasCustomerRevenueAndPaymentsSignal(text) && !hasAccountBalanceSignal(text)) {
|
||
return {
|
||
intent: "customer_revenue_and_payments",
|
||
confidence: "high",
|
||
reasons: ["customer_revenue_and_payments_signal_detected"]
|
||
};
|
||
}
|
||
if (hasSupplierPayoutsProfileSignal(text) && !hasAccountBalanceSignal(text)) {
|
||
return {
|
||
intent: "supplier_payouts_profile",
|
||
confidence: "high",
|
||
reasons: ["supplier_payouts_profile_signal_detected"]
|
||
};
|
||
}
|
||
if (hasContractUsageAndValueSignal(text) &&
|
||
!hasAccountBalanceSignal(text) &&
|
||
!hasOpenContractsListSignal(text)) {
|
||
return {
|
||
intent: "contract_usage_and_value",
|
||
confidence: "high",
|
||
reasons: ["contract_usage_and_value_signal_detected"]
|
||
};
|
||
}
|
||
if (hasContractListByCounterpartySignal(text)) {
|
||
return {
|
||
intent: "list_contracts_by_counterparty",
|
||
confidence: "medium",
|
||
reasons: ["contracts_by_counterparty_signal_detected"]
|
||
};
|
||
}
|
||
if (hasContractAnchorSignal(text) &&
|
||
hasBankOperationSignal(text)) {
|
||
return {
|
||
intent: "bank_operations_by_contract",
|
||
confidence: "medium",
|
||
reasons: ["bank_ops_by_contract_signal_detected"]
|
||
};
|
||
}
|
||
if (hasContractAnchorSignal(text) &&
|
||
(hasAny(text, DOCUMENTS_BY_CONTRACT_HINTS) || hasDocumentSignal(text))) {
|
||
return {
|
||
intent: "list_documents_by_contract",
|
||
confidence: "medium",
|
||
reasons: ["documents_by_contract_signal_detected"]
|
||
};
|
||
}
|
||
if (hasAny(text, BANK_OPERATIONS_BY_COUNTERPARTY_HINTS) &&
|
||
(hasPartyAnchorMention(text) || hasLooseByAnchorMention(text) || hasHeuristicCounterpartyAnchor(text))) {
|
||
return {
|
||
intent: "bank_operations_by_counterparty",
|
||
confidence: "medium",
|
||
reasons: ["bank_ops_by_counterparty_signal_detected"]
|
||
};
|
||
}
|
||
if ((hasAny(text, DOCUMENTS_BY_COUNTERPARTY_HINTS) || hasCounterpartyShipmentItemFlowSignal(text)) &&
|
||
(hasPartyAnchorMention(text) ||
|
||
hasLooseByAnchorMention(text) ||
|
||
hasImplicitCounterpartyAnchorAroundDocs(text) ||
|
||
hasHeuristicCounterpartyAnchor(text) ||
|
||
hasCounterpartyShipmentItemFlowSignal(text))) {
|
||
return {
|
||
intent: "list_documents_by_counterparty",
|
||
confidence: "medium",
|
||
reasons: [
|
||
hasCounterpartyShipmentItemFlowSignal(text)
|
||
? "counterparty_item_flow_signal_detected"
|
||
: "documents_by_counterparty_signal_detected"
|
||
]
|
||
};
|
||
}
|
||
if (hasAccountBalanceSignal(text)) {
|
||
return {
|
||
intent: "account_balance_snapshot",
|
||
confidence: "high",
|
||
reasons: ["account_balance_signal_detected"]
|
||
};
|
||
}
|
||
if (hasLooseByAnchorMention(text) && hasGenericAddressLookupSignal(text)) {
|
||
return {
|
||
intent: "list_documents_by_counterparty",
|
||
confidence: "low",
|
||
reasons: ["generic_lookup_with_loose_anchor_fallback"]
|
||
};
|
||
}
|
||
if (hasAny(text, OPEN_CONTRACTS_HINTS) && (text.includes("договор") || text.includes("контракт") || text.includes("contract"))) {
|
||
return {
|
||
intent: "open_contracts_confirmed_as_of_date",
|
||
confidence: "medium",
|
||
reasons: ["open_contract_signal_detected"]
|
||
};
|
||
}
|
||
return {
|
||
intent: "unknown",
|
||
confidence: "low",
|
||
reasons: ["intent_not_supported_in_v1"]
|
||
};
|
||
}
|