743 lines
36 KiB
TypeScript
743 lines
36 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
||
import { runAssistantMcpDiscoveryRuntimeEntryPoint } from "../src/services/assistantMcpDiscoveryRuntimeEntryPoint";
|
||
|
||
function buildDeps(rows: Array<Record<string, unknown>>, error: string | null = null) {
|
||
return {
|
||
executeAddressMcpQuery: vi.fn(async () => ({
|
||
fetched_rows: rows.length,
|
||
matched_rows: error ? 0 : rows.length,
|
||
raw_rows: rows,
|
||
rows: error ? [] : rows,
|
||
error
|
||
}))
|
||
};
|
||
}
|
||
|
||
function buildBidirectionalDeps(
|
||
incomingRows: Array<Record<string, unknown>>,
|
||
outgoingRows: Array<Record<string, unknown>>
|
||
) {
|
||
return {
|
||
executeAddressMcpQuery: vi
|
||
.fn()
|
||
.mockResolvedValueOnce({
|
||
fetched_rows: incomingRows.length,
|
||
matched_rows: incomingRows.length,
|
||
raw_rows: incomingRows,
|
||
rows: incomingRows,
|
||
error: null
|
||
})
|
||
.mockResolvedValueOnce({
|
||
fetched_rows: outgoingRows.length,
|
||
matched_rows: outgoingRows.length,
|
||
raw_rows: outgoingRows,
|
||
rows: outgoingRows,
|
||
error: null
|
||
})
|
||
};
|
||
}
|
||
|
||
function buildSequentialDeps(results: Array<{ rows: Array<Record<string, unknown>>; error?: string | null }>) {
|
||
const executeAddressMcpQuery = vi.fn(async () => {
|
||
const next = results.shift() ?? { rows: [] };
|
||
const rows = next.rows;
|
||
const error = next.error ?? null;
|
||
return {
|
||
fetched_rows: rows.length,
|
||
matched_rows: error ? 0 : rows.length,
|
||
raw_rows: rows,
|
||
rows: error ? [] : rows,
|
||
error
|
||
};
|
||
});
|
||
return { executeAddressMcpQuery };
|
||
}
|
||
|
||
function buildMetadataDeps(rows: Array<Record<string, unknown>>, error: string | null = null) {
|
||
return {
|
||
executeAddressMcpMetadata: vi.fn(async () => ({
|
||
fetched_rows: error ? 0 : rows.length,
|
||
raw_rows: error ? [] : rows,
|
||
rows: error ? [] : rows,
|
||
error
|
||
}))
|
||
};
|
||
}
|
||
|
||
describe("assistant MCP discovery runtime entry point", () => {
|
||
it("runs the bridge for discovery-eligible lifecycle turn context", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "Сколько лет мы работаем с Группа СВК?",
|
||
predecomposeContract: {
|
||
entities: { counterparty: "Группа СВК" },
|
||
period: {}
|
||
},
|
||
deps: buildDeps([{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST" }])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.hot_runtime_wired).toBe(false);
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.semantic_data_need).toBe("counterparty lifecycle evidence");
|
||
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
|
||
expect(result.bridge?.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||
expect(result.reason_codes).toContain("runtime_entry_point_bridge_executed");
|
||
});
|
||
|
||
it("skips supported exact turns before any discovery execution", async () => {
|
||
const deps = buildDeps([]);
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
assistantTurnMeaning: {
|
||
asked_domain_family: "counterparty",
|
||
asked_action_family: "list_documents",
|
||
explicit_intent_candidate: "list_documents_by_counterparty",
|
||
explicit_entity_candidates: [{ value: "SVK" }]
|
||
},
|
||
deps
|
||
});
|
||
|
||
expect(result.entry_status).toBe("skipped_not_applicable");
|
||
expect(result.discovery_attempted).toBe(false);
|
||
expect(result.bridge).toBeNull();
|
||
expect(deps.executeAddressMcpQuery).not.toHaveBeenCalled();
|
||
expect(result.reason_codes).toContain("runtime_entry_point_skipped_supported_exact_turn");
|
||
});
|
||
|
||
it("passes unsupported-but-understood value turns into bridge with normalized entity scope", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
assistantTurnMeaning: {
|
||
asked_domain_family: "counterparty",
|
||
asked_action_family: "counterparty_value_or_turnover",
|
||
unsupported_but_understood_family: "counterparty_value_or_turnover",
|
||
explicit_entity_candidates: [{ value: "SVK" }]
|
||
},
|
||
predecomposeContract: {
|
||
entities: { counterparty: "Группа СВК" },
|
||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||
},
|
||
deps: buildDeps([
|
||
{ Period: "2020-01-15T00:00:00", Amount: 1250, Counterparty: "SVK" },
|
||
{ Period: "2020-02-20T00:00:00", Amount: 2500, Counterparty: "SVK" }
|
||
])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toEqual(["SVK", "Группа СВК"]);
|
||
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
|
||
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
|
||
expect(result.bridge?.pilot.derived_value_flow?.total_amount).toBe(3750);
|
||
expect(result.bridge?.hot_runtime_wired).toBe(false);
|
||
expect(result.reason_codes).toContain("mcp_discovery_unsupported_but_understood_turn");
|
||
});
|
||
|
||
it("runs the business overview bridge from broad evaluation turn meaning through multi-probe evidence", async () => {
|
||
const deps = buildSequentialDeps([
|
||
{
|
||
rows: [
|
||
{ Period: "2020-01-15T00:00:00", Amount: 120000, Counterparty: "Client A" },
|
||
{ Period: "2020-02-15T00:00:00", Amount: 80000, Counterparty: "Client B" }
|
||
]
|
||
},
|
||
{
|
||
rows: [{ Period: "2020-01-20T00:00:00", Amount: 150000, Counterparty: "Supplier A" }]
|
||
},
|
||
{
|
||
rows: [
|
||
{ Period: "2020-01-15T00:00:00", Registrar: "Customer payment 1" },
|
||
{ Period: "2020-12-15T00:00:00", Registrar: "Customer payment 2" }
|
||
]
|
||
}
|
||
]);
|
||
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
assistantTurnMeaning: {
|
||
asked_domain_family: "business_summary",
|
||
asked_action_family: "broad_evaluation",
|
||
explicit_organization_scope: "Alternative Plus",
|
||
unsupported_but_understood_family: "broad_business_evaluation",
|
||
stale_replay_forbidden: true
|
||
},
|
||
deps
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.semantic_data_need).toBe("business overview evidence with bounded analyst interpretation");
|
||
expect(result.turn_input.data_need_graph?.business_fact_family).toBe("business_overview");
|
||
expect(result.turn_input.data_need_graph?.clarification_gaps).toEqual([]);
|
||
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
|
||
expect(result.bridge?.pilot.pilot_scope).toBe("business_overview_route_template_v1");
|
||
expect(result.bridge?.pilot.derived_business_overview).toMatchObject({
|
||
organization_scope: "Alternative Plus",
|
||
net_amount: 50000,
|
||
net_direction: "net_incoming"
|
||
});
|
||
expect(result.bridge?.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||
expect(result.bridge?.answer_draft.confirmed_lines.join("\n")).toContain("Client A");
|
||
expect(result.bridge?.answer_draft.inference_lines.join("\n")).toContain("\u043d\u0435 \u043f\u0440\u0438\u0431\u044b\u043b\u044c");
|
||
expect(result.bridge?.answer_draft.unknown_lines.join("\n")).toContain("VAT");
|
||
expect(result.reason_codes).toContain("pilot_derived_business_overview_from_confirmed_rows");
|
||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(3);
|
||
});
|
||
|
||
it("runs the bridge for raw metadata wording without an exact route owner", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "какие документы и поля есть в 1С по НДС?",
|
||
deps: buildMetadataDeps([
|
||
{
|
||
FullName: "Документ.СчетФактураВыданный",
|
||
MetaType: "Документ",
|
||
attributes: [{ Name: "Дата" }]
|
||
}
|
||
])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.semantic_data_need).toBe("1C metadata evidence");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "metadata",
|
||
asked_action_family: "inspect_fields"
|
||
});
|
||
expect(result.bridge?.pilot.pilot_scope).toBe("metadata_inspection_v1");
|
||
expect(result.bridge?.answer_draft.headline).toContain("схеме 1С");
|
||
expect(result.bridge?.answer_draft.headline).toContain("подходящие объекты");
|
||
});
|
||
|
||
it("runs the bridge for raw entity-resolution wording and executes the full grounding chain", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "найди в 1С контрагента Группа СВК",
|
||
deps: buildDeps([
|
||
{ Counterparty: "Группа СВК", CounterpartyRef: "Ref-1" },
|
||
{ Counterparty: "СВК Логистика", CounterpartyRef: "Ref-2" }
|
||
])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.semantic_data_need).toBe("entity discovery evidence");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "entity_resolution",
|
||
asked_action_family: "search_business_entity",
|
||
explicit_entity_candidates: ["Группа СВК"]
|
||
});
|
||
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
|
||
expect(result.bridge?.pilot.pilot_scope).toBe("entity_resolution_search_v1");
|
||
expect(result.bridge?.pilot.executed_primitives).toEqual([
|
||
"search_business_entity",
|
||
"resolve_entity_reference",
|
||
"probe_coverage"
|
||
]);
|
||
expect(result.bridge?.pilot.derived_entity_resolution).toMatchObject({
|
||
resolution_status: "resolved",
|
||
resolved_entity: "Группа СВК",
|
||
resolved_reference: "Ref-1"
|
||
});
|
||
expect(result.bridge?.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||
});
|
||
|
||
it("runs the bridge again when the user clarifies one ambiguous entity-resolution candidate", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "СВК-А",
|
||
followupContext: {
|
||
previous_discovery_pilot_scope: "entity_resolution_search_v1",
|
||
previous_discovery_entity_resolution_status: "ambiguous",
|
||
previous_discovery_entity_candidates: ["СВК", "СВК-А", "СВК-Б"],
|
||
previous_discovery_entity_ambiguity_candidates: ["СВК-А", "СВК-Б"]
|
||
},
|
||
deps: buildDeps([
|
||
{ Counterparty: "СВК-А", CounterpartyRef: "Ref-1" },
|
||
{ Counterparty: "СВК-Б", CounterpartyRef: "Ref-2" }
|
||
])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.source_signal).toBe("followup_context");
|
||
expect(result.turn_input.semantic_data_need).toBe("entity discovery evidence");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "entity_resolution",
|
||
asked_action_family: "search_business_entity",
|
||
explicit_entity_candidates: ["СВК-А"]
|
||
});
|
||
expect(result.bridge?.pilot.pilot_scope).toBe("entity_resolution_search_v1");
|
||
expect(result.bridge?.pilot.executed_primitives).toEqual([
|
||
"search_business_entity",
|
||
"resolve_entity_reference",
|
||
"probe_coverage"
|
||
]);
|
||
expect(result.bridge?.pilot.derived_entity_resolution).toMatchObject({
|
||
resolution_status: "resolved",
|
||
resolved_entity: "СВК-А",
|
||
resolved_reference: "Ref-1"
|
||
});
|
||
});
|
||
|
||
it("chains an ordinal ambiguity clarification directly into value-flow execution", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "второй вариант, сколько получили за 2020 год",
|
||
followupContext: {
|
||
previous_discovery_pilot_scope: "entity_resolution_search_v1",
|
||
previous_discovery_entity_resolution_status: "ambiguous",
|
||
previous_discovery_entity_candidates: ["СВК", "СВК-А", "СВК-Б"],
|
||
previous_discovery_entity_ambiguity_candidates: ["СВК-А", "СВК-Б"]
|
||
},
|
||
deps: buildDeps([
|
||
{ Period: "2020-01-15T00:00:00", Amount: 1200, Counterparty: "СВК-Б" },
|
||
{ Period: "2020-02-20T00:00:00", Amount: 800, Counterparty: "СВК-Б" }
|
||
])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.source_signal).toBe("followup_context");
|
||
expect(result.turn_input.semantic_data_need).toBe("counterparty value-flow evidence");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "turnover",
|
||
explicit_entity_candidates: ["СВК-Б"],
|
||
explicit_date_scope: "2020"
|
||
});
|
||
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
|
||
expect(result.bridge?.pilot.derived_value_flow).toMatchObject({
|
||
counterparty: "СВК-Б",
|
||
period_scope: "2020",
|
||
total_amount: 2000
|
||
});
|
||
});
|
||
|
||
it("chains an ordinal ambiguity clarification directly into document evidence execution", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "первый вариант, покажи документы за 2020 год",
|
||
followupContext: {
|
||
previous_discovery_pilot_scope: "entity_resolution_search_v1",
|
||
previous_discovery_entity_resolution_status: "ambiguous",
|
||
previous_discovery_entity_candidates: ["СВК", "СВК-А", "СВК-Б"],
|
||
previous_discovery_entity_ambiguity_candidates: ["СВК-А", "СВК-Б"]
|
||
},
|
||
deps: buildDeps([{ Period: "2020-03-12T00:00:00", Counterparty: "СВК-А", Registrar: "Doc-1" }])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.source_signal).toBe("followup_context");
|
||
expect(result.turn_input.semantic_data_need).toBe("document evidence");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "documents",
|
||
asked_action_family: "list_documents",
|
||
explicit_entity_candidates: ["СВК-А"],
|
||
explicit_date_scope: "2020"
|
||
});
|
||
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_document_evidence_query_documents_v1");
|
||
expect(result.bridge?.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||
});
|
||
|
||
it("runs raw incoming-vs-outgoing comparison as an open-scope value-flow chain without inventing a counterparty", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?",
|
||
predecomposeContract: {
|
||
entities: { organization: "ООО Альтернатива Плюс" },
|
||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||
},
|
||
deps: buildBidirectionalDeps(
|
||
[
|
||
{ Period: "2020-01-15T00:00:00", Amount: 2500, Counterparty: "Клиент-А" },
|
||
{ Period: "2020-06-20T00:00:00", Amount: 1000, Counterparty: "Клиент-Б" }
|
||
],
|
||
[{ Period: "2020-02-18T00:00:00", Amount: 900, Counterparty: "Поставщик-А" }]
|
||
)
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.data_need_graph?.comparison_need).toBe("incoming_vs_outgoing");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "net_value_flow",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||
explicit_date_scope: "2020"
|
||
});
|
||
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow_comparison");
|
||
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_bidirectional_value_flow_query_movements_v1");
|
||
expect(result.bridge?.answer_draft.confirmed_lines.join("\n")).toContain("получили");
|
||
expect(result.bridge?.answer_draft.confirmed_lines.join("\n")).toContain("заплатили");
|
||
});
|
||
|
||
it.skip("keeps mirrored predecompose organization and counterparty out of the subject lane for open comparison", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?",
|
||
predecomposeContract: {
|
||
entities: {
|
||
counterparty: "ООО Альтернатива Плюс",
|
||
organization: "ООО Альтернатива Плюс"
|
||
},
|
||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||
},
|
||
deps: buildBidirectionalDeps(
|
||
[{ Period: "2020-01-15T00:00:00", Amount: 2500, Counterparty: "Клиент-А" }],
|
||
[{ Period: "2020-02-18T00:00:00", Amount: 900, Counterparty: "Поставщик-А" }]
|
||
)
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "net_value_flow",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||
explicit_date_scope: "2020"
|
||
});
|
||
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||
expect(result.turn_input.data_need_graph?.subject_candidates).toEqual([]);
|
||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow_comparison");
|
||
});
|
||
|
||
it("keeps the same ranking loop after organization-only clarification when period is still missing", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "по ООО Альтернатива Плюс",
|
||
predecomposeContract: {
|
||
entities: { organization: "ООО Альтернатива Плюс" }
|
||
},
|
||
followupContext: {
|
||
previous_discovery_loop_status: "awaiting_clarification",
|
||
previous_discovery_loop_selected_chain_id: "value_flow_ranking",
|
||
previous_discovery_loop_pending_axes: ["organization", "period"],
|
||
previous_discovery_loop_provided_axes: ["aggregate_axis", "amount", "coverage_target"],
|
||
previous_discovery_loop_asked_domain_family: "counterparty_value",
|
||
previous_discovery_loop_asked_action_family: "turnover",
|
||
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover",
|
||
previous_discovery_ranking_need: "top_desc"
|
||
},
|
||
deps: buildDeps([])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "turnover",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||
seeded_ranking_need: "top_desc"
|
||
});
|
||
expect(result.turn_input.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
|
||
expect(result.bridge?.bridge_status).toBe("needs_clarification");
|
||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow_ranking");
|
||
expect(result.bridge?.loop_state).toMatchObject({
|
||
loop_status: "awaiting_clarification",
|
||
selected_chain_id: "value_flow_ranking",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||
explicit_date_scope: null,
|
||
ranking_need: "top_desc"
|
||
});
|
||
expect(result.bridge?.loop_state.pending_axes).toContain("period");
|
||
expect(result.bridge?.loop_state.pending_axes).not.toContain("organization");
|
||
expect(result.bridge?.answer_draft.next_step_line).toContain("период");
|
||
expect(result.bridge?.answer_draft.next_step_line).not.toContain("организацию");
|
||
});
|
||
|
||
it("completes the same ranking loop after the second clarification provides the period", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "за 2020 год",
|
||
followupContext: {
|
||
previous_discovery_loop_status: "awaiting_clarification",
|
||
previous_discovery_loop_selected_chain_id: "value_flow_ranking",
|
||
previous_discovery_loop_pending_axes: ["period"],
|
||
previous_discovery_loop_provided_axes: ["aggregate_axis", "amount", "coverage_target", "organization"],
|
||
previous_discovery_loop_asked_domain_family: "counterparty_value",
|
||
previous_discovery_loop_asked_action_family: "turnover",
|
||
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover",
|
||
previous_discovery_ranking_need: "top_desc",
|
||
previous_filters: {
|
||
organization: "ООО Альтернатива Плюс"
|
||
}
|
||
},
|
||
deps: buildDeps([
|
||
{ Period: "2020-01-10T00:00:00", Amount: 1200, Counterparty: "СВК-А" },
|
||
{ Period: "2020-03-11T00:00:00", Amount: 800, Counterparty: "СВК-Б" },
|
||
{ Period: "2020-05-12T00:00:00", Amount: 900, Counterparty: "СВК-А" }
|
||
])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "turnover",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||
explicit_date_scope: "2020",
|
||
seeded_ranking_need: "top_desc"
|
||
});
|
||
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
|
||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow_ranking");
|
||
expect(result.bridge?.pilot.derived_ranked_value_flow?.ranked_values[0]).toMatchObject({
|
||
axis_value: "СВК-А",
|
||
total_amount: 2100
|
||
});
|
||
expect(result.bridge?.loop_state.loop_status).toBe("ready_for_next_hop");
|
||
expect(result.bridge?.loop_state.pending_axes).toEqual([]);
|
||
});
|
||
|
||
it("keeps the same open total loop after organization-only clarification when period is still missing", async () => {
|
||
const periodToken = "\u043f\u0435\u0440\u0438\u043e\u0434";
|
||
const organizationToken = "\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446";
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "по ООО Альтернатива Плюс",
|
||
predecomposeContract: {
|
||
entities: { organization: "ООО Альтернатива Плюс" }
|
||
},
|
||
followupContext: {
|
||
previous_discovery_loop_status: "awaiting_clarification",
|
||
previous_discovery_loop_selected_chain_id: "value_flow",
|
||
previous_discovery_loop_pending_axes: ["organization", "period"],
|
||
previous_discovery_loop_provided_axes: ["amount", "coverage_target"],
|
||
previous_discovery_loop_asked_domain_family: "counterparty_value",
|
||
previous_discovery_loop_asked_action_family: "turnover",
|
||
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover"
|
||
},
|
||
deps: buildDeps([])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "turnover",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||
});
|
||
expect(result.turn_input.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
|
||
expect(result.bridge?.bridge_status).toBe("needs_clarification");
|
||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow");
|
||
expect(result.bridge?.loop_state).toMatchObject({
|
||
loop_status: "awaiting_clarification",
|
||
selected_chain_id: "value_flow",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||
explicit_date_scope: null
|
||
});
|
||
expect(result.bridge?.loop_state.pending_axes).toContain("period");
|
||
expect(result.bridge?.loop_state.pending_axes).not.toContain("organization");
|
||
expect(result.bridge?.answer_draft.next_step_line).toContain(periodToken);
|
||
expect(result.bridge?.answer_draft.next_step_line).not.toContain(organizationToken);
|
||
});
|
||
|
||
it("completes the same open total loop after the second clarification provides the period", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "Р·Р° 2020 РіРѕРґ",
|
||
followupContext: {
|
||
previous_discovery_loop_status: "awaiting_clarification",
|
||
previous_discovery_loop_selected_chain_id: "value_flow",
|
||
previous_discovery_loop_pending_axes: ["period"],
|
||
previous_discovery_loop_provided_axes: ["amount", "coverage_target", "organization"],
|
||
previous_discovery_loop_asked_domain_family: "counterparty_value",
|
||
previous_discovery_loop_asked_action_family: "turnover",
|
||
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover",
|
||
previous_filters: {
|
||
organization: "ООО Альтернатива Плюс"
|
||
}
|
||
},
|
||
deps: buildDeps([
|
||
{ Period: "2020-01-10T00:00:00", Amount: 1200, Counterparty: "РЎР’Рљ-Рђ" },
|
||
{ Period: "2020-03-11T00:00:00", Amount: 800, Counterparty: "РЎР’Рљ-Р‘" },
|
||
{ Period: "2020-05-12T00:00:00", Amount: 900, Counterparty: "РЎР’Рљ-Рђ" }
|
||
])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "turnover",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||
explicit_date_scope: "2020"
|
||
});
|
||
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
|
||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow");
|
||
expect(result.bridge?.pilot.derived_value_flow).toMatchObject({
|
||
counterparty: null,
|
||
period_scope: "2020",
|
||
total_amount: 2900
|
||
});
|
||
expect(result.bridge?.loop_state.loop_status).toBe("ready_for_next_hop");
|
||
expect(result.bridge?.loop_state.pending_axes).toEqual([]);
|
||
});
|
||
|
||
it.skip("keeps mirrored predecompose organization and counterparty out of the subject lane for open comparison (utf8-safe)", async () => {
|
||
const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?",
|
||
predecomposeContract: {
|
||
entities: {
|
||
counterparty: orgName,
|
||
organization: orgName
|
||
},
|
||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||
},
|
||
deps: buildBidirectionalDeps(
|
||
[{ Period: "2020-01-15T00:00:00", Amount: 2500, Counterparty: "\u041a\u043b\u0438\u0435\u043d\u0442-\u0410" }],
|
||
[{ Period: "2020-02-18T00:00:00", Amount: 900, Counterparty: "\u041f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a-\u0410" }]
|
||
)
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "net_value_flow",
|
||
explicit_organization_scope: orgName,
|
||
explicit_date_scope: "2020"
|
||
});
|
||
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||
expect(result.turn_input.data_need_graph?.subject_candidates).toEqual([]);
|
||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow_comparison");
|
||
});
|
||
it("runs raw organization-scoped incoming totals as an open value-flow chain without inventing a counterparty", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "сколько входящих денег за 2020 год по ООО Альтернатива Плюс?",
|
||
predecomposeContract: {
|
||
entities: { organization: "ООО Альтернатива Плюс" },
|
||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||
},
|
||
deps: buildDeps([
|
||
{ Period: "2020-01-15T00:00:00", Amount: 2500, Counterparty: "Клиент-А" },
|
||
{ Period: "2020-06-20T00:00:00", Amount: 1000, Counterparty: "Клиент-Б" }
|
||
])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "turnover",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||
explicit_date_scope: "2020"
|
||
});
|
||
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||
expect(result.turn_input.data_need_graph?.subject_candidates).toEqual([]);
|
||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow");
|
||
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
|
||
expect(result.bridge?.answer_draft.confirmed_lines.join("\n")).toContain("входящ");
|
||
});
|
||
|
||
it("runs raw organization-scoped outgoing totals as an open payout chain without inventing a counterparty", async () => {
|
||
const orgName = "ООО Альтернатива Плюс";
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "сколько исходящих денег за 2020 год по ООО Альтернатива Плюс?",
|
||
predecomposeContract: {
|
||
entities: { counterparty: orgName, organization: orgName },
|
||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||
},
|
||
deps: buildDeps([
|
||
{ Period: "2020-02-18T00:00:00", Amount: 900, Counterparty: "Поставщик-А" },
|
||
{ Period: "2020-08-07T00:00:00", Amount: 300, Counterparty: "Поставщик-Б" }
|
||
])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "payout",
|
||
explicit_organization_scope: orgName,
|
||
explicit_date_scope: "2020"
|
||
});
|
||
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow");
|
||
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_supplier_payout_query_movements_v1");
|
||
expect(result.bridge?.answer_draft.confirmed_lines.join("\n")).toContain("исход");
|
||
});
|
||
it("keeps a generic incoming total in organization clarification instead of asking for counterparty", async () => {
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "сколько входящих денег за 2020 год?",
|
||
deps: buildDeps([])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "turnover",
|
||
explicit_date_scope: "2020"
|
||
});
|
||
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||
expect(result.turn_input.data_need_graph?.clarification_gaps).toEqual(["organization"]);
|
||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow");
|
||
expect(result.bridge?.bridge_status).toBe("needs_clarification");
|
||
expect(result.bridge?.answer_draft.next_step_line).toContain("\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044e");
|
||
expect(result.bridge?.answer_draft.next_step_line).not.toContain(
|
||
"\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430"
|
||
);
|
||
});
|
||
|
||
it("resumes a generic incoming total after organization clarification without reintroducing a counterparty", async () => {
|
||
const orgName = "ООО Альтернатива Плюс";
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "по ООО Альтернатива Плюс",
|
||
predecomposeContract: {
|
||
entities: { organization: orgName }
|
||
},
|
||
followupContext: {
|
||
previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1",
|
||
previous_filters: {
|
||
period_from: "2020-01-01",
|
||
period_to: "2020-12-31"
|
||
}
|
||
},
|
||
deps: buildDeps([
|
||
{ Period: "2020-01-15T00:00:00", Amount: 2500, Counterparty: "Клиент-А" },
|
||
{ Period: "2020-06-20T00:00:00", Amount: 1000, Counterparty: "Клиент-Б" }
|
||
])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.source_signal).toBe("followup_context");
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "turnover",
|
||
explicit_organization_scope: orgName,
|
||
explicit_date_scope: "2020"
|
||
});
|
||
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow");
|
||
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
|
||
expect(result.bridge?.pilot.derived_value_flow).toMatchObject({
|
||
counterparty: null,
|
||
period_scope: "2020",
|
||
total_amount: 3500
|
||
});
|
||
});
|
||
it("overrides a supported exact intent when organization-only follow-up resolves an open-scope total", async () => {
|
||
const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||
userMessage: "\u043f\u043e \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
|
||
assistantTurnMeaning: {
|
||
asked_domain_family: "counterparty",
|
||
asked_action_family: "turnover",
|
||
explicit_intent_candidate: "customer_revenue_and_payments"
|
||
},
|
||
followupContext: {
|
||
previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1",
|
||
previous_filters: {
|
||
period_from: "2020-01-01",
|
||
period_to: "2020-12-31"
|
||
}
|
||
},
|
||
deps: buildDeps([
|
||
{ Period: "2020-01-15T00:00:00", Amount: 2500, Counterparty: "\u041a\u043b\u0438\u0435\u043d\u0442-\u0410" },
|
||
{ Period: "2020-06-20T00:00:00", Amount: 1000, Counterparty: "\u041a\u043b\u0438\u0435\u043d\u0442-\u0411" }
|
||
])
|
||
});
|
||
|
||
expect(result.entry_status).toBe("bridge_executed");
|
||
expect(result.discovery_attempted).toBe(true);
|
||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "turnover",
|
||
explicit_organization_scope: orgName,
|
||
explicit_date_scope: "2020"
|
||
});
|
||
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow");
|
||
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
|
||
expect(result.bridge?.pilot.derived_value_flow).toMatchObject({
|
||
counterparty: null,
|
||
period_scope: "2020",
|
||
total_amount: 3500
|
||
});
|
||
});
|
||
});
|