import { describe, expect, it, vi } from "vitest"; import { planAssistantMcpDiscovery } from "../src/services/assistantMcpDiscoveryPlanner"; import { executeAssistantMcpDiscoveryPilot } from "../src/services/assistantMcpDiscoveryPilotExecutor"; function buildDeps(rows: Array>, 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 buildSequentialDeps(results: Array<{ rows: Array>; 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 }; } describe("assistant MCP discovery pilot executor", () => { it("executes only the lifecycle query_documents primitive through injected MCP deps", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] } }); const deps = buildDeps([ { Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST", Контрагент: "SVK" }, { Период: "2023-12-20T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY", Контрагент: "SVK" } ]); const result = await executeAssistantMcpDiscoveryPilot(planner, deps); expect(result.pilot_status).toBe("executed"); expect(result.mcp_execution_performed).toBe(true); expect(result.executed_primitives).toEqual(["query_documents"]); expect(result.skipped_primitives).toEqual(["resolve_entity_reference", "probe_coverage", "explain_evidence_basis"]); expect(result.evidence.evidence_status).toBe("confirmed"); expect(result.evidence.confirmed_facts[0]).toContain("SVK"); expect(result.evidence.inferred_facts[0]).toContain("may be inferred"); expect(result.evidence.unknown_facts).toContain("Legal registration date is not proven by this MCP discovery pilot"); expect(result.source_rows_summary).toBe("2 MCP document rows fetched, 2 matched lifecycle scope"); expect(result.derived_activity_period).toEqual({ first_activity_date: "2020-01-15", latest_activity_date: "2023-12-20", matched_rows: 2, duration_total_months: 47, duration_years: 3, duration_months_remainder: 11, duration_human_ru: "3 года 11 месяцев", inference_basis: "first_and_latest_confirmed_1c_activity_rows" }); expect(result.reason_codes).toContain("pilot_query_documents_mcp_executed"); expect(result.reason_codes).toContain("pilot_derived_activity_period_from_confirmed_rows"); expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(1); const call = deps.executeAddressMcpQuery.mock.calls[0]?.[0]; expect(String(call?.query ?? "")).toContain("Документ.ПоступлениеНаРасчетныйСчет"); expect(call?.limit).toBeGreaterThan(0); }); it("does not execute MCP when dry-run still needs clarification", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["SVK"] } }); const deps = buildDeps([]); const result = await executeAssistantMcpDiscoveryPilot(planner, deps); expect(result.pilot_status).toBe("skipped_needs_clarification"); expect(result.mcp_execution_performed).toBe(false); expect(result.evidence.evidence_status).toBe("insufficient"); expect(deps.executeAddressMcpQuery).not.toHaveBeenCalled(); }); it("executes value-flow query_movements and derives a guarded turnover sum", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020" } }); const deps = buildDeps([ { Period: "2020-01-15T00:00:00", Amount: 1250, Counterparty: "SVK" }, { Period: "2020-02-20T00:00:00", Amount: "2 500,50", Counterparty: "SVK" } ]); const result = await executeAssistantMcpDiscoveryPilot(planner, deps); expect(result.pilot_status).toBe("executed"); expect(result.pilot_scope).toBe("counterparty_value_flow_query_movements_v1"); expect(result.mcp_execution_performed).toBe(true); expect(result.executed_primitives).toEqual(["query_movements"]); expect(result.skipped_primitives).toEqual(["resolve_entity_reference", "aggregate_by_axis", "probe_coverage"]); expect(result.evidence.evidence_status).toBe("confirmed"); expect(result.evidence.confirmed_facts[0]).toContain("value-flow rows"); expect(result.source_rows_summary).toBe("2 MCP value-flow rows fetched, 2 matched value-flow scope"); expect(result.derived_value_flow).toMatchObject({ counterparty: "SVK", period_scope: "2020", rows_matched: 2, rows_with_amount: 2, total_amount: 3750.5, coverage_limited_by_probe_limit: false, first_movement_date: "2020-01-15", latest_movement_date: "2020-02-20", inference_basis: "sum_of_confirmed_1c_value_flow_rows" }); expect(result.reason_codes).toContain("pilot_query_movements_mcp_executed"); expect(result.reason_codes).toContain("pilot_derived_value_flow_from_confirmed_rows"); expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(1); const call = deps.executeAddressMcpQuery.mock.calls[0]?.[0]; expect(String(call?.query ?? "")).toContain("ПоступлениеНаРасчетныйСчет"); expect(call?.limit).toBeGreaterThan(0); }); it("executes supplier payout query_movements and derives a guarded outgoing payment sum", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "payout", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "counterparty_payouts_or_outflow" } }); const deps = buildDeps([ { Period: "2020-03-15T00:00:00", Amount: 4100, Counterparty: "SVK" }, { Period: "2020-04-20T00:00:00", Amount: "900,25", Counterparty: "SVK" } ]); const result = await executeAssistantMcpDiscoveryPilot(planner, deps); expect(result.pilot_status).toBe("executed"); expect(result.pilot_scope).toBe("counterparty_supplier_payout_query_movements_v1"); expect(result.derived_value_flow).toMatchObject({ value_flow_direction: "outgoing_supplier_payout", counterparty: "SVK", period_scope: "2020", rows_matched: 2, rows_with_amount: 2, total_amount: 5000.25, coverage_limited_by_probe_limit: false, first_movement_date: "2020-03-15", latest_movement_date: "2020-04-20" }); expect(result.evidence.confirmed_facts[0]).toContain("supplier-payout rows"); expect(result.evidence.inferred_facts[0]).toContain("supplier-payout total"); expect(result.evidence.unknown_facts).toContain("Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot"); expect(result.reason_codes).toContain("pilot_supplier_payout_recipe_selected"); const call = deps.executeAddressMcpQuery.mock.calls[0]?.[0]; expect(String(call?.query ?? "")).toContain("СписаниеСРасчетногоСчета"); }); it("marks value-flow coverage as limited when the probe row limit is reached", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "payout", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "counterparty_payouts_or_outflow" } }); const rows = Array.from({ length: 100 }, (_, index) => ({ Period: `2020-01-${String((index % 28) + 1).padStart(2, "0")}T00:00:00`, Amount: 10, Counterparty: "SVK" })); const result = await executeAssistantMcpDiscoveryPilot(planner, buildDeps(rows)); expect(result.derived_value_flow?.coverage_limited_by_probe_limit).toBe(true); expect(result.evidence.unknown_facts).toContain( "Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached" ); }); it("recovers yearly value-flow coverage by splitting a limited broad probe into monthly subprobes", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "payout", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "counterparty_payouts_or_outflow" } }); const broadRows = Array.from({ length: 100 }, (_, index) => ({ Period: `2020-01-${String((index % 28) + 1).padStart(2, "0")}T00:00:00`, Amount: 10, Counterparty: "SVK" })); const monthlyResults = Array.from({ length: 12 }, (_, index) => ({ rows: [ { Period: `2020-${String(index + 1).padStart(2, "0")}-05T00:00:00`, Amount: (index + 1) * 100, Counterparty: "SVK" } ] })); const result = await executeAssistantMcpDiscoveryPilot( planner, buildSequentialDeps([{ rows: broadRows }, ...monthlyResults]) ); expect(result.derived_value_flow).toMatchObject({ value_flow_direction: "outgoing_supplier_payout", coverage_limited_by_probe_limit: false, coverage_recovered_by_period_chunking: true, period_chunking_granularity: "month", rows_matched: 12, rows_with_amount: 12, total_amount: 7800, first_movement_date: "2020-01-05", latest_movement_date: "2020-12-05" }); expect(result.evidence.inferred_facts).toContain( "Requested period coverage was recovered through monthly 1C value-flow probes after the broad probe hit the row limit" ); expect(result.evidence.unknown_facts).not.toContain( "Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached" ); expect(result.reason_codes).toContain("pilot_monthly_period_chunking_recovered_coverage"); }); it("executes bidirectional value-flow queries and derives guarded net cash flow", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "net_value_flow", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting" } }); const deps = buildSequentialDeps([ { rows: [ { Period: "2020-01-15T00:00:00", Amount: 10000, Counterparty: "SVK" }, { Period: "2020-02-20T00:00:00", Amount: "2 500,50", Counterparty: "SVK" } ] }, { rows: [{ Period: "2020-03-10T00:00:00", Amount: 4000, Counterparty: "SVK" }] } ]); const result = await executeAssistantMcpDiscoveryPilot(planner, deps); expect(result.pilot_status).toBe("executed"); expect(result.pilot_scope).toBe("counterparty_bidirectional_value_flow_query_movements_v1"); expect(result.mcp_execution_performed).toBe(true); expect(result.executed_primitives).toEqual(["query_movements"]); expect(result.derived_value_flow).toBeNull(); expect(result.derived_bidirectional_value_flow).toMatchObject({ counterparty: "SVK", period_scope: "2020", net_amount: 8500.5, net_amount_human_ru: "8 500,50 руб.", net_direction: "net_incoming", coverage_limited_by_probe_limit: false, incoming_customer_revenue: { rows_matched: 2, rows_with_amount: 2, total_amount: 12500.5, first_movement_date: "2020-01-15", latest_movement_date: "2020-02-20" }, outgoing_supplier_payout: { rows_matched: 1, rows_with_amount: 1, total_amount: 4000, first_movement_date: "2020-03-10", latest_movement_date: "2020-03-10" } }); expect(result.evidence.confirmed_facts[0]).toContain("bidirectional value-flow rows"); expect(result.evidence.inferred_facts[0]).toContain("net value-flow"); expect(result.evidence.unknown_facts).toContain( "Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot" ); expect(result.reason_codes).toContain("pilot_bidirectional_value_flow_recipes_selected"); expect(result.reason_codes).toContain("pilot_derived_bidirectional_value_flow_from_confirmed_rows"); expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(2); }); it("derives monthly bidirectional value-flow breakdown when the turn explicitly asks by month", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "net_value_flow", asked_aggregation_axis: "month", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting" } }); const deps = buildSequentialDeps([ { rows: [ { Period: "2020-01-15T00:00:00", Amount: 10000, Counterparty: "SVK" }, { Period: "2020-02-20T00:00:00", Amount: "2 500,50", Counterparty: "SVK" } ] }, { rows: [ { Period: "2020-01-10T00:00:00", Amount: 4000, Counterparty: "SVK" }, { Period: "2020-02-11T00:00:00", Amount: 1000, Counterparty: "SVK" } ] } ]); const result = await executeAssistantMcpDiscoveryPilot(planner, deps); expect(result.derived_bidirectional_value_flow?.aggregation_axis).toBe("month"); expect(result.derived_bidirectional_value_flow?.monthly_breakdown).toMatchObject([ { month_bucket: "2020-01", incoming_total_amount: 10000, incoming_rows_with_amount: 1, outgoing_total_amount: 4000, outgoing_rows_with_amount: 1, net_amount: 6000, net_direction: "net_incoming" }, { month_bucket: "2020-02", incoming_total_amount: 2500.5, incoming_rows_with_amount: 1, outgoing_total_amount: 1000, outgoing_rows_with_amount: 1, net_amount: 1500.5, net_direction: "net_incoming" } ]); expect(result.derived_bidirectional_value_flow?.monthly_breakdown[0]?.incoming_total_amount_human_ru).toContain("10 000"); expect(result.derived_bidirectional_value_flow?.monthly_breakdown[0]?.net_amount_human_ru).toContain("6 000"); expect(result.derived_bidirectional_value_flow?.monthly_breakdown[1]?.incoming_total_amount_human_ru).toContain("2 500,50"); expect(result.derived_bidirectional_value_flow?.monthly_breakdown[1]?.net_amount_human_ru).toContain("1 500,50"); expect(result.evidence.inferred_facts).toContain( "Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows" ); expect(result.reason_codes).toContain("pilot_derived_bidirectional_monthly_breakdown_from_confirmed_rows"); }); it("recovers bidirectional yearly coverage when one side is rebuilt from monthly subprobes", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "net_value_flow", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting" } }); const outgoingBroadRows = Array.from({ length: 100 }, (_, index) => ({ Period: `2020-01-${String((index % 28) + 1).padStart(2, "0")}T00:00:00`, Amount: 10, Counterparty: "SVK" })); const outgoingMonthlyResults = Array.from({ length: 12 }, (_, index) => ({ rows: [ { Period: `2020-${String(index + 1).padStart(2, "0")}-10T00:00:00`, Amount: (index + 1) * 50, Counterparty: "SVK" } ] })); const deps = buildSequentialDeps([ { rows: [ { Period: "2020-01-15T00:00:00", Amount: 10000, Counterparty: "SVK" }, { Period: "2020-02-20T00:00:00", Amount: 10000, Counterparty: "SVK" } ] }, { rows: outgoingBroadRows }, ...outgoingMonthlyResults ]); const result = await executeAssistantMcpDiscoveryPilot(planner, deps); expect(result.derived_bidirectional_value_flow).toMatchObject({ coverage_limited_by_probe_limit: false, coverage_recovered_by_period_chunking: true, period_chunking_granularity: "month", net_amount: 16100, incoming_customer_revenue: { total_amount: 20000, coverage_limited_by_probe_limit: false }, outgoing_supplier_payout: { total_amount: 3900, coverage_limited_by_probe_limit: false, coverage_recovered_by_period_chunking: true, period_chunking_granularity: "month" } }); expect(result.evidence.inferred_facts).toContain( "Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes after a broad probe hit the row limit" ); expect(result.evidence.unknown_facts).not.toContain( "Complete requested-period coverage for bidirectional value-flow is not proven because at least one MCP discovery probe row limit was reached" ); expect(result.reason_codes).toContain("pilot_bidirectional_outgoing_monthly_period_chunking_recovered_coverage"); expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(14); }); it("keeps non-lifecycle ready plans unsupported until a dedicated pilot exists", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_documents", asked_action_family: "list_documents", explicit_entity_candidates: ["SVK"] } }); const deps = buildDeps([]); const result = await executeAssistantMcpDiscoveryPilot(planner, deps); expect(result.pilot_status).toBe("unsupported"); expect(result.mcp_execution_performed).toBe(false); expect(result.skipped_primitives).toEqual(["resolve_entity_reference", "query_documents", "probe_coverage"]); expect(result.reason_codes).toContain("pilot_scope_unsupported_for_live_execution"); expect(deps.executeAddressMcpQuery).not.toHaveBeenCalled(); }); it("records MCP errors as limitations without converting them into facts", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] } }); const deps = buildDeps([], "MCP fetch failed: timeout"); const result = await executeAssistantMcpDiscoveryPilot(planner, deps); expect(result.pilot_status).toBe("executed"); expect(result.mcp_execution_performed).toBe(true); expect(result.evidence.evidence_status).toBe("insufficient"); expect(result.evidence.confirmed_facts).toEqual([]); expect(result.derived_activity_period).toBeNull(); expect(result.query_limitations).toContain("MCP fetch failed: timeout"); expect(result.reason_codes).toContain("pilot_query_documents_mcp_error"); }); });