NODEDC_1C/llm_normalizer/backend/tests/assistantMcpDiscoveryPilotE...

1095 lines
49 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 { planAssistantMcpDiscovery } from "../src/services/assistantMcpDiscoveryPlanner";
import { executeAssistantMcpDiscoveryPilot } from "../src/services/assistantMcpDiscoveryPilotExecutor";
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 buildSequentialDeps(results: Array<{ rows: Array<Record<string, unknown>>; error?: string | null }>) {
const executeAddressMcpQuery = vi.fn(async () => {
const next = results.shift() ?? { rows: [] };
const rows = next.rows;
const error = next.error ?? null;
return {
fetched_rows: rows.length,
matched_rows: error ? 0 : rows.length,
raw_rows: rows,
rows: error ? [] : rows,
error
};
});
return { executeAddressMcpQuery };
}
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 pilot executor", () => {
it("executes only the lifecycle query_documents primitive through injected MCP deps", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_lifecycle",
asked_action_family: "activity_duration",
explicit_entity_candidates: ["SVK"]
}
});
const deps = buildDeps([
{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST", Контрагент: "SVK" },
{ Период: "2023-12-20T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY", Контрагент: "SVK" }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.mcp_execution_performed).toBe(true);
expect(result.executed_primitives).toEqual(["query_documents"]);
expect(result.skipped_primitives).toEqual(["resolve_entity_reference", "probe_coverage", "explain_evidence_basis"]);
expect(result.evidence.evidence_status).toBe("confirmed");
expect(result.evidence.confirmed_facts[0]).toContain("SVK");
expect(result.evidence.inferred_facts[0]).toContain("may be inferred");
expect(result.evidence.unknown_facts).toContain("Legal registration date is not proven by this MCP discovery pilot");
expect(result.source_rows_summary).toBe("2 MCP document rows fetched, 2 matched lifecycle scope");
expect(result.derived_activity_period).toEqual({
first_activity_date: "2020-01-15",
latest_activity_date: "2023-12-20",
matched_rows: 2,
duration_total_months: 47,
duration_years: 3,
duration_months_remainder: 11,
duration_human_ru: "3 года 11 месяцев",
inference_basis: "first_and_latest_confirmed_1c_activity_rows"
});
expect(result.reason_codes).toContain("pilot_query_documents_mcp_executed");
expect(result.reason_codes).toContain("pilot_derived_activity_period_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(1);
const call = deps.executeAddressMcpQuery.mock.calls[0]?.[0];
expect(String(call?.query ?? "")).toContain("Документ.ПоступлениеНаРасчетныйСчет");
expect(call?.limit).toBeGreaterThan(0);
});
it("does not execute MCP when dry-run still needs clarification", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_entity_candidates: ["SVK"]
}
});
const deps = buildDeps([]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("skipped_needs_clarification");
expect(result.mcp_execution_performed).toBe(false);
expect(result.evidence.evidence_status).toBe("insufficient");
expect(deps.executeAddressMcpQuery).not.toHaveBeenCalled();
});
it("uses the explicit selected chain id when choosing the movement pilot scope", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "movements",
asked_action_family: "list_movements",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "movement_evidence"
}
});
const deps = buildDeps([{ Period: "2020-01-15T00:00:00", Amount: 1250, Counterparty: "SVK", Registrar: "Move1" }]);
const result = await executeAssistantMcpDiscoveryPilot(
{
...planner,
reason_codes: planner.reason_codes.filter((code) => !code.startsWith("planner_selected_"))
},
deps
);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("counterparty_movement_evidence_query_movements_v1");
expect(result.executed_primitives).toEqual(["query_movements"]);
});
it("executes generic document evidence through query_documents", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "documents",
asked_action_family: "list_documents",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "document_evidence"
}
});
const deps = buildDeps([
{ Period: "2020-01-15T00:00:00", Counterparty: "SVK", Registrar: "Doc1" },
{ Period: "2020-03-20T00:00:00", Counterparty: "SVK", Registrar: "Doc2" }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("counterparty_document_evidence_query_documents_v1");
expect(result.executed_primitives).toEqual(["query_documents"]);
expect(result.evidence.confirmed_facts).toContain("В 1С найдены строки документов по контрагенту SVK за 2020.");
expect(result.evidence.inferred_facts).toContain(
"Срез документов по контрагенту SVK за 2020 ограничен только подтвержденными строками документов, найденными этим поиском."
);
expect(result.evidence.unknown_facts).toContain(
"Полный исторический срез документов по контрагенту SVK вне периода 2020 этим поиском не подтвержден."
);
expect(result.source_rows_summary).toBe("2 MCP document rows fetched, 2 matched document scope");
});
it("executes generic movement evidence through query_movements without deriving turnover totals", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "movements",
asked_action_family: "list_movements",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "movement_evidence"
}
});
const deps = buildDeps([
{ Period: "2020-01-15T00:00:00", Amount: 1250, Counterparty: "SVK", Registrar: "Move1" },
{ Period: "2020-03-20T00:00:00", Amount: "900,25", Counterparty: "SVK", Registrar: "Move2" }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("counterparty_movement_evidence_query_movements_v1");
expect(result.executed_primitives).toEqual(["query_movements"]);
expect(result.derived_value_flow).toBeNull();
expect(result.derived_bidirectional_value_flow).toBeNull();
expect(result.evidence.confirmed_facts).toContain("В 1С найдены строки движений по контрагенту SVK за 2020.");
expect(result.evidence.inferred_facts).toContain(
"Срез движений по контрагенту SVK за 2020 ограничен только подтвержденными строками движений, найденными этим поиском."
);
expect(result.evidence.unknown_facts).toContain(
"Полный исторический срез движений по контрагенту SVK вне периода 2020 этим поиском не подтвержден."
);
expect(result.source_rows_summary).toBe("2 MCP movement rows fetched, 2 matched movement scope");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(1);
const call = deps.executeAddressMcpQuery.mock.calls[0]?.[0];
expect(String(call?.query ?? "")).toContain("Документ.СписаниеСРасчетногоСчета");
expect(String(call?.query ?? "")).toContain("Документ.ПоступлениеНаРасчетныйСчет");
expect(call?.limit).toBeGreaterThan(0);
});
it("executes inspect_1c_metadata and derives a confirmed metadata surface", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "metadata",
asked_action_family: "inspect_documents",
explicit_entity_candidates: ["НДС"]
}
});
const deps = buildMetadataDeps([
{
FullName: "Документ.СчетФактураВыданный",
MetaType: "Документ",
attributes: [{ Name: "Дата" }, { Name: "Организация" }]
},
{
FullName: "Документ.СчетФактураПолученный",
MetaType: "Документ",
attributes: [{ Name: "Контрагент" }]
}
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("metadata_inspection_v1");
expect(result.mcp_execution_performed).toBe(true);
expect(result.executed_primitives).toEqual(["inspect_1c_metadata"]);
expect(result.evidence.evidence_status).toBe("confirmed");
expect(result.source_rows_summary).toBe("2 MCP metadata rows fetched");
expect(result.derived_metadata_surface).toMatchObject({
metadata_scope: "НДС",
requested_meta_types: ["Документ"],
matched_rows: 2,
available_entity_sets: ["Документ"],
matched_objects: ["Документ.СчетФактураВыданный", "Документ.СчетФактураПолученный"],
selected_entity_set: "Документ",
selected_surface_objects: ["Документ.СчетФактураВыданный", "Документ.СчетФактураПолученный"],
downstream_route_family: "document_evidence",
recommended_next_primitive: "query_documents",
ambiguity_detected: false,
ambiguity_entity_sets: [],
available_fields: ["Дата", "Организация", "Контрагент"],
inference_basis: "confirmed_1c_metadata_surface_rows"
});
expect(result.reason_codes).toContain("pilot_inspect_1c_metadata_mcp_executed");
expect(result.reason_codes).toContain("pilot_derived_metadata_surface_from_confirmed_rows");
expect(deps.executeAddressMcpMetadata).toHaveBeenCalledTimes(1);
expect(deps.executeAddressMcpMetadata.mock.calls[0]?.[0]).toMatchObject({
meta_type: ["Документ"],
name_mask: "НДС"
});
});
it("executes catalog drilldown through a narrowed metadata probe seeded from the confirmed surface object", async () => {
const planner = planAssistantMcpDiscovery({
metadataSurface: {
selected_entity_set: "Catalog",
selected_surface_objects: ["Catalog.Counterparties"],
downstream_route_family: "catalog_drilldown",
route_family_selection_basis: "selected_entity_set",
recommended_next_primitive: "drilldown_related_objects",
ambiguity_detected: false,
ambiguity_entity_sets: []
},
turnMeaning: {
asked_domain_family: "metadata",
asked_action_family: "inspect_catalog",
unsupported_but_understood_family: "schema_surface"
}
});
const deps = buildMetadataDeps([
{
FullName: "Catalog.Counterparties",
MetaType: "Catalog",
attributes: [{ Name: "Description" }]
},
{
FullName: "Catalog.Contracts",
MetaType: "Catalog",
attributes: [{ Name: "Owner" }]
}
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("metadata_inspection_v1");
expect(result.executed_primitives).toEqual(["inspect_1c_metadata"]);
expect(result.derived_metadata_surface).toMatchObject({
metadata_scope: "Counterparties",
requested_meta_types: ["Catalog"],
available_entity_sets: ["Catalog"],
selected_entity_set: "Catalog",
downstream_route_family: "catalog_drilldown",
recommended_next_primitive: "drilldown_related_objects"
});
expect(result.reason_codes).toContain("pilot_catalog_drilldown_metadata_scope_seeded_from_surface_ref");
expect(deps.executeAddressMcpMetadata).toHaveBeenCalledTimes(1);
expect(deps.executeAddressMcpMetadata.mock.calls[0]?.[0]).toMatchObject({
meta_type: ["Catalog"],
name_mask: "Counterparties"
});
});
it("executes the full entity-resolution chain through the checked counterparty catalog slice", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "entity_resolution",
asked_action_family: "search_business_entity",
explicit_entity_candidates: ["Группа СВК"],
unsupported_but_understood_family: "entity_resolution"
}
});
const deps = buildDeps([
{ Counterparty: "Группа СВК", CounterpartyRef: "Ref-1" },
{ Counterparty: "СВК Логистика", CounterpartyRef: "Ref-2" }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("entity_resolution_search_v1");
expect(result.mcp_execution_performed).toBe(true);
expect(result.executed_primitives).toEqual([
"search_business_entity",
"resolve_entity_reference",
"probe_coverage"
]);
expect(result.skipped_primitives).toEqual([]);
expect(result.derived_entity_resolution).toMatchObject({
requested_entity: "Группа СВК",
resolution_status: "resolved",
resolved_entity: "Группа СВК",
resolved_reference: "Ref-1",
confidence: "high",
inference_basis: "catalog_counterparty_search_rows"
});
expect(result.evidence.confirmed_facts).toContain(
"В проверенном каталожном срезе 1С найден контрагент: Группа СВК"
);
expect(result.evidence.inferred_facts).toContain(
"Пока проверено только заземление сущности по каталогу 1С; документы, движения и денежные показатели еще не проверялись"
);
expect(result.evidence.unknown_facts).toContain(
"Документы, движения и денежные показатели по этому контрагенту еще не проверялись; пока был только каталожный поиск"
);
expect(result.reason_codes).toContain("pilot_search_business_entity_mcp_executed");
expect(result.reason_codes).toContain("pilot_resolve_entity_reference_from_catalog_rows");
expect(result.reason_codes).toContain("pilot_probe_coverage_executed_for_entity_resolution");
expect(result.reason_codes).toContain("pilot_entity_resolution_grounding_stable_for_downstream_probe");
expect(result.reason_codes).toContain("pilot_derived_entity_resolution_from_catalog_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(1);
});
it("keeps entity-resolution honest when several catalog candidates remain ambiguous", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "entity_resolution",
asked_action_family: "search_business_entity",
explicit_entity_candidates: ["СВК"],
unsupported_but_understood_family: "entity_resolution"
}
});
const deps = buildDeps([
{ Counterparty: "СВК-А", CounterpartyRef: "Ref-1" },
{ Counterparty: "СВК-Б", CounterpartyRef: "Ref-2" }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("entity_resolution_search_v1");
expect(result.executed_primitives).toEqual([
"search_business_entity",
"resolve_entity_reference",
"probe_coverage"
]);
expect(result.skipped_primitives).toEqual([]);
expect(result.derived_entity_resolution).toMatchObject({
requested_entity: "СВК",
resolution_status: "ambiguous",
resolved_entity: null,
ambiguity_candidates: ["СВК-А", "СВК-Б"],
confidence: "low"
});
expect(result.evidence.confirmed_facts).toEqual([]);
expect(result.evidence.unknown_facts).toContain(
"Точное заземление контрагента в 1С остается неоднозначным между вариантами: СВК-А, СВК-Б"
);
expect(result.reason_codes).toContain("pilot_resolve_entity_reference_requires_clarification");
expect(result.reason_codes).toContain("pilot_probe_coverage_executed_for_entity_resolution");
expect(result.reason_codes).toContain("pilot_entity_resolution_ambiguity_requires_clarification");
});
it("keeps metadata grounding ambiguous when several surface families compete", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "metadata",
asked_action_family: "inspect_fields",
explicit_entity_candidates: ["НДС"]
}
});
const deps = buildMetadataDeps([
{
FullName: "Документ.СчетФактураВыданный",
MetaType: "Документ",
attributes: [{ Name: "Дата" }]
},
{
FullName: "РегистрНакопления.НДСПокупок",
MetaType: "РегистрНакопления",
resources: [{ Name: "СуммаНДС" }]
}
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.derived_metadata_surface).toMatchObject({
metadata_scope: "НДС",
available_entity_sets: ["Документ", "РегистрНакопления"],
selected_entity_set: null,
downstream_route_family: null,
recommended_next_primitive: null,
ambiguity_detected: true,
ambiguity_entity_sets: ["Документ", "РегистрНакопления"]
});
expect(result.evidence.inferred_facts).toEqual([]);
expect(result.evidence.unknown_facts).toContain(
"Exact downstream metadata surface remains ambiguous across: Документ, РегистрНакопления"
);
});
it("selects a downstream lane from dominant metadata surface objects when one family clearly prevails", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "metadata",
asked_action_family: "inspect_surface",
explicit_entity_candidates: ["НДС"]
}
});
const deps = buildMetadataDeps([
{
FullName: "Документ.СчетФактураВыданный",
MetaType: "Документ",
attributes: [{ Name: "Дата" }]
},
{
FullName: "Документ.СчетФактураПолученный",
MetaType: "Документ",
attributes: [{ Name: "Контрагент" }]
},
{
FullName: "РегистрНакопления.НДСПокупок",
MetaType: "РегистрНакопления",
resources: [{ Name: "СуммаНДС" }]
}
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.derived_metadata_surface).toMatchObject({
metadata_scope: "НДС",
available_entity_sets: ["Документ", "РегистрНакопления"],
selected_entity_set: null,
selected_surface_objects: [
"Документ.СчетФактураВыданный",
"Документ.СчетФактураПолученный"
],
surface_family_scores: {
document_evidence: 2,
movement_evidence: 1,
catalog_drilldown: 0
},
downstream_route_family: "document_evidence",
route_family_selection_basis: "dominant_surface_objects",
recommended_next_primitive: "query_documents",
ambiguity_detected: false,
ambiguity_entity_sets: []
});
expect(result.reason_codes).toContain("pilot_selected_metadata_route_family_from_dominant_surface_objects");
expect(result.evidence.inferred_facts).toContain(
"A likely next checked lane may be inferred as document_evidence from the confirmed metadata surface"
);
expect(result.evidence.unknown_facts).not.toContain(
"Exact downstream metadata surface remains ambiguous across: Документ, РегистрНакопления"
);
});
it("can break a weak metadata family tie by ranking surface objects against the requested scope", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "metadata",
asked_action_family: "inspect_surface",
explicit_entity_candidates: ["НДС"]
}
});
const deps = buildMetadataDeps([
{
FullName: "Document.НДССчетФактура",
MetaType: "Document",
attributes: [{ Name: "Дата" }]
},
{
FullName: "AccumulationRegister.BankOperations",
MetaType: "AccumulationRegister",
resources: [{ Name: "Amount" }]
}
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.derived_metadata_surface).toMatchObject({
metadata_scope: "НДС",
available_entity_sets: ["Document", "AccumulationRegister"],
selected_entity_set: null,
selected_surface_objects: ["Document.НДССчетФактура"],
surface_family_scores: {
document_evidence: 1,
movement_evidence: 1,
catalog_drilldown: 0
},
downstream_route_family: "document_evidence",
route_family_selection_basis: "dominant_surface_objects",
recommended_next_primitive: "query_documents",
ambiguity_detected: false,
ambiguity_entity_sets: [],
surface_object_ranking_applied: true
});
expect(result.reason_codes).toContain("pilot_selected_metadata_route_family_from_surface_object_ranking");
});
it("keeps metadata ambiguity unresolved when surface-family scores are nearly tied", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "metadata",
asked_action_family: "inspect_surface",
explicit_entity_candidates: ["НДС"]
}
});
const deps = buildMetadataDeps([
{ FullName: "Документ.A", MetaType: "Документ" },
{ FullName: "Документ.B", MetaType: "Документ" },
{ FullName: "Документ.C", MetaType: "Документ" },
{ FullName: "Документ.D", MetaType: "Документ" },
{ FullName: "РегистрНакопления.A", MetaType: "РегистрНакопления" },
{ FullName: "РегистрНакопления.B", MetaType: "РегистрНакопления" },
{ FullName: "РегистрНакопления.C", MetaType: "РегистрНакопления" }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.derived_metadata_surface).toMatchObject({
metadata_scope: "НДС",
selected_entity_set: null,
surface_family_scores: {
document_evidence: 4,
movement_evidence: 3,
catalog_drilldown: 0
},
downstream_route_family: null,
route_family_selection_basis: null,
recommended_next_primitive: null,
ambiguity_detected: true
});
expect(result.derived_metadata_surface?.ambiguity_entity_sets).toContain("Документ");
expect(result.derived_metadata_surface?.ambiguity_entity_sets).toContain("РегистрНакопления");
expect(result.reason_codes).not.toContain("pilot_selected_metadata_route_family_from_dominant_surface_objects");
expect(result.evidence.unknown_facts).toContain(
"Exact downstream metadata surface remains ambiguous across: Документ, РегистрНакопления"
);
});
it("infers metadata entity-set families from object names when meta type columns are absent", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "metadata",
asked_action_family: "inspect_surface",
explicit_entity_candidates: ["НДС"]
}
});
const deps = buildMetadataDeps([
{
FullName: "Документ.СчетФактураВыданный",
attributes: [{ Name: "Дата" }]
},
{
FullName: "РегистрНакопления.НДСПокупок",
resources: [{ Name: "СуммаНДС" }]
},
{
FullName: "Справочник.КодыОперацийНДС"
}
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.derived_metadata_surface).toMatchObject({
metadata_scope: "НДС",
available_entity_sets: ["Документ", "РегистрНакопления", "Справочник"],
selected_entity_set: null,
downstream_route_family: null,
recommended_next_primitive: null,
ambiguity_detected: true,
ambiguity_entity_sets: ["Документ", "РегистрНакопления", "Справочник"]
});
expect(result.evidence.unknown_facts).toContain(
"Exact downstream metadata surface remains ambiguous across: Документ, РегистрНакопления, Справочник"
);
});
it("executes value-flow query_movements and derives a guarded turnover sum", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020"
}
});
const deps = buildDeps([
{ Period: "2020-01-15T00:00:00", Amount: 1250, Counterparty: "SVK" },
{ Period: "2020-02-20T00:00:00", Amount: "2 500,50", Counterparty: "SVK" }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
expect(result.mcp_execution_performed).toBe(true);
expect(result.executed_primitives).toEqual(["query_movements"]);
expect(result.skipped_primitives).toEqual(["resolve_entity_reference", "aggregate_by_axis", "probe_coverage"]);
expect(result.evidence.evidence_status).toBe("confirmed");
expect(result.evidence.confirmed_facts[0]).toContain("value-flow rows");
expect(result.source_rows_summary).toBe("2 MCP value-flow rows fetched, 2 matched value-flow scope");
expect(result.derived_value_flow).toMatchObject({
counterparty: "SVK",
period_scope: "2020",
rows_matched: 2,
rows_with_amount: 2,
total_amount: 3750.5,
coverage_limited_by_probe_limit: false,
first_movement_date: "2020-01-15",
latest_movement_date: "2020-02-20",
inference_basis: "sum_of_confirmed_1c_value_flow_rows"
});
expect(result.reason_codes).toContain("pilot_query_movements_mcp_executed");
expect(result.reason_codes).toContain("pilot_derived_value_flow_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(1);
const call = deps.executeAddressMcpQuery.mock.calls[0]?.[0];
expect(String(call?.query ?? "")).toContain("ПоступлениеНаРасчетныйСчет");
expect(call?.limit).toBeGreaterThan(0);
});
it("executes supplier payout query_movements and derives a guarded outgoing payment sum", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "payout",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_payouts_or_outflow"
}
});
const deps = buildDeps([
{ Period: "2020-03-15T00:00:00", Amount: 4100, Counterparty: "SVK" },
{ Period: "2020-04-20T00:00:00", Amount: "900,25", Counterparty: "SVK" }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("counterparty_supplier_payout_query_movements_v1");
expect(result.derived_value_flow).toMatchObject({
value_flow_direction: "outgoing_supplier_payout",
counterparty: "SVK",
period_scope: "2020",
rows_matched: 2,
rows_with_amount: 2,
total_amount: 5000.25,
coverage_limited_by_probe_limit: false,
first_movement_date: "2020-03-15",
latest_movement_date: "2020-04-20"
});
expect(result.evidence.confirmed_facts[0]).toContain("supplier-payout rows");
expect(result.evidence.inferred_facts[0]).toContain("supplier-payout total");
expect(result.evidence.unknown_facts).toContain("Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot");
expect(result.reason_codes).toContain("pilot_supplier_payout_recipe_selected");
const call = deps.executeAddressMcpQuery.mock.calls[0]?.[0];
expect(String(call?.query ?? "")).toContain("СписаниеСРасчетногоСчета");
});
it("marks value-flow coverage as limited when the probe row limit is reached", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "payout",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_payouts_or_outflow"
}
});
const rows = Array.from({ length: 100 }, (_, index) => ({
Period: `2020-01-${String((index % 28) + 1).padStart(2, "0")}T00:00:00`,
Amount: 10,
Counterparty: "SVK"
}));
const result = await executeAssistantMcpDiscoveryPilot(planner, buildDeps(rows));
expect(result.derived_value_flow?.coverage_limited_by_probe_limit).toBe(true);
expect(result.evidence.unknown_facts).toContain(
"Complete requested-period coverage is not proven by the available checked rows"
);
});
it("recovers yearly value-flow coverage by splitting a limited broad probe into monthly subprobes", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "payout",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_payouts_or_outflow"
}
});
const broadRows = Array.from({ length: 100 }, (_, index) => ({
Period: `2020-01-${String((index % 28) + 1).padStart(2, "0")}T00:00:00`,
Amount: 10,
Counterparty: "SVK"
}));
const monthlyResults = Array.from({ length: 12 }, (_, index) => ({
rows: [
{
Period: `2020-${String(index + 1).padStart(2, "0")}-05T00:00:00`,
Amount: (index + 1) * 100,
Counterparty: "SVK"
}
]
}));
const result = await executeAssistantMcpDiscoveryPilot(
planner,
buildSequentialDeps([{ rows: broadRows }, ...monthlyResults])
);
expect(result.derived_value_flow).toMatchObject({
value_flow_direction: "outgoing_supplier_payout",
coverage_limited_by_probe_limit: false,
coverage_recovered_by_period_chunking: true,
period_chunking_granularity: "month",
rows_matched: 12,
rows_with_amount: 12,
total_amount: 7800,
first_movement_date: "2020-01-05",
latest_movement_date: "2020-12-05"
});
expect(result.evidence.inferred_facts).toContain(
"Requested period coverage was recovered through monthly 1C value-flow probes"
);
expect(result.evidence.unknown_facts).not.toContain(
"Complete requested-period coverage is not proven by the available checked rows"
);
expect(result.reason_codes).toContain("pilot_monthly_period_chunking_recovered_coverage");
});
it("executes bidirectional value-flow queries and derives guarded net cash flow", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "net_value_flow",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting"
}
});
const deps = buildSequentialDeps([
{
rows: [
{ Period: "2020-01-15T00:00:00", Amount: 10000, Counterparty: "SVK" },
{ Period: "2020-02-20T00:00:00", Amount: "2 500,50", Counterparty: "SVK" }
]
},
{
rows: [{ Period: "2020-03-10T00:00:00", Amount: 4000, Counterparty: "SVK" }]
}
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("counterparty_bidirectional_value_flow_query_movements_v1");
expect(result.mcp_execution_performed).toBe(true);
expect(result.executed_primitives).toEqual(["query_movements"]);
expect(result.derived_value_flow).toBeNull();
expect(result.derived_bidirectional_value_flow).toMatchObject({
counterparty: "SVK",
period_scope: "2020",
net_amount: 8500.5,
net_amount_human_ru: "8 500,50 руб.",
net_direction: "net_incoming",
coverage_limited_by_probe_limit: false,
incoming_customer_revenue: {
rows_matched: 2,
rows_with_amount: 2,
total_amount: 12500.5,
first_movement_date: "2020-01-15",
latest_movement_date: "2020-02-20"
},
outgoing_supplier_payout: {
rows_matched: 1,
rows_with_amount: 1,
total_amount: 4000,
first_movement_date: "2020-03-10",
latest_movement_date: "2020-03-10"
}
});
expect(result.evidence.confirmed_facts[0]).toContain("bidirectional value-flow rows");
expect(result.evidence.inferred_facts[0]).toContain("net value-flow");
expect(result.evidence.unknown_facts).toContain(
"Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot"
);
expect(result.reason_codes).toContain("pilot_bidirectional_value_flow_recipes_selected");
expect(result.reason_codes).toContain("pilot_derived_bidirectional_value_flow_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(2);
});
it("derives monthly bidirectional value-flow breakdown when the turn explicitly asks by month", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "net_value_flow",
asked_aggregation_axis: "month",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting"
}
});
const deps = buildSequentialDeps([
{
rows: [
{ Period: "2020-01-15T00:00:00", Amount: 10000, Counterparty: "SVK" },
{ Period: "2020-02-20T00:00:00", Amount: "2 500,50", Counterparty: "SVK" }
]
},
{
rows: [
{ Period: "2020-01-10T00:00:00", Amount: 4000, Counterparty: "SVK" },
{ Period: "2020-02-11T00:00:00", Amount: 1000, Counterparty: "SVK" }
]
}
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.derived_bidirectional_value_flow?.aggregation_axis).toBe("month");
expect(result.derived_bidirectional_value_flow?.monthly_breakdown).toMatchObject([
{
month_bucket: "2020-01",
incoming_total_amount: 10000,
incoming_rows_with_amount: 1,
outgoing_total_amount: 4000,
outgoing_rows_with_amount: 1,
net_amount: 6000,
net_direction: "net_incoming"
},
{
month_bucket: "2020-02",
incoming_total_amount: 2500.5,
incoming_rows_with_amount: 1,
outgoing_total_amount: 1000,
outgoing_rows_with_amount: 1,
net_amount: 1500.5,
net_direction: "net_incoming"
}
]);
expect(result.derived_bidirectional_value_flow?.monthly_breakdown[0]?.incoming_total_amount_human_ru).toContain("10 000");
expect(result.derived_bidirectional_value_flow?.monthly_breakdown[0]?.net_amount_human_ru).toContain("6 000");
expect(result.derived_bidirectional_value_flow?.monthly_breakdown[1]?.incoming_total_amount_human_ru).toContain("2 500,50");
expect(result.derived_bidirectional_value_flow?.monthly_breakdown[1]?.net_amount_human_ru).toContain("1 500,50");
expect(result.evidence.inferred_facts).toContain(
"Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows"
);
expect(result.reason_codes).toContain("pilot_derived_bidirectional_monthly_breakdown_from_confirmed_rows");
});
it("recovers bidirectional yearly coverage when one side is rebuilt from monthly subprobes", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "net_value_flow",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting"
}
});
const outgoingBroadRows = Array.from({ length: 100 }, (_, index) => ({
Period: `2020-01-${String((index % 28) + 1).padStart(2, "0")}T00:00:00`,
Amount: 10,
Counterparty: "SVK"
}));
const outgoingMonthlyResults = Array.from({ length: 12 }, (_, index) => ({
rows: [
{
Period: `2020-${String(index + 1).padStart(2, "0")}-10T00:00:00`,
Amount: (index + 1) * 50,
Counterparty: "SVK"
}
]
}));
const deps = buildSequentialDeps([
{
rows: [
{ Period: "2020-01-15T00:00:00", Amount: 10000, Counterparty: "SVK" },
{ Period: "2020-02-20T00:00:00", Amount: 10000, Counterparty: "SVK" }
]
},
{ rows: outgoingBroadRows },
...outgoingMonthlyResults
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.derived_bidirectional_value_flow).toMatchObject({
coverage_limited_by_probe_limit: false,
coverage_recovered_by_period_chunking: true,
period_chunking_granularity: "month",
net_amount: 16100,
incoming_customer_revenue: {
total_amount: 20000,
coverage_limited_by_probe_limit: false
},
outgoing_supplier_payout: {
total_amount: 3900,
coverage_limited_by_probe_limit: false,
coverage_recovered_by_period_chunking: true,
period_chunking_granularity: "month"
}
});
expect(result.evidence.inferred_facts).toContain(
"Requested period coverage for bidirectional value-flow was recovered through monthly 1C side probes"
);
expect(result.evidence.unknown_facts).not.toContain(
"Complete requested-period coverage for bidirectional value-flow is not proven by the available checked rows"
);
expect(result.reason_codes).toContain("pilot_bidirectional_outgoing_monthly_period_chunking_recovered_coverage");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(14);
});
it("executes document-ready plans through the dedicated document pilot", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_documents",
asked_action_family: "list_documents",
explicit_entity_candidates: ["SVK"]
}
});
const deps = buildDeps([]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("counterparty_document_evidence_query_documents_v1");
expect(result.mcp_execution_performed).toBe(true);
expect(result.executed_primitives).toEqual(["query_documents"]);
expect(result.reason_codes).toContain("pilot_query_documents_mcp_executed");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(1);
});
it("keeps document and movement evidence scoped to the resolved entity and checked period", async () => {
const documentPlanner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "documents",
asked_action_family: "list_documents",
explicit_entity_candidates: ["Группа СВК"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "document_evidence"
}
});
const documentResult = await executeAssistantMcpDiscoveryPilot(
documentPlanner,
buildDeps([{ Period: "2020-01-15T00:00:00", Counterparty: "Группа СВК", Registrar: "Doc1" }])
);
expect(documentResult.evidence.confirmed_facts).toContain(
"В 1С найдены строки документов по контрагенту Группа СВК за 2020."
);
expect(documentResult.evidence.inferred_facts).toContain(
"Срез документов по контрагенту Группа СВК за 2020 ограничен только подтвержденными строками документов, найденными этим поиском."
);
expect(documentResult.evidence.unknown_facts).toContain(
"Полный исторический срез документов по контрагенту Группа СВК вне периода 2020 этим поиском не подтвержден."
);
const movementPlanner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "movements",
asked_action_family: "list_movements",
explicit_entity_candidates: ["Группа СВК"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "movement_evidence"
}
});
const movementResult = await executeAssistantMcpDiscoveryPilot(
movementPlanner,
buildDeps([{ Period: "2020-01-15T00:00:00", Counterparty: "Группа СВК", Registrar: "Move1" }])
);
expect(movementResult.evidence.confirmed_facts).toContain(
"В 1С найдены строки движений по контрагенту Группа СВК за 2020."
);
expect(movementResult.evidence.inferred_facts).toContain(
"Срез движений по контрагенту Группа СВК за 2020 ограничен только подтвержденными строками движений, найденными этим поиском."
);
expect(movementResult.evidence.unknown_facts).toContain(
"Полный исторический срез движений по контрагенту Группа СВК вне периода 2020 этим поиском не подтвержден."
);
});
it("records MCP errors as limitations without converting them into facts", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_lifecycle",
asked_action_family: "activity_duration",
explicit_entity_candidates: ["SVK"]
}
});
const deps = buildDeps([], "MCP fetch failed: timeout");
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.mcp_execution_performed).toBe(true);
expect(result.evidence.evidence_status).toBe("insufficient");
expect(result.evidence.confirmed_facts).toEqual([]);
expect(result.derived_activity_period).toBeNull();
expect(result.query_limitations).toContain("MCP fetch failed: timeout");
expect(result.reason_codes).toContain("pilot_query_documents_mcp_error");
});
it("emits Russian confirmed and bounded facts for resolved entity grounding", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "entity_resolution",
asked_action_family: "search_business_entity",
explicit_entity_candidates: ["\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a"],
unsupported_but_understood_family: "entity_resolution"
}
});
const deps = buildDeps([
{ Counterparty: "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a", CounterpartyRef: "Ref-1" },
{ Counterparty: "\u0421\u0412\u041a \u041b\u043e\u0433\u0438\u0441\u0442\u0438\u043a\u0430", CounterpartyRef: "Ref-2" }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.evidence.confirmed_facts.join("\n")).toContain("\u043d\u0430\u0439\u0434\u0435\u043d \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442");
expect(result.evidence.confirmed_facts.join("\n")).toContain("\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a");
expect(result.evidence.inferred_facts.join("\n")).toContain(
"\u041f\u043e\u043a\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u0437\u0435\u043c\u043b\u0435\u043d\u0438\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u043f\u043e \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0443 1\u0421"
);
expect(result.evidence.unknown_facts.join("\n")).toContain(
"\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b, \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u0438 \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u0438"
);
});
it("emits Russian ambiguity boundaries for entity grounding", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "entity_resolution",
asked_action_family: "search_business_entity",
explicit_entity_candidates: ["\u0421\u0412\u041a"],
unsupported_but_understood_family: "entity_resolution"
}
});
const deps = buildDeps([
{ Counterparty: "\u0421\u0412\u041a-\u0410", CounterpartyRef: "Ref-1" },
{ Counterparty: "\u0421\u0412\u041a-\u0411", CounterpartyRef: "Ref-2" }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.evidence.confirmed_facts).toEqual([]);
expect(result.evidence.unknown_facts.join("\n")).toContain(
"\u0422\u043e\u0447\u043d\u043e\u0435 \u0437\u0430\u0437\u0435\u043c\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430 \u0432 1\u0421 \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u043d\u0435\u043e\u0434\u043d\u043e\u0437\u043d\u0430\u0447\u043d\u044b\u043c"
);
expect(result.evidence.unknown_facts.join("\n")).toContain("\u0421\u0412\u041a-\u0410");
expect(result.evidence.unknown_facts.join("\n")).toContain("\u0421\u0412\u041a-\u0411");
});
});