import { describe, expect, it } from "vitest"; import { createEmptyAddressNavigationState, evolveAddressNavigationStateWithAssistantItem, normalizeAddressNavigationState } from "../src/services/addressNavigationState"; describe("address navigation state", () => { it("creates default empty state", () => { const state = createEmptyAddressNavigationState("asst-1", "2026-04-12T10:00:00.000Z"); expect(state.schema_version).toBe("address_navigation_state_v1"); expect(state.session_id).toBe("asst-1"); expect(state.result_sets).toEqual([]); expect(state.navigation_history).toEqual([]); expect(state.session_context.active_result_set_id).toBeNull(); }); it("captures result_set and focus from address assistant turn", () => { const base = createEmptyAddressNavigationState("asst-2", "2026-04-12T10:00:00.000Z"); const assistantItem = { message_id: "msg-a1", session_id: "asst-2", role: "assistant", text: [ "Топ-6 заказчиков по сумме поступлений:", "1. Группа | сумма: 12093465 | операций: 13", "4. Гамма-мебель, ООО | сумма: 471000 | операций: 2" ].join("\n"), reply_type: "factual", created_at: "2026-04-12T10:00:10.000Z", trace_id: "address-123", debug: { detected_mode: "address_query", detected_intent: "customer_revenue_and_payments", selected_recipe: "address_customer_revenue_and_payments_v1", extracted_filters: { period_from: "2020-01-01", period_to: "2020-12-31" }, anchor_type: "counterparty", anchor_value_resolved: "Гамма-мебель, ООО", dialog_continuation_contract_v2: { decision: "new_topic" } } } as any; const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 2); expect(evolved.result_sets.length).toBe(1); expect(evolved.result_sets[0]?.result_set_id).toBe("rs-msg-a1"); expect(evolved.result_sets[0]?.intent).toBe("customer_revenue_and_payments"); expect(evolved.result_sets[0]?.entity_refs.length).toBeGreaterThan(0); expect(evolved.session_context.active_result_set_id).toBe("rs-msg-a1"); expect(evolved.session_context.active_focus_object?.label).toBe("Гамма-мебель, ООО"); expect(evolved.navigation_history[0]?.action).toBe("open"); }); it("tracks drilldown event for follow-up continuation turn", () => { const initial = normalizeAddressNavigationState( { schema_version: "address_navigation_state_v1", session_id: "asst-3", updated_at: "2026-04-12T10:00:00.000Z", session_context: { active_result_set_id: "rs-prev", active_focus_object: null, last_confirmed_route: "address_customer_revenue_and_payments_v1", date_scope: { as_of_date: null, period_from: "2020-01-01", period_to: "2020-12-31" }, organization_scope: null }, result_sets: [], navigation_history: [] } as any, "asst-3" ); const assistantItem = { message_id: "msg-a2", session_id: "asst-3", role: "assistant", text: "Собран список договоров по контрагенту Гамма-мебель, ООО.", reply_type: "factual", created_at: "2026-04-12T10:02:00.000Z", trace_id: "address-456", debug: { detected_mode: "address_query", detected_intent: "list_contracts_by_counterparty", selected_recipe: "address_contracts_by_counterparty_v1", extracted_filters: { period_from: "2020-01-01", period_to: "2020-12-31", counterparty: "Гамма-мебель, ООО" }, anchor_type: "counterparty", anchor_value_resolved: "Гамма-мебель, ООО", dialog_continuation_contract_v2: { decision: "continue_previous" } } } as any; const evolved = evolveAddressNavigationStateWithAssistantItem(initial, assistantItem, 4); expect(evolved.navigation_history.length).toBe(1); expect(evolved.navigation_history[0]?.action).toBe("drilldown"); expect(evolved.navigation_history[0]?.source_result_set_id).toBe("rs-prev"); expect(evolved.session_context.active_result_set_id).toBe("rs-msg-a2"); expect(evolved.session_context.active_focus_object?.object_type).toBe("counterparty"); expect(evolved.session_context.date_scope.period_from).toBe("2020-01-01"); expect(evolved.session_context.date_scope.period_to).toBe("2020-12-31"); }); it("prefers grounded discovery counterparty over stale referential anchor when updating focus", () => { const initial = normalizeAddressNavigationState( { schema_version: "address_navigation_state_v1", session_id: "asst-3b", updated_at: "2026-04-12T10:00:00.000Z", session_context: { active_result_set_id: "rs-prev", active_focus_object: { object_type: "counterparty", object_id: "counterparty:нортон", label: "НОРТОН", provenance_result_set_id: "rs-prev", selected_at: "2026-04-12T09:59:00.000Z" }, last_confirmed_route: "address_customer_revenue_and_payments_v1", date_scope: { as_of_date: null, period_from: "2020-01-01", period_to: "2020-12-31" }, organization_scope: "ООО Альтернатива Плюс" }, result_sets: [], navigation_history: [] } as any, "asst-3b" ); const assistantItem = { message_id: "msg-a2b", session_id: "asst-3b", role: "assistant", text: "По Группа СВК подтверждены исходящие платежи за 2020 год.", reply_type: "factual", created_at: "2026-04-12T10:02:30.000Z", trace_id: "address-456b", debug: { detected_mode: "address_query", detected_intent: "supplier_payouts_profile", selected_recipe: "address_supplier_payouts_profile_v1", extracted_filters: { counterparty: "НОРТОН", period_from: "2020-01-01", period_to: "2020-12-31" }, anchor_type: "counterparty", anchor_value_raw: "он", anchor_value_resolved: "он", 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: "counterparty_value", asked_action_family: "payout", explicit_entity_candidates: ["Группа СВК"] } }, 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_factual" } } }, dialog_continuation_contract_v2: { decision: "continue_previous" } } } as any; const evolved = evolveAddressNavigationStateWithAssistantItem(initial, assistantItem, 4); expect(evolved.session_context.active_focus_object?.object_type).toBe("counterparty"); expect(evolved.session_context.active_focus_object?.label).toBe("Группа СВК"); expect(evolved.session_context.active_focus_object?.object_id).toBe("counterparty:группа свк"); expect(evolved.navigation_history[0]?.target_object_id).toBe("counterparty:группа свк"); }); it("captures item focus from inventory answers when no anchor is materialized", () => { const base = createEmptyAddressNavigationState("asst-4", "2026-04-12T10:00:00.000Z"); const assistantItem = { message_id: "msg-a3", session_id: "asst-4", role: "assistant", text: "Собран подтвержденный закупочный след по товару Диван трехместный до 14.04.2026.\n\n1. Авансовый отчет 00000000004 от 24.08.2018 12:00:04 | дата: 24.08.2018 | сумма: 34.490,00 ₽ | склад: Основной склад", reply_type: "factual", created_at: "2026-04-12T10:03:00.000Z", trace_id: "address-789", debug: { detected_mode: "address_query", detected_intent: "inventory_purchase_provenance_for_item", selected_recipe: "address_inventory_purchase_provenance_for_item_v1", extracted_filters: { item: "Диван трехместный", warehouse: "Основной склад", as_of_date: "2026-04-14" }, anchor_type: "unknown", anchor_value_resolved: null, anchor_value_raw: null, dialog_continuation_contract_v2: { decision: "new_topic" } } } as any; const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 3); expect(evolved.session_context.active_focus_object?.label).toBe("Диван трехместный"); expect(evolved.session_context.active_focus_object?.provenance_result_set_id).toBe("rs-msg-a3"); }); it("derives single organization scope from inventory answer text when filters omit organization", () => { const base = createEmptyAddressNavigationState("asst-5", "2026-04-12T10:00:00.000Z"); const assistantItem = { message_id: "msg-a4", session_id: "asst-5", role: "assistant", text: [ "На 31.05.2020 на складе подтверждено 1 позиция.", "1. Пуф арий | склад: Основной склад | количество: 1,000 | стоимость: 6.490,00 ₽ | организация: ООО Альтернатива Плюс | дата строки: 2020-05-31T23:59:59Z" ].join("\n"), reply_type: "factual", created_at: "2026-04-12T10:04:00.000Z", trace_id: "address-790", debug: { detected_mode: "address_query", detected_intent: "inventory_on_hand_as_of_date", selected_recipe: "address_inventory_on_hand_as_of_date_v1", extracted_filters: { as_of_date: "2020-05-31" }, anchor_type: "unknown", anchor_value_resolved: null, anchor_value_raw: null, dialog_continuation_contract_v2: { decision: "new_topic" } } } as any; const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 4); expect(evolved.result_sets[0]?.type).toBe("inventory_snapshot"); expect(evolved.result_sets[0]?.filters.organization).toBe("ООО Альтернатива Плюс"); expect(evolved.result_sets[0]?.entity_refs[0]?.entity_type).toBe("item"); expect(evolved.result_sets[0]?.entity_refs[0]?.value).toBe("Пуф арий"); expect(evolved.session_context.organization_scope).toBe("ООО Альтернатива Плюс"); expect(evolved.session_context.active_focus_object).toBeNull(); }); it("resets stale focus and date scope on explicit new topic turn", () => { const initial = normalizeAddressNavigationState( { schema_version: "address_navigation_state_v1", session_id: "asst-6", updated_at: "2026-04-12T10:00:00.000Z", session_context: { active_result_set_id: "rs-prev", active_focus_object: { object_type: "counterparty", object_id: "counterparty:chapurnov", label: "Чепурнов", provenance_result_set_id: "rs-prev", selected_at: "2026-04-12T09:59:00.000Z" }, last_confirmed_route: "address_documents_by_counterparty_v1", date_scope: { as_of_date: null, period_from: "2017-01-01", period_to: "2017-12-31" }, organization_scope: "РћРћРћ Альтернатива Плюс" }, result_sets: [], navigation_history: [] } as any, "asst-6" ); const assistantItem = { message_id: "msg-a5", session_id: "asst-6", role: "assistant", text: "РќР° 16.04.2026 РЅР° складе подтверждено 11 позиций.", reply_type: "factual", created_at: "2026-04-12T10:05:00.000Z", trace_id: "address-791", debug: { detected_mode: "address_query", detected_intent: "inventory_on_hand_as_of_date", selected_recipe: "address_inventory_on_hand_as_of_date_v1", extracted_filters: { as_of_date: "2026-04-16", organization: "РћРћРћ Альтернатива Плюс" }, anchor_type: "organization", anchor_value_resolved: "РћРћРћ Альтернатива Плюс", dialog_continuation_contract_v2: { decision: "new_topic" } } } as any; const evolved = evolveAddressNavigationStateWithAssistantItem(initial, assistantItem, 5); expect(evolved.session_context.active_result_set_id).toBe("rs-msg-a5"); expect(evolved.session_context.active_focus_object?.label).toBe("РћРћРћ Альтернатива Плюс"); expect(evolved.session_context.date_scope.as_of_date).toBe("2026-04-16"); expect(evolved.session_context.date_scope.period_from).toBeNull(); expect(evolved.session_context.date_scope.period_to).toBeNull(); expect(evolved.session_context.last_confirmed_route).toBe("address_inventory_on_hand_as_of_date_v1"); expect(evolved.navigation_history[0]?.action).toBe("open"); }); });