Закрепить margin follow-up по выбранной позиции
This commit is contained in:
+50
@@ -419,4 +419,54 @@ describe("inventory profitability selected-object regressions", () => {
|
||||
expect(reply).not.toContain("\u0430\u043c\u043e\u0440\u0442\u0438\u0437");
|
||||
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("keeps payment-bank margin false-source boundary factual in the full runtime", async () => {
|
||||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||||
fetched_rows: 2,
|
||||
matched_rows: 2,
|
||||
raw_rows: [
|
||||
{
|
||||
Period: "2020-01-10T00:00:00Z",
|
||||
Registrator: "Purchase document 1",
|
||||
AccountDt: "41.01",
|
||||
AccountKt: "60.01",
|
||||
Amount: 500,
|
||||
Quantity: 10,
|
||||
SubcontoDt1: "Item A",
|
||||
Organization: "OOO Alternative Plus"
|
||||
},
|
||||
{
|
||||
Period: "2020-02-10T00:00:00Z",
|
||||
Registrator: "Sales document 1",
|
||||
AccountDt: "62.01",
|
||||
AccountKt: "41.01",
|
||||
Amount: 1500,
|
||||
Quantity: 10,
|
||||
SubcontoKt1: "Item A",
|
||||
Organization: "OOO Alternative Plus"
|
||||
}
|
||||
],
|
||||
rows: [],
|
||||
error: null
|
||||
});
|
||||
|
||||
const service = new AddressQueryService();
|
||||
const result = await service.tryHandle(
|
||||
"Можно быстро понять маржинальность товаров за 2020 год по оплатам и банку?"
|
||||
);
|
||||
|
||||
expect(result?.handled).toBe(true);
|
||||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||||
expect(result?.reply_type).toBe("factual");
|
||||
expect(result?.debug.detected_intent).toBe("inventory_margin_ranking_for_nomenclature");
|
||||
expect(result?.debug.capability_id).toBe("inventory_inventory_margin_ranking_for_nomenclature");
|
||||
expect(result?.debug.address_coverage_evidence_v1?.coverage_status).toBe("full");
|
||||
const reply = String(result?.reply_text ?? "");
|
||||
expect(reply).toContain("По оплатам и банку");
|
||||
expect(reply).toContain("нельзя честно подтвердить");
|
||||
expect(reply).toContain("выручка реализации");
|
||||
expect(reply).toContain("себестоимостная база");
|
||||
expect(reply).not.toContain("Самая маржинальная позиция");
|
||||
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -612,6 +612,111 @@ describe("inventory selected-object follow-up", () => {
|
||||
expect(String(result?.reply_text ?? "")).toContain("Поступление товаров и услуг 00000000003");
|
||||
});
|
||||
|
||||
it("routes confirmation-document follow-up over selected item and deduplicates supplier labels", async () => {
|
||||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||||
fetched_rows: 1,
|
||||
matched_rows: 1,
|
||||
raw_rows: [
|
||||
{
|
||||
Period: "2020-06-22T00:00:00Z",
|
||||
Registrator: "Поступление товаров и услуг 00000000020 от 22.06.2020 0:00:00",
|
||||
AccountDt: "41.01",
|
||||
AccountKt: "60.01",
|
||||
Amount: 4744,
|
||||
SubcontoDt1: "Рулонные шторы. Цвет мадагаскар серый",
|
||||
SubcontoDt3: "Основной склад",
|
||||
SubcontoKt1: "ИП Тучкова ИП Тучкова",
|
||||
Counterparty: "ИП Тучкова",
|
||||
Organization: "ООО \\Альтернатива Плюс\\"
|
||||
}
|
||||
],
|
||||
rows: [],
|
||||
error: null
|
||||
});
|
||||
|
||||
const service = new AddressQueryService();
|
||||
const result = await service.tryHandle("Каким документом это подтверждено?", {
|
||||
followupContext: {
|
||||
previous_intent: "inventory_purchase_provenance_for_item",
|
||||
target_intent: "inventory_purchase_provenance_for_item",
|
||||
root_intent: "inventory_margin_ranking_for_nomenclature",
|
||||
previous_filters: {
|
||||
item: "Рулонные шторы. Цвет мадагаскар серый",
|
||||
organization: "ООО \\Альтернатива Плюс\\",
|
||||
as_of_date: "2020-12-31",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
previous_anchor_type: "item",
|
||||
previous_anchor_value: "Рулонные шторы. Цвет мадагаскар серый",
|
||||
current_frame_kind: "inventory_drilldown"
|
||||
}
|
||||
});
|
||||
|
||||
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("Рулонные шторы. Цвет мадагаскар серый");
|
||||
expect(result?.debug.extracted_filters?.counterparty).toBeUndefined();
|
||||
const reply = String(result?.reply_text ?? "");
|
||||
expect(reply.split(/\r?\n/u)[0]).toContain("Документ подтверждения");
|
||||
expect(reply).toContain("Поступление товаров и услуг 00000000020");
|
||||
expect(reply).toContain("Поставщик: ИП Тучкова");
|
||||
expect(reply).not.toContain("ИП Тучкова ИП Тучкова");
|
||||
expect(reply).not.toContain("уточните контрагента");
|
||||
});
|
||||
|
||||
it("deduplicates supplier labels in selected-item provenance answer", async () => {
|
||||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||||
fetched_rows: 1,
|
||||
matched_rows: 1,
|
||||
raw_rows: [
|
||||
{
|
||||
Period: "2020-06-22T00:00:00Z",
|
||||
Registrator: "Поступление товаров и услуг 00000000020 от 22.06.2020 0:00:00",
|
||||
AccountDt: "41.01",
|
||||
AccountKt: "60.01",
|
||||
Amount: 4744,
|
||||
SubcontoDt1: "Рулонные шторы. Цвет мадагаскар серый",
|
||||
SubcontoDt3: "Основной склад",
|
||||
SubcontoKt1: "ИП Тучкова ИП Тучкова",
|
||||
Counterparty: "ИП Тучкова",
|
||||
Organization: "ООО \\Альтернатива Плюс\\"
|
||||
}
|
||||
],
|
||||
rows: [],
|
||||
error: null
|
||||
});
|
||||
|
||||
const service = new AddressQueryService();
|
||||
const result = await service.tryHandle("А по этой позиции кто поставщик?", {
|
||||
followupContext: {
|
||||
previous_intent: "inventory_margin_ranking_for_nomenclature",
|
||||
target_intent: "inventory_margin_ranking_for_nomenclature",
|
||||
root_intent: "inventory_margin_ranking_for_nomenclature",
|
||||
previous_filters: {
|
||||
item: "Рулонные шторы. Цвет мадагаскар серый",
|
||||
organization: "ООО \\Альтернатива Плюс\\",
|
||||
as_of_date: "2020-12-31",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
previous_anchor_type: "item",
|
||||
previous_anchor_value: "Рулонные шторы. Цвет мадагаскар серый",
|
||||
current_frame_kind: "inventory_drilldown"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result?.handled).toBe(true);
|
||||
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
|
||||
const reply = String(result?.reply_text ?? "");
|
||||
expect(reply).toContain("подтвержден поставщик: ИП Тучкова");
|
||||
expect(reply).not.toContain("ИП Тучкова ИП Тучкова");
|
||||
expect(reply).not.toContain("однозначный поставщик не подтвержден");
|
||||
expect(reply).not.toContain("несколько поставщиков");
|
||||
});
|
||||
|
||||
it("routes buyer follow-up over the same selected item into sale trace instead of replaying provenance", async () => {
|
||||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||||
fetched_rows: 1,
|
||||
|
||||
@@ -452,6 +452,151 @@ describe("address navigation state", () => {
|
||||
expect(evolved.session_context.active_focus_object?.label).toBe("Диван трехместный");
|
||||
expect(evolved.session_context.active_focus_object?.provenance_result_set_id).toBe("rs-msg-a3");
|
||||
});
|
||||
|
||||
it("captures top margin-ranking item as active focus for selected-object follow-ups", () => {
|
||||
const base = createEmptyAddressNavigationState("asst-margin", "2026-04-12T10:00:00.000Z");
|
||||
const assistantItem = {
|
||||
message_id: "msg-margin",
|
||||
session_id: "asst-margin",
|
||||
role: "assistant",
|
||||
text: [
|
||||
"Самая маржинальная позиция за период 01.01.2020 - 31.12.2020: Рулонные шторы. Цвет мадагаскар серый — маржа 96,00%.",
|
||||
"Высокая валовая маржинальность (топ по проценту маржи):",
|
||||
"1. Рулонные шторы. Цвет мадагаскар серый — выручка 118.552,00 ₽, себестоимостная база 4.744,00 ₽, маржа 96,00%.",
|
||||
"2. Портьерные шторы Garden kolor — выручка 458.472,00 ₽, себестоимостная база 41.480,00 ₽, маржа 90,95%."
|
||||
].join("\n"),
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-12T10:03:00.000Z",
|
||||
trace_id: "address-margin",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_margin_ranking_for_nomenclature",
|
||||
selected_recipe: "address_inventory_margin_ranking_for_nomenclature_v1",
|
||||
extracted_filters: {
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31",
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
},
|
||||
anchor_type: "unknown",
|
||||
anchor_value_resolved: null,
|
||||
anchor_value_raw: null,
|
||||
dialog_continuation_contract_v2: {
|
||||
decision: "new_topic"
|
||||
}
|
||||
}
|
||||
} as any;
|
||||
|
||||
const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 3);
|
||||
expect(evolved.result_sets[0]?.type).toBe("inventory_trace");
|
||||
expect(evolved.result_sets[0]?.entity_refs[0]?.entity_type).toBe("item");
|
||||
expect(evolved.result_sets[0]?.entity_refs[0]?.value).toBe("Рулонные шторы. Цвет мадагаскар серый");
|
||||
expect(evolved.session_context.active_focus_object?.object_type).toBe("item");
|
||||
expect(evolved.session_context.active_focus_object?.label).toBe("Рулонные шторы. Цвет мадагаскар серый");
|
||||
});
|
||||
|
||||
it("keeps margin-ranking item focus when an explanatory follow-up resolves only organization anchor", () => {
|
||||
const base = createEmptyAddressNavigationState("asst-margin-followup", "2026-04-12T10:00:00.000Z");
|
||||
const rootItem = {
|
||||
message_id: "msg-margin-root",
|
||||
session_id: "asst-margin-followup",
|
||||
role: "assistant",
|
||||
text: [
|
||||
"Самая маржинальная позиция за период 01.01.2020 - 31.12.2020: Рулонные шторы. Цвет мадагаскар серый — маржа 96,00%.",
|
||||
"Высокая валовая маржинальность:",
|
||||
"1. Рулонные шторы. Цвет мадагаскар серый — выручка 118.552,00 ₽, себестоимостная база 4.744,00 ₽, маржа 96,00%."
|
||||
].join("\n"),
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-12T10:03:00.000Z",
|
||||
trace_id: "address-margin-root",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_margin_ranking_for_nomenclature",
|
||||
selected_recipe: "address_inventory_margin_ranking_for_nomenclature_v1",
|
||||
extracted_filters: {
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31",
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
},
|
||||
anchor_type: "unknown",
|
||||
dialog_continuation_contract_v2: {
|
||||
decision: "new_topic"
|
||||
}
|
||||
}
|
||||
} as any;
|
||||
const afterRoot = evolveAddressNavigationStateWithAssistantItem(base, rootItem, 1);
|
||||
const explanationItem = {
|
||||
message_id: "msg-margin-explain",
|
||||
session_id: "asst-margin-followup",
|
||||
role: "assistant",
|
||||
text: "Маржу посчитал по реализации и себестоимостной базе организации ООО Альтернатива Плюс.",
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-12T10:04:00.000Z",
|
||||
trace_id: "address-margin-explain",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_margin_ranking_for_nomenclature",
|
||||
selected_recipe: "address_inventory_margin_ranking_for_nomenclature_v1",
|
||||
extracted_filters: {
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31",
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
},
|
||||
anchor_type: "organization",
|
||||
anchor_value_resolved: "ООО Альтернатива Плюс",
|
||||
dialog_continuation_contract_v2: {
|
||||
decision: "continue_previous",
|
||||
previous_intent: "inventory_margin_ranking_for_nomenclature",
|
||||
target_intent: "inventory_margin_ranking_for_nomenclature"
|
||||
}
|
||||
}
|
||||
} as any;
|
||||
|
||||
const evolved = evolveAddressNavigationStateWithAssistantItem(afterRoot, explanationItem, 2);
|
||||
|
||||
expect(evolved.session_context.active_focus_object?.object_type).toBe("item");
|
||||
expect(evolved.session_context.active_focus_object?.label).toBe("Рулонные шторы. Цвет мадагаскар серый");
|
||||
expect(evolved.navigation_history[1]?.target_object_id).toBe(
|
||||
"item:рулонные шторы. цвет мадагаскар серый"
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers top margin-ranking item over organization anchor on new margin answers", () => {
|
||||
const base = createEmptyAddressNavigationState("asst-margin-org-anchor", "2026-04-12T10:00:00.000Z");
|
||||
const assistantItem = {
|
||||
message_id: "msg-margin-org-anchor",
|
||||
session_id: "asst-margin-org-anchor",
|
||||
role: "assistant",
|
||||
text: [
|
||||
"Самая маржинальная позиция за период 01.01.2020 - 31.12.2020: Рулонные шторы. Цвет мадагаскар серый — маржа 96,00%.",
|
||||
"Высокая валовая маржинальность:",
|
||||
"1. Рулонные шторы. Цвет мадагаскар серый — выручка 118.552,00 ₽, себестоимостная база 4.744,00 ₽, маржа 96,00%."
|
||||
].join("\n"),
|
||||
reply_type: "factual",
|
||||
created_at: "2026-04-12T10:03:00.000Z",
|
||||
trace_id: "address-margin-org-anchor",
|
||||
debug: {
|
||||
detected_mode: "address_query",
|
||||
detected_intent: "inventory_margin_ranking_for_nomenclature",
|
||||
selected_recipe: "address_inventory_margin_ranking_for_nomenclature_v1",
|
||||
extracted_filters: {
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31",
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
},
|
||||
anchor_type: "organization",
|
||||
anchor_value_resolved: "ООО Альтернатива Плюс",
|
||||
dialog_continuation_contract_v2: {
|
||||
decision: "new_topic"
|
||||
}
|
||||
}
|
||||
} as any;
|
||||
|
||||
const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 1);
|
||||
|
||||
expect(evolved.session_context.active_focus_object?.object_type).toBe("item");
|
||||
expect(evolved.session_context.active_focus_object?.label).toBe("Рулонные шторы. Цвет мадагаскар серый");
|
||||
});
|
||||
|
||||
it("derives single organization scope from inventory answer text when filters omit organization", () => {
|
||||
const base = createEmptyAddressNavigationState("asst-5", "2026-04-12T10:00:00.000Z");
|
||||
const assistantItem = {
|
||||
|
||||
Reference in New Issue
Block a user