Усилить reliability gate и принять margin semantic replay
This commit is contained in:
@@ -1670,10 +1670,9 @@ function hasNomenclatureMarginRankingSignal(text) {
|
||||
return false;
|
||||
}
|
||||
const hasNomenclatureCue = /(?:номенклатур|товар|позици|ассортимент|sku|item|product|goods)/iu.test(normalized);
|
||||
const hasRealizationCue = /(?:реализован|реализац|продан|продаж|отгруж|41(?:[.,]0?1)?|90(?:[.,]\d{1,2})?|sales?|sold)/iu.test(normalized);
|
||||
const hasMarginCue = /(?:прибыл|марж|рентаб|наценк|себестоим|выручк|profit|margin|profitability|gross\s+spread|cogs)/iu.test(normalized);
|
||||
const hasRankingCue = /(?:высок|низк|топ|сам(?:ая|ый|ое|ые)|больш|меньш|ранж|рейтинг|high|low|top|rank|best|worst)/iu.test(normalized);
|
||||
return hasNomenclatureCue && hasRealizationCue && hasMarginCue && hasRankingCue;
|
||||
const hasRankingCue = /(?:высок|низк|топ|сам(?:ая|ый|ое|ые|ой|ого|ому|ым|ых|ую)|больш|меньш|ранж|рейтинг|max|min|high|low|top|rank|best|worst)/iu.test(normalized);
|
||||
return hasNomenclatureCue && hasMarginCue && hasRankingCue;
|
||||
}
|
||||
function hasVatPeriodInspectionBridgeSignal(text) {
|
||||
const normalized = String(text ?? "").trim().toLowerCase();
|
||||
|
||||
@@ -21,6 +21,19 @@ function hasPlainRussianInventoryOnHandSignal(text) {
|
||||
const hasSnapshotCue = /(?:на\s+(?:дату|сегодня|сейчас|март|апрел|май|мая|июн|июл|август|сентябр|октябр|ноябр|декабр|январ|феврал)|\b(?:19|20)\d{2}\b)/iu.test(normalized);
|
||||
return hasRequestCue && (hasSnapshotCue || /остатк/iu.test(normalized));
|
||||
}
|
||||
function hasInventoryMarginRankingSignal(text) {
|
||||
const normalized = String(text ?? "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/ё/g, "е");
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasNomenclatureCue = /(?:номенклатур|товар|позици|ассортимент|sku|item|product|goods)/iu.test(normalized);
|
||||
const hasMarginCue = /(?:прибыл|марж|рентаб|наценк|себестоим|выручк|profit|margin|profitability|gross\s+spread|cogs)/iu.test(normalized);
|
||||
const hasRankingCue = /(?:высок|низк|топ|сам(?:ая|ый|ое|ые|ой|ого|ому|ым|ых|ую)|больш|меньш|ранж|рейтинг|max|min|high|low|top|rank|best|worst)/iu.test(normalized);
|
||||
return hasNomenclatureCue && hasMarginCue && hasRankingCue;
|
||||
}
|
||||
function hasInventoryOnHandSignal(text) {
|
||||
const hasColloquialStockSnapshotCue = /(?:что|ч[еёо])\s+(?:у\s+нас\s+)?на\s+склад(?:е|у|ом|ах)(?=$|[\s,.;:!?])/iu.test(text);
|
||||
const hasStockStateCue = /(?:(?:что|ч[еёо])\s+там\s+на\s+склад(?:е|у|ом|ах)|(?:что|ч[еёо]).*происход(?:ит|ило|ящее).*(?:на\s+)?склад(?:е|у|ом|ах)|происход(?:ит|ило|ящее)\s+на\s+склад(?:е|у|ом|ах)|ситуац(?:ия|ии)\s+на\s+склад(?:е|у|ом|ах)|обстановк(?:а|и)\s+на\s+склад(?:е|у|ом|ах)|what(?:'s| is)?\s+(?:there\s+)?(?:on|in)\s+(?:the\s+)?(?:warehouse|stock)|what(?:'s| is)?\s+happening\s+(?:on|in)\s+(?:the\s+)?(?:warehouse|stock))/iu.test(text);
|
||||
@@ -34,6 +47,7 @@ function hasInventoryOnHandSignal(text) {
|
||||
hasInventoryPurchaseDocumentsSignalV2(text) ||
|
||||
hasInventorySaleTraceSignalV2(text) ||
|
||||
hasInventoryAgingSignal(text) ||
|
||||
hasInventoryMarginRankingSignal(text) ||
|
||||
hasInventoryPurchaseToSaleChainSignal(text)) {
|
||||
return false;
|
||||
}
|
||||
@@ -185,6 +199,13 @@ function resolveInventoryAddressIntent(text) {
|
||||
reasons: ["inventory_aging_signal_detected_strong"]
|
||||
};
|
||||
}
|
||||
if (hasInventoryMarginRankingSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_margin_ranking_for_nomenclature",
|
||||
confidence: "high",
|
||||
reasons: ["inventory_margin_ranking_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasInventoryAccount41Anchor(text) && hasInventoryAsOfCue(text)) {
|
||||
return {
|
||||
intent: "inventory_on_hand_as_of_date",
|
||||
|
||||
@@ -639,10 +639,12 @@ function hasInventoryMarginRankingFollowupCue(text) {
|
||||
const wantsFoundRows = /(?:покажи|показать|выведи|дай|раскрой|show|list|покажи|показать|выведи|дай|раскрой)/iu.test(normalized) &&
|
||||
/(?:найденн|строк|реализац|себестоимостн|баз|найденн|строк|реализац|себестоимостн|баз)/iu.test(normalized) &&
|
||||
/(?:себестоимостн|реализац|марж|прибыл|номенклатур|себестоимостн|реализац|марж|прибыл|номенклат)/iu.test(normalized);
|
||||
const asksMarginBasis = /(?:из\s+чего|как\s+(?:ты\s+)?(?:это\s+)?посчитал|почему|какие\s+поля|чего\s+не\s+хватает|не\s+хватает|точн(?:ой|ая|ую)?\s+марж|basis|source|fields|calculated|missing)/iu.test(normalized) &&
|
||||
/(?:марж|прибыл|рентаб|себестоимост|выручк|номенклатур|рейтинг|top|margin|profit|cogs|revenue)/iu.test(normalized);
|
||||
const account41Not01 = /\b41(?:[.,]\d{1,2})?\b/iu.test(normalized) &&
|
||||
/\b01(?:[.,]\d{1,2})?\b/iu.test(normalized) &&
|
||||
/(?:\bне\b|вместо|а\s+не|not|instead|РЅРµ|вместо|Р°\s+РЅРµ)/iu.test(normalized);
|
||||
return wantsFoundRows || account41Not01;
|
||||
return wantsFoundRows || asksMarginBasis || account41Not01;
|
||||
}
|
||||
function hasAddressFollowupContextSignal(text) {
|
||||
const normalized = String(text ?? "").trim();
|
||||
@@ -1346,8 +1348,8 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo
|
||||
followupContext.root_anchor_type === "item" ||
|
||||
followupContext.current_frame_kind === "inventory_root" ||
|
||||
followupContext.current_frame_kind === "inventory_drilldown";
|
||||
const inventorySelectedObjectFollowup = inventoryLineageActive &&
|
||||
(hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal));
|
||||
const hasSelectedObjectReference = hasSelectedObjectInventorySignal(normalizedMessage);
|
||||
const inventorySelectedObjectFollowup = inventoryLineageActive && (hasSelectedObjectReference || (previousIsInventoryFamily && hasFollowupSignal));
|
||||
const previousCounterpartyLaneActive = hasPreviousCounterparty &&
|
||||
(followupContext.previous_anchor_type === "counterparty" ||
|
||||
sourceIntent === "list_documents_by_counterparty" ||
|
||||
@@ -1369,6 +1371,7 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo
|
||||
detectedIntent.intent === "account_balance_snapshot" ||
|
||||
detectedIntent.intent === "documents_forming_balance" ||
|
||||
detectedIntent.intent === "inventory_margin_ranking_for_nomenclature" ||
|
||||
(detectedIntent.intent === "inventory_profitability_for_item" && !hasSelectedObjectReference) ||
|
||||
detectedIntent.intent === sourceIntent)) {
|
||||
return {
|
||||
intent: "inventory_margin_ranking_for_nomenclature",
|
||||
|
||||
+37
-8
@@ -88,6 +88,10 @@ function asksForInventoryCostBaseRows(userMessage) {
|
||||
}
|
||||
return /(?:себестоимостн|себестоимост|себестоим|закупочн|закупк|90\.02|\b41\b|баз)/iu.test(text);
|
||||
}
|
||||
function asksForInventoryMarginBasis(userMessage) {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
return (/(?:из\s+чего|как\s+(?:ты\s+)?(?:это\s+)?посчитал|какие\s+поля|чего\s+не\s+хватает|не\s+хватает|точн(?:ой|ая|ую)?\s+марж|basis|source|fields|calculated|missing)/iu.test(text) && /(?:марж|прибыл|себестоимост|выручк|margin|profit|cogs|revenue)/iu.test(text));
|
||||
}
|
||||
function inventoryRowItemLabel(row, deps) {
|
||||
return deps.summarizeInventoryTraceRows([row]).item;
|
||||
}
|
||||
@@ -454,17 +458,19 @@ function composeInventoryReply(intent, rows, options, deps) {
|
||||
const entries = buildInventoryMarginRankingEntries(rows, deps);
|
||||
const confirmedEntries = entries.filter((entry) => entry.revenue > 0 && entry.costProxy > 0);
|
||||
const highMargin = [...confirmedEntries]
|
||||
.sort((left, right) => right.spread - left.spread || (right.marginPct ?? -Infinity) - (left.marginPct ?? -Infinity))
|
||||
.slice(0, 5);
|
||||
.sort((left, right) => (right.marginPct ?? -Infinity) - (left.marginPct ?? -Infinity) || right.spread - left.spread)
|
||||
.slice(0, 3);
|
||||
const lowMargin = [...confirmedEntries]
|
||||
.sort((left, right) => left.spread - right.spread || (left.marginPct ?? Infinity) - (right.marginPct ?? Infinity))
|
||||
.slice(0, 5);
|
||||
.sort((left, right) => (left.marginPct ?? Infinity) - (right.marginPct ?? Infinity) || left.spread - right.spread)
|
||||
.slice(0, 3);
|
||||
const salesWithoutCost = entries.filter((entry) => entry.revenue > 0 && entry.costProxy <= 0);
|
||||
const purchasesWithoutSales = entries.filter((entry) => entry.costProxy > 0 && entry.revenue <= 0);
|
||||
const periodLabel = inventoryProfitabilityPeriodLabel(options, deps);
|
||||
const totalRevenue = entries.reduce((sum, entry) => sum + entry.revenue, 0);
|
||||
const totalCostProxy = entries.reduce((sum, entry) => sum + entry.costProxy, 0);
|
||||
const totalSpread = totalRevenue - totalCostProxy;
|
||||
const topMarginEntry = highMargin[0] ?? null;
|
||||
const marginBasisRequested = asksForInventoryMarginBasis(options.userMessage);
|
||||
if (confirmedEntries.length === 0) {
|
||||
const costBaseRowsRequested = asksForInventoryCostBaseRows(options.userMessage);
|
||||
const lines = [
|
||||
@@ -512,12 +518,34 @@ function composeInventoryReply(intent, rows, options, deps) {
|
||||
]);
|
||||
return (0, replyContracts_1.buildFactualSummaryReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)(entries.length > 0 ? "medium" : "weak", false));
|
||||
}
|
||||
const directAnswerLine = confirmedEntries.length > 0
|
||||
? `За период ${periodLabel} собран рейтинг реализованной номенклатуры по валовой маржинальности: выручка ${deps.formatMoneyRub(totalRevenue)}, себестоимостная база ${deps.formatMoneyRub(totalCostProxy)}, расчетная валовая разница ${deps.formatMoneyRub(totalSpread)}.`
|
||||
: `За период ${periodLabel} не удалось подтвердить рейтинг прибыльности номенклатуры: нужны одновременно строки реализации и закупочного/себестоимостного следа по товарам.`;
|
||||
const directAnswerLine = topMarginEntry && marginBasisRequested
|
||||
? `Считал маржу за период ${periodLabel} как выручку реализации минус доступную себестоимостную базу: выручка ${deps.formatMoneyRub(totalRevenue)}, себестоимостная база ${deps.formatMoneyRub(totalCostProxy)}, валовая разница ${deps.formatMoneyRub(totalSpread)}.`
|
||||
: topMarginEntry
|
||||
? `Самая маржинальная позиция за период ${periodLabel}: ${topMarginEntry.item} — маржа ${formatInventoryPercent(topMarginEntry.marginPct, deps.formatNumberWithDots)}, выручка ${deps.formatMoneyRub(topMarginEntry.revenue)}, себестоимостная база ${deps.formatMoneyRub(topMarginEntry.costProxy)}, валовая разница ${deps.formatMoneyRub(topMarginEntry.spread)}.`
|
||||
: `За период ${periodLabel} не удалось подтвердить рейтинг прибыльности номенклатуры: нужны одновременно строки реализации и закупочного/себестоимостного следа по товарам.`;
|
||||
const lines = [directAnswerLine];
|
||||
if (marginBasisRequested) {
|
||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "База расчета:", [
|
||||
"выручка: подтвержденные строки реализации по номенклатуре;",
|
||||
"себестоимостная база: доступные строки закупочного/себестоимостного следа по той же номенклатуре;",
|
||||
"валовая маржа: (выручка - себестоимостная база) / выручка."
|
||||
]);
|
||||
const basisLimitations = [
|
||||
"это управленческий расчет валовой маржи, не показатель чистой прибыли;"
|
||||
];
|
||||
if (salesWithoutCost.length > 0) {
|
||||
basisLimitations.push(`по ${deps.formatNumberWithDots(salesWithoutCost.length)} позициям есть продажи без подтвержденной себестоимости реализации;`);
|
||||
}
|
||||
if (purchasesWithoutSales.length > 0) {
|
||||
basisLimitations.push(`по ${deps.formatNumberWithDots(purchasesWithoutSales.length)} позициям есть себестоимостная база без реализации в периоде;`);
|
||||
}
|
||||
basisLimitations.push("для строгого бухгалтерского расчета нужны проводки 90.01 / 90.02 и проверка закрытия себестоимости.");
|
||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Чего не хватает для точной маржи:", basisLimitations);
|
||||
lines.push("", "Следующий шаг: могу раскрыть строки выручки и себестоимостной базы по любой позиции из рейтинга.");
|
||||
return (0, replyContracts_1.buildFactualSummaryReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)(confirmedEntries.length > 0 ? "strong" : entries.length > 0 ? "medium" : "weak", confirmedEntries.length > 0));
|
||||
}
|
||||
if (highMargin.length > 0) {
|
||||
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Высокая валовая маржинальность:", highMargin.map((entry, index) => formatInventoryMarginRankingLine(entry, index, deps)));
|
||||
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Высокая валовая маржинальность (топ по проценту маржи):", highMargin.map((entry, index) => formatInventoryMarginRankingLine(entry, index, deps)));
|
||||
}
|
||||
if (lowMargin.length > 0) {
|
||||
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Низкая или отрицательная валовая маржинальность:", lowMargin.map((entry, index) => formatInventoryMarginRankingLine(entry, index, deps)));
|
||||
@@ -533,6 +561,7 @@ function composeInventoryReply(intent, rows, options, deps) {
|
||||
boundaryLines.push(`По ${deps.formatNumberWithDots(purchasesWithoutSales.length)} позициям есть себестоимостная база без реализации в этом периоде.`);
|
||||
}
|
||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Граница ответа:", boundaryLines);
|
||||
lines.push("", "Следующий шаг: могу раскрыть строки выручки и себестоимостной базы по выбранной позиции из рейтинга.");
|
||||
return (0, replyContracts_1.buildFactualSummaryReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)(confirmedEntries.length > 0 ? "strong" : entries.length > 0 ? "medium" : "weak", confirmedEntries.length > 0));
|
||||
}
|
||||
if (intent === "inventory_profitability_for_item") {
|
||||
|
||||
@@ -2159,19 +2159,15 @@ function hasNomenclatureMarginRankingSignal(text: string): boolean {
|
||||
}
|
||||
const hasNomenclatureCue =
|
||||
/(?:номенклатур|товар|позици|ассортимент|sku|item|product|goods)/iu.test(normalized);
|
||||
const hasRealizationCue =
|
||||
/(?:реализован|реализац|продан|продаж|отгруж|41(?:[.,]0?1)?|90(?:[.,]\d{1,2})?|sales?|sold)/iu.test(
|
||||
normalized
|
||||
);
|
||||
const hasMarginCue =
|
||||
/(?:прибыл|марж|рентаб|наценк|себестоим|выручк|profit|margin|profitability|gross\s+spread|cogs)/iu.test(
|
||||
normalized
|
||||
);
|
||||
const hasRankingCue =
|
||||
/(?:высок|низк|топ|сам(?:ая|ый|ое|ые)|больш|меньш|ранж|рейтинг|high|low|top|rank|best|worst)/iu.test(
|
||||
/(?:высок|низк|топ|сам(?:ая|ый|ое|ые|ой|ого|ому|ым|ых|ую)|больш|меньш|ранж|рейтинг|max|min|high|low|top|rank|best|worst)/iu.test(
|
||||
normalized
|
||||
);
|
||||
return hasNomenclatureCue && hasRealizationCue && hasMarginCue && hasRankingCue;
|
||||
return hasNomenclatureCue && hasMarginCue && hasRankingCue;
|
||||
}
|
||||
|
||||
function hasVatPeriodInspectionBridgeSignal(text: string): boolean {
|
||||
|
||||
@@ -35,6 +35,27 @@ function hasPlainRussianInventoryOnHandSignal(text: string): boolean {
|
||||
return hasRequestCue && (hasSnapshotCue || /остатк/iu.test(normalized));
|
||||
}
|
||||
|
||||
function hasInventoryMarginRankingSignal(text: string): boolean {
|
||||
const normalized = String(text ?? "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/ё/g, "е");
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasNomenclatureCue =
|
||||
/(?:номенклатур|товар|позици|ассортимент|sku|item|product|goods)/iu.test(normalized);
|
||||
const hasMarginCue =
|
||||
/(?:прибыл|марж|рентаб|наценк|себестоим|выручк|profit|margin|profitability|gross\s+spread|cogs)/iu.test(
|
||||
normalized
|
||||
);
|
||||
const hasRankingCue =
|
||||
/(?:высок|низк|топ|сам(?:ая|ый|ое|ые|ой|ого|ому|ым|ых|ую)|больш|меньш|ранж|рейтинг|max|min|high|low|top|rank|best|worst)/iu.test(
|
||||
normalized
|
||||
);
|
||||
return hasNomenclatureCue && hasMarginCue && hasRankingCue;
|
||||
}
|
||||
|
||||
function hasInventoryOnHandSignal(text: string): boolean {
|
||||
const hasColloquialStockSnapshotCue = /(?:что|ч[еёо])\s+(?:у\s+нас\s+)?на\s+склад(?:е|у|ом|ах)(?=$|[\s,.;:!?])/iu.test(
|
||||
text
|
||||
@@ -54,6 +75,7 @@ function hasInventoryOnHandSignal(text: string): boolean {
|
||||
hasInventoryPurchaseDocumentsSignalV2(text) ||
|
||||
hasInventorySaleTraceSignalV2(text) ||
|
||||
hasInventoryAgingSignal(text) ||
|
||||
hasInventoryMarginRankingSignal(text) ||
|
||||
hasInventoryPurchaseToSaleChainSignal(text)
|
||||
) {
|
||||
return false;
|
||||
@@ -304,6 +326,14 @@ export function resolveInventoryAddressIntent(text: string): AddressIntentResolu
|
||||
};
|
||||
}
|
||||
|
||||
if (hasInventoryMarginRankingSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_margin_ranking_for_nomenclature",
|
||||
confidence: "high",
|
||||
reasons: ["inventory_margin_ranking_signal_detected"]
|
||||
};
|
||||
}
|
||||
|
||||
if (hasInventoryAccount41Anchor(text) && hasInventoryAsOfCue(text)) {
|
||||
return {
|
||||
intent: "inventory_on_hand_as_of_date",
|
||||
|
||||
@@ -812,11 +812,18 @@ export function hasInventoryMarginRankingFollowupCue(text: string): boolean {
|
||||
/(?:покажи|показать|выведи|дай|раскрой|show|list|покажи|показать|выведи|дай|раскрой)/iu.test(normalized) &&
|
||||
/(?:найденн|строк|реализац|себестоимостн|баз|найденн|строк|реализац|себестоимостн|баз)/iu.test(normalized) &&
|
||||
/(?:себестоимостн|реализац|марж|прибыл|номенклатур|себестоимостн|реализац|марж|прибыл|номенклат)/iu.test(normalized);
|
||||
const asksMarginBasis =
|
||||
/(?:из\s+чего|как\s+(?:ты\s+)?(?:это\s+)?посчитал|почему|какие\s+поля|чего\s+не\s+хватает|не\s+хватает|точн(?:ой|ая|ую)?\s+марж|basis|source|fields|calculated|missing)/iu.test(
|
||||
normalized
|
||||
) &&
|
||||
/(?:марж|прибыл|рентаб|себестоимост|выручк|номенклатур|рейтинг|top|margin|profit|cogs|revenue)/iu.test(
|
||||
normalized
|
||||
);
|
||||
const account41Not01 =
|
||||
/\b41(?:[.,]\d{1,2})?\b/iu.test(normalized) &&
|
||||
/\b01(?:[.,]\d{1,2})?\b/iu.test(normalized) &&
|
||||
/(?:\bне\b|вместо|а\s+не|not|instead|РЅРµ|вместо|Р°\s+РЅРµ)/iu.test(normalized);
|
||||
return wantsFoundRows || account41Not01;
|
||||
return wantsFoundRows || asksMarginBasis || account41Not01;
|
||||
}
|
||||
|
||||
export function hasAddressFollowupContextSignal(text: string): boolean {
|
||||
@@ -1674,9 +1681,9 @@ function deriveIntentWithFollowupContext(
|
||||
followupContext.root_anchor_type === "item" ||
|
||||
followupContext.current_frame_kind === "inventory_root" ||
|
||||
followupContext.current_frame_kind === "inventory_drilldown";
|
||||
const hasSelectedObjectReference = hasSelectedObjectInventorySignal(normalizedMessage);
|
||||
const inventorySelectedObjectFollowup =
|
||||
inventoryLineageActive &&
|
||||
(hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal));
|
||||
inventoryLineageActive && (hasSelectedObjectReference || (previousIsInventoryFamily && hasFollowupSignal));
|
||||
const previousCounterpartyLaneActive =
|
||||
hasPreviousCounterparty &&
|
||||
(followupContext.previous_anchor_type === "counterparty" ||
|
||||
@@ -1707,6 +1714,7 @@ function deriveIntentWithFollowupContext(
|
||||
detectedIntent.intent === "account_balance_snapshot" ||
|
||||
detectedIntent.intent === "documents_forming_balance" ||
|
||||
detectedIntent.intent === "inventory_margin_ranking_for_nomenclature" ||
|
||||
(detectedIntent.intent === "inventory_profitability_for_item" && !hasSelectedObjectReference) ||
|
||||
detectedIntent.intent === sourceIntent)
|
||||
) {
|
||||
return {
|
||||
|
||||
@@ -170,6 +170,15 @@ function asksForInventoryCostBaseRows(userMessage: string | null | undefined): b
|
||||
return /(?:себестоимостн|себестоимост|себестоим|закупочн|закупк|90\.02|\b41\b|баз)/iu.test(text);
|
||||
}
|
||||
|
||||
function asksForInventoryMarginBasis(userMessage: string | null | undefined): boolean {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
return (
|
||||
/(?:из\s+чего|как\s+(?:ты\s+)?(?:это\s+)?посчитал|какие\s+поля|чего\s+не\s+хватает|не\s+хватает|точн(?:ой|ая|ую)?\s+марж|basis|source|fields|calculated|missing)/iu.test(
|
||||
text
|
||||
) && /(?:марж|прибыл|себестоимост|выручк|margin|profit|cogs|revenue)/iu.test(text)
|
||||
);
|
||||
}
|
||||
|
||||
interface InventoryMarginRankingEntry {
|
||||
item: string;
|
||||
revenue: number;
|
||||
@@ -627,17 +636,19 @@ export function composeInventoryReply(
|
||||
const entries = buildInventoryMarginRankingEntries(rows, deps);
|
||||
const confirmedEntries = entries.filter((entry) => entry.revenue > 0 && entry.costProxy > 0);
|
||||
const highMargin = [...confirmedEntries]
|
||||
.sort((left, right) => right.spread - left.spread || (right.marginPct ?? -Infinity) - (left.marginPct ?? -Infinity))
|
||||
.slice(0, 5);
|
||||
.sort((left, right) => (right.marginPct ?? -Infinity) - (left.marginPct ?? -Infinity) || right.spread - left.spread)
|
||||
.slice(0, 3);
|
||||
const lowMargin = [...confirmedEntries]
|
||||
.sort((left, right) => left.spread - right.spread || (left.marginPct ?? Infinity) - (right.marginPct ?? Infinity))
|
||||
.slice(0, 5);
|
||||
.sort((left, right) => (left.marginPct ?? Infinity) - (right.marginPct ?? Infinity) || left.spread - right.spread)
|
||||
.slice(0, 3);
|
||||
const salesWithoutCost = entries.filter((entry) => entry.revenue > 0 && entry.costProxy <= 0);
|
||||
const purchasesWithoutSales = entries.filter((entry) => entry.costProxy > 0 && entry.revenue <= 0);
|
||||
const periodLabel = inventoryProfitabilityPeriodLabel(options, deps);
|
||||
const totalRevenue = entries.reduce((sum, entry) => sum + entry.revenue, 0);
|
||||
const totalCostProxy = entries.reduce((sum, entry) => sum + entry.costProxy, 0);
|
||||
const totalSpread = totalRevenue - totalCostProxy;
|
||||
const topMarginEntry = highMargin[0] ?? null;
|
||||
const marginBasisRequested = asksForInventoryMarginBasis(options.userMessage);
|
||||
if (confirmedEntries.length === 0) {
|
||||
const costBaseRowsRequested = asksForInventoryCostBaseRows(options.userMessage);
|
||||
const lines: string[] = [
|
||||
@@ -700,19 +711,54 @@ export function composeInventoryReply(
|
||||
return buildFactualSummaryReply(lines, buildConfirmedBalanceSemantics(entries.length > 0 ? "medium" : "weak", false));
|
||||
}
|
||||
const directAnswerLine =
|
||||
confirmedEntries.length > 0
|
||||
? `За период ${periodLabel} собран рейтинг реализованной номенклатуры по валовой маржинальности: выручка ${deps.formatMoneyRub(
|
||||
topMarginEntry && marginBasisRequested
|
||||
? `Считал маржу за период ${periodLabel} как выручку реализации минус доступную себестоимостную базу: выручка ${deps.formatMoneyRub(
|
||||
totalRevenue
|
||||
)}, себестоимостная база ${deps.formatMoneyRub(totalCostProxy)}, расчетная валовая разница ${deps.formatMoneyRub(
|
||||
)}, себестоимостная база ${deps.formatMoneyRub(totalCostProxy)}, валовая разница ${deps.formatMoneyRub(
|
||||
totalSpread
|
||||
)}.`
|
||||
: `За период ${periodLabel} не удалось подтвердить рейтинг прибыльности номенклатуры: нужны одновременно строки реализации и закупочного/себестоимостного следа по товарам.`;
|
||||
: topMarginEntry
|
||||
? `Самая маржинальная позиция за период ${periodLabel}: ${topMarginEntry.item} — маржа ${formatInventoryPercent(
|
||||
topMarginEntry.marginPct,
|
||||
deps.formatNumberWithDots
|
||||
)}, выручка ${deps.formatMoneyRub(topMarginEntry.revenue)}, себестоимостная база ${deps.formatMoneyRub(
|
||||
topMarginEntry.costProxy
|
||||
)}, валовая разница ${deps.formatMoneyRub(topMarginEntry.spread)}.`
|
||||
: `За период ${periodLabel} не удалось подтвердить рейтинг прибыльности номенклатуры: нужны одновременно строки реализации и закупочного/себестоимостного следа по товарам.`;
|
||||
const lines: string[] = [directAnswerLine];
|
||||
|
||||
if (marginBasisRequested) {
|
||||
appendInventoryBulletSection(lines, "База расчета:", [
|
||||
"выручка: подтвержденные строки реализации по номенклатуре;",
|
||||
"себестоимостная база: доступные строки закупочного/себестоимостного следа по той же номенклатуре;",
|
||||
"валовая маржа: (выручка - себестоимостная база) / выручка."
|
||||
]);
|
||||
const basisLimitations = [
|
||||
"это управленческий расчет валовой маржи, не показатель чистой прибыли;"
|
||||
];
|
||||
if (salesWithoutCost.length > 0) {
|
||||
basisLimitations.push(
|
||||
`по ${deps.formatNumberWithDots(salesWithoutCost.length)} позициям есть продажи без подтвержденной себестоимости реализации;`
|
||||
);
|
||||
}
|
||||
if (purchasesWithoutSales.length > 0) {
|
||||
basisLimitations.push(
|
||||
`по ${deps.formatNumberWithDots(purchasesWithoutSales.length)} позициям есть себестоимостная база без реализации в периоде;`
|
||||
);
|
||||
}
|
||||
basisLimitations.push("для строгого бухгалтерского расчета нужны проводки 90.01 / 90.02 и проверка закрытия себестоимости.");
|
||||
appendInventoryBulletSection(lines, "Чего не хватает для точной маржи:", basisLimitations);
|
||||
lines.push("", "Следующий шаг: могу раскрыть строки выручки и себестоимостной базы по любой позиции из рейтинга.");
|
||||
return buildFactualSummaryReply(
|
||||
lines,
|
||||
buildConfirmedBalanceSemantics(confirmedEntries.length > 0 ? "strong" : entries.length > 0 ? "medium" : "weak", confirmedEntries.length > 0)
|
||||
);
|
||||
}
|
||||
|
||||
if (highMargin.length > 0) {
|
||||
appendInventorySection(
|
||||
lines,
|
||||
"Высокая валовая маржинальность:",
|
||||
"Высокая валовая маржинальность (топ по проценту маржи):",
|
||||
highMargin.map((entry, index) => formatInventoryMarginRankingLine(entry, index, deps))
|
||||
);
|
||||
}
|
||||
@@ -739,6 +785,7 @@ export function composeInventoryReply(
|
||||
);
|
||||
}
|
||||
appendInventoryBulletSection(lines, "Граница ответа:", boundaryLines);
|
||||
lines.push("", "Следующий шаг: могу раскрыть строки выручки и себестоимостной базы по выбранной позиции из рейтинга.");
|
||||
|
||||
return buildFactualSummaryReply(
|
||||
lines,
|
||||
|
||||
@@ -113,6 +113,16 @@ describe("addressIntentResolver regression bridges", () => {
|
||||
expect(result.reasons).toContain("unicode_nomenclature_margin_ranking_bridge_signal_detected");
|
||||
});
|
||||
|
||||
it("routes bare highest-margin nomenclature wording away from inventory snapshot", () => {
|
||||
const result = resolveAddressIntent(
|
||||
"\u041a\u0430\u043a\u0430\u044f \u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440\u0430 \u0431\u044b\u043b\u0430 \u0441\u0430\u043c\u043e\u0439 \u043c\u0430\u0440\u0436\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0439 \u0437\u0430 2020 \u0433\u043e\u0434?"
|
||||
);
|
||||
|
||||
expect(result.intent).toBe("inventory_margin_ranking_for_nomenclature");
|
||||
expect(result.intent).not.toBe("inventory_on_hand_as_of_date");
|
||||
expect(result.reasons).toContain("unicode_nomenclature_margin_ranking_bridge_signal_detected");
|
||||
});
|
||||
|
||||
it("detects bare historical inventory root with explicit month-year", () => {
|
||||
const result = resolveAddressIntent("остатки на март 2016");
|
||||
|
||||
|
||||
@@ -18,6 +18,15 @@ describe("addressInventoryIntentSignals", () => {
|
||||
expect(result?.reasons).toContain("inventory_on_hand_signal_detected");
|
||||
});
|
||||
|
||||
it("keeps bare highest-margin nomenclature wording out of the stock snapshot route", () => {
|
||||
const result = resolveInventoryAddressIntent(
|
||||
"\u041a\u0430\u043a\u0430\u044f \u043d\u043e\u043c\u0435\u043d\u043a\u043b\u0430\u0442\u0443\u0440\u0430 \u0431\u044b\u043b\u0430 \u0441\u0430\u043c\u043e\u0439 \u043c\u0430\u0440\u0436\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0439 \u0437\u0430 2020 \u0433\u043e\u0434?"
|
||||
);
|
||||
|
||||
expect(result?.intent).toBe("inventory_margin_ranking_for_nomenclature");
|
||||
expect(result?.reasons).toContain("inventory_margin_ranking_signal_detected");
|
||||
});
|
||||
|
||||
it("classifies selected-object purchase provenance wording through the extracted inventory owner", () => {
|
||||
const result = resolveInventoryAddressIntent("selected object supplier provenance");
|
||||
|
||||
|
||||
+27
@@ -232,6 +232,33 @@ describe("inventory profitability selected-object regressions", () => {
|
||||
expect(result?.intent.reasons).toContain("intent_adjusted_to_inventory_margin_ranking_followup_context");
|
||||
});
|
||||
|
||||
it("keeps margin basis follow-up inside ranking context instead of asking for an item", () => {
|
||||
const marginFollowupContext = {
|
||||
previous_intent: "inventory_margin_ranking_for_nomenclature" as const,
|
||||
target_intent: "inventory_margin_ranking_for_nomenclature" as const,
|
||||
root_intent: "inventory_margin_ranking_for_nomenclature" as const,
|
||||
previous_filters: {
|
||||
organization: "OOO Alternative Plus",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
previous_anchor_type: "unknown" as const,
|
||||
previous_anchor_value: null
|
||||
};
|
||||
|
||||
const result = runAddressDecomposeStage(
|
||||
"\u0410 \u0438\u0437 \u0447\u0435\u0433\u043e \u0442\u044b \u044d\u0442\u043e \u043f\u043e\u0441\u0447\u0438\u0442\u0430\u043b \u0438 \u0447\u0435\u0433\u043e \u043d\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u0434\u043b\u044f \u0442\u043e\u0447\u043d\u043e\u0439 \u043c\u0430\u0440\u0436\u0438?",
|
||||
marginFollowupContext
|
||||
);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("inventory_margin_ranking_for_nomenclature");
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2020-01-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2020-12-31");
|
||||
expect(result?.filters.missing_required_filters).toEqual([]);
|
||||
expect(result?.intent.reasons).toContain("intent_adjusted_to_inventory_margin_ranking_followup_context");
|
||||
});
|
||||
|
||||
it("does not pivot margin follow-up account-41 correction into a balance snapshot", () => {
|
||||
const marginFollowupContext = {
|
||||
previous_intent: "inventory_margin_ranking_for_nomenclature" as const,
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"run_id": "eval-QvCdJw3L2F",
|
||||
"timestamp": "2026-05-24T07:11:10.815Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 3
|
||||
},
|
||||
"cases_total": 3,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 33.33,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 33.33,
|
||||
"routed_fragment_rate": 66.67,
|
||||
"no_route_fragment_rate": 33.33,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 66.67,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 3,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 1,
|
||||
"no_route": 1,
|
||||
"batch_refresh_then_store": 1
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 1,
|
||||
"out_of_scope": 1,
|
||||
"clarification": 1
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь хвосты по поставщикам и разложи цепочку",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "6H5F0kDlkYF66l",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Как вообще по ФСБУ",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 1,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "out_of_scope",
|
||||
"predicted_route_status": "no_route",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": "out_of_scope",
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "e-Yrbn6kxhbKhv",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-003",
|
||||
"raw_question": "Покажи топ рисков за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 1,
|
||||
"fallback_type": "clarification",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "mwO8qxdx71dFCd",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"run_id": "eval-_NYgFC2nU2",
|
||||
"timestamp": "2026-05-24T07:11:23.663Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"store_feature_risk": 1,
|
||||
"hybrid_store_plus_live": 1
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "A1V4KbeK6NiYJK",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по счету 97",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "Bg-XArIzpQzoHW",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"run_id": "eval-lFWABdc8V1",
|
||||
"timestamp": "2026-05-24T07:11:24.006Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"store_feature_risk": 1,
|
||||
"hybrid_store_plus_live": 1
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "9NqELSDEJeKcZl",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по НДС и по закрытию",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "AWGkBh8taBraRd",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
"id": "preset-it0w_T10",
|
||||
"name": "NDC custom preset",
|
||||
"createdAt": "2026-03-23T13:37:13.324Z",
|
||||
"updatedAt": "2026-03-23T13:37:13.324Z",
|
||||
"prompt_version": "normalizer_v1",
|
||||
"systemPrompt": "Ты semantic-normalizer для бухгалтерического ассистента NDC.\n\nТвоя задача — НЕ отвечать на бухгалтерский вопрос по сути.\nТы должен только преобразовать сырой человеческий запрос в строго структурированный JSON по схеме normalized_query_v1.\n\nЖесткие правила:\n1. Возвращай только валидный JSON.\n2. Не добавляй пояснений вне JSON.\n3. Не выдумывай факты, которых нет в вопросе.\n4. Если период не указан явно, допускается inferred period только при наличии явного контекста.\n5. Если вопрос причинно-следственный, поднимай causal признаки.\n6. Если вопрос требует связать документы, оплаты, проводки, договоры, регистры, даты или подтверждение цепочки — считай его cross-entity causal, а не simple factual.\n7. Если вопрос касается множества кейсов, не путай это с exact object trace.\n8. Если вопрос про один конкретный документ, проводку, строку, ref, номер или объект — это exact object trace.\n9. Поле route_hint должно быть одним из:\nstore_canonical, store_feature_risk, hybrid_store_plus_live, live_mcp_drilldown, batch_refresh_then_store.\n10. Поле schema_version должно быть normalized_query_v1.",
|
||||
"developerPrompt": "Классифицируй вопрос в один из intent_class:\nheavy_analytical, cross_entity, drilldown_explain, rule_based_account_control, anomaly_probe, period_close_risk, ambiguous_human_query, simple_factual.\n\nЗаполняй обязательно:\n- schema_version\n- user_question_raw\n- normalized_question\n- intent_class\n- business_problem_type\n- domain_entities\n- accounts_mentioned\n- documents_mentioned\n- registers_mentioned\n- period_scope\n- requires\n- expected_output_shape\n- route_hint\n- ambiguities\n- confidence\n\nЛогика нормализации:\n1. Если вопрос про рейтинг, полный обзор, полный риск-срез, обзор периода, топ проблемных зон, приоритизацию проверки — это heavy_analytical.\n2. Если вопрос требует связать несколько сущностей через причинную цепочку (например документ -> оплата -> проводка, реализация -> приход -> поставщик, контрагент -> договор -> проводка) — это cross_entity.\n3. Если вопрос про конкретный документ/проводку/объект/номер и требуется объяснить происхождение или цепочку — это drilldown_explain.\n4. Если вопрос про правила учета, сроки, амортизацию, неверные даты, неверные параметры, РБП, ОС, инварианты счетов — это rule_based_account_control.\n5. Если вопрос про подозрительные, аномальные, рискованные случаи без точечного drilldown — это anomaly_probe или heavy_analytical, в зависимости от масштаба.\n6. Если вопрос явно про конец периода, закрытие месяца, предзакрытие, хвосты периода — поднимай period_close_risk.\n7. Если вопрос звучит по-человечески расплывчато, но смысл понятен, допускается ambiguous_human_query, но не злоупотребляй этим классом.\n\nПравила route_hint:\n- exact object trace -> live_mcp_drilldown\n- heavy whole-period aggregation / ranking / overview -> batch_refresh_then_store\n- causal cross-entity multi-entity questions -> hybrid_store_plus_live\n- trend / risk / anomaly / rule-based account control without causal chain -> store_feature_risk\n- simple factual within loaded slice -> store_canonical\n\nВажно:\n- Если в вопросе есть слова \"не бьется\", \"не сходится\", \"не видно\", \"не собралось в цепочку\", \"разложи по документам и оплатам\", \"чем подтверждается\", \"почему висит хвост\", это обычно causal cross-entity.\n- Не отправляй causal cross-entity вопрос в store_canonical только потому, что он звучит как обычный факт.\n- Не отправляй causal cross-entity вопрос в store_feature_risk только потому, что в нем есть слова \"риск\", \"аномалия\", \"проблема\".",
|
||||
"domainPrompt": "Контур: бухгалтерический ассистент 1С/NDC.\n\nДоменные ориентиры:\n- счета: 01, 02, 10, 41, 51, 60, 62, 68, 90, 97\n- типовые сущности:\n - контрагент\n - договор\n - документ\n - реализация\n - поступление\n - оплата\n - банковская выписка\n - проводка\n - регистр\n - товар\n - склад\n - основное средство\n - расход будущих периодов\n - взаиморасчеты\n - хвост периода\n - закрывающие документы\n - акт сверки\n\nСеманика живого языка:\n- \"не бьется\" = reconciliation mismatch\n- \"хвост\" = unresolved residual / unclosed balance\n- \"не собралось в цепочку\" = missing causal chain\n- \"чем подтверждается\" = evidence required\n- \"что проверить первым\" = prioritized review list\n- \"зависло\" = unresolved accounting case\n- \"продажа раньше прихода\" = sales before supply pattern\n- \"ошибка по дате\" = period/date inconsistency\n- \"реализация без оплаты\" = receivable not closed\n- \"не видно прихода под реализацию\" = causal join between sale and supply required\n\nЕсли вопрос связывает документы, оплаты, проводки, даты, договоры и контрагентов, это обычно causal cross-entity сценарий.",
|
||||
"schemaNotes": "schema_version: normalized_query_v1\nВозвращай только JSON.\nНикаких дополнительных полей вне схемы.\nВсе булевы requires-поля должны быть заполнены явно.\nЕсли поле неизвестно, используй пустой массив, null или missing/inferred по смыслу.",
|
||||
"fewShotExamples": "[EXAMPLE 1]\nQ: По каким поставщикам не бьются взаиморасчеты по 60 счету?\nA:\n{\n \"schema_version\": \"normalized_query_v1\",\n \"user_question_raw\": \"По каким поставщикам не бьются взаиморасчеты по 60 счету?\",\n \"normalized_question\": \"Показать поставщиков с расхождениями по взаиморасчетам на счете 60 с объяснимой связкой документов и оплат.\",\n \"intent_class\": \"cross_entity\",\n \"business_problem_type\": \"supplier_reconciliation_mismatch\",\n \"domain_entities\": [\"supplier\", \"settlements\", \"documents\", \"payments\", \"postings\"],\n \"accounts_mentioned\": [\"60\"],\n \"documents_mentioned\": [],\n \"registers_mentioned\": [],\n \"period_scope\": { \"type\": \"missing\", \"value\": null, \"confidence\": \"low\" },\n \"requires\": {\n \"needs_cross_entity_join\": true,\n \"needs_causal_chain\": true,\n \"needs_exact_object_trace\": false,\n \"needs_ranking\": false,\n \"needs_anomaly_summary\": false,\n \"needs_runtime_truth\": false,\n \"needs_period_cut\": false,\n \"needs_evidence\": true\n },\n \"expected_output_shape\": \"reconciliation_report\",\n \"route_hint\": \"hybrid_store_plus_live\",\n \"ambiguities\": [],\n \"confidence\": { \"overall\": \"high\", \"intent_class\": \"high\", \"route_hint\": \"high\" }\n}\n\n[EXAMPLE 2]\nQ: Сделай рейтинг самых проблемных хвостов на конец июня.\nA:\n{\n \"schema_version\": \"normalized_query_v1\",\n \"user_question_raw\": \"Сделай рейтинг самых проблемных хвостов на конец июня.\",\n \"normalized_question\": \"Построить рейтинг наиболее проблемных незакрытых хвостов на конец июня.\",\n \"intent_class\": \"heavy_analytical\",\n \"business_problem_type\": \"period_close_risk_prioritization\",\n \"domain_entities\": [\"period_close\", \"risk_cases\"],\n \"accounts_mentioned\": [],\n \"documents_mentioned\": [],\n \"registers_mentioned\": [],\n \"period_scope\": { \"type\": \"explicit\", \"value\": \"июнь\", \"confidence\": \"high\" },\n \"requires\": {\n \"needs_cross_entity_join\": false,\n \"needs_causal_chain\": false,\n \"needs_exact_object_trace\": false,\n \"needs_ranking\": true,\n \"needs_anomaly_summary\": true,\n \"needs_runtime_truth\": false,\n \"needs_period_cut\": true,\n \"needs_evidence\": false\n },\n \"expected_output_shape\": \"ranked_list\",\n \"route_hint\": \"batch_refresh_then_store\",\n \"ambiguities\": [],\n \"confidence\": { \"overall\": \"high\", \"intent_class\": \"high\", \"route_hint\": \"high\" }\n}\n\n[EXAMPLE 3]\nQ: Почему эта проводка вообще появилась?\nA:\n{\n \"schema_version\": \"normalized_query_v1\",\n \"user_question_raw\": \"Почему эта проводка вообще появилась?\",\n \"normalized_question\": \"Объяснить происхождение конкретной проводки и ее source-of-record цепочку.\",\n \"intent_class\": \"drilldown_explain\",\n \"business_problem_type\": \"posting_origin_trace\",\n \"domain_entities\": [\"posting\", \"document\", \"source_record\"],\n \"accounts_mentioned\": [],\n \"documents_mentioned\": [],\n \"registers_mentioned\": [],\n \"period_scope\": { \"type\": \"missing\", \"value\": null, \"confidence\": \"low\" },\n \"requires\": {\n \"needs_cross_entity_join\": false,\n \"needs_causal_chain\": true,\n \"needs_exact_object_trace\": true,\n \"needs_ranking\": false,\n \"needs_anomaly_summary\": false,\n \"needs_runtime_truth\": true,\n \"needs_period_cut\": false,\n \"needs_evidence\": true\n },\n \"expected_output_shape\": \"evidence_chain\",\n \"route_hint\": \"live_mcp_drilldown\",\n \"ambiguities\": [],\n \"confidence\": { \"overall\": \"medium\", \"intent_class\": \"high\", \"route_hint\": \"high\" }\n}"
|
||||
}
|
||||
"updatedAt": "2026-05-24T07:01:44Z",
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"systemPrompt": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.",
|
||||
"developerPrompt": "You are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and candidate labels.\n4. For each fragment set execution state fields:\n - execution_readiness\n - clarification_reason\n - soft_assumption_used\n - route_status\n - no_route_reason\n5. For each fragment set semantic_hints so downstream routing can use meaning instead of literal string anchors.\n6. Clarification must be rare and justified.\n\nExecution-state policy:\n- Every in-scope fragment must produce a consistent execution state.\n- If a fragment is routable, mark it as executable or executable_with_soft_assumptions.\n- Do not leave routable fragments in unresolved state.\n- If a fragment cannot be routed, set route_status=no_route and provide explicit no_route_reason.\n\nReadiness values:\n- executable\n- executable_with_soft_assumptions\n- needs_clarification\n- no_route\n\nRoute status values:\n- routed\n- no_route\n\nNo-route reason values:\n- out_of_scope\n- insufficient_specificity\n- missing_mapping\n- unsupported_fragment_type\n\nDo not over-require formality:\n- Do not require document IDs, exact periods, or exact object references for scan/review/anomaly/rule-check requests.\n- Colloquial accounting phrases like \"что висит\", \"что подозрительно\", \"что не сходится\", \"что криво\", \"что аукнется\" are executable if accounting area is understandable.\n\nFragment required fields:\n- fragment_id\n- raw_fragment_text\n- normalized_fragment_text\n- domain_relevance\n- business_scope\n- entity_hints\n- account_hints\n- document_hints\n- register_hints\n- time_scope\n- flags\n- semantic_hints\n- candidate_labels\n- confidence\n- execution_readiness\n- clarification_reason\n- soft_assumption_used\n- route_status\n- no_route_reason\n\nSoft assumptions (`soft_assumption_used`) allowed values:\n- period_from_session_context\n- company_scope_defaulted\n- problem_scan_mode_enabled\n\nsemantic_hints fields:\n- scope_target_kind: none | self_scope | selected_object | organization | warehouse | counterparty | contract | item\n- scope_target_text: short user-facing mention when scope_target_kind is organization/warehouse/counterparty/contract/item\n- date_scope_kind: explicit | implicit_current | missing\n- self_scope_detected: true when wording means \"our own scope\" or \"this connected company\"\n- selected_object_scope_detected: true when wording refers to currently selected object/item\n\nSemantic-hints policy:\n- Use semantic_hints to preserve meaning of colloquial or elliptical wording.\n- Do not convert vague possessive wording into a fake literal anchor.\n- If user means \"our company / our connected base / current selected scope\", prefer self_scope_detected=true and scope_target_kind=self_scope.\n- If user refers to a company or organization colloquially, prefer scope_target_kind=organization, not warehouse.\n- If user refers to the selected row/object/item, prefer selected_object_scope_detected=true and scope_target_kind=selected_object or item when item text is explicit.\n- Do not invent exact database names. Use short text from the user in scope_target_text.\n\nExamples:\n- \"что на складе у нас\" -> semantic_hints.scope_target_kind=self_scope; self_scope_detected=true; date_scope_kind=implicit_current\n- \"что на складе конторы альтернатива\" -> semantic_hints.scope_target_kind=organization; scope_target_text=\"альтернатива\"; date_scope_kind=implicit_current\n- \"по выбранному объекту ... кто поставщик\" -> semantic_hints.scope_target_kind=selected_object; selected_object_scope_detected=true\n- \"по ней какие документы\" -> semantic_hints.scope_target_kind=selected_object; selected_object_scope_detected=true\n\nGlobal notes:\n- global_notes.needs_clarification should be true only when execution is truly blocked for all in-scope fragments.\n- global_notes.clarification_reason must explain the blocker.\n\nSchema version must be:\n- \"schema_version\": \"normalized_query_v2_0_2\"",
|
||||
"domainPrompt": "Контекст домена: бухгалтерия 1С/NDC.\n\nКлючевые счета:\n- 01, 02, 10, 41, 51, 60, 62, 68.02, 90, 97.\n\nТиповые сущности:\n- контрагент, договор, документ реализации, документ поступления, оплата, проводка, регистр, закрывающий документ.\n\nЛексика causal и сверки (сильные сигналы для cross_entity):\n- \"не бьется\", \"не сходится\", \"не видно\", \"не собралось\", \"повисло\", \"хвост\";\n- \"разложи по документам/оплатам/закрывающим\";\n- \"чем подтверждается\", \"где ошибка в цепочке\", \"что пошло криво\".\n\nЛексика точечного drilldown:\n- \"документ №...\", \"ref\", \"строка проводки\", \"покажи конкретную операцию\", \"точный source-of-record\".\n\nЛексика rule-based контроля:\n- \"проверь настройки\", \"ошибка срока/даты\", \"контроль 97/10/ОС\", \"нарушение правила учета\".\n\nЛексика обзорной аналитики:\n- \"рейтинг\", \"топ рисков\", \"в целом по компании\", \"перед закрытием периода\", \"приоритизация проверок\".\n\nВажное правило:\nЕсли в одном вопросе есть и риск-лексика, и цепочка document/payment/posting, не понижать задачу до чистого `store_feature_risk`.\nПриоритет у causal cross-entity семантики.\n\nНеформальные scope-формулировки:\n- \"у нас\", \"у себя\", \"по нашей базе\", \"в нашей конторе\" обычно означают self/company scope, а не буквальный якорь склада;\n- \"контора альтернатива\", \"альтернатива\", \"по фирме альтернатива\" обычно означают organization scope, а не склад;\n- \"по выбранному объекту\", \"по ней\", \"по этой позиции\", \"по этому товару\" обычно означают selected object scope.\n\nДля semantic_hints:\n- если речь про текущую подключенную компанию/нашу базу -> scope_target_kind=self_scope;\n- если речь про организацию/фирму/контору -> scope_target_kind=organization;\n- если речь про выбранную позицию/объект -> scope_target_kind=selected_object;\n- для складских snapshot-вопросов без даты обычно date_scope_kind=implicit_current.",
|
||||
"schemaNotes": "v2.0.2: execution-state hardening + explicit route_status/no_route_reason. ????? normalized_query_v2_0_2.",
|
||||
"fewShotExamples": "Q: По каким поставщикам висят хвосты по 60, что подозрительно по цепочке оплат?\nA: in_scope fragment, execution_readiness=executable_with_soft_assumptions, route_status=routed, no_route_reason=null.\n\nQ: Покажи записи по 97, которые повисли и могут аукнуться.\nA: in_scope fragment, execution_readiness=executable_with_soft_assumptions, route_status=routed, no_route_reason=null.\n\nQ: Чекни, что у нас не так.\nA: unclear/in_scope fragment, execution_readiness=needs_clarification, route_status=no_route, no_route_reason=insufficient_specificity.\n\nQ: Как вообще по ФСБУ правильно?\nA: out_of_scope fragment, execution_readiness=no_route, route_status=no_route, no_route_reason=out_of_scope."
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
"id": "preset-rk8wKqPt",
|
||||
"name": "NDC",
|
||||
"createdAt": "2026-03-23T13:41:04.687Z",
|
||||
"updatedAt": "2026-03-23T13:41:04.687Z",
|
||||
"prompt_version": "normalizer_v1",
|
||||
"updatedAt": "2026-05-24T07:01:44Z",
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"systemPrompt": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.",
|
||||
"developerPrompt": "Классификация intent_class:\n- heavy_analytical: общий агрегированный риск-срез, рейтинг, приоритизация.\n- cross_entity: связки между документами/проводками/оплатами/договорами/контрагентами.\n- drilldown_explain: точечное объяснение причин по объекту или малому набору объектов.\n- rule_based_account_control: контрольные правила по счетам (ОС, 97, 10 и т.п.).\n- anomaly_probe: поиск нетипичных паттернов.\n- period_close_risk: фокус на предзакрытии периода.\n- ambiguous_human_query: широкая человеческая формулировка без точного scope.\n- simple_factual: простой факт без сложной аналитики.\n\nПравила route_hint:\n- live_mcp_drilldown: если точечный object trace.\n- hybrid_store_plus_live: если cross_entity + causal explain.\n- batch_refresh_then_store: если full-period heavy aggregate/ranking без готовой агрегации.\n- store_feature_risk: если тренд/аномалии/контроли, когда точечный runtime не обязателен.\n- store_canonical: простые факты и легкие запросы при достаточном контексте.\n\nПравила requires:\n- needs_cross_entity_join=true для связок между разными сущностями.\n- needs_causal_chain=true для формулировок \"почему\", \"чем подтверждается\", \"разложи цепочку\".\n- needs_exact_object_trace=true для конкретного документа/проводки/строки/номера/ref.\n- needs_period_cut=true если вопрос про конец периода или периодную сверку.\n- needs_evidence=true если требуется подтверждение документами/движениями/проводками.",
|
||||
"domainPrompt": "Домен бухгалтерии:\n- ключевые счета: 01, 02, 10, 41, 51, 60, 62, 68.02, 90, 97;\n- сущности: контрагент, договор, реализация, поступление, оплата, проводка, регистр;\n- типовые паттерны: \"не бьется\", \"хвост\", \"акт сверки\", \"закрывающие\", \"реализация без оплаты\";\n- товарные аномалии: \"продажа раньше прихода\", \"подозрительный остаток\";\n- ОС: \"амортизационная группа\", \"срок амортизации\", \"карточка ОС\";\n- банк: \"выписка\", \"движение по 51\", \"разрыв цепочки документ-проводка\";\n- периодная аналитика: предзакрытие, риск-срез, приоритизация ручных проверок.\n\nЕсли присутствуют одновременно риск-слова и document/payment/posting chain,\nне понижать сценарий до чистого risk-route автоматически.",
|
||||
"schemaNotes": "schema_version: normalized_query_v1\nВозвращай только JSON.\nНикаких дополнительных полей вне схемы.\nВсе булевы requires-поля должны быть заполнены явно.\nЕсли поле неизвестно, используй пустой массив, null или missing/inferred по смыслу.",
|
||||
"fewShotExamples": "Q: По каким покупателям у нас отгрузки без оплаты на конец июня, свяжи с реализациями, договорами и проводками.\nExpected:\n{\n \"intent_class\": \"cross_entity\",\n \"requires\": {\n \"needs_cross_entity_join\": true,\n \"needs_causal_chain\": true,\n \"needs_exact_object_trace\": false\n },\n \"expected_output_shape\": \"reconciliation_report\",\n \"route_hint\": \"hybrid_store_plus_live\"\n}\n\nQ: Сделай рейтинг самых рисковых счетов перед закрытием июня.\nExpected:\n{\n \"intent_class\": \"heavy_analytical\",\n \"requires\": {\n \"needs_ranking\": true,\n \"needs_period_cut\": true\n },\n \"expected_output_shape\": \"ranked_list\",\n \"route_hint\": \"batch_refresh_then_store\"\n}\n\nQ: Покажи документ №123 и проводку по нему, нужна точная строка.\nExpected:\n{\n \"intent_class\": \"drilldown_explain\",\n \"requires\": {\n \"needs_exact_object_trace\": true,\n \"needs_runtime_truth\": true\n },\n \"expected_output_shape\": \"evidence_chain\",\n \"route_hint\": \"live_mcp_drilldown\"\n}"
|
||||
}
|
||||
"developerPrompt": "You are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and candidate labels.\n4. For each fragment set execution state fields:\n - execution_readiness\n - clarification_reason\n - soft_assumption_used\n - route_status\n - no_route_reason\n5. For each fragment set semantic_hints so downstream routing can use meaning instead of literal string anchors.\n6. Clarification must be rare and justified.\n\nExecution-state policy:\n- Every in-scope fragment must produce a consistent execution state.\n- If a fragment is routable, mark it as executable or executable_with_soft_assumptions.\n- Do not leave routable fragments in unresolved state.\n- If a fragment cannot be routed, set route_status=no_route and provide explicit no_route_reason.\n\nReadiness values:\n- executable\n- executable_with_soft_assumptions\n- needs_clarification\n- no_route\n\nRoute status values:\n- routed\n- no_route\n\nNo-route reason values:\n- out_of_scope\n- insufficient_specificity\n- missing_mapping\n- unsupported_fragment_type\n\nDo not over-require formality:\n- Do not require document IDs, exact periods, or exact object references for scan/review/anomaly/rule-check requests.\n- Colloquial accounting phrases like \"что висит\", \"что подозрительно\", \"что не сходится\", \"что криво\", \"что аукнется\" are executable if accounting area is understandable.\n\nFragment required fields:\n- fragment_id\n- raw_fragment_text\n- normalized_fragment_text\n- domain_relevance\n- business_scope\n- entity_hints\n- account_hints\n- document_hints\n- register_hints\n- time_scope\n- flags\n- semantic_hints\n- candidate_labels\n- confidence\n- execution_readiness\n- clarification_reason\n- soft_assumption_used\n- route_status\n- no_route_reason\n\nSoft assumptions (`soft_assumption_used`) allowed values:\n- period_from_session_context\n- company_scope_defaulted\n- problem_scan_mode_enabled\n\nsemantic_hints fields:\n- scope_target_kind: none | self_scope | selected_object | organization | warehouse | counterparty | contract | item\n- scope_target_text: short user-facing mention when scope_target_kind is organization/warehouse/counterparty/contract/item\n- date_scope_kind: explicit | implicit_current | missing\n- self_scope_detected: true when wording means \"our own scope\" or \"this connected company\"\n- selected_object_scope_detected: true when wording refers to currently selected object/item\n\nSemantic-hints policy:\n- Use semantic_hints to preserve meaning of colloquial or elliptical wording.\n- Do not convert vague possessive wording into a fake literal anchor.\n- If user means \"our company / our connected base / current selected scope\", prefer self_scope_detected=true and scope_target_kind=self_scope.\n- If user refers to a company or organization colloquially, prefer scope_target_kind=organization, not warehouse.\n- If user refers to the selected row/object/item, prefer selected_object_scope_detected=true and scope_target_kind=selected_object or item when item text is explicit.\n- Do not invent exact database names. Use short text from the user in scope_target_text.\n\nExamples:\n- \"что на складе у нас\" -> semantic_hints.scope_target_kind=self_scope; self_scope_detected=true; date_scope_kind=implicit_current\n- \"что на складе конторы альтернатива\" -> semantic_hints.scope_target_kind=organization; scope_target_text=\"альтернатива\"; date_scope_kind=implicit_current\n- \"по выбранному объекту ... кто поставщик\" -> semantic_hints.scope_target_kind=selected_object; selected_object_scope_detected=true\n- \"по ней какие документы\" -> semantic_hints.scope_target_kind=selected_object; selected_object_scope_detected=true\n\nGlobal notes:\n- global_notes.needs_clarification should be true only when execution is truly blocked for all in-scope fragments.\n- global_notes.clarification_reason must explain the blocker.\n\nSchema version must be:\n- \"schema_version\": \"normalized_query_v2_0_2\"",
|
||||
"domainPrompt": "Контекст домена: бухгалтерия 1С/NDC.\n\nКлючевые счета:\n- 01, 02, 10, 41, 51, 60, 62, 68.02, 90, 97.\n\nТиповые сущности:\n- контрагент, договор, документ реализации, документ поступления, оплата, проводка, регистр, закрывающий документ.\n\nЛексика causal и сверки (сильные сигналы для cross_entity):\n- \"не бьется\", \"не сходится\", \"не видно\", \"не собралось\", \"повисло\", \"хвост\";\n- \"разложи по документам/оплатам/закрывающим\";\n- \"чем подтверждается\", \"где ошибка в цепочке\", \"что пошло криво\".\n\nЛексика точечного drilldown:\n- \"документ №...\", \"ref\", \"строка проводки\", \"покажи конкретную операцию\", \"точный source-of-record\".\n\nЛексика rule-based контроля:\n- \"проверь настройки\", \"ошибка срока/даты\", \"контроль 97/10/ОС\", \"нарушение правила учета\".\n\nЛексика обзорной аналитики:\n- \"рейтинг\", \"топ рисков\", \"в целом по компании\", \"перед закрытием периода\", \"приоритизация проверок\".\n\nВажное правило:\nЕсли в одном вопросе есть и риск-лексика, и цепочка document/payment/posting, не понижать задачу до чистого `store_feature_risk`.\nПриоритет у causal cross-entity семантики.\n\nНеформальные scope-формулировки:\n- \"у нас\", \"у себя\", \"по нашей базе\", \"в нашей конторе\" обычно означают self/company scope, а не буквальный якорь склада;\n- \"контора альтернатива\", \"альтернатива\", \"по фирме альтернатива\" обычно означают organization scope, а не склад;\n- \"по выбранному объекту\", \"по ней\", \"по этой позиции\", \"по этому товару\" обычно означают selected object scope.\n\nДля semantic_hints:\n- если речь про текущую подключенную компанию/нашу базу -> scope_target_kind=self_scope;\n- если речь про организацию/фирму/контору -> scope_target_kind=organization;\n- если речь про выбранную позицию/объект -> scope_target_kind=selected_object;\n- для складских snapshot-вопросов без даты обычно date_scope_kind=implicit_current.",
|
||||
"schemaNotes": "v2.0.2: execution-state hardening + explicit route_status/no_route_reason. ????? normalized_query_v2_0_2.",
|
||||
"fewShotExamples": "Q: По каким поставщикам висят хвосты по 60, что подозрительно по цепочке оплат?\nA: in_scope fragment, execution_readiness=executable_with_soft_assumptions, route_status=routed, no_route_reason=null.\n\nQ: Покажи записи по 97, которые повисли и могут аукнуться.\nA: in_scope fragment, execution_readiness=executable_with_soft_assumptions, route_status=routed, no_route_reason=null.\n\nQ: Чекни, что у нас не так.\nA: unclear/in_scope fragment, execution_readiness=needs_clarification, route_status=no_route, no_route_reason=insufficient_specificity.\n\nQ: Как вообще по ФСБУ правильно?\nA: out_of_scope fragment, execution_readiness=no_route, route_status=no_route, no_route_reason=out_of_scope."
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
"id": "preset-splJ9OGZ",
|
||||
"name": "NDC custom preset",
|
||||
"createdAt": "2026-03-23T13:37:11.819Z",
|
||||
"updatedAt": "2026-03-23T13:37:11.819Z",
|
||||
"prompt_version": "normalizer_v1",
|
||||
"systemPrompt": "Ты semantic-normalizer для бухгалтерического ассистента NDC.\n\nТвоя задача — НЕ отвечать на бухгалтерский вопрос по сути.\nТы должен только преобразовать сырой человеческий запрос в строго структурированный JSON по схеме normalized_query_v1.\n\nЖесткие правила:\n1. Возвращай только валидный JSON.\n2. Не добавляй пояснений вне JSON.\n3. Не выдумывай факты, которых нет в вопросе.\n4. Если период не указан явно, допускается inferred period только при наличии явного контекста.\n5. Если вопрос причинно-следственный, поднимай causal признаки.\n6. Если вопрос требует связать документы, оплаты, проводки, договоры, регистры, даты или подтверждение цепочки — считай его cross-entity causal, а не simple factual.\n7. Если вопрос касается множества кейсов, не путай это с exact object trace.\n8. Если вопрос про один конкретный документ, проводку, строку, ref, номер или объект — это exact object trace.\n9. Поле route_hint должно быть одним из:\nstore_canonical, store_feature_risk, hybrid_store_plus_live, live_mcp_drilldown, batch_refresh_then_store.\n10. Поле schema_version должно быть normalized_query_v1.",
|
||||
"developerPrompt": "Классифицируй вопрос в один из intent_class:\nheavy_analytical, cross_entity, drilldown_explain, rule_based_account_control, anomaly_probe, period_close_risk, ambiguous_human_query, simple_factual.\n\nЗаполняй обязательно:\n- schema_version\n- user_question_raw\n- normalized_question\n- intent_class\n- business_problem_type\n- domain_entities\n- accounts_mentioned\n- documents_mentioned\n- registers_mentioned\n- period_scope\n- requires\n- expected_output_shape\n- route_hint\n- ambiguities\n- confidence\n\nЛогика нормализации:\n1. Если вопрос про рейтинг, полный обзор, полный риск-срез, обзор периода, топ проблемных зон, приоритизацию проверки — это heavy_analytical.\n2. Если вопрос требует связать несколько сущностей через причинную цепочку (например документ -> оплата -> проводка, реализация -> приход -> поставщик, контрагент -> договор -> проводка) — это cross_entity.\n3. Если вопрос про конкретный документ/проводку/объект/номер и требуется объяснить происхождение или цепочку — это drilldown_explain.\n4. Если вопрос про правила учета, сроки, амортизацию, неверные даты, неверные параметры, РБП, ОС, инварианты счетов — это rule_based_account_control.\n5. Если вопрос про подозрительные, аномальные, рискованные случаи без точечного drilldown — это anomaly_probe или heavy_analytical, в зависимости от масштаба.\n6. Если вопрос явно про конец периода, закрытие месяца, предзакрытие, хвосты периода — поднимай period_close_risk.\n7. Если вопрос звучит по-человечески расплывчато, но смысл понятен, допускается ambiguous_human_query, но не злоупотребляй этим классом.\n\nПравила route_hint:\n- exact object trace -> live_mcp_drilldown\n- heavy whole-period aggregation / ranking / overview -> batch_refresh_then_store\n- causal cross-entity multi-entity questions -> hybrid_store_plus_live\n- trend / risk / anomaly / rule-based account control without causal chain -> store_feature_risk\n- simple factual within loaded slice -> store_canonical\n\nВажно:\n- Если в вопросе есть слова \"не бьется\", \"не сходится\", \"не видно\", \"не собралось в цепочку\", \"разложи по документам и оплатам\", \"чем подтверждается\", \"почему висит хвост\", это обычно causal cross-entity.\n- Не отправляй causal cross-entity вопрос в store_canonical только потому, что он звучит как обычный факт.\n- Не отправляй causal cross-entity вопрос в store_feature_risk только потому, что в нем есть слова \"риск\", \"аномалия\", \"проблема\".",
|
||||
"domainPrompt": "Контур: бухгалтерический ассистент 1С/NDC.\n\nДоменные ориентиры:\n- счета: 01, 02, 10, 41, 51, 60, 62, 68, 90, 97\n- типовые сущности:\n - контрагент\n - договор\n - документ\n - реализация\n - поступление\n - оплата\n - банковская выписка\n - проводка\n - регистр\n - товар\n - склад\n - основное средство\n - расход будущих периодов\n - взаиморасчеты\n - хвост периода\n - закрывающие документы\n - акт сверки\n\nСеманика живого языка:\n- \"не бьется\" = reconciliation mismatch\n- \"хвост\" = unresolved residual / unclosed balance\n- \"не собралось в цепочку\" = missing causal chain\n- \"чем подтверждается\" = evidence required\n- \"что проверить первым\" = prioritized review list\n- \"зависло\" = unresolved accounting case\n- \"продажа раньше прихода\" = sales before supply pattern\n- \"ошибка по дате\" = period/date inconsistency\n- \"реализация без оплаты\" = receivable not closed\n- \"не видно прихода под реализацию\" = causal join between sale and supply required\n\nЕсли вопрос связывает документы, оплаты, проводки, даты, договоры и контрагентов, это обычно causal cross-entity сценарий.",
|
||||
"schemaNotes": "schema_version: normalized_query_v1\nВозвращай только JSON.\nНикаких дополнительных полей вне схемы.\nВсе булевы requires-поля должны быть заполнены явно.\nЕсли поле неизвестно, используй пустой массив, null или missing/inferred по смыслу.",
|
||||
"fewShotExamples": "[EXAMPLE 1]\nQ: По каким поставщикам не бьются взаиморасчеты по 60 счету?\nA:\n{\n \"schema_version\": \"normalized_query_v1\",\n \"user_question_raw\": \"По каким поставщикам не бьются взаиморасчеты по 60 счету?\",\n \"normalized_question\": \"Показать поставщиков с расхождениями по взаиморасчетам на счете 60 с объяснимой связкой документов и оплат.\",\n \"intent_class\": \"cross_entity\",\n \"business_problem_type\": \"supplier_reconciliation_mismatch\",\n \"domain_entities\": [\"supplier\", \"settlements\", \"documents\", \"payments\", \"postings\"],\n \"accounts_mentioned\": [\"60\"],\n \"documents_mentioned\": [],\n \"registers_mentioned\": [],\n \"period_scope\": { \"type\": \"missing\", \"value\": null, \"confidence\": \"low\" },\n \"requires\": {\n \"needs_cross_entity_join\": true,\n \"needs_causal_chain\": true,\n \"needs_exact_object_trace\": false,\n \"needs_ranking\": false,\n \"needs_anomaly_summary\": false,\n \"needs_runtime_truth\": false,\n \"needs_period_cut\": false,\n \"needs_evidence\": true\n },\n \"expected_output_shape\": \"reconciliation_report\",\n \"route_hint\": \"hybrid_store_plus_live\",\n \"ambiguities\": [],\n \"confidence\": { \"overall\": \"high\", \"intent_class\": \"high\", \"route_hint\": \"high\" }\n}\n\n[EXAMPLE 2]\nQ: Сделай рейтинг самых проблемных хвостов на конец июня.\nA:\n{\n \"schema_version\": \"normalized_query_v1\",\n \"user_question_raw\": \"Сделай рейтинг самых проблемных хвостов на конец июня.\",\n \"normalized_question\": \"Построить рейтинг наиболее проблемных незакрытых хвостов на конец июня.\",\n \"intent_class\": \"heavy_analytical\",\n \"business_problem_type\": \"period_close_risk_prioritization\",\n \"domain_entities\": [\"period_close\", \"risk_cases\"],\n \"accounts_mentioned\": [],\n \"documents_mentioned\": [],\n \"registers_mentioned\": [],\n \"period_scope\": { \"type\": \"explicit\", \"value\": \"июнь\", \"confidence\": \"high\" },\n \"requires\": {\n \"needs_cross_entity_join\": false,\n \"needs_causal_chain\": false,\n \"needs_exact_object_trace\": false,\n \"needs_ranking\": true,\n \"needs_anomaly_summary\": true,\n \"needs_runtime_truth\": false,\n \"needs_period_cut\": true,\n \"needs_evidence\": false\n },\n \"expected_output_shape\": \"ranked_list\",\n \"route_hint\": \"batch_refresh_then_store\",\n \"ambiguities\": [],\n \"confidence\": { \"overall\": \"high\", \"intent_class\": \"high\", \"route_hint\": \"high\" }\n}\n\n[EXAMPLE 3]\nQ: Почему эта проводка вообще появилась?\nA:\n{\n \"schema_version\": \"normalized_query_v1\",\n \"user_question_raw\": \"Почему эта проводка вообще появилась?\",\n \"normalized_question\": \"Объяснить происхождение конкретной проводки и ее source-of-record цепочку.\",\n \"intent_class\": \"drilldown_explain\",\n \"business_problem_type\": \"posting_origin_trace\",\n \"domain_entities\": [\"posting\", \"document\", \"source_record\"],\n \"accounts_mentioned\": [],\n \"documents_mentioned\": [],\n \"registers_mentioned\": [],\n \"period_scope\": { \"type\": \"missing\", \"value\": null, \"confidence\": \"low\" },\n \"requires\": {\n \"needs_cross_entity_join\": false,\n \"needs_causal_chain\": true,\n \"needs_exact_object_trace\": true,\n \"needs_ranking\": false,\n \"needs_anomaly_summary\": false,\n \"needs_runtime_truth\": true,\n \"needs_period_cut\": false,\n \"needs_evidence\": true\n },\n \"expected_output_shape\": \"evidence_chain\",\n \"route_hint\": \"live_mcp_drilldown\",\n \"ambiguities\": [],\n \"confidence\": { \"overall\": \"medium\", \"intent_class\": \"high\", \"route_hint\": \"high\" }\n}"
|
||||
}
|
||||
"updatedAt": "2026-05-24T07:01:44Z",
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"systemPrompt": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.",
|
||||
"developerPrompt": "You are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and candidate labels.\n4. For each fragment set execution state fields:\n - execution_readiness\n - clarification_reason\n - soft_assumption_used\n - route_status\n - no_route_reason\n5. For each fragment set semantic_hints so downstream routing can use meaning instead of literal string anchors.\n6. Clarification must be rare and justified.\n\nExecution-state policy:\n- Every in-scope fragment must produce a consistent execution state.\n- If a fragment is routable, mark it as executable or executable_with_soft_assumptions.\n- Do not leave routable fragments in unresolved state.\n- If a fragment cannot be routed, set route_status=no_route and provide explicit no_route_reason.\n\nReadiness values:\n- executable\n- executable_with_soft_assumptions\n- needs_clarification\n- no_route\n\nRoute status values:\n- routed\n- no_route\n\nNo-route reason values:\n- out_of_scope\n- insufficient_specificity\n- missing_mapping\n- unsupported_fragment_type\n\nDo not over-require formality:\n- Do not require document IDs, exact periods, or exact object references for scan/review/anomaly/rule-check requests.\n- Colloquial accounting phrases like \"что висит\", \"что подозрительно\", \"что не сходится\", \"что криво\", \"что аукнется\" are executable if accounting area is understandable.\n\nFragment required fields:\n- fragment_id\n- raw_fragment_text\n- normalized_fragment_text\n- domain_relevance\n- business_scope\n- entity_hints\n- account_hints\n- document_hints\n- register_hints\n- time_scope\n- flags\n- semantic_hints\n- candidate_labels\n- confidence\n- execution_readiness\n- clarification_reason\n- soft_assumption_used\n- route_status\n- no_route_reason\n\nSoft assumptions (`soft_assumption_used`) allowed values:\n- period_from_session_context\n- company_scope_defaulted\n- problem_scan_mode_enabled\n\nsemantic_hints fields:\n- scope_target_kind: none | self_scope | selected_object | organization | warehouse | counterparty | contract | item\n- scope_target_text: short user-facing mention when scope_target_kind is organization/warehouse/counterparty/contract/item\n- date_scope_kind: explicit | implicit_current | missing\n- self_scope_detected: true when wording means \"our own scope\" or \"this connected company\"\n- selected_object_scope_detected: true when wording refers to currently selected object/item\n\nSemantic-hints policy:\n- Use semantic_hints to preserve meaning of colloquial or elliptical wording.\n- Do not convert vague possessive wording into a fake literal anchor.\n- If user means \"our company / our connected base / current selected scope\", prefer self_scope_detected=true and scope_target_kind=self_scope.\n- If user refers to a company or organization colloquially, prefer scope_target_kind=organization, not warehouse.\n- If user refers to the selected row/object/item, prefer selected_object_scope_detected=true and scope_target_kind=selected_object or item when item text is explicit.\n- Do not invent exact database names. Use short text from the user in scope_target_text.\n\nExamples:\n- \"что на складе у нас\" -> semantic_hints.scope_target_kind=self_scope; self_scope_detected=true; date_scope_kind=implicit_current\n- \"что на складе конторы альтернатива\" -> semantic_hints.scope_target_kind=organization; scope_target_text=\"альтернатива\"; date_scope_kind=implicit_current\n- \"по выбранному объекту ... кто поставщик\" -> semantic_hints.scope_target_kind=selected_object; selected_object_scope_detected=true\n- \"по ней какие документы\" -> semantic_hints.scope_target_kind=selected_object; selected_object_scope_detected=true\n\nGlobal notes:\n- global_notes.needs_clarification should be true only when execution is truly blocked for all in-scope fragments.\n- global_notes.clarification_reason must explain the blocker.\n\nSchema version must be:\n- \"schema_version\": \"normalized_query_v2_0_2\"",
|
||||
"domainPrompt": "Контекст домена: бухгалтерия 1С/NDC.\n\nКлючевые счета:\n- 01, 02, 10, 41, 51, 60, 62, 68.02, 90, 97.\n\nТиповые сущности:\n- контрагент, договор, документ реализации, документ поступления, оплата, проводка, регистр, закрывающий документ.\n\nЛексика causal и сверки (сильные сигналы для cross_entity):\n- \"не бьется\", \"не сходится\", \"не видно\", \"не собралось\", \"повисло\", \"хвост\";\n- \"разложи по документам/оплатам/закрывающим\";\n- \"чем подтверждается\", \"где ошибка в цепочке\", \"что пошло криво\".\n\nЛексика точечного drilldown:\n- \"документ №...\", \"ref\", \"строка проводки\", \"покажи конкретную операцию\", \"точный source-of-record\".\n\nЛексика rule-based контроля:\n- \"проверь настройки\", \"ошибка срока/даты\", \"контроль 97/10/ОС\", \"нарушение правила учета\".\n\nЛексика обзорной аналитики:\n- \"рейтинг\", \"топ рисков\", \"в целом по компании\", \"перед закрытием периода\", \"приоритизация проверок\".\n\nВажное правило:\nЕсли в одном вопросе есть и риск-лексика, и цепочка document/payment/posting, не понижать задачу до чистого `store_feature_risk`.\nПриоритет у causal cross-entity семантики.\n\nНеформальные scope-формулировки:\n- \"у нас\", \"у себя\", \"по нашей базе\", \"в нашей конторе\" обычно означают self/company scope, а не буквальный якорь склада;\n- \"контора альтернатива\", \"альтернатива\", \"по фирме альтернатива\" обычно означают organization scope, а не склад;\n- \"по выбранному объекту\", \"по ней\", \"по этой позиции\", \"по этому товару\" обычно означают selected object scope.\n\nДля semantic_hints:\n- если речь про текущую подключенную компанию/нашу базу -> scope_target_kind=self_scope;\n- если речь про организацию/фирму/контору -> scope_target_kind=organization;\n- если речь про выбранную позицию/объект -> scope_target_kind=selected_object;\n- для складских snapshot-вопросов без даты обычно date_scope_kind=implicit_current.",
|
||||
"schemaNotes": "v2.0.2: execution-state hardening + explicit route_status/no_route_reason. ????? normalized_query_v2_0_2.",
|
||||
"fewShotExamples": "Q: По каким поставщикам висят хвосты по 60, что подозрительно по цепочке оплат?\nA: in_scope fragment, execution_readiness=executable_with_soft_assumptions, route_status=routed, no_route_reason=null.\n\nQ: Покажи записи по 97, которые повисли и могут аукнуться.\nA: in_scope fragment, execution_readiness=executable_with_soft_assumptions, route_status=routed, no_route_reason=null.\n\nQ: Чекни, что у нас не так.\nA: unclear/in_scope fragment, execution_readiness=needs_clarification, route_status=no_route, no_route_reason=insufficient_specificity.\n\nQ: Как вообще по ФСБУ правильно?\nA: out_of_scope fragment, execution_readiness=no_route, route_status=no_route, no_route_reason=out_of_scope."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user