"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ROUTE_DISCIPLINE_RULE_TABLE = void 0; exports.simulateDeterministicRouting = simulateDeterministicRouting; exports.toRouteHintSummary = toRouteHintSummary; exports.toRouterInput = toRouterInput; function toRouteHintSummaryV1(normalized) { return { mode: "legacy_v1", intent_class: normalized.intent_class, route_hint: normalized.route_hint, confidence: normalized.confidence.route_hint, decision_flags: { needs_cross_entity_join: normalized.requires.needs_cross_entity_join, needs_causal_chain: normalized.requires.needs_causal_chain, needs_exact_object_trace: normalized.requires.needs_exact_object_trace, needs_ranking: normalized.requires.needs_ranking, needs_anomaly_summary: normalized.requires.needs_anomaly_summary, needs_runtime_truth: normalized.requires.needs_runtime_truth, needs_period_cut: normalized.requires.needs_period_cut, needs_evidence: normalized.requires.needs_evidence }, period_scope: normalized.period_scope, entities: { domain_entities: normalized.domain_entities, accounts_mentioned: normalized.accounts_mentioned, documents_mentioned: normalized.documents_mentioned, registers_mentioned: normalized.registers_mentioned } }; } const ACCOUNT_HINT_PATTERN = /(?:\b(?:account|acct|schet|счет|сч)\s*[:#]?\s*(?:[1-9][0-9](?:[./-][0-9]{1,2})?)|\b(?:19|20|21|23|25|26|28|29|44|51|60|62|68)\b)/i; const PERIOD_PATTERN = /\b20\d{2}(?:[-./](?:0[1-9]|1[0-2]))?\b/i; const SYMPTOM_MARKER_PATTERN = /(?:\bsymptom\b|\banomaly\b|\bproblem\b|\bissue\b|\btail\b|\bhanging\b|\bblocked\b|\bincomplete\b|remains?\s+open|not\s+(?:confirmed|observed|resolved|closed)|не\s+(?:подтвержден|закрыт|наблюдается)|хвост|сбой|проблем)/i; const LIFECYCLE_MARKER_PATTERN = /(?:\blifecycle\b|\bchain\b|\btransition\b|\bstep\b|\btrace\b|цепоч|этап|переход|связк|где\s+разрыв)/i; const CHAIN_BREAK_PATTERN = /(?:\bbreak\b|\bbroken\b|\bgap\b|missing\s+(?:transition|step|link)|chain\s+break|разрыв|обрыв|нет\s+переход|не\s+дошл|не\s+наблюд)/i; const PERIOD_IMPACT_PATTERN = /(?:period\s*close|month\s*close|month-end|residual|allocation|20[/-]44|закрыти|остатк|распредел|конец\s+месяц)/i; const CAUSAL_PATTERN = /(?:\bwhy\b|\bbecause\b|\breason\b|explain\s+mechanism|почему|объясни|механизм|причин)/i; const AMBIGUITY_PATTERN = /(?:\bmaybe\b|\bperhaps\b|not\s+sure|i\s+only\s+know|part\s+may\s+be\s+missing|возможно|может\s+быть|не\s+уверен|не\s+знаю|часть\s+цепочки\s+не\s+подтвержд)/i; const TRANSLIT_PROBLEM_PATTERN = /(?:raschet|oplata|zakryt|nds|vychet|zatrat|ostatok|cepoch|perehod|pochemu|prichin|period)/i; const DOMAIN_LEXICAL_ANCHOR_PATTERN = /(?:\b(?:settlement|payment|bank|supplier|customer|vat|nds|invoice|register|book|period\s*close|month\s*close|close\s*operation|allocation|residual|cost|expenses?)\b|оплат|расчет|РЅРґСЃ|СЃС‡[её]С‚.?фактур|РєРЅРёРі[аи]|затрат|закрыт|остатк)/i; exports.ROUTE_DISCIPLINE_RULE_TABLE = [ { query_class: "exact_object_trace", required_route: "live_mcp_drilldown", allowed_fallback: ["no_route"], forbidden_fallback: ["store_canonical", "hybrid_store_plus_live", "store_feature_risk", "batch_refresh_then_store"], description: "Exact object trace queries always run via live drilldown." }, { query_class: "ranking_or_period_summary", required_route: "batch_refresh_then_store", allowed_fallback: ["no_route"], forbidden_fallback: ["store_canonical", "hybrid_store_plus_live"], description: "Ranking and period summary queries require analytical batch path." }, { query_class: "symptom_first", required_route: "hybrid_store_plus_live", allowed_fallback: ["no_route"], forbidden_fallback: ["store_canonical"], description: "Symptom-first intents are deterministically promoted to hybrid path." }, { query_class: "lifecycle_first", required_route: "hybrid_store_plus_live", allowed_fallback: ["no_route"], forbidden_fallback: ["store_canonical"], description: "Lifecycle-first intents are deterministically promoted to hybrid path." }, { query_class: "chain_break", required_route: "hybrid_store_plus_live", allowed_fallback: ["no_route"], forbidden_fallback: ["store_canonical"], description: "Chain-break intents are deterministically promoted to hybrid path." }, { query_class: "period_impact", required_route: "hybrid_store_plus_live", allowed_fallback: ["no_route"], forbidden_fallback: ["store_canonical"], description: "Period-impact problem intents are deterministically promoted to hybrid path." }, { query_class: "causal_query", required_route: "hybrid_store_plus_live", allowed_fallback: ["no_route"], forbidden_fallback: ["store_canonical"], description: "Causal/mechanism intents are deterministically promoted to hybrid path." }, { query_class: "mixed_ambiguity", required_route: "hybrid_store_plus_live", allowed_fallback: ["no_route"], forbidden_fallback: ["store_canonical"], description: "Mixed ambiguity keeps hybrid as primary route, with explicit no-route fallback." }, { query_class: "rule_check_without_symptom", required_route: "store_feature_risk", allowed_fallback: ["no_route"], forbidden_fallback: ["store_canonical"], description: "Rule checks without symptom/lifecycle signals run via risk profile path." }, { query_class: "canonical_fact_lookup", required_route: "store_canonical", allowed_fallback: ["no_route"], forbidden_fallback: ["hybrid_store_plus_live"], description: "Only plain factual lookups are allowed to stay on canonical path." } ]; const ROUTE_DISCIPLINE_RULE_MAP = new Map(exports.ROUTE_DISCIPLINE_RULE_TABLE.map((item) => [item.query_class, item])); function mergedFragmentText(fragment) { return `${fragment.raw_fragment_text ?? ""} ${fragment.normalized_fragment_text ?? ""}`.toLowerCase(); } function hasLifecycleDomainHint(fragment, lowerText) { const accountHints = Array.isArray(fragment.account_hints) ? fragment.account_hints.map((item) => String(item)) : []; if (accountHints.some((item) => /^(97|01|02|08|19|20|21|23|25|26|28|29|44|68(?:\.\d+)?|51|60|62)$/.test(item))) { return true; } return (fragment.candidate_labels.includes("anomaly_probe") || fragment.candidate_labels.includes("period_close_risk") || PERIOD_IMPACT_PATTERN.test(lowerText)); } function hasSymptomSignal(fragment, lowerText) { return (fragment.flags.asks_for_anomaly_scan || fragment.candidate_labels.includes("anomaly_probe") || fragment.candidate_labels.includes("period_close_risk") || SYMPTOM_MARKER_PATTERN.test(lowerText) || TRANSLIT_PROBLEM_PATTERN.test(lowerText)); } function hasLifecycleSignal(fragment, lowerText) { return (fragment.flags.asks_for_chain_explanation || fragment.flags.mentions_period_close_context || LIFECYCLE_MARKER_PATTERN.test(lowerText) || hasLifecycleDomainHint(fragment, lowerText)); } function hasChainBreakSignal(lowerText) { return CHAIN_BREAK_PATTERN.test(lowerText); } function hasPeriodImpactSignal(lowerText) { return PERIOD_IMPACT_PATTERN.test(lowerText); } function hasCausalSignal(lowerText) { return CAUSAL_PATTERN.test(lowerText); } function hasAmbiguitySignal(fragment, lowerText) { return (AMBIGUITY_PATTERN.test(lowerText) || fragment.confidence === "low" || fragment.domain_relevance === "unclear" || fragment.business_scope === "unclear"); } function hasAccountOrPeriodAnchor(fragment, lowerText) { return fragment.account_hints.length > 0 || ACCOUNT_HINT_PATTERN.test(lowerText) || PERIOD_PATTERN.test(lowerText); } function resolveRouteClass(fragment) { const lowerText = mergedFragmentText(fragment); const symptomSignal = hasSymptomSignal(fragment, lowerText); const lifecycleSignal = hasLifecycleSignal(fragment, lowerText); const chainBreakSignal = hasChainBreakSignal(lowerText); const periodImpactSignal = hasPeriodImpactSignal(lowerText); const causalSignal = hasCausalSignal(lowerText); const ambiguitySignal = hasAmbiguitySignal(fragment, lowerText); const accountOrPeriodAnchor = hasAccountOrPeriodAnchor(fragment, lowerText); if (fragment.flags.asks_for_exact_object_trace) { return ROUTE_DISCIPLINE_RULE_MAP.get("exact_object_trace"); } if (fragment.flags.asks_for_ranking_or_top || fragment.flags.asks_for_period_summary) { return ROUTE_DISCIPLINE_RULE_MAP.get("ranking_or_period_summary"); } if (ambiguitySignal && (symptomSignal || lifecycleSignal || chainBreakSignal || periodImpactSignal || causalSignal)) { return ROUTE_DISCIPLINE_RULE_MAP.get("mixed_ambiguity"); } if (chainBreakSignal) { return ROUTE_DISCIPLINE_RULE_MAP.get("chain_break"); } if (periodImpactSignal && accountOrPeriodAnchor) { return ROUTE_DISCIPLINE_RULE_MAP.get("period_impact"); } if (lifecycleSignal) { return ROUTE_DISCIPLINE_RULE_MAP.get("lifecycle_first"); } if (symptomSignal) { return ROUTE_DISCIPLINE_RULE_MAP.get("symptom_first"); } if (causalSignal && accountOrPeriodAnchor) { return ROUTE_DISCIPLINE_RULE_MAP.get("causal_query"); } if (fragment.flags.asks_for_rule_check) { return ROUTE_DISCIPLINE_RULE_MAP.get("rule_check_without_symptom"); } return ROUTE_DISCIPLINE_RULE_MAP.get("canonical_fact_lookup"); } function shouldPromoteFromNoRoute(fragment, rule) { if (rule.required_route === "store_canonical") { return false; } if (explicitNoRouteReason(fragment) === "out_of_scope") { return false; } const lowerText = mergedFragmentText(fragment); const hasProblemSignal = hasSymptomSignal(fragment, lowerText) || hasLifecycleSignal(fragment, lowerText) || hasChainBreakSignal(lowerText) || hasPeriodImpactSignal(lowerText) || hasCausalSignal(lowerText); const hasAnchor = hasAccountOrPeriodAnchor(fragment, lowerText) || fragment.candidate_labels.includes("cross_entity") || DOMAIN_LEXICAL_ANCHOR_PATTERN.test(lowerText); return hasProblemSignal && hasAnchor; } function reasonForNoRoute(noRouteReason) { if (noRouteReason === "out_of_scope") { return "Fragment is out-of-scope for company-specific accounting contour."; } if (noRouteReason === "missing_mapping") { return "Fragment is in-scope but route mapping is currently missing."; } if (noRouteReason === "unsupported_fragment_type") { return "Fragment type is not supported by the current deterministic route map."; } return "Fragment requires clarification or is too underspecified for safe routing."; } function explicitRouteStatus(fragment) { return "route_status" in fragment ? fragment.route_status : null; } function explicitNoRouteReason(fragment) { return "no_route_reason" in fragment ? fragment.no_route_reason : null; } function executionReadiness(fragment) { return "execution_readiness" in fragment ? fragment.execution_readiness : null; } function clarificationReason(fragment) { return "clarification_reason" in fragment ? fragment.clarification_reason : null; } function softAssumptions(fragment) { return "soft_assumption_used" in fragment ? fragment.soft_assumption_used : []; } function buildNoRouteDecision(fragment, noRouteReason) { return { fragment_id: fragment.fragment_id, domain_relevance: fragment.domain_relevance, business_scope: fragment.business_scope, candidate_labels: fragment.candidate_labels, decision_flags: fragment.flags, execution_readiness: executionReadiness(fragment), clarification_reason: clarificationReason(fragment), soft_assumption_used: softAssumptions(fragment), route_status: "no_route", no_route_reason: noRouteReason ?? "insufficient_specificity", route: "no_route", reason: reasonForNoRoute(noRouteReason) }; } function decideRouteForFragment(fragment) { const status = explicitRouteStatus(fragment); const noRouteReason = explicitNoRouteReason(fragment); const readiness = executionReadiness(fragment); const clarification = clarificationReason(fragment); const soft = softAssumptions(fragment); const routeRule = resolveRouteClass(fragment); if (fragment.domain_relevance === "out_of_scope") { return buildNoRouteDecision(fragment, "out_of_scope"); } if (status === "no_route" || readiness === "needs_clarification" || readiness === "no_route") { if (shouldPromoteFromNoRoute(fragment, routeRule)) { return { fragment_id: fragment.fragment_id, domain_relevance: fragment.domain_relevance, business_scope: fragment.business_scope, candidate_labels: fragment.candidate_labels, decision_flags: fragment.flags, execution_readiness: readiness, clarification_reason: clarification, soft_assumption_used: soft, route_status: "routed", no_route_reason: null, route: routeRule.required_route, reason: `${routeRule.description} Query class: ${routeRule.query_class}. Promoted from no-route by anchor/symptom guardrail.` }; } return buildNoRouteDecision(fragment, noRouteReason); } if (status === "routed" || status === null) { return { fragment_id: fragment.fragment_id, domain_relevance: fragment.domain_relevance, business_scope: fragment.business_scope, candidate_labels: fragment.candidate_labels, decision_flags: fragment.flags, execution_readiness: readiness, clarification_reason: clarification, soft_assumption_used: soft, route_status: "routed", no_route_reason: null, route: routeRule.required_route, reason: `${routeRule.description} Query class: ${routeRule.query_class}. Allowed fallback: ${routeRule.allowed_fallback.join(", ")}. Forbidden fallback: ${routeRule.forbidden_fallback.join(", ")}.` }; } return buildNoRouteDecision(fragment, "missing_mapping"); } function fallbackMessageFor(type) { if (type === "out_of_scope") { return "Я работаю только с данными и бухгалтерским контуром текущей компании. Запрос вне доступной предметной области."; } if (type === "clarification") { return "Могу проверить это в контуре компании, но нужно уточнить период, документ, счет или участок учета."; } if (type === "partial") { return "Обработаю только часть запроса, которая относится к данным компании. Остальное выходит за пределы доступного контура."; } return null; } function simulateDeterministicRouting(normalized) { const decisions = normalized.fragments.map((fragment) => decideRouteForFragment(fragment)); const inScopeCount = decisions.filter((item) => item.domain_relevance === "in_scope").length; const outOfScopeCount = decisions.filter((item) => item.domain_relevance === "out_of_scope").length; const unclearCount = decisions.filter((item) => item.domain_relevance === "unclear").length; const routedInScopeCount = decisions.filter((item) => item.domain_relevance === "in_scope" && item.route !== "no_route").length; const clarificationInScopeCount = decisions.filter((item) => item.domain_relevance === "in_scope" && item.execution_readiness === "needs_clarification").length; const noRouteInScopeCount = decisions.filter((item) => item.domain_relevance === "in_scope" && item.route === "no_route").length; let fallbackType = "none"; if (!normalized.message_in_scope || inScopeCount === 0) { fallbackType = outOfScopeCount > 0 && unclearCount === 0 ? "out_of_scope" : "clarification"; } else if (routedInScopeCount === 0 && clarificationInScopeCount > 0) { fallbackType = "clarification"; } else if (routedInScopeCount === 0 && noRouteInScopeCount > 0) { fallbackType = "clarification"; } else if ((inScopeCount > 0 && outOfScopeCount > 0) || (routedInScopeCount > 0 && noRouteInScopeCount > 0)) { fallbackType = "partial"; } return { mode: "deterministic_v2", message_in_scope: normalized.message_in_scope, scope_confidence: normalized.scope_confidence, planner: { total_fragments: normalized.fragments.length, in_scope_fragments: inScopeCount, out_of_scope_fragments: outOfScopeCount, discarded_fragments: normalized.discarded_fragments.length, contains_multiple_tasks: normalized.contains_multiple_tasks }, decisions, fallback: { type: fallbackType, message: fallbackMessageFor(fallbackType) } }; } function toRouteHintSummary(normalized) { if (normalized.schema_version === "normalized_query_v2" || normalized.schema_version === "normalized_query_v2_0_1" || normalized.schema_version === "normalized_query_v2_0_2") { return simulateDeterministicRouting(normalized); } return toRouteHintSummaryV1(normalized); } function toRouterInput(normalized) { if (normalized.schema_version === "normalized_query_v2" || normalized.schema_version === "normalized_query_v2_0_1" || normalized.schema_version === "normalized_query_v2_0_2") { return { mode: "deterministic_v2", message_in_scope: normalized.message_in_scope, scope_confidence: normalized.scope_confidence, contains_multiple_tasks: normalized.contains_multiple_tasks, fragments: normalized.fragments.map((fragment) => ({ fragment_id: fragment.fragment_id, domain_relevance: fragment.domain_relevance, business_scope: fragment.business_scope, execution_readiness: "execution_readiness" in fragment ? fragment.execution_readiness : null, clarification_reason: "clarification_reason" in fragment ? fragment.clarification_reason : null, soft_assumption_used: "soft_assumption_used" in fragment ? fragment.soft_assumption_used : [], route_status: "route_status" in fragment ? fragment.route_status : null, no_route_reason: "no_route_reason" in fragment ? fragment.no_route_reason : null, flags: fragment.flags, candidate_labels: fragment.candidate_labels, confidence: fragment.confidence })) }; } return { mode: "legacy_v1", intent_class: normalized.intent_class, decision_flags: { needs_cross_entity_join: normalized.requires.needs_cross_entity_join, needs_causal_chain: normalized.requires.needs_causal_chain, needs_exact_object_trace: normalized.requires.needs_exact_object_trace, needs_ranking: normalized.requires.needs_ranking, needs_anomaly_summary: normalized.requires.needs_anomaly_summary, needs_runtime_truth: normalized.requires.needs_runtime_truth }, route_hint: normalized.route_hint, confidence: normalized.confidence.overall, entities: { domain_entities: normalized.domain_entities, accounts_mentioned: normalized.accounts_mentioned, documents_mentioned: normalized.documents_mentioned, registers_mentioned: normalized.registers_mentioned }, period_scope: normalized.period_scope }; }