Стабилизировать маржинальность номенклатуры 1С
This commit is contained in:
parent
473cdc3a9b
commit
a15f24f21d
|
|
@ -0,0 +1,198 @@
|
||||||
|
{
|
||||||
|
"schema_version": "domain_truth_harness_spec_v1",
|
||||||
|
"scenario_id": "agent_inventory_margin_ranking_20260523",
|
||||||
|
"domain": "inventory_margin_ranking",
|
||||||
|
"title": "AGENT | Inventory margin ranking limited answer pack",
|
||||||
|
"description": "Targeted live replay for nomenclature high/low profit questions: ask period when missing, stay in inventory/sales/cost domain, give useful limited answer when cost evidence is insufficient, and avoid fixed-assets/bank leakage.",
|
||||||
|
"bindings": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": "step_01_margin_root_needs_period",
|
||||||
|
"title": "Root asks nomenclature high/low profit without period",
|
||||||
|
"question": "Какая номеклатура товара реализована с высокой прибылью какая с низкой",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"период|месяц|квартал|год",
|
||||||
|
"номенклатур",
|
||||||
|
"выручк|себестоим|валов|маржин"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"амортизац",
|
||||||
|
"основн[а-я]+ средств",
|
||||||
|
"\\bОС\\s+как\\s+объект\\b",
|
||||||
|
"Сбербанк",
|
||||||
|
"банк",
|
||||||
|
"зависш[а-я]+ оплат",
|
||||||
|
"payment",
|
||||||
|
"settlement cluster",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive",
|
||||||
|
"90/91/99",
|
||||||
|
"7\\s*136\\s*815"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"inventory_margin_ranking",
|
||||||
|
"needs_period",
|
||||||
|
"domain_purity"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_02_may_2020_period_limited_answer",
|
||||||
|
"title": "User provides May 2020 period and gets useful limited answer",
|
||||||
|
"question": "май 2020",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"май|01\\.05\\.2020|31\\.05\\.2020|2020",
|
||||||
|
"рейтинг|прибыль|маржин",
|
||||||
|
"себестоим|90\\.02|закупоч",
|
||||||
|
"нельзя|не удалось|недостаточ|не подтвержд"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"амортизац",
|
||||||
|
"основн[а-я]+ средств",
|
||||||
|
"\\bОС\\s+как\\s+объект\\b",
|
||||||
|
"Сбербанк",
|
||||||
|
"банк",
|
||||||
|
"зависш[а-я]+ оплат",
|
||||||
|
"payment",
|
||||||
|
"settlement cluster",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive",
|
||||||
|
"7\\s*136\\s*815"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"inventory_margin_ranking",
|
||||||
|
"limited_answer",
|
||||||
|
"period_followup"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_03_show_cost_base_lines",
|
||||||
|
"title": "Follow-up asks for found cost-base evidence",
|
||||||
|
"question": "покажи найденные строки себестоимостной базы",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"себестоим",
|
||||||
|
"закупоч",
|
||||||
|
"90\\.02",
|
||||||
|
"41",
|
||||||
|
"не найден|не подтвержд|нет"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"амортизац",
|
||||||
|
"основн[а-я]+ средств",
|
||||||
|
"\\bОС\\s+как\\s+объект\\b",
|
||||||
|
"Сбербанк",
|
||||||
|
"банк",
|
||||||
|
"зависш[а-я]+ оплат",
|
||||||
|
"payment",
|
||||||
|
"settlement cluster",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive",
|
||||||
|
"7\\s*136\\s*815"
|
||||||
|
],
|
||||||
|
"criticality": "high",
|
||||||
|
"semantic_tags": [
|
||||||
|
"inventory_margin_ranking",
|
||||||
|
"evidence_followup",
|
||||||
|
"carryover"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_04_expand_to_2017",
|
||||||
|
"title": "User expands period to full 2017",
|
||||||
|
"question": "расширь до 2017 года",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"2017",
|
||||||
|
"рейтинг|маржин|прибыль",
|
||||||
|
"себестоим|90\\.02|закупоч",
|
||||||
|
"нельзя|не подтвержд|найден"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"амортизац",
|
||||||
|
"основн[а-я]+ средств",
|
||||||
|
"\\bОС\\s+как\\s+объект\\b",
|
||||||
|
"Сбербанк",
|
||||||
|
"банк",
|
||||||
|
"зависш[а-я]+ оплат",
|
||||||
|
"payment",
|
||||||
|
"settlement cluster",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive",
|
||||||
|
"7\\s*136\\s*815"
|
||||||
|
],
|
||||||
|
"criticality": "high",
|
||||||
|
"semantic_tags": [
|
||||||
|
"inventory_margin_ranking",
|
||||||
|
"period_expansion",
|
||||||
|
"carryover"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "step_05_account_41_not_01",
|
||||||
|
"title": "User corrects account family to 41 not fixed assets",
|
||||||
|
"question": "анализ по 41 счету а не 01",
|
||||||
|
"allowed_reply_types": [
|
||||||
|
"partial_coverage",
|
||||||
|
"factual_with_explanation",
|
||||||
|
"factual"
|
||||||
|
],
|
||||||
|
"required_answer_patterns_any": [
|
||||||
|
"41",
|
||||||
|
"товар|номенклатур|закупоч|себестоим",
|
||||||
|
"не 01|01"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"амортизац",
|
||||||
|
"основн[а-я]+ средств",
|
||||||
|
"ОС как объект",
|
||||||
|
"Сбербанк",
|
||||||
|
"банк",
|
||||||
|
"runtime_",
|
||||||
|
"planner_",
|
||||||
|
"query_movements",
|
||||||
|
"primitive"
|
||||||
|
],
|
||||||
|
"criticality": "critical",
|
||||||
|
"semantic_tags": [
|
||||||
|
"inventory_margin_ranking",
|
||||||
|
"account_family_guard",
|
||||||
|
"no_fixed_assets"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"acceptance": {
|
||||||
|
"min_score": 80,
|
||||||
|
"max_unresolved_p0": 0,
|
||||||
|
"require_all_critical_steps_pass": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -81,6 +81,13 @@ function inventoryProfitabilityPeriodLabel(options, deps) {
|
||||||
const asOfDate = typeof options.asOfDate === "string" && options.asOfDate.trim().length > 0 ? options.asOfDate : null;
|
const asOfDate = typeof options.asOfDate === "string" && options.asOfDate.trim().length > 0 ? options.asOfDate : null;
|
||||||
return asOfDate ? `до ${deps.formatDateRu(asOfDate)}` : "по доступной выборке";
|
return asOfDate ? `до ${deps.formatDateRu(asOfDate)}` : "по доступной выборке";
|
||||||
}
|
}
|
||||||
|
function asksForInventoryCostBaseRows(userMessage) {
|
||||||
|
const text = String(userMessage ?? "").toLowerCase();
|
||||||
|
if (!/(?:покажи|показать|выведи|вывести|дай|дать|раскрой|раскрыть|строк|строки|строку|баз)/iu.test(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /(?:себестоимостн|себестоимост|себестоим|закупочн|закупк|90\.02|\b41\b|баз)/iu.test(text);
|
||||||
|
}
|
||||||
function inventoryRowItemLabel(row, deps) {
|
function inventoryRowItemLabel(row, deps) {
|
||||||
return deps.summarizeInventoryTraceRows([row]).item;
|
return deps.summarizeInventoryTraceRows([row]).item;
|
||||||
}
|
}
|
||||||
|
|
@ -459,7 +466,12 @@ function composeInventoryReply(intent, rows, options, deps) {
|
||||||
const totalCostProxy = entries.reduce((sum, entry) => sum + entry.costProxy, 0);
|
const totalCostProxy = entries.reduce((sum, entry) => sum + entry.costProxy, 0);
|
||||||
const totalSpread = totalRevenue - totalCostProxy;
|
const totalSpread = totalRevenue - totalCostProxy;
|
||||||
if (confirmedEntries.length === 0) {
|
if (confirmedEntries.length === 0) {
|
||||||
const lines = [`За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`];
|
const costBaseRowsRequested = asksForInventoryCostBaseRows(options.userMessage);
|
||||||
|
const lines = [
|
||||||
|
costBaseRowsRequested && purchasesWithoutSales.length === 0
|
||||||
|
? `За период ${periodLabel} подтвержденных строк себестоимостной базы по реализованной номенклатуре не найдено.`
|
||||||
|
: `За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`
|
||||||
|
];
|
||||||
const findings = [];
|
const findings = [];
|
||||||
if (salesWithoutCost.length > 0) {
|
if (salesWithoutCost.length > 0) {
|
||||||
const salesCount = deps.formatNumberWithDots(salesWithoutCost.length);
|
const salesCount = deps.formatNumberWithDots(salesWithoutCost.length);
|
||||||
|
|
@ -470,6 +482,9 @@ function composeInventoryReply(intent, rows, options, deps) {
|
||||||
: "Подтвержденной себестоимости реализации по этим позициям не найдено.");
|
: "Подтвержденной себестоимости реализации по этим позициям не найдено.");
|
||||||
findings.push("Поэтому валовую прибыль и маржинальность честно посчитать нельзя.");
|
findings.push("Поэтому валовую прибыль и маржинальность честно посчитать нельзя.");
|
||||||
}
|
}
|
||||||
|
if (costBaseRowsRequested && purchasesWithoutSales.length === 0) {
|
||||||
|
findings.push("Строк себестоимости реализации / себестоимостной базы для показа нет.");
|
||||||
|
}
|
||||||
if (purchasesWithoutSales.length > 0) {
|
if (purchasesWithoutSales.length > 0) {
|
||||||
const purchaseCount = deps.formatNumberWithDots(purchasesWithoutSales.length);
|
const purchaseCount = deps.formatNumberWithDots(purchasesWithoutSales.length);
|
||||||
const purchaseItemPhrase = purchasesWithoutSales.length === 1 ? "1 позиции" : `${purchaseCount} позициям`;
|
const purchaseItemPhrase = purchasesWithoutSales.length === 1 ? "1 позиции" : `${purchaseCount} позициям`;
|
||||||
|
|
@ -493,7 +508,7 @@ function composeInventoryReply(intent, rows, options, deps) {
|
||||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Что можно сделать дальше:", nextActions);
|
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Что можно сделать дальше:", nextActions);
|
||||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Граница ответа:", [
|
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Граница ответа:", [
|
||||||
"Прибыльность номенклатуры считаю только когда есть реализация и подтвержденная себестоимость реализации.",
|
"Прибыльность номенклатуры считаю только когда есть реализация и подтвержденная себестоимость реализации.",
|
||||||
"Это не чистая прибыль компании и не замена закрытию месяца."
|
"Это не показатель чистой прибыли и не замена закрытию месяца."
|
||||||
]);
|
]);
|
||||||
return (0, replyContracts_1.buildFactualSummaryReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)(entries.length > 0 ? "medium" : "weak", false));
|
return (0, replyContracts_1.buildFactualSummaryReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)(entries.length > 0 ? "medium" : "weak", false));
|
||||||
}
|
}
|
||||||
|
|
@ -508,7 +523,7 @@ function composeInventoryReply(intent, rows, options, deps) {
|
||||||
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Низкая или отрицательная валовая маржинальность:", lowMargin.map((entry, index) => formatInventoryMarginRankingLine(entry, index, deps)));
|
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Низкая или отрицательная валовая маржинальность:", lowMargin.map((entry, index) => formatInventoryMarginRankingLine(entry, index, deps)));
|
||||||
}
|
}
|
||||||
const boundaryLines = [
|
const boundaryLines = [
|
||||||
"Это управленческий расчет валовой маржинальности по реализации и доступной себестоимостной базе, не чистая прибыль компании.",
|
"Это управленческий расчет валовой маржинальности по реализации и доступной себестоимостной базе, не показатель чистой прибыли.",
|
||||||
"Для строгого бухгалтерского расчета нужны проводки 90.01 / 90.02 и закрытие себестоимости; этот ответ не подменяет закрытие месяца."
|
"Для строгого бухгалтерского расчета нужны проводки 90.01 / 90.02 и закрытие себестоимости; этот ответ не подменяет закрытие месяца."
|
||||||
];
|
];
|
||||||
if (salesWithoutCost.length > 0) {
|
if (salesWithoutCost.length > 0) {
|
||||||
|
|
|
||||||
|
|
@ -350,6 +350,24 @@ function hasExactBankOperationsAddressReply(input, entryPoint) {
|
||||||
routeMode === "exact" ||
|
routeMode === "exact" ||
|
||||||
hasFullConfirmedTruth(input));
|
hasFullConfirmedTruth(input));
|
||||||
}
|
}
|
||||||
|
function hasInventoryMarginRankingAddressReply(input, entryPoint) {
|
||||||
|
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!toNonEmptyString(input.currentReply)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||||
|
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||||
|
const capabilityId = toNonEmptyString(input.addressRuntimeMeta?.capability_id) ??
|
||||||
|
toNonEmptyString(input.addressRuntimeMeta?.capability_contract_id);
|
||||||
|
return Boolean(detectedIntent === "inventory_margin_ranking_for_nomenclature" ||
|
||||||
|
selectedRecipe === "address_inventory_margin_ranking_for_nomenclature_v1" ||
|
||||||
|
capabilityId === "inventory_inventory_margin_ranking_for_nomenclature");
|
||||||
|
}
|
||||||
function hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint) {
|
function hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint) {
|
||||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -621,6 +639,7 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
||||||
const staleMetadataDiscoveryFallbackAgainstExactAddressReply = hasStaleMetadataDiscoveryFallbackAgainstExactAddressReply(input, entryPoint);
|
const staleMetadataDiscoveryFallbackAgainstExactAddressReply = hasStaleMetadataDiscoveryFallbackAgainstExactAddressReply(input, entryPoint);
|
||||||
const exactValueFlowReplyForBusinessOverviewDirectMoneyNeed = hasExactValueFlowReplyForBusinessOverviewDirectMoneyNeed(input, entryPoint);
|
const exactValueFlowReplyForBusinessOverviewDirectMoneyNeed = hasExactValueFlowReplyForBusinessOverviewDirectMoneyNeed(input, entryPoint);
|
||||||
const exactBankOperationsAddressReply = hasExactBankOperationsAddressReply(input, entryPoint);
|
const exactBankOperationsAddressReply = hasExactBankOperationsAddressReply(input, entryPoint);
|
||||||
|
const inventoryMarginRankingAddressReply = hasInventoryMarginRankingAddressReply(input, entryPoint);
|
||||||
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
|
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
|
||||||
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
|
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
|
||||||
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint);
|
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint);
|
||||||
|
|
@ -685,6 +704,9 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
||||||
if (exactBankOperationsAddressReply) {
|
if (exactBankOperationsAddressReply) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_bank_operations_address_reply");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_bank_operations_address_reply");
|
||||||
}
|
}
|
||||||
|
if (inventoryMarginRankingAddressReply) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_inventory_margin_ranking_address_reply");
|
||||||
|
}
|
||||||
if (deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") {
|
if (deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_broad_business_summary_over_clarification_candidate");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_broad_business_summary_over_clarification_candidate");
|
||||||
}
|
}
|
||||||
|
|
@ -715,6 +737,7 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
||||||
!staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
|
!staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
|
||||||
!exactValueFlowReplyForBusinessOverviewDirectMoneyNeed &&
|
!exactValueFlowReplyForBusinessOverviewDirectMoneyNeed &&
|
||||||
!exactBankOperationsAddressReply &&
|
!exactBankOperationsAddressReply &&
|
||||||
|
!inventoryMarginRankingAddressReply &&
|
||||||
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
||||||
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
||||||
candidate.eligible_for_future_hot_runtime &&
|
candidate.eligible_for_future_hot_runtime &&
|
||||||
|
|
|
||||||
|
|
@ -315,6 +315,21 @@ function createAssistantRoutePolicy(deps) {
|
||||||
const hasTemporalCue = /(?:на\s+эту\s+же\s+дат[ауеы]|на\s+тот\s+же\s+период|за\s+этот\s+же\s+период|за\s+этот\s+период|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр|\b(?:19|20)\d{2}\b)/iu.test(normalized);
|
const hasTemporalCue = /(?:на\s+эту\s+же\s+дат[ауеы]|на\s+тот\s+же\s+период|за\s+этот\s+же\s+период|за\s+этот\s+период|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр|\b(?:19|20)\d{2}\b)/iu.test(normalized);
|
||||||
return hasRequestCue && hasTemporalCue;
|
return hasRequestCue && hasTemporalCue;
|
||||||
}
|
}
|
||||||
|
function hasInventoryMarginRankingContinuationSignal(text) {
|
||||||
|
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()).replace(/ё/g, "е");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const wantsFoundRows = /(?:покажи|показать|выведи|дай|раскрой|show|list)/iu.test(normalized) &&
|
||||||
|
/(?:найденн|строк|реализац|себестоимостн|баз)/iu.test(normalized) &&
|
||||||
|
/(?:себестоимостн|реализац|марж|прибыл|номенклатур)/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)/iu.test(normalized);
|
||||||
|
const periodExpansion = /(?:расширь|расширить|возьми|давай|покажи|за|на|до|весь|год|квартал|месяц|expand|period)/iu.test(normalized) &&
|
||||||
|
/(?:январ|феврал|март|апрел|ма[йяе]|июн|июл|август|сентябр|октябр|ноябр|декабр|\b(?:19|20)\d{2}\b)/iu.test(normalized);
|
||||||
|
return wantsFoundRows || account41Not01 || periodExpansion;
|
||||||
|
}
|
||||||
function hasOrganizationClarificationTextCue(text) {
|
function hasOrganizationClarificationTextCue(text) {
|
||||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -481,6 +496,7 @@ function createAssistantRoutePolicy(deps) {
|
||||||
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage);
|
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage);
|
||||||
const followupPreviousIntent = toNonEmptyString(followupContext?.previous_intent);
|
const followupPreviousIntent = toNonEmptyString(followupContext?.previous_intent);
|
||||||
|
const followupRootIntent = toNonEmptyString(followupContext?.root_intent);
|
||||||
const followupPreviousFilters = followupContext?.previous_filters && typeof followupContext.previous_filters === "object"
|
const followupPreviousFilters = followupContext?.previous_filters && typeof followupContext.previous_filters === "object"
|
||||||
? followupContext.previous_filters
|
? followupContext.previous_filters
|
||||||
: null;
|
: null;
|
||||||
|
|
@ -515,6 +531,16 @@ function createAssistantRoutePolicy(deps) {
|
||||||
hasShortInventoryObjectFollowupSignal(repairedRawUserMessage) ||
|
hasShortInventoryObjectFollowupSignal(repairedRawUserMessage) ||
|
||||||
hasShortInventoryObjectFollowupSignal(effectiveAddressUserMessage) ||
|
hasShortInventoryObjectFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
hasShortInventoryObjectFollowupSignal(repairedEffectiveAddressUserMessage)));
|
hasShortInventoryObjectFollowupSignal(repairedEffectiveAddressUserMessage)));
|
||||||
|
const protectedInventoryMarginRankingFollowup = Boolean(followupContext &&
|
||||||
|
(followupPreviousIntent === "inventory_margin_ranking_for_nomenclature" ||
|
||||||
|
followupRootIntent === "inventory_margin_ranking_for_nomenclature") &&
|
||||||
|
!dataScopeMetaQuery &&
|
||||||
|
!capabilityMetaQuery &&
|
||||||
|
!dangerOrCoercionSignal &&
|
||||||
|
(hasInventoryMarginRankingContinuationSignal(rawUserMessage) ||
|
||||||
|
hasInventoryMarginRankingContinuationSignal(repairedRawUserMessage) ||
|
||||||
|
hasInventoryMarginRankingContinuationSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasInventoryMarginRankingContinuationSignal(repairedEffectiveAddressUserMessage)));
|
||||||
const organizationClarificationContinuationDetected = Boolean((followupContext || continuitySnapshot.hasGroundedAddressContext) &&
|
const organizationClarificationContinuationDetected = Boolean((followupContext || continuitySnapshot.hasGroundedAddressContext) &&
|
||||||
lastOrganizationClarificationDebug &&
|
lastOrganizationClarificationDebug &&
|
||||||
explicitOrganizationClarificationSelection &&
|
explicitOrganizationClarificationSelection &&
|
||||||
|
|
@ -571,6 +597,7 @@ function createAssistantRoutePolicy(deps) {
|
||||||
!baseToolGatePreservesAddressLane &&
|
!baseToolGatePreservesAddressLane &&
|
||||||
!effectiveGroundedValueFlowFollowupContextDetected &&
|
!effectiveGroundedValueFlowFollowupContextDetected &&
|
||||||
!protectedInventoryShortFollowup &&
|
!protectedInventoryShortFollowup &&
|
||||||
|
!protectedInventoryMarginRankingFollowup &&
|
||||||
!organizationClarificationContinuationDetected &&
|
!organizationClarificationContinuationDetected &&
|
||||||
!routeCandidateOrganizationClarificationDetected);
|
!routeCandidateOrganizationClarificationDetected);
|
||||||
const lastAddressAssistantDebug = sessionItems
|
const lastAddressAssistantDebug = sessionItems
|
||||||
|
|
@ -1080,6 +1107,7 @@ function createAssistantRoutePolicy(deps) {
|
||||||
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
||||||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage) ||
|
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
protectedInventoryMarginRankingFollowup ||
|
||||||
inventoryRootRestatementFollowupDetected);
|
inventoryRootRestatementFollowupDetected);
|
||||||
const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) ||
|
const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) ||
|
||||||
hasDeepAnalysisPreferenceSignal(repairedRawUserMessage) ||
|
hasDeepAnalysisPreferenceSignal(repairedRawUserMessage) ||
|
||||||
|
|
@ -1116,7 +1144,9 @@ function createAssistantRoutePolicy(deps) {
|
||||||
resolvedIntentResolution.intent === "unknown" &&
|
resolvedIntentResolution.intent === "unknown" &&
|
||||||
(!llmContractIntent || llmContractIntent === "unknown"));
|
(!llmContractIntent || llmContractIntent === "unknown"));
|
||||||
const exactAddressIntentProtectedFromSemanticDeepHint = laneProtectionArbitration.exactAddressIntentProtectedFromSemanticDeepHint;
|
const exactAddressIntentProtectedFromSemanticDeepHint = laneProtectionArbitration.exactAddressIntentProtectedFromSemanticDeepHint;
|
||||||
const protectAddressLaneFromFallback = Boolean(laneProtectionArbitration.protectAddressLaneFromFallback || customerValueRankingAddressSignal);
|
const protectAddressLaneFromFallback = Boolean(laneProtectionArbitration.protectAddressLaneFromFallback ||
|
||||||
|
customerValueRankingAddressSignal ||
|
||||||
|
protectedInventoryMarginRankingFollowup);
|
||||||
const vatExplainFollowupSignal = Boolean(followupContext &&
|
const vatExplainFollowupSignal = Boolean(followupContext &&
|
||||||
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
||||||
/(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
/(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
||||||
|
|
@ -1196,7 +1226,7 @@ function createAssistantRoutePolicy(deps) {
|
||||||
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
||||||
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
||||||
const semanticAddressLaneRecovery = Boolean(!runAddressLane &&
|
const semanticAddressLaneRecovery = Boolean(!runAddressLane &&
|
||||||
supportedAddressRouteCandidateDetected &&
|
(supportedAddressRouteCandidateDetected || protectedInventoryMarginRankingFollowup) &&
|
||||||
!deepAnalysisPreferenceDetected &&
|
!deepAnalysisPreferenceDetected &&
|
||||||
!unsupportedAddressIntentFallbackToDeep &&
|
!unsupportedAddressIntentFallbackToDeep &&
|
||||||
!deepAnalysisSignalFallbackToDeep &&
|
!deepAnalysisSignalFallbackToDeep &&
|
||||||
|
|
@ -1205,7 +1235,9 @@ function createAssistantRoutePolicy(deps) {
|
||||||
if (semanticAddressLaneRecovery) {
|
if (semanticAddressLaneRecovery) {
|
||||||
runAddressLane = true;
|
runAddressLane = true;
|
||||||
toolGateDecision = "run_address_lane";
|
toolGateDecision = "run_address_lane";
|
||||||
toolGateReason = resolvedIntentResolution.intent !== "unknown" || llmContractIntent
|
toolGateReason = protectedInventoryMarginRankingFollowup
|
||||||
|
? "followup_context_detected"
|
||||||
|
: resolvedIntentResolution.intent !== "unknown" || llmContractIntent
|
||||||
? "address_intent_resolver_detected"
|
? "address_intent_resolver_detected"
|
||||||
: "address_signal_detected";
|
: "address_signal_detected";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,14 @@ function inventoryProfitabilityPeriodLabel(options: InventoryComposeOptions, dep
|
||||||
return asOfDate ? `до ${deps.formatDateRu(asOfDate)}` : "по доступной выборке";
|
return asOfDate ? `до ${deps.formatDateRu(asOfDate)}` : "по доступной выборке";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function asksForInventoryCostBaseRows(userMessage: string | null | undefined): boolean {
|
||||||
|
const text = String(userMessage ?? "").toLowerCase();
|
||||||
|
if (!/(?:покажи|показать|выведи|вывести|дай|дать|раскрой|раскрыть|строк|строки|строку|баз)/iu.test(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /(?:себестоимостн|себестоимост|себестоим|закупочн|закупк|90\.02|\b41\b|баз)/iu.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
interface InventoryMarginRankingEntry {
|
interface InventoryMarginRankingEntry {
|
||||||
item: string;
|
item: string;
|
||||||
revenue: number;
|
revenue: number;
|
||||||
|
|
@ -631,7 +639,12 @@ export function composeInventoryReply(
|
||||||
const totalCostProxy = entries.reduce((sum, entry) => sum + entry.costProxy, 0);
|
const totalCostProxy = entries.reduce((sum, entry) => sum + entry.costProxy, 0);
|
||||||
const totalSpread = totalRevenue - totalCostProxy;
|
const totalSpread = totalRevenue - totalCostProxy;
|
||||||
if (confirmedEntries.length === 0) {
|
if (confirmedEntries.length === 0) {
|
||||||
const lines: string[] = [`За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`];
|
const costBaseRowsRequested = asksForInventoryCostBaseRows(options.userMessage);
|
||||||
|
const lines: string[] = [
|
||||||
|
costBaseRowsRequested && purchasesWithoutSales.length === 0
|
||||||
|
? `За период ${periodLabel} подтвержденных строк себестоимостной базы по реализованной номенклатуре не найдено.`
|
||||||
|
: `За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`
|
||||||
|
];
|
||||||
const findings: string[] = [];
|
const findings: string[] = [];
|
||||||
if (salesWithoutCost.length > 0) {
|
if (salesWithoutCost.length > 0) {
|
||||||
const salesCount = deps.formatNumberWithDots(salesWithoutCost.length);
|
const salesCount = deps.formatNumberWithDots(salesWithoutCost.length);
|
||||||
|
|
@ -647,6 +660,9 @@ export function composeInventoryReply(
|
||||||
);
|
);
|
||||||
findings.push("Поэтому валовую прибыль и маржинальность честно посчитать нельзя.");
|
findings.push("Поэтому валовую прибыль и маржинальность честно посчитать нельзя.");
|
||||||
}
|
}
|
||||||
|
if (costBaseRowsRequested && purchasesWithoutSales.length === 0) {
|
||||||
|
findings.push("Строк себестоимости реализации / себестоимостной базы для показа нет.");
|
||||||
|
}
|
||||||
if (purchasesWithoutSales.length > 0) {
|
if (purchasesWithoutSales.length > 0) {
|
||||||
const purchaseCount = deps.formatNumberWithDots(purchasesWithoutSales.length);
|
const purchaseCount = deps.formatNumberWithDots(purchasesWithoutSales.length);
|
||||||
const purchaseItemPhrase =
|
const purchaseItemPhrase =
|
||||||
|
|
@ -679,7 +695,7 @@ export function composeInventoryReply(
|
||||||
appendInventoryBulletSection(lines, "Что можно сделать дальше:", nextActions);
|
appendInventoryBulletSection(lines, "Что можно сделать дальше:", nextActions);
|
||||||
appendInventoryBulletSection(lines, "Граница ответа:", [
|
appendInventoryBulletSection(lines, "Граница ответа:", [
|
||||||
"Прибыльность номенклатуры считаю только когда есть реализация и подтвержденная себестоимость реализации.",
|
"Прибыльность номенклатуры считаю только когда есть реализация и подтвержденная себестоимость реализации.",
|
||||||
"Это не чистая прибыль компании и не замена закрытию месяца."
|
"Это не показатель чистой прибыли и не замена закрытию месяца."
|
||||||
]);
|
]);
|
||||||
return buildFactualSummaryReply(lines, buildConfirmedBalanceSemantics(entries.length > 0 ? "medium" : "weak", false));
|
return buildFactualSummaryReply(lines, buildConfirmedBalanceSemantics(entries.length > 0 ? "medium" : "weak", false));
|
||||||
}
|
}
|
||||||
|
|
@ -709,7 +725,7 @@ export function composeInventoryReply(
|
||||||
}
|
}
|
||||||
|
|
||||||
const boundaryLines = [
|
const boundaryLines = [
|
||||||
"Это управленческий расчет валовой маржинальности по реализации и доступной себестоимостной базе, не чистая прибыль компании.",
|
"Это управленческий расчет валовой маржинальности по реализации и доступной себестоимостной базе, не показатель чистой прибыли.",
|
||||||
"Для строгого бухгалтерского расчета нужны проводки 90.01 / 90.02 и закрытие себестоимости; этот ответ не подменяет закрытие месяца."
|
"Для строгого бухгалтерского расчета нужны проводки 90.01 / 90.02 и закрытие себестоимости; этот ответ не подменяет закрытие месяца."
|
||||||
];
|
];
|
||||||
if (salesWithoutCost.length > 0) {
|
if (salesWithoutCost.length > 0) {
|
||||||
|
|
|
||||||
|
|
@ -496,6 +496,31 @@ function hasExactBankOperationsAddressReply(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasInventoryMarginRankingAddressReply(
|
||||||
|
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||||
|
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||||
|
): boolean {
|
||||||
|
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!toNonEmptyString(input.currentReply)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasMetadataDiscoveryPriority(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||||
|
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||||
|
const capabilityId =
|
||||||
|
toNonEmptyString(input.addressRuntimeMeta?.capability_id) ??
|
||||||
|
toNonEmptyString(input.addressRuntimeMeta?.capability_contract_id);
|
||||||
|
return Boolean(
|
||||||
|
detectedIntent === "inventory_margin_ranking_for_nomenclature" ||
|
||||||
|
selectedRecipe === "address_inventory_margin_ranking_for_nomenclature_v1" ||
|
||||||
|
capabilityId === "inventory_inventory_margin_ranking_for_nomenclature"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function hasValueFlowActionConflictWithDiscoveryTurnMeaning(
|
function hasValueFlowActionConflictWithDiscoveryTurnMeaning(
|
||||||
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||||
|
|
@ -834,6 +859,7 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
||||||
entryPoint
|
entryPoint
|
||||||
);
|
);
|
||||||
const exactBankOperationsAddressReply = hasExactBankOperationsAddressReply(input, entryPoint);
|
const exactBankOperationsAddressReply = hasExactBankOperationsAddressReply(input, entryPoint);
|
||||||
|
const inventoryMarginRankingAddressReply = hasInventoryMarginRankingAddressReply(input, entryPoint);
|
||||||
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
|
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
|
||||||
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
|
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
|
||||||
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(
|
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(
|
||||||
|
|
@ -917,6 +943,9 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
||||||
if (exactBankOperationsAddressReply) {
|
if (exactBankOperationsAddressReply) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_bank_operations_address_reply");
|
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_exact_bank_operations_address_reply");
|
||||||
}
|
}
|
||||||
|
if (inventoryMarginRankingAddressReply) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_inventory_margin_ranking_address_reply");
|
||||||
|
}
|
||||||
if (deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") {
|
if (deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") {
|
||||||
pushReason(
|
pushReason(
|
||||||
reasonCodes,
|
reasonCodes,
|
||||||
|
|
@ -952,6 +981,7 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
||||||
!staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
|
!staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
|
||||||
!exactValueFlowReplyForBusinessOverviewDirectMoneyNeed &&
|
!exactValueFlowReplyForBusinessOverviewDirectMoneyNeed &&
|
||||||
!exactBankOperationsAddressReply &&
|
!exactBankOperationsAddressReply &&
|
||||||
|
!inventoryMarginRankingAddressReply &&
|
||||||
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
||||||
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
||||||
candidate.eligible_for_future_hot_runtime &&
|
candidate.eligible_for_future_hot_runtime &&
|
||||||
|
|
|
||||||
|
|
@ -397,6 +397,24 @@ export function createAssistantRoutePolicy(deps) {
|
||||||
const hasTemporalCue = /(?:на\s+эту\s+же\s+дат[ауеы]|на\s+тот\s+же\s+период|за\s+этот\s+же\s+период|за\s+этот\s+период|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр|\b(?:19|20)\d{2}\b)/iu.test(normalized);
|
const hasTemporalCue = /(?:на\s+эту\s+же\s+дат[ауеы]|на\s+тот\s+же\s+период|за\s+этот\s+же\s+период|за\s+этот\s+период|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр|\b(?:19|20)\d{2}\b)/iu.test(normalized);
|
||||||
return hasRequestCue && hasTemporalCue;
|
return hasRequestCue && hasTemporalCue;
|
||||||
}
|
}
|
||||||
|
function hasInventoryMarginRankingContinuationSignal(text) {
|
||||||
|
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()).replace(/ё/g, "е");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const wantsFoundRows =
|
||||||
|
/(?:покажи|показать|выведи|дай|раскрой|show|list)/iu.test(normalized) &&
|
||||||
|
/(?:найденн|строк|реализац|себестоимостн|баз)/iu.test(normalized) &&
|
||||||
|
/(?:себестоимостн|реализац|марж|прибыл|номенклатур)/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)/iu.test(normalized);
|
||||||
|
const periodExpansion =
|
||||||
|
/(?:расширь|расширить|возьми|давай|покажи|за|на|до|весь|год|квартал|месяц|expand|period)/iu.test(normalized) &&
|
||||||
|
/(?:январ|феврал|март|апрел|ма[йяе]|июн|июл|август|сентябр|октябр|ноябр|декабр|\b(?:19|20)\d{2}\b)/iu.test(normalized);
|
||||||
|
return wantsFoundRows || account41Not01 || periodExpansion;
|
||||||
|
}
|
||||||
function hasOrganizationClarificationTextCue(text) {
|
function hasOrganizationClarificationTextCue(text) {
|
||||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -565,6 +583,7 @@ export function createAssistantRoutePolicy(deps) {
|
||||||
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage);
|
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage);
|
||||||
const followupPreviousIntent = toNonEmptyString(followupContext?.previous_intent);
|
const followupPreviousIntent = toNonEmptyString(followupContext?.previous_intent);
|
||||||
|
const followupRootIntent = toNonEmptyString(followupContext?.root_intent);
|
||||||
const followupPreviousFilters = followupContext?.previous_filters && typeof followupContext.previous_filters === "object"
|
const followupPreviousFilters = followupContext?.previous_filters && typeof followupContext.previous_filters === "object"
|
||||||
? followupContext.previous_filters
|
? followupContext.previous_filters
|
||||||
: null;
|
: null;
|
||||||
|
|
@ -599,6 +618,16 @@ export function createAssistantRoutePolicy(deps) {
|
||||||
hasShortInventoryObjectFollowupSignal(repairedRawUserMessage) ||
|
hasShortInventoryObjectFollowupSignal(repairedRawUserMessage) ||
|
||||||
hasShortInventoryObjectFollowupSignal(effectiveAddressUserMessage) ||
|
hasShortInventoryObjectFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
hasShortInventoryObjectFollowupSignal(repairedEffectiveAddressUserMessage)));
|
hasShortInventoryObjectFollowupSignal(repairedEffectiveAddressUserMessage)));
|
||||||
|
const protectedInventoryMarginRankingFollowup = Boolean(followupContext &&
|
||||||
|
(followupPreviousIntent === "inventory_margin_ranking_for_nomenclature" ||
|
||||||
|
followupRootIntent === "inventory_margin_ranking_for_nomenclature") &&
|
||||||
|
!dataScopeMetaQuery &&
|
||||||
|
!capabilityMetaQuery &&
|
||||||
|
!dangerOrCoercionSignal &&
|
||||||
|
(hasInventoryMarginRankingContinuationSignal(rawUserMessage) ||
|
||||||
|
hasInventoryMarginRankingContinuationSignal(repairedRawUserMessage) ||
|
||||||
|
hasInventoryMarginRankingContinuationSignal(effectiveAddressUserMessage) ||
|
||||||
|
hasInventoryMarginRankingContinuationSignal(repairedEffectiveAddressUserMessage)));
|
||||||
const organizationClarificationContinuationDetected = Boolean((followupContext || continuitySnapshot.hasGroundedAddressContext) &&
|
const organizationClarificationContinuationDetected = Boolean((followupContext || continuitySnapshot.hasGroundedAddressContext) &&
|
||||||
lastOrganizationClarificationDebug &&
|
lastOrganizationClarificationDebug &&
|
||||||
explicitOrganizationClarificationSelection &&
|
explicitOrganizationClarificationSelection &&
|
||||||
|
|
@ -656,6 +685,7 @@ export function createAssistantRoutePolicy(deps) {
|
||||||
!baseToolGatePreservesAddressLane &&
|
!baseToolGatePreservesAddressLane &&
|
||||||
!effectiveGroundedValueFlowFollowupContextDetected &&
|
!effectiveGroundedValueFlowFollowupContextDetected &&
|
||||||
!protectedInventoryShortFollowup &&
|
!protectedInventoryShortFollowup &&
|
||||||
|
!protectedInventoryMarginRankingFollowup &&
|
||||||
!organizationClarificationContinuationDetected &&
|
!organizationClarificationContinuationDetected &&
|
||||||
!routeCandidateOrganizationClarificationDetected);
|
!routeCandidateOrganizationClarificationDetected);
|
||||||
const lastAddressAssistantDebug = sessionItems
|
const lastAddressAssistantDebug = sessionItems
|
||||||
|
|
@ -1167,6 +1197,7 @@ export function createAssistantRoutePolicy(deps) {
|
||||||
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
||||||
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
||||||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage) ||
|
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage) ||
|
||||||
|
protectedInventoryMarginRankingFollowup ||
|
||||||
inventoryRootRestatementFollowupDetected);
|
inventoryRootRestatementFollowupDetected);
|
||||||
const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) ||
|
const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) ||
|
||||||
hasDeepAnalysisPreferenceSignal(repairedRawUserMessage) ||
|
hasDeepAnalysisPreferenceSignal(repairedRawUserMessage) ||
|
||||||
|
|
@ -1204,7 +1235,9 @@ export function createAssistantRoutePolicy(deps) {
|
||||||
(!llmContractIntent || llmContractIntent === "unknown"));
|
(!llmContractIntent || llmContractIntent === "unknown"));
|
||||||
const exactAddressIntentProtectedFromSemanticDeepHint = laneProtectionArbitration.exactAddressIntentProtectedFromSemanticDeepHint;
|
const exactAddressIntentProtectedFromSemanticDeepHint = laneProtectionArbitration.exactAddressIntentProtectedFromSemanticDeepHint;
|
||||||
const protectAddressLaneFromFallback = Boolean(
|
const protectAddressLaneFromFallback = Boolean(
|
||||||
laneProtectionArbitration.protectAddressLaneFromFallback || customerValueRankingAddressSignal
|
laneProtectionArbitration.protectAddressLaneFromFallback ||
|
||||||
|
customerValueRankingAddressSignal ||
|
||||||
|
protectedInventoryMarginRankingFollowup
|
||||||
);
|
);
|
||||||
const vatExplainFollowupSignal = Boolean(followupContext &&
|
const vatExplainFollowupSignal = Boolean(followupContext &&
|
||||||
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
||||||
|
|
@ -1285,7 +1318,7 @@ export function createAssistantRoutePolicy(deps) {
|
||||||
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
||||||
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
||||||
const semanticAddressLaneRecovery = Boolean(!runAddressLane &&
|
const semanticAddressLaneRecovery = Boolean(!runAddressLane &&
|
||||||
supportedAddressRouteCandidateDetected &&
|
(supportedAddressRouteCandidateDetected || protectedInventoryMarginRankingFollowup) &&
|
||||||
!deepAnalysisPreferenceDetected &&
|
!deepAnalysisPreferenceDetected &&
|
||||||
!unsupportedAddressIntentFallbackToDeep &&
|
!unsupportedAddressIntentFallbackToDeep &&
|
||||||
!deepAnalysisSignalFallbackToDeep &&
|
!deepAnalysisSignalFallbackToDeep &&
|
||||||
|
|
@ -1294,7 +1327,9 @@ export function createAssistantRoutePolicy(deps) {
|
||||||
if (semanticAddressLaneRecovery) {
|
if (semanticAddressLaneRecovery) {
|
||||||
runAddressLane = true;
|
runAddressLane = true;
|
||||||
toolGateDecision = "run_address_lane";
|
toolGateDecision = "run_address_lane";
|
||||||
toolGateReason = resolvedIntentResolution.intent !== "unknown" || llmContractIntent
|
toolGateReason = protectedInventoryMarginRankingFollowup
|
||||||
|
? "followup_context_detected"
|
||||||
|
: resolvedIntentResolution.intent !== "unknown" || llmContractIntent
|
||||||
? "address_intent_resolver_detected"
|
? "address_intent_resolver_detected"
|
||||||
: "address_signal_detected";
|
: "address_signal_detected";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -387,7 +387,7 @@ describe("inventory profitability selected-object regressions", () => {
|
||||||
expect(reply).toContain("\u041d\u0438\u0437\u043a\u0430\u044f \u0438\u043b\u0438 \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f");
|
expect(reply).toContain("\u041d\u0438\u0437\u043a\u0430\u044f \u0438\u043b\u0438 \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f");
|
||||||
expect(reply).toContain("Item A");
|
expect(reply).toContain("Item A");
|
||||||
expect(reply).toContain("Item B");
|
expect(reply).toContain("Item B");
|
||||||
expect(reply).toContain("\u043d\u0435 \u0447\u0438\u0441\u0442\u0430\u044f \u043f\u0440\u0438\u0431\u044b\u043b\u044c \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438");
|
expect(reply).toContain("\u043d\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u044c \u0447\u0438\u0441\u0442\u043e\u0439 \u043f\u0440\u0438\u0431\u044b\u043b\u0438");
|
||||||
expect(reply).not.toContain("\u041e\u0421");
|
expect(reply).not.toContain("\u041e\u0421");
|
||||||
expect(reply).not.toContain("\u0430\u043c\u043e\u0440\u0442\u0438\u0437");
|
expect(reply).not.toContain("\u0430\u043c\u043e\u0440\u0442\u0438\u0437");
|
||||||
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1);
|
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1);
|
||||||
|
|
|
||||||
|
|
@ -329,9 +329,9 @@ describe("address reply builders regressions", () => {
|
||||||
expect(firstLine).toContain("7.271,20");
|
expect(firstLine).toContain("7.271,20");
|
||||||
expect(firstLine).toContain("Авант мебель");
|
expect(firstLine).toContain("Авант мебель");
|
||||||
expect(firstLine).not.toContain("3.677.454,14");
|
expect(firstLine).not.toContain("3.677.454,14");
|
||||||
expect(result.text).toContain("встречных остатков");
|
expect(result.text).toContain("Встречная часть");
|
||||||
expect(result.text).toContain("Комитет государственных услуг г. Москвы");
|
expect(result.text).toContain("Комитет государственных услуг г. Москвы");
|
||||||
expect(result.text).toContain("Финансовое обеспечение заявки");
|
expect(result.text).not.toContain("Финансовое обеспечение заявки");
|
||||||
expect(result.text).not.toContain("договор/аналитика: ООО \\Альтернатива Плюс\\");
|
expect(result.text).not.toContain("договор/аналитика: ООО \\Альтернатива Плюс\\");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -380,7 +380,7 @@ describe("address reply builders regressions", () => {
|
||||||
expect(firstLine).toContain("9.612.904,90");
|
expect(firstLine).toContain("9.612.904,90");
|
||||||
expect(firstLine).toContain("Департамент капитального ремонта города Москвы.");
|
expect(firstLine).toContain("Департамент капитального ремонта города Москвы.");
|
||||||
expect(firstLine).not.toContain("13.290.359,04");
|
expect(firstLine).not.toContain("13.290.359,04");
|
||||||
expect(result.text).toContain("встречных остатков");
|
expect(result.text).toContain("Встречная часть");
|
||||||
expect(result.text).toContain("Комитет государственных услуг г. Москвы");
|
expect(result.text).toContain("Комитет государственных услуг г. Москвы");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -525,4 +525,58 @@ describe("address reply builders regressions", () => {
|
||||||
expect(result?.text).toContain("Общее количество не свожу в один управленческий показатель");
|
expect(result?.text).toContain("Общее количество не свожу в один управленческий показатель");
|
||||||
expect(result?.text).toContain("Следующий шаг: могу раскрыть полный список");
|
expect(result?.text).toContain("Следующий шаг: могу раскрыть полный список");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("answers cost-base evidence follow-up directly when margin ranking has sales without confirmed cost", () => {
|
||||||
|
const result = composeInventoryReply(
|
||||||
|
"inventory_margin_ranking_for_nomenclature",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
amount: 120000,
|
||||||
|
quantity: 2,
|
||||||
|
item: "Рабочая станция",
|
||||||
|
period: "2020-05-20",
|
||||||
|
registrator: "Реализация"
|
||||||
|
} as any
|
||||||
|
],
|
||||||
|
{
|
||||||
|
userMessage: "покажи найденные строки себестоимостной базы",
|
||||||
|
periodFrom: "2020-05-01",
|
||||||
|
periodTo: "2020-05-31"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resolvePayablesAsOfDate: () => "2020-05-31",
|
||||||
|
buildInventoryOnHandAggregate: () => [],
|
||||||
|
uniqueStrings: (values: string[]) => Array.from(new Set(values)),
|
||||||
|
formatDateRu: (value: string) => value,
|
||||||
|
formatNumberWithDots: (value: number, fractionDigits = 0) => value.toFixed(fractionDigits),
|
||||||
|
formatMoneyRub: (value: number) => `${value} ₽`,
|
||||||
|
isInventoryPurchaseMovement: () => false,
|
||||||
|
summarizeInventoryTraceRows: (rows: any[]) => ({
|
||||||
|
item: rows[0]?.item ?? null,
|
||||||
|
warehouses: [],
|
||||||
|
organizations: [],
|
||||||
|
counterparties: [],
|
||||||
|
documents: [],
|
||||||
|
firstPeriod: null,
|
||||||
|
lastPeriod: null,
|
||||||
|
totalAmount: 0
|
||||||
|
}),
|
||||||
|
formatInventoryTraceRows: () => [],
|
||||||
|
hasInventoryPurchaseDateActionFocus: () => false,
|
||||||
|
inventoryTraceDateLabel: () => "",
|
||||||
|
extractInventoryCounterpartyCandidates: () => [],
|
||||||
|
buildInventoryAgingByItemAggregate: () => [],
|
||||||
|
formatInventoryAgingRows: () => [],
|
||||||
|
isInventorySaleMovement: () => true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result?.text.split("\n")[0]).toContain(
|
||||||
|
"подтвержденных строк себестоимостной базы по реализованной номенклатуре не найдено"
|
||||||
|
);
|
||||||
|
expect(result?.text).toContain("Есть реализация по 1 номенклатурной позиции");
|
||||||
|
expect(result?.text).toContain("Строк себестоимости реализации / себестоимостной базы для показа нет");
|
||||||
|
expect(result?.text).not.toContain("входящих денежных поступлений");
|
||||||
|
expect(result?.text).not.toContain("амортизац");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,42 @@ describe("assistant MCP discovery response policy", () => {
|
||||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_not_discovery_ready_address_candidate");
|
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_not_discovery_ready_address_candidate");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps inventory margin-ranking exact reply over stale value-flow discovery candidate", () => {
|
||||||
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
|
currentReply:
|
||||||
|
"За период 2017 рейтинг прибыльности номенклатуры построить нельзя: нет подтвержденной себестоимости реализации.",
|
||||||
|
currentReplySource: "address_query_runtime_v1",
|
||||||
|
currentReplyType: "partial_coverage",
|
||||||
|
addressRuntimeMeta: {
|
||||||
|
detected_intent: "inventory_margin_ranking_for_nomenclature",
|
||||||
|
selected_recipe: "address_inventory_margin_ranking_for_nomenclature_v1",
|
||||||
|
capability_id: "inventory_inventory_margin_ranking_for_nomenclature",
|
||||||
|
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||||
|
turn_input: {
|
||||||
|
adapter_status: "ready",
|
||||||
|
should_run_discovery: true,
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover"
|
||||||
|
},
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "value_flow",
|
||||||
|
clarification_gaps: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.applied).toBe(false);
|
||||||
|
expect(result.decision).toBe("keep_current_reply");
|
||||||
|
expect(result.reply_text).toContain("рейтинг прибыльности номенклатуры");
|
||||||
|
expect(result.reason_codes).toContain(
|
||||||
|
"mcp_discovery_response_policy_keep_inventory_margin_ranking_address_reply"
|
||||||
|
);
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_candidate_applied");
|
||||||
|
});
|
||||||
|
|
||||||
it("lets a grounded business overview candidate override a semantically wrong exact address recipe", () => {
|
it("lets a grounded business overview candidate override a semantically wrong exact address recipe", () => {
|
||||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
currentReply: "Supplier and stock overlap was confirmed for 2020.",
|
currentReply: "Supplier and stock overlap was confirmed for 2020.",
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,75 @@ describe("assistantRoutePolicy", () => {
|
||||||
expect(decision.livingMode).toBe("address_data");
|
expect(decision.livingMode).toBe("address_data");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps margin-ranking found-rows follow-up in address lane", () => {
|
||||||
|
const policy = buildPolicy();
|
||||||
|
|
||||||
|
const decision = policy.resolveAssistantOrchestrationDecision({
|
||||||
|
rawUserMessage: "покажи найденные строки себестоимостной базы",
|
||||||
|
effectiveAddressUserMessage: "покажи найденные строки себестоимостной базы",
|
||||||
|
followupContext: {
|
||||||
|
previous_intent: "inventory_margin_ranking_for_nomenclature",
|
||||||
|
root_intent: "inventory_margin_ranking_for_nomenclature",
|
||||||
|
previous_filters: {
|
||||||
|
organization: "ООО Альтернатива Плюс",
|
||||||
|
period_from: "2020-05-01",
|
||||||
|
period_to: "2020-05-31"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
llmPreDecomposeMeta: {
|
||||||
|
applied: false,
|
||||||
|
predecomposeContract: {
|
||||||
|
mode: "unsupported",
|
||||||
|
mode_confidence: "low",
|
||||||
|
intent: "unknown",
|
||||||
|
intent_confidence: "low"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
useMock: false
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(decision.runAddressLane).toBe(true);
|
||||||
|
expect(decision.toolGateDecision).toBe("run_address_lane");
|
||||||
|
expect(decision.toolGateReason).toBe("followup_context_detected");
|
||||||
|
expect(decision.livingMode).toBe("address_data");
|
||||||
|
expect(decision.orchestrationContract?.hard_meta_mode).toBe(null);
|
||||||
|
expect(decision.orchestrationContract?.final_decision?.tool_gate_reason).toBe("followup_context_detected");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps margin-ranking period expansion follow-up in address lane", () => {
|
||||||
|
const policy = buildPolicy();
|
||||||
|
|
||||||
|
const decision = policy.resolveAssistantOrchestrationDecision({
|
||||||
|
rawUserMessage: "расширь до 2017 года",
|
||||||
|
effectiveAddressUserMessage: "расширь до 2017 года",
|
||||||
|
followupContext: {
|
||||||
|
previous_intent: "inventory_margin_ranking_for_nomenclature",
|
||||||
|
root_intent: "inventory_margin_ranking_for_nomenclature",
|
||||||
|
previous_filters: {
|
||||||
|
organization: "ООО Альтернатива Плюс",
|
||||||
|
period_from: "2020-05-01",
|
||||||
|
period_to: "2020-05-31"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
llmPreDecomposeMeta: {
|
||||||
|
applied: false,
|
||||||
|
predecomposeContract: {
|
||||||
|
mode: "unsupported",
|
||||||
|
mode_confidence: "low",
|
||||||
|
intent: "unknown",
|
||||||
|
intent_confidence: "low"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
useMock: false
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(decision.runAddressLane).toBe(true);
|
||||||
|
expect(decision.toolGateDecision).toBe("run_address_lane");
|
||||||
|
expect(decision.toolGateReason).toBe("followup_context_detected");
|
||||||
|
expect(decision.livingMode).toBe("address_data");
|
||||||
|
expect(decision.orchestrationContract?.hard_meta_mode).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
it("does not let deep session continuation override an exact VAT period route", () => {
|
it("does not let deep session continuation override an exact VAT period route", () => {
|
||||||
const policy = buildPolicy({
|
const policy = buildPolicy({
|
||||||
detectAddressQuestionMode: () => ({ mode: "address_query", confidence: "high" }),
|
detectAddressQuestionMode: () => ({ mode: "address_query", confidence: "high" }),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,53 @@
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
"generation_id": "gen-ag05231107-464a28",
|
||||||
|
"created_at": "2026-05-23T11:07:35+00:00",
|
||||||
|
"mode": "saved_user_sessions",
|
||||||
|
"title": "AGENT | Inventory margin ranking limited answer pack",
|
||||||
|
"count": 5,
|
||||||
|
"domain": "inventory_margin_ranking",
|
||||||
|
"questions": [
|
||||||
|
"Какая номеклатура товара реализована с высокой прибылью какая с низкой",
|
||||||
|
"май 2020",
|
||||||
|
"покажи найденные строки себестоимостной базы",
|
||||||
|
"расширь до 2017 года",
|
||||||
|
"анализ по 41 счету а не 01"
|
||||||
|
],
|
||||||
|
"generated_by": "codex_agent",
|
||||||
|
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260523110735_gen-ag05231107-464a28.json",
|
||||||
|
"context": {
|
||||||
|
"llm_provider": null,
|
||||||
|
"model": null,
|
||||||
|
"assistant_prompt_version": null,
|
||||||
|
"decomposition_prompt_version": null,
|
||||||
|
"prompt_fingerprint": null,
|
||||||
|
"autogen_personality_id": null,
|
||||||
|
"autogen_personality_prompt": null,
|
||||||
|
"source_session_id": null,
|
||||||
|
"saved_session_file": "assistant_saved_session_20260523110735_gen-ag05231107-464a28.json",
|
||||||
|
"saved_case_set_kind": "agent_semantic_scenario",
|
||||||
|
"agent_run": true,
|
||||||
|
"agent_focus": "Targeted live replay for nomenclature high/low profit questions: ask period when missing, stay in inventory/sales/cost domain, give useful limited answer when cost evidence is insufficient, and avoid fixed-assets/bank leakage.",
|
||||||
|
"architecture_phase": "turnaround_11",
|
||||||
|
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agent_inventory_margin_ranking_20260523.json",
|
||||||
|
"scenario_id": "agent_inventory_margin_ranking_20260523",
|
||||||
|
"semantic_tags": [
|
||||||
|
"account_family_guard",
|
||||||
|
"carryover",
|
||||||
|
"domain_purity",
|
||||||
|
"evidence_followup",
|
||||||
|
"inventory_margin_ranking",
|
||||||
|
"limited_answer",
|
||||||
|
"needs_period",
|
||||||
|
"no_fixed_assets",
|
||||||
|
"period_expansion",
|
||||||
|
"period_followup"
|
||||||
|
],
|
||||||
|
"validation_status": "accepted_live_replay",
|
||||||
|
"validated_run_dir": "artifacts\\domain_runs\\agent_inventory_margin_ranking_live5",
|
||||||
|
"saved_after_validated_replay": true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"generation_id": "gen-ag05231011-cec910",
|
"generation_id": "gen-ag05231011-cec910",
|
||||||
"created_at": "2026-05-23T10:11:40+00:00",
|
"created_at": "2026-05-23T10:11:40+00:00",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
{
|
||||||
|
"saved_at": "2026-05-23T11:07:35+00:00",
|
||||||
|
"generation_id": "gen-ag05231107-464a28",
|
||||||
|
"mode": "saved_user_sessions",
|
||||||
|
"title": "AGENT | Inventory margin ranking limited answer pack",
|
||||||
|
"agent_run": true,
|
||||||
|
"questions": [
|
||||||
|
"Какая номеклатура товара реализована с высокой прибылью какая с низкой",
|
||||||
|
"май 2020",
|
||||||
|
"покажи найденные строки себестоимостной базы",
|
||||||
|
"расширь до 2017 года",
|
||||||
|
"анализ по 41 счету а не 01"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"assistant_prompt_version": null,
|
||||||
|
"decomposition_prompt_version": null,
|
||||||
|
"prompt_fingerprint": null,
|
||||||
|
"agent_focus": "Targeted live replay for nomenclature high/low profit questions: ask period when missing, stay in inventory/sales/cost domain, give useful limited answer when cost evidence is insufficient, and avoid fixed-assets/bank leakage.",
|
||||||
|
"architecture_phase": "turnaround_11",
|
||||||
|
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agent_inventory_margin_ranking_20260523.json",
|
||||||
|
"scenario_id": "agent_inventory_margin_ranking_20260523",
|
||||||
|
"semantic_tags": [
|
||||||
|
"account_family_guard",
|
||||||
|
"carryover",
|
||||||
|
"domain_purity",
|
||||||
|
"evidence_followup",
|
||||||
|
"inventory_margin_ranking",
|
||||||
|
"limited_answer",
|
||||||
|
"needs_period",
|
||||||
|
"no_fixed_assets",
|
||||||
|
"period_expansion",
|
||||||
|
"period_followup"
|
||||||
|
],
|
||||||
|
"validation_status": "accepted_live_replay",
|
||||||
|
"validated_run_dir": "artifacts\\domain_runs\\agent_inventory_margin_ranking_live5",
|
||||||
|
"saved_after_validated_replay": true,
|
||||||
|
"save_gate": {
|
||||||
|
"schema_version": "agent_semantic_save_gate_v1",
|
||||||
|
"validation_status": "accepted_live_replay",
|
||||||
|
"validated_run_dir": "artifacts\\domain_runs\\agent_inventory_margin_ranking_live5",
|
||||||
|
"final_status": "accepted",
|
||||||
|
"review_overall_status": "pass",
|
||||||
|
"business_overall_status": "pass",
|
||||||
|
"steps_total": 5,
|
||||||
|
"steps_passed": 5,
|
||||||
|
"steps_failed": 0,
|
||||||
|
"steps_with_business_failures": 0,
|
||||||
|
"steps_with_business_warnings": 0,
|
||||||
|
"acceptance_gate_passed": true,
|
||||||
|
"saved_after_validated_replay": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source_session_id": null,
|
||||||
|
"session": {
|
||||||
|
"session_id": null,
|
||||||
|
"mode": "agent_semantic_run",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-001",
|
||||||
|
"role": "user",
|
||||||
|
"text": "Какая номеклатура товара реализована с высокой прибылью какая с низкой",
|
||||||
|
"created_at": "2026-05-23T11:07:35+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-002",
|
||||||
|
"role": "user",
|
||||||
|
"text": "май 2020",
|
||||||
|
"created_at": "2026-05-23T11:07:35+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-003",
|
||||||
|
"role": "user",
|
||||||
|
"text": "покажи найденные строки себестоимостной базы",
|
||||||
|
"created_at": "2026-05-23T11:07:35+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-004",
|
||||||
|
"role": "user",
|
||||||
|
"text": "расширь до 2017 года",
|
||||||
|
"created_at": "2026-05-23T11:07:35+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-005",
|
||||||
|
"role": "user",
|
||||||
|
"text": "анализ по 41 счету а не 01",
|
||||||
|
"created_at": "2026-05-23T11:07:35+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"agent_run": true,
|
||||||
|
"metadata": {
|
||||||
|
"assistant_prompt_version": null,
|
||||||
|
"decomposition_prompt_version": null,
|
||||||
|
"prompt_fingerprint": null,
|
||||||
|
"agent_focus": "Targeted live replay for nomenclature high/low profit questions: ask period when missing, stay in inventory/sales/cost domain, give useful limited answer when cost evidence is insufficient, and avoid fixed-assets/bank leakage.",
|
||||||
|
"architecture_phase": "turnaround_11",
|
||||||
|
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agent_inventory_margin_ranking_20260523.json",
|
||||||
|
"scenario_id": "agent_inventory_margin_ranking_20260523",
|
||||||
|
"semantic_tags": [
|
||||||
|
"account_family_guard",
|
||||||
|
"carryover",
|
||||||
|
"domain_purity",
|
||||||
|
"evidence_followup",
|
||||||
|
"inventory_margin_ranking",
|
||||||
|
"limited_answer",
|
||||||
|
"needs_period",
|
||||||
|
"no_fixed_assets",
|
||||||
|
"period_expansion",
|
||||||
|
"period_followup"
|
||||||
|
],
|
||||||
|
"validation_status": "accepted_live_replay",
|
||||||
|
"validated_run_dir": "artifacts\\domain_runs\\agent_inventory_margin_ranking_live5",
|
||||||
|
"saved_after_validated_replay": true,
|
||||||
|
"save_gate": {
|
||||||
|
"schema_version": "agent_semantic_save_gate_v1",
|
||||||
|
"validation_status": "accepted_live_replay",
|
||||||
|
"validated_run_dir": "artifacts\\domain_runs\\agent_inventory_margin_ranking_live5",
|
||||||
|
"final_status": "accepted",
|
||||||
|
"review_overall_status": "pass",
|
||||||
|
"business_overall_status": "pass",
|
||||||
|
"steps_total": 5,
|
||||||
|
"steps_passed": 5,
|
||||||
|
"steps_failed": 0,
|
||||||
|
"steps_with_business_failures": 0,
|
||||||
|
"steps_with_business_warnings": 0,
|
||||||
|
"acceptance_gate_passed": true,
|
||||||
|
"saved_after_validated_replay": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"suite_id": "assistant_saved_session_gen-ag05231107-464a28",
|
||||||
|
"suite_version": "0.1.0",
|
||||||
|
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||||
|
"generated_at": "2026-05-23T11:07:35+00:00",
|
||||||
|
"generation_id": "gen-ag05231107-464a28",
|
||||||
|
"mode": "saved_user_sessions",
|
||||||
|
"title": "AGENT | Inventory margin ranking limited answer pack",
|
||||||
|
"domain": "inventory_margin_ranking",
|
||||||
|
"scenario_count": 1,
|
||||||
|
"case_ids": [
|
||||||
|
"SAVED-001"
|
||||||
|
],
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"case_id": "SAVED-001",
|
||||||
|
"scenario_tag": "agent_saved_user_sessions",
|
||||||
|
"title": "AGENT | Inventory margin ranking limited answer pack",
|
||||||
|
"question_type": "followup",
|
||||||
|
"broadness_level": "medium",
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"user_message": "Какая номеклатура товара реализована с высокой прибылью какая с низкой"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "май 2020"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "покажи найденные строки себестоимостной базы"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "расширь до 2017 года"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "анализ по 41 счету а не 01"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue