diff --git a/docs/orchestration/address_truth_harness_inventory_supplier_overlap_temporal_carryover.json b/docs/orchestration/address_truth_harness_inventory_supplier_overlap_temporal_carryover.json new file mode 100644 index 0000000..98f3692 --- /dev/null +++ b/docs/orchestration/address_truth_harness_inventory_supplier_overlap_temporal_carryover.json @@ -0,0 +1,80 @@ +{ + "schema_version": "domain_truth_harness_spec_v1", + "scenario_id": "address_truth_harness_inventory_supplier_overlap_temporal_carryover", + "domain": "inventory_supplier_overlap_temporal_carryover", + "title": "Supplier-overlap follow-up keeps the inventory root date", + "description": "Targeted replay for temporal carryover: after an inventory root slice for September 2021, colloquial supplier-overlap follow-ups with 'сейчас' / 'текущий товарный остаток' must stay on the confirmed root slice date instead of drifting to the wall-clock date.", + "bindings": {}, + "steps": [ + { + "step_id": "step_01_current_stock_anchor", + "title": "Historical inventory root anchor", + "question": "какие остатки на складе на сентябрь 2021", + "allowed_reply_types": [ + "factual" + ], + "expected_intents": [ + "inventory_on_hand_as_of_date" + ], + "required_filters": { + "as_of_date": "2021-09-30", + "period_from": "2021-09-01", + "period_to": "2021-09-30" + }, + "required_answer_patterns_all": [ + "(?i)30\\.09\\.2021", + "(?i)склад|остат" + ], + "forbidden_answer_patterns": [ + "(?i)03\\.06\\.2026", + "(?i)route_id|capability_id|runtime_|snapshot_items|answer_object|debug" + ] + }, + { + "step_id": "step_02_supplier_overlap_now", + "title": "Supplier overlap inherits root date despite сейчас", + "question": "У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад", + "allowed_reply_types": [ + "factual" + ], + "expected_intents": [ + "inventory_supplier_stock_overlap_as_of_date" + ], + "required_filters": { + "as_of_date": "2021-09-30" + }, + "required_answer_patterns_all": [ + "(?i)30\\.09\\.2021", + "(?i)поставщик|поставщиков|закупочн", + "(?i)Следующий шаг|проверьте|партионн" + ], + "forbidden_answer_patterns": [ + "(?i)03\\.06\\.2026", + "(?i)route_id|capability_id|runtime_|snapshot_items|answer_object|debug" + ] + }, + { + "step_id": "step_03_supplier_residue_now", + "title": "Current residue wording keeps root date", + "question": "По какому поставщику проходит текущий товарный остаток на складе Основной склад", + "allowed_reply_types": [ + "factual" + ], + "expected_intents": [ + "inventory_supplier_stock_overlap_as_of_date" + ], + "required_filters": { + "as_of_date": "2021-09-30" + }, + "required_answer_patterns_all": [ + "(?i)30\\.09\\.2021", + "(?i)поставщик|поставщиков|закупочн", + "(?i)Следующий шаг|проверьте|партионн" + ], + "forbidden_answer_patterns": [ + "(?i)03\\.06\\.2026", + "(?i)route_id|capability_id|runtime_|snapshot_items|answer_object|debug" + ] + } + ] +} diff --git a/llm_normalizer/backend/dist/services/assistantAddressOrchestrationRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantAddressOrchestrationRuntimeAdapter.js index 4b34f48..2879070 100644 --- a/llm_normalizer/backend/dist/services/assistantAddressOrchestrationRuntimeAdapter.js +++ b/llm_normalizer/backend/dist/services/assistantAddressOrchestrationRuntimeAdapter.js @@ -534,6 +534,7 @@ function shouldPreferRawFollowupMessage(userMessage, addressInputMessage, carryo const previousIntent = toNonEmptyString(followupContext?.previous_intent); const rootIntent = toNonEmptyString(followupContext?.root_intent); const previousAnchorType = toNonEmptyString(followupContext?.previous_anchor_type); + const previousFilters = toRecordObject(followupContext?.previous_filters); const hasReferentialDocumentExclusionFollowupCue = /(?:\u043a\u0440\u043e\u043c\u0435|\u043f\u043e\u043c\u0438\u043c\u043e)\s+(?:\u044d\u0442\u043e\u0433\u043e|\u044d\u0442\u043e\u0439|\u044d\u0442\u043e\u0442|\u044d\u0442\u0443|\u044d\u0442\u0438\u0445)(?:\s+(?:\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430|\u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0430|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430))?/iu.test(rawMessage); const hasInventoryItemCarryover = previousAnchorType === "item" && isInventorySelectedObjectOrRootIntent(previousIntent); const hasInventoryFrameCarryover = isInventorySelectedObjectOrRootIntent(previousIntent) || @@ -543,10 +544,11 @@ function shouldPreferRawFollowupMessage(userMessage, addressInputMessage, carryo const hasInventoryMarginRankingAccountCorrection = hasInventoryMarginRankingCarryover && [rawMessage, canonicalMessage].some((message) => hasInventoryMarginRankingAccountCorrectionCue(message)); const hasDocumentCarryover = previousIntent === "list_documents_by_counterparty" || previousIntent === "list_documents_by_contract"; + const hasCounterpartyCarryover = previousAnchorType === "counterparty" || Boolean(toNonEmptyString(previousFilters?.counterparty)); if (mode === "unsupported" && intent === "unknown") { return true; } - if (hasDocumentCarryover && hasReferentialDocumentExclusionFollowupCue) { + if ((hasDocumentCarryover || hasCounterpartyCarryover) && hasReferentialDocumentExclusionFollowupCue) { return true; } if (hasSameDateFollowupSignal(rawMessage) && hasExplicitCurrentDateSignal(canonicalMessage)) { diff --git a/llm_normalizer/backend/dist/services/assistantTransitionPolicy.js b/llm_normalizer/backend/dist/services/assistantTransitionPolicy.js index 875546e..f670a5d 100644 --- a/llm_normalizer/backend/dist/services/assistantTransitionPolicy.js +++ b/llm_normalizer/backend/dist/services/assistantTransitionPolicy.js @@ -27,6 +27,25 @@ function createAssistantTransitionPolicy(deps) { return wantsFoundRows || account41Not01; }); } + function hasInventorySupplierOverlapRootFollowupSignal(userMessage, alternateMessage = null, sourceIntentHint = null, hasInventoryRootFrame = false) { + if (!hasInventoryRootFrame || + (sourceIntentHint !== "inventory_on_hand_as_of_date" && + sourceIntentHint !== "inventory_supplier_stock_overlap_as_of_date")) { + return false; + } + return [userMessage, alternateMessage] + .filter((value) => deps.toNonEmptyString(value)) + .map((value) => normalizeFollowupText(value)) + .some((normalized) => { + if (!normalized) { + return false; + } + const hasSupplierCue = /(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|supplier|vendor|\u0443\s+\u043a\u0430\u043a\u043e\u0433\u043e|\u0443\s+\u043a\u043e\u0433\u043e|\u043f\u043e\s+\u043a\u0430\u043a\u043e\u043c\u0443)/iu.test(normalized); + const hasStockCue = /(?:\u043e\u0441\u0442\u0430\u0442|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043f\u043e\u0437\u0438\u0446|\u043b\u0435\u0436|\u0432\u0438\u0441|\u0442\u0435\u043a\u0443\u0449)/iu.test(normalized); + const hasPurchaseCue = /(?:\u043a\u0443\u043f\u0438\u043b|\u043a\u0443\u043f\u043b\u0435\u043d|\u043f\u043e\u043a\u0443\u043f|\u0437\u0430\u043a\u0443\u043f|\u043f\u043e\u0441\u0442\u0443\u043f\u043b|purchase|bought)/iu.test(normalized); + return hasSupplierCue && (hasStockCue || hasPurchaseCue); + }); + } function hasSamePeriodReferenceCue(...values) { return values .map((value) => normalizeFollowupText(value)) @@ -866,6 +885,7 @@ function createAssistantTransitionPolicy(deps) { const hasInventoryRootRestatementAlternate = deps.toNonEmptyString(alternateMessage) ? hasInventoryRootRestatementLikeSignal(String(alternateMessage ?? ""), sourceIntentHint, Boolean(recentInventoryRootFrame)) : false; + const hasInventorySupplierOverlapRootFollowup = hasInventorySupplierOverlapRootFollowupSignal(userMessage, alternateMessage, sourceIntentHint, Boolean(recentInventoryRootFrame)); const hasExplicitInventorySameDatePivotPrimary = hasExplicitInventorySameDatePivotSignal(userMessage); const hasExplicitInventorySameDatePivotAlternate = deps.toNonEmptyString(alternateMessage) ? hasExplicitInventorySameDatePivotSignal(String(alternateMessage ?? "")) @@ -882,6 +902,7 @@ function createAssistantTransitionPolicy(deps) { hasInventoryRootTemporalFollowupAlternate || hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate || + hasInventorySupplierOverlapRootFollowup || inventoryPurchaseDateVatBridge || explicitSummaryBundleReuseSignal || Boolean(debtRoleSwapIntent) || @@ -907,6 +928,7 @@ function createAssistantTransitionPolicy(deps) { hasInventoryRootTemporalFollowupAlternate || hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate || + hasInventorySupplierOverlapRootFollowup || inventoryPurchaseDateVatBridge || explicitSummaryBundleReuseSignal || Boolean(debtRoleSwapIntent) || @@ -938,6 +960,7 @@ function createAssistantTransitionPolicy(deps) { !hasInventoryRootTemporalFollowupAlternate && !hasInventoryRootRestatementPrimary && !hasInventoryRootRestatementAlternate && + !hasInventorySupplierOverlapRootFollowup && !shortValueFlowRetargetPrimary && !shortValueFlowRetargetAlternate && !selectedCounterpartyDocumentFollowupSignal && @@ -955,6 +978,7 @@ function createAssistantTransitionPolicy(deps) { !hasInventoryRootTemporalFollowupAlternate && !hasInventoryRootRestatementPrimary && !hasInventoryRootRestatementAlternate && + !hasInventorySupplierOverlapRootFollowup && !shortValueFlowRetargetPrimary && !shortValueFlowRetargetAlternate && !selectedCounterpartyDocumentFollowupSignal && @@ -1037,6 +1061,7 @@ function createAssistantTransitionPolicy(deps) { !hasInventoryRootTemporalFollowupAlternate && !hasInventoryRootRestatementPrimary && !hasInventoryRootRestatementAlternate && + !hasInventorySupplierOverlapRootFollowup && !inventoryShortFollowupPrimary && !inventoryShortFollowupAlternate && !businessOverviewBoundaryFollowupPrimary && @@ -1104,6 +1129,7 @@ function createAssistantTransitionPolicy(deps) { inventoryMarginRankingFollowup || explicitSummaryBundleReuseSignal || hasInventoryRootTemporalFollowupPrimary || + hasInventorySupplierOverlapRootFollowup || mcpDiscoveryOrganizationClarificationContinuation; hasAlternateFollowupSignal = deps.toNonEmptyString(alternateMessage) ? deps.hasAddressFollowupContextSignal(alternateMessage) || @@ -1116,6 +1142,7 @@ function createAssistantTransitionPolicy(deps) { inventoryMarginRankingFollowup || explicitSummaryBundleReuseSignal || hasInventoryRootTemporalFollowupAlternate || + hasInventorySupplierOverlapRootFollowup || mcpDiscoveryOrganizationClarificationContinuation : false; hasStrongFollowupReference = @@ -1129,6 +1156,7 @@ function createAssistantTransitionPolicy(deps) { inventoryShortFollowupAlternate || hasInventoryRootTemporalFollowupPrimary || hasInventoryRootTemporalFollowupAlternate || + hasInventorySupplierOverlapRootFollowup || inventoryPurchaseDateVatBridge || explicitSummaryBundleReuseSignal || Boolean(debtRoleSwapIntent) || @@ -1261,7 +1289,8 @@ function createAssistantTransitionPolicy(deps) { } const predecomposeIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent); const carryoverTargetIntentPreview = explicitIntent ?? predecomposeIntent ?? previousIntent; - if (shouldClearStaleDateScopeForCounterpartyDocsRoot(carryoverTargetIntentPreview, sourceIntentHint, userMessage, alternateMessage)) { + if (!selectedCounterpartyDocumentFollowupSignal && + shouldClearStaleDateScopeForCounterpartyDocsRoot(carryoverTargetIntentPreview, sourceIntentHint, userMessage, alternateMessage)) { delete previousFilters.as_of_date; delete previousFilters.period_from; delete previousFilters.period_to; @@ -1280,6 +1309,7 @@ function createAssistantTransitionPolicy(deps) { inventoryShortFollowupAlternate || hasInventoryRootTemporalFollowupPrimary || hasInventoryRootTemporalFollowupAlternate || + hasInventorySupplierOverlapRootFollowup || hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate || hasSelectedObjectInventorySignalPrimary || diff --git a/llm_normalizer/backend/src/services/assistantAddressOrchestrationRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantAddressOrchestrationRuntimeAdapter.ts index cf2a454..0a5e738 100644 --- a/llm_normalizer/backend/src/services/assistantAddressOrchestrationRuntimeAdapter.ts +++ b/llm_normalizer/backend/src/services/assistantAddressOrchestrationRuntimeAdapter.ts @@ -765,6 +765,7 @@ function shouldPreferRawFollowupMessage( const previousIntent = toNonEmptyString(followupContext?.previous_intent); const rootIntent = toNonEmptyString(followupContext?.root_intent); const previousAnchorType = toNonEmptyString(followupContext?.previous_anchor_type); + const previousFilters = toRecordObject(followupContext?.previous_filters); const hasReferentialDocumentExclusionFollowupCue = /(?:\u043a\u0440\u043e\u043c\u0435|\u043f\u043e\u043c\u0438\u043c\u043e)\s+(?:\u044d\u0442\u043e\u0433\u043e|\u044d\u0442\u043e\u0439|\u044d\u0442\u043e\u0442|\u044d\u0442\u0443|\u044d\u0442\u0438\u0445)(?:\s+(?:\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430|\u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0430|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430))?/iu.test( rawMessage ); @@ -781,12 +782,14 @@ function shouldPreferRawFollowupMessage( [rawMessage, canonicalMessage].some((message) => hasInventoryMarginRankingAccountCorrectionCue(message)); const hasDocumentCarryover = previousIntent === "list_documents_by_counterparty" || previousIntent === "list_documents_by_contract"; + const hasCounterpartyCarryover = + previousAnchorType === "counterparty" || Boolean(toNonEmptyString(previousFilters?.counterparty)); if (mode === "unsupported" && intent === "unknown") { return true; } - if (hasDocumentCarryover && hasReferentialDocumentExclusionFollowupCue) { + if ((hasDocumentCarryover || hasCounterpartyCarryover) && hasReferentialDocumentExclusionFollowupCue) { return true; } diff --git a/llm_normalizer/backend/src/services/assistantTransitionPolicy.ts b/llm_normalizer/backend/src/services/assistantTransitionPolicy.ts index b673936..89e468d 100644 --- a/llm_normalizer/backend/src/services/assistantTransitionPolicy.ts +++ b/llm_normalizer/backend/src/services/assistantTransitionPolicy.ts @@ -72,6 +72,42 @@ export function createAssistantTransitionPolicy(deps) { }); } + function hasInventorySupplierOverlapRootFollowupSignal( + userMessage, + alternateMessage = null, + sourceIntentHint = null, + hasInventoryRootFrame = false + ) { + if ( + !hasInventoryRootFrame || + (sourceIntentHint !== "inventory_on_hand_as_of_date" && + sourceIntentHint !== "inventory_supplier_stock_overlap_as_of_date") + ) { + return false; + } + return [userMessage, alternateMessage] + .filter((value) => deps.toNonEmptyString(value)) + .map((value) => normalizeFollowupText(value)) + .some((normalized) => { + if (!normalized) { + return false; + } + const hasSupplierCue = + /(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|supplier|vendor|\u0443\s+\u043a\u0430\u043a\u043e\u0433\u043e|\u0443\s+\u043a\u043e\u0433\u043e|\u043f\u043e\s+\u043a\u0430\u043a\u043e\u043c\u0443)/iu.test( + normalized + ); + const hasStockCue = + /(?:\u043e\u0441\u0442\u0430\u0442|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043f\u043e\u0437\u0438\u0446|\u043b\u0435\u0436|\u0432\u0438\u0441|\u0442\u0435\u043a\u0443\u0449)/iu.test( + normalized + ); + const hasPurchaseCue = + /(?:\u043a\u0443\u043f\u0438\u043b|\u043a\u0443\u043f\u043b\u0435\u043d|\u043f\u043e\u043a\u0443\u043f|\u0437\u0430\u043a\u0443\u043f|\u043f\u043e\u0441\u0442\u0443\u043f\u043b|purchase|bought)/iu.test( + normalized + ); + return hasSupplierCue && (hasStockCue || hasPurchaseCue); + }); + } + function hasSamePeriodReferenceCue(...values) { return values .map((value) => normalizeFollowupText(value)) @@ -1180,6 +1216,12 @@ export function createAssistantTransitionPolicy(deps) { Boolean(recentInventoryRootFrame) ) : false; + const hasInventorySupplierOverlapRootFollowup = hasInventorySupplierOverlapRootFollowupSignal( + userMessage, + alternateMessage, + sourceIntentHint, + Boolean(recentInventoryRootFrame) + ); const hasExplicitInventorySameDatePivotPrimary = hasExplicitInventorySameDatePivotSignal(userMessage); const hasExplicitInventorySameDatePivotAlternate = deps.toNonEmptyString(alternateMessage) ? hasExplicitInventorySameDatePivotSignal(String(alternateMessage ?? "")) @@ -1197,6 +1239,7 @@ export function createAssistantTransitionPolicy(deps) { hasInventoryRootTemporalFollowupAlternate || hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate || + hasInventorySupplierOverlapRootFollowup || inventoryPurchaseDateVatBridge || explicitSummaryBundleReuseSignal || Boolean(debtRoleSwapIntent) || @@ -1223,6 +1266,7 @@ export function createAssistantTransitionPolicy(deps) { hasInventoryRootTemporalFollowupAlternate || hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate || + hasInventorySupplierOverlapRootFollowup || inventoryPurchaseDateVatBridge || explicitSummaryBundleReuseSignal || Boolean(debtRoleSwapIntent) || @@ -1259,6 +1303,7 @@ export function createAssistantTransitionPolicy(deps) { !hasInventoryRootTemporalFollowupAlternate && !hasInventoryRootRestatementPrimary && !hasInventoryRootRestatementAlternate && + !hasInventorySupplierOverlapRootFollowup && !shortValueFlowRetargetPrimary && !shortValueFlowRetargetAlternate && !selectedCounterpartyDocumentFollowupSignal && @@ -1278,6 +1323,7 @@ export function createAssistantTransitionPolicy(deps) { !hasInventoryRootTemporalFollowupAlternate && !hasInventoryRootRestatementPrimary && !hasInventoryRootRestatementAlternate && + !hasInventorySupplierOverlapRootFollowup && !shortValueFlowRetargetPrimary && !shortValueFlowRetargetAlternate && !selectedCounterpartyDocumentFollowupSignal && @@ -1412,6 +1458,7 @@ export function createAssistantTransitionPolicy(deps) { !hasInventoryRootTemporalFollowupAlternate && !hasInventoryRootRestatementPrimary && !hasInventoryRootRestatementAlternate && + !hasInventorySupplierOverlapRootFollowup && !inventoryShortFollowupPrimary && !inventoryShortFollowupAlternate && !businessOverviewBoundaryFollowupPrimary && @@ -1483,6 +1530,7 @@ export function createAssistantTransitionPolicy(deps) { inventoryMarginRankingFollowup || explicitSummaryBundleReuseSignal || hasInventoryRootTemporalFollowupPrimary || + hasInventorySupplierOverlapRootFollowup || mcpDiscoveryOrganizationClarificationContinuation; hasAlternateFollowupSignal = deps.toNonEmptyString(alternateMessage) ? deps.hasAddressFollowupContextSignal(alternateMessage) || @@ -1495,6 +1543,7 @@ export function createAssistantTransitionPolicy(deps) { inventoryMarginRankingFollowup || explicitSummaryBundleReuseSignal || hasInventoryRootTemporalFollowupAlternate || + hasInventorySupplierOverlapRootFollowup || mcpDiscoveryOrganizationClarificationContinuation : false; hasStrongFollowupReference = @@ -1508,6 +1557,7 @@ export function createAssistantTransitionPolicy(deps) { inventoryShortFollowupAlternate || hasInventoryRootTemporalFollowupPrimary || hasInventoryRootTemporalFollowupAlternate || + hasInventorySupplierOverlapRootFollowup || inventoryPurchaseDateVatBridge || explicitSummaryBundleReuseSignal || Boolean(debtRoleSwapIntent) || @@ -1728,6 +1778,7 @@ export function createAssistantTransitionPolicy(deps) { const predecomposeIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent); const carryoverTargetIntentPreview = explicitIntent ?? predecomposeIntent ?? previousIntent; if ( + !selectedCounterpartyDocumentFollowupSignal && shouldClearStaleDateScopeForCounterpartyDocsRoot( carryoverTargetIntentPreview, sourceIntentHint, @@ -1754,6 +1805,7 @@ export function createAssistantTransitionPolicy(deps) { inventoryShortFollowupAlternate || hasInventoryRootTemporalFollowupPrimary || hasInventoryRootTemporalFollowupAlternate || + hasInventorySupplierOverlapRootFollowup || hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate || hasSelectedObjectInventorySignalPrimary || diff --git a/llm_normalizer/backend/tests/assistantTransitionPolicy.test.ts b/llm_normalizer/backend/tests/assistantTransitionPolicy.test.ts index f88028e..bdcbd80 100644 --- a/llm_normalizer/backend/tests/assistantTransitionPolicy.test.ts +++ b/llm_normalizer/backend/tests/assistantTransitionPolicy.test.ts @@ -153,6 +153,133 @@ describe("assistantTransitionPolicy", () => { expect(carryover?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date"); }); + it("carries inventory root date into supplier-overlap current-stock follow-up", () => { + const organization = "\u041e\u041e\u041e \"\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441\""; + const warehouse = "\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434"; + const rootFilters = { + period_from: "2021-09-01", + period_to: "2021-09-30", + as_of_date: "2021-09-30", + organization, + warehouse + }; + const policy = buildPolicy({ + findLastAddressAssistantItem: () => ({ + text: "\u041d\u0430 30.09.2021 \u043f\u043e \u0441\u043a\u043b\u0430\u0434\u0443 \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u044b \u043e\u0441\u0442\u0430\u0442\u043a\u0438.", + debug: { + detected_intent: "inventory_on_hand_as_of_date", + selected_recipe: "address_inventory_on_hand_as_of_date_v1", + extracted_filters: rootFilters, + anchor_type: "warehouse", + anchor_value_resolved: warehouse + } + }), + findRecentInventoryRootFrame: () => ({ + intent: "inventory_on_hand_as_of_date", + filters: rootFilters, + anchorType: "warehouse", + anchorValue: warehouse, + currentFrameKind: "inventory_root" + }), + hasAddressFollowupContextSignal: () => false, + hasStandaloneAddressTopicSignal: () => true, + resolveAddressIntent: () => ({ intent: "inventory_supplier_stock_overlap_as_of_date" }), + resolveAddressIntentFamily: (intent: unknown) => + String(intent ?? "").startsWith("inventory_") ? "inventory" : null + }); + + const carryover = policy.resolveAddressFollowupCarryoverContext( + "\u0423 \u043a\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0431\u044b\u043b\u0438 \u043a\u0443\u043f\u043b\u0435\u043d\u044b \u0442\u043e\u0432\u0430\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0435\u0439\u0447\u0430\u0441 \u043b\u0435\u0436\u0430\u0442 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434", + [], + "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0442\u043e\u0432\u0430\u0440\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434 \u0432 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442", + { + predecomposeContract: { + intent: "inventory_supplier_stock_overlap_as_of_date" + } + }, + { + session_context: { + date_scope: rootFilters, + organization_scope: organization, + active_focus_object: { + object_type: "organization", + label: organization + } + } + } + ); + + expect(carryover?.followupContext?.previous_intent).toBe("inventory_on_hand_as_of_date"); + expect(carryover?.followupContext?.target_intent).toBe("inventory_supplier_stock_overlap_as_of_date"); + expect(carryover?.followupContext?.previous_filters).toMatchObject(rootFilters); + expect(carryover?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date"); + expect(carryover?.followupContext?.root_filters).toMatchObject(rootFilters); + expect(carryover?.followupContext?.current_frame_kind).toBe("inventory_root"); + }); + + it("keeps supplier-overlap date on repeated current-residue supplier wording", () => { + const organization = "\u041e\u041e\u041e \"\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441\""; + const warehouse = "\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434"; + const rootFilters = { + period_from: "2021-09-01", + period_to: "2021-09-30", + as_of_date: "2021-09-30", + organization, + warehouse + }; + const policy = buildPolicy({ + findLastAddressAssistantItem: () => ({ + text: "\u041f\u043e \u0441\u043a\u043b\u0430\u0434\u0441\u043a\u043e\u043c\u0443 \u043e\u0441\u0442\u0430\u0442\u043a\u0443 \u043d\u0430 30.09.2021 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0438.", + debug: { + detected_intent: "inventory_supplier_stock_overlap_as_of_date", + selected_recipe: "address_inventory_supplier_stock_overlap_as_of_date_v1", + extracted_filters: rootFilters, + anchor_type: "warehouse", + anchor_value_resolved: warehouse + } + }), + findRecentInventoryRootFrame: () => ({ + intent: "inventory_on_hand_as_of_date", + filters: rootFilters, + anchorType: "warehouse", + anchorValue: warehouse, + currentFrameKind: "inventory_root" + }), + hasAddressFollowupContextSignal: () => false, + hasStandaloneAddressTopicSignal: () => true, + resolveAddressIntent: () => ({ intent: "inventory_supplier_stock_overlap_as_of_date" }), + resolveAddressIntentFamily: (intent: unknown) => + String(intent ?? "").startsWith("inventory_") ? "inventory" : null + }); + + const carryover = policy.resolveAddressFollowupCarryoverContext( + "\u041f\u043e \u043a\u0430\u043a\u043e\u043c\u0443 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0443 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0442\u043e\u0432\u0430\u0440\u043d\u044b\u0439 \u043e\u0441\u0442\u0430\u0442\u043e\u043a \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434", + [], + "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u043b\u0438 \u0442\u043e\u0432\u0430\u0440\u044b, \u043e\u0431\u0440\u0430\u0437\u0443\u044e\u0449\u0438\u0435 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043e\u0441\u0442\u0430\u0442\u043e\u043a \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434", + { + predecomposeContract: { + intent: "inventory_supplier_stock_overlap_as_of_date" + } + }, + { + session_context: { + date_scope: rootFilters, + organization_scope: organization, + active_focus_object: { + object_type: "warehouse", + label: warehouse + } + } + } + ); + + expect(carryover?.followupContext?.previous_intent).toBe("inventory_supplier_stock_overlap_as_of_date"); + expect(carryover?.followupContext?.target_intent).toBe("inventory_supplier_stock_overlap_as_of_date"); + expect(carryover?.followupContext?.previous_filters).toMatchObject(rootFilters); + expect(carryover?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date"); + expect(carryover?.followupContext?.current_frame_kind).toBe("inventory_root"); + }); + it("builds continuation contract from extracted root carryover", () => { const policy = buildPolicy(); diff --git a/llm_normalizer/data/autorun_generators/history.json b/llm_normalizer/data/autorun_generators/history.json index 88257f8..98043fd 100644 --- a/llm_normalizer/data/autorun_generators/history.json +++ b/llm_normalizer/data/autorun_generators/history.json @@ -1,4 +1,40 @@ [ + { + "generation_id": "gen-ag06030918-32dc7c", + "created_at": "2026-06-03T09:18:35+00:00", + "mode": "saved_user_sessions", + "title": "AGENT | Supplier-overlap follow-up keeps the inventory root date", + "count": 3, + "domain": "inventory_supplier_overlap_temporal_carryover", + "questions": [ + "какие остатки на складе на сентябрь 2021", + "У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад", + "По какому поставщику проходит текущий товарный остаток на складе Основной склад" + ], + "generated_by": "codex_agent", + "saved_case_set_file": "assistant_autogen_saved_user_sessions_20260603091835_gen-ag06030918-32dc7c.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_20260603091835_gen-ag06030918-32dc7c.json", + "saved_case_set_kind": "agent_semantic_scenario", + "agent_run": true, + "agent_focus": "Targeted replay for temporal carryover: after an inventory root slice for September 2021, colloquial supplier-overlap follow-ups with 'сейчас' / 'текущий товарный остаток' must stay on the confirmed root slice date instead of drifting to the wall-clock date.", + "architecture_phase": "turnaround_11", + "source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_inventory_supplier_overlap_temporal_carryover.json", + "scenario_id": "address_truth_harness_inventory_supplier_overlap_temporal_carryover", + "semantic_tags": [], + "validation_status": "accepted_live_replay", + "validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_temporal_carryover_p2", + "saved_after_validated_replay": true + } + }, { "generation_id": "gen-ag06030841-d61275", "created_at": "2026-06-03T08:41:17+00:00", diff --git a/llm_normalizer/data/autorun_generators/saved_sessions/assistant_saved_session_20260603091835_gen-ag06030918-32dc7c.json b/llm_normalizer/data/autorun_generators/saved_sessions/assistant_saved_session_20260603091835_gen-ag06030918-32dc7c.json new file mode 100644 index 0000000..45f85d8 --- /dev/null +++ b/llm_normalizer/data/autorun_generators/saved_sessions/assistant_saved_session_20260603091835_gen-ag06030918-32dc7c.json @@ -0,0 +1,133 @@ +{ + "saved_at": "2026-06-03T09:18:35+00:00", + "generation_id": "gen-ag06030918-32dc7c", + "mode": "saved_user_sessions", + "title": "AGENT | Supplier-overlap follow-up keeps the inventory root date", + "agent_run": true, + "questions": [ + "какие остатки на складе на сентябрь 2021", + "У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад", + "По какому поставщику проходит текущий товарный остаток на складе Основной склад" + ], + "metadata": { + "assistant_prompt_version": null, + "decomposition_prompt_version": null, + "prompt_fingerprint": null, + "agent_focus": "Targeted replay for temporal carryover: after an inventory root slice for September 2021, colloquial supplier-overlap follow-ups with 'сейчас' / 'текущий товарный остаток' must stay on the confirmed root slice date instead of drifting to the wall-clock date.", + "architecture_phase": "turnaround_11", + "source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_inventory_supplier_overlap_temporal_carryover.json", + "scenario_id": "address_truth_harness_inventory_supplier_overlap_temporal_carryover", + "semantic_tags": [], + "validation_status": "accepted_live_replay", + "validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_temporal_carryover_p2", + "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\\inventory_supplier_overlap_temporal_carryover_p2", + "final_status": "accepted", + "review_overall_status": "pass", + "business_overall_status": "pass", + "steps_total": 3, + "steps_passed": 3, + "steps_failed": 0, + "steps_with_business_failures": 0, + "steps_with_business_warnings": 0, + "acceptance_gate_passed": true, + "effective_runtime": { + "manifest_path": "artifacts\\domain_runs\\inventory_supplier_overlap_temporal_carryover_p2\\effective_runtime.json", + "runner": "domain_truth_harness.run-live", + "git_sha": "f0b84822d3e9283b5a8b823bd22ed571322a905f", + "backend_url": "http://127.0.0.1:8787", + "mcp_proxy_url": "http://127.0.0.1:6003", + "llm_provider": "local", + "llm_model": "unsloth/qwen3-30b-a3b-instruct-2507", + "temperature": 0.8, + "max_output_tokens": 900, + "prompt_version": "normalizer_v2_0_2", + "prompt_source": "file", + "prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1", + "prompt_registry_status": "pass" + }, + "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": "какие остатки на складе на сентябрь 2021", + "created_at": "2026-06-03T09:18:35+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-002", + "role": "user", + "text": "У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад", + "created_at": "2026-06-03T09:18:35+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-003", + "role": "user", + "text": "По какому поставщику проходит текущий товарный остаток на складе Основной склад", + "created_at": "2026-06-03T09:18: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 replay for temporal carryover: after an inventory root slice for September 2021, colloquial supplier-overlap follow-ups with 'сейчас' / 'текущий товарный остаток' must stay on the confirmed root slice date instead of drifting to the wall-clock date.", + "architecture_phase": "turnaround_11", + "source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_inventory_supplier_overlap_temporal_carryover.json", + "scenario_id": "address_truth_harness_inventory_supplier_overlap_temporal_carryover", + "semantic_tags": [], + "validation_status": "accepted_live_replay", + "validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_temporal_carryover_p2", + "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\\inventory_supplier_overlap_temporal_carryover_p2", + "final_status": "accepted", + "review_overall_status": "pass", + "business_overall_status": "pass", + "steps_total": 3, + "steps_passed": 3, + "steps_failed": 0, + "steps_with_business_failures": 0, + "steps_with_business_warnings": 0, + "acceptance_gate_passed": true, + "effective_runtime": { + "manifest_path": "artifacts\\domain_runs\\inventory_supplier_overlap_temporal_carryover_p2\\effective_runtime.json", + "runner": "domain_truth_harness.run-live", + "git_sha": "f0b84822d3e9283b5a8b823bd22ed571322a905f", + "backend_url": "http://127.0.0.1:8787", + "mcp_proxy_url": "http://127.0.0.1:6003", + "llm_provider": "local", + "llm_model": "unsloth/qwen3-30b-a3b-instruct-2507", + "temperature": 0.8, + "max_output_tokens": 900, + "prompt_version": "normalizer_v2_0_2", + "prompt_source": "file", + "prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1", + "prompt_registry_status": "pass" + }, + "saved_after_validated_replay": true + } + } + } +} diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_saved_user_sessions_20260603091835_gen-ag06030918-32dc7c.json b/llm_normalizer/data/eval_cases/assistant_autogen_saved_user_sessions_20260603091835_gen-ag06030918-32dc7c.json new file mode 100644 index 0000000..38b4f5c --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_saved_user_sessions_20260603091835_gen-ag06030918-32dc7c.json @@ -0,0 +1,34 @@ +{ + "suite_id": "assistant_saved_session_gen-ag06030918-32dc7c", + "suite_version": "0.1.0", + "schema_version": "assistant_saved_session_suite_v0_1", + "generated_at": "2026-06-03T09:18:35+00:00", + "generation_id": "gen-ag06030918-32dc7c", + "mode": "saved_user_sessions", + "title": "AGENT | Supplier-overlap follow-up keeps the inventory root date", + "domain": "inventory_supplier_overlap_temporal_carryover", + "scenario_count": 1, + "case_ids": [ + "SAVED-001" + ], + "cases": [ + { + "case_id": "SAVED-001", + "scenario_tag": "agent_saved_user_sessions", + "title": "AGENT | Supplier-overlap follow-up keeps the inventory root date", + "question_type": "followup", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие остатки на складе на сентябрь 2021" + }, + { + "user_message": "У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад" + }, + { + "user_message": "По какому поставщику проходит текущий товарный остаток на складе Основной склад" + } + ] + } + ] +}