NODEDC_1C/llm_normalizer/backend/dist/services/addressQueryClassifier.js

451 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectAddressQuestionMode = detectAddressQuestionMode;
const ADDRESS_ACTION_TOKENS = [
"show",
"list",
"find",
"get",
"lookup",
"open",
"balance",
"debt",
"owe",
"покажи",
"покаж",
"показ",
"проверь",
"провер",
"чекни",
"чекн",
"глянь",
"глян",
"посмотри",
"смотри",
"список",
"найди",
"найд",
"выведи",
"вывед",
"кто",
"кому",
"какой",
"какая",
"какое",
"какую",
"какие",
"каких",
"что по",
"че по",
"чё по",
"остаток",
"скока",
"сколько",
"долг",
"задолж",
"хвост",
"незакрыт"
];
const ADDRESS_ENTITY_TOKENS = [
"counterparty",
"counterparties",
"company",
"organization",
"supplier",
"vendor",
"customer",
"client",
"partner",
"contract",
"contracts",
"account",
"accounts",
"document",
"documents",
"balance",
"payable",
"payables",
"receivable",
"receivables",
"owe",
"owes",
"owed",
"контрагент",
"контра",
"компан",
"организац",
"поставщик",
"заказчик",
"клиент",
"покупател",
"партнер",
"контракт",
"банк",
"выписк",
"операц",
"транзак",
"договор",
"счет",
"счёт",
"документ",
"доки",
"док",
"остаток",
"дебитор",
"кредитор",
"аванс",
"оплат",
"приход",
"чек",
"доход",
"выруч",
"сделк",
"бюджет",
"топ",
"самый",
"самые",
"поступлен",
"поступлени",
"списан",
"списани",
"склад",
"складе",
"складу",
"товар",
"товары",
"товарн",
"номенклат",
"материал",
"долг",
"должен",
"должны",
"должна"
];
const DEEP_REASONING_TOKENS = [
"why",
"because",
"root cause",
"mechanism",
"prove",
"chain",
"почему",
"причин",
"механизм",
"докажи",
"цепоч",
"разрыв",
"ошибк"
];
function hasManagementProfileSignal(text) {
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+всего|крут)/iu.test(text)) {
return true;
}
if (/(?:сводк[ауи].*тип[а-яё]*\s+док(?:умент|ов|и)?).*(?:дол[ья]|объем|объ[её]м)/iu.test(text)) {
return true;
}
if (/(?:за\s+какие\s+год[а-яё]*\s+в\s+баз[еы]\s+есть\s+данн)/iu.test(text)) {
return true;
}
if (/(?:какой\s+год[а-яё]*\s+сам(?:ый|ая|ое)\s+актив)/iu.test(text)) {
return true;
}
if (/(?:какой\s+год[а-яё]*\s+сам(?:ый|ая|ое)\s+пассив|какой\s+год[а-яё]*\s+наименее\s+актив|год\s+с\s+минимальн)/iu.test(text)) {
return true;
}
if (/(?:какой\s+месяц[а-яё]*\s+сам(?:ый|ая|ое)\s+актив)/iu.test(text)) {
return true;
}
if (/(?:какой\s+месяц[а-яё]*\s+сам(?:ый|ая|ое)\s+пассив|какой\s+месяц[а-яё]*\s+наименее\s+актив|месяц\s+с\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+(?:реже|редк|наименее|миним))/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;
}
if (/(?:(?:сколько|скока|скок)\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+контр)/iu.test(text)) {
return true;
}
if (/(?:покажи|выведи|список|какие|кто).*(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?).*(?:за\s+вс[её]\s+время|all\s+time|(?:^|[^\d])(19|20)\d{2}(?:[^\d]|$)|(?:^|[^\d])\d{2}\s*(?:г(?:од|ода)?|г)(?:[^\p{L}\p{N}]|$)|за\s+год|в\s+году)/iu.test(text)) {
return true;
}
if (/(?:какие|кто|покажи|выведи|список).*(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?).*(?:работал(?:и)?|активн(?:ые|ых|а|о)?).*(?:за\s+вс[её]\s+время|(?:19|20)\d{2}|за\s+год|в\s+году)|(?:active\s+customers?|customer\s+activity)/iu.test(text)) {
return true;
}
if (/(?:сколько\s+(?:всего\s+)?договор(?:ов|а)?(?:\s+заведен[оы])?|договорн(?:ая|ой)\s+баз[аы]|total\s+vs\s+used).*(?:использ|used|договор|contract)?/iu.test(text)) {
return true;
}
return false;
}
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 hasAddressFollowupSignal(text) {
if (/(?:за\s+любой\s+период|за\s+вс[её]\s+время|for\s+all\s+time|all\s+time)/iu.test(text)) {
return true;
}
if (/(?:\bесть\s+что(?:-|\s)?то\b|\bесть\s+ли\b|\bчто\s+есть\b)/iu.test(text)) {
return true;
}
return false;
}
function hasSelectedObjectInventoryFollowupSignal(text) {
if (!/(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции)/iu.test(text)) {
return false;
}
return /(?:у\s+кого\s+купили|у\s+кого\s+куплено|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|товар|позицию))?|где\s+куплено|кто\s+(?:поставил|продал)|кому\s+(?:продали|реализовали)|когда\s+(?:примерно\s+)?купили|по\s+каким\s+документам\s+.*купили)/iu.test(text);
}
function hasDocsOrBankSignal(text) {
return /(?:док(?:и|умент|ументы|ументов)|docs?|documents?|банк|выписк|платеж|платёж|оплат|поступлен|списан|транзак|transactions?|bank\s+ops|bank\s+operations?)/iu.test(text);
}
function hasAccountCodeAnchor(text) {
return /(?<![\d-])\d{2}(?:[.,]\d{1,2})(?![\d-])/u.test(text);
}
function hasLikelyCounterpartyToken(text) {
const stopWords = new Set([
"за",
"с",
"по",
"на",
"и",
"или",
"док",
"доки",
"документ",
"документы",
"документов",
"банк",
"банковские",
"операции",
"платежи",
"платеж",
"платёж",
"контрагент",
"контрагенту",
"контрагента",
"компания",
"компании",
"организация",
"организации",
"год",
"года",
"г",
"плс",
"pls",
"пж",
"пжлст",
"пожалуйста",
"бля",
"блять",
"епт",
"ёпт",
"епта",
"нах",
"нахуй",
"покеж",
"покажи",
"показать",
"покаж",
"выведи",
"show",
"list",
"please",
"all",
"vse",
"количество",
"количеству",
"количества",
"активный",
"пассивный",
"наименее",
"минимум",
"реже",
"запрос",
"запросу",
"запроса",
"запросом",
"запросе",
"вопрос",
"вопросу",
"вопроса",
"вопросом",
"вопросе"
]);
const tokens = String(text ?? "")
.split(/[^a-zа-яё0-9._-]+/iu)
.map((token) => token.trim())
.filter((token) => token.length >= 2);
return tokens.some((token) => {
const lowered = token.toLowerCase();
if (stopWords.has(lowered)) {
return false;
}
if (/^\d+$/.test(lowered)) {
return false;
}
if (/^(?:19|20)\d{2}$/.test(lowered)) {
return false;
}
return true;
});
}
function hasAnyToken(text, tokens) {
return tokens.some((token) => text.includes(token));
}
function detectAddressQuestionMode(userMessage) {
const text = String(userMessage ?? "").trim().toLowerCase();
if (!text) {
return {
mode: "unsupported",
confidence: "low",
reasons: ["empty_message"]
};
}
const hasAddressAction = hasAnyToken(text, ADDRESS_ACTION_TOKENS);
const hasAddressEntity = hasAnyToken(text, ADDRESS_ENTITY_TOKENS);
const hasDeepReasoning = hasAnyToken(text, DEEP_REASONING_TOKENS);
const hasManagementSignal = hasManagementProfileSignal(text);
const hasLooseByAnchor = hasLooseByAnchorMention(text);
const hasFollowupSignal = hasAddressFollowupSignal(text);
const hasSelectedObjectInventoryFollowup = hasSelectedObjectInventoryFollowupSignal(text);
const hasAccountCode = hasAccountCodeAnchor(text);
if (hasAddressAction && (hasAddressEntity || hasAccountCode) && !hasDeepReasoning) {
return {
mode: "address_query",
confidence: "high",
reasons: ["address_action_detected", "address_entity_detected"]
};
}
if (hasSelectedObjectInventoryFollowup && !hasDeepReasoning) {
return {
mode: "address_query",
confidence: "medium",
reasons: ["selected_object_inventory_followup_detected"]
};
}
if (hasManagementSignal && !hasDeepReasoning) {
return {
mode: "address_query",
confidence: "medium",
reasons: ["management_profile_signal_detected"]
};
}
if (hasLooseByAnchor && (hasAddressAction || hasAddressEntity || hasFollowupSignal || hasAccountCode) && !hasDeepReasoning) {
return {
mode: "address_query",
confidence: "medium",
reasons: ["loose_by_anchor_detected", ...(hasFollowupSignal ? ["address_followup_signal_detected"] : [])]
};
}
if (hasAccountCode && !hasDeepReasoning) {
return {
mode: "address_query",
confidence: "medium",
reasons: ["account_code_detected"]
};
}
if (!hasDeepReasoning && hasDocsOrBankSignal(text) && (hasLooseByAnchor || hasLikelyCounterpartyToken(text))) {
return {
mode: "address_query",
confidence: "medium",
reasons: ["docs_or_bank_signal_detected", "anchor_like_token_detected"]
};
}
if (hasDeepReasoning) {
return {
mode: "deep_analysis",
confidence: "high",
reasons: ["deep_reasoning_signal_detected"]
};
}
return {
mode: "unsupported",
confidence: "low",
reasons: ["no_address_or_deep_signal"]
};
}