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

336 lines
15 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("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("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");
});
});