АРЧ АП11 - Архитектура после регресса: + Архитектура: восстановить bridge от provenance выбранной позиции к VAT-периоду и закрыть phase10 replay

This commit is contained in:
2026-04-18 11:50:48 +03:00
parent 9872ef5446
commit 31cb4ccbbb
27 changed files with 1655 additions and 49 deletions
@@ -1598,7 +1598,11 @@ function extractAddressFilters(userMessage, intent) {
}
}
}
if (intent === "vat_liability_confirmed_for_tax_period" && !periodRange.period_from && !periodRange.period_to) {
const monthPeriodWasDerived = warnings.includes("period_derived_from_month_phrase");
if (intent === "vat_liability_confirmed_for_tax_period" &&
!periodRange.period_from &&
!periodRange.period_to &&
!monthPeriodWasDerived) {
const periodToForQuarter = filters.period_to ?? vatAsOfDate ?? null;
if (periodToForQuarter) {
const quarterWindow = deriveQuarterWindowForDate(periodToForQuarter);
@@ -1,5 +1,7 @@
"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");
@@ -1449,11 +1451,21 @@ function hasCustomerRevenueRankingBridgeSignal(text) {
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;
}
return /(?:\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)/iu.test(normalized);
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 hasInventoryProvenanceBridgeSignal(text) {
const normalized = String(text ?? "").trim().toLowerCase();
@@ -1481,6 +1493,10 @@ function hasColloquialInventoryOnHandBridgeSignal(text) {
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) {
@@ -1489,15 +1505,74 @@ function hasColloquialInventoryOnHandBridgeSignal(text) {
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 resolveAddressIntent(userMessage) {
const text = String(userMessage ?? "").trim().toLowerCase();
if (hasCustomerRevenueRankingBridgeSignal(text)) {
const repairedText = repairLikelyUtf8Mojibake(text).trim().toLowerCase();
const bridgeText = repairedText && repairedText !== text ? `${text} ${repairedText}` : text;
const hasLooseVatPayableBridge = /(?:\u043d\u0434\u0441|vat)/iu.test(text) &&
/(?:\u043a\u0430\u043a\u043e\u0439\s+\u043d\u0434\u0441\s+(?:\u043d\u0430\u043c\s+)?(?:\u043d\u0430\u0434\u043e|\u043d\u0443\u0436\u043d\u043e)|\u043d\u0430\u043c\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\s+\u043d\u0443\u0436\u043d\u043e\s+\u0437\u0430\u043f\u043b\u0430\u0442\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})?|\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(text);
if (hasExplicitReceivablesSnapshotBridge) {
return {
intent: "receivables_confirmed_as_of_date",
confidence: "high",
reasons: ["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(text);
if (hasExplicitPayablesSnapshotBridge) {
return {
intent: "payables_confirmed_as_of_date",
confidence: "high",
reasons: ["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|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);
if (hasDirectRevenueAggregateBridge || hasCustomerRevenueRankingBridgeSignal(bridgeText)) {
return {
intent: "customer_revenue_and_payments",
confidence: "medium",
reasons: ["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",
@@ -487,14 +487,19 @@ function composeCounterpartyAnalyticsReply(intent, rows, options = {}, deps) {
}
if (focus === "top_years_by_total") {
const visible = rankedByYearTotal.slice(0, limit);
const heading = isSupplier
? `Топ-${visible.length} лет по сумме выплат:`
: `Топ-${visible.length} лет по сумме поступлений:`;
lines.unshift(heading);
if (visible.length === 0) {
lines.push("По доступному окну не удалось собрать годовые агрегаты по суммам.");
}
else {
const strongestYear = visible[0];
const directAnswerLine = isSupplier
? `Самый крупный год по подтвержденным выплатам: ${strongestYear.year} (${deps.formatMoneyRub(strongestYear.total)} по ${strongestYear.ops} операциям).`
: `Самый доходный год по подтвержденным поступлениям: ${strongestYear.year} (${deps.formatMoneyRub(strongestYear.total)} по ${strongestYear.ops} операциям). Это денежный поток, а не чистая прибыль.`;
const heading = isSupplier
? `Топ-${visible.length} лет по сумме выплат:`
: `Топ-${visible.length} лет по сумме поступлений:`;
lines.unshift(heading);
lines.unshift(directAnswerLine);
lines.push(...visible.map((item, index) => `${index + 1}. ${item.year} | сумма: ${deps.formatMoneyRub(item.total)} | операций: ${item.ops} | контрагентов: ${item.counterparties.size} | максимальная разовая сумма: ${deps.formatMoneyRub(item.maxSingle)}`));
}
return (0, replyContracts_1.buildFactualListReply)(lines);
@@ -556,7 +561,14 @@ function composeCounterpartyAnalyticsReply(intent, rows, options = {}, deps) {
const heading = isSupplier
? `Топ-${visible.length} поставщиков по сумме выплат:`
: `Топ-${visible.length} заказчиков по сумме поступлений:`;
const leadingCounterparty = visible[0] ?? null;
lines.unshift(heading);
if (leadingCounterparty) {
const directAnswerLine = isSupplier
? `Крупнейший поставщик по подтвержденным выплатам за доступное время: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям).`
: `Самый доходный клиент за доступное время по подтвержденным поступлениям: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это денежный поток, а не чистая прибыль.`;
lines.unshift(directAnswerLine);
}
lines.push(...visible.map((item, index) => {
const avgCheck = item.ops > 0 ? item.total / item.ops : 0;
return `${index + 1}. ${item.name} | сумма: ${deps.formatMoneyRub(item.total)} | операций: ${item.ops} | средний чек: ${deps.formatMoneyRub(avgCheck)} | максимальная разовая сумма: ${deps.formatMoneyRub(item.maxSingle)}`;
@@ -7,6 +7,7 @@ exports.hasInventoryPurchaseDateFollowupCue = hasInventoryPurchaseDateFollowupCu
exports.hasBareInventoryPurchaseDateFollowupCue = hasBareInventoryPurchaseDateFollowupCue;
exports.hasInventorySaleFollowupCue = hasInventorySaleFollowupCue;
exports.hasInventoryPurchaseToSaleChainFollowupCue = hasInventoryPurchaseToSaleChainFollowupCue;
exports.hasInventoryPurchaseDateVatBridgeCue = hasInventoryPurchaseDateVatBridgeCue;
exports.hasAddressFollowupContextSignal = hasAddressFollowupContextSignal;
exports.runAddressDecomposeStage = runAddressDecomposeStage;
const addressQueryClassifier_1 = require("../addressQueryClassifier");
@@ -537,6 +538,14 @@ function hasInventorySaleFollowupCue(text) {
function hasInventoryPurchaseToSaleChainFollowupCue(text) {
return /(?:через\s+какие\s+документы\s+прош[её]л\s+путь\s+товара|закупк.*склад.*продаж|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*(?:warehouse|stock)\s*->\s*sale)/iu.test(String(text ?? ""));
}
function hasInventoryPurchaseDateVatBridgeCue(text) {
const normalized = String(text ?? "").trim();
if (!normalized) {
return false;
}
return (/(?:ндс|vat)/iu.test(normalized) &&
/(?:на\s+дат[ауеы]\s+покупк|на\s+дат[ауеы]\s+закупк|по\s+дат[еу]\s+покупк|по\s+дат[еу]\s+закупк|дата\s+покупк|дата\s+закупк|purchase\s+date)/iu.test(normalized));
}
function hasAddressFollowupContextSignal(text) {
const normalized = String(text ?? "").trim();
if (!normalized) {
@@ -563,6 +572,9 @@ function hasAddressFollowupContextSignal(text) {
/(?:ндс|vat|прогноз|к\s+уплате|нул|ноль|\b0(?:[.,]0+)?\b)/iu.test(normalized)) {
return true;
}
if (tokenCount <= 14 && hasInventoryPurchaseDateVatBridgeCue(normalized)) {
return true;
}
const hasPeriodLiteral = /\b(?:19|20)\d{2}(?:[./-](?:0?[1-9]|1[0-2]))?\b/.test(normalized);
if (tokenCount <= 8 && hasPeriodLiteral) {
return true;
@@ -1056,6 +1068,17 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo
const isVatFollowup = hasVatCue(normalizedMessage);
const previousIsInventoryFamily = isInventoryIntent(sourceIntent ?? undefined);
const inventorySelectedObjectFollowup = hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal);
const inventoryPurchaseDateVatBridge = inventorySelectedObjectFollowup && hasInventoryPurchaseDateVatBridgeCue(normalizedMessage);
if (inventoryPurchaseDateVatBridge &&
(detectedIntent.intent === "unknown" ||
detectedIntent.intent === sourceIntent ||
detectedIntent.intent === "vat_payable_confirmed_as_of_date")) {
return {
intent: "vat_liability_confirmed_for_tax_period",
confidence: "low",
reasons: [...detectedIntent.reasons, "intent_adjusted_to_inventory_purchase_date_vat_bridge"]
};
}
if (detectedIntent.intent === "unknown" && isVatFollowup) {
const vatIntent = hasVatTaxPaymentCue(normalizedMessage)
? "vat_liability_confirmed_for_tax_period"
@@ -209,8 +209,8 @@ function composeInventoryReply(intent, rows, options, deps) {
.map((item) => `${item.item} (${deps.inventoryTraceDateLabel(item.firstPurchasePeriod)})`)
.join("; ");
const directAnswerLine = agingItems.length > 0
? `К старым закупкам на ${deps.formatDateRu(asOfDate)} в первую очередь относятся позиции с самой ранней первой закупкой: ${oldestAnswerPreview}.`
: `По доступному закупочному следу на ${deps.formatDateRu(asOfDate)} позиции старых закупок не материализованы.`;
? `К самым старым закупкам в текущем подтвержденном срезе относятся позиции с самой ранней первой закупкой: ${oldestAnswerPreview}.`
: "По доступному закупочному следу позиции со старыми закупками не материализованы.";
const lines = [directAnswerLine];
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Сводка:", [
`Дата среза: ${deps.formatDateRu(asOfDate)}.`,
@@ -683,6 +683,7 @@ function createAssistantRoutePolicy(deps) {
const deepSessionContinuationFallbackToDeep = Boolean(!followupContext &&
baseToolGate?.runAddressLane &&
!llmRuntimeUnavailableDetected &&
!protectAddressLaneFromFallback &&
hasDeepSessionContinuationSignal({
rawUserMessage,
repairedRawUserMessage,
+29 -5
View File
@@ -2642,6 +2642,9 @@ function hasAddressFollowupContextSignal(userMessage) {
!hasAny(/(?:по\s+этому|по\s+тому|по\s+нему|по\s+ней|по\s+ним)/iu)) {
return true;
}
if (shortFollowup && samples.some((sample) => (0, decomposeStage_1.hasInventoryPurchaseDateVatBridgeCue)(sample))) {
return true;
}
if (shortFollowup && samples.some((sample) => hasPeriodLiteral(sample))) {
return true;
}
@@ -3458,6 +3461,31 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
const candidatePredecomposeContract = (0, predecomposeContract_1.buildAddressLlmPredecomposeContractV1)({
sourceMessage: String(userMessage ?? ""),
canonicalMessage: candidate,
semanticHints: candidateMeta?.semanticHints ?? null
});
const sourceHasExplicitAccountAnchor = (0, addressIntentResolver_1.hasAccountNumberAnchor)(repairedSourceMessage || userMessage) ||
(0, addressIntentResolver_1.hasCompactAccountCodeToken)(repairedSourceMessage || userMessage);
const candidateInjectsAccountAnchor = Boolean(toNonEmptyString(candidatePredecomposeContract?.entities?.account));
if (sourceIntentResolution.intent === "inventory_on_hand_as_of_date" &&
candidateIntentResolution.intent === "inventory_on_hand_as_of_date" &&
!sourceHasExplicitAccountAnchor &&
candidateInjectsAccountAnchor) {
return attachAddressPredecomposeContract({
...baseMeta,
attempted: true,
applied: false,
traceId: normalized?.trace_id ?? null,
llmCanonicalCandidateDetected: true,
effectiveMessage: userMessage,
reason: "normalized_fragment_rejected_anchor_injection",
fallbackRuleHit: null,
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
const sourceAnchorQuality = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
const candidateAnchorQuality = evaluateAddressAnchorQuality(candidate);
const sameIntentForAnchorSafety = sourceAnchorQuality.intent !== "unknown" && sourceAnchorQuality.intent === candidateAnchorQuality.intent;
@@ -3554,11 +3582,7 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
const semanticContractForCandidate = (0, predecomposeContract_1.buildAddressSemanticExtractionContractV1)({
sourceMessage: String(userMessage ?? ""),
canonicalMessage: candidate,
predecomposeContract: (0, predecomposeContract_1.buildAddressLlmPredecomposeContractV1)({
sourceMessage: String(userMessage ?? ""),
canonicalMessage: candidate,
semanticHints: candidateMeta?.semanticHints ?? null
})
predecomposeContract: candidatePredecomposeContract
});
if (!semanticContractForCandidate.apply_canonical_recommended) {
const sourceDataSignalDetected = Boolean(semanticContractForCandidate?.guard_hints?.source_data_signal_detected);
@@ -3,6 +3,101 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAssistantTransitionPolicy = createAssistantTransitionPolicy;
function createAssistantTransitionPolicy(deps) {
function parseDmyDateToIso(value) {
const match = String(value ?? "").trim().match(/^(\d{2})\.(\d{2})\.(\d{4})$/);
if (!match) {
return null;
}
return `${match[3]}-${match[2]}-${match[1]}`;
}
function computeMonthWindowFromIso(isoDate) {
const match = String(isoDate ?? "").match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
return null;
}
const year = Number(match[1]);
const month = Number(match[2]);
if (!Number.isFinite(year) || !Number.isFinite(month) || month < 1 || month > 12) {
return null;
}
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
const mm = String(month).padStart(2, "0");
const dd = String(lastDay).padStart(2, "0");
return {
purchase_date: isoDate,
period_from: `${year}-${mm}-01`,
period_to: `${year}-${mm}-${dd}`,
as_of_date: `${year}-${mm}-${dd}`
};
}
function extractEarliestDmyDateFromEntityRefs(entityRefs) {
const dates = [];
for (const entityRef of Array.isArray(entityRefs) ? entityRefs : []) {
const value = deps.toNonEmptyString(entityRef?.value);
if (!value) {
continue;
}
const matches = String(value).match(/\b(\d{2}\.\d{2}\.\d{4})\b/g);
if (!matches) {
continue;
}
for (const token of matches) {
const isoDate = parseDmyDateToIso(token);
if (isoDate) {
dates.push(isoDate);
}
}
}
if (dates.length === 0) {
return null;
}
return dates.sort()[0] ?? null;
}
function extractPurchaseDateBridgeWindow(previousAddressItem, addressNavigationState) {
const replyText = deps.toNonEmptyString(previousAddressItem?.text);
if (replyText) {
const repairedText = deps.repairAddressMojibake(replyText);
const explicitFirstDateMatch = repairedText.match(/первая\s+найденная\s+дата\s+закупки:\s*(\d{2}\.\d{2}\.\d{4})/iu);
const explicitFirstDateIso = explicitFirstDateMatch ? parseDmyDateToIso(explicitFirstDateMatch[1]) : null;
if (explicitFirstDateIso) {
return computeMonthWindowFromIso(explicitFirstDateIso);
}
}
const sessionContext = addressNavigationState &&
typeof addressNavigationState === "object" &&
addressNavigationState.session_context &&
typeof addressNavigationState.session_context === "object"
? addressNavigationState.session_context
: null;
const focusObject = sessionContext && typeof sessionContext.active_focus_object === "object"
? sessionContext.active_focus_object
: null;
const preferredResultSetId = deps.toNonEmptyString(focusObject?.provenance_result_set_id) ??
deps.toNonEmptyString(sessionContext?.active_result_set_id);
const resultSets = Array.isArray(addressNavigationState?.result_sets) ? addressNavigationState.result_sets : [];
const preferredResultSet = (preferredResultSetId
? resultSets.find((item) => deps.toNonEmptyString(item?.result_set_id) === preferredResultSetId) ?? null
: null) ??
resultSets.find((item) => deps.toNonEmptyString(item?.intent) === "inventory_purchase_provenance_for_item") ??
null;
const earliestIsoDate = extractEarliestDmyDateFromEntityRefs(preferredResultSet?.entity_refs);
return earliestIsoDate ? computeMonthWindowFromIso(earliestIsoDate) : null;
}
function hasInventoryPurchaseDateVatBridgeSignal(userMessage, alternateMessage, sourceIntentHint, hasInventoryItemFocusHint) {
if (sourceIntentHint !== "inventory_purchase_provenance_for_item" &&
!hasInventoryItemFocusHint &&
!deps.isInventorySelectedObjectIntent(sourceIntentHint)) {
return false;
}
const samples = [userMessage, alternateMessage]
.map((item) => deps.compactWhitespace(deps.repairAddressMojibake(String(item ?? "")).toLowerCase()))
.filter((item) => item.length > 0);
if (samples.length === 0) {
return false;
}
return samples.some((sample) => /(?:ндс|vat)/iu.test(sample) &&
/(?:на\s+дат[ауеы]\s+покупк|на\s+дат[ауеы]\s+закупк|по\s+дат[еу]\s+покупк|по\s+дат[еу]\s+закупк|дата\s+покупк|дата\s+закупк|purchase\s+date)/iu.test(sample));
}
function hasInventoryRootRestatementLikeSignal(userMessage, sourceIntentHint, hasInventoryRootFrame) {
if (!hasInventoryRootFrame) {
return false;
@@ -159,6 +254,7 @@ function createAssistantTransitionPolicy(deps) {
(sourceIntentHint === "inventory_on_hand_as_of_date" ||
sourceIntentHint === "inventory_supplier_stock_overlap_as_of_date" ||
deps.isInventorySelectedObjectIntent(sourceIntentHint)));
const inventoryPurchaseDateVatBridge = hasInventoryPurchaseDateVatBridgeSignal(userMessage, alternateMessage, sourceIntentHint, hasNavigationInventoryItemFocusHint);
let inventoryShortFollowupPrimary = (deps.isInventorySelectedObjectIntent(sourceIntentHint) || hasNavigationInventoryItemFocusHint) &&
deps.hasShortInventoryObjectFollowupSignal(userMessage);
let inventoryShortFollowupAlternate = (deps.isInventorySelectedObjectIntent(sourceIntentHint) || hasNavigationInventoryItemFocusHint) &&
@@ -172,11 +268,15 @@ function createAssistantTransitionPolicy(deps) {
? deps.resolveDebtRoleSwapFollowupIntent(String(alternateMessage ?? ""), sourceIntentHint)
: null;
const debtRoleSwapIntent = debtRoleSwapPrimary ?? debtRoleSwapAlternate ?? null;
let hasPrimaryFollowupSignal = deps.hasAddressFollowupContextSignal(userMessage) || Boolean(debtRoleSwapPrimary) || inventoryShortFollowupPrimary;
let hasPrimaryFollowupSignal = deps.hasAddressFollowupContextSignal(userMessage) ||
Boolean(debtRoleSwapPrimary) ||
inventoryShortFollowupPrimary ||
inventoryPurchaseDateVatBridge;
let hasAlternateFollowupSignal = deps.toNonEmptyString(alternateMessage)
? deps.hasAddressFollowupContextSignal(alternateMessage) ||
Boolean(debtRoleSwapAlternate) ||
inventoryShortFollowupAlternate
inventoryShortFollowupAlternate ||
inventoryPurchaseDateVatBridge
: false;
const hasPrimaryIndexReferenceSignal = deps.extractDisplayedEntityIndexMention(userMessage) !== null;
const hasAlternateIndexReferenceSignal = deps.toNonEmptyString(alternateMessage)
@@ -206,6 +306,7 @@ function createAssistantTransitionPolicy(deps) {
hasInventoryRootTemporalFollowupAlternate ||
hasInventoryRootRestatementPrimary ||
hasInventoryRootRestatementAlternate ||
inventoryPurchaseDateVatBridge ||
Boolean(debtRoleSwapIntent) ||
deps.hasFollowupMarker(userMessage) ||
deps.hasReferentialPointer(userMessage) ||
@@ -340,11 +441,13 @@ function createAssistantTransitionPolicy(deps) {
deps.hasAddressFollowupContextSignal(userMessage) ||
Boolean(debtRoleSwapPrimary) ||
inventoryShortFollowupPrimary ||
inventoryPurchaseDateVatBridge ||
hasInventoryRootTemporalFollowupPrimary;
hasAlternateFollowupSignal = deps.toNonEmptyString(alternateMessage)
? deps.hasAddressFollowupContextSignal(alternateMessage) ||
Boolean(debtRoleSwapAlternate) ||
inventoryShortFollowupAlternate ||
inventoryPurchaseDateVatBridge ||
hasInventoryRootTemporalFollowupAlternate
: false;
hasStrongFollowupReference =
@@ -356,6 +459,7 @@ function createAssistantTransitionPolicy(deps) {
inventoryShortFollowupAlternate ||
hasInventoryRootTemporalFollowupPrimary ||
hasInventoryRootTemporalFollowupAlternate ||
inventoryPurchaseDateVatBridge ||
Boolean(debtRoleSwapIntent) ||
deps.hasFollowupMarker(userMessage) ||
deps.hasReferentialPointer(userMessage) ||
@@ -435,6 +539,13 @@ function createAssistantTransitionPolicy(deps) {
if (!deps.toNonEmptyString(previousFilters.organization) && organizationClarificationSelection) {
previousFilters.organization = organizationClarificationSelection;
}
if (inventoryPurchaseDateVatBridge) {
const purchaseBridgeWindow = extractPurchaseDateBridgeWindow(previousAddressItem, addressNavigationState);
if (purchaseBridgeWindow) {
previousFilters.period_from = purchaseBridgeWindow.period_from;
previousFilters.period_to = purchaseBridgeWindow.period_to;
}
}
const shouldBackfillPreviousDateScopeFromNavigation = sourceIntentHint === "inventory_on_hand_as_of_date" ||
sourceIntentHint === "inventory_supplier_stock_overlap_as_of_date" ||
sourceIntentHint === "inventory_purchase_provenance_for_item" ||
@@ -461,7 +572,8 @@ function createAssistantTransitionPolicy(deps) {
previousFilters.period_to = deps.toNonEmptyString(navigationDateScope?.period_to);
}
const rootContextOnlyPivot = Boolean((deps.isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") &&
deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage));
deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage) &&
!inventoryPurchaseDateVatBridge);
const inventoryRootTemporalPivot = Boolean(inventoryRootFrame &&
(deps.isInventorySelectedObjectIntent(sourceIntentHint) ||
deps.isInventoryRootFrameIntent(sourceIntentHint) ||
@@ -567,11 +679,13 @@ function createAssistantTransitionPolicy(deps) {
hasInventoryRootRestatementAlternate ||
hasSelectedObjectInventorySignalPrimary ||
hasSelectedObjectInventorySignalAlternate));
const carryoverTargetIntent = followupSelectionMode === "carry_root_context"
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
: explicitInventorySameDatePivot
? "inventory_on_hand_as_of_date"
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
const carryoverTargetIntent = inventoryPurchaseDateVatBridge
? "vat_liability_confirmed_for_tax_period"
: followupSelectionMode === "carry_root_context"
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
: explicitInventorySameDatePivot
? "inventory_on_hand_as_of_date"
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
return {
followupContext: {
previous_intent: previousIntent ?? undefined,
@@ -1848,7 +1848,13 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
}
}
}
if (intent === "vat_liability_confirmed_for_tax_period" && !periodRange.period_from && !periodRange.period_to) {
const monthPeriodWasDerived = warnings.includes("period_derived_from_month_phrase");
if (
intent === "vat_liability_confirmed_for_tax_period" &&
!periodRange.period_from &&
!periodRange.period_to &&
!monthPeriodWasDerived
) {
const periodToForQuarter = filters.period_to ?? vatAsOfDate ?? null;
if (periodToForQuarter) {
const quarterWindow = deriveQuarterWindowForDate(periodToForQuarter);
@@ -534,7 +534,7 @@ function hasFuzzyLexeme(text: string, lexemeRoots: string[]): boolean {
return false;
}
function hasCompactAccountCodeToken(text: string): boolean {
export function hasCompactAccountCodeToken(text: string): boolean {
// Match compact account tokens while reducing false positives on short-year literals like "22 РіРѕРґ".
const source = String(text ?? "");
if (!source) {
@@ -1599,7 +1599,7 @@ function hasGenericAddressLookupSignal(text: string): boolean {
);
}
function hasAccountNumberAnchor(text: string): boolean {
export function hasAccountNumberAnchor(text: string): boolean {
return /(?:account|сч[её]т|счет)\D{0,12}\d{2}(?:[.,]\d{1,2})?/i.test(text);
}
@@ -1790,6 +1790,20 @@ function hasCustomerRevenueRankingBridgeSignal(text: string): boolean {
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
@@ -1797,9 +1811,15 @@ function hasCustomerRevenueRankingBridgeSignal(text: string): boolean {
if (!hasMoneyCue) {
return false;
}
return /(?:\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)/iu.test(
normalized
);
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 hasInventoryProvenanceBridgeSignal(text: string): boolean {
@@ -1845,6 +1865,13 @@ function hasColloquialInventoryOnHandBridgeSignal(text: string): boolean {
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(
@@ -1860,10 +1887,81 @@ function hasColloquialInventoryOnHandBridgeSignal(text: string): boolean {
return hasQuestionCue && tokenCount <= 8;
}
function repairLikelyUtf8Mojibake(text: string): string {
const raw = String(text ?? "");
if (!raw) {
return "";
}
try {
const repaired = Buffer.from(raw, "latin1").toString("utf8");
return repaired || raw;
} catch {
return raw;
}
}
export function resolveAddressIntent(userMessage: string): AddressIntentResolution {
const text = String(userMessage ?? "").trim().toLowerCase();
const repairedText = repairLikelyUtf8Mojibake(text).trim().toLowerCase();
const bridgeText = repairedText && repairedText !== text ? `${text} ${repairedText}` : text;
if (hasCustomerRevenueRankingBridgeSignal(text)) {
const hasLooseVatPayableBridge =
/(?:\u043d\u0434\u0441|vat)/iu.test(text) &&
/(?:\u043a\u0430\u043a\u043e\u0439\s+\u043d\u0434\u0441\s+(?:\u043d\u0430\u043c\s+)?(?:\u043d\u0430\u0434\u043e|\u043d\u0443\u0436\u043d\u043e)|\u043d\u0430\u043c\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\s+\u043d\u0443\u0436\u043d\u043e\s+\u0437\u0430\u043f\u043b\u0430\u0442\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})?|\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(
text
);
if (hasExplicitReceivablesSnapshotBridge) {
return {
intent: "receivables_confirmed_as_of_date",
confidence: "high",
reasons: ["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(
text
);
if (hasExplicitPayablesSnapshotBridge) {
return {
intent: "payables_confirmed_as_of_date",
confidence: "high",
reasons: ["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|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
);
if (hasDirectRevenueAggregateBridge || hasCustomerRevenueRankingBridgeSignal(bridgeText)) {
return {
intent: "customer_revenue_and_payments",
confidence: "medium",
@@ -1871,6 +1969,19 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
};
}
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",
@@ -639,13 +639,18 @@ export function composeCounterpartyAnalyticsReply(
if (focus === "top_years_by_total") {
const visible = rankedByYearTotal.slice(0, limit);
const heading = isSupplier
? `Топ-${visible.length} лет по сумме выплат:`
: `Топ-${visible.length} лет по сумме поступлений:`;
lines.unshift(heading);
if (visible.length === 0) {
lines.push("По доступному окну не удалось собрать годовые агрегаты по суммам.");
} else {
const strongestYear = visible[0];
const directAnswerLine = isSupplier
? `Самый крупный год по подтвержденным выплатам: ${strongestYear.year} (${deps.formatMoneyRub(strongestYear.total)} по ${strongestYear.ops} операциям).`
: `Самый доходный год по подтвержденным поступлениям: ${strongestYear.year} (${deps.formatMoneyRub(strongestYear.total)} по ${strongestYear.ops} операциям). Это денежный поток, а не чистая прибыль.`;
const heading = isSupplier
? `Топ-${visible.length} лет по сумме выплат:`
: `Топ-${visible.length} лет по сумме поступлений:`;
lines.unshift(heading);
lines.unshift(directAnswerLine);
lines.push(
...visible.map(
(item, index) =>
@@ -742,7 +747,14 @@ export function composeCounterpartyAnalyticsReply(
const heading = isSupplier
? `Топ-${visible.length} поставщиков по сумме выплат:`
: `Топ-${visible.length} заказчиков по сумме поступлений:`;
const leadingCounterparty = visible[0] ?? null;
lines.unshift(heading);
if (leadingCounterparty) {
const directAnswerLine = isSupplier
? `Крупнейший поставщик по подтвержденным выплатам за доступное время: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям).`
: `Самый доходный клиент за доступное время по подтвержденным поступлениям: ${leadingCounterparty.name} (${deps.formatMoneyRub(leadingCounterparty.total)} по ${leadingCounterparty.ops} операциям). Это денежный поток, а не чистая прибыль.`;
lines.unshift(directAnswerLine);
}
lines.push(
...visible.map((item, index) => {
const avgCheck = item.ops > 0 ? item.total / item.ops : 0;
@@ -691,6 +691,19 @@ export function hasInventoryPurchaseToSaleChainFollowupCue(text: string): boolea
);
}
export function hasInventoryPurchaseDateVatBridgeCue(text: string): boolean {
const normalized = String(text ?? "").trim();
if (!normalized) {
return false;
}
return (
/(?:ндс|vat)/iu.test(normalized) &&
/(?:на\s+дат[ауеы]\s+покупк|на\s+дат[ауеы]\s+закупк|по\s+дат[еу]\s+покупк|по\s+дат[еу]\s+закупк|дата\s+покупк|дата\s+закупк|purchase\s+date)/iu.test(
normalized
)
);
}
export function hasAddressFollowupContextSignal(text: string): boolean {
const normalized = String(text ?? "").trim();
if (!normalized) {
@@ -728,6 +741,9 @@ export function hasAddressFollowupContextSignal(text: string): boolean {
) {
return true;
}
if (tokenCount <= 14 && hasInventoryPurchaseDateVatBridgeCue(normalized)) {
return true;
}
const hasPeriodLiteral = /\b(?:19|20)\d{2}(?:[./-](?:0?[1-9]|1[0-2]))?\b/.test(normalized);
if (tokenCount <= 8 && hasPeriodLiteral) {
return true;
@@ -1314,6 +1330,21 @@ function deriveIntentWithFollowupContext(
const previousIsInventoryFamily = isInventoryIntent(sourceIntent ?? undefined);
const inventorySelectedObjectFollowup =
hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal);
const inventoryPurchaseDateVatBridge =
inventorySelectedObjectFollowup && hasInventoryPurchaseDateVatBridgeCue(normalizedMessage);
if (
inventoryPurchaseDateVatBridge &&
(detectedIntent.intent === "unknown" ||
detectedIntent.intent === sourceIntent ||
detectedIntent.intent === "vat_payable_confirmed_as_of_date")
) {
return {
intent: "vat_liability_confirmed_for_tax_period",
confidence: "low",
reasons: [...detectedIntent.reasons, "intent_adjusted_to_inventory_purchase_date_vat_bridge"]
};
}
if (detectedIntent.intent === "unknown" && isVatFollowup) {
const vatIntent: AddressIntent = hasVatTaxPaymentCue(normalizedMessage)
@@ -316,8 +316,8 @@ export function composeInventoryReply(
.join("; ");
const directAnswerLine =
agingItems.length > 0
? `К старым закупкам на ${deps.formatDateRu(asOfDate)} в первую очередь относятся позиции с самой ранней первой закупкой: ${oldestAnswerPreview}.`
: `По доступному закупочному следу на ${deps.formatDateRu(asOfDate)} позиции старых закупок не материализованы.`;
? `К самым старым закупкам в текущем подтвержденном срезе относятся позиции с самой ранней первой закупкой: ${oldestAnswerPreview}.`
: "По доступному закупочному следу позиции со старыми закупками не материализованы.";
const lines: string[] = [directAnswerLine];
appendInventoryBulletSection(lines, "Сводка:", [
`Дата среза: ${deps.formatDateRu(asOfDate)}.`,
@@ -719,6 +719,7 @@ export function createAssistantRoutePolicy(deps) {
const deepSessionContinuationFallbackToDeep = Boolean(!followupContext &&
baseToolGate?.runAddressLane &&
!llmRuntimeUnavailableDetected &&
!protectAddressLaneFromFallback &&
hasDeepSessionContinuationSignal({
rawUserMessage,
repairedRawUserMessage,
@@ -2597,6 +2597,9 @@ function hasAddressFollowupContextSignal(userMessage) {
!hasAny(/(?:по\s+этому|по\s+тому|по\s+нему|по\s+ней|по\s+ним)/iu)) {
return true;
}
if (shortFollowup && samples.some((sample) => (0, decomposeStage_1.hasInventoryPurchaseDateVatBridgeCue)(sample))) {
return true;
}
if (shortFollowup && samples.some((sample) => hasPeriodLiteral(sample))) {
return true;
}
@@ -3413,6 +3416,31 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
const candidatePredecomposeContract = (0, predecomposeContract_1.buildAddressLlmPredecomposeContractV1)({
sourceMessage: String(userMessage ?? ""),
canonicalMessage: candidate,
semanticHints: candidateMeta?.semanticHints ?? null
});
const sourceHasExplicitAccountAnchor = (0, addressIntentResolver_1.hasAccountNumberAnchor)(repairedSourceMessage || userMessage) ||
(0, addressIntentResolver_1.hasCompactAccountCodeToken)(repairedSourceMessage || userMessage);
const candidateInjectsAccountAnchor = Boolean(toNonEmptyString(candidatePredecomposeContract?.entities?.account));
if (sourceIntentResolution.intent === "inventory_on_hand_as_of_date" &&
candidateIntentResolution.intent === "inventory_on_hand_as_of_date" &&
!sourceHasExplicitAccountAnchor &&
candidateInjectsAccountAnchor) {
return attachAddressPredecomposeContract({
...baseMeta,
attempted: true,
applied: false,
traceId: normalized?.trace_id ?? null,
llmCanonicalCandidateDetected: true,
effectiveMessage: userMessage,
reason: "normalized_fragment_rejected_anchor_injection",
fallbackRuleHit: null,
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
const sourceAnchorQuality = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
const candidateAnchorQuality = evaluateAddressAnchorQuality(candidate);
const sameIntentForAnchorSafety = sourceAnchorQuality.intent !== "unknown" && sourceAnchorQuality.intent === candidateAnchorQuality.intent;
@@ -3509,11 +3537,7 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
const semanticContractForCandidate = (0, predecomposeContract_1.buildAddressSemanticExtractionContractV1)({
sourceMessage: String(userMessage ?? ""),
canonicalMessage: candidate,
predecomposeContract: (0, predecomposeContract_1.buildAddressLlmPredecomposeContractV1)({
sourceMessage: String(userMessage ?? ""),
canonicalMessage: candidate,
semanticHints: candidateMeta?.semanticHints ?? null
})
predecomposeContract: candidatePredecomposeContract
});
if (!semanticContractForCandidate.apply_canonical_recommended) {
const sourceDataSignalDetected = Boolean(semanticContractForCandidate?.guard_hints?.source_data_signal_detected);
@@ -1,6 +1,118 @@
// @ts-nocheck
export function createAssistantTransitionPolicy(deps) {
function parseDmyDateToIso(value) {
const match = String(value ?? "").trim().match(/^(\d{2})\.(\d{2})\.(\d{4})$/);
if (!match) {
return null;
}
return `${match[3]}-${match[2]}-${match[1]}`;
}
function computeMonthWindowFromIso(isoDate) {
const match = String(isoDate ?? "").match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
return null;
}
const year = Number(match[1]);
const month = Number(match[2]);
if (!Number.isFinite(year) || !Number.isFinite(month) || month < 1 || month > 12) {
return null;
}
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
const mm = String(month).padStart(2, "0");
const dd = String(lastDay).padStart(2, "0");
return {
purchase_date: isoDate,
period_from: `${year}-${mm}-01`,
period_to: `${year}-${mm}-${dd}`,
as_of_date: `${year}-${mm}-${dd}`
};
}
function extractEarliestDmyDateFromEntityRefs(entityRefs) {
const dates = [];
for (const entityRef of Array.isArray(entityRefs) ? entityRefs : []) {
const value = deps.toNonEmptyString(entityRef?.value);
if (!value) {
continue;
}
const matches = String(value).match(/\b(\d{2}\.\d{2}\.\d{4})\b/g);
if (!matches) {
continue;
}
for (const token of matches) {
const isoDate = parseDmyDateToIso(token);
if (isoDate) {
dates.push(isoDate);
}
}
}
if (dates.length === 0) {
return null;
}
return dates.sort()[0] ?? null;
}
function extractPurchaseDateBridgeWindow(previousAddressItem, addressNavigationState) {
const replyText = deps.toNonEmptyString(previousAddressItem?.text);
if (replyText) {
const repairedText = deps.repairAddressMojibake(replyText);
const explicitFirstDateMatch = repairedText.match(/первая\s+найденная\s+дата\s+закупки:\s*(\d{2}\.\d{2}\.\d{4})/iu);
const explicitFirstDateIso = explicitFirstDateMatch ? parseDmyDateToIso(explicitFirstDateMatch[1]) : null;
if (explicitFirstDateIso) {
return computeMonthWindowFromIso(explicitFirstDateIso);
}
}
const sessionContext =
addressNavigationState &&
typeof addressNavigationState === "object" &&
addressNavigationState.session_context &&
typeof addressNavigationState.session_context === "object"
? addressNavigationState.session_context
: null;
const focusObject =
sessionContext && typeof sessionContext.active_focus_object === "object"
? sessionContext.active_focus_object
: null;
const preferredResultSetId =
deps.toNonEmptyString(focusObject?.provenance_result_set_id) ??
deps.toNonEmptyString(sessionContext?.active_result_set_id);
const resultSets = Array.isArray(addressNavigationState?.result_sets) ? addressNavigationState.result_sets : [];
const preferredResultSet =
(preferredResultSetId
? resultSets.find((item) => deps.toNonEmptyString(item?.result_set_id) === preferredResultSetId) ?? null
: null) ??
resultSets.find((item) => deps.toNonEmptyString(item?.intent) === "inventory_purchase_provenance_for_item") ??
null;
const earliestIsoDate = extractEarliestDmyDateFromEntityRefs(preferredResultSet?.entity_refs);
return earliestIsoDate ? computeMonthWindowFromIso(earliestIsoDate) : null;
}
function hasInventoryPurchaseDateVatBridgeSignal(userMessage, alternateMessage, sourceIntentHint, hasInventoryItemFocusHint) {
if (
sourceIntentHint !== "inventory_purchase_provenance_for_item" &&
!hasInventoryItemFocusHint &&
!deps.isInventorySelectedObjectIntent(sourceIntentHint)
) {
return false;
}
const samples = [userMessage, alternateMessage]
.map((item) => deps.compactWhitespace(deps.repairAddressMojibake(String(item ?? "")).toLowerCase()))
.filter((item) => item.length > 0);
if (samples.length === 0) {
return false;
}
return samples.some(
(sample) =>
/(?:ндс|vat)/iu.test(sample) &&
/(?:на\s+дат[ауеы]\s+покупк|на\s+дат[ауеы]\s+закупк|по\s+дат[еу]\s+покупк|по\s+дат[еу]\s+закупк|дата\s+покупк|дата\s+закупк|purchase\s+date)/iu.test(
sample
)
);
}
function hasInventoryRootRestatementLikeSignal(userMessage, sourceIntentHint, hasInventoryRootFrame) {
if (!hasInventoryRootFrame) {
return false;
@@ -197,6 +309,12 @@ export function createAssistantTransitionPolicy(deps) {
sourceIntentHint === "inventory_supplier_stock_overlap_as_of_date" ||
deps.isInventorySelectedObjectIntent(sourceIntentHint))
);
const inventoryPurchaseDateVatBridge = hasInventoryPurchaseDateVatBridgeSignal(
userMessage,
alternateMessage,
sourceIntentHint,
hasNavigationInventoryItemFocusHint
);
let inventoryShortFollowupPrimary =
(deps.isInventorySelectedObjectIntent(sourceIntentHint) || hasNavigationInventoryItemFocusHint) &&
deps.hasShortInventoryObjectFollowupSignal(userMessage);
@@ -214,11 +332,15 @@ export function createAssistantTransitionPolicy(deps) {
: null;
const debtRoleSwapIntent = debtRoleSwapPrimary ?? debtRoleSwapAlternate ?? null;
let hasPrimaryFollowupSignal =
deps.hasAddressFollowupContextSignal(userMessage) || Boolean(debtRoleSwapPrimary) || inventoryShortFollowupPrimary;
deps.hasAddressFollowupContextSignal(userMessage) ||
Boolean(debtRoleSwapPrimary) ||
inventoryShortFollowupPrimary ||
inventoryPurchaseDateVatBridge;
let hasAlternateFollowupSignal = deps.toNonEmptyString(alternateMessage)
? deps.hasAddressFollowupContextSignal(alternateMessage) ||
Boolean(debtRoleSwapAlternate) ||
inventoryShortFollowupAlternate
inventoryShortFollowupAlternate ||
inventoryPurchaseDateVatBridge
: false;
const hasPrimaryIndexReferenceSignal = deps.extractDisplayedEntityIndexMention(userMessage) !== null;
const hasAlternateIndexReferenceSignal = deps.toNonEmptyString(alternateMessage)
@@ -265,6 +387,7 @@ export function createAssistantTransitionPolicy(deps) {
hasInventoryRootTemporalFollowupAlternate ||
hasInventoryRootRestatementPrimary ||
hasInventoryRootRestatementAlternate ||
inventoryPurchaseDateVatBridge ||
Boolean(debtRoleSwapIntent) ||
deps.hasFollowupMarker(userMessage) ||
deps.hasReferentialPointer(userMessage) ||
@@ -418,11 +541,13 @@ export function createAssistantTransitionPolicy(deps) {
deps.hasAddressFollowupContextSignal(userMessage) ||
Boolean(debtRoleSwapPrimary) ||
inventoryShortFollowupPrimary ||
inventoryPurchaseDateVatBridge ||
hasInventoryRootTemporalFollowupPrimary;
hasAlternateFollowupSignal = deps.toNonEmptyString(alternateMessage)
? deps.hasAddressFollowupContextSignal(alternateMessage) ||
Boolean(debtRoleSwapAlternate) ||
inventoryShortFollowupAlternate ||
inventoryPurchaseDateVatBridge ||
hasInventoryRootTemporalFollowupAlternate
: false;
hasStrongFollowupReference =
@@ -434,6 +559,7 @@ export function createAssistantTransitionPolicy(deps) {
inventoryShortFollowupAlternate ||
hasInventoryRootTemporalFollowupPrimary ||
hasInventoryRootTemporalFollowupAlternate ||
inventoryPurchaseDateVatBridge ||
Boolean(debtRoleSwapIntent) ||
deps.hasFollowupMarker(userMessage) ||
deps.hasReferentialPointer(userMessage) ||
@@ -522,6 +648,13 @@ export function createAssistantTransitionPolicy(deps) {
if (!deps.toNonEmptyString(previousFilters.organization) && organizationClarificationSelection) {
previousFilters.organization = organizationClarificationSelection;
}
if (inventoryPurchaseDateVatBridge) {
const purchaseBridgeWindow = extractPurchaseDateBridgeWindow(previousAddressItem, addressNavigationState);
if (purchaseBridgeWindow) {
previousFilters.period_from = purchaseBridgeWindow.period_from;
previousFilters.period_to = purchaseBridgeWindow.period_to;
}
}
const shouldBackfillPreviousDateScopeFromNavigation =
sourceIntentHint === "inventory_on_hand_as_of_date" ||
sourceIntentHint === "inventory_supplier_stock_overlap_as_of_date" ||
@@ -556,7 +689,8 @@ export function createAssistantTransitionPolicy(deps) {
}
const rootContextOnlyPivot = Boolean(
(deps.isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") &&
deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage)
deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage) &&
!inventoryPurchaseDateVatBridge
);
const inventoryRootTemporalPivot = Boolean(
inventoryRootFrame &&
@@ -681,7 +815,9 @@ export function createAssistantTransitionPolicy(deps) {
hasSelectedObjectInventorySignalAlternate)
);
const carryoverTargetIntent =
followupSelectionMode === "carry_root_context"
inventoryPurchaseDateVatBridge
? "vat_liability_confirmed_for_tax_period"
: followupSelectionMode === "carry_root_context"
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
: explicitInventorySameDatePivot
? "inventory_on_hand_as_of_date"
@@ -0,0 +1,17 @@
import { describe, expect, it } from "vitest";
import { extractAddressFilters } from "../src/services/addressFilterExtractor";
describe("address filter extractor regressions", () => {
it("keeps explicit month window for confirmed VAT tax-period intent", () => {
const extracted = extractAddressFilters(
"сколько ндс надо заплатить в налоговую за декабрь 2019",
"vat_liability_confirmed_for_tax_period"
);
expect(extracted.extracted_filters.period_from).toBe("2019-12-01");
expect(extracted.extracted_filters.period_to).toBe("2019-12-31");
expect(extracted.warnings).toContain("period_derived_from_month_phrase");
expect(extracted.warnings).not.toContain("period_derived_from_tax_quarter_for_confirmed_vat_liability");
});
});
@@ -55,7 +55,7 @@ describe("address follow-up temporal regressions", () => {
});
expect(result).not.toBeNull();
expect(result?.intent.intent).toBe("vat_payable_confirmed_as_of_date");
expect(result?.intent.intent).toBe("vat_liability_confirmed_for_tax_period");
expect(result?.filters.extracted_filters.period_from).toBe("2017-05-01");
expect(result?.filters.extracted_filters.period_to).toBe("2017-05-31");
expect(result?.filters.extracted_filters.as_of_date).toBe("2017-05-31");
@@ -76,7 +76,7 @@ describe("address follow-up temporal regressions", () => {
});
expect(result).not.toBeNull();
expect(result?.intent.intent).toBe("vat_payable_confirmed_as_of_date");
expect(result?.intent.intent).toBe("vat_liability_confirmed_for_tax_period");
expect(result?.filters.extracted_filters.period_from).toBe("2017-05-01");
expect(result?.filters.extracted_filters.period_to).toBe("2017-05-31");
expect(result?.filters.extracted_filters.as_of_date).toBe("2017-05-31");
@@ -116,4 +116,26 @@ describe("address follow-up temporal regressions", () => {
expect(result?.filters.extracted_filters.warehouse).toBeUndefined();
expect(result?.baseReasons).toContain("as_of_date_from_followup_context");
});
it("retargets inventory purchase-date VAT bridge into confirmed VAT period with inherited purchase month", () => {
const result = runAddressDecomposeStage("ндс можешь прикинуть на дату покупки рабочей станции?", {
previous_intent: "inventory_purchase_provenance_for_item",
target_intent: "vat_liability_confirmed_for_tax_period",
previous_filters: {
item: "Рабочая станция универсального специалиста",
organization: 'ООО "Альтернатива Плюс"',
period_from: "2015-02-01",
period_to: "2015-02-28",
as_of_date: "2016-03-31"
},
previous_anchor_type: "item",
previous_anchor_value: "Рабочая станция универсального специалиста"
});
expect(result).not.toBeNull();
expect(result?.intent.intent).toBe("vat_liability_confirmed_for_tax_period");
expect(result?.filters.extracted_filters.period_from).toBe("2015-02-01");
expect(result?.filters.extracted_filters.period_to).toBe("2015-02-28");
expect(result?.baseReasons).toContain("period_from_followup_context");
});
});
@@ -0,0 +1,39 @@
import { describe, expect, it } from "vitest";
import { resolveAddressIntent } from "../src/services/addressIntentResolver";
describe("addressIntentResolver regression bridges", () => {
it("detects colloquial VAT liability for a month period", () => {
const result = resolveAddressIntent("прикинь какой ндс нам надо заплатить на февраль 2017");
expect(result.intent).toBe("vat_liability_confirmed_for_tax_period");
});
it("detects payables snapshot wording in plain human form", () => {
const result = resolveAddressIntent("мы должны комуто денег на сегодня?");
expect(result.intent).toBe("payables_confirmed_as_of_date");
});
it("detects top customer all-time revenue wording", () => {
const result = resolveAddressIntent("кто у нас самый доходный клиент за все время");
expect(result.intent).toBe("customer_revenue_and_payments");
});
it("detects top-year company revenue wording", () => {
const result = resolveAddressIntent("какой у нас самый доходный год");
expect(result.intent).toBe("customer_revenue_and_payments");
});
it("does not collapse very old stock request into generic inventory snapshot", () => {
const result = resolveAddressIntent("Есть ли остатки товара, которые закупались очень давно");
expect(result.intent).toBe("inventory_aging_by_purchase_date");
});
it("detects bare historical inventory root with explicit month-year", () => {
const result = resolveAddressIntent("остатки на март 2016");
expect(result.intent).toBe("inventory_on_hand_as_of_date");
});
});
@@ -2747,14 +2747,15 @@ describe("address filter extraction for balance drilldown", () => {
expect(extracted.warnings).toContain("period_to_derived_from_as_of_date_for_vat_forecast");
});
it("derives full tax quarter window for confirmed VAT tax-period intent from month phrase", () => {
it("keeps explicit month window for confirmed VAT tax-period intent from month phrase", () => {
const extracted = extractAddressFilters(
"сколько ндс надо заплатить в налоговую за декабрь 2019",
"vat_liability_confirmed_for_tax_period"
);
expect(extracted.extracted_filters.period_from).toBe("2019-10-01");
expect(extracted.extracted_filters.period_from).toBe("2019-12-01");
expect(extracted.extracted_filters.period_to).toBe("2019-12-31");
expect(extracted.warnings).toContain("period_derived_from_tax_quarter_for_confirmed_vat_liability");
expect(extracted.warnings).toContain("period_derived_from_month_phrase");
expect(extracted.warnings).not.toContain("period_derived_from_tax_quarter_for_confirmed_vat_liability");
});
it("derives VAT forecast quarter-to-date window for explicit day+month+year phrase", () => {
@@ -0,0 +1,145 @@
import { describe, expect, it } from "vitest";
import { composeCounterpartyAnalyticsReply } from "../src/services/address_runtime/counterpartyAnalyticsReplyBuilders";
import { composeInventoryReply } from "../src/services/address_runtime/inventoryReplyBuilders";
describe("address reply builders regressions", () => {
it("starts top customer aggregate reply with a direct business answer", () => {
const result = composeCounterpartyAnalyticsReply(
"customer_revenue_and_payments",
[
{
amount: 250000,
period: "2020-03-31",
registrator: "Поступление 1"
} as any
],
{
userMessage: "кто у нас самый доходный клиент за все время?"
},
{
formatPercent: () => null,
formatDateRu: (value: string) => value,
formatMoneyRub: (value: number) => `${value}`,
extractYearFromIso: (value: string | null) => (value ? Number(value.slice(0, 4)) : null),
detectCounterpartyProfileFocus: () => "full_profile",
detectCounterpartyLifecycleFocus: () => "active_customers_all_time",
hasCounterpartyLifecycleLongevityQuestion: () => false,
hasCounterpartyActivityAgeQuestion: () => false,
detectRankingLimit: () => 5,
detectValueRankingFocus: () => "top_by_total",
detectContractValueFocus: () => "top_by_turnover",
detectMinOpsForAvgCheck: () => 1,
extractRequestedYearFromQuestion: () => null,
extractCounterpartyName: () => "Чапурнов",
extractContractName: () => null,
counterpartyLookupMatches: () => false,
toUtcDayTimestamp: () => null,
formatAgeYearsMonthsDays: () => "0 дней",
normalizeQuestionText: (value: string | null | undefined) => String(value ?? "")
}
);
expect(result?.text.split("\n")[0]).toContain("Самый доходный клиент");
expect(result?.text.split("\n")[0]).toContain("Чапурнов");
});
it("starts top year aggregate reply with a direct business answer", () => {
const result = composeCounterpartyAnalyticsReply(
"customer_revenue_and_payments",
[
{
amount: 320000,
period: "2021-05-12",
registrator: "Поступление 2"
} as any
],
{
userMessage: "какой у нас самый доходный год"
},
{
formatPercent: () => null,
formatDateRu: (value: string) => value,
formatMoneyRub: (value: number) => `${value}`,
extractYearFromIso: (value: string | null) => (value ? Number(value.slice(0, 4)) : null),
detectCounterpartyProfileFocus: () => "full_profile",
detectCounterpartyLifecycleFocus: () => "active_customers_all_time",
hasCounterpartyLifecycleLongevityQuestion: () => false,
hasCounterpartyActivityAgeQuestion: () => false,
detectRankingLimit: () => 5,
detectValueRankingFocus: () => "top_years_by_total",
detectContractValueFocus: () => "top_by_turnover",
detectMinOpsForAvgCheck: () => 1,
extractRequestedYearFromQuestion: () => null,
extractCounterpartyName: () => "Чапурнов",
extractContractName: () => null,
counterpartyLookupMatches: () => false,
toUtcDayTimestamp: () => null,
formatAgeYearsMonthsDays: () => "0 дней",
normalizeQuestionText: (value: string | null | undefined) => String(value ?? "")
}
);
expect(result?.text.split("\n")[0]).toContain("Самый доходный год");
expect(result?.text.split("\n")[0]).toContain("2021");
});
it("keeps very old stock answer free of explicit as-of date in the first line", () => {
const result = composeInventoryReply(
"inventory_aging_by_purchase_date",
[
{
amount: 1000,
period: "2015-02-05",
registrator: "Поступление 3"
} as any
],
{
userMessage: "Есть ли остатки товара, которые закупались очень давно",
asOfDate: "2026-04-18"
},
{
resolvePayablesAsOfDate: () => "2026-04-18",
buildInventoryOnHandAggregate: () => [],
uniqueStrings: (values: string[]) => values,
formatDateRu: (value: string) => value,
formatNumberWithDots: (value: number) => String(value),
formatMoneyRub: (value: number) => `${value}`,
isInventoryPurchaseMovement: () => true,
summarizeInventoryTraceRows: () => ({
item: "Рабочая станция",
warehouses: ["Основной склад"],
organizations: ["ООО Альтернатива Плюс"],
counterparties: ["Чапурнов"],
documents: ["Поступление 3"],
firstPeriod: "2015-02-05",
lastPeriod: "2015-02-05",
totalAmount: 1000
}),
formatInventoryTraceRows: () => [],
hasInventoryPurchaseDateActionFocus: () => false,
inventoryTraceDateLabel: (value: string | null) => value ?? "дата не указана",
extractInventoryCounterpartyCandidates: () => ["Чапурнов"],
buildInventoryAgingByItemAggregate: () => [
{
item: "Рабочая станция",
warehouse: "Основной склад",
organization: "ООО Альтернатива Плюс",
firstPurchasePeriod: "2015-02-05",
lastPurchasePeriod: "2015-02-05",
operations: 1,
documentCount: 1,
counterparties: ["Чапурнов"],
ageDays: 4089
}
],
formatInventoryAgingRows: () => ["1. Рабочая станция | первая закупка: 2015-02-05"],
isInventorySaleMovement: () => false
}
);
const firstLine = result?.text.split("\n")[0] ?? "";
expect(firstLine).toContain("К самым старым закупкам");
expect(firstLine).not.toContain("2026-04-18");
});
});
@@ -157,6 +157,53 @@ describe("assistantRoutePolicy", () => {
expect(decision.orchestrationContract?.semantic_route_arbitration?.supported_address_intent_detected).toBe(true);
});
it("does not let deep session continuation override an exact VAT period route", () => {
const policy = buildPolicy({
detectAddressQuestionMode: () => ({ mode: "address_query", confidence: "high" }),
resolveAddressIntent: () => ({ intent: "vat_liability_confirmed_for_tax_period", confidence: "high" }),
resolveAddressToolGateDecision: () => ({
runAddressLane: true,
decision: "run_address_lane",
reason: "address_mode_classifier_detected"
}),
hasDeepSessionContinuationSignal: () => true
});
const decision = policy.resolveAssistantOrchestrationDecision({
rawUserMessage: "а какой НДС мы должны сгрузить на март 2020?",
effectiveAddressUserMessage: "а какой НДС мы должны сгрузить на март 2020?",
followupContext: null,
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "address_query",
mode_confidence: "high",
intent: "vat_liability_confirmed_for_tax_period",
intent_confidence: "high"
},
semanticExtractionContract: {
valid: true,
apply_canonical_recommended: true,
extraction: {
query_shape: "UNKNOWN",
aggregation_profile: "unknown"
},
guard_hints: {
deep_investigation_signal_detected: false
}
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.toolGateReason).toBe("address_mode_classifier_detected");
expect(decision.livingMode).toBe("address_data");
expect(decision.orchestrationContract?.deep_session_continuation_fallback_to_deep).toBe(false);
});
it("routes memory recap follow-up over grounded answer to chat", () => {
const policy = buildPolicy({
resolveRouteMemorySignals: () => ({
@@ -254,6 +254,133 @@ describe("assistantTransitionPolicy", () => {
expect(carryover?.followupContext?.root_context_only).toBeUndefined();
});
it("bridges selected-item purchase provenance into a VAT period follow-up", () => {
const item = "Рабочая станция универсального специалиста";
const policy = buildPolicy({
findLastAddressAssistantItem: () => ({
text: [
`По позиции ${item} однозначный поставщик не подтвержден.`,
"Подтверждение:",
"- Первая найденная дата закупки: 05.02.2015.",
"- Последняя найденная дата закупки: 22.07.2015."
].join("\n"),
debug: {
detected_intent: "inventory_purchase_provenance_for_item",
extracted_filters: {
item,
organization: 'ООО "Альтернатива Плюс"',
as_of_date: "2016-03-31"
},
anchor_type: "item",
anchor_value_resolved: item
}
}),
hasAddressFollowupContextSignal: () => false,
findRecentInventoryRootFrame: () => null,
resolveAddressIntent: () => ({ intent: "unknown" })
});
const carryover = policy.resolveAddressFollowupCarryoverContext(
"ндс можешь прикинуть на дату покупки рабочей станции?",
[],
null,
null,
{
session_context: {
active_focus_object: {
object_type: "item",
label: item,
provenance_result_set_id: "rs-provenance"
},
active_result_set_id: "rs-provenance"
},
result_sets: [
{
result_set_id: "rs-provenance",
intent: "inventory_purchase_provenance_for_item",
entity_refs: [
{
index: 1,
entity_type: "item",
value: "Поступление товаров и услуг 00000000023 от 05.02.2015 0:00:00"
}
]
}
]
}
);
expect(carryover?.followupContext?.previous_intent).toBe("inventory_purchase_provenance_for_item");
expect(carryover?.followupContext?.target_intent).toBe("vat_liability_confirmed_for_tax_period");
expect(carryover?.followupContext?.previous_filters).toMatchObject({
item,
organization: 'ООО "Альтернатива Плюс"',
period_from: "2015-02-01",
period_to: "2015-02-28"
});
});
it("keeps selected-object continuity for purchase-date VAT bridge even when an inventory root frame exists", () => {
const item = "Рабочая станция универсального специалиста";
const policy = buildPolicy({
findLastAddressAssistantItem: () => ({
text: [
`По позиции ${item} однозначный поставщик не подтвержден.`,
"Подтверждение:",
"- Первая найденная дата закупки: 05.02.2015."
].join("\n"),
debug: {
detected_intent: "inventory_purchase_provenance_for_item",
extracted_filters: {
item,
organization: 'ООО "Альтернатива Плюс"',
as_of_date: "2016-03-31"
},
anchor_type: "item",
anchor_value_resolved: item
}
}),
hasAddressFollowupContextSignal: () => false,
hasForeignAccountingPivotOverInventoryMessage: () => true,
resolveAddressIntent: () => ({ intent: "unknown" })
});
const carryover = policy.resolveAddressFollowupCarryoverContext(
"ндс можешь прикинуть на дату покупки рабочей станции?",
[],
null,
null,
{
session_context: {
active_focus_object: {
object_type: "item",
label: item,
provenance_result_set_id: "rs-provenance"
},
active_result_set_id: "rs-provenance"
},
result_sets: [
{
result_set_id: "rs-provenance",
intent: "inventory_purchase_provenance_for_item",
entity_refs: [
{
index: 1,
entity_type: "item",
value: "Поступление товаров и услуг 00000000023 от 05.02.2015 0:00:00"
}
]
}
]
}
);
expect(carryover?.followupSelectionMode).toBe("carry_previous_intent");
expect(carryover?.followupContext?.root_context_only).toBeUndefined();
expect(carryover?.followupContext?.previous_intent).toBe("inventory_purchase_provenance_for_item");
expect(carryover?.followupContext?.target_intent).toBe("vat_liability_confirmed_for_tax_period");
});
it("drops stale carryover for a fresh standalone topic from another intent family", () => {
const policy = buildPolicy({
findLastAddressAssistantItem: () => ({
@@ -0,0 +1,111 @@
{
"suite_id": "assistant_saved_session_runtime_job-vc0r4pNwB-",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_runtime_v0_1",
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "saved_user_sessions_runtime",
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "приветик - че как там дела"
},
{
"user_message": "расскажи что можешь интересного"
},
{
"user_message": "кайф - что там на складе по остаткам?"
},
{
"user_message": "а исторические остатки на другие даты умеешь?"
},
{
"user_message": "давай на июль 2017"
},
{
"user_message": "март 2016"
},
{
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
},
{
"user_message": "а кому продали?"
},
{
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
},
{
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
},
{
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
},
{
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
},
{
"user_message": "кто у нас самый доходный клиент за все время"
},
{
"user_message": "кто нам должен денег на май 2017"
},
{
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
},
{
"user_message": "мы должны комуто денег на сегодня?"
},
{
"user_message": "а нам?"
},
{
"user_message": "какой у нас самый доходный год"
},
{
"user_message": "а за 2017 мы скок заработали?"
},
{
"user_message": "сколько вообще денег мы заработали за все время?"
},
{
"user_message": "ты умеешь считать дельту по договорам?"
},
{
"user_message": "по чепурнову покажи все доки"
},
{
"user_message": "а по свк"
},
{
"user_message": "а сейчас у нас есть что на складе?"
},
{
"user_message": "что нам отгружать чепурнов? какой товар или услугу?"
},
{
"user_message": "какие остатки на складе на сегодня"
},
{
"user_message": "остатки на март 2016"
},
{
"user_message": "хвосты покажи по счету 60 на август 2022"
},
{
"user_message": "Есть ли остатки товара, которые закупались очень давно"
},
{
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
}
]
}
]
}