ЮИ - Перенести настройки ассистента в управление автопрогонами и убрать верхний режим Ассистент
This commit is contained in:
+82
@@ -0,0 +1,82 @@
|
||||
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("counterparty lifecycle organization scope regressions", () => {
|
||||
it("keeps carried organization scope without false empty-match when rows omit organization column", async () => {
|
||||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||||
fetched_rows: 3,
|
||||
matched_rows: 3,
|
||||
raw_rows: [
|
||||
{
|
||||
Period: "2020-01-15T00:00:00Z",
|
||||
Registrator: "CP_CUSTOMER_ACTIVITY",
|
||||
AccountDt: "62.01",
|
||||
AccountKt: "90.01",
|
||||
Amount: 12,
|
||||
Counterparty: 'ООО "Ромашка"'
|
||||
},
|
||||
{
|
||||
Period: "2020-02-20T00:00:00Z",
|
||||
Registrator: "CP_CUSTOMER_ACTIVITY",
|
||||
AccountDt: "62.01",
|
||||
AccountKt: "90.01",
|
||||
Amount: 8,
|
||||
Counterparty: 'ООО "Ландыш"'
|
||||
},
|
||||
{
|
||||
Period: "2020-03-10T00:00:00Z",
|
||||
Registrator: "CP_CUSTOMER_ACTIVITY",
|
||||
AccountDt: "62.01",
|
||||
AccountKt: "90.01",
|
||||
Amount: 4,
|
||||
Counterparty: 'ООО "Ромашка"'
|
||||
}
|
||||
],
|
||||
rows: [],
|
||||
error: null
|
||||
});
|
||||
|
||||
const service = new AddressQueryService();
|
||||
const result = await service.tryHandle("какие клиенты заплатили больше всего денег за все время", {
|
||||
followupContext: {
|
||||
previous_intent: "vat_payable_forecast",
|
||||
previous_filters: {
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-03-31"
|
||||
},
|
||||
previous_anchor_type: "organization",
|
||||
previous_anchor_value: 'ООО "Альтернатива Плюс"'
|
||||
}
|
||||
});
|
||||
|
||||
expect(result?.handled).toBe(true);
|
||||
expect(result?.reply_type).toBe("factual");
|
||||
expect(result?.response_type).toBe("FACTUAL_LIST");
|
||||
expect(result?.debug.detected_intent).toBe("counterparty_activity_lifecycle");
|
||||
expect(result?.debug.extracted_filters?.organization).toBe('ООО "Альтернатива Плюс"');
|
||||
expect(result?.debug.match_failure_reason).toBeNull();
|
||||
expect(result?.debug.rows_matched).toBe(3);
|
||||
expect(String(result?.reply_text ?? "")).toContain('ООО "Ромашка"');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { runAddressDecomposeStage } from "../src/services/address_runtime/decomposeStage";
|
||||
|
||||
describe("address follow-up temporal regressions", () => {
|
||||
it("replaces VAT month-only follow-up with the previous follow-up year", () => {
|
||||
const result = runAddressDecomposeStage("а на март", {
|
||||
previous_intent: "vat_payable_confirmed_as_of_date",
|
||||
previous_filters: {
|
||||
period_from: "2019-05-01",
|
||||
period_to: "2019-05-31",
|
||||
as_of_date: "2019-05-31"
|
||||
},
|
||||
previous_anchor_type: "unknown",
|
||||
previous_anchor_value: null
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("vat_payable_confirmed_as_of_date");
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2019-03-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2019-03-31");
|
||||
expect(result?.filters.extracted_filters.as_of_date).toBe("2019-03-31");
|
||||
expect(result?.baseReasons).toContain("period_derived_from_followup_context_year");
|
||||
});
|
||||
|
||||
it("replaces VAT month-only follow-up with a later month from the same inherited year", () => {
|
||||
const result = runAddressDecomposeStage("на сентябрь", {
|
||||
previous_intent: "vat_payable_confirmed_as_of_date",
|
||||
previous_filters: {
|
||||
period_from: "2019-05-01",
|
||||
period_to: "2019-05-31",
|
||||
as_of_date: "2019-05-31"
|
||||
},
|
||||
previous_anchor_type: "unknown",
|
||||
previous_anchor_value: null
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("vat_payable_confirmed_as_of_date");
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2019-09-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2019-09-30");
|
||||
expect(result?.filters.extracted_filters.as_of_date).toBe("2019-09-30");
|
||||
expect(result?.baseReasons).toContain("period_derived_from_followup_context_year");
|
||||
});
|
||||
|
||||
it("keeps same-date inventory pivot anchored to the previous VAT date", () => {
|
||||
const result = runAddressDecomposeStage("какие остатки по складу на эту же дату", {
|
||||
previous_intent: "vat_payable_confirmed_as_of_date",
|
||||
previous_filters: {
|
||||
period_from: "2019-09-01",
|
||||
period_to: "2019-09-30",
|
||||
as_of_date: "2019-09-30"
|
||||
},
|
||||
previous_anchor_type: "unknown",
|
||||
previous_anchor_value: null
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(result?.filters.extracted_filters.as_of_date).toBe("2019-09-30");
|
||||
expect(result?.filters.extracted_filters.warehouse).toBeUndefined();
|
||||
expect(result?.baseReasons).toContain("as_of_date_from_followup_context");
|
||||
});
|
||||
});
|
||||
@@ -56,7 +56,6 @@ describe("inventory root frame regressions", () => {
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("inventory_purchase_provenance_for_item");
|
||||
expect(result?.filters.extracted_filters.item).toBe("Зеркало для инвалидов поворотное травмобезопасное");
|
||||
expect(result?.filters.extracted_filters.as_of_date).toBe("2022-02-28");
|
||||
expect(
|
||||
result?.baseReasons?.includes("intent_adjusted_to_inventory_followup_context") ||
|
||||
result?.intent.reasons.includes("inventory_selected_object_provenance_signal_detected")
|
||||
@@ -111,4 +110,67 @@ describe("inventory root frame regressions", () => {
|
||||
expect(result?.intent.intent).not.toBe("bank_operations_by_counterparty");
|
||||
expect(result?.filters.extracted_filters.item).toBe("Четки Пост (84*117)");
|
||||
});
|
||||
it("restores stock snapshot intent for selected-object restatement on the previously reviewed date", () => {
|
||||
const result = runAddressDecomposeStage(
|
||||
'По выбранному объекту "Столешница 600*3050*26 альмандин": покажи еще раз остатки на дату которую до этого рассматривали',
|
||||
{
|
||||
previous_intent: "inventory_purchase_documents_for_item",
|
||||
previous_filters: {
|
||||
item: "Столешница 600*3050*26 альмандин",
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
as_of_date: "2019-05-31"
|
||||
},
|
||||
previous_anchor_type: "item",
|
||||
previous_anchor_value: "Столешница 600*3050*26 альмандин",
|
||||
root_intent: "inventory_on_hand_as_of_date",
|
||||
root_filters: {
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
period_from: "2019-05-01",
|
||||
period_to: "2019-05-31",
|
||||
as_of_date: "2019-05-31"
|
||||
},
|
||||
root_anchor_type: "organization",
|
||||
root_anchor_value: 'ООО "Альтернатива Плюс"',
|
||||
current_frame_kind: "inventory_drilldown"
|
||||
}
|
||||
);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(result?.intent.reasons).toContain("intent_restored_to_inventory_root_frame");
|
||||
expect(result?.filters.extracted_filters.item).toBe("Столешница 600*3050*26 альмандин");
|
||||
expect(result?.filters.extracted_filters.organization).toBe('ООО "Альтернатива Плюс"');
|
||||
expect(result?.filters.extracted_filters.as_of_date).toBe("2019-05-31");
|
||||
expect(result?.baseReasons).toContain("address_followup_context_applied");
|
||||
});
|
||||
|
||||
it("restores inventory root frame for root-context-only month follow-up after drilldown", () => {
|
||||
const result = runAddressDecomposeStage("остатки на июль 2019", {
|
||||
previous_filters: {
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
as_of_date: "2020-03-31",
|
||||
period_from: "2020-03-01",
|
||||
period_to: "2020-03-31"
|
||||
},
|
||||
previous_anchor_type: null,
|
||||
previous_anchor_value: null,
|
||||
root_intent: "inventory_on_hand_as_of_date",
|
||||
root_filters: {
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
as_of_date: "2020-03-31",
|
||||
period_from: "2020-03-01",
|
||||
period_to: "2020-03-31"
|
||||
},
|
||||
root_context_only: true,
|
||||
current_frame_kind: "inventory_root"
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(result?.intent.reasons).toContain("intent_restored_to_inventory_root_frame");
|
||||
expect(result?.filters.extracted_filters.organization).toBe('ООО "Альтернатива Плюс"');
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2019-07-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2019-07-31");
|
||||
expect(result?.filters.extracted_filters.as_of_date).toBe("2019-07-31");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,8 +21,8 @@ afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("inventory sale trace document route", () => {
|
||||
it("uses document sales route with native item resolution for selected-object buyer follow-up", async () => {
|
||||
describe("inventory sale trace movement route", () => {
|
||||
it("uses 41.01 movement route with native item resolution for selected-object buyer follow-up", async () => {
|
||||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||||
fetched_rows: 2,
|
||||
matched_rows: 2,
|
||||
@@ -30,25 +30,27 @@ describe("inventory sale trace document route", () => {
|
||||
{
|
||||
Period: "2015-02-25T12:00:00Z",
|
||||
Registrator: "Реализация товаров и услуг 00000000012 от 25.02.2015 12:00:00",
|
||||
AccountDt: "",
|
||||
AccountDt: "62.01",
|
||||
AccountKt: "41.01",
|
||||
Amount: 12605435.66,
|
||||
Quantity: 40,
|
||||
Item: "Рабочая станция универсального специалиста (индивидуальное изготовление)",
|
||||
Counterparty: "Комитет государственных услуг г. Москвы",
|
||||
Contract: "Гос.контракт № 42/15 от 20.02.2015г. Силино окна",
|
||||
SubcontoKt1: "Рабочая станция универсального специалиста (индивидуальное изготовление)",
|
||||
SubcontoKt3: "Основной склад",
|
||||
SubcontoDt1: "Комитет государственных услуг г. Москвы",
|
||||
SubcontoDt2: "Гос.контракт № 42/15 от 20.02.2015 г. Силино окна",
|
||||
Organization: "ООО \\Альтернатива Плюс\\"
|
||||
},
|
||||
{
|
||||
Period: "2015-02-09T12:00:14Z",
|
||||
Registrator: "Реализация товаров и услуг 00000000004 от 09.02.2015 12:00:14",
|
||||
AccountDt: "",
|
||||
AccountDt: "62.01",
|
||||
AccountKt: "41.01",
|
||||
Amount: 16421320.17,
|
||||
Quantity: 51,
|
||||
Item: "Рабочая станция универсального специалиста (индивидуальное изготовление)",
|
||||
Counterparty: "Комитет государственных услуг г. Москвы",
|
||||
Contract: "Гос.контракт № 17/15 от 02.02.2015г.",
|
||||
SubcontoKt1: "Рабочая станция универсального специалиста (индивидуальное изготовление)",
|
||||
SubcontoKt3: "Основной склад",
|
||||
SubcontoDt1: "Комитет государственных услуг г. Москвы",
|
||||
SubcontoDt2: "Гос.контракт № 17/15 от 02.02.2015 г.",
|
||||
Organization: "ООО \\Альтернатива Плюс\\"
|
||||
}
|
||||
],
|
||||
@@ -82,11 +84,12 @@ describe("inventory sale trace document route", () => {
|
||||
|
||||
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1);
|
||||
const query = String(executeAddressMcpQueryMock.mock.calls[0]?.[0]?.query ?? "");
|
||||
expect(query).toContain("Документ.РеализацияТоваровУслуг.Товары КАК Товары");
|
||||
expect(query).toContain("Товары.Номенклатура В (ВЫБРАТЬ Номенклатура.Ссылка");
|
||||
expect(query).toContain('Номенклатура.Наименование = "Рабочая станция универсального специалиста (индивидуальное изготовление)"');
|
||||
expect(query).toContain("ПРЕДСТАВЛЕНИЕ(Товары.Ссылка.Контрагент) КАК Контрагент");
|
||||
expect(query).toContain("ПРЕДСТАВЛЕНИЕ(Товары.Ссылка.Организация) КАК Организация");
|
||||
expect(query).toContain("РегистрБухгалтерии.Хозрасчетный.ДвиженияССубконто КАК Движения");
|
||||
expect(query).toContain('ПОДСТРОКА(ЕСТЬNULL(Движения.СчетКт.Код, ""), 1, 5) = "41.01"');
|
||||
expect(query).toContain("Движения.СубконтоКт1 В (ВЫБРАТЬ Номенклатура.Ссылка");
|
||||
expect(query).toContain(
|
||||
'Номенклатура.Наименование = "Рабочая станция универсального специалиста (индивидуальное изготовление)"'
|
||||
);
|
||||
expect(query).not.toContain("2016-06-30");
|
||||
expect(query).not.toContain("2016-06-01");
|
||||
expect(query).not.toContain('ПРЕДСТАВЛЕНИЕ(Движения.Организация) = "ООО \\Альтернатива Плюс\\"');
|
||||
|
||||
+7
-5
@@ -25,13 +25,14 @@ describe("inventory sale trace selected-object regressions", () => {
|
||||
const saleRow = {
|
||||
Period: "2021-04-15T00:00:00Z",
|
||||
Registrator: "Реализация товаров и услуг 00000000201 от 15.04.2021 0:00:00",
|
||||
AccountDt: "",
|
||||
AccountDt: "62.01",
|
||||
AccountKt: "41.01",
|
||||
Amount: 165.83,
|
||||
Quantity: 1,
|
||||
Item: "Кромка с клеем 33 дуб ниагара 137 м",
|
||||
Counterparty: "ООО \\Покупатель\\",
|
||||
Contract: "Договор реализации № 17 от 14.04.2021",
|
||||
SubcontoKt1: "Кромка с клеем 33 дуб ниагара 137 м",
|
||||
SubcontoKt3: "Основной склад",
|
||||
SubcontoDt1: "ООО \\Покупатель\\",
|
||||
SubcontoDt2: "Договор реализации № 17 от 14.04.2021",
|
||||
Organization: "ООО \\Альтернатива Плюс\\"
|
||||
};
|
||||
|
||||
@@ -70,7 +71,7 @@ describe("inventory sale trace selected-object regressions", () => {
|
||||
|
||||
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1);
|
||||
const query = String(executeAddressMcpQueryMock.mock.calls[0]?.[0]?.query ?? "");
|
||||
expect(query).toContain("Документ.РеализацияТоваровУслуг.Товары КАК Товары");
|
||||
expect(query).toContain("РегистрБухгалтерии.Хозрасчетный.ДвиженияССубконто КАК Движения");
|
||||
expect(query).toContain('Номенклатура.Наименование = "Кромка с клеем 33 дуб ниагара 137 м"');
|
||||
expect(query).not.toContain('Номенклатура.Наименование = "Кромка"');
|
||||
});
|
||||
@@ -132,6 +133,7 @@ describe("inventory sale trace selected-object regressions", () => {
|
||||
|
||||
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1);
|
||||
const query = String(executeAddressMcpQueryMock.mock.calls[0]?.[0]?.query ?? "");
|
||||
expect(query).toContain("РегистрБухгалтерии.Хозрасчетный.ДвиженияССубконто КАК Движения");
|
||||
expect(query).toContain('Номенклатура.Наименование = "Кромка с клеем 33 дуб ниагара 137 м"');
|
||||
expect(query).not.toContain('Номенклатура.Наименование = "Кромка"');
|
||||
});
|
||||
|
||||
@@ -193,7 +193,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
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.extracted_filters?.as_of_date).toBeUndefined();
|
||||
expect(String(result?.reply_text ?? "")).toContain("Торговый дом \\Союз МСК\\");
|
||||
});
|
||||
|
||||
@@ -242,7 +242,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
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(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
|
||||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
|
||||
});
|
||||
|
||||
@@ -292,7 +292,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
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).toBe("2020-06-30");
|
||||
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");
|
||||
@@ -345,7 +345,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
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(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
|
||||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
|
||||
});
|
||||
|
||||
@@ -394,7 +394,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
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(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
|
||||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
|
||||
});
|
||||
|
||||
@@ -444,7 +444,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
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).toBe("2016-05-31");
|
||||
expect(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
|
||||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
|
||||
});
|
||||
|
||||
@@ -491,7 +491,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
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(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
|
||||
expect(String(result?.reply_text ?? "")).toContain("Поступление товаров и услуг 00000000077");
|
||||
});
|
||||
|
||||
@@ -537,7 +537,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
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(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
|
||||
expect(String(result?.reply_text ?? "").split("\n")[0]).toContain("ИП Покупатель");
|
||||
expect(String(result?.reply_text ?? "")).toContain("Документы выбытия");
|
||||
});
|
||||
@@ -583,7 +583,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
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).toBe("2020-05-31");
|
||||
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("Документы выбытия");
|
||||
@@ -631,7 +631,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
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).toBe("2020-03-31");
|
||||
expect(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
|
||||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Покупатель\\");
|
||||
});
|
||||
|
||||
@@ -678,10 +678,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
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.reasons).toContain("lifecycle_execution_detached_from_snapshot_date");
|
||||
expect(result?.debug.reasons).toContain("as_of_date_cleared_for_history_recovery");
|
||||
expect(result?.debug.limitations).toContain("lifecycle_execution_detached_from_snapshot_date");
|
||||
expect(result?.debug.limitations).toContain("as_of_date_cleared_for_history_recovery");
|
||||
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");
|
||||
@@ -808,7 +805,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
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("2020-03-31");
|
||||
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");
|
||||
@@ -820,7 +817,7 @@ describe("inventory selected-object follow-up", () => {
|
||||
const query = String(executeAddressMcpQueryMock.mock.calls[0]?.[0]?.query ?? "");
|
||||
expect(query).toContain("Документ.ПоступлениеТоваровУслуг.Товары КАК Товары");
|
||||
expect(query).toContain("Товары.Номенклатура В (ВЫБРАТЬ Номенклатура.Ссылка");
|
||||
expect(query).toContain("Товары.Ссылка.Дата <= ДАТАВРЕМЯ(2020, 3, 31, 23, 59, 59)");
|
||||
expect(query).not.toContain("2020-03-31");
|
||||
expect(query).toContain("ПРЕДСТАВЛЕНИЕ(Товары.Ссылка.Организация) КАК Организация");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,6 +62,29 @@ describe("inventory warehouse anchor extraction", () => {
|
||||
"inventory_on_hand_as_of_date"
|
||||
).extracted_filters;
|
||||
|
||||
expect(filters.warehouse).toBeUndefined();
|
||||
});
|
||||
it("does not materialize self-scope slang tail as warehouse anchor in stock snapshot wording from the run", () => {
|
||||
const result = extractAddressFilters("что там на складе у нас висит", "inventory_on_hand_as_of_date");
|
||||
|
||||
expect(result.extracted_filters.warehouse).toBeUndefined();
|
||||
expect(result.warnings).toContain("warehouse_self_scope_detected");
|
||||
expect(result.semantic_frame?.scope_kind).toBe("implicit_self_scope");
|
||||
expect(result.semantic_frame?.anchor_kind).toBe("self_scope");
|
||||
expect(result.semantic_frame?.anchor_value).toBeNull();
|
||||
});
|
||||
it("does not materialize same-date phrasing as warehouse anchor in stock follow-up", () => {
|
||||
const filters = extractAddressFilters("какие остатки по складу на эту же дату", "inventory_on_hand_as_of_date").extracted_filters;
|
||||
|
||||
expect(filters.warehouse).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not materialize current-date phrasing as warehouse anchor in stock follow-up", () => {
|
||||
const filters = extractAddressFilters(
|
||||
"получить остатки по складу на текущую дату",
|
||||
"inventory_on_hand_as_of_date"
|
||||
).extracted_filters;
|
||||
|
||||
expect(filters.warehouse).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2376,5 +2376,228 @@ describe("assistant address follow-up carryover", () => {
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("treats short inventory month follow-up after drilldown as continuation of the recent inventory root frame", async () => {
|
||||
const calls: Array<{ message: string; options?: any }> = [];
|
||||
const followupMessage = "остатки на июль 2019";
|
||||
const itemLabel = "Четки Пост (84*117)";
|
||||
const organization = 'ООО "Альтернатива Плюс"';
|
||||
|
||||
const inventoryResult = {
|
||||
handled: true,
|
||||
reply_text: "Подтвержденный складской срез на 31.07.2019 собран.",
|
||||
reply_type: "factual",
|
||||
response_type: "FACTUAL_SUMMARY",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_on_hand_as_of_date",
|
||||
detected_intent_confidence: "high",
|
||||
extracted_filters: {
|
||||
as_of_date: "2019-07-31",
|
||||
period_from: "2019-07-01",
|
||||
period_to: "2019-07-31",
|
||||
organization
|
||||
},
|
||||
selected_recipe: "address_inventory_on_hand_as_of_date_v1",
|
||||
reasons: ["address_action_detected", "address_entity_detected", "address_followup_context_applied"]
|
||||
}
|
||||
} as any;
|
||||
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string, options?: any) => {
|
||||
calls.push({ message, options });
|
||||
if (message === followupMessage && options?.followupContext) {
|
||||
return inventoryResult;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
assistant_reply: "normalizer_fallback_should_not_be_used",
|
||||
reply_type: "partial_coverage",
|
||||
debug: {}
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const sessionId = `asst-address-followup-inventory-root-month-${Date.now()}`;
|
||||
sessions.appendItem(sessionId, {
|
||||
message_id: "msg-inventory-root-seed-july",
|
||||
session_id: sessionId,
|
||||
role: "assistant",
|
||||
text: "inventory root seed",
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-15T19:00:00.000Z",
|
||||
trace_id: "address-root-seed-july",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_on_hand_as_of_date",
|
||||
extracted_filters: {
|
||||
as_of_date: "2020-03-31",
|
||||
period_from: "2020-03-01",
|
||||
period_to: "2020-03-31",
|
||||
organization
|
||||
},
|
||||
selected_recipe: "address_inventory_on_hand_as_of_date_v1"
|
||||
}
|
||||
} as any);
|
||||
sessions.appendItem(sessionId, {
|
||||
message_id: "msg-inventory-drilldown-seed-july",
|
||||
session_id: sessionId,
|
||||
role: "assistant",
|
||||
text: "inventory provenance seed",
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-15T19:01:00.000Z",
|
||||
trace_id: "address-drilldown-seed-july",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_purchase_provenance_for_item",
|
||||
extracted_filters: {
|
||||
item: itemLabel,
|
||||
organization
|
||||
},
|
||||
selected_recipe: "address_inventory_purchase_provenance_for_item_v1",
|
||||
anchor_type: "item",
|
||||
anchor_value_raw: itemLabel,
|
||||
anchor_value_resolved: itemLabel
|
||||
}
|
||||
} as any);
|
||||
|
||||
const second = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: followupMessage,
|
||||
useMock: true
|
||||
} as any);
|
||||
|
||||
expect(second.ok).toBe(true);
|
||||
expect(second.reply_type).toBe("factual");
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].message).toBe(followupMessage);
|
||||
expect(calls[0].options?.followupContext?.previous_intent).toBeUndefined();
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.item).toBeUndefined();
|
||||
expect(calls[0].options?.followupContext?.root_context_only).toBe(true);
|
||||
expect(calls[0].options?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(calls[0].options?.followupContext?.root_filters?.organization).toBe(organization);
|
||||
expect(calls[0].options?.followupContext?.root_filters?.as_of_date).toBe("2020-03-31");
|
||||
expect(calls[0].options?.followupContext?.current_frame_kind).toBe("inventory_root");
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("treats colloquial supplier follow-up from an inventory root slice as continuation of the focused item", async () => {
|
||||
const calls: Array<{ message: string; options?: any }> = [];
|
||||
const followupMessage = "у кого мы модуль прямоугольный купили кстати";
|
||||
const itemLabel = "Модуль прямоугольый 1400*110*750";
|
||||
const organization = 'ООО "Альтернатива Плюс"';
|
||||
|
||||
const provenanceResult = {
|
||||
handled: true,
|
||||
reply_text: `По позиции ${itemLabel} подтвержден поставщик: Торговый дом "Союз".`,
|
||||
reply_type: "factual",
|
||||
response_type: "FACTUAL_SUMMARY",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_purchase_provenance_for_item",
|
||||
detected_intent_confidence: "medium",
|
||||
extracted_filters: {
|
||||
item: itemLabel,
|
||||
organization,
|
||||
as_of_date: "2020-05-31"
|
||||
},
|
||||
selected_recipe: "address_inventory_purchase_provenance_for_item_v1",
|
||||
reasons: ["address_action_detected", "address_entity_detected", "address_followup_context_applied"]
|
||||
}
|
||||
} as any;
|
||||
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string, options?: any) => {
|
||||
calls.push({ message, options });
|
||||
if (message === followupMessage && options?.followupContext) {
|
||||
return provenanceResult;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
} as any;
|
||||
|
||||
const normalizerService = {
|
||||
normalize: vi.fn(async () => ({
|
||||
assistant_reply: "normalizer_fallback_should_not_be_used",
|
||||
reply_type: "partial_coverage",
|
||||
debug: {}
|
||||
}))
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const service = new AssistantService(
|
||||
normalizerService,
|
||||
sessions as any,
|
||||
{} as any,
|
||||
{ persistSession: vi.fn() } as any,
|
||||
addressQueryService
|
||||
);
|
||||
|
||||
const sessionId = `asst-address-followup-root-item-focus-${Date.now()}`;
|
||||
sessions.appendItem(sessionId, {
|
||||
message_id: "msg-inventory-root-seed",
|
||||
session_id: sessionId,
|
||||
role: "assistant",
|
||||
text: `${itemLabel} был в остатках на 31.05.2020.`,
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-15T19:00:00.000Z",
|
||||
trace_id: "address-root-seed",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_on_hand_as_of_date",
|
||||
extracted_filters: {
|
||||
organization,
|
||||
as_of_date: "2020-05-31",
|
||||
period_from: "2020-05-01",
|
||||
period_to: "2020-05-31"
|
||||
},
|
||||
selected_recipe: "address_inventory_on_hand_as_of_date_v1"
|
||||
}
|
||||
} as any);
|
||||
sessions.setAddressNavigationState(sessionId, {
|
||||
session_id: sessionId,
|
||||
session_context: {
|
||||
active_focus_object: {
|
||||
object_type: "item",
|
||||
label: itemLabel
|
||||
},
|
||||
organization_scope: organization,
|
||||
date_scope: {
|
||||
as_of_date: "2020-05-31",
|
||||
period_from: "2020-05-01",
|
||||
period_to: "2020-05-31"
|
||||
}
|
||||
}
|
||||
} as any);
|
||||
|
||||
const response = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: followupMessage,
|
||||
useMock: true
|
||||
} as any);
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
expect(response.reply_type).toBe("factual");
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].message).toBe(followupMessage);
|
||||
expect(calls[0].options?.followupContext?.previous_intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(calls[0].options?.followupContext?.previous_anchor_type).toBe("item");
|
||||
expect(calls[0].options?.followupContext?.previous_anchor_value).toBe(itemLabel);
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.item).toBe(itemLabel);
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.organization).toBe(organization);
|
||||
expect(calls[0].options?.followupContext?.previous_filters?.as_of_date).toBe("2020-05-31");
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ describe("assistant address lane runtime adapter", () => {
|
||||
const runAddressLaneAttempt = vi.fn(async () => factualLane());
|
||||
|
||||
const result = await runAssistantAddressLaneRuntime({
|
||||
userMessage: "сырой вопрос",
|
||||
addressInputMessage: "нормализованный вопрос",
|
||||
userMessage: "raw question",
|
||||
addressInputMessage: "normalized question",
|
||||
carryover,
|
||||
shouldPreferContextualLane: true,
|
||||
canRetryWithRawUserMessage: true,
|
||||
@@ -44,24 +44,24 @@ describe("assistant address lane runtime adapter", () => {
|
||||
});
|
||||
|
||||
expect(result.handled).toBe(true);
|
||||
expect(result.selection?.messageUsed).toBe("нормализованный вопрос");
|
||||
expect(result.selection?.messageUsed).toBe("normalized question");
|
||||
expect(result.selection?.carryMeta).toBe(carryover);
|
||||
expect(result.retryAudit.attempted).toBe(false);
|
||||
expect(runAddressLaneAttempt).toHaveBeenCalledTimes(1);
|
||||
expect(runAddressLaneAttempt).toHaveBeenCalledWith("нормализованный вопрос", carryover);
|
||||
expect(runAddressLaneAttempt).toHaveBeenCalledWith("normalized question", carryover, null);
|
||||
});
|
||||
|
||||
it("retries with raw message after limited result and returns factual retry", async () => {
|
||||
const carryover: AssistantAddressFollowupCarryoverLike = { followupContext: { scope: "ctx" } };
|
||||
const runAddressLaneAttempt = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(limitedLane("empty_match")) // primary
|
||||
.mockResolvedValueOnce(limitedLane("empty_match")) // contextual
|
||||
.mockResolvedValueOnce(factualLane()); // raw contextual retry
|
||||
.mockResolvedValueOnce(limitedLane("empty_match"))
|
||||
.mockResolvedValueOnce(limitedLane("empty_match"))
|
||||
.mockResolvedValueOnce(factualLane());
|
||||
|
||||
const result = await runAssistantAddressLaneRuntime({
|
||||
userMessage: "сырой вопрос",
|
||||
addressInputMessage: "нормализованный вопрос",
|
||||
userMessage: "raw question",
|
||||
addressInputMessage: "normalized question",
|
||||
carryover,
|
||||
shouldPreferContextualLane: false,
|
||||
canRetryWithRawUserMessage: true,
|
||||
@@ -70,7 +70,7 @@ describe("assistant address lane runtime adapter", () => {
|
||||
});
|
||||
|
||||
expect(result.handled).toBe(true);
|
||||
expect(result.selection?.messageUsed).toBe("сырой вопрос");
|
||||
expect(result.selection?.messageUsed).toBe("raw question");
|
||||
expect(result.selection?.carryMeta).toBe(carryover);
|
||||
expect(result.retryAudit.attempted).toBe(true);
|
||||
expect(result.retryAudit.reason).toBe("limited_result_retry_with_raw_message");
|
||||
@@ -83,12 +83,12 @@ describe("assistant address lane runtime adapter", () => {
|
||||
it("returns pending limited result when retry is disabled", async () => {
|
||||
const runAddressLaneAttempt = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(limitedLane("missing_anchor")) // primary
|
||||
.mockResolvedValueOnce(unhandledLane()); // contextual fallback
|
||||
.mockResolvedValueOnce(limitedLane("missing_anchor"))
|
||||
.mockResolvedValueOnce(unhandledLane());
|
||||
|
||||
const result = await runAssistantAddressLaneRuntime({
|
||||
userMessage: "сырой вопрос",
|
||||
addressInputMessage: "нормализованный вопрос",
|
||||
userMessage: "raw question",
|
||||
addressInputMessage: "normalized question",
|
||||
carryover: { followupContext: { scope: "ctx" } },
|
||||
shouldPreferContextualLane: false,
|
||||
canRetryWithRawUserMessage: false,
|
||||
@@ -97,9 +97,8 @@ describe("assistant address lane runtime adapter", () => {
|
||||
});
|
||||
|
||||
expect(result.handled).toBe(true);
|
||||
expect(result.selection?.messageUsed).toBe("нормализованный вопрос");
|
||||
expect(result.selection?.messageUsed).toBe("normalized question");
|
||||
expect(result.selection?.addressLane.debug?.limited_reason_category).toBe("missing_anchor");
|
||||
expect(result.retryAudit.attempted).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -178,6 +178,80 @@ describe("assistant address orchestration runtime adapter", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers raw inventory temporal root follow-up over account-balance canonical drift", async () => {
|
||||
const resolveAddressFollowupCarryoverContext = vi.fn(() => ({
|
||||
followupContext: {
|
||||
previous_intent: "inventory_purchase_provenance_for_item",
|
||||
previous_filters: {
|
||||
item: "Четки Пост (84*117)",
|
||||
organization: "ООО \\Альтернатива Плюс\\"
|
||||
},
|
||||
previous_anchor_type: "item",
|
||||
previous_anchor_value: "Четки Пост (84*117)",
|
||||
root_intent: "inventory_on_hand_as_of_date",
|
||||
root_filters: {
|
||||
as_of_date: "2020-03-31",
|
||||
period_from: "2020-03-01",
|
||||
period_to: "2020-03-31",
|
||||
organization: "ООО \\Альтернатива Плюс\\"
|
||||
},
|
||||
current_frame_kind: "inventory_drilldown"
|
||||
}
|
||||
}));
|
||||
const resolveAssistantOrchestrationDecision = vi.fn(() => ({
|
||||
runAddressLane: true,
|
||||
livingMode: "address_data",
|
||||
livingReason: "address_lane_triggered",
|
||||
toolGateDecision: "run_address_lane",
|
||||
toolGateReason: "followup_context_detected",
|
||||
orchestrationContract: { schema_version: "assistant_orchestration_contract_v1" }
|
||||
}));
|
||||
const buildAddressLlmPredecomposeContractV1 = vi.fn(
|
||||
({ sourceMessage, canonicalMessage }: { sourceMessage: string; canonicalMessage: string }) => ({
|
||||
schema_version: "address_llm_predecompose_contract_v1",
|
||||
source_message: sourceMessage,
|
||||
canonical_message: canonicalMessage,
|
||||
mode: "address_query",
|
||||
intent: canonicalMessage === sourceMessage ? "inventory_on_hand_as_of_date" : "account_balance_snapshot"
|
||||
})
|
||||
);
|
||||
const rawMessage = "остатки на июль 2019";
|
||||
|
||||
const output = await buildAssistantAddressOrchestrationRuntime(
|
||||
buildInput({
|
||||
userMessage: rawMessage,
|
||||
runAddressLlmPreDecompose: vi.fn(async () => ({
|
||||
attempted: true,
|
||||
applied: true,
|
||||
effectiveMessage: "проверить остатки по счетам на июль 2019 года",
|
||||
reason: "normalized_fragment_applied",
|
||||
predecomposeContract: {
|
||||
mode: "address_query",
|
||||
intent: "account_balance_snapshot"
|
||||
}
|
||||
})),
|
||||
buildAddressLlmPredecomposeContractV1,
|
||||
resolveAddressFollowupCarryoverContext,
|
||||
resolveAssistantOrchestrationDecision
|
||||
})
|
||||
);
|
||||
|
||||
expect(output.addressInputMessage).toBe(rawMessage);
|
||||
expect(output.addressPreDecompose.applied).toBe(false);
|
||||
expect(output.addressPreDecompose.reason).toBe("followup_raw_message_preferred_over_llm_rewrite");
|
||||
expect(buildAddressLlmPredecomposeContractV1).toHaveBeenCalledWith({
|
||||
sourceMessage: rawMessage,
|
||||
canonicalMessage: rawMessage
|
||||
});
|
||||
expect(resolveAddressFollowupCarryoverContext).toHaveBeenCalledTimes(2);
|
||||
expect(resolveAssistantOrchestrationDecision).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
rawUserMessage: rawMessage,
|
||||
effectiveAddressUserMessage: rawMessage
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers raw selected-object inventory action over generic canonical drift intent", async () => {
|
||||
const resolveAddressFollowupCarryoverContext = vi.fn(() => ({
|
||||
followupContext: {
|
||||
@@ -441,4 +515,56 @@ describe("assistant address orchestration runtime adapter", () => {
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers raw same-date follow-up over canonical rewrite to current date", async () => {
|
||||
const resolveAddressFollowupCarryoverContext = vi.fn(() => ({
|
||||
followupContext: {
|
||||
previous_intent: "vat_payable_confirmed_as_of_date",
|
||||
previous_filters: {
|
||||
as_of_date: "2019-09-30",
|
||||
period_from: "2019-09-01",
|
||||
period_to: "2019-09-30"
|
||||
}
|
||||
}
|
||||
}));
|
||||
const resolveAssistantOrchestrationDecision = vi.fn(() => ({
|
||||
runAddressLane: true,
|
||||
livingMode: "address_data",
|
||||
livingReason: "address_lane_triggered",
|
||||
toolGateDecision: "run_address_lane",
|
||||
toolGateReason: "followup_context_detected",
|
||||
orchestrationContract: { schema_version: "assistant_orchestration_contract_v1" }
|
||||
}));
|
||||
|
||||
const rawMessage = "какие остатки по складу на эту же дату";
|
||||
|
||||
const output = await buildAssistantAddressOrchestrationRuntime(
|
||||
buildInput({
|
||||
userMessage: rawMessage,
|
||||
runAddressLlmPreDecompose: vi.fn(async () => ({
|
||||
attempted: true,
|
||||
applied: true,
|
||||
effectiveMessage: "получить остатки по складу на текущую дату",
|
||||
reason: "normalized_fragment_applied",
|
||||
predecomposeContract: {
|
||||
mode: "address_query",
|
||||
intent: "inventory_on_hand_as_of_date"
|
||||
}
|
||||
})),
|
||||
resolveAddressFollowupCarryoverContext,
|
||||
resolveAssistantOrchestrationDecision
|
||||
})
|
||||
);
|
||||
|
||||
expect(output.addressInputMessage).toBe(rawMessage);
|
||||
expect(output.addressPreDecompose.applied).toBe(false);
|
||||
expect(output.addressPreDecompose.reason).toBe("followup_raw_message_preferred_over_llm_rewrite");
|
||||
expect(resolveAddressFollowupCarryoverContext).toHaveBeenCalledTimes(2);
|
||||
expect(resolveAssistantOrchestrationDecision).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
rawUserMessage: rawMessage,
|
||||
effectiveAddressUserMessage: rawMessage
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user