import { afterEach, describe, expect, it, vi } from "vitest"; const { executeAddressMcpQueryMock } = vi.hoisted(() => ({ executeAddressMcpQueryMock: vi.fn() })); vi.mock("../src/services/addressMcpClient", async () => { const actual = await vi.importActual( "../src/services/addressMcpClient" ); return { ...actual, executeAddressMcpQuery: executeAddressMcpQueryMock }; it("keeps plain March stock wording in the inventory contour and asks to choose the company", async () => { executeAddressMcpQueryMock.mockResolvedValueOnce({ fetched_rows: 2, matched_rows: 2, raw_rows: [ { Period: "2021-03-31T23:59:59Z", Registrator: "Остатки товаров РЅР° складах", AccountDt: "41.01", AccountKt: "00.00", Amount: 6490, Quantity: 1, SubcontoDt1: "РџСѓС„ арий", Warehouse: "РћСЃРЅРѕРІРЅРѕР№ склад", Organization: "РћРћРћ Альтернатива Плюс" }, { Period: "2021-03-31T23:59:59Z", Registrator: "Остатки товаров РЅР° складах", AccountDt: "41.01", AccountKt: "00.00", Amount: 34490, Quantity: 1, SubcontoDt1: "Диван трехместный", Warehouse: "РћСЃРЅРѕРІРЅРѕР№ склад", Organization: "РћРћРћ Лайсвуд" } ], rows: [], error: null }); const service = new AddressQueryService(); const result = await service.tryHandle("какие остатки на складе на март 2021"); expect(result?.handled).toBe(true); expect(result?.debug.detected_intent).toBe("inventory_on_hand_as_of_date"); expect(result?.response_type).toBe("LIMITED_WITH_REASON"); expect(result?.debug.limited_reason_category).toBe("missing_anchor"); expect(result?.debug.organization_candidates).toEqual(["РћРћРћ Альтернатива Плюс", "РћРћРћ Лайсвуд"]); expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1); }); }); import { AddressQueryService } from "../src/services/addressQueryService"; afterEach(() => { executeAddressMcpQueryMock.mockReset(); vi.restoreAllMocks(); }); describe("inventory organization scope grounding", () => { it("asks for organization clarification when multiple known companies exist", async () => { const service = new AddressQueryService(); const result = await service.tryHandle("покажи остатки по складу", { knownOrganizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд"] }); expect(result?.handled).toBe(true); expect(result?.response_type).toBe("LIMITED_WITH_REASON"); expect(result?.debug.limited_reason_category).toBe("missing_anchor"); expect(result?.debug.organization_candidates).toEqual(["ООО Альтернатива Плюс", "ООО Лайсвуд"]); expect(String(result?.reply_text ?? "")).toContain("ООО Альтернатива Плюс"); expect(String(result?.reply_text ?? "")).toContain("ООО Лайсвуд"); expect(executeAddressMcpQueryMock).not.toHaveBeenCalled(); }); it("auto-selects the only known organization for inventory root queries", async () => { executeAddressMcpQueryMock.mockResolvedValueOnce({ fetched_rows: 1, matched_rows: 1, raw_rows: [ { Period: "2026-04-15T23:59:59Z", Registrator: "Остатки товаров на складах", AccountDt: "41.01", AccountKt: "00.00", Amount: 6490, Quantity: 1, SubcontoDt1: "Пуф арий", Warehouse: "Основной склад", Organization: "ООО Альтернатива Плюс" } ], rows: [], error: null }); const service = new AddressQueryService(); const result = await service.tryHandle("покажи остатки по складу", { knownOrganizations: ["ООО Альтернатива Плюс"] }); expect(result?.handled).toBe(true); expect(result?.response_type).toBe("FACTUAL_LIST"); expect(result?.debug.extracted_filters?.organization).toBe("ООО Альтернатива Плюс"); expect(result?.debug.reasons).toContain("organization_auto_selected_from_single_scope_candidate"); expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1); }); it("grounds organization from observed rows when the result belongs to a single company", async () => { executeAddressMcpQueryMock.mockResolvedValueOnce({ fetched_rows: 1, matched_rows: 1, raw_rows: [ { Period: "2020-05-31T23:59:59Z", Registrator: "Остатки товаров на складах", AccountDt: "41.01", AccountKt: "00.00", Amount: 13490, Quantity: 1, SubcontoDt1: "Кресло орион", Warehouse: "Основной склад", Organization: "ООО \\Альтернатива Плюс\\" } ], rows: [], error: null }); const service = new AddressQueryService(); const result = await service.tryHandle("покажи остатки по складу"); expect(result?.handled).toBe(true); expect(result?.response_type).toBe("FACTUAL_LIST"); expect(result?.debug.extracted_filters?.organization).toBe("ООО \\Альтернатива Плюс\\"); expect(result?.debug.reasons).toContain("organization_grounded_from_observed_rows"); }); it("asks for organization clarification when observed rows contain multiple companies", async () => { executeAddressMcpQueryMock.mockResolvedValueOnce({ fetched_rows: 2, matched_rows: 2, raw_rows: [ { Period: "2020-05-31T23:59:59Z", Registrator: "Остатки товаров на складах", AccountDt: "41.01", AccountKt: "00.00", Amount: 6490, Quantity: 1, SubcontoDt1: "Пуф арий", Warehouse: "Основной склад", Organization: "ООО Альтернатива Плюс" }, { Period: "2020-05-31T23:59:59Z", Registrator: "Остатки товаров на складах", AccountDt: "41.01", AccountKt: "00.00", Amount: 34490, Quantity: 1, SubcontoDt1: "Диван трехместный", Warehouse: "Основной склад", Organization: "ООО Лайсвуд" } ], rows: [], error: null }); const service = new AddressQueryService(); const result = await service.tryHandle("покажи остатки по складу"); expect(result?.handled).toBe(true); expect(result?.response_type).toBe("LIMITED_WITH_REASON"); expect(result?.debug.limited_reason_category).toBe("missing_anchor"); expect(result?.debug.organization_candidates).toEqual(["ООО Альтернатива Плюс", "ООО Лайсвуд"]); expect(String(result?.reply_text ?? "")).toContain("ООО Альтернатива Плюс"); expect(String(result?.reply_text ?? "")).toContain("ООО Лайсвуд"); expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1); }); it("defers company clarification for item-focused inventory queries and grounds the company from observed rows", async () => { const item = "Столешница 600*3050*26 дуб ниагара"; executeAddressMcpQueryMock.mockResolvedValueOnce({ fetched_rows: 1, matched_rows: 1, raw_rows: [ { Period: "2019-02-11T00:00:00Z", Registrator: "Поступление товаров и услуг 00000000077 от 11.02.2019 0:00:00", AccountDt: "41.01", AccountKt: "60.01", Amount: 3724.17, Quantity: 1, SubcontoDt1: item, SubcontoDt3: "Основной склад", SubcontoKt1: "Торговый дом \\Союз МСК\\", SubcontoKt2: "Договор поставки № 12 от 01.02.2019", Organization: "ООО \\Альтернатива Плюс\\" } ], rows: [], error: null }); const service = new AddressQueryService(); const result = await service.tryHandle("покажи документы по этой позиции", { knownOrganizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд"], followupContext: { previous_intent: "inventory_purchase_provenance_for_item", previous_filters: { item, as_of_date: "2021-03-31", period_from: "2021-03-01", period_to: "2021-03-31" }, previous_anchor_type: "unknown", previous_anchor_value: null } }); expect(result?.handled).toBe(true); expect(result?.response_type).toBe("FACTUAL_LIST"); expect(result?.debug.detected_intent).toBe("inventory_purchase_documents_for_item"); expect(result?.debug.extracted_filters?.organization).toBe("ООО \\Альтернатива Плюс\\"); expect(result?.debug.reasons).toContain("organization_grounded_from_observed_rows"); expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1); }); });