237 lines
9.6 KiB
TypeScript
237 lines
9.6 KiB
TypeScript
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<typeof import("../src/services/addressMcpClient")>(
|
||
"../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);
|
||
});
|
||
});
|