АРЧ АП11 - Добавить item-profitability intent для selected-object follow-up в inventory contour
This commit is contained in:
parent
a493e2fd69
commit
d7c4eb781a
|
|
@ -808,6 +808,7 @@ function isInventoryTraceIntent(intent) {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
|
|
@ -816,6 +817,7 @@ function isInventoryItemAnchoredIntent(intent) {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_aging_by_purchase_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain");
|
||||
}
|
||||
function usesRecipeDefaultLimit(intent) {
|
||||
|
|
@ -824,6 +826,7 @@ function usesRecipeDefaultLimit(intent) {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
|
|
@ -1197,6 +1200,7 @@ function requiredFiltersByIntent(intent) {
|
|||
}
|
||||
if (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain") {
|
||||
return ["item"];
|
||||
|
|
@ -1234,6 +1238,7 @@ function usesAsOfPrimaryWindow(intent) {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date" ||
|
||||
intent === "open_items_by_counterparty_or_contract" ||
|
||||
|
|
|
|||
|
|
@ -1357,6 +1357,9 @@ function hasSelectedObjectInventoryPurchaseDocumentsSignal(text) {
|
|||
function hasSelectedObjectInventorySaleTraceSignal(text) {
|
||||
return hasSelectedObjectInventoryCue(text) && (0, inventoryLifecycleCueHelpers_1.hasInventorySaleCue)(text);
|
||||
}
|
||||
function hasSelectedObjectInventoryProfitabilitySignal(text) {
|
||||
return hasSelectedObjectInventoryCue(text) && (0, inventoryLifecycleCueHelpers_1.hasInventoryProfitabilityCue)(text);
|
||||
}
|
||||
function hasInventoryProvenanceSignalV2(text) {
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product|остат(?:ок|ки)|склад)/iu.test(text);
|
||||
const hasSupplierCue = (0, inventoryLifecycleCueHelpers_1.hasInventorySupplierCue)(text) || /кем\s+поставлен/iu.test(text);
|
||||
|
|
@ -1593,6 +1596,13 @@ function resolveAddressIntent(userMessage) {
|
|||
reasons: ["inventory_purchase_documents_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasSelectedObjectInventoryProfitabilitySignal(text)) {
|
||||
return {
|
||||
intent: "inventory_profitability_for_item",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_selected_object_profitability_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasSelectedObjectInventorySaleTraceSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_sale_trace_for_item",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ const DISPLAY_ENTITY_TYPE_BY_INTENT = {
|
|||
inventory_purchase_documents_for_item: "item",
|
||||
inventory_supplier_stock_overlap_as_of_date: "item",
|
||||
inventory_sale_trace_for_item: "item",
|
||||
inventory_profitability_for_item: "item",
|
||||
inventory_purchase_to_sale_chain: "item",
|
||||
inventory_aging_by_purchase_date: "item"
|
||||
};
|
||||
|
|
@ -51,6 +52,7 @@ const RESULT_SET_TYPE_BY_INTENT = {
|
|||
inventory_purchase_documents_for_item: "inventory_trace",
|
||||
inventory_supplier_stock_overlap_as_of_date: "inventory_trace",
|
||||
inventory_sale_trace_for_item: "inventory_trace",
|
||||
inventory_profitability_for_item: "inventory_trace",
|
||||
inventory_purchase_to_sale_chain: "inventory_trace",
|
||||
inventory_aging_by_purchase_date: "inventory_trace",
|
||||
period_coverage_profile: "profile_summary",
|
||||
|
|
|
|||
|
|
@ -279,6 +279,7 @@ function hasSelectedObjectInventoryFollowupSignal(text) {
|
|||
return false;
|
||||
}
|
||||
return ((0, inventoryLifecycleCueHelpers_1.hasInventorySupplierCue)(text) ||
|
||||
(0, inventoryLifecycleCueHelpers_1.hasInventoryProfitabilityCue)(text) ||
|
||||
(0, inventoryLifecycleCueHelpers_1.hasInventorySaleCue)(text) ||
|
||||
/(?:кто\s+(?:поставил|продал)|по\s+каким\s+документам\s+.*купили)/iu.test(text) ||
|
||||
/(?:к[оа]му|куда)[\s\S]{0,80}(?:поставил|поставили|поставлен|поставлена|поставлено|отгрузил|отгрузили|отгружен|отгружена|отгружено)/iu.test(text) ||
|
||||
|
|
|
|||
|
|
@ -1239,6 +1239,7 @@ function isOrganizationScopedInventoryIntent(intent) {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
|
|
@ -1925,6 +1926,7 @@ function normalizeMissingAnchorLabel(anchor) {
|
|||
}
|
||||
function buildLimitedScopeLine(filters) {
|
||||
const organization = toNonEmptyFilterValue(filters.organization);
|
||||
const item = toNonEmptyFilterValue(filters.item);
|
||||
const asOfDate = toNonEmptyFilterValue(filters.as_of_date);
|
||||
const periodFrom = toNonEmptyFilterValue(filters.period_from);
|
||||
const periodTo = toNonEmptyFilterValue(filters.period_to);
|
||||
|
|
@ -1932,6 +1934,9 @@ function buildLimitedScopeLine(filters) {
|
|||
if (organization) {
|
||||
scopeParts.push(`организация ${organization}`);
|
||||
}
|
||||
if (item) {
|
||||
scopeParts.push(`товар ${item}`);
|
||||
}
|
||||
if (asOfDate) {
|
||||
scopeParts.push(`срез на ${asOfDate}`);
|
||||
}
|
||||
|
|
@ -1951,6 +1956,7 @@ function buildLimitedVariantSeedFingerprint(filters) {
|
|||
"contract",
|
||||
"account",
|
||||
"document_ref",
|
||||
"item",
|
||||
"as_of_date",
|
||||
"period_from",
|
||||
"period_to"
|
||||
|
|
@ -2001,6 +2007,9 @@ function buildLimitedOffers(input) {
|
|||
else if (input.intent === "inventory_sale_trace_for_item") {
|
||||
offers.push("показать подтвержденные движения выбытия товара со счета 41.01");
|
||||
}
|
||||
else if (input.intent === "inventory_profitability_for_item") {
|
||||
offers.push("показать выручку, прибыль или маржу по выбранному товару за период продаж");
|
||||
}
|
||||
else if (input.intent === "inventory_purchase_to_sale_chain") {
|
||||
offers.push("показать документальную цепочку по товару: поступление на 41.01 и последующее выбытие");
|
||||
}
|
||||
|
|
@ -2060,6 +2069,7 @@ function buildLimitedIntentSignalLine(input) {
|
|||
list_receivables_counterparties: "Сигнал запроса: нужен ранжированный список должников.",
|
||||
list_payables_counterparties: "Сигнал запроса: нужен ранжированный список кредиторов.",
|
||||
inventory_on_hand_as_of_date: "Сигнал запроса: нужен подтвержденный срез товаров на складе на дату.",
|
||||
inventory_profitability_for_item: "Сигнал запроса: нужен расчет выручки/прибыли/маржи по выбранной номенклатуре.",
|
||||
receivables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез дебиторской задолженности на дату.",
|
||||
payables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез обязательств к оплате на дату.",
|
||||
vat_payable_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез НДС к уплате на дату.",
|
||||
|
|
@ -2083,6 +2093,7 @@ function hasAggregateLimitedSignal(input) {
|
|||
input.intent === "contract_usage_overview" ||
|
||||
input.intent === "supplier_payouts_profile" ||
|
||||
input.intent === "customer_revenue_and_payments" ||
|
||||
input.intent === "inventory_profitability_for_item" ||
|
||||
input.intent === "contract_usage_and_value") {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.hasInventorySupplierFollowupCue = hasInventorySupplierFollowupCue;
|
||||
exports.hasInventoryPurchaseDocumentsFollowupCue = hasInventoryPurchaseDocumentsFollowupCue;
|
||||
exports.hasInventoryProfitabilityFollowupCue = hasInventoryProfitabilityFollowupCue;
|
||||
exports.hasInventoryPurchaseDateFollowupCue = hasInventoryPurchaseDateFollowupCue;
|
||||
exports.hasBareInventoryPurchaseDateFollowupCue = hasBareInventoryPurchaseDateFollowupCue;
|
||||
exports.hasInventorySaleFollowupCue = hasInventorySaleFollowupCue;
|
||||
|
|
@ -261,6 +262,7 @@ function isInventoryIntent(intent) {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
|
|
@ -271,6 +273,7 @@ function isInventoryDrilldownFrameIntent(intent) {
|
|||
return (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
|
|
@ -448,6 +451,9 @@ function hasInventoryPurchaseDocumentsFollowupCue(text) {
|
|||
return (/(?:по\s+каким\s+документам\s+(?:это|его|этот\s+товар|эту\s+позицию)\s+купили|по\s+каким\s+документам\s+(?:был\s+)?куплен|какими\s+документами\s+(?:это|его|этот\s+товар|эту\s+позицию)\s+купили|какими\s+документами\s+(?:был\s+)?куплен|покажи\s+документы\s+по\s+(?:этой\s+позиции|этому\s+товару|ней|нему)|документы\s+по\s+(?:этой\s+позиции|этому\s+товару|ней|нему)|purchase\s+documents|documents\s+of\s+purchase|through\s+which\s+documents)/iu.test(value) ||
|
||||
/(?:(?:покажи|показать|выведи|дай)?[\s\S]{0,30}док(?:и|умент[а-яё]*)[\s\S]{0,80}(?:по\s+(?:ним|ней|нему|этой\s+позиции|этому\s+товару)|операци)|(?:по\s+(?:ним|ней|нему|этой\s+позиции|этому\s+товару))[\s\S]{0,80}док(?:и|умент[а-яё]*))/iu.test(value));
|
||||
}
|
||||
function hasInventoryProfitabilityFollowupCue(text) {
|
||||
return (0, inventoryLifecycleCueHelpers_1.hasInventoryProfitabilityCue)(String(text ?? ""));
|
||||
}
|
||||
function hasInventoryPurchaseDateFollowupCue(text) {
|
||||
const value = String(text ?? "");
|
||||
return /(?:когда\s+(?:примерно\s+)?(?:мы\s+)?купили|когда\s+был\s+куплен|когда\s+куплен|когда\s+это\s+купили|когда\s+эту\s+позицию\s+купили|когда\s+ее\s+купили|дата\s+закупки|purchase\s+date)/iu.test(value) || (/когда/iu.test(value) && (0, inventoryLifecycleCueHelpers_1.hasInventoryPurchaseStem)(value));
|
||||
|
|
@ -622,6 +628,7 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date" ||
|
||||
intent === "payables_confirmed_as_of_date" ||
|
||||
|
|
@ -650,6 +657,7 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
|||
if ((intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date")) {
|
||||
const inheritedItem = previousItem ?? previousAnchorItem;
|
||||
|
|
@ -874,6 +882,7 @@ function resolveMissingRequiredFilters(intent, filters) {
|
|||
account_balance_snapshot: ["account", "as_of_date"],
|
||||
documents_forming_balance: ["account", "as_of_date"],
|
||||
inventory_on_hand_as_of_date: ["as_of_date"],
|
||||
inventory_profitability_for_item: ["item"],
|
||||
open_contracts_confirmed_as_of_date: ["as_of_date"],
|
||||
payables_confirmed_as_of_date: ["as_of_date"],
|
||||
receivables_confirmed_as_of_date: ["as_of_date"],
|
||||
|
|
@ -975,6 +984,23 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo
|
|||
};
|
||||
}
|
||||
}
|
||||
if (inventorySelectedObjectFollowup && hasInventoryProfitabilityFollowupCue(normalizedMessage)) {
|
||||
if (detectedIntent.intent === "unknown" ||
|
||||
detectedIntent.intent === "customer_revenue_and_payments" ||
|
||||
detectedIntent.intent === "list_documents_by_counterparty" ||
|
||||
detectedIntent.intent === "list_documents_by_contract" ||
|
||||
detectedIntent.intent === "bank_operations_by_counterparty" ||
|
||||
detectedIntent.intent === "bank_operations_by_contract" ||
|
||||
detectedIntent.intent === "inventory_on_hand_as_of_date" ||
|
||||
detectedIntent.intent === "inventory_sale_trace_for_item" ||
|
||||
detectedIntent.intent === previousIntent) {
|
||||
return {
|
||||
intent: "inventory_profitability_for_item",
|
||||
confidence: "low",
|
||||
reasons: [...detectedIntent.reasons, "intent_adjusted_to_inventory_followup_context"]
|
||||
};
|
||||
}
|
||||
}
|
||||
if (inventorySelectedObjectFollowup && hasInventoryPurchaseDateFollowupCue(normalizedMessage)) {
|
||||
if (detectedIntent.intent === "unknown" ||
|
||||
detectedIntent.intent === "inventory_purchase_provenance_for_item" ||
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ function resolvePrimaryAnchor(intent, filters) {
|
|||
if ((intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date") &&
|
||||
item) {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,17 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.buildAssistantAddressOrchestrationRuntime = buildAssistantAddressOrchestrationRuntime;
|
||||
const assistantRoutePolicyRuntimeAdapter_1 = require("./assistantRoutePolicyRuntimeAdapter");
|
||||
const inventoryLifecycleCueHelpers_1 = require("./inventoryLifecycleCueHelpers");
|
||||
function hasSelectedObjectInventorySignal(text) {
|
||||
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ним|selected\s+object)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function hasSelectedObjectInventoryActionCue(text) {
|
||||
return /(?:кому[\s\S]{0,80}(?:продал[аи]?|реализова[нлт][а-я]*|поставил[аи]?|поставлен[а-я]*|отгрузил[аи]?|отгружен[а-я]*)|кому\s+был\s+продан|куда[\s\S]{0,80}(?:продал[аи]?|реализова[нлт][а-я]*|поставил[аи]?|поставлен[а-я]*|отгрузил[аи]?|отгружен[а-я]*)|кто[\s\S]{0,40}купил|кто\s+это\s+поставил|кто\s+поставил|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+мы\s+купили|где\s+куплено|по\s+каким\s+документам|какими\s+документами|покажи\s+документы|документы[\s\S]{0,80}(?:по\s+(?:ним|ней|нему|этой\s+позиции|этому\s+товару)|операци)|документы\s+закупки|buyer|sale\s+trace|supplier|vendor|purchase\s+documents|purchase[\s-]?to[\s-]?sale|old\s+purchase|aged\s+stock)/iu.test(String(text ?? ""));
|
||||
const value = String(text ?? "");
|
||||
return (/(?:кому[\s\S]{0,80}(?:продал[аи]?|реализова[нлт][а-я]*|поставил[аи]?|поставлен[а-я]*|отгрузил[аи]?|отгружен[а-я]*)|кому\s+был\s+продан|куда[\s\S]{0,80}(?:продал[аи]?|реализова[нлт][а-я]*|поставил[аи]?|поставлен[а-я]*|отгрузил[аи]?|отгружен[а-я]*)|кто[\s\S]{0,40}купил|кто\s+это\s+поставил|кто\s+поставил|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+мы\s+купили|где\s+куплено|по\s+каким\s+документам|какими\s+документами|покажи\s+документы|документы[\s\S]{0,80}(?:по\s+(?:ним|ней|нему|этой\s+позиции|этому\s+товару)|операци)|документы\s+закупки|buyer|sale\s+trace|supplier|vendor|purchase\s+documents|purchase[\s-]?to[\s-]?sale|old\s+purchase|aged\s+stock)/iu.test(value) || (0, inventoryLifecycleCueHelpers_1.hasInventoryProfitabilityCue)(value));
|
||||
}
|
||||
function isGenericCanonicalDriftIntent(intent) {
|
||||
return (intent === "open_items_by_counterparty_or_contract" ||
|
||||
intent === "customer_revenue_and_payments" ||
|
||||
intent === "list_documents_by_counterparty" ||
|
||||
intent === "list_documents_by_contract" ||
|
||||
intent === "bank_operations_by_counterparty" ||
|
||||
|
|
|
|||
|
|
@ -2505,6 +2505,7 @@ function isInventoryDrilldownFrameIntent(intent) {
|
|||
return intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date";
|
||||
}
|
||||
|
|
@ -2781,6 +2782,7 @@ function isInventorySelectedObjectIntent(intent) {
|
|||
return intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain";
|
||||
}
|
||||
function hasShortInventoryObjectFollowupSignal(userMessage) {
|
||||
|
|
@ -3103,6 +3105,7 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
|
|||
sourceIntentHint === "inventory_purchase_provenance_for_item" ||
|
||||
sourceIntentHint === "inventory_purchase_documents_for_item" ||
|
||||
sourceIntentHint === "inventory_sale_trace_for_item" ||
|
||||
sourceIntentHint === "inventory_profitability_for_item" ||
|
||||
sourceIntentHint === "inventory_purchase_to_sale_chain" ||
|
||||
sourceIntentHint === "inventory_aging_by_purchase_date" ||
|
||||
hasSelectedObjectInventorySignalPrimary ||
|
||||
|
|
@ -3534,6 +3537,7 @@ function resolveRequiredAnchorTypeForIntent(intent) {
|
|||
if (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date") {
|
||||
return "item";
|
||||
|
|
@ -3586,7 +3590,9 @@ function hasSelectedObjectInventoryFollowupSignalForPredecompose(text) {
|
|||
return /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function isInventorySelectedObjectFollowupIntent(intent) {
|
||||
return intent === "inventory_purchase_provenance_for_item" || intent === "inventory_purchase_documents_for_item";
|
||||
return intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_profitability_for_item";
|
||||
}
|
||||
function hasSameDateAccountFollowupSignalForPredecompose(text) {
|
||||
const source = String(text ?? "");
|
||||
|
|
@ -4269,6 +4275,7 @@ const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
|||
"inventory_purchase_documents_for_item",
|
||||
"inventory_supplier_stock_overlap_as_of_date",
|
||||
"inventory_sale_trace_for_item",
|
||||
"inventory_profitability_for_item",
|
||||
"inventory_purchase_to_sale_chain",
|
||||
"inventory_aging_by_purchase_date",
|
||||
"contract_usage_overview",
|
||||
|
|
@ -4281,6 +4288,7 @@ const ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS = new Set([
|
|||
"inventory_purchase_provenance_for_item",
|
||||
"inventory_purchase_documents_for_item",
|
||||
"inventory_sale_trace_for_item",
|
||||
"inventory_profitability_for_item",
|
||||
"inventory_purchase_to_sale_chain"
|
||||
]);
|
||||
function shouldBypassStrictDeepInvestigationCueForAddressIntent(intent) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||
exports.hasInventoryPurchaseStem = hasInventoryPurchaseStem;
|
||||
exports.hasInventorySupplierCue = hasInventorySupplierCue;
|
||||
exports.hasInventorySaleCue = hasInventorySaleCue;
|
||||
exports.hasInventoryProfitabilityCue = hasInventoryProfitabilityCue;
|
||||
function toText(value) {
|
||||
return String(value ?? "");
|
||||
}
|
||||
|
|
@ -34,3 +35,15 @@ function hasInventorySaleCue(text) {
|
|||
}
|
||||
return /(?:^|[\s,.;:!?])(продано|продали|продан(?:а|о|ы)?|реализовано|реализовали|реализован(?:а|о|ы)?)(?=$|[\s,.;:!?])/iu.test(value);
|
||||
}
|
||||
function hasInventoryProfitabilityCue(text) {
|
||||
const value = toText(text);
|
||||
const hasExplicitEconomicsMetric = /(?:прибыл|марж|рентабел|наценк|выручк|доход|profit(?:ability)?|margin|revenue|unit\s+economics)/iu.test(value);
|
||||
if (hasExplicitEconomicsMetric) {
|
||||
return true;
|
||||
}
|
||||
if (/(?:заработ(?:ал|али|аем|ок|ан)|прин[её]с(?:ли)?)/iu.test(value) &&
|
||||
/(?:денег|деньг|с\s+продаж[а-яё]*|по\s+продаж[а-яё]*|от\s+продаж[а-яё]*|продаж[а-яё]*|реализац|sale|sales)/iu.test(value)) {
|
||||
return true;
|
||||
}
|
||||
return /(?:сколько|скока|скок)[\s\S]{0,60}(?:заработ|прин[её]с|денег[\s\S]{0,20}(?:с\s+продаж|по\s+продаж|от\s+продаж|продаж|реализац))/iu.test(value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -916,6 +916,7 @@ function isInventoryTraceIntent(intent: AddressIntent): boolean {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date"
|
||||
);
|
||||
|
|
@ -927,6 +928,7 @@ function isInventoryItemAnchoredIntent(intent: AddressIntent): boolean {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_aging_by_purchase_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain"
|
||||
);
|
||||
}
|
||||
|
|
@ -938,6 +940,7 @@ function usesRecipeDefaultLimit(intent: AddressIntent): boolean {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date"
|
||||
);
|
||||
|
|
@ -1373,6 +1376,7 @@ function requiredFiltersByIntent(intent: AddressIntent): Array<keyof AddressFilt
|
|||
if (
|
||||
intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain"
|
||||
) {
|
||||
|
|
@ -1415,6 +1419,7 @@ function usesAsOfPrimaryWindow(intent: AddressIntent): boolean {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date" ||
|
||||
intent === "open_items_by_counterparty_or_contract" ||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import type { AddressIntentResolution } from "../types/addressQuery";
|
||||
import { hasInventoryPurchaseStem, hasInventorySaleCue, hasInventorySupplierCue } from "./inventoryLifecycleCueHelpers";
|
||||
import {
|
||||
hasInventoryProfitabilityCue,
|
||||
hasInventoryPurchaseStem,
|
||||
hasInventorySaleCue,
|
||||
hasInventorySupplierCue
|
||||
} from "./inventoryLifecycleCueHelpers";
|
||||
|
||||
const RECEIVABLES_STRONG = [
|
||||
"кто должен нам",
|
||||
|
|
@ -1638,6 +1643,10 @@ function hasSelectedObjectInventorySaleTraceSignal(text: string): boolean {
|
|||
return hasSelectedObjectInventoryCue(text) && hasInventorySaleCue(text);
|
||||
}
|
||||
|
||||
function hasSelectedObjectInventoryProfitabilitySignal(text: string): boolean {
|
||||
return hasSelectedObjectInventoryCue(text) && hasInventoryProfitabilityCue(text);
|
||||
}
|
||||
|
||||
function hasInventoryProvenanceSignalV2(text: string): boolean {
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product|остат(?:ок|ки)|склад)/iu.test(text);
|
||||
const hasSupplierCue = hasInventorySupplierCue(text) || /кем\s+поставлен/iu.test(text);
|
||||
|
|
@ -1946,6 +1955,14 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
|
|||
};
|
||||
}
|
||||
|
||||
if (hasSelectedObjectInventoryProfitabilitySignal(text)) {
|
||||
return {
|
||||
intent: "inventory_profitability_for_item",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_selected_object_profitability_signal_detected"]
|
||||
};
|
||||
}
|
||||
|
||||
if (hasSelectedObjectInventorySaleTraceSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_sale_trace_for_item",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ const DISPLAY_ENTITY_TYPE_BY_INTENT: Partial<Record<AddressIntent, AddressFocusO
|
|||
inventory_purchase_documents_for_item: "item",
|
||||
inventory_supplier_stock_overlap_as_of_date: "item",
|
||||
inventory_sale_trace_for_item: "item",
|
||||
inventory_profitability_for_item: "item",
|
||||
inventory_purchase_to_sale_chain: "item",
|
||||
inventory_aging_by_purchase_date: "item"
|
||||
};
|
||||
|
|
@ -60,6 +61,7 @@ const RESULT_SET_TYPE_BY_INTENT: Partial<Record<AddressIntent, AddressResultSetT
|
|||
inventory_purchase_documents_for_item: "inventory_trace",
|
||||
inventory_supplier_stock_overlap_as_of_date: "inventory_trace",
|
||||
inventory_sale_trace_for_item: "inventory_trace",
|
||||
inventory_profitability_for_item: "inventory_trace",
|
||||
inventory_purchase_to_sale_chain: "inventory_trace",
|
||||
inventory_aging_by_purchase_date: "inventory_trace",
|
||||
period_coverage_profile: "profile_summary",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
import type { AddressModeDetection } from "../types/addressQuery";
|
||||
|
||||
import { hasInventoryPurchaseStem, hasInventorySaleCue, hasInventorySupplierCue } from "./inventoryLifecycleCueHelpers";
|
||||
import {
|
||||
hasInventoryProfitabilityCue,
|
||||
hasInventoryPurchaseStem,
|
||||
hasInventorySaleCue,
|
||||
hasInventorySupplierCue
|
||||
} from "./inventoryLifecycleCueHelpers";
|
||||
|
||||
const ADDRESS_ACTION_TOKENS = [
|
||||
"show",
|
||||
|
|
@ -290,6 +295,7 @@ function hasSelectedObjectInventoryFollowupSignal(text: string): boolean {
|
|||
}
|
||||
return (
|
||||
hasInventorySupplierCue(text) ||
|
||||
hasInventoryProfitabilityCue(text) ||
|
||||
hasInventorySaleCue(text) ||
|
||||
/(?:кто\s+(?:поставил|продал)|по\s+каким\s+документам\s+.*купили)/iu.test(text) ||
|
||||
/(?:к[оа]му|куда)[\s\S]{0,80}(?:поставил|поставили|поставлен|поставлена|поставлено|отгрузил|отгрузили|отгружен|отгружена|отгружено)/iu.test(text) ||
|
||||
|
|
|
|||
|
|
@ -1533,6 +1533,7 @@ function isOrganizationScopedInventoryIntent(intent: AddressIntent): boolean {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date"
|
||||
);
|
||||
|
|
@ -2418,6 +2419,7 @@ function normalizeMissingAnchorLabel(anchor: string): string {
|
|||
|
||||
function buildLimitedScopeLine(filters: AddressFilterSet): string | null {
|
||||
const organization = toNonEmptyFilterValue(filters.organization);
|
||||
const item = toNonEmptyFilterValue(filters.item);
|
||||
const asOfDate = toNonEmptyFilterValue(filters.as_of_date);
|
||||
const periodFrom = toNonEmptyFilterValue(filters.period_from);
|
||||
const periodTo = toNonEmptyFilterValue(filters.period_to);
|
||||
|
|
@ -2425,6 +2427,9 @@ function buildLimitedScopeLine(filters: AddressFilterSet): string | null {
|
|||
if (organization) {
|
||||
scopeParts.push(`организация ${organization}`);
|
||||
}
|
||||
if (item) {
|
||||
scopeParts.push(`товар ${item}`);
|
||||
}
|
||||
if (asOfDate) {
|
||||
scopeParts.push(`срез на ${asOfDate}`);
|
||||
} else if (periodFrom || periodTo) {
|
||||
|
|
@ -2444,6 +2449,7 @@ function buildLimitedVariantSeedFingerprint(filters: AddressFilterSet): string {
|
|||
"contract",
|
||||
"account",
|
||||
"document_ref",
|
||||
"item",
|
||||
"as_of_date",
|
||||
"period_from",
|
||||
"period_to"
|
||||
|
|
@ -2503,6 +2509,8 @@ function buildLimitedOffers(input: {
|
|||
offers.push("показать документы поступления по товару на 41.01");
|
||||
} else if (input.intent === "inventory_sale_trace_for_item") {
|
||||
offers.push("показать подтвержденные движения выбытия товара со счета 41.01");
|
||||
} else if (input.intent === "inventory_profitability_for_item") {
|
||||
offers.push("показать выручку, прибыль или маржу по выбранному товару за период продаж");
|
||||
} else if (input.intent === "inventory_purchase_to_sale_chain") {
|
||||
offers.push("показать документальную цепочку по товару: поступление на 41.01 и последующее выбытие");
|
||||
} else if (input.intent === "open_contracts_confirmed_as_of_date") {
|
||||
|
|
@ -2565,6 +2573,7 @@ function buildLimitedIntentSignalLine(input: {
|
|||
list_receivables_counterparties: "Сигнал запроса: нужен ранжированный список должников.",
|
||||
list_payables_counterparties: "Сигнал запроса: нужен ранжированный список кредиторов.",
|
||||
inventory_on_hand_as_of_date: "Сигнал запроса: нужен подтвержденный срез товаров на складе на дату.",
|
||||
inventory_profitability_for_item: "Сигнал запроса: нужен расчет выручки/прибыли/маржи по выбранной номенклатуре.",
|
||||
receivables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез дебиторской задолженности на дату.",
|
||||
payables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез обязательств к оплате на дату.",
|
||||
vat_payable_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез НДС к уплате на дату.",
|
||||
|
|
@ -2596,6 +2605,7 @@ function hasAggregateLimitedSignal(input: {
|
|||
input.intent === "contract_usage_overview" ||
|
||||
input.intent === "supplier_payouts_profile" ||
|
||||
input.intent === "customer_revenue_and_payments" ||
|
||||
input.intent === "inventory_profitability_for_item" ||
|
||||
input.intent === "contract_usage_and_value"
|
||||
) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,12 @@ import {
|
|||
isInventoryItemAnchorDegradation,
|
||||
isLowQualityInventoryItemAnchorValue
|
||||
} from "../addressFilterExtractor";
|
||||
import { hasInventoryPurchaseStem, hasInventorySaleCue, hasInventorySupplierCue } from "../inventoryLifecycleCueHelpers";
|
||||
import {
|
||||
hasInventoryProfitabilityCue,
|
||||
hasInventoryPurchaseStem,
|
||||
hasInventorySaleCue,
|
||||
hasInventorySupplierCue
|
||||
} from "../inventoryLifecycleCueHelpers";
|
||||
import { applyAddressLlmSemanticHintsToExtraction } from "./semanticHintOverlay";
|
||||
import type { AddressLlmSemanticHints } from "../../types/addressQuery";
|
||||
|
||||
|
|
@ -352,6 +357,7 @@ function isInventoryIntent(intent: AddressIntent | undefined): boolean {
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date"
|
||||
);
|
||||
|
|
@ -366,6 +372,7 @@ function isInventoryDrilldownFrameIntent(intent: AddressIntent | undefined): boo
|
|||
intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date"
|
||||
);
|
||||
|
|
@ -576,6 +583,10 @@ export function hasInventoryPurchaseDocumentsFollowupCue(text: string): boolean
|
|||
);
|
||||
}
|
||||
|
||||
export function hasInventoryProfitabilityFollowupCue(text: string): boolean {
|
||||
return hasInventoryProfitabilityCue(String(text ?? ""));
|
||||
}
|
||||
|
||||
export function hasInventoryPurchaseDateFollowupCue(text: string): boolean {
|
||||
const value = String(text ?? "");
|
||||
return /(?:когда\s+(?:примерно\s+)?(?:мы\s+)?купили|когда\s+был\s+куплен|когда\s+куплен|когда\s+это\s+купили|когда\s+эту\s+позицию\s+купили|когда\s+ее\s+купили|дата\s+закупки|purchase\s+date)/iu.test(
|
||||
|
|
@ -796,6 +807,7 @@ function mergeFollowupFilters(
|
|||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date" ||
|
||||
intent === "payables_confirmed_as_of_date" ||
|
||||
|
|
@ -829,6 +841,7 @@ function mergeFollowupFilters(
|
|||
(intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date")
|
||||
) {
|
||||
|
|
@ -1092,6 +1105,7 @@ function resolveMissingRequiredFilters(intent: AddressIntent, filters: AddressFi
|
|||
account_balance_snapshot: ["account", "as_of_date"],
|
||||
documents_forming_balance: ["account", "as_of_date"],
|
||||
inventory_on_hand_as_of_date: ["as_of_date"],
|
||||
inventory_profitability_for_item: ["item"],
|
||||
open_contracts_confirmed_as_of_date: ["as_of_date"],
|
||||
payables_confirmed_as_of_date: ["as_of_date"],
|
||||
receivables_confirmed_as_of_date: ["as_of_date"],
|
||||
|
|
@ -1215,6 +1229,26 @@ function deriveIntentWithFollowupContext(
|
|||
}
|
||||
}
|
||||
|
||||
if (inventorySelectedObjectFollowup && hasInventoryProfitabilityFollowupCue(normalizedMessage)) {
|
||||
if (
|
||||
detectedIntent.intent === "unknown" ||
|
||||
detectedIntent.intent === "customer_revenue_and_payments" ||
|
||||
detectedIntent.intent === "list_documents_by_counterparty" ||
|
||||
detectedIntent.intent === "list_documents_by_contract" ||
|
||||
detectedIntent.intent === "bank_operations_by_counterparty" ||
|
||||
detectedIntent.intent === "bank_operations_by_contract" ||
|
||||
detectedIntent.intent === "inventory_on_hand_as_of_date" ||
|
||||
detectedIntent.intent === "inventory_sale_trace_for_item" ||
|
||||
detectedIntent.intent === previousIntent
|
||||
) {
|
||||
return {
|
||||
intent: "inventory_profitability_for_item",
|
||||
confidence: "low",
|
||||
reasons: [...detectedIntent.reasons, "intent_adjusted_to_inventory_followup_context"]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (inventorySelectedObjectFollowup && hasInventoryPurchaseDateFollowupCue(normalizedMessage)) {
|
||||
if (
|
||||
detectedIntent.intent === "unknown" ||
|
||||
|
|
|
|||
|
|
@ -248,6 +248,7 @@ export function resolvePrimaryAnchor(intent: AddressIntent, filters: AddressFilt
|
|||
(intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date") &&
|
||||
item
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { runAssistantRoutePolicyRuntime } from "./assistantRoutePolicyRuntimeAdapter";
|
||||
|
||||
import { hasInventoryProfitabilityCue } from "./inventoryLifecycleCueHelpers";
|
||||
|
||||
export interface BuildAssistantAddressOrchestrationRuntimeInput {
|
||||
userMessage: string;
|
||||
sessionItems: unknown[];
|
||||
|
|
@ -67,14 +69,18 @@ function hasSelectedObjectInventorySignal(text: string | null): boolean {
|
|||
}
|
||||
|
||||
function hasSelectedObjectInventoryActionCue(text: string | null): boolean {
|
||||
return /(?:кому[\s\S]{0,80}(?:продал[аи]?|реализова[нлт][а-я]*|поставил[аи]?|поставлен[а-я]*|отгрузил[аи]?|отгружен[а-я]*)|кому\s+был\s+продан|куда[\s\S]{0,80}(?:продал[аи]?|реализова[нлт][а-я]*|поставил[аи]?|поставлен[а-я]*|отгрузил[аи]?|отгружен[а-я]*)|кто[\s\S]{0,40}купил|кто\s+это\s+поставил|кто\s+поставил|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+мы\s+купили|где\s+куплено|по\s+каким\s+документам|какими\s+документами|покажи\s+документы|документы[\s\S]{0,80}(?:по\s+(?:ним|ней|нему|этой\s+позиции|этому\s+товару)|операци)|документы\s+закупки|buyer|sale\s+trace|supplier|vendor|purchase\s+documents|purchase[\s-]?to[\s-]?sale|old\s+purchase|aged\s+stock)/iu.test(
|
||||
String(text ?? "")
|
||||
const value = String(text ?? "");
|
||||
return (
|
||||
/(?:кому[\s\S]{0,80}(?:продал[аи]?|реализова[нлт][а-я]*|поставил[аи]?|поставлен[а-я]*|отгрузил[аи]?|отгружен[а-я]*)|кому\s+был\s+продан|куда[\s\S]{0,80}(?:продал[аи]?|реализова[нлт][а-я]*|поставил[аи]?|поставлен[а-я]*|отгрузил[аи]?|отгружен[а-я]*)|кто[\s\S]{0,40}купил|кто\s+это\s+поставил|кто\s+поставил|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+мы\s+купили|где\s+куплено|по\s+каким\s+документам|какими\s+документами|покажи\s+документы|документы[\s\S]{0,80}(?:по\s+(?:ним|ней|нему|этой\s+позиции|этому\s+товару)|операци)|документы\s+закупки|buyer|sale\s+trace|supplier|vendor|purchase\s+documents|purchase[\s-]?to[\s-]?sale|old\s+purchase|aged\s+stock)/iu.test(
|
||||
value
|
||||
) || hasInventoryProfitabilityCue(value)
|
||||
);
|
||||
}
|
||||
|
||||
function isGenericCanonicalDriftIntent(intent: string | null): boolean {
|
||||
return (
|
||||
intent === "open_items_by_counterparty_or_contract" ||
|
||||
intent === "customer_revenue_and_payments" ||
|
||||
intent === "list_documents_by_counterparty" ||
|
||||
intent === "list_documents_by_contract" ||
|
||||
intent === "bank_operations_by_counterparty" ||
|
||||
|
|
|
|||
|
|
@ -2463,6 +2463,7 @@ function isInventoryDrilldownFrameIntent(intent) {
|
|||
return intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date";
|
||||
}
|
||||
|
|
@ -2739,6 +2740,7 @@ function isInventorySelectedObjectIntent(intent) {
|
|||
return intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain";
|
||||
}
|
||||
function hasShortInventoryObjectFollowupSignal(userMessage) {
|
||||
|
|
@ -3061,6 +3063,7 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
|
|||
sourceIntentHint === "inventory_purchase_provenance_for_item" ||
|
||||
sourceIntentHint === "inventory_purchase_documents_for_item" ||
|
||||
sourceIntentHint === "inventory_sale_trace_for_item" ||
|
||||
sourceIntentHint === "inventory_profitability_for_item" ||
|
||||
sourceIntentHint === "inventory_purchase_to_sale_chain" ||
|
||||
sourceIntentHint === "inventory_aging_by_purchase_date" ||
|
||||
hasSelectedObjectInventorySignalPrimary ||
|
||||
|
|
@ -3492,6 +3495,7 @@ function resolveRequiredAnchorTypeForIntent(intent) {
|
|||
if (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date") {
|
||||
return "item";
|
||||
|
|
@ -3544,7 +3548,9 @@ function hasSelectedObjectInventoryFollowupSignalForPredecompose(text) {
|
|||
return /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function isInventorySelectedObjectFollowupIntent(intent) {
|
||||
return intent === "inventory_purchase_provenance_for_item" || intent === "inventory_purchase_documents_for_item";
|
||||
return intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_profitability_for_item";
|
||||
}
|
||||
function hasSameDateAccountFollowupSignalForPredecompose(text) {
|
||||
const source = String(text ?? "");
|
||||
|
|
@ -4228,6 +4234,7 @@ const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
|||
"inventory_purchase_documents_for_item",
|
||||
"inventory_supplier_stock_overlap_as_of_date",
|
||||
"inventory_sale_trace_for_item",
|
||||
"inventory_profitability_for_item",
|
||||
"inventory_purchase_to_sale_chain",
|
||||
"inventory_aging_by_purchase_date",
|
||||
"contract_usage_overview",
|
||||
|
|
@ -4240,6 +4247,7 @@ const ADDRESS_INTENTS_ALLOW_STRICT_DEEP_INVESTIGATION_BYPASS = new Set([
|
|||
"inventory_purchase_provenance_for_item",
|
||||
"inventory_purchase_documents_for_item",
|
||||
"inventory_sale_trace_for_item",
|
||||
"inventory_profitability_for_item",
|
||||
"inventory_purchase_to_sale_chain"
|
||||
]);
|
||||
function shouldBypassStrictDeepInvestigationCueForAddressIntent(intent) {
|
||||
|
|
|
|||
|
|
@ -41,3 +41,23 @@ export function hasInventorySaleCue(text: string): boolean {
|
|||
value
|
||||
);
|
||||
}
|
||||
|
||||
export function hasInventoryProfitabilityCue(text: string): boolean {
|
||||
const value = toText(text);
|
||||
const hasExplicitEconomicsMetric =
|
||||
/(?:прибыл|марж|рентабел|наценк|выручк|доход|profit(?:ability)?|margin|revenue|unit\s+economics)/iu.test(value);
|
||||
if (hasExplicitEconomicsMetric) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
/(?:заработ(?:ал|али|аем|ок|ан)|прин[её]с(?:ли)?)/iu.test(value) &&
|
||||
/(?:денег|деньг|с\s+продаж[а-яё]*|по\s+продаж[а-яё]*|от\s+продаж[а-яё]*|продаж[а-яё]*|реализац|sale|sales)/iu.test(
|
||||
value
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return /(?:сколько|скока|скок)[\s\S]{0,60}(?:заработ|прин[её]с|денег[\s\S]{0,20}(?:с\s+продаж|по\s+продаж|от\s+продаж|продаж|реализац))/iu.test(
|
||||
value
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export type AddressIntent =
|
|||
| "inventory_purchase_documents_for_item"
|
||||
| "inventory_supplier_stock_overlap_as_of_date"
|
||||
| "inventory_sale_trace_for_item"
|
||||
| "inventory_profitability_for_item"
|
||||
| "inventory_purchase_to_sale_chain"
|
||||
| "inventory_aging_by_purchase_date"
|
||||
| "account_balance_snapshot"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { executeAddressMcpQueryMock } = vi.hoisted(() => ({
|
||||
executeAddressMcpQueryMock: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock("../src/services/addressMcpClient", async () => {
|
||||
const actual = await vi.importActual<typeof import("../src/services/addressMcpClient")>(
|
||||
"../src/services/addressMcpClient"
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
executeAddressMcpQuery: executeAddressMcpQueryMock
|
||||
};
|
||||
});
|
||||
|
||||
import { AddressQueryService } from "../src/services/addressQueryService";
|
||||
import { runAddressDecomposeStage } from "../src/services/address_runtime/decomposeStage";
|
||||
|
||||
afterEach(() => {
|
||||
executeAddressMcpQueryMock.mockReset();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("inventory profitability selected-object regressions", () => {
|
||||
const followupContext = {
|
||||
previous_intent: "inventory_on_hand_as_of_date" as const,
|
||||
previous_filters: {
|
||||
organization: "ООО \\Альтернатива Плюс\\",
|
||||
as_of_date: "2020-05-31",
|
||||
period_from: "2020-05-01",
|
||||
period_to: "2020-05-31"
|
||||
},
|
||||
previous_anchor_type: "unknown" as const,
|
||||
previous_anchor_value: null
|
||||
};
|
||||
|
||||
const selectedObjectProfitabilityMessage =
|
||||
'По выбранному объекту "Четки Пост (84*117)": а сколько денег мы заработали с продажжи этих четок';
|
||||
|
||||
it("routes selected-object profitability wording into an item profitability intent", () => {
|
||||
const result = runAddressDecomposeStage(selectedObjectProfitabilityMessage, followupContext);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("inventory_profitability_for_item");
|
||||
expect(result?.intent.intent).not.toBe("customer_revenue_and_payments");
|
||||
expect(result?.filters.extracted_filters.item).toBe("Четки Пост (84*117)");
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2020-05-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2020-05-31");
|
||||
});
|
||||
|
||||
it("returns a truthful recipe visibility gap until item profitability gets a dedicated recipe", async () => {
|
||||
const service = new AddressQueryService();
|
||||
const result = await service.tryHandle(selectedObjectProfitabilityMessage, {
|
||||
followupContext
|
||||
});
|
||||
|
||||
expect(result?.handled).toBe(true);
|
||||
expect(result?.response_type).toBe("LIMITED_WITH_REASON");
|
||||
expect(result?.debug.detected_intent).toBe("inventory_profitability_for_item");
|
||||
expect(result?.debug.selected_recipe).toBeNull();
|
||||
expect(result?.debug.limited_reason_category).toBe("recipe_visibility_gap");
|
||||
expect(result?.debug.extracted_filters?.item).toBe("Четки Пост (84*117)");
|
||||
expect(String(result?.reply_text ?? "")).toContain("Четки Пост (84*117)");
|
||||
expect(executeAddressMcpQueryMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -377,4 +377,68 @@ describe("assistant address orchestration runtime adapter", () => {
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers raw selected-object profitability wording over customer revenue canonical drift", async () => {
|
||||
const resolveAddressFollowupCarryoverContext = vi.fn(() => ({
|
||||
followupContext: {
|
||||
previous_intent: "inventory_on_hand_as_of_date",
|
||||
previous_filters: {
|
||||
as_of_date: "2020-05-31",
|
||||
period_from: "2020-05-01",
|
||||
period_to: "2020-05-31"
|
||||
}
|
||||
}
|
||||
}));
|
||||
const resolveAssistantOrchestrationDecision = vi.fn(() => ({
|
||||
runAddressLane: true,
|
||||
livingMode: "address_data",
|
||||
livingReason: "address_lane_triggered",
|
||||
toolGateDecision: "run_address_lane",
|
||||
toolGateReason: "address_mode_classifier_detected",
|
||||
orchestrationContract: { schema_version: "assistant_orchestration_contract_v1" }
|
||||
}));
|
||||
const buildAddressLlmPredecomposeContractV1 = vi.fn(({ sourceMessage, canonicalMessage }: { sourceMessage: string; canonicalMessage: string }) => ({
|
||||
schema_version: "address_llm_predecompose_contract_v1",
|
||||
source_message: sourceMessage,
|
||||
canonical_message: canonicalMessage,
|
||||
mode: "address_query",
|
||||
intent: "unknown"
|
||||
}));
|
||||
|
||||
const rawMessage =
|
||||
'По выбранному объекту "Четки Пост (84*117)": а сколько денег мы заработали с продажжи этих четок';
|
||||
|
||||
const output = await buildAssistantAddressOrchestrationRuntime(
|
||||
buildInput({
|
||||
userMessage: rawMessage,
|
||||
runAddressLlmPreDecompose: vi.fn(async () => ({
|
||||
attempted: true,
|
||||
applied: true,
|
||||
effectiveMessage: "Покажи выручку по контрагенту по выбранной позиции",
|
||||
reason: "normalized_fragment_applied",
|
||||
predecomposeContract: {
|
||||
mode: "address_query",
|
||||
intent: "customer_revenue_and_payments",
|
||||
semantics: {
|
||||
selected_object_scope_detected: true
|
||||
}
|
||||
}
|
||||
})),
|
||||
buildAddressLlmPredecomposeContractV1,
|
||||
resolveAddressFollowupCarryoverContext,
|
||||
resolveAssistantOrchestrationDecision
|
||||
})
|
||||
);
|
||||
|
||||
expect(output.addressInputMessage).toBe(rawMessage);
|
||||
expect(output.addressPreDecompose.applied).toBe(false);
|
||||
expect(output.addressPreDecompose.reason).toBe("followup_raw_message_preferred_over_llm_rewrite");
|
||||
expect(resolveAddressFollowupCarryoverContext).toHaveBeenCalledTimes(2);
|
||||
expect(resolveAssistantOrchestrationDecision).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
rawUserMessage: rawMessage,
|
||||
effectiveAddressUserMessage: rawMessage
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue