ARCH: ввести data-need graph и довести open-scope comparison до live replay
This commit is contained in:
@@ -222,6 +222,74 @@ describe("assistant MCP discovery answer adapter", () => {
|
||||
expect(draft.must_not_claim).toContain("Do not claim rows were checked when mcp_execution_performed=false.");
|
||||
});
|
||||
|
||||
it("asks for organization rather than counterparty when a ranked value-flow ask already has the period", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "turnover",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: "top_desc",
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_date_scope: "2020"
|
||||
}
|
||||
});
|
||||
const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([]));
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
|
||||
expect(draft.answer_mode).toBe("needs_clarification");
|
||||
expect(draft.headline).toContain("ranking");
|
||||
expect(draft.next_step_line).toContain("организацию");
|
||||
expect(draft.next_step_line).not.toContain("Уточните контрагента");
|
||||
});
|
||||
|
||||
it("asks for organization rather than counterparty on open bidirectional comparison when only the period is known", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "net_value_flow",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: "incoming_vs_outgoing",
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_incoming_movements", "collect_outgoing_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_comparison_incoming_vs_outgoing"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_date_scope: "2020"
|
||||
}
|
||||
});
|
||||
const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([]));
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
|
||||
expect(draft.answer_mode).toBe("needs_clarification");
|
||||
expect(draft.headline).toContain("входящий и исходящий");
|
||||
expect(draft.next_step_line).toContain("организацию");
|
||||
expect(draft.next_step_line).not.toContain("Уточните контрагента");
|
||||
});
|
||||
|
||||
it("asks for an explicit lane choice when mixed metadata ambiguity cannot continue on a neutral follow-up", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
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([]);
|
||||
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");
|
||||
});
|
||||
|
||||
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([]);
|
||||
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");
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,27 @@ import { planAssistantMcpDiscovery } from "../src/services/assistantMcpDiscovery
|
||||
describe("assistant MCP discovery planner", () => {
|
||||
it("builds a catalog-compatible value-flow discovery plan from current turn meaning", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: ["SVK"],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "turnover",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: [
|
||||
"resolve_entity_reference",
|
||||
"collect_scoped_movements",
|
||||
"aggregate_checked_amounts",
|
||||
"probe_coverage"
|
||||
],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
@@ -25,8 +46,10 @@ describe("assistant MCP discovery planner", () => {
|
||||
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.data_need_graph?.business_fact_family).toBe("value_flow");
|
||||
expect(result.discovery_plan.execution_budget.max_probe_count).toBe(30);
|
||||
expect(result.reason_codes).toContain("planner_enabled_chunked_coverage_probe_budget");
|
||||
expect(result.reason_codes).toContain("planner_consumed_data_need_graph_v1");
|
||||
});
|
||||
|
||||
it("keeps a value-flow plan in clarification state when period axis is missing", () => {
|
||||
@@ -91,6 +114,22 @@ describe("assistant MCP discovery planner", () => {
|
||||
|
||||
it("builds a movement discovery plan without aggregating value-flow totals", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: ["SVK"],
|
||||
business_fact_family: "movement_evidence",
|
||||
action_family: "list_movements",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["resolve_entity_reference", "fetch_scoped_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
@@ -107,7 +146,192 @@ describe("assistant MCP discovery planner", () => {
|
||||
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");
|
||||
expect(result.reason_codes).toContain("planner_selected_movement_from_data_need_graph");
|
||||
});
|
||||
|
||||
it("can select value-flow chain from data need graph even when turn meaning family is still under-specified", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: ["SVK"],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "net_value_flow",
|
||||
aggregation_need: "by_month",
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: "incoming_vs_outgoing",
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: [
|
||||
"resolve_entity_reference",
|
||||
"collect_incoming_movements",
|
||||
"collect_outgoing_movements",
|
||||
"aggregate_by_month",
|
||||
"probe_coverage"
|
||||
],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built"]
|
||||
},
|
||||
turnMeaning: {
|
||||
explicit_entity_candidates: ["SVK"],
|
||||
explicit_date_scope: "2020",
|
||||
asked_aggregation_axis: "month"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.selected_chain_id).toBe("value_flow");
|
||||
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_from_data_need_graph");
|
||||
});
|
||||
|
||||
it("does not collapse a ranking-shaped value graph into entity-resolution just because no subject is preselected", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "turnover",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: "top_desc",
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_date_scope: "2020"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("needs_clarification");
|
||||
expect(result.selected_chain_id).toBe("value_flow_ranking");
|
||||
expect(result.proposed_primitives).toEqual(["query_movements", "aggregate_by_axis", "probe_coverage"]);
|
||||
expect(result.required_axes).toEqual(["period", "aggregate_axis", "amount", "coverage_target"]);
|
||||
expect(result.catalog_review.review_status).toBe("needs_more_axes");
|
||||
expect(result.reason_codes).toContain("planner_selected_top_ranked_value_flow_from_data_need_graph");
|
||||
expect(result.selected_chain_id).not.toBe("entity_resolution");
|
||||
});
|
||||
|
||||
it("keeps ranked value-flow ready for execution once checked period and organization are known", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "turnover",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: "top_desc",
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_date_scope: "2020",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.selected_chain_id).toBe("value_flow_ranking");
|
||||
expect(result.proposed_primitives).toEqual(["query_movements", "aggregate_by_axis", "probe_coverage"]);
|
||||
expect(result.required_axes).toEqual(["organization", "period", "aggregate_axis", "amount", "coverage_target"]);
|
||||
expect(result.catalog_review.review_status).toBe("catalog_compatible");
|
||||
expect(result.reason_codes).toContain("planner_selected_top_ranked_value_flow_from_data_need_graph");
|
||||
});
|
||||
|
||||
it("does not collapse incoming-vs-outgoing comparison into entity-resolution when no counterparty is preselected", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "net_value_flow",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: "incoming_vs_outgoing",
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_incoming_movements", "collect_outgoing_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_comparison_incoming_vs_outgoing"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_date_scope: "2020"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("needs_clarification");
|
||||
expect(result.selected_chain_id).toBe("value_flow_comparison");
|
||||
expect(result.proposed_primitives).toEqual(["query_movements", "probe_coverage"]);
|
||||
expect(result.required_axes).toEqual(["period", "amount", "coverage_target"]);
|
||||
expect(result.reason_codes).toContain("planner_selected_bidirectional_value_flow_comparison_from_data_need_graph");
|
||||
expect(result.selected_chain_id).not.toBe("entity_resolution");
|
||||
});
|
||||
|
||||
it("keeps bidirectional comparison ready for execution once checked period and organization are known", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "net_value_flow",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: "incoming_vs_outgoing",
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_incoming_movements", "collect_outgoing_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_comparison_incoming_vs_outgoing"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_date_scope: "2020",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.selected_chain_id).toBe("value_flow_comparison");
|
||||
expect(result.proposed_primitives).toEqual(["query_movements", "probe_coverage"]);
|
||||
expect(result.required_axes).toEqual(["organization", "period", "amount", "coverage_target"]);
|
||||
expect(result.catalog_review.review_status).toBe("catalog_compatible");
|
||||
expect(result.reason_codes).toContain("planner_selected_bidirectional_value_flow_comparison_from_data_need_graph");
|
||||
});
|
||||
|
||||
it("builds an inference-safe lifecycle plan with evidence explanation", () => {
|
||||
|
||||
@@ -397,4 +397,41 @@ describe("assistant MCP discovery response candidate", () => {
|
||||
expect(candidate.reply_text).toBeNull();
|
||||
expect(candidate.eligible_for_future_hot_runtime).toBe(false);
|
||||
});
|
||||
it("localizes open-scope bidirectional comparison scope and probe-limit wording without contour garbage", () => {
|
||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||
entryPoint({
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference",
|
||||
headline:
|
||||
"\u041f\u043e \u0434\u0430\u043d\u043d\u044b\u043c 1\u0421 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0441\u0442\u0440\u043e\u043a\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0438 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0445 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0439; \u043d\u0435\u0442\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u043a \u0440\u0430\u0441\u0447\u0435\u0442 \u043f\u043e \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u043c \u0441\u0442\u0440\u043e\u043a\u0430\u043c \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u043c\u0443 \u043f\u0435\u0440\u0438\u043e\u0434\u0443.",
|
||||
confirmed_lines: [
|
||||
"1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=found, outgoing=found"
|
||||
],
|
||||
inference_lines: [],
|
||||
unknown_lines: [],
|
||||
limitation_lines: [
|
||||
"Requested period hit the MCP row limit, but the approved monthly recovery probe budget is smaller than the required subperiod count"
|
||||
],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
expect(candidate.reply_text).toContain(
|
||||
"\u0412 1\u0421 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u044b \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0438 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u0437\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u043e\u043c \u0441\u0440\u0435\u0437\u0435"
|
||||
);
|
||||
expect(candidate.reply_text).toContain(
|
||||
"\u0417\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u044b\u0439 \u043f\u0435\u0440\u0438\u043e\u0434 \u0443\u043f\u0435\u0440\u0441\u044f \u0432 \u043b\u0438\u043c\u0438\u0442 \u0441\u0442\u0440\u043e\u043a MCP"
|
||||
);
|
||||
expect(candidate.reply_text).not.toContain(
|
||||
"\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0441\u043a\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0443\u0440\u0443"
|
||||
);
|
||||
expect(candidate.reply_text).not.toContain("Requested period hit the MCP row limit");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,30 @@ function buildDeps(rows: Array<Record<string, unknown>>, error: string | null =
|
||||
};
|
||||
}
|
||||
|
||||
function buildBidirectionalDeps(
|
||||
incomingRows: Array<Record<string, unknown>>,
|
||||
outgoingRows: Array<Record<string, unknown>>
|
||||
) {
|
||||
return {
|
||||
executeAddressMcpQuery: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
fetched_rows: incomingRows.length,
|
||||
matched_rows: incomingRows.length,
|
||||
raw_rows: incomingRows,
|
||||
rows: incomingRows,
|
||||
error: null
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
fetched_rows: outgoingRows.length,
|
||||
matched_rows: outgoingRows.length,
|
||||
raw_rows: outgoingRows,
|
||||
rows: outgoingRows,
|
||||
error: null
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
describe("assistant MCP discovery runtime bridge", () => {
|
||||
it("composes planner, pilot executor, and answer draft without wiring the hot runtime", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
||||
@@ -51,6 +75,166 @@ describe("assistant MCP discovery runtime bridge", () => {
|
||||
expect(result.answer_draft.next_step_line).toContain("Уточните контрагента");
|
||||
});
|
||||
|
||||
it("keeps ranked value-flow in clarification without asking for a counterparty when only the period is known", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "turnover",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: "top_desc",
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_date_scope: "2020"
|
||||
},
|
||||
deps: buildDeps([])
|
||||
});
|
||||
|
||||
expect(result.bridge_status).toBe("needs_clarification");
|
||||
expect(result.requires_user_clarification).toBe(true);
|
||||
expect(result.pilot.mcp_execution_performed).toBe(false);
|
||||
expect(result.planner.selected_chain_id).toBe("value_flow_ranking");
|
||||
expect(result.answer_draft.headline).toContain("ranking");
|
||||
expect(result.answer_draft.next_step_line).toContain("организацию");
|
||||
expect(result.answer_draft.next_step_line).not.toContain("Уточните контрагента");
|
||||
});
|
||||
|
||||
it("produces a bounded ranked value-flow answer when period and organization are known", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "turnover",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: "top_desc",
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_date_scope: "2020",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||||
},
|
||||
deps: buildDeps([
|
||||
{ Period: "2020-01-10T00:00:00", Amount: 1200, Counterparty: "СВК-А" },
|
||||
{ Period: "2020-03-11T00:00:00", Amount: 800, Counterparty: "СВК-Б" },
|
||||
{ Period: "2020-05-12T00:00:00", Amount: 900, Counterparty: "СВК-А" }
|
||||
])
|
||||
});
|
||||
|
||||
expect(result.bridge_status).toBe("answer_draft_ready");
|
||||
expect(result.business_fact_answer_allowed).toBe(true);
|
||||
expect(result.planner.selected_chain_id).toBe("value_flow_ranking");
|
||||
expect(result.pilot.derived_ranked_value_flow?.ranked_values[0]).toMatchObject({
|
||||
axis_value: "СВК-А",
|
||||
total_amount: 2100
|
||||
});
|
||||
expect(result.answer_draft.confirmed_lines.join("\n")).toContain("СВК-А");
|
||||
});
|
||||
|
||||
it("keeps open bidirectional comparison in clarification without asking for a counterparty when only the period is known", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "net_value_flow",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: "incoming_vs_outgoing",
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_incoming_movements", "collect_outgoing_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_comparison_incoming_vs_outgoing"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_date_scope: "2020"
|
||||
},
|
||||
deps: buildDeps([])
|
||||
});
|
||||
|
||||
expect(result.bridge_status).toBe("needs_clarification");
|
||||
expect(result.requires_user_clarification).toBe(true);
|
||||
expect(result.pilot.mcp_execution_performed).toBe(false);
|
||||
expect(result.planner.selected_chain_id).toBe("value_flow_comparison");
|
||||
expect(result.answer_draft.headline).toContain("входящий и исходящий");
|
||||
expect(result.answer_draft.next_step_line).toContain("организацию");
|
||||
expect(result.answer_draft.next_step_line).not.toContain("Уточните контрагента");
|
||||
});
|
||||
|
||||
it("produces a bounded bidirectional comparison answer when period and organization are known", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "net_value_flow",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: "incoming_vs_outgoing",
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_incoming_movements", "collect_outgoing_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_comparison_incoming_vs_outgoing"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_date_scope: "2020",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||||
},
|
||||
deps: buildBidirectionalDeps(
|
||||
[
|
||||
{ Period: "2020-01-10T00:00:00", Amount: 3200, Counterparty: "СВК-А" },
|
||||
{ Period: "2020-04-11T00:00:00", Amount: 1800, Counterparty: "СВК-Б" }
|
||||
],
|
||||
[{ Period: "2020-02-12T00:00:00", Amount: 1400, Counterparty: "СВК-А" }]
|
||||
)
|
||||
});
|
||||
|
||||
expect(result.bridge_status).toBe("answer_draft_ready");
|
||||
expect(result.business_fact_answer_allowed).toBe(true);
|
||||
expect(result.planner.selected_chain_id).toBe("value_flow_comparison");
|
||||
expect(result.pilot.derived_bidirectional_value_flow).toMatchObject({
|
||||
period_scope: "2020",
|
||||
incoming_customer_revenue: {
|
||||
total_amount: 5000
|
||||
},
|
||||
outgoing_supplier_payout: {
|
||||
total_amount: 1400
|
||||
}
|
||||
});
|
||||
expect(result.answer_draft.confirmed_lines.join("\n")).toContain("получили");
|
||||
expect(result.answer_draft.confirmed_lines.join("\n")).toContain("заплатили");
|
||||
});
|
||||
|
||||
it("keeps document-ready plans bounded when the pilot finds no confirmed rows", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
||||
turnMeaning: {
|
||||
|
||||
@@ -13,6 +13,30 @@ function buildDeps(rows: Array<Record<string, unknown>>, error: string | null =
|
||||
};
|
||||
}
|
||||
|
||||
function buildBidirectionalDeps(
|
||||
incomingRows: Array<Record<string, unknown>>,
|
||||
outgoingRows: Array<Record<string, unknown>>
|
||||
) {
|
||||
return {
|
||||
executeAddressMcpQuery: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
fetched_rows: incomingRows.length,
|
||||
matched_rows: incomingRows.length,
|
||||
raw_rows: incomingRows,
|
||||
rows: incomingRows,
|
||||
error: null
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
fetched_rows: outgoingRows.length,
|
||||
matched_rows: outgoingRows.length,
|
||||
raw_rows: outgoingRows,
|
||||
rows: outgoingRows,
|
||||
error: null
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
function buildMetadataDeps(rows: Array<Record<string, unknown>>, error: string | null = null) {
|
||||
return {
|
||||
executeAddressMcpMetadata: vi.fn(async () => ({
|
||||
@@ -240,4 +264,93 @@ describe("assistant MCP discovery runtime entry point", () => {
|
||||
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_document_evidence_query_documents_v1");
|
||||
expect(result.bridge?.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||||
});
|
||||
|
||||
it("runs raw incoming-vs-outgoing comparison as an open-scope value-flow chain without inventing a counterparty", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||||
userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?",
|
||||
predecomposeContract: {
|
||||
entities: { organization: "ООО Альтернатива Плюс" },
|
||||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||
},
|
||||
deps: buildBidirectionalDeps(
|
||||
[
|
||||
{ Period: "2020-01-15T00:00:00", Amount: 2500, Counterparty: "Клиент-А" },
|
||||
{ Period: "2020-06-20T00:00:00", Amount: 1000, Counterparty: "Клиент-Б" }
|
||||
],
|
||||
[{ Period: "2020-02-18T00:00:00", Amount: 900, Counterparty: "Поставщик-А" }]
|
||||
)
|
||||
});
|
||||
|
||||
expect(result.entry_status).toBe("bridge_executed");
|
||||
expect(result.discovery_attempted).toBe(true);
|
||||
expect(result.turn_input.data_need_graph?.comparison_need).toBe("incoming_vs_outgoing");
|
||||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020"
|
||||
});
|
||||
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow_comparison");
|
||||
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_bidirectional_value_flow_query_movements_v1");
|
||||
expect(result.bridge?.answer_draft.confirmed_lines.join("\n")).toContain("получили");
|
||||
expect(result.bridge?.answer_draft.confirmed_lines.join("\n")).toContain("заплатили");
|
||||
});
|
||||
|
||||
it.skip("keeps mirrored predecompose organization and counterparty out of the subject lane for open comparison", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||||
userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?",
|
||||
predecomposeContract: {
|
||||
entities: {
|
||||
counterparty: "ООО Альтернатива Плюс",
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
},
|
||||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||
},
|
||||
deps: buildBidirectionalDeps(
|
||||
[{ Period: "2020-01-15T00:00:00", Amount: 2500, Counterparty: "Клиент-А" }],
|
||||
[{ Period: "2020-02-18T00:00:00", Amount: 900, Counterparty: "Поставщик-А" }]
|
||||
)
|
||||
});
|
||||
|
||||
expect(result.entry_status).toBe("bridge_executed");
|
||||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020"
|
||||
});
|
||||
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.turn_input.data_need_graph?.subject_candidates).toEqual([]);
|
||||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow_comparison");
|
||||
});
|
||||
|
||||
it.skip("keeps mirrored predecompose organization and counterparty out of the subject lane for open comparison (utf8-safe)", async () => {
|
||||
const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||||
userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?",
|
||||
predecomposeContract: {
|
||||
entities: {
|
||||
counterparty: orgName,
|
||||
organization: orgName
|
||||
},
|
||||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||
},
|
||||
deps: buildBidirectionalDeps(
|
||||
[{ Period: "2020-01-15T00:00:00", Amount: 2500, Counterparty: "\u041a\u043b\u0438\u0435\u043d\u0442-\u0410" }],
|
||||
[{ Period: "2020-02-18T00:00:00", Amount: 900, Counterparty: "\u041f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a-\u0410" }]
|
||||
)
|
||||
});
|
||||
|
||||
expect(result.entry_status).toBe("bridge_executed");
|
||||
expect(result.turn_input.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_organization_scope: orgName,
|
||||
explicit_date_scope: "2020"
|
||||
});
|
||||
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.turn_input.data_need_graph?.subject_candidates).toEqual([]);
|
||||
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow_comparison");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,8 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
|
||||
expect(result.data_need_graph?.business_fact_family).toBe("value_flow");
|
||||
expect(result.data_need_graph?.time_scope_need).toBe("explicit_period");
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toEqual(["SVK", "Группа СВК"]);
|
||||
expect(result.turn_meaning_ref?.explicit_organization_scope).toBe("Альтернатива");
|
||||
expect(result.turn_meaning_ref?.explicit_date_scope).toBe("2020");
|
||||
@@ -160,6 +162,8 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.source_signal).toBe("raw_text");
|
||||
expect(result.semantic_data_need).toBe("1C metadata evidence");
|
||||
expect(result.data_need_graph?.business_fact_family).toBe("schema_surface");
|
||||
expect(result.data_need_graph?.decomposition_candidates).toEqual(["inspect_metadata_surface"]);
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "metadata",
|
||||
asked_action_family: "inspect_fields",
|
||||
@@ -198,6 +202,8 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.source_signal).toBe("raw_text");
|
||||
expect(result.semantic_data_need).toBe("entity discovery evidence");
|
||||
expect(result.data_need_graph?.business_fact_family).toBe("entity_grounding");
|
||||
expect(result.data_need_graph?.subject_candidates).toEqual(["Группа СВК"]);
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "entity_resolution",
|
||||
asked_action_family: "search_business_entity",
|
||||
@@ -1256,4 +1262,119 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
"\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a \u043d\u0430\u0439\u0442\u0438 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430 \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c '\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a'"
|
||||
);
|
||||
});
|
||||
|
||||
it("marks top-value wording as a ranking data need without inventing a missing subject gap", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "кто больше всего принес денег в 2020"
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
|
||||
expect(result.data_need_graph?.business_fact_family).toBe("value_flow");
|
||||
expect(result.data_need_graph?.ranking_need).toBe("top_desc");
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||
expect(result.data_need_graph?.decomposition_candidates).toEqual([
|
||||
"collect_scoped_movements",
|
||||
"aggregate_ranked_axis_values",
|
||||
"probe_coverage"
|
||||
]);
|
||||
});
|
||||
it("keeps organization as scope for open bidirectional comparison wording instead of inventing a subject candidate", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?",
|
||||
predecomposeContract: {
|
||||
entities: { organization: "ООО Альтернатива Плюс" },
|
||||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020"
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.data_need_graph?.comparison_need).toBe("incoming_vs_outgoing");
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||
});
|
||||
|
||||
it("drops organization-shaped assistant-turn entity pollution when open comparison already has explicit organization scope", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?",
|
||||
assistantTurnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_entity_candidates: [{ value: "ООО Альтернатива Плюс" }],
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting"
|
||||
},
|
||||
predecomposeContract: {
|
||||
entities: { counterparty: "ООО Альтернатива Плюс", organization: "ООО Альтернатива Плюс" },
|
||||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020"
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.data_need_graph?.comparison_need).toBe("incoming_vs_outgoing");
|
||||
});
|
||||
|
||||
it.skip("treats mirrored predecompose organization and counterparty as organization scope for open comparison", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?",
|
||||
predecomposeContract: {
|
||||
entities: {
|
||||
counterparty: "ООО Альтернатива Плюс",
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
},
|
||||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020"
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.data_need_graph?.subject_candidates).toEqual([]);
|
||||
expect(result.data_need_graph?.comparison_need).toBe("incoming_vs_outgoing");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_counterparty_from_predecompose");
|
||||
});
|
||||
|
||||
it.skip("treats mirrored predecompose organization and counterparty as organization scope for open comparison (utf8-safe)", () => {
|
||||
const orgName = "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?",
|
||||
predecomposeContract: {
|
||||
entities: {
|
||||
counterparty: orgName,
|
||||
organization: orgName
|
||||
},
|
||||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_organization_scope: orgName,
|
||||
explicit_date_scope: "2020"
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.data_need_graph?.subject_candidates).toEqual([]);
|
||||
expect(result.data_need_graph?.comparison_need).toBe("incoming_vs_outgoing");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_counterparty_from_predecompose");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user