import { describe, expect, it } from "vitest"; import { planAssistantMcpDiscovery } from "../src/services/assistantMcpDiscoveryPlanner"; describe("assistant MCP discovery planner", () => { it("builds a catalog-compatible value-flow discovery plan from current turn meaning", () => { const result = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020" } }); expect(result.planner_status).toBe("ready_for_execution"); expect(result.semantic_data_need).toBe("counterparty value-flow evidence"); expect(result.proposed_primitives).toEqual([ "resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage" ]); expect(result.required_axes).toEqual(["counterparty", "period", "aggregate_axis", "amount", "coverage_target"]); expect(result.catalog_review.review_status).toBe("catalog_compatible"); expect(result.discovery_plan.answer_may_use_raw_model_claims).toBe(false); expect(result.discovery_plan.execution_budget.max_probe_count).toBe(30); expect(result.reason_codes).toContain("planner_enabled_chunked_coverage_probe_budget"); }); it("keeps a value-flow plan in clarification state when period axis is missing", () => { const result = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["SVK"] } }); expect(result.planner_status).toBe("needs_clarification"); expect(result.catalog_review.review_status).toBe("needs_more_axes"); expect(result.catalog_review.missing_axes_by_primitive.query_movements).toContainEqual(["period", "counterparty"]); expect(result.reason_codes).toContain("planner_needs_more_user_or_scope_context"); }); it("keeps requested monthly aggregation as an explicit planning axis for value-flow discovery", () => { const result = 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" } }); expect(result.planner_status).toBe("ready_for_execution"); expect(result.proposed_primitives).toEqual([ "resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage" ]); expect(result.required_axes).toEqual([ "counterparty", "period", "aggregate_axis", "amount", "coverage_target", "calendar_month" ]); expect(result.reason_codes).toContain("planner_selected_monthly_value_flow_recipe"); expect(result.discovery_plan.execution_budget.max_probe_count).toBe(30); }); it("builds a document discovery plan without falling back to movement primitives", () => { const result = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_documents", asked_action_family: "list_documents", explicit_entity_candidates: ["SVK"] } }); expect(result.planner_status).toBe("ready_for_execution"); expect(result.proposed_primitives).toEqual(["resolve_entity_reference", "query_documents", "probe_coverage"]); expect(result.proposed_primitives).not.toContain("query_movements"); expect(result.required_axes).toEqual(["counterparty", "coverage_target"]); }); it("builds a movement discovery plan without aggregating value-flow totals", () => { const result = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "movements", asked_action_family: "list_movements", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "movement_evidence" } }); expect(result.planner_status).toBe("ready_for_execution"); expect(result.semantic_data_need).toBe("movement evidence"); expect(result.proposed_primitives).toEqual(["resolve_entity_reference", "query_movements", "probe_coverage"]); expect(result.proposed_primitives).not.toContain("aggregate_by_axis"); expect(result.required_axes).toEqual(["counterparty", "period", "coverage_target"]); expect(result.reason_codes).toContain("planner_selected_movement_recipe"); }); it("builds an inference-safe lifecycle plan with evidence explanation", () => { const result = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] } }); expect(result.planner_status).toBe("ready_for_execution"); expect(result.proposed_primitives).toEqual([ "resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis" ]); expect(result.required_axes).toEqual(["counterparty", "document_date", "coverage_target", "evidence_basis"]); }); it("uses metadata-only planning when the user asks about available schema surface", () => { const result = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "metadata", asked_action_family: "inspect_catalog" } }); expect(result.planner_status).toBe("ready_for_execution"); expect(result.proposed_primitives).toEqual(["inspect_1c_metadata"]); expect(result.required_axes).toEqual(["metadata_scope"]); expect(result.catalog_review.evidence_floors.inspect_1c_metadata).toBe("source_summary"); }); it("keeps metadata document inspection on inspect_1c_metadata instead of query_documents", () => { const result = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "metadata", asked_action_family: "inspect_documents" } }); expect(result.planner_status).toBe("ready_for_execution"); expect(result.proposed_primitives).toEqual(["inspect_1c_metadata"]); expect(result.proposed_primitives).not.toContain("query_documents"); }); it("does not mark an unclassified turn as executable without turn meaning context", () => { const result = planAssistantMcpDiscovery({}); expect(result.planner_status).toBe("needs_clarification"); expect(result.discovery_plan.plan_status).toBe("needs_clarification"); expect(result.reason_codes).toContain("planner_needs_more_user_or_scope_context"); }); });