"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 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.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 headlineFor(mode, pilot) { const askedMonthlyBreakdown = pilot.derived_bidirectional_value_flow?.aggregation_axis === "month" || pilot.derived_value_flow?.aggregation_axis === "month"; 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 (mode === "confirmed_with_bounded_inference") { return "По данным 1С есть подтвержденная активность; длительность можно оценивать только как вывод из этих строк."; } if (mode === "bounded_inference_only") { return "Точный факт не подтвержден, но есть ограниченная оценка по найденной активности в 1С."; } if (mode === "needs_clarification") { return "Нужно уточнить контекст перед поиском в 1С."; } if (mode === "blocked") { return "Поиск в 1С заблокирован runtime-политикой до выполнения."; } return "Я проверил доступный контур, но подтвержденного факта для ответа не получил."; } function nextStepFor(mode, pilot) { if (mode === "needs_clarification") { return "Уточните контрагента, период или организацию, и я смогу выполнить проверку по 1С."; } 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.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 derivedValueFlowConfirmedLine(pilot) { const flow = pilot.derived_value_flow; if (!flow) { return null; } 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); const inferenceLines = derivedInferenceLine ? [derivedInferenceLine] : pilot.evidence.inferred_facts; const derivedValueLine = derivedBidirectionalValueFlowConfirmedLine(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] : 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) }; }