"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveAddressIntent = resolveAddressIntent; 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", "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 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 like 60.01 / 62, while avoiding date fragments. return /(? 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 resolveAddressIntent(userMessage) { const text = String(userMessage ?? "").trim().toLowerCase(); if (hasAny(text, RECEIVABLES_STRONG)) { return { intent: "list_receivables_counterparties", confidence: "high", reasons: ["receivables_signal_detected"] }; } if (hasAny(text, PAYABLES_STRONG)) { return { intent: "list_payables_counterparties", confidence: "high", reasons: ["payables_signal_detected"] }; } if (hasDocumentsFormingBalanceSignal(text) && hasDocumentsFormingBalanceAccountAnchor(text)) { return { intent: "documents_forming_balance", confidence: "high", reasons: ["documents_forming_balance_signal_detected"] }; } if (hasDocumentsByAccountDrilldownSignal(text)) { return { intent: "documents_forming_balance", confidence: "medium", reasons: ["documents_by_account_drilldown_signal_detected"] }; } if (hasOpenContractsListSignal(text)) { return { intent: "list_open_contracts", confidence: "medium", reasons: ["open_contract_signal_detected"] }; } if (hasAny(text, OPEN_ITEMS_HINTS) && (text.includes("контраг") || text.includes("договор") || text.includes("контракт") || text.includes("counterparty") || text.includes("contract"))) { return { intent: "open_items_by_counterparty_or_contract", confidence: "medium", reasons: ["open_items_signal_detected"] }; } if (hasPeriodCoverageProfileSignal(text) && !hasPartyAnchorMention(text) && !hasContractAnchorSignal(text) && !hasAccountBalanceSignal(text)) { return { intent: "period_coverage_profile", confidence: "high", reasons: ["period_coverage_profile_signal_detected"] }; } if (hasDocumentTypeAndAccountSectionProfileSignal(text) && !hasPartyAnchorMention(text) && !hasContractAnchorSignal(text) && !hasAccountBalanceSignal(text)) { return { intent: "document_type_and_account_section_profile", confidence: "high", reasons: ["document_type_and_account_section_profile_signal_detected"] }; } if (hasCounterpartyPopulationAndRolesSignal(text) && !hasContractAnchorSignal(text) && !hasAccountBalanceSignal(text)) { return { intent: "counterparty_population_and_roles", confidence: "high", reasons: ["counterparty_population_and_roles_signal_detected"] }; } if (hasCounterpartyActivityLifecycleSignal(text) && !hasContractAnchorSignal(text) && !hasAccountBalanceSignal(text)) { return { intent: "counterparty_activity_lifecycle", confidence: "high", reasons: ["counterparty_activity_lifecycle_signal_detected"] }; } if (hasContractUsageOverviewSignal(text) && !hasAccountBalanceSignal(text) && !hasOpenContractsListSignal(text)) { return { intent: "contract_usage_overview", confidence: "high", reasons: ["contract_usage_overview_signal_detected"] }; } if (hasCustomerRevenueAndPaymentsSignal(text) && !hasAccountBalanceSignal(text)) { return { intent: "customer_revenue_and_payments", confidence: "high", reasons: ["customer_revenue_and_payments_signal_detected"] }; } if (hasSupplierPayoutsProfileSignal(text) && !hasAccountBalanceSignal(text)) { return { intent: "supplier_payouts_profile", confidence: "high", reasons: ["supplier_payouts_profile_signal_detected"] }; } if (hasContractUsageAndValueSignal(text) && !hasAccountBalanceSignal(text) && !hasOpenContractsListSignal(text)) { return { intent: "contract_usage_and_value", confidence: "high", reasons: ["contract_usage_and_value_signal_detected"] }; } if (hasContractListByCounterpartySignal(text)) { return { intent: "list_contracts_by_counterparty", confidence: "medium", reasons: ["contracts_by_counterparty_signal_detected"] }; } if (hasContractAnchorSignal(text) && hasBankOperationSignal(text)) { return { intent: "bank_operations_by_contract", confidence: "medium", reasons: ["bank_ops_by_contract_signal_detected"] }; } if (hasContractAnchorSignal(text) && (hasAny(text, DOCUMENTS_BY_CONTRACT_HINTS) || hasDocumentSignal(text))) { return { intent: "list_documents_by_contract", confidence: "medium", reasons: ["documents_by_contract_signal_detected"] }; } if (hasAny(text, BANK_OPERATIONS_BY_COUNTERPARTY_HINTS) && (hasPartyAnchorMention(text) || hasLooseByAnchorMention(text) || hasHeuristicCounterpartyAnchor(text))) { return { intent: "bank_operations_by_counterparty", confidence: "medium", reasons: ["bank_ops_by_counterparty_signal_detected"] }; } if (hasAny(text, DOCUMENTS_BY_COUNTERPARTY_HINTS) && (hasPartyAnchorMention(text) || hasLooseByAnchorMention(text) || hasImplicitCounterpartyAnchorAroundDocs(text) || hasHeuristicCounterpartyAnchor(text))) { return { intent: "list_documents_by_counterparty", confidence: "medium", reasons: ["documents_by_counterparty_signal_detected"] }; } if (hasAccountBalanceSignal(text)) { return { intent: "account_balance_snapshot", confidence: "high", reasons: ["account_balance_signal_detected"] }; } if (hasLooseByAnchorMention(text) && hasGenericAddressLookupSignal(text)) { return { intent: "list_documents_by_counterparty", confidence: "low", reasons: ["generic_lookup_with_loose_anchor_fallback"] }; } if (hasAny(text, OPEN_CONTRACTS_HINTS) && (text.includes("договор") || text.includes("контракт") || text.includes("contract"))) { return { intent: "list_open_contracts", confidence: "medium", reasons: ["open_contract_signal_detected"] }; } return { intent: "unknown", confidence: "low", reasons: ["intent_not_supported_in_v1"] }; }