"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION = void 0; exports.buildAssistantMcpDiscoveryAnswerDraft = buildAssistantMcpDiscoveryAnswerDraft; const counterpartyRoleHeuristics_1 = require("./counterpartyRoleHeuristics"); exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION = "assistant_mcp_discovery_answer_draft_v1"; function normalizeReasonCode(value) { const normalized = value .trim() .replace(/[^\p{L}\p{N}_.:-]+/gu, "_") .replace(/^_+|_+$/g, "") .toLowerCase(); return normalized.length > 0 ? normalized.slice(0, 120) : null; } function pushReason(target, value) { const normalized = normalizeReasonCode(value); if (normalized && !target.includes(normalized)) { target.push(normalized); } } function uniqueStrings(values) { const result = []; for (const value of values) { const text = String(value ?? "").trim(); if (text && !result.includes(text)) { result.push(text); } } return result; } function formatNamedChoiceList(values) { return uniqueStrings(values) .slice(0, 6) .map((value, index) => `${index + 1}. ${value}`) .join("; "); } function isInternalMechanicsLine(value) { const text = value.toLowerCase(); return (text.includes("mcp fetch failed") || text.includes("this operation was aborted") || text.includes("entity-resolution") || text.includes("could not continue") || text.includes("checked catalog search step") || text.includes("primitive") || text.includes("query_documents") || text.includes("query_movements") || text.includes("resolve_entity_reference") || text.includes("probe_coverage") || text.includes("explain_evidence_basis") || text.includes("pilot_only_executes") || text.includes("pilot_") || text.includes("runtime_") || text.includes("planner_") || text.includes("catalog_") || text.includes("scope is not implemented yet") || text.includes("needs more scope before execution") || text.includes("mcp_execution_performed") || text.includes("confirmed 1c metadata surface") || text.includes("metadata surface family scores") || text.includes("available metadata object sets") || text.includes("selected metadata")); } function isMcpTransportFailureLine(value) { const text = value.toLowerCase(); return (text.includes("mcp fetch failed") || text.includes("this operation was aborted") || text.includes("operation was aborted")); } function userFacingUnknowns(values) { return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value)); } function rankedValueFlowUnknownLines(pilot) { if (!pilot.derived_ranked_value_flow) { return userFacingUnknowns(pilot.evidence.unknown_facts); } const ranking = pilot.derived_ranked_value_flow; const period = ranking.period_scope ? `периода ${ranking.period_scope}` : "проверенного окна"; return [`Полный рейтинг контрагентов вне ${period} этим поиском не подтвержден.`]; } function userFacingLimitations(values) { const result = []; for (const value of uniqueStrings(values)) { if (isMcpTransportFailureLine(value)) { const line = "Доступ к 1С во время проверки оборвался; подтвержденные строки не получены."; if (!result.includes(line)) { result.push(line); } continue; } if (!isInternalMechanicsLine(value)) { result.push(value); } } return result; } function modeFor(pilot) { if (pilot.pilot_status === "blocked") { return "blocked"; } if (pilot.pilot_status === "skipped_needs_clarification") { return "needs_clarification"; } if (pilot.pilot_scope === "entity_resolution_search_v1" && (pilot.reason_codes.includes("pilot_entity_resolution_ambiguity_requires_clarification") || pilot.derived_entity_resolution?.resolution_status === "ambiguous")) { return "needs_clarification"; } if (pilot.evidence.answer_permission === "confirmed_answer") { return "confirmed_with_bounded_inference"; } if (pilot.evidence.answer_permission === "bounded_inference") { return "bounded_inference_only"; } return "checked_sources_only"; } function isValueFlowPilot(pilot) { return (pilot.pilot_scope === "counterparty_value_flow_query_movements_v1" || pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1" || pilot.pilot_scope === "counterparty_bidirectional_value_flow_query_movements_v1"); } function isSingleDirectionValueFlowPilot(pilot) { return (pilot.pilot_scope === "counterparty_value_flow_query_movements_v1" || pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1"); } function isBusinessOverviewPilot(pilot) { return pilot.pilot_scope === "business_overview_route_template_v1"; } function isDocumentPilot(pilot) { return pilot.pilot_scope === "counterparty_document_evidence_query_documents_v1"; } function isMovementPilot(pilot) { return pilot.pilot_scope === "counterparty_movement_evidence_query_movements_v1"; } function isMetadataPilot(pilot) { return pilot.pilot_scope === "metadata_inspection_v1"; } function isInventoryTemplatePilot(pilot) { return pilot.pilot_scope === "inventory_route_template_v1"; } function isCatalogDrilldownPilot(pilot) { return (isMetadataPilot(pilot) && (pilot.reason_codes.includes("planner_selected_catalog_drilldown_from_confirmed_metadata_surface_ref") || pilot.dry_run.reason_codes.includes("planner_selected_catalog_drilldown_from_confirmed_metadata_surface_ref") || pilot.reason_codes.includes("pilot_catalog_drilldown_metadata_scope_seeded_from_surface_ref"))); } function isEntityResolutionPilot(pilot) { return pilot.pilot_scope === "entity_resolution_search_v1"; } function isMetadataLaneChoiceClarification(pilot) { return (pilot.reason_codes.includes("planner_selected_metadata_lane_clarification_recipe") || pilot.reason_codes.includes("planner_selected_metadata_lane_clarification_from_data_need_graph") || pilot.dry_run.reason_codes.includes("planner_selected_metadata_lane_clarification_recipe") || pilot.dry_run.reason_codes.includes("planner_selected_metadata_lane_clarification_from_data_need_graph")); } function askedActionFamily(pilot) { const action = pilot.evidence.query_plan.turn_meaning_ref?.asked_action_family; if (typeof action !== "string") { return null; } const normalized = action.trim().toLowerCase(); return normalized.length > 0 ? normalized : null; } function unsupportedFamily(pilot) { const unsupported = pilot.evidence.query_plan.turn_meaning_ref?.unsupported_but_understood_family; if (typeof unsupported !== "string") { return null; } const normalized = unsupported.trim().toLowerCase(); return normalized.length > 0 ? normalized : null; } function firstEntityCandidate(pilot) { const values = Array.isArray(pilot.evidence.query_plan.turn_meaning_ref?.explicit_entity_candidates) ? pilot.evidence.query_plan.turn_meaning_ref?.explicit_entity_candidates : []; for (const value of values) { const text = String(value ?? "").trim(); if (text) { return text; } } return null; } function explicitDateScope(pilot) { const value = pilot.evidence.query_plan.turn_meaning_ref?.explicit_date_scope; if (typeof value !== "string") { return null; } const normalized = value.trim(); return normalized.length > 0 ? normalized : null; } function explicitOrganizationScope(pilot) { const value = pilot.evidence.query_plan.turn_meaning_ref?.explicit_organization_scope; if (typeof value !== "string") { return null; } const normalized = value.trim(); return normalized.length > 0 ? normalized : null; } function hasExecutedZeroValueFlowRows(pilot) { const summary = pilot.source_rows_summary ?? ""; return (pilot.mcp_execution_performed && isSingleDirectionValueFlowPilot(pilot) && !pilot.derived_value_flow && (/0\s+MCP\s+value-flow\s+rows\s+fetched/i.test(summary) || /\b0\s+matched\s+value-flow\s+scope\b/i.test(summary))); } function valueFlowDirectionLabelRu(pilot) { return pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1" ? "исходящих платежей/списаний" : "входящих денежных поступлений"; } function valueFlowZeroResultConfirmedLine(pilot) { if (!hasExecutedZeroValueFlowRows(pilot)) { return null; } const counterparty = firstEntityCandidate(pilot); if (!counterparty) { return null; } const organization = explicitOrganizationScope(pilot); const period = explicitDateScope(pilot); const organizationPart = organization ? ` по организации ${organization}` : ""; const periodPart = period ? ` за период ${period}` : " в проверенном окне"; return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart} ${valueFlowDirectionLabelRu(pilot)} не найдено.`; } function valueFlowZeroResultUnknownLine(pilot) { if (!hasExecutedZeroValueFlowRows(pilot)) { return null; } const counterparty = firstEntityCandidate(pilot); if (!counterparty) { return null; } const period = explicitDateScope(pilot); const periodPart = period ? ` вне периода ${period}` : " вне проверенного окна"; return `Это не доказывает отсутствие операций с контрагентом ${counterparty}${periodPart} или вне доступного банковского контура.`; } function valueFlowZeroResultHeadline(pilot) { const confirmedLine = valueFlowZeroResultConfirmedLine(pilot); if (!confirmedLine) { return null; } return confirmedLine; } function hasAllTimeScope(pilot) { return (dryRunHasAxis(pilot, "all_time_scope") || pilot.reason_codes.includes("mcp_discovery_all_time_scope_signal_detected") || pilot.dry_run.reason_codes.includes("mcp_discovery_all_time_scope_signal_detected")); } function documentOrMovementScopeRu(pilot) { const entity = firstEntityCandidate(pilot); const period = explicitDateScope(pilot); const entityPart = entity ? ` по контрагенту ${entity}` : ""; const periodPart = period ? ` за ${period}` : hasAllTimeScope(pilot) ? " за все доступное время" : " в проверенном окне"; return `${entityPart}${periodPart}`; } function isMovementLaneClarification(pilot) { return (isMovementPilot(pilot) || pilot.reason_codes.includes("planner_selected_movement_recipe") || pilot.dry_run.reason_codes.includes("planner_selected_movement_recipe") || askedActionFamily(pilot) === "list_movements" || unsupportedFamily(pilot) === "movement_evidence"); } function isRankedValueFlowClarification(pilot) { return (pilot.reason_codes.includes("planner_selected_top_ranked_value_flow_from_data_need_graph") || pilot.reason_codes.includes("planner_selected_bottom_ranked_value_flow_from_data_need_graph") || pilot.dry_run.reason_codes.includes("planner_selected_top_ranked_value_flow_from_data_need_graph") || pilot.dry_run.reason_codes.includes("planner_selected_bottom_ranked_value_flow_from_data_need_graph")); } function isBidirectionalValueFlowComparisonClarification(pilot) { return (pilot.reason_codes.includes("planner_selected_bidirectional_value_flow_comparison_from_data_need_graph") || pilot.dry_run.reason_codes.includes("planner_selected_bidirectional_value_flow_comparison_from_data_need_graph")); } function isOpenScopeValueFlowClarification(pilot) { return (pilot.reason_codes.includes("planner_selected_open_scope_value_flow_total_from_data_need_graph") || pilot.reason_codes.includes("planner_selected_monthly_open_scope_value_flow_total_from_data_need_graph") || pilot.dry_run.reason_codes.includes("planner_selected_open_scope_value_flow_total_from_data_need_graph") || pilot.dry_run.reason_codes.includes("planner_selected_monthly_open_scope_value_flow_total_from_data_need_graph")); } function isDocumentLaneClarification(pilot) { return (isDocumentPilot(pilot) || pilot.reason_codes.includes("planner_selected_document_recipe") || pilot.dry_run.reason_codes.includes("planner_selected_document_recipe") || askedActionFamily(pilot) === "list_documents" || unsupportedFamily(pilot) === "document_evidence"); } function laneScopeSuffix(pilot) { const entity = firstEntityCandidate(pilot); return entity ? ` по "${entity}"` : ""; } function dryRunHasAxis(pilot, axis) { return pilot.dry_run.execution_steps.some((step) => step.provided_axes.includes(axis)); } function dryRunMissingAxis(pilot, axis) { if (dryRunHasAxis(pilot, axis)) { return false; } return pilot.dry_run.execution_steps.some((step) => step.missing_axis_options.some((option) => option.includes(axis))); } function queryPlanClarificationGaps(pilot) { const values = pilot.evidence.query_plan.clarification_gaps; return Array.isArray(values) ? uniqueStrings(values) : []; } function clarificationGapMissing(pilot, axis) { const gaps = queryPlanClarificationGaps(pilot); if (gaps.length > 0) { return gaps.includes(axis); } return dryRunMissingAxis(pilot, axis); } function clarificationNeedRu(pilot) { const needsPeriod = clarificationGapMissing(pilot, "period"); const organizationScopedOpenTotal = pilot.reason_codes.includes("data_need_graph_open_scope_total_needs_organization") || pilot.dry_run.reason_codes.includes("data_need_graph_open_scope_total_needs_organization") || pilot.reason_codes.includes("planner_requires_organization_scope_from_data_need_graph") || pilot.dry_run.reason_codes.includes("planner_requires_organization_scope_from_data_need_graph"); if (organizationScopedOpenTotal && !needsPeriod) { return { subject: "\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044e", verb: "\u043d\u0443\u0436\u043d\u043e" }; } const hasCounterparty = dryRunHasAxis(pilot, "counterparty"); const hasAccount = dryRunHasAxis(pilot, "account"); const needsOrganization = !hasCounterparty && !hasAccount && clarificationGapMissing(pilot, "organization"); if (needsPeriod && needsOrganization) { return { subject: "проверяемый период и организацию", verb: "нужно" }; } if (needsPeriod) { return { subject: "проверяемый период", verb: "нужен" }; } if (needsOrganization) { return { subject: "организацию", verb: "нужно" }; } return { subject: "контекст проверки", verb: "нужно" }; } function clarificationNextStepLine(pilot, laneLabel) { const organizationScopedOpenTotal = pilot.reason_codes.includes("data_need_graph_open_scope_total_needs_organization") || pilot.dry_run.reason_codes.includes("data_need_graph_open_scope_total_needs_organization") || pilot.reason_codes.includes("planner_requires_organization_scope_from_data_need_graph") || pilot.dry_run.reason_codes.includes("planner_requires_organization_scope_from_data_need_graph"); const needsPeriod = clarificationGapMissing(pilot, "period"); const needsOrganization = clarificationGapMissing(pilot, "organization"); const scopeSuffix = laneScopeSuffix(pilot); if (organizationScopedOpenTotal && !needsPeriod) { return `Уточните организацию, и я продолжу поиск по ${laneLabel}${scopeSuffix} в 1С.`; } if (needsPeriod && needsOrganization) { return `Уточните период и организацию, и я продолжу поиск по ${laneLabel}${scopeSuffix} в 1С.`; } if (needsPeriod) { return `Уточните период, и я продолжу поиск по ${laneLabel}${scopeSuffix} в 1С.`; } if (needsOrganization) { return `Уточните организацию, и я продолжу поиск по ${laneLabel}${scopeSuffix} в 1С.`; } return `Уточните контекст проверки, и я продолжу поиск по ${laneLabel}${scopeSuffix} в 1С.`; } function metadataRouteFamilyLabelRu(routeFamily) { if (routeFamily === "document_evidence") { return "контур документов"; } if (routeFamily === "movement_evidence") { return "контур движений/регистров"; } if (routeFamily === "catalog_drilldown") { return "контур справочников и связанных объектов"; } return null; } function isInventoryReserveBoundaryTurn(pilot) { const action = pilot.evidence.query_plan.turn_meaning_ref?.asked_action_family; const unsupported = pilot.evidence.query_plan.turn_meaning_ref?.unsupported_but_understood_family; return action === "inventory_reserve_boundary" || unsupported === "inventory_reserve_liquidation_boundary"; } function isProfitMarginBoundaryTurn(pilot) { const action = pilot.evidence.query_plan.turn_meaning_ref?.asked_action_family; const unsupported = pilot.evidence.query_plan.turn_meaning_ref?.unsupported_but_understood_family; return action === "profit_margin_boundary" || unsupported === "profit_margin_boundary"; } function isDebtDueDateBoundaryTurn(pilot) { const action = pilot.evidence.query_plan.turn_meaning_ref?.asked_action_family; const unsupported = pilot.evidence.query_plan.turn_meaning_ref?.unsupported_but_understood_family; return action === "debt_due_date_boundary" || unsupported === "debt_due_date_boundary"; } function isVendorRiskBoundaryTurn(pilot) { const action = pilot.evidence.query_plan.turn_meaning_ref?.asked_action_family; const unsupported = pilot.evidence.query_plan.turn_meaning_ref?.unsupported_but_understood_family; return action === "vendor_risk_procurement_boundary" || unsupported === "vendor_risk_procurement_boundary"; } function businessOverviewInventoryUnknownLabel(overview) { if (overview.inventory_quality_events) { return "рыночная ликвидационная стоимость и управленческий резерв склада"; } if (overview.inventory_staleness_risk_proxy) { return "резервы/списания/ликвидационная стоимость склада"; } if (overview.inventory_turnover_proxy) { return "FIFO-оборачиваемость/подтвержденная складская ликвидность"; } if (overview.inventory_position) { return "полноценная складская ликвидность"; } return "склад"; } function businessOverviewNextStepLine(overview) { const missing = new Set(overview.missing_signal_families); const checks = []; if (missing.has("accounting_profit_margin") || missing.has("profit_or_clean_margin")) { checks.push("чистую прибыль через себестоимость продаж, расходы и закрытие периода"); } else if (missing.has("profit_margin") || missing.has("profit_or_margin")) { checks.push("прибыль/маржу по проверенному финрезультату"); } if (missing.has("tax_position")) { checks.push("НДС/налоговую позицию за явный период"); } if (missing.has("debt_due_date_aging_quality")) { checks.push("договорные сроки оплаты и due-date aging"); } else if (missing.has("debt_open_settlement_quality")) { checks.push("качество открытых расчетов"); } else if (missing.has("debt_position")) { checks.push("долговой срез на дату"); } if (missing.has("inventory_reserve_liquidation_quality")) { checks.push("резервы, списания, неликвидность и ликвидационную стоимость склада"); } else if (missing.has("inventory_liquidity_quality")) { checks.push("FIFO-оборачиваемость и подтвержденную складскую ликвидность"); } else if (missing.has("inventory_turnover_quality")) { checks.push("скорость продаж и оборачиваемость склада"); } else if (missing.has("inventory_position")) { checks.push("складской остаток на дату"); } const target = checks.length > 0 ? checks.join("; ") : "оставшиеся непроверенные области по выбранному контуру"; return `Следующий шаг для полного бизнес-аудита: отдельно проверить ${target}, не смешивая эти будущие проверки с уже подтвержденным обзором.`; } function businessOverviewStrongestIncomingYear(overview) { const years = overview.yearly_breakdown ?? []; return [...years] .filter((bucket) => bucket.incoming_total_amount > 0) .sort((left, right) => right.incoming_total_amount - left.incoming_total_amount || left.year_bucket.localeCompare(right.year_bucket))[0] ?? null; } function inlineBusinessOverviewAmount(value) { return String(value ?? "") .trim() .replace(/\s*руб\.$/u, " рублей") .replace(/[\s.]+$/u, ""); } function isFinancialInstitutionBucket(bucket) { if (!bucket) { return false; } return (bucket.counterparty_role_hint === "bank_or_financial_institution" || (0, counterpartyRoleHeuristics_1.isLikelyFinancialInstitutionCounterparty)(bucket.axis_value)); } function firstNonFinancialInstitutionBucket(buckets) { return (buckets ?? []).find((bucket) => !isFinancialInstitutionBucket(bucket)) ?? null; } function rankedBucketAmountLabel(bucket) { return `${bucket.axis_value} — ${bucket.total_amount_human_ru}`; } function businessOverviewIncomingLeaderLine(overview) { const leader = overview.top_customers[0]; if (!leader) { return null; } if (!isFinancialInstitutionBucket(leader)) { return `Самый крупный подтвержденный клиент в проверенном срезе: ${rankedBucketAmountLabel(leader)}.`; } const nonFinancial = firstNonFinancialInstitutionBucket(overview.top_customers.slice(1)); const nonFinancialText = nonFinancial ? ` Крупнейший небанковский входящий контрагент в этом же срезе: ${rankedBucketAmountLabel(nonFinancial)}.` : ""; return (`Крупнейший входящий денежный источник в проверенном срезе: ${rankedBucketAmountLabel(leader)}. ` + "По названию это банк/финансовая организация, поэтому без проверки назначения платежа не называю это клиентской выручкой или бизнес-заказчиком." + nonFinancialText); } function businessOverviewOutgoingLeaderLine(overview) { const leader = overview.top_suppliers?.[0]; if (!leader) { return null; } if (!isFinancialInstitutionBucket(leader)) { return `Самый крупный подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${rankedBucketAmountLabel(leader)}.`; } const nonFinancial = firstNonFinancialInstitutionBucket(overview.top_suppliers.slice(1)); const nonFinancialText = nonFinancial ? ` Крупнейший небанковский получатель исходящих денег в этом же срезе: ${rankedBucketAmountLabel(nonFinancial)}.` : ""; return (`Крупнейший получатель исходящих денег в проверенном срезе: ${rankedBucketAmountLabel(leader)}. ` + "По названию это банк/финансовая организация, поэтому без назначения платежа/договора не считаю это обычным поставщиком." + nonFinancialText); } function businessOverviewSupplierBoundaryBasis(overview) { const leader = overview.top_suppliers?.[0] ?? null; if (!leader) { return "есть только общий срез исходящих платежей без надежного профиля поставщицкого риска"; } const share = percentText(leader.total_amount, overview.outgoing_supplier_payout.total_amount); if (isFinancialInstitutionBucket(leader)) { const base = share ? `крупнейший получатель исходящих денег ${leader.axis_value} держит около ${share} проверенного исходящего потока (${leader.total_amount_human_ru})` : `крупнейший получатель исходящих денег: ${rankedBucketAmountLabel(leader)}`; return `${base}; по названию это банк/финансовая организация, поэтому этот факт нельзя считать доказанной зависимостью от одного обычного поставщика`; } return share ? `крупнейший подтвержденный поставщик/получатель исходящих платежей ${leader.axis_value} держит около ${share} проверенного исходящего потока (${leader.total_amount_human_ru})` : `крупнейший подтвержденный поставщик/получатель исходящих платежей: ${rankedBucketAmountLabel(leader)}`; } function businessOverviewHeadlineMetricsLine(overview) { const parts = []; if (overview.incoming_customer_revenue.rows_with_amount > 0) { parts.push(`входящие поступления ${inlineBusinessOverviewAmount(overview.incoming_customer_revenue.total_amount_human_ru)}`); } if (overview.outgoing_supplier_payout.rows_with_amount > 0) { parts.push(`исходящие платежи/списания ${inlineBusinessOverviewAmount(overview.outgoing_supplier_payout.total_amount_human_ru)}`); } if (overview.incoming_customer_revenue.rows_with_amount > 0 || overview.outgoing_supplier_payout.rows_with_amount > 0) { parts.push(`расчетное операционное нетто ${inlineBusinessOverviewAmount(overview.net_amount_human_ru)}`); } if (overview.accounting_financial_result) { const result = overview.accounting_financial_result; const direction = result.final_result_direction === "profit" ? "учетная прибыль" : result.final_result_direction === "loss" ? "учетный убыток" : "нулевой учетный финрезультат"; const amount = result.final_result_direction === "loss" ? `минус ${inlineBusinessOverviewAmount(result.final_result_amount_human_ru)}` : inlineBusinessOverviewAmount(result.final_result_amount_human_ru); const margin = result.net_margin_to_revenue_pct === null ? "маржа к подтвержденной выручке не рассчитана" : `маржа к подтвержденной выручке ${result.net_margin_to_revenue_pct}%`; parts.push(`${direction} по закрытию счетов 90/91/99 ${amount}; ${margin}`); } const strongestIncomingYear = businessOverviewStrongestIncomingYear(overview); if (strongestIncomingYear) { parts.push(`самый сильный год по подтвержденным входящим поступлениям ${strongestIncomingYear.year_bucket}: ${inlineBusinessOverviewAmount(strongestIncomingYear.incoming_total_amount_human_ru)}`); } return parts.length > 0 ? overview.accounting_financial_result ? `${parts.join("; ")}. Финрезультат ограничен найденными строками 1С и не является внешним аудитом или юридически подтвержденной отчетностью` : `${parts.join("; ")}. Это операционный денежный сигнал по найденным строкам, не бухгалтерская прибыль и не финрезультат` : null; } function businessOverviewAccountingFinancialResultText(overview) { const result = overview.accounting_financial_result; if (!result) { return null; } const direction = result.final_result_direction === "profit" ? "учетная прибыль" : result.final_result_direction === "loss" ? "учетный убыток" : "нулевой учетный финрезультат"; const signedAmount = result.final_result_direction === "loss" ? `минус ${result.final_result_amount_human_ru}` : result.final_result_amount_human_ru; const marginText = result.net_margin_to_revenue_pct === null ? "маржа к подтвержденной выручке не рассчитана" : `маржа к подтвержденной выручке ${result.net_margin_to_revenue_pct}%`; const basis = result.final_transfer_basis === "account_99_to_84_period_close" ? "по закрытию 99 на 84" : "по закрытию 90/91 на 99"; return `Нет: денежное операционное нетто не стоит считать чистой прибылью. Отдельно по закрытию счетов 90/91/99 в 1С за ${result.period_scope} подтвержден ${direction}: ${signedAmount}; ${marginText}. Основа: ${basis}, ${result.period_close_rows_with_amount} строк(и) закрытия периода с суммой. Это учетный финрезультат по найденным строкам 1С, не внешний аудит и не юридически подтвержденная отчетность.`; } function businessOverviewDebtDueDateAgingText(overview) { const aging = overview.debt_due_date_aging; if (!aging) { return null; } if (aging.evidence_status === "confirmed_overdue") { const top = aging.top_overdue_items?.[0] ?? null; const topText = top ? ` Самая старая строка: due date ${top.due_date}, просрочка ${top.overdue_days} дн., ${top.amount_human_ru}${top.contract ? ` по договору ${top.contract}` : ""}.` : ""; return `Due-date aging на ${aging.as_of_date} проверен по срокам оплаты договоров и датам расчетных документов: подтверждено просроченных строк ${aging.overdue_rows}, сумма ${aging.overdue_amount_human_ru}.${topText}`; } if (aging.evidence_status === "no_payment_terms_configured") { return `Due-date aging на ${aging.as_of_date} проверен по открытым расчетам: брутто ${aging.gross_open_amount_human_ru}, строк с суммой ${aging.rows_with_amount}, но в проверенных договорах срок оплаты не установлен. Подтвержденной просрочки по договорным срокам оплаты нет.`; } if (aging.evidence_status === "insufficient_due_date_basis") { return `Due-date aging на ${aging.as_of_date} запускался, но по строкам с установленным сроком оплаты не хватило даты расчетного документа для честного расчета due date. Просрочка не подтверждена.`; } return `Due-date aging на ${aging.as_of_date} проверен: строк с установленным сроком оплаты ${aging.rows_with_payment_terms}, подтвержденной просрочки не найдено; не просрочено по расчету ${aging.not_yet_due_amount_human_ru}.`; } function financialFlowHintTextRuFromBucket(bucket) { const rows = bucket?.financial_flow_hint_rows ?? 0; const rowsText = rows > 0 ? ` (${rows} строк)` : ""; if (bucket?.financial_flow_hint === "loan_or_credit") { return ` По полям банковского документа доминирует кредитный/заемный признак${rowsText}; это не обычный поставщик и не клиентская выручка без отдельной проверки назначения.`; } if (bucket?.financial_flow_hint === "bank_fee_or_service") { return ` По полям банковского документа доминирует признак банковской комиссии/услуг банка${rowsText}; это не обычный поставщик товаров/услуг без отдельной проверки договора.`; } if (bucket?.financial_flow_hint === "tax_or_budget") { return ` По полям банковского документа доминирует налоговый/бюджетный признак${rowsText}.`; } if (bucket?.financial_flow_hint === "payroll_or_social") { return ` По полям банковского документа доминирует зарплатный/социальный признак${rowsText}.`; } if (bucket?.financial_flow_hint === "supplier_payment") { return ` По полям банковского документа доминирует признак оплаты поставщику${rowsText}; если получатель по названию является банком, это все равно требует осторожной трактовки.`; } return ""; } function businessOverviewVendorProcurementQualityText(overview) { const quality = overview.vendor_procurement_quality; if (!quality) { return null; } const period = quality.period_scope ?? "проверенное окно"; const total = quality.total_outgoing_amount_human_ru; const top = quality.top_outgoing_counterparty; const topName = top?.axis_value ?? "получатель не распознан"; const topShare = quality.top_outgoing_share_pct === null ? "" : `, около ${quality.top_outgoing_share_pct}%`; const topAmount = top?.total_amount_human_ru ? ` (${top.total_amount_human_ru})` : ""; const nonFinancial = quality.top_non_financial_supplier; const nonFinancialShare = quality.top_non_financial_supplier_share_pct === null ? "" : `, около ${quality.top_non_financial_supplier_share_pct}%`; const nonFinancialText = nonFinancial ? ` Крупнейший небанковский получатель: ${nonFinancial.axis_value}${nonFinancialShare}${nonFinancial.total_amount_human_ru ? ` (${nonFinancial.total_amount_human_ru})` : ""}.` : ""; const contractText = quality.used_contracts === null ? "" : quality.total_contracts === null ? ` Договорный профиль: используется ${quality.used_contracts} договоров.` : ` Договорный профиль: используется ${quality.used_contracts}/${quality.total_contracts} договоров${quality.used_contract_share_pct === null ? "" : ` (${quality.used_contract_share_pct}%)`}.`; if (quality.evidence_status === "financial_institution_leads_outgoing_cash") { return `Проверка концентрации закупок/исходящих платежей за ${period}: крупнейший получатель исходящих денег ${topName}${topShare}${topAmount}, всего исходящих платежей ${total}. По названию это банк/финансовая организация, поэтому зависимость от обычного поставщика этим не подтверждается.${financialFlowHintTextRuFromBucket(top)}${nonFinancialText}${contractText} Надежность поставщиков, качество поставок, назначение каждого платежа и полная структура расходов этим срезом не доказаны.`; } if (quality.evidence_status === "reviewed_procurement_concentration") { return `Проверка концентрации закупок/исходящих платежей за ${period}: крупнейший поставщик/получатель исходящих платежей ${topName}${topShare}${topAmount}, всего исходящих платежей ${total}.${contractText} Это проверенный сигнал концентрации закупок/исходящих платежей, но не аудит надежности поставщика, качества поставок и полной структуры расходов.`; } return `Проверка концентрации закупок/исходящих платежей за ${period} нашла исходящие платежи на ${total}, но надежной небанковской концентрации поставщика по найденным строкам не хватает.${contractText} Полный аудит поставщицкого риска не подтвержден.`; } function businessOverviewInventoryQualityEventsText(overview) { const quality = overview.inventory_quality_events; if (!quality) { return null; } const period = quality.period_scope ?? "проверенное окно"; const organization = overview.organization_scope ? ` по организации ${overview.organization_scope}` : ""; const eventWindow = quality.first_event_date && quality.latest_event_date ? ` Окно найденных событий: ${quality.first_event_date} - ${quality.latest_event_date}.` : ""; if (quality.evidence_status === "reviewed_no_quality_events_found") { return `Коротко: проверил складские документы списания, оприходования, инвентаризации и переоценки${organization} за ${period}; подтвержденных событий списания/корректировки/инвентаризации/переоценки не найдено. Это сильный отрицательный сигнал по доступным документам 1С, но не рыночная ликвидационная стоимость и не управленческий резерв под неликвиды.`; } if (quality.evidence_status === "reviewed_inventory_control_events_only") { return `Коротко: проверил складские quality-события${organization} за ${period}; списаний и оприходований/корректировок с суммой не найдено, но есть инвентаризации ${quality.inventory_count_rows} и переоценки ${quality.revaluation_rows}.${eventWindow} Это контрольные складские документы, а не подтвержденный резерв или рыночная ликвидационная оценка.`; } return `Коротко: проверил складские quality-события${organization} за ${period}; списаний ${quality.writeoff_rows} на ${quality.writeoff_amount_human_ru}, оприходований/корректировок ${quality.receipt_adjustment_rows} на ${quality.receipt_adjustment_amount_human_ru}, инвентаризаций ${quality.inventory_count_rows}, переоценок ${quality.revaluation_rows}.${eventWindow} Это подтвержденные документы 1С по складским событиям, но не самостоятельная рыночная ликвидационная стоимость и не расчет управленческого резерва.`; } function headlineFor(mode, pilot) { const askedMonthlyBreakdown = pilot.derived_bidirectional_value_flow?.aggregation_axis === "month" || pilot.derived_value_flow?.aggregation_axis === "month"; if (isEntityResolutionPilot(pilot) && mode === "confirmed_with_bounded_inference") { return "По каталогу 1С найден вероятный контрагент; это заземление сущности для следующего шага, а не еще бизнес-ответ по данным."; } if (isInventoryTemplatePilot(pilot) && mode === "confirmed_with_bounded_inference") { return "По exact inventory runtime в 1С найдены подтвержденные строки; ответ ограничен проверенным складским/товарным срезом."; } if (isInventoryTemplatePilot(pilot) && mode === "checked_sources_only") { if (pilot.mcp_execution_performed) { return "Exact inventory runtime был проверен, но подтвержденный складской/товарный факт в найденных строках не получен."; } return "Инвентарный route-template уже выбран, но live-исполнение этого generic MCP контура еще не подключено; складской/товарный факт не подтвержден."; } if (isBusinessOverviewPilot(pilot) && pilot.derived_business_overview && mode === "confirmed_with_bounded_inference") { const overview = pilot.derived_business_overview; if (isProfitMarginBoundaryTurn(pilot)) { const accountingFinancialResultText = businessOverviewAccountingFinancialResultText(overview); if (accountingFinancialResultText) { return accountingFinancialResultText; } return "Нельзя точно подтвердить чистую прибыль и маржу по текущему срезу 1С; есть только ограниченный операционный денежный/товарный сигнал, а не полный отчет о прибыли и не бухгалтерский финрезультат."; } if (isDebtDueDateBoundaryTurn(pilot)) { const dueDateText = businessOverviewDebtDueDateAgingText(overview); if (dueDateText) { return dueDateText; } return "Нельзя точно определить, какая дебиторка просрочена, по текущему срезу 1С; есть только debt-quality proxy, но нет проверенного due-date маршрута по договорам, срокам оплаты и погашению расчетов."; } if (isInventoryReserveBoundaryTurn(pilot)) { const inventoryQualityEventsText = businessOverviewInventoryQualityEventsText(overview); if (inventoryQualityEventsText) { return inventoryQualityEventsText; } const inventoryBasis = overview.inventory_staleness_risk_proxy ? "есть только складской staleness-risk proxy по найденным строкам" : overview.inventory_position || overview.inventory_turnover_proxy ? "есть только ограниченные складские proxy-сигналы по найденным строкам" : "нет отдельного складского среза на дату и проверки учетной политики резервов"; return `Коротко: точно подтвердить резерв под неликвиды по текущим данным нельзя; ${inventoryBasis}. Можно честно говорить только о необходимости отдельной проверки склада, списаний/резервов и ликвидационной стоимости, не превращая proxy в доказанный факт резерва.`; } if (isVendorRiskBoundaryTurn(pilot)) { const vendorQualityText = businessOverviewVendorProcurementQualityText(overview); if (vendorQualityText) { return vendorQualityText; } const supplierLeader = overview.top_suppliers?.[0] ?? null; const proxyLabel = isFinancialInstitutionBucket(supplierLeader) ? "outgoing cash concentration proxy" : "procurement concentration proxy"; return `Коротко: точный риск зависимости от одного поставщика по текущим данным не подтвержден; есть только ${proxyLabel}: ${businessOverviewSupplierBoundaryBasis(overview)}. Это сигнал концентрации исходящих платежей, а не полный аудит надежности поставщиков, условий, качества и структуры всех расходов.`; } const families = []; if (overview.incoming_customer_revenue.rows_with_amount > 0 || overview.outgoing_supplier_payout.rows_with_amount > 0) { families.push("денежный поток"); } if (overview.yearly_breakdown?.length) { families.push("годовая operating-flow динамика"); } if (overview.activity_period) { families.push("активность"); } if (overview.document_activity_profile) { families.push("профиль типов документов и разделов учета"); } if (overview.counterparty_profile) { families.push("профиль контрагентской базы"); } if (overview.contract_usage_profile) { families.push("договорной профиль"); } if (overview.tax_position) { families.push("НДС-позиция"); } if (overview.accounting_financial_result) { families.push("учетный финрезультат 90/91/99"); } if (overview.trading_margin_proxy) { families.push("торговый margin proxy"); } if (overview.debt_position) { families.push("долговой срез на дату"); } if (overview.debt_open_settlement_quality) { families.push("качество открытых расчетов"); if (overview.debt_open_settlement_quality.age_signal) { families.push("возрастной сигнал открытых расчетов"); } } if (overview.debt_staleness_risk_proxy) { families.push("staleness risk proxy открытых расчетов"); } if (overview.debt_due_date_aging) { families.push("due-date aging открытых расчетов"); } if (overview.inventory_position) { families.push("складской срез на дату"); } if (overview.inventory_turnover_proxy) { families.push("оборотный proxy склада"); } if (overview.inventory_staleness_risk_proxy) { families.push("staleness risk proxy склада"); } if (overview.inventory_quality_events) { families.push("складские quality-события"); } const unknownFamilies = overview.accounting_financial_result ? ["аудированная/юридически подтвержденная прибыль"] : [overview.trading_margin_proxy ? "чистая прибыль/точная маржа" : "прибыль/маржа"]; if (!overview.tax_position) { unknownFamilies.push("НДС"); } if (!overview.debt_position) { unknownFamilies.push("долговой срез"); } if (!overview.debt_due_date_aging) { unknownFamilies.push(overview.debt_staleness_risk_proxy ? "договорные сроки оплаты/due-date просрочка" : overview.debt_open_settlement_quality ? "due-date просрочка" : "качество открытых расчетов"); } unknownFamilies.push(businessOverviewInventoryUnknownLabel(overview)); const metricLead = businessOverviewHeadlineMetricsLine(overview); if (metricLead) { return `Ограниченный бизнес-обзор по подтвержденным строкам 1С: ${metricLead}. Проверенные контуры: ${families.join(", ")}; ${unknownFamilies.join(", ")} остаются отдельными непроверенными областями.`; } return `По данным 1С собран ограниченный бизнес-обзор: ${families.join(", ")} подтверждены найденными строками; ${unknownFamilies.join(", ")} остаются отдельными непроверенными областями.`; } if (isBusinessOverviewPilot(pilot) && mode === "checked_sources_only") { return "Бизнес-обзор был запущен, но подтвержденные денежные или activity-сигналы в найденных строках не получены."; } if (isEntityResolutionPilot(pilot) && mode === "needs_clarification") { return "По каталогу 1С нашлось несколько похожих контрагентов, и без уточнения нельзя честно выбрать правильную сущность."; } if (isEntityResolutionPilot(pilot) && mode === "checked_sources_only" && pilot.derived_entity_resolution?.resolution_status === "not_found") { return "По текущему каталожному поиску 1С точный контрагент пока не подтвержден."; } if (pilot.derived_ranked_value_flow && mode === "confirmed_with_bounded_inference") { return "По данным 1С можно построить ограниченный рейтинг по контрагентам на подтвержденных строках денежных движений."; } if (isMovementPilot(pilot) && mode === "confirmed_with_bounded_inference") { return `По движениям${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`; } if (isCatalogDrilldownPilot(pilot) && mode === "confirmed_with_bounded_inference") { return "По схеме 1С удалось углубиться в контур справочников и связанных объектов; это следующий безопасный шаг по проверенной схеме, а не бизнес-обороты."; } if (pilot.derived_metadata_surface && mode === "confirmed_with_bounded_inference") { if (pilot.derived_metadata_surface.ambiguity_detected) { return "По схеме 1С найдены несколько конкурирующих контуров; перед следующим шагом нужно явно выбрать нужный тип данных."; } if (pilot.derived_metadata_surface.downstream_route_family) { return "По схеме 1С найдены подходящие объекты; можно безопасно выбрать следующий контур проверки."; } return "По схеме 1С найдены доступные объекты для дальнейшего безопасного поиска."; } if (askedMonthlyBreakdown && pilot.derived_bidirectional_value_flow && mode === "confirmed_with_bounded_inference") { return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто и помесячная раскладка могут называться только как расчет по найденным строкам и проверенному периоду."; } if (askedMonthlyBreakdown && pilot.derived_value_flow && mode === "confirmed_with_bounded_inference") { if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") { return "По данным 1С найдены строки исходящих платежей/списаний; сумму и помесячную раскладку можно называть только в рамках проверенного периода и найденных строк."; } return "По данным 1С найдены строки входящих денежных поступлений; сумму и помесячную раскладку можно называть только в рамках проверенного периода и найденных строк."; } if (pilot.derived_bidirectional_value_flow && mode === "confirmed_with_bounded_inference") { return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду."; } if (pilot.derived_value_flow && mode === "confirmed_with_bounded_inference") { if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") { return "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк."; } return "По данным 1С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк."; } const zeroValueFlowHeadline = valueFlowZeroResultHeadline(pilot); if (mode === "checked_sources_only" && zeroValueFlowHeadline) { return zeroValueFlowHeadline; } if (isDocumentPilot(pilot) && mode === "confirmed_with_bounded_inference") { return `По документам${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`; } if (isMovementPilot(pilot) && mode === "confirmed_with_bounded_inference") { return `По движениям${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`; } if (mode === "confirmed_with_bounded_inference") { return "По данным 1С есть подтвержденная активность; длительность можно оценивать только как вывод из этих строк."; } if (isDocumentPilot(pilot) && mode === "bounded_inference_only") { return `По документам${documentOrMovementScopeRu(pilot)} полный срез не подтвержден; пока есть только ограниченная граница проверенного окна 1С.`; } if (isMovementPilot(pilot) && mode === "bounded_inference_only") { return `По движениям${documentOrMovementScopeRu(pilot)} полный срез не подтвержден; пока есть только ограниченная граница проверенного окна 1С.`; } if (mode === "bounded_inference_only") { return "Точный факт не подтвержден, но есть ограниченная оценка по найденной активности в 1С."; } if (mode === "needs_clarification" && isMetadataLaneChoiceClarification(pilot)) { return "По проверенной схеме 1С видно несколько возможных контуров, и без явного выбора дальше идти нельзя."; } if (mode === "needs_clarification" && isMovementLaneClarification(pilot)) { const need = clarificationNeedRu(pilot); return `Могу идти дальше по движениям/регистрам${laneScopeSuffix(pilot)}, но для запуска поиска в 1С ${need.verb} ${need.subject}.`; } if (mode === "needs_clarification" && isDocumentLaneClarification(pilot)) { const need = clarificationNeedRu(pilot); return `Могу идти дальше по документам${laneScopeSuffix(pilot)}, но для запуска поиска в 1С ${need.verb} ${need.subject}.`; } if (mode === "needs_clarification" && isBidirectionalValueFlowComparisonClarification(pilot)) { const need = clarificationNeedRu(pilot); return `Могу сравнить входящий и исходящий денежный поток, но для проверяемого поиска в 1С ${need.verb} ${need.subject}.`; } if (mode === "needs_clarification" && isRankedValueFlowClarification(pilot)) { const need = clarificationNeedRu(pilot); return `Могу посчитать рейтинг по денежному потоку между контрагентами, но для проверяемого поиска в 1С ${need.verb} ${need.subject}.`; } if (mode === "needs_clarification" && isOpenScopeValueFlowClarification(pilot)) { const need = clarificationNeedRu(pilot); return `Могу посчитать общий денежный поток в проверяемом окне, но для проверяемого поиска в 1С ${need.verb} ${need.subject}.`; } if (mode === "needs_clarification") { return "Нужно уточнить контекст перед поиском в 1С."; } if (mode === "blocked") { return "Поиск в 1С заблокирован runtime-политикой до выполнения."; } return "Я проверил доступный контур, но подтвержденного факта для ответа не получил."; } function nextStepFor(mode, pilot) { if (isEntityResolutionPilot(pilot) && mode === "needs_clarification") { const ambiguityCandidates = pilot.derived_entity_resolution?.ambiguity_candidates ?? []; if (ambiguityCandidates.length > 0) { return `Уточните, какой именно контрагент нужен: ${formatNamedChoiceList(ambiguityCandidates)}. Можно ответить названием или номером варианта.`; } return "Уточните точное название контрагента или добавьте ИНН, и я продолжу уже по нужной сущности в 1С."; } if (isEntityResolutionPilot(pilot) && mode === "confirmed_with_bounded_inference") { return "Теперь могу продолжить уже по найденному контрагенту и искать документы, движения или денежный поток."; } if (isEntityResolutionPilot(pilot) && mode === "checked_sources_only" && pilot.derived_entity_resolution?.resolution_status === "not_found") { return "Дайте точное название или ИНН, и я повторю поиск по каталогу 1С более прицельно."; } if (mode === "needs_clarification" && isMetadataLaneChoiceClarification(pilot)) { return "Уточните, в какой контур идти дальше: по документам или по движениям/регистрам."; } if (mode === "needs_clarification" && isMovementLaneClarification(pilot)) { return clarificationNextStepLine(pilot, "движениям/регистрам"); } if (mode === "needs_clarification" && isDocumentLaneClarification(pilot)) { return clarificationNextStepLine(pilot, "документам"); } if (mode === "needs_clarification" && isBidirectionalValueFlowComparisonClarification(pilot)) { return clarificationNextStepLine(pilot, "сравнению входящих и исходящих денежных потоков"); } if (mode === "needs_clarification" && isRankedValueFlowClarification(pilot)) { return clarificationNextStepLine(pilot, "рейтингу контрагентов по денежному потоку"); } if (mode === "needs_clarification" && isOpenScopeValueFlowClarification(pilot)) { return clarificationNextStepLine(pilot, "денежному потоку"); } if (mode === "needs_clarification") { return "Уточните контрагента, период или организацию, и я смогу выполнить проверку по 1С."; } if (mode === "checked_sources_only" && isInventoryTemplatePilot(pilot)) { if (pilot.mcp_execution_performed) { return "Можно уточнить дату, организацию, склад, поставщика или позицию и повторить exact inventory проверку."; } return "Следующий шаг - связать inventory route-template с exact inventory runtime и затем проверить live-прогоном."; } if (mode === "confirmed_with_bounded_inference" && isBusinessOverviewPilot(pilot)) { return pilot.derived_business_overview ? businessOverviewNextStepLine(pilot.derived_business_overview) : "Если нужен уже управленческий вывод, следующим шагом стоит отдельно проверить прибыль/маржу, долги, НДС и складскую ликвидность, а затем собрать полный бизнес-аудит."; } if (mode === "confirmed_with_bounded_inference" && pilot.derived_metadata_surface) { const surface = pilot.derived_metadata_surface; if (surface.ambiguity_detected && surface.ambiguity_entity_sets.length > 0) { return `Следующим шагом лучше выбрать один контур схемы: ${surface.ambiguity_entity_sets.join(", ")}.`; } const routeLabel = metadataRouteFamilyLabelRu(surface.downstream_route_family); if (surface.selected_entity_set && routeLabel) { return `Следующим шагом могу пойти в ${routeLabel} по типу «${surface.selected_entity_set}» и уже искать подтвержденные данные, а не только схему.`; } } if (mode === "checked_sources_only" && pilot.query_limitations.length > 0) { return "Можно повторить проверку после восстановления доступа к 1С или сузить вопрос до конкретного контрагента/периода."; } if (mode === "blocked") { return "Нужно сначала снять policy/blocking причину, иначе данные 1С использовать нельзя."; } return null; } function buildMustNotClaim(pilot) { const claims = [ "Do not expose MCP primitive names, query text, debug ids, or internal execution mechanics in the user answer.", "Do not claim rows were checked when mcp_execution_performed=false." ]; if (pilot.pilot_scope === "counterparty_lifecycle_query_documents_v1") { claims.push("Do not claim legal registration age unless a legal registration source is confirmed."); claims.push("Do not present inferred activity duration as a formally confirmed legal fact."); } if (isValueFlowPilot(pilot)) { claims.push("Do not claim full all-time turnover unless the checked period and coverage prove it."); claims.push("Do not present a derived sum as a legal/accounting final total outside the checked 1C rows."); } if (isBusinessOverviewPilot(pilot)) { claims.push("Do not present business overview cash-flow spread as profit or margin."); claims.push("Do not present business overview yearly operating-flow breakdown as profit, financial result, or a complete annual P&L."); claims.push("Do not present business overview trading-margin proxy as clean profit, accounting financial result, or exact cost-of-sales margin."); if (pilot.derived_business_overview?.vendor_procurement_quality) { claims.push("Do not present reviewed procurement concentration as supplier reliability, delivery quality, payment-purpose classification, or full expense structure."); } else { claims.push("Do not present business overview supplier concentration as vendor-risk audit, procurement quality, or full expense structure."); } claims.push("Do not present business overview document/account-section activity profile as process quality, accounting correctness, or completeness of all 1C activity."); claims.push("Do not present business overview counterparty or contract profile as CRM quality, counterparty due diligence, contract-risk audit, or legal completeness."); claims.push("Do not claim debt quality, VAT position, inventory health, or company health unless those contours were separately checked."); claims.push("Do not present a debt-position snapshot as debt aging, overdue debt, or credit-quality analysis."); claims.push("Do not present open-settlement concentration as contractual due-date aging or confirmed overdue debt."); claims.push("Do not present business overview debt staleness risk proxy as confirmed overdue debt, contractual delinquency, credit risk, or due-date aging."); claims.push("Do not claim contractual overdue debt unless the due-date aging route found configured payment terms and enough settlement-date evidence."); claims.push("Do not present an inventory snapshot or purchase-date aging signal as turnover, obsolescence, liquidation value, or full inventory health."); claims.push("Do not present business overview inventory turnover proxy as full inventory liquidity, FIFO turnover, obsolescence analysis, or liquidation value."); claims.push("Do not present business overview inventory staleness risk proxy as confirmed obsolete stock, reserve, write-off, or liquidation value."); if (pilot.derived_business_overview?.inventory_quality_events) { claims.push("Do not present reviewed inventory quality events as confirmed obsolete stock, reserve policy, market liquidation value, management reserve, or full inventory health."); } if (pilot.derived_business_overview?.top_customers?.some(isFinancialInstitutionBucket) || pilot.derived_business_overview?.top_suppliers?.some(isFinancialInstitutionBucket)) { claims.push("Do not present bank-like counterparties as ordinary customers, suppliers, revenue, procurement dependency, or business quality evidence without payment-purpose/contract proof."); } if (pilot.derived_business_overview?.missing_proof_families?.length) { claims.push("Do not present business overview missing proof families as checked, executed, or confirmed routes."); } claims.push("Do not expose business_overview_route_template_v1 or MCP primitive names in the user answer."); } if (pilot.derived_ranked_value_flow) { claims.push("Do not present a bounded ranking as a complete all-time ranking outside the checked period and organization."); claims.push("Do not imply the top-ranked counterparty is globally final when probe-limit or scope boundaries still exist."); if (pilot.derived_ranked_value_flow.ranked_values.some(isFinancialInstitutionBucket)) { claims.push("Do not present bank-like counterparties as ordinary customers, suppliers, revenue, procurement dependency, or business quality evidence without payment-purpose/contract proof."); } } if (isDocumentPilot(pilot)) { claims.push("Do not claim full document history outside the checked period."); claims.push("Do not present the confirmed document rows as a complete document universe."); } if (isMovementPilot(pilot)) { claims.push("Do not claim full movement history outside the checked period."); claims.push("Do not present the confirmed movement rows as a complete movement universe."); } if (isMetadataPilot(pilot)) { claims.push("Do not present metadata surface as confirmed business data rows."); claims.push("Do not claim a document/register exists outside the checked metadata probe results."); claims.push("Do not present the inferred next checked lane as already executed data retrieval."); } if (isEntityResolutionPilot(pilot)) { claims.push("Do not present catalog grounding as confirmed business activity, turnover, or document evidence."); claims.push("Do not claim legal identity uniqueness when several catalog candidates are still plausible."); claims.push("Do not imply that the resolved entity has already been used in a downstream data probe."); } if (isInventoryTemplatePilot(pilot)) { if (!pilot.mcp_execution_performed) { claims.push("Do not present inventory route-template planning as executed stock, supplier, purchase, or sale evidence."); } claims.push("Do not expose inventory_route_template_v1 or MCP primitive names in the user answer."); claims.push("Do not claim full inventory coverage outside the checked rows, date, organization, item, or supplier scope."); } if (pilot.evidence.confirmed_facts.length === 0) { claims.push("Do not claim a confirmed business fact when confirmed_facts is empty."); } return claims; } const RU_MONTH_LABELS_SHORT = [ "янв", "фев", "мар", "апр", "май", "июн", "июл", "авг", "сен", "окт", "ноя", "дек" ]; function monthLabelRu(monthBucket) { const match = monthBucket.match(/^(\d{4})-(\d{2})$/); if (!match) { return monthBucket; } const monthIndex = Number(match[2]) - 1; const label = RU_MONTH_LABELS_SHORT[monthIndex] ?? match[2]; return `${label} ${match[1]}`; } function netLabelRu(netDirection) { if (netDirection === "net_incoming") { return "нетто в нашу сторону"; } if (netDirection === "net_outgoing") { return "нетто исходящее"; } return "нетто нулевое"; } function derivedActivityInferenceLine(pilot) { const period = pilot.derived_activity_period; if (!period) { return null; } return [ `По подтвержденным строкам активности в 1С период взаимодействия можно оценить примерно как ${period.duration_human_ru}.`, `Первая найденная активность: ${period.first_activity_date}; последняя найденная активность: ${period.latest_activity_date}.`, "Это вывод по данным 1С, а не юридически подтвержденный возраст регистрации." ].join(" "); } function derivedMetadataConfirmedLine(pilot) { const surface = pilot.derived_metadata_surface; if (!surface) { return null; } const scope = surface.metadata_scope ? ` по области "${surface.metadata_scope}"` : ""; const entitySets = surface.available_entity_sets.length > 0 ? ` Тип объектов: ${surface.available_entity_sets.join(", ")}.` : ""; const objects = surface.matched_objects.length > 0 ? ` Найденные объекты: ${surface.matched_objects.slice(0, 8).join(", ")}.` : ""; const selectedObjects = surface.selected_surface_objects.length > 0 ? ` Для следующего шага подходят: ${surface.selected_surface_objects.slice(0, 6).join(", ")}.` : ""; const fields = surface.available_fields.length > 0 ? ` Доступные поля/секции: ${surface.available_fields.slice(0, 12).join(", ")}.` : ""; return `В схеме 1С${scope} найдены подтвержденные объекты: ${surface.matched_rows}.${entitySets}${objects}${selectedObjects}${fields}`.replace(/\s+/g, " ").trim(); } function derivedMetadataInferenceLine(pilot) { const surface = pilot.derived_metadata_surface; if (!surface) { return null; } if (surface.ambiguity_detected && surface.ambiguity_entity_sets.length > 0) { return `По проверенной схеме видно несколько возможных контуров: ${surface.ambiguity_entity_sets.join(", ")}. Следующий шаг пока нельзя выбрать без явного сужения.`; } const routeLabel = metadataRouteFamilyLabelRu(surface.downstream_route_family); if (!surface.selected_entity_set || !routeLabel) { return null; } return `Следующий проверяемый шаг можно вести в ${routeLabel} через тип «${surface.selected_entity_set}». Это пока выбор контура по схеме 1С, а не уже полученные бизнес-строки.`; } function derivedEntityResolutionConfirmedLine(pilot) { const resolution = pilot.derived_entity_resolution; if (!resolution || resolution.resolution_status !== "resolved" || !resolution.resolved_entity) { return null; } const requested = resolution.requested_entity ? ` по запросу "${resolution.requested_entity}"` : ""; const confidence = resolution.confidence === "high" ? " Точность совпадения выглядит высокой." : resolution.confidence === "medium" ? " Совпадение выглядит достаточно сильным, но это все еще catalog grounding." : " Совпадение выглядит вероятным, но его лучше считать рабочим заземлением сущности."; return `В текущем каталожном срезе 1С${requested} найден контрагент "${resolution.resolved_entity}".${confidence}`; } function derivedEntityResolutionInferenceLine(pilot) { const resolution = pilot.derived_entity_resolution; if (!resolution) { return null; } if (resolution.resolution_status === "resolved") { return "Сейчас подтверждено только заземление сущности по каталогу 1С; документы, движения и денежные показатели по ней еще не проверялись."; } if (resolution.resolution_status === "ambiguous" && resolution.ambiguity_candidates.length > 0) { return `В каталоге 1С нашлось несколько близких кандидатов: ${formatNamedChoiceList(resolution.ambiguity_candidates)}. Без уточнения нельзя честно выбрать одного контрагента для следующего шага.`; } return null; } function derivedRankedValueFlowInferenceLine(pilot) { const ranking = pilot.derived_ranked_value_flow; if (!ranking) { return null; } const organization = ranking.organization_scope ? ` по организации ${ranking.organization_scope}` : ""; const period = ranking.period_scope ? ` за период ${ranking.period_scope}` : " в проверенном окне"; return `Рейтинг по контрагентам${organization}${period} рассчитан только по подтвержденным строкам 1С и не доказывает полный исторический срез вне проверенного окна.`; } function derivedRankedValueFlowConfirmedLine(pilot) { const ranking = pilot.derived_ranked_value_flow; if (!ranking || ranking.ranked_values.length <= 0) { return null; } const leader = ranking.ranked_values[0]; const leaderLooksFinancial = isFinancialInstitutionBucket(leader); const organization = ranking.organization_scope ? ` по организации ${ranking.organization_scope}` : ""; const period = ranking.period_scope ? ` за период ${ranking.period_scope}` : " в проверенном окне"; const roleCaveat = leaderLooksFinancial ? ranking.value_flow_direction === "outgoing_supplier_payout" ? " По названию это банк/финансовая организация, поэтому без назначения платежа/договора не называю это обычным поставщиком." : " По названию это банк/финансовая организация, поэтому без назначения платежа не называю это клиентской выручкой или бизнес-заказчиком." : ""; if (ranking.ranked_values.length === 1) { const singleLead = leaderLooksFinancial ? ranking.value_flow_direction === "outgoing_supplier_payout" ? "В проверенных исходящих платежах найден один банковский/финансовый получатель" : "В проверенных входящих поступлениях найден один банковский/финансовый источник" : ranking.value_flow_direction === "outgoing_supplier_payout" ? "В проверенных исходящих платежах найден один контрагент" : "В проверенных входящих поступлениях найден один контрагент"; const limitCaveat = ranking.coverage_limited_by_probe_limit ? " Лимит строк проверки достигнут; сравнение с другими контрагентами может быть неполным." : " Других контрагентов в этом проверенном срезе не найдено, поэтому это не полноценный сравнительный рейтинг."; return `${singleLead} ${leader.axis_value}${organization}${period}: ${leader.total_amount_human_ru} по ${leader.rows_with_amount} строкам с суммой.${roleCaveat}${limitCaveat}`; } const directionLead = leaderLooksFinancial ? ranking.value_flow_direction === "outgoing_supplier_payout" ? "Крупнейший получатель исходящих денег" : "Крупнейший входящий денежный источник" : ranking.ranking_need === "bottom_asc" ? ranking.value_flow_direction === "outgoing_supplier_payout" ? "Меньше всего заплатили контрагенту" : "Меньше всего денег принёс контрагент" : ranking.value_flow_direction === "outgoing_supplier_payout" ? "Больше всего заплатили контрагенту" : "Больше всего денег принёс контрагент"; const tail = ranking.ranked_values .slice(1, 3) .map((bucket) => `${bucket.axis_value} — ${bucket.total_amount_human_ru}`) .join("; "); const trail = tail ? ` Следом: ${tail}.` : ""; const limitCaveat = ranking.coverage_limited_by_probe_limit ? " Лимит строк проверки достигнут; рейтинг может быть неполным." : ""; return `${directionLead} ${leader.axis_value}${organization}${period}: ${leader.total_amount_human_ru} по ${leader.rows_with_amount} строкам с суммой.${roleCaveat}${trail}${limitCaveat}`; } function derivedValueFlowConfirmedLine(pilot) { const flow = pilot.derived_value_flow; if (!flow) { return null; } const organizationScope = explicitOrganizationScope(pilot); const organization = organizationScope ? ` по организации ${organizationScope}` : ""; const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : ""; const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне"; const movementLabel = flow.value_flow_direction === "outgoing_supplier_payout" ? "исходящих платежей/списаний" : "входящих денежных поступлений"; const totalLabel = flow.value_flow_direction === "outgoing_supplier_payout" ? "сумма исходящих платежей/списаний составляет" : "сумма входящих денежных поступлений составляет"; const caveat = flow.value_flow_direction === "outgoing_supplier_payout" ? "Это расчет по найденным строкам 1С, а не подтверждение полного объема платежей вне проверенного окна." : "Это расчет по найденным строкам 1С, а не подтверждение полного объема поступлений вне проверенного окна."; const dates = flow.first_movement_date && flow.latest_movement_date ? ` Первая найденная дата движения: ${flow.first_movement_date}; последняя: ${flow.latest_movement_date}.` : ""; const limitCaveat = flow.coverage_limited_by_probe_limit ? " Лимит строк проверки достигнут; полный запрошенный период может быть покрыт не полностью." : ""; return `По найденным строкам ${movementLabel} в 1С${counterparty}${period} ${totalLabel} ${flow.total_amount_human_ru} Учтено строк с суммой: ${flow.rows_with_amount} из ${flow.rows_matched}.${dates}${limitCaveat} ${caveat}`; } function derivedValueFlowMonthlyLines(pilot) { const flow = pilot.derived_value_flow; if (!flow || flow.aggregation_axis !== "month" || flow.monthly_breakdown.length === 0) { return []; } return flow.monthly_breakdown.map((bucket) => { const monthLabel = monthLabelRu(bucket.month_bucket); if (flow.value_flow_direction === "outgoing_supplier_payout") { return `Помесячно: ${monthLabel} — заплатили ${bucket.total_amount_human_ru} по ${bucket.rows_with_amount} строкам с суммой`; } return `Помесячно: ${monthLabel} — получили ${bucket.total_amount_human_ru} по ${bucket.rows_with_amount} строкам с суммой`; }); } function sideDateRange(first, latest) { if (first && latest) { return ` первая дата ${first}, последняя ${latest}`; } return " даты движения не выделены"; } function derivedBidirectionalValueFlowConfirmedLine(pilot) { const flow = pilot.derived_bidirectional_value_flow; if (!flow) { return null; } const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : ""; const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне"; const incoming = flow.incoming_customer_revenue; const outgoing = flow.outgoing_supplier_payout; const netLabel = flow.net_direction === "net_incoming" ? "нетто в нашу сторону" : flow.net_direction === "net_outgoing" ? "нетто исходящий" : "нетто нулевое"; const limitCaveat = flow.coverage_limited_by_probe_limit ? " Лимит строк проверки достигнут хотя бы по одной стороне; полный запрошенный период может быть покрыт не полностью." : ""; return [ `По найденным строкам 1С${counterparty}${period}: получили ${incoming.total_amount_human_ru} по входящим движениям, заплатили ${outgoing.total_amount_human_ru} по исходящим платежам/списаниям.`, `Расчетное ${netLabel}: ${flow.net_amount_human_ru}`, `Входящие строки с суммой: ${incoming.rows_with_amount} из ${incoming.rows_matched};${sideDateRange(incoming.first_movement_date, incoming.latest_movement_date)}.`, `Исходящие строки с суммой: ${outgoing.rows_with_amount} из ${outgoing.rows_matched};${sideDateRange(outgoing.first_movement_date, outgoing.latest_movement_date)}.`, `${limitCaveat} Это расчет по найденным строкам 1С, а не подтверждение полного сальдо вне проверенного окна.` ] .join(" ") .replace(/\s+/g, " ") .trim(); } function derivedBidirectionalValueFlowMonthlyLines(pilot) { const flow = pilot.derived_bidirectional_value_flow; if (!flow || flow.aggregation_axis !== "month" || flow.monthly_breakdown.length === 0) { return []; } return flow.monthly_breakdown.map((bucket) => `Помесячно: ${monthLabelRu(bucket.month_bucket)} — получили ${bucket.incoming_total_amount_human_ru}, заплатили ${bucket.outgoing_total_amount_human_ru}, ${netLabelRu(bucket.net_direction)} ${bucket.net_amount_human_ru}`); } function businessOverviewNetDirectionRu(direction) { if (direction === "net_incoming") { return "операционный денежный поток в проверенном срезе больше входящий, чем исходящий"; } if (direction === "net_outgoing") { return "операционный денежный поток в проверенном срезе больше исходящий, чем входящий"; } return "входящий и исходящий денежный поток в проверенном срезе примерно сбалансированы"; } function amountHumanRu(value) { const rounded = Math.round(Math.abs(value) * 100) / 100; return `${new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 2 }).format(rounded)} руб.`; } function yearCountHumanRu(count) { const abs = Math.abs(count) % 100; const last = abs % 10; const noun = abs >= 11 && abs <= 14 ? "лет" : last === 1 ? "год" : last >= 2 && last <= 4 ? "года" : "лет"; return `${count} ${noun}`; } function percentOfTotal(part, total) { if (!Number.isFinite(part) || !Number.isFinite(total) || total <= 0) { return null; } return Math.round((part / total) * 10_000) / 100; } function percentText(part, total) { const pct = percentOfTotal(part, total); return pct === null ? null : `${pct}%`; } function inventoryStalenessRiskBandRu(riskBand) { if (riskBand === "high") { return "высокая зона внимания"; } if (riskBand === "elevated") { return "повышенная зона внимания"; } if (riskBand === "watch") { return "зона наблюдения"; } return "низкий видимый риск"; } function debtStalenessRiskBandRu(riskBand) { if (riskBand === "high") { return "высокая зона внимания"; } if (riskBand === "elevated") { return "повышенная зона внимания"; } if (riskBand === "watch") { return "зона наблюдения"; } return "низкий видимый риск"; } function derivedBusinessOverviewConfirmedLines(pilot) { const overview = pilot.derived_business_overview; if (!overview) { return []; } const organization = overview.organization_scope ? ` по организации ${overview.organization_scope}` : ""; const period = overview.period_scope ? ` за ${overview.period_scope}` : " за все доступное проверенное окно"; const lines = []; if (overview.incoming_customer_revenue.rows_with_amount > 0) { lines.push(`Входящие поступления${organization}${period}: ${overview.incoming_customer_revenue.total_amount_human_ru} по ${overview.incoming_customer_revenue.rows_with_amount} строкам с суммой.`); } if (overview.outgoing_supplier_payout.rows_with_amount > 0) { lines.push(`Исходящие платежи/списания${organization}${period}: ${overview.outgoing_supplier_payout.total_amount_human_ru} по ${overview.outgoing_supplier_payout.rows_with_amount} строкам с суммой.`); } const strongestIncomingYear = businessOverviewStrongestIncomingYear(overview); if (strongestIncomingYear) { lines.push(`Самый сильный год по подтвержденным входящим поступлениям: ${strongestIncomingYear.year_bucket} — ${strongestIncomingYear.incoming_total_amount_human_ru} по ${strongestIncomingYear.incoming_rows_with_amount} строкам с суммой. Это не бухгалтерская прибыль.`); } const incomingLeaderLine = businessOverviewIncomingLeaderLine(overview); if (incomingLeaderLine) { lines.push(incomingLeaderLine); } const outgoingLeaderLine = businessOverviewOutgoingLeaderLine(overview); if (outgoingLeaderLine) { lines.push(outgoingLeaderLine); } const vendorQualityText = businessOverviewVendorProcurementQualityText(overview); if (vendorQualityText) { lines.push(vendorQualityText); } if (overview.yearly_breakdown?.length) { lines.push(`Годовая раскладка операционного денежного потока построена по подтвержденным строкам 1С за ${yearCountHumanRu(overview.yearly_breakdown.length)}.`); } if (overview.incoming_customer_revenue.coverage_recovered_by_period_chunking || overview.outgoing_supplier_payout.coverage_recovered_by_period_chunking) { lines.push("Денежное покрытие бизнес-обзора за год восстановлено через помесячные 1С-проверки, а не только через широкий общий запрос."); } if (overview.activity_period) { lines.push(`Окно подтвержденной активности в 1С: ${overview.activity_period.first_activity_date} — ${overview.activity_period.latest_activity_date}; ориентировочно ${overview.activity_period.duration_human_ru}.`); } if (overview.document_activity_profile) { const profile = overview.document_activity_profile; const topDocument = profile.top_document_types[0]; const topSection = profile.top_account_sections[0]; const parts = []; if (topDocument) { const shareText = topDocument.share_pct === null ? "" : ` (${topDocument.share_pct}%)`; parts.push(`ведущий тип документов ${topDocument.document_type} — ${topDocument.count} документов${shareText}`); } if (topSection) { const shareText = topSection.share_pct === null ? "" : ` (${topSection.share_pct}%)`; parts.push(`ведущий раздел учета ${topSection.account_section} — ${topSection.operation_count} операций${shareText}`); } if (parts.length > 0) { lines.push(`Профиль операционной активности${organization}${period}: ${parts.join("; ")}. Это activity mix по найденным строкам 1С, а не аудит качества учета или полноты процессов.`); } } if (overview.counterparty_profile) { const profile = overview.counterparty_profile; const totalText = profile.total_counterparties === null ? `${profile.active_counterparties} активных контрагентов` : `${profile.total_counterparties} контрагентов в базе, ${profile.active_counterparties} активных по документальной активности`; lines.push(`Профиль контрагентской базы${organization}${period}: ${totalText}; заказчики ${profile.customer_only_count}, поставщики ${profile.supplier_only_count}, смешанная роль ${profile.mixed_role_count}. Это не CRM-аудит, не юридическая проверка контрагентов и не оценка качества клиентской базы.`); } if (overview.contract_usage_profile) { const profile = overview.contract_usage_profile; const totalText = profile.total_contracts === null ? `${profile.used_contracts} договоров с подтвержденной связью с операциями` : `${profile.used_contracts} из ${profile.total_contracts} договоров используются${profile.used_contract_share_pct === null ? "" : ` (${profile.used_contract_share_pct}%)`}`; const unusedText = profile.unused_contracts === null ? "" : `, неиспользуемых ${profile.unused_contracts}`; lines.push(`Договорной профиль${organization}${period}: ${totalText}${unusedText}. Это не contract-risk аудит, не проверка актуальности условий и не доказательство качества договорной базы.`); } if (overview.tax_position) { const taxDirection = overview.tax_position.net_vat_direction === "vat_to_pay" ? "к уплате" : overview.tax_position.net_vat_direction === "vat_to_recover_or_offset" ? "к вычету/зачету" : "сбалансирован"; lines.push(`НДС-позиция за ${overview.tax_position.period_scope}: книга продаж ${overview.tax_position.sales_vat_amount_human_ru}, книга покупок/вычеты ${overview.tax_position.purchase_vat_amount_human_ru}, нетто ${taxDirection} ${overview.tax_position.net_vat_amount_human_ru}.`); } if (overview.accounting_financial_result) { const accountingFinancialResultText = businessOverviewAccountingFinancialResultText(overview); if (accountingFinancialResultText) { lines.push(accountingFinancialResultText); } } if (overview.trading_margin_proxy) { const proxy = overview.trading_margin_proxy; const marginText = proxy.margin_to_revenue_pct === null ? "не рассчитана" : `${proxy.margin_to_revenue_pct}%`; lines.push(`Торговый margin proxy за ${proxy.period_scope}: выручка продаж ${proxy.sales_revenue_human_ru}, закупочный документный след ${proxy.purchase_cost_proxy_human_ru}, валовый спред proxy ${proxy.gross_spread_proxy_human_ru}, маржинальность к выручке ${marginText}. Это не чистая прибыль и не бухгалтерский финрезультат.`); } if (overview.debt_position) { const debtDirection = overview.debt_position.net_debt_position_direction === "net_receivable" ? "в пользу дебиторки" : overview.debt_position.net_debt_position_direction === "net_payable" ? "в сторону кредиторки" : "сбалансировано"; lines.push(`Долговой срез на ${overview.debt_position.as_of_date}: дебиторка ${overview.debt_position.receivables.total_amount_human_ru}, кредиторка ${overview.debt_position.payables.total_amount_human_ru}, нетто ${debtDirection} ${overview.debt_position.net_debt_position_amount_human_ru}.`); } if (overview.debt_open_settlement_quality) { const quality = overview.debt_open_settlement_quality; const topContract = quality.top_contracts[0]; const topContractText = topContract ? ` Крупнейший открытый договор: ${topContract.contract}${topContract.counterparty ? ` / ${topContract.counterparty}` : ""} — ${topContract.total_amount_human_ru}${topContract.share_of_gross_open_amount_pct === null ? "" : ` (${topContract.share_of_gross_open_amount_pct}%)`}.` : ""; lines.push(`Качество открытых расчетов на ${quality.as_of_date}: брутто открытых договорных остатков ${quality.gross_open_amount_human_ru}, договоров ${quality.unique_contracts}, контрагентов ${quality.unique_counterparties}.${topContractText}`); if (quality.age_signal?.oldest_start_date) { const ageText = quality.age_signal.max_age_days === null ? "" : `, максимальный возраст сигнала ${quality.age_signal.max_age_days} дн.`; lines.push(`Возрастной сигнал открытых расчетов: самая ранняя найденная дата договора ${quality.age_signal.oldest_start_date}${ageText}. Это не просрочка и не due-date анализ.`); } } if (overview.debt_staleness_risk_proxy) { const proxy = overview.debt_staleness_risk_proxy; const counterpartyText = proxy.top_contract_counterparty ? ` / ${proxy.top_contract_counterparty}` : ""; lines.push(`Staleness risk proxy открытых расчетов на ${proxy.as_of_date}: самый старый договорный сигнал ${proxy.oldest_contract_start_date}, возраст ${proxy.max_contract_age_days} дн.; старейший крупный договор ${proxy.top_contract}${counterpartyText} держит ${proxy.top_contract_amount_human_ru} (${proxy.top_contract_share_pct}% брутто открытых остатков); оценка ${debtStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная просрочка, не кредитный риск и не due-date aging.`); } const dueDateText = businessOverviewDebtDueDateAgingText(overview); if (dueDateText) { lines.push(dueDateText); } if (overview.inventory_position) { const leader = overview.inventory_position.top_items[0]; const leaderText = leader ? ` Крупнейшая подтвержденная позиция: ${leader.item} — ${leader.total_amount_human_ru}.` : ""; lines.push(`Складской срез на ${overview.inventory_position.as_of_date}: остаток ${overview.inventory_position.total_amount_human_ru} по ${overview.inventory_position.rows_with_amount} строкам с суммой и ${overview.inventory_position.rows_with_quantity} строкам с количеством.${leaderText}`); if (overview.inventory_position.aging_signal?.oldest_purchase_date) { const ageText = overview.inventory_position.aging_signal.max_age_days === null ? "" : `, максимальный возраст сигнала ${overview.inventory_position.aging_signal.max_age_days} дн.`; lines.push(`Возрастной сигнал склада: самая ранняя найденная дата закупки ${overview.inventory_position.aging_signal.oldest_purchase_date}${ageText}.`); } } if (overview.inventory_turnover_proxy) { const proxy = overview.inventory_turnover_proxy; const ratioText = proxy.sales_to_stock_amount_ratio === null ? "не рассчитано" : `${proxy.sales_to_stock_amount_ratio}x`; const stockShareText = proxy.stock_to_sales_revenue_pct === null ? "не рассчитана" : `${proxy.stock_to_sales_revenue_pct}%`; lines.push(`Оборотный proxy склада за ${proxy.period_scope}: продажи ${proxy.sales_revenue_human_ru}, остаток на ${proxy.as_of_date} ${proxy.inventory_amount_human_ru}, sales-to-stock ratio ${ratioText}, остаток к продажам ${stockShareText}. Это не полноценная складская ликвидность, не FIFO-оборачиваемость и не анализ устаревания.`); } if (overview.inventory_staleness_risk_proxy) { const proxy = overview.inventory_staleness_risk_proxy; lines.push(`Staleness risk proxy склада на ${proxy.as_of_date}: самая ранняя дата закупочного сигнала ${proxy.oldest_purchase_date}, возраст ${proxy.max_purchase_age_days} дн., sales-to-stock ${proxy.sales_to_stock_amount_ratio}x, оценка ${inventoryStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная неликвидность, не резерв и не ликвидационная стоимость.`); } const inventoryQualityEventsText = businessOverviewInventoryQualityEventsText(overview); if (inventoryQualityEventsText) { lines.push(inventoryQualityEventsText.replace(/^Коротко:\s*/u, "")); } return lines; } function businessOverviewCashSynthesisLine(overview) { const incoming = overview.incoming_customer_revenue; const outgoing = overview.outgoing_supplier_payout; if (incoming.rows_with_amount <= 0 && outgoing.rows_with_amount <= 0) { return null; } const checkedOperationalScale = Math.abs(incoming.total_amount) + Math.abs(outgoing.total_amount); return [ `Аналитический вывод по оборотам: проверенный операционный размах ${amountHumanRu(checkedOperationalScale)}; входящий поток ${incoming.total_amount_human_ru}, исходящий ${outgoing.total_amount_human_ru}.`, `${businessOverviewNetDirectionRu(overview.net_direction)}; расчетное нетто ${overview.net_amount_human_ru}.` ].join(" "); } function businessOverviewCustomerConcentrationLine(overview) { const leader = overview.top_customers[0]; if (!leader || overview.incoming_customer_revenue.total_amount <= 0) { return null; } const share = percentText(leader.total_amount, overview.incoming_customer_revenue.total_amount); if (isFinancialInstitutionBucket(leader)) { const base = share ? `Крупнейший входящий денежный источник ${leader.axis_value} дает около ${share} проверенных входящих поступлений (${leader.total_amount_human_ru})` : `Крупнейший входящий денежный источник в проверенном срезе: ${rankedBucketAmountLabel(leader)}`; const nonFinancial = firstNonFinancialInstitutionBucket(overview.top_customers.slice(1)); return `${base}. По названию это банк/финансовая организация, поэтому это не доказывает клиентскую выручку или зависимость от клиента.${nonFinancial ? ` Крупнейший небанковский входящий контрагент: ${rankedBucketAmountLabel(nonFinancial)}.` : ""}`; } return share ? `Концентрация входящего потока: крупнейший подтвержденный клиент ${leader.axis_value} дает около ${share} проверенных входящих поступлений (${leader.total_amount_human_ru}). Это сигнал зависимости от клиента, а не полный customer-risk аудит.` : `Крупнейший подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`; } function businessOverviewSupplierConcentrationLine(overview) { const leader = overview.top_suppliers?.[0]; if (!leader || overview.outgoing_supplier_payout.total_amount <= 0) { return null; } const share = percentText(leader.total_amount, overview.outgoing_supplier_payout.total_amount); if (isFinancialInstitutionBucket(leader)) { const base = share ? `Концентрация исходящего потока: крупнейший получатель исходящих денег ${leader.axis_value} держит около ${share} проверенных исходящих платежей (${leader.total_amount_human_ru})` : `Крупнейший получатель исходящих денег в проверенном срезе: ${rankedBucketAmountLabel(leader)}`; const nonFinancial = firstNonFinancialInstitutionBucket(overview.top_suppliers.slice(1)); return `${base}. По названию это банк/финансовая организация, поэтому это не доказательство зависимости от обычного поставщика без проверки назначения платежа/договора.${nonFinancial ? ` Крупнейший небанковский получатель исходящих денег: ${rankedBucketAmountLabel(nonFinancial)}.` : ""}`; } return share ? `Концентрация исходящего потока: крупнейший подтвержденный поставщик/получатель исходящих платежей ${leader.axis_value} держит около ${share} проверенных исходящих платежей (${leader.total_amount_human_ru}). Это сигнал концентрации закупок/исходящих платежей по найденным строкам, а не полный аудит поставщицкого риска или структура всех расходов.` : `Крупнейший подтвержденный поставщик/получатель исходящих платежей в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`; } function businessOverviewYearlyOperatingLine(overview) { const years = overview.yearly_breakdown ?? []; if (years.length === 0) { return null; } const strongestIncomingYear = businessOverviewStrongestIncomingYear(overview); const strongestNetYear = [...years] .filter((bucket) => bucket.net_amount !== 0) .sort((left, right) => right.net_amount - left.net_amount || left.year_bucket.localeCompare(right.year_bucket))[0]; if (!strongestIncomingYear && !strongestNetYear) { return null; } const parts = []; if (strongestIncomingYear) { parts.push(`самый сильный год по подтвержденным входящим поступлениям ${strongestIncomingYear.year_bucket}: ${strongestIncomingYear.incoming_total_amount_human_ru}`); } if (strongestNetYear) { const netText = strongestNetYear.net_direction === "net_outgoing" ? `нетто исходящее ${strongestNetYear.net_amount_human_ru}` : `нетто в плюс ${strongestNetYear.net_amount_human_ru}`; parts.push(`лучший год по расчетному операционному нетто ${strongestNetYear.year_bucket}: ${netText}`); } return `Годовая динамика по проверенным строкам: ${parts.join("; ")}. Это операционный денежный сигнал, не бухгалтерская прибыль и не финрезультат.`; } function businessOverviewRiskSynthesisLine(overview) { const signals = []; if (overview.tax_position) { const taxDirection = overview.tax_position.net_vat_direction === "vat_to_pay" ? `НДС к уплате ${overview.tax_position.net_vat_amount_human_ru}` : overview.tax_position.net_vat_direction === "vat_to_recover_or_offset" ? `НДС к вычету/зачету ${overview.tax_position.net_vat_amount_human_ru}` : "НДС-позиция сбалансирована"; signals.push(taxDirection); } if (overview.trading_margin_proxy) { const marginText = overview.trading_margin_proxy.margin_to_revenue_pct === null ? "маржинальность не рассчитана" : `маржинальность proxy ${overview.trading_margin_proxy.margin_to_revenue_pct}%`; signals.push(`торговый спред proxy ${overview.trading_margin_proxy.gross_spread_proxy_human_ru}, ${marginText}`); } if (overview.accounting_financial_result) { const result = overview.accounting_financial_result; const direction = result.final_result_direction === "profit" ? "учетная прибыль" : result.final_result_direction === "loss" ? "учетный убыток" : "нулевой учетный финрезультат"; const marginText = result.net_margin_to_revenue_pct === null ? "маржа к подтвержденной выручке не рассчитана" : `маржа к подтвержденной выручке ${result.net_margin_to_revenue_pct}%`; signals.push(`${direction} по закрытию счетов 90/91/99 ${result.final_result_amount_human_ru}, ${marginText}`); } if (overview.debt_position) { const debtDirection = overview.debt_position.net_debt_position_direction === "net_receivable" ? `дебиторка больше кредиторки на ${overview.debt_position.net_debt_position_amount_human_ru}` : overview.debt_position.net_debt_position_direction === "net_payable" ? `кредиторка больше дебиторки на ${overview.debt_position.net_debt_position_amount_human_ru}` : "дебиторка и кредиторка сбалансированы"; signals.push(debtDirection); } if (overview.debt_open_settlement_quality?.concentration_top_contract_pct !== null && overview.debt_open_settlement_quality?.top_contracts[0]) { const topContract = overview.debt_open_settlement_quality.top_contracts[0]; signals.push(`крупнейший открытый договор держит ${overview.debt_open_settlement_quality.concentration_top_contract_pct}% открытых остатков (${topContract.total_amount_human_ru})`); } if (overview.debt_open_settlement_quality?.age_signal?.max_age_days !== null && overview.debt_open_settlement_quality?.age_signal?.max_age_days !== undefined) { signals.push(`самый старый договорный возрастной сигнал ${overview.debt_open_settlement_quality.age_signal.max_age_days} дн.`); } if (overview.debt_staleness_risk_proxy) { signals.push(`staleness risk proxy открытых расчетов: ${debtStalenessRiskBandRu(overview.debt_staleness_risk_proxy.risk_band)}, возраст ${overview.debt_staleness_risk_proxy.max_contract_age_days} дн., концентрация старейшего крупного договора ${overview.debt_staleness_risk_proxy.top_contract_share_pct}%`); } if (overview.debt_due_date_aging) { const aging = overview.debt_due_date_aging; signals.push(aging.evidence_status === "confirmed_overdue" ? `due-date aging: подтвержденная просрочка ${aging.overdue_amount_human_ru}, строк ${aging.overdue_rows}` : aging.evidence_status === "no_payment_terms_configured" ? "due-date aging: проверено, но сроки оплаты в договорах не установлены; подтвержденной просрочки нет" : aging.evidence_status === "insufficient_due_date_basis" ? "due-date aging: не хватило даты расчетного документа для честного расчета просрочки" : `due-date aging: проверено, подтвержденной просрочки не найдено`); } if (overview.document_activity_profile) { const topDocument = overview.document_activity_profile.top_document_types[0]; const topSection = overview.document_activity_profile.top_account_sections[0]; const parts = [ topDocument ? `ведущий тип документов ${topDocument.document_type}` : null, topSection ? `ведущий раздел учета ${topSection.account_section}` : null ].filter((item) => Boolean(item)); if (parts.length > 0) { signals.push(`операционный activity mix: ${parts.join(", ")}`); } } if (overview.counterparty_profile) { const totalText = overview.counterparty_profile.total_counterparties === null ? `${overview.counterparty_profile.active_counterparties} активных контрагентов` : `${overview.counterparty_profile.total_counterparties} контрагентов, активных ${overview.counterparty_profile.active_counterparties}`; signals.push(`контрагентская база: ${totalText}`); } if (overview.contract_usage_profile) { const profile = overview.contract_usage_profile; const totalText = profile.total_contracts === null ? `${profile.used_contracts} используемых договоров` : `${profile.used_contracts}/${profile.total_contracts} договоров используются${profile.used_contract_share_pct === null ? "" : ` (${profile.used_contract_share_pct}%)`}`; signals.push(`договорной профиль: ${totalText}`); } if (overview.inventory_position) { signals.push(`складской остаток на дату ${overview.inventory_position.total_amount_human_ru}`); if (overview.inventory_position.aging_signal?.max_age_days !== null && overview.inventory_position.aging_signal?.max_age_days !== undefined) { signals.push(`самый старый складской purchase-date сигнал ${overview.inventory_position.aging_signal.max_age_days} дн.`); } } if (overview.inventory_turnover_proxy) { const ratioText = overview.inventory_turnover_proxy.sales_to_stock_amount_ratio === null ? "sales-to-stock не рассчитан" : `sales-to-stock ${overview.inventory_turnover_proxy.sales_to_stock_amount_ratio}x`; signals.push(`оборотный proxy склада: ${ratioText}`); } if (overview.inventory_staleness_risk_proxy) { signals.push(`staleness risk proxy склада: ${inventoryStalenessRiskBandRu(overview.inventory_staleness_risk_proxy.risk_band)}, возраст ${overview.inventory_staleness_risk_proxy.max_purchase_age_days} дн.`); } if (overview.inventory_quality_events) { const quality = overview.inventory_quality_events; if (quality.evidence_status === "reviewed_no_quality_events_found") { signals.push("складские quality-события: документы списания, оприходования, инвентаризации и переоценки проверены, подтвержденных событий не найдено"); } else { signals.push(`складские quality-события: списаний ${quality.writeoff_rows} на ${quality.writeoff_amount_human_ru}, оприходований/корректировок ${quality.receipt_adjustment_rows} на ${quality.receipt_adjustment_amount_human_ru}, инвентаризаций ${quality.inventory_count_rows}, переоценок ${quality.revaluation_rows}`); } } return signals.length > 0 ? `Риски и контуры внимания по подтвержденным данным: ${signals.join("; ")}.` : null; } function businessOverviewExecutiveVerdictLine(overview) { const hasCash = overview.incoming_customer_revenue.rows_with_amount > 0 || overview.outgoing_supplier_payout.rows_with_amount > 0; const hasTaxDebtInventorySignals = Boolean(overview.tax_position || overview.trading_margin_proxy || overview.debt_position || overview.debt_open_settlement_quality || overview.debt_staleness_risk_proxy || overview.inventory_position || overview.inventory_turnover_proxy || overview.inventory_staleness_risk_proxy || overview.inventory_quality_events); const hasOperationalProfileSignal = Boolean(overview.document_activity_profile || overview.counterparty_profile || overview.contract_usage_profile); const hasExtraSignals = hasTaxDebtInventorySignals || hasOperationalProfileSignal; if (!hasCash && !hasExtraSignals) { return null; } const cashTone = overview.net_direction === "net_incoming" ? "операционно входящий поток сильнее исходящего" : overview.net_direction === "net_outgoing" ? "операционно исходящий поток сильнее входящего, это зона внимания к расходам/закупкам" : "операционный поток выглядит сбалансированным"; const evidenceTone = hasTaxDebtInventorySignals ? "часть налоговых, долговых или складских контуров уже отдельно проверена" : hasOperationalProfileSignal ? "операционные профили по документам, контрагентам или договорам уже отдельно проверены" : "налоги, долги и склад еще не дают проверенного управленческого контекста"; return `Сводный LLM-аудит по подтвержденному: ${cashTone}; ${evidenceTone}. Это полезный управленческий срез по найденным строкам 1С, но не финальный вывод о прибыльности, марже или здоровье компании.`; } function derivedBusinessOverviewInferenceLines(pilot) { const overview = pilot.derived_business_overview; if (!overview) { return []; } return [ businessOverviewCashSynthesisLine(overview), businessOverviewCustomerConcentrationLine(overview), businessOverviewSupplierConcentrationLine(overview), businessOverviewYearlyOperatingLine(overview), businessOverviewRiskSynthesisLine(overview), businessOverviewExecutiveVerdictLine(overview), "Это аналитическая интерпретация подтвержденных строк, а не прибыль и не маржа: для финального управленческого вывода нужны отдельные расходы, себестоимость, закрывающие документы, долги, налоги и складская оборачиваемость." ].filter((line) => Boolean(line)); } function businessOverviewUnknownLines(pilot) { if (!pilot.derived_business_overview) { return userFacingUnknowns(pilot.evidence.unknown_facts); } return userFacingUnknowns(pilot.evidence.unknown_facts); } function appendValueFlowZeroResultUnknown(lines, pilot) { const zeroLine = valueFlowZeroResultUnknownLine(pilot); return zeroLine ? uniqueStrings([zeroLine, ...lines]) : lines; } function buildAssistantMcpDiscoveryAnswerDraft(pilot) { const mode = modeFor(pilot); const reasonCodes = [...pilot.reason_codes, ...pilot.evidence.reason_codes]; pushReason(reasonCodes, `answer_mode_${mode}`); if (pilot.evidence.unknown_facts.length > 0) { pushReason(reasonCodes, "answer_contains_unknown_fact_boundary"); } const businessOverviewInferenceLines = derivedBusinessOverviewInferenceLines(pilot); const derivedInferenceLine = derivedActivityInferenceLine(pilot) ?? derivedMetadataInferenceLine(pilot) ?? derivedRankedValueFlowInferenceLine(pilot) ?? derivedEntityResolutionInferenceLine(pilot); const inferenceLines = businessOverviewInferenceLines.length > 0 ? businessOverviewInferenceLines : derivedInferenceLine ? [derivedInferenceLine] : pilot.evidence.inferred_facts; if (inferenceLines.length > 0) { pushReason(reasonCodes, "answer_contains_bounded_inference"); } if (businessOverviewInferenceLines.length > 0) { pushReason(reasonCodes, "answer_contains_business_overview_analyst_synthesis"); } const derivedMetadataLine = derivedMetadataConfirmedLine(pilot); const derivedEntityResolutionLine = derivedEntityResolutionConfirmedLine(pilot); const derivedValueLine = derivedBidirectionalValueFlowConfirmedLine(pilot) ?? derivedRankedValueFlowConfirmedLine(pilot) ?? derivedValueFlowConfirmedLine(pilot); const monthlyConfirmedLines = derivedBidirectionalValueFlowMonthlyLines(pilot).length > 0 ? derivedBidirectionalValueFlowMonthlyLines(pilot) : derivedValueFlowMonthlyLines(pilot); const businessOverviewLines = derivedBusinessOverviewConfirmedLines(pilot); if (monthlyConfirmedLines.length > 0) { pushReason(reasonCodes, "answer_contains_monthly_breakdown"); } if (businessOverviewLines.length > 0) { pushReason(reasonCodes, "answer_contains_business_overview"); } if (pilot.derived_business_overview?.tax_position) { pushReason(reasonCodes, "answer_contains_business_overview_tax_position"); } if (pilot.derived_business_overview?.accounting_financial_result) { pushReason(reasonCodes, "answer_contains_business_overview_accounting_financial_result"); } if (pilot.derived_business_overview?.trading_margin_proxy) { pushReason(reasonCodes, "answer_contains_business_overview_trading_margin_proxy"); } if (pilot.derived_business_overview?.top_suppliers?.length) { pushReason(reasonCodes, "answer_contains_business_overview_supplier_concentration"); } if (pilot.derived_business_overview?.vendor_procurement_quality) { pushReason(reasonCodes, "answer_contains_business_overview_vendor_procurement_quality"); pushReason(reasonCodes, `answer_contains_business_overview_vendor_procurement_quality_${pilot.derived_business_overview.vendor_procurement_quality.evidence_status}`); } if (pilot.derived_business_overview?.yearly_breakdown?.length) { pushReason(reasonCodes, "answer_contains_business_overview_yearly_operating_breakdown"); } if (pilot.derived_business_overview?.document_activity_profile) { pushReason(reasonCodes, "answer_contains_business_overview_document_activity_profile"); } if (pilot.derived_business_overview?.counterparty_profile) { pushReason(reasonCodes, "answer_contains_business_overview_counterparty_profile"); } if (pilot.derived_business_overview?.contract_usage_profile) { pushReason(reasonCodes, "answer_contains_business_overview_contract_usage_profile"); } if (pilot.derived_business_overview?.debt_position) { pushReason(reasonCodes, "answer_contains_business_overview_debt_position"); } if (pilot.derived_business_overview?.debt_open_settlement_quality) { pushReason(reasonCodes, "answer_contains_business_overview_open_settlement_quality"); if (pilot.derived_business_overview.debt_open_settlement_quality.age_signal) { pushReason(reasonCodes, "answer_contains_business_overview_debt_age_signal"); } } if (pilot.derived_business_overview?.debt_staleness_risk_proxy) { pushReason(reasonCodes, "answer_contains_business_overview_debt_staleness_risk_proxy"); } if (pilot.derived_business_overview?.debt_due_date_aging) { pushReason(reasonCodes, "answer_contains_business_overview_debt_due_date_aging"); pushReason(reasonCodes, `answer_contains_business_overview_debt_due_date_aging_${pilot.derived_business_overview.debt_due_date_aging.evidence_status}`); } if (pilot.derived_business_overview?.inventory_position) { pushReason(reasonCodes, "answer_contains_business_overview_inventory_position"); } if (pilot.derived_business_overview?.inventory_turnover_proxy) { pushReason(reasonCodes, "answer_contains_business_overview_inventory_turnover_proxy"); } if (pilot.derived_business_overview?.inventory_staleness_risk_proxy) { pushReason(reasonCodes, "answer_contains_business_overview_inventory_staleness_risk_proxy"); } if (pilot.derived_business_overview?.inventory_quality_events) { pushReason(reasonCodes, "answer_contains_business_overview_inventory_quality_events"); pushReason(reasonCodes, `answer_contains_business_overview_inventory_quality_events_${pilot.derived_business_overview.inventory_quality_events.evidence_status}`); } if (pilot.derived_business_overview?.missing_proof_families?.length) { pushReason(reasonCodes, "answer_contains_business_overview_missing_proof_ledger"); } const confirmedLines = businessOverviewLines.length > 0 ? businessOverviewLines : pilot.derived_ranked_value_flow && derivedValueLine ? [derivedValueLine] : derivedValueLine ? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines] : valueFlowZeroResultConfirmedLine(pilot) ? [valueFlowZeroResultConfirmedLine(pilot)] : derivedEntityResolutionLine ? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine] : derivedMetadataLine ? [derivedMetadataLine] : pilot.evidence.confirmed_facts; const unknownLines = pilot.derived_business_overview ? businessOverviewUnknownLines(pilot) : pilot.derived_metadata_surface ? pilot.derived_metadata_surface.available_fields.length > 0 ? userFacingUnknowns(pilot.evidence.unknown_facts) : ["Детальный список полей этих объектов этим шагом не получен."] : appendValueFlowZeroResultUnknown(rankedValueFlowUnknownLines(pilot), pilot); return { schema_version: exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION, policy_owner: "assistantMcpDiscoveryAnswerAdapter", answer_mode: mode, headline: headlineFor(mode, pilot), confirmed_lines: uniqueStrings(confirmedLines), inference_lines: uniqueStrings(inferenceLines), unknown_lines: unknownLines, limitation_lines: userFacingLimitations([...pilot.query_limitations, ...pilot.evidence.query_limitations]), next_step_line: nextStepFor(mode, pilot), internal_mechanics_allowed: false, must_not_claim: buildMustNotClaim(pilot), reason_codes: uniqueStrings(reasonCodes) }; }