import { describe, expect, it, vi } from "vitest"; import { buildAssistantMcpDiscoveryAnswerDraft } from "../src/services/assistantMcpDiscoveryAnswerAdapter"; import { executeAssistantMcpDiscoveryPilot } from "../src/services/assistantMcpDiscoveryPilotExecutor"; import { planAssistantMcpDiscovery } from "../src/services/assistantMcpDiscoveryPlanner"; function buildDeps(rows: Array>, 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>; 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 buildCustomQueryDeps(result: { fetched_rows: number; matched_rows: number; rows: Array>; raw_rows?: Array>; error?: string | null; }) { return { executeAddressMcpQuery: vi.fn(async () => ({ fetched_rows: result.fetched_rows, matched_rows: result.matched_rows, rows: result.rows, raw_rows: result.raw_rows ?? result.rows, error: result.error ?? null })) }; } function buildMetadataDeps(rows: Array>, 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 answer adapter", () => { it("turns confirmed lifecycle evidence into a human-safe bounded answer draft", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] } }); const pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST", Контрагент: "SVK" }]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.internal_mechanics_allowed).toBe(false); expect(draft.headline).toContain("подтвержденная активность"); expect(draft.confirmed_lines[0]).toContain("SVK"); expect(draft.inference_lines[0]).toContain("меньше месяца"); expect(draft.inference_lines.join("\n")).toContain("Первая найденная активность: 2020-01-15"); expect(draft.inference_lines.join("\n")).toContain("не юридически подтвержденный возраст регистрации"); expect(draft.unknown_lines).toContain("Legal registration date is not proven by this MCP discovery pilot"); expect(draft.must_not_claim).toContain("Do not present inferred activity duration as a formally confirmed legal fact."); expect(draft.reason_codes).toContain("answer_contains_unknown_fact_boundary"); }); it("uses checked-sources mode when MCP failed and avoids confirmed facts", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] } }); const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([], "MCP fetch failed: timeout")); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("checked_sources_only"); expect(draft.confirmed_lines).toEqual([]); expect(draft.limitation_lines).toContain("MCP fetch failed: timeout"); expect(draft.next_step_line).toContain("MCP"); expect(draft.must_not_claim).toContain("Do not claim a confirmed business fact when confirmed_facts is empty."); }); it("turns generic document evidence into a bounded document answer draft", 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 pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([{ Period: "2020-01-15T00:00:00", Counterparty: "SVK", Registrar: "Doc1" }]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.headline).toContain("документ"); expect(draft.headline).toContain("2020"); expect(draft.headline).toContain("SVK"); expect(draft.confirmed_lines).toContain("В 1С найдены строки документов по контрагенту SVK за 2020."); expect(draft.inference_lines).toContain( "Срез документов по контрагенту SVK за 2020 ограничен только подтвержденными строками документов, найденными этим поиском." ); expect(draft.unknown_lines).toContain( "Полный исторический срез документов по контрагенту SVK вне периода 2020 этим поиском не подтвержден." ); expect(draft.must_not_claim).toContain("Do not claim full document history outside the checked period."); }); it("turns generic movement evidence into a bounded movement answer draft", 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 pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([{ Period: "2020-01-15T00:00:00", Counterparty: "SVK", Registrar: "Move1" }]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.headline).toContain("движени"); expect(draft.headline).toContain("2020"); expect(draft.headline).toContain("SVK"); expect(draft.confirmed_lines).toContain("В 1С найдены строки движений по контрагенту SVK за 2020."); expect(draft.inference_lines).toContain( "Срез движений по контрагенту SVK за 2020 ограничен только подтвержденными строками движений, найденными этим поиском." ); expect(draft.unknown_lines).toContain( "Полный исторический срез движений по контрагенту SVK вне периода 2020 этим поиском не подтвержден." ); expect(draft.must_not_claim).toContain("Do not claim full movement history outside the checked period."); expect(draft.must_not_claim).toContain("Do not present the confirmed movement rows as a complete movement universe."); }); it("keeps bounded-only movement answers tied to the resolved entity and checked period", async () => { const planner = 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 pilot = await executeAssistantMcpDiscoveryPilot( planner, buildCustomQueryDeps({ fetched_rows: 100, matched_rows: 0, rows: [], raw_rows: [{ Period: "2020-06-30T00:00:00", Counterparty: "Группа СВК", Registrar: "Move1" }] }) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("bounded_inference_only"); expect(draft.headline).toContain("движени"); expect(draft.headline).toContain("Группа СВК"); expect(draft.headline).toContain("2020"); expect(draft.inference_lines).toContain( "По движениям по контрагенту Группа СВК за 2020 удалось проверить только ограниченный срез 1С; подтвержденных строк движений этим поиском не найдено." ); expect(draft.unknown_lines).toContain( "Полный исторический срез движений по контрагенту Группа СВК вне периода 2020 этим поиском не подтвержден." ); }); it("asks for clarification when discovery did not execute due to missing scope", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["SVK"] } }); const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([])); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("needs_clarification"); expect(draft.headline).toBe("Нужно уточнить контекст перед поиском в 1С."); expect(draft.next_step_line).toContain("Уточните контрагента"); expect(draft.must_not_claim).toContain("Do not claim rows were checked when mcp_execution_performed=false."); }); it("asks for an explicit lane choice when mixed metadata ambiguity cannot continue on a neutral follow-up", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "metadata", asked_action_family: "resolve_next_lane", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "metadata_lane_choice_clarification" } }); const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([])); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("needs_clarification"); expect(draft.headline).toContain("data-lane"); expect(draft.next_step_line).toContain("по документам"); expect(draft.next_step_line).toContain("по движениям/регистрам"); expect(draft.must_not_claim).toContain("Do not claim rows were checked when mcp_execution_performed=false."); }); it("keeps movement clarification anchored to the chosen lane after metadata ambiguity was resolved", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "movements", asked_action_family: "list_movements", explicit_entity_candidates: ["НДС"], unsupported_but_understood_family: "movement_evidence" } }); const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([])); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("needs_clarification"); expect(draft.headline).toContain("движениям/регистрам"); expect(draft.headline).toContain("НДС"); expect(draft.headline).toContain("период"); expect(draft.next_step_line).toContain("движениям/регистрам"); expect(draft.next_step_line).toContain("НДС"); expect(draft.next_step_line).toContain("период"); }); it("turns resolved entity grounding into a human-safe entity search answer draft", 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 pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([ { Counterparty: "Группа СВК", CounterpartyRef: "Ref-1" }, { Counterparty: "СВК Логистика", CounterpartyRef: "Ref-2" } ]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.headline).toContain("вероятный контрагент"); expect(draft.confirmed_lines.join("\n")).toContain("Группа СВК"); expect(draft.inference_lines.join("\n")).toContain("заземление сущности"); expect(draft.next_step_line).toContain("искать документы, движения или денежный поток"); expect(draft.must_not_claim).toContain( "Do not present catalog grounding as confirmed business activity, turnover, or document evidence." ); }); it("asks for clarification when entity grounding stays 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 pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([ { Counterparty: "СВК-А", CounterpartyRef: "Ref-1" }, { Counterparty: "СВК-Б", CounterpartyRef: "Ref-2" } ]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("needs_clarification"); expect(draft.headline).toContain("несколько похожих контрагентов"); expect(draft.inference_lines.join("\n")).toContain("СВК-А"); expect(draft.inference_lines.join("\n")).toContain("1. СВК-А"); expect(draft.inference_lines.join("\n")).toContain("2. СВК-Б"); expect(draft.next_step_line).toContain("какой именно контрагент нужен"); expect(draft.next_step_line).toContain("1. СВК-А"); expect(draft.next_step_line).toContain("2. СВК-Б"); expect(draft.next_step_line).toContain("номером варианта"); }); it.skip("keeps entity search honest when no catalog candidate was confirmed", 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 pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([{ Counterparty: "Группа СВК", CounterpartyRef: "Ref-1" }]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("checked_sources_only"); expect(draft.headline).toContain("точный контрагент пока не подтвержден"); expect(draft.unknown_lines).toContain( 'No counterparty matching "Несуществующий Контрагент" was confirmed in the checked 1C catalog slice' ); expect(draft.next_step_line).toContain("Дайте точное название или ИНН"); }); it("turns metadata surface evidence into a human-safe metadata answer draft", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "metadata", asked_action_family: "inspect_documents", explicit_entity_candidates: ["НДС"] } }); const pilot = await executeAssistantMcpDiscoveryPilot( planner, buildMetadataDeps([ { FullName: "Документ.СчетФактураВыданный", MetaType: "Документ", attributes: [{ Name: "Дата" }, { Name: "Организация" }] } ]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); const confirmedText = draft.confirmed_lines.join("\n"); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.headline).toContain("заземлена вероятная поверхность"); expect(confirmedText).toContain("Подтвержденная metadata-поверхность 1С"); expect(confirmedText).toContain("Документ.СчетФактураВыданный"); expect(confirmedText).toContain("Выбранное family: Документ"); expect(confirmedText).toContain("Дата"); expect(draft.inference_lines.join("\n")).toContain("контур документов"); expect(draft.next_step_line).toContain("surface «Документ»"); expect(draft.must_not_claim).toContain("Do not present metadata surface as confirmed business data rows."); expect(draft.must_not_claim).toContain("Do not present the inferred next checked lane as already executed data retrieval."); }); it("keeps metadata answer honest when schema surface stays ambiguous", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "metadata", asked_action_family: "inspect_fields", explicit_entity_candidates: ["НДС"] } }); const pilot = await executeAssistantMcpDiscoveryPilot( planner, buildMetadataDeps([ { FullName: "Документ.СчетФактураВыданный", MetaType: "Документ", attributes: [{ Name: "Дата" }] }, { FullName: "РегистрНакопления.НДСПокупок", MetaType: "РегистрНакопления", resources: [{ Name: "СуммаНДС" }] } ]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.headline).toContain("конкурирующие schema-поверхности"); expect(draft.inference_lines.join("\n")).toContain("несколько конкурирующих family"); expect(draft.unknown_lines).toContain( "Exact downstream metadata surface remains ambiguous across: Документ, РегистрНакопления" ); expect(draft.next_step_line).toContain("Документ, РегистрНакопления"); }); it("turns value-flow evidence into a bounded turnover answer draft", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020" } }); const pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([ { Period: "2020-01-15T00:00:00", Amount: 1250, Counterparty: "SVK" }, { Period: "2020-02-20T00:00:00", Amount: "2 500,50", Counterparty: "SVK" } ]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); const confirmedText = draft.confirmed_lines.join("\n"); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.headline).toContain("входящих денежных поступлений"); expect(confirmedText).toContain("3 750,50 руб."); expect(confirmedText).toContain("входящих денежных поступлений"); expect(confirmedText).toContain("2020-01-15"); expect(confirmedText).toContain("2020-02-20"); expect(draft.unknown_lines).toContain("Full turnover outside the checked period is not proven by this MCP discovery pilot"); expect(draft.must_not_claim).toContain("Do not claim full all-time turnover unless the checked period and coverage prove it."); expect(draft.limitation_lines.join("\n")).not.toContain("pilot_"); }); it("turns supplier payout evidence into a bounded outgoing payment answer draft", 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 pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([ { Period: "2020-03-15T00:00:00", Amount: 4100, Counterparty: "SVK" }, { Period: "2020-04-20T00:00:00", Amount: "900,25", Counterparty: "SVK" } ]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); const confirmedText = draft.confirmed_lines.join("\n"); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.headline).toContain("исходящих платежей"); expect(confirmedText).toContain("исходящих платежей/списаний"); expect(confirmedText).toContain("5 000,25 руб."); expect(draft.inference_lines.join("\n")).toContain("supplier-payout total"); expect(draft.unknown_lines).toContain("Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot"); }); it("turns bidirectional value-flow evidence into a bounded net cash answer draft", 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 pilot = await executeAssistantMcpDiscoveryPilot( planner, 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 draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); const confirmedText = draft.confirmed_lines.join("\n"); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.headline).toContain("входящих и исходящих денежных движений"); expect(confirmedText).toContain("получили 12 500,50 руб."); expect(confirmedText).toContain("заплатили 4 000 руб."); expect(confirmedText).toContain("нетто в нашу сторону: 8 500,50 руб."); expect(draft.inference_lines.join("\n")).toContain("net value-flow"); expect(draft.unknown_lines).toContain("Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot"); expect(draft.must_not_claim).toContain("Do not present a derived sum as a legal/accounting final total outside the checked 1C rows."); }); it("renders monthly bidirectional breakdown lines when the turn explicitly asked 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 pilot = await executeAssistantMcpDiscoveryPilot( planner, 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 draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); const confirmedText = draft.confirmed_lines.join("\n"); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.headline).toContain("помесяч"); expect(confirmedText).toContain("Помесячно: янв 2020"); expect(confirmedText).toContain("получили 10 000 руб."); expect(confirmedText).toContain("заплатили 4 000 руб."); expect(confirmedText).toContain("Помесячно: фев 2020"); expect(confirmedText).toContain("нетто в нашу сторону 1 500,50 руб."); expect(draft.reason_codes).toContain("answer_contains_monthly_breakdown"); }); it("keeps recovered yearly coverage out of the unknown block and explains the recovery as bounded inference", 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 pilot = await executeAssistantMcpDiscoveryPilot( planner, buildSequentialDeps([{ rows: broadRows }, ...monthlyResults]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.inference_lines).toContain( "Requested period coverage was recovered through monthly 1C value-flow probes after the broad probe hit the row limit" ); expect(draft.unknown_lines).not.toContain( "Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached" ); }); it("does not leak primitive names or query text into user-facing lines", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] } }); const pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST", Контрагент: "SVK" }]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); const userFacing = [ draft.headline, ...draft.confirmed_lines, ...draft.inference_lines, ...draft.unknown_lines, ...draft.limitation_lines, draft.next_step_line ?? "" ].join("\n"); expect(userFacing).not.toContain("query_documents"); expect(userFacing).not.toContain("SELECT"); expect(userFacing).not.toContain("ВЫБРАТЬ"); expect(userFacing).not.toContain("primitive"); }); it("verbalizes activity duration from first and latest confirmed 1C rows", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] } }); const pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([ { Period: "2020-01-15T00:00:00", Counterparty: "SVK" }, { Period: "2023-12-20T00:00:00", Counterparty: "SVK" } ]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); const inferenceText = draft.inference_lines.join("\n"); expect(inferenceText).toContain("3 года 11 месяцев"); expect(inferenceText).toContain("2020-01-15"); expect(inferenceText).toContain("2023-12-20"); expect(inferenceText).toContain("не юридически подтвержденный возраст регистрации"); expect(draft.reason_codes).toContain("pilot_derived_activity_period_from_confirmed_rows"); }); it("keeps not-found entity search user-facing lines in Russian", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "entity_resolution", asked_action_family: "search_business_entity", explicit_entity_candidates: ["\u041d\u0435\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u041a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442"], unsupported_but_understood_family: "entity_resolution" } }); const pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([{ Counterparty: "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a", CounterpartyRef: "Ref-1" }]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); const unknownText = draft.unknown_lines.join("\n"); expect(draft.answer_mode).toBe("checked_sources_only"); expect(unknownText).toContain("\u043d\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442"); expect(unknownText).toContain("\u041d\u0435\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u041a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442"); expect(unknownText).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"); }); });