NODEDC_1C/llm_normalizer/backend/tests/assistantMcpDiscoveryRuntim...

677 lines
33 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, vi } from "vitest";
import { runAssistantMcpDiscoveryRuntimeEntryPoint } from "../src/services/assistantMcpDiscoveryRuntimeEntryPoint";
function buildDeps(rows: Array<Record<string, unknown>>, error: string | null = null) {
return {
executeAddressMcpQuery: vi.fn(async () => ({
fetched_rows: rows.length,
matched_rows: error ? 0 : rows.length,
raw_rows: rows,
rows: error ? [] : rows,
error
}))
};
}
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 () => ({
fetched_rows: error ? 0 : rows.length,
raw_rows: error ? [] : rows,
rows: error ? [] : rows,
error
}))
};
}
describe("assistant MCP discovery runtime entry point", () => {
it("runs the bridge for discovery-eligible lifecycle turn context", async () => {
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "Сколько лет мы работаем с Группа СВК?",
predecomposeContract: {
entities: { counterparty: "Группа СВК" },
period: {}
},
deps: buildDeps([{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST" }])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.hot_runtime_wired).toBe(false);
expect(result.discovery_attempted).toBe(true);
expect(result.turn_input.semantic_data_need).toBe("counterparty lifecycle evidence");
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
expect(result.bridge?.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference");
expect(result.reason_codes).toContain("runtime_entry_point_bridge_executed");
});
it("skips supported exact turns before any discovery execution", async () => {
const deps = buildDeps([]);
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
assistantTurnMeaning: {
asked_domain_family: "counterparty",
asked_action_family: "list_documents",
explicit_intent_candidate: "list_documents_by_counterparty",
explicit_entity_candidates: [{ value: "SVK" }]
},
deps
});
expect(result.entry_status).toBe("skipped_not_applicable");
expect(result.discovery_attempted).toBe(false);
expect(result.bridge).toBeNull();
expect(deps.executeAddressMcpQuery).not.toHaveBeenCalled();
expect(result.reason_codes).toContain("runtime_entry_point_skipped_supported_exact_turn");
});
it("passes unsupported-but-understood value turns into bridge with normalized entity scope", async () => {
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
assistantTurnMeaning: {
asked_domain_family: "counterparty",
asked_action_family: "counterparty_value_or_turnover",
unsupported_but_understood_family: "counterparty_value_or_turnover",
explicit_entity_candidates: [{ value: "SVK" }]
},
predecomposeContract: {
entities: { counterparty: "Группа СВК" },
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
},
deps: buildDeps([
{ Period: "2020-01-15T00:00:00", Amount: 1250, Counterparty: "SVK" },
{ Period: "2020-02-20T00:00:00", Amount: 2500, Counterparty: "SVK" }
])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toEqual(["SVK", "Группа СВК"]);
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
expect(result.bridge?.pilot.derived_value_flow?.total_amount).toBe(3750);
expect(result.bridge?.hot_runtime_wired).toBe(false);
expect(result.reason_codes).toContain("mcp_discovery_unsupported_but_understood_turn");
});
it("runs the bridge for raw metadata wording without an exact route owner", async () => {
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "какие документы и поля есть в 1С по НДС?",
deps: buildMetadataDeps([
{
FullName: "Документ.СчетФактураВыданный",
MetaType: "Документ",
attributes: [{ Name: "Дата" }]
}
])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.discovery_attempted).toBe(true);
expect(result.turn_input.semantic_data_need).toBe("1C metadata evidence");
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "metadata",
asked_action_family: "inspect_fields"
});
expect(result.bridge?.pilot.pilot_scope).toBe("metadata_inspection_v1");
expect(result.bridge?.answer_draft.headline).toContain("схеме 1С");
expect(result.bridge?.answer_draft.headline).toContain("подходящие объекты");
});
it("runs the bridge for raw entity-resolution wording and executes the full grounding chain", async () => {
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "найди в 1С контрагента Группа СВК",
deps: buildDeps([
{ Counterparty: "Группа СВК", CounterpartyRef: "Ref-1" },
{ Counterparty: "СВК Логистика", CounterpartyRef: "Ref-2" }
])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.discovery_attempted).toBe(true);
expect(result.turn_input.semantic_data_need).toBe("entity discovery evidence");
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "entity_resolution",
asked_action_family: "search_business_entity",
explicit_entity_candidates: ["Группа СВК"]
});
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
expect(result.bridge?.pilot.pilot_scope).toBe("entity_resolution_search_v1");
expect(result.bridge?.pilot.executed_primitives).toEqual([
"search_business_entity",
"resolve_entity_reference",
"probe_coverage"
]);
expect(result.bridge?.pilot.derived_entity_resolution).toMatchObject({
resolution_status: "resolved",
resolved_entity: "Группа СВК",
resolved_reference: "Ref-1"
});
expect(result.bridge?.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference");
});
it("runs the bridge again when the user clarifies one ambiguous entity-resolution candidate", async () => {
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "СВК-А",
followupContext: {
previous_discovery_pilot_scope: "entity_resolution_search_v1",
previous_discovery_entity_resolution_status: "ambiguous",
previous_discovery_entity_candidates: ["СВК", "СВК-А", "СВК-Б"],
previous_discovery_entity_ambiguity_candidates: ["СВК-А", "СВК-Б"]
},
deps: buildDeps([
{ Counterparty: "СВК-А", CounterpartyRef: "Ref-1" },
{ Counterparty: "СВК-Б", CounterpartyRef: "Ref-2" }
])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.discovery_attempted).toBe(true);
expect(result.turn_input.source_signal).toBe("followup_context");
expect(result.turn_input.semantic_data_need).toBe("entity discovery evidence");
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "entity_resolution",
asked_action_family: "search_business_entity",
explicit_entity_candidates: ["СВК-А"]
});
expect(result.bridge?.pilot.pilot_scope).toBe("entity_resolution_search_v1");
expect(result.bridge?.pilot.executed_primitives).toEqual([
"search_business_entity",
"resolve_entity_reference",
"probe_coverage"
]);
expect(result.bridge?.pilot.derived_entity_resolution).toMatchObject({
resolution_status: "resolved",
resolved_entity: "СВК-А",
resolved_reference: "Ref-1"
});
});
it("chains an ordinal ambiguity clarification directly into value-flow execution", async () => {
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "второй вариант, сколько получили за 2020 год",
followupContext: {
previous_discovery_pilot_scope: "entity_resolution_search_v1",
previous_discovery_entity_resolution_status: "ambiguous",
previous_discovery_entity_candidates: ["СВК", "СВК-А", "СВК-Б"],
previous_discovery_entity_ambiguity_candidates: ["СВК-А", "СВК-Б"]
},
deps: buildDeps([
{ Period: "2020-01-15T00:00:00", Amount: 1200, Counterparty: "СВК-Б" },
{ Period: "2020-02-20T00:00:00", Amount: 800, Counterparty: "СВК-Б" }
])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.discovery_attempted).toBe(true);
expect(result.turn_input.source_signal).toBe("followup_context");
expect(result.turn_input.semantic_data_need).toBe("counterparty value-flow evidence");
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_entity_candidates: ["СВК-Б"],
explicit_date_scope: "2020"
});
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
expect(result.bridge?.pilot.derived_value_flow).toMatchObject({
counterparty: "СВК-Б",
period_scope: "2020",
total_amount: 2000
});
});
it("chains an ordinal ambiguity clarification directly into document evidence execution", async () => {
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "первый вариант, покажи документы за 2020 год",
followupContext: {
previous_discovery_pilot_scope: "entity_resolution_search_v1",
previous_discovery_entity_resolution_status: "ambiguous",
previous_discovery_entity_candidates: ["СВК", "СВК-А", "СВК-Б"],
previous_discovery_entity_ambiguity_candidates: ["СВК-А", "СВК-Б"]
},
deps: buildDeps([{ Period: "2020-03-12T00:00:00", Counterparty: "СВК-А", Registrar: "Doc-1" }])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.discovery_attempted).toBe(true);
expect(result.turn_input.source_signal).toBe("followup_context");
expect(result.turn_input.semantic_data_need).toBe("document evidence");
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "documents",
asked_action_family: "list_documents",
explicit_entity_candidates: ["СВК-А"],
explicit_date_scope: "2020"
});
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("keeps the same ranking loop after organization-only clarification when period is still missing", async () => {
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "по ООО Альтернатива Плюс",
predecomposeContract: {
entities: { organization: "ООО Альтернатива Плюс" }
},
followupContext: {
previous_discovery_loop_status: "awaiting_clarification",
previous_discovery_loop_selected_chain_id: "value_flow_ranking",
previous_discovery_loop_pending_axes: ["organization", "period"],
previous_discovery_loop_provided_axes: ["aggregate_axis", "amount", "coverage_target"],
previous_discovery_loop_asked_domain_family: "counterparty_value",
previous_discovery_loop_asked_action_family: "turnover",
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover",
previous_discovery_ranking_need: "top_desc"
},
deps: buildDeps([])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_organization_scope: "ООО Альтернатива Плюс",
seeded_ranking_need: "top_desc"
});
expect(result.turn_input.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
expect(result.bridge?.bridge_status).toBe("needs_clarification");
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow_ranking");
expect(result.bridge?.loop_state).toMatchObject({
loop_status: "awaiting_clarification",
selected_chain_id: "value_flow_ranking",
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: null,
ranking_need: "top_desc"
});
expect(result.bridge?.loop_state.pending_axes).toContain("period");
expect(result.bridge?.loop_state.pending_axes).not.toContain("organization");
expect(result.bridge?.answer_draft.next_step_line).toContain("период");
expect(result.bridge?.answer_draft.next_step_line).not.toContain("организацию");
});
it("completes the same ranking loop after the second clarification provides the period", async () => {
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "за 2020 год",
followupContext: {
previous_discovery_loop_status: "awaiting_clarification",
previous_discovery_loop_selected_chain_id: "value_flow_ranking",
previous_discovery_loop_pending_axes: ["period"],
previous_discovery_loop_provided_axes: ["aggregate_axis", "amount", "coverage_target", "organization"],
previous_discovery_loop_asked_domain_family: "counterparty_value",
previous_discovery_loop_asked_action_family: "turnover",
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover",
previous_discovery_ranking_need: "top_desc",
previous_filters: {
organization: "ООО Альтернатива Плюс"
}
},
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.entry_status).toBe("bridge_executed");
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020",
seeded_ranking_need: "top_desc"
});
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow_ranking");
expect(result.bridge?.pilot.derived_ranked_value_flow?.ranked_values[0]).toMatchObject({
axis_value: "СВК-А",
total_amount: 2100
});
expect(result.bridge?.loop_state.loop_status).toBe("ready_for_next_hop");
expect(result.bridge?.loop_state.pending_axes).toEqual([]);
});
it("keeps the same open total loop after organization-only clarification when period is still missing", async () => {
const periodToken = "\u043f\u0435\u0440\u0438\u043e\u0434";
const organizationToken = "\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446";
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "по ООО Альтернатива Плюс",
predecomposeContract: {
entities: { organization: "ООО Альтернатива Плюс" }
},
followupContext: {
previous_discovery_loop_status: "awaiting_clarification",
previous_discovery_loop_selected_chain_id: "value_flow",
previous_discovery_loop_pending_axes: ["organization", "period"],
previous_discovery_loop_provided_axes: ["amount", "coverage_target"],
previous_discovery_loop_asked_domain_family: "counterparty_value",
previous_discovery_loop_asked_action_family: "turnover",
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover"
},
deps: buildDeps([])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_organization_scope: "ООО Альтернатива Плюс"
});
expect(result.turn_input.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
expect(result.bridge?.bridge_status).toBe("needs_clarification");
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow");
expect(result.bridge?.loop_state).toMatchObject({
loop_status: "awaiting_clarification",
selected_chain_id: "value_flow",
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: null
});
expect(result.bridge?.loop_state.pending_axes).toContain("period");
expect(result.bridge?.loop_state.pending_axes).not.toContain("organization");
expect(result.bridge?.answer_draft.next_step_line).toContain(periodToken);
expect(result.bridge?.answer_draft.next_step_line).not.toContain(organizationToken);
});
it("completes the same open total loop after the second clarification provides the period", async () => {
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "Р·Р° 2020 РіРѕРґ",
followupContext: {
previous_discovery_loop_status: "awaiting_clarification",
previous_discovery_loop_selected_chain_id: "value_flow",
previous_discovery_loop_pending_axes: ["period"],
previous_discovery_loop_provided_axes: ["amount", "coverage_target", "organization"],
previous_discovery_loop_asked_domain_family: "counterparty_value",
previous_discovery_loop_asked_action_family: "turnover",
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover",
previous_filters: {
organization: "ООО Альтернатива Плюс"
}
},
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.entry_status).toBe("bridge_executed");
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020"
});
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow");
expect(result.bridge?.pilot.derived_value_flow).toMatchObject({
counterparty: null,
period_scope: "2020",
total_amount: 2900
});
expect(result.bridge?.loop_state.loop_status).toBe("ready_for_next_hop");
expect(result.bridge?.loop_state.pending_axes).toEqual([]);
});
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");
});
it("runs raw organization-scoped incoming totals as an open 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: buildDeps([
{ Period: "2020-01-15T00:00:00", Amount: 2500, Counterparty: "Клиент-А" },
{ Period: "2020-06-20T00:00:00", Amount: 1000, Counterparty: "Клиент-Б" }
])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.discovery_attempted).toBe(true);
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
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");
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
expect(result.bridge?.answer_draft.confirmed_lines.join("\n")).toContain("входящ");
});
it("runs raw organization-scoped outgoing totals as an open payout chain without inventing a counterparty", async () => {
const orgName = "ООО Альтернатива Плюс";
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "сколько исходящих денег за 2020 год по ООО Альтернатива Плюс?",
predecomposeContract: {
entities: { counterparty: orgName, organization: orgName },
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
},
deps: buildDeps([
{ Period: "2020-02-18T00:00:00", Amount: 900, Counterparty: "Поставщик-А" },
{ Period: "2020-08-07T00:00:00", Amount: 300, Counterparty: "Поставщик-Б" }
])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.discovery_attempted).toBe(true);
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "payout",
explicit_organization_scope: orgName,
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");
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_supplier_payout_query_movements_v1");
expect(result.bridge?.answer_draft.confirmed_lines.join("\n")).toContain("исход");
});
it("keeps a generic incoming total in organization clarification instead of asking for counterparty", async () => {
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "сколько входящих денег за 2020 год?",
deps: buildDeps([])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.discovery_attempted).toBe(true);
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_date_scope: "2020"
});
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
expect(result.turn_input.data_need_graph?.clarification_gaps).toEqual(["organization"]);
expect(result.bridge?.planner.selected_chain_id).toBe("value_flow");
expect(result.bridge?.bridge_status).toBe("needs_clarification");
expect(result.bridge?.answer_draft.next_step_line).toContain("\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044e");
expect(result.bridge?.answer_draft.next_step_line).not.toContain(
"\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430"
);
});
it("resumes a generic incoming total after organization clarification without reintroducing a counterparty", async () => {
const orgName = "ООО Альтернатива Плюс";
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
userMessage: "по ООО Альтернатива Плюс",
predecomposeContract: {
entities: { organization: orgName }
},
followupContext: {
previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1",
previous_filters: {
period_from: "2020-01-01",
period_to: "2020-12-31"
}
},
deps: buildDeps([
{ Period: "2020-01-15T00:00:00", Amount: 2500, Counterparty: "Клиент-А" },
{ Period: "2020-06-20T00:00:00", Amount: 1000, Counterparty: "Клиент-Б" }
])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.discovery_attempted).toBe(true);
expect(result.turn_input.source_signal).toBe("followup_context");
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_organization_scope: orgName,
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");
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
expect(result.bridge?.pilot.derived_value_flow).toMatchObject({
counterparty: null,
period_scope: "2020",
total_amount: 3500
});
});
it("overrides a supported exact intent when organization-only follow-up resolves an open-scope total", 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: "\u043f\u043e \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
assistantTurnMeaning: {
asked_domain_family: "counterparty",
asked_action_family: "turnover",
explicit_intent_candidate: "customer_revenue_and_payments"
},
followupContext: {
previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1",
previous_filters: {
period_from: "2020-01-01",
period_to: "2020-12-31"
}
},
deps: buildDeps([
{ Period: "2020-01-15T00:00:00", Amount: 2500, Counterparty: "\u041a\u043b\u0438\u0435\u043d\u0442-\u0410" },
{ Period: "2020-06-20T00:00:00", Amount: 1000, Counterparty: "\u041a\u043b\u0438\u0435\u043d\u0442-\u0411" }
])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.discovery_attempted).toBe(true);
expect(result.turn_input.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_organization_scope: orgName,
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");
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
expect(result.bridge?.pilot.derived_value_flow).toMatchObject({
counterparty: null,
period_scope: "2020",
total_amount: 3500
});
});
});