Initial import NDC_1C

This commit is contained in:
2026-03-26 10:38:25 +03:00
commit a162d77ef7
2943 changed files with 3615871 additions and 0 deletions
+704
View File
@@ -0,0 +1,704 @@
"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);
}
function formatList(items) {
if (items.length === 0) {
return "";
}
return items.map((item) => `- ${item}`).join("\n");
}
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 ?? "не указан");
const operations = String(item.operations_count ?? "0");
const docs = String(item.document_refs_count ?? "0");
return `Контрагент ${counterparty}: операций ${operations}, документов в связке ${docs}.`;
});
lines.push(...top);
continue;
}
if (result.result_type === "ranking") {
const top = result.items
.slice(0, 5)
.map((item) => `${item.rank ?? "•"}. ${String(item.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 `${String(item.source_entity ?? "Запись")} (${String(item.source_id ?? "")}) — риск ${String(item.risk_score)}.`;
}
return `${String(item.source_entity ?? "Запись")} (${String(item.source_id ?? "")}).`;
});
lines.push(...top);
continue;
}
const top = result.items
.slice(0, 3)
.map((item) => `${String(item.source_entity ?? "Запись")} (${String(item.source_id ?? "")}).`);
lines.push(...top);
}
return lines;
}
function extractWhyIncluded(results) {
return uniqueStrings(results.flatMap((item) => item.why_included));
}
function extractSelectionReasons(results) {
return uniqueStrings(results.flatMap((item) => item.selection_reason));
}
function extractRiskFactors(results) {
return uniqueStrings(results.flatMap((item) => item.risk_factors));
}
function extractBusinessInterpretation(results) {
return uniqueStrings(results.flatMap((item) => item.business_interpretation));
}
function extractLimitations(results) {
return uniqueStrings(results.flatMap((item) => item.limitations));
}
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 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;
}
function flattenEvidence(results) {
return results.flatMap((item) => item.evidence);
}
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 ключевые записи по source_ref и зафиксируйте итог в рабочем файле проверки.");
}
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(`Начните проверку с source_ref: ${input.sourceRefs.slice(0, 2).join(", ")}.`);
}
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 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 evidenceLines = [
`coverage=${structure.evidence_block.coverage_note}`,
`evidence_ids=${structure.evidence_block.evidence_ids.length > 0 ? structure.evidence_block.evidence_ids.join(", ") : "none"}`
];
if (Array.isArray(structure.evidence_block.source_refs) && structure.evidence_block.source_refs.length > 0) {
evidenceLines.push(`source_refs=${structure.evidence_block.source_refs.join(", ")}`);
}
if (Array.isArray(structure.evidence_block.claim_evidence_links) && structure.evidence_block.claim_evidence_links.length > 0) {
const compactLinks = structure.evidence_block.claim_evidence_links
.slice(0, 4)
.map((item) => `${item.claim_ref}:${item.evidence_ids.join("|")}`);
evidenceLines.push(`claim_evidence_links=${compactLinks.join("; ")}`);
}
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 [
`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 claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults);
const aggregateEvidenceConfidence = aggregateConfidence(input.retrievalResults, evidenceItems);
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 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
};
}
function composeExplainableAnswer(input, scopeLabel) {
const facts = extractTopFacts(input.retrievalResults);
const whyIncluded = extractWhyIncluded(input.retrievalResults);
const selectionReasons = extractSelectionReasons(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 [
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 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;
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"
};
}