import { describe, expect, it } from "vitest"; import { buildAssistantMcpDiscoveryDataNeedGraph } from "../src/services/assistantMcpDiscoveryDataNeedGraph"; describe("assistant MCP discovery data need graph", () => { it("builds a monthly bidirectional value-flow graph from grounded turn meaning", () => { const result = buildAssistantMcpDiscoveryDataNeedGraph({ semanticDataNeed: "counterparty value-flow evidence", rawUtterance: "какое нетто по деньгам с SVK за 2020 год по месяцам", 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.business_fact_family).toBe("value_flow"); expect(result.action_family).toBe("net_value_flow"); expect(result.aggregation_need).toBe("by_month"); expect(result.time_scope_need).toBe("explicit_period"); expect(result.comparison_need).toBe("incoming_vs_outgoing"); expect(result.proof_expectation).toBe("coverage_checked_fact"); expect(result.clarification_gaps).toEqual([]); expect(result.decomposition_candidates).toEqual([ "resolve_entity_reference", "collect_incoming_movements", "collect_outgoing_movements", "aggregate_by_month", "probe_coverage" ]); expect(result.forbidden_overclaim_flags).toContain("no_unchecked_fact_totals"); }); it("marks metadata lane choice as a clarification-required graph", () => { const result = buildAssistantMcpDiscoveryDataNeedGraph({ semanticDataNeed: "metadata lane clarification", rawUtterance: "давай дальше", turnMeaning: { asked_domain_family: "metadata", asked_action_family: "resolve_next_lane", explicit_entity_candidates: ["SVK"], unsupported_but_understood_family: "metadata_lane_choice_clarification" } }); expect(result.business_fact_family).toBe("schema_surface"); expect(result.clarification_gaps).toEqual(["lane_family_choice"]); expect(result.proof_expectation).toBe("clarification_required"); }); it("keeps entity search as an entity-grounding graph", () => { const result = buildAssistantMcpDiscoveryDataNeedGraph({ semanticDataNeed: "entity discovery evidence", rawUtterance: "найди в 1С контрагента Группа СВК", turnMeaning: { asked_domain_family: "entity_resolution", asked_action_family: "search_business_entity", explicit_entity_candidates: ["Группа СВК"] } }); expect(result.business_fact_family).toBe("entity_grounding"); expect(result.subject_candidates).toEqual(["Группа СВК"]); expect(result.proof_expectation).toBe("entity_grounding"); expect(result.decomposition_candidates).toEqual([ "search_business_entity", "resolve_entity_reference", "probe_coverage" ]); expect(result.forbidden_overclaim_flags).toContain("no_unresolved_entity_claim"); }); it("treats top-value wording as a ranking ask rather than a missing-subject fact ask", () => { const result = buildAssistantMcpDiscoveryDataNeedGraph({ semanticDataNeed: "counterparty value-flow evidence", rawUtterance: "кто больше всего принес денег в 2020", turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_date_scope: "2020" } }); expect(result.business_fact_family).toBe("value_flow"); expect(result.ranking_need).toBe("top_desc"); expect(result.clarification_gaps).toEqual(["organization"]); expect(result.proof_expectation).toBe("clarification_required"); expect(result.decomposition_candidates).toEqual([ "collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage" ]); expect(result.reason_codes).toContain("data_need_graph_ranking_top_desc"); expect(result.reason_codes).toContain("data_need_graph_open_scope_total_needs_organization"); }); it("keeps organization-scoped ranking executable when the ranking axis comes from follow-up context", () => { const result = buildAssistantMcpDiscoveryDataNeedGraph({ semanticDataNeed: "counterparty value-flow evidence", rawUtterance: "по ООО Альтернатива Плюс", turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_organization_scope: "ООО Альтернатива Плюс", explicit_date_scope: "2020", seeded_ranking_need: "top_desc" } }); expect(result.business_fact_family).toBe("value_flow"); expect(result.ranking_need).toBe("top_desc"); expect(result.clarification_gaps).toEqual([]); expect(result.proof_expectation).toBe("coverage_checked_fact"); }); it("treats incoming-vs-outgoing comparison as an open-scope value need rather than a missing-subject fact ask", () => { const result = buildAssistantMcpDiscoveryDataNeedGraph({ semanticDataNeed: "counterparty value-flow evidence", rawUtterance: "что больше: входящие или исходящие деньги за 2020 год?", turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "net_value_flow", explicit_date_scope: "2020" } }); expect(result.business_fact_family).toBe("value_flow"); expect(result.comparison_need).toBe("incoming_vs_outgoing"); expect(result.clarification_gaps).toEqual(["organization"]); expect(result.decomposition_candidates).toEqual([ "collect_incoming_movements", "collect_outgoing_movements", "probe_coverage" ]); expect(result.reason_codes).toContain("data_need_graph_comparison_incoming_vs_outgoing"); }); it("treats organization-scoped incoming totals as an open-scope value need rather than a missing-subject fact ask", () => { const result = buildAssistantMcpDiscoveryDataNeedGraph({ semanticDataNeed: "counterparty value-flow evidence", rawUtterance: "сколько входящих денег за 2020 год по ООО Альтернатива Плюс?", turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_date_scope: "2020", explicit_organization_scope: "ООО Альтернатива Плюс" } }); expect(result.business_fact_family).toBe("value_flow"); expect(result.comparison_need).toBeNull(); expect(result.ranking_need).toBeNull(); expect(result.clarification_gaps).toEqual([]); expect(result.decomposition_candidates).toEqual([ "collect_scoped_movements", "aggregate_checked_amounts", "probe_coverage" ]); expect(result.reason_codes).toContain("data_need_graph_open_scope_total_without_subject"); }); it("treats a generic incoming total as an understood open-scope ask that still needs organization", () => { const result = buildAssistantMcpDiscoveryDataNeedGraph({ semanticDataNeed: "counterparty value-flow evidence", rawUtterance: "сколько входящих денег за 2020 год?", turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_date_scope: "2020" } }); expect(result.business_fact_family).toBe("value_flow"); expect(result.subject_candidates).toEqual([]); expect(result.clarification_gaps).toEqual(["organization"]); expect(result.proof_expectation).toBe("clarification_required"); expect(result.decomposition_candidates).toEqual([ "collect_scoped_movements", "aggregate_checked_amounts", "probe_coverage" ]); expect(result.reason_codes).toContain("data_need_graph_open_scope_total_without_subject"); expect(result.reason_codes).toContain("data_need_graph_open_scope_total_needs_organization"); }); it("treats colloquial open incoming total wording with filler words as an open-scope ask rather than a missing subject", () => { const result = buildAssistantMcpDiscoveryDataNeedGraph({ semanticDataNeed: "counterparty value-flow evidence", rawUtterance: "сколько вообще входящих денег было?", turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover" } }); expect(result.business_fact_family).toBe("value_flow"); expect(result.subject_candidates).toEqual([]); expect(result.clarification_gaps).toEqual(["organization", "period"]); expect(result.proof_expectation).toBe("clarification_required"); expect(result.decomposition_candidates).toEqual([ "collect_scoped_movements", "aggregate_checked_amounts", "probe_coverage" ]); expect(result.reason_codes).toContain("data_need_graph_open_scope_total_without_subject"); expect(result.reason_codes).toContain("data_need_graph_open_scope_total_needs_organization"); }); it("treats all-time open-scope totals as an open-ended period rather than a missing period", () => { const result = buildAssistantMcpDiscoveryDataNeedGraph({ semanticDataNeed: "counterparty value-flow evidence", rawUtterance: "сколько вообще денег мы заработали за все время?", turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_organization_scope: "ООО Альтернатива Плюс" } }); expect(result.business_fact_family).toBe("value_flow"); expect(result.subject_candidates).toEqual([]); expect(result.time_scope_need).toBe("all_time_scope"); expect(result.clarification_gaps).toEqual([]); expect(result.proof_expectation).toBe("coverage_checked_fact"); expect(result.decomposition_candidates).toEqual([ "collect_scoped_movements", "aggregate_checked_amounts", "probe_coverage" ]); expect(result.reason_codes).toContain("data_need_graph_open_scope_total_without_subject"); expect(result.reason_codes).toContain("data_need_graph_all_time_scope_hint"); }); });