import { describe, expect, it } from "vitest"; import { createAssistantTransitionPolicy } from "../src/services/assistantTransitionPolicy"; import { buildRootScopedCarryoverFilters } from "../src/services/assistantContinuityPolicy"; function toNonEmptyString(value: unknown): string | null { if (value === null || value === undefined) { return null; } const text = String(value).trim(); return text.length > 0 ? text : null; } function buildPolicy(overrides: Record = {}) { return createAssistantTransitionPolicy({ compactWhitespace: (value: string) => String(value ?? "").replace(/\s+/g, " ").trim(), repairAddressMojibake: (value: string) => value, countTokens: (value: string) => String(value ?? "").split(/\s+/).filter(Boolean).length, shouldHandleAsAssistantCapabilityMetaQuery: () => false, hasDataRetrievalRequestSignal: () => false, findLastAddressAssistantItem: () => ({ text: "1. Рабочая станция", debug: { detected_intent: "inventory_purchase_documents_for_item", extracted_filters: { item: "Рабочая станция" }, anchor_type: "item", anchor_value_resolved: "Рабочая станция" } }), findLastOrganizationClarificationAddressDebug: () => null, mergeKnownOrganizations: (values: unknown[]) => values, resolveOrganizationSelectionFromMessage: () => null, toNonEmptyString, buildAddressFollowupOffer: () => null, isImplicitAddressContinuationByLlm: () => false, isInventorySelectedObjectIntent: (intent: unknown) => [ "inventory_purchase_provenance_for_item", "inventory_purchase_documents_for_item", "inventory_sale_trace_for_item", "inventory_profitability_for_item", "inventory_purchase_to_sale_chain", "inventory_aging_by_purchase_date" ].includes(String(intent ?? "")), hasShortInventoryObjectFollowupSignal: () => false, resolveDebtRoleSwapFollowupIntent: () => null, hasAddressFollowupContextSignal: () => false, extractDisplayedEntityIndexMention: () => null, findRecentInventoryRootFrame: () => ({ intent: "inventory_on_hand_as_of_date", filters: { as_of_date: "2020-03-31", organization: 'ООО "Альтернатива Плюс"' }, anchorType: "organization", anchorValue: 'ООО "Альтернатива Плюс"' }), hasInventoryRootTemporalFollowupSignal: (message: string) => /март 2020/i.test(message), hasFollowupMarker: () => false, hasReferentialPointer: () => false, hasStandaloneAddressTopicSignal: () => false, resolveAddressIntent: () => ({ intent: "unknown" }), resolveAddressIntentFamily: (intent: unknown) => (intent ? String(intent) : null), readAddressFilterString: (debug: Record, key: string) => debug?.extracted_filters && typeof debug.extracted_filters === "object" ? toNonEmptyString((debug.extracted_filters as Record)[key]) : null, normalizeOrganizationScopeValue: (value: unknown) => toNonEmptyString(value), isInventoryDrilldownFrameIntent: (intent: unknown) => [ "inventory_purchase_provenance_for_item", "inventory_purchase_documents_for_item", "inventory_sale_trace_for_item", "inventory_profitability_for_item", "inventory_purchase_to_sale_chain", "inventory_aging_by_purchase_date" ].includes(String(intent ?? "")), isInventoryRootFrameIntent: (intent: unknown) => String(intent ?? "") === "inventory_on_hand_as_of_date", findRecentAddressFilterValue: () => null, hasForeignAccountingPivotOverInventoryMessage: () => false, buildRootScopedCarryoverFilters: ( previousFilters: Record, inventoryRootFrame: Record ) => ({ organization: toNonEmptyString(inventoryRootFrame?.filters?.organization) ?? toNonEmptyString(previousFilters?.organization), warehouse: toNonEmptyString(inventoryRootFrame?.filters?.warehouse) ?? toNonEmptyString(previousFilters?.warehouse), as_of_date: toNonEmptyString(previousFilters?.as_of_date) ?? toNonEmptyString(inventoryRootFrame?.filters?.as_of_date), period_from: toNonEmptyString(previousFilters?.period_from) ?? toNonEmptyString(inventoryRootFrame?.filters?.period_from), period_to: toNonEmptyString(previousFilters?.period_to) ?? toNonEmptyString(inventoryRootFrame?.filters?.period_to) }), inferDisplayedEntityTypeFromIntent: () => "item", extractDisplayedAddressEntityCandidates: () => [], resolveDisplayedAddressEntityMention: () => null, ...overrides }); } describe("assistantTransitionPolicy", () => { it("promotes inventory temporal follow-up into root-scoped carryover", () => { const policy = buildPolicy(); const carryover = policy.resolveAddressFollowupCarryoverContext( "остатки на март 2020", [], null, null, { session_context: { active_focus_object: { object_type: "item", label: "Рабочая станция" } } } ); expect(carryover?.followupSelectionMode).toBe("carry_root_context"); expect(carryover?.followupContext?.root_context_only).toBe(true); expect(carryover?.followupContext?.target_intent).toBe("inventory_on_hand_as_of_date"); expect(carryover?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ as_of_date: "2020-03-31", organization: 'ООО "Альтернатива Плюс"' }); }); it("promotes same-date inventory restatement after drilldown into root-scoped carryover", () => { const policy = buildPolicy({ hasInventoryRootTemporalFollowupSignal: () => false }); const carryover = policy.resolveAddressFollowupCarryoverContext( "покажи еще раз остатки на эту же дату", [], null, null, null ); expect(carryover?.followupSelectionMode).toBe("carry_root_context"); expect(carryover?.followupContext?.root_context_only).toBe(true); expect(carryover?.followupContext?.previous_intent).toBeUndefined(); expect(carryover?.followupContext?.previous_filters).toMatchObject({ as_of_date: "2020-03-31", organization: 'ООО "Альтернатива Плюс"' }); expect(carryover?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date"); }); it("builds continuation contract from extracted root carryover", () => { const policy = buildPolicy(); const contract = policy.buildAddressDialogContinuationContractV2( "остатки на эту дату", "остатки на эту дату", { followupContext: { root_intent: "inventory_on_hand_as_of_date", previous_anchor_type: "item", previous_anchor_value: "Рабочая станция" }, previousSourceIntent: "inventory_purchase_documents_for_item", previousAddressIntent: null, followupSelectionMode: "carry_root_context", hasImplicitContinuationSignal: true }, { predecomposeContract: { intent: "unknown" } } ); expect(contract.decision).toBe("continue_previous"); expect(contract.target_intent).toBe("inventory_on_hand_as_of_date"); expect(contract.decision_reasons).toContain("root_context_only_carryover"); expect(contract.decision_reasons).toContain("implicit_continuation_by_llm"); expect(contract.anchor_type).toBe("item"); expect(contract.anchor_value).toBe("Рабочая станция"); }); it("prefers carryover target intent over llm contract drift in continuation contract", () => { const policy = buildPolicy(); const contract = policy.buildAddressDialogContinuationContractV2( "покажи договор по гамме", "покажи договор по гамме", { followupContext: { previous_intent: "customer_revenue_and_payments", target_intent: "list_contracts_by_counterparty", previous_anchor_type: "counterparty", previous_anchor_value: "Гамма-мебель, ООО" }, previousSourceIntent: "customer_revenue_and_payments", previousAddressIntent: "customer_revenue_and_payments", followupSelectionMode: "carry_referenced_entity", hasImplicitContinuationSignal: false }, { predecomposeContract: { intent: "unknown" } } ); expect(contract.target_intent).toBe("list_contracts_by_counterparty"); expect(contract.decision).toBe("continue_previous"); }); it("retargets displayed counterparty follow-up through shared referenced-entity carryover", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "1. SVK Group\n2. Gamma", debug: { detected_intent: "customer_revenue_and_payments", extracted_filters: { organization: 'РћРћРћ "Альтернатива Плюс"', period_from: "2017-01-01", period_to: "2017-12-31" } } }), hasAddressFollowupContextSignal: () => true, inferDisplayedEntityTypeFromIntent: () => "counterparty", extractDisplayedAddressEntityCandidates: () => [{ entityType: "counterparty", value: "SVK Group" }], resolveDisplayedAddressEntityMention: () => ({ entityType: "counterparty", value: "SVK Group" }), resolveAddressIntent: () => ({ intent: "unknown" }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( "покажи договоры по СВК", [], null, null, null ); expect(carryover?.followupSelectionMode).toBe("carry_referenced_entity"); expect(carryover?.followupContext?.target_intent).toBe("list_contracts_by_counterparty"); expect(carryover?.followupContext?.previous_anchor_type).toBe("counterparty"); expect(carryover?.followupContext?.previous_anchor_value).toBe("SVK Group"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ organization: 'РћРћРћ "Альтернатива Плюс"', counterparty: "SVK Group", period_from: "2017-01-01", period_to: "2017-12-31" }); expect(carryover?.followupContext?.resolved_counterparty_from_display).toBe(true); }); it("retargets same-date inventory follow-up away from receivables intent", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Подтвержденная дебиторская задолженность на 31.03.2020 собрана.", debug: { detected_intent: "receivables_confirmed_as_of_date", extracted_filters: { organization: 'ООО "Альтернатива Плюс"', as_of_date: "2020-03-31", period_from: "2020-03-01", period_to: "2020-03-31" }, anchor_type: "organization", anchor_value_resolved: 'ООО "Альтернатива Плюс"' } }), hasAddressFollowupContextSignal: () => true, hasReferentialPointer: () => true, findRecentInventoryRootFrame: () => null, resolveAddressIntent: () => ({ intent: "unknown" }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( "остатки по складу на эту же дату", [], null, null, null ); expect(carryover?.followupSelectionMode).toBe("carry_previous_intent"); expect(carryover?.followupContext?.previous_intent).toBe("receivables_confirmed_as_of_date"); expect(carryover?.followupContext?.target_intent).toBe("inventory_on_hand_as_of_date"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ organization: 'ООО "Альтернатива Плюс"', as_of_date: "2020-03-31", period_from: "2020-03-01", period_to: "2020-03-31" }); expect(carryover?.followupContext?.root_context_only).toBeUndefined(); }); it("hydrates selected-item carryover through shared continuity helper for short object follow-up", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Подтвержден складской срез по выбранной позиции.", debug: { detected_intent: "inventory_on_hand_as_of_date", extracted_filters: { organization: 'РћРћРћ "Альтернатива Плюс"', as_of_date: "2020-03-31" } } }), hasAddressFollowupContextSignal: () => true, hasShortInventoryObjectFollowupSignal: () => true, findRecentInventoryRootFrame: () => null, resolveAddressIntent: () => ({ intent: "unknown" }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( "по этой позиции покажи документы", [], null, null, { session_context: { active_focus_object: { object_type: "item", label: "Workstation Focus" } } } ); expect(carryover?.followupContext?.previous_filters).toMatchObject({ organization: 'РћРћРћ "Альтернатива Плюс"', as_of_date: "2020-03-31", item: "Workstation Focus" }); expect(carryover?.followupContext?.previous_anchor_type).toBe("item"); expect(carryover?.followupContext?.previous_anchor_value).toBe("Workstation Focus"); }); it("hydrates follow-up organization from shared assistant authority when local history filters are empty", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Подтвержденная дебиторская задолженность на 31.03.2020 собрана.", debug: { detected_intent: "receivables_confirmed_as_of_date", extracted_filters: { as_of_date: "2020-03-31", period_from: "2020-03-01", period_to: "2020-03-31" }, anchor_type: "organization", anchor_value_resolved: null } }), hasAddressFollowupContextSignal: () => true, hasReferentialPointer: () => true, findRecentInventoryRootFrame: () => null, findRecentAddressFilterValue: () => null, resolveAddressIntent: () => ({ intent: "unknown" }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( "остатки по складу на эту же дату", [ { role: "assistant", text: "Компания уже выбрана в живом чате.", debug: { assistant_active_organization: 'ООО "Альтернатива Плюс"', assistant_known_organizations: ['ООО "Альтернатива Плюс"', 'ООО "Лайт"'] } } ], null, null, null ); expect(carryover?.followupContext?.previous_filters).toMatchObject({ organization: 'ООО "Альтернатива Плюс"', as_of_date: "2020-03-31", period_from: "2020-03-01", period_to: "2020-03-31" }); expect(carryover?.followupContext?.target_intent).toBe("inventory_on_hand_as_of_date"); }); it("hydrates carryover anchor from shared debug helpers when explicit anchor fields are absent", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Подтвержденный складской срез собран.", debug: { detected_intent: "inventory_on_hand_as_of_date", extracted_filters: { item: "Рабочая станция", period_from: "2020-03-01" }, address_root_frame_context: { as_of_date: "2020-03-31", period_to: "2020-03-31" }, anchor_type: null, anchor_value_resolved: null } }), hasAddressFollowupContextSignal: () => true, hasReferentialPointer: () => true, findRecentInventoryRootFrame: () => null, findRecentAddressFilterValue: () => null, resolveAddressIntent: () => ({ intent: "unknown" }) }); const carryover = policy.resolveAddressFollowupCarryoverContext("по этой позиции", [], null, null, null); expect(carryover?.followupContext?.previous_anchor_type).toBe("item"); expect(carryover?.followupContext?.previous_anchor_value).toBe("Рабочая станция"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ item: "Рабочая станция", period_from: "2020-03-01" }); }); it("bridges selected-item purchase provenance into a VAT period follow-up", () => { const item = "Рабочая станция универсального специалиста"; const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: [ `По позиции ${item} однозначный поставщик не подтвержден.`, "Подтверждение:", "- Первая найденная дата закупки: 05.02.2015.", "- Последняя найденная дата закупки: 22.07.2015." ].join("\n"), debug: { detected_intent: "inventory_purchase_provenance_for_item", extracted_filters: { item, organization: 'ООО "Альтернатива Плюс"', as_of_date: "2016-03-31" }, anchor_type: "item", anchor_value_resolved: item } }), hasAddressFollowupContextSignal: () => false, findRecentInventoryRootFrame: () => null, resolveAddressIntent: () => ({ intent: "unknown" }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( "ндс можешь прикинуть на дату покупки рабочей станции?", [], null, null, { session_context: { active_focus_object: { object_type: "item", label: item, provenance_result_set_id: "rs-provenance" }, active_result_set_id: "rs-provenance" }, result_sets: [ { result_set_id: "rs-provenance", intent: "inventory_purchase_provenance_for_item", entity_refs: [ { index: 1, entity_type: "item", value: "Поступление товаров и услуг 00000000023 от 05.02.2015 0:00:00" } ] } ] } ); expect(carryover?.followupContext?.previous_intent).toBe("inventory_purchase_provenance_for_item"); expect(carryover?.followupContext?.target_intent).toBe("vat_liability_confirmed_for_tax_period"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ item, organization: 'ООО "Альтернатива Плюс"', period_from: "2015-02-01", period_to: "2015-02-28" }); }); it("keeps selected-object continuity for purchase-date VAT bridge even when an inventory root frame exists", () => { const item = "Рабочая станция универсального специалиста"; const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: [ `По позиции ${item} однозначный поставщик не подтвержден.`, "Подтверждение:", "- Первая найденная дата закупки: 05.02.2015." ].join("\n"), debug: { detected_intent: "inventory_purchase_provenance_for_item", extracted_filters: { item, organization: 'ООО "Альтернатива Плюс"', as_of_date: "2016-03-31" }, anchor_type: "item", anchor_value_resolved: item } }), hasAddressFollowupContextSignal: () => false, hasForeignAccountingPivotOverInventoryMessage: () => true, resolveAddressIntent: () => ({ intent: "unknown" }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( "ндс можешь прикинуть на дату покупки рабочей станции?", [], null, null, { session_context: { active_focus_object: { object_type: "item", label: item, provenance_result_set_id: "rs-provenance" }, active_result_set_id: "rs-provenance" }, result_sets: [ { result_set_id: "rs-provenance", intent: "inventory_purchase_provenance_for_item", entity_refs: [ { index: 1, entity_type: "item", value: "Поступление товаров и услуг 00000000023 от 05.02.2015 0:00:00" } ] } ] } ); expect(carryover?.followupSelectionMode).toBe("carry_previous_intent"); expect(carryover?.followupContext?.root_context_only).toBeUndefined(); expect(carryover?.followupContext?.previous_intent).toBe("inventory_purchase_provenance_for_item"); expect(carryover?.followupContext?.target_intent).toBe("vat_liability_confirmed_for_tax_period"); }); it("keeps purchase-date VAT bridge after unsupported verification interrupt", () => { const item = "Рабочая станция универрсального специалиста"; const provenanceItem = { role: "assistant", text: [ `РџРѕ позиции ${item} однозначный поставщик РЅРµ подтвержден.`, "Подтверждение:", "- Первая найденная дата закупки: 05.02.2015." ].join("\n"), debug: { execution_lane: "address_query", answer_grounding_check: { status: "grounded" }, detected_intent: "inventory_purchase_provenance_for_item", extracted_filters: { item, organization: 'РћРћРћ "Альтернатива Плюс"', as_of_date: "2016-03-31" }, anchor_type: "item", anchor_value_resolved: item } } as any; const unsupportedInterrupt = { role: "assistant", text: "РџРѕРєР° РЅРµ РјРѕРіСѓ точно подтвердить, что именно это ты имеешь РІ РІРёРґСѓ.", debug: { execution_lane: "address_query", detected_intent: "unknown", answer_grounding_check: { status: "unsupported" } } } as any; const policy = buildPolicy({ findLastAddressAssistantItem: () => unsupportedInterrupt, hasAddressFollowupContextSignal: () => false, findRecentInventoryRootFrame: () => null, resolveAddressIntent: () => ({ intent: "unknown" }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( "ндс можешь прикинуть на дату покупки рабочей станции?", [provenanceItem, unsupportedInterrupt], null, null, { session_context: { active_focus_object: { object_type: "item", label: item, provenance_result_set_id: "rs-provenance-interrupt" }, active_result_set_id: "rs-provenance-interrupt" }, result_sets: [ { result_set_id: "rs-provenance-interrupt", intent: "inventory_purchase_provenance_for_item", entity_refs: [ { index: 1, entity_type: "item", value: "Поступление товаров и услуг 00000000023 от 05.02.2015 0:00:00" } ] } ] } ); expect(carryover?.followupContext?.previous_intent).toBe("inventory_purchase_provenance_for_item"); expect(carryover?.followupContext?.target_intent).toBe("vat_liability_confirmed_for_tax_period"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ item, organization: 'РћРћРћ "Альтернатива Плюс"', period_from: "2015-02-01", period_to: "2015-02-28" }); }); it("drops stale carryover for a fresh standalone topic from another intent family", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Прогноз НДС на март 2020 собран.", debug: { detected_intent: "vat_payable_forecast", extracted_filters: { period_from: "2020-03-01", period_to: "2020-03-31" } } }), hasAddressFollowupContextSignal: () => true, hasStandaloneAddressTopicSignal: () => true, resolveAddressIntent: () => ({ intent: "inventory_on_hand_as_of_date" }), resolveAddressIntentFamily: (intent: unknown) => { if (String(intent ?? "").startsWith("vat_")) return "vat"; if (String(intent ?? "").startsWith("inventory_")) return "inventory"; return null; } }); const carryover = policy.resolveAddressFollowupCarryoverContext( "остаток на складе за май 2020", [], null, null, null ); expect(carryover).toBeNull(); }); it("keeps document intent for short counterparty retarget wording with action verb", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Собран список документов по контрагенту Чапурнов.", debug: { detected_intent: "list_documents_by_counterparty", extracted_filters: { counterparty: "Чапурнов" }, anchor_type: "counterparty", anchor_value_resolved: "Чапурнов" } }), buildAddressFollowupOffer: () => ({ enabled: true, source_intent: "list_documents_by_counterparty", suggested_intents: ["bank_operations_by_counterparty"] }), isImplicitAddressContinuationByLlm: () => true }); const carryover = policy.resolveAddressFollowupCarryoverContext("покажи по свк", [], null, null, null); expect(carryover?.followupContext?.previous_intent).toBe("list_documents_by_counterparty"); expect(carryover?.followupSelectionMode).toBe("carry_previous_intent"); }); it("switches from documents to suggested bank operations on a pronoun follow-up with payment cue", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Собран СЃРїРёСЃРѕРє документов РїРѕ контрагенту Р–СѓРєРѕРІРєР° 51.", debug: { detected_intent: "list_documents_by_counterparty", extracted_filters: { counterparty: "Р–СѓРєРѕРІРєР° 51" }, anchor_type: "counterparty", anchor_value_resolved: "РўРЎР– \\Р–СѓРєРѕРІРєР° 51\\" } }), buildAddressFollowupOffer: () => ({ enabled: true, source_intent: "list_documents_by_counterparty", suggested_intents: ["bank_operations_by_counterparty", "list_contracts_by_counterparty"] }), hasAddressFollowupContextSignal: () => true, hasReferentialPointer: () => true }); const carryover = policy.resolveAddressFollowupCarryoverContext("Р° РїРѕ нему платежи?", [], null, null, null); expect(carryover?.followupContext?.previous_intent).toBe("bank_operations_by_counterparty"); expect(carryover?.followupContext?.target_intent).toBe("bank_operations_by_counterparty"); expect(carryover?.followupContext?.previous_anchor_type).toBe("counterparty"); expect(carryover?.followupSelectionMode).toBe("switch_to_suggested_intent"); expect(carryover?.hasSuggestedIntentPivotSignal).toBe(true); const contract = policy.buildAddressDialogContinuationContractV2( "Р° РїРѕ нему платежи?", "Покажи платежи, связанные СЃ этим объектом", carryover, { predecomposeContract: { intent: "unknown" } } ); expect(contract.decision).toBe("switch_to_suggested"); expect(contract.target_intent).toBe("bank_operations_by_counterparty"); expect(contract.decision_reasons).toContain("suggested_intent_followup_pivot"); }); it("switches from payments to suggested contracts on a pronoun follow-up even when generic follow-up signal is weak", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Собран список банковских операций по контрагенту ТСЖ \\Жуковка 51\\.", debug: { detected_intent: "bank_operations_by_counterparty", extracted_filters: { counterparty: "ТСЖ \\Жуковка 51\\" }, anchor_type: "counterparty", anchor_value_resolved: "ТСЖ \\Жуковка 51\\" } }), buildAddressFollowupOffer: () => ({ enabled: true, source_intent: "bank_operations_by_counterparty", suggested_intents: ["list_documents_by_counterparty", "list_contracts_by_counterparty"] }), hasAddressFollowupContextSignal: () => false, hasReferentialPointer: () => true }); const carryover = policy.resolveAddressFollowupCarryoverContext("А по нему договоры?", [], null, null, null); expect(carryover?.followupContext?.previous_intent).toBe("list_contracts_by_counterparty"); expect(carryover?.followupContext?.target_intent).toBe("list_contracts_by_counterparty"); expect(carryover?.followupContext?.previous_anchor_type).toBe("counterparty"); expect(carryover?.hasSuggestedIntentPivotSignal).toBe(true); expect(carryover?.followupSelectionMode).toBe("switch_to_suggested_intent"); const contract = policy.buildAddressDialogContinuationContractV2( "А по нему договоры?", "Покажи договоры, связанные с указанным объектом", carryover, { predecomposeContract: { intent: "unknown" } } ); expect(contract.decision).toBe("switch_to_suggested"); expect(contract.target_intent).toBe("list_contracts_by_counterparty"); expect(contract.decision_reasons).toContain("suggested_intent_followup_pivot"); }); it("keeps root-scoped carryover for foreign accounting pivot over inventory drilldown", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Собран sale trace по позиции.", debug: { detected_intent: "inventory_sale_trace_for_item", extracted_filters: { item: "Кромка с клеем 33 дуб ниагара 137 м", organization: 'ООО "Альтернатива Плюс"', as_of_date: "2021-03-31" }, anchor_type: "item", anchor_value_resolved: "Кромка с клеем 33 дуб ниагара 137 м" } }), hasAddressFollowupContextSignal: () => true, resolveAddressIntent: () => ({ intent: "vat_payable_confirmed_as_of_date" }), resolveAddressIntentFamily: (intent: unknown) => { if (String(intent ?? "").startsWith("vat_")) return "vat"; if (String(intent ?? "").startsWith("inventory_")) return "inventory"; return null; }, hasForeignAccountingPivotOverInventoryMessage: () => true, findRecentInventoryRootFrame: () => ({ intent: "inventory_on_hand_as_of_date", filters: { organization: 'ООО "Альтернатива Плюс"', warehouse: "Основной склад", as_of_date: "2021-03-31", period_from: "2021-03-01", period_to: "2021-03-31" }, anchorType: "organization", anchorValue: 'ООО "Альтернатива Плюс"' }) }); const carryover = policy.resolveAddressFollowupCarryoverContext("а ндс?", [], null, null, null); expect(carryover?.followupSelectionMode).toBe("carry_root_context"); expect(carryover?.followupContext?.root_context_only).toBe(true); expect(carryover?.followupContext?.previous_intent).toBeUndefined(); expect(carryover?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date"); expect(carryover?.followupContext?.previous_filters).toEqual({ organization: 'ООО "Альтернатива Плюс"', warehouse: "Основной склад", as_of_date: "2021-03-31", period_from: "2021-03-01", period_to: "2021-03-31" }); }); it("prefers the freshest previous date scope over a stale inventory root frame during same-date pivot", () => { const filters = buildRootScopedCarryoverFilters( { organization: 'ООО "Альтернатива Плюс"', as_of_date: "2020-03-31", period_from: "2020-03-01", period_to: "2020-03-31" }, { filters: { organization: 'ООО "Альтернатива Плюс"', warehouse: "Основной склад", as_of_date: "2021-03-31", period_from: "2021-03-01", period_to: "2021-03-31" } } ); expect(filters).toEqual({ organization: 'ООО "Альтернатива Плюс"', warehouse: "Основной склад", as_of_date: "2020-03-31", period_from: "2020-03-01", period_to: "2020-03-31" }); }); it("does not attach address follow-up carryover to explicit capability-meta questions", () => { const policy = buildPolicy({ shouldHandleAsAssistantCapabilityMetaQuery: (message: unknown) => /дельт[ауы]?\s+по\s+договорам/iu.test(String(message ?? "")), hasAddressFollowupContextSignal: () => true }); const carryover = policy.resolveAddressFollowupCarryoverContext( "ты умеешь считать дельту по договорам?", [], "проверить возможность расчета дельты по договорам", { predecomposeContract: { mode: "address_query", intent: "unknown" } }, null ); expect(carryover).toBeNull(); }); it("retargets selected-object provenance follow-up from inventory root when semantic scope is already detected", () => { const policy = buildPolicy({ hasAddressFollowupContextSignal: () => true, findLastAddressAssistantItem: () => ({ text: "На 31.03.2016 на складе подтверждено 2 позиции.", debug: { detected_intent: "inventory_on_hand_as_of_date", extracted_filters: { organization: 'ООО "Альтернатива Плюс"', warehouse: "Основной склад", as_of_date: "2016-03-31", period_from: "2016-03-01", period_to: "2016-03-31" }, anchor_type: "organization", anchor_value_resolved: 'ООО "Альтернатива Плюс"' } }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( 'По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где взяли это?', [], null, { predecomposeContract: { mode: "address_query", intent: "unknown", semantics: { selected_object_scope_detected: true } } }, null ); expect(carryover?.followupContext?.target_intent).toBe("inventory_purchase_provenance_for_item"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ organization: 'ООО "Альтернатива Плюс"', item: "Рабочая станция универсального специалиста (индивидуальное изготовление)" }); expect(carryover?.followupContext?.previous_anchor_type).toBe("item"); }); it("prefers root-frame dates over stale drilldown filters when hydrating previous filters", () => { const organization = "Org Alt"; const policy = buildPolicy({ findLastAddressAssistantItem: (_items: unknown[]) => ({ text: "Workstation drilldown", debug: { detected_intent: "inventory_purchase_documents_for_item", extracted_filters: { item: "Workstation", organization, as_of_date: "2021-04-15" }, anchor_type: "item", anchor_value_resolved: "Workstation", address_root_frame_context: { root_intent: "inventory_on_hand_as_of_date", root_filters: { organization, warehouse: "Main Warehouse", as_of_date: "2021-03-31", period_from: "2021-03-01", period_to: "2021-03-31" }, root_anchor_type: "organization", root_anchor_value: organization, current_frame_kind: "inventory_drilldown" } } }), findRecentInventoryRootFrame: () => null, hasInventoryRootTemporalFollowupSignal: (message: string) => /эту же дату/i.test(message) }); const items = [ { role: "assistant", text: "Workstation drilldown", debug: { execution_lane: "address_query", answer_grounding_check: { status: "grounded" }, detected_intent: "inventory_purchase_documents_for_item", extracted_filters: { item: "Workstation", organization, as_of_date: "2021-04-15" }, anchor_type: "item", anchor_value_resolved: "Workstation", address_root_frame_context: { root_intent: "inventory_on_hand_as_of_date", root_filters: { organization, warehouse: "Main Warehouse", as_of_date: "2021-03-31", period_from: "2021-03-01", period_to: "2021-03-31" }, root_anchor_type: "organization", root_anchor_value: organization, current_frame_kind: "inventory_drilldown" } } } ]; const carryover = policy.resolveAddressFollowupCarryoverContext( "остатки на эту же дату", items as any, null, null, null ); expect(carryover?.followupContext?.root_context_only).toBe(true); expect(carryover?.followupContext?.previous_filters).toMatchObject({ organization, warehouse: "Main Warehouse", as_of_date: "2021-03-31", period_from: "2021-03-01", period_to: "2021-03-31" }); expect(carryover?.followupContext?.previous_filters?.as_of_date).not.toBe("2021-04-15"); }); it("drops carryover when current-turn meaning forbids stale replay", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Documents by previous counterparty", debug: { execution_lane: "address_query", answer_grounding_check: { status: "grounded" }, detected_intent: "list_documents_by_counterparty", extracted_filters: { counterparty: "Previous Counterparty", organization: "Org Alt" }, anchor_type: "counterparty", anchor_value_resolved: "Previous Counterparty" } }), hasAddressFollowupContextSignal: () => true, resolveAssistantTurnMeaning: () => ({ schema_version: "assistant_turn_meaning_v1", asked_domain_family: "counterparty", asked_action_family: "counterparty_value_or_turnover", unsupported_but_understood_family: "counterparty_value_or_turnover", explicit_entity_candidates: [ { type: "counterparty", value: "svk", source: "current_turn_loose_entity_tail" } ], stale_replay_forbidden: true }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( "\u043a\u0430\u043a\u043e\u0439 \u043e\u0431\u043e\u0440\u043e\u0442 \u0431\u044b\u043b \u0441\u0432\u043a", [], null, null, null ); expect(carryover).toBeNull(); }); it("drops carryover for a supported current-turn counterparty revenue pivot with a new entity", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Documents by previous counterparty", debug: { execution_lane: "address_query", answer_grounding_check: { status: "grounded" }, detected_intent: "list_documents_by_counterparty", extracted_filters: { counterparty: "Previous Counterparty", organization: "Org Alt" }, anchor_type: "counterparty", anchor_value_resolved: "Previous Counterparty" } }), hasAddressFollowupContextSignal: () => true, isImplicitAddressContinuationByLlm: () => true, resolveAssistantTurnMeaning: () => ({ schema_version: "assistant_turn_meaning_v1", asked_domain_family: "counterparty", asked_action_family: "counterparty_value_or_turnover", explicit_intent_candidate: "customer_revenue_and_payments", intent_override_strength: "explicit_current_turn_intent", explicit_entity_candidates: [ { type: "counterparty", value: "svk", source: "current_turn_loose_entity_tail" } ], stale_replay_forbidden: false }), resolveAddressIntent: () => ({ intent: "customer_revenue_and_payments" }), resolveAddressIntentFamily: () => "counterparty" }); const carryover = policy.resolveAddressFollowupCarryoverContext( "\u043a\u0430\u043a\u043e\u0439 \u043e\u0431\u043e\u0440\u043e\u0442 \u0431\u044b\u043b \u0441\u0432\u043a", [], null, null, null ); expect(carryover).toBeNull(); }); it("drops carryover for broad business evaluation so lifecycle context does not stick to the new question", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Lifecycle answer", debug: { execution_lane: "address_query", answer_grounding_check: { status: "grounded" }, detected_intent: "counterparty_activity_lifecycle", extracted_filters: { organization: 'ООО "Альтернатива Плюс"', period_to: "2020-12-31" }, anchor_type: "organization", anchor_value_resolved: 'ООО "Альтернатива Плюс"' } }), hasAddressFollowupContextSignal: () => true, resolveAssistantTurnMeaning: () => ({ schema_version: "assistant_turn_meaning_v1", asked_domain_family: "business_summary", asked_action_family: "broad_evaluation", explicit_intent_candidate: null, unsupported_but_understood_family: "broad_business_evaluation", explicit_entity_candidates: [], stale_replay_forbidden: true }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( "Как ты оценишь деятельность компании?", [], null, null, null ); expect(carryover).toBeNull(); }); it("reuses grounded MCP discovery payout context for a short year-switch follow-up", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => null, hasAddressFollowupContextSignal: () => true }); const carryover = policy.resolveAddressFollowupCarryoverContext( "а теперь за 2021?", [ { role: "assistant", text: "Подтверждены исходящие платежи по Группа СВК за 2020 год.", debug: { execution_lane: "living_chat", mcp_discovery_response_applied: true, assistant_active_organization: "ООО Альтернатива Плюс", assistant_mcp_discovery_entry_point_v1: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", entry_status: "bridge_executed", turn_input: { turn_meaning_ref: { asked_action_family: "payout", explicit_entity_candidates: ["Группа СВК"], explicit_organization_scope: "ООО Альтернатива Плюс", explicit_date_scope: "2020" } }, bridge: { bridge_status: "answer_draft_ready", business_fact_answer_allowed: true, pilot: { pilot_scope: "counterparty_supplier_payout_query_movements_v1" }, answer_draft: { answer_mode: "confirmed_with_bounded_inference" } } } } } ], null, null, null ); expect(carryover?.followupSelectionMode).toBe("carry_previous_intent"); expect(carryover?.followupContext?.previous_intent).toBe("supplier_payouts_profile"); expect(carryover?.followupContext?.target_intent).toBe("supplier_payouts_profile"); expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe( "counterparty_supplier_payout_query_movements_v1" ); expect(carryover?.followupContext?.previous_anchor_type).toBe("counterparty"); expect(carryover?.followupContext?.previous_anchor_value).toBe("Группа СВК"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ counterparty: "Группа СВК", organization: "ООО Альтернатива Плюс", period_from: "2020-01-01", period_to: "2020-12-31" }); }); it("carries business overview boundary context through short capability-shaped follow-up", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => null, shouldHandleAsAssistantCapabilityMetaQuery: () => true, hasDataRetrievalRequestSignal: () => false, hasAddressFollowupContextSignal: () => false }); const organization = "ООО Альтернатива Плюс"; const carryover = policy.resolveAddressFollowupCarryoverContext( "то есть просрочку доказать нельзя, коротко почему?", [ { role: "assistant", text: "На 2020-12-31 подтвержденной просрочки нет: в договорах срок оплаты не установлен.", debug: { execution_lane: "living_chat", mcp_discovery_response_applied: true, assistant_active_organization: organization, assistant_mcp_discovery_entry_point_v1: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", entry_status: "bridge_executed", turn_input: { turn_meaning_ref: { asked_domain_family: "business_overview", asked_action_family: "debt_due_date_boundary", unsupported_but_understood_family: "debt_due_date_boundary", explicit_organization_scope: organization, explicit_date_scope: "2020" } }, bridge: { bridge_status: "answer_draft_ready", business_fact_answer_allowed: true, pilot: { pilot_scope: "business_overview_route_template_v1" }, answer_draft: { answer_mode: "confirmed_with_bounded_inference" } } } } } ], null, null, null ); expect(carryover?.followupSelectionMode).toBe("carry_previous_intent"); expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe( "business_overview_route_template_v1" ); expect(carryover?.followupContext?.previous_filters).toMatchObject({ organization, period_from: "2020-01-01", period_to: "2020-12-31" }); }); it("carries resolved entity candidates from grounded entity-resolution discovery into followup context", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => null, hasAddressFollowupContextSignal: () => true }); const carryover = policy.resolveAddressFollowupCarryoverContext( "по нему документы за 2020 год", [ { role: "assistant", text: "В текущем каталожном срезе 1С по запросу \"СВК\" найден контрагент \"Группа СВК\".", debug: { execution_lane: "living_chat", mcp_discovery_response_applied: true, assistant_mcp_discovery_entry_point_v1: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", entry_status: "bridge_executed", turn_input: { turn_meaning_ref: { asked_domain_family: "entity_resolution", asked_action_family: "search_business_entity", explicit_entity_candidates: ["СВК"] } }, bridge: { bridge_status: "answer_draft_ready", business_fact_answer_allowed: true, pilot: { pilot_scope: "entity_resolution_search_v1", derived_entity_resolution: { requested_entity: "СВК", resolution_status: "resolved", resolved_entity: "Группа СВК", ambiguity_candidates: [] } }, answer_draft: { answer_mode: "confirmed_with_bounded_inference" } } } } } ], null, null, null ); expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe("entity_resolution_search_v1"); expect(carryover?.followupContext?.previous_discovery_entity_candidates).toEqual(["Группа СВК", "СВК"]); expect(carryover?.followupContext?.previous_anchor_type).toBe("counterparty"); expect(carryover?.followupContext?.previous_anchor_value).toBe("Группа СВК"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ counterparty: "Группа СВК" }); }); it("carries ambiguity candidates from entity-resolution discovery into followup context", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => null, hasAddressFollowupContextSignal: () => true }); const carryover = policy.resolveAddressFollowupCarryoverContext( "СВК-А", [ { role: "assistant", text: "По каталогу 1С нашлось несколько похожих контрагентов: СВК-А, СВК-Б.", debug: { execution_lane: "living_chat", mcp_discovery_response_applied: true, assistant_mcp_discovery_entry_point_v1: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", entry_status: "bridge_executed", turn_input: { turn_meaning_ref: { asked_domain_family: "entity_resolution", asked_action_family: "search_business_entity", explicit_entity_candidates: ["СВК"] } }, bridge: { bridge_status: "answer_draft_ready", business_fact_answer_allowed: false, pilot: { pilot_scope: "entity_resolution_search_v1", derived_entity_resolution: { requested_entity: "СВК", resolution_status: "ambiguous", resolved_entity: null, ambiguity_candidates: ["СВК-А", "СВК-Б"] } }, answer_draft: { answer_mode: "needs_clarification" } } } } } ], null, null, null ); expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe("entity_resolution_search_v1"); expect(carryover?.followupContext?.previous_discovery_entity_resolution_status).toBe("ambiguous"); expect(carryover?.followupContext?.previous_discovery_entity_candidates).toEqual(["СВК", "СВК-А", "СВК-Б"]); expect(carryover?.followupContext?.previous_discovery_entity_ambiguity_candidates).toEqual([ "СВК-А", "СВК-Б" ]); }); it("keeps exact payout carryover for a short net follow-up without restating counterparty or year", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ role: "assistant", text: "Платежи по Группа СВК за 2021", debug: { execution_lane: "address_query", answer_grounding_check: { status: "grounded" }, detected_intent: "customer_revenue_and_payments", selected_recipe: "address_customer_revenue_and_payments_v1", extracted_filters: { counterparty: "Группа СВК", period_from: "2021-01-01", period_to: "2021-12-31" }, anchor_type: "counterparty", anchor_value_resolved: "Группа СВК" } }), hasAddressFollowupContextSignal: () => true }); const carryover = policy.resolveAddressFollowupCarryoverContext( "а какое нетто?", [], null, null, null ); expect(carryover?.followupSelectionMode).toBe("carry_previous_intent"); expect(carryover?.followupContext?.previous_intent).toBe("customer_revenue_and_payments"); expect(carryover?.followupContext?.target_intent).toBe("customer_revenue_and_payments"); expect(carryover?.followupContext?.previous_anchor_type).toBe("counterparty"); expect(carryover?.followupContext?.previous_anchor_value).toBe("Группа СВК"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ counterparty: "Группа СВК", period_from: "2021-01-01", period_to: "2021-12-31" }); }); it("carries ranking need from grounded discovery into followup context", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => null, hasAddressFollowupContextSignal: () => true }); const carryover = policy.resolveAddressFollowupCarryoverContext( "по ООО Альтернатива Плюс", [ { role: "assistant", text: "Нужно уточнить организацию, чтобы продолжить поиск по контрагентам.", debug: { execution_lane: "living_chat", mcp_discovery_response_applied: true, assistant_mcp_discovery_entry_point_v1: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", entry_status: "bridge_executed", turn_input: { data_need_graph: { business_fact_family: "value_flow", ranking_need: "top_desc", subject_candidates: [], clarification_gaps: ["organization"] }, turn_meaning_ref: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_date_scope: "2020", seeded_ranking_need: "top_desc" } }, bridge: { bridge_status: "answer_draft_ready", business_fact_answer_allowed: false, pilot: { pilot_scope: "counterparty_value_flow_query_movements_v1" }, answer_draft: { answer_mode: "needs_clarification" } } } } } ], null, null, null ); expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe( "counterparty_value_flow_query_movements_v1" ); expect(carryover?.followupContext?.previous_discovery_ranking_need).toBe("top_desc"); expect(carryover?.followupContext?.target_intent).toBe("customer_revenue_and_payments"); }); it("carries resumable discovery loop state into followup context", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => null, hasAddressFollowupContextSignal: () => true }); const carryover = policy.resolveAddressFollowupCarryoverContext( "ООО Альтернатива Плюс", [ { role: "assistant", text: "Нужно уточнить организацию, чтобы продолжить рейтинг.", debug: { execution_lane: "living_chat", mcp_discovery_response_applied: true, assistant_mcp_discovery_entry_point_v1: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", entry_status: "bridge_executed", turn_input: { data_need_graph: { business_fact_family: "value_flow", ranking_need: "top_desc", subject_candidates: [], clarification_gaps: ["organization"] }, turn_meaning_ref: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_date_scope: "2020", seeded_ranking_need: "top_desc" } }, bridge: { bridge_status: "needs_clarification", business_fact_answer_allowed: false, pilot: { pilot_scope: "counterparty_value_flow_query_movements_v1" }, loop_state: { schema_version: "assistant_mcp_discovery_loop_state_v1", policy_owner: "assistantMcpDiscoveryRuntimeBridge", loop_status: "awaiting_clarification", selected_chain_id: "value_flow_ranking", pilot_scope: "counterparty_value_flow_query_movements_v1", asked_domain_family: "counterparty_value", asked_action_family: "turnover", unsupported_but_understood_family: "counterparty_value_or_turnover", ranking_need: "top_desc", pending_axes: ["organization"], provided_axes: ["aggregate_axis", "amount", "coverage_target"], explicit_entity_candidates: [], explicit_organization_scope: null, explicit_date_scope: "2020" }, answer_draft: { answer_mode: "needs_clarification" } } } } } ], null, null, null ); expect(carryover?.followupContext?.previous_discovery_loop_status).toBe("awaiting_clarification"); expect(carryover?.followupContext?.previous_discovery_loop_selected_chain_id).toBe("value_flow_ranking"); expect(carryover?.followupContext?.previous_discovery_loop_pending_axes).toEqual(["organization"]); expect(carryover?.followupContext?.previous_discovery_loop_provided_axes).toEqual([ "aggregate_axis", "amount", "coverage_target" ]); expect(carryover?.followupContext?.previous_discovery_loop_asked_domain_family).toBe("counterparty_value"); expect(carryover?.followupContext?.previous_discovery_loop_asked_action_family).toBe("turnover"); expect(carryover?.followupContext?.previous_discovery_loop_unsupported_family).toBe( "counterparty_value_or_turnover" ); }); it("treats a plain organization reply as continuation of a pending route-candidate organization scope", () => { const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441"; const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ role: "assistant", text: "\u041d\u0443\u0436\u043d\u043e \u0443\u0442\u043e\u0447\u043d\u0438\u0442\u044c \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044e.", debug: { execution_lane: "living_chat", detected_intent: "customer_revenue_and_payments", mcp_discovery_response_applied: true, assistant_mcp_discovery_entry_point_v1: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", entry_status: "bridge_executed", bridge: { bridge_status: "needs_clarification", business_fact_answer_allowed: false, pilot: { pilot_scope: "counterparty_value_flow_query_movements_v1" }, loop_state: { schema_version: "assistant_mcp_discovery_loop_state_v1", loop_status: "awaiting_clarification", selected_chain_id: "value_flow_ranking", pilot_scope: "counterparty_value_flow_query_movements_v1", asked_domain_family: "counterparty_value", asked_action_family: "turnover", unsupported_but_understood_family: "counterparty_value_or_turnover", ranking_need: "top_desc", pending_axes: ["organization"], provided_axes: ["aggregate_axis", "amount", "coverage_target"], explicit_date_scope: "2020" } } } } }), hasAddressFollowupContextSignal: () => false }); const carryover = policy.resolveAddressFollowupCarryoverContext( orgName, [], null, { predecomposeContract: { mode: "unsupported", intent: "unknown", entities: { organization: orgName }, semantics: { anchor_kind: "organization", anchor_value: orgName } } }, null ); expect(carryover?.followupContext?.previous_discovery_loop_status).toBe("awaiting_clarification"); expect(carryover?.followupContext?.previous_discovery_loop_selected_chain_id).toBe("value_flow_ranking"); expect(carryover?.followupContext?.previous_discovery_loop_pending_axes).toEqual(["organization"]); expect(carryover?.followupContext?.target_intent).toBe("customer_revenue_and_payments"); }); it("carries grounded metadata downstream route hints into followup context", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => null, hasAddressFollowupContextSignal: () => true }); const carryover = policy.resolveAddressFollowupCarryoverContext( "then documents", [ { role: "assistant", text: "Metadata surface confirmed.", debug: { execution_lane: "living_chat", mcp_discovery_response_applied: true, assistant_mcp_discovery_entry_point_v1: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", entry_status: "bridge_executed", turn_input: { turn_meaning_ref: { asked_domain_family: "metadata", asked_action_family: "inspect_documents", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020" } }, bridge: { bridge_status: "answer_draft_ready", business_fact_answer_allowed: true, pilot: { pilot_scope: "metadata_inspection_v1", derived_metadata_surface: { selected_entity_set: "Документ", selected_surface_objects: ["Документ.СчетФактураВыданный"], downstream_route_family: "document_evidence", route_family_selection_basis: "selected_entity_set", recommended_next_primitive: "query_documents", ambiguity_detected: false } }, answer_draft: { answer_mode: "confirmed_with_bounded_inference" } } } } } ], null, null, null ); expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe("metadata_inspection_v1"); expect(carryover?.followupContext?.previous_discovery_metadata_route_family).toBe("document_evidence"); expect(carryover?.followupContext?.previous_discovery_metadata_route_family_selection_basis).toBe("selected_entity_set"); expect(carryover?.followupContext?.previous_discovery_metadata_selected_entity_set).toBe("Документ"); expect(carryover?.followupContext?.previous_discovery_metadata_selected_surface_objects).toEqual([ "Документ.СчетФактураВыданный" ]); expect(carryover?.followupContext?.previous_discovery_metadata_recommended_next_primitive).toBe("query_documents"); expect(carryover?.followupContext?.previous_discovery_metadata_ambiguity_detected).toBeUndefined(); }); it("carries metadata ambiguity entity sets into follow-up context for downstream lane arbitration", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "metadata ambiguity", debug: { execution_lane: "living_chat", mcp_discovery_response_applied: true, assistant_mcp_discovery_entry_point_v1: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", entry_status: "bridge_executed", turn_input: { turn_meaning_ref: { asked_domain_family: "metadata", asked_action_family: "inspect_documents", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020" } }, bridge: { bridge_status: "answer_draft_ready", business_fact_answer_allowed: true, pilot: { pilot_scope: "metadata_inspection_v1", derived_metadata_surface: { selected_entity_set: null, downstream_route_family: null, ambiguity_detected: true, ambiguity_entity_sets: ["Документ", "РегистрНакопления"] } }, answer_draft: { answer_mode: "confirmed_with_bounded_inference" } } } } }), hasAddressFollowupContextSignal: () => true, hasReferentialPointer: () => false, resolveAddressIntent: () => ({ intent: "unknown" }), resolveAddressIntentFamily: () => null, resolveAssistantTurnMeaning: () => null }); const carryover = policy.resolveAddressFollowupCarryoverContext( "по документам", [{ kind: "assistant", text: "metadata ambiguity" }], "по документам", { predecomposeContract: { intent: "unknown" } }, null ); expect(carryover?.followupContext?.previous_discovery_metadata_ambiguity_detected).toBe(true); expect(carryover?.followupContext?.previous_discovery_metadata_ambiguity_entity_sets).toEqual([ "Документ", "РегистрНакопления" ]); }); it("preserves metadata ambiguity choice sets through a clarification assistant turn", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "уточните: по документам или по движениям?", debug: { execution_lane: "living_chat", mcp_discovery_response_applied: true, assistant_mcp_discovery_entry_point_v1: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", entry_status: "bridge_executed", turn_input: { turn_meaning_ref: { asked_domain_family: "metadata", asked_action_family: "resolve_next_lane", explicit_entity_candidates: ["SVK"], metadata_ambiguity_entity_sets: ["Документ", "РегистрНакопления"], explicit_date_scope: "2020", unsupported_but_understood_family: "metadata_lane_choice_clarification" } }, bridge: { bridge_status: "needs_clarification", business_fact_answer_allowed: false, pilot: { pilot_scope: "metadata_inspection_v1" }, answer_draft: { answer_mode: "needs_clarification" } } } } }), hasAddressFollowupContextSignal: () => true, hasReferentialPointer: () => false, resolveAddressIntent: () => ({ intent: "unknown" }), resolveAddressIntentFamily: () => null, resolveAssistantTurnMeaning: () => null }); const carryover = policy.resolveAddressFollowupCarryoverContext( "по движениям", [{ kind: "assistant", text: "уточните: по документам или по движениям?" }], "по движениям", { predecomposeContract: { intent: "unknown" } }, null ); expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe("metadata_inspection_v1"); expect(carryover?.followupContext?.previous_discovery_metadata_ambiguity_detected).toBe(true); expect(carryover?.followupContext?.previous_discovery_metadata_ambiguity_entity_sets).toEqual([ "Документ", "РегистрНакопления" ]); }); it("switches to VAT tax-period intent while preserving carried period filters", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Подтвержденная дебиторская задолженность РЅР° 31.05.2017 собрана.", debug: { detected_intent: "receivables_confirmed_as_of_date", extracted_filters: { organization: 'РћРћРћ "Альтернатива Плюс"', as_of_date: "2017-05-31", period_from: "2017-05-01", period_to: "2017-05-31" }, anchor_type: "organization", anchor_value_resolved: 'РћРћРћ "Альтернатива Плюс"' } }), hasAddressFollowupContextSignal: () => true, hasReferentialPointer: (value: unknown) => /этот период/i.test(String(value ?? "")), resolveAddressIntent: () => ({ intent: "unknown" }), resolveAddressIntentFamily: (intent: unknown) => { if (String(intent ?? "").startsWith("receivables_")) return "receivables"; if (String(intent ?? "").startsWith("vat_")) return "vat"; return null; }, resolveAssistantTurnMeaning: () => ({ schema_version: "assistant_turn_meaning_v1", asked_domain_family: "vat", asked_action_family: "confirmed_tax_period", explicit_intent_candidate: "vat_liability_confirmed_for_tax_period", explicit_entity_candidates: [], intent_override_strength: "explicit_current_turn_intent", stale_replay_forbidden: false }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( "а какой ндс мы должны примерно заплатить за этот период?", [], "Какой НДС должен быть уплачен за текущий период?", { predecomposeContract: { intent: "unknown" } }, null ); expect(carryover?.followupSelectionMode).toBe("carry_previous_intent"); expect(carryover?.followupContext?.previous_intent).toBe("receivables_confirmed_as_of_date"); expect(carryover?.followupContext?.target_intent).toBe("vat_liability_confirmed_for_tax_period"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ organization: 'РћРћРћ "Альтернатива Плюс"', as_of_date: "2017-05-31", period_from: "2017-05-01", period_to: "2017-05-31" }); }); it("retargets same-period VAT payable follow-up away from current-date payable snapshots", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "Receivables as of 2017-05-31 were collected for May.", debug: { detected_intent: "receivables_confirmed_as_of_date", extracted_filters: { organization: 'ООО "Альтернатива Плюс"', as_of_date: "2017-05-31", period_from: "2017-05-01", period_to: "2017-05-31" }, anchor_type: "organization", anchor_value_resolved: 'ООО "Альтернатива Плюс"' } }), hasAddressFollowupContextSignal: () => true, hasReferentialPointer: () => true, resolveAddressIntent: () => ({ intent: "unknown" }), resolveAddressIntentFamily: (intent: unknown) => { if (String(intent ?? "").startsWith("receivables_")) return "receivables"; if (String(intent ?? "").startsWith("vat_")) return "vat"; return null; }, resolveAssistantTurnMeaning: () => ({ schema_version: "assistant_turn_meaning_v1", asked_domain_family: "vat", asked_action_family: "confirmed_snapshot", explicit_intent_candidate: "vat_payable_confirmed_as_of_date", explicit_entity_candidates: [], intent_override_strength: "explicit_current_turn_intent", stale_replay_forbidden: false }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( "а какой ндс мы должны примерно заплатить за этот период?", [], "Какой НДС мы должны заплатить за текущий период?", { predecomposeContract: { intent: "vat_payable_confirmed_as_of_date" } }, null ); expect(carryover?.followupContext?.previous_intent).toBe("receivables_confirmed_as_of_date"); expect(carryover?.followupContext?.target_intent).toBe("vat_liability_confirmed_for_tax_period"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ organization: 'ООО "Альтернатива Плюс"', period_from: "2017-05-01", period_to: "2017-05-31" }); }); it("carries metadata-scoped subjectless loop state through follow-up context", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "\u043d\u0443\u0436\u043d\u044b \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u0438 \u043f\u0435\u0440\u0438\u043e\u0434", debug: { execution_lane: "living_chat", mcp_discovery_response_applied: true, assistant_mcp_discovery_entry_point_v1: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", entry_status: "bridge_executed", turn_input: { turn_meaning_ref: { asked_domain_family: "movements", asked_action_family: "list_movements", metadata_scope_hint: "\u041d\u0414\u0421", subject_resolution_optional: true, unsupported_but_understood_family: "movement_evidence", stale_replay_forbidden: true } }, bridge: { bridge_status: "needs_clarification", business_fact_answer_allowed: false, pilot: { pilot_scope: "counterparty_movement_evidence_query_movements_v1" }, loop_state: { schema_version: "assistant_mcp_discovery_loop_state_v1", policy_owner: "assistantMcpDiscoveryRuntimeBridge", loop_status: "awaiting_clarification", selected_chain_id: "movement_evidence", pilot_scope: "counterparty_movement_evidence_query_movements_v1", asked_domain_family: "movements", asked_action_family: "list_movements", unsupported_but_understood_family: "movement_evidence", ranking_need: null, pending_axes: ["organization", "period"], provided_axes: [], explicit_entity_candidates: [], metadata_scope_hint: "\u041d\u0414\u0421", subject_resolution_optional: true, explicit_organization_scope: null, explicit_date_scope: null }, answer_draft: { answer_mode: "needs_clarification" } } } } }), hasAddressFollowupContextSignal: () => true, hasReferentialPointer: () => false, resolveAddressIntent: () => ({ intent: "unknown" }), resolveAddressIntentFamily: () => null, resolveAssistantTurnMeaning: () => null }); const carryover = policy.resolveAddressFollowupCarryoverContext( "\u043f\u043e \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441", [{ kind: "assistant", text: "\u043d\u0443\u0436\u043d\u044b \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u0438 \u043f\u0435\u0440\u0438\u043e\u0434" }], "\u043f\u043e \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441", { predecomposeContract: { intent: "unknown" } }, null ); expect(carryover?.followupContext?.previous_discovery_loop_selected_chain_id).toBe("movement_evidence"); expect(carryover?.followupContext?.previous_discovery_loop_pending_axes).toEqual([ "organization", "period" ]); expect(carryover?.followupContext?.previous_discovery_loop_metadata_scope_hint).toBe( "\u041d\u0414\u0421" ); expect(carryover?.followupContext?.previous_discovery_loop_subject_resolution_optional).toBe(true); }); it("does not backfill metadata scope into counterparty carryover during lane choice follow-up", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "\u0443\u0442\u043e\u0447\u043d\u0438\u0442\u0435: \u043f\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c \u0438\u043b\u0438 \u043f\u043e \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f\u043c?", debug: { execution_lane: "living_chat", mcp_discovery_response_applied: true, assistant_mcp_discovery_entry_point_v1: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", entry_status: "bridge_executed", turn_input: { turn_meaning_ref: { asked_domain_family: "metadata", asked_action_family: "resolve_next_lane", explicit_entity_candidates: ["\u041d\u0414\u0421"], metadata_scope_hint: "\u041d\u0414\u0421", metadata_ambiguity_entity_sets: [ "\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442", "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u041d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u0438\u044f" ], unsupported_but_understood_family: "metadata_lane_choice_clarification", stale_replay_forbidden: true } }, bridge: { bridge_status: "needs_clarification", business_fact_answer_allowed: false, pilot: { pilot_scope: "metadata_inspection_v1" }, loop_state: { schema_version: "assistant_mcp_discovery_loop_state_v1", policy_owner: "assistantMcpDiscoveryRuntimeBridge", loop_status: "awaiting_clarification", selected_chain_id: "metadata_lane_clarification", pilot_scope: "metadata_inspection_v1", asked_domain_family: "metadata", asked_action_family: "resolve_next_lane", unsupported_but_understood_family: "metadata_lane_choice_clarification", ranking_need: null, pending_axes: ["lane_family_choice"], provided_axes: [], explicit_entity_candidates: ["\u041d\u0414\u0421"], metadata_scope_hint: "\u041d\u0414\u0421", subject_resolution_optional: false, explicit_organization_scope: null, explicit_date_scope: null }, answer_draft: { answer_mode: "needs_clarification" } } } } }), hasAddressFollowupContextSignal: () => true, hasReferentialPointer: () => false, resolveAddressIntent: () => ({ intent: "unknown" }), resolveAddressIntentFamily: () => null, resolveAssistantTurnMeaning: () => null }); const carryover = policy.resolveAddressFollowupCarryoverContext( "\u043f\u043e \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f\u043c", [{ kind: "assistant", text: "\u0443\u0442\u043e\u0447\u043d\u0438\u0442\u0435: \u043f\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c \u0438\u043b\u0438 \u043f\u043e \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f\u043c?" }], "\u043f\u043e \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f\u043c", { predecomposeContract: { intent: "unknown" } }, null ); expect(carryover?.followupContext?.previous_filters?.counterparty).toBeUndefined(); expect(carryover?.followupContext?.previous_anchor_type).toBeUndefined(); expect(carryover?.followupContext?.previous_anchor_value).toBeNull(); expect(carryover?.followupContext?.previous_discovery_entity_candidates).toEqual(["\u041d\u0414\u0421"]); expect(carryover?.followupContext?.previous_discovery_pilot_scope).toBe("metadata_inspection_v1"); }); it("lets short receivables-to-payables mirror override an LLM open-items expansion", () => { const policy = buildPolicy({ findLastAddressAssistantItem: () => ({ text: "\u041a\u043e\u0440\u043e\u0442\u043a\u043e: \u043d\u0430\u043c \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430 13.05.2026.", debug: { detected_intent: "receivables_confirmed_as_of_date", selected_recipe: "address_receivables_confirmed_as_of_date_v1", extracted_filters: { organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441", as_of_date: "2026-05-13" } } }), hasAddressFollowupContextSignal: () => true, resolveDebtRoleSwapFollowupIntent: (message: string, previousIntent: string) => message === "\u0430 \u043c\u044b \u043a\u043e\u043c\u0443?" && previousIntent === "receivables_confirmed_as_of_date" ? "payables_confirmed_as_of_date" : null, resolveAddressIntent: () => ({ intent: "open_items_by_counterparty_or_contract" }) }); const carryover = policy.resolveAddressFollowupCarryoverContext( "\u0430 \u043c\u044b \u043a\u043e\u043c\u0443?", [], "\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c, \u043a\u043e\u043c\u0443 \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442 \u0442\u0435\u043a\u0443\u0449\u0430\u044f \u0437\u0430\u0434\u043e\u043b\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u044c", { predecomposeContract: { intent: "open_items_by_counterparty_or_contract" } }, null ); expect(carryover?.followupContext?.previous_intent).toBe("payables_confirmed_as_of_date"); expect(carryover?.followupContext?.target_intent).toBe("payables_confirmed_as_of_date"); expect(carryover?.followupContext?.previous_filters).toMatchObject({ organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441", as_of_date: "2026-05-13" }); }); });