Стабилизировать маржинальность номенклатуры 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;
|
||||
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) {
|
||||
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 totalSpread = totalRevenue - totalCostProxy;
|
||||
if (confirmedEntries.length === 0) {
|
||||
const lines = [`За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`];
|
||||
const costBaseRowsRequested = asksForInventoryCostBaseRows(options.userMessage);
|
||||
const lines = [
|
||||
costBaseRowsRequested && purchasesWithoutSales.length === 0
|
||||
? `За период ${periodLabel} подтвержденных строк себестоимостной базы по реализованной номенклатуре не найдено.`
|
||||
: `За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`
|
||||
];
|
||||
const findings = [];
|
||||
if (salesWithoutCost.length > 0) {
|
||||
const salesCount = deps.formatNumberWithDots(salesWithoutCost.length);
|
||||
|
|
@ -470,6 +482,9 @@ function composeInventoryReply(intent, rows, options, deps) {
|
|||
: "Подтвержденной себестоимости реализации по этим позициям не найдено.");
|
||||
findings.push("Поэтому валовую прибыль и маржинальность честно посчитать нельзя.");
|
||||
}
|
||||
if (costBaseRowsRequested && purchasesWithoutSales.length === 0) {
|
||||
findings.push("Строк себестоимости реализации / себестоимостной базы для показа нет.");
|
||||
}
|
||||
if (purchasesWithoutSales.length > 0) {
|
||||
const purchaseCount = deps.formatNumberWithDots(purchasesWithoutSales.length);
|
||||
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, "Граница ответа:", [
|
||||
"Прибыльность номенклатуры считаю только когда есть реализация и подтвержденная себестоимость реализации.",
|
||||
"Это не чистая прибыль компании и не замена закрытию месяца."
|
||||
"Это не показатель чистой прибыли и не замена закрытию месяца."
|
||||
]);
|
||||
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)));
|
||||
}
|
||||
const boundaryLines = [
|
||||
"Это управленческий расчет валовой маржинальности по реализации и доступной себестоимостной базе, не чистая прибыль компании.",
|
||||
"Это управленческий расчет валовой маржинальности по реализации и доступной себестоимостной базе, не показатель чистой прибыли.",
|
||||
"Для строгого бухгалтерского расчета нужны проводки 90.01 / 90.02 и закрытие себестоимости; этот ответ не подменяет закрытие месяца."
|
||||
];
|
||||
if (salesWithoutCost.length > 0) {
|
||||
|
|
|
|||
|
|
@ -350,6 +350,24 @@ function hasExactBankOperationsAddressReply(input, entryPoint) {
|
|||
routeMode === "exact" ||
|
||||
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) {
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
|
|
@ -621,6 +639,7 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
const staleMetadataDiscoveryFallbackAgainstExactAddressReply = hasStaleMetadataDiscoveryFallbackAgainstExactAddressReply(input, entryPoint);
|
||||
const exactValueFlowReplyForBusinessOverviewDirectMoneyNeed = hasExactValueFlowReplyForBusinessOverviewDirectMoneyNeed(input, entryPoint);
|
||||
const exactBankOperationsAddressReply = hasExactBankOperationsAddressReply(input, entryPoint);
|
||||
const inventoryMarginRankingAddressReply = hasInventoryMarginRankingAddressReply(input, entryPoint);
|
||||
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
|
||||
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
|
||||
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint);
|
||||
|
|
@ -685,6 +704,9 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
if (exactBankOperationsAddressReply) {
|
||||
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") {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_broad_business_summary_over_clarification_candidate");
|
||||
}
|
||||
|
|
@ -715,6 +737,7 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
!staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
|
||||
!exactValueFlowReplyForBusinessOverviewDirectMoneyNeed &&
|
||||
!exactBankOperationsAddressReply &&
|
||||
!inventoryMarginRankingAddressReply &&
|
||||
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
||||
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
||||
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);
|
||||
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) {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
if (!normalized) {
|
||||
|
|
@ -481,6 +496,7 @@ function createAssistantRoutePolicy(deps) {
|
|||
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
||||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage);
|
||||
const followupPreviousIntent = toNonEmptyString(followupContext?.previous_intent);
|
||||
const followupRootIntent = toNonEmptyString(followupContext?.root_intent);
|
||||
const followupPreviousFilters = followupContext?.previous_filters && typeof followupContext.previous_filters === "object"
|
||||
? followupContext.previous_filters
|
||||
: null;
|
||||
|
|
@ -515,6 +531,16 @@ function createAssistantRoutePolicy(deps) {
|
|||
hasShortInventoryObjectFollowupSignal(repairedRawUserMessage) ||
|
||||
hasShortInventoryObjectFollowupSignal(effectiveAddressUserMessage) ||
|
||||
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) &&
|
||||
lastOrganizationClarificationDebug &&
|
||||
explicitOrganizationClarificationSelection &&
|
||||
|
|
@ -571,6 +597,7 @@ function createAssistantRoutePolicy(deps) {
|
|||
!baseToolGatePreservesAddressLane &&
|
||||
!effectiveGroundedValueFlowFollowupContextDetected &&
|
||||
!protectedInventoryShortFollowup &&
|
||||
!protectedInventoryMarginRankingFollowup &&
|
||||
!organizationClarificationContinuationDetected &&
|
||||
!routeCandidateOrganizationClarificationDetected);
|
||||
const lastAddressAssistantDebug = sessionItems
|
||||
|
|
@ -1080,6 +1107,7 @@ function createAssistantRoutePolicy(deps) {
|
|||
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
||||
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
||||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage) ||
|
||||
protectedInventoryMarginRankingFollowup ||
|
||||
inventoryRootRestatementFollowupDetected);
|
||||
const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) ||
|
||||
hasDeepAnalysisPreferenceSignal(repairedRawUserMessage) ||
|
||||
|
|
@ -1116,7 +1144,9 @@ function createAssistantRoutePolicy(deps) {
|
|||
resolvedIntentResolution.intent === "unknown" &&
|
||||
(!llmContractIntent || llmContractIntent === "unknown"));
|
||||
const exactAddressIntentProtectedFromSemanticDeepHint = laneProtectionArbitration.exactAddressIntentProtectedFromSemanticDeepHint;
|
||||
const protectAddressLaneFromFallback = Boolean(laneProtectionArbitration.protectAddressLaneFromFallback || customerValueRankingAddressSignal);
|
||||
const protectAddressLaneFromFallback = Boolean(laneProtectionArbitration.protectAddressLaneFromFallback ||
|
||||
customerValueRankingAddressSignal ||
|
||||
protectedInventoryMarginRankingFollowup);
|
||||
const vatExplainFollowupSignal = Boolean(followupContext &&
|
||||
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}`)));
|
||||
|
|
@ -1196,7 +1226,7 @@ function createAssistantRoutePolicy(deps) {
|
|||
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
||||
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
||||
const semanticAddressLaneRecovery = Boolean(!runAddressLane &&
|
||||
supportedAddressRouteCandidateDetected &&
|
||||
(supportedAddressRouteCandidateDetected || protectedInventoryMarginRankingFollowup) &&
|
||||
!deepAnalysisPreferenceDetected &&
|
||||
!unsupportedAddressIntentFallbackToDeep &&
|
||||
!deepAnalysisSignalFallbackToDeep &&
|
||||
|
|
@ -1205,9 +1235,11 @@ function createAssistantRoutePolicy(deps) {
|
|||
if (semanticAddressLaneRecovery) {
|
||||
runAddressLane = true;
|
||||
toolGateDecision = "run_address_lane";
|
||||
toolGateReason = resolvedIntentResolution.intent !== "unknown" || llmContractIntent
|
||||
? "address_intent_resolver_detected"
|
||||
: "address_signal_detected";
|
||||
toolGateReason = protectedInventoryMarginRankingFollowup
|
||||
? "followup_context_detected"
|
||||
: resolvedIntentResolution.intent !== "unknown" || llmContractIntent
|
||||
? "address_intent_resolver_detected"
|
||||
: "address_signal_detected";
|
||||
}
|
||||
if (unsupportedAddressIntentFallbackToDeep) {
|
||||
runAddressLane = false;
|
||||
|
|
|
|||
|
|
@ -162,6 +162,14 @@ function inventoryProfitabilityPeriodLabel(options: InventoryComposeOptions, dep
|
|||
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 {
|
||||
item: string;
|
||||
revenue: number;
|
||||
|
|
@ -631,7 +639,12 @@ export function composeInventoryReply(
|
|||
const totalCostProxy = entries.reduce((sum, entry) => sum + entry.costProxy, 0);
|
||||
const totalSpread = totalRevenue - totalCostProxy;
|
||||
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[] = [];
|
||||
if (salesWithoutCost.length > 0) {
|
||||
const salesCount = deps.formatNumberWithDots(salesWithoutCost.length);
|
||||
|
|
@ -647,6 +660,9 @@ export function composeInventoryReply(
|
|||
);
|
||||
findings.push("Поэтому валовую прибыль и маржинальность честно посчитать нельзя.");
|
||||
}
|
||||
if (costBaseRowsRequested && purchasesWithoutSales.length === 0) {
|
||||
findings.push("Строк себестоимости реализации / себестоимостной базы для показа нет.");
|
||||
}
|
||||
if (purchasesWithoutSales.length > 0) {
|
||||
const purchaseCount = deps.formatNumberWithDots(purchasesWithoutSales.length);
|
||||
const purchaseItemPhrase =
|
||||
|
|
@ -679,7 +695,7 @@ export function composeInventoryReply(
|
|||
appendInventoryBulletSection(lines, "Что можно сделать дальше:", nextActions);
|
||||
appendInventoryBulletSection(lines, "Граница ответа:", [
|
||||
"Прибыльность номенклатуры считаю только когда есть реализация и подтвержденная себестоимость реализации.",
|
||||
"Это не чистая прибыль компании и не замена закрытию месяца."
|
||||
"Это не показатель чистой прибыли и не замена закрытию месяца."
|
||||
]);
|
||||
return buildFactualSummaryReply(lines, buildConfirmedBalanceSemantics(entries.length > 0 ? "medium" : "weak", false));
|
||||
}
|
||||
|
|
@ -709,7 +725,7 @@ export function composeInventoryReply(
|
|||
}
|
||||
|
||||
const boundaryLines = [
|
||||
"Это управленческий расчет валовой маржинальности по реализации и доступной себестоимостной базе, не чистая прибыль компании.",
|
||||
"Это управленческий расчет валовой маржинальности по реализации и доступной себестоимостной базе, не показатель чистой прибыли.",
|
||||
"Для строгого бухгалтерского расчета нужны проводки 90.01 / 90.02 и закрытие себестоимости; этот ответ не подменяет закрытие месяца."
|
||||
];
|
||||
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(
|
||||
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||
|
|
@ -834,6 +859,7 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
entryPoint
|
||||
);
|
||||
const exactBankOperationsAddressReply = hasExactBankOperationsAddressReply(input, entryPoint);
|
||||
const inventoryMarginRankingAddressReply = hasInventoryMarginRankingAddressReply(input, entryPoint);
|
||||
const openScopeValueFlowDiscoveryPriority = hasOpenScopeValueFlowDiscoveryPriority(input, entryPoint);
|
||||
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
|
||||
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(
|
||||
|
|
@ -917,6 +943,9 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
if (exactBankOperationsAddressReply) {
|
||||
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") {
|
||||
pushReason(
|
||||
reasonCodes,
|
||||
|
|
@ -952,6 +981,7 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
!staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
|
||||
!exactValueFlowReplyForBusinessOverviewDirectMoneyNeed &&
|
||||
!exactBankOperationsAddressReply &&
|
||||
!inventoryMarginRankingAddressReply &&
|
||||
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
||||
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
||||
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);
|
||||
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) {
|
||||
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
|
||||
if (!normalized) {
|
||||
|
|
@ -565,6 +583,7 @@ export function createAssistantRoutePolicy(deps) {
|
|||
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
||||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage);
|
||||
const followupPreviousIntent = toNonEmptyString(followupContext?.previous_intent);
|
||||
const followupRootIntent = toNonEmptyString(followupContext?.root_intent);
|
||||
const followupPreviousFilters = followupContext?.previous_filters && typeof followupContext.previous_filters === "object"
|
||||
? followupContext.previous_filters
|
||||
: null;
|
||||
|
|
@ -599,6 +618,16 @@ export function createAssistantRoutePolicy(deps) {
|
|||
hasShortInventoryObjectFollowupSignal(repairedRawUserMessage) ||
|
||||
hasShortInventoryObjectFollowupSignal(effectiveAddressUserMessage) ||
|
||||
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) &&
|
||||
lastOrganizationClarificationDebug &&
|
||||
explicitOrganizationClarificationSelection &&
|
||||
|
|
@ -656,6 +685,7 @@ export function createAssistantRoutePolicy(deps) {
|
|||
!baseToolGatePreservesAddressLane &&
|
||||
!effectiveGroundedValueFlowFollowupContextDetected &&
|
||||
!protectedInventoryShortFollowup &&
|
||||
!protectedInventoryMarginRankingFollowup &&
|
||||
!organizationClarificationContinuationDetected &&
|
||||
!routeCandidateOrganizationClarificationDetected);
|
||||
const lastAddressAssistantDebug = sessionItems
|
||||
|
|
@ -1167,6 +1197,7 @@ export function createAssistantRoutePolicy(deps) {
|
|||
hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) ||
|
||||
hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) ||
|
||||
hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage) ||
|
||||
protectedInventoryMarginRankingFollowup ||
|
||||
inventoryRootRestatementFollowupDetected);
|
||||
const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) ||
|
||||
hasDeepAnalysisPreferenceSignal(repairedRawUserMessage) ||
|
||||
|
|
@ -1204,7 +1235,9 @@ export function createAssistantRoutePolicy(deps) {
|
|||
(!llmContractIntent || llmContractIntent === "unknown"));
|
||||
const exactAddressIntentProtectedFromSemanticDeepHint = laneProtectionArbitration.exactAddressIntentProtectedFromSemanticDeepHint;
|
||||
const protectAddressLaneFromFallback = Boolean(
|
||||
laneProtectionArbitration.protectAddressLaneFromFallback || customerValueRankingAddressSignal
|
||||
laneProtectionArbitration.protectAddressLaneFromFallback ||
|
||||
customerValueRankingAddressSignal ||
|
||||
protectedInventoryMarginRankingFollowup
|
||||
);
|
||||
const vatExplainFollowupSignal = Boolean(followupContext &&
|
||||
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
||||
|
|
@ -1285,7 +1318,7 @@ export function createAssistantRoutePolicy(deps) {
|
|||
let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane");
|
||||
let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0");
|
||||
const semanticAddressLaneRecovery = Boolean(!runAddressLane &&
|
||||
supportedAddressRouteCandidateDetected &&
|
||||
(supportedAddressRouteCandidateDetected || protectedInventoryMarginRankingFollowup) &&
|
||||
!deepAnalysisPreferenceDetected &&
|
||||
!unsupportedAddressIntentFallbackToDeep &&
|
||||
!deepAnalysisSignalFallbackToDeep &&
|
||||
|
|
@ -1294,7 +1327,9 @@ export function createAssistantRoutePolicy(deps) {
|
|||
if (semanticAddressLaneRecovery) {
|
||||
runAddressLane = true;
|
||||
toolGateDecision = "run_address_lane";
|
||||
toolGateReason = resolvedIntentResolution.intent !== "unknown" || llmContractIntent
|
||||
toolGateReason = protectedInventoryMarginRankingFollowup
|
||||
? "followup_context_detected"
|
||||
: resolvedIntentResolution.intent !== "unknown" || llmContractIntent
|
||||
? "address_intent_resolver_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("Item A");
|
||||
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("\u0430\u043c\u043e\u0440\u0442\u0438\u0437");
|
||||
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1);
|
||||
|
|
|
|||
|
|
@ -329,9 +329,9 @@ describe("address reply builders regressions", () => {
|
|||
expect(firstLine).toContain("7.271,20");
|
||||
expect(firstLine).toContain("Авант мебель");
|
||||
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).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("Департамент капитального ремонта города Москвы.");
|
||||
expect(firstLine).not.toContain("13.290.359,04");
|
||||
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("Следующий шаг: могу раскрыть полный список");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "Supplier and stock overlap was confirmed for 2020.",
|
||||
|
|
|
|||
|
|
@ -183,6 +183,75 @@ describe("assistantRoutePolicy", () => {
|
|||
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", () => {
|
||||
const policy = buildPolicy({
|
||||
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",
|
||||
"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