"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.extractRequirementsForRoute = extractRequirementsForRoute; exports.evaluateCoverageForRequirements = evaluateCoverageForRequirements; exports.checkGroundingForRequirements = checkGroundingForRequirements; function summarizeUnique(values, limit = 6) { return Array.from(new Set(values.map((item) => String(item ?? "").trim()).filter(Boolean))).slice(0, limit); } const SUBJECT_TOKEN_RULES = { nds: { critical: true, patterns: [ "vat", "accumulationregister", "ндс", "книгипокупок", "книгипродаж", "налогнадобавленнуюстоимость" ] }, os: { critical: true, patterns: ["fixed_asset", "fixedasset", "основн", "амортиз"] }, saldo: { critical: true, patterns: ["balance", "saldo", "сальдо", "остат"] }, counterparty: { critical: false, patterns: [ "counterparty", "supplier", "buyer", "counterparty_id", "journal_counterparty", "document_has_counterparty", "контрагент", "поставщик", "покупател" ], routes: ["hybrid_store_plus_live", "store_feature_risk", "store_canonical"] }, document: { critical: false, patterns: [ "document", "recorder", "journal", "document_refs_count", "recorded_by_document", "journal_refers_to_document", "документ" ], routes: ["hybrid_store_plus_live", "store_feature_risk", "store_canonical", "live_mcp_drilldown"] }, anomaly: { critical: false, patterns: [ "risk", "risk_score", "unknown_link_count", "zero_guid", "navigation_links", "missing_counterparty_link", "аномал", "риск" ], routes: ["store_feature_risk", "batch_refresh_then_store"] }, chain: { critical: false, patterns: ["chain", "cross_entity_chain", "relation_types", "operations_count", "matched_counterparties", "цепоч"], routes: ["hybrid_store_plus_live"] } }; function hasRegexMatch(corpus, pattern) { try { return pattern.test(corpus); } catch { return false; } } function evaluateSubjectTokenMatch(token, corpus, executedRoutes) { if (token.startsWith("account_")) { const account = token.slice("account_".length).trim(); if (!account) { return { matched: false, critical: true }; } const escaped = account.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const accountPattern = new RegExp(`(^|[^0-9])${escaped}([^0-9]|$)`, "i"); return { matched: hasRegexMatch(corpus, accountPattern), critical: true }; } const rule = SUBJECT_TOKEN_RULES[token]; if (rule) { const byPattern = rule.patterns.some((pattern) => corpus.includes(pattern)); const byRoute = Array.isArray(rule.routes) ? rule.routes.some((route) => executedRoutes.has(route)) : false; return { matched: byPattern || byRoute, critical: rule.critical }; } return { matched: corpus.includes(token), critical: false }; } function evidenceCountForRequirement(requirementId, result) { const evidence = Array.isArray(result.evidence) ? result.evidence : []; if (evidence.length === 0) { return 0; } const tagged = evidence.filter((item) => { const claimRef = typeof item?.claim_ref === "string" ? item.claim_ref : ""; return claimRef.toLowerCase() === `requirement:${String(requirementId).toLowerCase()}`; }).length; if (tagged > 0) { return tagged; } if (Array.isArray(result.requirement_ids) && result.requirement_ids.length === 1 && result.requirement_ids[0] === requirementId) { return evidence.length; } return 0; } function hasSubstantiveCoverageForRequirement(requirementId, result) { const evidenceCount = evidenceCountForRequirement(requirementId, result); if (evidenceCount > 0) { return true; } const problemUnitsCount = Array.isArray(result.problem_units) ? result.problem_units.length : 0; const candidateEvidenceCount = Array.isArray(result.candidate_evidence) ? result.candidate_evidence.length : 0; if (problemUnitsCount > 0 || candidateEvidenceCount > 0) { if (Array.isArray(result.requirement_ids) && result.requirement_ids.length === 1 && result.requirement_ids[0] === requirementId) { return true; } } return false; } function extractRequirementsForRoute(input) { const byFragment = new Map(); const requirements = []; const pushRequirement = (item) => { const subjectTokens = input.extractSubjectTokens(item.requirement_text); requirements.push({ requirement_id: item.requirement_id, source_fragment_id: item.source_fragment_id, requirement_text: item.requirement_text, subject_tokens: subjectTokens, status: item.status, route: item.route }); if (item.source_fragment_id) { const current = byFragment.get(item.source_fragment_id) ?? []; current.push(item.requirement_id); byFragment.set(item.source_fragment_id, current); } }; if (!input.routeSummary) { pushRequirement({ requirement_id: "R1", source_fragment_id: null, requirement_text: input.userMessage, status: "clarification_needed", route: null }); return { requirements, byFragment }; } if (input.routeSummary.mode === "legacy_v1") { pushRequirement({ requirement_id: "R1", source_fragment_id: "F1", requirement_text: input.userMessage, status: "covered", route: input.routeSummary.route_hint }); return { requirements, byFragment }; } input.routeSummary.decisions.forEach((decision, index) => { const requirementId = `R${index + 1}`; const text = input.fragmentTextById.get(decision.fragment_id) ?? input.userMessage; let status = "covered"; if (decision.route === "no_route") { if (decision.no_route_reason === "out_of_scope") { status = "out_of_scope"; } else if (decision.no_route_reason === "insufficient_specificity") { status = "clarification_needed"; } else { status = "uncovered"; } } pushRequirement({ requirement_id: requirementId, source_fragment_id: decision.fragment_id, requirement_text: text, status, route: decision.route === "no_route" ? null : decision.route }); }); return { requirements, byFragment }; } function evaluateCoverageForRequirements(requirements, retrievalResults) { const statusByRequirement = new Map(); for (const result of retrievalResults) { for (const requirementId of result.requirement_ids) { const list = statusByRequirement.get(requirementId) ?? []; list.push({ status: result.status, substantive: hasSubstantiveCoverageForRequirement(requirementId, result) }); statusByRequirement.set(requirementId, list); } } const resolvedRequirements = requirements.map((requirement) => { if (requirement.status === "out_of_scope" || requirement.status === "clarification_needed") { return requirement; } const states = statusByRequirement.get(requirement.requirement_id) ?? []; if (states.length === 0) { return { ...requirement, status: "uncovered" }; } const hasAnySubstantive = states.some((item) => item.substantive); if (!hasAnySubstantive) { return { ...requirement, status: "uncovered" }; } const hasOk = states.some((item) => item.status === "ok"); const hasPartial = states.some((item) => item.status === "partial"); const hasEmpty = states.some((item) => item.status === "empty"); const hasError = states.some((item) => item.status === "error"); const hasWeakOk = states.some((item) => item.status === "ok" && !item.substantive); const hasSubstantiveOk = states.some((item) => item.status === "ok" && item.substantive); const hasSubstantivePartial = states.some((item) => item.status === "partial" && item.substantive); if (hasSubstantiveOk && !hasSubstantivePartial && !hasWeakOk && !hasEmpty && !hasError) { return { ...requirement, status: "covered" }; } if (hasSubstantiveOk || hasSubstantivePartial || hasOk || hasPartial) { return { ...requirement, status: "partially_covered" }; } return { ...requirement, status: "uncovered" }; }); const requirementsCovered = resolvedRequirements.filter((item) => item.status === "covered").length; const requirementsUncovered = resolvedRequirements .filter((item) => item.status === "uncovered") .map((item) => item.requirement_id); const requirementsPartiallyCovered = resolvedRequirements .filter((item) => item.status === "partially_covered") .map((item) => item.requirement_id); const clarificationNeededFor = resolvedRequirements .filter((item) => item.status === "clarification_needed") .map((item) => item.requirement_id); const outOfScopeRequirements = resolvedRequirements .filter((item) => item.status === "out_of_scope") .map((item) => item.requirement_id); return { requirements: resolvedRequirements, coverage: { requirements_total: resolvedRequirements.length, requirements_covered: requirementsCovered, requirements_uncovered: requirementsUncovered, requirements_partially_covered: requirementsPartiallyCovered, clarification_needed_for: clarificationNeededFor, out_of_scope_requirements: outOfScopeRequirements } }; } function checkGroundingForRequirements(input) { const whyIncludedSummary = summarizeUnique(input.retrievalResults.flatMap((item) => item.why_included)); const selectionReasonSummary = summarizeUnique(input.retrievalResults.flatMap((item) => item.selection_reason)); const hasMaterialResults = input.retrievalResults.some((item) => item.status === "ok" || item.status === "partial"); const subjectTokens = input.extractSubjectTokens(input.userMessage); const executedRoutes = new Set(input.retrievalResults .filter((item) => item.status !== "error") .map((item) => item.route) .filter(Boolean)); const retrievalCorpus = JSON.stringify(input.retrievalResults.map((item) => ({ route: item.route, result_type: item.result_type, summary: item.summary, items: item.items, evidence: item.evidence, why_included: item.why_included, selection_reason: item.selection_reason, risk_factors: item.risk_factors, business_interpretation: item.business_interpretation }))).toLowerCase(); const missingSubjectTokens = []; const missingCriticalTokens = []; for (const token of subjectTokens) { const match = evaluateSubjectTokenMatch(token, retrievalCorpus, executedRoutes); if (!match.matched) { missingSubjectTokens.push(token); if (match.critical) { missingCriticalTokens.push(token); } } } const onlyAccountCriticalMissing = missingCriticalTokens.length > 0 && missingCriticalTokens.every((token) => token.startsWith("account_")); const accountOnlyMismatchRecoverable = hasMaterialResults && input.coverage.requirements_covered > 0 && onlyAccountCriticalMissing && (whyIncludedSummary.length > 0 || selectionReasonSummary.length > 0); const routeSubjectMatch = !hasMaterialResults || missingCriticalTokens.length === 0 || accountOnlyMismatchRecoverable; let status = "grounded"; const reasons = []; if (!routeSubjectMatch) { status = "route_mismatch_blocked"; reasons.push(`Ключевые ориентиры вопроса не подтверждены в найденных данных: ${missingCriticalTokens.join(", ")}`); } else if (accountOnlyMismatchRecoverable) { status = "partial"; reasons.push(`Часть счетных ориентиров не подтвердилась напрямую (${missingCriticalTokens.join(", ")}), но есть опора для ограниченного вывода.`); } else if (input.coverage.requirements_covered === 0) { status = "no_grounded_answer"; reasons.push("Ни одно требование не получило подтвержденного покрытия."); } else if (input.coverage.requirements_uncovered.length > 0 || input.coverage.requirements_partially_covered.length > 0 || input.coverage.clarification_needed_for.length > 0 || input.coverage.out_of_scope_requirements.length > 0) { status = "partial"; reasons.push("Вопрос покрыт частично: есть непокрытые или требующие уточнения требования."); } if (whyIncludedSummary.length === 0) { reasons.push("В текущей выборке не хватает явных подтверждений, почему записи попали в ответ."); } if (missingSubjectTokens.length > 0 && missingCriticalTokens.length === 0) { reasons.push(`Часть контекста вопроса не подтверждена напрямую в найденных данных: ${missingSubjectTokens.join(", ")}`); } const missingRequirements = [ ...input.coverage.requirements_uncovered, ...input.coverage.requirements_partially_covered, ...input.coverage.clarification_needed_for, ...input.coverage.out_of_scope_requirements ]; return { status, route_subject_match: routeSubjectMatch, missing_requirements: missingRequirements, reasons, why_included_summary: whyIncludedSummary, selection_reason_summary: selectionReasonSummary }; }