NODEDC_1C/llm_normalizer/backend/tests/assistantMcpDiscoveryDataNe...

414 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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("defaults explicit-counterparty bidirectional value-flow without period to bounded all-time scope", () => {
const result = buildAssistantMcpDiscoveryDataNeedGraph({
semanticDataNeed: "counterparty value-flow evidence",
rawUtterance: "how much money passed with SVK, incoming and outgoing?",
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "net_value_flow",
explicit_entity_candidates: ["SVK"]
}
});
expect(result.business_fact_family).toBe("value_flow");
expect(result.comparison_need).toBe("incoming_vs_outgoing");
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([
"resolve_entity_reference",
"collect_incoming_movements",
"collect_outgoing_movements",
"aggregate_checked_amounts",
"probe_coverage"
]);
expect(result.reason_codes).toContain(
"data_need_graph_subject_bidirectional_value_flow_defaults_to_all_time_scope"
);
});
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("models broad business evaluation as a bounded business-overview evidence graph", () => {
const result = buildAssistantMcpDiscoveryDataNeedGraph({
semanticDataNeed: "business overview evidence with bounded analyst interpretation",
rawUtterance: "дай полный анализ компании и LLM-аудит бизнеса",
turnMeaning: {
asked_domain_family: "business_overview",
asked_action_family: "broad_evaluation",
explicit_organization_scope: "ООО Альтернатива Плюс"
}
});
expect(result.business_fact_family).toBe("business_overview");
expect(result.action_family).toBe("broad_evaluation");
expect(result.time_scope_need).toBe("all_time_scope");
expect(result.proof_expectation).toBe("bounded_inference");
expect(result.clarification_gaps).toEqual([]);
expect(result.decomposition_candidates).toEqual([
"collect_scoped_movements",
"aggregate_checked_amounts",
"aggregate_ranked_axis_values",
"fetch_supporting_documents",
"probe_coverage",
"explain_evidence_basis"
]);
expect(result.forbidden_overclaim_flags).toEqual(
expect.arrayContaining([
"no_unchecked_business_health_claim",
"no_profit_or_margin_claim_without_evidence"
])
);
expect(result.reason_codes).toContain("data_need_graph_family_business_overview");
expect(result.reason_codes).toContain("data_need_graph_business_overview_defaults_to_all_time_scope");
});
it("requires organization scope before a fresh broad business overview probe", () => {
const result = buildAssistantMcpDiscoveryDataNeedGraph({
semanticDataNeed: "business overview evidence with bounded analyst interpretation",
rawUtterance: "как оценишь деятельность компании?",
turnMeaning: {
asked_domain_family: "business_overview",
asked_action_family: "broad_evaluation",
unsupported_but_understood_family: "broad_business_evaluation"
}
});
expect(result.business_fact_family).toBe("business_overview");
expect(result.clarification_gaps).toEqual(["organization"]);
expect(result.proof_expectation).toBe("clarification_required");
});
it("builds a subjectless inventory stock snapshot graph with an as-of date gate", () => {
const result = buildAssistantMcpDiscoveryDataNeedGraph({
semanticDataNeed: "inventory stock snapshot evidence",
rawUtterance: "show stock snapshot for warehouse main as of 2021-09-30",
turnMeaning: {
asked_domain_family: "inventory_stock",
asked_action_family: "stock_snapshot",
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2021-09-30"
}
});
expect(result.business_fact_family).toBe("inventory_stock_snapshot");
expect(result.subject_candidates).toEqual([]);
expect(result.clarification_gaps).toEqual([]);
expect(result.time_scope_need).toBe("explicit_period");
expect(result.decomposition_candidates).toEqual([
"fetch_scoped_movements",
"aggregate_checked_amounts",
"probe_coverage",
"explain_evidence_basis"
]);
expect(result.forbidden_overclaim_flags).toContain("no_unchecked_stock_snapshot");
});
it("keeps inventory stock snapshot blocked on an explicit as-of date when no date exists", () => {
const result = buildAssistantMcpDiscoveryDataNeedGraph({
semanticDataNeed: "inventory stock snapshot evidence",
rawUtterance: "show current stock snapshot",
turnMeaning: {
asked_domain_family: "inventory_stock",
asked_action_family: "stock_snapshot",
explicit_organization_scope: "ООО Альтернатива Плюс"
}
});
expect(result.business_fact_family).toBe("inventory_stock_snapshot");
expect(result.clarification_gaps).toEqual(["as_of_date"]);
expect(result.proof_expectation).toBe("clarification_required");
});
it("builds inventory provenance and sale-trace graphs as checked document drilldowns", () => {
const provenance = buildAssistantMcpDiscoveryDataNeedGraph({
semanticDataNeed: "inventory purchase provenance evidence",
rawUtterance: "who supplied the selected stock item",
turnMeaning: {
asked_domain_family: "inventory_stock",
asked_action_family: "purchase_provenance",
explicit_entity_candidates: ["Столешница 600"]
}
});
const saleTrace = buildAssistantMcpDiscoveryDataNeedGraph({
semanticDataNeed: "inventory sale trace evidence",
rawUtterance: "who bought the selected stock item",
turnMeaning: {
asked_domain_family: "inventory_stock",
asked_action_family: "sale_trace",
explicit_entity_candidates: ["Столешница 600"]
}
});
expect(provenance.business_fact_family).toBe("inventory_purchase_provenance");
expect(provenance.decomposition_candidates).toEqual([
"resolve_entity_reference",
"fetch_scoped_documents",
"drilldown_related_objects",
"probe_coverage",
"explain_evidence_basis"
]);
expect(provenance.forbidden_overclaim_flags).toContain("no_unproven_supplier_attribution");
expect(saleTrace.business_fact_family).toBe("inventory_sale_trace");
expect(saleTrace.decomposition_candidates).toEqual([
"resolve_entity_reference",
"fetch_scoped_documents",
"drilldown_related_objects",
"probe_coverage",
"explain_evidence_basis"
]);
expect(saleTrace.forbidden_overclaim_flags).toContain("no_unproven_buyer_or_sale_trace");
});
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");
});
it("treats metadata-scoped movement evidence as subjectless and asks only for organization plus period", () => {
const result = buildAssistantMcpDiscoveryDataNeedGraph({
semanticDataNeed: "movement evidence",
rawUtterance: "по движениям",
turnMeaning: {
asked_domain_family: "movements",
asked_action_family: "list_movements",
metadata_scope_hint: "НДС",
subject_resolution_optional: true,
unsupported_but_understood_family: "movement_evidence"
}
});
expect(result.business_fact_family).toBe("movement_evidence");
expect(result.subject_candidates).toEqual([]);
expect(result.metadata_scope_hint).toBe("НДС");
expect(result.subject_resolution_optional).toBe(true);
expect(result.clarification_gaps).toEqual(["organization", "period"]);
expect(result.decomposition_candidates).toEqual(["fetch_scoped_movements", "probe_coverage"]);
expect(result.reason_codes).toContain("data_need_graph_metadata_scoped_open_lane_without_subject");
});
});