"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.composeAssistantAnswer = composeAssistantAnswer; function fallbackFromSummary(routeSummary) { if (!routeSummary || routeSummary.mode !== "deterministic_v2") { return "none"; } return routeSummary.fallback.type; } function uniqueStrings(values, limit = 6) { return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).slice(0, limit); } const UUID_PATTERN = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi; const LONG_HEX_PATTERN = /\b[0-9a-f]{24,}\b/gi; const RAW_REF_BLOB_PATTERN = /\bevidence_source_ref_v1\|[^\s,;]+/gi; const RAW_REF_TOKEN_PATTERN = /\b(?:source_ref|canonical_ref|entity_id|fragment_id|guid|uuid)\b/gi; const SYNTHETIC_PLACEHOLDER_PATTERN = /\bunknown_entity(?::[^\s,;]+)?\b/gi; const SYNTHETIC_FALLBACK_MARKER_PATTERN = /\b(?:unknown_source|unknown_record)\b/gi; const SYNTHETIC_ROUTE_TOKEN_PATTERN = /\bbatch_refresh_then_store:[^\s,;]+/gi; const CYRILLIC_MOJIBAKE_FRAGMENT_PATTERN = /(?:[\u0420\u0421][\u0080-\u04FF\u2000-\u20CF]){2,}/u; const LATIN_MOJIBAKE_FRAGMENT_PATTERN = /(?:[\u00D0\u00D1][\u0080-\u00FF]){2,}/u; const SHORT_CYRILLIC_MOJIBAKE_TOKEN_PATTERN = /^[\u0420\u0421][\u0080-\u04FF\u2000-\u20CF]{1,2}$/u; const PREFIXED_SHORT_CYRILLIC_MOJIBAKE_TOKEN_PATTERN = /^[\p{L}\p{N}_-]+[\u0420\u0421][\u0080-\u04FF\u2000-\u20CF]{1,2}$/u; const MOJIBAKE_SINGLE_MARKER_PATTERN = /^[\u0420\u0421\u00D0\u00D1]$/u; const MOJIBAKE_MARKER_CHAR_PATTERN = /[\u0402\u0403\u040A\u040C\u040E\u040F\u0452\u0453\u0459\u045A\u045C\u045E\u045F\u201A\u201E\u2020\u2021\u2026\u2030\u20AC\u2122]/u; const CYRILLIC_MOJIBAKE_FRAGMENT_GLOBAL_PATTERN = /(?:[\u0420\u0421][\u0080-\u04FF\u2000-\u20CF]){2,}/gu; const LATIN_MOJIBAKE_FRAGMENT_GLOBAL_PATTERN = /(?:[\u00D0\u00D1][\u0080-\u00FF]){2,}/g; const MOJIBAKE_MARKER_CHAR_GLOBAL_PATTERN = /[\u0402\u0403\u040A\u040C\u040E\u040F\u0452\u0453\u0459\u045A\u045C\u045E\u045F\u201A\u201E\u2020\u2021\u2026\u2030\u20AC\u2122]/gu; function normalizeToken(value) { return value.replace(/^[^\p{L}\p{N}_-]+|[^\p{L}\p{N}_-]+$/gu, ""); } function isLikelyMojibakeToken(value) { const token = normalizeToken(String(value ?? "")); if (!token) { return false; } if (MOJIBAKE_SINGLE_MARKER_PATTERN.test(token)) { return true; } if (SHORT_CYRILLIC_MOJIBAKE_TOKEN_PATTERN.test(token)) { return true; } if (token.length <= 8 && PREFIXED_SHORT_CYRILLIC_MOJIBAKE_TOKEN_PATTERN.test(token)) { return true; } return CYRILLIC_MOJIBAKE_FRAGMENT_PATTERN.test(token) || LATIN_MOJIBAKE_FRAGMENT_PATTERN.test(token); } function countMojibakeTokens(value) { return String(value ?? "") .split(/[\s,.;:!?()[\]{}"']+/g) .filter((token) => token.length > 0) .filter((token) => isLikelyMojibakeToken(token)).length; } function countMojibakeSingleMarkers(value) { return String(value ?? "") .split(/[\s,.;:!?()[\]{}"']+/g) .filter((token) => token.length > 0) .map((token) => normalizeToken(token)) .filter((token) => MOJIBAKE_SINGLE_MARKER_PATTERN.test(token)).length; } function stripMojibakeFragments(value) { const removedByToken = String(value ?? "") .split(/(\s+)/g) .map((part) => { if (/^\s+$/u.test(part)) { return part; } return isLikelyMojibakeToken(part) ? "" : part; }) .join(""); return removedByToken .replace(CYRILLIC_MOJIBAKE_FRAGMENT_GLOBAL_PATTERN, "") .replace(LATIN_MOJIBAKE_FRAGMENT_GLOBAL_PATTERN, "") .replace(MOJIBAKE_MARKER_CHAR_GLOBAL_PATTERN, "") .replace(/\s+([,.;:!?])/g, "$1") .replace(/\s{2,}/g, " ") .trim(); } function looksLikeMojibake(value) { const text = String(value ?? ""); if (!text.trim()) { return false; } const tokenHits = countMojibakeTokens(text); const singleMarkers = countMojibakeSingleMarkers(text); if (tokenHits >= 2 || (tokenHits >= 1 && singleMarkers >= 1) || singleMarkers >= 3) { return true; } if (MOJIBAKE_MARKER_CHAR_PATTERN.test(text)) { return true; } if (CYRILLIC_MOJIBAKE_FRAGMENT_PATTERN.test(text) || LATIN_MOJIBAKE_FRAGMENT_PATTERN.test(text)) { return true; } if (/\uFFFD/u.test(text)) { return true; } return false; } function looksLikeTechnicalIdentifier(value) { const text = String(value ?? "").trim(); if (!text) { return false; } if (UUID_PATTERN.test(text)) { UUID_PATTERN.lastIndex = 0; return true; } UUID_PATTERN.lastIndex = 0; if (LONG_HEX_PATTERN.test(text)) { LONG_HEX_PATTERN.lastIndex = 0; return true; } LONG_HEX_PATTERN.lastIndex = 0; return /(?:evidence_source_ref_v1\||cmp%3a|batch_refresh_then_store:|^cmp:)/i.test(text); } function scrubRawTechnicalRefs(value) { const raw = String(value ?? "").trim(); if (!raw) { return ""; } return raw .replace(RAW_REF_BLOB_PATTERN, "linked source") .replace(UUID_PATTERN, "[id]") .replace(LONG_HEX_PATTERN, "[id]") .replace(RAW_REF_TOKEN_PATTERN, "reference") .replace(/\(\s*\[id\]\s*\)/g, "") .replace(/\[\s*id\s*\](?:\s*,\s*\[\s*id\s*\])+/g, "[id]") .replace(/\s{2,}/g, " ") .trim(); } function stripSyntheticPlaceholders(value) { return String(value ?? "") .replace(SYNTHETIC_PLACEHOLDER_PATTERN, "") .replace(SYNTHETIC_FALLBACK_MARKER_PATTERN, "") .replace(SYNTHETIC_ROUTE_TOKEN_PATTERN, "") .replace(/[;,:]\s*[;,:]+/g, "; ") .replace(/\s{2,}/g, " ") .trim(); } function sanitizeUserFacingReply(value) { const normalized = scrubRawTechnicalRefs(value).replace(/[ \t]+\n/g, "\n"); const cleanedLines = normalized .split(/\r?\n/g) .map((line) => stripSyntheticPlaceholders(line)) .map((line) => stripMojibakeFragments(line)) .map((line) => line.trim()) .filter((line) => line.length > 0) .filter((line) => !looksLikeMojibake(line)); const cleaned = cleanedLines.join("\n").replace(/\n{3,}/g, "\n\n").trim(); return cleaned || "Available data requires clarification for a reliable user-facing answer."; } function sanitizeUserText(value) { const normalized = stripMojibakeFragments(stripSyntheticPlaceholders(scrubRawTechnicalRefs(String(value ?? "").replace(/\s+/g, " ").trim()))); if (!normalized) { return null; } if (looksLikeMojibake(normalized)) { return null; } return normalized; } function sanitizeUserLines(values, limit = 6) { const cleaned = values .map((item) => sanitizeUserText(item)) .filter((item) => Boolean(item)); return uniqueStrings(cleaned, limit); } function formatList(items) { if (items.length === 0) { return ""; } return items.map((item) => `- ${item}`).join("\n"); } function formatSafeItemLine(entity, sourceId, riskScore) { const entityLabel = sanitizeUserText(String(entity ?? "")) ?? "Record"; const idRaw = String(sourceId ?? "").trim(); const exposeId = idRaw.length > 0 && !looksLikeTechnicalIdentifier(idRaw); const subject = exposeId ? `${entityLabel} (${idRaw})` : entityLabel; if (riskScore !== undefined) { return `${subject} - risk ${String(riskScore)}.`; } return `${subject}.`; } function extractTopFacts(results) { const lines = []; for (const result of results.filter((item) => item.status === "ok").slice(0, 3)) { if (result.result_type === "chain") { const top = result.items.slice(0, 3).map((item) => { const counterparty = String(item.counterparty_id ?? "").trim(); const operations = String(item.operations_count ?? "0"); const docs = String(item.document_refs_count ?? "0"); const counterpartyLabel = counterparty.length > 0 && !looksLikeTechnicalIdentifier(counterparty) ? `Counterparty ${counterparty}` : "Counterparty"; return `${counterpartyLabel}: operations ${operations}, linked docs ${docs}.`; }); lines.push(...top); continue; } if (result.result_type === "ranking") { const top = result.items .slice(0, 5) .map((item) => `${item.rank ?? "*"}. ${String(item.entity ?? "Entity")} - ${String(item.records_count ?? 0)}.`); lines.push(...top); continue; } if (result.result_type === "list") { const top = result.items.slice(0, 5).map((item) => { if (item.risk_score !== undefined) { return formatSafeItemLine(item.source_entity ?? "Record", item.source_id ?? "", item.risk_score); } return formatSafeItemLine(item.source_entity ?? "Record", item.source_id ?? ""); }); lines.push(...top); continue; } const top = result.items .slice(0, 3) .map((item) => formatSafeItemLine(item.source_entity ?? "Record", item.source_id ?? "")); lines.push(...top); } return sanitizeUserLines(lines, 8); } function extractWhyIncluded(results) { return sanitizeUserLines(results.flatMap((item) => item.why_included)); } function extractSelectionReasons(results) { return sanitizeUserLines(results.flatMap((item) => item.selection_reason)); } function extractRiskFactors(results) { return sanitizeUserLines(results.flatMap((item) => item.risk_factors)); } function extractBusinessInterpretation(results) { return sanitizeUserLines(results.flatMap((item) => item.business_interpretation)); } function extractLimitations(results) { return sanitizeUserLines(results.flatMap((item) => item.limitations), 10); } function summaryValue(result, key) { const summary = result.summary ?? {}; return Object.prototype.hasOwnProperty.call(summary, key) ? summary[key] : undefined; } function summaryBoolean(result, key) { return summaryValue(result, key) === true; } function summaryString(result, key) { const value = summaryValue(result, key); return typeof value === "string" ? value : null; } function summaryNumber(result, key) { const value = summaryValue(result, key); return typeof value === "number" && Number.isFinite(value) ? value : null; } function summaryStringArray(result, key) { const value = summaryValue(result, key); if (!Array.isArray(value)) { return []; } return sanitizeUserLines(value.map((item) => String(item)), 6); } function buildFallbackWhyIncluded(results) { const lines = []; for (const result of results.slice(0, 2)) { const routeFocus = summaryString(result, "route_focus"); const sourceRecords = summaryNumber(result, "source_records"); const filteredRecords = summaryNumber(result, "filtered_records_after_narrowing"); const checkedRecords = summaryNumber(result, "checked_records"); if (routeFocus) { lines.push(`Проверка выполнена по профилю ${routeFocus}.`); } if (sourceRecords !== null && filteredRecords !== null && filteredRecords < sourceRecords) { lines.push(`Применено сужение выборки: ${filteredRecords} из ${sourceRecords} записей.`); } if (checkedRecords !== null) { lines.push(`Проверено записей в текущем проходе: ${checkedRecords}.`); } } return sanitizeUserLines(lines, 4); } function buildFallbackSelectionReasons(results) { const lines = []; for (const result of results.slice(0, 2)) { if (summaryBoolean(result, "semantic_narrowing_applied")) { lines.push("Отбор выполнен по семантическому сужению предметной области."); } const rankingBasis = summaryStringArray(result, "ranking_basis"); if (rankingBasis.length > 0) { lines.push(`Ранжирование основано на: ${rankingBasis.join(", ")}.`); } if (summaryBoolean(result, "broad_guard_applied")) { lines.push("Применен broad-query guard для контроля ложной точности."); } } if (lines.length === 0) { lines.push("Отбор выполнен по совпадению предметных сигналов и доступной evidence-опоры."); } return sanitizeUserLines(lines, 4); } function suggestNextStep(requirements, coverage) { const next = []; if (coverage.clarification_needed_for.length > 0) { next.push("Уточните период, счет, документ или контрагента для требований: " + coverage.clarification_needed_for.join(", ") + "."); } if (coverage.requirements_uncovered.length > 0) { next.push("Проверьте непокрытые требования: " + coverage.requirements_uncovered.join(", ") + "."); } if (coverage.out_of_scope_requirements.length > 0) { next.push("Часть запроса вне текущего учетного контура: " + coverage.out_of_scope_requirements.join(", ") + "."); } if (next.length === 0 && requirements.length > 0) { next.push("Следующим шагом можно открыть технический разбор и углубить проверку по выбранным объектам."); } return next; } const PROBLEM_HEAVY_TYPES = new Set([ "document_conflict", "broken_chain_segment", "lifecycle_anomaly_node", "unresolved_settlement_cluster", "period_risk_cluster", "cross_branch_inconsistency_cluster" ]); function flattenEvidence(results) { return results.flatMap((item) => item.evidence); } function flattenProblemUnits(results) { const units = []; for (const result of results) { if (!Array.isArray(result.problem_units)) { continue; } units.push(...result.problem_units); } const byId = new Map(); for (const unit of units) { byId.set(unit.problem_unit_id, unit); } return Array.from(byId.values()); } function selectProblemUnitSummary(results) { let selected = null; for (const result of results) { if (!result.problem_unit_summary) { continue; } if (!selected || result.problem_unit_summary.units_total > selected.units_total) { selected = result.problem_unit_summary; } } return selected; } function formatAffectedScope(unit) { const accountScope = sanitizeUserLines(unit.affected_accounts, 2); const counterpartyScope = sanitizeUserLines(unit.affected_counterparties, 2); const documentScope = sanitizeUserLines(unit.affected_documents, 2); const entityScope = sanitizeUserLines(unit.affected_entities, 2); const scopeParts = []; if (accountScope.length > 0) { scopeParts.push(`accounts: ${accountScope.join(", ")}`); } if (counterpartyScope.length > 0) { scopeParts.push(`counterparties: ${counterpartyScope.join(", ")}`); } if (documentScope.length > 0) { scopeParts.push(`documents: ${documentScope.join(", ")}`); } if (scopeParts.length === 0 && entityScope.length > 0) { scopeParts.push(`entities: ${entityScope.join(", ")}`); } if (scopeParts.length === 0) { return "affected scope requires clarification"; } return scopeParts.join("; "); } function formatLifecycleScope(unit) { if (!unit.lifecycle_domain) { return null; } const parts = [`domain=${unit.lifecycle_domain}`]; if (unit.current_lifecycle_state) { parts.push(`current=${unit.current_lifecycle_state}`); } if (unit.expected_lifecycle_state) { parts.push(`expected=${unit.expected_lifecycle_state}`); } if (unit.lifecycle_defect_type) { parts.push(`defect=${unit.lifecycle_defect_type}`); } if (unit.missing_transition) { parts.push(`missing_transition=${unit.missing_transition}`); } if (unit.invalid_transition) { parts.push(`invalid_transition=${unit.invalid_transition}`); } if (unit.stale_duration) { parts.push(`stale_duration=${unit.stale_duration}`); } return parts.join(", "); } function rankProblemUnitsForAnswer(units, lifecycleAnswerEnabled) { if (!lifecycleAnswerEnabled) { return units.slice().sort((left, right) => { const severityDiff = right.severity.score - left.severity.score; if (severityDiff !== 0) return severityDiff; return right.confidence.score - left.confidence.score; }); } return units.slice().sort((left, right) => { const lifecycleRankDiff = (right.lifecycle_ranking_score ?? 0) - (left.lifecycle_ranking_score ?? 0); if (lifecycleRankDiff !== 0) return lifecycleRankDiff; const lifecycleConfidenceDiff = (right.lifecycle_confidence?.score ?? 0) - (left.lifecycle_confidence?.score ?? 0); if (lifecycleConfidenceDiff !== 0) return lifecycleConfidenceDiff; const severityDiff = right.severity.score - left.severity.score; if (severityDiff !== 0) return severityDiff; return right.confidence.score - left.confidence.score; }); } function hasLifecycleResolution(units) { return units.some((unit) => Boolean(unit.lifecycle_domain) && Boolean(unit.current_lifecycle_state) && Boolean(unit.expected_lifecycle_state) && Boolean(unit.lifecycle_defect_type)); } function buildProblemCentricActions(input) { const actions = []; const unitTypes = new Set(input.units.map((item) => item.problem_unit_type)); if (unitTypes.has("broken_chain_segment")) { actions.push("Проверьте связку выписка -> документ -> проводка по проблемным участкам цепочки."); } if (unitTypes.has("unresolved_settlement_cluster")) { actions.push("Сверьте хвосты по расчетам: закрылся ли документ оплаты корректным закрывающим документом."); } if (unitTypes.has("period_risk_cluster")) { actions.push("Оцените влияние дефекта на закрытие периода и корректность регламентных операций."); } if (unitTypes.has("cross_branch_inconsistency_cluster")) { actions.push("Сверьте противоречия между документами, проводками и регистрами по НДС/межконтурным связям."); } if (unitTypes.has("lifecycle_anomaly_node")) { actions.push("Проверьте lifecycle объекта: ожидаемый этап не должен оставаться в partially_linked состоянии."); } for (const unit of input.units) { if (unit.lifecycle_defect_type === "stale_active_state") { actions.push("Проверьте, почему объект завис: ожидаемый переход не должен оставаться в активной стадии."); } if (unit.lifecycle_defect_type === "misclosed_state") { actions.push("Проверьте закрывающий документ и проводки: закрытие может быть формальным, но некорректным по пути."); } if (unit.lifecycle_defect_type === "cross_branch_state_conflict") { actions.push("Сверьте бухгалтерскую и смежную ветки (например, НДС/расчеты): обнаружен межконтурный конфликт состояния."); } } if (input.mode === "clarification_required") { if (input.missingAnchors.period) { actions.push("Уточните период проверки, чтобы зафиксировать границы проблемного контура."); } if (input.missingAnchors.account) { actions.push("Уточните счет или группу счетов для предметной локализации дефекта."); } if (input.missingAnchors.documentOrObject) { actions.push("Укажите конкретный документ или объект трассировки для проверки механизма отклонения."); } if (input.missingAnchors.counterparty) { actions.push("Укажите контрагента/договор, чтобы проверить хвосты и разрывы на конкретной связке."); } } if (input.coverageReport.requirements_uncovered.length > 0) { actions.push(`Закройте непокрытые требования: ${input.coverageReport.requirements_uncovered.join(", ")}.`); } return uniqueStrings(actions, 6); } function buildProblemCentricClarifications(input) { if (input.mode !== "clarification_required") { return []; } const questions = []; const unitTypes = new Set(input.units.map((item) => item.problem_unit_type)); if (input.missingAnchors.period) { questions.push("Уточните период (например, 2020-06), в котором нужно проверить проблемный кластер."); } if (input.missingAnchors.account) { questions.push("Уточните счет или связку счетов (например, 51/60), где вы ожидаете дефект."); } if (input.missingAnchors.documentOrObject) { questions.push("Укажите документ/объект, от которого нужно строить проверку цепочки."); } if (input.missingAnchors.counterparty) { questions.push("Укажите контрагента или договор, по которому проверить незакрытую экспозицию."); } if (unitTypes.has("broken_chain_segment")) { questions.push("Уточните участок цепочки: выписка, платежный документ или проводка."); } if (unitTypes.has("period_risk_cluster")) { questions.push("Уточните, какой этап закрытия периода критичен: начисление, закрытие счетов или НДС-блок."); } if (unitTypes.has("unresolved_settlement_cluster")) { questions.push("Уточните, интересуют хвосты поставщиков, покупателей или оба направления."); } if (input.coverageReport.clarification_needed_for.length > 0) { questions.push(`Закройте уточнения для требований: ${input.coverageReport.clarification_needed_for.join(", ")}.`); } return uniqueStrings(questions, 6); } function buildClaimEvidenceLinks(results) { const byClaim = new Map(); for (const evidence of flattenEvidence(results)) { const claimRef = String(evidence.claim_ref ?? "").trim(); const evidenceId = String(evidence.evidence_id ?? "").trim(); if (!claimRef || !evidenceId) { continue; } const current = byClaim.get(claimRef) ?? []; current.push(evidenceId); byClaim.set(claimRef, current); } return Array.from(byClaim.entries()) .slice(0, 10) .map(([claim_ref, evidenceIds]) => ({ claim_ref, evidence_ids: uniqueStrings(evidenceIds, 10) })); } function aggregatePolicySignals(results) { const broad_query_detected = results.some((item) => summaryBoolean(item, "broad_query_detected")); const broad_result_flag = results.some((item) => summaryBoolean(item, "broad_result_flag")); const minimum_evidence_failed = results.some((item) => summaryBoolean(item, "minimum_evidence_failed")); let degraded_to = null; for (const result of results) { const degraded = summaryString(result, "degraded_to"); if (degraded === "clarification") { degraded_to = "clarification"; break; } if (degraded === "partial") { degraded_to = "partial"; } } const narrowingOrder = { weak: 0, medium: 1, strong: 2 }; let narrowing_strength = null; for (const result of results) { const value = summaryString(result, "narrowing_strength"); if (value !== "weak" && value !== "medium" && value !== "strong") { continue; } if (!narrowing_strength || narrowingOrder[value] < narrowingOrder[narrowing_strength]) { narrowing_strength = value; } } return { broad_query_detected, broad_result_flag, minimum_evidence_failed, degraded_to, narrowing_strength }; } function confidenceToScore(value) { if (value === "high") return 3; if (value === "medium") return 2; return 1; } function aggregateConfidence(results, evidenceItems) { const scores = []; for (const evidence of evidenceItems) { scores.push(confidenceToScore(evidence.confidence)); } for (const result of results) { if (result.status === "error") { continue; } scores.push(confidenceToScore(result.confidence)); } if (scores.length === 0) { return "low"; } const average = scores.reduce((acc, item) => acc + item, 0) / scores.length; if (average >= 2.6) return "high"; if (average >= 1.8) return "medium"; return "low"; } function collectLimitationReasonCodes(evidenceItems) { const codes = evidenceItems .map((item) => item.limitation?.reason_code ?? null) .filter((item) => Boolean(item)); return uniqueStrings(codes, 8); } function limitationReasonToText(code) { if (code === "snapshot_only") return "Evidence is snapshot-only and may lag source-of-record."; if (code === "heuristic_inference") return "Part of the conclusion relies on heuristic inference."; if (code === "missing_mechanism") return "Mechanism is unresolved for part of the evidence."; if (code === "weak_source_mapping") return "Source mapping is weak for part of the evidence."; if (code === "insufficient_detail") return "Evidence lacks detail for a strong factual claim."; return "Some evidence limitations remain unresolved."; } function detectMissingAnchors(userMessage) { const lower = String(userMessage ?? "").toLowerCase(); const hasPeriod = /\b20\d{2}(?:[-./](?:0[1-9]|1[0-2]))?\b/.test(lower); const hasAccount = /(?:\bсчет\b|\baccount\b|\bschet\b|\b\d{2}(?:\.\d{2})?\b)/i.test(lower); const hasDocumentOrObject = /(?:документ|invoice|guid|object|obj|#\d+|\bid\b|\bref\b|dokument|doc)/i.test(lower); const hasCounterparty = /(?:контрагент|supplier|buyer|customer|kontragent|postavsh|pokupatel)/i.test(lower); const hasAnomalyType = /(?:аномал|risk|отклон|разрыв|mismatch|duplicate|tail|цепочк|anomali|hvost)/i.test(lower); return { period: !hasPeriod, account: !hasAccount, documentOrObject: !hasDocumentOrObject, counterparty: !hasCounterparty, anomalyType: !hasAnomalyType }; } function buildClarificationQuestions(input) { const questions = []; const shouldAsk = input.mode === "clarification_required" || input.coverageReport.clarification_needed_for.length > 0; if (!shouldAsk) { return questions; } if (input.missingAnchors.period) { questions.push("Уточните период проверки (например, 2020-06)."); } if (input.missingAnchors.account) { questions.push("Уточните счет или группу счетов (например, 19, 60, 62)."); } if (input.missingAnchors.documentOrObject) { questions.push("Укажите документ/GUID/конкретный объект для трассировки."); } if (input.missingAnchors.counterparty) { questions.push("Укажите контрагента или группу контрагентов."); } if (input.policySignals.broad_query_detected && input.missingAnchors.anomalyType) { questions.push("Уточните тип отклонения: разрыв цепочки, неверный документ или аномальный риск."); } if (input.coverageReport.clarification_needed_for.length > 0) { questions.push(`Закройте уточнения для требований: ${input.coverageReport.clarification_needed_for.join(", ")}.`); } return uniqueStrings(questions, 6); } function buildRecommendedActions(input) { const actions = []; if (input.mode === "focused_grounded") { actions.push("Проверьте 1-2 ключевые записи в учетной базе и зафиксируйте итог в рабочем файле проверки."); } if (input.mode === "broad_partial") { actions.push("Сузьте запрос до периода + счета или периода + документа и повторите проверку."); } if (input.mode === "clarification_required") { actions.push("Дайте недостающие якоря (период/счет/объект), иначе сильный factual вывод невозможен."); } if (input.coverageReport.requirements_uncovered.length > 0) { actions.push(`Закройте непокрытые требования: ${input.coverageReport.requirements_uncovered.join(", ")}.`); } if (input.coverageReport.requirements_partially_covered.length > 0) { actions.push(`Доуточните частично покрытые требования: ${input.coverageReport.requirements_partially_covered.join(", ")}.`); } if (input.policySignals.broad_query_detected && input.policySignals.narrowing_strength !== "strong") { actions.push("Добавьте более узкий контекст: тип отклонения, группу документов и бизнес-участок."); } if (input.limitationReasonCodes.includes("snapshot_only")) { actions.push("Сверьте критичные выводы с live source-of-record в 1C."); } if (input.limitationReasonCodes.includes("weak_source_mapping")) { actions.push("Проверьте source mapping для связей document/register по указанным ref."); } if (input.sourceRefs.length > 0) { actions.push(`Начните проверку с ${input.sourceRefs.length} подтвержденных записей и сверьте их с первичными документами.`); } return uniqueStrings(actions, 6); } function firstMeaningfulFact(results) { const facts = extractTopFacts(results); return facts.length > 0 ? facts[0] : null; } function buildPolicyDecision(input) { const hasCoverageGaps = input.coverageReport.requirements_uncovered.length > 0 || input.coverageReport.requirements_partially_covered.length > 0 || input.coverageReport.clarification_needed_for.length > 0 || input.coverageReport.out_of_scope_requirements.length > 0; if (input.fallbackType === "out_of_scope" && input.coverageReport.requirements_covered === 0) { return { mode: "out_of_scope", fallback_type: "out_of_scope", reply_type: "out_of_scope" }; } if (input.groundingCheck.status === "route_mismatch_blocked") { return { mode: "route_mismatch", fallback_type: "partial", reply_type: "route_mismatch_blocked" }; } if ((input.policySignals.degraded_to === "clarification" && input.policySignals.minimum_evidence_failed) || (input.fallbackType === "clarification" && !input.hasSupport) || (input.groundingCheck.status === "no_grounded_answer" && !input.hasSupport)) { return { mode: "clarification_required", fallback_type: "clarification", reply_type: "clarification_required" }; } if (input.errorResults.length > 0 && input.okResults.length === 0 && input.partialResults.length === 0) { return { mode: "backend_error", fallback_type: input.fallbackType, reply_type: "backend_error" }; } if (input.okResults.length === 0 && input.partialResults.length === 0 && input.emptyResults.length > 0) { return { mode: "empty", fallback_type: input.fallbackType, reply_type: "empty_but_valid" }; } if (input.groundingCheck.status === "no_grounded_answer" && input.okResults.length === 0 && input.partialResults.length === 0) { return { mode: "no_grounded", fallback_type: input.fallbackType, reply_type: "no_grounded_answer" }; } if (input.focusedStrong && !input.policySignals.broad_query_detected && !input.policySignals.minimum_evidence_failed && !hasCoverageGaps) { return { mode: "focused_grounded", fallback_type: "none", reply_type: "factual_with_explanation" }; } if (input.okResults.length > 0 || input.partialResults.length > 0 || hasCoverageGaps || input.policySignals.minimum_evidence_failed || input.policySignals.broad_result_flag || input.groundingCheck.status === "partial") { return { mode: "broad_partial", fallback_type: "partial", reply_type: "partial_coverage" }; } return { mode: "backend_error", fallback_type: "unknown", reply_type: "backend_error" }; } function buildAnswerSummary(mode) { if (mode === "focused_grounded") return "Сформирован прямой ответ на основе подтвержденной опоры."; if (mode === "broad_partial") return "Вывод ограничен: есть частичная опора, но не полный coverage."; if (mode === "clarification_required") return "Нужны уточнения: без сужения strong factual вывод ненадежен."; if (mode === "out_of_scope") return "Запрос вне доступного учетного контура."; if (mode === "route_mismatch") return "Результат маршрута не совпал с предметом вопроса."; if (mode === "empty") return "В текущем срезе данных релевантные записи не обнаружены."; if (mode === "no_grounded") return "Недостаточно опоры для обоснованного ответа."; return "Не удалось собрать обоснованный ответ по текущему запросу."; } function buildDirectAnswer(input) { const topFact = firstMeaningfulFact(input.retrievalResults); if (input.mode === "focused_grounded") { return topFact ?? "Подтвержденный результат получен; можно продолжать предметную проверку без деградации."; } if (input.mode === "broad_partial") { if (topFact) { return `Доступен ограниченный подтвержденный фрагмент: ${topFact}`; } return "Есть только ограниченная опора; вывод дан в частичном режиме без ложной точности."; } if (input.mode === "clarification_required") { return "Текущий запрос слишком широкий или недоопределен; надежный factual вывод пока невозможен."; } if (input.mode === "out_of_scope") { return "Могу отвечать только в пределах данных доступного учетного контура."; } if (input.mode === "route_mismatch") { return "Предмет результата не совпал с предметом вопроса; требуется уточнение фокуса."; } if (input.mode === "empty") { return "В текущем срезе данных проблемные записи по заданному условию не найдены."; } if (input.mode === "no_grounded") { return "Недостаточно подтвержденной опоры для ответа в требуемой точности."; } if (input.policySignals.minimum_evidence_failed) { return "Маршрут отработал, но минимальная evidence-опора не пройдена."; } return "Не удалось сформировать обоснованный ответ; нужно уточнение запроса."; } function buildProblemCentricAnswerSummary(input) { if (input.lifecycleEnriched && input.summary?.lifecycle_enriched_units && input.summary.lifecycle_enriched_units > 0) { if (input.mode === "clarification_required") { return "Выявлены lifecycle-дефекты, но для надежного вывода требуется уточнение предметных якорей."; } return `Сформирован lifecycle-aware problem срез: выделено ${input.summary.lifecycle_enriched_units} lifecycle-узлов с приоритетом по дефектам перехода.`; } if (input.mode === "clarification_required") { return "Выявлены проблемные кластеры, но для надежного вывода требуется предметное уточнение фокуса."; } if (input.weakUnits) { return "Сформирован problem-centric срез с ограниченной опорой; вывод предварительный и требует до-проверки."; } if (input.summary?.units_total && input.summary.units_total > 1) { return `Сформирован problem-centric срез: выделено ${input.summary.units_total} проблемных кластера с приоритетами.`; } return "Сформирован problem-centric срез: выделен ключевой проблемный кластер и затронутый контур."; } function buildProblemCentricDirectAnswer(input) { const lead = input.mode === "clarification_required" ? "Обнаружены проблемные зоны, но без уточнения якорей сильный factual-вывод преждевременен." : input.weakUnits ? "Выделены проблемные зоны с ограниченной надежностью; вывод дан в ограниченном режиме." : input.lifecycleAnswerEnabled && hasLifecycleResolution(input.units) ? "Выделены lifecycle-проблемы: определены текущие/ожидаемые стадии и тип нарушения перехода." : "Выделены ключевые проблемные зоны и их влияние на учетный контур."; const unitLines = input.units.map((unit) => { const scope = formatAffectedScope(unit); const lifecycleScope = input.lifecycleAnswerEnabled ? formatLifecycleScope(unit) : null; const lifecycleInterpretation = input.lifecycleAnswerEnabled && unit.business_lifecycle_interpretation ? sanitizeUserText(unit.business_lifecycle_interpretation) : null; const title = sanitizeUserText(unit.title) ?? "Problem cluster detected"; const defect = sanitizeUserText(unit.business_defect_class) ?? "detected_issue"; const segments = [ `${title}: ${defect}`, scope, lifecycleScope, lifecycleInterpretation, `severity=${unit.severity.grade}`, `confidence=${unit.confidence.grade}`, unit.lifecycle_confidence ? `lifecycle_confidence=${unit.lifecycle_confidence.grade}` : null ].filter((item) => Boolean(item)); return `- ${segments.join("; ")}.`; }); if (unitLines.length === 0) { return `${lead}\nПроблемные кластеры не удалось детализировать в текущем срезе.`; } return [lead, "Проблемные кластеры:", ...unitLines].join("\n"); } function buildProblemCentricAnswerStructure(input) { const weakUnits = input.selectedUnits.every((item) => item.confidence.grade === "low"); const lifecycleEnriched = input.lifecycleAnswerEnabled && hasLifecycleResolution(input.selectedUnits); const unitMechanismNotes = uniqueStrings(input.selectedUnits .map((item) => item.mechanism_summary) .filter((item) => typeof item === "string" && item.trim().length > 0), 6); const sourceRefs = uniqueStrings(input.evidenceItems .map((item) => item.source_ref?.canonical_ref) .filter((item) => typeof item === "string" && item.trim().length > 0), 6); const evidenceIds = uniqueStrings(input.evidenceItems.map((item) => item.evidence_id), 10); const mechanismStatus = unitMechanismNotes.length === 0 ? "unresolved" : weakUnits || input.limitationReasonCodes.includes("missing_mechanism") ? "limited" : "grounded"; const problemSpecificLimitations = []; if (weakUnits) { problemSpecificLimitations.push("Problem units remain weak-confidence; conclusions are intentionally limited."); } if (input.problemSummary?.duplicate_collapses && input.problemSummary.duplicate_collapses > 0) { problemSpecificLimitations.push("Part of the problem signal was merged due to duplicate collapse."); } const limitations = uniqueStrings([ ...problemSpecificLimitations, ...input.limitationReasonCodes.map((code) => limitationReasonToText(code)), ...extractLimitations(input.retrievalResults), ...input.groundingCheck.reasons ], 10); const openUncertainties = uniqueStrings([ ...input.groundingCheck.missing_requirements, ...(input.mode === "clarification_required" && input.missingAnchors.period ? ["missing_anchor:period"] : []), ...(input.mode === "clarification_required" && input.missingAnchors.account ? ["missing_anchor:account"] : []), ...(input.mode === "clarification_required" && input.missingAnchors.documentOrObject ? ["missing_anchor:document_or_object"] : []), ...(input.mode === "clarification_required" && input.missingAnchors.counterparty ? ["missing_anchor:counterparty"] : []) ], 8); return { schema_version: "answer_structure_v1_1", answer_summary: buildProblemCentricAnswerSummary({ mode: input.mode, weakUnits, summary: input.problemSummary, lifecycleEnriched }), direct_answer: buildProblemCentricDirectAnswer({ mode: input.mode, units: input.selectedUnits, weakUnits, lifecycleAnswerEnabled: input.lifecycleAnswerEnabled }), mechanism_block: { status: mechanismStatus, mechanism_notes: unitMechanismNotes, limitation_reason_codes: input.limitationReasonCodes }, evidence_block: { evidence_ids: evidenceIds, source_refs: sourceRefs, mechanism_notes: unitMechanismNotes, coverage_note: input.coverageReport.requirements_total > 0 && input.coverageReport.requirements_total === input.coverageReport.requirements_covered && input.coverageReport.requirements_uncovered.length === 0 && input.coverageReport.requirements_partially_covered.length === 0 ? "coverage_full_or_near_full" : "coverage_partial_or_limited", ...(input.claimEvidenceLinks.length > 0 ? { claim_evidence_links: input.claimEvidenceLinks } : {}) }, uncertainty_block: { open_uncertainties: openUncertainties, limitations }, next_step_block: { recommended_actions: buildProblemCentricActions({ units: input.selectedUnits, mode: input.mode, missingAnchors: input.missingAnchors, coverageReport: input.coverageReport }), clarification_questions: buildProblemCentricClarifications({ units: input.selectedUnits, missingAnchors: input.missingAnchors, coverageReport: input.coverageReport, mode: input.mode }) } }; } function renderPolicyReply(structure) { const mechanismLines = [`status=${structure.mechanism_block.status}`]; if (structure.mechanism_block.mechanism_notes.length > 0) { mechanismLines.push(...structure.mechanism_block.mechanism_notes.map((item) => `note: ${item}`)); } if (structure.mechanism_block.limitation_reason_codes.length > 0) { mechanismLines.push(`limitation_codes: ${structure.mechanism_block.limitation_reason_codes.join(", ")}`); } if (structure.mechanism_block.status === "unresolved" && structure.mechanism_block.mechanism_notes.length === 0) { mechanismLines.push("mechanism_note is intentionally omitted due to weak or missing mechanism evidence"); } const sourceRefCount = Array.isArray(structure.evidence_block.source_refs) ? structure.evidence_block.source_refs.length : 0; const claimLinkCount = Array.isArray(structure.evidence_block.claim_evidence_links) ? structure.evidence_block.claim_evidence_links.length : 0; const evidenceLines = [ `coverage=${structure.evidence_block.coverage_note}`, `supporting_evidence_count=${structure.evidence_block.evidence_ids.length}`, `supporting_source_count=${sourceRefCount}`, `claim_support_links=${claimLinkCount}` ]; if (sourceRefCount > 0) { evidenceLines.push("Detailed source references are available in debug payload."); } const uncertaintyLines = [ ...structure.uncertainty_block.open_uncertainties.map((item) => `open: ${item}`), ...structure.uncertainty_block.limitations.map((item) => `limit: ${item}`) ]; if (uncertaintyLines.length === 0) { uncertaintyLines.push("No material uncertainty detected in current scoped answer."); } const nextStepLines = [ ...structure.next_step_block.recommended_actions.map((item) => `action: ${item}`), ...structure.next_step_block.clarification_questions.map((item) => `clarify: ${item}`) ]; if (nextStepLines.length === 0) { nextStepLines.push("No additional action is required for this scoped answer."); } return sanitizeUserFacingReply([ `Answer summary: ${structure.answer_summary}`, `Direct answer:\n${structure.direct_answer}`, `Mechanism block:\n${formatList(mechanismLines)}`, `Evidence block:\n${formatList(evidenceLines)}`, `Uncertainty block:\n${formatList(uncertaintyLines)}`, `Next step block:\n${formatList(nextStepLines)}` ] .filter(Boolean) .join("\n\n")); } function composeAssistantAnswerV11(input) { const fallbackType = fallbackFromSummary(input.routeSummary); const okResults = input.retrievalResults.filter((item) => item.status === "ok"); const partialResults = input.retrievalResults.filter((item) => item.status === "partial"); const emptyResults = input.retrievalResults.filter((item) => item.status === "empty"); const errorResults = input.retrievalResults.filter((item) => item.status === "error"); const evidenceItems = flattenEvidence(input.retrievalResults); const policySignals = aggregatePolicySignals(input.retrievalResults); const limitationReasonCodes = collectLimitationReasonCodes(evidenceItems); const sourceRefs = uniqueStrings(evidenceItems .map((item) => item.source_ref?.canonical_ref) .filter((item) => typeof item === "string" && item.trim().length > 0), 8); const mechanismNotes = uniqueStrings(evidenceItems .map((item) => item.mechanism_note) .filter((item) => typeof item === "string" && item.trim().length > 0), 6); const lifecycleAnswerEnabled = Boolean(input.enableLifecycleAnswerV1); const problemUnits = flattenProblemUnits(input.retrievalResults); const problemUnitSummary = selectProblemUnitSummary(input.retrievalResults); const problemHeavyUnits = problemUnits.filter((item) => PROBLEM_HEAVY_TYPES.has(item.problem_unit_type)); const selectedProblemUnits = rankProblemUnitsForAnswer(problemHeavyUnits, lifecycleAnswerEnabled).slice(0, 4); const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults); const aggregateEvidenceConfidence = aggregateConfidence(input.retrievalResults, evidenceItems); const lowConfidenceSignals = evidenceItems.filter((item) => item.confidence === "low").length; const lowConfidenceShare = evidenceItems.length > 0 ? lowConfidenceSignals / evidenceItems.length : 0; const lowConfidenceConcentration = lowConfidenceShare >= 0.6; const hasSupport = okResults.length > 0 || partialResults.length > 0 || evidenceItems.length > 0 || input.retrievalResults.some((item) => item.items.length > 0); const hasCoverageGaps = input.coverageReport.requirements_uncovered.length > 0 || input.coverageReport.requirements_partially_covered.length > 0 || input.coverageReport.clarification_needed_for.length > 0 || input.coverageReport.out_of_scope_requirements.length > 0; const hasCriticalEvidenceLimitation = limitationReasonCodes.includes("weak_source_mapping") || limitationReasonCodes.includes("insufficient_detail"); const hasNonLowRouteConfidence = input.retrievalResults.some((item) => item.status === "ok" && item.confidence !== "low"); const focusedStrong = okResults.length > 0 && input.groundingCheck.status === "grounded" && !hasCoverageGaps && !policySignals.broad_query_detected && !policySignals.broad_result_flag && !policySignals.minimum_evidence_failed && !hasCriticalEvidenceLimitation && (aggregateEvidenceConfidence !== "low" || hasNonLowRouteConfidence); const decision = buildPolicyDecision({ fallbackType, coverageReport: input.coverageReport, groundingCheck: input.groundingCheck, okResults, partialResults, emptyResults, errorResults, hasSupport, focusedStrong, policySignals }); const missingAnchors = detectMissingAnchors(input.userMessage); const hasProblemWeakSignal = policySignals.narrowing_strength !== "strong" || policySignals.minimum_evidence_failed || limitationReasonCodes.includes("missing_mechanism") || limitationReasonCodes.includes("weak_source_mapping") || aggregateEvidenceConfidence === "low" || lowConfidenceConcentration; const hardBlockedMode = decision.mode === "out_of_scope" || decision.mode === "route_mismatch" || decision.mode === "backend_error"; const problemCentricModeEligible = decision.mode === "broad_partial" || decision.mode === "clarification_required" || (decision.mode === "focused_grounded" && hasProblemWeakSignal); const shouldUseProblemCentricAnswer = Boolean(input.enableProblemCentricAnswerV1) && !hardBlockedMode && problemCentricModeEligible && (!focusedStrong || hasProblemWeakSignal) && selectedProblemUnits.length > 0; if (shouldUseProblemCentricAnswer) { const problemCentricStructure = buildProblemCentricAnswerStructure({ mode: decision.mode, selectedUnits: selectedProblemUnits, problemSummary: problemUnitSummary, evidenceItems, claimEvidenceLinks, limitationReasonCodes, groundingCheck: input.groundingCheck, retrievalResults: input.retrievalResults, missingAnchors, coverageReport: input.coverageReport, lifecycleAnswerEnabled }); const lifecycleModeActive = lifecycleAnswerEnabled && hasLifecycleResolution(selectedProblemUnits); return { assistant_reply: renderPolicyReply(problemCentricStructure), fallback_type: decision.fallback_type, reply_type: decision.reply_type, answer_structure_v11: problemCentricStructure, problem_centric_answer_applied: true, problem_units_used_count: selectedProblemUnits.length, problem_answer_mode: lifecycleModeActive ? "stage3_lifecycle_aware_v1" : "stage2_problem_centric_v1", problem_unit_ids_used: selectedProblemUnits.map((item) => item.problem_unit_id) }; } const clarificationQuestions = buildClarificationQuestions({ mode: decision.mode, missingAnchors, coverageReport: input.coverageReport, policySignals }); const recommendedActions = buildRecommendedActions({ mode: decision.mode, coverageReport: input.coverageReport, policySignals, limitationReasonCodes, sourceRefs }); const limitations = uniqueStrings([ ...limitationReasonCodes.map((code) => limitationReasonToText(code)), ...extractLimitations(input.retrievalResults), ...input.groundingCheck.reasons, ...(policySignals.minimum_evidence_failed ? ["Minimum evidence gate failed for current scope."] : []), ...(policySignals.broad_query_detected && policySignals.narrowing_strength === "weak" ? ["Broad query remains weakly narrowed; precision is intentionally limited."] : []) ], 10); const openUncertainties = uniqueStrings([ ...input.groundingCheck.missing_requirements, ...(decision.mode === "clarification_required" && missingAnchors.period ? ["missing_anchor:period"] : []), ...(decision.mode === "clarification_required" && missingAnchors.account ? ["missing_anchor:account"] : []), ...(decision.mode === "clarification_required" && missingAnchors.documentOrObject ? ["missing_anchor:document_or_object"] : []), ...(decision.mode === "clarification_required" && missingAnchors.counterparty ? ["missing_anchor:counterparty"] : []) ], 8); const mechanismStatus = mechanismNotes.length === 0 ? "unresolved" : limitationReasonCodes.includes("missing_mechanism") || limitationReasonCodes.includes("heuristic_inference") ? "limited" : "grounded"; const answerStructure = { schema_version: "answer_structure_v1_1", answer_summary: buildAnswerSummary(decision.mode), direct_answer: buildDirectAnswer({ mode: decision.mode, retrievalResults: input.retrievalResults, policySignals }), mechanism_block: { status: mechanismStatus, mechanism_notes: mechanismNotes, limitation_reason_codes: limitationReasonCodes }, evidence_block: { evidence_ids: uniqueStrings(evidenceItems.map((item) => item.evidence_id), 10), source_refs: sourceRefs, mechanism_notes: mechanismNotes, coverage_note: input.coverageReport.requirements_total > 0 && input.coverageReport.requirements_total === input.coverageReport.requirements_covered && input.coverageReport.requirements_uncovered.length === 0 && input.coverageReport.requirements_partially_covered.length === 0 ? "coverage_full_or_near_full" : "coverage_partial_or_limited", ...(claimEvidenceLinks.length > 0 ? { claim_evidence_links: claimEvidenceLinks } : {}) }, uncertainty_block: { open_uncertainties: openUncertainties, limitations }, next_step_block: { recommended_actions: recommendedActions, clarification_questions: clarificationQuestions } }; return { assistant_reply: renderPolicyReply(answerStructure), fallback_type: decision.fallback_type, reply_type: decision.reply_type, answer_structure_v11: answerStructure, problem_centric_answer_applied: false, problem_units_used_count: 0, problem_answer_mode: "stage1_policy_v11" }; } function composeExplainableAnswer(input, scopeLabel) { const facts = extractTopFacts(input.retrievalResults); const whyIncludedRaw = extractWhyIncluded(input.retrievalResults); const selectionReasonsRaw = extractSelectionReasons(input.retrievalResults); const whyIncluded = whyIncludedRaw.length > 0 ? whyIncludedRaw : buildFallbackWhyIncluded(input.retrievalResults); const selectionReasons = selectionReasonsRaw.length > 0 ? selectionReasonsRaw : buildFallbackSelectionReasons(input.retrievalResults); const riskFactors = extractRiskFactors(input.retrievalResults); const interpretation = extractBusinessInterpretation(input.retrievalResults); const limitations = uniqueStrings([...extractLimitations(input.retrievalResults), ...input.groundingCheck.reasons]); const nextSteps = suggestNextStep(input.requirements, input.coverageReport); const lead = scopeLabel === "full" ? "Итог: запрос обработан по предмету, найденные объекты подтверждены данными контура." : "Итог: запрос обработан частично, ниже подтвержденная часть и ограничения."; return sanitizeUserFacingReply([ lead, facts.length > 0 ? "Подтвержденные результаты:\n" + formatList(facts) : "", whyIncluded.length > 0 ? "Почему это попало в ответ:\n" + formatList(whyIncluded) : "", selectionReasons.length > 0 ? "Основание отбора:\n" + formatList(selectionReasons) : "", riskFactors.length > 0 ? "Подтверждающие признаки:\n" + formatList(riskFactors) : "", interpretation.length > 0 ? "Практический смысл:\n" + formatList(interpretation) : "", limitations.length > 0 ? "Ограничения:\n" + formatList(limitations) : "", nextSteps.length > 0 ? "Что проверить дальше:\n" + formatList(nextSteps) : "" ] .filter(Boolean) .join("\n\n")); } function composeAssistantAnswer(input) { if (input.enableAnswerPolicyV11) { return composeAssistantAnswerV11(input); } const fallbackType = fallbackFromSummary(input.routeSummary); const okResults = input.retrievalResults.filter((item) => item.status === "ok"); const partialResults = input.retrievalResults.filter((item) => item.status === "partial"); const emptyResults = input.retrievalResults.filter((item) => item.status === "empty"); const errorResults = input.retrievalResults.filter((item) => item.status === "error"); const legacyEvidenceItems = flattenEvidence(input.retrievalResults); const legacyLimitationReasonCodes = collectLimitationReasonCodes(legacyEvidenceItems); const hasBroadMinimumEvidenceSignal = input.retrievalResults.some((item) => summaryBoolean(item, "broad_guard_applied") && summaryBoolean(item, "minimum_evidence_failed")); const hasBroadClarificationSignal = input.retrievalResults.some((item) => summaryBoolean(item, "broad_guard_applied") && summaryBoolean(item, "minimum_evidence_failed") && summaryString(item, "degraded_to") === "clarification"); if (fallbackType === "out_of_scope" && input.coverageReport.requirements_covered === 0) { return { assistant_reply: "Я могу отвечать только по данным вашей учетной базы. Этот запрос выходит за рамки доступного контура.", fallback_type: "out_of_scope", reply_type: "out_of_scope" }; } if (input.groundingCheck.status === "route_mismatch_blocked") { return { assistant_reply: [ "Не отправляю финальный ответ, потому что предмет результата не совпал с предметом вопроса.", "Уточните формулировку (например, нужный счет/участок учета), и я выполню повторный проход." ].join("\n\n"), fallback_type: "partial", reply_type: "route_mismatch_blocked" }; } if (input.groundingCheck.status === "no_grounded_answer" && okResults.length === 0 && !hasBroadMinimumEvidenceSignal) { return { assistant_reply: "Пока не удалось собрать предметно подтвержденный ответ по вашему вопросу. Нужны дополнительные уточнения по периоду или объекту проверки.", fallback_type: fallbackType, reply_type: "no_grounded_answer" }; } if (hasBroadClarificationSignal && okResults.length === 0 && partialResults.length === 0) { return { assistant_reply: "Запрос слишком широкий для надежного вывода по текущей опоре. Уточните период, участок учета или объект проверки, после чего я дам предметный результат.", fallback_type: "clarification", reply_type: "clarification_required" }; } if (fallbackType === "clarification" && okResults.length === 0 && partialResults.length === 0) { return { assistant_reply: "Уточните, пожалуйста, период, счет, документ или контрагента, чтобы закрыть все части вопроса корректно.", fallback_type: "clarification", reply_type: "clarification_required" }; } if (errorResults.length > 0 && okResults.length === 0 && partialResults.length === 0) { return { assistant_reply: "Не удалось получить данные из контура. Попробуйте повторить запрос или уточнить формулировку.", fallback_type: fallbackType, reply_type: "backend_error" }; } if (partialResults.length > 0 && okResults.length === 0) { return { assistant_reply: composeExplainableAnswer(input, "partial"), fallback_type: "partial", reply_type: "partial_coverage" }; } if (okResults.length === 0 && partialResults.length === 0 && emptyResults.length > 0) { return { assistant_reply: "По заданному условию в текущем срезе данных явных проблемных записей не найдено.", fallback_type: fallbackType, reply_type: "empty_but_valid" }; } const hasPartialCoverage = input.coverageReport.requirements_uncovered.length > 0 || input.coverageReport.requirements_partially_covered.length > 0 || input.coverageReport.clarification_needed_for.length > 0 || input.coverageReport.out_of_scope_requirements.length > 0 || input.groundingCheck.status === "partial" || errorResults.length > 0 || legacyLimitationReasonCodes.includes("weak_source_mapping") || legacyLimitationReasonCodes.includes("missing_mechanism"); if (okResults.length > 0 && hasPartialCoverage) { return { assistant_reply: composeExplainableAnswer(input, "partial"), fallback_type: "partial", reply_type: "partial_coverage" }; } if (okResults.length > 0) { return { assistant_reply: composeExplainableAnswer(input, "full"), fallback_type: "none", reply_type: "factual_with_explanation" }; } return { assistant_reply: "По текущему запросу не удалось построить обоснованный ответ. Уточните формулировку и попробуйте снова.", fallback_type: "unknown", reply_type: "backend_error" }; }