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

933 lines
43 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 upper bound 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");
expect(result?.debug.address_coverage_evidence_v1?.coverage_status).toBe("full");
expect(result?.debug.address_coverage_evidence_v1?.evidence_basis).toBe("matched_rows");
expect(result?.debug.address_truth_gate_v1?.truth_gate_status).toBe("limited_temporal_or_contextual");
expect(result?.debug.address_truth_gate_v1?.carryover_eligibility).toBe("object_only");
const replyLines = String(result?.reply_text ?? "").split("\n");
expect(replyLines[0]).toContain("По позиции Кромка с клеем 33 альмандин 137 м");
expect(replyLines[0]).toContain("до 31.03.2021 подтвержден поставщик");
expect(replyLines[0]).toContain("Торговый дом \\Союз МСК\\");
expect(String(result?.reply_text ?? "")).toContain("Для ответа учтены закупочные документы не позже 31.03.2021.");
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).toBeUndefined();
expect(String(result?.reply_text ?? "")).toContain("Торговый дом \\Союз МСК\\");
});
it("handles selected-object supplier wording 'кто нам это поставил' as provenance follow-up", 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: 3690,
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: "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("Столешница 600*3050*26 альмандин");
expect(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
expect(result?.debug.capability_id).toBe("inventory_inventory_purchase_provenance_for_item");
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).toBeUndefined();
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: "2020-06-18T00:00:00Z",
Registrator: "Поступление товаров и услуг 00000000101 от 18.06.2020 0:00:00",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 498472.5,
SubcontoDt1: "Конструкция трансформер рабочей станции 1300*900*2000",
SubcontoDt3: "Основной склад",
SubcontoKt1: "ООО \\Гамма-мебель\\",
SubcontoKt2: "Договор поставки № 11 от 15.06.2020",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle(
'По выбранному объекту "Конструкция трансформер рабочей станции 1300*900*2000": у кого куплено',
{
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
as_of_date: "2020-06-30",
period_from: "2020-06-01",
period_to: "2020-06-30",
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.selected_recipe).toBe("address_inventory_purchase_provenance_for_item_v1");
expect(result?.debug.extracted_filters?.item).toBe("Конструкция трансформер рабочей станции 1300*900*2000");
expect(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
expect(result?.debug.extracted_filters?.period_from).toBeUndefined();
expect(result?.debug.extracted_filters?.period_to).toBeUndefined();
expect(result?.debug.capability_id).toBe("inventory_inventory_purchase_provenance_for_item");
expect(result?.debug.capability_route_mode).toBe("exact");
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).toBeUndefined();
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).toBeUndefined();
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
});
it("handles selected-object typo 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.selected_recipe).toBe("address_inventory_purchase_provenance_for_item_v1");
expect(result?.debug.extracted_filters?.item).toBe("Рабочая станция универсального специалиста (индивидуальное изготовление)");
expect(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
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).toBeUndefined();
expect(String(result?.reply_text ?? "")).toContain("Поступление товаров и услуг 00000000077");
});
it("does not let carried counterparty scope steal selected-item document follow-up into open items", 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: 3690,
SubcontoDt1: "Столешница 600*3050*26 альмандин",
SubcontoDt3: "Основной склад",
SubcontoKt1: "Торговый дом \\Союз",
SubcontoKt2: "Договор поставки № 12 от 01.02.2019",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle("покажи документы по этой позиции", {
followupContext: {
previous_intent: "inventory_purchase_provenance_for_item",
previous_filters: {
item: "Столешница 600*3050*26 альмандин",
organization: "ООО \\Альтернатива Плюс\\",
counterparty: "Альтернатива Плюс, ООО",
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "ООО \\Альтернатива Плюс\\",
root_intent: "inventory_on_hand_as_of_date",
root_filters: {
organization: "ООО \\Альтернатива Плюс\\",
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31"
},
current_frame_kind: "inventory_drilldown"
}
});
expect(result?.handled).toBe(true);
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.capability_id).toBe("inventory_inventory_purchase_documents_for_item");
expect(result?.debug.reasons).not.toContain("open_items_from_followup_context");
expect(String(result?.reply_text ?? "")).toContain("Поступление товаров и услуг 00000000003");
});
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).toBeUndefined();
expect(String(result?.reply_text ?? "").split("\n")[0]).toContain("ИП Покупатель");
expect(String(result?.reply_text ?? "")).toContain("Документы выбытия");
});
it("routes selected-object wording 'куда мы продали эту позицию' into sale trace instead of replaying stock slice", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2020-06-18T00:00:00Z",
Registrator: "Реализация товаров и услуг 00000000131 от 18.06.2020 0:00:00",
AccountDt: "90.02",
AccountKt: "41.01",
Amount: 6490,
SubcontoKt1: "Пуф арий",
SubcontoKt3: "Основной склад",
SubcontoDt1: "ООО \\Ромашка\\",
SubcontoDt2: "Договор реализации № 14 от 17.06.2020",
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: "2020-05-31",
period_from: "2020-05-01",
period_to: "2020-05-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_sale_trace_for_item");
expect(result?.debug.selected_recipe).toBe("address_inventory_sale_trace_for_item_v1");
expect(result?.debug.extracted_filters?.item).toBe("Пуф арий");
expect(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
expect(result?.debug.reasons).toContain("inventory_selected_object_sale_trace_signal_detected");
expect(String(result?.reply_text ?? "").split("\n")[0]).toContain("ООО \\Ромашка\\");
expect(String(result?.reply_text ?? "")).toContain("Документы выбытия");
});
it("promotes short buyer follow-up after provenance answer into sale trace", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2020-04-15T00:00:00Z",
Registrator: "Реализация товаров и услуг 00000000119 от 15.04.2020 0:00:00",
AccountDt: "90.02",
AccountKt: "41.01",
Amount: 199,
SubcontoKt1: "Кромка с клеем 33 альмандин 137 м",
SubcontoDt1: "ООО \\Покупатель\\",
SubcontoDt2: "Договор реализации № 17 от 14.04.2020",
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: "Кромка с клеем 33 альмандин 137 м",
organization: "ООО \\Альтернатива Плюс\\"
},
previous_anchor_type: "item",
previous_anchor_value: "Кромка с клеем 33 альмандин 137 м"
}
});
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("Кромка с клеем 33 альмандин 137 м");
expect(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
expect(String(result?.reply_text ?? "")).toContain("ООО \\Покупатель\\");
});
it("detaches snapshot date from execution query during sale-trace history recovery", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2025-10-12T00:00:00Z",
Registrator: "Реализация товаров и услуг 00000000421 от 12.10.2025 0:00:00",
AccountDt: "90.02",
AccountKt: "41.01",
Amount: 165.83,
SubcontoKt1: "Кромка с клеем 33 дуб ниагара 137 м",
SubcontoKt3: "Основной склад",
SubcontoDt1: "ООО \\Покупатель\\",
SubcontoDt2: "Договор реализации № 55 от 01.10.2025",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle(
'По выбранному объекту "Кромка с клеем 33 дуб ниагара 137 м": куда в итоге продали эту позицию?',
{
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",
organization: "ООО \\Альтернатива Плюс\\"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "ООО \\Альтернатива Плюс\\"
}
}
);
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.extracted_filters?.as_of_date).toBeUndefined();
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1);
const query = String(executeAddressMcpQueryMock.mock.calls[0]?.[0]?.query ?? "");
expect(query).not.toContain("2019-03-31");
expect(query).not.toContain("2019-03-01");
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("совпадений не нашлось");
});
it.skip("keeps the full selected item when sale trace is asked in canonical wording after provenance", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2021-04-15T00:00:00Z",
Registrator: "Реализация товаров Рё услуг 00000000201 РѕС 15.04.2021 0:00:00",
AccountDt: "90.02",
AccountKt: "41.01",
Amount: 165.83,
SubcontoKt1: "Кромка с клеем 33 дуб ниагара 137 м",
SubcontoDt1: "ООО \\Покупатель\\",
SubcontoDt2: "Договор реализации в„– 17 РѕС 14.04.2021",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle(
"Определить контрагента, которому была продана позиция «Кромка с клеем 33 дуб ниагара 137 м» по выбранному объекту",
{
followupContext: {
previous_intent: "inventory_purchase_provenance_for_item",
previous_filters: {
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31",
item: "Кромка с клеем 33 дуб ниагара 137 м",
organization: "ООО \\Альтернатива Плюс\\"
},
previous_anchor_type: "item",
previous_anchor_value: "Кромка с клеем 33 дуб ниагара 137 м"
}
}
);
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.extracted_filters?.item).toBe("Кромка с клеем 33 дуб ниагара 137 м");
expect(String(result?.reply_text ?? "")).toContain("ООО \\Покупатель\\");
});
it("keeps snapshot date as an upper bound for selected-object provenance after dated stock slice", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2020-06-18T00:00:00Z",
Registrator: "Поступление товаров и услуг 00000000101 от 18.06.2020 0:00:00",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 13490,
SubcontoDt1: "Кресло орион",
SubcontoDt3: "Основной склад",
SubcontoKt1: "ООО \\Гамма-мебель\\",
SubcontoKt2: "Договор поставки № 11 от 15.06.2020",
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: "2020-03-31",
period_from: "2020-03-01",
period_to: "2020-03-31",
organization: "ООО \\Альтернатива Плюс\\"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "ООО \\Альтернатива Плюс\\"
}
});
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).toBeUndefined();
expect(result?.debug.extracted_filters?.period_from).toBeUndefined();
expect(result?.debug.extracted_filters?.period_to).toBeUndefined();
expect(result?.debug.reasons ?? []).not.toContain("lifecycle_execution_detached_from_snapshot_date");
expect(result?.debug.reasons ?? []).not.toContain("as_of_date_cleared_for_history_recovery");
expect(result?.debug.limitations ?? []).not.toContain("lifecycle_execution_detached_from_snapshot_date");
expect(result?.debug.limitations ?? []).not.toContain("as_of_date_cleared_for_history_recovery");
expect(String(result?.reply_text ?? "")).toContain("ООО \\Гамма-мебель\\");
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1);
const query = String(executeAddressMcpQueryMock.mock.calls[0]?.[0]?.query ?? "");
expect(query).toContain("Документ.ПоступлениеТоваровУслуг.Товары КАК Товары");
expect(query).toContain("Товары.Номенклатура В (ВЫБРАТЬ Номенклатура.Ссылка");
expect(query).not.toContain("2020-03-31");
expect(query).toContain("ПРЕДСТАВЛЕНИЕ(Товары.Ссылка.Организация) КАК Организация");
});
});