NODEDC_1C/llm_normalizer/backend/tests/addressCounterpartyItemFlow...

249 lines
10 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 { resolveAddressIntent } from "../src/services/addressIntentResolver";
import { AddressQueryService } from "../src/services/addressQueryService";
import { composeFactualReply } from "../src/services/address_runtime/composeStage";
afterEach(() => {
executeAddressMcpQueryMock.mockReset();
vi.restoreAllMocks();
});
describe("counterparty shipment item flow and open-items routing", () => {
it("routes counterparty shipment item-flow wording to documents by counterparty", () => {
const result = resolveAddressIntent("что нам отгружал чепурнов? какой товар или услугу?");
expect(result.intent).toBe("list_documents_by_counterparty");
expect(result.reasons).toContain("counterparty_item_flow_signal_detected");
});
it("routes account 60 tails wording to open items intent", () => {
const result = resolveAddressIntent("хвосты покажи по счету 60 на август 2022");
expect(result.intent).toBe("open_items_by_counterparty_or_contract");
});
it("includes resolved full counterparty name in document reply", () => {
const reply = composeFactualReply(
"list_documents_by_counterparty",
[
{
period: "2022-08-15T00:00:00Z",
registrator: "Поступление товаров и услуг 000000123 от 15.08.2022",
account_dt: "41.01",
account_kt: "60.01",
amount: 12500,
analytics: ["Чепурнов П.Д.", "Основной договор"],
item: "Кабель силовой",
organization: 'ООО "Альтернатива Плюс"'
}
],
{
userMessage: "покажи все документы по чапурнову",
counterpartyHint: "Чепурнов П.Д."
}
);
expect(reply.text).toContain("Контрагент: Чепурнов П.Д.");
});
it("uses purchase document query for fuzzy counterparty item-flow wording", async () => {
executeAddressMcpQueryMock
.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Counterparty: "Чепурнов П.Д.",
Registrator: "Чепурнов П.Д."
}
],
rows: [],
error: null
})
.mockResolvedValueOnce({
fetched_rows: 2,
matched_rows: 2,
raw_rows: [
{
Period: "2020-03-10T00:00:00Z",
Registrator: "Поступление товаров и услуг 000000001 от 10.03.2020",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 12000,
Nomenclature: "Кабель силовой",
Counterparty: "Чепурнов П.Д.",
Contract: "Основной договор",
Quantity: 2,
Organization: 'ООО "Альтернатива Плюс"'
},
{
Period: "2020-03-14T00:00:00Z",
Registrator: "Поступление товаров и услуг 000000002 от 14.03.2020",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 5400,
Nomenclature: "Патч-корд",
Counterparty: "Чепурнов П.Д.",
Contract: "Основной договор",
Quantity: 10,
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.detected_intent).toBe("list_documents_by_counterparty");
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("full_confirmed");
expect(String(result?.reply_text ?? "")).toContain("Контрагент: Чепурнов П.Д.");
expect(String(result?.reply_text ?? "")).toContain("Позиции:");
expect(String(result?.reply_text ?? "")).toContain("Кабель силовой");
expect(String(result?.reply_text ?? "")).toContain("договор:");
expect(String(result?.reply_text ?? "")).toContain("дата:");
const query = String(executeAddressMcpQueryMock.mock.calls.at(-1)?.[0]?.query ?? "");
expect(query).toContain("Документ.ПоступлениеТоваровУслуг.Товары");
expect(query).toContain("Документ.ПоступлениеТоваровУслуг.Услуги");
expect(query).toContain("Товары.Ссылка.Контрагент.Наименование ПОДОБНО");
expect(query).not.toContain("контрагентом");
});
it("explains supplier payments and return when no supply rows are found", async () => {
executeAddressMcpQueryMock.mockImplementation(async (request?: { query?: string }) => {
const query = String(request?.query ?? "");
if (query.includes("Справочник.Контрагенты")) {
return {
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Counterparty: "Чепурнов П.Д.",
Registrator: "Чепурнов П.Д."
}
],
rows: [],
error: null
};
}
if (query.includes("Документ.ПоступлениеТоваровУслуг.Товары") || query.includes("Документ.ПоступлениеТоваровУслуг.Услуги")) {
return {
fetched_rows: 0,
matched_rows: 0,
raw_rows: [],
rows: [],
error: null
};
}
return {
fetched_rows: 3,
matched_rows: 3,
raw_rows: [
{
Period: "2021-06-11T12:00:01Z",
Registrator: "Списание с расчетного счета 00000000124 от 11.06.2021",
AccountDt: "60.02",
AccountKt: "51",
Amount: 119210,
SubcontoDt1: "Чепурнов П.Д.",
SubcontoDt2: "Договор № 11/1 от 25.11.2020",
SubcontoKt1: "ПАО СБЕРБАНК"
},
{
Period: "2021-05-18T12:00:00Z",
Registrator: "Списание с расчетного счета 00000000112 от 18.05.2021",
AccountDt: "60.02",
AccountKt: "51",
Amount: 180230,
SubcontoDt1: "Чепурнов П.Д.",
SubcontoDt2: "Договор № 11/1 от 25.11.2020",
SubcontoKt1: "ПАО СБЕРБАНК"
},
{
Period: "2022-01-20T12:00:03Z",
Registrator: "Поступление на расчетный счет 00000000001 от 20.01.2022",
AccountDt: "51",
AccountKt: "60.02",
Amount: 299440,
SubcontoKt1: "Чепурнов П.Д.",
SubcontoKt2: "Договор № 11/1 от 25.11.2020",
SubcontoDt1: "ПАО СБЕРБАНК"
}
],
rows: [],
error: null
};
});
const service = new AddressQueryService();
const result = await service.tryHandle(
"какие товары или услуги были отгружены нашей компании контрагентом чапурновым?"
);
expect(result?.handled).toBe(true);
expect(String(result?.reply_text ?? "")).toContain("Подтвержденных поставок товаров или услуг не найдено");
expect(String(result?.reply_text ?? "")).toContain("исходящих оплат поставщику");
expect(String(result?.reply_text ?? "")).toContain("возвратов от поставщика");
expect(String(result?.reply_text ?? "")).toContain("Договор:");
expect(result?.debug.address_coverage_evidence_v1?.coverage_status).toBe("blocked");
expect(result?.debug.address_coverage_evidence_v1?.evidence_basis).toBe("unknown");
expect(result?.debug.reasons).toContain("counterparty_item_flow_no_supply_but_bank_activity_explained");
expect(executeAddressMcpQueryMock.mock.calls.length).toBeGreaterThanOrEqual(2);
});
it("keeps account 60 tails in open-items route and mentions the account in reply", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2022-08-31T23:59:59Z",
Registrator: "Поступление на расчетный счет 000000001 от 31.08.2022",
AccountDt: "51",
AccountKt: "60.01",
Amount: 150000,
SubcontoKt1: "ООО Поставщик",
SubcontoKt2: "Договор поставки",
Organization: 'ООО "Альтернатива Плюс"'
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle("хвосты покажи по счету 60 на август 2022");
expect(result?.handled).toBe(true);
expect(result?.response_type).toBe("FACTUAL_LIST");
expect(result?.debug.detected_intent).toBe("open_items_by_counterparty_or_contract");
expect(String(result?.reply_text ?? "")).toContain("счету 60");
const query = String(executeAddressMcpQueryMock.mock.calls[0]?.[0]?.query ?? "");
expect(query).toContain("РегистрБухгалтерии.Хозрасчетный.ДвиженияССубконто");
expect(query).toContain("60%");
});
});