NODEDC_1C/llm_normalizer/backend/tests/addressInventorySelectedObj...

471 lines
21 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
};
});
import { AddressQueryService } from "../src/services/addressQueryService";
afterEach(() => {
executeAddressMcpQueryMock.mockReset();
vi.restoreAllMocks();
});
describe("inventory selected-object follow-up", () => {
it("inherits dated stock window for selected-object provenance and then auto-broadens history", async () => {
executeAddressMcpQueryMock
.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2021-03-09T00:00:00Z",
Registrator: "Поступление товаров и услуг 00000000001 от 09.03.2021 0:00:00",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 442075,
SubcontoDt1: "Рабочая станция универсального специалиста с угловым элементом.",
SubcontoDt3: "Основной склад",
SubcontoKt1: "АСТРА",
SubcontoKt2: "А_03/2021 от 01.03.2021г.",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
})
.mockResolvedValueOnce({
fetched_rows: 2,
matched_rows: 2,
raw_rows: [
{
Period: "2020-02-11T00:00:00Z",
Registrator: "Поступление товаров и услуг 00000000077 от 11.02.2020 0:00:00",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 165.83,
SubcontoDt1: "Кромка с клеем 33 альмандин 137 м",
SubcontoDt3: "Основной склад",
SubcontoKt1: "Торговый дом \\Союз МСК\\",
SubcontoKt2: "Договор поставки № 12 от 01.02.2020",
Organization: "ООО \\Альтернатива Плюс\\"
},
{
Period: "2020-02-11T00:00:00Z",
Registrator: "Поступление товаров и услуг 00000000077 от 11.02.2020 0:00:00",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 165.83,
SubcontoDt1: "Кромка с клеем 33 дуб ниагара 137 м",
SubcontoDt3: "Основной склад",
SubcontoKt1: "Торговый дом \\Союз МСК\\",
SubcontoKt2: "Договор поставки № 12 от 01.02.2020",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle(
'По выбранному объекту "Кромка с клеем 33 альмандин 137 м | склад: Основной склад | количество: 1,000 | стоимость: 165,83 ₽ | организация: ООО \\\\Альтернатива Плюс\\\\ | дата строки: 2021-03-31T23:59:59Z": От какого поставщика куплен товар',
{
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31",
warehouse: "Основной склад",
organization: "ООО \\Альтернатива Плюс\\"
},
previous_anchor_type: "unknown",
previous_anchor_value: null
}
}
);
expect(result?.handled).toBe(true);
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
expect(result?.debug.extracted_filters?.item).toBe("Кромка с клеем 33 альмандин 137 м");
expect(result?.debug.extracted_filters?.as_of_date).toBe("2021-03-31");
expect(result?.debug.extracted_filters?.period_from).toBe("2021-03-01");
expect(result?.debug.extracted_filters?.period_to).toBe("2021-03-31");
expect(result?.debug.capability_id).toBe("inventory_inventory_purchase_provenance_for_item");
expect(result?.debug.capability_route_mode).toBe("exact");
expect(result?.debug.reasons).toContain("period_window_auto_broadened_to_available_data");
expect(result?.debug.limitations).toContain("period_window_auto_broadened_to_available_data");
const replyLines = String(result?.reply_text ?? "").split("\n");
expect(replyLines[0]).toContain("Товар Кромка с клеем 33 альмандин 137 м");
expect(replyLines[0]).toContain("Торговый дом \\Союз МСК\\");
expect(replyLines[1]).toContain("По окну 2021-03-01..2021-03-31 строк не найдено");
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(2);
});
it("uses analysis date hint for canonical item provenance wording without explicit date", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2019-02-12T00:00:00Z",
Registrator: "Поступление товаров и услуг 00000000003 от 12.02.2019 0:00:00",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 3724.17,
SubcontoDt1: "Столешница 600*3050*26 дуб ниагара",
SubcontoDt3: "Основной склад",
SubcontoKt1: "Торговый дом \\Союз",
SubcontoKt2: "Договор поставки № 12 от 01.02.2019",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle("От какого поставщика куплен товар Столешница 600*3050*26 дуб ниагара", {
analysisDateHint: "2019-03-31"
});
expect(result?.handled).toBe(true);
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
expect(result?.debug.extracted_filters?.item).toBe("Столешница 600*3050*26 дуб ниагара");
expect(result?.debug.extracted_filters?.as_of_date).toBe("2019-03-31");
expect(result?.debug.reasons).toContain("as_of_date_from_analysis_context");
expect(String(result?.reply_text ?? "")).toContain("Торговый дом \\Союз");
});
it("handles selected-object supplier slang 'кто это поставил нам' as provenance follow-up", async () => {
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,
SubcontoDt1: "Столешница 600*3050*26 дуб ниагара",
SubcontoDt3: "Основной склад",
SubcontoKt1: "Торговый дом \\Союз МСК\\",
SubcontoKt2: "Договор поставки № 12 от 01.02.2019",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle('По выбранному объекту "Столешница 600*3050*26 дуб ниагара": кто это поставил нам', {
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
as_of_date: "2019-03-31",
period_from: "2019-03-01",
period_to: "2019-03-31",
warehouse: "Основной склад",
organization: "ООО \\Альтернатива Плюс\\"
},
previous_anchor_type: "unknown",
previous_anchor_value: null
}
});
expect(result?.handled).toBe(true);
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
expect(result?.debug.extracted_filters?.item).toBe("Столешница 600*3050*26 дуб ниагара");
expect(result?.debug.extracted_filters?.as_of_date).toBe("2019-03-31");
expect(String(result?.reply_text ?? "")).toContain("Торговый дом \\Союз МСК\\");
});
it("handles selected-object colloquial supplier wording 'у кого купили' as provenance follow-up", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2016-07-20T00:00:00Z",
Registrator: "Поступление товаров и услуг 00000000011 от 20.07.2016 0:00:00",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 695360,
SubcontoDt1: "Рабочая станция универсального специалиста (индивидуальное изготовление)",
SubcontoDt3: "Основной склад",
SubcontoKt1: "ООО \\Производство мебели\\",
SubcontoKt2: "Договор поставки № 7 от 15.07.2016",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle(
'По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": у кого купили',
{
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
as_of_date: "2016-07-31",
period_from: "2016-07-01",
period_to: "2016-07-31",
warehouse: "Основной склад",
organization: "ООО \\Альтернатива Плюс\\"
},
previous_anchor_type: "unknown",
previous_anchor_value: null
}
}
);
expect(result?.handled).toBe(true);
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
expect(result?.debug.extracted_filters?.item).toBe("Рабочая станция универсального специалиста (индивидуальное изготовление)");
expect(result?.debug.extracted_filters?.as_of_date).toBe("2016-07-31");
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
});
it("handles selected-object wording 'где мы купили это' as provenance follow-up", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2016-05-20T00:00:00Z",
Registrator: "Поступление товаров и услуг 00000000009 от 20.05.2016 0:00:00",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 695360,
SubcontoDt1: "Рабочая станция универсального специалиста (индивидуальное изготовление)",
SubcontoDt3: "Основной склад",
SubcontoKt1: "ООО \\Производство мебели\\",
SubcontoKt2: "Договор поставки № 5 от 16.05.2016",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle(
'По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где мы купили это',
{
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
as_of_date: "2016-05-31",
period_from: "2016-05-01",
period_to: "2016-05-31",
warehouse: "Основной склад",
organization: "ООО \\Альтернатива Плюс\\"
},
previous_anchor_type: "unknown",
previous_anchor_value: null
}
}
);
expect(result?.handled).toBe(true);
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
expect(result?.debug.extracted_filters?.item).toBe("Рабочая станция универсального специалиста (индивидуальное изготовление)");
expect(result?.debug.extracted_filters?.as_of_date).toBe("2016-05-31");
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
});
it("handles selected-object wording 'где куплено!!' as provenance follow-up", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2016-05-20T00:00:00Z",
Registrator: "Поступление товаров и услуг 00000000009 от 20.05.2016 0:00:00",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 695360,
SubcontoDt1: "Рабочая станция универсального специалиста (индивидуальное изготовление)",
SubcontoDt3: "Основной склад",
SubcontoKt1: "ООО \\Производство мебели\\",
SubcontoKt2: "Договор поставки № 5 от 16.05.2016",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle(
'По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где куплено!!',
{
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
as_of_date: "2016-05-31",
period_from: "2016-05-01",
period_to: "2016-05-31",
warehouse: "Основной склад",
organization: "ООО \\Альтернатива Плюс\\"
},
previous_anchor_type: "unknown",
previous_anchor_value: null
}
}
);
expect(result?.handled).toBe(true);
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
expect(result?.debug.extracted_filters?.item).toBe("Рабочая станция универсального специалиста (индивидуальное изготовление)");
expect(result?.debug.extracted_filters?.as_of_date).toBe("2016-05-31");
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
});
it("handles selected-object purchase-doc slang 'по каким документам это купили' as exact purchase-doc follow-up", async () => {
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,
SubcontoDt1: "Столешница 600*3050*26 дуб ниагара",
SubcontoDt3: "Основной склад",
SubcontoKt1: "Торговый дом \\Союз МСК\\",
SubcontoKt2: "Договор поставки № 12 от 01.02.2019",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle('По выбранному объекту "Столешница 600*3050*26 дуб ниагара": по каким документам это купили', {
followupContext: {
previous_intent: "inventory_purchase_provenance_for_item",
previous_filters: {
as_of_date: "2019-03-31",
period_from: "2019-03-01",
period_to: "2019-03-31",
item: "Столешница 600*3050*26 дуб ниагара",
warehouse: "Основной склад"
},
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.selected_recipe).toBe("address_inventory_purchase_documents_for_item_v1");
expect(result?.debug.extracted_filters?.item).toBe("Столешница 600*3050*26 дуб ниагара");
expect(result?.debug.extracted_filters?.as_of_date).toBe("2019-03-31");
expect(String(result?.reply_text ?? "")).toContain("Поступление товаров и услуг 00000000077");
});
it("routes buyer follow-up over the same selected item into sale trace instead of replaying provenance", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2020-04-12T00:00:00Z",
Registrator: "Реализация товаров и услуг 00000000112 от 12.04.2020 0:00:00",
AccountDt: "90.02",
AccountKt: "41.01",
Amount: 833.33,
SubcontoKt1: "Четки Пост (84*117)",
SubcontoKt3: "Основной склад",
SubcontoDt1: "ИП Покупатель",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle("кому в итоге мы продали этот товар?", {
followupContext: {
previous_intent: "inventory_purchase_provenance_for_item",
previous_filters: {
as_of_date: "2020-03-31",
period_from: "2020-03-01",
period_to: "2020-03-31",
item: "Четки Пост (84*117)",
warehouse: "Основной склад"
},
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_sale_trace_for_item");
expect(result?.debug.selected_recipe).toBe("address_inventory_sale_trace_for_item_v1");
expect(result?.debug.extracted_filters?.item).toBe("Четки Пост (84*117)");
expect(result?.debug.extracted_filters?.as_of_date).toBe("2020-03-31");
expect(String(result?.reply_text ?? "").split("\n")[0]).toContain("ИП Покупатель");
expect(String(result?.reply_text ?? "")).toContain("Документы выбытия");
});
it("matches sale-trace item anchors from subconto fields when the item is not materialized explicitly", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2020-04-12T00:00:00Z",
Registrator: "Реализация товаров и услуг 00000000112 от 12.04.2020 0:00:00",
AccountDt: "90.02",
AccountKt: "41.01",
Amount: 833.33,
SubcontoDt1: "Шкаф картотечный 1000*400*2100",
SubcontoKt1: "ИП Покупатель",
SubcontoKt2: "Коммерческая структура",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle("Кому был продан товар Шкаф картотечный 1000*400*2100?", {});
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("inventory_sale_trace_for_item");
expect(result?.debug.mcp_call_status).toBe("matched_non_empty");
expect(result?.debug.rows_matched).toBeGreaterThan(0);
expect(String(result?.reply_text ?? "")).not.toContain("совпадений не нашлось");
});
});