АРЧ - Склад: сохранять полный item-anchor в selected-object sale trace и закрепить buyer follow-up регрессиями
This commit is contained in:
@@ -3,6 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isLowQualityInventoryItemAnchorValue = isLowQualityInventoryItemAnchorValue;
|
||||
exports.isInventoryItemAnchorDegradation = isInventoryItemAnchorDegradation;
|
||||
exports.extractSelectedObjectQuotedValue = extractSelectedObjectQuotedValue;
|
||||
exports.extractAddressFilters = extractAddressFilters;
|
||||
const iconv_lite_1 = __importDefault(require("iconv-lite"));
|
||||
const ACCOUNT_PATTERN = /(?:сч[её]т|счет|account)[^0-9]{0,12}(\d{2}(?:[.,]\d{1,2})?)/i;
|
||||
@@ -836,16 +839,47 @@ function isLowQualityInventoryItemAnchorValue(rawValue) {
|
||||
return true;
|
||||
}
|
||||
const lowQualityTokens = new Set([
|
||||
"в",
|
||||
"во",
|
||||
"на",
|
||||
"по",
|
||||
"у",
|
||||
"от",
|
||||
"из",
|
||||
"для",
|
||||
"и",
|
||||
"или",
|
||||
"это",
|
||||
"этот",
|
||||
"эту",
|
||||
"его",
|
||||
"ее",
|
||||
"её",
|
||||
"итог",
|
||||
"итоге",
|
||||
"итогу",
|
||||
"сейчас",
|
||||
"лежат",
|
||||
"лежит",
|
||||
"лежали",
|
||||
"был",
|
||||
"была",
|
||||
"было",
|
||||
"были",
|
||||
"куплен",
|
||||
"куплена",
|
||||
"куплены",
|
||||
"куплено",
|
||||
"продан",
|
||||
"продана",
|
||||
"проданы",
|
||||
"продано",
|
||||
"продали",
|
||||
"реализован",
|
||||
"реализована",
|
||||
"реализованы",
|
||||
"реализовано",
|
||||
"реализовали",
|
||||
"документам",
|
||||
"документами",
|
||||
"документы",
|
||||
@@ -865,6 +899,37 @@ function isLowQualityInventoryItemAnchorValue(rawValue) {
|
||||
.filter((token) => !lowQualityTokens.has(token));
|
||||
return meaningfulTokens.length === 0;
|
||||
}
|
||||
function normalizeInventoryItemAnchorForComparison(rawValue) {
|
||||
return cleanupAnchorValue(rawValue)
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/С‘/g, "Рµ")
|
||||
.replace(/\s+/g, " ");
|
||||
}
|
||||
function tokenizeInventoryItemAnchorForComparison(rawValue) {
|
||||
return normalizeInventoryItemAnchorForComparison(rawValue)
|
||||
.split(/[^a-zа-я0-9]+/iu)
|
||||
.map((token) => token.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
function isInventoryItemAnchorDegradation(sourceValue, candidateValue) {
|
||||
const sourceNormalized = normalizeInventoryItemAnchorForComparison(sourceValue);
|
||||
const candidateNormalized = normalizeInventoryItemAnchorForComparison(candidateValue);
|
||||
if (!sourceNormalized || !candidateNormalized || sourceNormalized === candidateNormalized) {
|
||||
return false;
|
||||
}
|
||||
if (isLowQualityInventoryItemAnchorValue(candidateNormalized)) {
|
||||
return true;
|
||||
}
|
||||
if (sourceNormalized.includes(candidateNormalized) && candidateNormalized.length < sourceNormalized.length) {
|
||||
return true;
|
||||
}
|
||||
const sourceTokens = tokenizeInventoryItemAnchorForComparison(sourceNormalized);
|
||||
const candidateTokens = tokenizeInventoryItemAnchorForComparison(candidateNormalized);
|
||||
return (candidateTokens.length > 0 &&
|
||||
candidateTokens.length < sourceTokens.length &&
|
||||
candidateTokens.every((token) => sourceTokens.includes(token)));
|
||||
}
|
||||
function cleanupInventoryItemAnchorValue(value) {
|
||||
return String(value ?? "")
|
||||
.replace(/^['"«»“”„`’‘]+|['"«»“”„`’‘]+$/gu, "")
|
||||
@@ -886,6 +951,31 @@ function trimInventoryItemAnchorTail(rawValue) {
|
||||
}
|
||||
return cleanupInventoryItemAnchorValue(value);
|
||||
}
|
||||
function extractQuotedAnchorValue(text) {
|
||||
const patterns = [/[«"]([^«»"\r\n]+)[»"]/u, /'([^'\r\n]+)'/u];
|
||||
for (const pattern of patterns) {
|
||||
const match = String(text ?? "").match(pattern);
|
||||
const candidate = cleanupInventoryItemAnchorValue(String(match?.[1] ?? ""));
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
function extractUnicodeQuotedAnchorValue(text) {
|
||||
const patterns = [
|
||||
/(?:\u00AB|\u0412\u00AB|")([^\u00AB\u00BB"\r\n]+?)(?:\u00BB|\u0412\u00BB|")/u,
|
||||
/'([^'\r\n]+)'/u
|
||||
];
|
||||
for (const pattern of patterns) {
|
||||
const match = String(text ?? "").match(pattern);
|
||||
const candidate = cleanupInventoryItemAnchorValue(String(match?.[1] ?? ""));
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
function extractSelectedObjectQuotedValue(text) {
|
||||
const patterns = [
|
||||
/(?:по\s+выбранному\s+объекту|for\s+selected\s+object)\s*[«"]([^»"\r\n]+)[»"]/iu,
|
||||
@@ -898,7 +988,7 @@ function extractSelectedObjectQuotedValue(text) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
return extractUnicodeQuotedAnchorValue(text) ?? extractQuotedAnchorValue(text);
|
||||
}
|
||||
function extractInventoryItemFromSelectedObject(text) {
|
||||
const selectedObject = extractSelectedObjectQuotedValue(text);
|
||||
@@ -923,6 +1013,10 @@ function extractInventoryItemAnchor(text) {
|
||||
if (selectedObjectItem) {
|
||||
return selectedObjectItem;
|
||||
}
|
||||
const quotedItem = extractUnicodeQuotedAnchorValue(text) ?? extractQuotedAnchorValue(text);
|
||||
if (quotedItem && !isLowQualityInventoryItemAnchorValue(quotedItem)) {
|
||||
return quotedItem;
|
||||
}
|
||||
const patterns = [
|
||||
/(?:товар(?:а|у|ом|ы)?|номенклатур(?:а|у|ы)|позици(?:я|ю|и)|item|product|sku)\s*[«"']([^«»"'?\r\n]+)[»"'](?=$|[\s,.;:!?])/iu,
|
||||
/(?:товар(?:а|у|ом|ы)?|номенклатур(?:а|у|ы)|позици(?:я|ю|и)|item|product|sku)\s+([^\r\n,.;:!?]+?)(?=\s+(?:на|по|у|от|из|для|и|когда|через|сейчас|еще|ещё|котор|которые|который|покупателю|поставщика|поставщику|за|в)\b|[:?]|$)/iu
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.resolveAddressIntent = resolveAddressIntent;
|
||||
const inventoryLifecycleCueHelpers_1 = require("./inventoryLifecycleCueHelpers");
|
||||
const RECEIVABLES_STRONG = [
|
||||
"кто должен нам",
|
||||
"кто нам должен",
|
||||
@@ -1345,21 +1346,19 @@ function hasSelectedObjectInventoryCue(text) {
|
||||
return /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+нему|по\s+ней|по\s+нему\s+же|по\s+ней\s+же|selected\s+object)/iu.test(text);
|
||||
}
|
||||
function hasSelectedObjectInventoryProvenanceSignal(text) {
|
||||
return (hasSelectedObjectInventoryCue(text) &&
|
||||
/(?:кто\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+куплено|supplier|vendor|поставщик)/iu.test(text));
|
||||
return hasSelectedObjectInventoryCue(text) && (0, inventoryLifecycleCueHelpers_1.hasInventorySupplierCue)(text);
|
||||
}
|
||||
function hasSelectedObjectInventoryPurchaseDocumentsSignal(text) {
|
||||
return (hasSelectedObjectInventoryCue(text) &&
|
||||
/(?:по\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(text));
|
||||
}
|
||||
function hasSelectedObjectInventorySaleTraceSignal(text) {
|
||||
return (hasSelectedObjectInventoryCue(text) &&
|
||||
/(?:кому\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали|кому\s+был\s+продан|куда\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали(?:\s+(?:это|его|этот\s+товар|эту\s+позицию))?|куда\s+(?:была\s+)?реализована\s+(?:позиция|номенклатура|продукция)|кто\s+купил|buyer|sale\s+trace|trace\s+of\s+sale)/iu.test(text));
|
||||
return hasSelectedObjectInventoryCue(text) && (0, inventoryLifecycleCueHelpers_1.hasInventorySaleCue)(text);
|
||||
}
|
||||
function hasInventoryProvenanceSignalV2(text) {
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product|остат(?:ок|ки)|склад)/iu.test(text);
|
||||
const hasSupplierCue = /(?:от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|товар|позицию))?|где\s+куплено|кто\s+(?:нам\s+)?поставил|кем\s+поставлен|поставщик|supplier|vendor)/iu.test(text);
|
||||
const hasPurchaseCue = /(?:куплен(?:ы|а|о)?|закупк|происхождени|откуда|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|товар|позицию))?|где\s+куплено|когда\s+был\s+куплен|когда\s+куплен|дата\s+закупк|кто\s+(?:нам\s+)?поставил|кем\s+поставлен|поставлен(?:ы|а)?|purchase\s+provenance|purchase\s+date)/iu.test(text);
|
||||
const hasSupplierCue = (0, inventoryLifecycleCueHelpers_1.hasInventorySupplierCue)(text) || /кем\s+поставлен/iu.test(text);
|
||||
const hasPurchaseCue = /(?:куплен(?:ы|а|о)?|закупк|происхождени|откуда|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|товар|позицию))?|где\s+куплено|когда\s+был\s+куплен|когда\s+куплен|дата\s+закупк|кто\s+(?:нам\s+)?поставил|кем\s+поставлен|поставлен(?:ы|а)?|purchase\s+provenance|purchase\s+date)/iu.test(text) || (0, inventoryLifecycleCueHelpers_1.hasInventoryPurchaseStem)(text);
|
||||
return hasItemCue && hasSupplierCue && hasPurchaseCue;
|
||||
}
|
||||
function hasInventoryPurchaseDateSignal(text) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.detectAddressQuestionMode = detectAddressQuestionMode;
|
||||
const inventoryLifecycleCueHelpers_1 = require("./inventoryLifecycleCueHelpers");
|
||||
const ADDRESS_ACTION_TOKENS = [
|
||||
"show",
|
||||
"list",
|
||||
@@ -277,7 +278,10 @@ function hasSelectedObjectInventoryFollowupSignal(text) {
|
||||
if (!/(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции)/iu.test(text)) {
|
||||
return false;
|
||||
}
|
||||
return /(?:у\s+кого\s+купили|у\s+кого\s+куплено|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|товар|позицию))?|где\s+куплено|кто\s+(?:поставил|продал)|кому\s+(?:продали|реализовали)|когда\s+(?:примерно\s+)?купили|по\s+каким\s+документам\s+.*купили)/iu.test(text);
|
||||
return ((0, inventoryLifecycleCueHelpers_1.hasInventorySupplierCue)(text) ||
|
||||
(0, inventoryLifecycleCueHelpers_1.hasInventorySaleCue)(text) ||
|
||||
/(?:кто\s+(?:поставил|продал)|по\s+каким\s+документам\s+.*купили)/iu.test(text) ||
|
||||
(/\bкогда\b/iu.test(text) && (0, inventoryLifecycleCueHelpers_1.hasInventoryPurchaseStem)(text)));
|
||||
}
|
||||
function hasDocsOrBankSignal(text) {
|
||||
return /(?:док(?:и|умент|ументы|ументов)|docs?|documents?|банк|выписк|платеж|платёж|оплат|поступлен|списан|транзак|transactions?|bank\s+ops|bank\s+operations?)/iu.test(text);
|
||||
|
||||
@@ -1611,15 +1611,11 @@ function shouldBoostAutoBroadenedLimit(intent) {
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
function shouldClearAsOfDateForHistoryRecovery(intent) {
|
||||
return (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
return (intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain");
|
||||
}
|
||||
function shouldDetachLifecycleExecutionFromSnapshotContext(intent, reasons) {
|
||||
if (intent !== "inventory_purchase_provenance_for_item" &&
|
||||
intent !== "inventory_purchase_documents_for_item" &&
|
||||
intent !== "inventory_sale_trace_for_item" &&
|
||||
if (intent !== "inventory_sale_trace_for_item" &&
|
||||
intent !== "inventory_purchase_to_sale_chain") {
|
||||
return false;
|
||||
}
|
||||
|
||||
+71
-14
@@ -42,6 +42,42 @@ __WHERE_CLAUSE__
|
||||
УПОРЯДОЧИТЬ ПО
|
||||
Движения.Период __ORDER_DIRECTION__
|
||||
`;
|
||||
const INVENTORY_SALE_DOCUMENTS_QUERY_TEMPLATE = `
|
||||
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||
Товары.Ссылка.Дата КАК Период,
|
||||
ПРЕДСТАВЛЕНИЕ(Товары.Ссылка) КАК Регистратор,
|
||||
"" КАК СчетДт,
|
||||
"41.01" КАК СчетКт,
|
||||
Товары.Сумма КАК Сумма,
|
||||
ПРЕДСТАВЛЕНИЕ(Товары.Номенклатура) КАК Номенклатура,
|
||||
ПРЕДСТАВЛЕНИЕ(Товары.Ссылка.Контрагент) КАК Контрагент,
|
||||
ПРЕДСТАВЛЕНИЕ(Товары.Ссылка.ДоговорКонтрагента) КАК Договор,
|
||||
ПРЕДСТАВЛЕНИЕ(Товары.Ссылка.Организация) КАК Организация,
|
||||
Товары.Количество КАК Количество
|
||||
ИЗ
|
||||
Документ.РеализацияТоваровУслуг.Товары КАК Товары
|
||||
__WHERE_CLAUSE__
|
||||
УПОРЯДОЧИТЬ ПО
|
||||
Товары.Ссылка.Дата __ORDER_DIRECTION__
|
||||
`;
|
||||
const INVENTORY_PURCHASE_DOCUMENTS_QUERY_TEMPLATE = `
|
||||
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||
Товары.Ссылка.Дата КАК Период,
|
||||
ПРЕДСТАВЛЕНИЕ(Товары.Ссылка) КАК Регистратор,
|
||||
"41.01" КАК СчетДт,
|
||||
"" КАК СчетКт,
|
||||
Товары.Сумма КАК Сумма,
|
||||
ПРЕДСТАВЛЕНИЕ(Товары.Номенклатура) КАК Номенклатура,
|
||||
ПРЕДСТАВЛЕНИЕ(Товары.Ссылка.Контрагент) КАК Контрагент,
|
||||
ПРЕДСТАВЛЕНИЕ(Товары.Ссылка.ДоговорКонтрагента) КАК Договор,
|
||||
ПРЕДСТАВЛЕНИЕ(Товары.Ссылка.Организация) КАК Организация,
|
||||
Товары.Количество КАК Количество
|
||||
ИЗ
|
||||
Документ.ПоступлениеТоваровУслуг.Товары КАК Товары
|
||||
__WHERE_CLAUSE__
|
||||
УПОРЯДОЧИТЬ ПО
|
||||
Товары.Ссылка.Дата __ORDER_DIRECTION__
|
||||
`;
|
||||
const PAYABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE = `
|
||||
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||
__AS_OF_EXPR__ КАК Период,
|
||||
@@ -938,15 +974,6 @@ function toDateTimeExpr(isoDate, endOfDay) {
|
||||
function toQueryStringLiteral(value) {
|
||||
return String(value ?? "").replace(/"/g, '""');
|
||||
}
|
||||
function buildOrganizationPresentationCondition(filters, fieldPath) {
|
||||
const organization = typeof filters.organization === "string" && filters.organization.trim().length > 0
|
||||
? filters.organization.trim()
|
||||
: "";
|
||||
if (!organization) {
|
||||
return null;
|
||||
}
|
||||
return `ПРЕДСТАВЛЕНИЕ(${fieldPath}) = "${toQueryStringLiteral(organization)}"`;
|
||||
}
|
||||
function buildWhereClause(filters, fieldPath, extraConditions = []) {
|
||||
const periodFromExpr = typeof filters.period_from === "string" && filters.period_from.trim().length > 0
|
||||
? toDateTimeExpr(filters.period_from, false)
|
||||
@@ -1087,10 +1114,40 @@ function buildInventoryMovementQuery(filters, resolvedLimit, side) {
|
||||
: side === "kt"
|
||||
? creditPredicate
|
||||
: `(${debitPredicate} ИЛИ ${creditPredicate})`;
|
||||
const organizationCondition = buildOrganizationPresentationCondition(filters, "Движения.Организация");
|
||||
return INVENTORY_MOVEMENTS_QUERY_TEMPLATE
|
||||
.replace("__LIMIT__", String(resolvedLimit))
|
||||
.replace("__WHERE_CLAUSE__", buildWhereClause(filters, "Движения.Период", [inventoryCondition, organizationCondition].filter((item) => Boolean(item))))
|
||||
.replace("__WHERE_CLAUSE__", buildWhereClause(filters, "Движения.Период", [inventoryCondition]))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
}
|
||||
function buildInventoryItemReferenceCondition(filters, fieldPaths) {
|
||||
const item = typeof filters.item === "string" ? filters.item.trim() : "";
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
const escapedItem = toQueryStringLiteral(item);
|
||||
const referenceSubquery = `(ВЫБРАТЬ Номенклатура.Ссылка ИЗ Справочник.Номенклатура КАК Номенклатура ` +
|
||||
`ГДЕ Номенклатура.Наименование = "${escapedItem}")`;
|
||||
const clauses = fieldPaths
|
||||
.map((fieldPath) => String(fieldPath ?? "").trim())
|
||||
.filter((fieldPath) => fieldPath.length > 0)
|
||||
.map((fieldPath) => `${fieldPath} В ${referenceSubquery}`);
|
||||
if (clauses.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return clauses.length === 1 ? clauses[0] : `(${clauses.join(" ИЛИ ")})`;
|
||||
}
|
||||
function buildInventorySaleDocumentQuery(filters, resolvedLimit) {
|
||||
const itemCondition = buildInventoryItemReferenceCondition(filters, ["Товары.Номенклатура"]);
|
||||
return INVENTORY_SALE_DOCUMENTS_QUERY_TEMPLATE
|
||||
.replace("__LIMIT__", String(resolvedLimit))
|
||||
.replace("__WHERE_CLAUSE__", buildWhereClause(filters, "Товары.Ссылка.Дата", ['Товары.Ссылка.Проведен = ИСТИНА', itemCondition].filter((item) => Boolean(item))))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
}
|
||||
function buildInventoryPurchaseDocumentQuery(filters, resolvedLimit) {
|
||||
const itemCondition = buildInventoryItemReferenceCondition(filters, ["Товары.Номенклатура"]);
|
||||
return INVENTORY_PURCHASE_DOCUMENTS_QUERY_TEMPLATE
|
||||
.replace("__LIMIT__", String(resolvedLimit))
|
||||
.replace("__WHERE_CLAUSE__", buildWhereClause(filters, "Товары.Ссылка.Дата", ['Товары.Ссылка.Проведен = ИСТИНА', itemCondition].filter((item) => Boolean(item))))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
}
|
||||
function shouldBoostLimitForAllTimeCounterparty(filters) {
|
||||
@@ -1269,13 +1326,13 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
})()
|
||||
: recipe.query_template === "inventory_purchase_provenance_profile"
|
||||
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
|
||||
? buildInventoryPurchaseDocumentQuery(filters, resolvedLimit)
|
||||
: recipe.query_template === "inventory_purchase_documents_profile"
|
||||
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
|
||||
? buildInventoryPurchaseDocumentQuery(filters, resolvedLimit)
|
||||
: recipe.query_template === "inventory_supplier_stock_overlap_profile"
|
||||
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
|
||||
: recipe.query_template === "inventory_sale_trace_profile"
|
||||
? buildInventoryMovementQuery(filters, resolvedLimit, "kt")
|
||||
? buildInventorySaleDocumentQuery(filters, resolvedLimit)
|
||||
: recipe.query_template === "inventory_purchase_to_sale_chain_profile"
|
||||
? buildInventoryMovementQuery(filters, resolvedLimit, "either")
|
||||
: recipe.query_template === "inventory_aging_by_purchase_date_profile"
|
||||
|
||||
@@ -3140,6 +3140,7 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
const purchaseRows = rows.filter((row) => isInventoryPurchaseMovement(row));
|
||||
const summary = summarizeInventoryTraceRows(purchaseRows);
|
||||
const itemLabel = summary.item ?? "товар не определен";
|
||||
const boundedAsOfLabel = asOfDate ? formatDateRu(asOfDate) : null;
|
||||
const purchaseDateActionFocus = hasInventoryPurchaseDateActionFocus(options.userMessage);
|
||||
if (purchaseDateActionFocus) {
|
||||
const firstPurchaseDate = inventoryTraceDateLabel(summary.firstPeriod);
|
||||
@@ -3148,7 +3149,9 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
? `По позиции ${itemLabel} подтвержденная дата закупки в доступном контуре не найдена.`
|
||||
: summary.firstPeriod === summary.lastPeriod
|
||||
? `Позиция ${itemLabel} куплена ${firstPurchaseDate}.`
|
||||
: `По позиции ${itemLabel} подтвержденный диапазон закупок: с ${firstPurchaseDate} по ${lastPurchaseDate}.`;
|
||||
: boundedAsOfLabel
|
||||
? `По позиции ${itemLabel} до ${boundedAsOfLabel} подтвержден диапазон закупок: с ${firstPurchaseDate} по ${lastPurchaseDate}.`
|
||||
: `По позиции ${itemLabel} подтвержденный диапазон закупок: с ${firstPurchaseDate} по ${lastPurchaseDate}.`;
|
||||
const lines = [directAnswerLine];
|
||||
if (purchaseRows.length > 0) {
|
||||
lines.push("", "Подтверждение:");
|
||||
@@ -3165,8 +3168,11 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
if (summary.documents.length > 0) {
|
||||
lines.push(`- Первый подтверждающий документ: ${summary.documents[0]}.`);
|
||||
}
|
||||
if (summary.firstPeriod && asOfDate && summary.firstPeriod < asOfDate) {
|
||||
lines.push(`- Дата вопроса по остатку: ${formatDateRu(asOfDate)}; дата закупки показана по подтвержденному закупочному следу.`);
|
||||
if (boundedAsOfLabel) {
|
||||
lines.push(`- Для ответа учтены закупочные документы не позже ${boundedAsOfLabel}.`);
|
||||
}
|
||||
if (summary.counterparties.length > 1) {
|
||||
lines.push(`- Без партионного учета нельзя однозначно связать остаток${boundedAsOfLabel ? ` на ${boundedAsOfLabel}` : ""} с одним конкретным поступлением.`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -3179,33 +3185,51 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||
}
|
||||
};
|
||||
}
|
||||
const directAnswerLine = summary.counterparties.length === 1
|
||||
? `Товар ${itemLabel} по доступным закупочным движениям связан с поставщиком: ${summary.counterparties[0]}.`
|
||||
: summary.counterparties.length > 1
|
||||
? `По доступным закупочным движениям по товару ${itemLabel} найдено несколько поставщиков: ${summary.counterparties.slice(0, 4).join("; ")}.`
|
||||
: `По товару ${itemLabel} найден закупочный след, но поставщик не материализован отдельным полем в текущем exact-контуре.`;
|
||||
const directAnswerLine = purchaseRows.length <= 0
|
||||
? boundedAsOfLabel
|
||||
? `По позиции ${itemLabel} подтвержденные закупочные документы до ${boundedAsOfLabel} не найдены.`
|
||||
: `По позиции ${itemLabel} подтвержденные закупочные документы не найдены.`
|
||||
: summary.counterparties.length === 1
|
||||
? boundedAsOfLabel
|
||||
? `По позиции ${itemLabel} до ${boundedAsOfLabel} подтвержден поставщик: ${summary.counterparties[0]}.`
|
||||
: `По позиции ${itemLabel} подтвержден поставщик: ${summary.counterparties[0]}.`
|
||||
: summary.counterparties.length > 1
|
||||
? boundedAsOfLabel
|
||||
? `По позиции ${itemLabel} до ${boundedAsOfLabel} однозначный поставщик не подтвержден: в документах встречаются ${summary.counterparties.slice(0, 4).join("; ")}.`
|
||||
: `По позиции ${itemLabel} однозначный поставщик не подтвержден: в документах встречаются ${summary.counterparties.slice(0, 4).join("; ")}.`
|
||||
: boundedAsOfLabel
|
||||
? `По позиции ${itemLabel} до ${boundedAsOfLabel} закупочные документы найдены, но поставщик в них не выделен отдельным полем.`
|
||||
: `По позиции ${itemLabel} закупочные документы найдены, но поставщик в них не выделен отдельным полем.`;
|
||||
const lines = [directAnswerLine, "", "Подтверждение:"];
|
||||
lines.push(`- Первая найденная дата закупки: ${inventoryTraceDateLabel(summary.firstPeriod)}.`);
|
||||
lines.push(`- Последняя найденная дата закупки: ${inventoryTraceDateLabel(summary.lastPeriod)}.`);
|
||||
lines.push(`- Документов поступления: ${formatNumberWithDots(summary.documents.length)}.`);
|
||||
lines.push(`- Операций поступления: ${formatNumberWithDots(purchaseRows.length)}.`);
|
||||
if (summary.counterparties.length === 1) {
|
||||
lines.push(`- По доступным закупочным движениям товар связан с поставщиком: ${summary.counterparties[0]}.`);
|
||||
if (purchaseRows.length > 0) {
|
||||
lines.push(`- Первая найденная дата закупки: ${inventoryTraceDateLabel(summary.firstPeriod)}.`);
|
||||
lines.push(`- Последняя найденная дата закупки: ${inventoryTraceDateLabel(summary.lastPeriod)}.`);
|
||||
lines.push(`- Документов поступления: ${formatNumberWithDots(summary.documents.length)}.`);
|
||||
lines.push(`- Операций поступления: ${formatNumberWithDots(purchaseRows.length)}.`);
|
||||
if (summary.counterparties.length === 1) {
|
||||
lines.push(`- Поставщик в найденных документах: ${summary.counterparties[0]}.`);
|
||||
}
|
||||
else if (summary.counterparties.length > 1) {
|
||||
lines.push(`- Поставщики в найденных документах: ${summary.counterparties.slice(0, 6).join("; ")}.`);
|
||||
}
|
||||
else {
|
||||
lines.push("- Закупочные документы найдены, но поставщик в них не выделен отдельным полем.");
|
||||
}
|
||||
if (boundedAsOfLabel) {
|
||||
lines.push(`- Для ответа учтены закупочные документы не позже ${boundedAsOfLabel}.`);
|
||||
}
|
||||
if (summary.counterparties.length > 1) {
|
||||
lines.push(`- Без партионного учета нельзя однозначно связать остаток${boundedAsOfLabel ? ` на ${boundedAsOfLabel}` : ""} с одним конкретным поступлением.`);
|
||||
}
|
||||
}
|
||||
else if (summary.counterparties.length > 1) {
|
||||
lines.push(`- По доступным закупочным движениям найдено несколько поставщиков: ${summary.counterparties.slice(0, 4).join("; ")}.`);
|
||||
}
|
||||
else if (purchaseRows.length > 0) {
|
||||
lines.push("- Закупочные документы найдены, но поставщик не материализован отдельным полем в текущем exact-контуре.");
|
||||
else if (boundedAsOfLabel) {
|
||||
lines.push(`- Для ответа проверены закупочные документы не позже ${boundedAsOfLabel}.`);
|
||||
}
|
||||
if (summary.documents.length > 0) {
|
||||
lines.push("", "Опорные документы:", ...formatInventoryTraceRows(purchaseRows, 8));
|
||||
}
|
||||
if (purchaseRows.length > 0) {
|
||||
lines.push("", "Сервисно:", "- Без партионности этот контур показывает документально наблюдаемый закупочный след, а не лот-level происхождение текущего остатка.");
|
||||
}
|
||||
return {
|
||||
responseType: purchaseRows.length > 0 ? "FACTUAL_SUMMARY" : "FACTUAL_SUMMARY",
|
||||
responseType: "FACTUAL_SUMMARY",
|
||||
text: joinLines(lines),
|
||||
semantics: {
|
||||
result_mode: "confirmed_balance",
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.hasInventorySupplierFollowupCue = hasInventorySupplierFollowupCue;
|
||||
exports.hasInventoryPurchaseDocumentsFollowupCue = hasInventoryPurchaseDocumentsFollowupCue;
|
||||
exports.hasInventoryPurchaseDateFollowupCue = hasInventoryPurchaseDateFollowupCue;
|
||||
exports.hasBareInventoryPurchaseDateFollowupCue = hasBareInventoryPurchaseDateFollowupCue;
|
||||
exports.hasInventorySaleFollowupCue = hasInventorySaleFollowupCue;
|
||||
exports.hasInventoryPurchaseToSaleChainFollowupCue = hasInventoryPurchaseToSaleChainFollowupCue;
|
||||
exports.hasAddressFollowupContextSignal = hasAddressFollowupContextSignal;
|
||||
exports.runAddressDecomposeStage = runAddressDecomposeStage;
|
||||
const addressQueryClassifier_1 = require("../addressQueryClassifier");
|
||||
const addressQueryShapeClassifier_1 = require("../addressQueryShapeClassifier");
|
||||
const addressIntentResolver_1 = require("../addressIntentResolver");
|
||||
const addressFilterExtractor_1 = require("../addressFilterExtractor");
|
||||
const inventoryLifecycleCueHelpers_1 = require("../inventoryLifecycleCueHelpers");
|
||||
const semanticHintOverlay_1 = require("./semanticHintOverlay");
|
||||
function hasExplicitPeriodWindow(filters) {
|
||||
return ((typeof filters.period_from === "string" && filters.period_from.trim().length > 0) ||
|
||||
@@ -402,13 +409,14 @@ function hasSelectedObjectInventorySignal(text) {
|
||||
return /(?:по\s+выбранному\s+объекту|for\s+selected\s+object)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function hasInventorySupplierFollowupCue(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+куплено|supplier|vendor|поставщик)/iu.test(String(text ?? ""));
|
||||
return (0, inventoryLifecycleCueHelpers_1.hasInventorySupplierCue)(String(text ?? ""));
|
||||
}
|
||||
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(String(text ?? ""));
|
||||
}
|
||||
function hasInventoryPurchaseDateFollowupCue(text) {
|
||||
return /(?:когда\s+(?:примерно\s+)?(?:мы\s+)?купили|когда\s+был\s+куплен|когда\s+куплен|когда\s+это\s+купили|когда\s+эту\s+позицию\s+купили|когда\s+ее\s+купили|дата\s+закупки|purchase\s+date)/iu.test(String(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));
|
||||
}
|
||||
function hasBareInventoryPurchaseDateFollowupCue(text) {
|
||||
const normalized = String(text ?? "").trim().toLowerCase();
|
||||
@@ -418,7 +426,7 @@ function hasBareInventoryPurchaseDateFollowupCue(text) {
|
||||
return /(?:^|\s)(?:когда|а\s+когда|ну\s+когда)(?=$|[\s,.;:!?])/iu.test(normalized) && normalized.split(/\s+/).filter(Boolean).length <= 3;
|
||||
}
|
||||
function hasInventorySaleFollowupCue(text) {
|
||||
return /(?:кому\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали|кому\s+был\s+продан|кому\s+дальше\s+продали|куда\s+(?:в\s+итоге\s+)?(?:мы\s+)?продали|куда\s+ушла\s+позиция|куда\s+ушел\s+товар|кто\s+купил|buyer|покупател)/iu.test(String(text ?? ""));
|
||||
return (0, inventoryLifecycleCueHelpers_1.hasInventorySaleCue)(String(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 ?? ""));
|
||||
@@ -608,12 +616,34 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date") &&
|
||||
!toNonEmptyString(merged.item)) {
|
||||
intent === "inventory_aging_by_purchase_date")) {
|
||||
const inheritedItem = previousItem ?? previousAnchorItem;
|
||||
if (inheritedItem && intent !== "inventory_aging_by_purchase_date") {
|
||||
const explicitQuotedItem = toNonEmptyString((0, addressFilterExtractor_1.extractSelectedObjectQuotedValue)(userMessage));
|
||||
const currentItem = toNonEmptyString(merged.item);
|
||||
const shouldAdoptExplicitQuotedItem = Boolean(explicitQuotedItem) &&
|
||||
(!currentItem ||
|
||||
currentItem !== explicitQuotedItem ||
|
||||
(0, addressFilterExtractor_1.isInventoryItemAnchorDegradation)(explicitQuotedItem ?? "", currentItem ?? ""));
|
||||
if (explicitQuotedItem && shouldAdoptExplicitQuotedItem) {
|
||||
merged.item = explicitQuotedItem;
|
||||
reasons.push(currentItem ? "item_replaced_from_explicit_quote" : "item_from_explicit_quote");
|
||||
}
|
||||
const effectiveCurrentItem = toNonEmptyString(merged.item);
|
||||
const hasExplicitDifferentQuotedItem = Boolean(explicitQuotedItem) &&
|
||||
Boolean(inheritedItem) &&
|
||||
explicitQuotedItem !== inheritedItem;
|
||||
const shouldInheritItem = Boolean(inheritedItem) &&
|
||||
intent !== "inventory_aging_by_purchase_date" &&
|
||||
!hasExplicitDifferentQuotedItem &&
|
||||
(!effectiveCurrentItem ||
|
||||
((0, addressFilterExtractor_1.isLowQualityInventoryItemAnchorValue)(effectiveCurrentItem) &&
|
||||
!(0, addressFilterExtractor_1.isLowQualityInventoryItemAnchorValue)(inheritedItem ?? "")) ||
|
||||
(effectiveCurrentItem &&
|
||||
inheritedItem &&
|
||||
(0, addressFilterExtractor_1.isInventoryItemAnchorDegradation)(inheritedItem, effectiveCurrentItem)));
|
||||
if (shouldInheritItem && inheritedItem) {
|
||||
merged.item = inheritedItem;
|
||||
reasons.push("item_from_followup_context");
|
||||
reasons.push(effectiveCurrentItem ? "item_replaced_from_followup_context" : "item_from_followup_context");
|
||||
}
|
||||
}
|
||||
if (sameDateRequested) {
|
||||
|
||||
+35
-5
@@ -2,6 +2,7 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.normalizeAddressLlmSemanticHints = normalizeAddressLlmSemanticHints;
|
||||
exports.applyAddressLlmSemanticHintsToExtraction = applyAddressLlmSemanticHintsToExtraction;
|
||||
const addressFilterExtractor_1 = require("../addressFilterExtractor");
|
||||
function toNonEmptyString(value) {
|
||||
if (value === null || value === undefined) {
|
||||
return null;
|
||||
@@ -66,6 +67,29 @@ function applyDateScopeHint(frame, dateScopeKind) {
|
||||
frame.date_basis_hint = "implicit_current_snapshot";
|
||||
}
|
||||
}
|
||||
function normalizeInventoryItemAnchorValue(value) {
|
||||
return String(value ?? "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, " ");
|
||||
}
|
||||
function shouldApplyInventoryItemSemanticHint(currentItemValue, hintedItemValue) {
|
||||
if (!hintedItemValue || (0, addressFilterExtractor_1.isLowQualityInventoryItemAnchorValue)(hintedItemValue)) {
|
||||
return false;
|
||||
}
|
||||
if (!currentItemValue || (0, addressFilterExtractor_1.isLowQualityInventoryItemAnchorValue)(currentItemValue)) {
|
||||
return true;
|
||||
}
|
||||
if ((0, addressFilterExtractor_1.isInventoryItemAnchorDegradation)(currentItemValue, hintedItemValue)) {
|
||||
return false;
|
||||
}
|
||||
const currentNormalized = normalizeInventoryItemAnchorValue(currentItemValue);
|
||||
const hintedNormalized = normalizeInventoryItemAnchorValue(hintedItemValue);
|
||||
if (currentNormalized === hintedNormalized) {
|
||||
return false;
|
||||
}
|
||||
return hintedNormalized.includes(currentNormalized);
|
||||
}
|
||||
function applyAddressLlmSemanticHintsToExtraction(extraction, semanticHintsInput) {
|
||||
const semanticHints = normalizeAddressLlmSemanticHints(semanticHintsInput);
|
||||
if (!semanticHints) {
|
||||
@@ -123,11 +147,17 @@ function applyAddressLlmSemanticHintsToExtraction(extraction, semanticHintsInput
|
||||
semanticFrame.anchor_value = scopeTargetText;
|
||||
}
|
||||
if (semanticHints.scope_target_kind === "item" && scopeTargetText) {
|
||||
extractedFilters.item = scopeTargetText;
|
||||
pushWarning(warnings, "item_from_llm_semantics");
|
||||
semanticFrame.scope_kind = "explicit_anchor";
|
||||
semanticFrame.anchor_kind = "item";
|
||||
semanticFrame.anchor_value = scopeTargetText;
|
||||
const currentItemValue = toNonEmptyString(extractedFilters.item);
|
||||
if (shouldApplyInventoryItemSemanticHint(currentItemValue, scopeTargetText)) {
|
||||
extractedFilters.item = scopeTargetText;
|
||||
pushWarning(warnings, "item_from_llm_semantics");
|
||||
semanticFrame.scope_kind = "explicit_anchor";
|
||||
semanticFrame.anchor_kind = "item";
|
||||
semanticFrame.anchor_value = scopeTargetText;
|
||||
}
|
||||
else if (currentItemValue && currentItemValue !== scopeTargetText) {
|
||||
pushWarning(warnings, "item_llm_semantics_ignored");
|
||||
}
|
||||
}
|
||||
return {
|
||||
...extraction,
|
||||
|
||||
+49
-3
@@ -57,6 +57,7 @@ const addressQueryService_1 = __importStar(require("./addressQueryService"));
|
||||
const addressQueryClassifier_1 = __importStar(require("./addressQueryClassifier"));
|
||||
const addressIntentResolver_1 = __importStar(require("./addressIntentResolver"));
|
||||
const addressFilterExtractor_1 = __importStar(require("./addressFilterExtractor"));
|
||||
const decomposeStage_1 = __importStar(require("./address_runtime/decomposeStage"));
|
||||
const predecomposeContract_1 = __importStar(require("./address_runtime/predecomposeContract"));
|
||||
const openaiResponsesClient_1 = __importStar(require("./openaiResponsesClient"));
|
||||
const addressMcpClient_1 = __importStar(require("./addressMcpClient"));
|
||||
@@ -2767,8 +2768,15 @@ function hasShortInventoryObjectFollowupSignal(userMessage) {
|
||||
if (minTokens > 8) {
|
||||
return false;
|
||||
}
|
||||
const hasDirectSaleFollowupCue = (sample) => /(?:кому|каму|куда)(?:\s+\S+){0,4}\s+(?:продали|продано|продан(?:о|а|ы)?|реализовали|реализован(?:о|а|ы)?)|(?:продали|продано|реализовали|реализован(?:о|а|ы)?)(?:\s+\S+){0,4}\s+(?:кому|каму|куда)|(?:^|\s)(?:продано|продали|реализовано|реализовали)(?=$|[\s,.;:!?])/iu.test(sample);
|
||||
return samples.some((sample) => /^(?:кто|когда|документы|сумма|поставщик|покупатель)(?:\?)?$/iu.test(sample) ||
|
||||
/^(?:когда\s+(?:примерно\s+)?купили(?:\s+ее)?|каким\s+документом|покажи\s+документы|по\s+каким\s+документам|все\s+закупки|все\s+поступления|кому\s+(?:мы\s+)?продали|кто\s+купил|цепочка|путь\s+товара)(?:\?)?$/iu.test(sample));
|
||||
hasDirectSaleFollowupCue(sample) ||
|
||||
(0, decomposeStage_1.hasInventorySupplierFollowupCue)(sample) ||
|
||||
(0, decomposeStage_1.hasInventoryPurchaseDocumentsFollowupCue)(sample) ||
|
||||
(0, decomposeStage_1.hasInventoryPurchaseDateFollowupCue)(sample) ||
|
||||
(0, decomposeStage_1.hasBareInventoryPurchaseDateFollowupCue)(sample) ||
|
||||
(0, decomposeStage_1.hasInventorySaleFollowupCue)(sample) ||
|
||||
(0, decomposeStage_1.hasInventoryPurchaseToSaleChainFollowupCue)(sample));
|
||||
}
|
||||
function resolveDebtRoleSwapFollowupIntent(userMessage, previousIntent) {
|
||||
const normalized = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
||||
@@ -3408,6 +3416,13 @@ function resolveRequiredAnchorTypeForIntent(intent) {
|
||||
if (intent === "list_documents_by_contract" || intent === "bank_operations_by_contract") {
|
||||
return "contract";
|
||||
}
|
||||
if (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date") {
|
||||
return "item";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function evaluateAddressAnchorQuality(message) {
|
||||
@@ -3425,7 +3440,9 @@ function evaluateAddressAnchorQuality(message) {
|
||||
const extracted = (0, addressFilterExtractor_1.extractAddressFilters)(String(message ?? ""), intent);
|
||||
const anchorValue = anchorType === "counterparty"
|
||||
? toNonEmptyString(extracted?.extracted_filters?.counterparty)
|
||||
: toNonEmptyString(extracted?.extracted_filters?.contract);
|
||||
: anchorType === "contract"
|
||||
? toNonEmptyString(extracted?.extracted_filters?.contract)
|
||||
: toNonEmptyString(extracted?.extracted_filters?.item);
|
||||
if (!anchorValue) {
|
||||
return {
|
||||
intent,
|
||||
@@ -3436,7 +3453,9 @@ function evaluateAddressAnchorQuality(message) {
|
||||
}
|
||||
const lowQuality = anchorType === "counterparty"
|
||||
? isLowQualityPredecomposeCounterpartyAnchor(anchorValue)
|
||||
: isLowQualityPredecomposeContractAnchor(anchorValue);
|
||||
: anchorType === "contract"
|
||||
? isLowQualityPredecomposeContractAnchor(anchorValue)
|
||||
: (0, addressFilterExtractor_1.isLowQualityInventoryItemAnchorValue)(anchorValue);
|
||||
return {
|
||||
intent,
|
||||
anchorType,
|
||||
@@ -3660,6 +3679,14 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||
const sourceAnchorQuality = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
|
||||
const candidateAnchorQuality = evaluateAddressAnchorQuality(candidate);
|
||||
const sameIntentForAnchorSafety = sourceAnchorQuality.intent !== "unknown" && sourceAnchorQuality.intent === candidateAnchorQuality.intent;
|
||||
const sourceSelectedObjectItemAnchorValue = toNonEmptyString((0, addressFilterExtractor_1.extractSelectedObjectQuotedValue)(userMessage)) ??
|
||||
toNonEmptyString((0, addressFilterExtractor_1.extractSelectedObjectQuotedValue)(repairedSourceMessage || userMessage));
|
||||
const candidateSemanticItemAnchorValue = (((sameIntentForAnchorSafety &&
|
||||
sourceAnchorQuality.anchorType === "item") ||
|
||||
Boolean(sourceSelectedObjectItemAnchorValue)) &&
|
||||
candidateMeta?.semanticHints?.scope_target_kind === "item"
|
||||
? toNonEmptyString(candidateMeta.semanticHints.scope_target_text)
|
||||
: null);
|
||||
const counterpartyAnchorSubstitutedByCandidate = sameIntentForAnchorSafety &&
|
||||
sourceAnchorQuality.anchorType === "counterparty" &&
|
||||
sourceAnchorQuality.quality >= 2 &&
|
||||
@@ -3684,6 +3711,25 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
|
||||
semanticHints: candidateMeta?.semanticHints ?? null
|
||||
}, userMessage);
|
||||
}
|
||||
const itemSemanticAnchorDegradedByCandidate = (sameIntentForAnchorSafety ||
|
||||
Boolean(sourceSelectedObjectItemAnchorValue)) &&
|
||||
Boolean(sourceSelectedObjectItemAnchorValue ?? sourceAnchorQuality.anchorValue) &&
|
||||
Boolean(candidateSemanticItemAnchorValue) &&
|
||||
(0, addressFilterExtractor_1.isInventoryItemAnchorDegradation)(sourceSelectedObjectItemAnchorValue ?? sourceAnchorQuality.anchorValue ?? "", candidateSemanticItemAnchorValue ?? "");
|
||||
if (itemSemanticAnchorDegradedByCandidate) {
|
||||
return attachAddressPredecomposeContract({
|
||||
...baseMeta,
|
||||
attempted: true,
|
||||
applied: false,
|
||||
traceId: normalized?.trace_id ?? null,
|
||||
llmCanonicalCandidateDetected: true,
|
||||
effectiveMessage: userMessage,
|
||||
reason: "normalized_fragment_rejected_anchor_degradation",
|
||||
fallbackRuleHit: null,
|
||||
sanitizedUserMessage,
|
||||
semanticHints: candidateMeta?.semanticHints ?? null
|
||||
}, userMessage);
|
||||
}
|
||||
const anchorDegradedByCandidate = sameIntentForAnchorSafety &&
|
||||
sourceAnchorQuality.anchorType &&
|
||||
sourceAnchorQuality.quality >= 2 &&
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.hasInventoryPurchaseStem = hasInventoryPurchaseStem;
|
||||
exports.hasInventorySupplierCue = hasInventorySupplierCue;
|
||||
exports.hasInventorySaleCue = hasInventorySaleCue;
|
||||
function toText(value) {
|
||||
return String(value ?? "");
|
||||
}
|
||||
function hasInventoryPurchaseStem(text) {
|
||||
return /купл[а-яёa-z0-9_-]*/iu.test(toText(text));
|
||||
}
|
||||
function hasInventorySupplierCue(text) {
|
||||
const value = toText(text);
|
||||
if (/(?:кто\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+куплено|supplier|vendor|поставщик)/iu.test(value)) {
|
||||
return true;
|
||||
}
|
||||
return hasInventoryPurchaseStem(value) && /(?:у\s+кого|от\s+кого|где)/iu.test(value);
|
||||
}
|
||||
function hasInventorySaleCue(text) {
|
||||
const value = toText(text);
|
||||
if (/(?:buyer|покупател)/iu.test(value)) {
|
||||
return true;
|
||||
}
|
||||
if (/(?:куда\s+ушла\s+позиция|куда\s+ушел\s+товар|кто\s+купил)/iu.test(value)) {
|
||||
return true;
|
||||
}
|
||||
const hasDirectionCue = /(?:кому|каму|куда)/iu.test(value);
|
||||
const hasSaleVerb = /(?:продал(?:и|а|о|ы)?|продан(?:а|о|ы)?|продано|реализовал(?:и|а|о|ы)?|реализован(?:а|о|ы)?|реализовано)/iu.test(value);
|
||||
if (hasDirectionCue && hasSaleVerb) {
|
||||
return true;
|
||||
}
|
||||
return /(?:^|[\s,.;:!?])(продано|продали|продан(?:а|о|ы)?|реализовано|реализовали|реализован(?:а|о|ы)?)(?=$|[\s,.;:!?])/iu.test(value);
|
||||
}
|
||||
Reference in New Issue
Block a user