2331 lines
185 KiB
JavaScript
2331 lines
185 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.hasCompactAccountCodeToken = hasCompactAccountCodeToken;
|
||
exports.hasAccountNumberAnchor = hasAccountNumberAnchor;
|
||
exports.resolveAddressIntent = resolveAddressIntent;
|
||
const addressCounterpartyIntentSignals_1 = require("./addressCounterpartyIntentSignals");
|
||
const addressInventoryIntentSignals_1 = require("./addressInventoryIntentSignals");
|
||
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 hasSelectedObjectInventoryCue = /(?:по\s+этой\s+позици(?:и|я|ю)|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|selected\s+object|по\s+выбранному\s+объекту)/iu.test(text);
|
||
if (hasSelectedObjectInventoryCue) {
|
||
return false;
|
||
}
|
||
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",
|
||
"этомуже",
|
||
"томуже"
|
||
]);
|
||
for (const semanticStopWord of [
|
||
"данным",
|
||
"этим",
|
||
"этими",
|
||
"итогу",
|
||
"итогам",
|
||
"всему",
|
||
"всей",
|
||
"всем",
|
||
"выводу",
|
||
"выводам",
|
||
"аудиту",
|
||
"прокси",
|
||
"покажи",
|
||
"показать",
|
||
"выведи",
|
||
"вывести",
|
||
"дай",
|
||
"дать",
|
||
"какие",
|
||
"список"
|
||
]) {
|
||
stopWords.add(semanticStopWord);
|
||
}
|
||
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) || hasSelectedObjectInventoryCue(text);
|
||
const hasPurchaseDateCue = /(?:когда\s+(?:примерно\s+)?(?:мы\s+)?купили|когда\s+был\s+куплен|когда\s+куплен|дата\s+закупк|purchase\s+date)/iu.test(text) ||
|
||
/(?:когда\s+был(?:а|и|о)?\s+закупк\w*|когда\s+закупк\w*)/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 value = String(text ?? "");
|
||
const hasPlainItemCue = /(?:\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|\u043f\u043e\u0437\u0438\u0446|\u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0438|sku|item|product)/iu.test(value);
|
||
const hasPlainTraceCue = /(?:\u043a\u043e\u043c\u0443\s+(?:\u0432\s+\u0438\u0442\u043e\u0433\u0435\s+)?(?:\u043c\u044b\s+)?(?:\u043f\u0440\u043e\u0434\u0430\u043b\u0438|\u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438|\u0432\u043f\u0430\u0440\u0438\u043b\u0438)|\u043a\u043e\u043c\u0443\s+(?:\u0431\u044b\u043b[\u0430\u0438\u043e]?|\u0431\u044b\u043b\u0438)?\s*(?:\u043f\u0440\u043e\u0434\u0430\u043d|\u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d)|\u043a\u0442\u043e\s+\u043a\u0443\u043f\u0438\u043b|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|buyer|sale\s+trace|trace\s+of\s+sale)/iu.test(value);
|
||
if (hasPlainItemCue && (hasPlainTraceCue || (0, inventoryLifecycleCueHelpers_1.hasInventorySaleCue)(value))) {
|
||
return true;
|
||
}
|
||
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 value = String(text ?? "");
|
||
const hasPlainItemCue = /(?:товар|номенклатур|позици|sku|item|product)/iu.test(value);
|
||
const hasPlainChainCue = /(?:закупк[а-яё]*\s*->\s*склад\s*->\s*продаж|закупк[а-яё]*[\s\S]{0,80}склад[\s\S]{0,80}продаж|через\s+какие\s+документы\s+прош[её]л\s+путь|путь\s+товар[а-яё]*[\s\S]{0,80}закуп|цепочк[а-яё]*\s+движен|документально\s+подтвержденн[а-яё]*\s+цепочк|supplier\s*->\s*item\s*->\s*(?:buyer|customer)|supplier\s+to\s+buyer|supplier\s+to\s+item\s+to\s+buyer|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale)/iu.test(value) || value.includes("->");
|
||
if (hasPlainItemCue && hasPlainChainCue) {
|
||
return true;
|
||
}
|
||
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 hasCustomerRevenueRankingBridgeSignal(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
const hasSupplierCue = /(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0432\u0435\u043d\u0434\u043e\u0440|supplier|vendor)/iu.test(normalized);
|
||
if (hasSupplierCue) {
|
||
return false;
|
||
}
|
||
const hasDirectRankingCue = /(?:\u0441\u0430\u043c(?:\u044b\u0439|\u0430\u044f|\u043e\u0435)?\s+\u0434\u043e\u0445\u043e\u0434\u043d(?:\u044b\u0439|\u0430\u044f|\u043e\u0435|\u044b\u0435|\u043e\u0433\u043e|\u043e\u043c\u0443|\u044b\u043c|\u044b\u0445)?\s+(?:\u043a\u043b\u0438\u0435\u043d\u0442|customer|client)|(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f)\s+(?:\u0443\s+\u043d\u0430\u0441\s+)?\u0441\u0430\u043c(?:\u044b\u0439|\u0430\u044f|\u043e\u0435)?\s+\u0434\u043e\u0445\u043e\u0434\u043d(?:\u044b\u0439|\u0430\u044f|\u043e\u0435|\u044b\u0435|\u043e\u0433\u043e|\u043e\u043c\u0443|\u044b\u043c|\u044b\u0445)?\s+\u0433\u043e\u0434|(?:\u0430\s+)?(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a)\s+(?:\u0432\u043e\u043e\u0431\u0449\u0435\s+)?(?:\u0434\u0435\u043d\u0435\u0433\s+)?\u043c\u044b\s+\u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438(?:\s+\u0437\u0430\s+\u0432\u0441\u0435\s+\u0432\u0440\u0435\u043c\u044f)?|(?:\u0430\s+)?(?:\u0437\u0430|for)\s+\d{4}\s+\u043c\u044b\s+(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a)\s+\u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438|(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a)\s+\u043c\u044b\s+\u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438\s+\u0437\u0430\s+\d{4}|(?:\u043e\u0431\u0449\u0430\u044f\s+)?\u0432\u044b\u0440\u0443\u0447\u043a\u0430\s+\u0437\u0430\s+\d{4})/iu.test(normalized);
|
||
if (hasDirectRankingCue) {
|
||
return true;
|
||
}
|
||
const hasMoneyCue = /(?:\u0434\u0435\u043d\u044c\u0433|\u0434\u0435\u043d\u0435\u0433|\u0432\u044b\u0440\u0443\u0447|\u0434\u043e\u0445\u043e\u0434|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover|money|inflow)/iu.test(normalized);
|
||
if (!hasMoneyCue) {
|
||
return false;
|
||
}
|
||
const hasCustomerRankingCue = /(?:\u043a\u0442\u043e\s+(?:\u043d\u0430\u043c\s+)?(?:\u0431\u043e\u043b\u044c\u0448\u0435(?:\s+\u0432\u0441\u0435\u0433\u043e)?\s+\u043f\u0440\u0438\u043d\u0435\u0441(?:\s+\u0434\u0435\u043d\u0435\u0433)?|\u043f\u0440\u0438\u043d\u0435\u0441\s+\u0431\u043e\u043b\u044c\u0448\u0435(?:\s+\u0432\u0441\u0435\u0433\u043e)?\s+\u0434\u0435\u043d\u0435\u0433)|who\s+brought\s+(?:us\s+)?(?:the\s+)?most\s+money|(?:\u0441\u0430\u043c(?:\u044b\u0439|\u0430\u044f|\u043e\u0435)?\s+\u0434\u043e\u0445\u043e\u0434\u043d\w*\s+(?:\u043a\u043b\u0438\u0435\u043d\u0442|customer|client)))/iu.test(normalized);
|
||
const hasRevenueAggregateCue = /(?:(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f)\s+(?:\u0443\s+\u043d\u0430\u0441\s+)?\u0441\u0430\u043c(?:\u044b\u0439|\u0430\u044f|\u043e\u0435)?\s+\u0434\u043e\u0445\u043e\u0434\u043d\w*\s+\u0433\u043e\u0434|(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|скок)\s+(?:\u0432\u043e\u043e\u0431\u0449\u0435\s+)?(?:\u0434\u0435\u043d\u0435\u0433\s+)?\u043c\u044b\s+\u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438|(?:\u0437\u0430|for)\s+\d{4}\s+\u043c\u044b\s+(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|скок)\s+\u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438|\u0432\u044b\u0440\u0443\u0447\u043a\w*\s+\u0437\u0430\s+\d{4})/iu.test(normalized);
|
||
return hasCustomerRankingCue || hasRevenueAggregateCue;
|
||
}
|
||
function hasOrganizationLevelEarningsOverviewBridgeSignal(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
if (/(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|\u0441\u0434\u0435\u043b\u043a|customer|client|counterparty|supplier|vendor|contract|item|product|deal)/iu.test(normalized)) {
|
||
return false;
|
||
}
|
||
const hasYearRankingCue = /(?:(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0438\u0435|\u043a\u0430\u043a\u0430\u044f|what|which)\s+(?:\u0443\s+\u043d\u0430\u0441\s+)?(?:[\p{L}0-9._-]+\s+){0,4}(?:\u0441\u0430\u043c|\u0441\u0430\u043c\u044b\u0435|best|top|most)[\s\S]{0,80}(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u043f\u0440\u0438\u0431\u044b\u043b|\u0432\u044b\u0440\u0443\u0447\u043a|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|profit|turnover)[\s\S]{0,40}(?:\u0433\u043e\u0434|year)|(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u043f\u0440\u0438\u0431\u044b\u043b|\u0432\u044b\u0440\u0443\u0447\u043a|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|profit|turnover)[\s\S]{0,60}(?:\u0441\u0430\u043c|\u0441\u0430\u043c\u044b\u0435|best|top|most)[\s\S]{0,40}(?:\u0433\u043e\u0434|year))/iu.test(normalized);
|
||
const hasCompanyEarningsCue = /(?:(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|how\s+much)[\s\S]{0,120}(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447)|(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447)[\s\S]{0,80}(?:\u0437\u0430\s+(?:\u0432\u0441\u0435\s+\u0432\u0440\u0435\u043c\u044f|\d{2,4}\s*\u0433\u043e\u0434|(?:19|20)\d{2})|all\s+time|\b(?:19|20)\d{2}\b)|(?:\u043e\u0431\u0449\w*\s+(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\u043a)|(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\u043a)[\s\S]{0,40}\u0437\u0430\s+\u0432\u0441\u0435\s+\u0432\u0440\u0435\u043c\u044f))/iu.test(normalized);
|
||
const hasCompanyProfitMarginCue = /(?:\u043f\u0440\u0438\u0431\u044b\u043b\w*|\u043c\u0430\u0440\u0436\w*|\u0440\u0435\u043d\u0442\u0430\u0431\w*|\u0444\u0438\u043d(?:\u0430\u043d\u0441\w*)?\s*[- ]?\s*\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442|p\s*&\s*l|profit(?:ability)?|margin|financial\s+result)/iu.test(normalized) &&
|
||
/(?:\u043a\u0430\u043a\w*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043c\u044b\b|\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time|what|which|how\s+much|show|give|company|business|organization|our|we|us)/iu.test(normalized);
|
||
return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue;
|
||
}
|
||
function hasOrganizationLevelDebtDueDateOverviewBridgeSignal(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
if (/(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test(normalized)) {
|
||
return false;
|
||
}
|
||
const hasDueDateDebtCue = /(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/iu.test(normalized);
|
||
const hasCompanyScopeCue = /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test(normalized);
|
||
return hasDueDateDebtCue && hasCompanyScopeCue;
|
||
}
|
||
function hasOrganizationLevelDebtPositionOverviewBridgeSignal(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
const hasReceivablesCue = /(?:\u0434\u0435\u0431\u0438\u0442\u043e\u0440\w*|\u043a\u0442\u043e\s+\u043d\u0430\u043c\s+\u0434\u043e\u043b\u0436|\u043d\u0430\u043c\s+\u0434\u043e\u043b\u0436\w*|receivables?|accounts\s+receivable)/iu.test(normalized);
|
||
const hasPayablesCue = /(?:\u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0440\w*|\u043a\u043e\u043c\u0443\s+\u043c\u044b\s+\u0434\u043e\u043b\u0436|\u043c\u044b\s+\u0434\u043e\u043b\u0436\w*|payables?|accounts\s+payable)/iu.test(normalized);
|
||
const hasOverviewCue = /(?:\u0441\u0440\u0435\u0437|\u043f\u043e\u0437\u0438\u0446\w*|\u0431\u0430\u043b\u0430\u043d\u0441|\u0441\u0430\u043b\u044c\u0434\u043e|\u043d\u0435\u0442\u0442\u043e|\u043d\u0430\s+\u0441\u0435\u0433\u043e\u0434\u043d|\u043d\u0430\s+\u0434\u0430\u0442\u0443|\u0443\s+\u043d\u0430\u0441|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u043e\u0431\u0437\u043e\u0440|\u0430\u043d\u0430\u043b\u0438\u0437|as\s+of|overview|balance|net|company|business)/iu.test(normalized);
|
||
return hasReceivablesCue && hasPayablesCue && hasOverviewCue;
|
||
}
|
||
function hasOrganizationLevelInventoryReserveLiquidationOverviewBridgeSignal(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
if (/(?:\u043a\u0430\u043a\u0438\u0435\s+(?:\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|\u043f\u043e\u0437\u0438\u0446)|\u0447\u0442\u043e\s+(?:\u043b\u0435\u0436\u0438\u0442|\u043d\u0430\s+\u0441\u043a\u043b\u0430\u0434)|which\s+(?:items|products|goods))/iu.test(normalized)) {
|
||
return false;
|
||
}
|
||
const hasInventoryQualityCue = /(?:\u043d\u0435\u043b\u0438\u043a\u0432\u0438\u0434\w*|\u0440\u0435\u0437\u0435\u0440\u0432\w*|\u0441\u043f\u0438\u0441\u0430\u043d\w*|\u043b\u0438\u043a\u0432\u0438\u0434\u0430\u0446\w*|\u0443\u0441\u0442\u0430\u0440\u0435\u0432\w*|\u043e\u0431\u0435\u0441\u0446\u0435\u043d\w*|obsolete|obsolescence|reserve|write[-\s]?off|liquidation|inventory\s+quality|stock\s+quality)/iu.test(normalized);
|
||
const hasInventoryScopeCue = /(?:\u0441\u043a\u043b\u0430\u0434|\u043e\u0441\u0442\u0430\u0442|\u0437\u0430\u043f\u0430\u0441|\u0442\u043e\u0432\u0430\u0440|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|inventory|stock|warehouse)/iu.test(normalized);
|
||
const hasCompanyScopeCue = /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u0441\u043a\u043b\u0430\u0434|\u043e\u0441\u0442\u0430\u0442|\u0437\u0430\u043f\u0430\u0441|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|warehouse|stock|inventory|our|we|us|show|give|analysis)/iu.test(normalized);
|
||
return hasInventoryQualityCue && hasInventoryScopeCue && hasCompanyScopeCue;
|
||
}
|
||
function hasOrganizationLevelSupplierQualityOverviewBridgeSignal(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
if (/(?:\u0445\u0432\u043e\u0441\u0442|\u0434\u043e\u043b\u0433|\u0437\u0430\u0434\u043e\u043b\u0436|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u043f\u043b\u0430\u0442|\u043f\u043b\u0430\u0442[\u0435\u0451]\u0436|\u0430\u043a\u0442|\u043f\u0440\u0438\u0445\u043e\u0434|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\b60\b|open\s+items?|payable|payment|invoice|bill|settlement|reconciliation)/iu.test(normalized)) {
|
||
return false;
|
||
}
|
||
const hasSupplierScopeCue = /(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0432\u0435\u043d\u0434\u043e\u0440|\u0437\u0430\u043a\u0443\u043f|\u0441\u043d\u0430\u0431\u0436|supplier|vendor|procurement|sourcing)/iu.test(normalized);
|
||
const hasSupplierQualityCue = /(?:\u0440\u0438\u0441\u043a\w*|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*|\u043f\u0440\u043e\u0431\u043b\u0435\u043c\w*|\u0437\u0430\u0432\u0438\u0441\u0438\w*|\u0437\u0430\u0432\u044f\u0437\u0430\u043d\w*|\u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0430\u0446\w*|\u043a\u043b\u044e\u0447\u0435\u0432\w*|\u043a\u0440\u0438\u0442\u0438\u0447\w*|risk|quality|problem|dependency|concentration|critical|key\s+supplier)/iu.test(normalized);
|
||
const hasCompanyScopeCue = /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test(normalized);
|
||
return hasSupplierScopeCue && hasSupplierQualityCue && hasCompanyScopeCue;
|
||
}
|
||
function hasSpecificCounterpartyRevenueBridgeSignal(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
const hasSupplierCue = /(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0432\u0435\u043d\u0434\u043e\u0440|supplier|vendor)/iu.test(normalized);
|
||
const hasNonRevenueEntityCue = /(?:\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0434\u043e\u043a\u0438|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u0431\u0430\u043d\u043a|\u043f\u043b\u0430\u0442\u0435\u0436|docs?|documents?|contract|bank|payment)/iu.test(normalized);
|
||
if (hasSupplierCue || hasNonRevenueEntityCue) {
|
||
return false;
|
||
}
|
||
const hasRevenueCue = /(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\w*|\u0434\u043e\u0445\u043e\u0434\w*|revenue|turnover)/iu.test(normalized);
|
||
if (!hasRevenueCue) {
|
||
return false;
|
||
}
|
||
const explicitEntityMatch = normalized.match(/(?:^|[\s,.;:!?])(?:\u043f\u043e|by|for)\s+([\p{L}\d][\p{L}\d._-]{1,})\s*$/iu) ??
|
||
normalized.match(/(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\w*|\u0434\u043e\u0445\u043e\u0434\w*|revenue|turnover)\s+(?:(?:\u0431\u044b\u043b(?:\u0430|\u043e)?|was)\s+)?(?:(?:\u0443|\u043f\u043e|by|for)\s+)?([\p{L}\d][\p{L}\d._-]{1,})\s*$/iu);
|
||
const entity = explicitEntityMatch?.[1] ? String(explicitEntityMatch[1]).toLowerCase() : null;
|
||
if (!entity || /^\d+$/.test(entity)) {
|
||
return false;
|
||
}
|
||
const ignoredEntityTails = new Set([
|
||
"\u043d\u0430\u043c",
|
||
"\u043d\u0430\u0441",
|
||
"\u0432\u0441\u0435",
|
||
"\u0432\u0441\u0435\u043c",
|
||
"\u0433\u043e\u0434",
|
||
"\u0433\u043e\u0434\u0430",
|
||
"\u043c\u0435\u0441\u044f\u0446",
|
||
"year",
|
||
"month"
|
||
]);
|
||
return !ignoredEntityTails.has(entity);
|
||
}
|
||
function hasInventoryProvenanceBridgeSignal(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
const hasItemCue = /(?:\u0442\u043e\u0432\u0430\u0440|\u043f\u043e\u0437\u0438\u0446|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|sku|item|product)/iu.test(normalized);
|
||
const hasSupplierCue = /(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043e\u0442\s+\u043a\u0430\u043a\u043e\u0433\u043e|\u043a\u0442\u043e\s+\u043f\u043e\u0441\u0442\u0430\u0432\u0438\u043b|supplier|vendor)/iu.test(normalized);
|
||
const hasPurchaseCue = /(?:\u043a\u0443\u043f\u043b\u0435\u043d|\u0437\u0430\u043a\u0443\u043f|\u043a\u043e\u0433\u0434\u0430\s+\u043a\u0443\u043f\u0438\u043b|\u043a\u0443\u043f\u0438\u043b\u0438|purchase)/iu.test(normalized);
|
||
return hasItemCue && hasSupplierCue && hasPurchaseCue;
|
||
}
|
||
function hasInventoryDocumentaryChainBridgeSignal(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
const hasChainCue = /(?:\u0446\u0435\u043f\u043e\u0447|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043b|\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|->|\u2192|chain|trace)/iu.test(normalized);
|
||
const hasSupplierCue = /(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|supplier|vendor)/iu.test(normalized);
|
||
const hasBuyerCue = /(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|buyer|customer|client)/iu.test(normalized);
|
||
const hasItemCue = /(?:\u0442\u043e\u0432\u0430\u0440|\u043f\u043e\u0437\u0438\u0446|\u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440|sku|item|product)/iu.test(normalized);
|
||
return hasChainCue && hasSupplierCue && hasBuyerCue && hasItemCue;
|
||
}
|
||
function hasColloquialInventoryOnHandBridgeSignal(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
const hasInventoryAgingCue = /(?:\u043e\u0447\u0435\u043d\u044c\s+\u0434\u0430\u0432\u043d\u043e|\u0434\u0430\u0432\u043d\u043e\s+\u043a\u0443\u043f\u043b|\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u0438\u043e\u0431\u0440\u0435\u0442|\u0441\u0442\u0430\u0440(?:\u044b\u0435|\u044b\u043c|\u044b\u0445)?\s+\u0437\u0430\u043a\u0443\u043f|\u0441\u0442\u0430\u0440\u044b\u0439\s+\u0442\u043e\u0432\u0430\u0440|old\s+stock|old\s+purchase|aging\s+by\s+purchase\s+date)/iu.test(normalized);
|
||
if (hasInventoryAgingCue) {
|
||
return false;
|
||
}
|
||
const tokenCount = normalized.split(/\s+/u).filter(Boolean).length;
|
||
const hasWarehouseCue = /(?:\u0441\u043a\u043b\u0430\u0434(?:\u0430\u0445|\u0435|\u0443|\u043e\u043c|\u044b)?|\u043e\u0441\u0442\u0430\u0442|warehouse|stock|inventory)/iu.test(normalized);
|
||
if (!hasWarehouseCue) {
|
||
return false;
|
||
}
|
||
const hasQuestionCue = /(?:\u0447\u0442\u043e|\u0447\u0435|\u0447\u0451|\u043a\u0430\u043a\u0438\u0435|\u043f\u043e\u043a\u0430\u0436\u0438|\u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c|show|list|what)/iu.test(normalized);
|
||
return hasQuestionCue && tokenCount <= 8;
|
||
}
|
||
function repairLikelyUtf8Mojibake(text) {
|
||
const raw = String(text ?? "");
|
||
if (!raw) {
|
||
return "";
|
||
}
|
||
try {
|
||
const repaired = Buffer.from(raw, "latin1").toString("utf8");
|
||
return repaired || raw;
|
||
}
|
||
catch {
|
||
return raw;
|
||
}
|
||
}
|
||
function unicodeBridgeResolution(intent, confidence, reason) {
|
||
return { intent, confidence, reasons: [reason] };
|
||
}
|
||
function hasBidirectionalValueFlowComparisonSignal(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return false;
|
||
}
|
||
const hasIncomingCue = /(?:\u0432\u0445\u043e\u0434\u044f\u0449|\u043f\u043e\u0441\u0442\u0443\u043f|\u043f\u043e\u043b\u0443\u0447|inflow|incoming)/iu.test(normalized);
|
||
const hasOutgoingCue = /(?:\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u0441\u043f\u0438\u0441\u0430\u043d|\u0437\u0430\u043f\u043b\u0430\u0442|\u043f\u043b\u0430\u0442\u0438\u043b|\u0432\u044b\u043f\u043b\u0430\u0442|\u0432\u044b\u043f\u043b\u0430\u0447|\u0443\u043f\u043b\u0430\u0442|\u043e\u043f\u043b\u0430\u0442|outflow|outgoing|payout|paid)/iu.test(normalized);
|
||
const hasComparisonCue = /(?:\u0431\u043e\u043b\u044c\u0448|\u043c\u0435\u043d\u044c\u0448|\u0441\u0440\u0430\u0432|\u0438\u043b\u0438|\u043d\u0435\u0442\u0442\u043e|\u0441\u0430\u043b\u044c\u0434\u043e|vs|versus)/iu.test(normalized);
|
||
const hasValueFlowCue = /(?:\u0434\u0435\u043d\u044c\u0433|\u0434\u0435\u043d\u0435\u0433|\u0434\u0435\u043d\u0435\u0436|\u0441\u0440\u0435\u0434\u0441\u0442\u0432|\u043f\u043e\u0442\u043e\u043a|\u043e\u0431\u043e\u0440\u043e\u0442|money|cash|funds|flow)/iu.test(normalized);
|
||
const hasNetAmountCue = /(?:сколько|сумм|итог|нетто|сальдо|минус|net|total|sum)/iu.test(normalized);
|
||
return hasIncomingCue && hasOutgoingCue && hasComparisonCue && (hasValueFlowCue || hasNetAmountCue);
|
||
}
|
||
function hasVatPeriodInspectionBridgeSignal(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!/(?:ндс|vat)/iu.test(normalized)) {
|
||
return false;
|
||
}
|
||
const hasPeriodCue = /(?:\b(?:19|20)\d{2}\b|за\s+(?:\d{4}|год|период|квартал|месяц|январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)|\b[1-4]\s*(?:кв|квартал))/iu.test(normalized);
|
||
const hasInspectionCue = /(?:что\s+с|позици|основан|не\s+хватает|налогов[а-яё]*\s+вывод|вывод|декларац|книга\s+(?:продаж|покупок)|расшифр|разбор)/iu.test(normalized);
|
||
const forecastOnlyCue = /(?:прогноз|план|примерн|ориентировочн)/iu.test(normalized) && !hasInspectionCue;
|
||
const hasVatMovementInspectionCue = /(?:покаж|движен|операц|по\s+сч(?:е|ё)т|покаж|движен|операц|РїРѕ\s+СЃС‡(?:Рµ|С‘)С‚|show|movement|movements|operation|operations|account)/iu.test(normalized);
|
||
return hasPeriodCue && (hasInspectionCue || hasVatMovementInspectionCue) && !forecastOnlyCue;
|
||
}
|
||
function resolveUnicodeAddressIntentBridge(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return null;
|
||
}
|
||
const hasAccountAnchor = /(?:\b(?:60|62|76)(?:[.,]\d{2})?\b|сч(?:е|ё)т(?:а|у|ом|е|ов)?|account)/iu.test(normalized);
|
||
const hasDocumentCue = /(?:док(?:умент(?:ы|ов|а|ам|ами|ах)?|и|ам|ами|ах|ов|а)?|docs?|documents?)/iu.test(normalized);
|
||
const hasBankCue = /(?:банк|банковск|плат[её]ж|оплат|транзакц|51|bank|payment|transaction)/iu.test(normalized);
|
||
const hasContractCue = /(?:договор|дог(?:\s|$)|контракт|contract|dogovor)/iu.test(normalized);
|
||
const hasSpecificContractCue = /(?:\b\d{1,4}\/\d{1,4}\b|этому\s+же\s+договор)/iu.test(normalized);
|
||
const hasCounterpartyCue = /(?:контрагент|компани|организац|клиент|покупател|заказчик|поставщик|свк|альфа|жуковк|альтернатива|counterpart|company|supplier|customer|client|buyer)/iu.test(normalized);
|
||
const byAnchorMatch = normalized.match(/(?:^|[\s,.;:!?])(?:по|для)\s+([\p{L}\d._-]{2,})/iu);
|
||
const byAnchorToken = String(byAnchorMatch?.[1] ?? "").toLowerCase();
|
||
const hasLooseCounterpartyByAnchor = !!byAnchorToken &&
|
||
!new Set([
|
||
"количеству",
|
||
"документам",
|
||
"докам",
|
||
"договору",
|
||
"договорам",
|
||
"счету",
|
||
"счёту",
|
||
"остатку",
|
||
"операциям",
|
||
"оплате",
|
||
"платежам",
|
||
"сальдо",
|
||
"дате",
|
||
"периоду",
|
||
"данным",
|
||
"этим",
|
||
"этими",
|
||
"итогу",
|
||
"итогам",
|
||
"всему",
|
||
"всей",
|
||
"всем",
|
||
"выводу",
|
||
"выводам",
|
||
"аудиту",
|
||
"прокси",
|
||
"складу",
|
||
"товару",
|
||
"этому",
|
||
"этой",
|
||
"нему",
|
||
"ней"
|
||
]).has(byAnchorToken);
|
||
const hasMoneyCue = /(?:деньг|денег|выручк|доход|оборот|заработ|прин[её]с|чек|ликвидн|revenue|turnover|money)/iu.test(normalized);
|
||
const hasRankingCue = /(?:топ|ранк|сам(?:ый|ая|ое|ые)|больше\s+всего|наибольш|крупн|жирн|max|top|rank)/iu.test(normalized);
|
||
const hasInventoryPurchaseToSaleDocumentChainCue = /(?:закупк[а-яё]*[\s\S]{0,80}склад[\s\S]{0,80}продаж|путь\s+товар[а-яё]*[\s\S]{0,80}закуп|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale|->\s*(?:склад|warehouse|stock)\s*->\s*(?:продаж|sale))/iu.test(normalized) && /(?:товар|позици|номенклатур|sku|item|product)/iu.test(normalized);
|
||
if (hasInventoryPurchaseToSaleDocumentChainCue) {
|
||
return unicodeBridgeResolution("inventory_purchase_to_sale_chain", "high", "unicode_inventory_purchase_to_sale_chain_bridge_signal_detected");
|
||
}
|
||
const hasSelectedObjectProfitabilityCue = /(?:\u043f\u043e\s+\u0432\u044b\u0431\u0440\u0430\u043d\u043d(?:\u043e\u043c\u0443|\u043e\u0439)\s+(?:\u043e\u0431\u044a\u0435\u043a\u0442\u0443|\u043f\u043e\u0437\u0438\u0446\u0438\u0438)|selected\s+object)/iu.test(normalized) &&
|
||
(/(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u0440\u0438\u0431\u044b\u043b|\u043c\u0430\u0440\u0436|profit|margin)/iu.test(normalized) ||
|
||
(/(?:\u043f\u0440\u043e\u0434\u0430\u0436|sale)/iu.test(normalized) &&
|
||
/(?:\u0437\u0430\u043a\u0443\u043f|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|purchase|document)/iu.test(normalized)));
|
||
if (hasSelectedObjectProfitabilityCue) {
|
||
return unicodeBridgeResolution("inventory_profitability_for_item", "high", "unicode_selected_object_profitability_bridge_signal_detected");
|
||
}
|
||
const hasOpenItemsAccountCue = /(?:хвост|долг|незакрыт|вис)/iu.test(normalized) &&
|
||
/(?:сч(?:е|ё)т(?:а|у|ом|е|ов)?\s*(?:№|#)?\s*(?:60|62|76)(?:[.,]\d{1,2})?|\b(?:60|62|76)(?:[.,]\d{1,2})?\b\s*сч(?:е|ё)т)/iu.test(normalized);
|
||
if (hasOpenItemsAccountCue) {
|
||
return unicodeBridgeResolution("open_items_by_counterparty_or_contract", "medium", "open_items_signal_detected");
|
||
}
|
||
const hasCounterpartyShipmentItemFlowCue = /(?:отгруж(?:ал|али|ен[аоы]?|енн(?:ый|ая|ое|ые)?))/iu.test(normalized) &&
|
||
/(?:товар|услуг|позици|номенклатур)/iu.test(normalized) &&
|
||
!/(?:выбранн(?:ый|ому)\s+объект|selected\s+object)/iu.test(normalized);
|
||
if (hasCounterpartyShipmentItemFlowCue) {
|
||
return unicodeBridgeResolution("list_documents_by_counterparty", "medium", "counterparty_item_flow_signal_detected");
|
||
}
|
||
const hasHighestValueCustomerCue = /(?:сам(?:ый|ые|ая|ое)|топ|кто|какой|какие|больше\s+всего|наибольш)/iu.test(normalized) &&
|
||
/(?:доходн|выручк|денег|деньг|оборот|поступлен|прин[её]с)/iu.test(normalized) &&
|
||
/(?:клиент|покупател|заказчик|контрагент)/iu.test(normalized);
|
||
if (!hasContractCue && hasHighestValueCustomerCue) {
|
||
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_bridge_signal_detected");
|
||
}
|
||
const hasCustomerConcentrationCue = /(?:\u043a\u0440\u0443\u043f\u043d\w*|\u043e\u0441\u043d\u043e\u0432\u043d\w*|\u0433\u043b\u0430\u0432\u043d\w*|\u0442\u043e\u043f|top|largest|main|key|concentration|\u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0430\u0446\w*|\u0437\u0430\u0432\u0438\u0441\w*|\u043e\u0434\u043d(?:\u043e\u0433\u043e|\u043e\u043c\u0443)\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442))/iu.test(normalized) &&
|
||
/(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|customer|client|buyer|counterparty)/iu.test(normalized);
|
||
if (!hasContractCue && hasCustomerConcentrationCue) {
|
||
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_concentration_bridge_signal_detected");
|
||
}
|
||
const hasTopYearRevenueRankingCue = /(?:(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0438\u0435|\u043a\u0430\u043a\u0430\u044f|what|which)[\s\S]{0,80}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,80}(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447\u043a|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,60}(?:\u0433\u043e\u0434|year)|(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447\u043a|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,60}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,60}(?:\u0433\u043e\u0434|year))/iu.test(normalized);
|
||
if (!hasContractCue && (hasTopYearRevenueRankingCue || hasCustomerRevenueRankingBridgeSignal(normalized))) {
|
||
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_ranking_bridge_signal_detected");
|
||
}
|
||
if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) {
|
||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
|
||
}
|
||
if (hasOrganizationLevelDebtPositionOverviewBridgeSignal(normalized)) {
|
||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_debt_position_deferred_to_discovery");
|
||
}
|
||
if (hasOrganizationLevelDebtDueDateOverviewBridgeSignal(normalized)) {
|
||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_debt_due_date_deferred_to_discovery");
|
||
}
|
||
if (hasOrganizationLevelInventoryReserveLiquidationOverviewBridgeSignal(normalized)) {
|
||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_inventory_reserve_liquidation_deferred_to_discovery");
|
||
}
|
||
if (hasOrganizationLevelSupplierQualityOverviewBridgeSignal(normalized)) {
|
||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_supplier_quality_deferred_to_discovery");
|
||
}
|
||
if (hasBidirectionalValueFlowComparisonSignal(normalized)) {
|
||
return unicodeBridgeResolution("unknown", "high", "unicode_bidirectional_value_flow_deferred_to_discovery");
|
||
}
|
||
if (/(?:за\s+какие\s+годы|диапазон\s+лет|покрыт(?:ие|ый)\s+период|какой\s+год.*актив|какой\s+месяц.*актив|год.*пассив|месяц.*пассив|минимальн.*док|минимальн.*операц|месяц[\s-]*пик|profile\s+period|top\s*year|top\s*month)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("period_coverage_profile", "high", "unicode_period_coverage_bridge_signal_detected");
|
||
}
|
||
if (/(?:тип(?:ы|ов)?\s+док|док(?:умент|ов).*?(?:чаще|редк|больше\s+всего|меньше\s+всего|крутит)|раздел(?:ы|ов)?\s+уч[её]та|сводк.*тип.*док|document\s+type|account\s+section)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("document_type_and_account_section_profile", "high", "unicode_document_type_profile_bridge_signal_detected");
|
||
}
|
||
if (hasAccountAnchor &&
|
||
hasDocumentCue &&
|
||
/(?:формирующ.*остат|раскрой\s+остат|остат.*по\s+документ|по\s+докам.*(?:60|62|76)|(?:60|62|76)(?:[.,]\d{2})?.*(?:по\s+докам|по\s+документ))/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("documents_forming_balance", "high", "unicode_documents_forming_balance_bridge_signal_detected");
|
||
}
|
||
if (/(?:договор[а-я]*.*(?:все|список).*по\s+[\p{L}\d]|(?:покажи|показать).*договор[а-я]*.*по\s+[\p{L}\d])/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("list_contracts_by_counterparty", "high", "unicode_contracts_by_counterparty_bridge_signal_detected");
|
||
}
|
||
if (/(?:проконтрол|акты\s+без\s+приход|без\s+приходок|засорять\s+бухгалтер)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("unknown", "low", "unsupported_supplier_control_signal_detected");
|
||
}
|
||
if (/(?:кроме\s+этого\s+документ.*(?:есть\s+еще\s+что|есть\s+ещ[её]\s+что|что[-\s]?то))/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("list_documents_by_counterparty", "medium", "generic_document_followup_with_previous_counterparty");
|
||
}
|
||
if (/(?:плат[её]ж|оплат|отгрузк|документ|аванс|взаиморасчет|закрыт)/iu.test(normalized) &&
|
||
/(?:без\s+(?:закрыт|документ|оплат)|нет\s+(?:документ|оплат)|не\s+закрыт|оплат[а-я]*\s+нет|документ[а-я]*\s+есть|требует\s+ручн)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("list_open_contracts", "high", "unicode_open_contract_gap_bridge_signal_detected");
|
||
}
|
||
if (/(?:долгожител|долго\s+долж|задолженн(?:ост|остям).*(?:давн|долго)|не\s+плат|не\s+оплат|не\s+оплачен|неоплачен|просроч|сроки\s+давно\s+прошл|слишком\s+длинн.*оплат)/iu.test(normalized) &&
|
||
/(?:покупател|клиент|заказ|отгрузк|товар|услуг|задолженн|сальдо|не\s+плат|не\s+оплат|не\s+оплачен|неоплачен|просроч)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("list_receivables_counterparties", "high", "receivables_debt_lifecycle_signal_detected");
|
||
}
|
||
if (hasVatPeriodInspectionBridgeSignal(normalized)) {
|
||
return unicodeBridgeResolution("vat_liability_confirmed_for_tax_period", "high", "vat_period_inspection_bridge_signal_detected");
|
||
}
|
||
const inventoryBridgeIntent = (0, addressInventoryIntentSignals_1.resolveInventoryAddressIntent)(normalized);
|
||
if (inventoryBridgeIntent) {
|
||
if (inventoryBridgeIntent.intent === "inventory_aging_by_purchase_date") {
|
||
return { ...inventoryBridgeIntent, confidence: "high" };
|
||
}
|
||
return inventoryBridgeIntent;
|
||
}
|
||
if (/(?:поставщик|vendor|supplier)/iu.test(normalized) &&
|
||
/(?:хвост|задержк|проблем|систематическ)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("list_payables_counterparties", "high", "supplier_tail_risk_signal_detected");
|
||
}
|
||
if (hasDocumentCue && (hasCounterpartyCue || hasLooseCounterpartyByAnchor) && !hasContractCue) {
|
||
return unicodeBridgeResolution("list_documents_by_counterparty", "high", "unicode_documents_by_counterparty_bridge_signal_detected");
|
||
}
|
||
if (hasBankCue && (hasCounterpartyCue || hasLooseCounterpartyByAnchor) && !hasContractCue) {
|
||
return unicodeBridgeResolution("bank_operations_by_counterparty", "high", "unicode_bank_ops_by_counterparty_bridge_signal_detected");
|
||
}
|
||
if (/(?:есть\s+что[-\s]?то|что[-\s]?то)/iu.test(normalized) &&
|
||
(hasLooseCounterpartyByAnchor || /по\s+(?:ней|нему|этой|этому)/iu.test(normalized))) {
|
||
return unicodeBridgeResolution("list_documents_by_counterparty", "medium", "generic_lookup_with_loose_anchor_fallback");
|
||
}
|
||
if (hasDocumentCue &&
|
||
(hasLooseCounterpartyByAnchor || hasCounterpartyCue || /по\s+(?:ней|нему|этой|этому)/iu.test(normalized)) &&
|
||
!hasContractCue &&
|
||
!/(?:купил|куплен|закуп|товар|позици|номенклатур)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("list_documents_by_counterparty", "medium", "unicode_documents_by_counterparty_bridge_signal_detected");
|
||
}
|
||
if (/(?:долгожител|долго\s+долж|задолженн(?:ост|остям).*(?:давн|долго)|не\s+плат|не\s+оплат|не\s+оплачен|неоплачен|просроч|сроки\s+давно\s+прошл|слишком\s+длинн.*оплат)/iu.test(normalized) &&
|
||
/(?:покупател|клиент|заказ|отгрузк|товар|услуг|задолженн|сальдо|не\s+плат|не\s+оплат|не\s+оплачен|неоплачен|просроч)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("list_receivables_counterparties", "high", "receivables_debt_lifecycle_signal_detected");
|
||
}
|
||
if (/\b41(?:[.,]\d{2})?\b/iu.test(normalized) && /(?:товар|склад|остат|состоит|номенклатур)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("inventory_on_hand_as_of_date", "high", "unicode_inventory_on_hand_bridge_signal_detected");
|
||
}
|
||
if (/(?:год.*(?:док|операц).*(?:актив|пик|жив|много|движов)|год.*движов.*(?:док|операц)|(?:док|операц).*год.*(?:актив|пик|жив|много|движов)|месяц[\s-]*пик)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("period_coverage_profile", "high", "unicode_period_coverage_bridge_signal_detected");
|
||
}
|
||
if (!hasContractCue &&
|
||
/(?:скольк|скока).*(?:деньг|денег|выручк|доход|оборот)|(?:деньг|денег|выручк|доход|оборот).*(?:прин[её]с|зан[её]с|плат)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_bridge_signal_detected");
|
||
}
|
||
if (!hasContractCue &&
|
||
/(?:кто|какие|выведи|покажи|список|самые)/iu.test(normalized) &&
|
||
/(?:список\s+(?:заказчик|клиент|покупател).*за\s+\d{2,4}\s*год|актив.*отвал|ровно\s+один\s+раз|один\s+раз.*пропал|стар(?:ые|ые)?\s+по\s+сотруднич|сотрудничеству\s+кто)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("counterparty_activity_lifecycle", "high", "unicode_counterparty_lifecycle_bridge_signal_detected");
|
||
}
|
||
if (!hasContractCue &&
|
||
/(?:кто|какие|выведи|покажи|список|разбей|раздели|самые)/iu.test(normalized) &&
|
||
/(?:заказчик|клиент|покупател|поставщик|контрагент|зак(?!рыт))/iu.test(normalized) &&
|
||
/(?:работал|работают|актив|все\s+время|вообще|регулярн|эпизодич|частот|давно\s+не\s+использ|операционн|разов|один\s+раз|пропал|отвал|сотруднич)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("counterparty_activity_lifecycle", "high", "unicode_counterparty_lifecycle_bridge_signal_detected");
|
||
}
|
||
if (/(?:поставщик|vendor|supplier|кому\s+(?:ушло|платили|заплатили)|выплат|исходящ|списан|сгрузил)/iu.test(normalized) &&
|
||
!/(?:аванс.*(?:не\s+)?закрыт|закрыт.*аванс)/iu.test(normalized) &&
|
||
(hasMoneyCue || hasRankingCue || /плат[её]ж|оплат|выплат|outflow|payout|хвост|задержк|проблем/iu.test(normalized))) {
|
||
return unicodeBridgeResolution(/(?:хвост|задержк|проблем)/iu.test(normalized) ? "list_payables_counterparties" : "supplier_payouts_profile", "high", /(?:хвост|задержк|проблем)/iu.test(normalized)
|
||
? "supplier_tail_risk_signal_detected"
|
||
: "unicode_supplier_payouts_bridge_signal_detected");
|
||
}
|
||
if (!hasContractCue &&
|
||
(/(?:клиент|покупател|заказчик|контрагент|альтернатива|свк)/iu.test(normalized) || hasRankingCue) &&
|
||
(hasMoneyCue || /поступлен|приход|входящ|сделк|бюджет|inflow/iu.test(normalized))) {
|
||
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_bridge_signal_detected");
|
||
}
|
||
if (!hasContractCue &&
|
||
/(?:кто.*(?:деньг|денег|доход|выручк).*(?:прин[её]с|зан[её]с|плат)|(?:жирн|ликвидн).*контрагент.*(?:деньг|денег))/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_bridge_signal_detected");
|
||
}
|
||
if (/(?:общие\s+обороты|общая\s+выручк|оборот.*за\s+все\s+время|выручк.*за\s+все\s+время)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_bridge_signal_detected");
|
||
}
|
||
if (/(?:открыт(?:ые|ая|ый)?\s+задолж|открыт(?:ые|ая|ый)?\s+позици|позици.*по\s+договор|open\s+items?)/iu.test(normalized) &&
|
||
(hasContractCue || hasCounterpartyCue || hasAccountAnchor || /покупател|клиент/iu.test(normalized))) {
|
||
return unicodeBridgeResolution("open_items_by_counterparty_or_contract", "high", "unicode_open_items_bridge_signal_detected");
|
||
}
|
||
if (hasContractCue &&
|
||
/(?:нескольк(?:ими|о)?\s+договор|контрагент.*нескольк.*договор|какие\s+из\s+договор.*актив)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("contract_usage_and_value", "high", "unicode_contract_usage_value_bridge_signal_detected");
|
||
}
|
||
if (hasContractCue && (hasMoneyCue || hasRankingCue || /оборот|бюджет|сумм|стоим|value|amount/iu.test(normalized))) {
|
||
return unicodeBridgeResolution("contract_usage_and_value", "high", "unicode_contract_usage_value_bridge_signal_detected");
|
||
}
|
||
if (/(?:сальдо.*(?:расход|не\s+совпад)|расход.*сальдо|акт(?:ом|ах)?\s+сверк|плат[её]ж[и]?,?\s+но\s+нет\s+док|документ(?:ы)?\s+есть,?\s+а\s+оплат\s+нет|(?:оплат|плат[её]ж|отгрузк|закрыти[ея]\s+счет)[\p{L}\s,]*\s+без\s+(?:закрыт|документ|подтвержд)|аванс.*давно\s+не\s+закрыт)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("list_open_contracts", "high", "unicode_open_contracts_list_bridge_signal_detected");
|
||
}
|
||
if (/(?:долгожител|долго\s+долж|задолженн(?:ост|остям).*(?:давн|долго)|не\s+плат|не\s+оплат|не\s+оплачен|неоплачен|просроч|сроки\s+давно\s+прошл|слишком\s+длинн.*оплат)/iu.test(normalized) &&
|
||
/(?:покупател|клиент|заказ|отгрузк|товар|услуг|задолженн|сальдо|не\s+плат|не\s+оплат|не\s+оплачен|неоплачен|просроч)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("list_receivables_counterparties", "high", "receivables_debt_lifecycle_signal_detected");
|
||
}
|
||
if (/(?:сальдо.*(?:расход|не\s+совпад)|расход.*сальдо|акт(?:ом|ах)?\s+сверк|плат[её]ж[и]?,?\s+но\s+нет\s+док|документ(?:ы)?\s+есть,?\s+а\s+оплат\s+нет|(?:оплат|плат[её]ж|отгрузк|закрыти[ея]\s+счет)[\p{L}\s,]*\s+без\s+(?:закрыт|документ|подтвержд)|аванс.*давно\s+не\s+закрыт)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("list_open_contracts", "high", "unicode_open_contracts_list_bridge_signal_detected");
|
||
}
|
||
if (/(?:открыт(?:ые|ая|ый)?\s+позици|позици.*по\s+договор|open\s+items?)/iu.test(normalized) &&
|
||
(hasContractCue || hasCounterpartyCue || hasAccountAnchor)) {
|
||
return unicodeBridgeResolution("open_items_by_counterparty_or_contract", "high", "unicode_open_items_bridge_signal_detected");
|
||
}
|
||
if (hasAccountAnchor &&
|
||
hasDocumentCue &&
|
||
/(?:формир|под\s+остат|раскр(?:ой|ыть|ывай)|остат(?:ок|ком)?\s+по\s+док|documents?\s+forming|docs?\s+forming)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("documents_forming_balance", "high", "unicode_documents_forming_balance_bridge_signal_detected");
|
||
}
|
||
if (hasContractCue && hasSpecificContractCue && hasBankCue) {
|
||
return unicodeBridgeResolution("bank_operations_by_contract", "high", "unicode_bank_ops_by_contract_bridge_signal_detected");
|
||
}
|
||
if (hasContractCue && hasSpecificContractCue && hasDocumentCue) {
|
||
return unicodeBridgeResolution("list_documents_by_contract", "high", "unicode_documents_by_contract_bridge_signal_detected");
|
||
}
|
||
if (hasAccountAnchor &&
|
||
!hasDocumentCue &&
|
||
/(?:баланс|остат(?:ок)?|сальдо|что\s+на\s+сч(?:е|ё)те|по\s+сч(?:е|ё)ту|скольк|скока|account\s+balance|balance\s+account|as\s+of)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("account_balance_snapshot", "high", "unicode_account_balance_bridge_signal_detected");
|
||
}
|
||
if (/(?:ндс|vat)/iu.test(normalized)) {
|
||
const hasVatDebtCue = /(?:долг|должн|подтвержд)/iu.test(normalized);
|
||
const hasTaxPeriodCue = /(?:налогов|налоговую|бюджет|декларац|квартал|\b[1-4]\s*кв)/iu.test(normalized);
|
||
const hasVatMonthPeriodCue = /(?:за|на|в)\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?/iu.test(normalized) &&
|
||
!/\b\d{1,2}\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?/iu.test(normalized);
|
||
if ((hasTaxPeriodCue || (hasVatMonthPeriodCue && !hasVatDebtCue)) &&
|
||
/(?:скольк|скока|надо|нужно|заплат|уплат|оплат|прикин)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("vat_liability_confirmed_for_tax_period", "high", "vat_liability_confirmed_tax_period_signal_detected");
|
||
}
|
||
if (/(?:прогноз|прикин|план)/iu.test(normalized) ||
|
||
(!hasVatDebtCue && /(?:надо|нужно)\s+(?:заплат|оплат|уплат)/iu.test(normalized))) {
|
||
return unicodeBridgeResolution("vat_payable_forecast", "high", "forecast_tax_signal_detected");
|
||
}
|
||
if (/(?:долг|подтвержд|скольк|скока|надо|нужно|заплат|уплат|оплат)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution(hasTaxPeriodCue
|
||
? "vat_liability_confirmed_for_tax_period"
|
||
: "vat_payable_confirmed_as_of_date", "high", "vat_payable_confirmed_signal_detected");
|
||
}
|
||
}
|
||
if (/(?:незакрыт|открыт).*договор/iu.test(normalized) &&
|
||
!/(?:долг|задолж|хвост|висит|расчет|расчёт)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("open_contracts_confirmed_as_of_date", "high", "unicode_open_contracts_snapshot_bridge_signal_detected");
|
||
}
|
||
if (/(?:долг|задолж|хвост|висит|открыт(?:ые|ая|ый)?\s+задолж|open\s+items?)/iu.test(normalized) &&
|
||
(hasContractCue || hasCounterpartyCue || hasAccountAnchor || /покупател|клиент/iu.test(normalized))) {
|
||
return unicodeBridgeResolution("open_items_by_counterparty_or_contract", "high", "unicode_open_items_bridge_signal_detected");
|
||
}
|
||
if (hasContractCue &&
|
||
/(?:без\s+(?:закрыт|оплат|плат[её]ж|док)|не\s+закрыт|аванс|отгрузк|плат[её]ж.*без|док.*без|расхожд|mismatch)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("list_open_contracts", "high", "unicode_open_contracts_list_bridge_signal_detected");
|
||
}
|
||
if (hasContractCue &&
|
||
/(?:скольк.*(?:всего\s+)?договор|договор.*(?:заведен|использовал|реально\s+использ)|сколько\s+из\s+них)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("contract_usage_overview", "high", "unicode_contract_usage_overview_bridge_signal_detected");
|
||
}
|
||
if (hasContractCue &&
|
||
/(?:нескольк(?:ими|о)?\s+договор|контрагент.*нескольк.*договор|какие\s+из\s+договор.*актив)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("contract_usage_and_value", "high", "unicode_contract_usage_value_bridge_signal_detected");
|
||
}
|
||
if (hasContractCue &&
|
||
/(?:все|покажи|показать|какие|список|list|show)/iu.test(normalized) &&
|
||
!hasSpecificContractCue &&
|
||
!hasDocumentCue &&
|
||
!hasBankCue &&
|
||
(hasCounterpartyCue || hasLooseCounterpartyByAnchor)) {
|
||
return unicodeBridgeResolution("list_contracts_by_counterparty", "high", "unicode_contracts_by_counterparty_bridge_signal_detected");
|
||
}
|
||
if (hasContractCue && !hasSpecificContractCue && !hasDocumentCue && !hasBankCue && hasCounterpartyCue) {
|
||
return unicodeBridgeResolution("list_contracts_by_counterparty", "high", "unicode_contracts_by_counterparty_bridge_signal_detected");
|
||
}
|
||
if (hasBankCue && (hasCounterpartyCue || hasLooseCounterpartyByAnchor) && !hasContractCue) {
|
||
return unicodeBridgeResolution("bank_operations_by_counterparty", "high", "unicode_bank_ops_by_counterparty_bridge_signal_detected");
|
||
}
|
||
if (hasDocumentCue && (hasCounterpartyCue || hasLooseCounterpartyByAnchor) && !hasContractCue && !hasAccountAnchor) {
|
||
return unicodeBridgeResolution("list_documents_by_counterparty", "high", "unicode_documents_by_counterparty_bridge_signal_detected");
|
||
}
|
||
if (/(?:за\s+какие\s+годы|диапазон\s+лет|покрыт(?:ие|ый)\s+период|какой\s+год.*актив|какой\s+месяц.*актив|год.*пассив|месяц.*пассив|минимальн.*док|минимальн.*операц|profile\s+period|top\s*year|top\s*month)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("period_coverage_profile", "high", "unicode_period_coverage_bridge_signal_detected");
|
||
}
|
||
if (/(?:тип(?:ы|ов)?\s+док|документ.*(?:чаще|редк|больше\s+всего|меньше\s+всего)|раздел(?:ы|ов)?\s+уч[её]та|сводк.*тип.*док|document\s+type|account\s+section)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("document_type_and_account_section_profile", "high", "unicode_document_type_profile_bridge_signal_detected");
|
||
}
|
||
if (/(?:скольк|скока|число|количеств|разбей|раздели|сформируй\s+сводк)/iu.test(normalized) &&
|
||
/(?:контрагент|поставщик|клиент|покупател|заказчик|рол)/iu.test(normalized) &&
|
||
!/(?:активн|давно|нов(?:ые|ых)|однораз|уш[её]л|исчез|регулярн|эпизодич|частот|разов|churn|lifecycle)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("counterparty_population_and_roles", "high", "unicode_counterparty_population_bridge_signal_detected");
|
||
}
|
||
if (/(?:скок|скока|сколько)\s+(?:клиент|покупател|заказчик)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("counterparty_population_and_roles", "high", "unicode_counterparty_population_bridge_signal_detected");
|
||
}
|
||
if (/(?:активн(?:ые|ость)?\s+(?:клиент|покупател|поставщик|контрагент)|все\s+время|однораз|давно\s+(?:не\s+)?(?:покупал|платил|актив)|уш[её]л|исчез|нов(?:ые|ых)\s+(?:клиент|контрагент)|регулярн|разов(?:ый|ые)|stale\s+supplier|churn|lifecycle)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("counterparty_activity_lifecycle", "high", "unicode_counterparty_lifecycle_bridge_signal_detected");
|
||
}
|
||
if (hasContractCue && /(?:давно\s+не\s+использ|не\s+использ|stale|inactive)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("contract_usage_overview", "high", "unicode_contract_usage_overview_bridge_signal_detected");
|
||
}
|
||
if (hasContractCue && (hasMoneyCue || hasRankingCue || /оборот|бюджет|сумм|стоим|value|amount/iu.test(normalized))) {
|
||
return unicodeBridgeResolution("contract_usage_and_value", "high", "unicode_contract_usage_value_bridge_signal_detected");
|
||
}
|
||
if (/(?:поставщик|vendor|supplier|кому\s+(?:ушло|платили|заплатили)|выплат|исходящ|списан|сгрузил)/iu.test(normalized) &&
|
||
(hasMoneyCue || hasRankingCue || /плат[её]ж|оплат|выплат|outflow|payout/iu.test(normalized))) {
|
||
return unicodeBridgeResolution("supplier_payouts_profile", "high", "unicode_supplier_payouts_bridge_signal_detected");
|
||
}
|
||
if ((/(?:клиент|покупател|заказчик|контрагент|альтернатива|свк)/iu.test(normalized) || hasRankingCue) &&
|
||
(hasMoneyCue || /поступлен|приход|входящ|inflow/iu.test(normalized))) {
|
||
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_bridge_signal_detected");
|
||
}
|
||
if (/(?:к[оа]му\s+мы\s+должны|мы\s+должны\s+к[оа]му|кредитор|payables?)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("payables_confirmed_as_of_date", "high", "payables_debt_lifecycle_signal_detected");
|
||
}
|
||
if (/(?:кто\s+нам\s+должен|нам\s+должны|дебитор|receivables?)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("receivables_confirmed_as_of_date", "high", "unicode_receivables_snapshot_bridge_signal_detected");
|
||
}
|
||
if (/(?:покупател|клиент).*(?:не\s+плат|просроч|долго\s+долж|долг.*давн)|(?:долг|задолж).*(?:покупател|клиент)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("list_receivables_counterparties", "high", "unicode_receivables_list_bridge_signal_detected");
|
||
}
|
||
if (/(?:что|че|чё|какие|покажи|показать|список).*(?:склад|остат|товар)|(?:склад|остат).*(?:сейчас|лежит|есть|на\s+дату|на\s+конец|what|show|list)/iu.test(normalized) &&
|
||
!/(?:поставщик|продаж|реализ|цепоч|документал|давно|стар(?:ые|ый|ым|ых)|закуп)/iu.test(normalized)) {
|
||
return unicodeBridgeResolution("inventory_on_hand_as_of_date", "high", "unicode_inventory_on_hand_bridge_signal_detected");
|
||
}
|
||
return null;
|
||
}
|
||
function resolveDirectDebtSnapshotIntent(text) {
|
||
const normalized = String(text ?? "").trim().toLowerCase();
|
||
if (!normalized) {
|
||
return null;
|
||
}
|
||
if (/(?:ндс|vat)/iu.test(normalized)) {
|
||
return null;
|
||
}
|
||
const hasSnapshotCue = /(?:кто|сколько|есть\s+ли|по\s+состоянию|на\s+сегодня|на\s+дату|срез|остаток|сальдо|баланс|на\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?|на\s+(?:19|20)\d{2}|as\s+of|today|current|balance)/iu.test(normalized);
|
||
const hasReceivablesCue = /(?:кто\s+(?:является\s+)?дебитором|дебитор(?:[а-яё]{0,8})?|дебиторск(?:[а-яё]{0,8})?|кто\s+нам\s+долж(?:ен|ны|на|но)?|нам\s+(?:кто-то\s+|кто\s+)?долж(?:ен|ны|на|но)?|нам\s+торч(?:ат|ит|ишь|у|али)?|к\s+получению|к\s+взысканию|who\s+owes\s+us|receivables?|accounts\s+receivable)/iu.test(normalized);
|
||
const hasPayablesCue = /(?:кто\s+(?:является\s+)?кредитором|кредитор(?:[а-яё]{0,8})?|кому\s+мы\s+долж(?:ны|н[ао])?|мы\s+долж(?:ны|н[ао])?\s+кому|мы\s+долж(?:ны|н[ао])?|к\s+оплате|who\s+we\s+owe|payables?|accounts\s+payable)/iu.test(normalized);
|
||
if (hasReceivablesCue && !hasPayablesCue && hasSnapshotCue) {
|
||
return {
|
||
intent: "receivables_confirmed_as_of_date",
|
||
confidence: "high",
|
||
reasons: ["receivables_debt_lifecycle_signal_detected", "direct_debt_snapshot_signal_detected"]
|
||
};
|
||
}
|
||
if (hasPayablesCue && !hasReceivablesCue && hasSnapshotCue) {
|
||
return {
|
||
intent: "payables_confirmed_as_of_date",
|
||
confidence: "high",
|
||
reasons: ["payables_debt_lifecycle_signal_detected", "direct_debt_snapshot_signal_detected"]
|
||
};
|
||
}
|
||
return null;
|
||
}
|
||
function resolveAddressIntent(userMessage) {
|
||
const text = String(userMessage ?? "").trim().toLowerCase();
|
||
const repairedText = repairLikelyUtf8Mojibake(text).trim().toLowerCase();
|
||
const bridgeText = repairedText && repairedText !== text ? `${text} ${repairedText}` : text;
|
||
const turnNoiseNormalizedBridgeText = bridgeText
|
||
.replace(/(^|[^\p{L}0-9_])намс(?=$|[^\p{L}0-9_])/giu, "$1нам")
|
||
.replace(/(^|[^\p{L}0-9_])какиек(?=$|[^\p{L}0-9_])/giu, "$1какие");
|
||
const currentTurnBridgeText = turnNoiseNormalizedBridgeText !== bridgeText ? `${bridgeText} ${turnNoiseNormalizedBridgeText}` : bridgeText;
|
||
const directDebtSnapshotIntent = resolveDirectDebtSnapshotIntent(currentTurnBridgeText);
|
||
if (directDebtSnapshotIntent) {
|
||
const reasons = [...directDebtSnapshotIntent.reasons];
|
||
if (currentTurnBridgeText !== bridgeText && !reasons.includes("current_turn_noise_normalized")) {
|
||
reasons.push("current_turn_noise_normalized");
|
||
}
|
||
return {
|
||
...directDebtSnapshotIntent,
|
||
reasons
|
||
};
|
||
}
|
||
const unicodeAddressIntent = resolveUnicodeAddressIntentBridge(currentTurnBridgeText);
|
||
if (unicodeAddressIntent) {
|
||
const reasons = [...unicodeAddressIntent.reasons];
|
||
if (currentTurnBridgeText !== bridgeText && !reasons.includes("current_turn_noise_normalized")) {
|
||
reasons.push("current_turn_noise_normalized");
|
||
}
|
||
if (unicodeAddressIntent.intent === "customer_revenue_and_payments" &&
|
||
[text, repairedText, turnNoiseNormalizedBridgeText, currentTurnBridgeText].some((sample) => hasSpecificCounterpartyRevenueBridgeSignal(sample)) &&
|
||
!reasons.includes("specific_counterparty_revenue_bridge_signal_detected")) {
|
||
reasons.push("specific_counterparty_revenue_bridge_signal_detected");
|
||
}
|
||
return {
|
||
...unicodeAddressIntent,
|
||
reasons
|
||
};
|
||
}
|
||
const hasExplicitVatLiabilityPeriodBridge = /(?:\u043d\u0434\u0441|vat)/iu.test(text) &&
|
||
/(?:\b(?:19|20)\d{2}\b|\u0437\u0430\s+(?:\d{4}|\u0433\u043e\u0434|\u043f\u0435\u0440\u0438\u043e\u0434|\u043a\u0432\u0430\u0440\u0442\u0430\u043b|\u043c\u0435\u0441\u044f\u0446))/iu.test(text) &&
|
||
/(?:\u043a\u0430\u043a\u043e\u0439|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u043d\u0430\u0447\u0438\u0441\u043b|\u0443\u043f\u043b\u0430\u0447|\u0443\u043f\u043b\u0430\u0442|\u043f\u0440\u043e\u0434\u0430\u0436|\u043f\u043e\u043a\u0443\u043f|\u0432\u044b\u0447\u0435\u0442|\u043a\s+\u0443\u043f\u043b\u0430\u0442|\u043a\s+\u0432\u043e\u0437\u043c\u0435\u0449|\u043f\u043e\u0437\u0438\u0446|liability|payable|charged|paid|sales|purchase|deduction|position)/iu.test(text);
|
||
if (hasExplicitVatLiabilityPeriodBridge) {
|
||
return {
|
||
intent: "vat_liability_confirmed_for_tax_period",
|
||
confidence: "high",
|
||
reasons: ["vat_liability_explicit_period_bridge_signal_detected"]
|
||
};
|
||
}
|
||
const hasLooseVatPayableBridge = /(?:\u043d\u0434\u0441|vat)/iu.test(text) &&
|
||
/(?:\u043a\u0430\u043a\u043e\u0439\s+\u043d\u0434\u0441\s+(?:(?:\u043d\u0430\u043c|(?:\u043c\u044b\s+)?\u0434\u043e\u043b\u0436\u043d\u044b)\s+)?(?:\u043d\u0430\u0434\u043e|\u043d\u0443\u0436\u043d\u043e|\u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e)|(?:\u043d\u0430\u043c|\u043c\u044b\s+)?\u043d\u0430\u0434\u043e\s+(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c|\u0441\u0433\u0440\u0443\u0437\u0438\u0442\u044c)|(?:\u043d\u0430\u043c|\u043c\u044b\s+)?\u043d\u0443\u0436\u043d\u043e\s+(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c|\u0441\u0433\u0440\u0443\u0437\u0438\u0442\u044c)|\u043c\u044b\s+\u0434\u043e\u043b\u0436\u043d\u044b\s+(?:\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c|\u0441\u0433\u0440\u0443\u0437\u0438\u0442\u044c)|\u043d\u0434\u0441\s+\u043a\s+\u0443\u043f\u043b\u0430\u0442\u0435)/iu.test(text) &&
|
||
/(?:\u0437\u0430\s+(?:\d{4}|(?:\u044f\u043d\u0432\u0430\u0440|\u0444\u0435\u0432\u0440\u0430\u043b|\u043c\u0430\u0440\u0442|\u0430\u043f\u0440\u0435\u043b|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d|\u0438\u044e\u043b|\u0430\u0432\u0433\u0443\u0441\u0442|\u0441\u0435\u043d\u0442\u044f\u0431\u0440|\u043e\u043a\u0442\u044f\u0431\u0440|\u043d\u043e\u044f\u0431\u0440|\u0434\u0435\u043a\u0430\u0431\u0440)\S*(?:\s+(?:19|20)\d{2})?)|\u043d\u0430\s+(?:\u044f\u043d\u0432\u0430\u0440|\u0444\u0435\u0432\u0440\u0430\u043b|\u043c\u0430\u0440\u0442|\u0430\u043f\u0440\u0435\u043b|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d|\u0438\u044e\u043b|\u0430\u0432\u0433\u0443\u0441\u0442|\u0441\u0435\u043d\u0442\u044f\u0431\u0440|\u043e\u043a\u0442\u044f\u0431\u0440|\u043d\u043e\u044f\u0431\u0440|\u0434\u0435\u043a\u0430\u0431\u0440)\S*(?:\s+(?:19|20)\d{2})?|\u0432\s+(?:\u044f\u043d\u0432\u0430\u0440|\u0444\u0435\u0432\u0440\u0430\u043b|\u043c\u0430\u0440\u0442|\u0430\u043f\u0440\u0435\u043b|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d|\u0438\u044e\u043b|\u0430\u0432\u0433\u0443\u0441\u0442|\u0441\u0435\u043d\u0442\u044f\u0431\u0440|\u043e\u043a\u0442\u044f\u0431\u0440|\u043d\u043e\u044f\u0431\u0440|\u0434\u0435\u043a\u0430\u0431\u0440)\S*(?:\s+(?:19|20)\d{2})?|\b[1-4]\s*(?:\u043a\u0432\u0430\u0440\u0442\u0430\u043b|\u043a\u0432\.?)\b)/iu.test(text);
|
||
if (hasLooseVatPayableBridge) {
|
||
return {
|
||
intent: "vat_liability_confirmed_for_tax_period",
|
||
confidence: "high",
|
||
reasons: ["vat_liability_colloquial_bridge_signal_detected"]
|
||
};
|
||
}
|
||
const hasExplicitReceivablesSnapshotBridge = /(?:\u043d\u0430\u043c\s+\u043a\u0442\u043e-\u0442\u043e\s+\u0434\u043e\u043b\u0436\u0435\u043d|\u043d\u0430\u043c\s+\u043a\u0442\u043e\s+\u0434\u043e\u043b\u0436\u0435\u043d|\u043a\u0442\u043e\s+\u043d\u0430\u043c\s+\u0434\u043e\u043b\u0436\u0435\u043d|\u0435\u0441\u0442\u044c\s+\u043b\u0438\s+\u0434\u0435\u0431\u0438\u0442\u043e\u0440\u0441\u043a\w+\s+\u0437\u0430\u0434\u043e\u043b\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u044c|\u0437\u0430\u0434\u043e\u043b\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u044c\s+\u043d\u0430\u043c|receivables?)/iu.test(currentTurnBridgeText);
|
||
if (hasExplicitReceivablesSnapshotBridge) {
|
||
return {
|
||
intent: "receivables_confirmed_as_of_date",
|
||
confidence: "high",
|
||
reasons: currentTurnBridgeText !== bridgeText
|
||
? ["receivables_snapshot_bridge_signal_detected", "current_turn_noise_normalized"]
|
||
: ["receivables_snapshot_bridge_signal_detected"]
|
||
};
|
||
}
|
||
const hasExplicitPayablesSnapshotBridge = /(?:\u043c\u044b\s+\u0434\u043e\u043b\u0436\u043d\u044b\s+\u043a\u043e\u043c\u0443-\u0442\u043e\s+\u0434\u0435\u043d\u0435\u0433|\u043c\u044b\s+\u0434\u043e\u043b\u0436\u043d\u044b\s+\u043a\u043e\u043c\u0443\u0442\u043e\s+\u0434\u0435\u043d\u0435\u0433|\u043c\u044b\s+\u043a\u043e\u043c\u0443-\u0442\u043e\s+\u0434\u043e\u043b\u0436\u043d\u044b|\u043a\u043e\u043c\u0443\s+\u043c\u044b\s+\u0434\u043e\u043b\u0436\u043d\u044b|\u0435\u0441\u0442\u044c\s+\u043b\u0438\s+\u0437\u0430\u0434\u043e\u043b\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u044c\s+\u043f\u0435\u0440\u0435\u0434\s+\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430\u043c\u0438|\u0437\u0430\u0434\u043e\u043b\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u044c\s+\u043f\u0435\u0440\u0435\u0434\s+\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430\u043c\u0438|payables?)/iu.test(currentTurnBridgeText);
|
||
if (hasExplicitPayablesSnapshotBridge) {
|
||
return {
|
||
intent: "payables_confirmed_as_of_date",
|
||
confidence: "high",
|
||
reasons: currentTurnBridgeText !== bridgeText
|
||
? ["payables_snapshot_bridge_signal_detected", "current_turn_noise_normalized"]
|
||
: ["payables_snapshot_bridge_signal_detected"]
|
||
};
|
||
}
|
||
const hasDirectInventoryAgingBridge = /(?:\u043e\u0447\u0435\u043d\u044c\s+\u0434\u0430\u0432\u043d\u043e|\u0434\u0430\u0432\u043d\u043e\s+\u043a\u0443\u043f\u043b|\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u0438\u043e\u0431\u0440\u0435\u0442|\u0441\u0442\u0430\u0440(?:\u044b\u0435|\u044b\u043c|\u044b\u0445)?\s+\u0437\u0430\u043a\u0443\u043f|\u0441\u0442\u0430\u0440(?:\u044b\u0439|\u0430\u044f|\u043e\u0435)?\s+\u0442\u043e\u0432\u0430\u0440|\u0437\u0430\u043a\u0443\u043f\u043b\u0435\u043d(?:\u043d\u044b\u0435|\u043d\u044b\u043c|\u043d\u044b\u0445|\u0430\u044f|\u044b\u0439)?\s+\u0437\u0430\u0434\u043e\u043b\u0433\u043e\s+\u0434\u043e|\u0437\u0430\u0434\u043e\u043b\u0433\u043e\s+\u0434\u043e|old\s+stock|old\s+purchase|very\s+old\s+stock|aging\s+by\s+purchase\s+date)/iu.test(bridgeText);
|
||
if (hasDirectInventoryAgingBridge || hasInventoryAgingSignal(text) || hasInventoryAgingSignal(repairedText)) {
|
||
return {
|
||
intent: "inventory_aging_by_purchase_date",
|
||
confidence: "high",
|
||
reasons: ["inventory_aging_bridge_signal_detected"]
|
||
};
|
||
}
|
||
const hasDirectRevenueAggregateBridge = /(?:\u0441\u0430\u043c(?:\u044b\u0439|\u0430\u044f|\u043e\u0435)?\s+\u0434\u043e\u0445\u043e\u0434\u043d(?:\u044b\u0439|\u0430\u044f|\u043e\u0435|\u044b\u0435|\u043e\u0433\u043e|\u043e\u043c\u0443|\u044b\u043c|\u044b\u0445)?\s+\u043a\u043b\u0438\u0435\u043d\u0442|(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f)\s+(?:\u0443\s+\u043d\u0430\u0441\s+)?\u0441\u0430\u043c(?:\u044b\u0439|\u0430\u044f|\u043e\u0435)?\s+\u0434\u043e\u0445\u043e\u0434\u043d(?:\u044b\u0439|\u0430\u044f|\u043e\u0435|\u044b\u0435|\u043e\u0433\u043e|\u043e\u043c\u0443|\u044b\u043c|\u044b\u0445)?\s+\u0433\u043e\u0434|(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a).*(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438|\u0432\u044b\u0440\u0443\u0447\u0438\u043b\u0438)|\u0432\u044b\u0440\u0443\u0447\u043a\u0430\s+\u0437\u0430\s+\d{4})/iu.test(bridgeText);
|
||
const hasSpecificCounterpartyRevenueBridge = [text, repairedText, turnNoiseNormalizedBridgeText, currentTurnBridgeText].some((sample) => hasSpecificCounterpartyRevenueBridgeSignal(sample));
|
||
if (hasDirectRevenueAggregateBridge ||
|
||
hasCustomerRevenueRankingBridgeSignal(bridgeText) ||
|
||
hasSpecificCounterpartyRevenueBridge) {
|
||
return {
|
||
intent: "customer_revenue_and_payments",
|
||
confidence: "medium",
|
||
reasons: [
|
||
hasSpecificCounterpartyRevenueBridge
|
||
? "specific_counterparty_revenue_bridge_signal_detected"
|
||
: "customer_revenue_ranking_bridge_signal_detected"
|
||
]
|
||
};
|
||
}
|
||
const hasHistoricalInventorySnapshotBridge = [text, repairedText, bridgeText].some((sample) => /(?:\u043e\u0441\u0442\u0430\u0442|inventory|stock|\u0441\u043a\u043b\u0430\u0434|остат|склад)/iu.test(sample) &&
|
||
/(?:(?:\u043d\u0430|\u0437\u0430|на|за)\s+(?:\u044f\u043d\u0432\u0430\u0440|\u0444\u0435\u0432\u0440\u0430\u043b|\u043c\u0430\u0440\u0442|\u0430\u043f\u0440\u0435\u043b|\u043c\u0430[\u0439\u044f]|\u0438\u044e\u043d|\u0438\u044e\u043b|\u0430\u0432\u0433\u0443\u0441\u0442|\u0441\u0435\u043d\u0442\u044f\u0431\u0440|\u043e\u043a\u0442\u044f\u0431\u0440|\u043d\u043e\u044f\u0431\u0440|\u0434\u0435\u043a\u0430\u0431\u0440|март|апрел|май|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?|(?:\u043d\u0430|\u0437\u0430|на|за)\s+\d{4}|\b(?:19|20)\d{2}\b)/iu.test(sample));
|
||
if (hasHistoricalInventorySnapshotBridge) {
|
||
return {
|
||
intent: "inventory_on_hand_as_of_date",
|
||
confidence: "medium",
|
||
reasons: ["inventory_historical_snapshot_bridge_signal_detected"]
|
||
};
|
||
}
|
||
if (hasInventoryDocumentaryChainBridgeSignal(text)) {
|
||
return {
|
||
intent: "inventory_purchase_to_sale_chain",
|
||
confidence: "medium",
|
||
reasons: ["inventory_documentary_chain_bridge_signal_detected"]
|
||
};
|
||
}
|
||
if (hasInventoryProvenanceBridgeSignal(text)) {
|
||
return {
|
||
intent: "inventory_purchase_provenance_for_item",
|
||
confidence: "medium",
|
||
reasons: ["inventory_provenance_bridge_signal_detected"]
|
||
};
|
||
}
|
||
if (hasColloquialInventoryOnHandBridgeSignal(text)) {
|
||
return {
|
||
intent: "inventory_on_hand_as_of_date",
|
||
confidence: "medium",
|
||
reasons: ["inventory_on_hand_colloquial_bridge_signal_detected"]
|
||
};
|
||
}
|
||
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"]
|
||
};
|
||
}
|
||
const inventoryIntent = (0, addressInventoryIntentSignals_1.resolveInventoryAddressIntent)(text);
|
||
if (inventoryIntent) {
|
||
return inventoryIntent;
|
||
}
|
||
const counterpartyIntent = (0, addressCounterpartyIntentSignals_1.resolveCounterpartyAddressIntent)(text, {
|
||
hasAny,
|
||
openItemsHints: OPEN_ITEMS_HINTS,
|
||
openContractsHints: OPEN_CONTRACTS_HINTS,
|
||
documentsByCounterpartyHints: DOCUMENTS_BY_COUNTERPARTY_HINTS,
|
||
bankOperationsByCounterpartyHints: BANK_OPERATIONS_BY_COUNTERPARTY_HINTS,
|
||
documentsByContractHints: DOCUMENTS_BY_CONTRACT_HINTS,
|
||
hasCounterpartyDebtLongevitySignal,
|
||
hasInventoryAgingSignal,
|
||
hasInventoryProvenanceSignalV2,
|
||
hasInventoryPurchaseDocumentsSignalV2,
|
||
hasInventorySaleTraceSignalV2,
|
||
hasAccountNumberAnchor,
|
||
hasCompactAccountCodeToken,
|
||
hasPeriodCoverageProfileSignal,
|
||
hasPartyAnchorMention,
|
||
hasContractAnchorSignal,
|
||
hasAccountBalanceSignal,
|
||
hasDocumentTypeAndAccountSectionProfileSignal,
|
||
hasCounterpartyPopulationAndRolesSignal,
|
||
hasCounterpartyActivityLifecycleSignal,
|
||
hasContractUsageOverviewSignal,
|
||
hasOpenContractsListSignal,
|
||
hasCustomerRevenueAndPaymentsSignal,
|
||
hasSupplierPayoutsProfileSignal,
|
||
hasContractUsageAndValueSignal,
|
||
hasContractListByCounterpartySignal,
|
||
hasBankOperationSignal,
|
||
hasDocumentSignal,
|
||
hasLooseByAnchorMention,
|
||
hasHeuristicCounterpartyAnchor,
|
||
hasCounterpartyShipmentItemFlowSignal,
|
||
hasImplicitCounterpartyAnchorAroundDocs,
|
||
hasGenericAddressLookupSignal
|
||
});
|
||
if (counterpartyIntent) {
|
||
return counterpartyIntent;
|
||
}
|
||
return {
|
||
intent: "unknown",
|
||
confidence: "low",
|
||
reasons: ["intent_not_supported_in_v1"]
|
||
};
|
||
}
|