АРЧ АП11 - Архитектура после регресса: + Архитектура: восстановить bridge от provenance выбранной позиции к VAT-периоду и закрыть phase10 replay
This commit is contained in:
@@ -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",
|
||||
|
||||
+16
-4
@@ -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"
|
||||
|
||||
+2
-2
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user