761 lines
34 KiB
TypeScript
761 lines
34 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import {
|
||
createEmptyAddressNavigationState,
|
||
evolveAddressNavigationStateWithAssistantItem,
|
||
normalizeAddressNavigationState
|
||
} from "../src/services/addressNavigationState";
|
||
|
||
describe("address navigation state", () => {
|
||
it("creates default empty state", () => {
|
||
const state = createEmptyAddressNavigationState("asst-1", "2026-04-12T10:00:00.000Z");
|
||
expect(state.schema_version).toBe("address_navigation_state_v1");
|
||
expect(state.session_id).toBe("asst-1");
|
||
expect(state.result_sets).toEqual([]);
|
||
expect(state.navigation_history).toEqual([]);
|
||
expect(state.session_context.active_result_set_id).toBeNull();
|
||
});
|
||
|
||
it("captures result_set and focus from address assistant turn", () => {
|
||
const base = createEmptyAddressNavigationState("asst-2", "2026-04-12T10:00:00.000Z");
|
||
const assistantItem = {
|
||
message_id: "msg-a1",
|
||
session_id: "asst-2",
|
||
role: "assistant",
|
||
text: [
|
||
"Топ-6 заказчиков по сумме поступлений:",
|
||
"1. Группа | сумма: 12093465 | операций: 13",
|
||
"4. Гамма-мебель, ООО | сумма: 471000 | операций: 2"
|
||
].join("\n"),
|
||
reply_type: "factual",
|
||
created_at: "2026-04-12T10:00:10.000Z",
|
||
trace_id: "address-123",
|
||
debug: {
|
||
detected_mode: "address_query",
|
||
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"
|
||
},
|
||
anchor_type: "counterparty",
|
||
anchor_value_resolved: "Гамма-мебель, ООО",
|
||
dialog_continuation_contract_v2: {
|
||
decision: "new_topic"
|
||
}
|
||
}
|
||
} as any;
|
||
|
||
const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 2);
|
||
expect(evolved.result_sets.length).toBe(1);
|
||
expect(evolved.result_sets[0]?.result_set_id).toBe("rs-msg-a1");
|
||
expect(evolved.result_sets[0]?.intent).toBe("customer_revenue_and_payments");
|
||
expect(evolved.result_sets[0]?.entity_refs.length).toBeGreaterThan(0);
|
||
expect(evolved.session_context.active_result_set_id).toBe("rs-msg-a1");
|
||
expect(evolved.session_context.active_focus_object?.label).toBe("Гамма-мебель, ООО");
|
||
expect(evolved.navigation_history[0]?.action).toBe("open");
|
||
});
|
||
|
||
it("captures organization focus from applied business overview discovery turns", () => {
|
||
const base = createEmptyAddressNavigationState("asst-bo", "2026-04-12T10:00:00.000Z");
|
||
const assistantItem = {
|
||
message_id: "msg-bo1",
|
||
session_id: "asst-bo",
|
||
role: "assistant",
|
||
text: "Коротко: по данным ООО Альтернатива Плюс за 2020 подтвержден бизнес-обзор.",
|
||
reply_type: "partial_coverage",
|
||
created_at: "2026-04-12T10:10:00.000Z",
|
||
trace_id: "address-bo",
|
||
debug: {
|
||
detected_mode: "address_query",
|
||
detected_intent: "unknown",
|
||
selected_recipe: null,
|
||
extracted_filters: {},
|
||
anchor_type: "unknown",
|
||
mcp_discovery_response_applied: true,
|
||
mcp_discovery_selected_chain_id: "business_overview",
|
||
assistant_mcp_discovery_entry_point_v1: {
|
||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||
entry_status: "bridge_executed",
|
||
turn_input: {
|
||
turn_meaning_ref: {
|
||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||
explicit_date_scope: "2020"
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference"
|
||
}
|
||
}
|
||
},
|
||
dialog_continuation_contract_v2: {
|
||
decision: "new_topic"
|
||
}
|
||
}
|
||
} as any;
|
||
|
||
const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 2);
|
||
expect(evolved.result_sets[0]?.route_id).toBe("business_overview");
|
||
expect(evolved.result_sets[0]?.filters.organization).toBe("ООО Альтернатива Плюс");
|
||
expect(evolved.session_context.active_focus_object?.object_type).toBe("organization");
|
||
expect(evolved.session_context.active_focus_object?.label).toBe("ООО Альтернатива Плюс");
|
||
expect(evolved.session_context.organization_scope).toBe("ООО Альтернатива Плюс");
|
||
expect(evolved.session_context.date_scope.period_from).toBe("2020-01-01");
|
||
expect(evolved.session_context.date_scope.period_to).toBe("2020-12-31");
|
||
});
|
||
|
||
it("does not store broad money role wording as a counterparty focus object", () => {
|
||
const base = createEmptyAddressNavigationState("asst-bo-money", "2026-04-12T10:00:00.000Z");
|
||
const organization = 'ООО "Альтернатива Плюс"';
|
||
const assistantItem = {
|
||
message_id: "msg-bo-money",
|
||
session_id: "asst-bo-money",
|
||
role: "assistant",
|
||
text:
|
||
"По компании ООО \"Альтернатива Плюс\" за 2020 получили 47 628 853,03 руб.; крупнейший небанковский входящий контрагент: Группа СВК.",
|
||
reply_type: "partial_coverage",
|
||
created_at: "2026-04-12T10:11:00.000Z",
|
||
trace_id: "address-bo-money",
|
||
debug: {
|
||
detected_mode: "address_query",
|
||
detected_intent: "unknown",
|
||
selected_recipe: null,
|
||
extracted_filters: {
|
||
counterparty: "и главный поставщик в",
|
||
organization,
|
||
period_from: "2020-01-01",
|
||
period_to: "2020-12-31"
|
||
},
|
||
anchor_type: "unknown",
|
||
mcp_discovery_response_applied: true,
|
||
mcp_discovery_selected_chain_id: "business_overview",
|
||
assistant_mcp_discovery_entry_point_v1: {
|
||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||
entry_status: "bridge_executed",
|
||
turn_input: {
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "business_overview",
|
||
asked_action_family: "broad_evaluation",
|
||
explicit_organization_scope: organization,
|
||
explicit_date_scope: "2020"
|
||
},
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
subject_candidates: []
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference"
|
||
}
|
||
}
|
||
},
|
||
dialog_continuation_contract_v2: {
|
||
decision: "continue_previous"
|
||
}
|
||
}
|
||
} as any;
|
||
|
||
const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 2);
|
||
|
||
expect(evolved.result_sets[0]?.filters.counterparty).toBeUndefined();
|
||
expect(evolved.result_sets[0]?.filters.organization).toBe(organization);
|
||
expect(evolved.session_context.active_focus_object?.object_type).toBe("organization");
|
||
expect(evolved.session_context.active_focus_object?.label).toBe(organization);
|
||
expect(evolved.navigation_history[0]?.target_object_id).toBe(`organization:${organization}`.toLowerCase());
|
||
});
|
||
|
||
it("captures counterparty focus from applied discovery turns even when the response stayed in chat mode", () => {
|
||
const base = createEmptyAddressNavigationState("asst-vf", "2026-04-12T10:00:00.000Z");
|
||
const assistantItem = {
|
||
message_id: "msg-vf1",
|
||
session_id: "asst-vf",
|
||
role: "assistant",
|
||
text: "\u041f\u043e \u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a \u0437\u0430 2020 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u044b \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0438 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0434\u0435\u043d\u044c\u0433\u0438.",
|
||
reply_type: "partial_coverage",
|
||
created_at: "2026-04-12T10:12:00.000Z",
|
||
trace_id: "chat-vf",
|
||
debug: {
|
||
detected_mode: "chat",
|
||
detected_intent: "unknown",
|
||
selected_recipe: null,
|
||
extracted_filters: {},
|
||
anchor_type: "unknown",
|
||
mcp_discovery_response_applied: true,
|
||
mcp_discovery_selected_chain_id: "value_flow_comparison",
|
||
assistant_mcp_discovery_entry_point_v1: {
|
||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||
entry_status: "bridge_executed",
|
||
turn_input: {
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "compare_incoming_outgoing",
|
||
explicit_entity_candidates: ["\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a"],
|
||
stale_replay_forbidden: true,
|
||
explicit_date_scope: "2020"
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
answer_draft: {
|
||
answer_mode: "confirmed_factual"
|
||
}
|
||
}
|
||
},
|
||
dialog_continuation_contract_v2: {
|
||
decision: "new_topic"
|
||
}
|
||
}
|
||
} as any;
|
||
|
||
const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 2);
|
||
expect(evolved.result_sets[0]?.route_id).toBe("value_flow_comparison");
|
||
expect(evolved.result_sets[0]?.intent).toBe("customer_revenue_and_payments");
|
||
expect(evolved.result_sets[0]?.filters.counterparty).toBe("\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a");
|
||
expect(evolved.session_context.active_focus_object?.object_type).toBe("counterparty");
|
||
expect(evolved.session_context.active_focus_object?.label).toBe("\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a");
|
||
expect(evolved.session_context.date_scope.period_from).toBe("2020-01-01");
|
||
expect(evolved.session_context.date_scope.period_to).toBe("2020-12-31");
|
||
});
|
||
|
||
it("keeps selected counterparty focus for company boundary summaries", () => {
|
||
const initial = createEmptyAddressNavigationState("asst-boundary", "2026-04-12T10:00:00.000Z");
|
||
const assistantItem = {
|
||
message_id: "msg-boundary",
|
||
session_id: "asst-boundary",
|
||
role: "assistant",
|
||
text:
|
||
"Коротко: по компании Альтернатива Плюс подтвержден company-level денежный срез.\n" +
|
||
"Отдельно по выбранному контрагенту Группа СВК: суммы компании на него не переношу.",
|
||
created_at: "2026-04-12T10:05:00.000Z",
|
||
debug: {
|
||
detected_intent: "unknown",
|
||
mcp_discovery_selected_chain_id: "business_overview",
|
||
mcp_discovery_response_applied: true,
|
||
extracted_filters: {
|
||
organization: "Альтернатива Плюс"
|
||
},
|
||
assistant_mcp_discovery_entry_point_v1: {
|
||
turn_input: {
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "business_overview",
|
||
asked_action_family: "broad_evaluation",
|
||
business_overview_separate_entity_candidates: ["Группа СВК"],
|
||
explicit_organization_scope: "Альтернатива Плюс"
|
||
},
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
subject_candidates: []
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} as any;
|
||
|
||
const evolved = evolveAddressNavigationStateWithAssistantItem(initial, assistantItem, 1);
|
||
|
||
expect(evolved.session_context.organization_scope).toBe("Альтернатива Плюс");
|
||
expect(evolved.session_context.active_focus_object?.object_type).toBe("counterparty");
|
||
expect(evolved.session_context.active_focus_object?.label).toBe("Группа СВК");
|
||
expect(evolved.session_context.comparison_scope?.organization?.label).toBe("Альтернатива Плюс");
|
||
expect(evolved.session_context.comparison_scope?.counterparty?.label).toBe("Группа СВК");
|
||
expect(evolved.navigation_history[0]?.target_object_id).toBe("counterparty:группа свк");
|
||
});
|
||
|
||
it("carries comparison proof bundles through organization clarification", () => {
|
||
const org = "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||
const counterparty = "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a";
|
||
const valueFlowBundle = {
|
||
counterparty,
|
||
incoming_customer_revenue: { total_amount_human_ru: "20 653 490 руб." },
|
||
outgoing_supplier_payout: { total_amount_human_ru: "2 129 651 руб." },
|
||
net_amount_human_ru: "18 523 839 руб."
|
||
};
|
||
const documentBundle = { counterparty, document_count: 19 };
|
||
const initial = createEmptyAddressNavigationState("asst-proof", "2026-04-12T10:00:00.000Z");
|
||
const boundaryItem = {
|
||
message_id: "msg-boundary-proof",
|
||
session_id: "asst-proof",
|
||
role: "assistant",
|
||
text: `Коротко: уточните организацию для сравнения ${counterparty}.`,
|
||
created_at: "2026-04-12T10:05:00.000Z",
|
||
debug: {
|
||
detected_intent: "unknown",
|
||
mcp_discovery_selected_chain_id: "business_overview",
|
||
mcp_discovery_response_applied: true,
|
||
extracted_filters: {},
|
||
assistant_mcp_discovery_entry_point_v1: {
|
||
turn_input: {
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "business_overview",
|
||
asked_action_family: "broad_evaluation",
|
||
business_overview_separate_entity_candidates: [counterparty],
|
||
previous_counterparty_value_flow_bundle: valueFlowBundle,
|
||
previous_counterparty_document_bundle: documentBundle
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} as any;
|
||
|
||
const afterBoundary = evolveAddressNavigationStateWithAssistantItem(initial, boundaryItem, 1);
|
||
expect(afterBoundary.session_context.comparison_scope?.organization).toBeNull();
|
||
expect(afterBoundary.session_context.comparison_scope?.counterparty?.label).toBe(counterparty);
|
||
expect(afterBoundary.session_context.comparison_scope?.proof_bundles?.counterparty_value_flow_bundle).toEqual(
|
||
valueFlowBundle
|
||
);
|
||
|
||
const clarifiedItem = {
|
||
message_id: "msg-boundary-org",
|
||
session_id: "asst-proof",
|
||
role: "assistant",
|
||
text: `Коротко: по компании ${org} подтвержден company-level срез. Отдельно по ${counterparty}: границу не теряю.`,
|
||
created_at: "2026-04-12T10:06:00.000Z",
|
||
debug: {
|
||
detected_intent: "business_overview",
|
||
mcp_discovery_selected_chain_id: "business_overview",
|
||
mcp_discovery_response_applied: true,
|
||
extracted_filters: { organization: org },
|
||
assistant_mcp_discovery_entry_point_v1: {
|
||
turn_input: {
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "business_overview",
|
||
asked_action_family: "broad_evaluation",
|
||
business_overview_separate_entity_candidates: [counterparty],
|
||
explicit_organization_scope: org
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} as any;
|
||
|
||
const afterClarification = evolveAddressNavigationStateWithAssistantItem(afterBoundary, clarifiedItem, 2);
|
||
expect(afterClarification.session_context.comparison_scope?.organization?.label).toBe(org);
|
||
expect(afterClarification.session_context.comparison_scope?.counterparty?.label).toBe(counterparty);
|
||
expect(afterClarification.session_context.comparison_scope?.proof_bundles?.counterparty_value_flow_bundle).toEqual(
|
||
valueFlowBundle
|
||
);
|
||
expect(afterClarification.session_context.comparison_scope?.proof_bundles?.counterparty_document_bundle).toEqual(
|
||
documentBundle
|
||
);
|
||
});
|
||
|
||
it("tracks drilldown event for follow-up continuation turn", () => {
|
||
const initial = normalizeAddressNavigationState(
|
||
{
|
||
schema_version: "address_navigation_state_v1",
|
||
session_id: "asst-3",
|
||
updated_at: "2026-04-12T10:00:00.000Z",
|
||
session_context: {
|
||
active_result_set_id: "rs-prev",
|
||
active_focus_object: null,
|
||
last_confirmed_route: "address_customer_revenue_and_payments_v1",
|
||
date_scope: {
|
||
as_of_date: null,
|
||
period_from: "2020-01-01",
|
||
period_to: "2020-12-31"
|
||
},
|
||
organization_scope: null
|
||
},
|
||
result_sets: [],
|
||
navigation_history: []
|
||
} as any,
|
||
"asst-3"
|
||
);
|
||
const assistantItem = {
|
||
message_id: "msg-a2",
|
||
session_id: "asst-3",
|
||
role: "assistant",
|
||
text: "Собран список договоров по контрагенту Гамма-мебель, ООО.",
|
||
reply_type: "factual",
|
||
created_at: "2026-04-12T10:02:00.000Z",
|
||
trace_id: "address-456",
|
||
debug: {
|
||
detected_mode: "address_query",
|
||
detected_intent: "list_contracts_by_counterparty",
|
||
selected_recipe: "address_contracts_by_counterparty_v1",
|
||
extracted_filters: {
|
||
period_from: "2020-01-01",
|
||
period_to: "2020-12-31",
|
||
counterparty: "Гамма-мебель, ООО"
|
||
},
|
||
anchor_type: "counterparty",
|
||
anchor_value_resolved: "Гамма-мебель, ООО",
|
||
dialog_continuation_contract_v2: {
|
||
decision: "continue_previous"
|
||
}
|
||
}
|
||
} as any;
|
||
|
||
const evolved = evolveAddressNavigationStateWithAssistantItem(initial, assistantItem, 4);
|
||
expect(evolved.navigation_history.length).toBe(1);
|
||
expect(evolved.navigation_history[0]?.action).toBe("drilldown");
|
||
expect(evolved.navigation_history[0]?.source_result_set_id).toBe("rs-prev");
|
||
expect(evolved.session_context.active_result_set_id).toBe("rs-msg-a2");
|
||
expect(evolved.session_context.active_focus_object?.object_type).toBe("counterparty");
|
||
expect(evolved.session_context.date_scope.period_from).toBe("2020-01-01");
|
||
expect(evolved.session_context.date_scope.period_to).toBe("2020-12-31");
|
||
});
|
||
|
||
it("prefers grounded discovery counterparty over stale referential anchor when updating focus", () => {
|
||
const initial = normalizeAddressNavigationState(
|
||
{
|
||
schema_version: "address_navigation_state_v1",
|
||
session_id: "asst-3b",
|
||
updated_at: "2026-04-12T10:00:00.000Z",
|
||
session_context: {
|
||
active_result_set_id: "rs-prev",
|
||
active_focus_object: {
|
||
object_type: "counterparty",
|
||
object_id: "counterparty:нортон",
|
||
label: "НОРТОН",
|
||
provenance_result_set_id: "rs-prev",
|
||
selected_at: "2026-04-12T09:59:00.000Z"
|
||
},
|
||
last_confirmed_route: "address_customer_revenue_and_payments_v1",
|
||
date_scope: {
|
||
as_of_date: null,
|
||
period_from: "2020-01-01",
|
||
period_to: "2020-12-31"
|
||
},
|
||
organization_scope: "ООО Альтернатива Плюс"
|
||
},
|
||
result_sets: [],
|
||
navigation_history: []
|
||
} as any,
|
||
"asst-3b"
|
||
);
|
||
const assistantItem = {
|
||
message_id: "msg-a2b",
|
||
session_id: "asst-3b",
|
||
role: "assistant",
|
||
text: "По Группа СВК подтверждены исходящие платежи за 2020 год.",
|
||
reply_type: "factual",
|
||
created_at: "2026-04-12T10:02:30.000Z",
|
||
trace_id: "address-456b",
|
||
debug: {
|
||
detected_mode: "address_query",
|
||
detected_intent: "supplier_payouts_profile",
|
||
selected_recipe: "address_supplier_payouts_profile_v1",
|
||
extracted_filters: {
|
||
counterparty: "НОРТОН",
|
||
period_from: "2020-01-01",
|
||
period_to: "2020-12-31"
|
||
},
|
||
anchor_type: "counterparty",
|
||
anchor_value_raw: "он",
|
||
anchor_value_resolved: "он",
|
||
mcp_discovery_response_applied: true,
|
||
assistant_mcp_discovery_entry_point_v1: {
|
||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||
entry_status: "bridge_executed",
|
||
turn_input: {
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "payout",
|
||
explicit_entity_candidates: ["Группа СВК"]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
business_fact_answer_allowed: true,
|
||
pilot: {
|
||
pilot_scope: "counterparty_supplier_payout_query_movements_v1"
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_factual"
|
||
}
|
||
}
|
||
},
|
||
dialog_continuation_contract_v2: {
|
||
decision: "continue_previous"
|
||
}
|
||
}
|
||
} as any;
|
||
|
||
const evolved = evolveAddressNavigationStateWithAssistantItem(initial, assistantItem, 4);
|
||
expect(evolved.session_context.active_focus_object?.object_type).toBe("counterparty");
|
||
expect(evolved.session_context.active_focus_object?.label).toBe("Группа СВК");
|
||
expect(evolved.session_context.active_focus_object?.object_id).toBe("counterparty:группа свк");
|
||
expect(evolved.navigation_history[0]?.target_object_id).toBe("counterparty:группа свк");
|
||
});
|
||
|
||
it("captures item focus from inventory answers when no anchor is materialized", () => {
|
||
const base = createEmptyAddressNavigationState("asst-4", "2026-04-12T10:00:00.000Z");
|
||
const assistantItem = {
|
||
message_id: "msg-a3",
|
||
session_id: "asst-4",
|
||
role: "assistant",
|
||
text: "Собран подтвержденный закупочный след по товару Диван трехместный до 14.04.2026.\n\n1. Авансовый отчет 00000000004 от 24.08.2018 12:00:04 | дата: 24.08.2018 | сумма: 34.490,00 ₽ | склад: Основной склад",
|
||
reply_type: "factual",
|
||
created_at: "2026-04-12T10:03:00.000Z",
|
||
trace_id: "address-789",
|
||
debug: {
|
||
detected_mode: "address_query",
|
||
detected_intent: "inventory_purchase_provenance_for_item",
|
||
selected_recipe: "address_inventory_purchase_provenance_for_item_v1",
|
||
extracted_filters: {
|
||
item: "Диван трехместный",
|
||
warehouse: "Основной склад",
|
||
as_of_date: "2026-04-14"
|
||
},
|
||
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.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 = {
|
||
message_id: "msg-a4",
|
||
session_id: "asst-5",
|
||
role: "assistant",
|
||
text: [
|
||
"На 31.05.2020 на складе подтверждено 1 позиция.",
|
||
"1. Пуф арий | склад: Основной склад | количество: 1,000 | стоимость: 6.490,00 ₽ | организация: ООО Альтернатива Плюс | дата строки: 2020-05-31T23:59:59Z"
|
||
].join("\n"),
|
||
reply_type: "factual",
|
||
created_at: "2026-04-12T10:04:00.000Z",
|
||
trace_id: "address-790",
|
||
debug: {
|
||
detected_mode: "address_query",
|
||
detected_intent: "inventory_on_hand_as_of_date",
|
||
selected_recipe: "address_inventory_on_hand_as_of_date_v1",
|
||
extracted_filters: {
|
||
as_of_date: "2020-05-31"
|
||
},
|
||
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, 4);
|
||
expect(evolved.result_sets[0]?.type).toBe("inventory_snapshot");
|
||
expect(evolved.result_sets[0]?.filters.organization).toBe("ООО Альтернатива Плюс");
|
||
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.organization_scope).toBe("ООО Альтернатива Плюс");
|
||
expect(evolved.session_context.active_focus_object).toBeNull();
|
||
});
|
||
it("resets stale focus and date scope on explicit new topic turn", () => {
|
||
const initial = normalizeAddressNavigationState(
|
||
{
|
||
schema_version: "address_navigation_state_v1",
|
||
session_id: "asst-6",
|
||
updated_at: "2026-04-12T10:00:00.000Z",
|
||
session_context: {
|
||
active_result_set_id: "rs-prev",
|
||
active_focus_object: {
|
||
object_type: "counterparty",
|
||
object_id: "counterparty:chapurnov",
|
||
label: "Чепурнов",
|
||
provenance_result_set_id: "rs-prev",
|
||
selected_at: "2026-04-12T09:59:00.000Z"
|
||
},
|
||
last_confirmed_route: "address_documents_by_counterparty_v1",
|
||
date_scope: {
|
||
as_of_date: null,
|
||
period_from: "2017-01-01",
|
||
period_to: "2017-12-31"
|
||
},
|
||
organization_scope: "ООО Альтернатива Плюс"
|
||
},
|
||
result_sets: [],
|
||
navigation_history: []
|
||
} as any,
|
||
"asst-6"
|
||
);
|
||
const assistantItem = {
|
||
message_id: "msg-a5",
|
||
session_id: "asst-6",
|
||
role: "assistant",
|
||
text: "На 16.04.2026 на складе подтверждено 11 позиций.",
|
||
reply_type: "factual",
|
||
created_at: "2026-04-12T10:05:00.000Z",
|
||
trace_id: "address-791",
|
||
debug: {
|
||
detected_mode: "address_query",
|
||
detected_intent: "inventory_on_hand_as_of_date",
|
||
selected_recipe: "address_inventory_on_hand_as_of_date_v1",
|
||
extracted_filters: {
|
||
as_of_date: "2026-04-16",
|
||
organization: "ООО Альтернатива Плюс"
|
||
},
|
||
anchor_type: "organization",
|
||
anchor_value_resolved: "ООО Альтернатива Плюс",
|
||
dialog_continuation_contract_v2: {
|
||
decision: "new_topic"
|
||
}
|
||
}
|
||
} as any;
|
||
|
||
const evolved = evolveAddressNavigationStateWithAssistantItem(initial, assistantItem, 5);
|
||
expect(evolved.session_context.active_result_set_id).toBe("rs-msg-a5");
|
||
expect(evolved.session_context.active_focus_object?.label).toBe("ООО Альтернатива Плюс");
|
||
expect(evolved.session_context.date_scope.as_of_date).toBe("2026-04-16");
|
||
expect(evolved.session_context.date_scope.period_from).toBeNull();
|
||
expect(evolved.session_context.date_scope.period_to).toBeNull();
|
||
expect(evolved.session_context.last_confirmed_route).toBe("address_inventory_on_hand_as_of_date_v1");
|
||
expect(evolved.navigation_history[0]?.action).toBe("open");
|
||
});
|
||
});
|