"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION = void 0; exports.buildAssistantMcpDiscoveryAnswerDraft = buildAssistantMcpDiscoveryAnswerDraft; 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("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_")); } function userFacingLimitations(values) { return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value)); } 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 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 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 documentOrMovementScopeRu(pilot) { const entity = firstEntityCandidate(pilot); const period = explicitDateScope(pilot); const entityPart = entity ? ` по контрагенту ${entity}` : ""; const periodPart = period ? ` за ${period}` : " в проверенном окне"; 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 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 (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С удалось углубиться в контур справочников и связанных объектов; это уже не общий обзор схемы, а следующий безопасный catalog drilldown."; } if (pilot.derived_metadata_surface && mode === "confirmed_with_bounded_inference") { if (pilot.derived_metadata_surface.ambiguity_detected) { return "По метаданным 1С найдены конкурирующие schema-поверхности; перед следующим шагом нужно удержать неоднозначность явно."; } 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С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк."; } 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 "По подтвержденной metadata-поверхности видно несколько конкурирующих data-lane, и без явного выбора дальше идти нельзя."; } 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 === "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 до одного семейства: ${surface.ambiguity_entity_sets.join(", ")}.`; } const routeLabel = metadataRouteFamilyLabelRu(surface.downstream_route_family); if (surface.selected_entity_set && routeLabel) { return `Следующим шагом могу пойти в ${routeLabel} по surface «${surface.selected_entity_set}» и уже искать подтвержденные данные, а не только схему.`; } } if (mode === "checked_sources_only" && pilot.query_limitations.length > 0) { return "Можно повторить проверку после восстановления MCP-доступа или сузить вопрос до конкретного контрагента/периода."; } 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 (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 (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 (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 selectedEntitySet = surface.selected_entity_set ? ` Выбранное family: ${surface.selected_entity_set}.` : ""; const selectedObjects = surface.selected_surface_objects.length > 0 ? ` Выбранные surface-объекты: ${surface.selected_surface_objects.slice(0, 6).join(", ")}.` : ""; const fields = surface.available_fields.length > 0 ? ` Доступные поля/секции: ${surface.available_fields.slice(0, 12).join(", ")}.` : ""; return `Подтвержденная metadata-поверхность 1С${scope}: ${surface.matched_rows} строк metadata-ответа.${entitySets}${objects}${selectedEntitySet}${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 `По подтвержденной metadata-поверхности видно несколько конкурирующих family: ${surface.ambiguity_entity_sets.join(", ")}. Следующий data-lane пока нельзя выбрать без явного сужения.`; } const routeLabel = metadataRouteFamilyLabelRu(surface.downstream_route_family); if (!surface.selected_entity_set || !routeLabel) { return null; } return `По подтвержденной metadata-поверхности следующий проверяемый шаг можно ограниченно оценить как ${routeLabel} через family «${surface.selected_entity_set}». Это еще не выполненный data-fetch, а только grounded выбор следующего контура.`; } 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 organization = ranking.organization_scope ? ` по организации ${ranking.organization_scope}` : ""; const period = ranking.period_scope ? ` за период ${ranking.period_scope}` : " в проверенном окне"; const directionLead = 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} строкам с суммой.${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 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"); } if (pilot.evidence.inferred_facts.length > 0) { pushReason(reasonCodes, "answer_contains_bounded_inference"); } const derivedInferenceLine = derivedActivityInferenceLine(pilot) ?? derivedMetadataInferenceLine(pilot) ?? derivedRankedValueFlowInferenceLine(pilot) ?? derivedEntityResolutionInferenceLine(pilot); const inferenceLines = derivedInferenceLine ? [derivedInferenceLine] : pilot.evidence.inferred_facts; 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); if (monthlyConfirmedLines.length > 0) { pushReason(reasonCodes, "answer_contains_monthly_breakdown"); } const confirmedLines = derivedValueLine ? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines] : derivedEntityResolutionLine ? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine] : derivedMetadataLine ? [...pilot.evidence.confirmed_facts, derivedMetadataLine] : pilot.evidence.confirmed_facts; 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: uniqueStrings(pilot.evidence.unknown_facts), 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) }; }