// @ts-nocheck import * as nanoid_1 from "nanoid"; import * as stage1Contracts_1 from "../types/stage1Contracts"; import * as config_1 from "../config"; import * as log_1 from "../utils/log"; import * as answerComposer_1 from "./answerComposer"; import * as assistantDataLayer_1 from "./assistantDataLayer"; import * as assistantSessionLogger_1 from "./assistantSessionLogger"; import * as investigationState_1 from "./investigationState"; import * as retrievalResultNormalizer_1 from "./retrievalResultNormalizer"; import * as questionTypeResolver_1 from "./questionTypeResolver"; import * as companyAnchorResolver_1 from "./companyAnchorResolver"; import * as assistantRuntimeGuards_1 from "./assistantRuntimeGuards"; import * as assistantClaimBoundEvidence_1 from "./assistantClaimBoundEvidence"; function retrievalSummaryForRoute(route) { if (route === "store_canonical") return "Canonical accounting data path selected."; if (route === "store_feature_risk") return "Risk/control profile path selected."; if (route === "hybrid_store_plus_live") return "Hybrid chain analysis path selected."; if (route === "live_mcp_drilldown") return "Live drilldown path selected."; if (route === "batch_refresh_then_store") return "Heavy analytical batch path selected."; return "Route selected."; } function mapNoRouteReason(reason) { if (reason === "out_of_scope") return "Fragment out of scope."; if (reason === "insufficient_specificity") return "Needs clarification."; if (reason === "missing_mapping") return "Route mapping is missing."; if (reason === "unsupported_fragment_type") return "Fragment type unsupported."; return "No-route decision."; } function extractFragments(normalized) { if (!normalized || typeof normalized !== "object") { return []; } const source = normalized; return Array.isArray(source.fragments) ? source.fragments : []; } function hasExplicitPeriodAnchorFromNormalized(normalized) { const fragments = extractFragments(normalized); const explicitPeriodPattern = /(?:\b20\d{2}(?:[-./](?:0?[1-9]|1[0-2]))?(?:[-./](?:0?[1-9]|[12]\d|3[01]))?\b|\b(?:0?[1-9]|[12]\d|3[01])[./-](?:0?[1-9]|1[0-2])[./-](?:\d{2}|\d{4})\b|\b(?:январ[ьяе]|феврал[ьяе]|март[ае]?|апрел[ьяе]|ма[йея]|июн[ьяе]|июл[ьяе]|август[ае]?|сентябр[ьяе]|октябр[ьяе]|ноябр[ьяе]|декабр[ьяе]|january|february|march|april|may|june|july|august|september|october|november|december)\b)/i; for (const item of fragments) { if (!item || typeof item !== "object") { continue; } const fragment = item; const timeScope = fragment.time_scope && typeof fragment.time_scope === "object" ? fragment.time_scope : null; if (timeScope) { const type = String(timeScope.type ?? "").trim().toLowerCase(); const value = String(timeScope.value ?? "").trim(); const confidence = String(timeScope.confidence ?? "").trim().toLowerCase(); if ((type === "explicit" || type === "range") && value.length > 0 && confidence !== "low") { return true; } } const rawText = `${typeof fragment.raw_fragment_text === "string" ? fragment.raw_fragment_text : ""} ${typeof fragment.normalized_fragment_text === "string" ? fragment.normalized_fragment_text : ""}`; if (explicitPeriodPattern.test(rawText)) { return true; } } return false; } function extractExecutionState(normalized) { const fragments = extractFragments(normalized); return fragments.map((item) => { if (!item || typeof item !== "object") { return {}; } const fragment = item; return { fragment_id: fragment.fragment_id ?? null, execution_readiness: fragment.execution_readiness ?? null, route_status: fragment.route_status ?? null, no_route_reason: fragment.no_route_reason ?? null }; }); } function collectBusinessScopesFromNormalized(normalized) { const scopes = []; for (const item of extractFragments(normalized)) { if (!item || typeof item !== "object") { continue; } const scope = String(item.business_scope ?? "").trim(); if (scope) { scopes.push(scope); } } return Array.from(new Set(scopes)); } function hasJuly2020SnapshotSignal(userMessage, companyAnchors) { const lower = String(userMessage ?? "").toLowerCase(); if (/(?:\b2020[-/.]0?7\b|\bиюл[ьяе]?\b(?:\s+20\d{2})?|\bjuly\b(?:\s+20\d{2})?)/i.test(lower)) { return true; } const periods = Array.isArray(companyAnchors?.periods) ? companyAnchors.periods : []; const dates = Array.isArray(companyAnchors?.dates) ? companyAnchors.dates : []; return [...periods, ...dates].some((item) => /2020[-/.]0?7|июл|july/i.test(String(item ?? "").toLowerCase())); } function hasP0DomainSignal(userMessage, companyAnchors) { if (inferP0DomainFromMessage(userMessage)) { return true; } const accounts = Array.isArray(companyAnchors?.accounts) ? companyAnchors.accounts : []; if (accounts.some((item) => /^(?:01|02|08|19|20|21|23|25|26|28|29|44|51|60|62|68|76|97)(?:\.|$)/.test(String(item ?? "").trim()))) { return true; } return /(?:ндс|vat|рбп|deferred|амортиз|supplier|customer|settlement|month\s*close|закрыти[ея]\s+месяц|поставщ|покупат)/i.test(String(userMessage ?? "").toLowerCase()); } function resolveBusinessScopeAlignment(input) { const rawScopes = collectBusinessScopesFromNormalized(input.normalized); const needsCompanyGrounding = hasJuly2020SnapshotSignal(input.userMessage, input.companyAnchors) && hasP0DomainSignal(input.userMessage, input.companyAnchors); const reasons = []; if (needsCompanyGrounding) { reasons.push("july_2020_snapshot_p0_signal"); } if (!input.routeSummary || input.routeSummary.mode !== "deterministic_v2" || !needsCompanyGrounding) { return { business_scope_raw: rawScopes, business_scope_resolved: rawScopes, company_grounding_applied: false, scope_resolution_reason: reasons, route_summary_resolved: input.routeSummary }; } let changed = false; const decisions = input.routeSummary.decisions.map((decision) => { const scopeValue = String(decision.business_scope ?? "").trim(); if (scopeValue !== "generic_accounting" && scopeValue !== "unclear") { return decision; } changed = true; return { ...decision, business_scope: "company_specific_accounting" }; }); const resolvedSummary = changed ? { ...input.routeSummary, decisions } : input.routeSummary; const resolvedScopes = changed ? Array.from(new Set(decisions.map((decision) => String(decision.business_scope ?? "").trim()).filter(Boolean))) : rawScopes; return { business_scope_raw: rawScopes, business_scope_resolved: resolvedScopes, company_grounding_applied: changed, scope_resolution_reason: changed ? [...reasons, "generic_or_unclear_to_company_specific_override"] : reasons, route_summary_resolved: resolvedSummary }; } function isJuly2020TemporalResolved(temporalGuard) { if (!temporalGuard || typeof temporalGuard !== "object") { return false; } const resolvedAnchor = String(temporalGuard.resolved_time_anchor ?? "").trim(); if (/^2020-07(?:-\d{2})?$/.test(resolvedAnchor)) { return true; } const effective = temporalGuard.effective_primary_period && typeof temporalGuard.effective_primary_period === "object" ? temporalGuard.effective_primary_period : null; if (effective) { const from = String(effective.from ?? "").trim(); const to = String(effective.to ?? "").trim(); if (/^2020-07-\d{2}$/.test(from) && /^2020-07-\d{2}$/.test(to)) { return true; } } const resolvedPrimary = temporalGuard.resolved_primary_period && typeof temporalGuard.resolved_primary_period === "object" ? temporalGuard.resolved_primary_period : null; if (!resolvedPrimary) { return false; } const from = String(resolvedPrimary.from ?? "").trim(); const to = String(resolvedPrimary.to ?? "").trim(); return /^2020-07-\d{2}$/.test(from) && /^2020-07-\d{2}$/.test(to); } function hasP0ClaimSignal(claimType, focusDomainHint) { const claim = String(claimType ?? "").trim(); if (claim === "prove_settlement_closure_state" || claim === "prove_advance_offset_state" || claim === "prove_vat_chain_completeness" || claim === "prove_month_close_state" || claim === "prove_rbp_tail_state" || claim === "prove_fixed_asset_amortization_coverage") { return true; } return (focusDomainHint === "settlements_60_62" || focusDomainHint === "vat_document_register_book" || focusDomainHint === "month_close_costs_20_44" || focusDomainHint === "fixed_asset_amortization"); } function resolveBusinessScopeFromLiveContext(input) { const current = input.current; const routeSummary = current?.route_summary_resolved; const julyResolved = isJuly2020TemporalResolved(input.temporalGuard); const p0Signal = hasP0ClaimSignal(input.claimType, input.focusDomainHint); if (!julyResolved || !p0Signal) { return current; } const reasons = Array.isArray(current.scope_resolution_reason) ? [...current.scope_resolution_reason] : []; if (!reasons.includes("temporal_claim_bound_company_scope_recovery")) { reasons.push("temporal_claim_bound_company_scope_recovery"); } const currentScopes = Array.isArray(current.business_scope_resolved) ? current.business_scope_resolved : []; let changed = false; const normalizedScopes = currentScopes .map((item) => String(item ?? "").trim()) .filter(Boolean) .map((item) => { if (item === "generic_accounting" || item === "unclear") { changed = true; return "company_specific_accounting"; } return item; }); if (!normalizedScopes.includes("company_specific_accounting")) { normalizedScopes.push("company_specific_accounting"); changed = true; } let routeSummaryResolved = routeSummary; if (routeSummary && routeSummary.mode === "deterministic_v2" && Array.isArray(routeSummary.decisions)) { const decisions = routeSummary.decisions.map((decision) => { const scopeValue = String(decision.business_scope ?? "").trim(); if (scopeValue !== "generic_accounting" && scopeValue !== "unclear") { return decision; } changed = true; return { ...decision, business_scope: "company_specific_accounting" }; }); routeSummaryResolved = changed ? { ...routeSummary, decisions } : routeSummary; } return { ...current, business_scope_resolved: Array.from(new Set(normalizedScopes)), company_grounding_applied: current.company_grounding_applied || changed, scope_resolution_reason: reasons, route_summary_resolved: routeSummaryResolved }; } function escapeRegex(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function enrichFragmentTextWithHints(fragment, text) { const baseText = String(text ?? "").trim(); const accountHints = Array.isArray(fragment.account_hints) ? Array.from(new Set(fragment.account_hints .map((item) => String(item ?? "").trim()) .filter((item) => item.length > 0))) : []; if (accountHints.length === 0) { return baseText; } const hasAccountInText = accountHints.some((account) => new RegExp(`\\b${escapeRegex(account)}\\b`, "i").test(baseText)); if (hasAccountInText) { return baseText; } return `${baseText}, по счету ${accountHints.join(", ")}`; } function fragmentTextById(normalized) { const result = new Map(); for (const item of extractFragments(normalized)) { if (!item || typeof item !== "object") { continue; } const fragment = item; const fragmentId = typeof fragment.fragment_id === "string" ? fragment.fragment_id : ""; if (!fragmentId) { continue; } const text = (typeof fragment.raw_fragment_text === "string" && fragment.raw_fragment_text.trim()) || (typeof fragment.normalized_fragment_text === "string" && fragment.normalized_fragment_text.trim()) || ""; result.set(fragmentId, enrichFragmentTextWithHints(fragment, text)); } return result; } function extractDiscardedIntentSegments(normalized) { if (!normalized || typeof normalized !== "object") { return []; } const source = normalized; if (!Array.isArray(source.discarded_fragments)) { return []; } return source.discarded_fragments .map((item) => { if (!item || typeof item !== "object") { return null; } const value = item; const text = typeof value.raw_fragment_text === "string" ? value.raw_fragment_text.trim() : ""; return text || null; }) .filter((item) => Boolean(item)); } function collectDateSpans(text) { const spans = []; const datePatterns = [ /\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 datePattern of datePatterns) { let match = null; while ((match = datePattern.exec(text)) !== null) { spans.push({ start: match.index, end: match.index + match[0].length }); } } return spans; } function collectContractSpans(text) { const spans = []; const contractPatterns = [ /(?:\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 contractPattern of contractPatterns) { let match = null; while ((match = contractPattern.exec(text)) !== null) { spans.push({ start: match.index, end: match.index + match[0].length }); } } return spans; } function collectAmountSpans(text) { const spans = []; const amountPatterns = [/\b\d{1,3}(?:[ \u00A0]\d{3})+(?:[.,]\d{2})?\b/g, /\b\d+[.,]\d{2}\b/g]; for (const amountPattern of amountPatterns) { let match = null; while ((match = amountPattern.exec(text)) !== null) { spans.push({ start: match.index, end: match.index + match[0].length }); } } return spans; } function collectPercentSpans(text) { const spans = []; const percentPattern = /\b\d{1,3}(?:[.,]\d+)?\s*%/g; let match = null; while ((match = percentPattern.exec(text)) !== null) { spans.push({ start: match.index, end: match.index + match[0].length }); } return spans; } function intersectsAnySpan(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 extractAccountTokens(text) { const lower = String(text ?? "").toLowerCase(); const explicitAccounts = new Set(); const knownAccountPrefixes = 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 contextualPattern = /(?:\bсчет(?:а|у|ом|ов)?\b|\bсч\.?\b|\baccount(?:s)?\b|\bschet(?:a|u|om|ov)?\b)\s*(?:№|#|:)?\s*(\d{2}(?:\.\d{2})?)/giu; let contextual = null; while ((contextual = contextualPattern.exec(lower)) !== null) { if (contextual[1]) { const token = String(contextual[1]).trim(); const prefix = token.match(/^(\d{2})/)?.[1]; if (prefix && knownAccountPrefixes.has(prefix)) { explicitAccounts.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]; const rightPrefix = right.match(/^(\d{2})/)?.[1]; if (leftPrefix && knownAccountPrefixes.has(leftPrefix)) { explicitAccounts.add(left); } if (rightPrefix && knownAccountPrefixes.has(rightPrefix)) { explicitAccounts.add(right); } } if (explicitAccounts.size > 0) { return Array.from(explicitAccounts); } const contractSpans = collectContractSpans(lower); const spans = [...collectDateSpans(lower), ...collectAmountSpans(lower), ...collectPercentSpans(lower), ...contractSpans]; const hasAccountingLexeme = /(?:\bсчет(?:а|у|ом|ов)?\b|\bсч\.?\b|\baccount(?:s)?\b|\bschet(?:a|u|om|ov)?\b|оплат|расчет|аванс|долг|settlement|payment)/iu.test(lower); if (!hasAccountingLexeme) { return []; } const accountList = []; const genericPattern = /\b\d{2}(?:\.\d{2})?\b/g; let generic = null; while ((generic = genericPattern.exec(lower)) !== null) { const value = generic[0]; const start = generic.index; const end = start + value.length; if (intersectsAnySpan(start, end, spans)) { continue; } if (!hasAccountContextAround(lower, start, end)) { continue; } const prefix = value.match(/^(\d{2})/)?.[1]; if (!prefix || !knownAccountPrefixes.has(prefix)) { continue; } accountList.push(value); } return Array.from(new Set(accountList)); } function extractSubjectTokens(text) { const lower = text.toLowerCase(); const tokens = []; const push = (token, match) => { if (match) tokens.push(token); }; push("nds", /\b(?:\u043d\u0434\u0441|vat)\b/iu.test(lower)); push("os", /(?:\b\u043e\u0441\b|\u043e\u0441\u043d\u043e\u0432\u043d(?:\u044b\u0435|\u044b\u0445)\s+\u0441\u0440\u0435\u0434|osnovn(?:ye|yh)\s+sred)/iu.test(lower)); push("counterparty", /(?:\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|supplier|buyer|counterparty|kontragent|postavshch|pokupatel)/iu.test(lower)); push("document", /(?:\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d|\u0432\u044b\u043f\u0438\u0441\u043a|\u043f\u043b\u0430\u0442\u0435\u0436|document|invoice|posting|dokument|realiz|postuplen|vypisk|platezh)/iu.test(lower)); push("saldo", /(?:\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u0441\u0442\u0430\u0442\u043a|saldo|balance)/iu.test(lower)); push("anomaly", /(?:\u0430\u043d\u043e\u043c\u0430\u043b|\u0440\u0438\u0441\u043a|\u043f\u043e\u0434\u043e\u0437\u0440|\u0445\u0432\u043e\u0441\u0442|anomaly|risk|anomali|hvost|tail)/iu.test(lower)); push("chain", /(?:\u0446\u0435\u043f\u043e\u0447|\u0440\u0430\u0437\u043b\u043e\u0436|\u0441\u0432\u044f\u0437\u043a|\u0440\u0430\u0437\u0440\u044b\u0432|chain|razlozh|svyaz|razryv)/iu.test(lower)); const accountMatches = extractAccountTokens(lower); for (const account of accountMatches) { tokens.push(`account_${account}`); } return Array.from(new Set(tokens)); } function extractRequirements(routeSummary, normalized, userMessage) { const fragmentText = fragmentTextById(normalized); const byFragment = new Map(); const requirements = []; const pushRequirement = (input) => { const subjectTokens = extractSubjectTokens(input.requirement_text); requirements.push({ requirement_id: input.requirement_id, source_fragment_id: input.source_fragment_id, requirement_text: input.requirement_text, subject_tokens: subjectTokens, status: input.status, route: input.route }); if (input.source_fragment_id) { const current = byFragment.get(input.source_fragment_id) ?? []; current.push(input.requirement_id); byFragment.set(input.source_fragment_id, current); } }; if (!routeSummary) { pushRequirement({ requirement_id: "R1", source_fragment_id: null, requirement_text: userMessage, status: "clarification_needed", route: null }); return { requirements, byFragment }; } if (routeSummary.mode === "legacy_v1") { pushRequirement({ requirement_id: "R1", source_fragment_id: "F1", requirement_text: userMessage, status: "covered", route: routeSummary.route_hint }); return { requirements, byFragment }; } routeSummary.decisions.forEach((decision, index) => { const requirementId = `R${index + 1}`; const text = fragmentText.get(decision.fragment_id) ?? 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 toExecutionPlan(routeSummary, normalized, userMessage, requirementByFragment) { if (!routeSummary) { return []; } const fragmentText = fragmentTextById(normalized); if (routeSummary.mode === "legacy_v1") { return [ { fragment_id: "F1", requirement_ids: requirementByFragment.get("F1") ?? ["R1"], route: routeSummary.route_hint, should_execute: true, fragment_text: userMessage, no_route_reason: null, clarification_reason: null } ]; } return routeSummary.decisions.map((decision) => { const text = fragmentText.get(decision.fragment_id) ?? userMessage; if (decision.route === "no_route") { return { fragment_id: decision.fragment_id, requirement_ids: requirementByFragment.get(decision.fragment_id) ?? [], route: "no_route", should_execute: false, fragment_text: text, no_route_reason: decision.no_route_reason ?? null, clarification_reason: decision.clarification_reason ?? null }; } return { fragment_id: decision.fragment_id, requirement_ids: requirementByFragment.get(decision.fragment_id) ?? [], route: decision.route, should_execute: true, fragment_text: text, no_route_reason: null, clarification_reason: decision.clarification_reason ?? null }; }); } function enrichRbpFragmentForLive(fragmentText, temporalGuard) { const base = compactWhitespace(String(fragmentText ?? "")); const hints = ["Списание РБП", "объект РБП", "остаток на конец периода", "счет 97"]; const effective = temporalGuard && typeof temporalGuard === "object" ? temporalGuard.effective_primary_period : null; if (effective && effective.from && effective.to) { hints.push(`период ${effective.from}..${effective.to}`); } const hintText = hints.filter(Boolean).join(", "); if (!base) { return hintText; } if (/списани[ея]\s+рбп|счет\s*97|account\s*97|остат/i.test(base)) { return base; } return `${base}; ${hintText}`; } function enforceRbpLiveRoutePlan(input) { if (input.claimType !== "prove_rbp_tail_state") { return { executionPlan: input.executionPlan, audit: null }; } const requiredLiveCalls = [ "find_rbp_writeoff_documents_in_period", "find_rbp_object_movements_account_97", "find_month_close_entries_linked_to_rbp", "compute_end_period_residual_by_rbp_object" ]; let routeAdjusted = 0; let rescuedNoRoute = 0; const replacedRoutes = []; const adjustedPlan = input.executionPlan.map((item) => { if (!item || typeof item !== "object") { return item; } if (item.should_execute !== true && item.no_route_reason === "insufficient_specificity") { rescuedNoRoute += 1; routeAdjusted += 1; return { ...item, route: "live_mcp_drilldown", should_execute: true, no_route_reason: null, clarification_reason: null, fragment_text: enrichRbpFragmentForLive(item.fragment_text, input.temporalGuard) }; } if (item.should_execute === true && item.route !== "hybrid_store_plus_live" && item.route !== "live_mcp_drilldown") { routeAdjusted += 1; if (item.route && item.route !== "no_route") { replacedRoutes.push(String(item.route)); } return { ...item, route: "hybrid_store_plus_live", fragment_text: enrichRbpFragmentForLive(item.fragment_text, input.temporalGuard) }; } if (item.should_execute === true) { return { ...item, fragment_text: enrichRbpFragmentForLive(item.fragment_text, input.temporalGuard) }; } return item; }); return { executionPlan: adjustedPlan, audit: { claim_type: "prove_rbp_tail_state", required_live_calls: requiredLiveCalls, route_adjustments_applied: routeAdjusted, rescued_no_route_fragments: rescuedNoRoute, replaced_routes: Array.from(new Set(replacedRoutes)), route_gap_reason: routeAdjusted > 0 ? "rbp_claim_bound_live_route_override_applied" : null } }; } function collectRbpLiveRouteAudit(input) { if (input.claimType !== "prove_rbp_tail_state") { return null; } const required = new Set(Array.isArray(input.planAudit?.required_live_calls) ? input.planAudit.required_live_calls : []); const executed = []; const missing = new Set(); const routeGaps = []; let matchedRowsTotal = 0; let returnedRowsTotal = 0; let fetchedRowsTotal = 0; for (const result of input.retrievalResults) { if (!result || typeof result !== "object") { continue; } const summary = result.summary && typeof result.summary === "object" ? result.summary : null; const live = summary && typeof summary.live_mcp === "object" && summary.live_mcp ? summary.live_mcp : null; if (!live) { continue; } const requiredCalls = Array.isArray(live.required_live_calls) ? live.required_live_calls : []; for (const callId of requiredCalls) { required.add(String(callId ?? "").trim()); } const executedCalls = Array.isArray(live.executed_live_calls) ? live.executed_live_calls : []; for (const call of executedCalls) { if (!call || typeof call !== "object") { continue; } executed.push(call); } const missingCalls = Array.isArray(live.missing_live_calls) ? live.missing_live_calls : []; for (const callId of missingCalls) { const token = String(callId ?? "").trim(); if (token) { missing.add(token); } } const routeGapReason = String(live.route_gap_reason ?? "").trim(); if (routeGapReason) { routeGaps.push(routeGapReason); } fetchedRowsTotal += Number(live.fetched_rows ?? 0) || 0; matchedRowsTotal += Number(live.matched_rows ?? 0) || 0; returnedRowsTotal += Number(live.returned_rows ?? 0) || 0; } const requiredList = Array.from(required).filter(Boolean); const executedList = executed; const missingFromExecuted = requiredList.filter((callId) => !executedList.some((item) => String(item.call_id ?? "") === callId)); for (const callId of missingFromExecuted) { missing.add(callId); } const missingList = Array.from(missing); const routeGapReason = missingList.length > 0 ? "required_live_calls_not_executed" : matchedRowsTotal <= 0 ? "claim_live_calls_executed_but_zero_matches" : routeGaps[0] ?? null; const executionRate = requiredList.length > 0 ? Number(((requiredList.length - missingList.length) / requiredList.length).toFixed(4)) : 1; return { claim_type: "prove_rbp_tail_state", required_live_calls: requiredList, executed_live_calls: executedList, missing_live_calls: missingList, route_gap_reason: routeGapReason, live_route_execution_rate: executionRate, fetched_rows_total: fetchedRowsTotal, matched_rows_total: matchedRowsTotal, returned_rows_total: returnedRowsTotal, plan_override: input.planAudit ?? null }; } function enrichFaFragmentForLive(fragmentText, temporalGuard) { const base = compactWhitespace(String(fragmentText ?? "")); const hints = [ "Начисление амортизации", "объект ОС", "expected set ОС", "счет 01/02" ]; const effective = temporalGuard && typeof temporalGuard === "object" ? temporalGuard.effective_primary_period : null; if (effective && effective.from && effective.to) { hints.push(`период ${effective.from}..${effective.to}`); } const hintText = hints.filter(Boolean).join(", "); if (!base) { return hintText; } if (/амортиз|основн(?:ые|ых)\s+сред|fixed\s*asset|depreciat|счет\s*0[12]|account\s*0[12]/i.test(base)) { return base; } return `${base}; ${hintText}`; } function enforceFaLiveRoutePlan(input) { if (input.claimType !== "prove_fixed_asset_amortization_coverage") { return { executionPlan: input.executionPlan, audit: null }; } const requiredLiveCalls = [ "find_amortization_documents_in_period", "find_fixed_asset_movements_accounts_01_02", "find_fixed_asset_cards_expected_for_period", "match_expected_vs_actual_fa_coverage" ]; let routeAdjusted = 0; let rescuedNoRoute = 0; const replacedRoutes = []; const adjustedPlan = input.executionPlan.map((item) => { if (!item || typeof item !== "object") { return item; } if (item.should_execute !== true && item.no_route_reason === "insufficient_specificity") { rescuedNoRoute += 1; routeAdjusted += 1; return { ...item, route: "live_mcp_drilldown", should_execute: true, no_route_reason: null, clarification_reason: null, fragment_text: enrichFaFragmentForLive(item.fragment_text, input.temporalGuard) }; } if (item.should_execute === true && item.route !== "hybrid_store_plus_live" && item.route !== "live_mcp_drilldown") { routeAdjusted += 1; if (item.route && item.route !== "no_route") { replacedRoutes.push(String(item.route)); } return { ...item, route: "hybrid_store_plus_live", fragment_text: enrichFaFragmentForLive(item.fragment_text, input.temporalGuard) }; } if (item.should_execute === true) { return { ...item, fragment_text: enrichFaFragmentForLive(item.fragment_text, input.temporalGuard) }; } return item; }); return { executionPlan: adjustedPlan, audit: { claim_type: "prove_fixed_asset_amortization_coverage", required_live_calls: requiredLiveCalls, route_adjustments_applied: routeAdjusted, rescued_no_route_fragments: rescuedNoRoute, replaced_routes: Array.from(new Set(replacedRoutes)), route_gap_reason: routeAdjusted > 0 ? "fa_claim_bound_live_route_override_applied" : null } }; } function collectFaLiveRouteAudit(input) { if (input.claimType !== "prove_fixed_asset_amortization_coverage") { return null; } const required = new Set(Array.isArray(input.planAudit?.required_live_calls) ? input.planAudit.required_live_calls : []); const executed = []; const missing = new Set(); const routeGaps = []; let matchedRowsTotal = 0; let returnedRowsTotal = 0; let fetchedRowsTotal = 0; for (const result of input.retrievalResults) { if (!result || typeof result !== "object") { continue; } const summary = result.summary && typeof result.summary === "object" ? result.summary : null; const live = summary && typeof summary.live_mcp === "object" && summary.live_mcp ? summary.live_mcp : null; if (!live) { continue; } const requiredCalls = Array.isArray(live.required_live_calls) ? live.required_live_calls : []; for (const callId of requiredCalls) { required.add(String(callId ?? "").trim()); } const executedCalls = Array.isArray(live.executed_live_calls) ? live.executed_live_calls : []; for (const call of executedCalls) { if (!call || typeof call !== "object") { continue; } executed.push(call); } const missingCalls = Array.isArray(live.missing_live_calls) ? live.missing_live_calls : []; for (const callId of missingCalls) { const token = String(callId ?? "").trim(); if (token) { missing.add(token); } } const routeGapReason = String(live.route_gap_reason ?? "").trim(); if (routeGapReason) { routeGaps.push(routeGapReason); } fetchedRowsTotal += Number(live.fetched_rows ?? 0) || 0; matchedRowsTotal += Number(live.matched_rows ?? 0) || 0; returnedRowsTotal += Number(live.returned_rows ?? 0) || 0; } const requiredList = Array.from(required).filter(Boolean); const executedList = executed; const missingFromExecuted = requiredList.filter((callId) => !executedList.some((item) => String(item.call_id ?? "") === callId)); for (const callId of missingFromExecuted) { missing.add(callId); } const missingList = Array.from(missing); const routeGapReason = missingList.length > 0 ? "required_live_calls_not_executed" : matchedRowsTotal <= 0 ? "claim_live_calls_executed_but_zero_matches" : routeGaps[0] ?? null; const executionRate = requiredList.length > 0 ? Number(((requiredList.length - missingList.length) / requiredList.length).toFixed(4)) : 1; return { claim_type: "prove_fixed_asset_amortization_coverage", required_live_calls: requiredList, executed_live_calls: executedList, missing_live_calls: missingList, route_gap_reason: routeGapReason, live_route_execution_rate: executionRate, fetched_rows_total: fetchedRowsTotal, matched_rows_total: matchedRowsTotal, returned_rows_total: returnedRowsTotal, plan_override: input.planAudit ?? null }; } function toDebugRoutes(routeSummary) { if (!routeSummary) { return []; } if (routeSummary.mode === "legacy_v1") { return [ { fragment_id: "F1", route: routeSummary.route_hint, reason: retrievalSummaryForRoute(routeSummary.route_hint), confidence: routeSummary.confidence, intent_class: routeSummary.intent_class } ]; } return routeSummary.decisions.map((decision) => ({ fragment_id: decision.fragment_id, route: decision.route, reason: decision.reason, route_status: decision.route_status ?? null, no_route_reason: decision.no_route_reason ?? null, clarification_reason: decision.clarification_reason ?? null, execution_readiness: decision.execution_readiness ?? null })); } function buildSkippedResult(item) { return (0, retrievalResultNormalizer_1.normalizeRetrievalResult)(item.fragment_id, item.requirement_ids, "no_route", { status: "empty", result_type: "summary", items: [], summary: { skipped: true, reason: mapNoRouteReason(item.no_route_reason), no_route_reason: item.no_route_reason, clarification_reason: item.clarification_reason }, evidence: [], why_included: [], selection_reason: [mapNoRouteReason(item.no_route_reason)], risk_factors: [], business_interpretation: ["Данный фрагмент не был выполнен из-за no-route решения."], confidence: "low", limitations: ["Фрагмент требует уточнения или отсутствует поддерживаемый маршрут."], errors: [] }); } function summarizeUnique(values, limit = 6) { return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).slice(0, limit); } const SUBJECT_TOKEN_RULES = { nds: { critical: true, patterns: [ "vat", "accumulationregister", "\u043d\u0434\u0441", "\u043a\u043d\u0438\u0433\u0438\u043f\u043e\u043a\u0443\u043f\u043e\u043a", "\u043a\u043d\u0438\u0433\u0438\u043f\u0440\u043e\u0434\u0430\u0436", "\u043d\u0430\u043b\u043e\u0433\u043d\u0430\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u0443\u044e\u0441\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u044c" ] }, os: { critical: true, patterns: ["fixed_asset", "fixedasset", "\u043e\u0441\u043d\u043e\u0432\u043d", "\u0430\u043c\u043e\u0440\u0442\u0438\u0437"] }, saldo: { critical: true, patterns: ["balance", "saldo", "\u0441\u0430\u043b\u044c\u0434\u043e", "\u043e\u0441\u0442\u0430\u0442"] }, counterparty: { critical: false, patterns: [ "counterparty", "supplier", "buyer", "counterparty_id", "journal_counterparty", "document_has_counterparty", "\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442", "\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a", "\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b" ], 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", "\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442" ], 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", "\u0430\u043d\u043e\u043c\u0430\u043b", "\u0440\u0438\u0441\u043a" ], routes: ["store_feature_risk", "batch_refresh_then_store"] }, chain: { critical: false, patterns: ["chain", "cross_entity_chain", "relation_types", "operations_count", "matched_counterparties", "\u0446\u0435\u043f\u043e\u0447"], 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 evaluateCoverage(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 } }; } export function evaluateCoverageForTests(requirements, retrievalResults) { return evaluateCoverage(requirements, retrievalResults); } export function extractSubjectTokensForTests(text) { return extractSubjectTokens(text); } function checkGrounding(userMessage, requirements, coverage, retrievalResults) { const whyIncludedSummary = summarizeUnique(retrievalResults.flatMap((item) => item.why_included)); const selectionReasonSummary = summarizeUnique(retrievalResults.flatMap((item) => item.selection_reason)); const hasMaterialResults = retrievalResults.some((item) => item.status === "ok" || item.status === "partial"); const subjectTokens = extractSubjectTokens(userMessage); const executedRoutes = new Set(retrievalResults .filter((item) => item.status !== "error") .map((item) => item.route) .filter(Boolean)); const retrievalCorpus = JSON.stringify(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 && 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 (coverage.requirements_covered === 0) { status = "no_grounded_answer"; reasons.push("Ни одно требование не получило подтвержденного покрытия."); } else if (coverage.requirements_uncovered.length > 0 || coverage.requirements_partially_covered.length > 0 || coverage.clarification_needed_for.length > 0 || coverage.out_of_scope_requirements.length > 0) { status = "partial"; reasons.push("Вопрос покрыт частично: есть непокрытые или требующие уточнения требования."); } if (whyIncludedSummary.length === 0) { reasons.push("Нет explainable-сигналов why_included в результатах выборки."); } if (missingSubjectTokens.length > 0 && missingCriticalTokens.length === 0) { reasons.push(`Часть контекстных токенов не подтверждена напрямую: ${missingSubjectTokens.join(", ")}`); } const missingRequirements = [ ...coverage.requirements_uncovered, ...coverage.requirements_partially_covered, ...coverage.clarification_needed_for, ...coverage.out_of_scope_requirements ]; return { status, route_subject_match: routeSubjectMatch, missing_requirements: missingRequirements, reasons, why_included_summary: whyIncludedSummary, selection_reason_summary: selectionReasonSummary }; } function firstNonEmptyLine(text) { const line = text .split("\n") .map((item) => item.trim()) .find((item) => item.length > 0); return (line ?? text).slice(0, 220); } function buildClaimEvidenceLinks(retrievalResults) { const byClaim = new Map(); for (const result of retrievalResults) { for (const evidence of result.evidence) { const claimRef = String(evidence.claim_ref ?? "").trim(); if (!claimRef) { continue; } const evidenceId = String(evidence.evidence_id ?? "").trim(); if (!evidenceId) { continue; } const current = byClaim.get(claimRef) ?? []; current.push(evidenceId); byClaim.set(claimRef, current); } } return Array.from(byClaim.entries()) .slice(0, 10) .map(([claimRef, evidenceIds]) => ({ claim_ref: claimRef, evidence_ids: summarizeUnique(evidenceIds, 10) })); } function buildAnswerStructureV11(input) { const evidenceIds = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence.map((evidence) => evidence.evidence_id)), 10); const mechanismNotes = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence .map((evidence) => evidence.mechanism_note) .filter((note) => typeof note === "string" && note.trim().length > 0)), 6); const sourceRefs = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence .map((evidence) => evidence.source_ref?.canonical_ref) .filter((value) => typeof value === "string" && value.trim().length > 0)), 8); const limitationReasonCodes = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence .flatMap((evidence) => { const code = evidence.limitation?.reason_code; return typeof code === "string" && code.trim().length > 0 ? [code] : []; })), 8); const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults); const limitations = summarizeUnique([...input.retrievalResults.flatMap((item) => item.limitations), ...input.groundingCheck.reasons], 8); const clarificationQuestions = input.coverageReport.clarification_needed_for.map((item) => `Уточните требование ${item}.`); const recommendedActions = summarizeUnique([ ...input.coverageReport.requirements_uncovered.map((item) => `Проверить непокрытое требование ${item}.`), ...input.coverageReport.requirements_partially_covered.map((item) => `Доуточнить частично покрытое требование ${item}.`) ], 6); const mechanismStatus = mechanismNotes.length === 0 ? "unresolved" : limitationReasonCodes.includes("missing_mechanism") || limitationReasonCodes.includes("heuristic_inference") ? "limited" : "grounded"; return { schema_version: stage1Contracts_1.ANSWER_STRUCTURE_SCHEMA_VERSION, answer_summary: firstNonEmptyLine(input.assistantReply), direct_answer: input.assistantReply, mechanism_block: { status: mechanismStatus, mechanism_notes: mechanismNotes, limitation_reason_codes: limitationReasonCodes }, evidence_block: { evidence_ids: evidenceIds, source_refs: sourceRefs, mechanism_notes: mechanismNotes, coverage_note: input.coverageReport.requirements_total === input.coverageReport.requirements_covered ? "coverage_full_or_near_full" : "coverage_partial_or_limited", ...(config_1.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 && claimEvidenceLinks.length > 0 ? { claim_evidence_links: claimEvidenceLinks } : {}) }, uncertainty_block: { open_uncertainties: input.groundingCheck.missing_requirements, limitations }, next_step_block: { recommended_actions: recommendedActions, clarification_questions: clarificationQuestions } }; } const FOLLOWUP_ROUTE_HINTS = new Set(["store_canonical", "store_feature_risk", "hybrid_store_plus_live", "live_mcp_drilldown", "batch_refresh_then_store"]); const FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP = { settlements_60_62: "hybrid_store_plus_live", vat_document_register_book: "hybrid_store_plus_live", month_close_costs_20_44: "hybrid_store_plus_live", fixed_asset_amortization: "hybrid_store_plus_live" }; const FOLLOWUP_BUSINESS_CONTEXT_MAX = 320; const FOLLOWUP_SUBJECT_MAX = 160; const FOLLOWUP_QUESTION_APPEND_MAX = 260; function compactWhitespace(value) { return value.replace(/\s+/g, " ").trim(); } function hasAccountingSignal(text) { const lower = text.toLowerCase(); if (/(?:^|[\s,;:])\d{2}(?:\.\d{2})?(?=$|[\s,.;:])/i.test(lower)) { return true; } return /(проводк|документ|реализац|поступлен|взаиморасчет|сальдо|остатк|счет|счёт|ндс|амортиз|рбп|ос|контрагент|поставщик|покупател|оплат|банк|выписк|склад|товар|материал|закрыти|период|postavshchik|kontragent|schet|schetu|period|counterparty|supplier|invoice|posting|ledger|account|anomaly|risk)/i.test(lower); } function hasFollowupMarker(text) { const compact = compactWhitespace(text.toLowerCase()); return /^(и|а еще|а ещё|еще|ещё|добав|уточн|продолж|также|а если|plus|also|dobav|utochn|prodolzh)/i.test(compact); } function hasReferentialPointer(text) { return /(по этому|по тому|это же|этой|этим|этому|из этого|в этом|тот же|same thing|that one|po etomu|po tomu)/i.test(text.toLowerCase()); } function hasSmallTalkSignal(text) { return /(привет|как дела|спасибо|благодарю|thanks|thank you|hello|hi)\b/i.test(text.toLowerCase()); } function countTokens(text) { return compactWhitespace(text) .split(" ") .filter(Boolean).length; } function hasPeriodLiteral(text) { return /\b(20\d{2}(?:[-/.](?:0[1-9]|1[0-2]))?)\b/.test(text); } function extractNormalizedPeriodLiteral(text) { const monthly = text.match(/\b(20\d{2})[-/.](0[1-9]|1[0-2])\b/); if (monthly) { return `${monthly[1]}-${monthly[2]}`; } const yearly = text.match(/\b(20\d{2})\b/); if (yearly) { return yearly[1]; } return null; } function extractFollowupAccountAnchorsLoose(text) { const lower = String(text ?? "").toLowerCase(); const spans = [...collectDateSpans(lower), ...collectAmountSpans(lower), ...collectPercentSpans(lower), ...collectContractSpans(lower)]; const anchors = []; const followupAccountPattern = /\b(?:01|02|08|19|20|21|23|25|26|28|29|44|51|60|62|68|76|97)(?:\.\d{2})?\b/g; let match = null; while ((match = followupAccountPattern.exec(lower)) !== null) { const value = String(match[0] ?? "").trim(); const start = match.index; const end = start + value.length; if (intersectsAnySpan(start, end, spans)) { continue; } anchors.push(value); } return Array.from(new Set(anchors)); } function accountPrefixToken(value) { const token = String(value ?? "").trim(); const match = token.match(/^(\d{2})/); return match ? match[1] : null; } function hasCrossScopeConflictWithState(userMessage, state) { const explicitPeriod = extractNormalizedPeriodLiteral(userMessage); const statePeriod = compactWhitespace(state.focus.period ?? ""); if (explicitPeriod && statePeriod && explicitPeriod !== statePeriod) { return true; } const inferredDomain = inferP0DomainFromMessage(userMessage); const stateDomain = compactWhitespace(state.followup_context?.active_domain ?? state.focus.domain ?? ""); if (inferredDomain && stateDomain && inferredDomain !== stateDomain) { return true; } const explicitAccounts = extractAccountTokens(userMessage); const fallbackAccounts = explicitAccounts.length > 0 ? explicitAccounts : extractFollowupAccountAnchorsLoose(userMessage); const knownAccounts = Array.isArray(state.focus.primary_accounts) ? state.focus.primary_accounts : []; if (fallbackAccounts.length > 0 && knownAccounts.length > 0) { const knownPrefixes = new Set(knownAccounts.map((item) => accountPrefixToken(item)).filter(Boolean)); const newPrefixes = new Set(fallbackAccounts.map((item) => accountPrefixToken(item)).filter(Boolean)); if (newPrefixes.size > 0 && knownPrefixes.size > 0) { let intersects = false; for (const prefix of newPrefixes) { if (knownPrefixes.has(prefix)) { intersects = true; break; } } if (!intersects) { return true; } } } return false; } function inferP0DomainFromMessage(text) { if (typeof investigationState_1.inferP0DomainFromMessage === "function") { return investigationState_1.inferP0DomainFromMessage(text); } return null; } function hasStrongFollowupAnchors(userMessage, state) { const explicitPeriod = extractNormalizedPeriodLiteral(userMessage); if (explicitPeriod && state.focus.period && explicitPeriod !== state.focus.period) { const periodLooksLikeFollowupRefinement = hasFollowupMarker(userMessage) || hasReferentialPointer(userMessage); if (!periodLooksLikeFollowupRefinement) { return true; } } const inferredDomain = inferP0DomainFromMessage(userMessage); const activeDomain = compactWhitespace(state.followup_context?.active_domain ?? state.focus.domain ?? ""); if (inferredDomain && activeDomain && inferredDomain !== activeDomain) { const domainLooksLikeFollowupRefinement = hasFollowupMarker(userMessage) && hasReferentialPointer(userMessage); if (!domainLooksLikeFollowupRefinement) { return true; } } const explicitAccounts = extractAccountTokens(userMessage); const followupAccounts = explicitAccounts.length > 0 ? explicitAccounts : extractFollowupAccountAnchorsLoose(userMessage); if (followupAccounts.length > 0) { const knownAccounts = new Set(state.focus.primary_accounts.map((item) => item.trim())); if (knownAccounts.size === 0) { return true; } if (followupAccounts.some((item) => !knownAccounts.has(item))) { return true; } } return false; } function routeFromInvestigationState(state) { const rawDomain = compactWhitespace(state.focus.domain ?? ""); if (!rawDomain) { const mappedFromFollowup = state.followup_context?.active_domain ? FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP[compactWhitespace(state.followup_context.active_domain)] ?? null : null; return mappedFromFollowup; } if (FOLLOWUP_ROUTE_HINTS.has(rawDomain)) { return rawDomain; } for (const candidate of rawDomain.split(",").map((item) => compactWhitespace(item))) { if (FOLLOWUP_ROUTE_HINTS.has(candidate)) { return candidate; } if (Object.prototype.hasOwnProperty.call(FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP, candidate)) { return FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP[candidate]; } } const mappedFromFollowup = state.followup_context?.active_domain ? FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP[compactWhitespace(state.followup_context.active_domain)] ?? null : null; if (mappedFromFollowup) { return mappedFromFollowup; } return null; } function withCappedLength(value, max) { return value.length <= max ? value : value.slice(0, max); } function mergeBusinessContext(existing, patchParts) { const existingText = compactWhitespace(existing ?? ""); const patchText = compactWhitespace(patchParts.filter(Boolean).join("; ")); if (!existingText && !patchText) { return undefined; } const merged = existingText && patchText ? `${existingText}; ${patchText}` : existingText || patchText; return withCappedLength(merged, FOLLOWUP_BUSINESS_CONTEXT_MAX); } function buildFollowupStateBinding(input) { const userMessage = String(input.userMessage ?? "").trim(); if (!userMessage || input.investigationState.status !== "active" || input.investigationState.turn_index <= 0) { return { normalizedQuestion: userMessage, mergedContext: input.payloadContext, usage: null }; } const strongSignal = hasAccountingSignal(userMessage); const followupMarker = hasFollowupMarker(userMessage); const referentialPointer = hasReferentialPointer(userMessage); const shortPrompt = countTokens(userMessage) <= 10; const smallTalkSignal = hasSmallTalkSignal(userMessage); const problemState = input.investigationState.problem_unit_state; const problemContinuityAvailable = config_1.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 && Boolean(problemState) && ((problemState?.active_problem_units.length ?? 0) > 0 || (problemState?.focus_problem_types.length ?? 0) > 0); const strongNewAnchorDetected = hasStrongFollowupAnchors(userMessage, input.investigationState); const scopeConflictDetected = hasCrossScopeConflictWithState(userMessage, input.investigationState); const periodRefinementFollowup = hasPeriodLiteral(userMessage) && problemContinuityAvailable; const shouldBind = !smallTalkSignal && !strongNewAnchorDetected && !scopeConflictDetected && (followupMarker || referentialPointer || periodRefinementFollowup || (!strongSignal && shortPrompt)); if (!shouldBind) { return { normalizedQuestion: userMessage, mergedContext: input.payloadContext, usage: null }; } const context = { ...(input.payloadContext ?? {}) }; const hasExplicitExpectedRoute = Boolean(input.payloadContext?.expected_route); const expectedRouteFromState = !context?.expected_route ? routeFromInvestigationState(input.investigationState) : null; const periodHintFromState = !context?.period_hint ? input.investigationState.focus.period : null; const followupContext = input.investigationState.followup_context; if (expectedRouteFromState) { context.expected_route = expectedRouteFromState; } if (periodHintFromState) { context.period_hint = periodHintFromState; } const subject = withCappedLength(compactWhitespace(input.investigationState.focus.active_query_subject ?? ""), FOLLOWUP_SUBJECT_MAX); const businessContextPatch = ["followup_state_binding_v1"]; let problemContinuityApplied = false; let problemContinuitySkippedReason = null; if (input.investigationState.focus.period) { businessContextPatch.push("active_period"); } if (subject) { businessContextPatch.push(`focus_subject:${subject}`); } if (input.investigationState.focus.primary_accounts.length > 0) { businessContextPatch.push(`focus_accounts:${input.investigationState.focus.primary_accounts.join(",")}`); } if (followupContext?.active_domain) { businessContextPatch.push(`focus_domain:${followupContext.active_domain}`); } if ((followupContext?.active_requirement_ids?.length ?? 0) > 0) { businessContextPatch.push(`active_requirements:${followupContext.active_requirement_ids.slice(0, 4).join(",")}`); } if ((followupContext?.uncovered_requirement_ids?.length ?? 0) > 0) { businessContextPatch.push(`uncovered_requirements:${followupContext.uncovered_requirement_ids.slice(0, 4).join(",")}`); } if (followupContext?.last_problem_unit_id) { businessContextPatch.push(`last_problem_unit:${followupContext.last_problem_unit_id}`); } if ((followupContext?.evidence_summary?.length ?? 0) > 0) { businessContextPatch.push(`evidence_state:${followupContext.evidence_summary.slice(0, 3).join("|")}`); } if ((followupContext?.settlement_next_actions?.length ?? 0) > 0) { businessContextPatch.push("settlement_focus_retained_v1"); } if (problemContinuityAvailable) { if (hasExplicitExpectedRoute) { problemContinuitySkippedReason = "explicit_expected_route"; } else { const focusTypes = (problemState?.focus_problem_types ?? []).slice(0, 3); const activeCount = problemState?.active_problem_units.length ?? 0; businessContextPatch.push("problem_unit_continuity_v1"); if (focusTypes.length > 0) { businessContextPatch.push(`problem_focus_types:${focusTypes.join(",")}`); } businessContextPatch.push(`problem_active_count:${activeCount}`); problemContinuityApplied = true; } } const mergedBusinessContext = mergeBusinessContext(context?.business_context, businessContextPatch); if (mergedBusinessContext) { context.business_context = mergedBusinessContext; } const shouldAugmentQuestion = Boolean(subject) && (followupMarker || referentialPointer || !strongSignal); let normalizedQuestion = userMessage; if (shouldAugmentQuestion) { const appendParts = [`Фокус текущего разбора: ${subject}`]; if (input.investigationState.focus.primary_accounts.length > 0 && !/\b\d{2}(?:\.\d{2})?\b/.test(userMessage)) { appendParts.push(`Счета фокуса: ${input.investigationState.focus.primary_accounts.join(", ")}`); } if (periodHintFromState && !hasPeriodLiteral(userMessage)) { appendParts.push(`Период фокуса: ${periodHintFromState}`); } const appendBlock = withCappedLength(compactWhitespace(appendParts.join("; ")), FOLLOWUP_QUESTION_APPEND_MAX); normalizedQuestion = `${userMessage}\n${appendBlock}`.trim(); } const reason = followupMarker ? "followup_marker" : referentialPointer ? "referential_pointer" : "underspecified_short_followup"; return { normalizedQuestion, mergedContext: context, usage: { applied: true, reason, state_turn_index: input.investigationState.turn_index, context_patch: { period_hint_from_state: Boolean(periodHintFromState), expected_route_from_state: Boolean(expectedRouteFromState), business_context_from_state: Boolean(mergedBusinessContext), question_augmented: shouldAugmentQuestion, problem_continuity_available: problemContinuityAvailable, problem_continuity_applied: problemContinuityApplied, problem_continuity_skipped_reason: problemContinuityApplied ? null : problemContinuitySkippedReason, strong_new_anchor_detected: strongNewAnchorDetected, scope_isolation_applied: true, scope_carryover_allowed: !scopeConflictDetected, scope_reset_reason: scopeConflictDetected ? "cross_scope_conflict" : null } } }; } function cloneItems(items) { return items.map((item) => ({ ...item, debug: item.debug ? { ...item.debug } : null })); } export class AssistantService { normalizerService; sessions; dataLayer; sessionLogger; constructor(normalizerService, sessions, dataLayer = new assistantDataLayer_1.AssistantDataLayer(), sessionLogger = new assistantSessionLogger_1.AssistantSessionLogger()) { this.normalizerService = normalizerService; this.sessions = sessions; this.dataLayer = dataLayer; this.sessionLogger = sessionLogger; } getSession(sessionId) { return this.sessions.getSession(sessionId); } async handleMessage(payload) { const session = this.sessions.ensureSession(payload.session_id); const sessionId = session.session_id; const userMessage = String(payload.user_message ?? payload.message ?? "").trim(); const userItem = { message_id: `msg-${(0, nanoid_1.nanoid)(10)}`, session_id: sessionId, role: "user", text: userMessage, reply_type: null, created_at: new Date().toISOString(), trace_id: null, debug: null }; this.sessions.appendItem(sessionId, userItem); const followupBinding = config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 && config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 && session.investigation_state ? buildFollowupStateBinding({ userMessage, payloadContext: payload.context, investigationState: session.investigation_state }) : { normalizedQuestion: userMessage, mergedContext: payload.context, usage: null }; const normalizePayload = { apiKey: payload.apiKey, model: payload.model, baseUrl: payload.baseUrl, temperature: payload.temperature, maxOutputTokens: payload.maxOutputTokens, promptVersion: payload.promptVersion ?? "normalizer_v2_0_2", systemPrompt: payload.systemPrompt, developerPrompt: payload.developerPrompt, domainPrompt: payload.domainPrompt, fewShotExamples: payload.fewShotExamples, userQuestion: followupBinding.normalizedQuestion, context: followupBinding.mergedContext, useMock: Boolean(payload.useMock) }; const normalized = await this.normalizerService.normalize(normalizePayload); const companyAnchors = (0, companyAnchorResolver_1.resolveCompanyAnchors)(userMessage); const initialBusinessScopeResolution = resolveBusinessScopeAlignment({ userMessage, companyAnchors, normalized: normalized.normalized, routeSummary: normalized.route_hint_summary }); const inferredDomainByMessage = inferP0DomainFromMessage(userMessage); const focusDomainForGuards = inferredDomainByMessage === "settlements_60_62" || inferredDomainByMessage === "vat_document_register_book" || inferredDomainByMessage === "month_close_costs_20_44" || inferredDomainByMessage === "fixed_asset_amortization" ? inferredDomainByMessage : null; const temporalGuard = (0, assistantRuntimeGuards_1.resolveTemporalGuard)({ userMessage, normalized: normalized.normalized, companyAnchors }); const domainPolarityGuardInitial = (0, assistantRuntimeGuards_1.resolveDomainPolarityGuard)({ userMessage, companyAnchors, focusDomainHint: focusDomainForGuards }); const claimAnchorAudit = (0, assistantClaimBoundEvidence_1.resolveClaimBoundAnchors)({ userMessage, companyAnchors, focusDomainHint: focusDomainForGuards, primaryPeriod: temporalGuard.effective_primary_period ?? temporalGuard.primary_period_window }); const businessScopeResolution = resolveBusinessScopeFromLiveContext({ current: initialBusinessScopeResolution, temporalGuard, claimType: claimAnchorAudit.claim_type, focusDomainHint: focusDomainForGuards }); const resolvedRouteSummary = businessScopeResolution.route_summary_resolved; const requirementExtraction = extractRequirements(resolvedRouteSummary, normalized.normalized, userMessage); let executionPlan = toExecutionPlan(resolvedRouteSummary, normalized.normalized, userMessage, requirementExtraction.byFragment); const rbpRoutePlanEnforcement = enforceRbpLiveRoutePlan({ executionPlan, claimType: claimAnchorAudit.claim_type, temporalGuard }); executionPlan = rbpRoutePlanEnforcement.executionPlan; const faRoutePlanEnforcement = enforceFaLiveRoutePlan({ executionPlan, claimType: claimAnchorAudit.claim_type, temporalGuard }); executionPlan = faRoutePlanEnforcement.executionPlan; executionPlan = (0, assistantRuntimeGuards_1.applyTemporalHintToExecutionPlan)(executionPlan, temporalGuard); executionPlan = (0, assistantRuntimeGuards_1.applyPolarityHintToExecutionPlan)(executionPlan, domainPolarityGuardInitial); const retrievalCalls = []; const retrievalResultsRaw = []; let retrievalResults = []; for (const planItem of executionPlan) { if (!planItem.should_execute) { retrievalCalls.push({ fragment_id: planItem.fragment_id, requirement_ids: planItem.requirement_ids, route: planItem.route, status: "skipped", query_text: planItem.fragment_text, reason: mapNoRouteReason(planItem.no_route_reason) }); retrievalResults.push(buildSkippedResult(planItem)); continue; } retrievalCalls.push({ fragment_id: planItem.fragment_id, requirement_ids: planItem.requirement_ids, route: planItem.route, status: "executed", query_text: planItem.fragment_text, reason: null }); try { const raw = await this.dataLayer.executeRouteRuntime(planItem.route, planItem.fragment_text); retrievalResultsRaw.push({ fragment_id: planItem.fragment_id, route: planItem.route, raw_result: raw }); retrievalResults.push((0, retrievalResultNormalizer_1.normalizeRetrievalResult)(planItem.fragment_id, planItem.requirement_ids, planItem.route, raw)); } catch (error) { const message = error instanceof Error ? error.message : String(error); retrievalCalls[retrievalCalls.length - 1].status = "failed"; retrievalCalls[retrievalCalls.length - 1].reason = message; const rawError = { status: "error", result_type: "summary", items: [], summary: { route: planItem.route }, evidence: [], why_included: [], selection_reason: [], risk_factors: [], business_interpretation: [], confidence: "low", limitations: ["Route executor failed."], errors: [message] }; retrievalResultsRaw.push({ fragment_id: planItem.fragment_id, route: planItem.route, raw_result: rawError }); retrievalResults.push((0, retrievalResultNormalizer_1.normalizeRetrievalResult)(planItem.fragment_id, planItem.requirement_ids, planItem.route, rawError)); } } const polarityGuardResult = (0, assistantRuntimeGuards_1.applyDomainPolarityGuardToRetrievalResults)({ retrievalResults, guard: domainPolarityGuardInitial }); retrievalResults = polarityGuardResult.retrievalResults; const targetedEvidenceResult = (0, assistantClaimBoundEvidence_1.applyTargetedEvidenceAcquisition)({ retrievalResults, claimAudit: claimAnchorAudit }); retrievalResults = targetedEvidenceResult.retrievalResults; const evidenceGateResult = (0, assistantRuntimeGuards_1.applyEvidenceAdmissibilityGate)({ retrievalResults, temporal: temporalGuard, focusDomainHint: focusDomainForGuards, polarity: polarityGuardResult.audit.polarity, companyAnchors, userMessage }); retrievalResults = evidenceGateResult.retrievalResults; const rbpLiveRouteAudit = collectRbpLiveRouteAudit({ claimType: claimAnchorAudit.claim_type, retrievalResults, planAudit: rbpRoutePlanEnforcement.audit }); const faLiveRouteAudit = collectFaLiveRouteAudit({ claimType: claimAnchorAudit.claim_type, retrievalResults, planAudit: faRoutePlanEnforcement.audit }); const coverageEvaluation = evaluateCoverage(requirementExtraction.requirements, retrievalResults); const groundingCheckBase = checkGrounding(userMessage, coverageEvaluation.requirements, coverageEvaluation.coverage, retrievalResults); const groundedAnswerEligibilityGuard = (0, assistantRuntimeGuards_1.evaluateGroundedAnswerEligibility)({ temporal: temporalGuard, polarity: polarityGuardResult.audit, evidence: evidenceGateResult.audit, claimAnchors: claimAnchorAudit, targetedEvidenceHitRate: targetedEvidenceResult.audit.targeted_evidence_hit_rate, businessScopeResolved: businessScopeResolution.business_scope_resolved }); const groundingCheck = (0, assistantRuntimeGuards_1.applyEligibilityToGroundingCheck)(groundingCheckBase, groundedAnswerEligibilityGuard); const focusDomainHint = followupBinding.usage?.applied ? session.investigation_state?.followup_context?.active_domain ?? session.investigation_state?.focus.domain ?? null : null; const questionTypeClass = (0, questionTypeResolver_1.resolveQuestionType)(userMessage); const hasPeriodInCompanyAnchors = (Array.isArray(companyAnchors?.dates) && companyAnchors.dates.some((item) => String(item ?? "").trim().length > 0)) || (Array.isArray(companyAnchors?.periods) && companyAnchors.periods.some((item) => String(item ?? "").trim().length > 0)); const normalizationPeriodExplicit = hasExplicitPeriodAnchorFromNormalized(normalized.normalized) || hasPeriodInCompanyAnchors; const composition = (0, answerComposer_1.composeAssistantAnswer)({ userMessage, routeSummary: resolvedRouteSummary, retrievalResults, requirements: coverageEvaluation.requirements, coverageReport: coverageEvaluation.coverage, groundingCheck, focusDomainHint, questionTypeHint: questionTypeClass, companyAnchors, normalizationPeriodExplicit, enableAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, enableProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1, enableLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 }); const safeAssistantReplyBase = (0, answerComposer_1.sanitizeAssistantReplyForUserFacing)(composition.assistant_reply); const safeAssistantReply = String(safeAssistantReplyBase ?? "") .replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "") .replace(/\b(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "") .trim(); const answerStructureV11 = config_1.FEATURE_ASSISTANT_CONTRACTS_V11 ? config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11 && composition.answer_structure_v11 ? composition.answer_structure_v11 : buildAnswerStructureV11({ assistantReply: safeAssistantReply, coverageReport: coverageEvaluation.coverage, groundingCheck, retrievalResults }) : null; const investigationStateSnapshot = config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 && session.investigation_state ? (0, investigationState_1.updateInvestigationState)({ previous: session.investigation_state, timestamp: new Date().toISOString(), questionId: userItem.message_id, userMessage, routeSummary: resolvedRouteSummary, requirements: coverageEvaluation.requirements, coverageReport: coverageEvaluation.coverage, retrievalResults, replyType: composition.reply_type, followupApplied: Boolean(followupBinding.usage?.applied) }) : null; if (config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 && investigationStateSnapshot) { this.sessions.setInvestigationState(sessionId, investigationStateSnapshot); } const debug = { trace_id: normalized.trace_id, prompt_version: normalized.prompt_version, schema_version: normalized.schema_version, fallback_type: composition.fallback_type, route_summary: resolvedRouteSummary, fragments: extractFragments(normalized.normalized), requirements_extracted: coverageEvaluation.requirements, coverage_report: coverageEvaluation.coverage, routes: toDebugRoutes(resolvedRouteSummary), retrieval_status: retrievalResults.map((item) => ({ fragment_id: item.fragment_id, requirement_ids: item.requirement_ids, route: item.route, status: item.status, result_type: item.result_type })), retrieval_results: retrievalResults, answer_grounding_check: groundingCheck, dropped_intent_segments: extractDiscardedIntentSegments(normalized.normalized), question_type_class: questionTypeClass, company_anchors: companyAnchors, business_scope_raw: businessScopeResolution.business_scope_raw, business_scope_resolved: businessScopeResolution.business_scope_resolved, company_grounding_applied: businessScopeResolution.company_grounding_applied, scope_resolution_reason: businessScopeResolution.scope_resolution_reason, company_scope_resolution_reason: businessScopeResolution.scope_resolution_reason, raw_time_anchor: temporalGuard.raw_time_anchor, raw_time_scope: temporalGuard.raw_time_scope, resolved_time_anchor: temporalGuard.resolved_time_anchor, resolved_primary_period: temporalGuard.resolved_primary_period, effective_primary_period: temporalGuard.effective_primary_period, temporal_guard_input: temporalGuard.temporal_guard_input, temporal_alignment_status: temporalGuard.temporal_alignment_status, temporal_resolution_source: temporalGuard.temporal_resolution_source, temporal_guard_basis: temporalGuard.temporal_guard_basis, temporal_guard_applied: temporalGuard.temporal_guard_applied, temporal_guard_outcome: temporalGuard.temporal_guard_outcome, temporal_guard: temporalGuard, raw_numeric_tokens: polarityGuardResult.audit.raw_numeric_tokens, classified_numeric_tokens: polarityGuardResult.audit.classified_numeric_tokens, rejected_as_non_accounts: polarityGuardResult.audit.rejected_as_non_accounts, resolved_account_anchors: polarityGuardResult.audit.resolved_account_anchors, domain_polarity_guard: polarityGuardResult.audit, claim_anchor_audit: claimAnchorAudit, targeted_evidence_acquisition: targetedEvidenceResult.audit, evidence_admissibility_gate: evidenceGateResult.audit, ...(rbpLiveRouteAudit ? { rbp_live_route_audit: rbpLiveRouteAudit } : {}), ...(faLiveRouteAudit ? { fa_live_route_audit: faLiveRouteAudit } : {}), eligibility_time_basis: groundedAnswerEligibilityGuard.eligibility_time_basis, grounded_answer_eligibility_guard: groundedAnswerEligibilityGuard, ...(followupBinding.usage ? { followup_state_usage: followupBinding.usage } : {}), problem_centric_answer_applied: composition.problem_centric_answer_applied ?? false, problem_units_used_count: composition.problem_units_used_count ?? 0, problem_answer_mode: composition.problem_answer_mode ?? "stage1_policy_v11", ...(Array.isArray(composition.problem_unit_ids_used) && composition.problem_unit_ids_used.length > 0 ? { problem_unit_ids_used: composition.problem_unit_ids_used } : {}), answer_structure_v11: answerStructureV11, investigation_state_snapshot: investigationStateSnapshot, normalized: normalized.normalized }; const assistantItem = { message_id: `msg-${(0, nanoid_1.nanoid)(10)}`, session_id: sessionId, role: "assistant", text: safeAssistantReply, reply_type: composition.reply_type, created_at: new Date().toISOString(), trace_id: normalized.trace_id, debug }; this.sessions.appendItem(sessionId, assistantItem); const current = this.sessions.getSession(sessionId); if (current) { this.sessionLogger.persistSession(current); } const conversation = cloneItems(current?.items ?? []); (0, log_1.logJson)({ timestamp: new Date().toISOString(), level: "info", service: "assistant_loop", message: "assistant_message_processed", sessionId, eventType: "assistant_message", details: { session_id: sessionId, message_id: assistantItem.message_id, user_message: userMessage, normalizer_output: normalized.normalized, execution_plan: executionPlan, resolved_execution_state: extractExecutionState(normalized.normalized), routes: toDebugRoutes(resolvedRouteSummary), retrieval_calls: retrievalCalls, retrieval_results_raw: retrievalResultsRaw, retrieval_results_normalized: retrievalResults, requirements_extracted: coverageEvaluation.requirements, requirements_total: coverageEvaluation.coverage.requirements_total, requirements_covered: coverageEvaluation.coverage.requirements_covered, requirements_uncovered: coverageEvaluation.coverage.requirements_uncovered, coverage_status: coverageEvaluation.coverage.requirements_total === coverageEvaluation.coverage.requirements_covered && coverageEvaluation.coverage.requirements_uncovered.length === 0 && coverageEvaluation.coverage.requirements_partially_covered.length === 0 ? "full" : "partial_or_limited", answer_grounding_status: groundingCheck.status, reply_semantic_type: composition.reply_type, why_included_summary: groundingCheck.why_included_summary, selection_reason_summary: groundingCheck.selection_reason_summary, route_subject_match: groundingCheck.route_subject_match, clarification_target: coverageEvaluation.coverage.clarification_needed_for, dropped_intent_segments: extractDiscardedIntentSegments(normalized.normalized), question_type_class: questionTypeClass, company_anchors: companyAnchors, business_scope_raw: businessScopeResolution.business_scope_raw, business_scope_resolved: businessScopeResolution.business_scope_resolved, company_grounding_applied: businessScopeResolution.company_grounding_applied, scope_resolution_reason: businessScopeResolution.scope_resolution_reason, company_scope_resolution_reason: businessScopeResolution.scope_resolution_reason, raw_time_anchor: temporalGuard.raw_time_anchor, raw_time_scope: temporalGuard.raw_time_scope, resolved_time_anchor: temporalGuard.resolved_time_anchor, resolved_primary_period: temporalGuard.resolved_primary_period, effective_primary_period: temporalGuard.effective_primary_period, temporal_guard_input: temporalGuard.temporal_guard_input, temporal_alignment_status: temporalGuard.temporal_alignment_status, temporal_resolution_source: temporalGuard.temporal_resolution_source, temporal_guard_basis: temporalGuard.temporal_guard_basis, temporal_guard_applied: temporalGuard.temporal_guard_applied, temporal_guard_outcome: temporalGuard.temporal_guard_outcome, temporal_guard: temporalGuard, raw_numeric_tokens: polarityGuardResult.audit.raw_numeric_tokens, classified_numeric_tokens: polarityGuardResult.audit.classified_numeric_tokens, rejected_as_non_accounts: polarityGuardResult.audit.rejected_as_non_accounts, resolved_account_anchors: polarityGuardResult.audit.resolved_account_anchors, domain_polarity_guard: polarityGuardResult.audit, claim_anchor_audit: claimAnchorAudit, targeted_evidence_acquisition: targetedEvidenceResult.audit, evidence_admissibility_gate: evidenceGateResult.audit, ...(rbpLiveRouteAudit ? { rbp_live_route_audit: rbpLiveRouteAudit } : {}), ...(faLiveRouteAudit ? { fa_live_route_audit: faLiveRouteAudit } : {}), eligibility_time_basis: groundedAnswerEligibilityGuard.eligibility_time_basis, grounded_answer_eligibility_guard: groundedAnswerEligibilityGuard, ...(followupBinding.usage ? { followup_state_usage: followupBinding.usage } : {}), problem_centric_answer_applied: composition.problem_centric_answer_applied ?? false, problem_units_used_count: composition.problem_units_used_count ?? 0, problem_answer_mode: composition.problem_answer_mode ?? "stage1_policy_v11", ...(Array.isArray(composition.problem_unit_ids_used) && composition.problem_unit_ids_used.length > 0 ? { problem_unit_ids_used: composition.problem_unit_ids_used } : {}), answer_structure_v11: answerStructureV11, investigation_state_snapshot: investigationStateSnapshot, fallback_type: composition.fallback_type, assistant_reply: safeAssistantReply, reply_type: composition.reply_type, trace_id: normalized.trace_id } }); return { ok: true, session_id: sessionId, assistant_reply: safeAssistantReply, reply_type: composition.reply_type, conversation_item: assistantItem, debug, conversation }; } }