ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Этап 4.8: референциальная continuity для follow-up по контрагентам и pivot операции
This commit is contained in:
@@ -3203,6 +3203,29 @@ describe("address decompose stage follow-up carryover", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps entity carryover for customer value follow-up when counterparty is resolved from displayed list", () => {
|
||||
const result = runAddressDecomposeStage("сколько денег за 2020 принес калинин?", {
|
||||
previous_intent: "counterparty_activity_lifecycle",
|
||||
previous_filters: {
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
previous_anchor_type: "counterparty",
|
||||
previous_anchor_value: "ИП Калинин Н.М.",
|
||||
resolved_counterparty_from_display: true
|
||||
});
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.mode.mode).toBe("address_query");
|
||||
expect(result?.intent.intent).toBe("customer_revenue_and_payments");
|
||||
expect(result?.filters.extracted_filters.counterparty).toBe("ИП Калинин Н.М.");
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2020-01-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2020-12-31");
|
||||
expect(
|
||||
result?.baseReasons?.includes("counterparty_replaced_from_followup_context") ||
|
||||
result?.baseReasons?.includes("counterparty_from_followup_context")
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("promotes open-items intent from follow-up wording with inherited contract anchor", () => {
|
||||
const result = runAddressDecomposeStage("а теперь открытые позиции по нему", {
|
||||
previous_intent: "bank_operations_by_contract",
|
||||
|
||||
@@ -605,6 +605,109 @@ describe("assistant address follow-up carryover", () => {
|
||||
expect(normalizerService.normalize).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("resolves counterparty mention from previous displayed list and carries it into value follow-up", async () => {
|
||||
const calls: Array<{ message: string; options?: any }> = [];
|
||||
const firstMessage = "с кем мы работали в 2020 годы- покажи клиентов";
|
||||
const followupMessage = "сколько денег за 2020 принес калинин?";
|
||||
const lifecycleReply = [
|
||||
"Активные заказчики в 2020 году: 3.",
|
||||
"1. Группа | операций: 13 | последняя активность: 2020-12-30T12:00:00Z | лет в базе: 1",
|
||||
"2. ИП Калинин Н.М. | операций: 2 | последняя активность: 2020-03-02T12:00:03Z | лет в базе: 1",
|
||||
"3. Смарт | операций: 1 | последняя активность: 2020-02-07T12:00:03Z | лет в базе: 1"
|
||||
].join("\n");
|
||||
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn(async (message: string, options?: any) => {
|
||||
calls.push({ message, options });
|
||||
if (message === followupMessage) {
|
||||
if (options?.followupContext?.previous_filters?.counterparty !== "ИП Калинин Н.М.") {
|
||||
return null;
|
||||
}
|
||||
return buildAddressLaneResult({
|
||||
reply_text: "ИП Калинин Н.М. | сумма: 216600 | операций: 2",
|
||||
debug: {
|
||||
...buildAddressLaneResult().debug,
|
||||
detected_intent: "customer_revenue_and_payments",
|
||||
selected_recipe: "address_customer_revenue_and_payments_v1",
|
||||
extracted_filters: {
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31",
|
||||
counterparty: "ИП Калинин Н.М."
|
||||
},
|
||||
anchor_type: "counterparty",
|
||||
anchor_value_raw: "калинин",
|
||||
anchor_value_resolved: "ИП Калинин Н.М.",
|
||||
reasons: ["address_action_detected", "customer_revenue_and_payments_signal_detected", "address_followup_context_applied"]
|
||||
}
|
||||
});
|
||||
}
|
||||
return buildAddressLaneResult({
|
||||
reply_text: lifecycleReply,
|
||||
debug: {
|
||||
...buildAddressLaneResult().debug,
|
||||
detected_intent: "counterparty_activity_lifecycle",
|
||||
selected_recipe: "address_counterparty_activity_lifecycle_v1",
|
||||
extracted_filters: {
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
},
|
||||
anchor_type: "unknown",
|
||||
anchor_value_raw: null,
|
||||
anchor_value_resolved: null,
|
||||
reasons: ["address_action_detected", "counterparty_activity_lifecycle_signal_detected"]
|
||||
}
|
||||
});
|
||||
})
|
||||
} 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-kalinin-${Date.now()}`;
|
||||
const first = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: firstMessage,
|
||||
useMock: true
|
||||
} as any);
|
||||
expect(first.ok).toBe(true);
|
||||
expect(first.reply_type).toBe("factual");
|
||||
|
||||
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(second.debug?.detected_intent).toBe("customer_revenue_and_payments");
|
||||
expect(second.debug?.extracted_filters?.counterparty).toBe("ИП Калинин Н.М.");
|
||||
|
||||
const contextualCall = calls.find(
|
||||
(entry) => entry.message === followupMessage && entry.options?.followupContext?.previous_filters?.counterparty === "ИП Калинин Н.М."
|
||||
);
|
||||
expect(contextualCall).toBeTruthy();
|
||||
expect(contextualCall?.options?.followupContext?.previous_anchor_type).toBe("counterparty");
|
||||
expect(contextualCall?.options?.followupContext?.previous_anchor_value).toBe("ИП Калинин Н.М.");
|
||||
expect(contextualCall?.options?.followupContext?.resolved_counterparty_from_display).toBe(true);
|
||||
expect(second.debug?.dialog_continuation_contract_v2?.target_intent).toBe("customer_revenue_and_payments");
|
||||
expect(second.debug?.dialog_continuation_contract_v2?.decision).toBe("continue_previous");
|
||||
expect(normalizerService.normalize).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not carry address follow-up context into capability question", async () => {
|
||||
const calls: Array<{ message: string; options?: any }> = [];
|
||||
const firstMessage = "покажи документы по свк за 2020";
|
||||
|
||||
Reference in New Issue
Block a user