"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveTemporalGuard = resolveTemporalGuard; exports.applyTemporalHintToExecutionPlan = applyTemporalHintToExecutionPlan; exports.resolveDomainPolarityGuard = resolveDomainPolarityGuard; exports.applyPolarityHintToExecutionPlan = applyPolarityHintToExecutionPlan; exports.applyDomainPolarityGuardToRetrievalResults = applyDomainPolarityGuardToRetrievalResults; exports.applyEvidenceAdmissibilityGate = applyEvidenceAdmissibilityGate; exports.evaluateGroundedAnswerEligibility = evaluateGroundedAnswerEligibility; exports.applyEligibilityToGroundingCheck = applyEligibilityToGroundingCheck; const iconv_lite_1 = __importDefault(require("iconv-lite")); const JULY_YEAR = "2020"; const JULY_MONTH = "07"; const JULY_WINDOW = { from: "2020-07-01", to: "2020-07-31", granularity: "month" }; const KNOWN_ACCOUNT_PREFIXES = new Set([ "01", "02", "07", "08", "10", "13", "19", "20", "21", "23", "25", "26", "28", "29", "41", "43", "44", "45", "50", "51", "52", "55", "57", "58", "60", "62", "66", "67", "68", "69", "70", "71", "73", "76", "90", "91", "94", "96", "97" ]); const RUS_MONTH_TO_NUMBER = { "\u044f\u043d\u0432\u0430\u0440\u044f": "01", "\u044f\u043d\u0432\u0430\u0440\u044c": "01", "\u0444\u0435\u0432\u0440\u0430\u043b\u044f": "02", "\u0444\u0435\u0432\u0440\u0430\u043b\u044c": "02", "\u043c\u0430\u0440\u0442\u0430": "03", "\u043c\u0430\u0440\u0442": "03", "\u0430\u043f\u0440\u0435\u043b\u044f": "04", "\u0430\u043f\u0440\u0435\u043b\u044c": "04", "\u043c\u0430\u044f": "05", "\u043c\u0430\u0439": "05", "\u0438\u044e\u043d\u044f": "06", "\u0438\u044e\u043d\u044c": "06", "\u0438\u044e\u043b\u044f": "07", "\u0438\u044e\u043b\u044c": "07", "\u0430\u0432\u0433\u0443\u0441\u0442\u0430": "08", "\u0430\u0432\u0433\u0443\u0441\u0442": "08", "\u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044f": "09", "\u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044c": "09", "\u043e\u043a\u0442\u044f\u0431\u0440\u044f": "10", "\u043e\u043a\u0442\u044f\u0431\u0440\u044c": "10", "\u043d\u043e\u044f\u0431\u0440\u044f": "11", "\u043d\u043e\u044f\u0431\u0440\u044c": "11", "\u0434\u0435\u043a\u0430\u0431\u0440\u044f": "12", "\u0434\u0435\u043a\u0430\u0431\u0440\u044c": "12" }; function uniqueStrings(values) { return Array.from(new Set(values.map((item) => String(item ?? "").trim()).filter(Boolean))); } function toObject(value) { if (!value || typeof value !== "object" || Array.isArray(value)) { return null; } return value; } function toObjectArray(value) { if (!Array.isArray(value)) { return []; } return value.filter((item) => Boolean(item) && typeof item === "object"); } function accountPrefix(value) { const token = String(value ?? "").trim(); const match = token.match(/^(\d{2})/); return match ? match[1] : null; } function collectDateLikeSpans(text) { const spans = []; const patterns = [ /\b20\d{2}(?:[-/.](?:0?[1-9]|1[0-2]))(?:[-/.](?:0?[1-9]|[12]\d|3[01]))?\b/g, /\b(?:0?[1-9]|[12]\d|3[01])[./-](?:0?[1-9]|1[0-2])[./-](?:\d{2}|\d{4})\b/g, /\b(?:0?[1-9]|[12]\d|3[01])\s+(?:январ[ьяе]|феврал[ьяе]|март[ае]?|апрел[ьяе]|ма[йея]|июн[ьяе]?|июл[ьяе]?|август[ае]?|сентябр[ьяе]?|октябр[ьяе]?|ноябр[ьяе]?|декабр[ьяе]?|january|february|march|april|may|june|july|august|september|october|november|december)(?:\s+20\d{2})?\b/giu ]; for (const pattern of patterns) { let match = null; while ((match = pattern.exec(text)) !== null) { spans.push({ start: match.index, end: match.index + match[0].length }); } } return spans; } function collectAmountLikeSpans(text) { const spans = []; const patterns = [/\b\d{1,3}(?:[ \u00A0]\d{3})+(?:[.,]\d{2})?\b/g, /\b\d+[.,]\d{2}\b/g]; for (const pattern of patterns) { let match = null; while ((match = pattern.exec(text)) !== null) { spans.push({ start: match.index, end: match.index + match[0].length }); } } return spans; } function collectPercentLikeSpans(text) { const spans = []; const pattern = /\b\d{1,3}(?:[.,]\d+)?\s*%/g; let match = null; while ((match = pattern.exec(text)) !== null) { spans.push({ start: match.index, end: match.index + match[0].length }); } return spans; } function collectContractLikeSpans(text) { const spans = []; const patterns = [ /(?:\b(?:договор(?:а|у|ом|е)?|contract)\b[^\r\n]{0,24}(?:№|#|n|no\.?)\s*[a-zа-я0-9][a-zа-я0-9/_-]{1,})/giu, /(?:№|#)\s*[a-zа-я0-9_-]{1,10}\/[a-zа-я0-9_-]{1,12}/giu, /\b\d{2}\/\d{2}(?:-[a-zа-я]{1,10})?\b/giu ]; for (const pattern of patterns) { let match = null; while ((match = pattern.exec(text)) !== null) { spans.push({ start: match.index, end: match.index + match[0].length }); } } return spans; } function intersectsSpan(start, end, spans) { return spans.some((span) => start < span.end && end > span.start); } function hasAccountContextAround(text, start, end) { const left = text.slice(Math.max(0, start - 28), start); const right = text.slice(end, Math.min(text.length, end + 28)); return /(?:счет|сч\.?|account|schet|оплат|расч[её]т|расчет|аванс|зач[её]т|долг|постав|покуп|supplier|customer|settlement|payment|ндс|vat|проводк|posting)/iu.test(`${left} ${right}`); } function extractAccountsFromTextDetailed(text, options) { const lower = String(text ?? "").toLowerCase(); const accounts = new Set(); const dateSpans = collectDateLikeSpans(lower); const amountSpans = collectAmountLikeSpans(lower); const percentSpans = collectPercentLikeSpans(lower); const contractSpans = collectContractLikeSpans(lower); const blockedSpans = [...dateSpans, ...amountSpans, ...percentSpans, ...contractSpans]; const hasAccountingLexeme = /(?:\bсчет(?:а|у|ом|ов)?\b|\bсч\.?\b|\baccount(?:s)?\b|\bschet(?:a|u|om|ov)?\b|оплат|расч[её]т|расчет|аванс|долг|settlement|payment|supplier|customer|постав|покуп)/iu.test(lower); const contextualPattern = /(?:\b(?:счет(?:а|у|ом|ов)?|сч\.?|account(?:s)?|schet(?:a|u|om|ov)?)\b\s*(?:№|#|:)?\s*)(\d{2}(?:\.\d{2})?)/giu; let contextualMatch = null; while ((contextualMatch = contextualPattern.exec(lower)) !== null) { const token = String(contextualMatch[1] ?? "").trim(); const prefix = token.match(/^(\d{2})/)?.[1] ?? null; if (token && prefix && KNOWN_ACCOUNT_PREFIXES.has(prefix)) { accounts.add(token); } } const pairPattern = /\b(\d{2}\.\d{2})\s*\/\s*(\d{2}\.\d{2})\b/g; let pairMatch = null; while ((pairMatch = pairPattern.exec(lower)) !== null) { const left = String(pairMatch[1] ?? "").trim(); const right = String(pairMatch[2] ?? "").trim(); const leftPrefix = left.match(/^(\d{2})/)?.[1] ?? null; const rightPrefix = right.match(/^(\d{2})/)?.[1] ?? null; if (left && leftPrefix && KNOWN_ACCOUNT_PREFIXES.has(leftPrefix)) accounts.add(left); if (right && rightPrefix && KNOWN_ACCOUNT_PREFIXES.has(rightPrefix)) accounts.add(right); } const genericAccountPattern = /\b(\d{2}(?:\.\d{2})?)\b/g; let genericMatch = null; const classifiedNumericTokens = []; const rejectedAsNonAccounts = new Set(); while ((genericMatch = genericAccountPattern.exec(lower)) !== null) { const token = String(genericMatch[1] ?? "").trim(); const start = genericMatch.index; const end = start + token.length; const prefix = token.match(/^(\d{2})/)?.[1] ?? null; if (options?.forceAccountContext === true && prefix && KNOWN_ACCOUNT_PREFIXES.has(prefix)) { accounts.add(token); classifiedNumericTokens.push({ token, classification: "account_token" }); continue; } if (intersectsSpan(start, end, dateSpans)) { classifiedNumericTokens.push({ token, classification: "date_token" }); rejectedAsNonAccounts.add(token); continue; } if (intersectsSpan(start, end, amountSpans)) { classifiedNumericTokens.push({ token, classification: "amount_token" }); rejectedAsNonAccounts.add(token); continue; } if (intersectsSpan(start, end, percentSpans)) { classifiedNumericTokens.push({ token, classification: "percent_token" }); rejectedAsNonAccounts.add(token); continue; } if (intersectsSpan(start, end, contractSpans)) { classifiedNumericTokens.push({ token, classification: "other_numeric" }); rejectedAsNonAccounts.add(token); continue; } if (!prefix || !KNOWN_ACCOUNT_PREFIXES.has(prefix)) { classifiedNumericTokens.push({ token, classification: "other_numeric" }); rejectedAsNonAccounts.add(token); continue; } if (!hasAccountingLexeme || !hasAccountContextAround(lower, start, end)) { classifiedNumericTokens.push({ token, classification: "other_numeric" }); rejectedAsNonAccounts.add(token); continue; } accounts.add(token); classifiedNumericTokens.push({ token, classification: "account_token" }); } const rawNumericTokens = uniqueStrings((lower.match(/\b\d{1,4}(?:[.,]\d{1,4})?\b/g) ?? []).map((item) => String(item))); for (const token of accounts) { if (!classifiedNumericTokens.some((item) => item.token === token && item.classification === "account_token")) { classifiedNumericTokens.push({ token, classification: "account_token" }); } } // Numeric tokens hidden behind blocked spans still need explicit audit markers. const blockedMatchPattern = /\b\d{2}(?:\.\d{2})?\b/g; let blockedMatch = null; while ((blockedMatch = blockedMatchPattern.exec(lower)) !== null) { const token = String(blockedMatch[0] ?? "").trim(); const start = blockedMatch.index; const end = start + token.length; if (!intersectsSpan(start, end, blockedSpans)) { continue; } if (classifiedNumericTokens.some((item) => item.token === token)) { continue; } const classification = intersectsSpan(start, end, dateSpans) ? "date_token" : intersectsSpan(start, end, amountSpans) ? "amount_token" : "percent_token"; classifiedNumericTokens.push({ token, classification }); rejectedAsNonAccounts.add(token); } return { resolved_account_anchors: Array.from(accounts), raw_numeric_tokens: rawNumericTokens, classified_numeric_tokens: classifiedNumericTokens, rejected_as_non_accounts: Array.from(rejectedAsNonAccounts) }; } function extractAccountsFromText(text) { return extractAccountsFromTextDetailed(text).resolved_account_anchors; } function extractAccountsFromUnknown(value, pathKey = "") { if (Array.isArray(value)) { return uniqueStrings(value.flatMap((item) => extractAccountsFromUnknown(item, pathKey))); } if (value && typeof value === "object") { return uniqueStrings(Object.entries(value).flatMap(([key, item]) => extractAccountsFromUnknown(item, `${pathKey}.${String(key).toLowerCase()}`))); } if (typeof value !== "string" && typeof value !== "number") { return []; } const contextPath = String(pathKey ?? "").toLowerCase(); if (contextPath.length > 0 && !/(?:account|счет|сч|debit|credit|дт|кт|konto|subkonto|analytics|context)/iu.test(contextPath)) { return []; } const forceAccountContext = /(?:account|счет|сч|debit|credit|дт|кт|konto|subkonto|analytics|context)/iu.test(contextPath); return extractAccountsFromTextDetailed(String(value), { forceAccountContext }).resolved_account_anchors; } function normalizeTwoDigits(value) { return String(value).padStart(2, "0"); } function parseYear(raw) { const token = String(raw ?? "").trim(); if (token.length === 2) { return `20${token}`; } return token; } function normalizeDateIso(input) { const year = String(input.year ?? "").trim(); const month = normalizeTwoDigits(input.month ?? ""); if (!/^\d{4}$/.test(year) || !/^\d{2}$/.test(month)) { return null; } if (input.day === undefined || input.day === null || String(input.day).trim().length === 0) { return `${year}-${month}`; } const day = normalizeTwoDigits(input.day ?? ""); if (!/^\d{2}$/.test(day)) { return null; } return `${year}-${month}-${day}`; } function parseDateLike(raw) { const value = String(raw ?? "").trim().toLowerCase(); if (!value) { return null; } const isoDay = value.match(/\b(20\d{2})[-/.](0[1-9]|1[0-2])[-/.](0[1-9]|[12]\d|3[01])\b/); if (isoDay) { return normalizeDateIso({ year: isoDay[1], month: isoDay[2], day: isoDay[3] }); } const isoMonth = value.match(/\b(20\d{2})[-/.](0[1-9]|1[0-2])\b/); if (isoMonth) { return normalizeDateIso({ year: isoMonth[1], month: isoMonth[2] }); } const dayMonthYear = value.match(/\b(0?[1-9]|[12]\d|3[01])[./-](0?[1-9]|1[0-2])[./-](\d{2}|\d{4})\b/); if (dayMonthYear) { return normalizeDateIso({ year: parseYear(dayMonthYear[3]), month: dayMonthYear[2], day: dayMonthYear[1] }); } const rusMonthYear = value.match(/\b(январь|февраль|март|апрель|май|июнь|июль|август|сентябрь|октябрь|ноябрь|декабрь)\s+(20\d{2})\b/i); if (rusMonthYear) { const month = RUS_MONTH_TO_NUMBER[String(rusMonthYear[1] ?? "").toLowerCase()]; if (!month) return null; return normalizeDateIso({ year: rusMonthYear[2], month }); } return null; } function monthStart(iso) { const month = String(iso ?? "").slice(0, 7); if (!/^\d{4}-\d{2}$/.test(month)) { return null; } return `${month}-01`; } function normalizeEvidenceDate(value) { const parsed = parseDateLike(value); if (!parsed) { return null; } if (/^\d{4}-\d{2}-\d{2}$/.test(parsed)) { return parsed; } if (/^\d{4}-\d{2}$/.test(parsed)) { return monthStart(parsed); } return null; } function isPeriodWithinWindow(periodIso, window) { const normalized = normalizeEvidenceDate(periodIso); if (!normalized) { return false; } return normalized >= window.from && normalized <= window.to; } function shiftIsoDay(iso, deltaDays) { const normalized = normalizeEvidenceDate(iso); if (!normalized) { return null; } const date = new Date(`${normalized}T00:00:00Z`); if (Number.isNaN(date.getTime())) { return null; } date.setUTCDate(date.getUTCDate() + deltaDays); const year = date.getUTCFullYear(); const month = String(date.getUTCMonth() + 1).padStart(2, "0"); const day = String(date.getUTCDate()).padStart(2, "0"); return `${year}-${month}-${day}`; } function buildAllowedContextWindow(primaryWindow) { if (!primaryWindow) { return null; } const from = shiftIsoDay(primaryWindow.from, -365); const to = shiftIsoDay(primaryWindow.to, 365); if (!from || !to) { return null; } return { from, to, granularity: "month" }; } function extractNormalizedFragments(normalized) { if (!normalized || typeof normalized !== "object") { return []; } const source = normalized; return toObjectArray(source.fragments); } function normalizedAnchorFromFragments(normalized) { const fragments = extractNormalizedFragments(normalized); for (const fragment of fragments) { const timeScope = toObject(fragment.time_scope); const type = String(timeScope?.type ?? "").trim().toLowerCase(); const value = String(timeScope?.value ?? "").trim(); if (!value) { continue; } const parsed = parseDateLike(value); if (parsed) { return { value: parsed, source: `normalized_time_scope:${type || "unknown"}` }; } if (/(?:июл|july|июл)/i.test(value)) { return { value: `${JULY_YEAR}-${JULY_MONTH}`, source: `normalized_time_scope:${type || "unknown"}` }; } } return { value: null, source: "normalized_time_scope:missing" }; } function collectRawTemporalAnchorText(userMessage, companyAnchors) { return [userMessage, ...(companyAnchors?.periods ?? []), ...(companyAnchors?.dates ?? [])] .map((item) => String(item ?? "").trim()) .filter(Boolean) .join(" "); } function resolveJulyAnchor(rawText) { const raw = String(rawText ?? ""); const lower = raw.toLowerCase(); const explicitYear = lower.match(/\b(20\d{2})\b/)?.[1] ?? null; const dayByNamedJuly = lower.match(/(?:^|\D)(0?[1-9]|[12]\d|3[01])\s+(?:июл(?:я|ь)?|july)(?:\D|$)/i); const dayByNumeric = lower.match(/\b(0?[1-9]|[12]\d|3[01])[./-](0?7)(?:[./-](\d{2}|\d{4}))?\b/); const monthByNamed = /(?:июл|july|июл)/i.test(lower); const monthByNumeric = /\b20\d{2}[-/.]0?7\b/.test(lower); if (!dayByNamedJuly && !dayByNumeric && !monthByNamed && !monthByNumeric) { return { raw: null, resolved: null, source: "no_july_anchor", window: null, applyGuard: false }; } const dayValue = dayByNamedJuly?.[1] ?? dayByNumeric?.[1] ?? null; const explicitDayYear = dayByNumeric?.[3] ? parseYear(dayByNumeric[3]) : null; const anchorYear = explicitDayYear ?? explicitYear ?? JULY_YEAR; const applyGuard = anchorYear === JULY_YEAR; if (!applyGuard) { return { raw: dayByNamedJuly?.[0] ?? dayByNumeric?.[0] ?? (monthByNamed ? "июль" : "07"), resolved: normalizeDateIso({ year: anchorYear, month: JULY_MONTH, ...(dayValue ? { day: dayValue } : {}) }), source: "explicit_non_snapshot_year", window: null, applyGuard: false }; } if (dayValue) { const dayIso = normalizeDateIso({ year: JULY_YEAR, month: JULY_MONTH, day: dayValue }); if (dayIso) { return { raw: dayByNamedJuly?.[0] ?? dayByNumeric?.[0] ?? null, resolved: dayIso, source: "company_snapshot_july_day_lock", window: { from: dayIso, to: dayIso, granularity: "day" }, applyGuard: true }; } } return { raw: monthByNamed ? "июль" : "2020-07", resolved: `${JULY_YEAR}-${JULY_MONTH}`, source: "company_snapshot_july_month_lock", window: JULY_WINDOW, applyGuard: true }; } function inferPrimaryWindowFromAnchor(anchor) { const raw = String(anchor ?? "").trim(); if (/^\d{4}-\d{2}$/.test(raw)) { return { from: `${raw}-01`, to: `${raw}-31`, granularity: "month" }; } const normalized = normalizeEvidenceDate(raw); if (!normalized) { return null; } if (/^\d{4}-\d{2}-\d{2}$/.test(normalized)) { return { from: normalized, to: normalized, granularity: "day" }; } const month = normalized.slice(0, 7); if (!/^\d{4}-\d{2}$/.test(month)) { return null; } return { from: `${month}-01`, to: `${month}-31`, granularity: "month" }; } function toTemporalGuardInput(window, fallback) { if (window) { return `${window.from}..${window.to}`; } const value = String(fallback ?? "").trim(); return value || null; } function normalizeIsoDate(value) { if (typeof value !== "string") { return null; } const trimmed = value.trim(); const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); if (!match) { return null; } const year = Number(match[1]); const month = Number(match[2]); const day = Number(match[3]); if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { return null; } const candidate = new Date(Date.UTC(year, month - 1, day)); if (candidate.getUTCFullYear() !== year || candidate.getUTCMonth() + 1 !== month || candidate.getUTCDate() !== day) { return null; } return `${match[1]}-${match[2]}-${match[3]}`; } function normalizeTemporalWindow(input) { const asOfDate = normalizeIsoDate(input.asOfDate); if (asOfDate) { return { from: asOfDate, to: asOfDate, granularity: "day" }; } const from = normalizeIsoDate(input.periodFrom); const to = normalizeIsoDate(input.periodTo); if (!from || !to) { return null; } return { from, to, granularity: from === to ? "day" : "month" }; } function resolveTemporalGuard(input) { const analysisWindow = normalizeTemporalWindow({ asOfDate: input.analysisContext?.as_of_date, periodFrom: input.analysisContext?.period_from, periodTo: input.analysisContext?.period_to }); if (analysisWindow) { const source = String(input.analysisContext?.source ?? "").trim() || "analysis_context"; const guardInput = toTemporalGuardInput(analysisWindow, analysisWindow.from); return { raw_time_anchor: analysisWindow.from, raw_time_scope: guardInput, resolved_time_anchor: analysisWindow.granularity === "day" ? analysisWindow.from : null, resolved_primary_period: analysisWindow, effective_primary_period: analysisWindow, temporal_guard_input: guardInput, temporal_alignment_status: "aligned", temporal_resolution_source: source, temporal_guard_basis: "raw_time_scope_unlocked", temporal_guard_applied: false, temporal_guard_outcome: "passed", primary_period_window: null, allowed_context_window: null, controlled_temporal_expansion_enabled: false, context_expansion_reasons_allowed: ["prehistory", "carryover", "post_period_closure", "long_running_contract_context"], normalized_anchor_drift_detected: false, reason_codes: ["analysis_context_applied"] }; } const rawAnchorText = collectRawTemporalAnchorText(input.userMessage, input.companyAnchors); const julyAnchor = resolveJulyAnchor(rawAnchorText); const normalizedAnchor = normalizedAnchorFromFragments(input.normalized); const reasonCodes = []; if (!julyAnchor.applyGuard) { const resolvedWindow = inferPrimaryWindowFromAnchor(normalizedAnchor.value); const guardInput = toTemporalGuardInput(resolvedWindow, normalizedAnchor.value); return { raw_time_anchor: julyAnchor.raw, raw_time_scope: normalizedAnchor.value, resolved_time_anchor: normalizedAnchor.value, resolved_primary_period: resolvedWindow, effective_primary_period: resolvedWindow, temporal_guard_input: guardInput, temporal_alignment_status: normalizedAnchor.value ? "aligned" : "conflicting", temporal_resolution_source: normalizedAnchor.source, temporal_guard_basis: normalizedAnchor.value ? "raw_time_scope_unlocked" : "none", temporal_guard_applied: false, temporal_guard_outcome: "passed", primary_period_window: null, allowed_context_window: null, controlled_temporal_expansion_enabled: false, context_expansion_reasons_allowed: ["prehistory", "carryover", "post_period_closure", "long_running_contract_context"], normalized_anchor_drift_detected: false, reason_codes: normalizedAnchor.value ? [] : ["missing_resolved_primary_period"] }; } let outcome = "passed"; let normalizedAnchorDriftDetected = false; let temporalAlignmentStatus = "aligned"; if (normalizedAnchor.value && julyAnchor.window && !isPeriodWithinWindow(normalizedAnchor.value, julyAnchor.window)) { normalizedAnchorDriftDetected = true; temporalAlignmentStatus = "corrected"; reasonCodes.push("normalized_anchor_out_of_primary_window_overridden"); } else if (!normalizedAnchor.value && !julyAnchor.resolved) { outcome = "ambiguous_limited"; temporalAlignmentStatus = "conflicting"; reasonCodes.push("missing_time_anchor_under_snapshot_lock"); } const allowedContextWindow = buildAllowedContextWindow(julyAnchor.window); const resolvedPrimaryPeriod = julyAnchor.window; const effectivePrimaryPeriod = resolvedPrimaryPeriod ?? inferPrimaryWindowFromAnchor(julyAnchor.resolved ?? normalizedAnchor.value); const guardInput = toTemporalGuardInput(effectivePrimaryPeriod, julyAnchor.resolved ?? normalizedAnchor.value); return { raw_time_anchor: julyAnchor.raw, raw_time_scope: normalizedAnchor.value, resolved_time_anchor: julyAnchor.resolved ?? normalizedAnchor.value, resolved_primary_period: resolvedPrimaryPeriod, effective_primary_period: effectivePrimaryPeriod, temporal_guard_input: guardInput, temporal_alignment_status: temporalAlignmentStatus, temporal_resolution_source: julyAnchor.source, temporal_guard_basis: julyAnchor.window ? "resolved_primary_period" : "none", temporal_guard_applied: true, temporal_guard_outcome: outcome, primary_period_window: julyAnchor.window, allowed_context_window: allowedContextWindow, controlled_temporal_expansion_enabled: true, context_expansion_reasons_allowed: ["prehistory", "carryover", "post_period_closure", "long_running_contract_context"], normalized_anchor_drift_detected: normalizedAnchorDriftDetected, reason_codes: reasonCodes }; } function applyTemporalHintToExecutionPlan(executionPlan, temporal) { if (!temporal.temporal_guard_applied) { return executionPlan; } const primaryWindow = temporal.effective_primary_period ?? temporal.primary_period_window; const periodLabel = primaryWindow ? `${primaryWindow.from}..${primaryWindow.to}` : temporal.resolved_time_anchor ? temporal.resolved_time_anchor : "active_period"; const hint = primaryWindow?.granularity === "day" && temporal.resolved_time_anchor ? `primary period ${temporal.resolved_time_anchor}; controlled temporal expansion only for linked entities` : `primary period ${periodLabel}; controlled temporal expansion only for linked entities`; return executionPlan.map((item) => { if (!item.should_execute) { return item; } const text = String(item.fragment_text ?? "").trim(); if (/2020-07|июл|июл|july/i.test(text)) { return item; } return { ...item, fragment_text: `${text}; ${hint}`.trim() }; }); } function mojibakeScoreForRuntimeGuards(value) { const source = String(value ?? ""); const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length; const latin = (source.match(/[A-Za-z]/g) ?? []).length; const hardMarkers = (source.match(/[ѓ“‚„…†‡€‰‹‰ЉЊ‹Џ‘’“”•–—™љ›њћџ]/g) ?? []).length; const pairMarkers = (source.match(/(?:Р.|С.|Гђ.|Г‘.)/g) ?? []).length; const doubleEncodedMarkers = (source.match(/(?:Р“.|Р’.|Гѓ.|Г‚.)/gu) ?? []).length; return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2 - doubleEncodedMarkers * 2; } function looksLikeMojibakeForRuntimeGuards(value) { const source = String(value ?? ""); if (!source.trim()) { return false; } if (/[ѓ“‚„…†‡€‰‹‰ЉЊ‹Џ‘’“”•–—™љ›њћџ]/.test(source)) { return true; } if ((source.match(/(?:Р.|С.|Гђ.|Г‘.)/g) ?? []).length >= 2) { return true; } return (source.match(/(?:Р“.|Р’.|Гѓ.|Г‚.)/gu) ?? []).length >= 2; } function repairRuntimeGuardsMojibake(value) { const source = String(value ?? ""); if (!looksLikeMojibakeForRuntimeGuards(source)) { return source; } let candidate = source; for (let pass = 0; pass < 3; pass += 1) { let improved = false; try { const fromWin1251 = iconv_lite_1.default.encode(candidate, "win1251").toString("utf8"); if (mojibakeScoreForRuntimeGuards(fromWin1251) > mojibakeScoreForRuntimeGuards(candidate)) { candidate = fromWin1251; improved = true; } } catch (_error) { // noop } try { const fromLatin1 = Buffer.from(candidate, "latin1").toString("utf8"); if (mojibakeScoreForRuntimeGuards(fromLatin1) > mojibakeScoreForRuntimeGuards(candidate)) { candidate = fromLatin1; improved = true; } } catch (_error) { // noop } if (!improved) { break; } } return candidate; } function resolveDomainPolarityGuard(input) { const repairedMessage = repairRuntimeGuardsMojibake(String(input.userMessage ?? "")); const lower = repairedMessage.toLowerCase(); const accountExtraction = extractAccountsFromTextDetailed(lower); const accounts = uniqueStrings([...(input.companyAnchors?.accounts ?? []), ...accountExtraction.resolved_account_anchors]); const prefixes = new Set(accounts.map((item) => accountPrefix(item)).filter((item) => Boolean(item))); const settlementSignal = input.focusDomainHint === "settlements_60_62" || prefixes.has("60") || prefixes.has("62") || prefixes.has("51") || prefixes.has("76") || /(?:расч[её]т|оплат|аванс|долг|settlement|payment|tail|хвост|незакры|зач[её]т|расч|оплат|аванс|долг|хвост)/i.test(lower); if (!settlementSignal) { return { applied: false, polarity: "not_applicable", outcome: "not_applicable", supplier_score: 0, customer_score: 0, account_scope: accounts, raw_numeric_tokens: accountExtraction.raw_numeric_tokens, classified_numeric_tokens: accountExtraction.classified_numeric_tokens, rejected_as_non_accounts: accountExtraction.rejected_as_non_accounts, resolved_account_anchors: accounts, rejected_problem_units: 0, rejected_evidence: 0, critical_contradiction: false, reason_codes: [] }; } const supplierScore = (/(?:поставщ|supplier|vendor|кредитор|обязательств|payable|поставщ|кредитор|обязательств)/i.test(lower) ? 2 : 0) + (prefixes.has("60") ? 2 : 0) + (/(?:сч[её]т\s*60|по\s*60|счет\s*60)/i.test(lower) ? 1 : 0); const customerScore = (/(?:покупат|customer|buyer|дебитор|receivable|покупат|дебитор)/i.test(lower) ? 2 : 0) + (prefixes.has("62") ? 2 : 0) + (/(?:сч[её]т\s*62|по\s*62|счет\s*62)/i.test(lower) ? 1 : 0); let polarity = "mixed_or_unresolved"; if (supplierScore > 0 || customerScore > 0) { if (supplierScore >= customerScore + 2) { polarity = "supplier_payable"; } else if (customerScore >= supplierScore + 2) { polarity = "customer_receivable"; } else if (prefixes.has("60") && !prefixes.has("62")) { polarity = "supplier_payable"; } else if (prefixes.has("62") && !prefixes.has("60")) { polarity = "customer_receivable"; } } const unresolved = polarity === "mixed_or_unresolved"; const reasonCodes = unresolved ? ["unresolved_supplier_customer_polarity"] : []; if (unresolved && supplierScore > 0 && customerScore > 0) { reasonCodes.push("supplier_customer_signals_conflict"); } if (unresolved && supplierScore === 0 && customerScore === 0) { reasonCodes.push("supplier_customer_signals_absent"); } return { applied: true, polarity, outcome: unresolved ? "limited_unresolved_polarity" : "passed", supplier_score: supplierScore, customer_score: customerScore, account_scope: accounts, raw_numeric_tokens: accountExtraction.raw_numeric_tokens, classified_numeric_tokens: accountExtraction.classified_numeric_tokens, rejected_as_non_accounts: accountExtraction.rejected_as_non_accounts, resolved_account_anchors: accounts, rejected_problem_units: 0, rejected_evidence: 0, critical_contradiction: unresolved, reason_codes: uniqueStrings(reasonCodes) }; } function applyPolarityHintToExecutionPlan(executionPlan, polarity) { if (!polarity.applied || polarity.polarity === "mixed_or_unresolved" || polarity.polarity === "not_applicable") { return executionPlan; } const hint = polarity.polarity === "supplier_payable" ? "context: supplier settlement, payable, account 60" : "context: customer settlement, receivable, account 62"; return executionPlan.map((item) => { if (!item.should_execute) { return item; } const text = String(item.fragment_text ?? "").trim(); if (polarity.polarity === "supplier_payable" && /(поставщ|supplier|сч[её]т\s*60|по\s*60|поставщ|счет\s*60)/i.test(text)) { return item; } if (polarity.polarity === "customer_receivable" && /(покупат|customer|сч[её]т\s*62|по\s*62|покупат|счет\s*62)/i.test(text)) { return item; } return { ...item, fragment_text: `${text}; ${hint}`.trim() }; }); } function containsReceivableSignal(value) { return /(?:customer_settlement|stale_receivable|receivable_closed|receivable|дебитор)/i.test(value); } function containsPayableSignal(value) { return /(?:bank_settlement|payable|обязательств|supplier|поставщ|счет\s*60|\b60(?:\.\d{2})?\b)/i.test(value); } function problemUnitCorpus(unit) { return [ unit.lifecycle_domain ?? "", unit.problem_unit_type, unit.business_defect_class ?? "", unit.failed_expected_edge ?? "", unit.invalid_transition ?? "", unit.mechanism_summary ?? "", unit.business_lifecycle_interpretation ?? "", ...(unit.affected_accounts ?? []) ] .join(" ") .toLowerCase(); } function isProblemUnitCompatible(unit, polarity) { if (polarity === "supplier_payable") { return !containsReceivableSignal(problemUnitCorpus(unit)); } if (polarity === "customer_receivable") { return !containsPayableSignal(problemUnitCorpus(unit)); } return true; } function evidenceCorpus(evidence) { return JSON.stringify({ limitation: evidence.limitation, payload: evidence.payload, mechanism: evidence.mechanism_note, source_ref: evidence.source_ref, pointer: evidence.pointer }).toLowerCase(); } function evidenceAccounts(evidence) { const payload = toObject(evidence.payload); const direct = uniqueStrings([ ...extractAccountsFromUnknown(payload?.account_context), ...extractAccountsFromUnknown(payload?.account_debit), ...extractAccountsFromUnknown(payload?.account_credit) ]); if (direct.length > 0) { return direct; } return uniqueStrings([ ...extractAccountsFromUnknown(evidence.payload), ...extractAccountsFromUnknown(evidence.pointer ?? null) ]); } function isEvidenceCompatibleWithPolarity(evidence, polarity) { const corpus = evidenceCorpus(evidence); const accounts = evidenceAccounts(evidence).map((item) => accountPrefix(item)).filter((item) => Boolean(item)); if (polarity === "supplier_payable") { if (containsReceivableSignal(corpus)) { return false; } if (accounts.length > 0 && accounts.every((item) => item === "62")) { return false; } } if (polarity === "customer_receivable") { if (containsPayableSignal(corpus)) { return false; } if (accounts.length > 0 && accounts.every((item) => item === "60")) { return false; } } return true; } function applyDomainPolarityGuardToRetrievalResults(input) { if (!input.guard.applied || input.guard.polarity === "not_applicable" || input.guard.polarity === "mixed_or_unresolved") { return { retrievalResults: input.retrievalResults, audit: input.guard }; } let rejectedProblemUnits = 0; let rejectedEvidence = 0; let criticalContradiction = false; const adjusted = input.retrievalResults.map((result) => { const originalUnits = Array.isArray(result.problem_units) ? result.problem_units : []; const filteredUnits = originalUnits.filter((unit) => isProblemUnitCompatible(unit, input.guard.polarity)); rejectedProblemUnits += Math.max(0, originalUnits.length - filteredUnits.length); const originalEvidence = Array.isArray(result.evidence) ? result.evidence : []; const filteredEvidence = originalEvidence.filter((item) => isEvidenceCompatibleWithPolarity(item, input.guard.polarity)); rejectedEvidence += Math.max(0, originalEvidence.length - filteredEvidence.length); if (originalUnits.length > 0 && filteredUnits.length === 0 && originalEvidence.length > 0 && filteredEvidence.length === 0) { criticalContradiction = true; } return { ...result, evidence: filteredEvidence, ...(Array.isArray(result.problem_units) ? { problem_units: filteredUnits } : {}) }; }); const reasonCodes = []; if (rejectedProblemUnits > 0) { reasonCodes.push("polarity_problem_unit_filter_applied"); } if (rejectedEvidence > 0) { reasonCodes.push("polarity_evidence_filter_applied"); } if (criticalContradiction) { reasonCodes.push("critical_domain_polarity_contradiction"); } return { retrievalResults: adjusted, audit: { ...input.guard, rejected_problem_units: rejectedProblemUnits, rejected_evidence: rejectedEvidence, critical_contradiction: criticalContradiction, outcome: criticalContradiction ? "blocked_conflict" : "passed", reason_codes: uniqueStrings([...(input.guard.reason_codes ?? []), ...reasonCodes]) } }; } function initRejectBreakdown() { return { wrong_period: 0, wrong_domain: 0, wrong_account_scope: 0, weak_source_mapping: 0, zero_live_match: 0, future_dated_or_out_of_window: 0 }; } function isVatPrefix(prefix) { return prefix === "19" || prefix === "68"; } function isSettlementPrefix(prefix) { return prefix === "51" || prefix === "60" || prefix === "62" || prefix === "76"; } function isMonthClosePrefix(prefix) { const numeric = Number(prefix); if (prefix === "97") { return true; } if (!Number.isFinite(numeric)) { return false; } return numeric >= 20 && numeric <= 44; } function isFixedAssetPrefix(prefix) { return prefix === "01" || prefix === "02" || prefix === "08"; } function expectedAccountPrefixes(input) { const explicit = uniqueStrings([...(input.companyAnchors?.accounts ?? []), ...extractAccountsFromText(input.userMessage)]) .map((item) => accountPrefix(item)) .filter((item) => Boolean(item)); if (explicit.length > 0) { return uniqueStrings(explicit); } if (input.focusDomainHint === "vat_document_register_book") { return ["19", "68"]; } if (input.focusDomainHint === "month_close_costs_20_44") { return ["20", "25", "26", "44", "97", "01", "02", "08"]; } if (input.focusDomainHint === "fixed_asset_amortization") { return ["01", "02", "08"]; } if (input.focusDomainHint === "settlements_60_62") { if (input.polarity === "supplier_payable") { return ["60", "51", "76"]; } if (input.polarity === "customer_receivable") { return ["62", "51"]; } return ["60", "62", "51", "76"]; } return []; } function isLiveEvidence(evidence) { const payload = toObject(evidence.payload); if (String(payload?.source_layer ?? "").trim().toLowerCase() === "mcp_live_probe") { return true; } const sourceEntity = String(evidence.pointer?.source?.entity ?? "").toLowerCase(); return sourceEntity.includes("mcplivemovement"); } function extractEvidencePeriod(evidence) { const payload = toObject(evidence.payload); return (String(evidence.source_ref?.period ?? "").trim() || String(evidence.pointer?.source?.period ?? "").trim() || String(payload?.period ?? "").trim() || null); } function isExpectedAccountScopeMatch(accounts, expectedPrefixes) { if (accounts.length === 0 || expectedPrefixes.length === 0) { return true; } const prefixes = accounts.map((item) => accountPrefix(item)).filter((item) => Boolean(item)); if (prefixes.length === 0) { return true; } return prefixes.some((prefix) => expectedPrefixes.includes(prefix)); } function hasWrongDomainByAccounts(accounts, focusDomainHint) { if (accounts.length === 0 || !focusDomainHint) { return false; } const prefixes = accounts.map((item) => accountPrefix(item)).filter((item) => Boolean(item)); if (prefixes.length === 0) { return false; } if (focusDomainHint === "settlements_60_62") { return prefixes.every((prefix) => isVatPrefix(prefix)); } if (focusDomainHint === "vat_document_register_book") { return prefixes.every((prefix) => isSettlementPrefix(prefix) || isMonthClosePrefix(prefix)); } if (focusDomainHint === "month_close_costs_20_44") { return prefixes.every((prefix) => isSettlementPrefix(prefix) || isVatPrefix(prefix)); } if (focusDomainHint === "fixed_asset_amortization") { const hasFixedAsset = prefixes.some((prefix) => isFixedAssetPrefix(prefix)); if (hasFixedAsset) { return false; } return prefixes.every((prefix) => isSettlementPrefix(prefix) || isVatPrefix(prefix) || isMonthClosePrefix(prefix)); } return false; } function extractLiveMatchedRows(result) { const summary = toObject(result.summary); const live = toObject(summary?.live_mcp); const value = Number(live?.matched_rows); return Number.isFinite(value) ? value : null; } function liveAccountScopeWasApplied(result) { const summary = toObject(result.summary); const live = toObject(summary?.live_mcp); const accountScope = live?.account_scope; return Array.isArray(accountScope) && accountScope.length > 0; } function evidenceContextExpansionMeta(evidence) { const payload = toObject(evidence.payload); const allowed = Boolean(payload?.context_expansion_allowed); const reason = String(payload?.context_expansion_reason ?? "").trim() || null; return { allowed, reason }; } function itemContextExpansionMeta(item) { const allowed = Boolean(item.context_expansion_allowed); const reason = String(item.context_expansion_reason ?? "").trim() || null; return { allowed, reason }; } function withinAllowedContextWindow(normalizedPeriod, temporal) { if (!temporal.allowed_context_window) { return false; } return normalizedPeriod >= temporal.allowed_context_window.from && normalizedPeriod <= temporal.allowed_context_window.to; } function effectivePrimaryPeriodWindow(temporal) { return temporal.effective_primary_period ?? temporal.primary_period_window; } function evidenceAdmissibilityReasons(input) { const reasons = new Set(); if (input.evidence.limitation?.reason_code === "weak_source_mapping") { reasons.add("weak_source_mapping"); } if (input.zeroLiveMatch && isLiveEvidence(input.evidence)) { reasons.add("zero_live_match"); } const period = extractEvidencePeriod(input.evidence); const primaryWindow = effectivePrimaryPeriodWindow(input.temporal); if (period && primaryWindow) { const normalized = normalizeEvidenceDate(period); const expansionMeta = evidenceContextExpansionMeta(input.evidence); if (normalized && !isPeriodWithinWindow(normalized, primaryWindow)) { const insideAllowed = withinAllowedContextWindow(normalized, input.temporal); if (insideAllowed && expansionMeta.allowed && expansionMeta.reason) { // Allowed controlled temporal expansion: period is outside primary but linked and explained. } else if (normalized > primaryWindow.to && !insideAllowed) { reasons.add("future_dated_or_out_of_window"); } else { reasons.add("wrong_period"); } } } const accounts = evidenceAccounts(input.evidence); if (!isExpectedAccountScopeMatch(accounts, input.expectedPrefixes)) { reasons.add("wrong_account_scope"); } if (hasWrongDomainByAccounts(accounts, input.focusDomainHint)) { reasons.add("wrong_domain"); } return Array.from(reasons); } function isLiveItem(item) { return String(item.source_layer ?? "").trim().toLowerCase() === "mcp_live_probe"; } function itemPeriod(item) { const value = String(item.period ?? item.Period ?? "").trim(); return value || null; } function itemAccounts(item) { const direct = uniqueStrings([ ...extractAccountsFromUnknown(item.account_context), ...extractAccountsFromUnknown(item.account_debit), ...extractAccountsFromUnknown(item.account_credit) ]); if (direct.length > 0) { return direct; } return uniqueStrings([...extractAccountsFromUnknown(item)]); } function itemRejectReasons(input) { const reasons = new Set(); if (input.zeroLiveMatch && isLiveItem(input.item)) { reasons.add("zero_live_match"); } const period = itemPeriod(input.item); const primaryWindow = effectivePrimaryPeriodWindow(input.temporal); if (period && primaryWindow) { const normalized = normalizeEvidenceDate(period); const expansionMeta = itemContextExpansionMeta(input.item); if (normalized && !isPeriodWithinWindow(normalized, primaryWindow)) { const insideAllowed = withinAllowedContextWindow(normalized, input.temporal); if (insideAllowed && expansionMeta.allowed && expansionMeta.reason) { // Allowed controlled temporal expansion: period is outside primary but linked and explained. } else if (normalized > primaryWindow.to && !insideAllowed) { reasons.add("future_dated_or_out_of_window"); } else { reasons.add("wrong_period"); } } } const accounts = itemAccounts(input.item); if (!isExpectedAccountScopeMatch(accounts, input.expectedPrefixes)) { reasons.add("wrong_account_scope"); } if (hasWrongDomainByAccounts(accounts, input.focusDomainHint)) { reasons.add("wrong_domain"); } return Array.from(reasons); } function addRejectReason(target, reason) { target[reason] += 1; } function applyEvidenceAdmissibilityGate(input) { const rejectBreakdown = initRejectBreakdown(); const categoryBreakdown = { hard_evidence: 0, supporting_signal: 0, inadmissible_noise: 0 }; let candidateEvidenceTotal = 0; let admissibleEvidenceCount = 0; let rejectedEvidenceCount = 0; let rejectedItemCount = 0; const expectedPrefixes = expectedAccountPrefixes({ focusDomainHint: input.focusDomainHint, polarity: input.polarity, companyAnchors: input.companyAnchors, userMessage: input.userMessage }); const adjusted = input.retrievalResults.map((result) => { const matchedRows = extractLiveMatchedRows(result); const zeroLiveMatch = matchedRows === 0 && liveAccountScopeWasApplied(result); const evidence = Array.isArray(result.evidence) ? result.evidence : []; candidateEvidenceTotal += evidence.length; const admissibleEvidence = []; for (const item of evidence) { const reasons = evidenceAdmissibilityReasons({ evidence: item, temporal: input.temporal, focusDomainHint: input.focusDomainHint, expectedPrefixes, zeroLiveMatch }); if (reasons.length > 0) { rejectedEvidenceCount += 1; categoryBreakdown.inadmissible_noise += 1; for (const reason of reasons) { addRejectReason(rejectBreakdown, reason); } continue; } const limitationCode = String(item.limitation?.reason_code ?? "").trim(); const payload = toObject(item.payload); const expandedByContext = Boolean(payload?.context_expansion_reason); if (!limitationCode && item.confidence !== "low" && !expandedByContext) { categoryBreakdown.hard_evidence += 1; } else { categoryBreakdown.supporting_signal += 1; } admissibleEvidenceCount += 1; admissibleEvidence.push(item); } const items = Array.isArray(result.items) ? result.items : []; const admissibleItems = []; for (const item of items) { const reasons = itemRejectReasons({ item, temporal: input.temporal, focusDomainHint: input.focusDomainHint, expectedPrefixes, zeroLiveMatch }); if (reasons.length > 0) { rejectedItemCount += 1; for (const reason of reasons) { addRejectReason(rejectBreakdown, reason); } continue; } admissibleItems.push(item); } const summary = { ...(toObject(result.summary) ?? {}), evidence_admissibility_gate: { candidate_evidence: evidence.length, admissible_evidence: admissibleEvidence.length, rejected_evidence: Math.max(0, evidence.length - admissibleEvidence.length), rejected_items: Math.max(0, items.length - admissibleItems.length) } }; const limitations = [...(result.limitations ?? [])]; if (zeroLiveMatch) { limitations.push("Live probe matched_rows=0; live evidence excluded from grounded answer."); } if (admissibleEvidence.length === 0 && evidence.length > 0) { limitations.push("Admissibility gate removed non-admissible evidence for current scope."); } const normalizedStatus = result.status === "ok" && admissibleEvidence.length === 0 && admissibleItems.length === 0 ? "partial" : result.status; return { ...result, status: normalizedStatus, items: admissibleItems, evidence: admissibleEvidence, summary, limitations: uniqueStrings(limitations) }; }); const reasonCodes = []; if (rejectedEvidenceCount > 0) { reasonCodes.push("inadmissible_evidence_filtered"); } if (admissibleEvidenceCount === 0) { reasonCodes.push("no_admissible_evidence_for_grounded_answer"); } if (rejectedItemCount > 0) { reasonCodes.push("inadmissible_items_filtered"); } return { retrievalResults: adjusted, audit: { candidate_evidence_total: candidateEvidenceTotal, admissible_evidence_count: admissibleEvidenceCount, rejected_evidence_count: rejectedEvidenceCount, rejected_item_count: rejectedItemCount, reject_breakdown: rejectBreakdown, category_breakdown: categoryBreakdown, reason_codes: uniqueStrings(reasonCodes) } }; } function evaluateGroundedAnswerEligibility(input) { const temporalPassed = input.temporal.temporal_guard_outcome === "passed"; const eligibilityTimeBasis = input.temporal.temporal_guard_basis; const scopeValues = Array.isArray(input.businessScopeResolved) ? input.businessScopeResolved : []; const hasCompanyScope = scopeValues.includes("company_specific_accounting"); const hasOnlyGenericScope = scopeValues.length > 0 && scopeValues.every((item) => String(item ?? "").trim() === "generic_accounting"); const businessScopePassed = scopeValues.length === 0 ? true : hasCompanyScope || !hasOnlyGenericScope; const polarityPassed = !input.polarity.applied || input.polarity.outcome === "passed" || input.polarity.outcome === "not_applicable"; const claimAnchorResolutionRate = input.claimAnchors ? Number(input.claimAnchors.claim_anchor_resolution_rate ?? 0) : null; const missingRequiredAnchors = input.claimAnchors ? Number(input.claimAnchors.missing_anchors?.length ?? 0) : 0; const requiredAnchorsCount = input.claimAnchors ? Number(input.claimAnchors.required_anchors?.length ?? 0) : 0; const claimAnchorsPassed = !input.claimAnchors || ((claimAnchorResolutionRate ?? 1) >= 0.5 && missingRequiredAnchors <= Math.max(1, Math.floor(Math.max(requiredAnchorsCount, 1) / 2))); const admissibleEvidenceCount = input.evidence.admissible_evidence_count; const criticalContradiction = Boolean(input.polarity.critical_contradiction); const targetedEvidencePassed = input.targetedEvidenceHitRate == null || Number.isNaN(Number(input.targetedEvidenceHitRate)) ? true : Number(input.targetedEvidenceHitRate) > 0; const eligible = temporalPassed && businessScopePassed && polarityPassed && claimAnchorsPassed && admissibleEvidenceCount > 0 && targetedEvidencePassed && !criticalContradiction; const reasonCodes = []; if (!temporalPassed) { reasonCodes.push(`temporal_guard_${input.temporal.temporal_guard_outcome}`); } if (!polarityPassed) { reasonCodes.push(`polarity_guard_${input.polarity.outcome}`); } if (!businessScopePassed) { reasonCodes.push("business_scope_generic_unresolved"); } if (!claimAnchorsPassed) { reasonCodes.push("claim_anchor_coverage_insufficient"); } if (admissibleEvidenceCount <= 0) { reasonCodes.push("admissible_evidence_count_zero"); } if (!targetedEvidencePassed) { reasonCodes.push("targeted_evidence_hit_rate_zero"); } if (criticalContradiction) { reasonCodes.push("critical_domain_or_account_contradiction"); } return { eligible, temporal_passed: temporalPassed, eligibility_time_basis: eligibilityTimeBasis, business_scope_passed: businessScopePassed, polarity_passed: polarityPassed, claim_anchors_passed: claimAnchorsPassed, claim_anchor_resolution_rate: claimAnchorResolutionRate, missing_required_anchors: missingRequiredAnchors, admissible_evidence_count: admissibleEvidenceCount, critical_contradiction: criticalContradiction, outcome: eligible ? "grounded_allowed" : "limited_or_insufficient_evidence", grounding_mode: eligible ? "grounded_positive" : "limited_or_insufficient_evidence", reason_codes: uniqueStrings(reasonCodes) }; } function applyEligibilityToGroundingCheck(groundingCheck, eligibility) { if (eligibility.eligible) { return groundingCheck; } const status = eligibility.admissible_evidence_count <= 0 || !eligibility.temporal_passed || !eligibility.claim_anchors_passed || !eligibility.business_scope_passed ? "no_grounded_answer" : "partial"; const reasonMap = { admissible_evidence_count_zero: "Недостаточно подтвержденных данных для уверенного ответа.", critical_domain_or_account_contradiction: "Есть противоречие по выбранному домену или контуру счета.", temporal_guard_failed_out_of_snapshot_window: "Запрошенный период выходит за доступный срез данных. Temporal anchor outside snapshot window.", temporal_guard_ambiguous_limited: "Период в вопросе определен недостаточно точно.", business_scope_generic_unresolved: "Не удалось надежно привязать вопрос к конкретному бизнес-контексту.", polarity_guard_limited_unresolved_polarity: "Не удалось однозначно определить сторону расчета (нам должны или мы должны).", polarity_guard_blocked_conflict: "В данных есть конфликт по стороне расчета.", claim_anchor_coverage_insufficient: "Не хватает ключевых ориентиров в вопросе (период, объект или контрагент).", targeted_evidence_hit_rate_zero: "Не хватило целевых подтверждений по выбранному сценарию." }; const reasons = [ ...(Array.isArray(groundingCheck.reasons) ? groundingCheck.reasons : []), ...eligibility.reason_codes.map((code) => reasonMap[code] ?? code) ]; return { ...groundingCheck, status, reasons: uniqueStrings(reasons) }; }