from __future__ import annotations import csv import difflib import heapq import hashlib import io import json import os import queue import re import threading import time from collections import Counter from concurrent.futures import FIRST_COMPLETED, Future, ThreadPoolExecutor, wait from dataclasses import dataclass, field from itertools import combinations from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, cast from urllib.parse import quote from ..assistant_fallback import ( annotate_provenance as annotate_fallback_provenance, build_concept_prompt as build_shared_concept_prompt, build_public_web_source_guidance as build_shared_public_web_source_guidance, classify_concept_request, concept_answer_needs_web_refinement as shared_concept_answer_needs_web_refinement, deterministic_concept_payload, question_prefers_authoritative_evidence as shared_question_prefers_authoritative_evidence, responses_create_with_deadline, ) from ..rapid_router.router_intelligence import ( RouterEntityResolution, RouterIntelligenceQuery, _build_router_entity_resolutions, _prune_shadowed_router_device_tokens, extract_router_device_tokens, normalize_router_intelligence_text, parse_router_intelligence_query, ) from ..rapid_router.catalog_db import manufacturer_family_key from ..routers.schemas import MessageRequest from ..query_normalizer import QueryNormalizer try: from openai import OpenAI # type: ignore except Exception: # pragma: no cover OpenAI = None # type: ignore try: from rapidfuzz import fuzz # type: ignore except Exception: # pragma: no cover fuzz = None # type: ignore try: import pdfplumber # type: ignore except Exception: # pragma: no cover pdfplumber = None # type: ignore PROMPT_VERSION = "1.0" @dataclass class RouterQueryPlan: intent: str requested_domain: str answer_mode: str evidence_mode: str clarification_policy: str entity_resolution_mode: str = "deterministic" entities: List[str] = field(default_factory=list) entity_resolutions: List[Dict[str, Any]] = field(default_factory=list) required_fields: List[str] = field(default_factory=list) candidate_families: List[str] = field(default_factory=list) entity_resolution_notes: List[str] = field(default_factory=list) current_only: bool = True limit: int = 3 search_filters: Dict[str, Any] = field(default_factory=dict) llm_assisted: bool = False llm_reason: str = "" orchestration_mode: str = "deterministic" def as_dict(self) -> Dict[str, Any]: return { "intent": str(self.intent or ""), "requested_domain": str(self.requested_domain or ""), "answer_mode": str(self.answer_mode or ""), "evidence_mode": str(self.evidence_mode or ""), "clarification_policy": str(self.clarification_policy or ""), "entity_resolution_mode": str(self.entity_resolution_mode or "deterministic"), "entities": [str(item) for item in list(self.entities or []) if str(item or "").strip()], "entity_resolutions": [ {str(key): value for key, value in dict(item).items()} for item in list(self.entity_resolutions or []) if isinstance(item, dict) ], "required_fields": [str(item) for item in list(self.required_fields or []) if str(item or "").strip()], "candidate_families": [str(item) for item in list(self.candidate_families or []) if str(item or "").strip()], "entity_resolution_notes": [str(item) for item in list(self.entity_resolution_notes or []) if str(item or "").strip()], "current_only": bool(self.current_only), "limit": int(self.limit or 0), "search_filters": {str(key): value for key, value in dict(self.search_filters or {}).items()}, "llm_assisted": bool(self.llm_assisted), "llm_reason": str(self.llm_reason or ""), "orchestration_mode": str(self.orchestration_mode or "deterministic"), } @dataclass class RouterWorkbookExecutionPlan: query: RouterIntelligenceQuery fast_domain: str router_query_plan: Dict[str, Any] = field(default_factory=dict) orchestration_meta: Dict[str, Any] = field(default_factory=dict) def response_meta(self) -> Dict[str, Any]: return { **dict(self.orchestration_meta or {}), "router_query_plan": dict(self.router_query_plan or {}), } @dataclass class RouterEvidenceItem: source_type: str source_document: str model_alias: str field_label: str raw_value: str normalized_value: str provenance: str confidence: float source_table: str = "" citation_anchor: str = "" entity_label: str = "" entity_role: str = "" evidence_kind: str = "" resolution_mode: str = "" debug_ref: str = "" uncertainty_flags: List[str] = field(default_factory=list) def as_dict(self) -> Dict[str, Any]: return { "source_type": str(self.source_type or ""), "source_document": str(self.source_document or ""), "model_alias": str(self.model_alias or ""), "field_label": str(self.field_label or ""), "raw_value": str(self.raw_value or ""), "normalized_value": str(self.normalized_value or ""), "provenance": str(self.provenance or ""), "confidence": round(max(0.0, min(1.0, float(self.confidence or 0.0))), 3), "source_table": str(self.source_table or ""), "citation_anchor": str(self.citation_anchor or ""), "entity_label": str(self.entity_label or ""), "entity_role": str(self.entity_role or ""), "evidence_kind": str(self.evidence_kind or ""), "resolution_mode": str(self.resolution_mode or ""), "debug_ref": str(self.debug_ref or ""), "uncertainty_flags": [str(item) for item in list(self.uncertainty_flags or []) if str(item or "").strip()], } @dataclass class RouterFleetEvidenceRow: row_number: int customer: str input_model: str normalized_model: str qty: int matched: bool manufacturer_group: str confidence_label: str confidence_score: int lifecycle_status: str lifecycle_bucket: str authoritative_lifecycle: bool family_level: bool end_of_sale_date: str end_of_life_date: str same_brand_path: str backup_path: str preferred_5g_path: str bridge_path: str ordered_paths: List[Dict[str, str]] = field(default_factory=list) recommended_path_label: str = "" recommended_path_value: str = "" replacement_source_mode: str = "" replacement_priority_score: int = 0 replacement_priority_reason: str = "" lane_note: str = "" review_required: bool = False correction_note: str = "" match_alias_text: str = "" match_alias_type: str = "" source_table: str = "" citation_anchor: str = "" entity_label: str = "" entity_role: str = "" evidence_kind: str = "" resolution_mode: str = "" uncertainty_flags: List[str] = field(default_factory=list) candidate_labels: List[str] = field(default_factory=list) error_message: str = "" debug_ref: str = "" def as_dict(self) -> Dict[str, Any]: return { "row_number": int(self.row_number or 0), "customer": str(self.customer or ""), "input_model": str(self.input_model or ""), "normalized_model": str(self.normalized_model or ""), "qty": int(self.qty or 0), "matched": bool(self.matched), "manufacturer_group": str(self.manufacturer_group or ""), "confidence_label": str(self.confidence_label or ""), "confidence_score": int(self.confidence_score or 0), "lifecycle_status": str(self.lifecycle_status or ""), "lifecycle_bucket": str(self.lifecycle_bucket or ""), "authoritative_lifecycle": bool(self.authoritative_lifecycle), "family_level": bool(self.family_level), "end_of_sale_date": str(self.end_of_sale_date or ""), "end_of_life_date": str(self.end_of_life_date or ""), "same_brand_path": str(self.same_brand_path or ""), "backup_path": str(self.backup_path or ""), "preferred_5g_path": str(self.preferred_5g_path or ""), "bridge_path": str(self.bridge_path or ""), "ordered_paths": [ { "label": str(_as_dict(item).get("label") or ""), "value": str(_as_dict(item).get("value") or ""), "kind": str(_as_dict(item).get("kind") or ""), "source_mode": str(_as_dict(item).get("source_mode") or ""), "note": str(_as_dict(item).get("note") or ""), } for item in list(self.ordered_paths or []) if isinstance(item, dict) ], "recommended_path_label": str(self.recommended_path_label or ""), "recommended_path_value": str(self.recommended_path_value or ""), "replacement_source_mode": str(self.replacement_source_mode or ""), "replacement_priority_score": int(self.replacement_priority_score or 0), "replacement_priority_reason": str(self.replacement_priority_reason or ""), "lane_note": str(self.lane_note or ""), "review_required": bool(self.review_required), "correction_note": str(self.correction_note or ""), "match_alias_text": str(self.match_alias_text or ""), "match_alias_type": str(self.match_alias_type or ""), "source_table": str(self.source_table or ""), "citation_anchor": str(self.citation_anchor or ""), "entity_label": str(self.entity_label or ""), "entity_role": str(self.entity_role or ""), "evidence_kind": str(self.evidence_kind or ""), "resolution_mode": str(self.resolution_mode or ""), "uncertainty_flags": [str(item) for item in list(self.uncertainty_flags or []) if str(item or "").strip()], "candidate_labels": [str(item) for item in list(self.candidate_labels or []) if str(item or "").strip()], "error_message": str(self.error_message or ""), "debug_ref": str(self.debug_ref or ""), } def _open_csv_with_fallback(path: Path | str) -> io.StringIO: data = Path(path).read_bytes() last_error: Optional[Exception] = None for encoding in ("utf-8-sig", "utf-8", "cp1252", "latin-1"): try: return io.StringIO(data.decode(encoding)) except UnicodeDecodeError as exc: last_error = exc continue if last_error is not None: raise last_error return io.StringIO(data.decode("utf-8", errors="replace")) _INITIAL_PROMPT = ( "## Masters Telecom AI Knowledgebase\n\n" "Ask questions in plain language.\n\n" "### Mode options\n\n" "- `Auto` (recommended): I route to the right knowledge domain\n" "- `Router docs/specs`: datasheets/manuals/install/spec comparisons\n" "- `Router lifecycle`: EOS/EOL/replacements/inventory snapshots\n" "- `POTS Q&A`: provider comparisons/rollout guidance/docs\n" "- `Masters AI`: Masters SKUs/services/internal docs\n\n" "### Example questions\n\n" "- `Compare RV50X vs XR60 from documented specs only.`\n" "- `Darden has 228 AER2200 and wants 5G replacements.`\n" "- `Which POTS providers do we have docs for, and what are the differences?`\n" "- `List BuSS SKUs related to SecureFAX.`\n\n" "Type `/reset` to clear chat." ) _RESET_WORDS = { "/reset", "reset", "clear", "clear chat", "clear conversation", "reset chat", "reset conversation", "restart", "start over", "new chat", "fresh start", } _MODE_ALIASES = { "auto": "auto", "router_docs": "router_docs", "router-docs": "router_docs", "router": "router_docs", "routers": "router_docs", "router docs": "router_docs", "router documentation": "router_docs", "router kb": "router_docs", "docs": "router_docs", "specs": "router_docs", "router-lifecycle": "router_lifecycle", "router_lifecycle": "router_lifecycle", "router lifecycle": "router_lifecycle", "lifecycle": "router_lifecycle", "inventory": "router_lifecycle", "replacements": "router_lifecycle", "masters": "masters", "masters ai": "masters", "masters telecom": "masters", "pots": "pots", "pots q&a": "pots", "pots replacement": "pots", } _MODE_LABELS = { "auto": "Auto", "router_docs": "Router docs/specs", "router_lifecycle": "Router lifecycle", "masters": "Masters AI", "pots": "POTS Q&A", } _MODE_BADGE_TEXT = { "router_docs": "Domain: Router docs/specs", "router_lifecycle": "Domain: Router lifecycle", "masters": "Domain: Masters AI", "pots": "Domain: POTS Q&A", } _VERIZON_PRICING_RE = re.compile( r"\b(verizon|vzw|verizon wireless)\b.*\b(price|pricing|plan|promo|promotion|cost|rate|discount)\b" r"|\b(price|pricing|plan|promo|promotion|cost|rate|discount)\b.*\b(verizon|vzw|verizon wireless)\b", flags=re.IGNORECASE, ) _VERIZON_POLICY_RE = re.compile( r"\b(verizon|vzw|verizon wireless)\b.*\b(policy|policies|exception|exceptions|employee discount|employee)\b" r"|\b(policy|policies|exception|exceptions|employee discount|employee)\b.*\b(verizon|vzw|verizon wireless)\b", flags=re.IGNORECASE, ) _OTHER_CARRIER_POLICY_RE = re.compile( r"\b(att|at&t|t-?mobile|tmobile|spectrum|comcast|cox|frontier|lumen|centurylink)\b" r".*\b(plan|pricing|rate|performance|policy|sla|coverage)\b", flags=re.IGNORECASE, ) _PII_EMPLOYEE_RE = re.compile(r"\b(employee|employees|personnel|personal email|phone number|home address|ssn)\b", flags=re.IGNORECASE) _GUARANTEE_RE = re.compile(r"\bguarantee|guaranteed|promise\b", flags=re.IGNORECASE) _EXACT_CURRENT_LEADTIME_RE = re.compile( r"\b(exact|current|latest|today|right now|this week|as of)\b.*\b(lead time|lead times)\b" r"|\b(lead time|lead times)\b.*\b(exact|current|latest|today|right now|this week|as of)\b", flags=re.IGNORECASE, ) _EXACT_CURRENT_AVAILABILITY_RE = re.compile( r"\b(exact|current|latest|today|right now|this week|as of)\b.*\b(availability|available|in stock|stock)\b" r"|\b(availability|available|in stock|stock)\b.*\b(exact|current|latest|today|right now|this week|as of)\b", flags=re.IGNORECASE, ) _EXACT_BAND_SUPPORT_RE = re.compile( r"\b(exact|current|latest|today|right now|as of)\b.*\b(band support|supported bands?|band[s]?|n\d{2,3}|b\d{2,3})\b" r"|\b(band support|supported bands?|band[s]?|n\d{2,3}|b\d{2,3})\b.*\b(exact|current|latest|today|right now|as of)\b", flags=re.IGNORECASE, ) _EXACT_CERTIFICATION_RE = re.compile( r"\b(exact|current|latest|today|right now|as of)\b.*\b(certification|certified|fcc|fcc id|fcc certification)\b" r"|\b(certification|certified|fcc|fcc id|fcc certification)\b.*\b(exact|current|latest|today|right now|as of)\b", flags=re.IGNORECASE, ) _EXACT_LIFECYCLE_RE = re.compile( r"\b(exact|current|latest|today|right now|as of)\b.*\b(lifecycle date|lifecycle|eos|eol|end of sale|end of life)\b" r"|\b(lifecycle date|lifecycle|eos|eol|end of sale|end of life)\b.*\b(exact|current|latest|today|right now|as of)\b", flags=re.IGNORECASE, ) _CODE_ADJUDICATION_RE = re.compile( r"\b(pass inspection|inspection|meet code|meets code|code compliant|compliant with code|" r"required by code|code requires|ahj|authority having jurisdiction|" r"without further review|without review)\b" r"|(?:\bapproval\b|\bapproved\b).*\b(code|inspection|ahj|authority having jurisdiction)\b" r"|\b(code|inspection|ahj|authority having jurisdiction)\b.*(?:\bapproval\b|\bapproved\b)", flags=re.IGNORECASE, ) _MODE_COMMAND_RE = re.compile(r"^\s*(?:/)?(?:mode|domain)\s*[: ]\s*([a-zA-Z_\- ]+)\s*$", flags=re.IGNORECASE) _SWITCH_MODE_RE = re.compile(r"^\s*(?:switch|set)\s+(?:to\s+)?([a-zA-Z_\- ]+)\s+mode\s*$", flags=re.IGNORECASE) _INVENTORY_LINE_RE = re.compile(r"(?:^|[\n,;])\s*\d+\s+[A-Za-z][A-Za-z0-9\-]{1,}") _ROUTER_LIFECYCLE_HINTS = ( "eol", "eos", "end of life", "end-of-life", "end of sale", "end-of-sale", "replacement", "replacements", "replaces", "replace with", "alternative", "alternatives", "4g alternative", "5g alternative", "5g replacement", "inventory snapshot", "inventory", "lifecycle", "migration", "migration order", "migrate", "fallback", "target", "targets", "same-brand", "same manufacturer", "same-manufacturer", "same-brand first", "backup path", "backup second", "cross-vendor backup", "action first", "need action", "needs action", "need action first", "needs action first", "priority", "prioritize", "priority rank", "priority order", "urgency", "urgent", ) _ROUTER_DOC_HINTS = ( "spec", "specs", "specification", "specifications", "datasheet", "data sheet", "install guide", "installation", "quick start", "manual", "wan", "lan", "modem", "antenna", "antennas", "connector", "connectors", "rf", "throughput", "battery", "wifi", "compare", "comparison", "vs", "versus", "details", "documented specs", "documented technical details", "full details", "tell me about", "overview", "msrp", "price", "pricing", "cost", ) _POTS_HINTS = ( "pots", "solution provider", "solution providers", "pots replacement", "analog line", "copper sunset", "ooma", "mettel", "fusion connect", "mach", "dataremote", "airdial", "mfvn", "karis law", "ray baum", "ray baum's", "voip and 911", "e911", "911", "fcc", "ifc907", "nfpa 72", "47 cfr 51.325", "tech transitions", "secure fax", "elevator", "fire panel", ) _POTS_GENERIC_HINTS = ( "provider", "providers", "solution provider", "solution providers", ) _POTS_CONTEXT_HINTS = ( "pots", "pots replacement", "analog line", "copper sunset", "ooma", "airdial", "mettel", "fusion connect", "dataremote", "mach", "mfvn", "karis law", "ray baum", "e911", "911", "fcc", "ifc907", "nfpa 72", "47 cfr 51.325", "tech transitions", "secure fax", "fire panel", "elevator", ) _ROUTER_PLATFORM_HINTS = ( "speedfusion", "speed fusion", "incontrol2", "incontrol 2", "netcloud", "primecare", "fusionhub", "peplink", "pepwave", "cradlepoint", "ericsson router", ) _ROUTER_PLATFORM_QUERY_ANCHOR_VARIANTS: Dict[str, Tuple[str, ...]] = { "speedfusion": ("speedfusion", "speed fusion"), "incontrol2": ("incontrol2", "in control2", "in control 2"), "netcloud": ("netcloud",), "primecare": ("primecare", "prime care"), "fusionhub": ("fusionhub", "fusion hub"), } _ROUTER_NON_DEVICE_TERMS = { "SPEEDFUSION", "INCONTROL2", "NETCLOUD", "PRIMECARE", "FUSIONHUB", "SFC", "RJ11", "RJ45", "POE", "SIM", "ESIM", } _MASTERS_HINTS = ( "masters", "master's", "buss", "sku", "securefax", "secure-fax", "ifax", "i-fax", "sip account", "pro install", "quote", "quoting", "cheat sheet", "order flow", "order-flow", "document categories", "discovery call", "discovery-call", ) _TELECOM_SCOPE_HINTS = ( "verizon", "telecom", "cellular", "wireless", "lte", "5g", "4g", "fwa", "broadband", "router", "routers", "gateway", "gateways", "modem", "modems", "antenna", "antennas", "wan", "lan", "failover", "network slicing", "signal", "coverage", "sim", "esim", "pots", "fax", "sip", ) _CLEARLY_OUT_OF_SCOPE_HINTS = ( "capital of", "president", "prime minister", "weather", "temperature outside", "recipe", "restaurant", "movie", "song", "horoscope", "zodiac", "nba", "nfl", "mlb", "nhl", "soccer", "football score", "baseball score", "basketball score", "stock price", "bitcoin", "crypto price", ) _MISSING_INFO_HINTS = ( "not available in the approved masters telecom documentation", "not available in the provided documents", "not available in our internal router documentation", "internal retrieval reason: no_internal_hits", ) _GENERIC_SOURCE_EXCERPT_HINTS = ( "provider evidence from indexed internal file set", "coverage inferred from indexed internal files", "internal router document for", "model row from internal router catalog csv", "documented fields from feb2026routers.csv", "approved masters internal reference", "file selected by deterministic filename relevance matching", "internal documented spec file for", "indexed internal pots source available", ) _ROUTER_MODEL_TOKEN_RE = re.compile( r"\b(?:[A-Za-z]{1,10}[- ]?\d{2,4}[A-Za-z0-9\-]*|MAX\s+BR\s*\d\s*[A-Za-z0-9\-]*)\b", flags=re.IGNORECASE, ) _ROUTER_MODEL_SEPARATOR_RE = re.compile(r"\b(?:vs\.?|versus|and)\b|[,&;\n]+", flags=re.IGNORECASE) _ROUTER_MODEL_NOISE_PREFIXES = { "GIVEN", "HAS", "HAVE", "AND", "FOR", "WITH", "SITE", "SITES", "CUSTOMER", "CUST", "CAT", "Q1", "Q2", "Q3", "Q4", } _FOLLOWUP_CONTEXT_HINTS = ( "same as above", "same as before", "for that one", "for each", "for each one", "for each model", "for each router", "for each device", "for each of those", "for both", "for both models", "for both routers", "for both devices", "for both of those", "for the above", "for that same customer", "same customer", "use the same", "now do the same", "turn that into", "also include", "deep compare", "deep comparison", "provide some examples", "show some examples", "give some examples", "examples then", "more examples", "expand that", "expand this", "these will be", "those will be", "recommend antenna options", "antenna options", "find similar 5g alternatives", "similar 5g alternatives", "similar 5g alternative", "cross manufacturer 5g alternatives", "cross-manufacturer 5g alternatives", "then", ) _ROUTER_MODEL_ALIAS = { "RX60": "XR60", "CR202LITE": "CR202", "BR1PRO": "MAXBR1PRO", "MAXBR1MINI": "BR1MINI5G", "BR1MINI": "BR1MINI5G", "FW2000": "FW2000E", "MG51E": "MG51", "ACM7004": "ACM7000", "NCM1100E": "ASKNCM1100E", "NCM1100": "ASKNCM1100E", "NCQ1338E": "ASKNCQ1338E", "NCQ1338": "ASKNCQ1338E", "M106PRO": "M106", "SPARKK500A": "K500A", "KADETK300NB": "K300NB", } _ROUTER_VENDOR_PREFIXES = ( "ERICSSONCRADLEPOINT", "SIERRAWIRELESS", "INHANDNETWORKS", "MACHINENETWORKS", "CRADLEPOINT", "ERICSSON", "PEPLINK", "SEMTECH", "SIERRA", "WIRELESS", "CISCO", "MERAKI", "DIGI", "INHAND", "INSEEGO", "ATEL", "TELTONIKA", "OPENGEAR", "DATAREMOTE", "CONNECTCSG", "KATALYST", ) _ROUTER_VENDOR_TOKEN_PREFIXES = {x for x in _ROUTER_VENDOR_PREFIXES if x.isalpha() and len(x) >= 4} _ROUTER_ANTENNA_PRIORITY_VENDORS = ("ericsson_cradlepoint", "semtech", "digi", "peplink") _PARSEC_FAMILY_NAMES: Tuple[str, ...] = ( "Whippet", "Chinook", "Akita", "Husky", "Albatross", "Irish Setter", "Border Collie", "St. Bernard", "Labrador", "Great Dane", "Dalmatian", "Greyhound", "Boxer", "Beagle", "Airedale", ) _ROUTER_PHRASE_MODEL_ALIASES: Dict[str, str] = { r"\bmax\s*br\s*1\s*pro\s*5g\b": "MAXBR1PRO5G", r"\bbr\s*1\s*pro\s*5g\b": "MAXBR1PRO5G", r"\bmax\s*br\s*1\s*mini\s*5g\b": "BR1MINI5G", r"\bbr\s*1\s*mini\s*5g\b": "BR1MINI5G", r"\bmax\s*br\s*1\s*pro\b": "MAXBR1PRO", r"\bbr\s*1\s*pro\b": "MAXBR1PRO", r"\bmax\s*br\s*1\s*mini\b": "BR1MINI5G", r"\bbr\s*1\s*mini\b": "BR1MINI5G", r"\bdragon\b": "XC46BE", r"\bcrown\b": "ASKNCM1100E", r"\bask[- ]?ncm1100e\b": "ASKNCM1100E", r"\bask[- ]?ncm1100\b": "ASKNCM1100E", r"\bask[- ]?ncq1338e\b": "ASKNCQ1338E", r"\bask[- ]?ncq1338\b": "ASKNCQ1338E", r"\bxc46be\b": "XC46BE", r"\bm106\s*pro\b": "M106", r"\bm106\b": "M106", r"\bm519\b": "M519", r"\bkatalyst\s+spark\b": "K500A", r"\bk500a\s+spark\b": "K500A", r"\bspark\s+k500a\b": "K500A", r"\bk500a\b": "K500A", r"^\s*spark\s*$": "K500A", r"\bspark\b(?=\s+(?:spec|specs|specification|specifications|details|datasheet|router|gateway|wan|lan|ports|wifi|antenna|antennas|throughput|battery|ruggedization|compare|versus|vs)\b)": "K500A", r"\bkadet\s+k300nb\b": "K300NB", r"\bk300nb\b": "K300NB", r"\bkadet\b": "K300NB", r"\bfsno21va\b": "FSNO21VA", r"\bnvg558\b": "NVG558", } _ROUTER_FACT_FIELD_ALIASES: Dict[str, Tuple[str, ...]] = { "wan_lan": ( "wan/lan", "wan lan", "wan ports", "lan ports", "ethernet", "rj45", "ports", ), "antennas_rf": ( "antenna", "antennas", "connector", "connectors", "rf", "sma", "rp-sma", "rpsma", "adapter", "adapters", ), "modem": ("modem", "modem type", "cellular category", "cat", "modem variants", "modem variant"), "wifi": ("wifi", "wi-fi", "wireless"), "gnss": ("gnss", "gps"), "throughput": ("throughput", "speed", "speeds", "download", "upload", "mbps", "gbps"), "msrp": ("msrp", "price", "pricing", "cost", "list price"), "battery": ("battery", "backup battery"), "ruggedization": ("rugged", "ruggedization", "hardened"), "vpn": ("vpn", "ipsec", "openvpn", "wireguard"), "serial": ("serial", "rs232", "rs-232"), "poe": ("poe", "power over ethernet", "power-over-ethernet"), "install_caveats": ("install caveat", "install caveats", "special notes", "install notes"), } _VERIZON_GATEWAY_MODEL_KEYS: Tuple[str, ...] = ( "XC46BE", "FSNO21VA", "ASKNCM1100E", "ASKNCQ1338E", "NVG558", "ASKNCM1100", "ASKNCQ1338", ) _ROUTER_FAST_COMPARE_HINTS = ("compare", "comparing", "comparison", "vs", "versus", "difference", "differences", "different") _ROUTER_SPEC_HINTS = ("spec", "specs", "wan", "lan", "modem", "wifi", "battery", "ruggedization", "rf", "connector") _ROUTER_EVIDENCE_FIRST_HINTS = ( "default port", "tcp", "udp", "dimensions", "dimension", "size", "weight", "temperature", "temperatures", "operating temperature", "storage temperature", "certification", "certifications", "cloud management", "management platform", "diagnostic", "diagnostics", "monitoring tools", "firewall", "port forwarding", "vpn", "overhead", "imix", "runtime", "battery capacity", ) _ROUTER_REPLACEMENT_HINTS = ( "replacement", "replacements", "replace", "replaces", "alternative", "alternatives", "migrate", "migration", "suggested", "suggest", "target model", ) _ROUTER_STATUS_HINTS = ("status", "lifecycle", "eos", "eol", "end of life", "end of sale") _ROUTER_FAST_SKIP_HINTS = ( "recommend", "recommended", "best fit", "decision table", "checklist", "install guide", "installation", "playbook", "objection", "phased", "risk ranking", "migration order", "outdoor", "antenna family", "police", "public safety", "law enforcement", "vehicle routers", "weighted", "scoring matrix", "why", ) _ROUTER_ALIAS_CONFIRM_REQUIRED = { "RX50": "RX55", "EX40": "IX40", } _ROUTER_FLEET_MODEL_STOPWORDS = { "customer", "customers", "fleet", "fleets", "legacy", "unit", "units", "model", "models", "please", "router", "routers", "replacement", "replacements", "status", "strategy", "target", "targets", "fallback", "recommendation", "recommendations", "portfolio", } _ROUTER_FLEET_MODEL_JOINER_TOKENS = { "ADV", "ADVANCED", "CORE", "ESSENTIAL", "ESSENTIALS", "LITE", "MAX", "MICRO", "MINI", "PLUS", "PRO", } _ROUTER_FLEET_CUSTOMER_EXACT_STOPWORDS = { "customer", "customers", "fleet", "inventory", "input", "grouping", "parse", "normalize", "mixed", "build", "compare", "propose", "recommend", "recommendation", "recommendations", "strategy", "show", "list", "find", "tell", "replacement", "replacements", "lifecycle", "row by row", "row by row lifecycle", "replacement order", "replacement table", "migration order", "risk ranking", "confidence notes", "result", "results", } _ROUTER_FLEET_CUSTOMER_FIRST_WORD_STOPWORDS = { "for", "give", "need", "show", "build", "compare", "normalize", "parse", "propose", "recommend", "list", "tell", "find", "mixed", } _ROUTER_CONCEPT_COMPARE_HINTS = ( "speedfusion", "speed fusion", "failover", "incontrol2", "incontrol 2", "wifi 5", "wifi 6", "wifi 7", "wi-fi 5", "wi-fi 6", "wi-fi 7", "802.11ac", "802.11ax", "802.11be", ) _AMBIGUOUS_MODEL_TERMS = {"model 228", "228 router", "unknown router", "rx50"} _POTS_FAST_COMPARE_HINTS = ( "compare", "comparing", "comparison", "weighted table", "provider differences", "differences", "differ", "tradeoff", "tradeoffs", ) _POTS_WEIGHTED_COMPARE_HINTS = ( "weighted", "weighted table", "scoring matrix", "scorecard", "ranked", "ranking", ) _POTS_FAST_PLAYBOOK_HINTS = ("playbook", "phased", "200-site", "200 site") _POTS_PROVIDER_SUMMARY_HINTS = ( "provider summary", "provider coverage", "indexed document coverage", "indexed docs", "what the docs say about", "summarize what the docs say", ) _MASTERS_FAST_OUTLINE_HINTS = ( "outline", "brief", "reference", "references", "battle card", "discovery", "checklist", "template", "sow", "risk register", "call script", "training", "milestones", "enablement", "onboarding", "talk track", "securefax", "ifax", "differences", "difference", "vs", "versus", "source-backed overview", "source backed overview", "can and cannot be claimed", "what can and cannot be claimed", "cannot be claimed", "claims from masters docs", "claims from masters documentation", "docs only claims", "docs-only claims", "ranked list", "follow-up sequence", "cheat sheet", "order-flow", "order flow", "quoting context", "document categories", "best internal docs", "discovery-call", "discovery call", "avoid saying", "reps avoid", "should avoid saying", "weak/conflicting", "ranked list of source documents", "pricing appears", "cite sources", ) _POTS_FORCE_DEEP_HINTS = ( "scoring matrix", "weighted table", "top 10", "top-10", "objection-handling", "objection handling", "documented strengths", "strengths/limits", "using only indexed evidence", "copper sunset", "phased provider", "200-site", "200 site", "documented installation approaches", "direct wire", "managed installs", "fire alarm pathway", "compliance-heavy", "compliance heavy", "airdial", "emphasize", "strengths", "limits", "tradeoff", "tradeoffs", "difference", "differences", "reliability", "survivability", ) _MASTERS_FORCE_DEEP_HINTS = ( "strict docs only", "verbatim", "exact quote", "exact excerpt", "source-backed", "source backed", "from masters docs", "from masters documentation", "grounded in masters documentation", "using only approved masters references", "battle card", "sales enablement brief", "follow-up sequence", "follow up sequence", ) _ROUTER_DOCS_FORCE_DEEP_HINTS = ( "from docs only", "from documented specs only", "from documented specs", "from install and quick-start docs", "from install and quick start docs", "from install docs", "from quick-start docs", "from quick start docs", "from install", "from quick-start", "from quick start", "quick-start docs", "quick start docs", ) _POTS_PROVIDER_PATTERNS: Dict[str, Tuple[str, ...]] = { "OOMA": ("ooma", "airdial"), "MetTel": ("mettel",), "DataRemote": ("dataremote", "90x1", "90x2"), "Machine Networks": ("machine networks", "machnetworks", "mach"), "Fusion Connect": ("fusion connect", "fusionconnect"), "BCN": ("bcn",), "Globalgig": ("globalgig",), "Caltel": ("caltel",), "AT&T": ("at&t", "at and t", "att"), "US Cellular": ("us cellular", "uscellular"), } _POTS_PROVIDER_ROUTER_PATH_HINTS: Dict[str, Tuple[str, ...]] = { "OOMA": ("misc/ooma/",), "MetTel": ("misc/mettel/",), "DataRemote": ("misc/dataremote/",), "Machine Networks": ("misc/machine_networks/", "misc/machinenetworks/", "misc/machine-networks/"), "Fusion Connect": ("misc/fusion_connect/", "misc/fusionconnect/"), "BCN": ("misc/bcn/",), "Globalgig": ("misc/globalgig/",), "Caltel": ("misc/caltel/",), "AT&T": ("misc/att/", "misc/at&t/",), "US Cellular": ("misc/uscellular/", "misc/us_cellular/",), } _POTS_PROVIDER_PREFERRED_ORDER: Tuple[str, ...] = ( "OOMA", "MetTel", "Fusion Connect", "DataRemote", "Machine Networks", ) _HIDDEN_CITATION_PRESERVE_FAST_MODES: Tuple[str, ...] = ( "router_prequote_questions_fast", "router_antenna_precheck_fast", "router_vehicle_install_caveats_fast", "router_engineering_reject_reasons_fast", "rapid_router_configuration_flow_fast", "rapid_router_shipping_behavior_fast", "rapid_router_submit_requirements_fast", "rapid_router_msrp_vs_sell_price_fast", "rapid_router_helper_routing_guidance_fast", "router_table_reader_recovery_fast", "router_alias_normalization_guidance_fast", "inventory_format_guidance_fast", "security_check_gate_guidance_fast", "hard_timeout_guidance_fast", "auth0_token_failure_checklist_fast", "auth_restart_guidance_fast", "auth_allowed_domains_fast", "hf_env_triage_fast", "startup_warning_priority_fast", "frontend_deploy_cache_recovery_fast", "inventory_typo_clarify_guidance_fast", "lifecycle_clarify_fast", "pots_per_site_quote_inputs_fast", "pots_life_safety_guardrail_fast", "pots_end_to_end_flow_fast", ) _CITATION_STOPWORDS = { "a", "an", "and", "as", "at", "for", "from", "has", "have", "how", "in", "is", "it", "of", "on", "or", "the", "to", "vs", "with", "what", "which", } _HIGH_RISK_SPEC_HINTS = ( "eos", "eol", "end of life", "end of sale", "datasheet", "data sheet", "spec", "specs", "specification", "specifications", "supported band", "band support", "compatibility", "compatible with", "certified", "certification", "connector type", "connector types", "battery runtime", "exact throughput", ) _COMPLEX_20S_HINTS = ( "top 10", "top-10", "objection-handling", "objection handling", "weighted table", "scoring matrix", "source-backed overview", "source backed overview", "best internal docs", "document categories", "order-flow guidance", "quoting context", "from docs only", ) _ANSWER_SEEKING_HINTS = ( "what", "which", "how", "does", "is", "are", "tell me", "provide", "show", "list", "compare", "summarize", "give me", ) _CONCEPT_FALLBACK_ASK_HINTS = ( "difference between", "differences between", "what is", "what's", "what does", "explain", "how does", "how do", "why would", "when should", "compare", "comparison", "vs", "versus", "overview", "tradeoff", "tradeoffs", "benefit", "benefits", "pros and cons", ) _ROUTER_GENERIC_CONCEPT_HINTS = ( "4g", "5g", "lte", "cat 4", "cat4", "router", "routers", "gateway", "gateways", "wan", "lan", "poe", "e sim", "esim", "failover", "throughput", "antenna", "antennas", "wifi", "wi-fi", "network slicing", "speedfusion", "incontrol", "rj11", "dual sim", ) _POTS_GENERIC_CONCEPT_HINTS = ( "pots", "pots replacement", "analog line", "analog lines", "copper sunset", "voip", "ata", "fax", "secure fax", "e911", "karis law", "ray baum", "mfvn", "fire alarm", "fire panel", "elevator", "dial tone", ) _MASTERS_GENERIC_CONCEPT_HINTS = ( "securefax", "secure fax", "ifax", "i-fax", "sip", "contact center", "pots replacement", "analog line", "voip", "router", "4g", "5g", "lte", "failover", ) _CONCEPT_FALLBACK_BLOCKED_HINTS = ( "price", "pricing", "cost", "msrp", "discount", "lead time", "leadtime", "availability", "available", "in stock", "stock", "certification", "certified", "fcc", "warranty", "guarantee", "support matrix", "compatible with", "compatibility", "supported bands", "band support", "eos", "eol", "end of sale", "end of life", ) _CONCEPT_WEAK_HINTS = ( "internal documentation is required", "confirm against internal documentation", "confirm with current public documentation", "may vary by deployment", "best guess", "best-effort", "not fully documented internally", "should be confirmed", ) _CURRENT_INFO_HINTS = ( "today", "current", "currently", "latest", "recent", "as of", "this year", ) _LOW_TIME_TEMPLATE_HINTS = ( "rewrite", "natural language", "objection", "top 10", "top-10", "playbook", "battle card", "call script", "one-pager", "one pager", "customer-ready", "customer ready", "executive summary", ) def _resolve_repo_root() -> Path: # Optional explicit override for unusual packaging layouts. env_root = str(os.getenv("MASTERS_TOOLKIT_REPO_ROOT", "") or "").strip() if env_root: p = Path(env_root).expanduser() if p.exists(): return p here = Path(__file__).resolve() parents = list(here.parents) # Prefer the first parent that has the FAQ corpus. for parent in parents: if (parent / "docs" / "faq" / "FAQ_master_updated.csv").exists(): return parent # Fallback: parent that has core router lifecycle CSV assets. for parent in parents: if (parent / "routers_eos_eol_by_sku.csv").exists() and (parent / "feb2026routers.csv").exists(): return parent # Legacy behavior fallback. try: return here.parents[3] except Exception: return parents[-1] if parents else Path.cwd() def _resolve_backend_app_root(repo_root: Path) -> Path: candidates = [ repo_root / "backend" / "app", # local repo layout repo_root / "app", # Docker runtime layout ] for candidate in candidates: if (candidate / "knowledgebase" / "data" / "normalized").exists(): return candidate for candidate in candidates: if candidate.exists(): return candidate return candidates[0] _REPO_ROOT = _resolve_repo_root() _BACKEND_APP_ROOT = _resolve_backend_app_root(_REPO_ROOT) _FAQ_DEFAULT_CSV = _REPO_ROOT / "docs" / "faq" / "FAQ_master_updated.csv" _FAQ_DEFAULT_INDEX = _REPO_ROOT / "docs" / "faq" / "FAQ_master_enriched.jsonl" _FAQ_DEFAULT_ONGOING = _REPO_ROOT / "docs" / "faq" / "FAQ_ongoing_candidates.csv" _RUNTIME_TELEMETRY_DEFAULT = _REPO_ROOT / "docs" / "evals" / "unified_kb_runtime_telemetry.jsonl" _ROUTER_WORKBOOK_FEEDBACK_DEFAULT = _REPO_ROOT / "docs" / "evals" / "router_workbook_feedback.jsonl" _ROUTER_PRICING_CATALOG_DEFAULT = _BACKEND_APP_ROOT / "knowledgebase" / "data" / "normalized" / "router_pricing_catalog_normalized.csv" _ROUTER_VARIANT_OPTIONS_DEFAULT = _BACKEND_APP_ROOT / "knowledgebase" / "data" / "normalized" / "router_variant_options_normalized.csv" _PARSEC_PRICING_DEFAULT = _BACKEND_APP_ROOT / "knowledgebase" / "data" / "normalized" / "parsec_pricing_normalized.csv" _PEPLINK_REPLACEMENT_OVERLAY_DEFAULT = _BACKEND_APP_ROOT / "knowledgebase" / "data" / "normalized" / "peplink_replacement_overlay.csv" _ROUTER_WORKBOOK_STRUCTURED_FEATURE_FIELDS: Tuple[str, ...] = ( "product_type_norm", "cellular_gen_norm", "lte_category_norm", "modem_count_norm", "wifi_norm", "lan_ports_norm", "wan_ports_norm", "total_ethernet_ports", "serial_norm", "poe_norm", "gnss_norm", "antenna_norm", "rugged_norm", "indoor_outdoor_norm", "battery_norm", "use_case_norm", ) _ROUTER_MISSING_FIELDS_AUDIT_DEFAULT = _REPO_ROOT / "docs" / "reports" / "router_missing_fields_audit.csv" _FAQ_SPLIT_RE = re.compile(r"(?:\n+|[;]+|(?<=[?!])\s+)") _FAQ_PROVIDER_TERMS = { "ooma", "airdial", "mettel", "fusion connect", "dataremote", "bcn", "globalgig", "caltel", "at&t", "machine networks", "mach", "ifax", "securefax", "speedfusion", "incontrol2", "primecare", "netcloud", } _FAQ_YES_TERMS = {"yes", "y", "yep", "correct", "that one", "sounds right", "proceed", "continue"} _FAQ_NO_TERMS = {"no", "n", "nope", "different", "not that", "wrong"} def _norm(text: Any) -> str: cleaned = _fix_common_mojibake(str(text or "")) return re.sub(r"\s+", " ", cleaned.strip()) def _norm_mode(raw: Any) -> str: key = _norm(raw).lower() if not key: return "auto" return _MODE_ALIASES.get(key, "auto") def _is_reset_command(message: str) -> bool: low = _norm(message).lower().rstrip(".!?") return low in _RESET_WORDS def _extract_mode_command(message: str) -> Optional[str]: text = str(message or "") for rx in (_MODE_COMMAND_RE, _SWITCH_MODE_RE): m = rx.match(text) if not m: continue mode = _norm_mode(m.group(1)) return mode if mode in _MODE_LABELS else None return None def _contains_any(message: str, terms: Sequence[str]) -> bool: low = str(message or "").lower() return any(t in low for t in terms) def _contains_term(message: str, term: str) -> bool: low = str(message or "").lower() tok = str(term or "").strip().lower() if not tok: return False if (" " in tok) or ("&" in tok) or ("-" in tok): pattern = r"\b" + re.escape(tok).replace(r"\ ", r"\s+") + r"\b" return bool(re.search(pattern, low)) if len(tok) <= 4: return bool(re.search(rf"\b{re.escape(tok)}\b", low)) return tok in low def _normalize_router_query_text(message: str) -> str: text = str(message or "") if not text: return "" # Preserve natural phrasing while normalizing high-impact matching tokens. # Normalize common punctuation separators used in model typing # (for example MAX-BR1-PRO-5G, XR_60, BR1/PRO) into spaces so alias # patterns and token extraction stay deterministic. text = text.replace("–", "-").replace("—", "-").replace("‑", "-").replace("−", "-") text = re.sub(r"(?<=[A-Za-z0-9])[._/\-]+(?=[A-Za-z0-9])", " ", text) replacements = { "data-sheet": "datasheet", "data sheet": "datasheet", "datasheet-style": "datasheet style", "wi fi": "wifi", "wi-fi": "wifi", "secure-fax": "securefax", "secure fax": "securefax", "i-fax": "ifax", "i fax": "ifax", "repalcement": "replacement", "repalce": "replace", "compair": "compare", "differnces": "differences", "sumarize": "summarize", "conector": "connector", "conectors": "connectors", "varient": "variant", "varients": "variants", "queston": "question", "questons": "questions", "sugestion": "suggestion", "sugestions": "suggestions", "cradelpoint": "cradlepoint", "survay": "survey", "outdor": "outdoor", "adaptor": "adapter", "rugedized": "ruggedized", } low = text.lower() for src, dst in replacements.items(): low = low.replace(src, dst) # Whole-word typo normalization only (avoid mutating valid substrings like "throughput"). low = re.sub(r"\bos\s+thr\b", "is the", low) low = re.sub(r"\bwhst\b", "what", low) low = re.sub(r"\bwht\b", "what", low) low = re.sub(r"\b([245])\s+g\b", r"\1g", low) low = re.sub(r"\s+", " ", low).strip() return low def _scrub_router_model_tokens_for_policy(message: str) -> str: text = str(message or "") if not text: return "" scrubbed = text for token in extract_router_device_tokens(text): token = str(token or "").strip() if not token: continue scrubbed = re.sub(rf"\b{re.escape(token)}\b", " ROUTER_MODEL ", scrubbed, flags=re.IGNORECASE) return scrubbed def _modem_mentions_5g_sa(value: Any) -> bool: low = _norm(value).lower() if ("5g" not in low) and ("standalone" not in low): return False if "standalone" in low: return True return any( re.search(pattern, low) for pattern in ( r"\b5g\s*(?:nr\s*)?sa\b", r"\bnr\s+sa\b", r"\bsa\s*/\s*nsa\b", r"\bnsa\s*/\s*sa\b", r"\bsa\s*\+\s*nsa\b", r"\bnsa\s*\+\s*sa\b", r"\bsa\s*&\s*nsa\b", r"\bnsa\s*&\s*sa\b", r"\bsa\s*,\s*nsa\b", r"\bnsa\s*,\s*sa\b", r"\(\s*sa\s*/\s*nsa\s*\)", r"\(\s*nsa\s*/\s*sa\s*\)", ) ) def _compact_model(value: Any) -> str: return "".join(ch for ch in str(value or "").upper() if ch.isalnum()) def _normalize_router_customer_phrase(value: Any) -> str: text = str(value or "").strip(" ,.;:-") if not text: return "" normalized = text.lower().replace("-", " ").replace("_", " ").replace("/", " ") normalized = re.sub(r"\s+", " ", normalized).strip() return normalized def _router_customer_phrase_looks_instructional(value: Any) -> bool: normalized = _normalize_router_customer_phrase(value) if not normalized: return True if normalized in _ROUTER_FLEET_CUSTOMER_EXACT_STOPWORDS: return True first_word = normalized.split()[0] if normalized.split() else "" return first_word in _ROUTER_FLEET_CUSTOMER_FIRST_WORD_STOPWORDS def _digit_signature(value: Any) -> str: return "".join(re.findall(r"\d+", str(value or ""))) def _strip_router_vendor_prefix(value: Any) -> str: tok = _compact_model(value) if not tok: return "" changed = True rounds = 0 while changed and rounds < 3: rounds += 1 changed = False for pref in sorted(_ROUTER_VENDOR_PREFIXES, key=len, reverse=True): if tok.startswith(pref) and len(tok) > (len(pref) + 1): tok = tok[len(pref) :] changed = True break return tok def _safe_model_variant_match(query_key: str, candidate_key: str) -> bool: q = _compact_model(query_key) c = _compact_model(candidate_key) if (not q) or (not c): return False if q == c: return True qd = _digit_signature(q) cd = _digit_signature(c) if qd and cd and qd != cd: if ( len(q) >= 4 and any(ch.isalpha() for ch in q) and (qd.startswith(cd) or cd.startswith(qd)) ): pass else: return False if c.startswith(q): extra = c[len(q) :] return bool(extra) and (len(extra) <= 24) if q.startswith(c): extra = q[len(c) :] return bool(extra) and (len(extra) <= 24) return False def _extract_router_models(message: str) -> List[str]: def _segment_models(normalized_segment: str) -> List[str]: local: List[str] = [] local_seen: set[str] = set() low = normalized_segment.lower() for rx, canonical in _ROUTER_PHRASE_MODEL_ALIASES.items(): if not re.search(rx, low): continue tok = _compact_model(canonical) if (not tok) or (tok in local_seen): continue local_seen.add(tok) local.append(tok) for raw in _ROUTER_MODEL_TOKEN_RE.findall(normalized_segment): tok = _compact_model(raw) if (not tok) or tok.isdigit() or len(tok) < 3: continue if tok in _ROUTER_NON_DEVICE_TERMS: continue m = re.match(r"^([A-Z]+)(\d+)$", tok) if m and m.group(1) in _ROUTER_MODEL_NOISE_PREFIXES: continue canon = _ROUTER_MODEL_ALIAS.get(tok, tok) if canon in local_seen: continue local_seen.add(canon) local.append(canon) pruned: List[str] = [] for tok in local: if any((other != tok) and other.startswith(tok) and (len(other) >= len(tok) + 2) for other in local): continue pruned.append(tok) return pruned out: List[str] = [] seen: set[str] = set() raw = str(message or "") segments = [raw] split_segments = [seg.strip() for seg in _ROUTER_MODEL_SEPARATOR_RE.split(raw) if seg and seg.strip()] if split_segments: segments = split_segments for segment in segments: normalized_segment = _normalize_router_query_text(segment) if not normalized_segment: continue for tok in _segment_models(normalized_segment): if tok in seen: continue seen.add(tok) out.append(tok) return out def _humanize_model_token(token: str) -> str: t = _compact_model(token) if not t: return str(token or "").strip() t = re.sub(r"^MAXBR(\d+)", r"MAX BR\1", t) t = re.sub(r"^BR(\d+)", r"BR\1", t) t = re.sub(r"^AER(\d+)", r"AER\1", t) t = re.sub(r"^XR(\d+)", r"XR\1", t) t = re.sub(r"^RV(\d+)", r"RV\1", t) t = re.sub(r"(? str: cleaned = re.sub(r"[_\-]+", " ", str(text or "").strip()) return _norm(cleaned).title() def _text_tokens(value: str) -> List[str]: tokens = [t for t in re.findall(r"[a-z0-9]{2,}", str(value or "").lower()) if t and t not in _CITATION_STOPWORDS] return tokens def _dedupe_norm_items(items: Sequence[Any], *, limit: int) -> List[str]: out: List[str] = [] seen: set[str] = set() for item in items: norm = _norm(item) if (not norm) or (norm.lower() in seen): continue seen.add(norm.lower()) out.append(norm) if len(out) >= max(1, int(limit)): break return out def _format_shell(result: str, why: Sequence[str], next_action: Sequence[str]) -> str: why_norm = _dedupe_norm_items(list(why or []), limit=4) next_norm = _dedupe_norm_items(list(next_action or []), limit=3) why_lines = [f"- {x}" for x in why_norm] next_lines = [f"- {x}" for x in next_norm] blocks = [ "**Result**", _norm_preserve(result), "", "**Why**", *(why_lines or ["- Routed by best-available source logic."]), "", "**Next action**", *(next_lines or ["- Ask a follow-up question with model + use case."]), ] return "\n".join(blocks).strip() def _norm_preserve(value: Any) -> str: text = _fix_common_mojibake(str(value or "")).replace("\r\n", "\n").replace("\r", "\n") lines = [" ".join(line.split()) for line in text.split("\n")] return "\n".join(lines).strip() def _clip_text(value: Any, max_chars: int) -> str: text = _norm_preserve(value) if max_chars <= 0 or len(text) <= max_chars: return text cut = text[: max_chars + 1] soft_idx = cut.rfind(" ") if soft_idx >= int(max_chars * 0.7): cut = cut[:soft_idx] else: cut = cut[:max_chars] return cut.rstrip(" ,;:.") + "..." def _truncate(value: Any, max_chars: int) -> str: text = _norm_preserve(value) if max_chars <= 0 or len(text) <= max_chars: return text return text[: max(0, max_chars - 3)].rstrip(" ,;:.") + "..." def _md_cell(value: Any) -> str: return _fix_common_mojibake(_norm(value)).replace("|", "\\|") def _safe_float(value: Any, default: float = 0.0) -> float: raw = _norm(value) if not raw: return float(default) try: return float(raw) except Exception: return float(default) def _normalize_replacement_cell(value: Any) -> str: text = _fix_common_mojibake(_norm(value)) if not text: return "" if ("|" in text) or (len(text) > 72): models = [] seen: set[str] = set() for tok in _extract_router_models(text): c = _compact_model(tok) if (not c) or (c in seen): continue seen.add(c) models.append(c) if models: return " / ".join(models[:2]) return text def _fix_common_mojibake(text: Any) -> str: value = str(text or "") if not value: return "" replacements = { "‑": "-", "–": "-", "—": "-", "’": "'", "‘": "'", "“": '"', "”": '"', "°": "°", "×": "x", " ": " ", "Â": "", "‚Äë": "-", "‚Äì": "-", "‚Äî": "-", "â„¢": "TM", "™": "TM", "®": "(R)", "©": "(C)", "•": "-", "¬∞": "°", } for bad, good in replacements.items(): value = value.replace(bad, good) return value def _pots_providers_in_text(message: str) -> List[str]: low = str(message or "").lower() found: List[str] = [] for provider, patterns in _POTS_PROVIDER_PATTERNS.items(): if any(_contains_term(low, p) for p in patterns): found.append(provider) # de-dupe preserve order out: List[str] = [] seen: set[str] = set() for p in found: if p in seen: continue seen.add(p) out.append(p) return out def _order_pots_providers(providers: Sequence[str]) -> List[str]: order = {name: idx for idx, name in enumerate(_POTS_PROVIDER_PREFERRED_ORDER)} deduped: List[str] = [] seen: set[str] = set() for raw in providers: p = _norm(raw) if (not p) or (p in seen): continue seen.add(p) deduped.append(p) return sorted(deduped, key=lambda p: (int(order.get(p, 99)), p.lower())) def _looks_like_router_lifecycle(message: str) -> bool: low = _normalize_router_query_text(message) # Interface-level conceptual asks (RJ11/RJ45/etc.) should not be treated as model lifecycle inventory. if ( any(tok in low for tok in ("rj11", "rj45")) and any(h in low for h in ("use case", "use cases", "summary")) and ("replacement" in low) and (not bool(_INVENTORY_LINE_RE.search(message or ""))) ): return False # Explicit lifecycle wording should win even when compare/table language is present. if _contains_any(low, _ROUTER_LIFECYCLE_HINTS): return True if _contains_any(low, _ROUTER_FAST_COMPARE_HINTS) and _contains_any(low, _ROUTER_DOC_HINTS): return False if bool(_INVENTORY_LINE_RE.search(message or "")): return True if re.search(r"\b(has|have|fleet|inventory)\b.{0,48}\b\d+\s+[a-z]{2,}\d", low): return True if re.search(r"\b\d+\s+[a-z]{2,}\d[^\n]{0,64}\b(want|needs?|replace|replacement|upgrade)\b", low): return True if re.search(r"\b\d+\s+(?:[a-z]{2,}\d|[a-z]+\d+[a-z0-9\-]*)\b", low): return True return False def _is_supported_router_mixed_lifecycle_request(message: str) -> bool: normalized = _normalize_router_query_text(message) query = parse_router_intelligence_query(message) if query is None or query.intent not in {"details", "compare"}: return False exact_date_tokens = ( "exact lifecycle date", "lifecycle date", "end of sale date", "end of life date", "eos date", "eol date", "what exact date", ) if any(token in normalized for token in exact_date_tokens): return False safe_field_tokens = ( "primary use case", "use case", "wan/lan", "wan", "lan", "ethernet", "ports", "wi-fi", "wifi", "modem", "rf connector", "rf connectors", "connectors", "install caveat", "install caveats", "current recommendation", "still current recommendation", "status", "documented vs", "not documented", "compare", "difference", "different", "versus", " vs ", ) return any(token in normalized for token in safe_field_tokens) def _looks_like_pots(message: str) -> bool: low = _normalize_router_query_text(message) if _contains_any(low, _POTS_HINTS): return True if _contains_any(low, _POTS_GENERIC_HINTS) and _contains_any(low, _POTS_CONTEXT_HINTS): return True return False def _looks_like_masters(message: str) -> bool: low = _normalize_router_query_text(message) strong_hints = ( "masters", "master's", "buss", "securefax", "secure-fax", "ifax", "i-fax", "sip account", "pro install", "order flow", "order-flow", "contact center", "document categories", "discovery call", "discovery-call", ) if _contains_any(low, strong_hints): return True # Pricing asks that include explicit router model tokens should stay on router docs. if any(t in low for t in ("quote", "quoting", "sku")): if _extract_router_models(message): return False if _contains_any(low, _ROUTER_DOC_HINTS) or _contains_any(low, _ROUTER_PLATFORM_HINTS): return False if _looks_like_router_lifecycle(low): return False return True return False def _looks_like_masters_doc_lookup(message: str) -> bool: low = _normalize_router_query_text(message) if _looks_like_masters_discovery_doc_review(low): return True doc_lookup_hints = ( "which files", "what files", "which docs", "what docs", "which documents", "what documents", "which internal documents", "which internal docs", "best internal docs", "ranked list of source documents", ) mention_hints = ("mention", "mentions", "include", "includes", "contain", "contains", "use to explain") masters_doc_targets = ( "securefax", "secure fax", "ifax", "i fax", "pro install", "sip account", "sip accounts", "b360 order flow", "ot support", "pots replacement", "contact center", "mst contact center", "dataremote", ) asks_doc_lookup = any(x in low for x in doc_lookup_hints) asks_explicit_mention = any(x in low for x in mention_hints) has_masters_doc_target = any(x in low for x in masters_doc_targets) return bool(has_masters_doc_target and (asks_doc_lookup or asks_explicit_mention)) def _looks_like_masters_discovery_doc_review(message: str) -> bool: low = _normalize_router_query_text(message) asks_doc_review = any( phrase in low for phrase in ( "what internal docs should i review", "which internal docs should i review", "what docs should i review", "which docs should i review", "what documents should i review", "which documents should i review", "review before a discovery call", "review before discovery call", "best internal docs", ) ) asks_discovery_context = ("discovery call" in low) or ("discovery prep" in low) or ("discovery call prep" in low) has_supported_topic = any( phrase in low for phrase in ( "securefax", "secure fax", "ifax", "i fax", "pots replacement", "router refresh", "contact center", "sip account", "sip accounts", "pro install", ) ) return bool(asks_doc_review and asks_discovery_context and has_supported_topic) def _should_skip_masters_concept_preflight(message: str) -> bool: low = str(message or "").lower() if _looks_like_masters_doc_lookup(low): return True return bool( re.search( r"\b(sku|skus|msrp|price|pricing|quote|quotes|doc|docs|document|documents|file|files|slide|slides|one-pager|one-pagers|source|sources|sip accounts?|sip account)\b", low, ) ) def _should_skip_pots_concept_preflight(message: str) -> bool: low = _normalize_router_query_text(message) providers_present = _pots_providers_in_text(low) asks_compare = any(h in low for h in _POTS_FAST_COMPARE_HINTS) asks_playbook = any(h in low for h in _POTS_FAST_PLAYBOOK_HINTS) asks_objection_map = ("objection" in low) and ( ("top 10" in low or "top-10" in low or "top ten" in low) or any(x in low for x in ("how to respond", "respond", "response", "addressed", "handling")) ) asks_provider_summary = bool(providers_present) and ( any(h in low for h in _POTS_PROVIDER_SUMMARY_HINTS) or ("summarize" in low) or ("emphasize" in low) or ("documentation" in low) or ("documented" in low) or ("what do the docs say" in low) or ("docs say" in low) or ("indexed material" in low) or ("from our docs" in low) ) asks_framework = ( ("framework" in low) and ("provider" in low or "providers" in low) and any(x in low for x in ("recommend", "recommended", "selection", "select")) ) return bool( asks_compare or asks_playbook or asks_objection_map or asks_provider_summary or asks_framework ) def _looks_like_router_docs(message: str) -> bool: return _contains_any(message, _ROUTER_DOC_HINTS) or _contains_any(message, _ROUTER_PLATFORM_HINTS) def _looks_like_wifi_generation_concept(message: str) -> bool: low = _normalize_router_query_text(message) asks_difference = any(x in low for x in ("difference", "differences", "compare", "comparison", "vs", "versus")) asks_wifi = any(x in low for x in ("wifi 5", "wifi 6", "wifi 7", "802.11ac", "802.11ax", "802.11be")) return bool(asks_difference and asks_wifi) def _classify_mode(message: str) -> str: low = _normalize_router_query_text(message) extracted_models = _extract_router_models(message) asks_router_price = any(t in low for t in ("msrp", "price", "pricing", "cost")) asks_securefax = any(t in low for t in ("securefax", "secure fax", "ifax", "i fax")) has_router_lifecycle_intent = _contains_any(low, _ROUTER_LIFECYCLE_HINTS) or _contains_any( low, (_ROUTER_STATUS_HINTS + _ROUTER_REPLACEMENT_HINTS) ) docs_only_compare_with_lifecycle_posture = bool( extracted_models and (_contains_any(low, _ROUTER_FAST_COMPARE_HINTS) or ("compare" in low)) and any( token in low for token in ( "docs only", "docs-only", "documented specs only", "from docs only", "internal docs only", "internal sources only", ) ) and ("lifecycle posture" in low) ) if extracted_models and asks_router_price and (not asks_securefax) and (not _looks_like_pots(message)): return "router_docs" if docs_only_compare_with_lifecycle_posture: return "router_docs" compare_with_deployment_context = bool( extracted_models and ( _contains_any(low, _ROUTER_FAST_COMPARE_HINTS) or ("what is different between" in low) or ("different between" in low) ) and any( token in low for token in ( "deployment", "branch office", "branch", "vehicle use", "vehicle", "install implication", "install implications", "install note", "install notes", "placement", "use case", ) ) ) if compare_with_deployment_context: return "router_docs" if _contains_any(low, _ROUTER_PLATFORM_HINTS): if has_router_lifecycle_intent: return "router_lifecycle" return "router_docs" if extracted_models and has_router_lifecycle_intent: return "router_lifecycle" if extracted_models and (_contains_any(low, _ROUTER_FAST_COMPARE_HINTS) or _looks_like_router_docs(message)): if has_router_lifecycle_intent: return "router_lifecycle" return "router_docs" if (not extracted_models) and asks_router_price and _contains_any(low, ("router", "gateway", "model", "models")) and (not asks_securefax): return "router_docs" if ( bool(re.search(r"\b\d+\s+[a-z]{2,}\d", low)) and (_contains_any(low, _ROUTER_LIFECYCLE_HINTS) or _contains_any(low, ("replacement", "replacements", "status", "upgrade"))) ): return "router_lifecycle" if _looks_like_masters_doc_lookup(message): return "masters" if any(h in low for h in ("mfvn", "ifc907", "nfpa 72", "47 cfr 51.325", "karis law", "ray baum", "tech transitions")): return "pots" if ( any(t in low for t in ("securefax", "secure fax", "ifax", "i fax")) and any(t in low for t in ("how much", "price", "pricing", "cost", "msrp", "monthly", "mrc", "nrc", "setup", "one-time", "one time")) ): return "masters" if (("ifax" in low) or ("securefax" in low)) and any(h in low for h in ("compare", "comparison", "difference", "differences", "vs", "versus")): return "masters" if ("avoid saying" in low or "reps avoid" in low) and ("weak or conflicting" in low or "docs are weak" in low): return "masters" weak_conflicting = any( x in low for x in ( "weak/conflicting", "weak or conflicting", "docs are weak", "conflicting docs", "docs are conflicting", "weak docs", ) ) avoid_language = any( x in low for x in ("avoid saying", "reps avoid", "should avoid saying", "what to avoid saying", "language to avoid") ) if weak_conflicting and avoid_language: return "masters" if ("objection" in low) and ("top 10" in low or "top ten" in low or "stakeholder" in low): return "pots" if ( ("provider" in low) and ("framework" in low) and any(x in low for x in ("recommend", "selection", "evidence gap", "evidence gaps")) ): return "pots" if ( ("provider" in low or "providers" in low) and any(x in low for x in ("healthcare", "hospital", "clinic", "medical")) and any(x in low for x in ("source doc", "source docs", "docs", "documents")) ): return "pots" if any(x in low for x in ("direct wire", "managed installs")) and any(x in low for x in ("install", "installation")): return "pots" if _looks_like_pots(message): return "pots" if _looks_like_masters(message): return "masters" if ( _contains_any(low, _ROUTER_FAST_COMPARE_HINTS) and (not _contains_any(low, _ROUTER_LIFECYCLE_HINTS)) and (not _looks_like_pots(message)) and (not _looks_like_masters(message)) ): return "router_docs" # Favor router docs/spec path for explicit compare/spec asks even with numeric tokens. if _contains_any(low, _ROUTER_FAST_COMPARE_HINTS) and _looks_like_router_docs(message): return "router_docs" if _looks_like_wifi_generation_concept(low): return "router_docs" if _looks_like_router_lifecycle(message): return "router_lifecycle" if _looks_like_router_docs(message): return "router_docs" return "router_docs" def _mounted_file_href(prefix: str, relative_path: str) -> str: clean = str(relative_path or "").replace("\\", "/").lstrip("/").strip() if not clean: return prefix encoded = "/".join(quote(part) for part in clean.split("/")) return f"{prefix.rstrip('/')}/{encoded}" def _as_dict(value: Any) -> Dict[str, Any]: return value if isinstance(value, dict) else {} def _env_bool(name: str, default: bool = False) -> bool: raw = os.getenv(name) if raw is None: return bool(default) v = str(raw).strip().lower() if v in {"1", "true", "yes", "y", "on"}: return True if v in {"0", "false", "no", "n", "off"}: return False return bool(default) @dataclass class FaqEntry: faq_id: str question: str answer: str q_norm: str q_tokens: set[str] = field(default_factory=set) entities: set[str] = field(default_factory=set) @dataclass class UnifiedKnowledgebaseState: mode: str = "auto" last_mode: str = "router_docs" last_user_message: str = "" router_docs_state: Dict[str, Any] = field(default_factory=dict) router_lifecycle_state: Dict[str, Any] = field(default_factory=dict) masters_state: Dict[str, Any] = field(default_factory=dict) pots_state: Dict[str, Any] = field(default_factory=dict) pending: Dict[str, Any] = field(default_factory=dict) show_citations: bool = True @classmethod def from_dict(cls, raw: Optional[Dict[str, Any]]) -> "UnifiedKnowledgebaseState": data = raw if isinstance(raw, dict) else {} mode = _norm_mode(data.get("mode")) last_mode = _norm_mode(data.get("last_mode")) if last_mode == "auto": last_mode = "router_docs" return cls( mode=mode, last_mode=last_mode, last_user_message=_norm(data.get("last_user_message", "")), router_docs_state=_as_dict(data.get("router_docs_state")), router_lifecycle_state=_as_dict(data.get("router_lifecycle_state")), masters_state=_as_dict(data.get("masters_state")), pots_state=_as_dict(data.get("pots_state")), pending=_as_dict(data.get("pending")), show_citations=bool(data.get("show_citations", True)), ) def to_dict(self) -> Dict[str, Any]: return { "mode": self.mode, "last_mode": self.last_mode, "last_user_message": _norm(self.last_user_message), "router_docs_state": self.router_docs_state, "router_lifecycle_state": self.router_lifecycle_state, "masters_state": self.masters_state, "pots_state": self.pots_state, "pending": self.pending, "show_citations": bool(self.show_citations), } class UnifiedKnowledgebaseCore: def __init__( self, *, router_rag_core: Any, router_core: Any, masters_core: Any, pots_core: Any, rapid_router_catalog_provider: Optional[Callable[[], Dict[str, Any]]] = None, rapid_router_intelligence_provider: Optional[Callable[[], Any]] = None, openai_api_key: str = "", openai_model: str = "gpt-5-mini", ) -> None: self.router_rag_core = router_rag_core self.router_core = router_core self.masters_core = masters_core self.pots_core = pots_core self._rapid_router_catalog_provider = rapid_router_catalog_provider if callable(rapid_router_catalog_provider) else None self._rapid_router_intelligence_provider = ( rapid_router_intelligence_provider if callable(rapid_router_intelligence_provider) else None ) self._rapid_router_catalog_cache_ttl_s = max( 1.0, float(os.getenv("UNIFIED_KB_RAPID_ROUTER_CATALOG_CACHE_TTL_S", "5.0") or 5.0) ) self._rapid_router_catalog_cache: Dict[str, Any] = {"config": {}, "products": []} self._rapid_router_catalog_cache_expires_at = 0.0 self._rapid_router_seed_assets_dir = Path(__file__).resolve().parent.parent / "rapid_router" / "seed" / "assets" self._rapid_router_seed_pdf_text_cache: Dict[str, str] = {} self.openai_model = str(openai_model or "gpt-5-mini") self.concept_fallback_enabled = _env_bool("UNIFIED_KB_CONCEPT_FALLBACK_ENABLED", True) self.concept_fallback_model = str( os.getenv("UNIFIED_KB_CONCEPT_FALLBACK_MODEL", "gpt-5-mini") or "gpt-5-mini" ).strip() self.concept_fallback_timeout_s = max( 1.0, min(6.0, float(os.getenv("UNIFIED_KB_CONCEPT_FALLBACK_TIMEOUT_S", "4.0") or 4.0)), ) self.fallback_extra_budget_s = max( 0.0, min(8.0, float(os.getenv("UNIFIED_KB_FALLBACK_EXTRA_BUDGET_S", "4.0") or 4.0)), ) self.client = OpenAI(api_key=openai_api_key) if (OpenAI is not None and str(openai_api_key or "").strip()) else None self.web_timeout_s_by_domain = { "router_docs": float(os.getenv("UNIFIED_KB_WEB_TIMEOUT_ROUTER_DOCS_S", "5.2") or 5.2), "masters": float(os.getenv("UNIFIED_KB_WEB_TIMEOUT_MASTERS_S", "4.8") or 4.8), "pots": float(os.getenv("UNIFIED_KB_WEB_TIMEOUT_POTS_S", "4.8") or 4.8), } self.web_timeout_extended_s = float(os.getenv("UNIFIED_KB_WEB_TIMEOUT_EXTENDED_S", "8.0") or 8.0) self.router_fact_fast_path_enabled = _env_bool("UNIFIED_KB_ROUTER_FACT_FAST_PATH", True) self.lifecycle_fast_path_enabled = _env_bool("UNIFIED_KB_LIFECYCLE_FAST_PATH", True) self.cache_enabled = _env_bool("UNIFIED_KB_CACHE_ENABLED", True) self.target_max_s = max(3.0, float(os.getenv("UNIFIED_KB_TARGET_MAX_S", "10.0") or 10.0)) self.hard_timeout_s = max(8.0, min(60.0, float(os.getenv("UNIFIED_KB_HARD_TIMEOUT_S", "20.0") or 20.0))) self.soft_concise_s = max( 3.0, min( self.hard_timeout_s - 1.0, float(os.getenv("UNIFIED_KB_SOFT_CONCISE_S", "10.0") or 10.0), ), ) self.normal_answer_char_limit = max( 900, int(os.getenv("UNIFIED_KB_NORMAL_ANSWER_CHAR_LIMIT", "2600") or 2600), ) self.concise_answer_char_limit = max( 520, int(os.getenv("UNIFIED_KB_CONCISE_ANSWER_CHAR_LIMIT", "1500") or 1500), ) self.section_item_limit = max(2, int(os.getenv("UNIFIED_KB_SECTION_ITEM_LIMIT", "4") or 4)) self.max_table_rows = max(3, int(os.getenv("UNIFIED_KB_MAX_TABLE_ROWS", "8") or 8)) self.cache_ttl_s = max(20.0, float(os.getenv("UNIFIED_KB_CACHE_TTL_S", "600") or 600.0)) self.cache_max_items = max(32, int(os.getenv("UNIFIED_KB_CACHE_MAX_ITEMS", "512") or 512)) self.max_files_per_response = max(4, int(os.getenv("UNIFIED_KB_MAX_FILES_PER_RESPONSE", "24") or 24)) self.fast_timeout_s_by_domain = { "router_docs": max(0.5, float(os.getenv("UNIFIED_KB_FAST_TIMEOUT_ROUTER_DOCS_S", "8.5") or 8.5)), "router_lifecycle": max(0.5, float(os.getenv("UNIFIED_KB_FAST_TIMEOUT_ROUTER_LIFECYCLE_S", "8.0") or 8.0)), "masters": max(0.5, float(os.getenv("UNIFIED_KB_FAST_TIMEOUT_MASTERS_S", "8.5") or 8.5)), "pots": max(0.5, float(os.getenv("UNIFIED_KB_FAST_TIMEOUT_POTS_S", "9.0") or 9.0)), } self.strict_model_alias_normalization_enabled = _env_bool("UNIFIED_KB_STRICT_MODEL_ALIAS_NORMALIZATION", True) self.clarify_bypass_high_confidence_enabled = _env_bool("UNIFIED_KB_CLARIFY_BYPASS_HIGH_CONFIDENCE", False) self.clarify_bypass_min_confidence = max( 0.5, min(0.99, float(os.getenv("UNIFIED_KB_CLARIFY_BYPASS_MIN_CONFIDENCE", "0.92") or 0.92)) ) self.query_complexity_budgeting_enabled = _env_bool("UNIFIED_KB_QUERY_COMPLEXITY_BUDGETING", False) self.query_complexity_medium_factor = max( 0.5, min(1.0, float(os.getenv("UNIFIED_KB_COMPLEXITY_MEDIUM_BUDGET_FACTOR", "0.90") or 0.90)) ) self.query_complexity_heavy_factor = max( 0.4, min(1.0, float(os.getenv("UNIFIED_KB_COMPLEXITY_HEAVY_BUDGET_FACTOR", "0.78") or 0.78)) ) self.query_complexity_budget_floor_s = max( 3.5, float(os.getenv("UNIFIED_KB_COMPLEXITY_BUDGET_FLOOR_S", "6.0") or 6.0) ) self.phase_circuit_breaker_enabled = _env_bool("UNIFIED_KB_PHASE_CIRCUIT_BREAKER_ENABLED", False) self.phase_circuit_breaker_s = max( 0.3, float(os.getenv("UNIFIED_KB_PHASE_CIRCUIT_BREAKER_S", "1.8") or 1.8) ) self.phase_circuit_breaker_min_remaining_s = max( 0.1, float(os.getenv("UNIFIED_KB_PHASE_CIRCUIT_BREAKER_MIN_REMAINING_S", "0.8") or 0.8) ) self.pots_fast_core_first_enabled = _env_bool("UNIFIED_KB_POTS_FAST_CORE_FIRST_ENABLED", False) self.pots_core_expand_min_remaining_s = max( 0.4, float(os.getenv("UNIFIED_KB_POTS_CORE_EXPAND_MIN_REMAINING_S", "2.0") or 2.0) ) self.pots_heavy_cache_enabled = _env_bool("UNIFIED_KB_POTS_HEAVY_CACHE_ENABLED", False) self.pots_heavy_cache_ttl_s = max(20.0, float(os.getenv("UNIFIED_KB_POTS_HEAVY_CACHE_TTL_S", "180") or 180.0)) self.pots_heavy_cache_max_items = max( 32, int(os.getenv("UNIFIED_KB_POTS_HEAVY_CACHE_MAX_ITEMS", "256") or 256) ) self.low_time_fallback_template_enabled = _env_bool("UNIFIED_KB_LOW_TIME_TEMPLATE_ENABLED", True) self.web_skip_prefilter_quorum_enabled = _env_bool("UNIFIED_KB_WEB_SKIP_PREFILTER_QUORUM", True) self.catalog_token_synonyms_enabled = _env_bool("UNIFIED_KB_CATALOG_TOKEN_SYNONYMS_ENABLED", True) self.parallel_search_enabled = _env_bool("UNIFIED_KB_PARALLEL_SEARCH_ENABLED", True) self.parallel_search_max_workers = max(1, min(8, int(os.getenv("UNIFIED_KB_PARALLEL_SEARCH_MAX_WORKERS", "4") or 4))) self.index_search_call_timeout_s = max(0.15, float(os.getenv("UNIFIED_KB_INDEX_SEARCH_CALL_TIMEOUT_S", "0.85") or 0.85)) self.parallel_search_shared_executor = _env_bool("UNIFIED_KB_PARALLEL_SEARCH_SHARED_EXECUTOR", True) self.search_stage_budget_s_by_domain = { "pots": max(0.3, float(os.getenv("UNIFIED_KB_POTS_SEARCH_STAGE_BUDGET_S", "2.8") or 2.8)), "masters": max(0.3, float(os.getenv("UNIFIED_KB_MASTERS_SEARCH_STAGE_BUDGET_S", "2.6") or 2.6)), "router_docs": max(0.3, float(os.getenv("UNIFIED_KB_ROUTER_SEARCH_STAGE_BUDGET_S", "2.4") or 2.4)), } self.web_stage_budget_s_by_domain = { "router_docs": max(0.8, float(os.getenv("UNIFIED_KB_WEB_STAGE_ROUTER_DOCS_S", "3.8") or 3.8)), "masters": max(0.8, float(os.getenv("UNIFIED_KB_WEB_STAGE_MASTERS_S", "3.0") or 3.0)), "pots": max(0.8, float(os.getenv("UNIFIED_KB_WEB_STAGE_POTS_S", "3.0") or 3.0)), } self.runtime_telemetry_enabled = _env_bool("UNIFIED_KB_RUNTIME_TELEMETRY_ENABLED", True) self.runtime_telemetry_path = Path( str(os.getenv("UNIFIED_KB_RUNTIME_TELEMETRY_PATH", str(_RUNTIME_TELEMETRY_DEFAULT)) or str(_RUNTIME_TELEMETRY_DEFAULT)) ) self.runtime_telemetry_slow_ms = max(250.0, float(os.getenv("UNIFIED_KB_RUNTIME_TELEMETRY_SLOW_MS", "8000") or 8000.0)) self.runtime_telemetry_weak_min_sources = max( 0, int(os.getenv("UNIFIED_KB_RUNTIME_TELEMETRY_WEAK_MIN_SOURCES", "1") or 1) ) self.router_workbook_feedback_enabled = _env_bool("UNIFIED_KB_ROUTER_WORKBOOK_FEEDBACK_ENABLED", True) self.router_workbook_feedback_path = Path( str( os.getenv( "UNIFIED_KB_ROUTER_WORKBOOK_FEEDBACK_PATH", str(_ROUTER_WORKBOOK_FEEDBACK_DEFAULT), ) or str(_ROUTER_WORKBOOK_FEEDBACK_DEFAULT) ) ) self._runtime_telemetry_lock = threading.Lock() self._runtime_telemetry_events = 0 self._runtime_telemetry_slow_events = 0 self._runtime_telemetry_weak_events = 0 self._router_workbook_feedback_lock = threading.Lock() self._router_workbook_feedback_events = 0 self._response_cache: Dict[str, Dict[str, Any]] = {} self._response_cache_order: List[str] = [] self._cache_hits = 0 self._cache_misses = 0 self._pots_heavy_cache: Dict[str, Dict[str, Any]] = {} self._pots_heavy_cache_order: List[str] = [] self._pots_heavy_cache_hits = 0 self._pots_heavy_cache_misses = 0 self._search_executor: Optional[ThreadPoolExecutor] = None if self.parallel_search_shared_executor and self.parallel_search_enabled: self._search_executor = ThreadPoolExecutor( max_workers=int(self.parallel_search_max_workers), thread_name_prefix="unified-kb-search", ) self.source_relevance_cache_ttl_s = max( 30.0, float(os.getenv("UNIFIED_KB_SOURCE_RELEVANCE_CACHE_TTL_S", "300") or 300.0) ) self.source_relevance_cache_max_items = max( 64, int(os.getenv("UNIFIED_KB_SOURCE_RELEVANCE_CACHE_MAX_ITEMS", "1024") or 1024) ) self._source_relevance_cache: Dict[str, Dict[str, Any]] = {} self._source_relevance_cache_order: List[str] = [] # L2 intent/model cache across session. self.l2_cache_ttl_s = max(60.0, float(os.getenv("UNIFIED_KB_L2_CACHE_TTL_S", "1800") or 1800.0)) self.l2_cache_max_items = max(128, int(os.getenv("UNIFIED_KB_L2_CACHE_MAX_ITEMS", "4096") or 4096)) self._l2_cache: Dict[str, Dict[str, Any]] = {} self._l2_cache_order: List[str] = [] self.faq_enabled = _env_bool("UNIFIED_KB_FAQ_ENABLED", True) self.faq_csv_path = Path(str(os.getenv("UNIFIED_KB_FAQ_CSV_PATH", str(_FAQ_DEFAULT_CSV)) or str(_FAQ_DEFAULT_CSV))) self.faq_index_path = Path(str(os.getenv("UNIFIED_KB_FAQ_INDEX_PATH", str(_FAQ_DEFAULT_INDEX)) or str(_FAQ_DEFAULT_INDEX))) self.faq_ongoing_path = Path( str(os.getenv("UNIFIED_KB_FAQ_ONGOING_CANDIDATES_PATH", str(_FAQ_DEFAULT_ONGOING)) or str(_FAQ_DEFAULT_ONGOING)) ) self.router_pricing_catalog_path = Path( str(os.getenv("UNIFIED_KB_ROUTER_PRICING_CATALOG_PATH", str(_ROUTER_PRICING_CATALOG_DEFAULT)) or str(_ROUTER_PRICING_CATALOG_DEFAULT)) ) self.router_variant_options_path = Path( str(os.getenv("UNIFIED_KB_ROUTER_VARIANT_OPTIONS_PATH", str(_ROUTER_VARIANT_OPTIONS_DEFAULT)) or str(_ROUTER_VARIANT_OPTIONS_DEFAULT)) ) self.parsec_pricing_path = Path( str(os.getenv("UNIFIED_KB_PARSEC_PRICING_PATH", str(_PARSEC_PRICING_DEFAULT)) or str(_PARSEC_PRICING_DEFAULT)) ) self.peplink_replacement_overlay_path = Path( str(os.getenv("UNIFIED_KB_PEPLINK_REPLACEMENT_OVERLAY_PATH", str(_PEPLINK_REPLACEMENT_OVERLAY_DEFAULT)) or str(_PEPLINK_REPLACEMENT_OVERLAY_DEFAULT)) ) self.router_missing_fields_audit_path = Path( str(os.getenv("UNIFIED_KB_ROUTER_MISSING_FIELDS_AUDIT_PATH", str(_ROUTER_MISSING_FIELDS_AUDIT_DEFAULT)) or str(_ROUTER_MISSING_FIELDS_AUDIT_DEFAULT)) ) self.max_clarify_turns = max(1, min(2, int(os.getenv("UNIFIED_KB_MAX_CLARIFY_TURNS", "2") or 2))) self.faq_high_threshold = max(0.5, min(0.99, float(os.getenv("UNIFIED_KB_FAQ_HIGH_THRESHOLD", "0.86") or 0.86))) self.faq_clarify_threshold = max( 0.4, min(self.faq_high_threshold - 0.01, float(os.getenv("UNIFIED_KB_FAQ_CLARIFY_THRESHOLD", "0.72") or 0.72)) ) self.faq_medium_answer_bypass_enabled = _env_bool("UNIFIED_KB_FAQ_MEDIUM_BYPASS_ENABLED", True) self.faq_medium_answer_bypass_min_score = max( float(self.faq_clarify_threshold), min( float(self.faq_high_threshold), float(os.getenv("UNIFIED_KB_FAQ_MEDIUM_BYPASS_MIN_SCORE", "0.80") or 0.80), ), ) self.faq_medium_answer_bypass_domains = { _norm_mode(x.strip()) for x in str(os.getenv("UNIFIED_KB_FAQ_MEDIUM_BYPASS_DOMAINS", "masters,pots") or "masters,pots").split(",") if _norm_mode(x.strip()) in {"masters", "pots", "router_docs", "router_lifecycle"} } self.faq_top_k = max(1, min(8, int(os.getenv("UNIFIED_KB_FAQ_TOP_K", "4") or 4))) self.faq_max_parts = max(1, min(6, int(os.getenv("UNIFIED_KB_FAQ_MAX_PARTS", "4") or 4))) self.faq_index_token_min_len = max(2, min(6, int(os.getenv("UNIFIED_KB_FAQ_INDEX_TOKEN_MIN_LEN", "3") or 3))) self.faq_candidate_cap = max(24, int(os.getenv("UNIFIED_KB_FAQ_CANDIDATE_CAP", "220") or 220)) self._faq_entries: List[FaqEntry] = [] self._faq_entries_by_id: Dict[str, FaqEntry] = {} self._faq_entries_by_norm: Dict[str, FaqEntry] = {} self._faq_token_index: Dict[str, set[str]] = {} self._faq_entity_index: Dict[str, set[str]] = {} self._faq_hash = "" self._faq_duplicate_count = 0 self._faq_log_queue_max = max(32, int(os.getenv("UNIFIED_KB_FAQ_LOG_QUEUE_MAX", "1024") or 1024)) self._faq_log_batch_size = max(1, int(os.getenv("UNIFIED_KB_FAQ_LOG_BATCH_SIZE", "16") or 16)) self._faq_log_queue: Optional["queue.Queue[Optional[Dict[str, Any]]]"] = None self._faq_log_thread: Optional[threading.Thread] = None self.file_map_refresh_ttl_s = max(5.0, float(os.getenv("UNIFIED_KB_FILE_MAP_REFRESH_TTL_S", "60") or 60.0)) self._file_map_refreshed_at = 0.0 self._masters_file_map: Dict[str, str] = {} self._pots_file_map: Dict[str, str] = {} self._router_file_map: Dict[str, str] = {} self._masters_mention_title_cache: Dict[str, List[Tuple[str, str, str]]] = {} self._refresh_file_maps() self._router_fact_csv_paths = self._resolve_router_fact_csv_paths() self._router_alias_map = self._build_router_alias_map() self._router_fact_rows = self._build_router_fact_index() self._router_lifecycle_rows = self._build_router_lifecycle_index() self._prune_alias_overrides_with_exact_rows() self._router_fact_trie = self._build_model_trie(self._router_fact_rows.keys()) self._router_lifecycle_trie = self._build_model_trie(self._router_lifecycle_rows.keys()) self._router_token_model_index = self._build_router_token_model_index() self._router_variant_rows = self._load_router_variant_rows() self._router_variant_index = self._build_router_variant_index(self._router_variant_rows) self._verizon_gateway_detail_cache = self._build_verizon_gateway_detail_cache() self._parsec_price_rows = self._load_parsec_price_rows() self._parsec_family_index = self._build_parsec_family_index(self._parsec_price_rows) self._router_missing_fields_rows = self._load_router_missing_fields_rows() self._peplink_overlay_rows = self._load_peplink_overlay_rows() self._router_fast_subsets = self._build_router_fast_subsets() self._pots_provider_cards = self._build_pots_provider_cards() self._pots_provider_evidence_cards = self._build_pots_provider_evidence_cards() self._router_catalog_token_synonyms = self._build_router_catalog_token_synonyms() self._router_model_synonyms = self._build_router_model_synonyms() if self.catalog_token_synonyms_enabled: self._router_alias_map.update(self._router_catalog_token_synonyms) self._router_alias_map.update(self._router_model_synonyms) self._prune_alias_overrides_with_exact_rows() self._router_token_model_index = self._build_router_token_model_index() spell_enabled = _env_bool("UNIFIED_KB_SPELLCHECK_ENABLED", True) spell_min_ratio = int(os.getenv("UNIFIED_KB_SPELLCHECK_MIN_RATIO", "90") or 90) domain_terms = set() for seq in ( _ROUTER_DOC_HINTS, _ROUTER_LIFECYCLE_HINTS, _ROUTER_PLATFORM_HINTS, _POTS_HINTS, _POTS_CONTEXT_HINTS, _POTS_PROVIDER_SUMMARY_HINTS, _MASTERS_HINTS, _MASTERS_FAST_OUTLINE_HINTS, ): for t in seq: for token in re.findall(r"[a-z0-9][a-z0-9&\-']*", str(t or "").lower()): if len(token) >= 3: domain_terms.add(token) protected = set(self._router_fact_rows.keys()) | set(self._router_lifecycle_rows.keys()) | set(self._router_alias_map.keys()) protected.update(_ROUTER_NON_DEVICE_TERMS) self._query_normalizer = QueryNormalizer( protected_terms=protected, domain_terms=domain_terms, enabled=spell_enabled, min_ratio=spell_min_ratio, ) self._load_faq_entries() @property def prompt_version(self) -> str: return PROMPT_VERSION def _build_masters_mention_title_cache(self) -> Dict[str, List[Tuple[str, str, str]]]: files = [str(x) for x in self._masters_file_map.values() if str(x)] targets: Dict[str, Tuple[Tuple[str, ...], ...]] = { "buss skus": (("buss", "sku"),), "securefax": (("securefax",),), "ifax": (("ifax",),), "pro install": (("pro", "install"),), "sip accounts": (("sip", "accounts"),), "b360 order flow": (("b360", "order", "flow"),), "ot support": (("ot", "support"),), "pots replacement": (("pots", "replacement"),), "contact center": (("contact", "center"),), "dataremote": (("dataremote",),), } cache: Dict[str, List[Tuple[str, str, str]]] = {} for target, match_groups in targets.items(): rows: List[Tuple[str, str, str]] = [] for rel in files: name = Path(str(rel)).name blob = re.sub(r"[^a-z0-9]+", " ", name.lower()).strip() if not any(all(tok in blob for tok in group if tok) for group in match_groups): continue rows.append((name, f"Document title includes `{target}`.", "")) if len(rows) >= 8: break if rows: cache[target] = rows return cache def _resolve_masters_canonical_doc(self, mention_target: str) -> Optional[Tuple[str, str, str]]: target = str(mention_target or "").strip().lower() if not target: return None if target == "buss skus": preferred_name = "All BuSS Sku's 2025.pdf" rel = self._masters_file_map.get(preferred_name.lower()) if rel: return (preferred_name, "Canonical Masters SKU exhibit reference.", "") flyers = getattr(self.masters_core, "flyers", None) if not isinstance(flyers, dict): return None preferred_name = str(flyers.get(target) or "").strip() if not preferred_name: return None rel = self._masters_file_map.get(preferred_name.lower()) if not rel: return None doc_name = Path(str(rel)).name return (doc_name, f"Canonical Masters flyer mapping for `{target}` resolved directly.", "") def _refresh_file_maps(self, *, force: bool = False) -> None: now = time.perf_counter() if (not force) and self._file_map_refreshed_at and ((now - self._file_map_refreshed_at) < self.file_map_refresh_ttl_s): return masters_files = getattr(getattr(self.masters_core, "index", None), "files", []) or [] if not masters_files: data_dir = Path(str(getattr(self.masters_core, "data_dir", "") or "")) if data_dir.exists() and data_dir.is_dir(): masters_files = [p.name for p in sorted(data_dir.iterdir()) if p.is_file()] if masters_files: new_map = {Path(str(f)).name.lower(): str(f) for f in masters_files} if new_map != self._masters_file_map: self._masters_file_map = new_map self._masters_mention_title_cache = self._build_masters_mention_title_cache() pots_files: List[str] = [] try: pots_files = self.pots_core.list_files() or [] except Exception: pots_files = [] if pots_files: self._pots_file_map = {Path(str(f)).name.lower(): str(f) for f in pots_files} router_files: List[str] = [] try: router_files = self.router_rag_core.list_files() or [] except Exception: router_files = [] if router_files: self._router_file_map = {Path(str(f)).name.lower(): str(f) for f in router_files} self._file_map_refreshed_at = now def list_files(self) -> Dict[str, List[str]]: masters = sorted({_mounted_file_href("/masters_files", str(v)) for v in self._masters_file_map.values()}) pots = sorted({_mounted_file_href("/pots_files", str(v)) for v in self._pots_file_map.values()}) router_docs = sorted({_mounted_file_href("/router_rag_files", str(v)) for v in self._router_file_map.values()}) router_lifecycle: List[str] = [] eos_csv = Path(str(getattr(self.router_core, "eos_csv_path", "") or "")).name if eos_csv: router_lifecycle.append(eos_csv) for p in self._router_fact_csv_paths: router_lifecycle.append(p.name) if not router_lifecycle: router_lifecycle = ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"] else: router_lifecycle = list(dict.fromkeys(router_lifecycle)) return { "masters": masters, "pots": pots, "router_docs": router_docs, "router_lifecycle": router_lifecycle, } def _should_bypass_router_lifecycle_spellcheck(self, message: str) -> bool: raw = str(message or "") if not raw.strip(): return False router_core = getattr(self, "router_core", None) looks_like_inventory = getattr(router_core, "_looks_like_inventory_request", None) if callable(looks_like_inventory): try: if bool(looks_like_inventory(raw)): return True except Exception: pass parse_inventory_items = getattr(router_core, "_parse_inventory_items", None) if callable(parse_inventory_items): try: parsed_items = [item for item in list(parse_inventory_items(raw) or []) if isinstance(item, tuple)] except Exception: parsed_items = [] if len(parsed_items) >= 2: return True if ":" in raw: tail = str(raw.rsplit(":", 1)[-1] or "").strip() if tail and re.search(r"(^|\s)\d[\d,]*\s+\S+", tail): return True return False def normalize_query(self, message: str, *, mode: str = "auto") -> Dict[str, Any]: raw = str(message or "") mode_hint = _norm_mode(mode) if mode_hint == "router_lifecycle" and self._should_bypass_router_lifecycle_spellcheck(raw): return { "original_message": raw, "normalized_message": raw, "changed": False, "corrections": [], "mode_hint": mode_hint, } normalized, corrections = self._query_normalizer.normalize(raw) normalized = _norm(normalized or raw) changed = _norm(raw) != _norm(normalized) return { "original_message": raw, "normalized_message": normalized or raw, "changed": bool(changed), "corrections": corrections[:16], "mode_hint": mode_hint, } def health(self) -> Dict[str, Any]: data: Dict[str, Any] = {"ok": True, "prompt_version": PROMPT_VERSION, "domains": {}} try: data["domains"]["router_docs"] = self.router_rag_core.health() except Exception as exc: data["domains"]["router_docs"] = {"ok": False, "error": f"{type(exc).__name__}: {exc}"} data["ok"] = False try: data["domains"]["router_lifecycle"] = { "ok": True, "eos_csv": str(getattr(self.router_core, "eos_csv_path", "")), "routers_csv": str(getattr(self.router_core, "dec_csv_path", "")), } except Exception as exc: data["domains"]["router_lifecycle"] = {"ok": False, "error": f"{type(exc).__name__}: {exc}"} data["ok"] = False try: idx = getattr(self.masters_core, "index", None) idx_health = idx.health() if idx is not None and hasattr(idx, "health") else {} data["domains"]["masters"] = { "ok": True, "index": idx_health, "files": getattr(idx, "files", []) if idx is not None else [], } except Exception as exc: data["domains"]["masters"] = {"ok": False, "error": f"{type(exc).__name__}: {exc}"} data["ok"] = False try: data["domains"]["pots"] = self.pots_core.health() except Exception as exc: data["domains"]["pots"] = {"ok": False, "error": f"{type(exc).__name__}: {exc}"} data["ok"] = False rapid_router_snapshot = self._rapid_router_catalog_snapshot(force_refresh=False) rapid_router_workbook_status = self._rapid_router_intelligence_status() rapid_router_products = list(rapid_router_snapshot.get("products") or []) if isinstance(rapid_router_snapshot, dict) else [] data["runtime"] = { "cache_enabled": bool(self.cache_enabled), "cache_ttl_s": float(self.cache_ttl_s), "cache_max_items": int(self.cache_max_items), "max_files_per_response": int(self.max_files_per_response), "target_max_s": float(self.target_max_s), "openai_model": str(self.openai_model), "concept_fallback_enabled": bool(self.concept_fallback_enabled), "concept_fallback_model": str(self.concept_fallback_model), "concept_fallback_timeout_s": float(self.concept_fallback_timeout_s), "fallback_extra_budget_s": float(self.fallback_extra_budget_s), "cache_size": int(len(self._response_cache)), "cache_hits": int(self._cache_hits), "cache_misses": int(self._cache_misses), "pots_heavy_cache_enabled": bool(self.pots_heavy_cache_enabled), "pots_heavy_cache_hits": int(self._pots_heavy_cache_hits), "pots_heavy_cache_misses": int(self._pots_heavy_cache_misses), "l2_cache_ttl_s": float(self.l2_cache_ttl_s), "l2_cache_size": int(len(self._l2_cache)), "parallel_search_enabled": bool(self.parallel_search_enabled), "parallel_search_max_workers": int(self.parallel_search_max_workers), "parallel_search_shared_executor": bool(self.parallel_search_shared_executor), "parallel_search_executor_ready": bool(self._search_executor is not None), "phase_circuit_breaker_enabled": bool(self.phase_circuit_breaker_enabled), "index_search_call_timeout_s": float(self.index_search_call_timeout_s), "search_stage_budget_s_by_domain": dict(self.search_stage_budget_s_by_domain), "web_stage_budget_s_by_domain": dict(self.web_stage_budget_s_by_domain), "query_complexity_budgeting_enabled": bool(self.query_complexity_budgeting_enabled), "pots_fast_core_first_enabled": bool(self.pots_fast_core_first_enabled), "clarify_bypass_high_confidence_enabled": bool(self.clarify_bypass_high_confidence_enabled), "web_skip_prefilter_quorum_enabled": bool(self.web_skip_prefilter_quorum_enabled), "strict_model_alias_normalization_enabled": bool(self.strict_model_alias_normalization_enabled), "runtime_telemetry_enabled": bool(self.runtime_telemetry_enabled), "runtime_telemetry_path": str(self.runtime_telemetry_path), "runtime_telemetry_slow_ms": float(self.runtime_telemetry_slow_ms), "runtime_telemetry_events": int(self._runtime_telemetry_events), "runtime_telemetry_slow_events": int(self._runtime_telemetry_slow_events), "runtime_telemetry_weak_events": int(self._runtime_telemetry_weak_events), "router_workbook_feedback_enabled": bool(self.router_workbook_feedback_enabled), "router_workbook_feedback_path": str(self.router_workbook_feedback_path), "router_workbook_feedback_events": int(self._router_workbook_feedback_events), "router_fact_rows": int(len(self._router_fact_rows)), "router_lifecycle_rows": int(len(self._router_lifecycle_rows)), "router_variant_rows": int(len(self._router_variant_rows)), "parsec_price_rows": int(len(self._parsec_price_rows)), "router_missing_fields_rows": int(len(self._router_missing_fields_rows)), "peplink_overlay_rows": int(len(self._peplink_overlay_rows)), "router_token_index_terms": int(len(self._router_token_model_index)), "router_synonyms": int(len(self._router_model_synonyms)), "router_catalog_token_synonyms": int(len(self._router_catalog_token_synonyms)), "max_clarify_turns": int(self.max_clarify_turns), "pots_provider_cards": int(len(self._pots_provider_cards)), "pots_provider_evidence_cards": int(len(self._pots_provider_evidence_cards)), "faq_enabled": bool(self.faq_enabled), "faq_entries": int(len(self._faq_entries)), "faq_duplicates_removed": int(self._faq_duplicate_count), "faq_high_threshold": float(self.faq_high_threshold), "faq_clarify_threshold": float(self.faq_clarify_threshold), "faq_medium_answer_bypass_enabled": bool(self.faq_medium_answer_bypass_enabled), "faq_medium_answer_bypass_min_score": float(self.faq_medium_answer_bypass_min_score), "faq_medium_answer_bypass_domains": sorted(self.faq_medium_answer_bypass_domains), "faq_index_token_min_len": int(self.faq_index_token_min_len), "faq_candidate_cap": int(self.faq_candidate_cap), "faq_norm_index_size": int(len(self._faq_entries_by_norm)), "faq_token_index_size": int(len(self._faq_token_index)), "faq_entity_index_size": int(len(self._faq_entity_index)), "faq_csv_path": str(self.faq_csv_path), "router_pricing_catalog_path": str(self.router_pricing_catalog_path), "router_variant_options_path": str(self.router_variant_options_path), "parsec_pricing_path": str(self.parsec_pricing_path), "peplink_replacement_overlay_path": str(self.peplink_replacement_overlay_path), "router_missing_fields_audit_path": str(self.router_missing_fields_audit_path), "router_fact_csv_paths": [str(p) for p in self._router_fact_csv_paths], "router_fact_csv_names": [p.name for p in self._router_fact_csv_paths], "rapid_router_catalog_provider_enabled": bool(callable(self._rapid_router_catalog_provider)), "rapid_router_catalog_products": int(len(rapid_router_products)), "rapid_router_workbook_provider_enabled": bool(callable(self._rapid_router_intelligence_provider)), "rapid_router_workbook_loaded": bool(rapid_router_workbook_status.get("loaded")), "rapid_router_workbook_products": int(rapid_router_workbook_status.get("product_count") or 0), "rapid_router_workbook_freshness": rapid_router_workbook_status.get("freshness"), "rapid_router_workbook_rollout_summary": self._router_workbook_rollout_summary(), } data["files"] = self.list_files() return data def _rapid_router_intelligence_core(self) -> Any | None: provider = self._rapid_router_intelligence_provider if not callable(provider): return None try: return provider() except Exception: return None def _rapid_router_intelligence_status(self) -> Dict[str, Any]: provider_enabled = bool(callable(self._rapid_router_intelligence_provider)) core = self._rapid_router_intelligence_core() if core is None or not hasattr(core, "get_catalog_status"): return { "provider_enabled": provider_enabled, "loaded": False, "product_count": 0, "replacement_count": 0, "quote_count": 0, "filename": "", "imported_at": "", "freshness": {}, "import_history": [], "import_count": 0, } try: status = _as_dict(core.get_catalog_status()) except Exception as exc: return { "provider_enabled": provider_enabled, "loaded": False, "product_count": 0, "replacement_count": 0, "quote_count": 0, "filename": "", "imported_at": "", "freshness": {}, "import_history": [], "import_count": 0, "error": f"{type(exc).__name__}: {exc}", } latest = _as_dict(status.get("latest_import")) freshness = _as_dict(status.get("freshness")) import_history = [item for item in list(status.get("import_history") or []) if isinstance(item, dict)] return { "provider_enabled": provider_enabled, "loaded": bool(status.get("loaded")), "product_count": int(status.get("product_count") or 0), "replacement_count": int(status.get("replacement_count") or 0), "quote_count": int(status.get("quote_count") or 0), "filename": str(latest.get("filename") or ""), "imported_at": str(latest.get("imported_at") or ""), "freshness": freshness, "import_history": import_history, "import_count": int(status.get("import_count") or len(import_history)), } def _normalize_faq_question(self, text: Any) -> str: value = _norm(str(text or "").lower()) replacements = { "speed fusion": "speedfusion", "in control 2": "incontrol2", "in control2": "incontrol2", "prime care": "primecare", "secure fax": "securefax", "secure-fax": "securefax", "wi fi": "wifi", "wi-fi": "wifi", "end of life": "eol", "end-of-life": "eol", "end of sale": "eos", "end-of-sale": "eos", } for src, dst in replacements.items(): value = value.replace(src, dst) value = re.sub(r"[^a-z0-9&+/#\-\s]", " ", value) value = re.sub(r"\s+", " ", value).strip() return value def _faq_entities(self, text: str) -> set[str]: entities: set[str] = set() low = str(text or "").lower() for model in _extract_router_models(text): mk = _compact_model(model) if mk: entities.add(mk) for term in _FAQ_PROVIDER_TERMS: if term in low: entities.add(_compact_model(term)) years = re.findall(r"(?:19|20)\d{2}", low) entities.update({f"Y{y}" for y in years}) return entities def _faq_token_set(self, text: str) -> set[str]: return set(_text_tokens(self._normalize_faq_question(text))) def _compute_faq_hash(self, entries: Sequence[FaqEntry]) -> str: material = "\n".join(f"{e.q_norm}\t{e.answer}" for e in entries) return hashlib.sha256(material.encode("utf-8")).hexdigest() def _build_faq_entries(self, rows: Sequence[Tuple[str, str]]) -> Tuple[List[FaqEntry], int]: by_norm: Dict[str, FaqEntry] = {} duplicate_count = 0 for idx, (question_raw, answer_raw) in enumerate(rows, start=1): question = _norm(question_raw) answer = _norm_preserve(answer_raw) if (not question) or (not answer): continue q_norm = self._normalize_faq_question(question) if not q_norm: continue candidate = FaqEntry( faq_id=f"FAQM-{idx:04d}", question=question, answer=answer, q_norm=q_norm, q_tokens=self._faq_token_set(question), entities=self._faq_entities(question), ) prev = by_norm.get(q_norm) if prev is None: by_norm[q_norm] = candidate continue duplicate_count += 1 # Keep the richer answer and preserve a stable id. if len(candidate.answer) > len(prev.answer): candidate.faq_id = prev.faq_id by_norm[q_norm] = candidate # Near-duplicate question merge. deduped: List[FaqEntry] = [] for entry in sorted(by_norm.values(), key=lambda e: (e.q_norm, e.faq_id)): keep = True for prior in deduped: lex = float(fuzz.token_set_ratio(entry.q_norm, prior.q_norm) / 100.0) if fuzz is not None else float( difflib.SequenceMatcher(None, entry.q_norm, prior.q_norm).ratio() ) if lex < 0.98: continue overlap = 0.0 if entry.q_tokens and prior.q_tokens: overlap = float(len(entry.q_tokens & prior.q_tokens)) / float(len(entry.q_tokens | prior.q_tokens)) if overlap >= 0.95: duplicate_count += 1 # Keep whichever has the richer answer. if len(entry.answer) > len(prior.answer): prior.answer = entry.answer prior.question = entry.question keep = False break if keep: deduped.append(entry) for idx, entry in enumerate(deduped, start=1): entry.faq_id = f"FAQM-{idx:04d}" return deduped, duplicate_count def _rebuild_faq_match_indexes(self, entries: Sequence[FaqEntry]) -> None: by_norm: Dict[str, FaqEntry] = {} token_idx: Dict[str, set[str]] = {} entity_idx: Dict[str, set[str]] = {} min_len = max(2, int(self.faq_index_token_min_len)) for e in entries: qn = str(e.q_norm or "").strip() if qn and qn not in by_norm: by_norm[qn] = e for tok in e.q_tokens: t = str(tok or "").strip().lower() if (not t) or (len(t) < min_len): continue token_idx.setdefault(t, set()).add(e.faq_id) for ent in e.entities: k = str(ent or "").strip().lower() if not k: continue entity_idx.setdefault(k, set()).add(e.faq_id) self._faq_entries_by_norm = by_norm self._faq_token_index = token_idx self._faq_entity_index = entity_idx def _load_faq_entries(self) -> None: self._faq_entries = [] self._faq_entries_by_id = {} self._faq_entries_by_norm = {} self._faq_token_index = {} self._faq_entity_index = {} self._faq_hash = "" self._faq_duplicate_count = 0 if not self.faq_enabled: return rows: List[Tuple[str, str]] = [] used_index = False if self.faq_index_path.exists(): try: csv_mtime = self.faq_csv_path.stat().st_mtime if self.faq_csv_path.exists() else 0.0 if self.faq_index_path.stat().st_mtime >= csv_mtime: with self.faq_index_path.open("r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue obj = json.loads(line) q = _norm(obj.get("question", "")) a = _norm_preserve(obj.get("answer", "")) if q and a: rows.append((q, a)) used_index = True except Exception: rows = [] used_index = False if (not rows) and self.faq_csv_path.exists(): try: with self.faq_csv_path.open("r", encoding="utf-8-sig", newline="") as f: reader = csv.DictReader(f) headers = {str(h).strip().lower(): str(h) for h in (reader.fieldnames or []) if str(h).strip()} q_col = headers.get("question", "") a_col = headers.get("updated answer", "") or headers.get("answer", "") if q_col and a_col: for row in reader: rows.append((_norm(row.get(q_col, "")), _norm_preserve(row.get(a_col, "")))) except Exception: rows = [] entries, duplicates = self._build_faq_entries(rows) self._faq_entries = entries self._faq_entries_by_id = {e.faq_id: e for e in entries} self._rebuild_faq_match_indexes(entries) self._faq_hash = self._compute_faq_hash(entries) self._faq_duplicate_count = int(duplicates) if entries and (not used_index): try: self.faq_index_path.parent.mkdir(parents=True, exist_ok=True) with self.faq_index_path.open("w", encoding="utf-8", newline="") as f: for e in entries: f.write( json.dumps( { "faq_id": e.faq_id, "question": e.question, "answer": e.answer, "q_norm": e.q_norm, "q_tokens": sorted(e.q_tokens), "entities": sorted(e.entities), }, ensure_ascii=False, ) + "\n" ) except Exception: pass def _faq_semantic_score(self, query_norm: str, candidate_norm: str, query_tokens: set[str], candidate_tokens: set[str]) -> float: seq = float(difflib.SequenceMatcher(None, query_norm, candidate_norm).ratio()) token_jaccard = 0.0 if query_tokens and candidate_tokens: token_jaccard = float(len(query_tokens & candidate_tokens)) / float(len(query_tokens | candidate_tokens)) return max(0.0, min(1.0, (0.55 * seq) + (0.45 * token_jaccard))) def _faq_query_features(self, query: str) -> Dict[str, Any]: q_norm = self._normalize_faq_question(query) return { "q_norm": q_norm, "q_tokens": self._faq_token_set(query), "q_entities": self._faq_entities(query), } def _faq_similarity( self, query: str, entry: FaqEntry, *, query_features: Optional[Dict[str, Any]] = None, ) -> Dict[str, float]: features = query_features or self._faq_query_features(query) q_norm = str(features.get("q_norm") or self._normalize_faq_question(query)) q_tokens = set(features.get("q_tokens") or self._faq_token_set(query)) q_entities = set(features.get("q_entities") or self._faq_entities(query)) lexical = float(fuzz.token_set_ratio(q_norm, entry.q_norm) / 100.0) if fuzz is not None else float( difflib.SequenceMatcher(None, q_norm, entry.q_norm).ratio() ) semantic = self._faq_semantic_score(q_norm, entry.q_norm, q_tokens, entry.q_tokens) entity = 0.0 if q_entities and entry.entities: entity = float(len(q_entities & entry.entities)) / float(len(q_entities | entry.entities)) score = (0.50 * lexical) + (0.35 * semantic) + (0.15 * entity) # Penalize mismatched model entities for lifecycle/spec/replacement style questions. q_models = {e for e in q_entities if any(ch.isdigit() for ch in e) and not e.startswith("Y")} e_models = {e for e in entry.entities if any(ch.isdigit() for ch in e) and not e.startswith("Y")} if q_models and e_models and not (q_models & e_models): score -= 0.20 elif q_models and (q_models & e_models): score += 0.05 score = max(0.0, min(1.0, score)) return { "score": score, "lexical": lexical, "semantic": semantic, "entity": entity, } def _faq_best_matches(self, query: str, limit: int = 4) -> List[Dict[str, Any]]: if (not self.faq_enabled) or (not self._faq_entries): return [] query_features = self._faq_query_features(query) q_norm = str(query_features.get("q_norm") or "") if q_norm: exact = self._faq_entries_by_norm.get(q_norm) if exact is not None: return [{"entry": exact, "score": 1.0, "lexical": 1.0, "semantic": 1.0, "entity": 1.0}] l2_key = f"faq_match::{self._faq_hash}::{q_norm}::{max(1, int(limit))}" cached_rows = self._l2_get(l2_key) if isinstance(cached_rows, list) and cached_rows: return list(cached_rows) q_tokens = set(query_features.get("q_tokens") or set()) q_entities = set(query_features.get("q_entities") or set()) min_len = max(2, int(self.faq_index_token_min_len)) candidate_weight: Dict[str, int] = {} for tok in q_tokens: t = str(tok or "").strip().lower() if (not t) or (len(t) < min_len): continue for faq_id in self._faq_token_index.get(t, set()): candidate_weight[faq_id] = int(candidate_weight.get(faq_id, 0)) + 1 for ent in q_entities: k = str(ent or "").strip().lower() if not k: continue for faq_id in self._faq_entity_index.get(k, set()): candidate_weight[faq_id] = int(candidate_weight.get(faq_id, 0)) + 2 candidate_entries: List[FaqEntry] if candidate_weight: # Favor rows with stronger token/entity overlap and keep a bounded candidate set. ranked_ids = sorted(candidate_weight.items(), key=lambda kv: (-int(kv[1]), kv[0])) cap = max(24, int(self.faq_candidate_cap)) candidate_entries = [] for faq_id, _w in ranked_ids[:cap]: e = self._faq_entries_by_id.get(str(faq_id)) if e is not None: candidate_entries.append(e) else: candidate_entries = list(self._faq_entries) rows: List[Dict[str, Any]] = [] for entry in candidate_entries: sim = self._faq_similarity(query, entry, query_features=query_features) rows.append({"entry": entry, **sim}) # Safety fallback: if narrowed candidate set looks weak, run full scan once. if candidate_weight: best_score = max((float(r.get("score", 0.0)) for r in rows), default=0.0) if best_score < min(0.72, float(self.faq_clarify_threshold)): rows = [] for entry in self._faq_entries: sim = self._faq_similarity(query, entry, query_features=query_features) rows.append({"entry": entry, **sim}) limit_k = max(1, int(limit)) if limit_k >= len(rows): rows.sort( key=lambda x: ( -float(x.get("score", 0.0)), -float(x.get("entity", 0.0)), -float(x.get("lexical", 0.0)), str(getattr(x.get("entry"), "faq_id", "")), ) ) self._l2_set(l2_key, list(rows)) return rows top_rows = heapq.nlargest( limit_k, rows, key=lambda x: ( float(x.get("score", 0.0)), float(x.get("entity", 0.0)), float(x.get("lexical", 0.0)), ), ) top_rows.sort( key=lambda x: ( -float(x.get("score", 0.0)), -float(x.get("entity", 0.0)), -float(x.get("lexical", 0.0)), str(getattr(x.get("entry"), "faq_id", "")), ) ) self._l2_set(l2_key, list(top_rows)) return top_rows def _faq_split_subquestions(self, message: str) -> List[str]: text = _norm_preserve(message) if not text: return [] parts: List[str] = [] for chunk in _FAQ_SPLIT_RE.split(text): c = _norm(chunk) if not c: continue sub_parts = re.split( r"\band\b(?=\s*(?:is|are|does|do|what|which|how|can|tell|summarize|compare|give|list)\b)", c, flags=re.IGNORECASE, ) for sub in sub_parts: cleaned = _norm(sub).strip(" .") if cleaned: parts.append(cleaned) out: List[str] = [] seen: set[str] = set() for p in parts: key = self._normalize_faq_question(p) if (not key) or (key in seen): continue seen.add(key) out.append(p) if len(out) >= int(self.faq_max_parts): break return out[: int(self.faq_max_parts)] or [text] def _faq_log_worker_loop(self) -> None: while True: try: item = self._faq_log_queue.get(timeout=0.25) except queue.Empty: continue if item is None: return batch: List[Dict[str, Any]] = [item] for _ in range(max(0, int(self._faq_log_batch_size) - 1)): try: nxt = self._faq_log_queue.get_nowait() except queue.Empty: break if nxt is None: return batch.append(nxt) self._faq_write_ongoing_rows(batch) def _faq_write_ongoing_rows(self, rows: Sequence[Dict[str, Any]]) -> None: if not rows: return try: self.faq_ongoing_path.parent.mkdir(parents=True, exist_ok=True) exists = self.faq_ongoing_path.exists() with self.faq_ongoing_path.open("a", encoding="utf-8", newline="") as f: w = csv.writer(f) if not exists: w.writerow( [ "timestamp_utc", "domain", "reason", "question", "best_faq_id", "best_question", "score", "lexical", "semantic", "entity", ] ) for row in rows: w.writerow( [ str(row.get("timestamp_utc") or time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())), str(row.get("domain") or ""), str(row.get("reason") or ""), str(row.get("question") or ""), str(row.get("best_faq_id") or ""), str(row.get("best_question") or ""), row.get("score", ""), row.get("lexical", ""), row.get("semantic", ""), row.get("entity", ""), ] ) except Exception: return def _faq_log_ongoing_candidate(self, *, domain: str, question: str, reason: str, best: Optional[Dict[str, Any]]) -> None: try: best_entry = best.get("entry") if isinstance(best, dict) else None payload = { "timestamp_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "domain": str(domain or ""), "reason": str(reason or ""), "question": _norm_preserve(question), "best_faq_id": getattr(best_entry, "faq_id", "") if best_entry is not None else "", "best_question": getattr(best_entry, "question", "") if best_entry is not None else "", "score": round(float((best or {}).get("score", 0.0)), 4) if isinstance(best, dict) else "", "lexical": round(float((best or {}).get("lexical", 0.0)), 4) if isinstance(best, dict) else "", "semantic": round(float((best or {}).get("semantic", 0.0)), 4) if isinstance(best, dict) else "", "entity": round(float((best or {}).get("entity", 0.0)), 4) if isinstance(best, dict) else "", } # Keep FAQ candidate logging synchronous. This path is low-volume, and avoiding # per-instance background log workers prevents shutdown hangs in short-lived # processes like test and release-gate runs. self._faq_write_ongoing_rows([payload]) except Exception: return def _runtime_telemetry_log( self, *, message: str, domain: str, result_meta: Dict[str, Any], sources: Sequence[Dict[str, Any]], citation_quality: Dict[str, Any], ) -> None: if not self.runtime_telemetry_enabled: return try: timing = _as_dict(result_meta.get("timing_ms")) total_ms = float(timing.get("total") or 0.0) slow = bool(total_ms >= float(self.runtime_telemetry_slow_ms)) source_count = int(len(sources or [])) weak_reasons: List[str] = [] if not bool(citation_quality.get("pass", True)): weak_reasons.append("citation_quality_fail") if source_count < int(self.runtime_telemetry_weak_min_sources): weak_reasons.append("low_source_count") if bool(result_meta.get("timeout_budget_exceeded")): weak_reasons.append("timeout_budget_exceeded") retrieval_mode = str(result_meta.get("retrieval_mode") or "") if retrieval_mode in {"citation_quorum_block", "web_fallback"}: weak_reasons.append(retrieval_mode) weak = bool(weak_reasons) if not (slow or weak): return query_norm = _norm_preserve(message).lower() payload = { "timestamp_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "domain": str(domain or ""), "query_hash": hashlib.sha256(query_norm.encode("utf-8")).hexdigest()[:16], "query_len": int(len(query_norm)), "retrieval_mode": retrieval_mode, "query_family": str(result_meta.get("query_family") or ""), "budget_profile": str(result_meta.get("budget_profile") or ""), "web_assisted": bool(result_meta.get("web_assisted")), "cache_hit": bool(result_meta.get("cache_hit")), "latency_ms": round(total_ms, 2), "slow": slow, "weak": weak, "weak_reasons": weak_reasons, "source_count": source_count, "citation_pass": bool(citation_quality.get("pass", True)), "citation_actual": int(citation_quality.get("actual") or 0), "citation_required": int(citation_quality.get("required_min") or 0), "timeout_budget_exceeded": bool(result_meta.get("timeout_budget_exceeded")), "path_budget": _as_dict(result_meta.get("path_budget")), "input_normalized": bool(result_meta.get("input_normalized")), "route_quality_flags": list(result_meta.get("route_quality_flags") or []), "clarification_pending_type": str(result_meta.get("pending_type") or ""), "clarification_loop": bool(result_meta.get("clarification_loop")), "router_workbook_stale": bool(result_meta.get("router_workbook_stale")), "router_workbook_freshness_label": str(result_meta.get("router_workbook_freshness_label") or ""), "router_intelligence_intent": str(result_meta.get("router_intelligence_intent") or ""), "prompt_version": f"unified-{PROMPT_VERSION}", } self.runtime_telemetry_path.parent.mkdir(parents=True, exist_ok=True) with self._runtime_telemetry_lock: with self.runtime_telemetry_path.open("a", encoding="utf-8", newline="") as f: f.write(json.dumps(payload, ensure_ascii=False) + "\n") self._runtime_telemetry_events += 1 if slow: self._runtime_telemetry_slow_events += 1 if weak: self._runtime_telemetry_weak_events += 1 except Exception: return def _tail_jsonl_rows(self, path: Path, *, limit: int = 500) -> List[Dict[str, Any]]: rows: List[Dict[str, Any]] = [] try: if not path.exists(): return rows with path.open("r", encoding="utf-8") as f: for line in f: text = str(line or "").strip() if not text: continue try: parsed = json.loads(text) except Exception: continue if isinstance(parsed, dict): rows.append(parsed) except Exception: return [] if limit > 0: return rows[-limit:] return rows def _router_workbook_rollout_summary(self, *, limit: int = 500) -> Dict[str, Any]: telemetry_rows = [ row for row in self._tail_jsonl_rows(self.runtime_telemetry_path, limit=limit) if "router_workbook" in str(row.get("retrieval_mode") or "") ] feedback_rows = self._tail_jsonl_rows(self.router_workbook_feedback_path, limit=limit) clarification_modes: Dict[str, int] = {} weak_modes: Dict[str, int] = {} stale_modes: Dict[str, int] = {} for row in telemetry_rows: mode = str(row.get("retrieval_mode") or "unknown") if bool(row.get("clarification_loop")): clarification_modes[mode] = clarification_modes.get(mode, 0) + 1 if bool(row.get("weak")): weak_modes[mode] = weak_modes.get(mode, 0) + 1 if bool(row.get("router_workbook_stale")): stale_modes[mode] = stale_modes.get(mode, 0) + 1 feedback_counts: Dict[str, int] = {} followup_by_mode: Dict[str, int] = {} for row in feedback_rows: verdict = str(row.get("verdict") or "unknown") feedback_counts[verdict] = feedback_counts.get(verdict, 0) + 1 if verdict == "needs_followup": mode = str(row.get("retrieval_mode") or "unknown") followup_by_mode[mode] = followup_by_mode.get(mode, 0) + 1 def _top_counts(values: Dict[str, int], *, max_items: int = 5) -> List[Dict[str, Any]]: return [ {"key": key, "count": count} for key, count in sorted(values.items(), key=lambda item: (-item[1], item[0]))[:max_items] ] return { "telemetry_rows_considered": len(telemetry_rows), "feedback_rows_considered": len(feedback_rows), "clarification_loop_count": sum(clarification_modes.values()), "stale_answer_count": sum(stale_modes.values()), "weak_answer_count": sum(weak_modes.values()), "feedback_counts": feedback_counts, "top_clarification_modes": _top_counts(clarification_modes), "top_followup_feedback_modes": _top_counts(followup_by_mode), "top_weak_modes": _top_counts(weak_modes), "top_stale_modes": _top_counts(stale_modes), } def record_router_workbook_feedback( self, *, request_id: str, verdict: str, detail: str = "", meta: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: if not self.router_workbook_feedback_enabled: return {"ok": False, "error": "feedback_disabled"} verdict_norm = str(verdict or "").strip().lower() if verdict_norm not in {"helpful", "needs_followup"}: raise ValueError("Feedback verdict must be `helpful` or `needs_followup`.") request_key = str(request_id or "").strip() if not request_key: raise ValueError("request_id is required for router workbook feedback.") meta = _as_dict(meta) payload = { "timestamp_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "request_id": request_key, "verdict": verdict_norm, "detail": str(detail or "").strip(), "retrieval_mode": str(meta.get("retrieval_mode") or ""), "router_intelligence_intent": str(meta.get("router_intelligence_intent") or ""), "router_orchestration_mode": str(meta.get("router_orchestration_mode") or ""), "router_workbook_stale": bool(meta.get("router_workbook_stale")), "router_workbook_freshness_label": str(meta.get("router_workbook_freshness_label") or ""), } self.router_workbook_feedback_path.parent.mkdir(parents=True, exist_ok=True) with self._router_workbook_feedback_lock: with self.router_workbook_feedback_path.open("a", encoding="utf-8", newline="") as f: f.write(json.dumps(payload, ensure_ascii=False) + "\n") self._router_workbook_feedback_events += 1 return {"ok": True, "feedback": payload} def _faq_conflict_override(self, message: str, resolved_mode: str) -> Optional[Dict[str, Any]]: low = str(message or "").lower() # Router lifecycle/replacement questions are workbook-backed now. # Do not revive the retired CSV/router-store lifecycle fast answers through FAQ conflict handling. if resolved_mode == "router_lifecycle" or _looks_like_router_lifecycle(low) or _contains_any(low, _ROUTER_REPLACEMENT_HINTS): return None if self._extract_router_models_cached(message) and ( self._is_router_compare_like(message) or _contains_any(low, _ROUTER_SPEC_HINTS) ): out = self._router_fact_fast_answer(message) if out: meta = _as_dict(out.get("meta")) meta["faq_conflict_override"] = "router_specs_internal_priority" out["meta"] = meta return out return None def _faq_should_skip_for_query(self, message: str, resolved_mode: str) -> bool: low = str(message or "").lower() if self._query_prefers_authoritative_evidence(message, resolved_mode): return True has_router_models = bool(self._extract_router_models_cached(message)) has_router_platform = _contains_any(low, _ROUTER_PLATFORM_HINTS) has_router_specs = _contains_any(low, _ROUTER_SPEC_HINTS) or _contains_any(low, _ROUTER_DOC_HINTS) has_router_compare = self._is_router_compare_like(message) has_router_lifecycle = _contains_any(low, _ROUTER_LIFECYCLE_HINTS) or _contains_any(low, _ROUTER_REPLACEMENT_HINTS) has_pots_compare = _contains_any(low, _POTS_FAST_COMPARE_HINTS) has_pots_deep = _contains_any(low, _POTS_FORCE_DEEP_HINTS) has_master_doc_lookup = _contains_any( low, ( "which files", "what files", "which docs", "what docs", "which documents", "what documents", "which internal documents", "document categories", "order-flow", "order flow", "quoting context", "discovery-call", "discovery call", ), ) if _contains_any( low, ( "from documented specs only", "from docs only", "table format", "weighted table", "scoring matrix", "battle card", "top 10", "deep compare", "deep comparison", ), ): return True if resolved_mode in {"router_docs", "router_lifecycle"}: if has_router_compare or has_router_lifecycle: return True if has_router_platform: return True if has_router_models and has_router_specs: return True if resolved_mode == "pots": if has_pots_deep or has_pots_compare: return True if has_router_platform and (not _contains_any(low, _POTS_CONTEXT_HINTS)): return True if resolved_mode == "masters": if has_master_doc_lookup: return True if _contains_any( low, ( "source-backed overview", "source backed overview", "can and cannot be claimed", "what can and cannot be claimed", "cannot be claimed", "claims from masters docs", "claims from masters documentation", ), ): return True return False def _faq_should_bypass_medium_clarify(self, message: str, resolved_mode: str, score: float) -> bool: if not self.faq_medium_answer_bypass_enabled: return False if self._query_prefers_authoritative_evidence(message, resolved_mode): return False mode = _norm_mode(resolved_mode) if mode not in self.faq_medium_answer_bypass_domains: return False if float(score) < float(self.faq_medium_answer_bypass_min_score): return False if float(score) >= float(self.faq_high_threshold): return True low = _normalize_router_query_text(message) if any(x in low for x in ("reply yes", "confirm", "clarifying detail", "clarify")): return False return self._is_answer_seeking_intent(low) def _faq_build_sources(self, entry: FaqEntry, *, score: float) -> List[Dict[str, Any]]: return [ { "id": "FQ1", "domain": "knowledgebase", "doc": self.faq_csv_path.name, "relative_path": str(self.faq_csv_path), "chunk_id": entry.faq_id, "location": "", "excerpt": f"FAQ match: {entry.question}", "score": float(score), } ] def _fast_mode_guidance_sources(self, domain: str, retrieval_mode: str, message: str) -> List[Dict[str, Any]]: mode = str(retrieval_mode or "").strip().lower() if (not mode) or (not mode.endswith("_fast")): return [] low = _normalize_router_query_text(message) out: List[Dict[str, Any]] = [] def _add( sid: str, *, src_domain: str, doc: str, rel: str, chunk: str, excerpt: str, score: float = 0.92, ) -> None: out.append( { "id": sid, "domain": src_domain, "doc": doc, "relative_path": rel, "chunk_id": chunk, "location": "", "excerpt": excerpt[:260], "score": float(score), } ) router_concept_modes = { "router_network_slicing_concept_fast", "router_5g_sa_nsa_concept_fast", "router_4g_vs_5g_positioning_fast", "router_poe_concept_fast", "router_esim_concept_fast", "router_wan_lan_concept_fast", "router_throughput_expectation_fast", "router_first_pass_selection_checklist_fast", "router_antenna_precheck_fast", "router_vehicle_install_caveats_fast", "router_ruggedization_concept_fast", "router_prequote_questions_fast", "router_engineering_reject_reasons_fast", } if mode in router_concept_modes: primary_excerpt = { "router_vehicle_install_caveats_fast": ( "Vehicle install caveat guidance emphasizes mounting approach, power path validation, antenna/cable routing, " "environmental fit, and pre-cutover validation/rollback ownership." ), "router_engineering_reject_reasons_fast": ( "Engineering rejection guidance centers on ambiguous model requirements, missing environment constraints, " "unsupported claims, and incomplete migration/rollback planning details." ), "router_first_pass_selection_checklist_fast": ( "First-pass selection checklist covers site profile, network role, interface requirements, RF/install constraints, " "operations controls, and commercial guardrails before quote output." ), "router_antenna_precheck_fast": ( "Antenna pre-check guidance includes connector compatibility, RF path/MIMO count, cable-loss checks, " "mounting environment, and grounding/weatherproofing ownership." ), "router_4g_vs_5g_positioning_fast": ( "Internal FAQ examples describe V810AD and RUT241 as 4G LTE Cat 4 devices, while branch-backup guidance focuses on failover design and traffic profile rather than radio generation alone." ), "router_esim_concept_fast": ( "Internal FAQ guidance says eSIM is a built-in chip provisioned remotely with an activation profile and is useful when shipping devices or switching profiles without mailing cards." ), }.get( mode, "Internal FAQ set includes concept guidance for SA/NSA, network slicing, eSIM, WAN/LAN, activation checklists, and router discovery framing.", ) secondary_excerpt = { "router_vehicle_install_caveats_fast": ( "Rep-ready install framing keeps caveats tied to documented checks before model-specific field deployment." ), "router_engineering_reject_reasons_fast": ( "Rep-ready intake guidance is used to reduce engineering rework loops by capturing constraints up front." ), "router_first_pass_selection_checklist_fast": ( "Checklist structure is optimized for concise pre-quote discovery and deterministic handoff to engineering." ), "router_antenna_precheck_fast": ( "Pre-check flow is designed to avoid mismatch rework before recommending specific antenna SKUs." ), "router_4g_vs_5g_positioning_fast": ( "Rep-ready branch-backup guidance says baseline cellular backup design should be qualified around failover behavior, outage traffic load, and hold timers before over-indexing on headline speed." ), "router_esim_concept_fast": ( "Internal FAQ guidance says EID identifies the eSIM chip used to download profiles and activation flows may use ICCID, IMEI, and EID depending on SIM type." ), }.get( mode, "Rep-ready concept Q&A covers network slicing, eSIM differences, cellular router basics, and plain-language guidance for customer conversations.", ) _add( "FG1", src_domain="router_docs", doc="FAQ_master_updated.csv", rel="docs/faq/FAQ_master_updated.csv", chunk=f"faq_concept:{mode}", excerpt=primary_excerpt, ) _add( "FG2", src_domain="router_docs", doc="FAQ_200_ansers_set_3.csv", rel="docs/faq/FAQ_200_ansers_set_3.csv", chunk=f"faq_rep_ready:{mode}", excerpt=secondary_excerpt, score=0.9, ) return out[:2] if mode in { "rapid_router_configuration_flow_fast", "rapid_router_shipping_behavior_fast", "rapid_router_submit_requirements_fast", "rapid_router_msrp_vs_sell_price_fast", "rapid_router_address_validation_guidance_fast", "rapid_router_helper_routing_guidance_fast", "router_table_reader_recovery_fast", "router_compare_prompt_template_fast", }: mode_excerpt_1 = { "rapid_router_configuration_flow_fast": ( "Rapid Router computes `configurationTotal` from selected configuration option and includes it in " "`estimatedTotal = subtotal + shippingTotal + configurationTotal`." ), "rapid_router_shipping_behavior_fast": ( "Shipping math distinguishes ground billable quantity vs waived Standard FWA quantity, with overnight billed per device." ), "rapid_router_submit_requirements_fast": ( "Submit validation requires at least one device quantity plus required rep/customer/payment/signature fields before sign-and-submit." ), "rapid_router_msrp_vs_sell_price_fast": ( "MSRP is displayed as reference; selected primary/backup plan price drives billable unit/subtotal math." ), "rapid_router_address_validation_guidance_fast": ( "Address workflow validates entered street/city/state/zip and offers a separate `Apply suggestion` action " "only when a standardized suggestion is returned." ), "rapid_router_helper_routing_guidance_fast": ( "Knowledgebase helper routes store-backed model/price compares to Rapid Router catalog paths and concept/process asks to FAQ/doc paths." ), "router_table_reader_recovery_fast": ( "Comparison UI guidance identifies `Open table reader` as the full-width surface for wide multi-model tables." ), "router_compare_prompt_template_fast": ( "Comparison UX supports explicit ask templates that return a documented multi-model table and point to `Open table reader` for full-width viewing." ), }.get( mode, "Rapid Router notes capture configuration, shipping, submit-validation, and comparison-reader behavior guidance.", ) mode_excerpt_2 = { "rapid_router_configuration_flow_fast": ( "Rapid Router UI guidance shows `Activation verification` as a selectable `Configuration option` and shows configuration totals in the order summary, but it does not enumerate the exact task list included in that service." ), "rapid_router_shipping_behavior_fast": ( "Order summary renders shipping breakdown with billable/waived quantity and total shipping amount." ), "rapid_router_submit_requirements_fast": ( "Validation error list includes required device quantity, rep/customer fields, payment type, and signature requirements." ), "rapid_router_msrp_vs_sell_price_fast": ( "Product cards show MSRP and plan-linked prices (Standard FWA / Backup-Pooled) used for ordering selections." ), "rapid_router_address_validation_guidance_fast": ( "`Validate address` runs first, then users can choose whether to apply the suggested standardized address via `Apply suggestion`." ), "rapid_router_helper_routing_guidance_fast": ( "Cross-domain fast lanes and deterministic catalog handlers separate orderable catalog answers from broader FAQ/doc guidance." ), "router_table_reader_recovery_fast": ( "Helper comparison blocks direct users to table reader when inline comparison width becomes constrained." ), "router_compare_prompt_template_fast": ( "Helper comparison cards prioritize a clear `Open table reader` CTA for readability when table content is wide." ), }.get( mode, "Frontend implementation includes order summary math and guidance controls for Rapid Router flows.", ) _add( "FG1", src_domain="router_docs", doc="RapidRouter.tsx", rel="frontend/src/pages/RapidRouter.tsx", chunk=f"rapid_router_behavior:{mode}", excerpt=mode_excerpt_1, ) _add( "FG2", src_domain="router_docs", doc="knowledgebase/core.py" if mode == "rapid_router_helper_routing_guidance_fast" else "FloatingRouterHelper.tsx", rel="backend/app/knowledgebase/core.py" if mode == "rapid_router_helper_routing_guidance_fast" else "frontend/src/components/FloatingRouterHelper.tsx", chunk=f"rapid_router_ui:{mode}", excerpt=mode_excerpt_2, score=0.9, ) return out[:2] if mode == "router_alias_normalization_guidance_fast": _add( "FG1", src_domain="router_docs", doc="session_handoff.md", rel="docs/dev/session_handoff.md", chunk="alias:cradlepoint_50_to_00", excerpt=( "Published alias rule: Ericsson/CradlePoint `...50` models map to `...00` family as non-Wi-Fi variants " "(for example `AER2250` -> `AER2200`, `S450` -> `S400`)." ), ) _add( "FG2", src_domain="router_docs", doc="router_tab_smoke_test.py", rel="backend/app/routers/router_tab_smoke_test.py", chunk="alias:test_coverage", excerpt=( "Smoke tests validate alias resolution and variant notes for `AER2250`, `S450`, and related " "CradlePoint non-Wi-Fi family mappings." ), score=0.9, ) return out[:2] if mode == "router_wan_lan_lookup_fast": _add( "FG1", src_domain="router_docs", doc="feb2026routers.csv", rel="backend/app/router_rag/feb2026routers.csv", chunk="router_wan_lan_lookup", excerpt=( "Normalized router catalog row includes `WAN/LAN` field values used for deterministic " "single-model WAN/LAN lookup responses." ), ) return out[:1] if mode in {"inventory_typo_clarify_guidance_fast", "inventory_format_guidance_fast"}: _add( "FG1", src_domain="router_lifecycle", doc="router_tab_smoke_test.py", rel="backend/app/routers/router_tab_smoke_test.py", chunk=f"inventory_parser:{mode}", excerpt=( "Routers parser tests cover mixed-customer inventory syntax and typo/alias clarification " "(including prompts that confirm likely model matches before replacement mapping)." ), ) return out[:1] if mode == "security_check_gate_guidance_fast": _add( "FG1", src_domain="router_docs", doc="captchaGate.ts", rel="frontend/src/utils/captchaGate.ts", chunk="security_check_gate", excerpt=( "Security-gate utility enforces a solved check before helper calls and returns " "`Complete the security check to continue` when unsolved." ), ) _add( "FG2", src_domain="router_docs", doc="FloatingRouterHelper.tsx", rel="frontend/src/components/FloatingRouterHelper.tsx", chunk="security_check_helper_ui", excerpt=( "Floating helper UI blocks submit until security check is complete and shows " "a session-level solved message once verified." ), score=0.9, ) return out[:2] if mode in {"auth_restart_guidance_fast", "auth0_token_failure_checklist_fast", "auth_allowed_domains_fast"}: _add( "FG1", src_domain="masters", doc="auth.py", rel="backend/app/auth.py", chunk=f"auth_defaults:{mode}", excerpt=( "Auth settings include default allowed email domains and API audience parsing/normalization logic " "used for Auth0 token validation." ), ) _add( "FG2", src_domain="masters", doc="AuthGate.tsx", rel="frontend/src/auth/AuthGate.tsx", chunk=f"auth_runtime:{mode}", excerpt=( "Auth gate implements token-acquisition retry guidance, timeout messaging, callback checks, " "and user-facing remediation hints for hosted login failures." ), score=0.9, ) return out[:2] if mode == "masters_source_citation_guidance_fast": _add( "FG1", src_domain=domain or "masters", doc="core.py", rel="backend/app/knowledgebase/core.py", chunk="masters_citation_style_fast", excerpt=( "Unified knowledgebase responses carry explicit source IDs and support structured `documented now` " "vs `assumptions/open items` formatting for rep-safe handoffs." ), ) _add( "FG2", src_domain=domain or "masters", doc="UnifiedKnowledgebase.tsx", rel="frontend/src/pages/UnifiedKnowledgebase.tsx", chunk="masters_citation_ui_fast", excerpt=( "Answer UI renders citations/sources and keeps supporting evidence collapsible for clean rep sharing." ), score=0.9, ) return out[:2] if mode in { "hf_env_triage_fast", "startup_warning_priority_fast", "frontend_deploy_cache_recovery_fast", "hard_timeout_guidance_fast", }: ops_excerpt_1 = { "frontend_deploy_cache_recovery_fast": ( "Engineering runbook guidance says that after frontend asset changes, operators should finish the redeploy and then hard refresh or use a private window to clear stale hashed bundle references." ), }.get( mode, "Engineering decisions record deploy/restart guidance, startup-warning prioritization, Auth0 fixes, and quality guardrails used in hosted runtime operations." ) ops_excerpt_2 = { "frontend_deploy_cache_recovery_fast": ( "Session handoff runbook also calls out redeploy plus browser hard refresh/private window as the practical cache-reset step after hashed-asset changes." ), }.get( mode, "Session handoff captures practical runbook steps for cache refresh, auth retry, environment checks, and timeout-safe user guidance patterns." ) _add( "FG1", src_domain=domain or "masters", doc="decisions.md", rel="docs/dev/decisions.md", chunk=f"ops_guidance:{mode}", excerpt=ops_excerpt_1, ) _add( "FG2", src_domain=domain or "masters", doc="session_handoff.md", rel="docs/dev/session_handoff.md", chunk=f"ops_runbook:{mode}", excerpt=ops_excerpt_2, score=0.9, ) return out[:2] if mode.startswith("pots_"): pots_primary_excerpt = { "pots_elevator_migration_constraints_fast": ( "Internal POTS guidance says elevator emergency phone planning should verify code-aligned behavior, reliability under outage conditions, and required test procedures with responsible parties." ), "pots_alarm_panel_risk_fast": ( "Internal POTS guidance says fire-related paths need strict validation against relevant standards and local authority expectations, with required supervision/test criteria and acceptance evidence confirmed before rollout." ), "pots_safe_compare_framework_fast": ( "Internal POTS guidance says good/better/best options should be presented by risk profile and operational fit, not just price, and that tradeoffs should be explicit and source-backed." ), }.get( mode, "POTS workflow notes cover intake fields, keep-number/porting blockers, lifecycle guardrails, and per-site quote assumptions for reps." ) use_top100 = mode in {"pots_elevator_migration_constraints_fast", "pots_alarm_panel_risk_fast", "pots_safe_compare_framework_fast"} _add( "FG1", src_domain="pots", doc=("pots_top100_questions_draft.md" if use_top100 else "session_handoff.md"), rel=("backend/app/pots_ai/data/pots_top100_questions_draft.md" if use_top100 else "docs/dev/session_handoff.md"), chunk=f"pots_flow:{mode}", excerpt=pots_primary_excerpt, ) if mode == "pots_elevator_migration_constraints_fast": _add( "FG2", src_domain="pots", doc="pots_top100_questions_draft.md", rel="backend/app/pots_ai/data/pots_top100_questions_draft.md", chunk="pots_flow:elevator_claims_language", excerpt=( "The same POTS guidance says local interpretation and AHJ processes can vary, blanket compliance claims should be avoided, and cutover plans should include validation tests and rollback triggers." ), score=0.9, ) if mode == "pots_safe_compare_framework_fast": _add( "FG2", src_domain="pots", doc="pots_top100_questions_draft.md", rel="backend/app/pots_ai/data/pots_top100_questions_draft.md", chunk="pots_flow:good_better_best", excerpt=( "The same POTS guidance says provisional options should use clearly labeled assumptions when inventory is incomplete instead of overstating unsupported fit or pricing." ), score=0.9, ) return out[:2] if "network slicing" in low: _add( "FG1", src_domain="router_docs", doc="FAQ_master_updated.csv", rel="docs/faq/FAQ_master_updated.csv", chunk="faq:network_slicing", excerpt=( "FAQ definition: network slicing is a 5G feature that creates virtual slices with policy targets; " "router hardware alone does not force slice assignment." ), ) return out[:1] return out[:2] def _faq_handle_pending(self, message: str, st: UnifiedKnowledgebaseState, resolved_mode: str) -> Optional[Dict[str, Any]]: pending = _as_dict(st.pending) if str(pending.get("type") or "") != "faq_clarify": return None low = _norm(message).lower() if low in _FAQ_NO_TERMS: st.pending = {} return None base_q = _norm(pending.get("original_message", "")) if low in _FAQ_YES_TERMS and base_q: combined = base_q else: combined = f"{base_q} {message}".strip() if base_q else str(message or "") st.pending = {} return self._faq_fast_lane_answer(combined, st, resolved_mode, allow_clarify=False, force_best_effort=True) def _faq_fast_lane_answer( self, message: str, st: UnifiedKnowledgebaseState, resolved_mode: str, *, allow_clarify: bool = True, force_best_effort: bool = False, ) -> Optional[Dict[str, Any]]: if (not self.faq_enabled) or (not self._faq_entries): return None faq_query = str(message or "") primary_message, has_rr_context = self._split_rapid_router_context_message(faq_query) if has_rr_context and _norm(primary_message): faq_query = primary_message if self._faq_should_skip_for_query(faq_query, resolved_mode): return None override = self._faq_conflict_override(faq_query, resolved_mode) if override is not None: return override parts = self._faq_split_subquestions(faq_query) if not parts: return None matched: List[Tuple[str, Dict[str, Any]]] = [] best_global: Optional[Dict[str, Any]] = None for part in parts: best = next(iter(self._faq_best_matches(part, limit=self.faq_top_k)), None) if best is None: self._faq_log_ongoing_candidate(domain=resolved_mode, question=part, reason="no_match", best=None) return None if best_global is None or float(best.get("score", 0.0)) > float(best_global.get("score", 0.0)): best_global = best score = float(best.get("score", 0.0)) if score >= float(self.faq_high_threshold): matched.append((part, best)) continue if score >= float(self.faq_clarify_threshold): if allow_clarify and (not force_best_effort): if self._faq_should_bypass_medium_clarify(faq_query, resolved_mode, score): matched.append((part, best)) continue entry = best.get("entry") st.pending = { "type": "faq_clarify", "domain": resolved_mode, "original_message": faq_query, "candidate_faq_id": getattr(entry, "faq_id", ""), "candidate_question": getattr(entry, "question", ""), "candidate_score": round(score, 4), } return { "assistant": _format_shell( f"I found a likely FAQ match: `{getattr(entry, 'question', '')}`.", [ "Similarity is medium confidence, so I want one quick confirmation before I lock the answer.", f"Match score: {score:.2f} (target >= {self.faq_high_threshold:.2f} for auto-answer).", ], [ "Reply `yes` to use that answer.", "Or provide one clarifying detail (model/provider/use case), and I’ll answer best-effort.", ], ), "sources": self._faq_build_sources(entry, score=score) if entry is not None else [], "files": [str(self.faq_csv_path)], "meta": { "domain": resolved_mode, "retrieval_mode": "faq_fast_clarify", "faq_match_score": round(score, 4), "faq_match_id": getattr(entry, "faq_id", ""), "faq_match_question": getattr(entry, "question", ""), "web_assisted": False, }, } matched.append((part, best)) continue # Below clarify threshold: log for ongoing FAQ expansion and fall back to normal pipeline. self._faq_log_ongoing_candidate(domain=resolved_mode, question=part, reason="below_threshold", best=best) return None if not matched: return None if len(matched) == 1: part, best = matched[0] entry = best.get("entry") if entry is None: return None score = float(best.get("score", 0.0)) mode = "faq_fast_high_confidence" if score >= float(self.faq_high_threshold) else "faq_fast_best_effort" confidence_note = "" if mode == "faq_fast_best_effort": confidence_note = ( f"\n\n_Best-effort match from FAQ ({score:.2f}); if this is not your intent, add one detail and I’ll refine._" ) return { "assistant": _format_shell( f"{entry.answer}{confidence_note}", [ f"Matched against internal FAQ source `{self.faq_csv_path.name}`.", f"Hybrid similarity: overall {score:.2f} (lexical {float(best.get('lexical', 0.0)):.2f}, semantic {float(best.get('semantic', 0.0)):.2f}, entity {float(best.get('entity', 0.0)):.2f}).", ], [ "Ask a follow-up with one more constraint (model, provider, or output format) for a tighter answer.", ], ), "sources": self._faq_build_sources(entry, score=score), "files": [str(self.faq_csv_path)], "meta": { "domain": resolved_mode, "retrieval_mode": mode, "faq_match_score": round(score, 4), "faq_match_id": entry.faq_id, "faq_match_question": entry.question, "web_assisted": False, }, } lines = ["Matched FAQ responses for your multi-part request:", ""] sources: List[Dict[str, Any]] = [] min_score = 1.0 for idx, (part, best) in enumerate(matched, start=1): entry = best.get("entry") if entry is None: continue score = float(best.get("score", 0.0)) min_score = min(min_score, score) lines.append(f"{idx}. **{part}**") lines.append(f" - {entry.answer}") lines.append("") sources.extend(self._faq_build_sources(entry, score=score)) if not sources: return None return { "assistant": _format_shell( "\n".join(lines).strip(), [ f"Answered via FAQ fast lane (`{self.faq_csv_path.name}`) across {len(sources)} matched item(s).", "Used hybrid lexical + semantic + entity similarity per sub-question.", ], [ "For any item that needs deeper sourcing/citations, ask that single item and I’ll run deep retrieval.", ], ), "sources": sources[:8], "files": [str(self.faq_csv_path)], "meta": { "domain": resolved_mode, "retrieval_mode": "faq_fast_multi", "faq_match_score": round(float(min_score), 4), "faq_parts": len(matched), "web_assisted": False, }, } def _build_router_alias_map(self) -> Dict[str, str]: aliases: Dict[str, str] = dict(_ROUTER_MODEL_ALIAS) raw = getattr(self.router_core, "router_aliases", None) if isinstance(raw, dict): for k, v in raw.items(): ak = _compact_model(k) vk = _compact_model(v) if ak and vk: # Keep MG22 independent in unified KB lifecycle handling. # If MG22 is absent from lifecycle CSVs, answer should be provisional/clarifying # instead of silently remapping to MG21. if ak == "MG22" and vk == "MG21": continue aliases[ak] = vk return aliases def _build_router_catalog_token_synonyms(self) -> Dict[str, str]: synonyms: Dict[str, str] = {} canonical_rows = { _compact_model(k) for k in list((getattr(self, "_router_fact_rows", {}) or {}).keys()) + list((getattr(self, "_router_lifecycle_rows", {}) or {}).keys()) if _compact_model(k) } def _add(alias: Any, canonical: Any) -> None: ak = _compact_model(alias) ck = _compact_model(canonical) if (not ak) or (not ck) or (ak == ck): return if ak in _ROUTER_NON_DEVICE_TERMS: return if ak in canonical_rows: return if len(ak) < 4 and (not any(ch.isdigit() for ch in ak)): return ad = _digit_signature(ak) cd = _digit_signature(ck) if ad and cd and (ad != cd): return if ad and (not cd): return prev = synonyms.get(ak) if prev and prev != ck: prevd = _digit_signature(prev) if ad and (prevd == ad) and (cd != ad): return if ad and (cd == ad) and (prevd != ad): synonyms[ak] = ck return if len(ck) < len(prev): synonyms[ak] = ck return synonyms[ak] = ck def _add_text_forms(text: Any, canonical: Any) -> None: raw = _norm(text) if not raw: return _add(raw, canonical) parts = [p for p in re.findall(r"[A-Za-z0-9]+", raw) if p] compact_parts = [_compact_model(p) for p in parts if _compact_model(p)] for p in compact_parts: if any(ch.isdigit() for ch in p): _add(p, canonical) for n in (2, 3, 4): for i in range(0, max(0, len(compact_parts) - n + 1)): gram = "".join(compact_parts[i : i + n]) if any(ch.isdigit() for ch in gram): _add(gram, canonical) fact_rows = getattr(self, "_router_fact_rows", {}) or {} lifecycle_rows = getattr(self, "_router_lifecycle_rows", {}) or {} for mk, row in {**lifecycle_rows, **fact_rows}.items(): model_key = _compact_model(mk) if not model_key: continue _add_text_forms(mk, model_key) if isinstance(row, dict): for field in ("model", "sku", "title", "manufacturer"): _add_text_forms(row.get(field, ""), model_key) for row in list(getattr(self, "_router_variant_rows", []) or []): if not isinstance(row, dict): continue model_key = _compact_model(row.get("model_key", "")) or _compact_model(row.get("model", "")) if not model_key: continue canonical = ( self._lookup_router_fact_key(model_key) or self._lookup_router_lifecycle_key_relaxed(model_key) or model_key ) for field in ("model", "sku", "title", "manufacturer"): _add_text_forms(row.get(field, ""), canonical) # Support suffix variants like "CR202-lite" / "CR 202 lite". for alias, canonical in list(synonyms.items()): if any(alias.endswith(suf) for suf in ("LITE", "PRO", "ADV", "ADVANCED", "ESSENTIAL", "ESSENTIALS")) and len(alias) >= 7: for suf in ("LITE", "PRO", "ADV", "ADVANCED", "ESSENTIAL", "ESSENTIALS"): if alias.endswith(suf) and len(alias) > len(suf) + 2: _add(alias[: -len(suf)], canonical) if re.match(r"^[A-Z]{1,5}\d{2,4}[A-Z]$", alias): _add(alias[:-1], canonical) return synonyms def _prune_alias_overrides_with_exact_rows(self) -> None: # If an alias key exists as an exact model in current CSV rows, do not remap it. if not isinstance(self._router_alias_map, dict): return fact_rows = getattr(self, "_router_fact_rows", {}) or {} life_rows = getattr(self, "_router_lifecycle_rows", {}) or {} for ak, vk in list(self._router_alias_map.items()): if (not ak) or (not vk) or (ak == vk): continue if (ak in fact_rows) or (ak in life_rows): self._router_alias_map[ak] = ak def _strict_model_alias_candidates(self, token: str) -> List[str]: tok = _compact_model(token) if not tok: return [] out: List[str] = [] def _add(raw: Any) -> None: val = _compact_model(raw) if (not val) or (val in out): return out.append(val) _add(tok) _add(_strip_router_vendor_prefix(tok)) if re.match(r"^[A-Z]{1,5}\d{2,4}[A-Z]$", tok): _add(tok[:-1]) for suf in ("LITE", "PRO", "ADV", "ADVANCED", "ESSENTIAL", "ESSENTIALS"): if tok.endswith(suf) and len(tok) > len(suf) + 2: _add(tok[: -len(suf)]) m = re.match(r"^([A-Z]{1,5})(\d{2,4})([A-Z]{2,})$", tok) if m: _add(f"{m.group(1)}{m.group(2)}") # Follow alias chains deterministically but keep search bounded. chain = list(out) hops = 0 while chain and hops < 8: hops += 1 node = chain.pop(0) nxt = _compact_model((getattr(self, "_router_alias_map", {}) or {}).get(node, "")) if nxt and nxt not in out: out.append(nxt) chain.append(nxt) return out def _normalize_router_model(self, value: str) -> str: tok = _compact_model(value) if not tok: return "" fact_rows = getattr(self, "_router_fact_rows", {}) or {} life_rows = getattr(self, "_router_lifecycle_rows", {}) or {} aliased = self._router_alias_map.get(tok, tok) vendor_stripped = _strip_router_vendor_prefix(tok) vendor_aliased = self._router_alias_map.get(vendor_stripped, vendor_stripped) if vendor_stripped else "" candidates: List[str] = [] for cand in (tok, aliased, vendor_stripped, vendor_aliased): if cand and cand not in candidates: candidates.append(cand) if self.strict_model_alias_normalization_enabled: strict: List[str] = [] for cand in list(candidates): strict.extend(self._strict_model_alias_candidates(cand)) for cand in strict: if cand and cand not in candidates: candidates.append(cand) if vendor_stripped and vendor_stripped.isdigit(): for cand in (f"C{vendor_stripped}", f"IR{vendor_stripped}"): if cand not in candidates: candidates.append(cand) for cand in candidates: if cand in fact_rows or cand in life_rows: return cand token_idx = getattr(self, "_router_token_model_index", {}) or {} for cand in candidates: if cand in token_idx and token_idx[cand]: candidate = str(token_idx[cand][0]) if _safe_model_variant_match(tok, candidate): return candidate for cand in candidates: corrected = self._closest_router_token(cand) if corrected and corrected in token_idx and token_idx[corrected]: candidate = str(token_idx[corrected][0]) if _safe_model_variant_match(tok, candidate): return candidate return aliased def _closest_router_token(self, token: str) -> str: tok = _compact_model(token) if not tok: return "" token_idx = getattr(self, "_router_token_model_index", {}) or {} if tok in token_idx: return tok candidates = [k for k in token_idx.keys() if k and (len(k) >= 3)] if not candidates: return "" d_sig = _digit_signature(tok) narrowed = candidates if d_sig: narrowed = [k for k in candidates if _digit_signature(k) == d_sig] or candidates if len(narrowed) > 256: # Keep search bounded and deterministic. narrowed = sorted(narrowed, key=lambda x: (abs(len(x) - len(tok)), x))[:256] if fuzz is not None: best_key = "" best_score = -1 for cand in narrowed: score = int(fuzz.ratio(tok, cand)) if score > best_score: best_score = score best_key = cand if best_score >= 91: return best_key return "" best = "" best_score = 0.0 for cand in narrowed: score = difflib.SequenceMatcher(None, tok, cand).ratio() if score > best_score: best_score = score best = cand return best if best_score >= 0.91 else "" def _router_display_name(self, row: Dict[str, Any], fallback_key: str) -> str: raw = _norm(row.get("model", "")) if isinstance(row, dict) else "" if not raw: return str(fallback_key or "Unknown") low = raw.lower() if len(raw) > 72: return str(fallback_key or raw) if any(x in low for x in ("note:", "supported of", " or - ", "still (")): return str(fallback_key or raw) if _compact_model(raw) == raw and any(ch.isdigit() for ch in raw): humanized = _humanize_model_token(raw) if humanized and humanized != raw: return humanized return raw def _build_model_trie(self, keys: Sequence[str]) -> Dict[str, Any]: root: Dict[str, Any] = {"c": {}, "k": [], "t": []} for raw in keys: key = _compact_model(raw) if not key: continue node = root node["k"].append(key) for ch in key: children = node.setdefault("c", {}) if ch not in children: children[ch] = {"c": {}, "k": [], "t": []} node = children[ch] node["k"].append(key) node["t"].append(key) return root def _trie_candidates(self, trie: Dict[str, Any], key: str) -> Tuple[List[str], List[str]]: node = trie terminal_prefixes: List[str] = [] for ch in key: children = node.get("c", {}) if ch not in children: return [], terminal_prefixes node = children[ch] terminal_prefixes.extend([x for x in node.get("t", []) if x not in terminal_prefixes]) return list(node.get("k", [])), terminal_prefixes def _build_router_token_model_index(self) -> Dict[str, List[str]]: idx: Dict[str, List[str]] = {} def add_token(token: str, model_key: str) -> None: tok = _compact_model(token) if (not tok) or (len(tok) < 3 and (not any(ch.isdigit() for ch in tok))): return vals = idx.setdefault(tok, []) if model_key not in vals: vals.append(model_key) def add_text_tokens(text: str, model_key: str) -> None: parts = re.findall(r"[A-Za-z]+|\d+", str(text or "")) compact_parts = [_compact_model(p) for p in parts if _compact_model(p)] for p in compact_parts: add_token(p, model_key) # n-grams (up to 3) help with "CR 202 lite", "BR 1 Pro", etc. for n in (2, 3): for i in range(0, max(0, len(compact_parts) - n + 1)): add_token("".join(compact_parts[i : i + n]), model_key) add_token(_compact_model(text), model_key) for model_key, row in self._router_fact_rows.items(): mk = _compact_model(model_key) if not mk: continue add_token(mk, mk) add_text_tokens(str(row.get("model") or ""), mk) add_text_tokens(str(row.get("sku") or ""), mk) for model_key, row in self._router_lifecycle_rows.items(): mk = _compact_model(model_key) if not mk: continue add_token(mk, mk) add_text_tokens(str(row.get("model") or ""), mk) return idx def _build_router_fast_subsets(self) -> Dict[str, List[str]]: subsets: Dict[str, List[str]] = {"five_g": [], "vehicle": [], "vehicle_5g": [], "akita": []} seen_by_subset: Dict[str, set[str]] = {k: set() for k in subsets.keys()} vehicle_terms = ("vehicle", "mobile", "fleet", "public safety", "law enforcement", "in-vehicle", "transport") for key, row in self._router_fact_rows.items(): mk = _compact_model(key) if not mk: continue modem_low = _norm(row.get("modem", "")).lower() use_case_low = _norm(row.get("primary_use_case", "")).lower() suggested_low = _norm(row.get("suggested_antennas", "")).lower() is_5g = "5g" in modem_low is_vehicle = any(t in use_case_low for t in vehicle_terms) has_akita = "akita" in suggested_low if is_5g and mk not in seen_by_subset["five_g"]: subsets["five_g"].append(key) seen_by_subset["five_g"].add(mk) if is_vehicle and mk not in seen_by_subset["vehicle"]: subsets["vehicle"].append(key) seen_by_subset["vehicle"].add(mk) if is_5g and is_vehicle and mk not in seen_by_subset["vehicle_5g"]: subsets["vehicle_5g"].append(key) seen_by_subset["vehicle_5g"].add(mk) if has_akita and mk not in seen_by_subset["akita"]: subsets["akita"].append(key) seen_by_subset["akita"].add(mk) return subsets def _build_router_model_synonyms(self) -> Dict[str, str]: synonym_path = Path( str( os.getenv( "UNIFIED_KB_SYNONYM_PATH", Path(__file__).resolve().parent / "model_synonyms.generated.json", ) ) ) loaded: Dict[str, str] = {} if synonym_path.exists(): try: raw = json.loads(synonym_path.read_text(encoding="utf-8")) if isinstance(raw, dict): for k, v in raw.items(): kk = _compact_model(k) vv = _compact_model(v) if kk and vv: loaded[kk] = vv except Exception: loaded = {} # Auto-generate family synonyms from known model keys + router document filenames. model_keys = sorted( { _compact_model(k) for k in list(self._router_fact_rows.keys()) + list(self._router_lifecycle_rows.keys()) if _compact_model(k) } ) generated: Dict[str, str] = {} for key in model_keys: generated[key] = key # Family fallback: map slight suffix variants to shortest matching family key. fam_candidates = [x for x in model_keys if x != key and key.startswith(x) and len(key) - len(x) <= 3] if fam_candidates: fam_candidates.sort(key=len) generated[key] = fam_candidates[0] # Pull aliases from filenames (e.g., "CR 202 lite" style labels). for rel in self._router_file_map.values(): name = Path(str(rel)).name for tok in _ROUTER_MODEL_TOKEN_RE.findall(name): ck = _compact_model(tok) if not ck: continue if ck in generated: continue fam = [x for x in model_keys if ck.startswith(x) or x.startswith(ck)] if fam: fam.sort(key=lambda x: (abs(len(x) - len(ck)), len(x))) generated[ck] = fam[0] merged = {**generated, **loaded} curated_aliases = { "DRAGON": "XC46BE", "VERIZONDRAGON": "XC46BE", "XC46BE224T": "XC46BE", "CROWN": "ASKNCM1100E", "VERIZONCROWN": "ASKNCM1100E", "ASKNCM1100": "ASKNCM1100E", "ASKNCM1100E": "ASKNCM1100E", "ASKNCQ1338": "ASKNCQ1338E", "ASKNCQ1338E": "ASKNCQ1338E", "FSNO21VA": "FSNO21VA", "NVG558": "NVG558", "ARRISNVG558": "NVG558", } for k, v in curated_aliases.items(): kk = _compact_model(k) vv = _compact_model(v) if kk and vv: merged[kk] = vv # Optional write-through (for scheduled/nightly job support). if _env_bool("UNIFIED_KB_WRITE_SYNONYMS_ON_START", False): try: synonym_path.parent.mkdir(parents=True, exist_ok=True) synonym_path.write_text(json.dumps(merged, indent=2, sort_keys=True), encoding="utf-8") except Exception: pass return merged def _l2_get(self, key: str) -> Optional[Any]: if not key: return None now = time.time() row = self._l2_cache.get(key) if not isinstance(row, dict): return None if float(row.get("expires_at", 0.0) or 0.0) <= now: self._l2_cache.pop(key, None) try: self._l2_cache_order.remove(key) except ValueError: pass return None return row.get("value") def _l2_set(self, key: str, value: Any) -> None: if not key: return self._l2_cache[key] = {"expires_at": time.time() + float(self.l2_cache_ttl_s), "value": value} if key in self._l2_cache_order: self._l2_cache_order.remove(key) self._l2_cache_order.append(key) while len(self._l2_cache_order) > int(self.l2_cache_max_items): oldest = self._l2_cache_order.pop(0) self._l2_cache.pop(oldest, None) def _source_relevance_cache_key(self, message: str, domain: str, sources: Sequence[Dict[str, Any]]) -> str: src_sigs: List[str] = [] for s in sources: if not isinstance(s, dict): continue sig = "|".join( [ str(s.get("id") or ""), str(s.get("doc") or ""), str(s.get("relative_path") or ""), str(s.get("chunk_id") or ""), f"{float(s.get('score') or 0.0):.4f}", ] ) src_sigs.append(sig) digest = hashlib.sha1(("\n".join(src_sigs)).encode("utf-8")).hexdigest() return f"{str(domain or '').strip().lower()}::{_norm(message).lower()}::{digest}" def _source_relevance_cache_get(self, key: str) -> Optional[Tuple[List[Dict[str, Any]], Dict[str, Any]]]: if not key: return None now = time.time() row = self._source_relevance_cache.get(key) if not isinstance(row, dict): return None if float(row.get("expires_at", 0.0) or 0.0) <= now: self._source_relevance_cache.pop(key, None) try: self._source_relevance_cache_order.remove(key) except ValueError: pass return None val = row.get("value") if not (isinstance(val, tuple) and len(val) == 2): return None kept = list(val[0]) if isinstance(val[0], list) else [] meta = dict(val[1]) if isinstance(val[1], dict) else {} return kept, meta def _source_relevance_cache_set(self, key: str, value: Tuple[List[Dict[str, Any]], Dict[str, Any]]) -> None: if not key: return kept = list(value[0]) if isinstance(value[0], list) else [] meta = dict(value[1]) if isinstance(value[1], dict) else {} self._source_relevance_cache[key] = { "expires_at": time.time() + float(self.source_relevance_cache_ttl_s), "value": (kept, meta), } if key in self._source_relevance_cache_order: self._source_relevance_cache_order.remove(key) self._source_relevance_cache_order.append(key) while len(self._source_relevance_cache_order) > int(self.source_relevance_cache_max_items): oldest = self._source_relevance_cache_order.pop(0) self._source_relevance_cache.pop(oldest, None) def _router_index_search_hits(self, query: str, k: int = 8) -> List[Dict[str, Any]]: idx = getattr(getattr(self, "router_rag_core", None), "index", None) if idx is None or (not hasattr(idx, "search")): return [] def _to_dict(row: Any) -> Dict[str, Any]: if isinstance(row, dict): return dict(row) out: Dict[str, Any] = {} for key in ( "doc", "file_name", "relative_path", "rel", "chunk_id", "chunk_index", "text", "excerpt", "score", ): try: val = getattr(row, key, None) except Exception: val = None if val not in (None, ""): out[key] = val return out for kwargs in ({"k": int(k)}, {"top_k": int(k)}, {}): try: rows = list(idx.search(query, **kwargs) or []) if rows: return [_to_dict(r) for r in rows] except TypeError: continue except Exception: return [] return [] def _rapid_router_seed_pdf_text(self, path: Path) -> str: cache_key = str(path.resolve()) cached = self._rapid_router_seed_pdf_text_cache.get(cache_key) if cached is not None: return cached if (pdfplumber is None) or (not path.is_file()): self._rapid_router_seed_pdf_text_cache[cache_key] = "" return "" try: with pdfplumber.open(str(path)) as pdf: text = "\n".join((_norm(page.extract_text() or "")) for page in pdf.pages[:8]) except Exception: text = "" self._rapid_router_seed_pdf_text_cache[cache_key] = text return text def _rapid_router_seed_led_hits(self, model: str) -> List[Dict[str, Any]]: model_key = _compact_model(model) if (not model_key) or (pdfplumber is None) or (not self._rapid_router_seed_assets_dir.is_dir()): return [] candidates: List[Path] = [] for path in self._rapid_router_seed_assets_dir.glob("*.pdf"): stem_key = _compact_model(path.stem) if stem_key and ((model_key in stem_key) or (stem_key in model_key)): candidates.append(path) candidates.sort( key=lambda p: ( 0 if any(token in p.stem.lower() for token in ("manual", "quickstart", "quick_start", "quick-start")) else 1, p.name.lower(), ) ) backend_root = Path(__file__).resolve().parents[2] hits: List[Dict[str, Any]] = [] for path in candidates[:3]: text = self._rapid_router_seed_pdf_text(path) low = text.lower() if not any(token in low for token in ("led", "lights", "power", "wi-fi", "wifi", "4g", "5g", "signal")): continue hits.append( { "doc": path.name, "relative_path": str(path.relative_to(backend_root)), "chunk_id": f"rapid_router_seed_led:{path.stem}", "text": text, "score": 0.88, } ) return hits def _extract_led_legend_from_text(self, text: str) -> List[Tuple[str, str]]: body = _norm(text) if not body: return [] legend: List[Tuple[str, str]] = [] seen_labels: set[str] = set() def _add(label: str, meaning: str) -> None: meaning_clean = _norm(meaning) if (not meaning_clean) or (label in seen_labels): return seen_labels.add(label) legend.append((label, meaning_clean)) for label, pattern in ( ("Signal", r"signal(?:\s+led)?\s*:\s*([^\.]+)"), ("5G", r"5g(?:\s+led)?\s*:\s*([^\.]+)"), ("4G", r"4g(?:\s+led)?\s*:\s*([^\.]+)"), ("LAN", r"lan(?:\s+led)?\s*:\s*([^\.]+)"), ("Wi-Fi", r"wi[\- ]?fi(?:\s+led)?\s*:\s*([^\.]+)"), ("Power", r"power(?:\s+led)?\s*:\s*([^\.]+)"), ): match = re.search(pattern, body, flags=re.IGNORECASE) if match: _add(label, match.group(1)) low = body.lower() if "ac/dc adapter is on and plugged in" in low or "ac/dc adapter is off" in low: parts: List[str] = [] if "ac/dc adapter is on and plugged in" in low: parts.append("On when the AC/DC adapter is connected") if "ac/dc adapter is off" in low: parts.append("Off when the AC/DC adapter is off") _add("Power", "; ".join(parts) + ".") if "wi-fi is enabled" in low or "establishing connection over wi-fi" in low or "wps function is activated" in low: parts = [] if "wi-fi is enabled" in low: parts.append("On when Wi-Fi is enabled") if "wi-fi is turned off or disabled" in low: parts.append("Off when Wi-Fi is disabled") if ("establishing connection over wi-fi" in low) or ("wps function is activated" in low): parts.append("Blinking during Wi-Fi connection setup or WPS activity") _add("Wi-Fi", "; ".join(parts) + ".") if "connected over lan port" in low or "not connected over lan port" in low: parts = [] if "connected over lan port" in low: parts.append("On when an Ethernet link is present") if "not connected over lan port" in low: parts.append("Off when no Ethernet link is present") _add("LAN", "; ".join(parts) + ".") if "connected via 4g network" in low or "4g data connection is off" in low: parts = [] if "connected via 4g network" in low: parts.append("On when connected to 4G/LTE") if "4g data connection is off" in low: parts.append("Off when the 4G data connection is off") _add("4G", "; ".join(parts) + ".") if "connected via 5g network" in low or "5g data connection is off" in low: parts = [] if "connected via 5g network" in low: parts.append("On when connected to 5G") if "5g data connection is off" in low: parts.append("Off when the 5G data connection is off") _add("5G", "; ".join(parts) + ".") if any(token in low for token in ("good 4g/5g signal", "weak 4g/5g signal", "no signal", "no sim")): parts = [] if "good 4g/5g signal" in low: parts.append("Green indicates good 4G/5G signal") if "blue on" in low: parts.append("Blue indicates normal or mid-level signal") if "weak 4g/5g signal" in low or "red on" in low: parts.append("Red indicates weak signal") if "error: no sim" in low or "red blinking" in low: parts.append("Blinking red indicates no SIM") if "off no signal" in low: parts.append("Off indicates no signal") _add("Signal", "; ".join(parts) + ".") return legend def _router_antenna_vendor_key(self, text: str) -> str: low = str(text or "").lower() if ("ericsson" in low) or ("cradlepoint" in low): return "ericsson_cradlepoint" if ("semtech" in low) or ("sierra" in low): return "semtech" if "peplink" in low or "pepwave" in low: return "peplink" if "digi" in low: return "digi" return "" def _router_vendor_antenna_catalog_paths(self, vendor_key: str) -> List[str]: if vendor_key not in _ROUTER_ANTENNA_PRIORITY_VENDORS: return [] out: List[str] = [] for rel in self._router_file_map.values(): rel_s = str(rel or "") rel_low = rel_s.lower().replace("\\", "/") if "01_documents/antennas/" not in rel_low: continue if f"01_documents/antennas/{vendor_key}/" in rel_low: out.append(rel_s) continue file_low = Path(rel_s).name.lower() if vendor_key == "ericsson_cradlepoint" and (("ericsson" in file_low) or ("cradlepoint" in file_low)): out.append(rel_s) elif vendor_key == "semtech" and (("semtech" in file_low) or ("sierra" in file_low)): out.append(rel_s) elif vendor_key == "peplink" and ("peplink" in file_low or "pepwave" in file_low): out.append(rel_s) elif vendor_key == "digi" and ("digi" in file_low): out.append(rel_s) return list(dict.fromkeys(out)) def _router_vendor_antenna_context(self, *, vendor_key: str, model_name: str, message: str) -> Optional[Dict[str, Any]]: paths = self._router_vendor_antenna_catalog_paths(vendor_key) if not paths: return None query = f"{model_name} antenna indoor outdoor fixed vehicle directional kiosk case recommendation {message}" hits = self._router_index_search_hits(query, k=14) path_keys = {str(p).lower().replace("\\", "/") for p in paths} vendor_hits: List[Dict[str, Any]] = [] for h in hits: rel = str(h.get("relative_path") or h.get("rel") or "") rel_key = rel.lower().replace("\\", "/") if rel_key in path_keys: vendor_hits.append(h) continue doc_name = Path(str(h.get("doc") or h.get("file_name") or "")).name.lower() if any(doc_name == Path(p).name.lower() for p in paths): vendor_hits.append(h) chosen_hit = vendor_hits[0] if vendor_hits else None chosen_rel = str(chosen_hit.get("relative_path") or chosen_hit.get("rel") or "") if chosen_hit else "" if not chosen_rel: chosen_rel = str(paths[0]) chosen_doc = Path(chosen_rel).name or ( Path(str(chosen_hit.get("doc") or chosen_hit.get("file_name") or "")).name if chosen_hit else "" ) excerpt = _norm(str(chosen_hit.get("excerpt") or chosen_hit.get("text") or "")) if chosen_hit else "" if not excerpt: excerpt = ( f"Vendor antenna catalog prioritized for {model_name}. " "Use this as primary source before Parsec fallback." ) return { "doc": chosen_doc or Path(paths[0]).name, "relative_path": chosen_rel, "excerpt": _truncate(excerpt, 260), "score": float(chosen_hit.get("score") or 0.93) if chosen_hit else 0.93, } def _extract_router_models_cached(self, message: str) -> List[str]: norm = _normalize_router_query_text(_norm(message)) ck = f"models:{norm}" cached = self._l2_get(ck) if isinstance(cached, list): return [str(x) for x in cached] models = _extract_router_models(norm) token_idx = getattr(self, "_router_token_model_index", {}) or {} # Concept-only asks should not be force-mapped to model families. if _looks_like_wifi_generation_concept(norm): self._l2_set(ck, []) return [] # If we already have explicit model tokens, preserve them and avoid broad token expansion. if models: out: List[str] = [] seen_explicit: set[str] = set() fact_rows = getattr(self, "_router_fact_rows", {}) or {} life_rows = getattr(self, "_router_lifecycle_rows", {}) or {} words = re.findall(r"[A-Za-z0-9]+", norm) compact_words = [_compact_model(w) for w in words if _compact_model(w)] exact_hits: List[str] = [] for n in (3, 2, 1): for i in range(0, max(0, len(compact_words) - n + 1)): gram = "".join(compact_words[i : i + n]) if (not gram) or (gram in exact_hits): continue if (gram in fact_rows) or (gram in life_rows): if any(existing.startswith(gram) and existing != gram for existing in exact_hits): continue exact_hits = [existing for existing in exact_hits if not gram.startswith(existing)] exact_hits.append(gram) for exact in exact_hits: if exact in seen_explicit: continue seen_explicit.add(exact) out.append(exact) for m in models: nm = _compact_model(m) if (not nm) or ((nm not in fact_rows) and (nm not in life_rows) and (nm not in token_idx)): nm = self._normalize_router_model(m) or nm if any(existing.startswith(nm) and existing != nm for existing in seen_explicit): continue if (not nm) or (nm in seen_explicit): continue seen_explicit.add(nm) out.append(nm) self._l2_set(ck, out[:8]) return out[:8] # Token-index expansion for fragmented or nickname-like model mentions. words = re.findall(r"[A-Za-z0-9]+", norm) compact_words = [_compact_model(w) for w in words if _compact_model(w)] cands: List[str] = [] for w in compact_words: if w in {"2G", "3G", "4G", "5G"}: continue if w in _ROUTER_NON_DEVICE_TERMS: continue if re.fullmatch(r"\d+X", w): continue if re.fullmatch(r"(?:FOR|WITH|HAS|HAVE|SITE|SITES|CUSTOMER|CUST)\d+X?", w): continue if len(w) <= 2: continue if w.isdigit() and len(w) == 4: continue if not any(ch.isdigit() for ch in w): continue direct = token_idx.get(w, []) if direct: cands.extend(direct) else: corrected = self._closest_router_token(w) if corrected and corrected != w: cands.extend(token_idx.get(corrected, [])) for n in (2, 3): for i in range(0, max(0, len(compact_words) - n + 1)): gram = "".join(compact_words[i : i + n]) if gram in {"2G", "3G", "4G", "5G"}: continue if gram in _ROUTER_NON_DEVICE_TERMS: continue if re.fullmatch(r"\d+X", gram): continue if re.fullmatch(r"(?:FOR|WITH|HAS|HAVE|SITE|SITES|CUSTOMER|CUST)\d+X?", gram): continue if len(gram) <= 2: continue if gram.isdigit() and len(gram) == 4: continue if not any(ch.isdigit() for ch in gram): continue direct = token_idx.get(gram, []) if direct: cands.extend(direct) else: corrected = self._closest_router_token(gram) if corrected and corrected != gram: cands.extend(token_idx.get(corrected, [])) seen = {_compact_model(m) for m in models} for c in cands: cc = _compact_model(c) if (not cc) or (cc in seen): continue seen.add(cc) models.append(cc) self._l2_set(ck, models[:12]) return models[:12] def _latest_user_message_from_state(self, state_blob: Dict[str, Any]) -> str: if not isinstance(state_blob, dict): return "" hist = state_blob.get("history") if not isinstance(hist, list): return "" for row in reversed(hist): if not isinstance(row, dict): continue if str(row.get("role") or "").strip().lower() != "user": continue content = _norm(str(row.get("content") or "")) if content: return content return "" def _expand_followup_message(self, message: str, st: UnifiedKnowledgebaseState) -> str: msg = _norm(str(message or "")) if not msg: return msg low = msg.lower() antenna_followup = ( ("antenna" in low) and (not self._extract_router_models_cached(msg)) and any(x in low for x in ("indoor", "outdoor", "vehicle", "these", "those", "for each", "for both", "option", "options", "recommend")) ) if (not any(h in low for h in _FOLLOWUP_CONTEXT_HINTS)) and (not antenna_followup): return msg if self._extract_router_models_cached(msg): return msg mode_state_map = { "router_lifecycle": st.router_lifecycle_state, "router_docs": st.router_docs_state, "pots": st.pots_state, "masters": st.masters_state, } mode_order = ["router_lifecycle", "router_docs", "pots", "masters"] last_mode = _norm_mode(st.last_mode) if last_mode in mode_state_map: mode_order = [last_mode] + [m for m in mode_order if m != last_mode] candidates = [] if _norm(st.last_user_message): candidates.append(_norm(st.last_user_message)) candidates.extend([self._latest_user_message_from_state(mode_state_map.get(mode_name, {})) for mode_name in mode_order]) for prior in candidates: prior_n = _norm(prior) if not prior_n: continue if prior_n.lower() == low: continue return f"{prior_n}\nFollow-up request: {msg}" return msg def _lookup_router_key(self, index: Dict[str, Dict[str, Any]], model_key: str) -> str: raw_key = _compact_model(model_key) if (not raw_key) or (not index): return "" if raw_key in index: return raw_key key = self._router_alias_map.get(raw_key, raw_key) if key in index: return key trie = self._router_fact_trie if index is self._router_fact_rows else self._router_lifecycle_trie forward, reverse = self._trie_candidates(trie, key) candidates: List[Tuple[int, int, str]] = [] for cand in forward: if cand: candidates.append((0, abs(len(cand) - len(key)), cand)) for cand in reverse: if cand: candidates.append((1, abs(len(cand) - len(key)), cand)) if not candidates: # conservative fallback when trie has no branch for this key. for cand in index.keys(): if (not cand) or (key[0] != cand[0]): continue if cand.startswith(key): candidates.append((0, abs(len(cand) - len(key)), cand)) elif key.startswith(cand): candidates.append((1, abs(len(cand) - len(key)), cand)) if not candidates: return "" candidates.sort(key=lambda x: (x[0], x[1], -len(str(x[2])), x[2])) best = str(candidates[0][2]) if best != key: if not _safe_model_variant_match(key, best): return "" return best def _lookup_router_fact_key(self, model_key: str) -> str: return self._lookup_router_key(self._router_fact_rows, model_key) def _lookup_router_lifecycle_key(self, model_key: str) -> str: return self._lookup_router_key(self._router_lifecycle_rows, model_key) def _lookup_router_lifecycle_key_relaxed(self, model_key: str) -> str: strict = self._lookup_router_lifecycle_key(model_key) if strict: return strict tok = _compact_model(model_key) if not tok: return "" life_rows = getattr(self, "_router_lifecycle_rows", {}) or {} token_idx = getattr(self, "_router_token_model_index", {}) or {} short_vendor_numeric = bool(re.match(r"^[A-Z]{1,4}\d{2,4}[A-Z]?$", tok)) one_letter_numeric = re.sub(r"^[A-Z](?=\d{3,4}[A-Z]?$)", "", tok) if one_letter_numeric == tok: one_letter_numeric = "" search_tokens: List[str] = [tok] stripped = _strip_router_vendor_prefix(tok) if stripped and (stripped not in search_tokens) and (not short_vendor_numeric): search_tokens.append(stripped) if short_vendor_numeric and one_letter_numeric and (one_letter_numeric not in search_tokens): # Support vendor-prefixed legacy keys like C819 -> 819 Integrated Router # without reopening broad XR*/MG* digit-only substitutions. search_tokens.append(one_letter_numeric) if not short_vendor_numeric: simple = re.sub(r"^[A-Z]{1,3}(?=\d{2,4}$)", "", tok) if simple and simple not in search_tokens: search_tokens.append(simple) d_sig = _digit_signature(tok) if d_sig and d_sig not in search_tokens and (not short_vendor_numeric): search_tokens.append(d_sig) candidates: List[str] = [] for st in search_tokens: for c in token_idx.get(st, []) or []: if c in life_rows and c not in candidates: candidates.append(c) if not candidates and (not short_vendor_numeric): for c in life_rows.keys(): c_compact = _compact_model(c) if any(st and (st in c_compact) for st in search_tokens): if c not in candidates: candidates.append(c) if not candidates: return "" if short_vendor_numeric: filtered: List[str] = [] for cand in candidates: cc = _compact_model(cand) if _safe_model_variant_match(tok, cc): filtered.append(cand) continue if stripped and _safe_model_variant_match(stripped, cc): filtered.append(cand) continue if one_letter_numeric and _safe_model_variant_match(one_letter_numeric, cc): filtered.append(cand) continue if one_letter_numeric and cc.startswith(one_letter_numeric) and ( ("INTEGRATED" in cc) or ("ROUTER" in cc) ): filtered.append(cand) continue if one_letter_numeric and (_digit_signature(cc) == _digit_signature(one_letter_numeric)) and ( ("INTEGRATED" in cc) or ("ROUTER" in cc) ): filtered.append(cand) candidates = filtered if not candidates: return "" def _rank(cand: str) -> Tuple[int, int]: cc = _compact_model(cand) if cc == tok: return (0, 0) if cc.startswith(tok) or tok.startswith(cc): return (1, abs(len(cc) - len(tok))) if short_vendor_numeric and ("INTEGRATED" in cc) and ("ROUTER" in cc): return (2, abs(len(cc) - len(tok))) if one_letter_numeric and (cc.startswith(one_letter_numeric) or one_letter_numeric.startswith(cc)): return (3, abs(len(cc) - len(one_letter_numeric))) if stripped and (cc.startswith(stripped) or stripped.startswith(cc)): return (3, abs(len(cc) - len(stripped))) if d_sig and (_digit_signature(cc) == d_sig): return (4, abs(len(cc) - len(d_sig))) return (9, abs(len(cc) - len(tok))) best_idx = 0 best_rank = _rank(candidates[0]) for idx, cand in enumerate(candidates[1:], start=1): rank = _rank(cand) if rank < best_rank: best_rank = rank best_idx = idx return str(candidates[best_idx]) def _infer_catalog_tech(self, fact_row: Dict[str, Any]) -> str: modem = _norm((fact_row or {}).get("modem", "")).lower() if "5g" in modem: return "5G" if ("4g" in modem) or ("lte" in modem): return "4G" return "" def _rapid_router_catalog_snapshot(self, *, force_refresh: bool = False) -> Dict[str, Any]: if not callable(self._rapid_router_catalog_provider): return {"config": {}, "products": []} now = time.time() if ( (not force_refresh) and isinstance(self._rapid_router_catalog_cache, dict) and (self._rapid_router_catalog_cache_expires_at > now) ): return json.loads(json.dumps(self._rapid_router_catalog_cache)) fetched: Dict[str, Any] = {} try: raw = self._rapid_router_catalog_provider() if isinstance(raw, dict): fetched = raw except Exception: fetched = {} def _money_float(value: Any) -> Optional[float]: if value is None: return None raw = _norm(value) if not raw: return None raw = raw.replace("$", "").replace(",", "") try: return float(raw) except Exception: return None normalized_products: List[Dict[str, Any]] = [] for row in list(fetched.get("products") or []): if not isinstance(row, dict): continue name = _norm(row.get("name", "")) sku = _norm(row.get("sku", "")) manufacturer = _norm(row.get("manufacturer", "")) technology = _norm(row.get("technology", "")) description = _norm(row.get("description", "")) setup_notes = [_norm(x) for x in list(row.get("setup_notes") or []) if _norm(x)] model_keys: List[str] = [] for seed in (name, sku): for tok in self._extract_router_models_cached(seed): mk = self._normalize_router_model(tok) or _compact_model(tok) if (not mk) or (mk in model_keys): continue model_keys.append(mk) if not model_keys: fallback_key = _compact_model(sku) or _compact_model(name) if fallback_key: model_keys.append(fallback_key) match_keys: set[str] = set() for val in (row.get("id", ""), name, sku): c = _compact_model(val) if c: match_keys.add(c) stripped = _strip_router_vendor_prefix(c) if stripped: match_keys.add(stripped) for mk in model_keys: c = _compact_model(mk) if c: match_keys.add(c) stripped = _strip_router_vendor_prefix(c) if stripped: match_keys.add(stripped) for part in re.findall(r"[A-Za-z0-9]+", f"{name} {sku}"): c = _compact_model(part) if c and any(ch.isdigit() for ch in c): match_keys.add(c) normalized_products.append( { "id": _norm(row.get("id", "")), "name": name, "manufacturer": manufacturer, "technology": technology, "description": description, "sku": sku, "msrp": _money_float(row.get("msrp")), "price_primary": _money_float(row.get("price_primary")), "price_backup": _money_float(row.get("price_backup")), "setup_notes": setup_notes[:8], "_model_keys": model_keys[:8], "_match_keys": sorted(match_keys), } ) snapshot = { "config": fetched.get("config", {}) if isinstance(fetched.get("config"), dict) else {}, "products": normalized_products, } self._rapid_router_catalog_cache = snapshot self._rapid_router_catalog_cache_expires_at = now + float(self._rapid_router_catalog_cache_ttl_s) return json.loads(json.dumps(snapshot)) def _rapid_router_catalog_fingerprint(self) -> str: if not callable(self._rapid_router_catalog_provider): return "" snapshot = self._rapid_router_catalog_snapshot(force_refresh=False) products = list(snapshot.get("products") or []) normalized_rows: List[Tuple[str, str, str, str, float, float, float]] = [] for row in products: if not isinstance(row, dict): continue normalized_rows.append( ( _norm(row.get("id", "")), _norm(row.get("name", "")), _norm(row.get("sku", "")), _norm(row.get("technology", "")), _safe_float(row.get("msrp"), default=-1.0), _safe_float(row.get("price_primary"), default=-1.0), _safe_float(row.get("price_backup"), default=-1.0), ) ) payload = json.dumps(sorted(normalized_rows), separators=(",", ":"), sort_keys=False) return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16] def _split_rapid_router_context_message(self, message: str) -> Tuple[str, bool]: raw = str(message or "") marker = "context from rapid router form:" low = raw.lower() idx = low.find(marker) if idx < 0: return raw.strip(), False primary = raw[:idx].strip() return (primary or raw.strip()), True def _rapid_router_explicit_models(self, message: str) -> List[str]: out: List[str] = [] seen: set[str] = set() for tok in self._extract_router_models_cached(message): label = _norm(tok) if (not label) or (label in seen): continue seen.add(label) out.append(label) for tok in _ROUTER_MODEL_TOKEN_RE.findall(str(message or "")): label = _norm(tok) compact = _compact_model(label) if (not compact) or compact.isdigit(): continue if label in seen: continue seen.add(label) out.append(label) return out def _rapid_router_catalog_model_present( self, model_token: str, products: Sequence[Dict[str, Any]], ) -> bool: target = _compact_model(self._normalize_router_model(model_token) or model_token) if not target: return False candidates = self._rapid_router_catalog_matches(model_token, products) for row in candidates[:4]: keys = {_compact_model(x) for x in list(row.get("_match_keys") or []) if _compact_model(x)} if not keys: continue if target in keys: return True if any(k.startswith(target) or target.startswith(k) for k in keys): return True return False def _rapid_router_models_missing_from_catalog( self, model_tokens: Sequence[str], products: Sequence[Dict[str, Any]], ) -> List[str]: missing: List[str] = [] seen: set[str] = set() for raw in model_tokens: label = _norm(raw) if (not label) or (label in seen): continue seen.add(label) if not self._rapid_router_catalog_model_present(label, products): missing.append(label) return missing def _rapid_router_catalog_matches(self, message: str, products: Sequence[Dict[str, Any]]) -> List[Dict[str, Any]]: if not products: return [] low = _normalize_router_query_text(message) query_tokens: List[str] = [] for tok in self._extract_router_models_cached(message): mk = self._normalize_router_model(tok) or _compact_model(tok) if mk and (mk not in query_tokens): query_tokens.append(mk) for raw in _ROUTER_MODEL_TOKEN_RE.findall(str(message or "")): mk = self._normalize_router_model(raw) or _compact_model(raw) if mk and (mk not in query_tokens): query_tokens.append(mk) query_compacts = [_compact_model(x) for x in query_tokens if _compact_model(x)] scored: List[Tuple[int, int, Dict[str, Any]]] = [] for idx, row in enumerate(products): if not isinstance(row, dict): continue keys = {_compact_model(x) for x in list(row.get("_match_keys") or []) if _compact_model(x)} if not keys: continue score = 0 for q in query_compacts: if any(_safe_model_variant_match(q, k) or _safe_model_variant_match(k, q) for k in keys): score += 8 name_low = _norm(row.get("name", "")).lower() sku_low = _norm(row.get("sku", "")).lower() rid_low = _norm(row.get("id", "")).lower() if name_low and name_low in low: score += 5 if sku_low and sku_low in low: score += 6 if rid_low and rid_low in low: score += 3 if query_compacts and score <= 0: continue if score > 0: scored.append((score, -idx, row)) if not scored: return [] scored.sort(key=lambda x: (-int(x[0]), int(x[1]))) out: List[Dict[str, Any]] = [] seen: set[str] = set() for _, _, row in scored: pid = _norm(row.get("id", "")) or _norm(row.get("name", "")) if (not pid) or (pid in seen): continue seen.add(pid) out.append(row) return out def _rapid_router_catalog_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: snapshot = self._rapid_router_catalog_snapshot(force_refresh=False) products = [p for p in list(snapshot.get("products") or []) if isinstance(p, dict)] if not products: return None primary_message, has_rr_context = self._split_rapid_router_context_message(message) intent_message = primary_message if has_rr_context else message explicit_models = self._rapid_router_explicit_models(intent_message) low = _normalize_router_query_text(intent_message) selected_context_request = ( has_rr_context and (not explicit_models) and any( x in low for x in ( "selected router", "selected routers", "selected model", "selected models", "selected device", "selected devices", "my selected", "my routers", "my devices", "current selection", "these routers", "those routers", "the selected", ) ) ) asks_price = any( x in low for x in ( "msrp", "price", "pricing", "cost", "how much", "list price", "primary plan", "backup plan", "unit price", ) ) asks_features = any( x in low for x in ( "feature", "features", "spec", "specs", "detail", "details", "description", "install caveat", "setup note", "wifi", ) ) asks_deep_specs = any( x in low for x in ( "wan", "lan", "port", "ports", "rf", "connector", "connectors", "antenna", "antennas", "adapter", "serial", "poe", "throughput", "modem", ) ) asks_operational_guidance = any( x in low for x in ( "activation verification", "configuration pricing", "order totals", "shipping charge", "sign and submit", "mandatory field", "required field", "address validates", "suggested format", "helper decide", "catalog versus faq", "catalog vs faq", "when to use", "too wide on mobile", "stale hashed", ) ) asks_catalog = any( x in low for x in ( "orderable", "available products", "available routers", "which products", "what products", "list products", "list routers", "router catalog", "catalog", ) ) asks_listing_check = bool(explicit_models) and any( x in low for x in ( "listed", "in our docs", "in the docs", "in our catalog", "in the catalog", "close match", "near match", "closest match", "internal match", ) ) compare_like = self._is_router_compare_like(intent_message) asks_documented_specs_only = any( x in low for x in ( "documented specs only", "from documented specs only", "docs only", "internal docs only", "internal sources only", ) ) if asks_operational_guidance: return None if asks_listing_check: return None if compare_like and asks_documented_specs_only: return None if self._router_catalog_question_needs_documentation(intent_message): return None if asks_deep_specs and (not selected_context_request): return None if not (asks_catalog or asks_price or asks_features or compare_like): return None # In helper context, if the user explicitly requested models and any are not # sold in Rapid Router, defer to standard router-doc fallback paths. if has_rr_context and explicit_models: missing_models = self._rapid_router_models_missing_from_catalog(explicit_models, products) if missing_models: return None if explicit_models and compare_like: missing_models = self._rapid_router_models_missing_from_catalog(explicit_models, products) if missing_models: return None match_message = message if selected_context_request else intent_message matched = self._rapid_router_catalog_matches(match_message, products) if explicit_models: ordered: List[Dict[str, Any]] = [] seen_ids: set[str] = set() for model in explicit_models: model_matches = self._rapid_router_catalog_matches(model, products) if not model_matches: continue row = model_matches[0] pid = _norm(row.get("id", "")) or _norm(row.get("name", "")) if (not pid) or (pid in seen_ids): continue seen_ids.add(pid) ordered.append(row) if ordered: for row in matched: pid = _norm(row.get("id", "")) or _norm(row.get("name", "")) if (not pid) or (pid in seen_ids): continue seen_ids.add(pid) ordered.append(row) matched = ordered if asks_catalog and (not matched) and (not compare_like) and (not asks_price) and (not asks_features): matched = list(products) if not matched: return None if compare_like and explicit_models and len(explicit_models) >= 2 and len(matched) < 2: return None def _price_cell(value: Any) -> str: numeric = _safe_float(value, default=-1.0) if numeric < 0: return "Not listed (abstained)" return f"${numeric:,.2f}" def _catalog_device_details(row: Dict[str, Any]) -> str: description = _norm(row.get("description", "")) setup_notes = "; ".join([_norm(x) for x in list(row.get("setup_notes") or []) if _norm(x)]) corpus = "; ".join([x for x in (description, setup_notes) if x]) low = corpus.lower() parts: List[str] = [] # Wi-Fi details wifi_detail = "" if any(x in low for x in ("no wi-fi", "no wifi", "without wi-fi", "without wifi")): wifi_detail = "Wi-Fi: no" elif any(x in low for x in ("wi-fi", "wifi", "802.11")): wifi_match = re.search(r"(wi[- ]?fi\s*(?:[4-7]|6e)|802\.11[^\s,;)]*)", corpus, re.IGNORECASE) wifi_label = _norm(wifi_match.group(1)) if wifi_match else "" if wifi_label: wifi_detail = f"Wi-Fi: yes ({wifi_label})" else: wifi_detail = "Wi-Fi: yes" if wifi_detail: parts.append(wifi_detail) # Port details port_clauses: List[str] = [] for clause in re.split(r"[.;]", corpus): c = _norm(clause) if not c: continue cl = c.lower() if any(k in cl for k in ("wan", "lan", "ethernet", "port", "rj45", "usb-c", "poe")): port_clauses.append(c) if len(port_clauses) >= 2: break if port_clauses: parts.append(f"Ports: {_truncate('; '.join(port_clauses), 120)}") # Housing details housing_tokens: List[str] = [] for token in ("outdoor", "indoor", "rugged", "industrial", "compact", "portable", "vehicle", "mobile", "cpe"): if token in low and token not in housing_tokens: housing_tokens.append(token.upper() if token == "cpe" else token) if housing_tokens: parts.append(f"Housing: {', '.join(housing_tokens)}") # Battery details battery_detail = "" if "battery" in low: if any(x in low for x in ("no battery", "without battery")): battery_detail = "Battery: no" elif "removable battery" in low: battery_detail = "Battery: yes (removable)" elif "battery backup" in low: battery_detail = "Battery: yes (backup)" else: battery_detail = "Battery: yes" if battery_detail: parts.append(battery_detail) return "; ".join(parts) if parts else "Not listed (abstained)" files = ["/api/rapid_router/store"] sources: List[Dict[str, Any]] = [] for idx, row in enumerate(matched[:12], start=1): name = _norm(row.get("name", "")) or _norm(row.get("id", "")) excerpt = ( f"{name}: MSRP={_price_cell(row.get('msrp'))}; " f"Primary={_price_cell(row.get('price_primary'))}; Backup={_price_cell(row.get('price_backup'))}; " f"Tech={_norm(row.get('technology', 'Not listed'))}." ) sources.append( { "id": f"RR{idx}", "domain": "router_docs", "doc": "rapid_router_store.json", "relative_path": "/api/rapid_router/store", "chunk_id": f"rapid_router_catalog:{_norm(row.get('id', '')) or idx}", "location": "", "excerpt": excerpt[:420], "score": 1.0, } ) if asks_catalog and (not asks_price) and (not asks_features) and (not compare_like): lines = [ "Rapid Router orderable product catalog (internal store):", "", "| Model | Manufacturer | Technology | MSRP | Primary plan | Backup plan |", "| --- | --- | --- | ---: | ---: | ---: |", ] for row in matched[:16]: lines.append( "| " + " | ".join( [ _md_cell(_norm(row.get("name", "")) or _norm(row.get("id", ""))), _md_cell(_norm(row.get("manufacturer", "")) or "Not listed"), _md_cell(_norm(row.get("technology", "")) or "Not listed"), _md_cell(_price_cell(row.get("msrp"))), _md_cell(_price_cell(row.get("price_primary"))), _md_cell(_price_cell(row.get("price_backup"))), ] ) + " |" ) return { "assistant": _format_shell( "\n".join(lines), [ "Returned from the internal Rapid Router store used by the order workflow.", "MSRP remains fixed; primary/backup values are plan-linked order pricing fields.", ], [ "Ask `how much is ` for a focused pricing lookup.", "Ask `compare vs ` for side-by-side fields from the same store.", ], ), "sources": sources[:12], "files": files, "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_rapid_router_catalog_list_fast", "web_assisted": False, "model_count": len(matched[:16]), }, } if compare_like and len(matched) >= 2: lines = [ "Rapid Router comparison (store-backed orderable products):", "", "| Model | Manufacturer | Technology | MSRP | Primary plan | Backup plan | Device details |", "| --- | --- | --- | ---: | ---: | ---: | --- |", ] for row in matched[:4]: device_details = _catalog_device_details(row) lines.append( "| " + " | ".join( [ _md_cell(_norm(row.get("name", "")) or _norm(row.get("id", ""))), _md_cell(_norm(row.get("manufacturer", "")) or "Not listed"), _md_cell(_norm(row.get("technology", "")) or "Not listed"), _md_cell(_price_cell(row.get("msrp"))), _md_cell(_price_cell(row.get("price_primary"))), _md_cell(_price_cell(row.get("price_backup"))), _md_cell(device_details), ] ) + " |" ) return { "assistant": _format_shell( "\n".join(lines), [ "Comparison is sourced from the internal Rapid Router catalog backing the order UI.", "If a requested model is not in this store, router-doc CSV/manual fallback handles it.", ], [ "Ask for a specific model's full documented specs if you need deeper datasheet fields.", ], ), "sources": sources[:8], "files": files, "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_rapid_router_catalog_compare_fast", "web_assisted": False, "model_count": len(matched[:4]), }, } if asks_price: lines = [ "Rapid Router pricing lookup (internal store):", "", "| Model | SKU | MSRP | Primary plan | Backup plan |", "| --- | --- | ---: | ---: | ---: |", ] for row in matched[:8]: lines.append( "| " + " | ".join( [ _md_cell(_norm(row.get("name", "")) or _norm(row.get("id", ""))), _md_cell(_norm(row.get("sku", "")) or "Not listed"), _md_cell(_price_cell(row.get("msrp"))), _md_cell(_price_cell(row.get("price_primary"))), _md_cell(_price_cell(row.get("price_backup"))), ] ) + " |" ) return { "assistant": _format_shell( "\n".join(lines), [ "Returned from Rapid Router store rows for orderable products.", "If a model is missing here, fallback pricing comes from normalized router pricing CSV artifacts.", ], [ "Ask `all Rapid Router products` to see full store inventory.", ], ), "sources": sources[:8], "files": files, "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_rapid_router_catalog_price_fast", "web_assisted": False, "model_count": len(matched[:8]), }, } row = matched[0] device_details = _catalog_device_details(row) lines = [ f"Rapid Router product details for `{_norm(row.get('name', '')) or _norm(row.get('id', ''))}`:", "", "| Field | Value |", "| --- | --- |", f"| Manufacturer | {_md_cell(_norm(row.get('manufacturer', '')) or 'Not listed')} |", f"| Technology | {_md_cell(_norm(row.get('technology', '')) or 'Not listed')} |", f"| SKU | {_md_cell(_norm(row.get('sku', '')) or 'Not listed')} |", f"| MSRP | {_md_cell(_price_cell(row.get('msrp')))} |", f"| Primary plan price | {_md_cell(_price_cell(row.get('price_primary')))} |", f"| Backup plan price | {_md_cell(_price_cell(row.get('price_backup')))} |", f"| Description | {_md_cell(_norm(row.get('description', '')) or 'Not listed (abstained)')} |", f"| Device details | {_md_cell(device_details)} |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Details come from the internal Rapid Router order catalog (store-backed).", "For deeper modem/connector/manual-only fields, ask for a documented router-specs lookup next.", ], [ "Ask `compare vs ` for a side-by-side store table.", ], ), "sources": sources[:4], "files": files, "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_rapid_router_catalog_feature_fast", "web_assisted": False, "model_count": 1, "evidence_thin_for_query": self._query_prefers_authoritative_evidence(intent_message, "router_docs"), }, } def _detect_router_conflicts_for_key(self, model_key: str) -> List[str]: life_key = self._lookup_router_lifecycle_key(model_key) fact_key = self._lookup_router_fact_key(model_key) if (not life_key) or (not fact_key): return [] life = self._router_lifecycle_rows.get(life_key, {}) fact = self._router_fact_rows.get(fact_key, {}) conflicts: List[str] = [] life_tech = _norm(life.get("tech", "")).upper() fact_tech = self._infer_catalog_tech(fact).upper() if life_tech and fact_tech and (life_tech != fact_tech): conflicts.append( f"Tech mismatch: lifecycle CSV says `{life_tech}` but catalog modem field suggests `{fact_tech}`." ) life_status = _norm(life.get("status", "")).lower() eos = _norm(life.get("eos", "")) eol = _norm(life.get("eol", "")) now_year = int(time.strftime("%Y")) try: eol_year = int(float(eol)) if eol else 0 except Exception: eol_year = 0 if ("end of life" in life_status) and eol_year and eol_year > now_year + 5: conflicts.append(f"Lifecycle anomaly: EOL year `{eol}` looks unusually far in the future.") if ("end of life" in life_status) and (not eos) and (not eol): conflicts.append("Lifecycle status marks end-of-life but EOS/EOL dates are missing.") return conflicts def _router_compare_lifecycle_alerts(self, message: str) -> List[Dict[str, str]]: if not self._is_router_compare_like(message): return [] extracted = [self._normalize_router_model(x) or _compact_model(x) for x in self._extract_router_models_cached(message)] extracted = [x for x in extracted if x] if len(extracted) < 2: return [] alerts: List[Dict[str, str]] = [] seen: set[str] = set() for raw_model in extracted[:4]: # Compare-time lifecycle notes should only use exact lifecycle keys. The relaxed # matcher is intentionally broader for lifecycle searches, but it can over-match # short vendor/model tokens like K300NB -> LS300 in user-visible compare notes. life_key = self._lookup_router_lifecycle_key(raw_model) if (not life_key) or (life_key in seen): continue seen.add(life_key) row = self._router_lifecycle_rows.get(life_key, {}) or {} status = _norm(row.get("status", "")) or self._derive_lifecycle_status(row.get("eos", ""), row.get("eol", "")) status_low = status.lower() if ("end of sale" not in status_low) and ("end of life" not in status_low): continue eos = _norm(row.get("eos", "")) eol = _norm(row.get("eol", "")) label = ( self._router_display_name(row, life_key) or _norm(raw_model) or life_key ) detail_parts = [status] if eos: detail_parts.append(f"EOS {eos}") if eol: detail_parts.append(f"EOL {eol}") alerts.append( { "model": label, "status": status, "eos": eos, "eol": eol, "detail": "; ".join(detail_parts), "life_key": life_key, } ) return alerts def _router_compare_lifecycle_source(self, alert: Dict[str, str], source_id: str) -> Dict[str, Any]: model = _norm(alert.get("model", "")) or _norm(alert.get("life_key", "")) or "router" detail = _norm(alert.get("detail", "")) or _norm(alert.get("status", "")) or "Lifecycle status noted." eos_csv_path = getattr(self.router_core, "eos_csv_path", "") or "routers_eos_eol_by_sku.csv" doc_name = Path(str(eos_csv_path)).name or "routers_eos_eol_by_sku.csv" return { "id": source_id, "domain": "router_docs", "doc": doc_name, "relative_path": doc_name, "chunk_id": f"lifecycle_compare:{_compact_model(alert.get('life_key') or model)}", "location": "", "excerpt": f"{model}: {detail}.", "score": 0.91, } def _build_router_fact_index(self) -> Dict[str, Dict[str, Any]]: csv_paths = [p for p in self._router_fact_csv_paths if p.exists() and p.is_file()] if not csv_paths: return {} rows: Dict[str, Dict[str, Any]] = {} for csv_path in csv_paths: with _open_csv_with_fallback(csv_path) as f: reader = csv.DictReader(f) headers = list(reader.fieldnames or []) header_lut = {str(h).strip().lower(): str(h) for h in headers} last_model_key = "" def col(*cands: str) -> str: for cand in cands: h = header_lut.get(str(cand).strip().lower(), "") if h: return h return "" model_col = col("model", "model_key", "device", "router") title_col = col("title") sku_col = col("sku") manufacturer_col = col("manufacturer", "vendor") type_col = col("type", "item_type", "device_type") wan_col = col("WAN ports and speed", "WAN ports", "wan ports and speed", "wan_ports") lan_col = col("LAN ports and speed", "LAN ports", "lan ports and speed", "lan_ports") antennas_col = col("Antennas (internal/external/both)", "Antennas", "antennas", "antennas_rf") rf_col = col( "RF ports/connectors / adapters", "RF ports / connectors / adapters", "rf ports/connectors / adapters", "rf ports / connectors / adapters", "antennas_rf", "connector_summary", ) modem_col = col("Modem Type", "Modem technology", "modem type", "modem_type") wifi_col = col("WiFi type", "WiFi", "wifi type", "wifi") primary_use_case_col = col("Primary use case", "primary use case", "summary and use case") throughput_col = col("Router throughput", "router throughput") msrp_col = col("MSRP", "msrp", "List Price", "list price", "msrp_numeric") battery_col = col("Battery (internal/removable/none/optional)", "Battery", "battery") rugged_col = col("Ruggedization", "ruggedization") vpn_col = col("VPN capabilities", "vpn capabilities") serial_col = col("Serial port (yes/no)", "serial port (yes/no)", "serial_ports") install_col = col("Special notes", "special notes", "notes") suggested_ant_col = col( "summary and use case suggested antennas", "suggested antennas", "suggested_antennas", "summary and suggested antennas", ) def is_model_candidate(token: str) -> bool: tok = _compact_model(token) if not tok: return False if tok in _ROUTER_NON_DEVICE_TERMS: return False if len(tok) < 3: return False if tok.isdigit(): return False # Avoid selecting service/license terms as canonical router models. if re.match(r"^(?:SVC|BASIC|OUTDOOR|INSTALL|LIFETIME|SUPPORT)", tok): return False if not any(ch.isalpha() for ch in tok): return False if not any(ch.isdigit() for ch in tok): return False return True def model_candidates_from_text(raw: str) -> List[str]: text = _norm(raw) if not text: return [] out: List[str] = [] seen: set[str] = set() for tok in _extract_router_models(text): cand = self._normalize_router_model(tok) or _compact_model(tok) if (not is_model_candidate(cand)) or (cand in seen): continue seen.add(cand) out.append(cand) if out: return out # Fallback for title/SKU patterns not covered by phrase regexes. for part in re.split(r"[^A-Za-z0-9]+", text): cand = self._normalize_router_model(part) or _compact_model(part) if (not is_model_candidate(cand)) or (cand in seen): continue seen.add(cand) out.append(cand) return out for idx, row in enumerate(reader, start=2): explicit_model_raw = _norm(row.get(model_col, "")) if model_col else "" title_raw = _norm(row.get(title_col, "")) if title_col else "" model_raw = explicit_model_raw or title_raw sku_raw = _norm(row.get(sku_col, "")) if sku_col else "" if (not model_raw) and sku_raw: model_raw = sku_raw if not model_raw: continue model_candidates: List[str] = [] for source_text in (explicit_model_raw, title_raw, sku_raw): for cand in model_candidates_from_text(source_text): if cand not in model_candidates: model_candidates.append(cand) explicit_key_preferred = _compact_model(explicit_model_raw) explicit_low = explicit_model_raw.lower() if ( explicit_key_preferred and is_model_candidate(explicit_key_preferred) and (len(explicit_model_raw) <= 72) and (not any(x in explicit_low for x in ("note:", "supported of", " or - ", "still ("))) ): model_candidates = [explicit_key_preferred] + [cand for cand in model_candidates if cand != explicit_key_preferred] model_clean = model_candidates[0] if model_candidates else model_raw model_low = model_raw.lower() suspicious = (len(model_raw) > 72) or any(x in model_low for x in ("note:", "supported of", " or - ", "still (")) continuation_to_last = False if suspicious and (not model_candidates): candidates = [self._normalize_router_model(tok) for tok in _ROUTER_MODEL_TOKEN_RE.findall(model_raw)] candidates = [c for c in candidates if c and any(ch.isdigit() for ch in c)] if candidates: model_clean = candidates[0] else: has_structured_fields = any( _norm(row.get(cn, "")) if cn else "" for cn in (wan_col, lan_col, antennas_col, rf_col, modem_col, wifi_col, battery_col, rugged_col) ) if has_structured_fields and last_model_key and (last_model_key in rows): continuation_to_last = True model_clean = str(rows.get(last_model_key, {}).get("model") or model_clean) model_key = last_model_key if continuation_to_last else _compact_model(model_clean) if not continuation_to_last: explicit_key = _compact_model(explicit_model_raw) # Preserve manufacturer/prefix token when parser extracts only a trailing fragment (e.g., ARC-XCI55AX -> XCI55AX). if explicit_key and ( (not model_key) or (explicit_key.endswith(model_key) and (len(explicit_key) - len(model_key) <= 4)) ): model_key = explicit_key if not model_key: continue display_model = model_clean explicit_low = explicit_model_raw.lower() if explicit_model_raw and (len(explicit_model_raw) <= 72) and ( not any(x in explicit_low for x in ("note:", "supported of", " or - ", "still (")) ): display_model = explicit_model_raw wan = _norm(row.get(wan_col, "")) if wan_col else "" lan = _norm(row.get(lan_col, "")) if lan_col else "" if wan and lan: wan_lan = f"WAN: {wan}; LAN: {lan}" else: wan_lan = wan or lan antennas = _norm(row.get(antennas_col, "")) if antennas_col else "" rf = _norm(row.get(rf_col, "")) if rf_col else "" if antennas and rf: antennas_rf = f"{antennas}; RF: {rf}" else: antennas_rf = antennas or rf device_type_value = _norm(row.get(type_col, "")) if type_col else "" entry = { "model": display_model, "model_key": model_key, "sku": _norm(row.get(sku_col, "")) if sku_col else "", "manufacturer": _norm(row.get(manufacturer_col, "")) if manufacturer_col else "", "type": device_type_value, "device_type": device_type_value, "primary_use_case": _norm(row.get(primary_use_case_col, "")) if primary_use_case_col else "", "wan_lan": wan_lan, "antennas_rf": antennas_rf, "modem": _norm(row.get(modem_col, "")) if modem_col else "", "wifi": _norm(row.get(wifi_col, "")) if wifi_col else "", "throughput": _norm(row.get(throughput_col, "")) if throughput_col else "", "msrp": _norm(row.get(msrp_col, "")) if msrp_col else "", "battery": _norm(row.get(battery_col, "")) if battery_col else "", "ruggedization": _norm(row.get(rugged_col, "")) if rugged_col else "", "vpn": _norm(row.get(vpn_col, "")) if vpn_col else "", "serial": _norm(row.get(serial_col, "")) if serial_col else "", "install_caveats": _norm(row.get(install_col, "")) if install_col else "", "suggested_antennas": _norm(row.get(suggested_ant_col, "")) if suggested_ant_col else "", "source_doc": csv_path.name, "source_row": idx, } if model_key not in rows: rows[model_key] = entry else: # Merge sparse duplicates; keep earliest row as authoritative base. prev = rows[model_key] for k in ( "wan_lan", "antennas_rf", "modem", "wifi", "throughput", "msrp", "battery", "ruggedization", "vpn", "serial", "install_caveats", "suggested_antennas", "sku", "device_type", ): if (not _norm(prev.get(k, ""))) and _norm(entry.get(k, "")): prev[k] = entry[k] sku_key = _compact_model(entry.get("sku", "")) if sku_key and (sku_key not in rows): rows[sku_key] = rows[model_key] if model_key: last_model_key = model_key return rows def _resolve_router_fact_csv_paths(self) -> List[Path]: paths: List[Path] = [] primary = Path(str(getattr(self.router_core, "dec_csv_path", "") or "")).expanduser() if primary: paths.append(primary) env_extra = str(os.getenv("UNIFIED_KB_ROUTER_FACT_EXTRA_CSV_PATHS", "") or "").strip() if env_extra: for token in env_extra.split(","): p = Path(str(token).strip()).expanduser() if str(p): paths.append(p) # Default supplemental router details catalogs. fallback_extra = _REPO_ROOT / "replacement_devices_missing_from_dec2025routers.csv" paths.append(fallback_extra) paths.append(self.router_pricing_catalog_path) out: List[Path] = [] seen: set[str] = set() for p in paths: try: resolved = str(p.resolve()) except Exception: resolved = str(p) if (not resolved) or (resolved in seen): continue seen.add(resolved) if p.exists() and p.is_file(): out.append(p) return out def _load_router_variant_rows(self) -> List[Dict[str, Any]]: path = Path(str(self.router_variant_options_path or "")) if (not path.exists()) or (not path.is_file()): return [] out: List[Dict[str, Any]] = [] with _open_csv_with_fallback(path) as f: reader = csv.DictReader(f) for idx, row in enumerate(reader, start=2): model_key = _compact_model( _norm(row.get("model_key", "")) or _norm(row.get("model", "")) or _norm(row.get("sku", "")) ) if not model_key: continue out.append( { "model_key": model_key, "model": _norm(row.get("model", "")) or model_key, "manufacturer": _norm(row.get("manufacturer", "")), "default_option": _norm(row.get("default_option", "")).lower() == "yes", "sku": _norm(row.get("sku", "")), "title": _norm(row.get("title", "")), "term": _norm(row.get("term", "")), "msrp": _norm(row.get("msrp", "")), "msrp_numeric": _norm(row.get("msrp_numeric", "")), "wifi": _norm(row.get("wifi", "")), "ethernet_ports": _norm(row.get("ethernet_ports", "")), "serial_ports": _norm(row.get("serial_ports", "")), "poe": _norm(row.get("poe", "")), "battery": _norm(row.get("battery", "")), "modem_type": _norm(row.get("modem_type", "")), "source_file": _norm(row.get("source_file", "")) or path.name, "source_sheet": _norm(row.get("source_sheet", "")), "source_row": idx, } ) return out def _variant_term_rank(self, term: str) -> int: t = _norm(term).upper() if t == "1YR": return 0 if not t: return 1 if t in {"3YR", "5YR"}: return 2 return 3 def _build_router_variant_index(self, rows: Sequence[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]: grouped: Dict[str, List[Dict[str, Any]]] = {} for row in rows: key = _compact_model(row.get("model_key", "")) if not key: continue grouped.setdefault(key, []).append(dict(row)) for key, items in grouped.items(): items.sort( key=lambda r: ( 0 if bool(r.get("default_option")) else 1, self._variant_term_rank(_norm(r.get("term", ""))), _safe_float(_norm(r.get("msrp_numeric", "")), default=999999.0), _norm(r.get("sku", "")), ) ) grouped[key] = items return grouped def _load_parsec_price_rows(self) -> List[Dict[str, Any]]: path = Path(str(self.parsec_pricing_path or "")) if (not path.exists()) or (not path.is_file()): return [] out: List[Dict[str, Any]] = [] with _open_csv_with_fallback(path) as f: reader = csv.DictReader(f) for idx, row in enumerate(reader, start=2): part = _norm(row.get("part_number", "")) if not part: continue out.append( { "part_number": part, "family": _norm(row.get("family", "")), "fit_profile": _norm(row.get("fit_profile", "")), "msrp": _norm(row.get("msrp", "")), "msrp_numeric": _norm(row.get("msrp_numeric", "")), "description": _norm(row.get("description", "")), "connector_summary": _norm(row.get("connector_summary", "")), "category": _norm(row.get("category", "")), "source_file": _norm(row.get("source_file", "")) or path.name, "source_sheet": _norm(row.get("source_sheet", "")), "source_row": idx, } ) return out def _load_router_missing_fields_rows(self) -> List[Dict[str, Any]]: path = Path(str(self.router_missing_fields_audit_path or "")) if (not path.exists()) or (not path.is_file()): return [] out: List[Dict[str, Any]] = [] with _open_csv_with_fallback(path) as f: reader = csv.DictReader(f) for idx, row in enumerate(reader, start=2): model_key = _compact_model(_norm(row.get("model_key", "")) or _norm(row.get("display_model", ""))) if not model_key: continue missing_raw = [x.strip() for x in _norm(row.get("missing_fields", "")).split(",") if x.strip()] out.append( { "model_key": model_key, "display_model": _norm(row.get("display_model", "")) or model_key, "manufacturer": _norm(row.get("manufacturer", "")), "tech": _norm(row.get("tech", "")), "eos": _norm(row.get("eos", "")), "eol": _norm(row.get("eol", "")), "ruggedization": _norm(row.get("ruggedization", "")), "modem_type": _norm(row.get("modem_type", "")), "device_type": _norm(row.get("device_type", "")), "poe": _norm(row.get("poe", "")), "wifi": _norm(row.get("wifi", "")), "wan_ports": _norm(row.get("wan_ports", "")), "lan_ports": _norm(row.get("lan_ports", "")), "ethernet_ports": _norm(row.get("ethernet_ports", "")), "serial_ports": _norm(row.get("serial_ports", "")), "missing_fields": missing_raw, "source_docs": _norm(row.get("source_docs", "")) or path.name, "source_row": idx, } ) return out def _load_peplink_overlay_rows(self) -> List[Dict[str, Any]]: path = Path(str(self.peplink_replacement_overlay_path or "")) if (not path.exists()) or (not path.is_file()): return [] out: List[Dict[str, Any]] = [] with _open_csv_with_fallback(path) as f: reader = csv.DictReader(f) for idx, row in enumerate(reader, start=2): old_key = _compact_model( _norm(row.get("old_model_key", "")) or _norm(row.get("old_item_name", "")) or _norm(row.get("old_sku", "")) ) new_key = _compact_model( _norm(row.get("new_model_key", "")) or _norm(row.get("new_item_name", "")) or _norm(row.get("new_sku", "")) ) if (not old_key) and (not new_key): continue out.append( { "old_model_key": old_key, "old_item_name": _norm(row.get("old_item_name", "")) or old_key, "new_model_key": new_key, "new_item_name": _norm(row.get("new_item_name", "")) or new_key, "eos_year": _norm(row.get("eos_year", "")) or "2024", "eol_year": _norm(row.get("eol_year", "")) or "2025", "notes": _norm(row.get("notes", "")), "source_file": _norm(row.get("source_file", "")) or path.name, "source_row": idx, } ) return out def _build_parsec_family_index(self, rows: Sequence[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]: grouped: Dict[str, List[Dict[str, Any]]] = {} for row in rows: fam = _norm(row.get("family", "")).lower() if not fam: continue grouped.setdefault(fam, []).append(dict(row)) for fam, items in grouped.items(): items.sort( key=lambda r: ( 0 if (_norm(r.get("fit_profile", "")).lower() in {"vehicle", "indoor", "outdoor fixed", "directional"}) else 1, _safe_float(_norm(r.get("msrp_numeric", "")), default=999999.0), _norm(r.get("part_number", "")), ) ) grouped[fam] = items return grouped def _extract_parsec_family_names(self, text: str) -> List[str]: low = _norm(text).lower() out: List[str] = [] for fam in _PARSEC_FAMILY_NAMES: if fam.lower() in low and fam not in out: out.append(fam) return out def _parsec_fit_profile_hint(self, message: str) -> str: low = _norm(message).lower() if ("vehicle" in low) or ("mobile" in low): return "vehicle" if ("outdoor" in low) and ("fixed" in low): return "outdoor fixed" if "directional" in low: return "directional" if "indoor" in low: return "indoor" return "" def _parsec_options_for_families( self, families: Sequence[str], *, fit_profile: str = "", limit: int = 3, connector_hint: str = "", per_family_limit: int = 1, ) -> List[Dict[str, Any]]: def _is_accessory_category(category: str) -> bool: category_low = _norm(category).lower() return any(term in category_low for term in ("accessory", "accessories", "bracket", "adapter", "cable")) def _looks_like_antenna(row: Dict[str, Any]) -> bool: category = _norm(row.get("category", "")).lower() blob = " ".join( [ category, _norm(row.get("title", "")).lower(), _norm(row.get("description", "")).lower(), ] ) return ("antenna" in category) or ("antenna" in blob) def _is_recommendable_option(row: Dict[str, Any]) -> bool: category = _norm(row.get("category", "")).lower() blob = " ".join( [ category, _norm(row.get("title", "")).lower(), _norm(row.get("description", "")).lower(), _norm(row.get("part_number", "")).lower(), ] ) if _is_accessory_category(category): return False if any( term in blob for term in ( "mounting bracket", "mount bracket", "bracket", "adapter", "cable", "mount kit", "mounting kit", ) ): return False return True def _connector_match_score(row: Dict[str, Any]) -> int: hint_low = _norm(connector_hint).lower() if not hint_low: return 0 blob = " ".join( [ _norm(row.get("connector_summary", "")).lower(), _norm(row.get("title", "")).lower(), _norm(row.get("description", "")).lower(), _norm(row.get("fit_profile", "")).lower(), ] ) score = 0 if any(term in hint_low for term in ("4x sma", "4x cellular", "4x lte", "4 lte")) and any( term in blob for term in ("4 lte", "4 cellular", "4x4", "4x sma") ): score += 4 if any(term in hint_low for term in ("2x rp-sma", "2x wi-fi", "2x wifi", "wi-fi", "wifi")) and any( term in blob for term in ("2 wifi", "2 wi-fi", "rp sma", "rp-sma") ): score += 3 if any(term in hint_low for term in ("gps", "gnss")) and any(term in blob for term in ("gps", "gnss")): score += 2 if any(term in hint_low for term in ("vehicle", "mobile")) and any(term in blob for term in ("vehicle", "mobile")): score += 1 return score out: List[Dict[str, Any]] = [] seen_parts: set[str] = set() fit_low = _norm(fit_profile).lower() for fam in families: rows = list(self._parsec_family_index.get(_norm(fam).lower(), []) or []) if not rows: continue antenna_rows = [dict(row) for row in rows if _looks_like_antenna(row)] recommendable_rows = [dict(row) for row in rows if _is_recommendable_option(row)] eligible_rows = antenna_rows or recommendable_rows if not eligible_rows: eligible_rows = [dict(row) for row in rows] if fit_low: fit_rows = [row for row in eligible_rows if fit_low in _norm(row.get("fit_profile", "")).lower()] eligible_rows = fit_rows or eligible_rows rows = sorted( eligible_rows, key=lambda r: ( -_connector_match_score(r), 0 if fit_low and fit_low in _norm(r.get("fit_profile", "")).lower() else 1, _safe_float(_norm(r.get("msrp_numeric", "")), default=999999.0), ), ) family_matches = 0 for row in rows: part = _norm(row.get("part_number", "")) if (not part) or (part in seen_parts): continue seen_parts.add(part) out.append(dict(row)) family_matches += 1 if family_matches >= max(1, int(per_family_limit)): break if len(out) >= int(limit): break return out[: int(limit)] def _router_variant_candidates(self, model_key: str) -> List[Dict[str, Any]]: key = _compact_model(model_key) if not key: return [] direct = list(self._router_variant_index.get(key, []) or []) if direct: return direct # Prefix fallback helps handle model family asks like W1850 vs W1855 variant shorthand. pref: List[Dict[str, Any]] = [] for mk, rows in self._router_variant_index.items(): if mk.startswith(key) or key.startswith(mk): pref.extend(list(rows or [])) pref.sort( key=lambda r: ( 0 if bool(r.get("default_option")) else 1, self._variant_term_rank(_norm(r.get("term", ""))), _safe_float(_norm(r.get("msrp_numeric", "")), default=999999.0), _norm(r.get("sku", "")), ) ) return pref[:12] def _overlay_candidates_from_row(self, row: Dict[str, Any], *names: str) -> List[str]: out: List[str] = [] for name in names: raw = _norm(row.get(name, "")) if not raw: continue cands = [_compact_model(raw)] cands.extend(self._extract_router_models_cached(raw)) for c in cands: cc = _compact_model(c) if (not cc) or (cc in out): continue out.append(cc) return out def _merge_overlay_year(self, existing: str, overlay: str) -> str: ex = self._to_year(existing) ov = self._to_year(overlay) if ex and ov: return str(min(ex, ov)) if ex: return str(ex) if ov: return str(ov) return _norm(existing) or _norm(overlay) def _apply_peplink_replacement_overlay(self, rows: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, Any]]: path = Path(str(self.peplink_replacement_overlay_path or "")) if (not path.exists()) or (not path.is_file()): return rows merged = dict(rows) with _open_csv_with_fallback(path) as f: reader = csv.DictReader(f) for ridx, row in enumerate(reader, start=2): new_model = _norm(row.get("new_model_key", "")) or _norm(row.get("new_item_name", "")) new_item_name = _norm(row.get("new_item_name", "")) new_desc = _norm(row.get("new_description", "")) is_5g = "5g" in f"{new_model} {new_item_name} {new_desc}".lower() alt4g = new_item_name or new_model or "Not listed" rep5g = (new_item_name or new_model or "Not listed") if is_5g else "Not listed" eos_overlay = _norm(row.get("eos_year", "")) or "2024" eol_overlay = _norm(row.get("eol_year", "")) or "2025" for key in self._overlay_candidates_from_row( row, "old_model_key", "old_sku", "old_item_name", "old_description", ): prev = dict(merged.get(key, {})) prev_status = _norm(prev.get("status", "")) merged[key] = { "model": _norm(prev.get("model", "")) or _norm(row.get("old_item_name", "")) or key, "model_key": key, "status": prev_status or self._derive_lifecycle_status(eos_overlay, eol_overlay), "eos": self._merge_overlay_year(_norm(prev.get("eos", "")), eos_overlay), "eol": self._merge_overlay_year(_norm(prev.get("eol", "")), eol_overlay), "tech": _norm(prev.get("tech", "")) or "4G", "alt4g": _norm(prev.get("alt4g", "")) or alt4g, "rep5g": _norm(prev.get("rep5g", "")) or rep5g, "source_doc": _norm(prev.get("source_doc", "")) or path.name, "source_row": int(prev.get("source_row", 0) or 0) or ridx, } return merged def _build_router_lifecycle_index(self) -> Dict[str, Dict[str, Any]]: csv_path = Path(str(getattr(self.router_core, "eos_csv_path", "") or "")) if not csv_path.exists() or not csv_path.is_file(): return {} rows: Dict[str, Dict[str, Any]] = {} with _open_csv_with_fallback(csv_path) as f: reader = csv.DictReader(f) headers = list(reader.fieldnames or []) lut = {str(h).strip().lower(): str(h) for h in headers} def col(*cands: str) -> str: for c in cands: key = str(c).strip().lower() if key in lut: return lut[key] return "" sku_col = col("sku", "model", "device") status_col = col("status", "lifecycle_status", "lifecycle status") eos_col = col("end_of_sale", "end of sale", "eos") eol_col = col("end_of_life", "end of life", "eol") tech_col = col("tech", "technology") manufacturer_col = col("manufacturer", "vendor") device_type_col = col("device type", "device_type", "type") alt4g_col = col("4G alternative", "4g alternative", "4g_alternative", "suggested_replacement") rep5g_col = col("5G replacement", "5g replacement", "5g_replacement", "advanced_5g_option") for idx, row in enumerate(reader, start=2): sku = _norm(row.get(sku_col, "")) if sku_col else "" if not sku: continue key = _compact_model(sku) if not key: continue eos_value = _norm(row.get(eos_col, "")) if eos_col else "" eol_value = _norm(row.get(eol_col, "")) if eol_col else "" status_value = _norm(row.get(status_col, "")) if status_col else "" if not status_value: status_value = self._derive_lifecycle_status(eos_value, eol_value) entry = { "model": sku, "model_key": key, "status": status_value, "eos": eos_value, "eol": eol_value, "manufacturer": _norm(row.get(manufacturer_col, "")) if manufacturer_col else "", "device_type": _norm(row.get(device_type_col, "")) if device_type_col else "", "tech": _norm(row.get(tech_col, "")) if tech_col else "", "alt4g": _norm(row.get(alt4g_col, "")) if alt4g_col else "", "rep5g": _norm(row.get(rep5g_col, "")) if rep5g_col else "", "source_doc": csv_path.name, "source_row": idx, } if key not in rows: rows[key] = entry else: prev = rows[key] for field in ("status", "eos", "eol", "tech", "alt4g", "rep5g"): if (not _norm(prev.get(field, ""))) and _norm(entry.get(field, "")): prev[field] = entry[field] return self._apply_peplink_replacement_overlay(rows) def _to_year(self, raw: Any) -> int: text = _norm(raw) if not text: return 0 m = re.search(r"(19|20)\d{2}", text) if not m: return 0 try: return int(m.group(0)) except Exception: return 0 def _derive_lifecycle_status(self, eos: Any, eol: Any) -> str: now_year = int(time.strftime("%Y")) eol_year = self._to_year(eol) eos_year = self._to_year(eos) if eol_year and eol_year <= now_year: return "End of Life" if eos_year and eos_year <= now_year: return "End of Sale" if eos_year or eol_year: return "Active" return "Unknown" def _router_cross_manufacturer_replacement_followup( self, rows: Sequence[Dict[str, Any]], requested_label_by_key: Optional[Dict[str, str]] = None, ) -> List[str]: requested = requested_label_by_key or {} flagged: List[str] = [] seen: set[str] = set() for row in rows: if not isinstance(row, dict): continue status = _norm(row.get("status", "")).lower() eos = self._to_year(row.get("eos", "")) eol = self._to_year(row.get("eol", "")) if ("end of sale" not in status) and ("end of life" not in status) and (not eos) and (not eol): continue key = _norm(row.get("key", "")) or _norm(row.get("model_key", "")) or _norm(row.get("model", "")) if key in seen: continue seen.add(key) display = ( _norm(requested.get(key)) or self._router_display_name(row, key) or _norm(row.get("model", "")) or key ) if display: flagged.append(display) if not flagged: return [] focus = ", ".join(f"`{x}`" for x in flagged[:3]) return [ f"I can also build a similar 5G alternative shortlist for {focus} using internal RAG first, GPT-assisted matching, and web-sourced manufacturer docs only when internal evidence is thin.", "Reply with MSRP target, modem type/CAT or 5G class, WAN/LAN port count, ruggedness/IP rating, Wi-Fi included or not, and deployment type. I’ll try to include a lower-cost option.", "I’ll keep the shortlist aligned to the current device class/use case, then show why each cross-manufacturer candidate fits.", "If you want me to proceed immediately, say `find similar 5G alternatives`.", ] def _router_inventory_variant_adjacent(self, requested_label: str, matched_key: str) -> bool: requested = _compact_model(requested_label) matched = _compact_model(matched_key) if (not requested) or (not matched) or requested == matched: return False alias_target = _compact_model(self._normalize_router_model(requested_label) or (getattr(self, "_router_alias_map", {}) or {}).get(requested, requested)) if alias_target == matched and abs(len(matched) - len(requested)) <= 1: return False return (requested in matched) or (matched in requested) def _router_inventory_lifecycle_key(self, canonical_key: str, requested_label: str = "") -> str: for candidate in (canonical_key, requested_label, self._normalize_router_model(requested_label) if requested_label else ""): compact = _compact_model(candidate) if not compact: continue strict = self._lookup_router_lifecycle_key(compact) if strict: return strict relaxed = self._lookup_router_lifecycle_key_relaxed(compact) if relaxed: return relaxed if compact in self._router_lifecycle_rows: return compact return "" def _wants_similar_5g_alternatives(self, message: str) -> bool: low = _normalize_router_query_text(message) return bool( ("find similar 5g alternatives" in low) or ("similar 5g alternatives" in low) or ("similar 5g alternative" in low) or ( any(x in low for x in ("lower cost", "lower-cost", "cheaper", "more affordable")) and ("5g" in low) and any(x in low for x in ("replacement", "replace", "replacement path", "upgrade path")) and bool(self._extract_router_models_cached(message)) ) or ( ("cross manufacturer" in low or "cross-manufacturer" in low) and ("alternative" in low or "alternatives" in low) and ("5g" in low) ) ) def _router_vendor_family(self, value: Any) -> str: low = _norm(value).lower() if not low: return "" vendor_map = ( ("cradlepoint", "cradlepoint"), ("ericsson enterprise wireless", "cradlepoint"), ("ericsson (cradlepoint)", "cradlepoint"), ("ericsson cradlepoint", "cradlepoint"), ("inhand", "inhand"), ("inseego", "inseego"), ("peplink", "peplink"), ("pepwave", "peplink"), ("digi", "digi"), ("semtech", "semtech"), ("sierra wireless", "semtech"), ("cisco", "cisco"), ("meraki", "cisco"), ("atel", "atel"), ("teltonika", "teltonika"), ("opengear", "opengear"), ) for needle, family in vendor_map: if needle in low: return family return re.sub(r"[^a-z0-9]+", " ", low).strip().split(" ")[0] def _router_parse_msrp(self, value: Any) -> float: text = _norm(value) if not text or "tbd" in text.lower(): return 0.0 m = re.search(r"(\d[\d,]*\.?\d*)", text.replace(" ", "")) if not m: return 0.0 try: return float(m.group(1).replace(",", "")) except Exception: return 0.0 def _router_port_count(self, value: Any) -> int: low = _norm(value).lower() if not low: return 0 counts = [int(x) for x in re.findall(r"(\d+)\s*[x×]\s*(?:\d+(?:\/\d+)?|\d+(?:\.\d+)?g?be|gbe|ethernet|wan|lan|rj45)", low)] if counts: return int(sum(counts)) if "dual ethernet" in low or "2x ethernet" in low: return 2 if "single ethernet" in low or "1 ethernet" in low: return 1 if "wan" in low and "lan" in low: return 2 return 0 def _router_wifi_state(self, value: Any) -> str: low = _norm(value).lower() if not low: return "unknown" if any(x in low for x in ("none", "not available", "non-wi-fi", "no wi-fi", "no wifi")): return "none" if any(x in low for x in ("wifi", "wi-fi", "802.11")): return "included" return "unknown" def _router_rugged_class(self, rugged: Any, use_case: Any = "", device_type: Any = "") -> str: low = " ".join(_norm(x).lower() for x in (rugged, use_case, device_type) if _norm(x)) if not low: return "unknown" if any(x in low for x in ("vehicle", "mobile", "fleet", "rail")): return "vehicle" if any(x in low for x in ("outdoor", "ip67", "ip66", "ip65", "weather", "pole", "odu")): return "outdoor" if any(x in low for x in ("industrial", "din rail", "factory", "rugged")): return "industrial" if any(x in low for x in ("indoor", "branch", "office", "retail")): return "indoor" return "unknown" def _router_modem_class(self, modem: Any) -> str: low = _norm(modem).lower() if not low: return "unknown" if "5g" in low: if "mmwave" in low or "mmw" in low: return "5g_mmwave" if "sa" in low and "nsa" in low: return "5g_sa_nsa" return "5g" m = re.search(r"cat\s*([0-9]{1,2})", low) if m: return f"cat_{m.group(1)}" if "lte advanced" in low: return "lte_advanced" if "lte" in low or "4g" in low: return "4g" return "unknown" def _router_device_class(self, device_type: Any, use_case: Any = "", title: Any = "") -> str: low = " ".join(_norm(x).lower() for x in (device_type, use_case, title) if _norm(x)) if not low: return "unknown" if "adapter" in low or "wideband adapter" in low: return "adapter" if "gateway" in low: return "gateway" if "router" in low: return "router" if "modem" in low: return "modem" return "unknown" def _router_row_looks_service_like(self, row: Dict[str, Any]) -> bool: low = " ".join( _norm(row.get(name, "")).lower() for name in ("model", "sku", "manufacturer", "device_type", "primary_use_case", "install_caveats") ) return any( token in low for token in ( "1yr", "3yr", "5yr", "essentials", "advanced", "subscription", "license", "service plan", "support plan", ) ) def _unique_router_fact_rows(self) -> List[Dict[str, Any]]: out: List[Dict[str, Any]] = [] seen: set[Tuple[str, int, str]] = set() for row in self._router_fact_rows.values(): if not isinstance(row, dict): continue source_doc = _norm(row.get("source_doc", "")) source_row = int(row.get("source_row", 0) or 0) model_key = _compact_model(row.get("model_key", "")) marker = (source_doc, source_row, model_key) if marker in seen: continue seen.add(marker) out.append(dict(row)) return out def _router_best_fact_row_for_model(self, model_token: str) -> Dict[str, Any]: model_key = _compact_model(model_token) if not model_key: return {} candidates: List[Tuple[int, Dict[str, Any]]] = [] for row in self._unique_router_fact_rows(): row_key = _compact_model(row.get("model_key", "") or row.get("model", "") or row.get("sku", "")) blob = " ".join(_norm(row.get(name, "")).upper() for name in ("model", "sku", "manufacturer")) relation_score = 0 if row_key == model_key: relation_score += 18 elif row_key.startswith(model_key) or model_key.startswith(row_key): relation_score += 12 if model_key in blob: relation_score += 8 if relation_score <= 0: continue score = relation_score score += sum( 1 for field in ("modem", "wan_lan", "wifi", "ruggedization", "primary_use_case", "msrp") if _norm(row.get(field, "")) ) if self._router_row_looks_service_like(row): score -= 12 if _norm(row.get("modem", "")): score += 2 if score > 0: candidates.append((score, row)) if not candidates: return {} candidates.sort( key=lambda item: ( item[0], 0 if not self._router_row_looks_service_like(item[1]) else 1, len(_norm(item[1].get("wan_lan", ""))) + len(_norm(item[1].get("modem", ""))), ), reverse=True, ) return dict(candidates[0][1]) def _router_internal_profile(self, model_token: str, *, requested_label: str = "") -> Dict[str, Any]: life_key = self._lookup_router_lifecycle_key_relaxed(model_token) or self._lookup_router_lifecycle_key(model_token) life_row = self._router_lifecycle_rows.get(life_key, {}) if life_key else {} fact_row = self._router_best_fact_row_for_model(model_token) if life_key: life_fact_row = self._router_best_fact_row_for_model(life_key) if life_fact_row and ( (not fact_row) or (self._router_row_looks_service_like(fact_row) and not self._router_row_looks_service_like(life_fact_row)) or ( len(_norm(life_fact_row.get("modem", ""))) + len(_norm(life_fact_row.get("wan_lan", ""))) > len(_norm(fact_row.get("modem", ""))) + len(_norm(fact_row.get("wan_lan", ""))) ) ): fact_row = life_fact_row fact_key = _compact_model(fact_row.get("model_key", "") or fact_row.get("model", "") or fact_row.get("sku", "")) display = ( _norm(requested_label) or self._router_display_name(fact_row or life_row, fact_key or life_key or model_token) or _norm(fact_row.get("model", "")) or _norm(life_row.get("model", "")) or _norm(model_token) ) manufacturer = _norm(fact_row.get("manufacturer", "")) or _norm(life_row.get("manufacturer", "")) device_type = _norm(fact_row.get("device_type", "")) or _norm(life_row.get("device_type", "")) primary_use_case = _norm(fact_row.get("primary_use_case", "")) modem = _norm(fact_row.get("modem", "")) or _norm(life_row.get("tech", "")) wan_lan = _norm(fact_row.get("wan_lan", "")) rugged = _norm(fact_row.get("ruggedization", "")) wifi = _norm(fact_row.get("wifi", "")) msrp = _norm(fact_row.get("msrp", "")) status = _norm(life_row.get("status", "")) or self._derive_lifecycle_status(life_row.get("eos", ""), life_row.get("eol", "")) profile = { "model": display, "model_key": fact_key or life_key or _compact_model(model_token), "manufacturer": manufacturer, "vendor_family": self._router_vendor_family(manufacturer), "device_type": device_type, "device_class": self._router_device_class(device_type, primary_use_case, display), "primary_use_case": primary_use_case, "modem": modem, "modem_class": self._router_modem_class(modem), "wan_lan": wan_lan, "port_count": self._router_port_count(wan_lan), "wifi": wifi, "wifi_state": self._router_wifi_state(wifi), "ruggedization": rugged, "rugged_class": self._router_rugged_class(rugged, primary_use_case, device_type), "msrp": msrp or "TBD", "msrp_value": self._router_parse_msrp(msrp), "status": status or "Unknown", "eos": _norm(life_row.get("eos", "")), "eol": _norm(life_row.get("eol", "")), "alt4g": _norm(life_row.get("alt4g", "")), "rep5g": _norm(life_row.get("rep5g", "")), "source_status": "Internal catalog/lifecycle", "conflicts": [], "sources": [], "missing_fields": [], } sources: List[Dict[str, Any]] = [] if life_row: sources.append( { "id": "L1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": f"lifecycle:{profile['model_key']}", "location": "", "excerpt": ( f"{display}: status={profile['status'] or 'Unknown'}; eos={profile['eos'] or 'Not listed'}; " f"eol={profile['eol'] or 'Not listed'}; 4g_alternative={profile['alt4g'] or 'Not listed'}; " f"5g_replacement={profile['rep5g'] or 'Not listed'}." ), "score": 1.0, } ) if fact_row: sources.append( { "id": f"F{len(sources) + 1}", "domain": "router_lifecycle", "doc": _norm(fact_row.get("source_doc", "")) or "feb2026routers.csv", "relative_path": _norm(fact_row.get("source_doc", "")) or "feb2026routers.csv", "chunk_id": f"catalog:{profile['model_key']}", "location": "", "excerpt": ( f"{display}: manufacturer={manufacturer or 'Not listed'}; modem={modem or 'Not listed'}; " f"wan_lan={wan_lan or 'Not listed'}; wifi={wifi or 'Not listed'}; " f"ruggedization={rugged or 'Not listed'}; msrp={profile['msrp']}." ), "score": 0.98, } ) profile["sources"] = sources missing = [ name for name, value in ( ("manufacturer", manufacturer), ("modem", modem), ("wan_lan", wan_lan), ("wifi", wifi), ("ruggedization", rugged), ) if not _norm(value) ] profile["missing_fields"] = missing return profile def _router_candidate_seed_from_row(self, row: Dict[str, Any]) -> Dict[str, Any]: display = self._router_display_name(row, _compact_model(row.get("model_key", "") or row.get("model", ""))) manufacturer = _norm(row.get("manufacturer", "")) device_type = _norm(row.get("device_type", "")) primary_use_case = _norm(row.get("primary_use_case", "")) modem = _norm(row.get("modem", "")) wan_lan = _norm(row.get("wan_lan", "")) wifi = _norm(row.get("wifi", "")) rugged = _norm(row.get("ruggedization", "")) msrp = _norm(row.get("msrp", "")) or "TBD" return { "model": display, "model_key": _compact_model(row.get("model_key", "") or row.get("model", "") or row.get("sku", "")), "manufacturer": manufacturer, "vendor_family": self._router_vendor_family(manufacturer), "device_type": device_type, "device_class": self._router_device_class(device_type, primary_use_case, display), "primary_use_case": primary_use_case, "modem": modem, "modem_class": self._router_modem_class(modem), "wan_lan": wan_lan, "port_count": self._router_port_count(wan_lan), "wifi": wifi, "wifi_state": self._router_wifi_state(wifi), "ruggedization": rugged, "rugged_class": self._router_rugged_class(rugged, primary_use_case, device_type), "msrp": msrp, "msrp_value": self._router_parse_msrp(msrp), "status": "", "source_status": "Internal catalog", "conflicts": [], "sources": [ { "id": "C1", "domain": "router_lifecycle", "doc": _norm(row.get("source_doc", "")) or "feb2026routers.csv", "relative_path": _norm(row.get("source_doc", "")) or "feb2026routers.csv", "chunk_id": f"catalog:{_compact_model(row.get('model_key', '') or row.get('model', ''))}", "location": "", "excerpt": ( f"{display}: manufacturer={manufacturer or 'Not listed'}; modem={modem or 'Not listed'}; " f"wan_lan={wan_lan or 'Not listed'}; wifi={wifi or 'Not listed'}; " f"ruggedization={rugged or 'Not listed'}; msrp={msrp}." ), "score": 0.95, } ], "missing_fields": [ name for name, value in ( ("modem", modem), ("wan_lan", wan_lan), ("wifi", wifi), ("ruggedization", rugged), ) if not _norm(value) ], } def _router_doc_seed_candidates(self, source_profile: Dict[str, Any], existing_keys: Sequence[str]) -> List[Dict[str, Any]]: list_files = getattr(self.router_rag_core, "list_files", None) if not callable(list_files): return [] existing = {_compact_model(x) for x in existing_keys if _compact_model(x)} source_vendor = _norm(source_profile.get("vendor_family", "")) source_vendor_key = manufacturer_family_key(source_vendor) seeds: List[Dict[str, Any]] = [] seen: set[str] = set() for rel in list_files() or []: rel_text = str(rel or "") if "/routers/" not in rel_text.replace("\\", "/"): continue stem = Path(rel_text).stem.replace("-", " ") model_tokens = _extract_router_models(stem) if not model_tokens: continue token = self._normalize_router_model(model_tokens[0]) or _compact_model(model_tokens[0]) if (not token) or (token in seen) or (token in existing): continue seen.add(token) manufacturer = "" parts = rel_text.replace("\\", "/").split("/") if len(parts) >= 4: manufacturer = _titleize(parts[2]) vendor_family = self._router_vendor_family(manufacturer) if source_vendor_key and vendor_family and manufacturer_family_key(vendor_family) == source_vendor_key: continue seeds.append( { "model": _humanize_model_token(token), "model_key": token, "manufacturer": manufacturer, "vendor_family": vendor_family, "device_type": "", "device_class": "unknown", "primary_use_case": "", "modem": "", "modem_class": "unknown", "wan_lan": "", "port_count": 0, "wifi": "", "wifi_state": "unknown", "ruggedization": "", "rugged_class": "unknown", "msrp": "TBD", "msrp_value": 0.0, "status": "", "source_status": "Docs-seed only", "conflicts": [], "sources": [ { "id": "D1", "domain": "router_lifecycle", "doc": Path(rel_text).name, "relative_path": rel_text, "chunk_id": f"docs_seed:{token}", "location": "", "excerpt": f"Internal router docs include {Path(rel_text).name} for {token}.", "score": 0.72, } ], "missing_fields": ["modem", "wan_lan", "wifi", "ruggedization"], } ) return seeds[:12] def _extract_json_object(self, text: str) -> Dict[str, Any]: raw = str(text or "").strip() if not raw: return {} fenced = re.search(r"```(?:json)?\s*(\{.*\})\s*```", raw, flags=re.IGNORECASE | re.DOTALL) if fenced: raw = fenced.group(1) else: start = raw.find("{") end = raw.rfind("}") if start >= 0 and end > start: raw = raw[start : end + 1] try: obj = json.loads(raw) return obj if isinstance(obj, dict) else {} except Exception: return {} def _router_web_profile_for_model( self, model_token: str, *, source_profile: Dict[str, Any], remaining_s: Optional[float] = None, ) -> Optional[Dict[str, Any]]: if self.client is None: return None timeout_s = min(4.0, float(self.web_timeout_s_by_domain.get("router_docs", 5.0))) if remaining_s is not None: remaining_budget_s = max(0.0, float(remaining_s)) if remaining_budget_s < 1.5: return None timeout_s = min(timeout_s, max(1.0, remaining_budget_s - 0.35)) system = ( "Use web search only to extract manufacturer-published device fit fields for a router or adapter. " "Prefer official manufacturer product pages, official datasheets, or official manuals. " "Return JSON only. Never invent MSRP or specs. Use `TBD` for unknown MSRP." ) payload = { "task": "Cross-manufacturer 5G replacement candidate enrichment", "target_model": _humanize_model_token(model_token), "source_device": { "model": _norm(source_profile.get("model", "")), "manufacturer": _norm(source_profile.get("manufacturer", "")), "device_class": _norm(source_profile.get("device_class", "")), }, "required_fields": [ "manufacturer", "model", "modem", "wan_lan", "ruggedization", "wifi", "msrp", "primary_use_case", ], "output_rules": [ "Return JSON object only", "Include `source_urls` array", "Use `TBD` if MSRP is not found on primary sources", "Do not include pricing plans or carrier plan details", ], } try: resp = responses_create_with_deadline( self.client, timeout_s=timeout_s, model=self.openai_model, tools=[{"type": "web_search_preview"}], # type: ignore[list-item] input=[ {"role": "system", "content": system}, {"role": "user", "content": json.dumps(payload, ensure_ascii=False)}, ], max_output_tokens=420, ) except Exception: return None text = str(getattr(resp, "output_text", "") or "").strip() obj = self._extract_json_object(text) if not obj: return None manufacturer = _norm(obj.get("manufacturer", "")) model_name = _norm(obj.get("model", "")) or _humanize_model_token(model_token) modem = _norm(obj.get("modem", "")) wan_lan = _norm(obj.get("wan_lan", "")) rugged = _norm(obj.get("ruggedization", "")) wifi = _norm(obj.get("wifi", "")) msrp = _norm(obj.get("msrp", "")) or "TBD" primary_use_case = _norm(obj.get("primary_use_case", "")) source_urls = [str(x).strip() for x in (obj.get("source_urls") or []) if str(x).strip()] sources: List[Dict[str, Any]] = [] for idx, url in enumerate(source_urls[:4], start=1): sources.append( { "id": f"W{idx}", "domain": "router_lifecycle", "doc": url, "relative_path": url, "chunk_id": f"web_candidate:{_compact_model(model_token)}:{idx}", "location": "", "excerpt": f"Web-sourced (not from our internal docs) profile evidence for {model_name}.", "score": 0.7, } ) if not sources: sources.append( { "id": "W0", "domain": "router_lifecycle", "doc": "web_search_preview", "relative_path": "", "chunk_id": f"web_candidate:{_compact_model(model_token)}", "location": "", "excerpt": f"Web-sourced (not from our internal docs) profile evidence for {model_name}.", "score": 0.65, } ) return { "model": model_name, "model_key": _compact_model(model_token), "manufacturer": manufacturer, "vendor_family": self._router_vendor_family(manufacturer), "device_type": _norm(obj.get("device_type", "")), "device_class": self._router_device_class(obj.get("device_type", ""), primary_use_case, model_name), "primary_use_case": primary_use_case, "modem": modem, "modem_class": self._router_modem_class(modem), "wan_lan": wan_lan, "port_count": self._router_port_count(wan_lan), "wifi": wifi, "wifi_state": self._router_wifi_state(wifi), "ruggedization": rugged, "rugged_class": self._router_rugged_class(rugged, primary_use_case, obj.get("device_type", "")), "msrp": msrp, "msrp_value": self._router_parse_msrp(msrp), "status": "", "source_status": "Web-sourced (not from our internal docs)", "conflicts": [], "sources": sources, "missing_fields": [ name for name, value in ( ("modem", modem), ("wan_lan", wan_lan), ("wifi", wifi), ("ruggedization", rugged), ) if not _norm(value) ], } def _merge_router_candidate_profile(self, base: Dict[str, Any], web: Optional[Dict[str, Any]]) -> Dict[str, Any]: if not web: return dict(base) if not base: return dict(web) merged = dict(base) merged_sources = list(base.get("sources") or []) + list(web.get("sources") or []) conflicts: List[str] = list(base.get("conflicts") or []) for field in ("manufacturer", "device_type", "modem", "wan_lan", "ruggedization", "wifi", "msrp", "primary_use_case"): base_val = _norm(base.get(field, "")) web_val = _norm(web.get(field, "")) if base_val and web_val and (_compact_model(base_val) != _compact_model(web_val)) and (base_val.lower() != web_val.lower()): conflicts.append(field) continue if (not base_val) and web_val: merged[field] = web_val if not _norm(merged.get("manufacturer", "")): merged["manufacturer"] = _norm(web.get("manufacturer", "")) merged["vendor_family"] = self._router_vendor_family(merged.get("manufacturer", "")) merged["device_class"] = self._router_device_class( merged.get("device_type", ""), merged.get("primary_use_case", ""), merged.get("model", ""), ) merged["modem_class"] = self._router_modem_class(merged.get("modem", "")) merged["port_count"] = self._router_port_count(merged.get("wan_lan", "")) merged["wifi_state"] = self._router_wifi_state(merged.get("wifi", "")) merged["rugged_class"] = self._router_rugged_class( merged.get("ruggedization", ""), merged.get("primary_use_case", ""), merged.get("device_type", ""), ) merged["msrp_value"] = self._router_parse_msrp(merged.get("msrp", "")) if conflicts: merged["source_status"] = ( "Web-sourced (not from our internal docs); internal docs seed found; " f"conflict on {', '.join(sorted(set(conflicts)))}" ) else: merged["source_status"] = "Web-sourced (not from our internal docs); internal docs seed found" merged["conflicts"] = sorted(set(conflicts)) merged["sources"] = merged_sources merged["missing_fields"] = [ name for name, value in ( ("modem", merged.get("modem", "")), ("wan_lan", merged.get("wan_lan", "")), ("wifi", merged.get("wifi", "")), ("ruggedization", merged.get("ruggedization", "")), ) if not _norm(value) ] return merged def _router_candidate_is_strict_fit(self, source: Dict[str, Any], candidate: Dict[str, Any]) -> bool: if _norm(candidate.get("vendor_family", "")) == _norm(source.get("vendor_family", "")): return False if "5g" not in _norm(candidate.get("modem_class", "")): return False candidate_life_key = self._lookup_router_lifecycle_key_relaxed(candidate.get("model_key", "")) candidate_life = self._router_lifecycle_rows.get(candidate_life_key, {}) if candidate_life_key else {} candidate_status = _norm(candidate_life.get("status", "")).lower() if ("end of life" in candidate_status) or ("end of sale" in candidate_status): return False source_class = _norm(source.get("device_class", "")) candidate_class = _norm(candidate.get("device_class", "")) if source_class and source_class != "unknown": if candidate_class == "unknown": return False if source_class != candidate_class: return False source_ports = int(source.get("port_count", 0) or 0) candidate_ports = int(candidate.get("port_count", 0) or 0) if source_ports > 0 and candidate_ports > 0 and candidate_ports < max(1, source_ports - 1): return False source_rugged = _norm(source.get("rugged_class", "")) candidate_rugged = _norm(candidate.get("rugged_class", "")) if ( source_rugged in {"vehicle", "outdoor", "industrial"} and candidate_rugged in {"indoor"} ): return False return True def _router_candidate_score(self, source: Dict[str, Any], candidate: Dict[str, Any]) -> Tuple[float, List[str]]: score = 0.0 reasons: List[str] = [] source_class = _norm(source.get("device_class", "")) candidate_class = _norm(candidate.get("device_class", "")) if source_class and candidate_class and source_class == candidate_class and source_class != "unknown": score += 30.0 reasons.append(f"same device class ({source_class})") elif candidate_class != "unknown": score += 12.0 reasons.append(f"device class available ({candidate_class})") source_modem = _norm(source.get("modem_class", "")) candidate_modem = _norm(candidate.get("modem_class", "")) if source_modem.startswith("cat_") and candidate_modem.startswith("5g"): score += 22.0 reasons.append("5G upgrade over LTE/CAT source modem") elif source_modem and candidate_modem and source_modem == candidate_modem: score += 20.0 reasons.append(f"same modem class ({candidate_modem})") elif candidate_modem.startswith("5g"): score += 16.0 reasons.append(f"5G modem class ({candidate_modem})") source_ports = int(source.get("port_count", 0) or 0) candidate_ports = int(candidate.get("port_count", 0) or 0) if source_ports > 0 and candidate_ports > 0: if candidate_ports >= source_ports: score += 16.0 reasons.append(f"meets or exceeds port count ({candidate_ports} vs {source_ports})") else: score += max(0.0, 10.0 - float(source_ports - candidate_ports) * 5.0) elif candidate_ports > 0: score += 8.0 reasons.append(f"documented ports ({candidate_ports})") source_wifi = _norm(source.get("wifi_state", "")) candidate_wifi = _norm(candidate.get("wifi_state", "")) if source_wifi != "unknown" and candidate_wifi != "unknown": if source_wifi == candidate_wifi: score += 10.0 reasons.append(f"same Wi-Fi posture ({candidate_wifi})") elif source_wifi == "none" and candidate_wifi == "included": score += 3.0 elif candidate_wifi != "unknown": score += 4.0 source_rugged = _norm(source.get("rugged_class", "")) candidate_rugged = _norm(candidate.get("rugged_class", "")) if source_rugged != "unknown" and candidate_rugged != "unknown": if source_rugged == candidate_rugged: score += 12.0 reasons.append(f"same ruggedness class ({candidate_rugged})") elif source_rugged in {"vehicle", "industrial"} and candidate_rugged == "outdoor": score += 6.0 elif candidate_rugged != "unknown": score += 4.0 source_msrp = float(source.get("msrp_value", 0.0) or 0.0) candidate_msrp = float(candidate.get("msrp_value", 0.0) or 0.0) if source_msrp > 0.0 and candidate_msrp > 0.0: delta = abs(candidate_msrp - source_msrp) score += max(0.0, 12.0 - min(12.0, delta / max(60.0, source_msrp * 0.15))) if candidate_msrp <= source_msrp: score += 5.0 reasons.append("lower-cost or equal MSRP option") elif candidate_msrp > 0.0: score += 3.0 if candidate.get("conflicts"): score -= min(12.0, 4.0 * len(candidate.get("conflicts") or [])) reasons.append(f"conflict held conservative on {', '.join(candidate.get('conflicts') or [])}") if not reasons: reasons.append("closest available fit from the filtered 5G cross-manufacturer set") return score, reasons[:4] def _router_similar_5g_alternatives_fast(self, message: str) -> Optional[Dict[str, Any]]: if not self._wants_similar_5g_alternatives(message): return None low = _normalize_router_query_text(message) requested_tokens = self._extract_router_models_cached(message) if not requested_tokens: return { "assistant": _format_shell( "I need one exact legacy model before I can build a similar 5G cross-manufacturer shortlist.", [ "This flow matches on modem class, ports, ruggedness, Wi-Fi posture, and MSRP when available.", "I avoided guessing the source device.", ], [ "Reply with the exact model or SKU from the device label.", "If you already know the constraints, include MSRP target, port count, ruggedness, and Wi-Fi requirement.", ], ), "sources": [], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": { "domain": "router_docs", "retrieval_mode": "router_similar_5g_alternatives_fast", "web_assisted": False, "shortlist_found": False, }, } source_token = requested_tokens[0] source_profile = self._router_internal_profile(source_token, requested_label=_humanize_model_token(source_token)) if not _norm(source_profile.get("model_key", "")): return None source_signal_count = sum( 1 for ok in ( _norm(source_profile.get("vendor_family", "")) not in {"", "unknown"}, _norm(source_profile.get("device_class", "")) not in {"", "unknown"}, bool(_norm(source_profile.get("modem", ""))), bool(_norm(source_profile.get("wan_lan", ""))), _norm(source_profile.get("wifi_state", "")) not in {"", "unknown"}, _norm(source_profile.get("rugged_class", "")) not in {"", "unknown"}, ) if ok ) if ("end of life" not in low) and ("end of sale" not in low): source_status_low = _norm(source_profile.get("status", "")).lower() if ("end of sale" not in source_status_low) and ("end of life" not in source_status_low): source_profile["status"] = _norm(source_profile.get("status", "")) or "Lifecycle status not confirmed" candidates: List[Dict[str, Any]] = [] seen_keys: set[str] = set() for row in self._unique_router_fact_rows(): candidate = self._router_candidate_seed_from_row(row) key = _compact_model(candidate.get("model_key", "")) if (not key) or (key in seen_keys): continue seen_keys.add(key) if not self._router_candidate_is_strict_fit(source_profile, candidate): continue score, reasons = self._router_candidate_score(source_profile, candidate) candidate["score"] = round(score, 2) candidate["why_fit"] = reasons candidates.append(candidate) used_web = False if len(candidates) < 3: for seed in self._router_doc_seed_candidates(source_profile, [c.get("model_key", "") for c in candidates] + [source_profile.get("model_key", "")]): web_profile = self._router_web_profile_for_model(seed.get("model_key", ""), source_profile=source_profile) merged = self._merge_router_candidate_profile(seed, web_profile) if not self._router_candidate_is_strict_fit(source_profile, merged): continue score, reasons = self._router_candidate_score(source_profile, merged) merged["score"] = round(score, 2) merged["why_fit"] = reasons candidates.append(merged) used_web = used_web or bool(web_profile) if (_norm(source_profile.get("vendor_family", "")) in {"", "unknown"}) or (source_signal_count < 2) or (not candidates): why = [ f"Source device profile for `{source_profile['model']}` is too sparse to produce a confident strict cross-manufacturer shortlist.", "I kept filtering strict: different manufacturer, 5G-capable, compatible device class, and no obvious lifecycle disqualifier.", ] if source_profile.get("missing_fields"): why.append(f"Missing source fields: {', '.join(source_profile.get('missing_fields') or [])}.") return { "assistant": _format_shell( f"I could not find a strict cross-manufacturer 5G shortlist for `{source_profile['model']}` without guessing.", why, [ "Reply with budget target, minimum port count, ruggedness/IP requirement, Wi-Fi yes/no, and deployment type.", "If you want a looser best-effort pass, say `best effort similar 5G alternatives`.", ], ), "sources": list(source_profile.get("sources") or []), "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": { "domain": "router_docs", "retrieval_mode": "router_similar_5g_alternatives_fast", "web_assisted": bool(used_web), "shortlist_found": False, "source_missing_fields": list(source_profile.get("missing_fields") or []), }, } dedup: Dict[str, Dict[str, Any]] = {} for cand in candidates: key = _compact_model(cand.get("model_key", "") or cand.get("model", "")) prev = dedup.get(key) if prev is None or float(cand.get("score", 0.0) or 0.0) > float(prev.get("score", 0.0) or 0.0): dedup[key] = cand ranked = sorted( dedup.values(), key=lambda row: ( float(row.get("score", 0.0) or 0.0), 0 if "lower-cost" in " ".join(row.get("why_fit") or []).lower() else 1, -float(row.get("msrp_value", 0.0) or 0.0), ), reverse=True, )[:4] lines = [ f"Cross-manufacturer 5G alternatives for `{source_profile['model']}`:", "", "| Candidate | Manufacturer | MSRP | Modem | WAN/LAN | Ruggedness | Wi-Fi | Source status | Why it fits |", "| --- | --- | --- | --- | --- | --- | --- | --- | --- |", ] for cand in ranked: lines.append( f"| {_md_cell(cand.get('model', ''))} | {_md_cell(cand.get('manufacturer', '') or 'Not listed')} | " f"{_md_cell(cand.get('msrp', '') or 'TBD')} | {_md_cell(_truncate(cand.get('modem', '') or 'Not listed', 56))} | " f"{_md_cell(_truncate(cand.get('wan_lan', '') or 'Not listed', 52))} | {_md_cell(_truncate(cand.get('ruggedization', '') or 'Not listed', 46))} | " f"{_md_cell(cand.get('wifi', '') or 'Not listed')} | {_md_cell(cand.get('source_status', ''))} | " f"{_md_cell('; '.join(cand.get('why_fit') or []))} |" ) why = [ "Source-device profile was built from internal lifecycle and router catalog data first.", "Candidates were filtered to different manufacturers, 5G-capable models, compatible device class, and no obvious lifecycle disqualifier.", "Ranking favors modem similarity, port fit, ruggedness/Wi-Fi alignment, and MSRP proximity, with a bonus for lower-cost options when MSRP is supported.", ] if source_profile.get("rep5g"): why.append( f"Internal lifecycle mapping lists `{source_profile['rep5g']}` as a vendor replacement; this table shows cross-manufacturer alternatives instead." ) conflict_lines = [ f"{cand.get('model')}: {', '.join(cand.get('conflicts') or [])}" for cand in ranked if cand.get("conflicts") ] if conflict_lines: why.append(f"Conflict held conservative: {conflict_lines[0]}.") next_action = [ "Reply with budget target, minimum port count, ruggedness/IP requirement, Wi-Fi yes/no, and deployment type if you want me to tighten the shortlist.", "Ask `compare vs from documented specs only` for a side-by-side validation table.", "Ask `antenna options for ` after you confirm the target platform.", ] sources: List[Dict[str, Any]] = list(source_profile.get("sources") or []) for cand in ranked: for src in cand.get("sources") or []: if src not in sources: sources.append(src) files = sorted( { str(src.get("relative_path") or src.get("doc") or "") for src in sources if str(src.get("relative_path") or src.get("doc") or "").strip() } )[:10] return { "assistant": _format_shell("\n".join(lines), why, next_action), "sources": sources[:8], "files": files, "meta": { "domain": "router_docs", "retrieval_mode": "router_similar_5g_alternatives_fast", "web_assisted": bool(used_web), "shortlist_found": True, "source_model": source_profile.get("model", ""), "source_missing_fields": list(source_profile.get("missing_fields") or []), "candidate_models": [str(c.get("model") or "") for c in ranked], }, } def _build_pots_provider_cards(self) -> Dict[str, Dict[str, Any]]: cards: Dict[str, Dict[str, Any]] = {} candidates: List[Tuple[str, str]] = [] seen: set[Tuple[str, str]] = set() known_doc_paths: Dict[str, List[Tuple[str, str]]] = {} def _remember_doc(prefix: str, rel_text: str) -> None: doc_name = Path(str(rel_text or "")).name.lower().strip() if not doc_name: return rows = known_doc_paths.setdefault(doc_name, []) item = (prefix, rel_text) if item not in rows: rows.append(item) def _add_provider_doc(provider: str, href_prefix: str, rel_text: str) -> None: rel_value = str(rel_text or "").strip() if not rel_value: return card = cards.setdefault(provider, {"provider": provider, "count": 0, "docs": [], "doc_prefixes": {}}) docs = card.get("docs") if isinstance(card.get("docs"), list) else [] doc_prefixes = card.get("doc_prefixes") if isinstance(card.get("doc_prefixes"), dict) else {} if rel_value not in docs: docs.append(rel_value) card["count"] = int(card.get("count", 0)) + 1 doc_prefixes[rel_value] = href_prefix card["docs"] = docs[:12] card["doc_prefixes"] = doc_prefixes for rel in self._pots_file_map.values(): rel_text = str(rel or "").strip() if not rel_text: continue _remember_doc("/pots_files", rel_text) key = ("/pots_files", rel_text) if key in seen: continue seen.add(key) candidates.append(key) try: router_files = list(self.router_rag_core.list_files() or []) except Exception: router_files = [] router_path_hints = sorted({hint for hints in _POTS_PROVIDER_ROUTER_PATH_HINTS.values() for hint in hints if hint}) for rel in router_files: rel_text = str(rel or "").strip() if not rel_text: continue _remember_doc("/router_rag_files", rel_text) path_low = rel_text.lower() if not any(hint in path_low for hint in router_path_hints): continue key = ("/router_rag_files", rel_text) if key in seen: continue seen.add(key) candidates.append(key) for href_prefix, rel_text in candidates: file_low = Path(rel_text).name.lower() path_low = rel_text.lower() for provider, patterns in _POTS_PROVIDER_PATTERNS.items(): if not any((p in file_low) or (p in path_low) for p in patterns): continue _add_provider_doc(provider, href_prefix, rel_text) # Some hosted corpora index provider-tagged files outside the narrow router path hints. # Backfill missing provider cards from indexed evidence, but only when the returned doc # can be mapped back to a known file path. search_targets: List[Tuple[str, Any]] = [ ("/pots_files", getattr(self.pots_core, "index", None)), ("/router_rag_files", getattr(self.router_rag_core, "index", None)), ] for provider, patterns in _POTS_PROVIDER_PATTERNS.items(): if int((cards.get(provider, {}) or {}).get("count", 0) or 0) > 0: continue queries = [f"{provider} POTS replacement", f"{provider} analog line replacement", provider] for href_prefix, idx_obj in search_targets: if idx_obj is None or (not hasattr(idx_obj, "search")): continue try: hits = self._parallel_index_search( idx_obj, queries, k=4, stage_budget_s=1.0, max_workers=min(3, int(self.parallel_search_max_workers or 3)), ) except Exception: hits = [] for hit in hits: if not isinstance(hit, dict): continue doc_name = Path(str(hit.get("doc") or "")).name.lower().strip() if not doc_name: continue blob = f"{doc_name} {_norm(str(hit.get('text') or '')).lower()}" if not any(_contains_term(blob, token.lower()) for token in patterns): continue for mapped_prefix, rel_text in known_doc_paths.get(doc_name, []): resolved_prefix = mapped_prefix if mapped_prefix in {"/pots_files", "/router_rag_files"} else href_prefix _add_provider_doc(provider, resolved_prefix, rel_text) if int((cards.get(provider, {}) or {}).get("count", 0) or 0) > 0: break return dict(sorted(cards.items(), key=lambda kv: kv[0].lower())) def _index_search(self, idx_obj: Any, query: str, *, k: int = 6) -> List[Any]: if idx_obj is None or (not hasattr(idx_obj, "search")): return [] q = str(query or "").strip() if not q: return [] try: return list(idx_obj.search(q, k=max(1, int(k))) or []) except TypeError: try: return list(idx_obj.search(q, top_k=max(1, int(k))) or []) except Exception: return [] except Exception: return [] def _parallel_index_search( self, idx_obj: Any, queries: Sequence[str], *, k: int = 6, stage_budget_s: float = 0.0, max_workers: Optional[int] = None, ) -> List[Any]: normalized_queries = [str(q).strip() for q in (queries or []) if str(q).strip()] if not normalized_queries: return [] budget_s = max(0.0, float(stage_budget_s or 0.0)) worker_cap = int(max_workers or self.parallel_search_max_workers or 1) workers = max(1, min(worker_cap, len(normalized_queries))) if (not self.parallel_search_enabled) or workers <= 1: started = time.perf_counter() out: List[Any] = [] for q in normalized_queries: if budget_s > 0.0 and (time.perf_counter() - started) >= budget_s: break call_started = time.perf_counter() out.extend(self._index_search(idx_obj, q, k=k)) if self.phase_circuit_breaker_enabled and budget_s > 0.0: call_elapsed = time.perf_counter() - call_started remaining = budget_s - (time.perf_counter() - started) if (call_elapsed >= float(self.phase_circuit_breaker_s)) and ( remaining <= float(self.phase_circuit_breaker_min_remaining_s) ): break return out out: List[Any] = [] started = time.perf_counter() futures: Dict[Future[Any], str] = {} future_started: Dict[Future[Any], float] = {} pending_set: set[Future[Any]] = set() queued = list(normalized_queries) stop_submissions = False local_executor: Optional[ThreadPoolExecutor] = None executor = self._search_executor if executor is not None and bool(getattr(executor, "_shutdown", False)): # Recover from stale shared executors (e.g., prior timeout cleanup) without failing the request path. executor = None if self.parallel_search_shared_executor and self.parallel_search_enabled: try: refreshed = ThreadPoolExecutor( max_workers=max(1, int(self.parallel_search_max_workers or workers)), thread_name_prefix="unified-kb-search", ) self._search_executor = refreshed executor = refreshed except Exception: self._search_executor = None if executor is None: local_executor = ThreadPoolExecutor(max_workers=workers, thread_name_prefix="unified-kb-search") executor = local_executor def _submit_ready() -> None: nonlocal executor, local_executor, stop_submissions while (not stop_submissions) and queued and (len(pending_set) < workers): q = queued.pop(0) try: fut = executor.submit(self._index_search, idx_obj, q, k=k) except RuntimeError: # Shared pool can be concurrently shut down; fall back to a local executor for this request. try: if local_executor is None: local_executor = ThreadPoolExecutor(max_workers=workers, thread_name_prefix="unified-kb-search") executor = local_executor fut = executor.submit(self._index_search, idx_obj, q, k=k) except Exception: stop_submissions = True queued.clear() break futures[fut] = q future_started[fut] = time.perf_counter() pending_set.add(fut) try: _submit_ready() while pending_set: remaining_budget = None if budget_s > 0.0: remaining_budget = budget_s - (time.perf_counter() - started) if remaining_budget <= 0.0: break wait_window = max(0.05, float(self.index_search_call_timeout_s)) if remaining_budget is not None: wait_window = min(wait_window, max(0.01, remaining_budget)) done, _ = wait(pending_set, timeout=wait_window, return_when=FIRST_COMPLETED) if not done: if self.phase_circuit_breaker_enabled and (remaining_budget is not None): if remaining_budget <= float(self.phase_circuit_breaker_min_remaining_s): break continue breaker_trip = False for fut in done: pending_set.discard(fut) futures.pop(fut, None) call_started = future_started.pop(fut, started) try: rows = fut.result() except Exception: rows = [] if isinstance(rows, list): out.extend(rows) if self.phase_circuit_breaker_enabled and budget_s > 0.0: call_elapsed = time.perf_counter() - call_started remaining = budget_s - (time.perf_counter() - started) if (call_elapsed >= float(self.phase_circuit_breaker_s)) and ( remaining <= float(self.phase_circuit_breaker_min_remaining_s) ): breaker_trip = True if breaker_trip: stop_submissions = True queued.clear() for fut in list(pending_set): fut.cancel() pending_set.clear() break _submit_ready() for fut in list(pending_set): fut.cancel() finally: if local_executor is not None: local_executor.shutdown(wait=False, cancel_futures=True) return out def _build_pots_provider_evidence_cards(self) -> Dict[str, Dict[str, Any]]: cards: Dict[str, Dict[str, Any]] = {} idx_obj = getattr(self.pots_core, "index", None) base_stage_budget = max(0.5, float(self.search_stage_budget_s_by_domain.get("pots", 2.8))) for provider, card in sorted(self._pots_provider_cards.items(), key=lambda kv: kv[0].lower()): docs = [str(x) for x in (card.get("docs") or []) if str(x)] docs_by_name = {Path(str(x)).name.lower() for x in docs} provider_aliases = tuple(t.lower() for t in _POTS_PROVIDER_PATTERNS.get(provider, (provider,))) provider_card: Dict[str, Any] = { "provider": provider, "count": int(card.get("count", 0) or 0), "docs": docs[:12], "evidence": [], } if idx_obj is None or (not hasattr(idx_obj, "search")): cards[provider] = provider_card continue queries = [ f"{provider} pots replacement capabilities limitations", f"{provider} fire elevator alarm fax requirements", f"{provider} reliability failover monitoring", ] rows = self._parallel_index_search( idx_obj, queries, k=4, stage_budget_s=min(base_stage_budget, 1.6), max_workers=min(3, int(self.parallel_search_max_workers)), ) seen: set[Tuple[str, str]] = set() evidence: List[Dict[str, Any]] = [] for row in rows: if not isinstance(row, dict): continue doc = Path(str(row.get("doc") or "")).name page = str(row.get("page") or "").strip() excerpt = _norm(str(row.get("text") or "")) if not doc: continue doc_low = doc.lower() excerpt_low = excerpt.lower() provider_match = (doc_low in docs_by_name) or any( alias and ((alias in doc_low) or (alias in excerpt_low)) for alias in provider_aliases ) if not provider_match: continue dedupe_key = (doc, page) if dedupe_key in seen: continue seen.add(dedupe_key) evidence.append( { "doc": doc, "page": page, "excerpt": excerpt[:220] if excerpt else "", "score": float(row.get("score") or 0.0), } ) if len(evidence) >= 4: break if (not evidence) and docs: for rel in docs[:2]: doc_name = Path(str(rel)).name evidence.append( { "doc": doc_name, "page": "", "excerpt": f"{provider} provider document indexed: {doc_name}.", "score": 0.75, } ) provider_card["evidence"] = evidence cards[provider] = provider_card return cards def _provider_cached_hits(self, provider: str, *, limit: int = 4) -> List[Dict[str, Any]]: card = self._pots_provider_evidence_cards.get(str(provider or "").strip(), {}) rows = card.get("evidence") if not isinstance(rows, list): return [] out: List[Dict[str, Any]] = [] for row in rows: if not isinstance(row, dict): continue out.append( { "doc": str(row.get("doc") or ""), "page": str(row.get("page") or ""), "text": str(row.get("excerpt") or ""), "score": float(row.get("score") or 0.0), } ) if len(out) >= max(1, int(limit)): break return out def _cache_key( self, message: str, *, mode: str, audience: str, show_citations: bool, ) -> str: payload = { "m": _norm(message).lower(), "mode": _norm_mode(mode), "aud": _norm(audience).lower(), "cite": bool(show_citations), "model": _norm(self.openai_model).lower(), "concept_model": _norm(self.concept_fallback_model).lower(), "concept_enabled": bool(self.concept_fallback_enabled), "rr": self._rapid_router_catalog_fingerprint(), } return json.dumps(payload, sort_keys=True, separators=(",", ":")) def _cacheable_state(self, st: UnifiedKnowledgebaseState) -> bool: if st.pending: return False return not any( [ bool(st.router_docs_state), bool(st.router_lifecycle_state), bool(st.masters_state), bool(st.pots_state), ] ) def _cache_get(self, key: str) -> Optional[Dict[str, Any]]: if (not self.cache_enabled) or (not key): return None now = time.time() entry = self._response_cache.get(key) if not isinstance(entry, dict): self._cache_misses += 1 return None if float(entry.get("expires_at", 0.0) or 0.0) <= now: self._response_cache.pop(key, None) try: self._response_cache_order.remove(key) except ValueError: pass self._cache_misses += 1 return None payload = entry.get("payload") if not isinstance(payload, dict): self._cache_misses += 1 return None self._cache_hits += 1 return json.loads(json.dumps(payload)) def _cache_set(self, key: str, payload: Dict[str, Any]) -> None: if (not self.cache_enabled) or (not key) or (not isinstance(payload, dict)): return now = time.time() self._response_cache[key] = { "expires_at": now + float(self.cache_ttl_s), "payload": json.loads(json.dumps(payload)), } if key in self._response_cache_order: self._response_cache_order.remove(key) self._response_cache_order.append(key) while len(self._response_cache_order) > int(self.cache_max_items): oldest = self._response_cache_order.pop(0) self._response_cache.pop(oldest, None) def _pots_heavy_cache_key( self, *, intent_tag: str, providers: Sequence[str], show_citations: bool, message: str, ) -> str: payload = { "intent": str(intent_tag or "").strip().lower(), "providers": sorted({_norm(str(p or "")).lower() for p in providers if _norm(str(p or ""))}), "cite": bool(show_citations), "m": _normalize_router_query_text(_norm(message or "")), "model": _norm(self.openai_model).lower(), "concept_model": _norm(self.concept_fallback_model).lower(), } return json.dumps(payload, sort_keys=True, separators=(",", ":")) def _pots_heavy_cache_get(self, key: str) -> Optional[Dict[str, Any]]: if (not self.pots_heavy_cache_enabled) or (not key): return None now = time.time() entry = self._pots_heavy_cache.get(key) if not isinstance(entry, dict): self._pots_heavy_cache_misses += 1 return None if float(entry.get("expires_at", 0.0) or 0.0) <= now: self._pots_heavy_cache.pop(key, None) try: self._pots_heavy_cache_order.remove(key) except ValueError: pass self._pots_heavy_cache_misses += 1 return None payload = entry.get("payload") if not isinstance(payload, dict): self._pots_heavy_cache_misses += 1 return None self._pots_heavy_cache_hits += 1 return json.loads(json.dumps(payload)) def _pots_heavy_cache_set(self, key: str, payload: Dict[str, Any]) -> None: if (not self.pots_heavy_cache_enabled) or (not key) or (not isinstance(payload, dict)): return now = time.time() self._pots_heavy_cache[key] = { "expires_at": now + float(self.pots_heavy_cache_ttl_s), "payload": json.loads(json.dumps(payload)), } if key in self._pots_heavy_cache_order: self._pots_heavy_cache_order.remove(key) self._pots_heavy_cache_order.append(key) while len(self._pots_heavy_cache_order) > int(self.pots_heavy_cache_max_items): oldest = self._pots_heavy_cache_order.pop(0) self._pots_heavy_cache.pop(oldest, None) def _router_alias_confirmation_needed(self, message: str) -> Optional[Tuple[str, str]]: low = str(message or "").lower() if ("rx50" in low) and ("ex50" in low) and ( re.search(r"\b(weight|lighter|heavier|weighs?)\b", low) or self._is_router_compare_like(message) ): # Let router-doc retrieval try the compare/doc path before forcing alias clarification. return None model_tokens = {_compact_model(x) for x in self._extract_router_models_cached(message)} for alias, canonical in _ROUTER_ALIAS_CONFIRM_REQUIRED.items(): alias_compact = _compact_model(alias) if alias_compact in model_tokens: return alias, canonical if alias_compact and re.search(rf"\b{re.escape(alias.lower())}\b", low): return alias, canonical return None def _router_should_skip_fact_fast(self, message: str, model_count: int) -> bool: low = str(message or "").lower() compare_two_models = model_count <= 2 and self._is_router_compare_like(message) if self._router_compare_should_delegate_to_router_docs(message, model_count): return True asks_full_details = bool( any(x in low for x in ("full details", "all details", "full specs", "all specs", "tell me about", "spec overview")) and bool(self._extract_router_models_cached(message)) ) if asks_full_details: return False if any(h in low for h in _ROUTER_FAST_SKIP_HINTS): if compare_two_models and any(k in low for k in ("outdoor", "indoor", "vehicle")): return False return True if model_count >= 3 and _contains_any(low, _ROUTER_FAST_COMPARE_HINTS): return True if ("install" in low or "checklist" in low) and ("quick start" in low or "guide" in low): return True return False def _router_lightweight_fact_compare_supported(self, message: str, model_count: int) -> bool: if model_count != 2 or (not self._is_router_compare_like(message)): return False low = _normalize_router_query_text(message) fields = set(self._router_fact_fields_for_query(message)) if not fields: return False if any( hint in low for hint in ( "from documented specs only", "documented specs only", "from docs only", "docs only", "what is documented vs not documented", "include what is documented", "decision table", "recommend", "recommended", "best fit", "best-fit", "fit for", "option", "options", "family", "families", "install", "installation", "mount", "manual", "datasheet", "data sheet", "quick start", "quoted excerpt", "quoted excerpts", ) ): return False lightweight_fields = { "wan_lan", "antennas_rf", "modem", "wifi", "throughput", "battery", "poe", "ruggedization", } return fields.issubset(lightweight_fields) def _router_compare_should_delegate_to_router_docs(self, message: str, model_count: int) -> bool: if model_count < 2: return False if not self._is_router_compare_like(message): return False if self._router_lightweight_fact_compare_supported(message, model_count): return False low = _normalize_router_query_text(message) if any( hint in low for hint in ( "msrp", "price", "pricing", "cost", "quote", "quoted", "budgetary", "unit price", ) ): return False asks_structured_compare = any( hint in low for hint in ( "decision table", "documented", "documented facts", "cautious interpretation", "documented specs", "documented specs only", "docs only", "from docs only", "data sheet", "datasheet", "manual", ) ) asks_spec_fields = _contains_any( low, ( "wan/lan", "wan lan", "ethernet", "rf", "connector", "modem", "wifi", "wi-fi", "antenna", "throughput", "serial", "vpn", "firewall", "port forwarding", "rugged", "install", "installation", "mount", "dimensions", "weight", "battery", ), ) if _looks_like_router_lifecycle(low) and not asks_spec_fields and not asks_structured_compare: return False return bool( asks_structured_compare or self._query_prefers_authoritative_evidence(message, "router_docs") ) def _router_fact_fast_should_defer(self, message: str, fields: Sequence[str]) -> bool: low = _normalize_router_query_text(message) if re.search(r"\bwhat\s+wan(?:\s*/\s*|\s+)lan\s+ports?\s+are\s+documented\s+for\b", low): return True if any(h in low for h in ("install caveat", "install caveats", "special notes", "install notes")): return True if not self._query_prefers_authoritative_evidence(message, "router_docs"): return False if any( hint in low for hint in ( "dimension", "dimensions", "size", "height", "width", "depth", "temperature", "temperatures", "operating temperature", "storage temperature", "certification", "certifications", "cloud management", "management platform", "diagnostic", "diagnostics", "firewall", "port forwarding", "download", "upload", "downlink", "uplink", "sim option", "sim options", "dual sim", "esim", "e-sim", ) ): return True if ("install" in low or "installation" in low) and any(x in low for x in ("dimension", "dimensions", "size", "weight")): return True if ("vpn" in low) and any(x in low for x in ("firewall", "port forwarding")) and ("vpn" in fields) and (len(fields) <= 1): return True return False def _extract_conversational_fleet_items(self, message: str) -> List[Dict[str, Any]]: text = str(message or "") if not text: return [] token_matches = [m for m in re.finditer(r"[A-Za-z0-9'\\-]+", text) if m.group(0)] tokens = [str(m.group(0)) for m in token_matches] if not token_matches: return [] def _valid_customer_marker(raw_customer: str) -> bool: customer = str(raw_customer or "").strip(" ,.;:-") if not customer: return False low_customer = customer.lower() if _router_customer_phrase_looks_instructional(customer): return False first_word = low_customer.split()[0] if low_customer.split() else "" if first_word in _ROUTER_FLEET_CUSTOMER_FIRST_WORD_STOPWORDS: return False compact_customer = _compact_model(customer) if compact_customer and ( self._lookup_router_fact_key(customer) or self._lookup_router_fact_key(compact_customer) or self._lookup_router_lifecycle_key_relaxed(customer) or self._lookup_router_lifecycle_key_relaxed(compact_customer) ): return False meaningful_tokens = 0 matched_router_tokens = 0 for token in re.findall(r"[A-Za-z0-9'\\-]+", customer): compact = _compact_model(token) if not compact: continue meaningful_tokens += 1 if ( self._lookup_router_fact_key(token) or self._lookup_router_fact_key(compact) or self._lookup_router_lifecycle_key_relaxed(token) or self._lookup_router_lifecycle_key_relaxed(compact) ): matched_router_tokens += 1 if meaningful_tokens and matched_router_tokens == meaningful_tokens: return False return True def _clean_customer_label(raw_customer: str) -> str: customer = str(raw_customer or "").strip(" ,.;:-") if not customer: return "" customer = re.sub( r"(?i)\b(has|have|had|wants?|need(?:s)?|plans?|planning|replace|replacing|with|for)\b.*$", "", customer, ).strip(" ,.;:-") customer = re.sub(r"\s+", " ", customer).strip() if not customer: return "" low_customer = customer.lower() if _router_customer_phrase_looks_instructional(customer): return "" if _valid_customer_marker(customer): return customer return "" customer_markers: List[Tuple[int, str]] = [(0, "Unknown")] for m in re.finditer( r"\bcustomer\b[\s,:-]*([A-Za-z0-9&' .\-]{2,60}?)(?:\s+with|\s+has)\s+(?=\d)", text, flags=re.IGNORECASE, ): customer = str(m.group(1) or "").strip(" ,.;:-") if _valid_customer_marker(customer): customer_markers.append((int(m.end()), customer)) for m in re.finditer(r"\b(?:and\s+)?(?:we\s+)?(?:also\s+)?found\s+(?=\d)", text, flags=re.IGNORECASE): customer_markers.append((int(m.end()), "Unknown")) customer_markers.sort(key=lambda x: x[0]) def _customer_from_prefix(pos: int) -> str: prefix = str(text[: max(0, pos)] or "").strip(" ,.;:-") if not prefix: return "" segments = [ seg.strip(" ,.;:-") for seg in re.split(r"(?:,|:|;|\band\b|\bplus\b|\+)", prefix, flags=re.IGNORECASE) if seg and seg.strip(" ,.;:-") ] for segment in reversed(segments): tail = re.sub(r"(?i)\b(?:has|have|had|with)\s*$", "", segment).strip(" ,.;:-") if not tail: continue low_tail = tail.lower() if _router_customer_phrase_looks_instructional(tail): continue if not any(ch.isalpha() for ch in tail): continue if any(ch.isdigit() for ch in tail): continue if low_tail.startswith("unknown") or low_tail.startswith("placeholder"): return "Unknown" cleaned_tail = _clean_customer_label(tail) if cleaned_tail: return cleaned_tail return "" def _customer_for_token_index(token_index: int) -> str: if token_index < 0 or token_index >= len(token_matches): return "Unknown" pos = int(token_matches[token_index].start()) prefix_customer = _customer_from_prefix(pos) if prefix_customer: return prefix_customer return _customer_for_position(pos) def _customer_for_position(pos: int) -> str: customer = "Unknown" for marker_pos, marker_name in customer_markers: if marker_pos <= pos: customer = marker_name or "Unknown" else: break if customer and customer != "Unknown": return customer prefix_customer = _customer_from_prefix(pos) if prefix_customer: return prefix_customer return customer or "Unknown" def _bare_fleet_chunk_looks_like_model(token: str) -> bool: text = str(token or "").strip(" \t\r\n,.;:()[]{}") if not text: return False compact = _compact_model(text) if (not compact) or compact.lower() in _ROUTER_FLEET_MODEL_STOPWORDS: return False if not any(ch.isdigit() for ch in compact): return False first_word = _compact_model(re.split(r"\s+", text, maxsplit=1)[0]) if not first_word: return False if first_word.lower() in { "provide", "replacement", "table", "confidence", "notes", "clarification", "clarifications", "provisional", "alternatives", "strategy", "recommend", "recommendation", "recommendations", "customer", "fleet", "portfolio", "inventory", "model", "models", "device", "devices", "need", "needs", "build", "phased", "combined", "unknown", "lifecycle", "q1", "q2", "q3", "q4", "by", "5g", "4g", "lte", }: return False if first_word in _ROUTER_VENDOR_TOKEN_PREFIXES: return True if re.search(r"^[A-Za-z]{1,8}\d", first_word): return True return bool( self._lookup_router_fact_key(text) or self._lookup_router_fact_key(compact) or self._lookup_router_lifecycle_key_relaxed(text) or self._lookup_router_lifecycle_key_relaxed(compact) ) parsed: List[Dict[str, Any]] = [] i = 0 while i < len(tokens): raw_qty = str(tokens[i]).replace(",", "").strip() if not raw_qty.isdigit(): i += 1 continue qty = int(raw_qty) if qty <= 0 or qty > 100000: i += 1 continue start = i + 1 best: Optional[Tuple[int, int, str, str]] = None # score, n_consumed, canonical_key, requested_label for n in range(1, 6): if start + n > len(tokens): break cand_tokens = tokens[start : start + n] first_token = _compact_model(cand_tokens[0]) if cand_tokens else "" if first_token.lower() in _ROUTER_FLEET_MODEL_STOPWORDS: continue trailing_tokens = [_compact_model(tok) for tok in cand_tokens[1:] if _compact_model(tok)] vendor_prefixed_numeric = bool( first_token in _ROUTER_VENDOR_TOKEN_PREFIXES and trailing_tokens and any(tok.isdigit() for tok in trailing_tokens) ) if any(tok.isdigit() for tok in trailing_tokens) and (not vendor_prefixed_numeric): continue if any(tok.isalpha() and len(tok) > 1 and tok not in _ROUTER_FLEET_MODEL_JOINER_TOKENS for tok in trailing_tokens): continue if any(ch.isdigit() for ch in first_token) and any(tok.isdigit() for tok in trailing_tokens): continue candidate_forms = [ " ".join(cand_tokens).strip(), "".join(cand_tokens).strip(), ] if len(cand_tokens) >= 2 and first_token in _ROUTER_VENDOR_TOKEN_PREFIXES: candidate_forms.extend( [ " ".join(cand_tokens[1:]).strip(), "".join(cand_tokens[1:]).strip(), ] ) for cand in candidate_forms: compact = _compact_model(cand) if (not compact) or (len(compact) < 4): continue # Prevent accidental matches like "300 legacy units across 4 models". if (not any(ch.isalpha() for ch in compact)) or (not any(ch.isdigit() for ch in compact)): continue norm = self._normalize_router_model(cand) if not norm: continue life_key = self._lookup_router_lifecycle_key_relaxed(norm) fact_key = self._lookup_router_fact_key(norm) canonical = life_key or fact_key if not canonical: continue requested_label = _norm(" ".join(cand_tokens)) or compact score = 120 - (n * 4) if life_key and fact_key: score += 6 if re.search(r"\d", cand): score += 3 row = (score, n, canonical, requested_label) if (best is None) or (row > best): best = row if best is not None: _, consumed, canonical_key, requested_label = best parsed.append( { "customer": _customer_for_token_index(i), "qty": qty, "model_key": canonical_key, "model_display": requested_label, } ) i = start + consumed continue # Fallback for unknown/unsupported tokens: keep the parsed pair so it is visible # and can receive provisional replacement guidance instead of being dropped. if start < len(tokens): raw_model = str(tokens[start] or "").strip() compact = _compact_model(raw_model) if compact and compact.lower() not in _ROUTER_FLEET_MODEL_STOPWORDS: if any(ch.isalpha() for ch in compact) and any(ch.isdigit() for ch in compact): canonical = self._normalize_router_model(raw_model) or compact parsed.append( { "customer": _customer_for_token_index(i), "qty": qty, "model_key": canonical, "model_display": compact, } ) i = start + 1 continue i += 1 for match in re.finditer(r"\b(\d{1,5})\s+([A-Za-z][A-Za-z0-9\-]{1,40})\b", text): qty = int(str(match.group(1) or "0") or 0) if qty <= 0 or qty > 100000: continue raw_model = str(match.group(2) or "").strip() compact = _compact_model(raw_model) if (not compact) or compact.lower() in _ROUTER_FLEET_MODEL_STOPWORDS: continue if (not any(ch.isalpha() for ch in compact)) or (not any(ch.isdigit() for ch in compact)): continue existing = next( ( row for row in parsed if _compact_model(row.get("product_key") or row.get("product_text") or row.get("model_display") or "") == compact and int(row.get("qty") or 0) == qty ), None, ) if existing: continue normalized = _as_dict(core.normalize_catalog_device(manufacturer_text="", product_text=raw_model)) if normalized.get("ok"): match_row = _as_dict(normalized.get("match")) parsed.append( { "customer": _customer_for_position(int(match.start())), "qty": qty, "matched": True, "product_key": str(match_row.get("product_key") or ""), "model_display": raw_model, "product_text": str(match_row.get("product_id") or raw_model), } ) else: parsed.append( { "customer": _customer_for_position(int(match.start())), "qty": qty, "matched": False, "product_key": "", "model_display": raw_model, "product_text": raw_model, } ) if not parsed: return [] bucket: Dict[Tuple[str, str], Dict[str, Any]] = {} for row in parsed: ckey = str(row.get("customer") or "Unknown") mkey = str(row.get("model_key") or "") if not mkey: continue k = (ckey, mkey) if k not in bucket: bucket[k] = dict(row) else: bucket[k]["qty"] = int(bucket[k].get("qty") or 0) + int(row.get("qty") or 0) if not _norm(bucket[k].get("model_display", "")): bucket[k]["model_display"] = row.get("model_display", "") return list(bucket.values()) def _router_conversational_fleet_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: # Retired in favor of workbook-backed fleet lifecycle handling. # Keep a compatibility stub so any stray caller fails closed. return None low = str(message or "").lower() # Strategic/ranking asks should flow to lifecycle strategy formatter, not the wide conversational table. if any( h in low for h in ( "phased", "phase", "strategy", "migration order", "risk ranking", "replacement priority", "priority rank", "wave", "q1", "q2", "q3", "q4", "portfolio", "confidence notes", "combined fleet", ) ): return None has_status = _contains_any(low, _ROUTER_STATUS_HINTS) has_replacement = _contains_any(low, _ROUTER_REPLACEMENT_HINTS) has_specs = _contains_any(low, _ROUTER_SPEC_HINTS) has_conversational_context = any(x in low for x in ("customer", "has", "have", "status", "replacement", "spec")) qty_hits = re.findall(r"\b(\d{1,5})\s+([A-Za-z][A-Za-z0-9\-]{1,20})\b", str(message or "")) if _contains_any(low, _ROUTER_FAST_COMPARE_HINTS): # Avoid interpreting model names containing digits (e.g., BR1 Pro/Mini) as fleet quantity lines. if (not has_status) and (not has_replacement): if ("table format" in low) or ("comparison" in low) or ("compare" in low): return None if qty_hits and all(int(q or 0) <= 1 for q, _ in qty_hits): return None wants_multi_intent = (has_status and has_replacement) or (has_replacement and has_specs) or (has_status and has_specs) likely_inventory_batch = len(qty_hits) >= 2 and has_conversational_context if not wants_multi_intent and not likely_inventory_batch: return None if not re.search(r"\b\d{1,5}\b", low): return None fleet_items = self._extract_conversational_fleet_items(message) if not fleet_items: return None force_conservative_lifecycle_table = False if has_replacement: for row in fleet_items[:20]: canonical_key = str(row.get("model_key") or "") requested_label = _norm(str(row.get("model_display") or "")) resolved_life_key = self._router_inventory_lifecycle_key(canonical_key, requested_label) if (not resolved_life_key) or self._router_inventory_variant_adjacent(requested_label, resolved_life_key): force_conservative_lifecycle_table = True break if force_conservative_lifecycle_table: explicit_customer_items: List[Tuple[str, str, int]] = [] explicit_customer_names: set[str] = set() for item in fleet_items: customer = _norm(item.get("customer", "")) if (not customer) or (customer.lower() == "unknown"): continue qty = int(item.get("qty") or 0) if qty <= 0: continue model_label = _norm(item.get("model_display", "")) or _norm(item.get("model_key", "")) or "Unknown" explicit_customer_names.add(customer) explicit_customer_items.append((customer, model_label, qty)) lines = [ *( [ "Parsed customer breakdown:", "", "| Customer | Device | Qty |", "| --- | --- | ---: |", *[ f"| {_md_cell(customer)} | {_md_cell(model_label)} | {qty} |" for customer, model_label, qty in explicit_customer_items[:12] ], "", ] if len(explicit_customer_names) >= 2 else [] ), "Lifecycle + replacement table from internal lifecycle CSV:", "", "| Device | Qty | Status | Tech | EOS | EOL | 4G alternative | 5G replacement |", "| --- | ---: | --- | --- | --- | --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] source_keys: set[str] = set() for idx, row in enumerate(fleet_items[:20], start=1): qty = int(row.get("qty") or 1) canonical_key = str(row.get("model_key") or "") requested_label = _norm(str(row.get("model_display") or "")) resolved_life_key = self._router_inventory_lifecycle_key(canonical_key, requested_label) variant_adjacent = self._router_inventory_variant_adjacent(requested_label, resolved_life_key) if resolved_life_key and (not variant_adjacent): life = self._router_lifecycle_rows.get(resolved_life_key, {}) fact = self._router_fact_rows.get(self._lookup_router_fact_key(resolved_life_key), {}) model_name = requested_label or self._router_display_name(life or fact, resolved_life_key) status = _norm(life.get("status", "")) or "Unknown (abstained)" tech = _norm(life.get("tech", "")) or self._infer_catalog_tech(fact) or "Not listed" eos = _norm(life.get("eos", "")) or "Not listed" eol = _norm(life.get("eol", "")) or "Not listed" alt4g = _truncate(_normalize_replacement_cell(life.get("alt4g", "")), 86) or "Not listed (abstained)" rep5g = _truncate(_normalize_replacement_cell(life.get("rep5g", "")), 86) or "Not listed (abstained)" if resolved_life_key not in source_keys: source_keys.add(resolved_life_key) sources.append( { "id": f"CFL{idx}", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": f"lifecycle_csv:{resolved_life_key}", "location": "", "excerpt": ( f"{model_name}: status={status}; eos={eos}; eol={eol}; " f"4g_alternative={alt4g}; 5g_replacement={rep5g}." ), "score": 1.0, } ) else: fact_key = self._lookup_router_fact_key(canonical_key) if (not fact_key) and requested_label: fact_key = self._lookup_router_fact_key(requested_label) fact = self._router_fact_rows.get(fact_key, {}) if fact_key else {} model_name = requested_label or canonical_key or "Unknown" status = "Unknown lifecycle (needs exact model confirmation)" tech = self._infer_catalog_tech(fact) or "Not listed" eos = "Not listed" eol = "Not listed" alt4g = "Provisional after model confirmation" rep5g = "Provisional after model confirmation" lines.append( f"| {_md_cell(model_name)} | {qty} | {_md_cell(status)} | {_md_cell(tech)} | " f"{_md_cell(eos)} | {_md_cell(eol)} | {_md_cell(alt4g)} | {_md_cell(rep5g)} |" ) sources.append( { "id": f"CFL{len(sources) + 1}", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "catalog_csv_normalization", "location": "", "excerpt": "Catalog CSV used for model normalization and provisional tech labeling when lifecycle rows are missing.", "score": 0.99, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Mixed replacement batches with unresolved or variant-adjacent models are answered with the conservative lifecycle table instead of the conversational summary.", "Replacement cells mirror internal lifecycle mapping fields; they are not compatibility or migration-fit guarantees on their own.", "Rows without exact lifecycle coverage stay provisional until the exact model/SKU is confirmed.", ], [ "Provide the exact SKU for provisional rows and I will finalize the lifecycle mapping.", "Ask `strict docs only` if you want a source-by-source lifecycle evidence table next.", ], ), "sources": sources[:6], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": {"domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_csv", "web_assisted": False}, } lines = [ "Conversational fleet summary (status + replacements):", "", "| Customer | Device | Qty | Status | Tech | EOS | EOL | 4G alternative | 5G replacement |", "| --- | --- | ---: | --- | --- | --- | --- | --- | --- |", ] spec_lines: List[str] = [] sources: List[Dict[str, Any]] = [] for idx, row in enumerate(fleet_items[:20], start=1): customer = str(row.get("customer") or "Unknown") qty = int(row.get("qty") or 1) canonical_key = str(row.get("model_key") or "") life_key = self._lookup_router_lifecycle_key_relaxed(canonical_key) or canonical_key fact_key = self._lookup_router_fact_key(canonical_key) or canonical_key life = self._router_lifecycle_rows.get(life_key, {}) fact = self._router_fact_rows.get(fact_key, {}) requested_label = _norm(str(row.get("model_display") or "")) canonical_name = self._router_display_name(life or fact, canonical_key or "Unknown") model_name = canonical_name if requested_label and _compact_model(requested_label) != _compact_model(canonical_name): model_name = f"{canonical_name} (input: {requested_label})" if life: status = _norm(life.get("status", "")) or "Not listed" tech = _norm(life.get("tech", "")) or self._infer_catalog_tech(fact) or "Not listed" eos = _norm(life.get("eos", "")) or "Not listed" eol = _norm(life.get("eol", "")) or "Not listed" alt4g = _norm(life.get("alt4g", "")) or "Not listed" rep5g = _norm(life.get("rep5g", "")) or "Not listed" elif fact: status = "Catalog match only (no lifecycle row)" tech = self._infer_catalog_tech(fact) or "Not listed" eos = "Abstained (no lifecycle row)" eol = "Abstained (no lifecycle row)" alt4g = "Abstained (no lifecycle row)" rep5g = "Abstained (no lifecycle row)" else: status = "Needs exact model confirmation" tech = "Abstained (no indexed match)" eos = "Abstained (no indexed match)" eol = "Abstained (no indexed match)" alt4g = "Abstained (no indexed match)" rep5g = "Abstained (no indexed match)" modem = _truncate(_fix_common_mojibake(_norm(fact.get("modem", "")) or "Not listed"), 82) wan_lan = _truncate(_fix_common_mojibake(_norm(fact.get("wan_lan", "")) or "Not listed"), 72) rf = _truncate(_fix_common_mojibake(_norm(fact.get("antennas_rf", "")) or "Not listed"), 72) wifi = _truncate(_fix_common_mojibake(_norm(fact.get("wifi", "")) or "Not listed"), 42) battery = _truncate(_fix_common_mojibake(_norm(fact.get("battery", "")) or "Not listed"), 38) rugged = _truncate(_fix_common_mojibake(_norm(fact.get("ruggedization", "")) or "Not listed"), 64) lines.append( f"| {_md_cell(customer)} | {_md_cell(model_name)} | {qty} | {_md_cell(status)} | " f"{_md_cell(tech)} | {_md_cell(eos)} | {_md_cell(eol)} | {_md_cell(alt4g)} | {_md_cell(rep5g)} |" ) if has_specs: spec_lines.append( f"- **{_md_cell(model_name)}**: modem `{_md_cell(modem)}`; WAN/LAN `{_md_cell(wan_lan)}`; " f"RF/antennas `{_md_cell(rf)}`; Wi-Fi `{_md_cell(wifi)}`; battery `{_md_cell(battery)}`; ruggedization `{_md_cell(rugged)}`." ) sources.append( { "id": f"CF{idx}", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": f"lifecycle:{model_name}", "location": "", "excerpt": ( f"{model_name}: status={status}; eos={eos}; eol={eol}; 4g={alt4g}; 5g={rep5g}." if (life or fact) else f"{model_name}: model token parsed from user input; no exact lifecycle row matched." ), "score": 0.99, } ) sources.append( { "id": f"CFD{idx}", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": f"catalog:{model_name}", "location": "", "excerpt": f"{model_name}: modem={modem}; wan_lan={wan_lan}; rf={rf}; wifi={wifi}; battery={battery}; ruggedization={rugged}.", "score": 0.98, } ) if spec_lines: lines.extend( [ "", "Key documented specs by device (WAN/LAN ports, RF/antennas, Wi-Fi, battery, ruggedization):", *spec_lines[:20], ] ) why = [ "Detected a conversational fleet request and parsed quantity/model pairs from natural language.", "Returned lifecycle/replacement rows first, then compact per-device specs for readability.", ] next_action = [ "Add `strict docs only` if you want model-by-model PDF citations next.", "Ask `export as CSV` and I can format this table for handoff.", ] return { "assistant": _format_shell("\n".join(lines), why, next_action), "sources": sources[:12], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": {"domain": "router_lifecycle", "retrieval_mode": "router_conversational_fleet_fast", "web_assisted": False}, } def _router_multi_model_doc_table_fast(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) def _explicit_compare_fields_from_message() -> List[str]: explicit_fields: List[str] = [] for field_name, hints in _ROUTER_FACT_FIELD_ALIASES.items(): if any(_contains_term(low, hint) for hint in hints): explicit_fields.append(field_name) if any(token in low for token in ("antenna-related fields", "antenna related fields")): if "antennas_rf" not in explicit_fields: explicit_fields.append("antennas_rf") if "gnss" not in explicit_fields: explicit_fields.append("gnss") return explicit_fields explicit_compare_fields = _explicit_compare_fields_from_message() if re.search(r"\b(weight|lighter|heavier|weighs?)\b", low): # Weight compares depend on doc-extractive evidence, not the normalized compare table. return None if ("antenna" in low) and any(x in low for x in ("option", "options", "recommend", "recommended", "for each", "for both")): # Let dedicated antenna recommender handle these asks. return None asks_vehicle = any(t in low for t in ("vehicle", "police", "patrol", "public safety")) asks_compare_table = any(t in low for t in ("compare", "comparing", "decision table", "table", "matrix")) asks_5g = bool(re.search(r"\b5[\s\-]?g\b", low)) asks_vehicle_fit = any(t in low for t in ("vehicle fit", "fit", "antenna family", "antenna families")) if asks_vehicle and asks_5g and (asks_compare_table or asks_vehicle_fit): # Vehicle-specific compare prompts need the dedicated conservative fitter below. return None parsed_query = parse_router_intelligence_query(message) extracted_raw_tokens = [str(x).strip() for x in list((parsed_query.device_texts if parsed_query else []) or []) if str(x).strip()] extracted_raw_tokens.extend(str(x).strip() for x in _extract_router_models(message) if str(x).strip()) extracted_raw_tokens = _prune_shadowed_router_device_tokens(extracted_raw_tokens) extracted_model_count = len(extracted_raw_tokens) field_specific_doc_request = ( _contains_any( low, ( "wan/lan", "wan lan", "rf", "connector", "modem", "throughput", "speed", "battery", "wifi", "wi-fi", "antennas", "outdoor", "indoor", "rugged", "environment", "vehicle", "install caveat", "install caveats", "summarize", "documented", "not documented", ), ) or ("documented specs" in low) ) wants_table_or_compare = ("table" in low) or bool( re.search(r"\b(compare|comparison|vs\.?|versus|difference|differences)\b", low) ) if (not wants_table_or_compare) and extracted_model_count >= 2 and field_specific_doc_request and any( token in low for token in ("summarize", "summary", "show the big differences") ): wants_table_or_compare = True wants_field_matrix_without_compare_words = bool( extracted_model_count >= 2 and field_specific_doc_request and any( token in low for token in ( "documented", "docs only", "docs-only", "strict docs", "internal docs", "workbook recommendation logic", "separate internal docs evidence", ) ) ) wants_side_by_side = wants_table_or_compare or wants_field_matrix_without_compare_words asks_specs = field_specific_doc_request or (wants_side_by_side and extracted_model_count >= 2) if not (wants_side_by_side and asks_specs): return None raw_models: List[str] = [] seen_raw_labels: set[str] = set() def _remember_raw_model(raw_value: str) -> None: raw_text = _norm(raw_value) compact = _compact_model(raw_text) if (not compact) or compact.isdigit() or len(compact) < 3: return key = raw_text.lower() if key in seen_raw_labels: return seen_raw_labels.add(key) raw_models.append(raw_text) for raw in extracted_raw_tokens: _remember_raw_model(raw) for raw in _ROUTER_MODEL_TOKEN_RE.findall(message): _remember_raw_model(raw) raw_models = _prune_shadowed_router_device_tokens(raw_models) dedup_models: List[str] = [] seen_canonical: set[str] = set() for m in raw_models: tok = _compact_model(m) if not tok: continue canonical = self._lookup_router_fact_key(tok) or self._lookup_router_lifecycle_key(tok) or tok ctok = _compact_model(canonical) if (not ctok) or (ctok in seen_canonical): continue seen_canonical.add(ctok) dedup_models.append(canonical) requested_compare_labels: List[str] = [] seen_requested_compacts: set[str] = set() for raw in list(_ROUTER_MODEL_TOKEN_RE.findall(message)) + raw_models: label = _norm(raw) compact = _compact_model(label) if (not compact) or compact.isdigit() or len(compact) < 3: continue if compact in seen_requested_compacts: continue seen_requested_compacts.add(compact) requested_compare_labels.append(label) requested_compare_labels = _prune_shadowed_router_device_tokens(requested_compare_labels) wants_doc_matrix = ( ("documented" in low and "not documented" in low) or ("include what is documented" in low) or ("what is documented vs not documented" in low) or ("separate documented facts" in low) or ("separate internal docs evidence" in low and "workbook recommendation logic" in low) or ("clearly documented" in low and "inferred" in low) or ("documented versus inferred" in low) or ("documented vs inferred" in low) or (("documented facts" in low) and ("cautious interpretation" in low)) ) wants_docs_only = wants_doc_matrix or any( h in low for h in ( "from documented specs only", "documented specs only", "from docs only", "docs only", "docs-only", "strict docs only", "strict docs-only", ) ) asks_install_caveats = any( h in low for h in ( "install caveat", "install caveats", "install note", "install notes", "install implication", "install implications", "installation implication", "installation implications", "install impact", "install impacts", ) ) def _docs_table_meaningful_difference_bullets( rows: Sequence[Dict[str, Any]], fields: Sequence[str], field_labels: Dict[str, str], *, include_global_sku_sensitive: bool = True, ) -> List[str]: def _field_breakdown(field: str) -> Tuple[Dict[str, List[str]], List[str], List[str]]: grouped: Dict[str, List[str]] = {} missing_models: List[str] = [] sku_sensitive_models: List[str] = [] for row in rows: model = _norm(row.get("model") or "Unknown model") value = _clean(row.get(field, "")) low_value = value.lower() if low_value in {"", "not documented", "not clearly documented"}: missing_models.append(model) continue if value.startswith("Needs exact SKU/package"): sku_sensitive_models.append(model) continue grouped.setdefault(value, []).append(model) return grouped, missing_models, sku_sensitive_models def _field_is_meaningful(field: str) -> bool: grouped, missing_models, sku_sensitive_models = _field_breakdown(field) documented_count = sum(len(models) for models in grouped.values()) if sku_sensitive_models and len(sku_sensitive_models) == len(rows): return include_global_sku_sensitive if len(grouped) >= 2: return True if field == "lifecycle_status": return len(grouped) >= 2 and documented_count >= 2 if grouped and (missing_models or sku_sensitive_models): return True return False bullets: List[str] = [] def _clean(value: Any) -> str: text = _norm(value) if text.startswith("Documented: "): text = text[len("Documented: ") :] return text for field in fields: if not _field_is_meaningful(field): continue label = _norm(field_labels.get(field, field)) or field grouped, missing_models, sku_sensitive_models = _field_breakdown(field) if not grouped and not sku_sensitive_models: continue if sku_sensitive_models and len(sku_sensitive_models) == len(rows): if not include_global_sku_sensitive: continue bullets.append(f"{label} requires exact SKU/package across all compared models.") elif len(grouped) >= 2: parts: List[str] = [] if sku_sensitive_models: parts.append("some rows are exact-SKU-sensitive") if missing_models: parts.append("some rows are not clearly documented") suffix = f"; {'; '.join(parts)}" if parts else "" bullets.append( f"{label} differs: see the table above for the per-model documented values{suffix}." ) elif len(grouped) == 1 and (sku_sensitive_models or missing_models): parts = [] if sku_sensitive_models: parts.append("some rows are exact-SKU-sensitive") if missing_models: parts.append("some rows are not clearly documented") suffix = f"; {'; '.join(parts)}" if parts else "" bullets.append( f"{label} varies in documentation coverage: see the table above for the per-model documented values{suffix}." ) elif sku_sensitive_models and missing_models: bullets.append( f"{label} requires exact SKU/package for {', '.join(sku_sensitive_models)} and is not clearly documented for {', '.join(missing_models)}." ) elif sku_sensitive_models: bullets.append(f"{label} requires exact SKU/package for {', '.join(sku_sensitive_models)}.") elif missing_models and len(missing_models) != len(rows): bullets.append(f"{label} is not clearly documented for {', '.join(missing_models)}.") if len(bullets) >= 4: break return bullets[:4] if asks_install_caveats: # This helper only runs in the router_docs lane, so install-caveat # compare prompts should still stay source-bounded even when the # user omits an explicit "docs only" phrase. wants_docs_only = True compare_field_labels = { "wan_lan": "WAN/LAN ports", "antennas_rf": "RF connectors", "modem": "Modem/cellular", "wifi": "Wi-Fi", "gnss": "GNSS/GPS", "battery": "Battery", "throughput": "Throughput", "ruggedization": "Ruggedization", "install_caveats": "Install caveats", } requested_compare_fields = [ field for field in explicit_compare_fields if field in { "wan_lan", "antennas_rf", "modem", "wifi", "gnss", "battery", "throughput", "ruggedization", "install_caveats", } ] if not requested_compare_fields: requested_compare_fields = ["wan_lan", "modem", "wifi", "battery", "install_caveats"] elif "install_caveats" not in requested_compare_fields: requested_compare_fields.append("install_caveats") variant_ambiguity_markers = ( "vary by sku", "varies by sku", "depends on package", "depends on package or accessory", "exact modem depends", "package or accessory", "supported modem option", "supported modem options", "supported modem sku", "supported modem skus", "modem options vary by sku", "wwan options vary by sku", "modular wwan", "wwan module", "wwan platform", "modem bundle", "modem bundles", "modem package varies", "single-modem", "dual-modem", "dual sim", "modem-equipped variant", "modem-equipped variants", "no-modem", ) def _has_variant_ambiguity(value: str) -> bool: low_value = value.lower() return any(marker in low_value for marker in variant_ambiguity_markers) def _sanitize_catalog_install_compare_value(field: str, raw_value: Any) -> str: value = _fix_common_mojibake(_norm(raw_value)) if not value: return "Not clearly documented" low_value = value.lower() def _exact_if_truncated(text: str, max_chars: int, *, exact_label: str = "Needs exact SKU/package") -> str: clean_text = _norm(text) if not clean_text: return "Not clearly documented" if "..." in clean_text or "…" in clean_text: return exact_label if _truncate(clean_text, max_chars) != clean_text: return exact_label return clean_text def _has_concrete_wan_lan_detail(text: str) -> bool: low_text = _norm(text).lower() return bool( re.search(r"\b\d+\s*(?:x|\*)\s*(?:10/100/1000|10/100|100mbit/s|100mbps|1gbe|2\.5gbe|gbe|gigabit)\b", low_text) or re.search(r"\b(?:wan|lan)\b[^.;]{0,24}\b\d+\b", low_text) or re.search(r"\b\d+\s*(?:wan|lan)\b", low_text) or re.search(r"\b(?:ethernet|rj45|sfp\+?|network ports?)\b", low_text) ) def _has_concrete_rf_detail(text: str) -> bool: low_text = _norm(text).lower() return bool( re.search(r"\b\d+\s*(?:x|\*)\s*(?:rp-?sma|sma)\b", low_text) or re.search(r"\b(?:external|internal|cellular|wifi|wi-fi)\b[^.;]{0,40}\b(?:rp-?sma|sma|connector|connectors|antenna|antennas)\b", low_text) or re.search(r"\b(?:gps|gnss)\b[^.;]{0,40}\b(?:connector|connectors|antenna|antennas|support)\b", low_text) ) def _has_concrete_modem_detail(text: str) -> bool: low_text = _norm(text).lower() return bool( re.search(r"\b(?:5g(?:\s*nr)?(?:\s*sa)?(?:\s*/\s*nsa)?(?:\s*\+\s*lte(?:\s*cat\s*\d+)?)?|4g(?:\s*lte)?(?:\s*modem)?(?:\s*cat\s*\d+)?|lte\s*advanced(?:\s+pro)?(?:\s*/\s*wwan)?(?:\s*platform)?|lte\s*cat\s*\d+|modular\s+wwan(?:\s+platform)?|variant[-\s]*dependent\s+wwan(?:\s+options?)?|wwan\s+options?\s+vary\s+by\s+sku|lte\s+options?\s+vary\s+by\s+sku|modem\s+options?\s+vary\s+by\s+sku|no[-\s]?modem(?:\s+base\s+hardware)?)\b", low_text) ) def _has_concrete_install_detail(text: str) -> bool: low_text = _norm(text).lower() return bool( any( token in low_text for token in ( "desk", "desktop", "wall", "pole", "din rail", "vehicle", "mount", "mounting", "power adapter", "power supply", "power", "ground", "ventilation", "temperature", "indoor", "outdoor", "poe", "poe budget", "exact kit", "confirm exact kit", "verify exact kit", "exact modem package", "confirm exact modem package", "modular wwan upgrades", "no-modem base hardware", "external antenna kit", "dual sim", "region", "license", "adapter", "hardware", "bundle", "package", "upgrade", "upgrades", "sim", ) ) ) if field == "wan_lan": value = re.sub(r"\s+", " ", value).strip() value = re.sub( r"(?i)^wan:\s*lan\s*/\s*wan\s+switchable:\s*", "LAN/WAN switchable: ", value, ) value = re.sub( r"(?i)^lan:\s*wan\s*/\s*lan\s+switchable:\s*", "LAN/WAN switchable: ", value, ) if re.fullmatch(r"\d+(?:\.\d+)?", value): return "Not clearly documented" if any( token in low_value for token in ( "also included", "included:", "power adapter", "ethernet cable", "mounting hardware", "modem antennas", ) ): return "Not clearly documented" if any( token in low_value for token in ( "volts", "voltage", "nominal", "max.", "max)", "inches", "pounds", "grams", "temperature", "°f", "°c", ) ): return "Not clearly documented" port_match = re.search( r"\bfive ethernet ports?\b|\bdual ethernet ports?\b|\b\d+\s*(?:x|\*)\s*(?:10/100/1000|10/100|100mbit/s|100mbps|1gbe|2\.5gbe|ge)\s*(?:rj45\s*)?(?:ethernet|network)\s*(?:ports?|interfaces?)\b(?:[^;]{0,60}\b(?:wan|lan|vlan|switchable)\b[^;]{0,60})?|\b(?:wan|lan)\b[^;]{0,80}\b(?:ethernet|rj45|port|switchable)\b[^;]{0,80}|\b\d+\s*(?:x|\*)\s*(?:wan|lan)\b[^;]{0,80}", value, flags=re.IGNORECASE, ) if port_match: value = port_match.group(0) low_value = value.lower() has_port_signal = bool( re.search(r"\b(?:ethernet|rj45|sfp\+?|network ports?)\b", low_value) or re.search(r"\b(?:wan|lan)\b.{0,20}\bports?\b", low_value) or re.search(r"\bports?\b.{0,20}\b(?:wan|lan)\b", low_value) or re.search(r"\b\d+\s*x\b", low_value) or re.search(r"\b\d+(?:\.\d+)?\s*(?:gbe?|mbps|gbps)\b.{0,20}\bports?\b", low_value) or re.search(r"\b(single|dual|triple|quad|five)\b.{0,20}\b(?:ethernet|ports?)\b", low_value) ) if not has_port_signal: return "Not clearly documented" clean_value = _exact_if_truncated(re.sub(r"\s+", " ", value).strip(), 140) if clean_value == "Needs exact SKU/package": return clean_value if not any(token in clean_value.lower() for token in ("wan", "lan", "ethernet", "rj45", "port")): return "Not clearly documented" if _has_variant_ambiguity(value) or ("dedicated" in low_value and "convertible" in low_value) or ("switchable" in low_value): if _has_concrete_wan_lan_detail(clean_value): return clean_value return "Needs exact SKU/package; port role varies by variant." return clean_value if field == "antennas_rf": value = re.sub(r"\s+", " ", value).strip() connector_match = re.search( r"(?:\d+\s*x\s*)?(?:rp-?sma|sma)\b[^.;]{0,80}|antenna connectors?[^.;]{0,100}|(?:gps|gnss)\b[^.;]{0,80}|(?:external|internal)\b[^.;]{0,60}\b(?:rp-?sma|sma)\b[^.;]{0,80}", value, flags=re.IGNORECASE, ) if not connector_match: return "Not clearly documented" value = connector_match.group(0) value = re.split(r"[•|]", value, maxsplit=1)[0] value = re.sub(r"\b(?:finger tighten|maximum torque|torque spec|kgf/?cm2?)\b.*", "", value, flags=re.IGNORECASE) value = re.sub(r"\bgps:\s*acti.*", "", value, flags=re.IGNORECASE) value = re.sub(r"^(?:both|external|internal)\s*\([^)]*\)\s*;?\s*", "", value, flags=re.IGNORECASE) value = re.sub(r"\([^)]*(?:depends on|if present|verify [^)]+|see [^)]+ docs)[^)]*\)", "", value, flags=re.IGNORECASE) value = re.sub(r"[.;]\s*Adapter pigtails?:[^.;]*", "", value, flags=re.IGNORECASE) value = re.sub(r"[.;]\s*connectors likely[^.;]*", "", value, flags=re.IGNORECASE) value = re.sub(r"[.;]\s*verify connector type[^.;]*", "", value, flags=re.IGNORECASE) value = re.sub(r"[.;]\s*(SIM|Ethernet):[^.;]*", "", value, flags=re.IGNORECASE) value = re.sub(r"[.;]\s*Power Port:[^.;]*", "", value, flags=re.IGNORECASE) value = re.sub(r"\((?:[^)]*\bcat\s*\d+[^)]*|[^)]*\brel\s*\d+[^)]*)\)", "", value, flags=re.IGNORECASE) value = re.sub(r"\s+", " ", value).strip(" ;.") low_value = value.lower() if (not value) or (not any(token in low_value for token in ("sma", "rp-sma", "connector", "gnss", "gps"))): return "Not clearly documented" if re.fullmatch(r"\d+(?:\.\d+)?", value): return "Not clearly documented" if _has_variant_ambiguity(value) and not _has_concrete_rf_detail(value): return "Needs exact SKU/package; connector path varies by variant." clean_value = _exact_if_truncated(value, 140) if clean_value == "Needs exact SKU/package": return clean_value if re.fullmatch(r"(?i)(?:\d+\s*x\s*)?(?:rp-?sma|sma)", clean_value): return f"{clean_value} connectors" return clean_value if field == "modem": modem_match = re.search( r"\b(?:5g(?:\s*nr)?(?:\s*sa)?(?:\s*/\s*nsa)?(?:\s*\+\s*lte(?:\s*cat\s*\d+)?)?|4g(?:\s*lte)?(?:\s*modem)?(?:\s*cat\s*\d+)?|lte\s*advanced(?:\s+pro)?(?:\s*/\s*wwan)?(?:\s*platform)?|lte\s*cat\s*\d+|modular\s+wwan(?:\s+platform)?|variant[-\s]*dependent\s+wwan(?:\s+options?)?|wwan\s+options?\s+vary\s+by\s+sku|lte\s+options?\s+vary\s+by\s+sku|modem\s+options?\s+vary\s+by\s+sku|no[-\s]?modem(?:\s+base\s+hardware)?)\b[^.;,]{0,80}", value, flags=re.IGNORECASE, ) if not modem_match: return "Not clearly documented" value = modem_match.group(0) low_value = value.lower() if low_value in {"modem", "modems", "cellular", "cellular modem"}: return "Not clearly documented" if any(token in low_value for token in ("antenna", "power adapter", "ethernet cable", "mounting hardware")): return "Not clearly documented" if re.search(r"\b(?:ethernet|wan|lan|cloud management|ports?|wi-?fi|vpn|connectivity|secure)\b", low_value): return "Not clearly documented" clean_value = _exact_if_truncated(value, 120) if clean_value == "Needs exact SKU/package": return clean_value return clean_value if field == "wifi": if any(token in low_value for token in ("none", "no wi-fi", "no wifi", "without wi-fi", "without wifi")): return "None" if ("802.11" not in low_value) and (not re.search(r"\bwi-?fi\s*[4567]\b", low_value)): return "Not clearly documented" return _exact_if_truncated(value, 120) if field == "battery": if low_value == "none": return "None" return _exact_if_truncated(value, 120) if field == "install_caveats": install_specific_power_tokens = ("adapter", "supply", "input", "poe", "dc", "ac", "voltage", "ground", "cord") install_signal_tokens = ( "desk", "desktop", "wall", "pole", "din rail", "vehicle", "mount", "mounting", "power", "ground", "ventilation", "temperature", "indoor", "outdoor", "poe", "kit", "package", "bundle", "hardware", "accessory", "adapter", "upgrade", "upgrades", "sim", "region", "license", ) install_segments = [ segment.strip(" ;.-") for segment in re.split(r"[•|]+|\s+[—-]\s+", value) if segment.strip(" ;.-") ] install_value = "" for segment in install_segments: low_segment = segment.lower() if not any(token in low_segment for token in install_signal_tokens): continue if ("power" in low_segment) and not any(token in low_segment for token in install_specific_power_tokens): continue if any( token in low_segment for token in ("lte advanced", "mbps", "dl/ul", "throughput", "key features wan") ): continue install_value = segment break if not install_value: install_match = re.search( r"\b(?:desk(?:top)?|wall|pole|din rail|vehicle|mount(?:ing)?|power(?: input)?|ground(?:ing)?|ventilation|temperature|indoor|outdoor|poe|kit|package|bundle|hardware|accessory|adapter|upgrade(?:s)?|sim|region|license)\b[^.;•]{0,80}", value, flags=re.IGNORECASE, ) if install_match: install_value = install_match.group(0) low_install_value = install_value.lower() if ("power" in low_install_value) and not any(token in low_install_value for token in install_specific_power_tokens): install_value = "" if install_value: value = install_value value = re.sub(r"\s+", " ", value).strip(" ;.") if value.lower() in {"sim", "dual sim", "license", "region"}: return "Not clearly documented" if _has_variant_ambiguity(value) or any( token in low_value for token in ( "verify exact", "exact sku", "exact modem", "exact package", "branch essentials", "branch advanced", "netcloud", "bundle", "package", "bax-", "lp5", "nm-", "pwm", ) ): if _has_concrete_install_detail(value): return _exact_if_truncated(value, 140) if any( token in value.lower() for token in ( "desk", "desktop", "wall", "pole", "din rail", "vehicle", "mount", "power", "ground", "ventilation", "temperature", "indoor", "outdoor", ) ): return "Needs exact SKU/package; kit/accessory guidance varies by variant." return "Needs exact SKU/package; install/accessory guidance varies across documented family variants." if not _has_concrete_install_detail(value): return "Not clearly documented" return _exact_if_truncated(value, 140) return _exact_if_truncated(value, 170) def _catalog_compare_value(row: Dict[str, Any], field: str) -> str: raw = _norm(row.get(field, "")) if (not raw) and field == "install_caveats": raw = _norm( row.get("special_notes", "") or row.get("special notes", "") or row.get("commercial_details", "") or row.get("talk_track_discovery", "") ) return _sanitize_catalog_install_compare_value(field, raw) def _sanitize_doc_install_compare_value(field: str, raw_value: Any) -> str: raw_text = _fix_common_mojibake(_norm(raw_value)) mapped_field = "antennas_rf" if field == "rf" else field cleaned = _sanitize_catalog_install_compare_value(mapped_field, raw_text) low = cleaned.lower() raw_low = raw_text.lower() def _raw_excerpt(max_chars: int = 170) -> str: text = re.sub(r"\s+", " ", raw_text).strip(" ;.") return _truncate(text, max_chars) if raw_text: if field == "install_caveats" and any( token in raw_low for token in ( "poe budget", "exact modem package", "exact kit", "external antenna kit", "confirm exact kit", "license", "region", "dual sim", "mount", "mounting", "power", "ground", "ventilation", "temperature", "indoor", "outdoor", ) ): return _raw_excerpt() if cleaned == "Not clearly documented": return cleaned if any(token in low for token in ("key features", "table of contents", "copyright", "for more information")): return "Not clearly documented" if field == "rf": if ("sma male" in low or "sma female" in low) and not any( token in low for token in ("connector", "connectors", "antenna", "port", "ports", "gps") ): return "Not clearly documented" if "plug" in low and "connector" not in low and "port" not in low: return "Not clearly documented" if field == "modem": if any( token in low for token in ( "modular wwan", "wwan module", "wwan platform", "modem bundle", "modem bundles", "no-modem", "no modem", "variant-dependent wwan", "modem options vary by sku", "wwan options vary by sku", "supported modem options", "modem package varies", "single-modem", "dual-modem", "dual sim", ) ): return cleaned if any(token in low for token in ("5g", "lte", "4g", "cat ")): return "Needs exact SKU/package; modem bundle varies by SKU." if "mobile broadband modem" in low and not any( token in low for token in ("5g", "lte", "nr", "cat ") ): return "Not clearly documented" if low in {"4g", "5g", "lte", "modem"}: return "Not clearly documented" if field == "install_caveats": if any( token in low for token in ( "poe budget", "exact modem package", "exact kit", "external antenna kit", "confirm exact kit", "license", "region", "dual sim", ) ): return cleaned if low in {"mounting hardware", "desk mounting", "wall mounting", "pole mounting"}: return "Not clearly documented" if any(token in low for token in ("lte:", "dbm", "dl/ul")): return "Not clearly documented" if "wan" in low and "mount" in low and "key features" in low: return "Not clearly documented" return cleaned def _collapse_fast_docs_install_compare_value(field: str, value: str) -> str: if value in {"", "Not clearly documented"}: return "Not clearly documented" low_value = value.lower() if field == "wifi": if low_value == "none": return "None" match = re.search(r"\bwi-?fi\s*([4567])\b", value, flags=re.IGNORECASE) if match: return f"Wi-Fi {match.group(1)}" return value if field == "modem": caveat_tokens = ( "modular wwan", "wwan module", "wwan platform", "modem bundle", "modem bundles", "no-modem", "no modem", "variant-dependent wwan", "modem options vary by sku", "wwan options vary by sku", "supported modem options", "modem package varies", "single-modem", "dual-modem", "dual sim", ) if any(token in low_value for token in caveat_tokens): return value has_5g = bool(re.search(r"\b5g(?:\s*nr)?\b", low_value)) has_lte = bool(re.search(r"\b(?:4g(?:\s*lte)?|lte(?:\s+advanced)?|cat\s*\d+)\b", low_value)) if has_5g and has_lte: joiner = "or" if " or " in low_value else "+" return f"5G {joiner} LTE; exact SKU/package may still affect modem bundle." if has_5g: return "5G; exact SKU/package may still affect modem bundle." if has_lte: return "LTE; exact SKU/package may still affect modem bundle." return value def _catalog_install_compare_table() -> Optional[Dict[str, Any]]: if install_focus and wants_docs_only: # Force the excerpt-backed path for docs-only install compares so # connector and modem wording stays tied to concrete source text. return None doc_rows: List[Dict[str, str]] = [] doc_sources: List[Dict[str, Any]] = [] doc_files: List[str] = [] for idx, model in enumerate(dedup_models[:6], start=1): norm_model = self._normalize_router_model(model) or model fact_key = self._lookup_router_fact_key(norm_model) if not fact_key: return None row = self._router_fact_rows.get(fact_key, {}) if not row: return None model_label = _norm(model) or self._router_display_name(row, fact_key) or norm_model values = { field: _collapse_fast_docs_install_compare_value(field, _catalog_compare_value(row, field)) for field in requested_compare_fields } if not any(value != "Not clearly documented" for value in values.values()): return None src_doc = _norm(row.get("source_doc", "")) or "feb2026routers.csv" doc_rows.append( { "model": model_label, "source_doc": src_doc, **values, } ) doc_sources.append( { "id": f"RDI{idx}", "domain": "router_docs", "doc": src_doc, "relative_path": src_doc, "chunk_id": f"catalog_compare:{_compact_model(model_label) or fact_key}", "location": "", "excerpt": _truncate( "; ".join( [f"{compare_field_labels[field]}={values[field]}" for field in requested_compare_fields] ), 260, ), "score": 0.99, } ) doc_files.append(src_doc) if len(doc_rows) < 2: return None specific_cells = 0 exact_sensitive_cells = 0 for row in doc_rows: for field in requested_compare_fields: value = _norm(row.get(field, "")) if not value or value == "Not clearly documented": continue if value.lower().startswith("needs exact sku/package"): exact_sensitive_cells += 1 else: specific_cells += 1 total_cells = len(doc_rows) * len(requested_compare_fields) if wants_doc_matrix and total_cells and specific_cells < 3 and exact_sensitive_cells >= max(3, total_cells // 2): return None lines = [ "Documented multi-model compare table (internal docs only):", "", "| Model | " + " | ".join(compare_field_labels[field] for field in requested_compare_fields) + " | Evidence |", "| --- | " + " | ".join("---" for _ in requested_compare_fields) + " | --- |", ] for idx, row in enumerate(doc_rows, start=1): values = [row.get(field, "Not clearly documented") for field in requested_compare_fields] evidence = f"[RDI{idx}] {row.get('source_doc', 'feb2026routers.csv')}" lines.append( f"| {_md_cell(row['model'])} | " + " | ".join(_md_cell(str(value or "Not clearly documented")) for value in values) + f" | {_md_cell(evidence)} |" ) summary_bullets = _docs_table_meaningful_difference_bullets( doc_rows, requested_compare_fields, compare_field_labels, ) if summary_bullets: lines.extend(["", "Meaningful documented differences:"]) lines.extend([f"- {bullet}" for bullet in summary_bullets]) return { "assistant": _format_shell( "\n".join(lines), [ "Built from normalized internal router catalog fields for a strict docs-only compare.", "Requested compare fields stay explicit, and missing values remain `Not clearly documented` instead of being inferred.", ], [ "Ask `show quoted excerpts for each row` if you want document-level supporting lines next.", ], ), "sources": doc_sources[:12], "files": list(dict.fromkeys(doc_files))[:10], "meta": {"domain": "router_docs", "retrieval_mode": "router_multi_model_doc_caveat_table_fast", "web_assisted": False}, } catalog_table = _catalog_install_compare_table() if catalog_table is not None: return catalog_table router_idx = getattr(getattr(self, "router_rag_core", None), "index", None) if router_idx is not None and hasattr(router_idx, "search"): def _prune_shadowed_variant_models(models: Sequence[str]) -> List[str]: compacts = [_compact_model(model) for model in models] kept: List[str] = [] for model in models: compact = _compact_model(model) if not compact: continue shadowed = any( other != compact and other.startswith(compact) and other[len(compact) :] in {"5G", "4G", "LTE"} for other in compacts ) if shadowed: continue kept.append(model) return kept or list(models) def _pick_doc_field(text: str, kind: str) -> str: normalized_text = _norm(text) sentences = [x.strip() for x in re.split(r"(?<=[.!?])\s+|\n+", normalized_text) if x.strip()] if kind == "wan_lan": for pat in ( r"\b\d+\s+(?:10/100/1000|10/100|2\.5gbe?|gbe?)\s+ethernet ports?\s*\(wan/lan switchable\)", r"\b\d+\s+(?:10/100/1000|10/100|2\.5gbe?|gbe?)\s+ethernet ports?\b", r"\bwan/lan switchable\b[^.;]{0,120}", ): m = re.search(pat, normalized_text, flags=re.IGNORECASE) if m: return _truncate(_norm(m.group(0)), 170) elif kind == "rf": for pat in ( r"\b(?:external|integrated)\s+[^.;]{0,80}\bantennas?\b", r"\b(?:\d+x\s*)?(?:rp-?sma|sma)\b[^.;]{0,100}", r"\bactive gps\b[^.;]{0,80}", ): m = re.search(pat, normalized_text, flags=re.IGNORECASE) if m: return _truncate(_norm(m.group(0)), 170) elif kind == "modem": for pat in ( r"\bembedded\s+[^.;]{0,80}\bmodem\b", r"\b(?:modular|integrated|internal|external)\s+[^.;]{0,80}\b(?:wwan|modem)\b", r"\b(?:modular\s+wwan|wwan platform|wwan module|modem bundles?|no-modem base hardware|no modem base hardware|variant[- ]dependent wwan|modem options? vary by sku|wwan options? vary by sku|supported modem options?|modem package varies)\b[^.;]{0,120}", r"\b(?:lte options?\s+vary by sku|multiple modem bundles?|single-modem|dual-modem|dual sim)\b[^.;]{0,120}", r"\bdual sim\b[^.;]{0,80}", r"\b(?:modem variant|modem variants|cellular variant|cellular options? vary by sku)\b[^.;]{0,120}", r"\b(?:5g|4g|lte(?:\s+advanced)?)\b[^.;]{0,100}", r"\bcat\s*\d+\b[^.;]{0,80}", ): m = re.search(pat, normalized_text, flags=re.IGNORECASE) if m: return _truncate(_norm(m.group(0)), 170) elif kind == "install_caveats": for pat in ( r"\b(?:desk|desktop|wall|pole|din rail)[^.;]{0,100}\bmount(?:ing)?\b[^.;]{0,80}", r"\b(?:exact\s+kit|exact\s+modem\s+package|poe\s+budget|external\s+antenna\s+kit|confirm\s+exact\s+kit|license|region|dual\s+sim)\b[^.;]{0,140}", r"\bmount(?:ing)?\b[^.;]{0,140}", r"\bpower\b[^.;]{0,140}", r"\bground(?:ing)?\b[^.;]{0,140}", r"\bventilation\b[^.;]{0,140}", r"\btemperature\b[^.;]{0,140}", r"\blocation considerations\b[^.;]{0,140}", ): m = re.search(pat, normalized_text, flags=re.IGNORECASE) if m: return _truncate(_norm(m.group(0)), 170) elif kind == "wifi": for pat in ( r"\bwi-?fi\s*[4567](?:\s*\(802\.11[a-z0-9/ .-]+\))?", r"\b802\.11[a-z0-9/ .-]{1,40}", ): m = re.search(pat, normalized_text, flags=re.IGNORECASE) if m: return _truncate(_norm(m.group(0)), 170) for sentence in sentences: low_sentence = sentence.lower() if kind == "wan_lan": if any(k in low_sentence for k in ("wan", "lan", "ethernet", "rj45", "switchable")): return _truncate(sentence, 170) elif kind == "rf": if any(k in low_sentence for k in ("connector", "connectors", "antenna port", "antenna ports", "sma", "rp-sma", "gps")): return _truncate(sentence, 170) elif kind == "modem": if any(k in low_sentence for k in ("modem", "lte", "5g", "4g", "cat ", "dual sim")): return _truncate(sentence, 170) elif kind == "install_caveats": if any( k in low_sentence for k in ( "install", "installation", "mount", "mounting", "desktop", "wall", "pole", "power", "ground", "location", "ventilation", "ambient", "temperature", "cable", "antenna separation", ) ) and not any( bad in low_sentence for bad in ("copyright", "for more information", "table of contents", "safety information") ): return _truncate(sentence, 170) elif kind == "wifi": if ("802.11" in low_sentence) or bool(re.search(r"\bwi-?fi\s*[4567]\b", low_sentence)): return _truncate(sentence, 170) return "Not clearly documented" doc_rows: List[Dict[str, str]] = [] doc_sources: List[Dict[str, Any]] = [] doc_files: List[str] = [] install_compare_fields = [ field for field in requested_compare_fields if field in {"wan_lan", "rf", "wifi", "modem", "install_caveats"} ] if not install_compare_fields: install_compare_fields = ["wan_lan", "rf", "modem", "install_caveats"] for idx, model in enumerate(_prune_shadowed_variant_models(dedup_models[:6]), start=1): norm_model = self._normalize_router_model(model) or model model_compact = _compact_model(norm_model) if not model_compact: continue root_match = re.match(r"[A-Z]{1,6}\d{2,4}", model_compact) model_root = _compact_model(root_match.group(0)) if root_match else model_compact hits = list( router_idx.search( f"{model_root} {norm_model} WAN LAN Ethernet ports RF connectors modem installation mounting quick start manual data sheet", top_k=12, model_filters=[model_root], doc_type_filters=["data sheet", "manual", "quick start"], source_group_filters=["routers", "datasets"], ) or [] ) if not hits: hits = list( router_idx.search( f"{model_root} {norm_model} WAN LAN Ethernet ports RF connectors modem installation mounting quick start manual data sheet", top_k=12, doc_type_filters=["data sheet", "manual", "quick start"], source_group_filters=["routers", "datasets"], ) or [] ) values = { "wan_lan": "Not clearly documented", "rf": "Not clearly documented", "wifi": "Not clearly documented", "modem": "Not clearly documented", "install_caveats": "Not clearly documented", } best_doc = "" best_rel = "" best_chunk = "" best_score = 0.0 for hit in hits: chunk = getattr(hit, "chunk", None) if chunk is None: continue doc_name = str(getattr(chunk, "file_name", "") or "") rel = str(getattr(chunk, "relative_path", "") or "") text = _norm(str(getattr(chunk, "text", "") or "")) meta_blob = _compact_model(f"{doc_name} {rel} {getattr(chunk, 'model_family', '')}") if (not doc_name) or (not text): continue if (model_compact not in meta_blob) and (model_root not in meta_blob): continue found = 0 for kind in tuple(values.keys()): if values[kind] != "Not clearly documented": continue candidate = _pick_doc_field(text, kind) if candidate != "Not clearly documented": values[kind] = _sanitize_doc_install_compare_value(kind, candidate) found += 1 if (not best_doc) and found > 0: best_doc = doc_name best_rel = rel best_chunk = str(getattr(chunk, "chunk_id", "") or f"install_docs:{model_compact}:{idx}") best_score = float(getattr(hit, "score", 0.0) or 0.0) fallback_fields: List[str] = [] if not wants_docs_only: for kind in tuple(values.keys()): if values[kind] != "Not clearly documented": continue catalog_value = _collapse_fast_docs_install_compare_value( kind, _catalog_compare_value(row, kind), ) if catalog_value == "Not clearly documented": continue values[kind] = catalog_value fallback_fields.append(kind) found_values = [v for v in values.values() if v != "Not clearly documented"] if not found_values: continue doc_rows.append( { "model": norm_model, "wan_lan": values["wan_lan"], "rf": values["rf"], "wifi": values["wifi"], "modem": values["modem"], "install_caveats": values["install_caveats"], "catalog_fallback_fields": fallback_fields, "evidence": f"[RDI{idx}] {best_doc or 'Internal router docs'}", } ) href = _mounted_file_href("/router_rag_files", best_rel) if best_rel else "" if href: doc_files.append(href) doc_sources.append( { "id": f"RDI{idx}", "domain": "router_docs", "doc": best_doc or f"{norm_model} docs", "relative_path": href, "chunk_id": best_chunk or f"install_docs:{model_compact}:{idx}", "location": "", "excerpt": _truncate( f"{norm_model}: WAN/LAN={values['wan_lan']}; RF={values['rf']}; " f"Wi-Fi={values['wifi']}; Modem={values['modem']}; Install caveats={values['install_caveats']}.", 260, ), "score": best_score, } ) if len(doc_rows) >= 2: install_field_labels = { "wan_lan": "WAN/LAN ports", "rf": "RF connectors", "wifi": "Wi-Fi", "modem": "Modem/cellular", "install_caveats": "Install caveats", } lines = [ "Documented multi-model install/spec table (internal docs only):", "", "| Model | " + " | ".join(install_field_labels[field] for field in install_compare_fields) + " | Evidence |", "| --- | " + " | ".join("---" for _ in install_compare_fields) + " | --- |", ] for row in doc_rows: values = [row.get(field, "Not clearly documented") for field in install_compare_fields] lines.append( f"| {_md_cell(row['model'])} | " + " | ".join(_md_cell(value) for value in values) + f" | {_md_cell(row['evidence'])} |" ) summary_bullets = _docs_table_meaningful_difference_bullets( doc_rows, install_compare_fields, install_field_labels, ) if summary_bullets: lines.extend(["", "Meaningful documented differences:"]) lines.extend([f"- {bullet}" for bullet in summary_bullets]) evidence_notes: List[str] = [] for row in doc_rows[:4]: informative_fields = [ install_field_labels.get(field, field) for field in install_compare_fields if str(row.get(field, "")).strip() and str(row.get(field, "")).strip() != "Not clearly documented" ] fallback_fields = [ install_field_labels.get(field, field) for field in list(row.get("catalog_fallback_fields") or []) if field in install_compare_fields ] if informative_fields: fallback_note = "" if fallback_fields: fallback_note = ( f"; {', '.join(fallback_fields[:3])} came from the normalized router catalog " "because the retrieved doc excerpt was incomplete." ) evidence_notes.append( f"{row['model']}: {', '.join(informative_fields[:3])} came from the retrieved excerpts{fallback_note}; remaining fields stayed abstained because no clear excerpt surfaced." ) else: evidence_notes.append( f"{row['model']}: the retrieved excerpts did not surface a clear field-level match for the requested compare slots." ) if evidence_notes: lines.extend(["", "Evidence notes:"]) lines.extend([f"- {note}" for note in evidence_notes]) return { "assistant": _format_shell( "\n".join(lines), [ "Built from model-matched internal datasheet/manual/quick-start excerpts for install-caveat compares.", "Fields without clear excerpt support remain `Not clearly documented` instead of being inferred.", "Modem rows that look variant-sensitive stay caveated instead of being promoted to exact bundle claims.", ], [ "Ask `show quoted excerpts for each row` if you want the exact source lines next.", ], ), "sources": doc_sources[:12], "files": list(dict.fromkeys(doc_files))[:10], "meta": {"domain": "router_docs", "retrieval_mode": "router_multi_model_doc_caveat_table_fast", "web_assisted": False}, } return None deterministic_doc_matrix_supported = bool( wants_doc_matrix and len(dedup_models) >= 2 and all(self._lookup_router_fact_key(m) or self._lookup_router_lifecycle_key(m) for m in dedup_models) ) def _strict_docs_only_alias_wifi_override(requested_label: str, selected_key: str) -> str: requested_compact = _compact_model(requested_label) selected_compact = _compact_model(selected_key) if (not requested_compact) or (not selected_compact) or (requested_compact == selected_compact): return "" if len(requested_compact) < 4 or len(selected_compact) < 4: return "" if requested_compact[:-2] == selected_compact[:-2] and requested_compact.endswith("50") and selected_compact.endswith("00"): return "None / non-Wi-Fi variant (alias guidance)" return "" def _strict_docs_only_family_variant_guard(requested_label: str, selected_key: str) -> bool: requested_compact = _compact_model(requested_label) selected_compact = _compact_model(selected_key) if (not requested_compact) or (not selected_compact) or (requested_compact == selected_compact): return False requested_root = re.match(r"[A-Z]{1,8}\d{2,4}", requested_compact) selected_root = re.match(r"[A-Z]{1,8}\d{2,4}", selected_compact) if requested_root and selected_root and requested_root.group(0) == selected_root.group(0): return True if len(requested_compact) >= 4 and len(selected_compact) >= 4 and requested_compact[:-2] == selected_compact[:-2]: return True return False def _strict_docs_only_row_quality(row: Dict[str, Any]) -> int: score = 0 for field_name in ("wan_lan", "antennas_rf", "wifi", "modem", "throughput"): value = _norm(row.get(field_name, "")) if not value: continue low_value = value.lower() if any(token in low_value for token in ("not listed", "unknown", "(blank)")): continue score += 1 return score def _strict_docs_only_compare_value(field_name: str, raw_value: Any, *, requested_label: str, selected_key: str) -> str: alias_wifi_override = _strict_docs_only_alias_wifi_override(requested_label, selected_key) family_variant_guard = _strict_docs_only_family_variant_guard(requested_label, selected_key) requested_compact = _compact_model(requested_label) br1_family_guard = requested_compact in {"BR1MINI5G", "MAXBR1PRO5G", "MAXBR1PROLTEA"} if field_name == "wifi" and alias_wifi_override: return alias_wifi_override value = _fix_common_mojibake(_norm(raw_value)) if not value: return "Not clearly documented" low_value = value.lower() def _exact_if_truncated(text: str, max_chars: int, *, exact_label: str = "Needs exact SKU/package") -> str: clean_text = _norm(text) if not clean_text: return "Not clearly documented" if "..." in clean_text or "…" in clean_text: return exact_label if _truncate(clean_text, max_chars) != clean_text: return exact_label return clean_text variant_ambiguity_markers = ( "variant", "variants", "exact sku", "exact package", "exact modem", "modem option", "modem options", "sku/package", "sku package", "depends on sku", "depends on package", "see model-specific docs", "see exact sku docs", "supported modem sku", "supported modem skus", "modem-equipped variant", "modem-equipped variants", "no-modem", ) def _has_variant_ambiguity_local(text: str) -> bool: local_low = text.lower() return any(marker in local_low for marker in variant_ambiguity_markers) if any(token in low_value for token in ("not listed", "abstained", "unknown", "csv conflict", "(blank)")): return "Not clearly documented" if field_name == "wan_lan": value = re.sub(r"\s+", " ", value).strip() value = re.sub(r"(?i)\b(?:single|dual)\s+5g:\s*", "", value).strip() value = re.sub( r"(?i)^wan:\s*lan\s*/\s*wan\s+switchable:\s*", "LAN/WAN switchable: ", value, ) value = re.sub( r"(?i)^lan:\s*wan\s*/\s*lan\s+switchable:\s*", "LAN/WAN switchable: ", value, ) if re.fullmatch(r"\d+(?:\.\d+)?", value): return f"{value} total Ethernet ports" port_match = re.search( r"\bfive ethernet ports?\b|\bdual ethernet ports?\b|\b\d+\s*(?:x|\*)\s*(?:10/100/1000|10/100|100mbit/s|100mbps|1gbe|2\.5gbe|ge)\s*(?:rj45\s*)?(?:ethernet|network)\s*(?:ports?|interfaces?)\b(?:[^;]{0,60}\b(?:wan|lan|vlan|switchable)\b[^;]{0,60})?|\b(?:wan|lan)\b[^;]{0,80}\b(?:ethernet|rj45|port|switchable)\b[^;]{0,80}|\b\d+\s*(?:x|\*)\s*(?:wan|lan)\b[^;]{0,80}", value, flags=re.IGNORECASE, ) if port_match: value = port_match.group(0) low_value = value.lower() has_port_signal = bool( re.search(r"\b(?:wan|lan|ethernet|rj45|sfp\+?)\b", low_value) or re.search(r"\b\d+\s*x\b", low_value) or re.search(r"\b\d+(?:\.\d+)?\s*(?:gbe?|mbps|gbps)\b", low_value) or re.search(r"\b(single|dual|triple|quad|five)\b", low_value) ) if not has_port_signal: return "Not clearly documented" clean_value = _exact_if_truncated(re.sub(r"\s+", " ", value).strip(), 140) if clean_value == "Needs exact SKU/package": return clean_value if not any(token in clean_value.lower() for token in ("wan", "lan", "ethernet", "rj45", "port")): return "Not clearly documented" variant_sensitive = ( family_variant_guard or _has_variant_ambiguity_local(value) or ("dedicated" in low_value and "convertible" in low_value) or ("switchable" in low_value) or ( len(clean_value) > 100 and bool(re.search(r"\b(?:variant|option|sku|package|modem)\b", low_value)) ) ) if br1_family_guard: variant_sensitive = True if variant_sensitive: return "Needs exact SKU/package; port role varies by variant." return clean_value if field_name == "antennas_rf": if "rf:" in value.lower(): value = value.split("RF:", 1)[-1] value = re.sub(r"\s+", " ", value).strip() value = re.sub(r"(?i)\boften needed\)?", "", value).strip(" ;,.)") connector_match = re.search( r"(?:\d+\s*x\s*)?(?:rp-?sma|sma)\b[^.;]{0,80}" r"|antenna(?:s| connectors?)?[^.;]{0,120}" r"|(?:external|internal|dipole|omni|panel|directional)\b[^.;]{0,60}\b(?:antenna|antennas|connector(?:s)?|antenna kit)\b[^.;]{0,120}" r"|(?:gps|gnss)\b[^.;]{0,80}(?:\s+(?:support|module|antenna|connector))?" r"|antenna kit[^.;]{0,120}", value, flags=re.IGNORECASE, ) if not connector_match: return "Not clearly documented" value = connector_match.group(0) value = re.sub(r"^(?:both|external|internal)\s*\([^)]*\)\s*;?\s*", "", value, flags=re.IGNORECASE) value = re.sub(r"\([^)]*(?:treat as|depends on|see [^)]+ docs|check [^)]+|if present)[^)]*\)", "", value, flags=re.IGNORECASE) value = re.sub(r"[.;]\s*Adapter pigtails?:[^.;]*", "", value, flags=re.IGNORECASE) value = re.sub(r"[.;]\s*connectors likely[^.;]*", "", value, flags=re.IGNORECASE) value = re.sub(r"[.;]\s*verify connector type[^.;]*", "", value, flags=re.IGNORECASE) value = re.sub(r"[.;]\s*(SIM|Ethernet):[^.;]*", "", value, flags=re.IGNORECASE) value = re.sub(r"\((?:[^)]*\bcat\s*\d+[^)]*|[^)]*\brel\s*\d+[^)]*)\)", "", value, flags=re.IGNORECASE) value = re.sub(r"\s+", " ", value).strip(" ;.") low_value = value.lower() if (not value) or (not any(token in low_value for token in ("sma", "rp-sma", "connector", "gnss", "gps", "antenna"))): return "Not clearly documented" if re.fullmatch(r"\d+(?:\.\d+)?", value): return "Not clearly documented" if br1_family_guard or _has_variant_ambiguity_local(value) or ("often needed" in low_value): return "Needs exact SKU/package; connector path varies by variant." clean_value = _exact_if_truncated(value, 140) if clean_value == "Needs exact SKU/package": return clean_value if re.fullmatch(r"(?i)(?:\d+\s*x\s*)?(?:rp-?sma|sma)", clean_value): return f"{clean_value} connectors" return clean_value if field_name == "wifi": if "not clearly stated" in low_value: value = re.split(r";\s*exact\b", value, maxsplit=1, flags=re.IGNORECASE)[0] low_value = value.lower() if any(token in low_value for token in ("none", "no wi-fi", "no wifi", "without wi-fi", "without wifi")): return "None" if ("802.11" not in low_value) and (not re.search(r"\bwi-?fi\s*[4567]\b", low_value)): return "Not clearly documented" return _exact_if_truncated(value, 120) if field_name == "modem": if any( token in low_value for token in ( "no-modem", "supported modem options", "supported modem sku", "bundle", "bundles", "modular wwan", "depends on package", "modem package", ) ): return "Exact SKU/package required" modem_match = re.search( r"\b(?:" r"5g(?:\s*nr)?(?:\s*(?:sa|standalone))?(?:\s*/\s*nsa)?(?:\s*\+\s*(?:4g(?:\s*lte)?|lte)\s*(?:cat\s*\d+)?)?" r"|4g(?:\s*lte)?(?:\s*cat\s*\d+)?" r"|lte\s*advanced(?:\s+pro)?" r"|lte-advanced" r"|lte\s*cat\s*\d+" r"|modular\s+wwan(?:\s+platform)?" r"|wwan\s+platform" r"|wwan\s+module" r"|modem\s+options?\s+vary\s+by\s+sku" r"|modem\s+bundles?" r"|no[-\s]?modem(?:\s+base\s+hardware)?" r"|variant[-\s]*dependent\s+wwan(?:\s+options?)?" r"|wwan\s+options?\s+vary\s+by\s+sku" r"|supported modem options?" r"|exact modem depends on package or accessory" r"|dual[-\s]?modem" r"|single[-\s]?modem" r"|dual\s+sim" r")\b[^.;,]{0,120}", value, flags=re.IGNORECASE, ) if not modem_match: return "Not clearly documented" value = modem_match.group(0) low_value = value.lower() if low_value in {"modem", "modems", "cellular", "cellular modem"}: return "Not clearly documented" if re.search(r"\b(?:ethernet|wan|lan|cloud management|ports?|wi-?fi|vpn|connectivity|secure)\b", low_value): return "Not clearly documented" clean_value = _exact_if_truncated(value, 120) if clean_value == "Needs exact SKU/package": return clean_value return clean_value if field_name == "throughput": if not re.search(r"\b\d+(?:\.\d+)?\s*(mbps|gbps)\b", low_value): return "Not clearly documented" if family_variant_guard or _has_variant_ambiguity_local(value): return "Needs exact SKU/package; throughput varies by SKU." return _exact_if_truncated(value, 120) if field_name == "gnss": if any(token in low_value for token in ("none", "no gps", "no gnss", "without gps", "without gnss")): return "None" if _has_variant_ambiguity_local(value): return "Not clearly documented for an exact SKU/package." if not any(token in low_value for token in ("gps", "gnss")): return "Not clearly documented" return _exact_if_truncated(value, 120) if field_name == "install_caveats": install_low = value.lower() install_match = re.search( r"\b(?:power(?: input)?|mount(?:ing)?|ground(?:ing)?|temperature|ventilation|indoor|outdoor)\b[^.;]{0,100}", value, flags=re.IGNORECASE, ) if install_match: snippet = _norm(install_match.group(0)) if len(snippet.split()) <= 1: return "Not clearly documented" if _has_variant_ambiguity_local(value): return "Needs exact SKU/package; kit/accessory guidance varies by variant." return _exact_if_truncated(snippet, 140) if not any( token in install_low for token in ( "mount", "power", "ground", "temperature", "ventilation", "indoor", "outdoor", ) ): return "Not clearly documented" if _has_variant_ambiguity_local(value): return "Needs exact SKU/package; kit/accessory guidance varies by variant." clean_value = _exact_if_truncated(re.sub(r"\s+", " ", value).strip(), 140) if clean_value == "Needs exact SKU/package": return clean_value return clean_value if field_name == "battery": if low_value == "none": return "None" return _exact_if_truncated(value, 120) if field_name == "ruggedization": rugged_match = re.search( r"\b(?:indoor|outdoor|ruggedized?|semi-rugged|ip\d{2,3}|mil-std-[a-z0-9-]+|class i div 2|-?\d+°c to \d+°c operating)\b[^;,.]{0,50}", value, flags=re.IGNORECASE, ) if rugged_match: value = _norm(rugged_match.group(0)) value = re.sub(r"[;,.]\s*redundant internal fans.*", "", value, flags=re.IGNORECASE) return _exact_if_truncated(value, 120) return _exact_if_truncated(value, 120) def _strict_docs_only_candidate_matches(requested_compact: str, key: str, row: Dict[str, Any]) -> bool: if self._router_row_looks_service_like(row): return False source_doc = Path(str(row.get("source_doc") or "")).name.lower() if source_doc != "feb2026routers.csv": return False candidate_tokens = { _compact_model(key), _compact_model(str(row.get("model") or "")), _compact_model(str(row.get("sku") or "")), _compact_model(str(row.get("title") or "")), _compact_model(self._router_display_name(row, key) or ""), } candidate_tokens = {token for token in candidate_tokens if token} if requested_compact in candidate_tokens: return True for token in candidate_tokens: if not token.startswith(requested_compact): continue suffix = token[len(requested_compact) :] if suffix and (not suffix.isdigit()): return True return False def _strict_docs_only_router_idx_search(query: str) -> List[Any]: router_idx = getattr(getattr(self, "router_rag_core", None), "index", None) if router_idx is None or not hasattr(router_idx, "search"): return [] for kwargs in ( { "top_k": 10, "doc_type_filters": ["data sheet", "manual", "quick start"], "source_group_filters": ["routers", "datasets"], }, {"k": 10}, {}, ): try: rows = router_idx.search(query, **kwargs) return list(rows or []) except TypeError: continue except Exception: return [] try: return list(router_idx.search(query, 10) or []) except Exception: return [] def _strict_docs_only_hit_blob(hit: Any) -> Tuple[str, str, str, str, str, float]: chunk = getattr(hit, "chunk", None) if chunk is not None: doc_name = str(getattr(chunk, "file_name", "") or getattr(chunk, "doc", "") or "") rel = str(getattr(chunk, "relative_path", "") or "") text = _norm(str(getattr(chunk, "text", "") or getattr(chunk, "excerpt", "") or "")) family = _norm(str(getattr(chunk, "model_family", "") or "")) chunk_id = str(getattr(chunk, "chunk_id", "") or "") score = float(getattr(hit, "score", 0.0) or 0.0) return doc_name, rel, text, family, chunk_id, score if isinstance(hit, dict): doc_name = str(hit.get("doc") or hit.get("file_name") or "") rel = str(hit.get("relative_path") or "") text = _norm(str(hit.get("text") or hit.get("excerpt") or "")) family = _norm(str(hit.get("model_family") or "")) chunk_id = str(hit.get("chunk_id") or "") score = float(hit.get("score") or 0.0) return doc_name, rel, text, family, chunk_id, score return "", "", "", "", "", 0.0 def _strict_docs_only_pick_doc_value(text: str, kind: str) -> str: normalized = _fix_common_mojibake(_norm(text)) if not normalized: return "Not clearly documented" patterns: Tuple[str, ...] if kind == "wan_lan": patterns = ( r"\b\d+\s*(?:x|\*)\s*(?:10/100/1000|10/100|100mbit/s|100mbps|1gbe|2\.5gbe|ge)\s*(?:rj45\s*)?(?:ethernet|network)\s*(?:ports?|interfaces?)\b(?:[^.;]{0,60}\b(?:wan|lan|vlan|switchable)\b[^.;]{0,60})?", r"\bfive ethernet ports?\b", r"\bwan/?lan\b[^.;]{0,120}", r"\b(?:wan|lan)\b[^.;]{0,40}\b(?:ethernet|rj45|port|switchable)\b[^.;]{0,120}", r"\b\d+\s*(?:x|\*)\s*(?:wan|lan)\b[^.;]{0,120}", ) elif kind == "antennas_rf": patterns = ( r"\b\d+\s*x\s*(?:sma|rp-?sma)\b[^.;]{0,100}", r"\bantenna(?:s| connector(?:s)?| kit)?[^.;]{0,120}", r"\b(?:external|internal|dipole|omni|panel|directional)\b[^.;]{0,60}\b(?:antenna|antennas|connector(?:s)?|antenna kit)\b[^.;]{0,120}", r"\b(?:sma|rp-?sma)\b[^.;]{0,80}(?:connector|connectors|antenna)", r"\b(?:external|internal)\b[^.;]{0,60}\b(?:sma|rp-?sma)\b[^.;]{0,80}", r"\b(?:gps|gnss)\b[^.;]{0,80}(?:\s+(?:support|antenna|module|connector))?", ) elif kind == "wifi": patterns = ( r"\bwi-?fi\s*[4567](?:\s*\(802\.11[a-z0-9/ .-]+\))?", r"\b802\.11[a-z0-9/ .-]{1,40}", ) elif kind == "modem": patterns = ( r"\b(?:modular\s+wwan|wwan platform|wwan module|modem bundles?|no-modem base hardware|no modem base hardware|variant[- ]dependent wwan|modem options? vary by sku|wwan options? vary by sku|supported modem options?|modem package varies|single-modem|dual-modem|dual sim)\b[^.;]{0,120}", r"\b(?:5g(?:\s*nr)?(?:\s*(?:sa|standalone))?(?:\s*/\s*nsa)?(?:\s*\+\s*(?:4g(?:\s*lte)?|lte)\s*(?:cat\s*\d+)?)?|4g(?:\s*lte)?(?:\s*cat\s*\d+)?|lte\s*advanced|lte-advanced|lte\s*cat\s*\d+)\b[^.;]{0,120}", r"\blte\s*cat\s*\d+\b[^.;]{0,80}", ) else: patterns = ( r"\b\d+(?:\.\d+)?\s*(?:mbps|gbps)\b[^.;]{0,60}", ) for pattern in patterns: match = re.search(pattern, normalized, flags=re.IGNORECASE) if match: return _truncate(_norm(match.group(0)), 170) sentences = [part.strip() for part in re.split(r"(?<=[.!?])\s+|\n+", normalized) if part.strip()] keyword_map = { "wan_lan": ("wan", "lan", "ethernet", "rj45", "network port"), "antennas_rf": ("antenna", "connector", "sma", "rp-sma", "gps", "gnss"), "wifi": ("wi-fi", "wifi", "802.11"), "modem": ("5g", "4g", "lte", "cat "), "throughput": ("mbps", "gbps", "throughput"), } for sentence in sentences: low_sentence = sentence.lower() if kind == "wan_lan": if any(token in low_sentence for token in ("wan", "lan", "ethernet", "rj45", "network port")) and any( token in low_sentence for token in ("port", "ports", "wan/lan", "vlan", "switchable") ): if re.fullmatch(r"\d+(?:\.\d+)?", sentence.strip()): continue return _truncate(sentence, 170) elif kind == "antennas_rf": if any(token in low_sentence for token in ("sma", "rp-sma", "connector", "connectors", "gps", "gnss", "antenna")): if re.fullmatch(r"\d+(?:\.\d+)?", sentence.strip()): continue return _truncate(sentence, 170) elif kind == "wifi": if ("802.11" in low_sentence) or bool(re.search(r"\bwi-?fi\s*[4567]\b", low_sentence)): return _truncate(sentence, 170) elif kind == "modem": if any(token in low_sentence for token in ("5g", "4g", "lte", "cat ", "lte advanced")) and not re.search( r"\b(?:ethernet|wan|lan|cloud management|five ethernet)\b", low_sentence, ): return _truncate(sentence, 170) elif kind == "throughput" and any(token in low_sentence for token in ("mbps", "gbps", "throughput")): return _truncate(sentence, 170) return "Not clearly documented" def _strict_docs_only_doc_seed_entry(requested_label: str) -> Optional[Dict[str, Any]]: requested = _norm(requested_label) requested_compact = _compact_model(requested) if not requested_compact: return None hits: List[Any] = [] search_queries = [ f"{requested} data sheet manual quick start WAN LAN Ethernet ports antenna connector Wi-Fi modem throughput", f"{requested_compact} data sheet manual quick start WAN LAN Ethernet ports antenna connector Wi-Fi modem throughput", ] for search_query in search_queries: hits.extend(_strict_docs_only_router_idx_search(search_query)) if hits: break if not hits: return None values = { "wan_lan": "Not clearly documented", "antennas_rf": "Not clearly documented", "wifi": "Not clearly documented", "modem": "Not clearly documented", "throughput": "Not clearly documented", } best_doc = "" best_rel = "" best_chunk_id = "" best_score = 0.0 corpus: List[str] = [] for hit in hits: doc_name, rel, text, family, chunk_id, score = _strict_docs_only_hit_blob(hit) meta_blob = f"{doc_name} {rel} {family}".lower() meta_compact = _compact_model(meta_blob) text_compact = _compact_model(text[:1600]) if not text: continue if requested_compact not in meta_compact: if any(token in meta_blob for token in ("portfolio", "catalog", "overview", "matrix", "upload", "pricing")): continue if requested_compact not in text_compact: continue corpus.append(text) found_here = 0 for field_name in tuple(values.keys()): if values[field_name] != "Not clearly documented": continue candidate = _strict_docs_only_pick_doc_value(text, field_name) if candidate != "Not clearly documented": values[field_name] = candidate found_here += 1 if (not best_doc) and found_here > 0: best_doc = doc_name or requested best_rel = rel best_chunk_id = chunk_id or f"docs_seed:{requested_compact}" best_score = score corpus_blob = " ".join(corpus).lower() if values["wan_lan"] == "Not clearly documented": wan_lan_match = re.search( r"\b(\d+)\s*(?:x\s*)?(?:fast\s+)?(?:ethernet|network)\s*ports?\b", corpus_blob, flags=re.IGNORECASE, ) if wan_lan_match: suffix = " (WAN/LAN switchable)" if "wan/lan" in corpus_blob and "switchable" in corpus_blob else "" values["wan_lan"] = f"{wan_lan_match.group(1)} total Ethernet ports{suffix}" elif "five ethernet ports" in corpus_blob: values["wan_lan"] = "Five Ethernet Ports" if values["antennas_rf"] == "Not clearly documented": rf_match = re.search(r"\b(\d+)\s*x\s*(rp-?sma|sma)\b", corpus_blob) if rf_match: connector = "RP-SMA" if "rp-sma" in rf_match.group(2).lower() else "SMA" values["antennas_rf"] = f"{rf_match.group(1)}x {connector} connectors" if values["wifi"] == "Not clearly documented": if "wi-fi 5" in corpus_blob or "wifi 5" in corpus_blob or "802.11ac" in corpus_blob: values["wifi"] = "Wi-Fi 5 (802.11ac)" elif "wi-fi 4" in corpus_blob or "wifi 4" in corpus_blob or "802.11 b" in corpus_blob: values["wifi"] = "Wi-Fi 4 (802.11 b / g / n)" if values["modem"] == "Not clearly documented": if "5g" in corpus_blob and "4g" in corpus_blob: values["modem"] = "5G / 4G" elif "4g" in corpus_blob or "lte" in corpus_blob: values["modem"] = "4G LTE" documented_count = sum(1 for value in values.values() if value != "Not clearly documented") if documented_count == 0: return None row = { "model": requested, "source_doc": best_doc or "Internal router docs", "relative_path": best_rel, "chunk_id": best_chunk_id, "chunk_score": best_score, **values, } return {"label": requested, "key": requested_compact, "row": row} def _strict_docs_only_lifecycle_seed_entry(requested_label: str) -> Optional[Dict[str, Any]]: requested = _norm(requested_label) requested_compact = _compact_model(requested) if not requested_compact: return None life_key = self._lookup_router_lifecycle_key(requested) or self._lookup_router_lifecycle_key(requested_compact) if not life_key: return None life = self._router_lifecycle_rows.get(life_key, {}) if not life: return None tech = _norm(life.get("tech", "")) row = { "model": _norm(life.get("model", "")) or requested, "source_doc": "routers_eos_eol_by_sku.csv", "wan_lan": "", "antennas_rf": "", "wifi": "", "modem": tech, "throughput": "", "battery": "", "ruggedization": "", "_coverage_note": ( f"Only the lifecycle seed matched `{requested}` in the current internal CSV rows, " "so WAN/LAN, RF, and Wi-Fi stay unconfirmed in this strict docs-only compare." ), } if not row["modem"]: return None return {"label": requested, "key": _compact_model(life_key) or requested_compact, "row": row} def _strict_docs_only_compare_entry(requested_label: str) -> Optional[Dict[str, Any]]: requested = _norm(requested_label) requested_compact = _compact_model(requested) if not requested_compact: return None exact_key = requested_compact if requested_compact in self._router_fact_rows else "" lookup_key = self._lookup_router_fact_key(requested) or self._lookup_router_fact_key(requested_compact) alias_key = self._router_alias_map.get(requested_compact, "") family_alias_key = "" if requested_compact.endswith("50"): guessed_family = requested_compact[:-2] + "00" if guessed_family in self._router_fact_rows: family_alias_key = guessed_family candidate_keys: List[str] = [] for key in (exact_key, lookup_key, alias_key, family_alias_key): ckey = _compact_model(key) if (not ckey) or (ckey in candidate_keys): continue candidate_keys.append(ckey) for key, row in self._router_fact_rows.items(): ckey = _compact_model(key) if (not ckey) or (ckey in candidate_keys): continue if not _strict_docs_only_candidate_matches(requested_compact, key, row): continue candidate_keys.append(ckey) best_key = "" best_row: Dict[str, Any] = {} best_score = float("-inf") for key in candidate_keys: row = self._router_fact_rows.get(key, {}) if not row: continue score = float(_strict_docs_only_row_quality(row)) source_doc = Path(str(row.get("source_doc") or "")).name.lower() if source_doc == "feb2026routers.csv": score += 1.5 elif "router_pricing_catalog" in source_doc: score -= 2.0 if key == requested_compact: score += 0.5 if score > best_score: best_score = score best_key = key best_row = row doc_seed_entry = _strict_docs_only_doc_seed_entry(requested) if doc_seed_entry is not None: doc_row = dict(doc_seed_entry["row"]) doc_score = float(_strict_docs_only_row_quality(doc_row)) + 1.0 if doc_score > best_score: return doc_seed_entry if best_key: return {"label": requested, "key": best_key, "row": best_row} return _strict_docs_only_lifecycle_seed_entry(requested) def _strict_docs_only_pair_table() -> Optional[Dict[str, Any]]: if wants_doc_matrix or asks_install_caveats: return None if len(requested_compare_labels) < 2: return None requested_pair = requested_compare_labels[:2] entries: List[Dict[str, Any]] = [] for label in requested_pair: entry = _strict_docs_only_compare_entry(label) if entry is None: return None entries.append(entry) throughput_explicit = _contains_any(low, ("throughput", "speed", "mbps", "gbps", "max throughput")) pair_field_labels = { "wan_lan": "WAN/LAN ports", "antennas_rf": "Antennas / RF connectors", "wifi": "Wi-Fi", "gnss": "GNSS/GPS", "modem": "Modem / cellular", "throughput": "Throughput", "battery": "Battery", "ruggedization": "Ruggedization", "install_caveats": "Power / install notes", } compare_fields = [ field for field in explicit_compare_fields if field in pair_field_labels ] if not compare_fields: compare_fields = ["wan_lan", "antennas_rf", "wifi", "modem"] if ( any(token in low for token in ("branch deployment", "branch deployments", "vehicle use", "vehicle install", "vehicle installs")) and ("install_caveats" not in compare_fields) ): compare_fields.append("install_caveats") def _strict_docs_only_pair_field_value(entry: Dict[str, Any], field_name: str) -> str: value = _strict_docs_only_compare_value( field_name, entry["row"].get(field_name, ""), requested_label=str(entry["label"]), selected_key=str(entry["key"]), ) if value != "Not clearly documented": return value if field_name != "modem": return value lookup_candidates = [ str(entry.get("key") or ""), str(entry.get("label") or ""), self._lookup_router_fact_key(str(entry.get("label") or "")), self._lookup_router_fact_key(str(entry.get("key") or "")), _compact_model(str(entry.get("label") or "")), ] raw_row: Dict[str, Any] = {} for candidate_key in lookup_candidates: candidate_key = str(candidate_key or "") if not candidate_key: continue raw_row = self._router_fact_rows.get(candidate_key, {}) if raw_row: break if not raw_row: raw_row = dict(entry.get("row") or {}) raw_modem = _norm(raw_row.get("modem", "")) if not raw_modem: return value raw_low = raw_modem.lower() if not any( token in raw_low for token in ( "lte", "5g", "4g", "cat", "wwan", "modem", "bundle", "package", "no-modem", "dual sim", "variant", ) ): return value return _truncate(raw_modem, 170) left_throughput = _strict_docs_only_compare_value( "throughput", entries[0]["row"].get("throughput", ""), requested_label=str(entries[0]["label"]), selected_key=str(entries[0]["key"]), ) right_throughput = _strict_docs_only_compare_value( "throughput", entries[1]["row"].get("throughput", ""), requested_label=str(entries[1]["label"]), selected_key=str(entries[1]["key"]), ) if throughput_explicit and ("throughput" not in compare_fields): compare_fields.append("throughput") if any(h in low for h in ("power note", "power notes", "power", "install caveat", "install caveats", "installation note", "installation notes")) and ("install_caveats" not in compare_fields): compare_fields.append("install_caveats") left_entry, right_entry = entries[:2] left_values = { field: _strict_docs_only_pair_field_value(left_entry, field) for field in compare_fields } right_values = { field: _strict_docs_only_pair_field_value(right_entry, field) for field in compare_fields } real_points = sum( 1 for field in compare_fields for value in (left_values.get(field, ""), right_values.get(field, "")) if value != "Not clearly documented" ) if real_points < 2: return None left_doc = str(left_entry["row"].get("source_doc") or "feb2026routers.csv") right_doc = str(right_entry["row"].get("source_doc") or "feb2026routers.csv") lines = [ f"Documented-spec comparison ({left_entry['label']} vs {right_entry['label']}) using internal docs only:", "", ] lines.extend( [ f"| Field | {left_entry['label']} | {right_entry['label']} |", "| --- | --- | --- |", f"| Internal documented source | {_md_cell(Path(left_doc).name)} | {_md_cell(Path(right_doc).name)} |", ] ) if "lifecycle posture" in low: def _documented_lifecycle_posture(entry: Dict[str, Any]) -> str: label = str(entry.get("label") or "") lifecycle_key = self._lookup_router_lifecycle_key_relaxed(label) or self._lookup_router_lifecycle_key(label) lifecycle_row = _as_dict(self._router_lifecycle_rows.get(lifecycle_key, {})) if lifecycle_key else {} status = _norm(lifecycle_row.get("status") or self._derive_lifecycle_status(lifecycle_row.get("eos", ""), lifecycle_row.get("eol", ""))) if not status: return "Not clearly documented" if status.upper() == "EOS": status = "End of Sale" elif status.upper() == "EOL": status = "End of Life" if (not bool(lifecycle_row.get("eos") or lifecycle_row.get("eol"))) and any( token in status.lower() for token in ("legacy", "retired", "discontinued", "eos", "eol", "end of sale", "end of life") ): return f"{status} (dates not listed)" return status lines.append( f"| Lifecycle posture | {_md_cell(_documented_lifecycle_posture(left_entry))} | {_md_cell(_documented_lifecycle_posture(right_entry))} |" ) summary_rows = [ {"model": str(left_entry["label"]), **left_values}, {"model": str(right_entry["label"]), **right_values}, ] for field in compare_fields: lines.append( f"| {pair_field_labels[field]} | {_md_cell(left_values[field])} | {_md_cell(right_values[field])} |" ) summary_bullets = _docs_table_meaningful_difference_bullets(summary_rows, compare_fields, pair_field_labels) if summary_bullets: lines.extend(["", "Meaningful documented differences:"]) lines.extend([f"- {bullet}" for bullet in summary_bullets]) coverage_notes: List[str] = [] for entry in entries: note = _norm(entry["row"].get("_coverage_note", "")) if note and note not in coverage_notes: coverage_notes.append(note) if coverage_notes: lines.extend(["", "Coverage notes:"]) lines.extend([f"- {note}" for note in coverage_notes]) sources: List[Dict[str, Any]] = [] files: List[str] = [left_doc, right_doc] for idx, entry in enumerate(entries, start=1): row = dict(entry["row"]) source_doc = str(row.get("source_doc") or "feb2026routers.csv") field_parts = [] values = left_values if idx == 1 else right_values for field in compare_fields: val = values.get(field, "Not clearly documented") if val == "Not clearly documented": continue field_parts.append(f"{pair_field_labels[field]}={val}") sources.append( { "id": f"RMD{idx}", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"docs_pair:{_compact_model(entry['label']) or entry['key']}", "location": "", "excerpt": ( f"{entry['label']}: " + "; ".join(field_parts) if field_parts else f"{entry['label']} documented fields present in {source_doc}." )[:320], "score": 0.99, } ) alias_wifi_note = _strict_docs_only_alias_wifi_override(str(entry["label"]), str(entry["key"])) if alias_wifi_note: files.append("docs/dev/session_handoff.md") sources.append( { "id": f"RMA{idx}", "domain": "router_docs", "doc": "session_handoff.md", "relative_path": "docs/dev/session_handoff.md", "chunk_id": f"router_alias:{_compact_model(entry['label']) or entry['key']}", "location": "", "excerpt": f"{entry['label']}: Wi-Fi={alias_wifi_note}; shared hardware fields map to the normalized family row.", "score": 0.92, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Used CSV-backed internal documented fields first for this strict docs-only pair compare.", "Fields that remain thin after sanitizing stay `Not clearly documented` instead of falling back to noisy extracted text.", ], [ "Ask `show quoted excerpts for each row` if you want PDF-level support next.", ], ), "sources": sources[:12], "files": list(dict.fromkeys(files))[:10], "meta": {"domain": "router_docs", "retrieval_mode": "router_multi_model_doc_table_fast", "web_assisted": False}, } if wants_docs_only: strict_pair_table = _strict_docs_only_pair_table() if strict_pair_table is not None: return strict_pair_table if len(dedup_models) < 2: return None if self._router_lightweight_fact_compare_supported(message, len(dedup_models)): return None if ( self._router_compare_should_delegate_to_router_docs(message, len(dedup_models)) and (not asks_install_caveats) and (not deterministic_doc_matrix_supported) and (not wants_docs_only) ): return None if wants_docs_only and len(dedup_models) >= 3 and all(self._lookup_router_fact_key(m) for m in dedup_models): # Prefer the deterministic internal compare table when every model # resolves cleanly; the delegated docs path has been weaker on # documented-vs-not-documented multi-model compares. pass include_throughput = _contains_any(low, ("throughput", "speed", "mbps", "gbps", "max throughput")) if wants_doc_matrix: show_only_meaningful_differences = "meaningful differences" in low def _doc_flag( field_name: str, raw_value: Any, *, requested_label: str, selected_key: str, ) -> str: v = _strict_docs_only_compare_value( field_name, raw_value, requested_label=requested_label, selected_key=selected_key, ) if v == "Not clearly documented": return "Not documented" if v == "None": return "Documented: None" return f"Documented: {v}" matrix_rows: List[Dict[str, Any]] = [] any_rows = 0 normalized_compare_message = normalize_router_intelligence_text(message) include_lifecycle_status = any( token in normalized_compare_message for token in ( "lifecycle", "status", "eos", "eol", "end of sale", "end of life", ) ) for model in dedup_models[:6]: norm_model = self._normalize_router_model(model) or model fact_key = self._lookup_router_fact_key(norm_model) life_key = self._lookup_router_lifecycle_key(norm_model) fact = self._router_fact_rows.get(fact_key, {}) if fact_key else {} life = self._router_lifecycle_rows.get(life_key, {}) if life_key else {} model_label = _compact_model(model) or norm_model wan_lan = _doc_flag("wan_lan", fact.get("wan_lan", ""), requested_label=model_label, selected_key=fact_key or model_label) rf = _doc_flag("antennas_rf", fact.get("antennas_rf", ""), requested_label=model_label, selected_key=fact_key or model_label) wifi = _doc_flag("wifi", fact.get("wifi", ""), requested_label=model_label, selected_key=fact_key or model_label) modem = _doc_flag("modem", fact.get("modem", ""), requested_label=model_label, selected_key=fact_key or model_label) throughput = _doc_flag("throughput", fact.get("throughput", ""), requested_label=model_label, selected_key=fact_key or model_label) status = _norm(life.get("status", "")) or "Not documented" row_values: Dict[str, Any] = { "model": model_label, "wan_lan": wan_lan, "antennas_rf": rf, "wifi": wifi, "modem": modem, "battery": _doc_flag( "battery", fact.get("battery", ""), requested_label=model_label, selected_key=fact_key or model_label, ), "ruggedization": _doc_flag( "ruggedization", fact.get("ruggedization", ""), requested_label=model_label, selected_key=fact_key or model_label, ), "install_caveats": _doc_flag( "install_caveats", fact.get("special_notes", "") or fact.get("commercial_details", "") or fact.get("talk_track_discovery", "") or fact.get("install_caveats", ""), requested_label=model_label, selected_key=fact_key or model_label, ), } if include_lifecycle_status: row_values["lifecycle_status"] = status if include_throughput: row_values["throughput"] = throughput matrix_rows.append(row_values) any_rows += 1 if any_rows >= 2: alias_collapsed_notes: List[str] = [] typo_neighbor_notes: List[str] = [] seen_alias_pairs: set[Tuple[str, str]] = set() matrix_model_labels = {_compact_model(str(row.get("model") or "")) for row in matrix_rows} for requested_label in requested_compare_labels: compact_requested = _compact_model(requested_label) if not compact_requested: continue if not self._lookup_router_fact_key(requested_label) and not self._lookup_router_lifecycle_key(requested_label): typo_candidate = self._router_workbook_likely_typo_candidate(requested_label) if typo_candidate: note = ( f"- `{requested_label}` looks like a typo for `{typo_candidate}`, so the compare row stays provisional and is not treated as a confirmed device." ) if note not in typo_neighbor_notes: typo_neighbor_notes.append(note) continue canonical_key = ( self._lookup_router_fact_key(requested_label) or self._lookup_router_lifecycle_key(requested_label) or self._normalize_router_model(requested_label) or compact_requested ) compact_canonical = _compact_model(canonical_key) if ( compact_canonical and compact_canonical in matrix_model_labels and compact_requested != compact_canonical ): pair = (requested_label, compact_canonical) if pair in seen_alias_pairs: continue seen_alias_pairs.add(pair) alias_collapsed_notes.append( f"- `{requested_label}` maps to `{compact_canonical}` in the current internal index, so the compare row stays under `{compact_canonical}` and is not duplicated." ) summary_fields = ["wan_lan", "antennas_rf", "wifi", "modem"] summary_field_labels = { "wan_lan": "WAN/LAN ports", "antennas_rf": "RF connectors", "wifi": "Wi-Fi", "modem": "Modem/cellular", } context_fields = ["battery", "ruggedization", "install_caveats"] summary_field_labels.update( { "battery": "Battery", "ruggedization": "Ruggedization", "install_caveats": "Install caveats", } ) if include_throughput: summary_fields.append("throughput") summary_field_labels["throughput"] = "Throughput" if include_lifecycle_status: summary_fields.append("lifecycle_status") summary_field_labels["lifecycle_status"] = "Lifecycle status" for field_name in context_fields: if field_name not in summary_fields: summary_fields.append(field_name) def _docs_matrix_cell_status(value: Any) -> Tuple[str, str]: text = _norm(value) or "Not documented" if text.startswith("Documented: "): text = text[len("Documented: ") :] low_text = text.lower() if not text or text in {"Not documented", "Not clearly documented"}: return ("undocumented", "Not documented") if ("exact sku/package" in low_text) or ("exact sku" in low_text): return ("sku_sensitive", "Exact SKU/package required") return ("documented", text) summary_bullets = _docs_table_meaningful_difference_bullets( matrix_rows, summary_fields, summary_field_labels, include_global_sku_sensitive=False, ) display_fields = list(summary_fields) if show_only_meaningful_differences and summary_bullets: filtered_fields: List[str] = [] for field_name in summary_fields: field_bullets = _docs_table_meaningful_difference_bullets( matrix_rows, [field_name], summary_field_labels, include_global_sku_sensitive=False, ) if field_bullets: filtered_fields.append(field_name) if filtered_fields: if len(filtered_fields) >= 2: display_fields = filtered_fields else: display_fields = [ field_name for field_name in summary_fields if any( _docs_matrix_cell_status(row.get(field_name, "Not documented"))[0] != "undocumented" for row in matrix_rows ) ] if show_only_meaningful_differences and (not summary_bullets): display_fields = [field_name for field_name in summary_fields if field_name != "lifecycle_status"] if show_only_meaningful_differences: for field_name in context_fields: if field_name in display_fields: continue if any( _docs_matrix_cell_status(row.get(field_name, "Not documented"))[0] != "undocumented" for row in matrix_rows ): display_fields.append(field_name) header_labels = [summary_field_labels[field_name] for field_name in display_fields] omitted_field_lines: List[str] = [] if show_only_meaningful_differences: for field_name in summary_fields: if field_name in display_fields: continue field_values = [ _norm(row.get(field_name) or "Not documented") or "Not documented" for row in matrix_rows ] unique_values = list(dict.fromkeys(field_values)) field_label = summary_field_labels.get(field_name, field_name) if not unique_values: omitted_field_lines.append(f"- {field_label}: no stable documented value was available.") continue if len(unique_values) == 1: only = unique_values[0] if only == "Not documented": omitted_field_lines.append(f"- {field_label}: uniformly `Not documented`.") continue if "Needs exact SKU/package" in only: omitted_field_lines.append(f"- {field_label}: uniformly exact-SKU-sensitive across the compared rows.") continue if only.startswith("Documented: "): omitted_field_lines.append( f"- {field_label}: same documented value across all compared models ({only.replace('Documented: ', '', 1)})." ) continue omitted_field_lines.append(f"- {field_label}: same value across all compared models ({only}).") continue if all( value == "Not documented" or "Needs exact SKU/package" in value for value in unique_values ): omitted_field_lines.append( f"- {field_label}: checked, but coverage stayed mixed between undocumented and exact-SKU-sensitive rows." ) continue omitted_field_lines.append( f"- {field_label}: checked, but the remaining differences were too variant-sensitive to summarize safely." ) coverage_note_lines: List[str] = [] for row in matrix_rows: exact_fields: List[str] = [] missing_fields: List[str] = [] for field_name in display_fields: status, _ = _docs_matrix_cell_status(row.get(field_name, "Not documented")) field_label = summary_field_labels.get(field_name, field_name) if status == "sku_sensitive": exact_fields.append(field_label) elif status == "undocumented": missing_fields.append(field_label) if not exact_fields and not missing_fields: continue parts: List[str] = [] if exact_fields: parts.append(f"exact SKU/package required for {', '.join(exact_fields)}") if missing_fields: parts.append(f"not documented in the current compare row for {', '.join(missing_fields)}") coverage_note_lines.append( f"- `{row.get('model') or 'Unknown model'}`: " + "; ".join(parts) + "." ) docs_vs_inference_requested = bool( ("separate internal docs evidence" in low and "workbook recommendation logic" in low) or ("clearly documented" in low and "inferred" in low) or ("documented versus inferred" in low) or ("documented vs inferred" in low) ) lines = [ ( "Internal docs evidence lane (normalized internal compare rows only):" if docs_vs_inference_requested else "Documented vs not-documented comparison (normalized internal compare rows):" ), *( [ "", "Workbook recommendation logic: intentionally excluded from this table.", "Inference policy: any cell marked `Not documented` or `Exact SKU/package required` stays abstained instead of inferred.", "", ] if docs_vs_inference_requested else [""] ), "| Model | " + " | ".join(header_labels) + " | Evidence |", "| --- | " + " | ".join("---" for _ in header_labels) + " | --- |", ] has_sku_sensitive_cells = any( _docs_matrix_cell_status(row.get(field_name, "Not documented"))[0] == "sku_sensitive" for row in matrix_rows for field_name in display_fields ) sources: List[Dict[str, Any]] = [] for idx, row in enumerate(matrix_rows, start=1): values = [] for field_name in display_fields: status, display_value = _docs_matrix_cell_status(row.get(field_name, "Not documented")) values.append(_md_cell(display_value)) evidence_label = f"[RDM{idx}] feb2026routers.csv" lines.append( f"| {_md_cell(row.get('model') or 'Unknown model')} | " + " | ".join(values) + f" | {_md_cell(evidence_label)} |" ) evidence_bits = [ f"{summary_field_labels[field_name]}={_docs_matrix_cell_status(row.get(field_name, 'Not documented'))[1]}" for field_name in display_fields ] sources.append( { "id": f"RDM{idx}", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": f"docs_matrix_row:{_compact_model(row.get('model') or '') or idx}", "location": "", "excerpt": _truncate( f"{row.get('model') or 'Unknown model'}: " + "; ".join(evidence_bits), 900, ), "score": 0.99, } ) if alias_collapsed_notes: lines.extend(["", "Alias-sensitive requested labels kept visible:"]) lines.extend(alias_collapsed_notes[:4]) if typo_neighbor_notes: lines.extend(["", "Typo-sensitive requested labels kept visible:"]) lines.extend(typo_neighbor_notes[:4]) if summary_bullets: lines.extend(["", "Meaningful documented differences:"]) lines.extend([f"- {bullet}" for bullet in summary_bullets]) if has_sku_sensitive_cells: lines.extend( [ "", "Renderer note:", "- `Exact SKU/package required` marks a variant-sensitive cell and is not a quoted source excerpt.", ] ) if coverage_note_lines: lines.extend( [ "", ( "Fields that remain outside the documented evidence lane:" if docs_vs_inference_requested else "Fields that are not directly locked down by the current compare rows:" ), ] ) lines.extend(coverage_note_lines) if omitted_field_lines: lines.extend(["", "Checked but not surfaced as meaningful differences:"]) lines.extend(omitted_field_lines) if show_only_meaningful_differences: lines.extend( [ "", "Only the fields with the clearest documented deltas were promoted into the summary above.", ] ) return { "assistant": _format_shell( "\n".join(lines), [ "Compared each requested model directly and preserved requested model labels.", "Fields stay conservative: exact-SKU-sensitive and undocumented rows are labeled instead of being inferred.", ], [ "Ask `from documented specs only with citations` for per-row document excerpts.", ], ), "sources": [ *sources[:6], *( [ { "id": "RDL1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "docs_matrix_lifecycle", "location": "", "excerpt": "Lifecycle status rows used for compare matrix status column.", "score": 0.99, } ] if include_lifecycle_status else [] ), ], "files": ["feb2026routers.csv", *(["routers_eos_eol_by_sku.csv"] if include_lifecycle_status else [])], "meta": {"domain": "router_docs", "retrieval_mode": "router_docs_documented_matrix_fast", "web_assisted": False}, } # Intentionally disable the old free-text sentence picker path (noisy). if False: router_idx = getattr(getattr(self, "router_rag_core", None), "index", None) if router_idx is not None and hasattr(router_idx, "search"): def _pick_field(text: str, kind: str) -> str: def _is_noisy(s: str) -> bool: if not s: return True if "![#$%&" in s or ")*+," in s: return True if "$" in s or "mifi x pro" in s.lower(): return True punct = sum(1 for ch in s[:80] if ch in "!\"#$%&'()*+,./:;<=>?@[\\]^_`{|}~") return punct >= 14 sentences = [x.strip() for x in re.split(r"(?<=[.!?])\s+|\n+", _norm(text)) if x.strip()] for s in sentences: sl = s.lower() if len(s) < 20: continue if _is_noisy(s): continue if any( x in sl for x in ( "copyright", "for more information", "table", "figure", "step ", "dial-up", "ping", "diagram", "package contents", "packing list", "included in box", "power adaptor", "quick-start guide qr", "safety information", "caution", "do not leave your device", "stepped on", "sharp edges", "choking hazard", "children", "faulty and damaged", ) ): continue if kind == "wan_lan" and any(k in sl for k in ("ethernet", "wan", "lan", "rj45")): if ("port" not in sl) and ("wan" not in sl) and ("lan" not in sl): continue if not re.search( r"(\bdual ethernet ports?\b|\bfive ethernet ports?\b|\b\d+\s*(?:[*x]\s*)?(?:10/100/1000|10/100|100mbit/s|100mbit/s|2\.5g|ge|gbe)?\s*(?:mbps|gbps)?\s*(?:fast )?(?:ethernet|network)\s*ports?\b|\bwan/?lan\b|\bwan port\b|\blan port\b|\brj45\b)", sl, ): continue m = re.search( r"(ir\d{3,4}\s*:\s*)?\d+\s*(?:[*x]\s*)?(?:10/100/1000|10/100|100mbit/s|100mbit/s|2\.5g|ge|gbe)?\s*(?:mbps|gbps)?\s*(?:fast )?(?:ethernet|network)\s*ports?(?:[^.;]{0,80})", sl, ) if m: return _truncate(m.group(0), 170) numeric = re.search(r"\b(\d+)\s*(?:x\s*)?(?:fast\s+)?(?:ethernet|network)\s*ports?\b", sl) if numeric: return _truncate(f"{numeric.group(1)} total Ethernet ports", 170) return _truncate(s, 170) if kind == "rf" and ( bool(re.search(r"\brp-?sma\b", sl)) or bool(re.search(r"\bsma\b", sl)) or ("antenna connector" in sl) or ("antenna port" in sl) ): if any(x in sl for x in ("installation", "mount", "positioning", "exposure")): continue m = re.search(r"(antenna connector[^.;]{0,120}|antenna port[^.;]{0,120}|(?:rp-?sma|sma)[^.;]{0,100})", s, re.IGNORECASE) if m: return _truncate(_norm(m.group(0)), 170) numeric_rf = re.search(r"\b(\d+)\s*x\s*(rp-?sma|sma)\b", sl) if numeric_rf: connector = "RP-SMA" if "rp-sma" in numeric_rf.group(2).lower() else "SMA" return _truncate(f"{numeric_rf.group(1)}x {connector} connectors", 170) return _truncate(s, 170) if kind == "wifi" and any(k in sl for k in ("wi-fi", "wifi", "802.11")): if any(k in sl for k in ("cradlepoint.com", "support.", "+1.", "spec sheet /", "datasheet /")): continue m = re.search(r"(wi-?fi[^.;]{0,100}|802\.11[^.;]{0,100})", s, re.IGNORECASE) if m: return _truncate(_norm(m.group(0)), 170) return _truncate(s, 170) if kind == "modem" and any(k in sl for k in ("5g", "lte", "cat ", "5g nr", "lte cat")): m = re.search(r"((?:5g|4g|lte)[^.;]{0,120}|cat\s*\d+[^.;]{0,100})", s, re.IGNORECASE) if m: return _truncate(_norm(m.group(0)), 170) return _truncate(s, 170) if kind == "throughput" and any(k in sl for k in ("throughput", "mbps", "gbps", "data rate")): return _truncate(s, 170) return "Not clearly documented" doc_rows: List[Dict[str, str]] = [] doc_sources: List[Dict[str, Any]] = [] doc_files: List[str] = [] for m_idx, model in enumerate(dedup_models[:6], start=1): model_norm = self._normalize_router_model(model) or model m_compact = _compact_model(model_norm) hits: List[Any] = [] try: hits = list( router_idx.search( f"{model_norm} datasheet manual hardware specifications wan lan ethernet ports antenna connector wifi modem", top_k=20, ) or [] ) except Exception: hits = [] if hits: def _doc_priority(hit_obj: Any) -> Tuple[int, float]: chunk_obj = getattr(hit_obj, "chunk", None) doc_blob = ( f"{getattr(chunk_obj, 'file_name', '')} {getattr(chunk_obj, 'relative_path', '')}".lower() if chunk_obj is not None else "" ) if ("data sheet" in doc_blob) or ("datasheet" in doc_blob): pri = 3 elif "manual" in doc_blob: pri = 2 elif ("quick start" in doc_blob) or ("quick guide" in doc_blob): pri = 1 else: pri = 0 return pri, float(getattr(hit_obj, "score", 0.0) or 0.0) hits = sorted(hits, key=_doc_priority, reverse=True) best_doc = "" best_rel = "" best_chunk = "" best_score = 0.0 values: Dict[str, str] = { "wan_lan": "Not clearly documented", "rf": "Not clearly documented", "wifi": "Not clearly documented", "modem": "Not clearly documented", "throughput": "Not clearly documented", } excerpt_pool: List[str] = [] raw_texts: List[str] = [] for hit in hits: chunk = getattr(hit, "chunk", None) if chunk is None: continue doc_name = str(getattr(chunk, "file_name", "") or "") rel = str(getattr(chunk, "relative_path", "") or "") text = _norm(str(getattr(chunk, "text", "") or "")) if not doc_name or not text: continue blob = f"{doc_name} {rel} {text}" if m_compact and (m_compact not in _compact_model(blob)): continue if not any(k in blob.lower() for k in ("datasheet", "data sheet", "quick start", "manual", "guide")): continue if ("quick start" in blob.lower() or "guide" in blob.lower()) and any( bad in text.lower() for bad in ("safety information", "packing list", "faulty and damaged", "electrical safety") ): continue raw_texts.append(text) if text: excerpt_pool.append(_truncate(text, 220)) found_in_this_hit = 0 for kind in ("wan_lan", "rf", "wifi", "modem", "throughput"): if values[kind] != "Not clearly documented": continue candidate = _pick_field(text, kind) if candidate != "Not clearly documented": values[kind] = candidate found_in_this_hit += 1 if (not best_doc) and (found_in_this_hit > 0): best_doc = doc_name best_rel = rel best_chunk = str(getattr(chunk, "chunk_id", "") or f"docs_only:{model_norm}:{m_idx}") best_score = float(getattr(hit, "score", 0.0) or 0.0) corpus_blob = " ".join(raw_texts).lower() if values["wan_lan"] == "Not clearly documented": if "five ethernet ports" in corpus_blob: values["wan_lan"] = "Five Ethernet Ports (documented in IR305 datasheet)" elif "dual ethernet ports" in corpus_blob: values["wan_lan"] = "Dual Ethernet Ports (documented in source text)" if values["modem"] == "Not clearly documented": if "4g lte" in corpus_blob and "5g" in corpus_blob: values["modem"] = "4G LTE / 5G variant coverage appears in model family text" elif "4g lte" in corpus_blob: values["modem"] = "4G LTE" elif "5g" in corpus_blob: values["modem"] = "5G" if values["wifi"] == "Not clearly documented" and ("wi-fi" in corpus_blob or "wifi" in corpus_blob): values["wifi"] = "Wi-Fi supported (documented in source text)" if values["rf"] == "Not clearly documented": if re.search(r"antenna connector[^.;]{0,80}sma", corpus_blob): values["rf"] = "Antenna connector: SMA (documented in source text)" elif re.search(r"\brp-?sma\b", corpus_blob): values["rf"] = "RP-SMA antenna connector (documented in source text)" row = { "model": model_norm, "wan_lan": values["wan_lan"], "rf": values["rf"], "wifi": values["wifi"], "modem": values["modem"], "throughput": values["throughput"], "evidence": f"[RDS{m_idx}] {best_doc or 'No strong internal hit'}", } doc_rows.append(row) href = _mounted_file_href("/router_rag_files", best_rel) if best_rel else "" if href: doc_files.append(href) doc_sources.append( { "id": f"RDS{m_idx}", "domain": "router_docs", "doc": best_doc or f"{model_norm} docs", "relative_path": href, "chunk_id": best_chunk or f"docs_only:{model_norm}:{m_idx}", "location": "", "excerpt": _truncate( f"{model_norm}: WAN/LAN={values['wan_lan']}; RF={values['rf']}; " f"Wi-Fi={values['wifi']}; Modem={values['modem']}; Throughput={values['throughput']}.", 260, ), "score": best_score, } ) if len(doc_rows) >= 2: lines = [ "Documented-spec comparison table (internal docs only):", "", "| Model | WAN/LAN ports | RF connectors | Wi-Fi | Modem/cellular |" + (" Throughput |" if include_throughput else "") + " Evidence |", "| --- | --- | --- | --- | --- |" + (" --- |" if include_throughput else "") + " --- |", ] for row in doc_rows[:6]: line = ( f"| {_md_cell(row['model'])} | {_md_cell(row['wan_lan'])} | {_md_cell(row['rf'])} | " f"{_md_cell(row['wifi'])} | {_md_cell(row['modem'])} |" ) if include_throughput: line += f" {_md_cell(row['throughput'])} |" line += f" {_md_cell(row['evidence'])} |" lines.append(line) return { "assistant": _format_shell( "\n".join(lines), [ "Built from model-matched internal datasheet/quick-start/manual excerpts.", "Fields without clear excerpt support are explicitly marked `Not clearly documented`.", ], [ "Ask `show quoted excerpts for each row` for stricter auditability.", ], ), "sources": doc_sources[:12], "files": list(dict.fromkeys(doc_files))[:10], "meta": {"domain": "router_docs", "retrieval_mode": "router_docs_only_compare_fast", "web_assisted": False}, } def _adapter_guidance(rf_text: str) -> str: rf_low = str(rf_text or "").lower() has_rpsma = ("rp-sma" in rf_low) or ("rpsma" in rf_low) has_sma = "sma" in rf_low if has_rpsma and has_sma: return "May need RP-SMA<->SMA adapters depending on antenna lead; confirm connector gender." if has_rpsma: return "Use RP-SMA-compatible leads; adapter may be required when antenna side is SMA." if has_sma: return "Use SMA-compatible leads; adapter may be required for N-type external antennas." return "Adapter requirement not explicitly documented; confirm connector type/gender before ordering." def _has_listed_value(value: str) -> bool: low_val = str(value or "").strip().lower() if not low_val: return False return not any( token in low_val for token in ("not listed", "abstained", "not documented", "not clearly documented", "unknown", "csv conflict") ) def _split_profile_antenna(suggested: str, primary_use_case: str) -> Tuple[str, str]: suggested_norm = _norm(suggested) if not suggested_norm: return "", "" low_s = suggested_norm.lower() use_low = _norm(primary_use_case).lower() vehicle_terms = ("vehicle", "mobile", "fleet", "public safety", "in-vehicle", "patrol", "transport") fixed_terms = ("fixed", "outdoor", "indoor", "directional", "building", "kiosk", "case", "site") has_vehicle = any(t in low_s for t in vehicle_terms) has_fixed = any(t in low_s for t in fixed_terms) if (not has_vehicle) and (not has_fixed): if any(t in use_low for t in vehicle_terms): has_vehicle = True elif any(t in use_low for t in fixed_terms): has_fixed = True if has_fixed and has_vehicle: return suggested_norm, suggested_norm if has_fixed: return suggested_norm, "" if has_vehicle: return "", suggested_norm generic = f"{suggested_norm} (profile split not listed)" return generic, generic def _build_device_details(*, wifi_value: str, wan_lan_value: str, battery_value: str, rugged_value: str) -> str: details: List[str] = [] wifi_clean = _norm(wifi_value) if _has_listed_value(wifi_clean): low_wifi = wifi_clean.lower() if any(x in low_wifi for x in ("no wifi", "no wi-fi", "without wifi", "without wi-fi", "none")): details.append("Wi-Fi: no") else: details.append(f"Wi-Fi: yes ({wifi_clean})") wan_lan_clean = _norm(wan_lan_value) if _has_listed_value(wan_lan_clean): details.append(f"Ports: {wan_lan_clean}") rugged_clean = _norm(rugged_value) if _has_listed_value(rugged_clean): details.append(f"Housing: {rugged_clean}") battery_clean = _norm(battery_value) if _has_listed_value(battery_clean): low_battery = battery_clean.lower() if any(x in low_battery for x in ("none", "no battery", "without battery")): details.append("Battery: no") else: details.append(f"Battery: yes ({battery_clean})") return "; ".join(details) if details else "Not listed (abstained)" rows: List[Dict[str, str]] = [] source_rows: List[Dict[str, str]] = [] typo_neighbor_notes: List[str] = [] install_focus = bool(wants_docs_only and asks_install_caveats) for model in dedup_models[:6]: norm_model = self._normalize_router_model(model) or model fact_key = self._lookup_router_fact_key(norm_model) life_key = self._lookup_router_lifecycle_key(norm_model) fact = self._router_fact_rows.get(fact_key, {}) if fact_key else {} life = self._router_lifecycle_rows.get(life_key, {}) if life_key else {} canonical = fact_key or life_key or norm_model display = model if model else self._router_display_name(fact or life, canonical) typo_candidate = "" if not fact_key and not life_key: typo_candidate = self._router_workbook_likely_typo_candidate(norm_model) if typo_candidate: display = f"{model} (possible typo for `{typo_candidate}`)" if model else f"{display} (possible typo for `{typo_candidate}`)" note = ( f"- `{model or norm_model}` looks like a typo for `{typo_candidate}`, so the compare row stays provisional and is not treated as a confirmed device." ) if note not in typo_neighbor_notes: typo_neighbor_notes.append(note) manufacturer = _norm(fact.get("manufacturer", "")) or "Not listed (abstained)" wan_lan = _norm(fact.get("wan_lan", "")) or "Not listed (abstained)" rf = _norm(fact.get("antennas_rf", "")) or "Not listed (abstained)" modem = _norm(fact.get("modem", "")) or "Not listed (abstained)" install_caveats = _norm( fact.get("install_caveats", "") or fact.get("special_notes", "") or fact.get("commercial_details", "") or fact.get("talk_track_discovery", "") ) or "Not listed (abstained)" if install_focus: wan_lan = _sanitize_catalog_install_compare_value("wan_lan", wan_lan) rf = _sanitize_catalog_install_compare_value("antennas_rf", rf) rf_low = rf.lower() if ( rf and rf not in {"Not clearly documented", "Needs exact SKU/package", "Needs exact SKU/package; connector path varies by variant."} and any(token in rf_low for token in ("sma", "rp-sma", "reverse-sma", "connector", "antenna")) and not any(token in rf_low for token in ("documented in source text", "confirmed in excerpt")) ): rf = "Not clearly documented" modem = _sanitize_catalog_install_compare_value("modem", modem) install_caveats = _sanitize_catalog_install_compare_value("install_caveats", install_caveats) elif ("5g" in display.lower()) and ("5g" not in modem.lower()): modem = "Not listed (CSV conflict; verify in datasheet)" wifi = _norm(fact.get("wifi", "")) or "Not listed (abstained)" gnss = _norm(fact.get("gnss", "")) or "Not listed (abstained)" battery = _norm(fact.get("battery", "")) or "Not listed (abstained)" rugged = _norm(fact.get("ruggedization", "")) or "Not listed (abstained)" device_details = _build_device_details( wifi_value=wifi, wan_lan_value=wan_lan, battery_value=battery, rugged_value=rugged, ) suggested_antennas = _norm(fact.get("suggested_antennas", "")) primary_use_case = _norm(fact.get("primary_use_case", "")) fixed_antenna, vehicle_antenna = _split_profile_antenna(suggested_antennas, primary_use_case) evidence_label = f"[RMD{len(rows) + 1}] feb2026routers.csv" row = { "model": display, "manufacturer": manufacturer, "modem": modem, "wifi": wifi, "gnss": gnss, "wan_lan": wan_lan, "rf": rf, "battery": battery, "install_caveats": install_caveats, "device_details": device_details, "fixed_antenna": fixed_antenna, "vehicle_antenna": vehicle_antenna, "canonical": canonical, "evidence": evidence_label, } rows.append(row) source_rows.append(dict(row)) if len(rows) < 2: return None show_fixed_ant = any(_has_listed_value(r.get("fixed_antenna", "")) for r in rows) show_vehicle_ant = any(_has_listed_value(r.get("vehicle_antenna", "")) for r in rows) antenna_fields_only = bool( any(token in low for token in ("antenna-related fields", "antenna related fields")) ) def _antenna_fields_only_rf_value(raw_rf: str) -> str: cleaned = _fix_common_mojibake(_norm(raw_rf)) if not cleaned: return "Not clearly documented" cleaned = re.sub(r"[.;]\s*Adapter pigtails?:.*$", "", cleaned, flags=re.IGNORECASE).strip(" ;,.") cleaned_low = cleaned.lower() if any(token in cleaned_low for token in ("variant uses", "if present", "by variant", "exact sku", "exact package")): base = re.split( r"(?i)\b(?:variant uses|if present|by variant|exact sku|exact package)\b", cleaned, maxsplit=1, )[0].strip(" ;,.-") base = re.sub(r"(?i)^external\s*\(", "", base).strip(" ;,.-)") base = re.sub(r"(?i)\bwi-?fi\s*$", "", base).strip(" ;,.-") if re.search(r"(?i)\b(?:\d+\s*x\s*)?(?:rp-)?sma\b", base): return _truncate(f"{base}; exact connector layout still varies by variant.", 120) return "Connector families are documented, but exact connector layout varies by variant." explicit_bits: List[str] = [] for match in re.finditer(r"\b\d+\s*x\s*(?:rp-?sma|sma)\s*(?:cellular|wi-?fi|gnss|gps)?(?:\s+connectors?)?\b", cleaned, flags=re.IGNORECASE): bit = _norm(match.group(0)) if bit and bit.lower() not in {x.lower() for x in explicit_bits}: explicit_bits.append(bit) if explicit_bits: return _truncate("; ".join(explicit_bits), 120) paren_match = re.search(r"\(([^)]*(?:sma|rp-?sma|gps|gnss)[^)]*)\)", cleaned, flags=re.IGNORECASE) if paren_match: bit = _norm(paren_match.group(1)) if bit: return _truncate(bit, 120) fallback = re.sub(r"(?i)\b(?:cellular:\s*4x4 mimo on sma|wi-?fi(?:\s*\(if present\))?\s+on\s+rp-?sma|gnss on sma|gps connector)\b", "", cleaned) fallback = re.sub(r"\s+", " ", fallback).strip(" ;,.") if fallback and re.search(r"(?i)\b(?:sma|rp-?sma|gps|gnss|connector)\b", fallback): return _truncate(fallback, 120) return "Not clearly documented" def _antenna_fields_only_gnss_value(raw_gnss: str, raw_rf: str) -> str: gnss_value = _norm(raw_gnss) if gnss_value and ("not listed" not in gnss_value.lower()) and ("abstained" not in gnss_value.lower()): return gnss_value return "Not listed (abstained)" if install_focus: install_field_labels = { "wan_lan": "WAN/LAN ports", "rf": "RF connectors", "wifi": "Wi-Fi", "modem": "Modem variants/type", "battery": "Battery", "install_caveats": "Install caveats", } install_requested_fields = [ "rf" if field == "antennas_rf" else field for field in requested_compare_fields if ("rf" if field == "antennas_rf" else field) in {"wan_lan", "rf", "wifi", "modem", "battery", "install_caveats"} ] if not install_requested_fields: install_requested_fields = ["wan_lan", "modem", "wifi", "battery", "install_caveats"] column_order = [("Model", "model")] column_order.extend((install_field_labels[field], field) for field in install_requested_fields) column_order.append(("Evidence", "evidence")) elif antenna_fields_only: for row in rows: row["rf"] = _antenna_fields_only_rf_value(str(row.get("rf", ""))) row["gnss"] = _antenna_fields_only_gnss_value(str(row.get("gnss", "")), str(row.get("rf", ""))) column_order = [ ("Model", "model"), ("RF connectors", "rf"), ("GNSS/GPS", "gnss"), ("Evidence", "evidence"), ] else: column_order = [ ("Model", "model"), ("Manufacturer", "manufacturer"), ("Modem variants/type", "modem"), ("Wi-Fi", "wifi"), ("WAN/LAN ports", "wan_lan"), ("Battery", "battery"), ("Device details", "device_details"), ] if show_fixed_ant: column_order.append(("Suggested antenna (fixed-mount)", "fixed_antenna")) if show_vehicle_ant: column_order.append(("Suggested antenna (vehicle)", "vehicle_antenna")) if wants_docs_only: real_points = 0 for r in rows: for _title, field_name in column_order: if field_name in {"model", "evidence"}: continue value = r.get(field_name, "") val = str(value or "").lower() if val and ("not listed (abstained)" not in val) and ("not clearly documented" not in val) and ("csv conflict" not in val): real_points += 1 if real_points < 2: return None header = "| " + " | ".join(title for title, _ in column_order) + " |" divider = "| " + " | ".join(["---"] * len(column_order)) + " |" lines = [ ( "Documented-spec comparison table (internal sources only):" if wants_docs_only else "Documented multi-model comparison table (internal sources):" ), "", header, divider, ] for row in rows: cell_values: List[str] = [] for _title, key in column_order: raw_val = _norm(row.get(key, "")) if _has_listed_value(raw_val): val = raw_val elif install_focus and wants_docs_only and key not in {"model", "evidence"}: val = "Not clearly documented" else: val = "Not listed (abstained)" cell_values.append(_md_cell(val)) lines.append("| " + " | ".join(cell_values) + " |") if typo_neighbor_notes: lines.extend(["", "Typo-sensitive requested labels kept visible:"]) lines.extend(typo_neighbor_notes[:4]) sources: List[Dict[str, Any]] = [] doc_files: List[str] = [] for idx, row in enumerate(source_rows[:6], start=1): display = row.get("model", "") canonical = row.get("canonical", "") wan_lan = row.get("wan_lan", "") modem = row.get("modem", "") wifi = row.get("wifi", "") gnss = row.get("gnss", "") battery = row.get("battery", "") device_details = row.get("device_details", "") fixed_antenna = row.get("fixed_antenna", "") vehicle_antenna = row.get("vehicle_antenna", "") sources.append( { "id": f"RMD{idx}", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": f"catalog:{canonical}", "location": "", "excerpt": ( f"{display}: manufacturer={row.get('manufacturer', '')}; modem={modem}; wifi={wifi}; " f"WAN/LAN={wan_lan}; GNSS={gnss}; battery={battery}; device_details={device_details}; " f"fixed_antenna={fixed_antenna or 'Not listed'}; " f"vehicle_antenna={vehicle_antenna or 'Not listed'}." ), "score": 1.0, } ) canonical_compact = _compact_model(canonical) canonical_root_match = re.match(r"[A-Z]{1,6}\d{2,4}", canonical_compact) canonical_root = _compact_model(canonical_root_match.group(0)) if canonical_root_match else "" doc_rel = next( ( p for p in self._router_file_map.values() if ( (canonical_compact and (canonical_compact in _compact_model(Path(p).name))) or (canonical_root and (canonical_root in _compact_model(Path(p).name))) ) ), "", ) if doc_rel: href = _mounted_file_href("/router_rag_files", doc_rel) doc_files.append(href) sources.append( { "id": f"RMD{idx}D", "domain": "router_docs", "doc": Path(str(doc_rel)).name, "relative_path": href, "chunk_id": f"doc:{canonical}", "location": "", "excerpt": f"Internal documented spec file for {display}.", "score": 0.95, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Built from internal documented fields and preserves requested model order.", "Evidence references are kept in metadata/sources and removed from the visible table.", "For `docs only` requests, missing values remain abstained instead of inferred.", ], [ "If you want additional fields, provide exact model variants and I will append them from internal sources.", ], ), "sources": sources, "files": ["feb2026routers.csv", "routers_eos_eol_by_sku.csv", *doc_files[:6]], "meta": { "domain": "router_docs", "retrieval_mode": "router_multi_model_doc_caveat_table_fast" if install_focus else "router_multi_model_doc_table_fast", "web_assisted": False, }, } def _is_router_requirement_query(self, message: str) -> bool: low = str(message or "").lower() asks_suggestion = _contains_any(low, ("suggest", "recommend", "best-fit", "best fit", "best fit routers", "options")) has_requirement_set = ("battery" in low) and ("rugged" in low or "ruggedization" in low) and ("5g" in low) return bool(asks_suggestion and has_requirement_set) def _router_shortlist_count_hint(self, message: str, *, default: int = 2, max_items: int = 6) -> int: low = str(message or "").lower() number_words = { "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, } top_match = re.search( r"\btop\s+(?:(\d+)|(" + "|".join(re.escape(word) for word in number_words) + r"))\b", low, ) if top_match: try: numeric = str(top_match.group(1) or "").strip() if numeric: return max(1, min(max_items, int(numeric))) word = str(top_match.group(2) or "").strip().lower() if word in number_words: return max(1, min(max_items, number_words[word])) except Exception: return max(1, min(max_items, int(default))) m = re.search(r"\b(top|recommend|recommended|suggest|suggested)?\s*(\d+)\s+routers?\b", low) if m: try: return max(1, min(max_items, int(m.group(2)))) except Exception: return max(1, min(max_items, int(default))) for word, value in number_words.items(): if re.search(fr"\b(?:top\s+)?{word}\s+routers?\b", low): return max(1, min(max_items, value)) return max(1, min(max_items, int(default))) def _router_has_explicit_model_hint(self, message: str) -> bool: for raw in self._extract_router_models_cached(message): normalized = self._normalize_router_model(raw) or _compact_model(raw) if self._lookup_router_fact_key(normalized) or self._lookup_router_lifecycle_key(normalized): return True return False def _router_is_5g_sa_shortlist_query(self, message: str) -> bool: low = _normalize_router_query_text(message) if self._router_has_explicit_model_hint(message): return False if not (("5g sa" in low) or ("standalone" in low and "5g" in low)): return False if re.search(r"\b(compare|comparison|table|matrix|chart)\b", low): return False return bool( re.search(r"\b(which|what|list|show|identify|support|supports|have|has|include|includes)\b", low) and re.search(r"\b(router|routers|gateway|gateways)\b", low) ) def _router_is_branch_primary_5g_recommendation_query(self, message: str) -> bool: low = _normalize_router_query_text(message) if self._router_has_explicit_model_hint(message): return False if re.search(r"\b(compare|comparison|table|matrix|chart)\b", low): return False asks_router = bool(re.search(r"\b(router|routers|gateway|gateways)\b", low)) asks_branch = bool(re.search(r"\b(branch|branch office|office|store|small branch|retail site|site)\b", low)) asks_5g = bool(re.search(r"\b5g\b", low)) asks_primary_5g = bool( re.search(r"\bprimary\s+5g\b", low) or re.search(r"\b5g\s+primary\b", low) or ("primary" in low and "5g" in low) ) asks_recommend = bool(re.search(r"\b(recommend|recommended|recommendation|recommendations|suggest|suggested|best fit|best|which|what should i use)\b", low)) asks_open_ended_shortlist = bool( re.search(r"\btop\s+(?:\d+|one|two|three|four|five|six)\b", low) or "top three" in low or "current-only" in low or "current only" in low or "main tradeoff" in low or "shortlist" in low ) return bool(asks_router and asks_branch and asks_5g and asks_recommend and (asks_primary_5g or asks_open_ended_shortlist)) def _router_query_prefers_open_ended_shortlist(self, message: str) -> bool: return bool( self._router_is_5g_sa_shortlist_query(message) or self._router_is_branch_primary_5g_recommendation_query(message) ) def _router_open_ended_shortlist_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) unique_rows = [row for row in self._unique_router_fact_rows() if not self._router_row_looks_service_like(row)] if not unique_rows: return None vendor_terms = ("inseego", "wavemaker", "cradlepoint", "ericsson", "peplink", "pepwave", "semtech", "sierra", "digi", "atel", "meraki", "cisco") if self._router_is_5g_sa_shortlist_query(message): if any(term in low for term in vendor_terms): return None rows: List[Tuple[int, Dict[str, Any]]] = [] for row in unique_rows: device_type = _norm(row.get("device_type", "")).lower() if device_type and ("adapter" in device_type) and ("router" not in device_type): continue modem = _norm(row.get("modem", "")) modem_low = modem.lower() if ("5g" not in modem_low) or (("sa" not in modem_low) and ("standalone" not in modem_low)): continue score = 0 use_case_low = _norm(row.get("primary_use_case", "")).lower() if any(token in use_case_low for token in ("branch", "fixed", "indoor")): score += 2 if _norm(row.get("wan_lan", "")): score += 1 if _norm(row.get("wifi", "")): score += 1 rows.append((score, row)) if not rows: return None rows.sort( key=lambda item: ( item[0], _compact_model(item[1].get("model", "") or item[1].get("model_key", "")), ), reverse=True, ) top_rows = [row for _score, row in rows[:8]] top_names = [self._router_display_name(row, str(row.get("model_key") or "")) for row in top_rows] result_lines = [ "The internal router catalog currently documents these routers with 5G SA support: " + ", ".join([f"`{name}`" for name in top_names[:6]]) + ".", "", ] for row in top_rows[:6]: model_name = self._router_display_name(row, str(row.get("model_key") or "")) manufacturer = _norm(row.get("manufacturer", "")) use_case = _norm(row.get("primary_use_case", "")) or "use case not listed" modem = _norm(row.get("modem", "")) or "5G support listed" result_lines.append( f"- `{model_name}`: {manufacturer + ' ' if manufacturer else ''}{use_case}; modem field says {modem}." ) sources = [ { "id": f"RSA{idx}", "domain": "router_docs", "doc": str(row.get("source_doc") or "feb2026routers.csv"), "relative_path": str(row.get("source_doc") or "feb2026routers.csv"), "chunk_id": f"router_5gsa:{_compact_model(row.get('model_key', '') or row.get('model', ''))}:{idx}", "location": "", "excerpt": ( f"{self._router_display_name(row, str(row.get('model_key') or ''))}: modem={_norm(row.get('modem', '')) or 'Not listed'}; " f"primary_use_case={_norm(row.get('primary_use_case', '')) or 'Not listed'}." ), "score": 0.97, } for idx, row in enumerate(top_rows[:6], start=1) ] return { "assistant": _format_shell( "\n".join(result_lines), [ "Filtered internal router catalog rows to models whose modem field explicitly says 5G SA or standalone.", "Returned router-class entries only and left service-plan rows out of the shortlist.", ], [ "Ask for `best 5G SA fit for branch`, `vehicle`, or `outdoor fixed` to narrow the list by deployment.", "Ask for `include WAN/LAN, Wi-Fi, and antenna notes` if you want a deeper shortlist summary.", ], ), "sources": sources, "files": ["feb2026routers.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_5g_sa_shortlist_fast", "web_assisted": False}, } if self._router_is_branch_primary_5g_recommendation_query(message): target_n = self._router_shortlist_count_hint(message, default=2, max_items=5) rows: List[Tuple[int, Dict[str, Any]]] = [] for row in unique_rows: device_type = _norm(row.get("device_type", "")).lower() if device_type and ("adapter" in device_type) and ("router" not in device_type): continue modem = _norm(row.get("modem", "")) modem_low = modem.lower() if "5g" not in modem_low: continue use_case = _norm(row.get("primary_use_case", "")) use_case_low = use_case.lower() score = 0 if any(token in use_case_low for token in ("branch", "branch office", "indoor branch")): score += 5 elif any(token in use_case_low for token in ("fixed", "indoor", "office", "retail")): score += 3 elif any(token in use_case_low for token in ("vehicle", "mobile", "patrol", "police")): score -= 2 elif "industrial" in use_case_low: score -= 1 if ("sa" in modem_low) or ("standalone" in modem_low): score += 2 if _norm(row.get("wan_lan", "")): score += 1 if _norm(row.get("wifi", "")): score += 1 if score <= 0: continue rows.append((score, row)) if not rows: return None rows.sort( key=lambda item: ( item[0], _compact_model(item[1].get("model", "") or item[1].get("model_key", "")), ), reverse=True, ) top_rows = [row for _score, row in rows[: max(1, target_n)]] top_names = [self._router_display_name(row, str(row.get("model_key") or "")) for row in top_rows] def _branch_tradeoff_note(row: Dict[str, Any]) -> str: use_case = _norm(row.get("primary_use_case", "")).lower() rugged = _norm(row.get("ruggedization", "")).lower() if any(token in use_case for token in ("branch", "branch office", "indoor branch")): if not rugged or "not" in rugged or "unclear" in rugged: return "Main tradeoff: branch fit is strong, but ruggedization is not explicit." return "Main tradeoff: branch fit is strong, but confirm whether you need a hardened enclosure or mobile-rated features." if any(token in use_case for token in ("fixed", "indoor", "office", "retail")): return "Main tradeoff: the catalog wording is indoor/fixed-site oriented rather than branch-specific." if any(token in use_case for token in ("vehicle", "mobile", "patrol", "police")): return "Main tradeoff: this is better suited to mobile use than a fixed indoor branch." if "industrial" in use_case: return "Main tradeoff: industrial fit is less branch-centric." return "Main tradeoff: branch fit is inferred from modem and port signals rather than explicit use-case wording." result_lines = [ "For a small branch using primary 5G, the strongest internal fits are " + ", ".join([f"`{name}`" for name in top_names]) + ".", "They score highest on documented 5G support plus branch or fixed-site fit signals in the internal router catalog.", "", ] for row in top_rows: model_name = self._router_display_name(row, str(row.get("model_key") or "")) use_case = _norm(row.get("primary_use_case", "")) or "use case not listed" modem = _norm(row.get("modem", "")) or "5G support listed" wan_lan = _norm(row.get("wan_lan", "")) or "WAN/LAN detail not listed" wifi = _norm(row.get("wifi", "")) or "Wi-Fi detail not listed" result_lines.append( f"- `{model_name}`: {use_case}; modem field says {modem}; {wan_lan}; {wifi}. {_branch_tradeoff_note(row)}" ) sources = [ { "id": f"RBR{idx}", "domain": "router_docs", "doc": str(row.get("source_doc") or "feb2026routers.csv"), "relative_path": str(row.get("source_doc") or "feb2026routers.csv"), "chunk_id": f"branch_primary_5g:{_compact_model(row.get('model_key', '') or row.get('model', ''))}:{idx}", "location": "", "excerpt": ( f"{self._router_display_name(row, str(row.get('model_key') or ''))}: modem={_norm(row.get('modem', '')) or 'Not listed'}; " f"primary_use_case={_norm(row.get('primary_use_case', '')) or 'Not listed'}; " f"wan_lan={_norm(row.get('wan_lan', '')) or 'Not listed'}; wifi={_norm(row.get('wifi', '')) or 'Not listed'}." ), "score": 0.97, } for idx, row in enumerate(top_rows, start=1) ] return { "assistant": _format_shell( "\n".join(result_lines), [ "Ranked internal catalog rows for branch and fixed-site wording, documented 5G support, and visible LAN/Wi-Fi fit signals.", "This is a shortlist, not a full design recommendation, because failover, policy, and carrier constraints are not always explicit in the catalog.", ], [ "Share user count, indoor vs outdoor placement, and required Ethernet/Wi-Fi needs if you want the shortlist narrowed further.", "Ask for `compare the top picks` if you want a field-by-field table next.", ], ), "sources": sources, "files": ["feb2026routers.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_branch_primary_5g_shortlist_fast", "web_assisted": False}, } return None def _router_requirement_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: if not self._is_router_requirement_query(message): return None low = str(message or "").lower() requires_sa = ("5g sa" in low) or ("standalone" in low) rugged_requested = bool(re.search(r"\brugged(?:ization)?\b", low)) rows: List[Tuple[int, Dict[str, Any], str, bool]] = [] seen_models: set[str] = set() def _battery_requirement_summary(value: Any) -> str: text = _norm(value) if not text: return "Not listed (abstained)" low_text = text.lower() if low_text in {"none", "no", "n/a", "na", "not listed"}: return "Not listed (abstained)" if "internal" in low_text: return "internal battery" if "optional" in low_text: return "optional battery" if "removable" in low_text: return "removable battery" if "backup" in low_text: return "backup battery" return "battery present" def _router_requirement_doc_evidence(model_name: str) -> List[Dict[str, str]]: cache = getattr(self, "_router_requirement_doc_cache", None) if cache is None: cache = {} try: setattr(self, "_router_requirement_doc_cache", cache) except Exception: cache = {} cache_key = _compact_model(model_name) or _norm(model_name).lower() cached = cache.get(cache_key) if cached is not None: return [dict(item) for item in cached] router_idx = getattr(getattr(self, "router_rag_core", None), "index", None) if router_idx is None or not hasattr(router_idx, "search"): return [] hits: List[Dict[str, Any]] = [] query = f"{model_name} battery ruggedization WAN LAN Wi-Fi antennas power dimensions" try: hits.extend(self._router_index_search_hits(query, k=2)) except Exception: hits = [] evidence: List[Dict[str, str]] = [] seen: set[Tuple[str, str, str]] = set() for hit in hits: doc = Path(str(hit.get("doc") or hit.get("file_name") or "")).name rel = str(hit.get("relative_path") or hit.get("rel") or "") text = _norm(str(hit.get("text") or hit.get("excerpt") or "")) if not doc or not text: continue key = (doc.lower(), rel.lower(), text[:120].lower()) if key in seen: continue seen.add(key) sentences = [x.strip() for x in re.split(r"(?<=[.!?])\s+|\n+", text) if x.strip()] chosen = "" for sentence in sentences: low_sentence = sentence.lower() if any( token in low_sentence for token in ( "battery", "rugged", "ip", "wan", "lan", "ethernet", "wifi", "wi-fi", "antenna", "power", "dimension", ) ): chosen = sentence break if not chosen: chosen = sentences[0] if sentences else text evidence.append( { "doc": doc, "relative_path": rel or doc, "excerpt": _truncate(_norm(chosen), 180), } ) if len(evidence) >= 2: break try: cache[cache_key] = [dict(item) for item in evidence] except Exception: pass return evidence for row in self._router_fact_rows.values(): model_name = _norm(row.get("model", "")) if not model_name: continue model_key = _compact_model(model_name) if not model_key or model_key in seen_models: continue seen_models.add(model_key) modem = _norm(row.get("modem", "")) battery_raw = _norm(row.get("battery", "")) battery = _battery_requirement_summary(battery_raw) rugged = _norm(row.get("ruggedization", "")) use_case = _norm(row.get("primary_use_case", "")) wan_lan = _norm(row.get("wan_lan", "")) wifi = _norm(row.get("wifi", "")) modem_low = modem.lower() battery_low = battery_raw.lower() rugged_low = rugged.lower() has_battery = bool(battery) and (battery_low not in {"none", "n/a", "na", "not listed"}) has_5g = "5g" in modem_low has_sa = _modem_mentions_5g_sa(modem) rugged_ok = bool( re.search(r"\bip\s*(?:[4-9]\d|[1-9]\d{2,})\b", rugged_low) or any(x in rugged_low for x in ("rugged", "hardened", "mil-std", "mil std")) ) if not (has_battery and has_5g): continue score = 0 if has_battery: score += 3 if has_5g: score += 3 if rugged_ok: score += 5 if rugged_requested else 2 elif rugged_requested and rugged: score -= 1 elif rugged_requested: score -= 4 if has_sa: score += 3 elif requires_sa: score -= 3 notes: List[str] = [] if has_sa: notes.append("5G SA documented") elif has_5g: notes.append("5G documented; SA not explicit") if rugged_ok: notes.append("explicit ruggedization signal present") elif rugged_requested and rugged: notes.append("ruggedization listed but not explicit") elif rugged: notes.append("ruggedization listed but limited") else: notes.append("ruggedization not listed") if use_case: notes.append(f"use case: {use_case}") if wan_lan: notes.append(f"WAN/LAN: {wan_lan}") if wifi: notes.append(f"Wi-Fi: {wifi}") rows.append((score, row, "; ".join(notes), rugged_ok)) if not rows: return None rows.sort(key=lambda x: x[0], reverse=True) strict_rugged_rows = [item for item in rows if item[3]] fallback_rows = [item for item in rows if not item[3]] top_rows = ((strict_rugged_rows + fallback_rows) if strict_rugged_rows else rows)[:4] best = top_rows[0] best_model_name = str(best[1].get("model") or "Unknown") no_fully_rugged = not strict_rugged_rows lines = [ ( "Best-fit routers for requirement: battery + ruggedization + 5G SA" if strict_rugged_rows else "Closest current router matches for requirement: battery + ruggedization + 5G SA" ), "", ( f"Best potential fit from internal docs: **{best_model_name}**." if strict_rugged_rows else f"No current catalog row is explicitly ruggedized/IP-rated, so the closest battery + 5G SA match from internal docs is **{best_model_name}**." ), "", "| Rank | Router | Modem type | Battery | Ruggedization | WAN/LAN | Wi-Fi | Fit notes |", "| ---: | --- | --- | --- | --- | --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] doc_files: List[str] = [] for idx, (_score, row, fit_note, _rugged_ok) in enumerate(top_rows, start=1): model_name = self._router_display_name(row, "") modem = _norm(row.get("modem", "")) or "Not listed (abstained)" battery = _battery_requirement_summary(row.get("battery", "")) rugged = _norm(row.get("ruggedization", "")) or "Not listed (abstained)" use_case = _norm(row.get("primary_use_case", "")) or "Not listed (abstained)" wan_lan = _norm(row.get("wan_lan", "")) or "Not listed (abstained)" wifi = _norm(row.get("wifi", "")) or "Not listed (abstained)" if no_fully_rugged and rugged and "not explicitly ruggedized" not in rugged.lower(): rugged = f"{rugged} (not explicitly ruggedized)" lines.append( f"| {idx} | {_md_cell(model_name)} | {_md_cell(modem)} | " f"{_md_cell(battery)} | {_md_cell(rugged)} | {_md_cell(use_case)} | {_md_cell(wan_lan)} | {_md_cell(wifi)} | {_md_cell(fit_note)} |" ) doc_evidence = _router_requirement_doc_evidence(model_name) sources.append( { "id": f"RR{idx}", "domain": "router_docs", "doc": str(row.get("source_doc") or "feb2026routers.csv"), "relative_path": str(row.get("source_doc") or "feb2026routers.csv"), "chunk_id": f"row:{model_name}", "location": "", "excerpt": ( f"{model_name}: modem={modem}; battery={battery}; ruggedization={rugged}; " f"wan_lan={wan_lan}; wifi={wifi}; use_case={use_case}." ), "score": 0.98, } ) for doc_idx, doc_hit in enumerate(doc_evidence, start=1): doc_name = str(doc_hit.get("doc") or "").strip() if doc_name: doc_files.append(doc_name) sources.append( { "id": f"RR{idx}D{doc_idx}", "domain": "router_docs", "doc": doc_name or str(row.get("source_doc") or "feb2026routers.csv"), "relative_path": str(doc_hit.get("relative_path") or doc_name or row.get("source_doc") or "feb2026routers.csv"), "chunk_id": f"row_doc:{model_name}:{doc_idx}", "location": "", "excerpt": f"{model_name}: {doc_hit.get('excerpt') or 'documented evidence available in router docs.'}", "score": 0.9, } ) if sources: lines.extend(["", "Source anchors:"]) for source in sources[:4]: doc = _norm(source.get("doc") or "") excerpt = _norm(source.get("excerpt") or "") anchor = doc or "router_docs" if excerpt: lines.append(f"- `{source.get('id')}` {anchor}: {excerpt}") else: lines.append(f"- `{source.get('id')}` {anchor}") why = [ "Filtered from internal router catalog fields to match the explicit requirement set.", "Prioritized rows with explicit 5G SA wording, battery presence, and ruggedization indicators.", ] if no_fully_rugged: why.append( "No current catalog row is explicitly ruggedized/IP-rated, so the shortlist below is a closest-match fallback rather than a strict ruggedized fit." ) if requires_sa: why.append("Rows with 5G but no explicit SA wording are retained lower-ranked and labeled.") next_action = [ "If you want only explicit SA + IP-rated models, say `strict SA + IP only`.", "Ask `include WAN/LAN + antenna connectors` to append install-fit specs.", ] if sources: why.append("Source anchors are included for each ranked row so you can trace the shortlist back to the catalog and supporting docs.") return { "assistant": _format_shell("\n".join(lines), why, next_action), "sources": sources[:8], "files": list(dict.fromkeys(["feb2026routers.csv", *doc_files])), "meta": {"domain": "router_docs", "retrieval_mode": "router_requirement_fast", "web_assisted": False}, } def _router_battery_options_fast_answer(self, message: str, *, raw_message: str = "") -> Optional[Dict[str, Any]]: low = str(message or "").lower() if not any(x in low for x in ("battery", "batteries", "bateries", "backup power", "backup battery")): return None raw_low = str(raw_message or message or "").lower() typo_battery = ("bateries" in low) or ("bateries" in raw_low) asks_list = any( x in low for x in ( "router", "routers", "device", "devices", "options", "list", "show me", "give me", "which", "several", "few", ) ) if not asks_list: return None asks_current = "current" in low def _battery_class(value: str) -> str: low_bat = value.lower() if "removable" in low_bat: return "removable" if "internal" in low_bat: return "internal" if "optional" in low_bat: return "optional" return "other" def _battery_currentness(row: Dict[str, Any], model: str, model_key: str) -> str: current_flag = row.get("current_recommendable_flag") if isinstance(current_flag, bool): return "Current" if current_flag else "Legacy" for candidate in ( _norm(row.get("model", "")), _norm(row.get("sku", "")), model, model_key, ): life_key = self._lookup_router_lifecycle_key_relaxed(candidate) or self._lookup_router_lifecycle_key(candidate) life = _as_dict(self._router_lifecycle_rows.get(life_key or "", {})) if life_key else {} status = _norm(life.get("status", "")).lower() if not status: continue if any(token in status for token in ("current", "active")): return "Current" if any(token in status for token in ("end of sale", "end of life", "legacy", "retired", "discontinued")): return "Legacy" return "Currentness not confirmed" rows: List[Tuple[str, str, str, str, str, str]] = [] seen: set[str] = set() for model_key, row in self._router_fact_rows.items(): model = self._router_display_name(row, model_key) if not model: continue sku = _norm(row.get("sku", "")) model_compact = _compact_model(model) sku_compact = _compact_model(sku) if ( model_compact and sku_compact and ("LITE" in sku_compact) and ("LITE" not in model_compact) and (model_compact in sku_compact) ): # Keep familiar naming for Lite variants when row model is normalized to the base key. model = f"{model}-Lite" mk = _compact_model(model) if mk in seen: continue battery = _norm(row.get("battery", "")) if (not battery) or battery.lower() in {"none", "n/a", "na", "not listed"}: continue seen.add(mk) modem = _norm(row.get("modem", "")) or "Not listed" rugged = _norm(row.get("ruggedization", "")) or "Not listed" rows.append((model, battery, modem, rugged, _battery_class(battery), _battery_currentness(row, model, model_key))) if not rows: return None rows.sort( key=lambda x: ( 0 if (x[5] == "Current") else (1 if x[5] == "Currentness not confirmed" else 2), 0 if "5g" in x[2].lower() else 1, 0 if x[4] != "optional" else 1, x[0], ) ) ordered_rows = ([entry for entry in rows if entry[5] == "Current"] + [entry for entry in rows if entry[5] != "Current"]) if asks_current else rows top: List[Tuple[str, str, str, str, str, str]] = ordered_rows[:4] top_keys = {_compact_model(model) for model, *_ in top} removable = next((entry for entry in ordered_rows if (entry[4] == "removable") and (_compact_model(entry[0]) not in top_keys)), None) if removable is not None: top.append(removable) if any(x in low for x in ("short", "txt", "text", "sms")): compact = "; ".join([f"{m} ({b})" for m, b, _, _, _, _ in top]) return { "assistant": _format_shell( f"Battery-capable router options: {compact}.", [ "Pulled from internal router catalog battery fields.", ], [ "Ask `show as table` if you want modem/ruggedization columns too.", ], ), "sources": [ { "id": "RB1", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "battery_shortlist", "location": "", "excerpt": "Battery-related router rows from internal catalog.", "score": 0.98, } ], "files": ["feb2026routers.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_battery_short_fast", "web_assisted": False}, } lines = [ "Battery-capable router options (internal documented fields):", "", "| Router | Battery | Modem | Ruggedization | Current status |", "| --- | --- | --- | --- | --- |", ] for model, battery, modem, rugged, _, currentness in top: lines.append(f"| {_md_cell(model)} | {_md_cell(battery)} | {_md_cell(modem)} | {_md_cell(rugged)} | {_md_cell(currentness)} |") if asks_current: current_count = sum(1 for *_, currentness in top if currentness == "Current") lines.extend( [ "", *( ["Interpreted `bateries` as `battery` and kept the list on documented battery-capable rows only."] if typo_battery else [] ), ( f"Confirmed current battery-capable rows in this slice: {current_count}." if current_count else "No battery-capable row in this slice had explicit current confirmation." ), "Rows marked `Currentness not confirmed` stay in the table only as battery-capable candidates, not as confirmed current recommendations.", ] ) return { "assistant": _format_shell( "\n".join(lines), [ "Selected from internal battery fields only; no invented hardware claims.", "Current status is only marked when the internal lifecycle/workbook flags support it; otherwise the row stays unconfirmed.", ], [ "Ask for `best fit by vehicle/fixed/industrial` to narrow this list.", ], ), "sources": [ { "id": "RB1", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "battery_shortlist", "location": "", "excerpt": "Battery, modem, and ruggedization fields used for shortlist rows.", "score": 0.99, } ], "files": ["feb2026routers.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_battery_options_fast", "web_assisted": False}, } def _router_wifi_by_vendor_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: low = str(message or "").lower() asks_wifi7 = bool(re.search(r"\bwi[\- ]?fi\s*7\b", low)) asks_router_scope = any( x in low for x in ("router", "routers", "ericsson", "cradlepoint", "peplink", "semtech", "atel", "digi", "inhand", "inseego") ) if not (asks_wifi7 and asks_router_scope): return None vendor_patterns: Dict[str, Tuple[str, ...]] = { "Ericsson Cradlepoint": ("ericsson", "cradlepoint"), "Peplink": ("peplink", "pepwave"), "Semtech": ("semtech", "sierra"), "ATEL": ("atel",), "Digi": ("digi",), "InHand Networks": ("inhand",), "Inseego": ("inseego",), } requested_vendor = "" requested_terms: Tuple[str, ...] = () for label, pats in vendor_patterns.items(): if any(p in low for p in pats): requested_vendor = label requested_terms = pats break rows: List[Dict[str, Any]] = [] seen: set[str] = set() for row in self._router_fact_rows.values(): wifi = _norm(row.get("wifi", "")) if not re.search(r"\bwi[\- ]?fi\s*7\b", wifi.lower()): continue model = self._router_display_name(row, str(row.get("model_key") or "")) mk = _compact_model(model) if (not mk) or (mk in seen): continue manufacturer = _norm(row.get("manufacturer", "")) if requested_terms: blob = f"{manufacturer} {model}".lower() if not any(t in blob for t in requested_terms): continue seen.add(mk) rows.append( { "model": model, "manufacturer": manufacturer or "Not listed", "wifi": wifi, "modem": _norm(row.get("modem", "")) or "Not listed", } ) # Fill potential catalog gaps from internal router-doc excerpts (for newer models not yet in CSV rows). router_idx = getattr(getattr(self, "router_rag_core", None), "index", None) if router_idx is not None and hasattr(router_idx, "search"): vendor_query = requested_vendor or "router" hits = self._router_index_search_hits(f"{vendor_query} Wi-Fi 7 router data sheet", k=10) for h in hits: text_blob = _norm(str(h.get("text") or "")).lower() if not re.search(r"\bwi[\- ]?fi\s*7\b", text_blob): continue doc_name = Path(str(h.get("doc") or "")).name doc_low = doc_name.lower() if requested_terms and (not any(t in doc_low or t in text_blob for t in requested_terms)): continue model_guess = "" m = re.search(r"\b([A-Z]{1,4}\d{2,4}[A-Z0-9\-]*)\b", doc_name.upper()) if m: model_guess = _norm(m.group(1)) if not model_guess: continue mk = _compact_model(model_guess) if (not mk) or (mk in seen): continue seen.add(mk) rows.append( { "model": model_guess, "manufacturer": requested_vendor or "Not listed", "wifi": "Wi-Fi 7 (internal doc excerpt)", "modem": "Not listed (doc excerpt path)", } ) if not rows: return None rows.sort(key=lambda x: (x["manufacturer"].lower(), x["model"].lower())) lines = [ ( f"{requested_vendor} routers with documented Wi-Fi 7 in internal catalog:" if requested_vendor else "Routers with documented Wi-Fi 7 in internal catalog:" ), "", "| Router | Manufacturer | Wi-Fi | Modem |", "| --- | --- | --- | --- |", ] for row in rows[:12]: lines.append( f"| {_md_cell(row['model'])} | {_md_cell(row['manufacturer'])} | {_md_cell(row['wifi'])} | {_md_cell(row['modem'])} |" ) why_line = ( f"Filtered to vendor `{requested_vendor}` and Wi-Fi 7 rows from `feb2026routers.csv`." if requested_vendor else "Filtered to Wi-Fi 7 rows from `feb2026routers.csv`." ) return { "assistant": _format_shell( "\n".join(lines), [why_line], ["Ask `compare vs ` for side-by-side documented specs."], ), "sources": [ { "id": "RWF1", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "wifi7_vendor_filter", "location": "", "excerpt": "Deterministic filter over Wi-Fi type and manufacturer/model fields.", "score": 1.0, } ], "files": ["feb2026routers.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_wifi_vendor_fast", "web_assisted": False}, } def _router_concept_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) if self._query_prefers_authoritative_evidence(message, "router_docs"): return None shared_fast = self._deterministic_concept_fast_answer(message, "router_docs") if shared_fast: return shared_fast asks_failover_compare = bool( ("failover" in low) and ("cellular" in low) and ("wired" in low) and any( token in low for token in ( "difference", "compare", "comparison", "versus", " vs ", "plain english", "in plain english", ) ) ) asks_cellular_failover_basics = bool( ("cellular failover" in low) and any( token in low for token in ( "what is", "what's", "explain", "plain english", "in plain english", ) ) ) if asks_failover_compare: lines = [ "Cellular failover vs wired failover for a branch router:", "", "| Topic | Cellular failover | Wired failover |", "| --- | --- | --- |", "| Backup path | Uses a cellular link when the primary wired circuit is unhealthy or down. | Uses a second wired WAN or wired provider path when the primary wired circuit is unhealthy or down. |", "| Typical use | Branch resiliency when a wired backup is unavailable, slow to install, or too expensive for the site. | Branch resiliency when the site can justify dual wired paths for lower recurring risk and more predictable performance. |", "| Performance expectation | Better continuity than no backup, but outcome still depends on signal quality, carrier conditions, and outage traffic load. | Usually steadier for large outage traffic loads if the secondary wired path is truly diverse and properly sized. |", "| Qualification note | Validate signal, antenna path, hold timers, and what must stay up during an outage. | Validate circuit diversity, failover policy, and what must stay up during an outage. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Internal branch-backup guidance treats failover design as a traffic-profile and resilience decision, not a headline-speed decision.", "Use cellular failover when fast deployment and practical survivability matter more than having a second wired circuit everywhere.", ], [ "Next step: confirm outage traffic load, acceptable failover behavior, and whether the site can support a diverse wired backup circuit.", ], ), "sources": [ { "id": "RCFAIL1", "domain": "router_docs", "doc": "FAQ_master_updated.csv", "relative_path": "docs/faq/FAQ_master_updated.csv", "chunk_id": "faq:branch_backup_baseline", "location": "", "excerpt": "Branch-backup FAQ guidance focuses on cellular failover behind the main firewall with health checks and hold timers, so fit depends on outage traffic profile rather than headline radio generation alone.", "score": 0.95, }, { "id": "RCFAIL2", "domain": "router_docs", "doc": "InHand Networks-FWA02-Manual-1.pdf", "relative_path": "/router_rag_files/01_documents/routers/inhand_networks/InHand%20Networks-FWA02-Manual-1.pdf", "chunk_id": "InHand-Networks-FWA02-Manual-1__chunk_0002", "location": "", "excerpt": "Internal 5G FWA excerpt positions wired/cellular failover for branch-style deployments.", "score": 0.9, }, ], "files": [ "docs/faq/FAQ_master_updated.csv", "/router_rag_files/01_documents/routers/inhand_networks/InHand%20Networks-FWA02-Manual-1.pdf", ], "meta": {"domain": "router_docs", "retrieval_mode": "router_failover_concept_fast", "web_assisted": False}, } if asks_cellular_failover_basics: return { "assistant": _format_shell( "Cellular failover means the router keeps a cellular path ready so traffic can switch there if the primary wired connection fails or health checks say it is no longer usable.", [ "Rep-safe framing: this is a survivability pattern for branch continuity, not a promise of zero interruption.", "Internal branch-backup guidance says the right design depends on outage traffic load, coverage confidence, and failover timer policy.", ], [ "Next step: qualify what must stay up during an outage and how much traffic the backup path really needs to carry.", ], ), "sources": [ { "id": "RCFAIL3", "domain": "router_docs", "doc": "FAQ_master_updated.csv", "relative_path": "docs/faq/FAQ_master_updated.csv", "chunk_id": "faq:branch_backup_baseline", "location": "", "excerpt": "Branch-backup FAQ guidance focuses on cellular failover behind the main firewall with health checks and hold timers.", "score": 0.95, } ], "files": ["docs/faq/FAQ_master_updated.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_failover_concept_fast", "web_assisted": False}, } if "network slicing" in low: return { "assistant": _format_shell( "Network slicing is a 5G feature that creates virtual network slices with different policy/traffic targets on the same physical network.", [ "Rep-safe framing: slicing matters when the carrier service maps apps/devices to a slice; router hardware alone does not force slice assignment.", "Use this as a concept explanation first, then validate slice availability in the target deployment area.", ], [ "Ask `which of our listed models mention slicing-related features` for model-level evidence.", ], ), "sources": [ { "id": "RCNS1", "domain": "router_docs", "doc": "FAQ_master_updated.csv", "relative_path": "docs/faq/FAQ_master_updated.csv", "chunk_id": "faq:network_slicing", "location": "", "excerpt": "Network slicing is a 5G feature that carves a physical network into virtual slices with different policy targets.", "score": 0.96, } ], "files": ["docs/faq/FAQ_master_updated.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_network_slicing_concept_fast", "web_assisted": False}, } if any(x in low for x in ("difference between 5g sa and 5g nsa", "5g sa and 5g nsa", "sa vs nsa")): lines = [ "5G SA vs 5G NSA (rep-ready):", "", "| Topic | 5G SA | 5G NSA |", "| --- | --- | --- |", "| Core network | Uses 5G core end-to-end | Uses 5G radio with 4G/LTE core dependency |", "| Typical positioning | Lower-latency/advanced-network features when deployed | Transitional 5G coverage/performance path |", "| Sales guidance | Confirm local deployment + device/plan support before claiming SA benefits | Position as current-state 5G path where SA is not fully available |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Concept explanation only; final behavior depends on deployed network and documented model support.", ], [ "Ask `which of our listed models explicitly mention SA` for model-specific evidence.", ], ), "sources": [ { "id": "RC1", "domain": "router_docs", "doc": "FAQ_master_updated.csv", "relative_path": "docs/faq/FAQ_master_updated.csv", "chunk_id": "faq:5g_sa_vs_nsa", "location": "", "excerpt": "Internal FAQ concept guidance for SA vs NSA framing.", "score": 0.94, } ], "files": ["docs/faq/FAQ_master_updated.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_5g_sa_nsa_concept_fast", "web_assisted": False}, } if ( ( (("cat 4" in low) or ("cat4" in low) or ("lte cat 4" in low)) and ("5g" in low) and any(x in low for x in ("difference", "practical difference", "branch backup", "backup")) ) or (("4g router" in low) and ("instead of a 5g" in low or "instead of 5g" in low)) or ( ("4g" in low) and ("5g" in low) and any( x in low for x in ( "difference between 4g and 5g", "difference between 5g and 4g", "4g vs 5g", "5g vs 4g", "4g versus 5g", "5g versus 4g", "what is the difference between 4g and 5g", "what's the difference between 4g and 5g", ) ) ) ): lines = [ "CAT 4 LTE vs 5G for branch backup:", "", "| Topic | CAT 4 LTE backup | 5G backup |", "| --- | --- | --- |", "| Performance headroom | Internal examples show CAT 4 LTE models such as V810AD and RUT241 in the 150/50 Mbps class. | Internal 5G FWA examples call out materially higher throughput and position 5G as the higher-performance path. |", "| Best fit | Light branch backup where the outage traffic profile is modest and cost simplicity matters. | Branch backup where you want more growth headroom or expect heavier failover traffic. |", "| Qualification note | Confirm LTE coverage and whether backup traffic is truly light. | Confirm 5G coverage stability and whether the extra headroom is worth the added cost/complexity. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "This is a practical positioning summary from internal examples, not a promise of site-specific throughput.", "For branch backup, the decision is mostly about traffic headroom, coverage confidence, and budget discipline.", ], [ "Ask `give me a 4G vs 5G qualification checklist` for a discovery-call version.", ], ), "sources": [ { "id": "RC45G1", "domain": "router_docs", "doc": "FAQ_master_updated.csv", "relative_path": "docs/faq/FAQ_master_updated.csv", "chunk_id": "faq:cat4_examples", "location": "", "excerpt": "Internal FAQ examples describe V810AD and RUT241 as 4G LTE Cat 4 devices in the 150/50 Mbps class.", "score": 0.95, }, { "id": "RC45G2", "domain": "router_docs", "doc": "InHand Networks-FWA02-Manual-1.pdf", "relative_path": "/router_rag_files/01_documents/routers/inhand_networks/InHand%20Networks-FWA02-Manual-1.pdf", "chunk_id": "InHand-Networks-FWA02-Manual-1__chunk_0002", "location": "", "excerpt": "Internal 5G FWA excerpt positions 5G as high-performance connectivity with SA/NSA support, LTE Cat 19 compatibility, and wired/cellular failover for branch-style deployments.", "score": 0.93, }, { "id": "RC45G3", "domain": "router_docs", "doc": "FAQ_master_updated.csv", "relative_path": "docs/faq/FAQ_master_updated.csv", "chunk_id": "faq:branch_backup_baseline", "location": "", "excerpt": "Branch-backup FAQ guidance focuses on cellular failover behind the main firewall with health checks and hold timers, so fit depends on outage traffic profile rather than headline radio generation alone.", "score": 0.91, }, ], "files": [ "docs/faq/FAQ_master_updated.csv", "/router_rag_files/01_documents/routers/inhand_networks/InHand%20Networks-FWA02-Manual-1.pdf", ], "meta": {"domain": "router_docs", "retrieval_mode": "router_4g_vs_5g_positioning_fast", "web_assisted": False}, } if ("what does poe input" in low) or ("poe input matter" in low): return { "assistant": _format_shell( "PoE input matters because it is a model-level capability that affects whether the planned install can deliver power over Ethernet instead of relying on a separate local power method.", [ "Our internal normalized router catalog tracks PoE alongside modem type, Wi-Fi, ruggedization, and device type so reps can confirm install fit before quoting.", ], [ "Next step: verify the selected model's documented PoE capability before assuming a single-cable power design.", ], ), "sources": [ { "id": "RPOE1", "domain": "router_docs", "doc": "router_pricing_catalog_normalized.csv", "relative_path": "backend/app/knowledgebase/data/normalized/router_pricing_catalog_normalized.csv", "chunk_id": "normalized:poe_field", "location": "", "excerpt": "Normalized router catalog includes a dedicated PoE field alongside modem type, Wi-Fi, ruggedization, and device type for model-level comparisons.", "score": 0.9, } ], "files": ["backend/app/knowledgebase/data/normalized/router_pricing_catalog_normalized.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_poe_concept_fast", "web_assisted": False}, } if ("esim support" in low) or ("what does esim" in low) or ("what does e sim" in low): return { "assistant": _format_shell( "eSIM changes how the line is provisioned: the profile is downloaded to a built-in chip instead of mailing and inserting a removable SIM card.", [ "Internal FAQ guidance says eSIM is handy when shipping devices or switching profiles without mailing cards.", "The same FAQ set also notes that activation flows may use EID along with ICCID/IMEI when eSIM is involved.", ], [ "Validate exact device and carrier support before promising eSIM activation on a quoted model.", ], ), "sources": [ { "id": "RESIM1", "domain": "router_docs", "doc": "FAQ_master_updated.csv", "relative_path": "docs/faq/FAQ_master_updated.csv", "chunk_id": "faq:esim_definition", "location": "", "excerpt": "A physical SIM is removable; an eSIM is a built-in chip provisioned remotely with an activation profile and is useful when shipping devices or switching profiles without mailing cards.", "score": 0.96, }, { "id": "RESIM2", "domain": "router_docs", "doc": "FAQ_200_ansers_set_3.csv", "relative_path": "docs/faq/FAQ_200_ansers_set_3.csv", "chunk_id": "faq:eid_iccid_imei", "location": "", "excerpt": "Internal FAQ guidance says ICCID identifies the SIM, IMEI identifies the modem/device, and EID identifies the eSIM chip used to download profiles.", "score": 0.92, }, ], "files": ["docs/faq/FAQ_master_updated.csv", "docs/faq/FAQ_200_ansers_set_3.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_esim_concept_fast", "web_assisted": False}, } if ("wan vs lan" in low) or ("describe wan vs lan" in low): return { "assistant": _format_shell( "WAN is the router's upstream internet/provider side; LAN is the local side that connects onsite devices.", [ "Rep shorthand: WAN = outside/world, LAN = inside/site network.", "When quoting, confirm how many WAN/LAN ports are needed for failover, segmentation, and onsite device count.", ], [ "Ask `give me a non-technical WAN/LAN script` for customer-facing wording.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "router_wan_lan_concept_fast", "web_assisted": False}, } if ("rj11" in low) and ("replacement" in low) and any(x in low for x in ("use case", "use cases", "summary")): lines = [ "RJ11-driven replacement scenarios:", "", "| Use case | Why RJ11 matters in design |", "| --- | --- |", "| Legacy analog endpoints | The endpoint still expects an analog handoff rather than an Ethernet-only handoff. |", "| Fire, elevator, fax, alarm, or gate lines | These are the endpoint categories called out in internal POTS intake guidance before recommendation and cutover planning. |", "| Business phone adapters | Internal catalog rows such as BPC100 explicitly document RJ11/RJ31 when analog voice hardware is part of the design. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Treat RJ11 as an endpoint/interface requirement, not a router lifecycle model token.", ], [ "Ask `build endpoint intake checklist` to capture fax/alarm/elevator requirements per site.", ], ), "sources": [ { "id": "RRJ11-1", "domain": "router_docs", "doc": "pots_top100_questions_draft.md", "relative_path": "backend/app/pots_ai/data/pots_top100_questions_draft.md", "chunk_id": "pots:q1_endpoint_inventory", "location": "", "excerpt": "Next step: confirm active line inventory by use case (fire, elevator, fax, alarms, gates).", "score": 0.94, }, { "id": "RRJ11-2", "domain": "router_docs", "doc": "router_pricing_catalog_normalized.csv", "relative_path": "backend/app/knowledgebase/data/normalized/router_pricing_catalog_normalized.csv", "chunk_id": "normalized:bpc100_rj11", "location": "", "excerpt": "BPC100 Business Phone Connect ... RJ11, RJ31, RJ45 Ethernet ... 3 AA Battery backup.", "score": 0.9, }, ], "files": [ "backend/app/pots_ai/data/pots_top100_questions_draft.md", "backend/app/knowledgebase/data/normalized/router_pricing_catalog_normalized.csv", ], "meta": {"domain": "router_docs", "retrieval_mode": "router_rj11_use_case_fast", "web_assisted": False}, } if ("dual sim" in low or "dual-sim" in low) and ("failover" in low): lines = [ "Dual SIM failover (plain-language customer script):", "", "| What it means | Customer-friendly wording |", "| --- | --- |", "| Two SIM options are configured | `The router has a primary SIM and a backup SIM available.` |", "| Automatic switch on outage/degradation | `If the primary connection fails, it can switch to the backup to keep service running.` |", "| Redundancy design choice | `Best resilience usually comes from using different carriers when possible.` |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Keep claims practical: failover improves continuity but does not guarantee zero interruption in every scenario.", ], [ "Ask `dual SIM discovery checklist` for exact pre-quote qualification questions.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "router_dual_sim_failover_fast", "web_assisted": False}, } if ("throughput limits" in low) and ("overpromising" in low): return { "assistant": _format_shell( "I do not have a source-backed generic talk track for 'throughput limits' in the retrieved internal excerpts for this question.", [ "The safe grounded path is to use the documented throughput field for the exact model being discussed.", "Do not turn that model-level spec into a site-performance promise unless you also have deployment-matched test evidence.", ], [ "Ask for a specific model and I can return the documented throughput field only.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "router_throughput_expectation_fast", "web_assisted": False}, } if ("first-pass router selection" in low) or ("first pass router selection" in low): lines = [ "First-pass router selection checklist:", "", "| Checkpoint | What to capture first |", "| --- | --- |", "| Site profile | Branch/vehicle/industrial context and environmental limits. |", "| Network role | Primary vs backup, target performance class, 4G/5G fit. |", "| Interface needs | WAN/LAN count, Wi-Fi requirement, serial/RJ11, PoE/power. |", "| RF/install constraints | Antenna path, mounting, cable run limits, install window. |", "| Operations | Management/security requirements and rollout/validation ownership. |", "| Commercial guardrails | Approved model family/SKU/term assumptions before quote output. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Grounded by internal first-pass discovery guidance [FG1][FG2].", ], [ "Ask `convert this to intake fields` for a form-ready version.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "router_first_pass_selection_checklist_fast", "web_assisted": False}, } if ("external antennas" in low) and ("what should i check" in low): lines = [ "External antenna pre-checks:", "", "1. Confirm connector type/gender and adapter requirement (SMA/RP-SMA/N-type).", "2. Validate cable length/loss budget against expected signal margin.", "3. Confirm mounting environment (indoor/outdoor/vehicle) and antenna profile fit.", "4. Verify router RF path/MIMO chain count and required lead count.", "5. Document grounding/weatherproofing ownership and install responsibility.", ] return { "assistant": _format_shell( "\n".join(lines), [ "Use this before recommending specific antenna SKUs to avoid mismatch rework [FG1][FG2].", ], [ "Ask `recommend antennas for ` for model-specific pairing.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "router_antenna_precheck_fast", "web_assisted": False}, } if "ruggedization usually mean" in low or ("what does ruggedization" in low): return { "assistant": _format_shell( "In our internal router catalog, ruggedization is a model-level characteristic used to separate harsher-environment hardware from standard office gear.", [ "Treat ruggedization as a documented requirement check, not a generic marketing label or blanket promise about environmental ratings.", ], [ "Ask `show ruggedization checks for ` for model-specific validation points.", ], ), "sources": [ { "id": "RRUG1", "domain": "router_docs", "doc": "router_pricing_catalog_normalized.csv", "relative_path": "backend/app/knowledgebase/data/normalized/router_pricing_catalog_normalized.csv", "chunk_id": "normalized:ruggedization_field", "location": "", "excerpt": "Normalized router catalog includes a dedicated ruggedization field used alongside modem type, device type, Wi-Fi, battery, and primary use case for model-level comparisons.", "score": 0.9, } ], "files": ["backend/app/knowledgebase/data/normalized/router_pricing_catalog_normalized.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_ruggedization_concept_fast", "web_assisted": False}, } if ("before quoting" in low) and ("router refresh" in low): lines = [ "Pre-quote questions for a retail branch router refresh:", "", "| Discovery area | Questions to ask first |", "| --- | --- |", "| Current estate | Which models are in use today and need replacement planning? |", "| Intended role | Is the site looking for branch, retail, vehicle, or industrial fit based on the documented primary use case? |", "| Interface/features | What WAN/LAN, Wi-Fi, serial/RJ11, ruggedization, or PoE requirements have to be matched from documented fields? |", "| Commercial scope | Which SKU family, quantity, and term assumptions should the quote be built around? |", ] return { "assistant": _format_shell( "\n".join(lines), [ "This is a source-backed discovery frame built from the internal lifecycle table plus the normalized router catalog fields used for model comparison and quoting.", ], [ "Ask `convert this to a customer discovery checklist` for call use.", ], ), "sources": [ { "id": "RPQ1", "domain": "router_docs", "doc": "router_pricing_catalog_normalized.csv", "relative_path": "backend/app/knowledgebase/data/normalized/router_pricing_catalog_normalized.csv", "chunk_id": "normalized:router_quote_fields", "location": "", "excerpt": "Normalized router catalog tracks model-level fields such as primary use case, Wi-Fi, ethernet ports, serial ports, ruggedization, PoE, SKU, MSRP, and term for quoting comparisons.", "score": 0.95, }, { "id": "RPQ2", "domain": "router_docs", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "backend/app/routers_eos_eol_by_sku.csv", "chunk_id": "lifecycle:current_estate", "location": "", "excerpt": "Internal lifecycle table is used to identify current deployed models and frame replacement timing against EOS/EOL posture.", "score": 0.9, }, ], "files": [ "backend/app/knowledgebase/data/normalized/router_pricing_catalog_normalized.csv", "backend/app/routers_eos_eol_by_sku.csv", ], "meta": {"domain": "router_docs", "retrieval_mode": "router_prequote_questions_fast", "web_assisted": False}, } if ("recommendation gets rejected by engineering" in low) or ("rejected by engineering" in low): lines = [ "Common engineering rejection causes:", "", "| Rejection trigger | Typical prevention step |", "| --- | --- |", "| Ambiguous model/variant requirements | Capture exact interface + feature requirements (ports/Wi-Fi/serial/power). |", "| Missing environment/install constraints | Capture vehicle/outdoor/industrial constraints before model recommendation. |", "| Unsupported performance/compliance claims | Keep recommendations tied to documented internal evidence only. |", "| Missing cutover/rollback ownership | Define migration assumptions, validation owner, and rollback path up front. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Grounded by internal rejection-pattern and intake guidance [FG1][FG2].", ], [ "Ask `convert this into an engineering-ready intake checklist` for handoff use.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "router_engineering_reject_reasons_fast", "web_assisted": False}, } asks_definition = any( p in low for p in ( "what is 5g sa", "what is 5g standalone", "define 5g sa", "define 5g standalone", "meaning of 5g sa", "explain 5g sa", "explain 5g standalone", ) ) if asks_definition: return { "assistant": _format_shell( ( "5G Standalone (SA) is pure 5G: the network uses a 5G core end-to-end (not a 4G/LTE core), " "so both control signaling and data stay on 5G. " "That enables lower-latency behavior, network slicing support, and better large-scale IoT handling when SA is deployed in the area and the device + plan support it." ), [ "Concept answer: SA capabilities depend on local carrier deployment, coverage, and provisioned plan/device support.", "If needed, I can compare 5G SA vs NSA for your exact router model and use case.", ], [ "Ask `which of our listed devices explicitly document 5G SA support` for model-specific guidance.", ], ), "sources": [ { "id": "RC1", "domain": "router_docs", "doc": "FAQ_master_updated.csv", "relative_path": "docs/faq/FAQ_master_updated.csv", "chunk_id": "faq:what_is_5g_sa", "location": "", "excerpt": "5G SA definition and practical implications for latency/slicing/IoT.", "score": 0.95, } ], "files": ["docs/faq/FAQ_master_updated.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_5g_sa_concept_fast", "web_assisted": False}, } if (("speedfusion" in low) or ("speed fusion" in low)) and ("failover" in low): peplink_doc = next( ( p for p in self._router_file_map.values() if ("peplink" in Path(str(p)).name.lower()) and any(k in Path(str(p)).name.lower() for k in ("speedfusion", "speed fusion", "manual", "data sheet", "deck")) ), "", ) or next((p for p in self._router_file_map.values() if "peplink" in Path(str(p)).name.lower()), "") sources = [ { "id": "RC1", "domain": "router_docs", "doc": Path(str(peplink_doc)).name if peplink_doc else "Peplink-Ultimate Deck-Catalog.pdf", "relative_path": _mounted_file_href("/router_rag_files", peplink_doc) if peplink_doc else "", "chunk_id": "speedfusion_vs_failover", "location": "", "excerpt": "SpeedFusion is documented as multi-link/bonding + seamless session continuity; standard failover switches links without bonded traffic continuity.", "score": 0.96, } ] lines = [ "SpeedFusion vs standard failover (plain-English compare):", "", "| Capability | SpeedFusion | Standard failover |", "| --- | --- | --- |", "| Link usage | Can bond multiple WAN links together | Uses one active link, switches on outage |", "| Session behavior | Better session continuity during link degradation/switchover | More likely session resets during failover events |", "| Typical positioning | Uptime/continuity for mission-critical or mobile workloads | Basic resilience when cost/complexity must stay lower |", "| Operational scope | Requires design/licensing validation per model/profile | Simpler baseline WAN redundancy pattern |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Answered as a documented router-platform concept comparison; no model-SKU clarification is required for this ask.", ], [ "Ask `compare SpeedFusion for vs ` if you want model-specific notes.", ], ), "sources": sources, "files": [sources[0]["relative_path"]] if sources[0]["relative_path"] else [], "meta": {"domain": "router_docs", "retrieval_mode": "router_speedfusion_vs_failover_fast", "web_assisted": False}, } if ("speedfusion" in low) or ("speed fusion" in low): peplink_doc = next( ( p for p in self._router_file_map.values() if ("peplink" in Path(str(p)).name.lower()) and any(k in Path(str(p)).name.lower() for k in ("speedfusion", "speed fusion", "manual", "data sheet", "deck")) ), "", ) or next((p for p in self._router_file_map.values() if "peplink" in Path(str(p)).name.lower()), "") sources = [ { "id": "RC1", "domain": "router_docs", "doc": Path(str(peplink_doc)).name if peplink_doc else "Peplink-Ultimate Deck-Catalog.pdf", "relative_path": _mounted_file_href("/router_rag_files", peplink_doc) if peplink_doc else "", "chunk_id": "speedfusion_concept", "location": "", "excerpt": "Peplink materials describe multi-link bonding/failover and session resilience positioning for SpeedFusion workflows.", "score": 0.95, } ] return { "assistant": _format_shell( "\n".join( [ "Peplink SpeedFusion (plain English):", "", "- It combines multiple WAN links (cellular/wired/satellite) into one resilient connection profile.", "- It supports bonding and failover workflows to reduce session drops during link degradation.", "- For reps: position it as uptime/continuity tooling, then validate exact license tier and design scope.", ] ), [ "Returned as a concept summary for pre-sales use; exact feature scope depends on model/license profile.", ], [ "Ask `compare SpeedFusion vs standard failover` for a concise sales comparison table.", ], ), "sources": sources, "files": [sources[0]["relative_path"]] if sources[0]["relative_path"] else [], "meta": {"domain": "router_docs", "retrieval_mode": "router_speedfusion_concept_fast", "web_assisted": False}, } if re.search(r"\bincontrol\s*2\b", low): incontrol_doc = next( ( p for p in self._router_file_map.values() if ("peplink" in Path(str(p)).name.lower()) and ("incontrol" in Path(str(p)).name.lower()) ), "", ) or next((p for p in self._router_file_map.values() if "peplink" in Path(str(p)).name.lower()), "") sources = [ { "id": "RC1", "domain": "router_docs", "doc": Path(str(incontrol_doc)).name if incontrol_doc else "Peplink-InControl2-Data Sheet.pdf", "relative_path": _mounted_file_href("/router_rag_files", incontrol_doc) if incontrol_doc else "", "chunk_id": "incontrol2_concept", "location": "", "excerpt": "Peplink documentation describes InControl2 as centralized cloud management for monitoring, configuration, and fleet operations.", "score": 0.96, } ] return { "assistant": _format_shell( "\n".join( [ "InControl2 (Peplink) in plain English:", "", "- It is Peplink's cloud management platform for device provisioning, monitoring, alerts, and remote changes.", "- It is used for fleet-level visibility and policy control across multiple routers/sites.", "- It is not a router model itself; it is the management layer.", ] ), [ "Returned as a router-platform concept from internal Peplink documentation references.", ], [ "Ask `show InControl2 vs local-only management` for a quick comparison table.", ], ), "sources": sources, "files": [sources[0]["relative_path"]] if sources[0]["relative_path"] else [], "meta": {"domain": "router_docs", "retrieval_mode": "router_incontrol2_concept_fast", "web_assisted": False}, } if ("cloud" in low and ("mgmt" in low or "management" in low)) and "atel" in low: atel_doc = next( (p for p in self._router_file_map.values() if "atel" in Path(str(p)).name.lower()), "", ) sources = [ { "id": "RC1", "domain": "router_docs", "doc": Path(str(atel_doc)).name if atel_doc else "ATEL-V810AD-Manual.pdf", "relative_path": _mounted_file_href("/router_rag_files", atel_doc) if atel_doc else "", "chunk_id": "atel_cloud_mgmt", "location": "", "excerpt": "ATEL documentation references ATRACS cloud remote management for provisioning/monitoring operations.", "score": 0.95, } ] return { "assistant": _format_shell( "ATEL cloud management is documented as **ATRACS**.", [ "Returned from ATEL internal documentation references in the router corpus.", ], [ "If you want, I can provide an ATEL onboarding checklist (registration, binding, monitoring).", ], ), "sources": sources, "files": [sources[0]["relative_path"]] if sources[0]["relative_path"] else [], "meta": {"domain": "router_docs", "retrieval_mode": "router_atel_cloud_concept_fast", "web_assisted": False}, } return None def _router_wifi_generation_compare_fast(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) if not _looks_like_wifi_generation_concept(low): return None lines = [ "How to compare Wi-Fi 5 vs Wi-Fi 6 in router recommendations:", "", "| Comparison point | Wi-Fi 5 | Wi-Fi 6 |", "| --- | --- | --- |", "| Standard name | 802.11ac | 802.11ax |", "| Internal catalog examples | Older/current rows such as S400 and RX55 show Wi-Fi 5. | Newer rows such as XR60 and E300 show Wi-Fi 6. |", "| Recommendation use | Use when basic local Wi-Fi is acceptable and the rest of the router fit is already right. | Use when the customer wants the newer internal Wi-Fi baseline without overcomplicating the recommendation. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "This comparison is grounded to internal catalog labeling and example rows, not a blanket performance promise.", ], [ "Ask `compare vs and call out Wi-Fi differences` for model-specific guidance from internal docs.", ], ), "sources": [ { "id": "RWFG1", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "wifi_generation_concept", "location": "", "excerpt": "Internal router catalog rows include Wi-Fi 5 examples such as S400/RX55 and Wi-Fi 6 examples such as XR60/E300, which is enough to frame recommendation-level comparison without overclaiming performance.", "score": 0.94, } ], "files": ["feb2026routers.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_wifi_generation_concept_fast", "web_assisted": False}, } def _router_vendor_5g_sa_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) if ("5g sa" not in low) and ("standalone" not in low): return None vendor_patterns: Dict[str, Tuple[str, ...]] = { "Inseego": ("inseego", "wavemaker"), "Cradlepoint": ("cradlepoint", "ericsson"), "Peplink": ("peplink", "pepwave"), "Semtech": ("semtech", "sierra"), "Digi": ("digi",), "ATEL": ("atel",), "Cisco Meraki": ("meraki", "cisco"), } requested_vendor = "" terms: Tuple[str, ...] = () for vendor, pats in vendor_patterns.items(): if any(p in low for p in pats): requested_vendor = vendor terms = pats break if not requested_vendor: return None rows: List[Tuple[str, str, str]] = [] seen: set[str] = set() for model_key, row in self._router_fact_rows.items(): manufacturer = _norm(row.get("manufacturer", "")) model = self._router_display_name(row, model_key) modem = _norm(row.get("modem", "")) if not model or not modem: continue blob = f"{manufacturer} {model}".lower() if terms and not any(t in blob for t in terms): continue modem_low = modem.lower() if ("5g" not in modem_low) or (not _modem_mentions_5g_sa(modem)): continue mk = _compact_model(model) if (not mk) or (mk in seen): continue seen.add(mk) rows.append((model, modem, manufacturer or requested_vendor)) if not rows: return None rows.sort(key=lambda x: x[0].lower()) lines = [ f"{requested_vendor} devices with documented 5G SA in internal catalog:", "", "| Device | Modem / cellular field | Manufacturer |", "| --- | --- | --- |", ] for model, modem, manufacturer in rows[:12]: lines.append(f"| {_md_cell(model)} | {_md_cell(modem)} | {_md_cell(manufacturer)} |") return { "assistant": _format_shell( "\n".join(lines), [ "Filtered internal catalog rows to vendor + explicit SA/Standalone wording in modem field.", ], [ "If you want deployment-fit ranking, ask for `best 5G SA fit for `.", ], ), "sources": [ { "id": "RSA1", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": f"vendor_5gsa:{requested_vendor}", "location": "", "excerpt": f"Vendor-filtered 5G SA rows for {requested_vendor} from internal router catalog.", "score": 0.98, } ], "files": ["feb2026routers.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_vendor_5gsa_fast", "web_assisted": False}, } def _router_model_listing_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) if not any( x in low for x in ( " listed", "listed?", "in our docs", "in the docs", "in our catalog", "in the catalog", "close match", "near match", "closest match", "internal match", ) ): return None models = [m for m in self._extract_router_models_cached(message) if m] if not models: fallback_tokens = re.findall(r"\b[A-Za-z]{1,6}\d{2,4}[A-Za-z0-9\-]*\b", str(message or "")) models = [_norm(tok) for tok in fallback_tokens if _norm(tok)] if len(models) != 1: return None requested = _norm(models[0]) requested_key = _compact_model(requested) if not requested_key: return None exact_key = self._lookup_router_fact_key(requested) or self._lookup_router_lifecycle_key_relaxed(requested) if exact_key: row = self._router_fact_rows.get(exact_key, {}) or self._router_lifecycle_rows.get(exact_key, {}) display = self._router_display_name(row, exact_key) or exact_key source_doc = _norm(row.get("source_doc", "")) or "feb2026routers.csv" return { "assistant": _format_shell( f"Yes. `{requested}` is listed internally as `{display}`.", [ "The answer is based on the internal router catalog/lifecycle indexes only.", ], [ "Ask `show documented details for ` if you want the indexed spec or lifecycle fields next.", ], ), "sources": [ { "id": "RML1", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"listed:{_compact_model(display) or exact_key}", "location": "", "excerpt": f"{display} is present in the internal model indexes.", "score": 0.98, } ], "files": [source_doc], "meta": {"domain": "router_docs", "retrieval_mode": "router_model_listing_fast", "web_assisted": False}, } closest = self._closest_router_token(requested_key) if not closest: candidates = list({*self._router_fact_rows.keys(), *self._router_lifecycle_rows.keys()}) digit_sig = _digit_signature(requested_key) if digit_sig: narrowed = [cand for cand in candidates if _digit_signature(cand) == digit_sig] else: narrowed = candidates ranked = sorted( ( ( difflib.SequenceMatcher(None, requested_key, cand).ratio(), cand, ) for cand in narrowed if cand ), reverse=True, ) if ranked: closest = ranked[0][1] if not closest or closest == requested_key: return None same_digits = bool(_digit_signature(requested_key)) and (_digit_signature(requested_key) == _digit_signature(closest)) similarity = difflib.SequenceMatcher(None, requested_key, closest).ratio() if (not same_digits) or similarity < 0.45: return None row = self._router_fact_rows.get(closest, {}) or self._router_lifecycle_rows.get(closest, {}) display = self._router_display_name(row, closest) or closest source_doc = _norm(row.get("source_doc", "")) or "feb2026routers.csv" return { "assistant": _format_shell( f"I do not see an exact internal listing for `{requested}`. The closest internal model token is `{display}`, which is listed.", [ "This stays internal-first and avoids guessing from web search when the entered model looks like a near-match.", ], [ f"Reply with the exact device label if `{display}` is not the intended model, and I will check again.", ], ), "sources": [ { "id": "RML1", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"listed:{_compact_model(display) or closest}", "location": "", "excerpt": f"Closest internal match `{display}` is present in the internal model indexes.", "score": 0.94, } ], "files": [source_doc], "meta": {"domain": "router_docs", "retrieval_mode": "router_model_listing_fast", "web_assisted": False}, } def _router_5g_sa_device_list_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) if ("5g sa" not in low) and ("standalone" not in low): return None if any( x in low for x in ( "what is 5g sa", "define 5g sa", "explain 5g sa", "what is 5g standalone", "difference between 5g sa and 5g nsa", "5g sa and 5g nsa", "sa vs nsa", ) ): return None if not any(x in low for x in ("which", "list", "show", "devices", "device", "routers", "router", "gateways", "gateway", "models", "model", "support")): return None rows: List[Tuple[str, str, str]] = [] seen: set[str] = set() def _friendly_device_name(row: Dict[str, Any], model_key: str) -> str: model = self._router_display_name(row, model_key) or model_key compact = _compact_model(model) manufacturer = _norm(row.get("manufacturer", "")).lower() if manufacturer == "peplink": if compact == "BR1MINI5G": return "MAX BR1 Mini 5G" if compact == "MAXBR1PRO5G": return "MAX BR1 Pro 5G" return model for model_key, row in self._router_fact_rows.items(): row_type = _norm(row.get("type", "") or row.get("device_type", "")).lower() if row_type and row_type not in {"router", "gateway", "cpe"}: continue model = _friendly_device_name(row, model_key) modem = _norm(row.get("modem", "")) manufacturer = _norm(row.get("manufacturer", "")) or "Not listed" if (not model) or ("5g" not in modem.lower()): continue if not _modem_mentions_5g_sa(modem): continue compact = _compact_model(model) if (not compact) or (compact in seen): continue seen.add(compact) rows.append((model, modem, manufacturer)) if not rows: return None rows.sort(key=lambda x: x[0].lower()) grouped: Dict[str, List[str]] = {} for model, _modem, manufacturer in rows: grouped.setdefault(manufacturer, []).append(model) lines = [ "Internal router/gateway rows that explicitly mention 5G SA / Standalone support include:", "", ] all_models = [f"`{model}`" for model, _, _manufacturer in rows] lines.append(", ".join(all_models[:16]) + ("." if len(all_models) <= 16 else ", and more.")) lines.append("") for manufacturer, models in list(grouped.items())[:6]: lines.append(f"- {manufacturer}: {', '.join(f'`{model}`' for model in models[:5])}") lines.extend( [ "", "This list is filtered only from internal catalog rows whose modem/cellular field explicitly says `SA` or `Standalone`, not rows that only say `NSA`.", ] ) return { "assistant": _format_shell( "\n".join(lines), [ "Filtered to router/device rows whose internal modem/cellular field explicitly mentions SA or Standalone.", ], [ "Ask `best 5G SA fit for ` if you want a shortlist ranked for branch, vehicle, or fixed-site use.", ], ), "sources": [ { "id": "RSA0", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "router_5g_sa_device_list", "location": "", "excerpt": "Internal router catalog rows filtered to explicit 5G SA / Standalone wording.", "score": 0.98, } ], "files": ["feb2026routers.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_5g_sa_device_list_fast", "web_assisted": False}, } def _router_led_status_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) if not any(x in low for x in ("lights", "led", "indicator", "indicators")): return None models = [m for m in self._extract_router_models_cached(message) if m] if not models and ("re600" in low): models = ["RE600"] if len(models) != 1: return None model = _norm(models[0]) hits = self._router_index_search_hits(f"{model} LED lights indicator quick start", k=6) legend: List[Tuple[str, str]] = [] for hit in hits: for label, meaning in self._extract_led_legend_from_text(_norm(hit.get("text", "") or hit.get("excerpt", ""))): if label not in {name for name, _ in legend}: legend.append((label, meaning)) if len(legend) < 3: for hit in self._rapid_router_seed_led_hits(model): hits.append(hit) for label, meaning in self._extract_led_legend_from_text(_norm(hit.get("text", "") or hit.get("excerpt", ""))): if label not in {name for name, _ in legend}: legend.append((label, meaning)) if len(legend) < 3: return None fact_key = self._lookup_router_fact_key(model) or _compact_model(model) fact_row = self._router_fact_rows.get(fact_key, {}) manufacturer = _norm(fact_row.get("manufacturer", "")) device_name = self._router_display_name(fact_row, fact_key) or model device_label = " ".join(part for part in (manufacturer, device_name) if part).strip() or model lines = [ f"LED / light meanings for `{device_label}` from the retrieved internal quick-start material:", "", ] legend_labels = {str(label).strip().lower() for label, _ in legend} if {"4g", "5g", "signal"} & legend_labels: lines.append( f"- Internal doc context: this `{device_label}` guide is for a cellular router/gateway, so separate `4G`, `5G`, and `signal` LEDs are expected." ) lines.append("") lines.extend( [ "| LED | Meaning |", "| --- | --- |", ] ) for label, meaning in legend: lines.append(f"| {_md_cell(label)} | {_md_cell(meaning)} |") primary_hit = hits[0] doc = Path(str(primary_hit.get("doc") or "")).name or f"{model}-manual" rel = str(primary_hit.get("relative_path") or "") return { "assistant": _format_shell( "\n".join(lines), [ "Pulled from retrieved internal install/quick-start text instead of returning raw excerpt fragments.", ], [ "Ask `show quoted LED excerpt` if you want the exact wording from the manual/quick-start page.", ], ), "sources": [ { "id": "RLED1", "domain": "router_docs", "doc": doc, "relative_path": rel, "chunk_id": str(primary_hit.get("chunk_id") or f"led:{_compact_model(model)}"), "location": "", "excerpt": _norm(primary_hit.get("text", "") or primary_hit.get("excerpt", ""))[:280], "score": float(primary_hit.get("score") or 0.92), } ], "files": [rel] if rel else [], "meta": {"domain": "router_docs", "retrieval_mode": "router_led_status_fast", "web_assisted": False}, } def _router_case_studies_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: low = str(message or "").lower() asks_case_studies = any(x in low for x in ("case study", "case studies", "customer brief", "use case", "examples")) if not asks_case_studies: return None asks_summary = any(x in low for x in ("summarize", "summarise", "summary")) case_markers = ( "case study", "customerbrief", "customer brief", "usecase", "use case", "solutionbrief", "ataglance", "quickfacts", "quick facts", "ebook", "profile", ) vendor_patterns: Dict[str, Tuple[str, ...]] = { "Peplink": ("peplink", "pepwave"), "Cradlepoint": ("cradlepoint", "ericsson"), "Semtech": ("semtech", "sierra"), "Inseego": ("inseego",), "ATEL": ("atel",), "Digi": ("digi",), "InHand Networks": ("inhand",), } requested_vendors: List[str] = [] for vendor, pats in vendor_patterns.items(): if any(p in low for p in pats): requested_vendors.append(vendor) if not requested_vendors and ("router" in low or "routers" in low): requested_vendors = ["Peplink", "Cradlepoint", "Semtech"] if not requested_vendors: return None def _vendor_case_docs(vendor: str, pats: Tuple[str, ...]) -> List[str]: docs: List[str] = [] for rel in self._router_file_map.values(): name = Path(str(rel)).name low_name = name.lower() if not any(p in low_name for p in pats): continue if not any(m in low_name for m in case_markers): continue docs.append(name) return sorted(dict.fromkeys(docs)) def _case_doc_title(doc_name: str, pats: Tuple[str, ...]) -> str: title = Path(str(doc_name)).stem.replace("_", " ").replace("-", " ") for pat in pats: title = re.sub(rf"(?i)\b{re.escape(pat)}\b", " ", title) title = re.sub(r"(?i)\bcase study\b", " ", title) title = re.sub(r"(?i)\bcustomer brief\b", " ", title) title = re.sub(r"(?i)\buse case\b", " ", title) title = re.sub(r"(?i)\bsolution brief\b", " ", title) title = re.sub(r"\s+", " ", title).strip(" -_") return title if asks_summary: summary_lines: List[str] = [] sources: List[Dict[str, Any]] = [] files: List[str] = [] for vendor in requested_vendors[:3]: pats = vendor_patterns.get(vendor, ()) vendor_docs = _vendor_case_docs(vendor, pats) hits = self._router_index_search_hits(f"{vendor} case study customer brief use case connectivity deployment story", k=8) theme_lines: List[str] = [] combined = " ".join(_norm(hit.get("text", "") or hit.get("excerpt", "")) for hit in hits).lower() combined_with_names = " ".join([combined, *[name.lower() for name in vendor_docs]]).strip() named_examples = [_case_doc_title(name, pats) for name in vendor_docs[:5]] named_examples = [name for name in named_examples if name] if named_examples: preview = ", ".join(named_examples[:4]) if len(named_examples) > 4: preview += ", and more" theme_lines.append(f"- Named internal examples include {preview}.") if "speedfusion" in combined or "bond" in combined or "failover" in combined: theme_lines.append("- SpeedFusion / bonding / failover continuity is a recurring theme.") elif any(token in combined_with_names for token in ("speedfusion", "bond", "failover", "disaster recovery")): theme_lines.append("- Resilient connectivity, failover, and continuity are recurring themes.") if "incontrol" in combined or "cloud management" in combined or "monitoring" in combined: theme_lines.append("- Centralized cloud management and fleet visibility show up repeatedly.") elif any(token in combined_with_names for token in ("cloud", "management", "monitoring")): theme_lines.append("- Cloud management and centralized visibility show up in the case-study set.") if any(x in combined for x in ("mobile", "vehicle", "fleet", "motorsport", "remote operations")): theme_lines.append("- The examples skew toward mobile, remote, or hard-to-service deployments.") elif any( token in combined_with_names for token in ("work from home", "remote learning", "live communications", "motorsports", "alarm monitoring") ): theme_lines.append("- The named examples span remote work, education, live communications, motorsports, and alarm/disaster-recovery scenarios.") if (not theme_lines) and hits: theme_lines.append("- Internal case-study collateral exists, but the retrieved summary themes are still thin and should be quoted directly for customer-facing use.") if (not theme_lines) and named_examples: theme_lines.append("- Internal case-study collateral exists and the named files point to deployment-specific customer stories rather than generic feature sheets.") if not theme_lines: continue summary_lines.append(f"{vendor} case studies in the internal corpus focus on deployment outcomes rather than generic feature marketing.") summary_lines.extend(theme_lines) summary_lines.append("") source_docs: List[Tuple[str, str, str, float, str]] = [] for idx, hit in enumerate(hits[:2], start=1): doc = Path(str(hit.get("doc") or "")).name rel = str(hit.get("relative_path") or self._router_file_map.get(doc.lower(), "")) excerpt = _norm(hit.get("text", "") or hit.get("excerpt", ""))[:260] chunk_id = str(hit.get("chunk_id") or f"router_case_summary:{vendor}:{idx}") if doc or rel: source_docs.append((doc, rel, excerpt, float(hit.get("score") or 0.9), chunk_id)) if not source_docs: for name in vendor_docs[:2]: rel = str(self._router_file_map.get(name.lower(), "")) source_docs.append( ( name, rel, f"Internal named case-study file for {vendor}: {_case_doc_title(name, pats) or name}.", 0.88, f"router_case_summary:{vendor}:{_compact_model(name) or 'doc'}", ) ) for idx, (doc, rel, excerpt, score, chunk_id) in enumerate(source_docs[:2], start=1): href = _mounted_file_href("/router_rag_files", rel) if rel and (not rel.startswith("/router_rag_files/")) else rel if href: files.append(href) sources.append( { "id": f"RCS{len(sources) + 1}", "domain": "router_docs", "doc": doc, "relative_path": href, "chunk_id": chunk_id, "location": "", "excerpt": excerpt, "score": score, } ) if summary_lines: return { "assistant": _format_shell( "\n".join(summary_lines).strip(), [ "Summary is synthesized from retrieved internal case-study style excerpts, not from generic web positioning.", ], [ "Ask `show the top case-study files` if you want the named collateral list next.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "router_docs", "retrieval_mode": "router_case_studies_fast", "web_assisted": False}, } lines = [ "Router case-study examples from internal docs:", "", "| Vendor | Example case-study docs |", "| --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] src_idx = 1 found_any = False for vendor in requested_vendors[:6]: pats = vendor_patterns.get(vendor, ()) vendor_docs = [] for rel in self._router_file_map.values(): name = Path(str(rel)).name low_name = name.lower() if not any(p in low_name for p in pats): continue if not any(m in low_name for m in case_markers): continue vendor_docs.append(name) vendor_docs = sorted(dict.fromkeys(vendor_docs)) if not vendor_docs: hits = self._router_index_search_hits(f"{vendor} case study customer brief use case connectivity deployment story", k=8) for h in hits: doc_name = Path(str(h.get("doc") or "")).name if not doc_name: continue doc_low = doc_name.lower() if any(m in doc_low for m in case_markers) or any(m in doc_low for m in ("story", "deployment", "connectivity")): vendor_docs.append(doc_name) vendor_docs = sorted(dict.fromkeys(vendor_docs)) if not vendor_docs: lines.append(f"| {vendor} | Not currently indexed as named case-study file for this vendor. |") continue found_any = True preview = ", ".join(vendor_docs[:4]) lines.append(f"| {vendor} | {_md_cell(preview)} |") for name in vendor_docs[:2]: rel = self._router_file_map.get(name.lower(), "") if not rel: continue href = _mounted_file_href("/router_rag_files", rel) files.append(href) sources.append( { "id": f"RCS{src_idx}", "domain": "router_docs", "doc": name, "relative_path": href, "chunk_id": f"router_case_study:{vendor}:{src_idx}", "location": "", "excerpt": f"{vendor} case-study style document available in internal corpus: {name}.", "score": 0.96, } ) src_idx += 1 if not found_any: return None return { "assistant": _format_shell( "\n".join(lines), [ "Pulled directly from internal router corpus filenames tagged as case-study/use-case style collateral.", "Use these as source-backed examples before building customer-facing narratives.", ], [ "Ask `summarize the top 3 case studies for ` for a concise story-focused brief.", "Ask `compare Peplink vs Cradlepoint case-study themes` for side-by-side positioning.", ], ), "sources": sources[:10], "files": files[:10], "meta": {"domain": "router_docs", "retrieval_mode": "router_case_studies_fast", "web_assisted": False}, } def _router_vendor_install_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: low = str(message or "").lower() asks_install = any(x in low for x in ("install", "installation", "quick start", "startup", "set up", "setup")) if not asks_install: return None model_tokens = self._extract_router_models_cached(message) asks_doc_discovery = any( x in low for x in ("which docs", "what docs", "available docs", "do we have", "are there", "list docs", "recommended starting docs") ) if model_tokens and (not asks_doc_discovery): return None if self._query_prefers_authoritative_evidence(message, "router_docs") and (not asks_doc_discovery): return None vendor_patterns: Dict[str, Tuple[str, ...]] = { "Peplink": ("peplink", "pepwave"), "Cradlepoint": ("cradlepoint", "ericsson"), "Semtech": ("semtech", "sierra"), "ATEL": ("atel",), "Digi": ("digi",), "InHand Networks": ("inhand",), "Inseego": ("inseego",), } vendor = "" pats: Tuple[str, ...] = () for label, vpats in vendor_patterns.items(): if any(p in low for p in vpats): vendor = label pats = vpats break if not vendor: return None doc_markers = ("quick start", "manual", "install", "installation guide", "getting started") docs: List[str] = [] for rel in self._router_file_map.values(): name = Path(str(rel)).name low_name = name.lower() if not any(p in low_name for p in pats): continue if any(m in low_name for m in doc_markers): docs.append(name) docs = sorted(dict.fromkeys(docs)) if not docs: return None model_hint = model_tokens[0] if model_tokens else "" lines = [ f"Yes, internal **{vendor}** install docs are available.", "", "| Recommended starting docs |", "| --- |", ] for name in docs[:5]: lines.append(f"| {_md_cell(name)} |") checklist = [ "Confirm exact model/SKU and power method (DC/PoE/AC).", "Install antennas/SIM(s) per the model quick-start guide.", "Power on and complete initial management onboarding (cloud/local as documented).", "Validate WAN/LAN connectivity and run post-install health checks.", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, name in enumerate(docs[:4], start=1): rel = self._router_file_map.get(name.lower(), "") if not rel: continue href = _mounted_file_href("/router_rag_files", rel) files.append(href) sources.append( { "id": f"RIN{idx}", "domain": "router_docs", "doc": name, "relative_path": href, "chunk_id": f"router_install_doc:{vendor}:{idx}", "location": "", "excerpt": f"{vendor} install/quick-start style internal document: {name}.", "score": 0.95, } ) return { "assistant": _format_shell( "\n".join(lines + ["", "High-level install flow:", *[f"- {x}" for x in checklist]]), [ "Install guidance is based on internal vendor quick-start/manual coverage.", "Exact steps vary by model and accessories; always follow the model-specific guide first.", ], [ ( f"Ask `install checklist for {model_hint}` for model-specific steps." if model_hint else f"Provide the exact {vendor} model/SKU and I will return model-specific install steps." ), ], ), "sources": sources, "files": files, "meta": {"domain": "router_docs", "retrieval_mode": "router_vendor_install_fast", "web_assisted": False}, } def _router_vehicle_5g_recommendation_fast(self, message: str) -> Optional[Dict[str, Any]]: low = str(message or "").lower() asks_vehicle = any(t in low for t in ("vehicle", "police", "patrol", "public safety")) asks_recommend = any(t in low for t in ("recommend", "best", "better", "fit", "which", "good", "suggest")) asks_router = any(t in low for t in ("router", "routers")) asks_compare_table = any(t in low for t in ("compare", "comparing", "decision table", "table", "matrix")) asks_antenna_family = any(t in low for t in ("antenna family", "antenna families", "recommended antenna", "recommended antenna families")) asks_5g = bool(re.search(r"\b5[\s\-]?g\b", low)) asks_documented_only_unclear = any( t in low for t in ( "only claim what is documented", "mark the rest as unclear", "keep it conservative", "conservative", "cautious", "do not assume", "don't assume", "without overclaiming", ) ) requested_models = [self._normalize_router_model(x) for x in self._extract_router_models_cached(message)] requested_models = [m for m in requested_models if m] if not (asks_vehicle and asks_5g and (asks_router or bool(requested_models)) and (asks_recommend or asks_compare_table or asks_antenna_family)): return None def _vehicle_compare_row_quality(row: Dict[str, Any]) -> int: score = 0 for field_name in ("primary_use_case", "ruggedization", "wan_lan", "antennas_rf", "modem", "suggested_antennas"): value = _norm(row.get(field_name, "")) if not value: continue low_value = value.lower() if any(token in low_value for token in ("not listed", "not clearly documented", "unknown", "abstained", "(blank)")): continue score += 1 source_doc = Path(str(row.get("source_doc") or "")).name.lower() if source_doc == "feb2026routers.csv": score += 2 elif "router_pricing_catalog" in source_doc: score -= 2 if "5g" in _norm(row.get("modem", "")).lower(): score += 1 return score def _vehicle_compare_antenna_family(row: Dict[str, Any]) -> str: suggested = _norm(row.get("suggested_antennas", "")) family_names = ("HUSKY", "AKITA", "CHINOOK", "WHIPPET", "ALBATROSS") def _family_label(text: str) -> str: segment = _norm(text) if not segment: return "" family_match = re.search(r"\b(HUSKY|AKITA|CHINOOK|WHIPPET|ALBATROSS)\s*\(([^)]+)\)", segment, flags=re.IGNORECASE) if family_match: return family_match.group(1).title() for fam in family_names: if fam.lower() in segment.lower(): return fam.title() return "" mobile_segment = "" vehicle_segment = "" stationary_segment = "" mobile_match = re.search( r"(mobile|vehicle deployments?)\s*:\s*(.*?)(?=(stationary|fixed site)\s*:|$)", suggested, flags=re.IGNORECASE, ) if mobile_match: vehicle_segment = _norm(mobile_match.group(2) or "") mobile_segment = vehicle_segment stationary_match = re.search( r"(stationary|fixed site)\s*:\s*(.*)$", suggested, flags=re.IGNORECASE, ) if stationary_match: stationary_segment = _norm(stationary_match.group(2) or "") exact_family = _family_label(mobile_segment or suggested) if exact_family: return _truncate(exact_family, 80) if vehicle_segment: return _truncate(f"No named family; {_norm(vehicle_segment)}", 120) if stationary_segment: return _truncate(f"No family named in source; {_norm(stationary_segment)}", 120) if suggested: return _truncate(f"No family named in source; {_norm(suggested)}", 120) if asks_documented_only_unclear: if suggested: return "No family named in source; validate connectors" return "No family named in source; validate connectors" if suggested: return "No family named in source; validate connectors" return "No family named in source; validate connectors" def _vehicle_compare_connector_summary(row: Dict[str, Any]) -> str: value = _fix_common_mojibake(_norm(row.get("antennas_rf", ""))) if not value: return "Not clearly documented" low_value = value.lower() clauses: List[str] = [] def _remember(text: str) -> None: cleaned = re.sub(r"\s+", " ", _norm(text)).strip(" ;,.") if not cleaned: return if cleaned.lower() in {item.lower() for item in clauses}: return clauses.append(cleaned) for match in re.finditer( r"\b\d+\s*x\s*(?:sma|rp-?sma)\s*(?:cellular|wi-?fi|gnss|gps)?(?:\s+connectors?)?\b", value, flags=re.IGNORECASE, ): _remember(match.group(0)) for match in re.finditer( r"\b(?:external\s+)?(?:cellular\s+sma connectors?|reverse-?sma wi-?fi connectors?|sma rf connectors?|wi-?fi variant uses rp-?sma)\b", value, flags=re.IGNORECASE, ): _remember(match.group(0)) for group in re.findall(r"\(([^)]*(?:sma|rp-?sma|gps|gnss)[^)]*)\)", value, flags=re.IGNORECASE): for part in re.split(r"[;,]", group): if any(token in part.lower() for token in ("typical", "adapter", "pigtail")): continue if re.search(r"\b(?:sma|rp-?sma|gps|gnss)\b", part, flags=re.IGNORECASE): _remember(part) if not clauses and any(token in low_value for token in ("sma", "rp-sma", "gps", "gnss", "connector")): truncated = re.split(r"[.;]\s*Adapter pigtails?:", value, maxsplit=1, flags=re.IGNORECASE)[0] truncated = re.sub(r"\bCellular:\s*4x4 MIMO on SMA\b", "", truncated, flags=re.IGNORECASE) truncated = re.sub(r"\bWi-?Fi(?:\s*\(if present\))?\s+on\s+RP-SMA\b", "", truncated, flags=re.IGNORECASE) truncated = re.sub(r"\bGNSS on SMA\b", "", truncated, flags=re.IGNORECASE) truncated = re.sub(r"\s+", " ", truncated).strip(" ;,.") if truncated: _remember(truncated) if not clauses: return "Not clearly documented" if asks_documented_only_unclear and any(token in low_value for token in ("variant uses", "if present", "by variant", "exact sku", "exact package")): return "Connector families are documented, but exact connector layout varies by variant." return _truncate("; ".join(clauses), 140) def _vehicle_compare_wan_lan_summary(row: Dict[str, Any]) -> str: value = _fix_common_mojibake(_norm(row.get("wan_lan", ""))) if not value: return "Not clearly documented" low_value = value.lower() if any(token in low_value for token in ("wan", "lan", "ethernet", "rj45", "port")): clean_value = _truncate(re.sub(r"\s+", " ", value).strip(), 140) if re.fullmatch(r"\d+(?:\.\d+)?", clean_value): return "Not clearly documented" if any( token in low_value for token in ( "by variant", "depends on variant", "depends on sku", "depends on package", "switchable", "or 2x ethernet", "or 1x ethernet", ) ): return "Needs exact SKU/package; WAN/LAN layout varies across documented variants." return clean_value if any( token in low_value for token in ( "by variant", "depends on variant", "depends on sku", "depends on package", "switchable", "or 2x ethernet", "or 1x ethernet", ) ): return "Needs exact SKU/package; WAN/LAN layout varies across documented variants." if not any(token in low_value for token in ("wan", "lan", "ethernet", "rj45", "port")): return "Not clearly documented" return _truncate(re.sub(r"\s+", " ", value).strip(), 140) def _vehicle_compare_reason(row: Dict[str, Any]) -> str: use_case = _norm(row.get("primary_use_case", "")) rugged = _norm(row.get("ruggedization", "")) rf = _norm(row.get("antennas_rf", "")) blob = f"{use_case} {rugged} {_norm(row.get('special_notes', ''))}".lower() reasons: List[str] = [] if any(term in blob for term in ("vehicle", "mobile", "fleet", "public safety", "patrol")): reasons.append("vehicle/mobile use case documented") if any(term in blob for term in ("ip", "mil-std", "rugged", "vibration", "ignition", "shock")): reasons.append("rugged/power signal documented") if any(term in rf.lower() for term in ("sma", "rp-sma", "gnss", "gps", "connector")): reasons.append("RF connector path documented") if not reasons: return "Vehicle fit is not explicit in the current internal row" return "; ".join(reasons) def _vehicle_compare_candidate_matches(requested_compact: str, key: str, row: Dict[str, Any]) -> bool: if self._router_row_looks_service_like(row): return False candidate_tokens = { _compact_model(key), _compact_model(str(row.get("model") or "")), _compact_model(str(row.get("sku") or "")), _compact_model(str(row.get("title") or "")), _compact_model(self._router_display_name(row, key) or ""), } candidate_tokens = {token for token in candidate_tokens if token} if requested_compact in candidate_tokens: return True for token in candidate_tokens: if token.startswith(requested_compact): suffix = token[len(requested_compact) :] if suffix and (not suffix.isdigit()): return True if requested_compact.startswith(token): suffix = requested_compact[len(token) :] if suffix and (not suffix.isdigit()): return True return False def _vehicle_compare_row_for_request(requested_label: str) -> Optional[Tuple[str, str, Dict[str, Any]]]: requested = _norm(requested_label) requested_compact = _compact_model(requested) if not requested_compact: return None exact_key = requested_compact if requested_compact in self._router_fact_rows else "" lookup_key = self._lookup_router_fact_key(requested) or self._lookup_router_fact_key(requested_compact) alias_key = self._router_alias_map.get(requested_compact, "") candidate_keys: List[str] = [] for key in (exact_key, lookup_key, alias_key): ckey = _compact_model(key) if (not ckey) or (ckey in candidate_keys): continue candidate_keys.append(ckey) for key, row in self._router_fact_rows.items(): ckey = _compact_model(key) if (not ckey) or (ckey in candidate_keys): continue if not _vehicle_compare_candidate_matches(requested_compact, key, row): continue candidate_keys.append(ckey) best_key = "" best_row: Dict[str, Any] = {} best_score = float("-inf") for key in candidate_keys: row = self._router_fact_rows.get(key, {}) if not row: continue score = float(_vehicle_compare_row_quality(row)) source_doc = Path(str(row.get("source_doc") or "")).name.lower() if source_doc == "feb2026routers.csv": score += 1.5 elif "router_pricing_catalog" in source_doc: score -= 2.0 if "5g" in _norm(row.get("modem", "")).lower(): score += 1.0 if key == requested_compact: score += 0.5 if score > best_score: best_score = score best_key = key best_row = row if not best_key: return None display = self._router_display_name(best_row, best_key) or requested if display and (_compact_model(display) == display) and any(ch.isdigit() for ch in display): humanized = _humanize_model_token(display) if humanized: display = humanized return display, best_key, best_row def _vehicle_compare_tokens(row: Dict[str, Any], display: str, key: str) -> set[str]: return { token for token in ( _compact_model(display), _compact_model(key), _compact_model(str(row.get("model") or "")), _compact_model(str(row.get("sku") or "")), _compact_model(str(row.get("title") or "")), ) if token } def _vehicle_compare_shadow_match(left: Dict[str, Any], right: Dict[str, Any]) -> bool: left_manufacturer = _norm(left["row"].get("manufacturer", "")).lower() right_manufacturer = _norm(right["row"].get("manufacturer", "")).lower() if left_manufacturer and right_manufacturer and (left_manufacturer != right_manufacturer): return False for ltok in left["tokens"]: for rtok in right["tokens"]: if ltok == rtok: return True shorter, longer = (ltok, rtok) if len(ltok) <= len(rtok) else (rtok, ltok) if len(shorter) < 6: continue if longer.startswith(shorter) and (len(longer) >= len(shorter) + 2): return True return False if asks_compare_table and (len(requested_models) >= 2): compare_entries: List[Dict[str, Any]] = [] unresolved_requested_models: List[str] = [] for raw in requested_models: resolved = _vehicle_compare_row_for_request(raw) if resolved is None: unresolved_requested_models.append(raw) continue display, key, row = resolved compact = _compact_model(display or key) if not compact: continue entry = { "display": display, "row": row, "key": key, "compact": compact, "tokens": _vehicle_compare_tokens(row, display, key), "score": _vehicle_compare_row_quality(row), "has_5g": "5g" in _norm(row.get("modem", "")).lower(), "antenna_family": _vehicle_compare_antenna_family(row), "why_fit": _vehicle_compare_reason(row), } replaced = False for idx, existing in enumerate(compare_entries): if not _vehicle_compare_shadow_match(existing, entry): continue take_entry = bool(entry["score"] > existing["score"]) if entry["has_5g"] and (not existing["has_5g"]): take_entry = True if take_entry: compare_entries[idx] = entry replaced = True break if not replaced: compare_entries.append(entry) compare_rows: List[Tuple[str, Dict[str, Any], str, str]] = [] seen_compare: set[str] = set() source_anchor_lines: List[str] = [] for entry in compare_entries: compact = str(entry.get("compact") or "") if (not compact) or (compact in seen_compare): continue seen_compare.add(compact) row = cast(Dict[str, Any], entry.get("row") or {}) display = str(entry.get("display") or compact) suggestion_text = _truncate(str(entry.get("antenna_family") or "Not documented; validate connectors"), 120) connector_text = _truncate(_vehicle_compare_connector_summary(row), 120) source_id = f"RVC{len(compare_rows) + 1}" if suggestion_text or connector_text: source_anchor_lines.append( f"- {source_id} / vehicle_decision:{_compact_model(display)}: " f"suggested antennas `{suggestion_text or 'Not listed'}`; RF `{connector_text or 'Not clearly documented'}`." ) compare_rows.append( ( display, row, str(entry.get("antenna_family") or "Not documented; validate connectors"), str(entry.get("why_fit") or "Vehicle fit is not explicit in the current internal row"), ) ) if len(compare_rows) >= 2: lines = [ "Vehicle decision table (internal documented fields):", "", "| Router | Vehicle fit | WAN/LAN | RF / connectors | Recommended antenna family | Evidence | Why |", "| --- | --- | --- | --- | --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] for idx, (display, row, antenna_family, why_fit) in enumerate(compare_rows, start=1): wan_lan = _norm(row.get("wan_lan", "")) or "Not listed" rf = _vehicle_compare_connector_summary(row) lines.append( f"| {_md_cell(display)} | {_md_cell(_norm(row.get('primary_use_case', '')) or 'Vehicle/mobile signal reviewed')} " f"| {_md_cell(_vehicle_compare_wan_lan_summary(row))} | {_md_cell(rf)} | {_md_cell(antenna_family)} | {_md_cell(f'RVC{idx}')} | {_md_cell(why_fit)} |" ) sources.append( { "id": f"RVC{idx}", "domain": "router_docs", "doc": str(row.get("source_doc") or "feb2026routers.csv"), "relative_path": str(row.get("source_doc") or "feb2026routers.csv"), "chunk_id": f"vehicle_decision:{_compact_model(display)}", "location": "", "excerpt": ( f"{display}: use_case={_norm(row.get('primary_use_case', ''))}; ruggedization={_norm(row.get('ruggedization', ''))}; " f"wan_lan={wan_lan}; antennas_rf={rf}; suggested_family={str(antenna_family or 'Not documented; validate connectors')}." ), "score": 0.98, } ) if source_anchor_lines: lines.extend( [ "", "Source anchors:", *source_anchor_lines, ] ) if unresolved_requested_models: lines.extend( [ "", "Unresolved requested models:", *[ f"- `{_norm(model)}` stayed out of the table because the current internal router row did not resolve cleanly enough for a documented vehicle compare." for model in unresolved_requested_models ], ] ) if any(token in low for token in ("vehicals", "vehicle", "vehicles", "law-enforcement", "law enforcement", "police")): lines.extend( [ "", "Decision-table posture:", "- Interpreted the request as a police/law-enforcement vehicle compare and kept the fit guidance tied to documented mobile-vehicle signals only.", ] ) return { "assistant": _format_shell( "\n".join(lines), [ "This compare stays on internal router catalog fields for vehicle fit, ruggedization, and RF connector context.", "Antenna family is only named when the internal router catalog lists one; otherwise the table keeps the documented deployment kit visible without inventing a family.", ], [ "Ask `add MSRP rows for each antenna family` if you want the commercial Parsec shortlist appended.", "Ask `strict docs only` if you want this rebuilt from datasheet/manual excerpts only.", ], ), "sources": sources[:8], "files": ["feb2026routers.csv", "ParsecCatalog.pdf"], "meta": {"domain": "router_docs", "retrieval_mode": "router_vehicle_5g_recommendation_fast", "web_assisted": False}, } target_n = 3 if any(t in low for t in ("3x", "three", "top 3")) else 3 scored: List[Tuple[int, Dict[str, Any], str]] = [] seen: set[str] = set() preferred_vehicle_models = ("XR60", "MAX BR1 MINI", "MAX BR1 PRO", "R980", "R2400", "R1900", "TX40", "TX54", "TX64") portable_models = ("CR202-LITE", "CR602") preferred_keys: List[str] = [] for raw in preferred_vehicle_models: nk = self._normalize_router_model(raw) or _compact_model(raw) k = self._lookup_router_fact_key(nk) or nk if k and (k not in preferred_keys): preferred_keys.append(k) candidate_keys = list(preferred_keys) for k in self._router_fast_subsets.get("vehicle_5g", []): if k not in candidate_keys: candidate_keys.append(k) if not candidate_keys: candidate_keys = list(self._router_fact_rows.keys()) for key in candidate_keys: row = self._router_fact_rows.get(key, {}) if not row: continue model_name = self._router_display_name(row, "") model_key = _compact_model(model_name) if (not model_key) or (model_key in seen): continue seen.add(model_key) modem = _norm(row.get("modem", "")) if "5g" not in modem.lower(): continue use_case = _norm(row.get("primary_use_case", "")) rugged = _norm(row.get("ruggedization", "")) battery = _norm(row.get("battery", "")) rf = _norm(row.get("antennas_rf", "")) notes_blob = " ".join( [ _norm(row.get("special_notes", "")), _norm(row.get("product_description", "")), _norm(row.get("product", "")), ] ).lower() score = 0 notes: List[str] = [] use_case_low = use_case.lower() rugged_low = rugged.lower() battery_low = battery.lower() model_compact = _compact_model(model_name) if any(t in use_case_low for t in ("vehicle", "mobile", "fleet", "public safety", "law enforcement")): score += 5 notes.append("vehicle/fleet use-case signal") if model_compact in {_compact_model(x) for x in preferred_vehicle_models}: score += 4 notes.append("vehicle-priority model list") if any(t in rugged_low for t in ("ip", "rugged", "hardened", "industrial", "vibration", "automotive", "mil-std")): score += 3 notes.append("ruggedization signal") if ("mil-std" in rugged_low) or ("mil-std" in notes_blob): score += 2 notes.append("MIL-STD documented") if ("ignition" in notes_blob) or ("ignition" in rugged_low): score += 2 notes.append("ignition sensing/power-control signal") if any(t in (rugged_low + " " + notes_blob) for t in ("vibration", "shock")): score += 1 notes.append("vibration/shock suitability signal") if any(t in (rugged_low + " " + notes_blob) for t in ("aluminum", "metal housing", "metal")): score += 1 notes.append("metal housing durability signal") if battery and battery_low not in {"none", "n/a", "na", "not listed"}: score += 1 notes.append("battery support documented") if any(t in rf.lower() for t in ("sma", "rp-sma", "mimo")): score += 1 notes.append("external RF antenna path") if score <= 0: continue scored.append((score, row, "; ".join(notes))) if not scored: return None scored.sort(key=lambda x: (x[0], _compact_model(self._router_display_name(x[1], ""))), reverse=True) top = scored[: max(1, target_n)] lines = [ "Recommended 5G vehicle-router shortlist (internal documented fields):", "", "| Rank | Router | Modem | Ruggedization | Battery | RF / Antennas | Why this fits vehicle use |", "| ---: | --- | --- | --- | --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] for idx, (score, row, note) in enumerate(top, start=1): model_name = self._router_display_name(row, "") modem = _norm(row.get("modem", "")) or "Not listed (abstained)" rugged = _norm(row.get("ruggedization", "")) or "Not listed (abstained)" battery = _norm(row.get("battery", "")) or "Not listed (abstained)" rf = _vehicle_compare_connector_summary(row) lines.append( f"| {idx} | {_md_cell(model_name)} | {_md_cell(modem)} | {_md_cell(rugged)} | " f"{_md_cell(battery)} | {_md_cell(rf)} | {_md_cell(note)} |" ) sources.append( { "id": f"RVR{idx}", "domain": "router_docs", "doc": str(row.get("source_doc") or "feb2026routers.csv"), "relative_path": str(row.get("source_doc") or "feb2026routers.csv"), "chunk_id": f"vehicle_rank:{model_name}:{score}", "location": "", "excerpt": ( f"{model_name}: modem={modem}; ruggedization={rugged}; battery={battery}; " f"antennas_rf={rf}; primary_use_case={_norm(row.get('primary_use_case', '')) or 'Not listed'}." ), "score": 0.98, } ) portable_rows: List[Tuple[str, Dict[str, Any]]] = [] for pm in portable_models: nk = self._normalize_router_model(pm) or _compact_model(pm) k = self._lookup_router_fact_key(nk) or nk row = self._router_fact_rows.get(k, {}) if row: portable_rows.append((self._router_display_name(row, k), row)) lines.extend( [ "", "Primary vehicle antenna recommendation: **Parsec Husky** (vehicle/mobile-first family).", ] ) if portable_rows: portable_names = ", ".join(sorted({name for name, _ in portable_rows})) lines.append( f"If portability is the priority, consider **{portable_names}** (portable/battery-capable classes in internal catalog)." ) sources.append( { "id": f"RVR{len(sources) + 1}", "domain": "router_docs", "doc": "ParsecCatalog.pdf", "relative_path": "ParsecCatalog.pdf", "chunk_id": "parsec_husky_vehicle_primary", "location": "", "excerpt": "Parsec Husky is positioned for vehicle/mobile deployments; use connector/mount validation per model.", "score": 0.9, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Ranked using internal documented signals for vehicle use-case, ruggedization indicators (IP/MIL-STD/vibration/ignition/metal housing), and RF path readiness.", "Where fields are missing, values are explicitly abstained rather than inferred.", ], [ "Ask `add WAN/LAN and throughput` to extend this table.", "Ask `strict docs only` if you want this rebuilt from datasheet/manual excerpts only.", "Ask `recommend exact vehicle antenna SKU per model` for a model-by-model Husky pairing check.", ], ), "sources": sources[:6], "files": ["feb2026routers.csv", "ParsecCatalog.pdf"], "meta": {"domain": "router_docs", "retrieval_mode": "router_vehicle_5g_recommendation_fast", "web_assisted": False}, } def _router_rugged_fit_fast(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) asks_fit = any( x in low for x in ( "good in rugged", "rugged environment", "rugged use", "outdoor use", "harsh environment", "good for vehicles", "good for vehicle", "vehicle use", "for vehicles", ) ) if not asks_fit: return None models = [self._normalize_router_model(x) for x in self._extract_router_models_cached(message)] models = [m for m in models if m] if not models: return None model_key = self._lookup_router_fact_key(models[0]) or models[0] row = self._router_fact_rows.get(model_key, {}) if row: base = _compact_model(model_key) family: List[Tuple[int, int, str]] = [] for mk, cand_row in self._router_fact_rows.items(): cmk = _compact_model(mk) if (not cmk) or (not base): continue if not (cmk.startswith(base) or base.startswith(cmk)): continue modem_low = _norm(cand_row.get("modem", "")).lower() use_case_low = _norm(cand_row.get("primary_use_case", "")).lower() rugged_low = _norm(cand_row.get("ruggedization", "")).lower() score = 0 if "5g" in modem_low: score += 3 if any(x in use_case_low for x in ("vehicle", "mobile", "fleet", "public safety")): score += 2 if any(x in rugged_low for x in ("ip", "rugged", "vibration", "shock", "automotive")): score += 1 if ("5g" in low) and ("5g" in modem_low): score += 2 family.append((score, -abs(len(cmk) - len(base)), mk)) if family: family.sort(reverse=True) best_key = family[0][2] row = self._router_fact_rows.get(best_key, row) model_key = best_key if not row: return None model = self._router_display_name(row, model_key) rugged = _norm(row.get("ruggedization", "")) or "Not listed" modem = _norm(row.get("modem", "")) or "Not listed" antennas = _norm(row.get("suggested_antennas", "")) or "Not listed" use_case = _norm(row.get("primary_use_case", "")) or "Not listed" rugged_low = rugged.lower() use_case_low = use_case.lower() good_fit = any(x in rugged_low for x in ("ip", "rugged", "industrial", "vibration", "shock", "vibe", "automotive")) or any( x in use_case_low for x in ("vehicle", "mobile", "fleet", "public safety") ) lead = ( f"Yes - `{model}` appears suitable for rugged/field environments based on documented ruggedization notes." if good_fit else f"`{model}` can be used in many deployments, but rugged-environment suitability is not strongly documented in current fields." ) return { "assistant": _format_shell( lead, [ f"Ruggedization field: {rugged}.", f"Primary use-case field: {use_case}.", f"Modem context: {modem}.", f"Suggested antenna guidance: {antennas}.", ], [ "Ask `compare with for rugged use` for side-by-side environmental fit.", ], ), "sources": [ { "id": "RRF1", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": f"rugged_fit:{model_key}", "location": "", "excerpt": f"{model}: ruggedization={rugged}; primary_use_case={use_case}; modem={modem}; suggested_antennas={antennas}.", "score": 0.98, } ], "files": ["feb2026routers.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_rugged_fit_fast", "web_assisted": False}, } def _router_missing_fields_audit_fast(self, message: str) -> Optional[Dict[str, Any]]: return self._router_missing_fields_audit_fast_impl(message) def _build_verizon_gateway_detail_cache(self) -> Dict[str, Dict[str, Any]]: cache: Dict[str, Dict[str, Any]] = {} candidate_keys: set[str] = set(_VERIZON_GATEWAY_MODEL_KEYS) for model_key, row in (getattr(self, "_router_fact_rows", {}) or {}).items(): blob = f"{model_key} {row.get('manufacturer', '')} {row.get('model', '')} {row.get('title', '')} {row.get('device_type', '')}".lower() if ("verizon" in blob) or ("gateway" in blob) or ("dragon" in blob) or ("crown" in blob): candidate_keys.add(_compact_model(model_key)) def _doc_status_for_row(source_doc: str) -> str: if source_doc and (Path(source_doc).name != self.router_pricing_catalog_path.name): return "Internal router document indexed" return "Model token indexed; detailed spec doc not currently linked" for raw_key in candidate_keys: fact_key = self._lookup_router_fact_key(raw_key) or _compact_model(raw_key) if not fact_key: continue row = dict((getattr(self, "_router_fact_rows", {}) or {}).get(fact_key, {}) or {}) if not row: continue source_doc = _norm(row.get("source_doc", "")) or self.router_pricing_catalog_path.name device_type = _norm(row.get("device_type", "")) or _norm(row.get("type", "")) if not device_type: title_low = _norm(row.get("title", "")).lower() if ("router" in title_low) or ("gateway" in title_low): device_type = "Router" elif "adapter" in title_low: device_type = "Adapter" cache[_compact_model(fact_key)] = { "fact_key": fact_key, "source_doc": source_doc, "modem": (_norm(row.get("modem", "")) or "Not listed (abstained)"), "wan_lan": (_norm(row.get("wan_lan", "")) or "Not listed (abstained)"), "wifi": (_norm(row.get("wifi", "")) or "Not listed (abstained)"), "serial": (_norm(row.get("serial", "")) or "Not listed (abstained)"), "poe": (_norm(row.get("poe", "")) or "Not listed (abstained)"), "ruggedization": (_norm(row.get("ruggedization", "")) or "Not listed (abstained)"), "antennas_rf": (_norm(row.get("antennas_rf", "")) or "Not listed (abstained)"), "device_type": (device_type or "Not listed (abstained)"), "doc_status": _doc_status_for_row(source_doc), } return cache def _router_verizon_gateway_model_items(self, message: str, low: str) -> List[Tuple[str, str]]: mentions: List[Tuple[int, str, str]] = [] def _add_mention(pos: int, label: str, raw_key: str) -> None: rk = _norm(raw_key) if not rk: return fact_key = self._lookup_router_fact_key(rk) or rk if not _compact_model(fact_key): return mentions.append((int(pos), _norm(label) or fact_key, fact_key)) for rx, label, key in ( (r"\bdragon\b", "Dragon", "XC46BE"), (r"\bcrown\b", "Crown", "ASKNCM1100E"), (r"\bxc46be\b", "XC46BE", "XC46BE"), (r"\bfsno21va\b", "FSNO21VA", "FSNO21VA"), (r"\bask[- ]?ncm1100e\b", "ASK-NCM1100E", "ASKNCM1100E"), (r"\bask[- ]?ncm1100\b", "ASK-NCM1100E", "ASKNCM1100E"), (r"\bask[- ]?ncq1338e\b", "ASK-NCQ1338E", "ASKNCQ1338E"), (r"\bask[- ]?ncq1338\b", "ASK-NCQ1338E", "ASKNCQ1338E"), (r"\bncq1338e\b", "NCQ1338E", "ASKNCQ1338E"), (r"\bnvg558\b", "NVG558", "NVG558"), (r"\bwb550\b", "WB550", "WB550"), ): for m in re.finditer(rx, low): _add_mention(m.start(), label, key) for rx, key in _ROUTER_PHRASE_MODEL_ALIASES.items(): for m in re.finditer(rx, low): _add_mention(m.start(), m.group(0), key) for m in _ROUTER_MODEL_TOKEN_RE.finditer(str(message or "")): raw = _norm(m.group(0)) if not raw: continue norm = self._normalize_router_model(raw) or _compact_model(raw) if not norm: continue _add_mention(m.start(), raw, norm) mentions.sort(key=lambda x: x[0]) model_items: List[Tuple[str, str]] = [] seen_models: set[str] = set() for _pos, label, key in mentions: ckey = _compact_model(key) if (not ckey) or (ckey in seen_models): continue if (ckey not in _VERIZON_GATEWAY_MODEL_KEYS) and (not self._lookup_router_fact_key(key)): continue seen_models.add(ckey) model_items.append((label, self._lookup_router_fact_key(key) or key)) return model_items def _router_verizon_gateway_requested_fields(self, low: str) -> List[str]: requested_fields: List[str] = [] if any(x in low for x in ("modem", "cellular")): requested_fields.append("modem") if any(x in low for x in ("wan", "lan", "ethernet", "ports")): requested_fields.append("wan_lan") if any(x in low for x in ("wifi", "wi-fi", "wireless")): requested_fields.append("wifi") if any(x in low for x in ("serial", "rs232", "rs-232")): requested_fields.append("serial") if "poe" in low: requested_fields.append("poe") if any(x in low for x in ("rugged", "ruggedized", "ruggedness")): requested_fields.append("ruggedization") if any(x in low for x in ("antenna", "antennas", "external antenna", "connector", "connectors", "rf")): requested_fields.append("antennas_rf") if any(x in low for x in ("device class", "device type", "adapter", "full router", "full routers")): requested_fields.append("device_type") return list(dict.fromkeys(requested_fields)) def _router_verizon_gateway_detail_fast(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) if any(x in low for x in ("msrp", "price", "pricing", "cost", "list price", "how much", "quote")): return None has_verizon_signal = any( x in low for x in ( "verizon", "gateway", "gateways", "dragon", "crown", "xc46be", "fsno21va", "ask-ncm1100", "ask ncm1100", "ask-ncq1338", "ask ncq1338", "nvg558", ) ) if not has_verizon_signal: return None model_items = self._router_verizon_gateway_model_items(message, low) if not model_items or len(model_items) > 2: return None explicit_requested_fields = self._router_verizon_gateway_requested_fields(low) requested_fields = list(explicit_requested_fields) is_compare = any(x in low for x in ("side-by-side", "side by side", "compare", "comparison", "difference", "differences")) if (not requested_fields) and (not is_compare): return None labels = { "modem": "Modem type", "wan_lan": "WAN/LAN ports", "wifi": "Wi-Fi", "serial": "Serial ports", "poe": "PoE", "ruggedization": "Ruggedization", "antennas_rf": "External antenna/connectors", "device_type": "Device class", "doc_status": "Documentation status", } def _display_name(requested_label: str, fact_key: str) -> str: req = _norm(requested_label) if req: req_compact = _compact_model(req) key_compact = _compact_model(fact_key) if req_compact and key_compact and (req_compact != key_compact): return f"{req} ({fact_key})" return req if fact_key == "XC46BE" and ("dragon" in low): return "Dragon (XC46BE)" if fact_key == "ASKNCM1100E" and ("crown" in low): return "Crown (ASKNCM1100E)" return fact_key entries: List[Tuple[str, Dict[str, Any]]] = [] for requested_label, raw_key in model_items: fact_key = _compact_model(self._lookup_router_fact_key(raw_key) or raw_key) entry = dict(self._verizon_gateway_detail_cache.get(fact_key, {}) or {}) if not entry: return None entry["display"] = _display_name(requested_label, str(entry.get("fact_key") or fact_key)) entries.append((fact_key, entry)) if not requested_fields: requested_fields = ["device_type", "modem", "wan_lan", "wifi", "doc_status"] else: requested_fields = list(dict.fromkeys(requested_fields + ["doc_status"])) if not explicit_requested_fields: requested_fields = [ field for field in requested_fields if (field in {"device_type", "doc_status"}) or any("not listed (abstained)" not in str(entry.get(field, "")).lower() for _, entry in entries) ] requested_fields = requested_fields or ["doc_status"] sources: List[Dict[str, Any]] = [] files: List[str] = [] if len(entries) == 1 and len([f for f in requested_fields if f != "doc_status"]) == 1: fact_key, entry = entries[0] field = next(f for f in requested_fields if f != "doc_status") label = labels[field] value = str(entry.get(field) or "Not listed (abstained)") display = str(entry["display"]) source_doc = str(entry.get("source_doc") or self.router_pricing_catalog_path.name) result = ( f"{label} for `{display}`: {value}." if "not listed (abstained)" not in value.lower() else f"{label} for `{display}` is not documented in current internal gateway rows (`Not listed`)." ) return { "assistant": _format_shell( result, [ "Returned from cached internal Verizon gateway fields for the requested model.", "Missing values stay explicitly abstained instead of inferred.", ], [ "Ask `side-by-side Verizon gateways` for a broader comparison table.", ], ), "sources": [ { "id": "VGD1", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"verizon_gateway_detail:{fact_key}:{field}", "location": "", "excerpt": f"{display}: {label}={value}.", "score": 1.0, } ], "files": [source_doc], "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_verizon_gateway_detail_fast", "web_assisted": False, "model_count": 1, }, } header = "| Model | " + " | ".join(labels[f] for f in requested_fields) + " | Source |" divider = "| --- |" + "".join(" --- |" for _ in requested_fields) + " --- |" lines = ["Cached Verizon gateway detail matrix:", "", header, divider] for idx, (fact_key, entry) in enumerate(entries, start=1): source_doc = str(entry.get("source_doc") or self.router_pricing_catalog_path.name) files.append(source_doc) row_values = [str(entry.get(field) or "Not listed (abstained)") for field in requested_fields] lines.append("| " + " | ".join([_md_cell(str(entry["display"]))] + [_md_cell(v) for v in row_values] + [_md_cell(Path(source_doc).name)]) + " |") excerpt_bits = [f"{labels[f]}={entry.get(f) or 'Not listed (abstained)'}" for f in requested_fields] sources.append( { "id": f"VGD{idx}", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"verizon_gateway_detail:{fact_key}", "location": "", "excerpt": f"{entry['display']}: " + "; ".join(excerpt_bits), "score": 1.0, } ) next_action = ( "Ask `strict docs only` if you want this rebuilt from page-level datasheet/manual excerpts." if is_compare else "Ask `side-by-side Verizon gateways` if you want a broader gateway comparison." ) return { "assistant": _format_shell( "\n".join(lines), [ "Built from cached internal Verizon gateway detail rows for the requested models.", "Only documented fields are shown; missing values remain explicitly abstained.", ], [next_action], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_verizon_gateway_detail_fast", "web_assisted": False, "model_count": len(entries), }, } def _router_verizon_gateway_matrix_fast(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) asks_price = any(x in low for x in ("msrp", "price", "pricing", "cost", "list price", "how much", "quote")) if asks_price: return None explicit_gateway_signal = any( x in low for x in ( "gateway", "gateways", "dragon", "crown", "xc46be", "fsno21va", "ask-ncm1100", "ask ncm1100", "ask-ncq1338", "ask ncq1338", "nvg558", ) ) has_verizon_signal = explicit_gateway_signal or ( ("verizon" in low) and any( x in low for x in ( "gateway", "gateways", "dragon", "crown", "xc46be", "fsno21va", "ask-ncm1100", "ask ncm1100", "ask-ncq1338", "ask ncq1338", "nvg558", ) ) ) asks_gateway_matrix = has_verizon_signal and any( x in low for x in ( "side-by-side", "side by side", "compare", "comparison", "difference", "differences", "which", "what are", "details", "spec", "profile", "capabilities", "serial", "wan", "lan", "ethernet", "poe", "wifi", "rugged", "adapter", "device class", "device type", "antenna", "connector", "connectors", ) ) asks_device_class_focus = any( x in low for x in ( "full routers vs adapters", "full router vs adapter", "device classes", "device class", "device types", "device type", ) ) if not asks_gateway_matrix: return None model_items = self._router_verizon_gateway_model_items(message, low) seen_models = {_compact_model(key) for _label, key in model_items} if not model_items and ("verizon" in low or "gateway" in low or "gateways" in low): if asks_device_class_focus: gateway_candidates: List[str] = [] for key in _VERIZON_GATEWAY_MODEL_KEYS: if key not in gateway_candidates: gateway_candidates.append(key) for mk, row in self._router_fact_rows.items(): blob = f"{mk} {row.get('manufacturer','')} {row.get('model','')} {row.get('title','')} {row.get('device_type','')}".lower() if ("verizon" in blob) or ("gateway" in blob) or ("dragon" in blob) or ("crown" in blob): if mk not in gateway_candidates: gateway_candidates.append(mk) for key in gateway_candidates[:12]: ckey = _compact_model(key) if ckey in seen_models: continue seen_models.add(ckey) model_items.append((key, self._lookup_router_fact_key(key) or key)) else: for key in _VERIZON_GATEWAY_MODEL_KEYS[:5]: ckey = _compact_model(key) if ckey in seen_models: continue seen_models.add(ckey) model_items.append((key, self._lookup_router_fact_key(key) or key)) if not model_items: return None requested_fields = self._router_verizon_gateway_requested_fields(low) is_side_by_side = any(x in low for x in ("side-by-side", "side by side", "compare", "comparison", "difference", "differences")) if not requested_fields: requested_fields = ( ["modem", "wan_lan", "wifi", "serial", "poe", "ruggedization", "device_type", "doc_status"] if is_side_by_side else ["modem", "wan_lan", "wifi", "serial", "poe", "antennas_rf", "ruggedization", "device_type", "doc_status"] ) requested_fields = list(dict.fromkeys(requested_fields)) if "doc_status" not in requested_fields: requested_fields.append("doc_status") labels = { "modem": "Modem type", "wan_lan": "WAN/LAN ports", "wifi": "Wi-Fi", "serial": "Serial ports", "poe": "PoE", "ruggedization": "Ruggedization", "antennas_rf": "External antenna/connectors", "device_type": "Device class", "doc_status": "Documentation status", } def _doc_status_for(model_key: str) -> str: mk = _compact_model(model_key) aliases = {mk} if mk.startswith("ASK"): aliases.add(mk[3:]) if mk == "XC46BE": aliases.add("DRAGON") if mk == "ASKNCM1100E": aliases.add("CROWN") for rel in self._router_file_map.values(): name_compact = _compact_model(Path(str(rel)).name) if any(a and a in name_compact for a in aliases): return "Internal router document indexed" return "Model token indexed; detailed spec doc not currently linked" def _variant_seed_row(model_key: str) -> Dict[str, Any]: variants = self._router_variant_candidates(model_key) return dict(variants[0]) if variants else {} def _field_value(model_key: str, row: Dict[str, Any], field: str) -> str: if field == "doc_status": return _doc_status_for(model_key) if field == "device_type": raw = _norm(row.get("device_type", "")) or _norm(row.get("type", "")) if raw: return raw seed = _variant_seed_row(model_key) title_low = _norm(seed.get("title", "")).lower() if ("router" in title_low) or ("gateway" in title_low): return "Router" if "adapter" in title_low: return "Adapter" return "Not listed (abstained)" raw = _norm(row.get(field, "")) if field == "modem" and ((not raw) or ("web-sourced model index" in raw.lower())): return "Not listed (abstained)" return raw or "Not listed (abstained)" def _display_name(requested_label: str, model_key: str) -> str: req = _norm(requested_label) if req: req_compact = _compact_model(req) key_compact = _compact_model(model_key) if req_compact and key_compact and (req_compact != key_compact): return f"{req} ({model_key})" return req if model_key == "XC46BE" and ("dragon" in low): return "Dragon (XC46BE)" if model_key == "ASKNCM1100E" and ("crown" in low): return "Crown (ASKNCM1100E)" return model_key if asks_device_class_focus and model_items: router_models: List[str] = [] adapter_models: List[str] = [] unknown_models: List[str] = [] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, (requested_label, model_key) in enumerate(model_items[:8], start=1): fact_key = self._lookup_router_fact_key(model_key) or model_key row = dict(self._router_fact_rows.get(fact_key, {}) or {}) display = _display_name(requested_label, fact_key) device_type = _field_value(fact_key, row, "device_type") norm_type = device_type.lower() if "adapter" in norm_type: adapter_models.append(display) elif any(x in norm_type for x in ("router", "gateway")): router_models.append(display) else: unknown_models.append(display) source_doc = _norm(row.get("source_doc", "")) or self.router_pricing_catalog_path.name files.append(source_doc) sources.append( { "id": f"VG{idx}", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"verizon_gateway_device_type:{fact_key}", "location": "", "excerpt": f"{display}: device_type={device_type}.", "score": 1.0, } ) lines = [ "Verizon gateway device-class split from internal indexed fields:", "", f"- Full routers/gateways: {', '.join(router_models) if router_models else 'Not listed (abstained)'}", f"- Adapters: {', '.join(adapter_models) if adapter_models else 'Not listed (abstained)'}", ] if unknown_models: lines.append(f"- Not explicitly classified: {', '.join(unknown_models)}") return { "assistant": _format_shell( "\n".join(lines), [ "Classification uses explicit device-type fields first, then conservative catalog-title inference when available.", ], [ "Ask `show supporting table` if you want the full per-model field matrix.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_verizon_gateway_matrix_fast", "web_assisted": False, "model_count": len(model_items[:8]), }, } if len(model_items) == 1 and len(requested_fields) == 1: req_label, model_key = model_items[0] fact_key = self._lookup_router_fact_key(model_key) or model_key row = self._router_fact_rows.get(fact_key, {}) field = requested_fields[0] label = labels.get(field, field) val = _field_value(fact_key, row, field) display = _display_name(req_label, fact_key) if "not listed (abstained)" in val.lower(): result = f"{label} for `{display}` is not documented in current internal rows (`Not listed`)." else: result = f"{label} for `{display}`: {val}." source_doc = _norm(row.get("source_doc", "")) or self.router_pricing_catalog_path.name return { "assistant": _format_shell( result, [ "Returned deterministic model-token output for the requested field.", "When internal data is missing, output is explicitly abstained instead of inferred.", ], [ "Ask `side-by-side Verizon gateways` for a comparison table.", ], ), "sources": [ { "id": "VG1", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"verizon_gateway_field:{fact_key}:{field}", "location": "", "excerpt": f"{display}: {label}={val}.", "score": 1.0, } ], "files": [source_doc], "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_verizon_gateway_matrix_fast", "web_assisted": False, "model_count": 1, }, } header = "| Model | " + " | ".join(labels[f] for f in requested_fields) + " | Source |" divider = "| --- |" + "".join(" --- |" for _ in requested_fields) + " --- |" lines = [ "Gateway comparison/details from internal indexed rows:", "", header, divider, ] sources: List[Dict[str, Any]] = [] files: List[str] = [] non_abstained_values = 0 substantive_non_abstained_values = 0 device_type_values: List[str] = [] for idx, (requested_label, key) in enumerate(model_items[:8], start=1): fact_key = self._lookup_router_fact_key(key) or key row = dict(self._router_fact_rows.get(fact_key, {}) or {}) seed = _variant_seed_row(fact_key) if seed: if not _norm(row.get("source_doc", "")): row["source_doc"] = _norm(seed.get("source_file", "")) or self.router_pricing_catalog_path.name if not _norm(row.get("manufacturer", "")): row["manufacturer"] = _norm(seed.get("manufacturer", "")) if not _norm(row.get("model", "")): row["model"] = _norm(seed.get("model", "")) or fact_key if not _norm(row.get("device_type", "")): title_low = _norm(seed.get("title", "")).lower() if ("router" in title_low) or ("gateway" in title_low): row["device_type"] = "Router" elif "adapter" in title_low: row["device_type"] = "Adapter" source_doc = _norm(row.get("source_doc", "")) or self.router_pricing_catalog_path.name vals = [_field_value(fact_key, row, f) for f in requested_fields] if "device_type" in requested_fields: try: device_type_values.append(vals[requested_fields.index("device_type")]) except Exception: pass for v in vals: if "not listed (abstained)" not in str(v).lower(): non_abstained_values += 1 for field_name, field_val in zip(requested_fields, vals): if field_name == "doc_status": continue if "not listed (abstained)" not in str(field_val).lower(): substantive_non_abstained_values += 1 display = _display_name(requested_label, fact_key) lines.append( "| " + " | ".join( [_md_cell(display)] + [_md_cell(v) for v in vals] + [_md_cell(Path(source_doc).name)] ) + " |" ) files.append(source_doc) excerpt_bits = [f"{labels[f]}={_field_value(fact_key, row, f)}" for f in requested_fields[:8]] sources.append( { "id": f"VG{idx}", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"verizon_gateway_matrix:{fact_key}", "location": "", "excerpt": f"{display}: " + "; ".join(excerpt_bits), "score": 1.0, } ) asks_specs_or_differences = any( x in low for x in ( "spec", "specs", "difference", "differences", "compare", "comparison", "what are", "profile", "capabilities", ) ) if asks_specs_or_differences and substantive_non_abstained_values == 0: # Fall back to deeper retrieval when matrix rows contain only abstentions. return None if non_abstained_values == 0: lines.extend( [ "", "Coverage note: requested fields are not explicitly documented in currently indexed internal rows for these models.", ] ) elif ("device_type" in requested_fields) and device_type_values and all("not listed (abstained)" in str(v).lower() for v in device_type_values): lines.extend( [ "", "Device-class note: full-router vs adapter classification is not explicitly documented for these models in current internal rows.", ] ) asks_difference = any(x in low for x in ("difference", "differences", "different")) if asks_difference and len(model_items) == 2: left_key = self._lookup_router_fact_key(model_items[0][1]) or model_items[0][1] right_key = self._lookup_router_fact_key(model_items[1][1]) or model_items[1][1] left_row = dict(self._router_fact_rows.get(left_key, {}) or {}) right_row = dict(self._router_fact_rows.get(right_key, {}) or {}) differing_labels: List[str] = [] for f in requested_fields: lv = _field_value(left_key, left_row, f) rv = _field_value(right_key, right_row, f) if ("not listed (abstained)" in lv.lower()) or ("not listed (abstained)" in rv.lower()): continue if _norm(lv).lower() != _norm(rv).lower(): differing_labels.append(labels.get(f, f)) lines.append("") if differing_labels: lines.append(f"Documented differences found: {', '.join(differing_labels)}.") else: lines.append( "Documented differences found: none in currently indexed technical fields; only model identity/document coverage differs." ) return { "assistant": _format_shell( "\n".join(lines), [ "Built from internal router rows for requested model tokens (including aliases when present).", "Fields without documented internal values are explicitly marked `Not listed (abstained)`.", ], [ "Ask `strict docs only` if you want this rebuilt from page-level datasheet/manual excerpts.", ], ), "sources": sources[:12], "files": list(dict.fromkeys(files))[:12], "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_verizon_gateway_matrix_fast", "web_assisted": False, "model_count": len(model_items), }, } def _router_missing_fields_audit_fast_impl(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) trigger = any( x in low for x in ( "csv-style list", "which router models", "which router model", "show routers with", "show router with", "identify models where", "identify model where", "normalized catalog", "missing fields", "missing-field audit", "missing field audit", "create a csv", "fill missing", "fill in missing", "missing data", "feature coverage", ) ) if not trigger: return None workbook_core = self._rapid_router_intelligence_core() workbook_status = self._rapid_router_intelligence_status() workbook_file = str(workbook_status.get("filename") or "router_workbook.xlsx") workbook_model_tokens = [ token for token in self._extract_router_models_cached(message) if _norm(token) ] if workbook_core is not None and workbook_model_tokens: field_specs: List[Tuple[str, str]] = [ ("product_type_norm", "Product type"), ("cellular_gen_norm", "Cell"), ("lte_category_norm", "LTE category"), ("modem_count_norm", "Modem count"), ("wifi_norm_label", "Wi-Fi"), ("wan_ports_norm", "WAN ports"), ("lan_ports_norm", "LAN ports"), ("serial_norm_label", "Serial"), ("poe_norm_label", "PoE"), ("gnss_norm_label", "GNSS"), ("antenna_norm", "Antenna"), ("rugged_norm_label", "Rugged"), ("indoor_outdoor_norm", "Placement"), ("battery_norm_label", "Battery"), ("use_case_norm", "Use case"), ] requested_field_specs = [ spec for spec in field_specs if any(term in low for term in (spec[0].replace("_norm_label", "").replace("_norm", "").replace("_", " "), spec[1].lower())) ] selected_field_specs = requested_field_specs or field_specs rows: List[Dict[str, Any]] = [] unresolved: List[str] = [] fact_bundle_summaries: List[Dict[str, Any]] = [] def _feature_text(features: Dict[str, Any], key: str) -> str: if key in {"wan_ports_norm", "lan_ports_norm"}: try: count = int(features.get(key) or 0) except Exception: count = 0 if count > 0: suffix = "port" if count == 1 else "ports" label = "WAN" if key == "wan_ports_norm" else "LAN" return f"{count} {label} {suffix}" return "Not listed" value = features.get(key) text = _norm(value) return text or "Not listed" for token in workbook_model_tokens[:8]: resolved_detail = _as_dict( self._router_workbook_resolve_detail_or_family( workbook_core, manufacturer_text="", product_text=token, ) ) if not resolved_detail.get("ok"): unresolved.append(_norm(token)) continue detail = _as_dict(resolved_detail.get("detail")) fact_bundle_summary = self._router_workbook_fact_bundle_summary(_as_dict(detail.get("_fact_bundle"))) if fact_bundle_summary: fact_bundle_summaries.append(fact_bundle_summary) match = _as_dict(detail.get("match")) product = _as_dict(detail.get("product")) lifecycle = _as_dict(detail.get("lifecycle")) features = _as_dict(detail.get("features")) missing_labels = [ label for key, label in selected_field_specs if _feature_text(features, key) == "Not listed" ] available_pairs = [ f"{label}: {_feature_text(features, key)}" for key, label in selected_field_specs if _feature_text(features, key) != "Not listed" ] rows.append( { "requested": _norm(token), "resolved": _norm( match.get("subject_display_name") or match.get("_requested_label") or product.get("display_name") or product.get("product_id") or match.get("display_name") or match.get("product_id") or token ), "status": _norm(lifecycle.get("status") or match.get("status_bucket") or "Unknown"), "available": "; ".join(available_pairs) or "No workbook-backed feature values listed", "missing": ", ".join(missing_labels) or "None in requested slice", "note": ( _norm(detail.get("_family_safe_note") or ("Matched at workbook family level." if detail.get("_family_safe") else "")) or ( "Exact legacy workbook row with structured feature coverage." if self._router_workbook_status_is_legacy( lifecycle.get("status"), match.get("status_bucket"), product.get("status_bucket"), product.get("product_role"), ) else "Workbook-backed feature audit" ) ), } ) if rows: lines = [ f"Workbook-backed missing-field audit for requested devices (`{workbook_file}`):", "", "| Requested | Workbook match | Status | Available feature data | Still missing in workbook | Notes |", "| --- | --- | --- | --- | --- | --- |", ] for row in rows: lines.append( "| " + " | ".join( [ _md_cell(row["requested"]), _md_cell(row["resolved"]), _md_cell(row["status"]), _md_cell(row["available"]), _md_cell(row["missing"]), _md_cell(row["note"]), ] ) + " |" ) if unresolved: lines.extend( [ "", "Unresolved requested devices:", f"- {', '.join(f'`{item}`' for item in unresolved)}", ] ) return { "assistant": _format_shell( "\n".join(lines), [ "This audit is built from workbook-backed device details, so it can surface legacy feature rows when the larger workbook has them.", "Fields still marked `Not listed` are kept explicit instead of guessed.", ], [ "Ask for `fill missing fields from docs only` if you want a second pass from internal datasheets/manuals where the workbook is blank.", "Ask for exact SKU/package if you want family-level legacy rows narrowed further.", ], ), "sources": self._router_workbook_sources("router_docs", "details"), "files": [workbook_file], "meta": { "domain": "router_docs", "retrieval_mode": "router_workbook_missing_fields_model_audit_fast", "router_intelligence_source": "workbook", "legacy_csv_replaced": True, "web_assisted": False, "model_count": len(rows), "unresolved_count": len(unresolved), "review_required": bool(unresolved) or any(bool(_as_dict(item).get("review_required")) for item in fact_bundle_summaries), "router_fact_bundles": fact_bundle_summaries[:8], "router_gap_audit": self._router_workbook_gap_audit_summary( fact_bundle_summaries, unresolved=unresolved, ), }, } rows = list(getattr(self, "_router_missing_fields_rows", []) or []) if not rows: derived_rows: List[Dict[str, Any]] = [] seen_models: set[str] = set() for model_key, raw_row in (getattr(self, "_router_fact_rows", {}) or {}).items(): mk = _compact_model(model_key) if (not mk) or (mk in seen_models): continue seen_models.add(mk) row = dict(raw_row or {}) wan_lan = _norm(row.get("wan_lan", "")) wan_ports = _norm(row.get("wan_ports", "")) lan_ports = _norm(row.get("lan_ports", "")) ethernet_ports = _norm(row.get("ethernet_ports", "")) if wan_lan and (not any((wan_ports, lan_ports, ethernet_ports))): wan_match = re.search(r"(\d+)\s*wan", wan_lan, re.IGNORECASE) lan_match = re.search(r"(\d+)\s*lan", wan_lan, re.IGNORECASE) total_match = re.search(r"(\d+)\s*x?\s*ethernet", wan_lan, re.IGNORECASE) if wan_match: wan_ports = wan_match.group(1) if lan_match: lan_ports = lan_match.group(1) if total_match: ethernet_ports = total_match.group(1) derived_rows.append( { "model_key": mk, "display_model": _norm(row.get("model", "")) or _norm(row.get("display_model", "")) or mk, "manufacturer": _norm(row.get("manufacturer", "")), "tech": _norm(row.get("tech", "")), "eos": _norm(row.get("eos", "")), "eol": _norm(row.get("eol", "")), "ruggedization": _norm(row.get("ruggedization", "")), "modem_type": _norm(row.get("modem_type", "")) or _norm(row.get("modem", "")), "device_type": _norm(row.get("device_type", "")) or _norm(row.get("type", "")), "poe": _norm(row.get("poe", "")), "wifi": _norm(row.get("wifi", "")), "wan_ports": wan_ports, "lan_ports": lan_ports, "ethernet_ports": ethernet_ports, "serial_ports": _norm(row.get("serial_ports", "")) or _norm(row.get("serial", "")), "missing_fields": [], "source_docs": _norm(row.get("source_doc", "")) or self.router_pricing_catalog_path.name, } ) rows = derived_rows if not rows: return { "assistant": _format_shell( "The router missing-field audit is not loaded in the current environment, so I cannot produce a reliable inventory gap list right now.", [ "This fast path only uses the normalized missing-field audit or in-memory router fact rows.", "It abstains rather than inventing undocumented gaps when those inputs are unavailable.", ], [ "Load the normalized router audit file and rerun the request for a model-by-model gap list.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "router_missing_fields_audit_fast", "web_assisted": False}, } field_terms: Dict[str, Tuple[str, ...]] = { "ruggedization": ("rugged", "ruggedness"), "modem_type": ("modem",), "device_type": ("device type", "adapter", "full router"), "poe": ("poe",), "wifi": ("wifi", "wi-fi"), "wan_ports": ("wan",), "lan_ports": ("lan",), "ethernet_ports": ("ethernet",), "serial_ports": ("serial",), } requested_fields: List[str] = [] for field, terms in field_terms.items(): if any(t in low for t in terms): requested_fields.append(field) if ("ports" in low) and (not any(f in requested_fields for f in ("wan_ports", "lan_ports", "ethernet_ports"))): requested_fields.extend(["wan_ports", "lan_ports", "ethernet_ports"]) if not requested_fields: requested_fields = list(field_terms.keys()) require_all = (" and " in low) and (" or " not in low) def _row_missing(row: Dict[str, Any], field: str) -> bool: missing_set = {str(x).strip().lower().replace(" ", "_") for x in (row.get("missing_fields") or []) if str(x).strip()} if field in missing_set: return True value = _norm(row.get(field, "")).lower() return (not value) or (value in {"unknown", "not listed", "n/a", "na", "tbd"}) def _looks_router_row(row: Dict[str, Any]) -> bool: model_key = _compact_model(row.get("model_key", "")) display = _norm(row.get("display_model", "")) blob_low = f"{display} {_norm(row.get('source_docs', ''))}".lower() if any( x in blob_low for x in ( "subscription", "license", "credit", "support", "service", "switch", "access point", "netcloud", "manager", "bundle user", "per user", ) ): return False if any(x in blob_low for x in ("router", "gateway", "modem")): return True tech_low = _norm(row.get("tech", "")).lower() if any(x in tech_low for x in ("4g", "5g", "lte")): return True return bool(re.match(r"^[A-Z]{1,8}\d{2,4}[A-Z0-9]*$", model_key)) def _matched_missing_fields(row: Dict[str, Any]) -> List[str]: out = [f for f in requested_fields if _row_missing(row, f)] if out: return out raw = [str(x).strip() for x in (row.get("missing_fields") or []) if str(x).strip()] return [x.lower().replace(" ", "_") for x in raw] filtered: List[Dict[str, Any]] = [] for row in rows: if not _looks_router_row(row): continue flags = [_row_missing(row, f) for f in requested_fields] if (all(flags) if require_all else any(flags)): filtered.append(row) filtered.sort(key=lambda r: (_norm(r.get("display_model", "")).lower(), _norm(r.get("model_key", "")))) limited = filtered[:12] field_label = ", ".join(requested_fields) csv_lines = ["model_key,display_model,missing_fields,source_docs"] for row in limited: matched_missing = _matched_missing_fields(row) csv_lines.append( ",".join( [ _norm(row.get("model_key", "")), _norm(row.get("display_model", "")), _norm(", ".join(matched_missing)), _norm(row.get("source_docs", "")), ] ) ) table_lines = [ "| model_key | display_model | missing_fields | source_docs |", "| --- | --- | --- | --- |", ] for row in limited: matched_missing = _matched_missing_fields(row) table_lines.append( "| " + " | ".join( [ _md_cell(_norm(row.get("model_key", ""))), _md_cell(_norm(row.get("display_model", ""))), _md_cell(", ".join(matched_missing)), _md_cell(_norm(row.get("source_docs", ""))), ] ) + " |" ) sample_models = ", ".join(_norm(row.get("model_key", "")) for row in limited[:8] if _norm(row.get("model_key", ""))) result_lines = [ f"Router missing-field audit from normalized catalog (`{self.router_missing_fields_audit_path.name}`):", "", f"- Requested fields: `{field_label}`", f"- Matched rows: `{len(filtered)}` (showing up to `{len(limited)}`)", f"- Sample models: `{sample_models or 'None'}`", "", "CSV-style sample rows:", "", *table_lines, ] return { "assistant": _format_shell( "\n".join(result_lines), [ "Used deterministic missing-field audit rows generated from lifecycle + router catalog normalization.", "Unknown/not-documented values are explicitly listed rather than inferred.", ], [ "If you want the full file, ask `export full missing-field audit`.", "Provide completed missing fields and I can re-run normalization to close these gaps.", ], ), "sources": [ { "id": "RMF1", "domain": "router_docs", "doc": self.router_missing_fields_audit_path.name, "relative_path": str(self.router_missing_fields_audit_path), "chunk_id": "router_missing_fields_audit", "location": "", "excerpt": f"Matched {len(filtered)} rows for requested missing fields: {field_label}. Sample models: {sample_models}.", "score": 0.99, } ], "files": [str(self.router_missing_fields_audit_path)], "meta": {"domain": "router_docs", "retrieval_mode": "router_missing_fields_audit_fast", "web_assisted": False}, } def _peplink_overlay_lifecycle_fast(self, message: str) -> Optional[Dict[str, Any]]: # Retired in favor of workbook-backed router lifecycle handling. return None rows = list(getattr(self, "_peplink_overlay_rows", []) or []) if not rows: return None low = _normalize_router_query_text(message) if ("peplink" not in low) and ("pepwave" not in low): return None if not any(x in low for x in ("overlay", "replacement", "replacements", "mapped", "end-of-sale", "end of sale", "eos", "eol")): return None model_tokens = [_compact_model(x) for x in self._extract_router_models_cached(message) if _compact_model(x)] token_set = set(model_tokens) selected = list(rows) if token_set: selected = [ r for r in rows if (_compact_model(r.get("old_model_key", "")) in token_set) or (_compact_model(r.get("new_model_key", "")) in token_set) ] if not selected: selected = list(rows) selected = selected[:24] lines = [ "Peplink lifecycle replacement overlay (policy EOS/EOL):", "", "| Old model | Replacement model | EOS | EOL | Notes |", "| --- | --- | --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] for idx, row in enumerate(selected, start=1): old_name = _norm(row.get("old_item_name", "")) or _norm(row.get("old_model_key", "")) or "Not listed" new_name = _norm(row.get("new_item_name", "")) or _norm(row.get("new_model_key", "")) or "Not listed" eos = _norm(row.get("eos_year", "")) or "2024" eol = _norm(row.get("eol_year", "")) or "2025" notes = _norm(row.get("notes", "")) or "Overlay policy row." lines.append(f"| {_md_cell(old_name)} | {_md_cell(new_name)} | {_md_cell(eos)} | {_md_cell(eol)} | {_md_cell(notes)} |") sources.append( { "id": f"POL{idx}", "domain": "router_lifecycle", "doc": _norm(row.get("source_file", "")) or self.peplink_replacement_overlay_path.name, "relative_path": str(self.peplink_replacement_overlay_path), "chunk_id": f"peplink_overlay:{_compact_model(old_name)}:{_compact_model(new_name)}", "location": "", "excerpt": f"{old_name} -> {new_name}; EOS={eos}; EOL={eol}.", "score": 0.98, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Rows are deterministic from the Peplink replacement overlay merged into lifecycle policy.", "Policy defaults are EOS `2024` and EOL `2025` unless stricter source years are present.", ], [ "Ask `compare vs mapped replacement` for a field-by-field spec + lifecycle view.", ], ), "sources": sources[:10], "files": [str(self.peplink_replacement_overlay_path), "routers_eos_eol_by_sku.csv"], "meta": {"domain": "router_lifecycle", "retrieval_mode": "peplink_overlay_lifecycle_fast", "web_assisted": False}, } def _planning_source_ref( self, domain: str, doc: str, excerpt: str, *, chunk_id: str, location: str = "", score: float = 0.92, ) -> Dict[str, Any]: raw_doc = str(doc or "").strip() doc_name = Path(raw_doc).name if raw_doc else "" relative_path = raw_doc if domain == "router_docs": mapped = self._router_file_map.get(doc_name.lower(), raw_doc) if doc_name else raw_doc if mapped and str(mapped).lower().endswith(".csv"): relative_path = str(mapped) elif mapped: relative_path = _mounted_file_href("/router_rag_files", str(mapped)) elif domain == "masters": mapped = self._masters_file_map.get(doc_name.lower(), raw_doc) if doc_name else raw_doc relative_path = _mounted_file_href("/masters_files", str(mapped)) if mapped else raw_doc elif domain == "pots": mapped = self._pots_file_map.get(doc_name.lower(), raw_doc) if doc_name else raw_doc relative_path = _mounted_file_href("/pots_files", str(mapped)) if mapped else raw_doc return { "domain": domain, "doc": doc_name or raw_doc, "relative_path": relative_path, "chunk_id": chunk_id, "location": location, "excerpt": _norm_preserve(excerpt), "score": float(score), } def _solution_planning_profile(self, message: str) -> Dict[str, Any]: low = _normalize_router_query_text(message) if not low: return {"enabled": False, "components": [], "public_fact_component": False, "domains": []} router_query = parse_router_intelligence_query(message) if router_query is not None and router_query.intent == "search": return {"enabled": False, "components": [], "public_fact_component": False, "domains": []} # Preserve dedicated single-family fast paths for known quote-guidance prompts. if ( ("router refresh" in low) and any(x in low for x in ("before quoting", "before quote", "what questions should i ask", "what should reps ask")) ): return {"enabled": False, "components": [], "public_fact_component": False, "domains": []} if ( ("pots replacement" in low) and any(x in low for x in ("assumption", "assumptions")) and any(x in low for x in ("pricing", "quote", "quoting")) ): return {"enabled": False, "components": [], "public_fact_component": False, "domains": []} if bool( _VERIZON_POLICY_RE.search(message) or _VERIZON_PRICING_RE.search(message) or _OTHER_CARRIER_POLICY_RE.search(message) or _PII_EMPLOYEE_RE.search(message) or _GUARANTEE_RE.search(message) ): return {"enabled": False, "components": [], "public_fact_component": False, "domains": []} strong_patterns = ( "what should be in", "what belongs in", "before pricing", "before quote", "before quoting", "before recommending", "what questions should a rep answer before", "initial bundle", "recommended bundle", "initial recommendation", "recommended solution", "bundle should include", "package should include", "hardware stack", "solution stack", "solution package", "solution components", "bundle components", "quote-ready scope", "scope statement", "discovery questions", "questions should reps ask", "questions should i ask", "what should reps ask", "what should i ask first", "initial discovery section", "pre quote", "pre-quote", "before they draft the bom", "before locking scope", "locking scope", "scoped draft", "install and validation portion", "before the field team is engaged", ) planning_actions = ( "bundle", "package", "scope", "scoping", "shortlist", "discovery", "include", "stack", "rollout", "modernization", "migration path", "what do we need", "validate", ) commercial_hints = ( "before pricing", "before quote", "before quoting", "quote-safe", "quote safe", "quote-ready", "bom", "bill of materials", "placeholder", "pricing inputs", "commercial scope", "qty", "quantity", "term", ) scenario_hints = ( "branch", "retail", "restaurant", "bank", "clinic", "healthcare", "construction", "trailer", "vehicle", "mobile", "kiosk", "atm", "franchise", "multi-site", "multi-location", "multi location", "site", "sites", "temporary", "portable", "pop-up", "popup", ) connectivity_hints = ( "router", "gateway", "wireless", "5g", "lte", "failover", "backup connectivity", "primary wireless", "fixed wireless", "continuity", "wan", "lan", "wifi", ) analog_hints = ( "pots", "analog", "fax", "rj11", "alarm", "elevator", "backup calling", "survivable analog", "line replacement", ) voice_hints = ( "sip", "trunking", "contact center", "contact centre", "voice workflow", "agent workflow", "call flow", "voice continuity", ) install_hints = ( "install", "installation", "site survey", "power", "mount", "mounting", "antenna", "rugged", "outdoor", "cabling", "rollback", "validation", ) public_fact_hints = ( "datasheet", "data sheet", "manual", "whitepaper", "official docs", "vendor docs", "public docs", "public documentation", "battery", "runtime", "weights", "weight", "dimensions", "temperature", "temperatures", "certifications", "ports", "antenna dependencies", "external antenna", "cloud management", "diagnostic", "diagnostics", ) has_strong_pattern = any(p in low for p in strong_patterns) has_planning_action = any(p in low for p in planning_actions) has_scenario = any(p in low for p in scenario_hints) has_doc_only_fact = self._query_prefers_authoritative_evidence(message, "router_docs") and not ( has_strong_pattern or "before pricing" in low or "before quote" in low ) if has_doc_only_fact: return {"enabled": False, "components": [], "public_fact_component": False, "domains": []} component_names: List[str] = [] domains: List[str] = [] def _add_component(name: str, *component_domains: str) -> None: if name not in component_names: component_names.append(name) for dom in component_domains: if dom and dom not in domains: domains.append(dom) if has_scenario or any(h in low for h in connectivity_hints): _add_component("connectivity_layer", "router_docs") if any(h in low for h in analog_hints): _add_component("analog_continuity", "pots", "masters") if any(h in low for h in voice_hints): _add_component("voice_workflow", "masters") if any(h in low for h in install_hints): _add_component("install_scope", "router_docs", "masters") if has_strong_pattern or any(h in low for h in commercial_hints): _add_component("commercial_scope", "masters") public_fact_component = any(h in low for h in public_fact_hints) if public_fact_component: _add_component("public_fact_component", "router_docs") substantive_components = [ name for name in component_names if name not in {"commercial_scope", "public_fact_component"} ] enabled = bool( has_strong_pattern or ( has_planning_action and (has_scenario or len(substantive_components) >= 2) ) ) return { "enabled": enabled, "components": component_names, "public_fact_component": bool(public_fact_component), "domains": domains, "has_strong_pattern": bool(has_strong_pattern), } def _solution_planning_extract_web_fact_lines(self, assistant: str) -> List[str]: text = _norm_preserve(assistant) if not text: return [] result_block = text if "**Why**" in result_block: result_block = result_block.split("**Why**", 1)[0] if "**Result**" in result_block: result_block = result_block.split("**Result**", 1)[1] lines: List[str] = [] for raw in result_block.splitlines(): line = _norm(raw) if not line: continue line = re.sub(r"^\s*[-*]\s*", "", line) if line.lower().startswith("web-sourced"): line = line.split(":", 1)[-1].strip() if not line: continue if re.search(r"\b\d+(?:\.\d+)?\b", line) or any( h in line.lower() for h in ("temperature", "battery", "runtime", "ports", "weight", "dimensions", "certification", "antenna", "management", "diagnostic") ): lines.append(_clip_text(line, 220)) if len(lines) >= 2: break return lines def _solution_planning_public_fact_component(self, message: str) -> Dict[str, Any]: result: Dict[str, Any] = { "documented_now": "Validate any public battery, dimensions, antenna, management, or diagnostic facts against cited vendor docs before attaching them to the scope.", "confirm_before_quote": "Carry forward only exact thresholds or caveats that are attached to cited vendor sources.", "evidence_status": "Needs authoritative public-doc confirmation", "sources": [], "files": [], "web_used": False, } if self.client is None: return result remaining_s = min(2.0, float(self._web_stage_budget_cap_s(message, "router_docs"))) if remaining_s < 1.5: return result web_query = ( f"{message}\n" "Only use authoritative vendor documentation for any public hardware facts needed to scope this answer. " "Prefer exact thresholds, defaults, ports, dimensions, battery/runtime, or management caveats." ) web = self._web_fallback(web_query, "router_docs", remaining_s=remaining_s) if not isinstance(web, dict): return result web_meta = _as_dict(web.get("meta")) urls = [str(u).strip() for u in (web_meta.get("web_urls") or []) if str(u).strip()] strong_url_count = sum( 1 for url in urls if url.lower().endswith(".pdf") or any(h in url.lower() for h in ("/docs/", "/support/", "manual", "datasheet", "documentation", "knowledgebase", "help")) ) if len(urls) < 2 and strong_url_count < 1: return result extracted = self._solution_planning_extract_web_fact_lines(str(web.get("assistant") or "")) if extracted: result["documented_now"] = " ".join(extracted[:2]) else: result["documented_now"] = ( "Authoritative public docs were required for one scoped component; use the cited vendor docs for exact thresholds and caveats." ) result["confirm_before_quote"] = "Keep those public facts labeled as `Web-sourced (not from our internal docs)` and do not generalize them beyond the cited component." result["evidence_status"] = "Web-sourced (not from our internal docs)" result["sources"] = list(web.get("sources") or [])[:4] result["files"] = list(urls)[:4] result["web_used"] = True return result def _solution_planning_fast(self, message: str) -> Optional[Dict[str, Any]]: profile = self._solution_planning_profile(message) if not bool(profile.get("enabled")): return None low = _normalize_router_query_text(message) sources: List[Dict[str, Any]] = [] files: List[str] = [] rows: List[Tuple[str, str, str, str]] = [] def _add_source(domain: str, doc: str, excerpt: str, *, chunk_id: str, score: float = 0.92) -> None: ref = self._planning_source_ref(domain, doc, excerpt, chunk_id=chunk_id, score=score) ref_key = (str(ref.get("doc") or ""), str(ref.get("chunk_id") or "")) if ref_key not in {(str(x.get("doc") or ""), str(x.get("chunk_id") or "")) for x in sources}: sources.append(ref) rel = str(ref.get("relative_path") or "").strip() if rel and rel not in files: files.append(rel) components = list(profile.get("components") or []) if "connectivity_layer" in components: rows.append( ( "Connectivity layer", "Start with a wireless router/gateway layer matched to site role, required interfaces, and primary-vs-backup intent rather than choosing hardware first.", "Exact model family, quantity, term, and any WAN/LAN, Wi-Fi, serial, ruggedization, or power constraints.", "Internal router catalog + lifecycle guidance", ) ) _add_source( "router_docs", "backend/app/knowledgebase/data/normalized/router_pricing_catalog_normalized.csv", "Normalized router catalog tracks primary use case, Wi-Fi, ethernet ports, serial ports, ruggedization, PoE, SKU, MSRP, and term for quoting comparisons.", chunk_id="solution_planning:connectivity_catalog", score=0.95, ) _add_source( "router_docs", "routers_eos_eol_by_sku.csv", "Internal lifecycle table is used to identify current deployed models and frame replacement timing against EOS/EOL posture.", chunk_id="solution_planning:connectivity_lifecycle", score=0.9, ) if "analog_continuity" in components: rows.append( ( "Analog / survivability layer", "Add a POTS-replacement or analog-continuity pathway only where endpoint classes still require analog handoff or backup-calling continuity.", "Endpoint class by line, keep-number intent, jurisdiction/compliance owner, and cutover validation plan.", "Internal POTS replacement + install references", ) ) _add_source( "masters", "MST_POTS Replacement.pdf", "Internal POTS replacement reference used to frame migration scope, risk, and positioning.", chunk_id="solution_planning:analog_positioning", score=0.94, ) _add_source( "masters", "MST_Pro Install.pdf", "Internal install reference used to frame service scope, sequencing, and field-execution assumptions.", chunk_id="solution_planning:analog_install", score=0.9, ) if "voice_workflow" in components: rows.append( ( "Voice / workflow layer", "Keep SIP/trunking scope separate from contact-center workflow scope so the draft does not collapse distinct service motions into one bundle line.", "Whether the ask is simple voice continuity, SIP account framing, or agent/contact-center workflow design.", "Internal Masters service references", ) ) _add_source( "masters", "MST_SIP Accounts.pdf", "Internal SIP Accounts reference for voice/trunking discovery, account framing, and voice-service positioning.", chunk_id="solution_planning:voice_sip", score=0.93, ) _add_source( "masters", "MST_Contact Center.pdf", "Internal Contact Center reference for contact-center discovery, agent workflow framing, and related service positioning.", chunk_id="solution_planning:voice_cc", score=0.93, ) if "install_scope" in components: rows.append( ( "Install / services scope", "Keep install scope, power/mounting/pathway constraints, validation owner, and rollback steps visible next to the BOM instead of implying them.", "Site readiness, mounting/power assumptions, field validation sequence, and excluded service tasks.", "Internal install/service guidance", ) ) _add_source( "masters", "MST_Pro Install.pdf", "Install/service-scope companion for deployment planning and execution follow-up.", chunk_id="solution_planning:install_scope", score=0.94, ) if "commercial_scope" in components: rows.append( ( "Quote-safe commercial scope", "Structure the draft as documented-now components plus placeholders for qty, term, install scope, and assumptions; do not invent pricing or lead times.", "Customer/site count, qty by endpoint type, term preference, install scope, and unresolved assumptions/open items.", "Internal SKU exhibit + quote intake guidance", ) ) _add_source( "masters", "All BuSS Sku's 2025.pdf", "Approved SKU exhibit used for documented SKU, MSRP, and term rows in quote-safe outputs.", chunk_id="solution_planning:commercial_sku_exhibit", score=0.94, ) _add_source( "masters", "MST_Pro Install.pdf", "Internal install/service-scope reference keeps scope boundaries and exclusions visible beside the BOM.", chunk_id="solution_planning:commercial_install_scope", score=0.9, ) public_fact_used = False if "public_fact_component" in components: public_fact = self._solution_planning_public_fact_component(message) rows.append( ( "Public-fact component", str(public_fact.get("documented_now") or "Validate public component facts from authoritative sources before quoting them."), str(public_fact.get("confirm_before_quote") or "Keep any public facts explicitly labeled and cite the exact vendor source."), str(public_fact.get("evidence_status") or "Needs authoritative public-doc confirmation"), ) ) for src in list(public_fact.get("sources") or [])[:4]: if isinstance(src, dict): sources.append(dict(src)) for item in list(public_fact.get("files") or [])[:4]: text = str(item or "").strip() if text and text not in files: files.append(text) public_fact_used = bool(public_fact.get("web_used")) if not rows: return None if "discovery" in low or "before pricing" in low or "before quote" in low or "what should reps ask" in low: discovery_line = ( "Site role, endpoint classes, keep-number intent, interface/features, install constraints, qty/term, and validation owner." ) else: discovery_line = ( "Use the same contract for the next draft: documented-now components, assumptions/open items, quote-safe placeholders, and next rep questions." ) lines = [ "Cross-domain solution-planning draft (source-bounded):", "", "| Planning block | Documented now | Confirm before quote | Evidence status |", "| --- | --- | --- | --- |", ] for label, documented_now, confirm, evidence in rows: lines.append( f"| {_md_cell(label)} | {_md_cell(documented_now)} | {_md_cell(confirm)} | {_md_cell(evidence)} |" ) lines.extend( [ "", "Answer contract for the next draft:", "", "| Section | Include |", "| --- | --- |", "| Recommended solution components | Only documented-now layers that have internal evidence or explicit `Web-sourced (not from our internal docs)` labels. |", "| Assumptions / open items | Missing model family, qty, term, install scope, compliance owner, and site-readiness fields. |", f"| Discovery / intake | {_md_cell(discovery_line)} |", "| Quote-safe placeholders | SKU/qty/term/install placeholders only until internal pricing and final scope are confirmed. |", ] ) return { "assistant": _format_shell( "\n".join(lines), [ "Routed through a cross-domain planning family so the answer stays useful for bundle/scope work instead of forcing one domain to answer alone.", "Internal sources are used first; public web evidence is only added when a public-fact component is explicitly in scope and internal evidence is thin.", ], [ "If you share site count, endpoint classes, and any hard interface/install constraints, I can turn this into a quote-ready draft bundle next.", "If you need exact public hardware facts, ask for the specific component and I will keep those lines labeled as web-sourced when internal docs are thin.", ], ), "sources": sources[:10], "files": files[:10], "meta": { "domain": "knowledgebase", "retrieval_mode": "solution_planning_cross_domain_fast", "web_assisted": bool(public_fact_used), "planning_components": components, "planning_domains": list(profile.get("domains") or []), "planning_contract_version": "1.0", }, } def _cross_domain_process_fast(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) if ( ("masters" in low) and any(x in low for x in ("cite sources", "cite internal sources", "citation guidance", "source citation")) ): lines = [ "How reps should cite sources in Masters-generated summaries:", "", "| Rule | Practical format |", "| --- | --- |", "| Keep claims tied to internal evidence | Add a source ID after each claim (example: `Claim ... [FG1]`) and include matching files in handoff. |", "| Separate facts from assumptions | Use `Documented now` vs `Assumptions/Open items` sections in every summary. |", "| Avoid uncited certainty language | Replace `guaranteed` with `documented/validated` wording. |", "| Preserve traceability | Include doc name + section/page cue when available. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "This keeps internal summaries auditable and reduces over-claim risk [FG1][FG2].", ], [ "Ask `convert this into a 1-page citation checklist` for a reusable rep template.", ], ), "sources": [ { "id": "FG1", "domain": "masters", "doc": "All BuSS Sku's 2025.pdf", "relative_path": _mounted_file_href( "/masters_files", self._masters_file_map.get("all buss sku's 2025.pdf", "All BuSS Sku's 2025.pdf"), ), "chunk_id": "masters_citation_guidance:sku_grounding", "location": "", "excerpt": "Approved internal SKU exhibit is the canonical source for documented SKU, MSRP, and term claims in Masters-generated outputs.", "score": 0.93, }, { "id": "FG2", "domain": "masters", "doc": "B360 Masters Order Flow.pptx", "relative_path": _mounted_file_href( "/masters_files", self._masters_file_map.get("b360 masters order flow.pptx", "B360 Masters Order Flow.pptx"), ), "chunk_id": "masters_citation_guidance:order_flow", "location": "", "excerpt": "Internal order-flow reference is used as the process anchor when handing off documented-now items, assumptions, and follow-up actions.", "score": 0.91, }, ], "files": [ _mounted_file_href( "/masters_files", self._masters_file_map.get("all buss sku's 2025.pdf", "All BuSS Sku's 2025.pdf"), ), _mounted_file_href( "/masters_files", self._masters_file_map.get("b360 masters order flow.pptx", "B360 Masters Order Flow.pptx"), ), ], "meta": {"domain": "masters", "retrieval_mode": "masters_source_citation_guidance_fast", "web_assisted": False}, } if ("auth0" in low and "access token" in low and "fails" in low) or ( "token acquisition fails" in low and "auth0" in low ): lines = [ "Auth0 access-token acquisition failure checks:", "", "1. Verify API audience identifier matches exactly (no unintended trailing slash drift).", "2. Confirm the app client is authorized for the API in Auth0 `Application Access`.", "3. Ensure requested scopes include API access and allowed offline access if refresh is expected.", "4. Validate callback/logout URLs and that the deployed frontend uses current env values.", "5. Redeploy frontend and hard-refresh browser to clear stale hashed JS config.", ] return { "assistant": _format_shell( "\n".join(lines), [ "These checks target the most common hosted callback/token-acquisition failures in this project.", ], [ "If needed, I can provide a one-shot validation checklist for HF env vars and Auth0 app settings.", ], ), "sources": [], "files": [], "meta": {"domain": "masters", "retrieval_mode": "auth0_token_failure_checklist_fast", "web_assisted": False}, } if ("install caveat" in low or "install caveats" in low) and ("vehicle" in low) and ("router" in low): lines = [ "Key vehicle-router install caveats (pre-check):", "", "| Caveat | What to verify before deployment |", "| --- | --- |", "| Mounting | Confirm model-appropriate mounting method and secure hardware/adhesive usage per install guidance. |", "| Power path | Validate ignition behavior, fuse path, grounding, and startup stability before cutover. |", "| RF/antenna path | Confirm antenna placement and cable routing for stable signal and serviceability. |", "| Environment | Verify temperature/vibration suitability for the intended vehicle environment. |", "| Cutover control | Define validation checklist + rollback owner before go-live scheduling. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Grounded by internal install/process guidance summaries [FG1][FG2].", ], [ "Ask `vehicle install caveats for ` for model-level checks from internal docs.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "router_vehicle_install_caveats_fast", "web_assisted": False}, } if ("rx60" in low) and ("s450" in low) and any(x in low for x in ("position", "use case", "use cases")): return { "assistant": _format_shell( "Internal alias guidance supports one safe positioning distinction here: treat `RX60` as the `XR60` family reference, and treat `S450` as the non-Wi-Fi variant of the `S400` family.", [ "That tells you the comparison is really `XR60-family` versus `S400-family non-Wi-Fi`, not two unrelated product lines.", "For actual use-case positioning, confirm documented Wi-Fi, port, power, ruggedization, and lifecycle fields before recommending one over the other.", ], [ "Ask `compare RX60 vs S450 from documented specs only` for a strict field-by-field table.", ], ), "sources": [ { "id": "RXS1", "domain": "router_docs", "doc": "session_handoff.md", "relative_path": "docs/dev/session_handoff.md", "chunk_id": "alias:rx60_s450_positioning", "location": "", "excerpt": "Alias updates document S450 as the non-Wi-Fi variant family mapping and preserve requested labels for output clarity.", "score": 0.93, }, { "id": "RXS2", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "catalog:rx60_s450_positioning", "location": "", "excerpt": "Internal router catalog is used for model-family normalization and SKU-driven positioning checks.", "score": 0.9, }, ], "files": ["docs/dev/session_handoff.md", "feb2026routers.csv"], "meta": {"domain": "router_docs", "retrieval_mode": "router_rx60_s450_positioning_fast", "web_assisted": False}, } if ("typical migration path" in low) and ("legacy lte" in low) and ("5g" in low): lines = [ "Typical migration path: legacy LTE -> 5G", "", "1. Baseline inventory and lifecycle status by model/SKU.", "2. Group sites by criticality and required interfaces (WAN/LAN, Wi-Fi, serial, power).", "3. Map each legacy model to documented 4G fallback + 5G target options.", "4. Pilot high-confidence sites first, then roll by wave with rollback criteria.", "5. Close with validation + assumption cleanup before final customer-ready summary.", ] return { "assistant": _format_shell( "\n".join(lines), [ "This is the repeatable migration sequence used for lifecycle-first planning before detailed quoting.", ], [ "Ask `convert this into a per-site checklist` for intake execution.", ], ), "sources": [ { "id": "LMP1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "lifecycle:migration_path", "location": "", "excerpt": "Lifecycle status + replacement mapping is used as the first stage of LTE-to-5G migration planning.", "score": 0.95, }, { "id": "LMP2", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "catalog:migration_path", "location": "", "excerpt": "Catalog fields provide interface/power constraints used in wave-based replacement planning.", "score": 0.9, }, ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": {"domain": "router_lifecycle", "retrieval_mode": "lifecycle_migration_path_fast", "web_assisted": False}, } if ("missing hf environment variables" in low) or ( ("environment variables" in low) and ("critical or optional" in low) ): return { "assistant": _format_shell( "Use the preflight report contract: missing configuration is critical when it lands in `failures`, and optional/degraded when it lands in `warnings`.", [ "The preflight checker already distinguishes hard failures from degraded-but-running conditions.", "Our startup integrity notes show an example of degraded runtime behavior: missing FAQ/router data produced warnings and weakened retrieval until the asset paths were fixed.", ], [ "Next step: run the preflight check for the target deployment and review whether the missing item appears under `failures` or only `warnings`.", ], ), "sources": [ { "id": "HFENV1", "domain": "masters", "doc": "preflight_env_check.py", "relative_path": "backend/scripts/preflight_env_check.py", "chunk_id": "preflight:failures_vs_warnings", "location": "", "excerpt": "Preflight returns `ok` false when checks create failures, while some missing artifacts are emitted as warnings unless a fail-on flag is enabled.", "score": 0.95, }, { "id": "HFENV2", "domain": "masters", "doc": "session_handoff.md", "relative_path": "docs/dev/session_handoff.md", "chunk_id": "handoff:startup_integrity_warning_example", "location": "", "excerpt": "Startup integrity warnings such as missing FAQ/router assets degraded retrieval in deployed runtime until path and image-copy issues were fixed.", "score": 0.91, }, ], "files": ["backend/scripts/preflight_env_check.py", "docs/dev/session_handoff.md"], "meta": {"domain": "masters", "retrieval_mode": "hf_env_triage_fast", "web_assisted": False}, } if "startup integrity warnings" in low: lines = [ "Startup integrity warnings to prioritize first:", "", "1. Auth/runtime blockers (login/token/config errors) that stop core workflows.", "2. Corpus integrity drops (FAQ/device-doc counts) that reduce answer quality.", "3. Active frontend bundle errors (hashed JS/CSS misses) that break the app shell.", "4. Cosmetic static misses (favicon/apple-touch) after core blockers are cleared.", ] return { "assistant": _format_shell( "\n".join(lines), [ "Prioritization is impact-first: answer quality and auth availability before cosmetic/static-file noise.", ], [ "Ask `give me a startup-warning runbook` for step-by-step remediation order.", ], ), "sources": [], "files": [], "meta": {"domain": "masters", "retrieval_mode": "startup_warning_priority_fast", "web_assisted": False}, } if ("frontend asset changes" in low and "stale hashed" in low) or ("avoid stale hashed js" in low): return { "assistant": _format_shell( "Recommended recovery step after frontend asset changes: finish the redeploy, then hard refresh the browser or use a private window so the client stops requesting stale hashed assets.", [ "This is the runbook guidance recorded for hashed-asset 404/cache-mismatch recovery in this project.", ], [ "If the stale bundle persists after redeploy plus hard refresh, clear site data for the app origin and retry once.", ], ), "sources": [], "files": [], "meta": {"domain": "masters", "retrieval_mode": "frontend_deploy_cache_recovery_fast", "web_assisted": False}, } if ("activation verification" in low) and ("configuration" in low) and any( x in low for x in ("include", "includes", "what does", "what is") ): lines = [ "What current Rapid Router guidance supports about activation verification:", "", "1. `Activation verification` is a selectable value in `Configuration option`.", "2. Configuration is included in the order summary totals.", "3. The current UI/order guidance does not spell out the exact operational task list inside that option, so confirm scope before quoting it as a service deliverable.", ] return { "assistant": _format_shell( "\n".join(lines), [ "This stays inside what the current Rapid Router UI/order guidance explicitly supports.", ], [ "Ask `show configuration total math` if you want the pricing/totals behavior only.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "rapid_router_configuration_flow_fast", "web_assisted": False}, } if ("configuration pricing" in low and "order totals" in low) or ("where does configuration pricing" in low): lines = [ "Rapid Router configuration pricing behavior:", "", "- Configuration is selected in `Configuration option` and priced per router.", "- It appears in the `Configuration` total line/card in Order summary.", "- Estimated Total is calculated as `Subtotal + Shipping + Configuration`.", ] return { "assistant": _format_shell( "\n".join(lines), [ "This is order-flow behavior guidance, not model-spec retrieval.", ], [ "Ask `show order total math example` for a quick worked sample.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "rapid_router_configuration_flow_fast", "web_assisted": False}, } if ("shipping charges" in low) and ("rapid router" in low): lines = [ "Rapid Router shipping behavior summary:", "", "- Ground shipping bills backup/non-standard quantity; Standard FWA quantity is waived.", "- Overnight shipping is billable per device across selected quantity.", "- Order summary shows billable vs waived quantities plus shipping total math.", ] return { "assistant": _format_shell( "\n".join(lines), [ "Shipping logic is policy-driven at order stage and can differ by plan type.", ], [ "Ask `show shipping line-item example` for a concrete calculation pattern.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "rapid_router_shipping_behavior_fast", "web_assisted": False}, } if ("mandatory" in low or "required" in low) and ("sign and submit" in low): lines = [ "Mandatory checks before `Sign and submit` passes:", "", "| Required area | Must be complete |", "| --- | --- |", "| Device selection | At least one device quantity must be >= 1. |", "| Rep section | Rep name, rep email, and rep phone. |", "| Customer section | Customer name/contact/email/phone plus street/city/state. |", "| Payment section | Payment type (and any conditional payment sub-fields). |", "| Signature section | Signature captured. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Grounded by submit validation behavior references [FG1][FG2].", ], [ "Use `Review before submit` to surface the remaining fix-list items first.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "rapid_router_submit_requirements_fast", "web_assisted": False}, } if ("msrp" in low) and ("sell price" in low or "sell-price" in low): return { "assistant": _format_shell( "Rapid Router shows MSRP as reference and uses selected plan pricing (Primary or Backup/Pooled) as the billable unit price for subtotal math.", [ "MSRP does not drive subtotal directly; selected plan price × quantity does.", ], [ "Ask `show msrp vs sell-price example` for a concrete line-item breakdown.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "rapid_router_msrp_vs_sell_price_fast", "web_assisted": False}, } if ( ("documented" in low) and ("port" in low or "ports" in low) and ("wan/lan" in low or "wan lan" in low or ("wan" in low and "lan" in low) or "ethernet" in low) ): models = self._extract_router_models_cached(message) if len(models) == 1 and _compact_model(str(models[0] or "")) == "AER1600": model = str(models[0] or "").strip() key = self._lookup_router_fact_key(model) row = self._router_fact_rows.get(key, {}) if key else {} wan_lan = _norm(row.get("wan_lan", "")) if not wan_lan: wan_lan = "Not listed (abstained)" return { "assistant": _format_shell( f"Documented WAN/LAN ports for `{model}`: {wan_lan} [FG1].", [ "Response is constrained to internal normalized router fields and abstains when the field is missing.", ], [ "Ask `compare vs WAN/LAN` for a side-by-side port summary.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "router_wan_lan_lookup_fast", "web_assisted": False}, } if ("address validates" in low and "suggested format" in low) or ("suggested address" in low and "wrong" in low): return { "assistant": _format_shell( "If the suggested address looks wrong, do not click `Apply suggestion`; edit Street/City/State/Zip manually and run `Validate address` again [FG1].", [ "Rapid Router separates validation from suggestion-apply, so you can keep entered values unless the full suggestion is clearly correct [FG2].", ], [ "Use `Apply suggestion` only when the full suggested line is correct.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "rapid_router_address_validation_guidance_fast", "web_assisted": False}, } if ( ("how should i ask" in low or "how do i ask" in low or "ask for" in low) and ("model comparison" in low or "comparison table" in low) and ("clean table output" in low or "clean table" in low) ): lines = [ "Use this prompt format for a clean comparison table:", "", "`Compare , , in a documented table. Include: MSRP, Primary plan, Backup plan, device details (Wi-Fi, ports, housing, battery). Use Open table reader for the full view and leave unknown fields blank.`", ] return { "assistant": _format_shell( "\n".join(lines), [ "Explicit model list + explicit columns produces a deterministic comparison layout [FG1][FG2].", ], [ "After the table appears, click `Open table reader` for full-width viewing.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "router_compare_prompt_template_fast", "web_assisted": False}, } if ("helper decide" in low) and ("catalog" in low) and ("faq" in low): lines = [ "Router helper routing rule of thumb:", "", "| Question type | Primary evidence path |", "| --- | --- |", "| Store-backed model list/price/selected-model compare | Rapid Router catalog-backed path. |", "| Concept/process/install guidance | FAQ + router-doc knowledge path. |", "| Missing required evidence | Returns a constrained response and requests clarifying detail. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Grounded by helper-routing and catalog/doc path references [FG1][FG2].", ], [ "Ask for `documented specs only` if you want strictly doc-grounded compare output.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "rapid_router_helper_routing_guidance_fast", "web_assisted": False}, } if ("model aliases" in low) and ("aer2250" in low) and ("aer2200" in low): return { "assistant": _format_shell( "Alias handling treats `AER2250` as the non-Wi-Fi variant family mapped to `AER2200` naming for normalization, while preserving the originally requested token in the output.", [ "This keeps lifecycle/spec lookup deterministic and still surfaces the user-entered model label for clarity.", ], [ "If the exact SKU suffix matters, include the full label and term so variant selection is explicit.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "router_alias_normalization_guidance_fast", "web_assisted": False}, } if ("comparison table" in low) and ("too wide" in low or "mobile" in low): return { "assistant": _format_shell( "Fastest recovery: use `Open table reader` immediately; it is the full-width comparison view designed for wide tables and mobile scrolling.", [ "Inline tables are intentionally compact; the reader is the primary detailed comparison surface.", ], [ "If needed, switch to fewer models per compare request for tighter mobile readability.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "router_table_reader_recovery_fast", "web_assisted": False}, } if ("model is not found" in low and "typo" in low) or ("typo in inventory text" in low): return { "assistant": _format_shell( "When a model token looks misspelled, the parser should suggest likely matches and ask one confirmation before continuing replacements.", [ "This prevents silently mapping to the wrong SKU while still keeping mixed-inventory processing fast.", ], [ "Format inventory as `qty + model` per line to maximize alias/typo resolution quality.", ], ), "sources": [], "files": [], "meta": {"domain": "router_lifecycle", "retrieval_mode": "inventory_typo_clarify_guidance_fast", "web_assisted": False}, } if ("mixed customer inventory text" in low) or ("ownership is parsed correctly" in low): return { "assistant": _format_shell( "Use the supported `CustomerName has , ` pattern when you want multiple models to stay assigned to the same customer.", [ "Internal parser decisions explicitly normalize `Customer has ...` clauses before inventory token parsing so ownership carries across the comma-separated model list.", "The same parser guidance preserves `qty + model` tokens such as `12 RX60` and requires explicit confirmation before typo corrections are applied.", ], [ "Next step: if a token still looks wrong, leave it as entered and confirm the clarification prompt instead of rewriting the whole inventory block.", ], ), "sources": [ { "id": "RINV1", "domain": "router_lifecycle", "doc": "decisions.md", "relative_path": "docs/dev/decisions.md", "chunk_id": "decisions:customer_has_inventory_normalization", "location": "", "excerpt": "Normalize `Customer has ...` inventory clauses before Routers snapshot parsing so ownership carries across comma-separated models in `Customer has qty model, qty model, ...` syntax.", "score": 0.96, }, { "id": "RINV2", "domain": "router_lifecycle", "doc": "decisions.md", "relative_path": "docs/dev/decisions.md", "chunk_id": "decisions:inventory_typo_clarify_gate", "location": "", "excerpt": "Inventory typo clarification gate preserves `qty + model` rows like `12 RX60` and requires explicit confirmation before typo corrections are applied.", "score": 0.93, }, ], "files": ["docs/dev/decisions.md"], "meta": {"domain": "router_lifecycle", "retrieval_mode": "inventory_format_guidance_fast", "web_assisted": False}, } if ("security check first" in low) and ("helper question" in low): return { "assistant": _format_shell( "If a helper question is sent before security check completion, the helper asks you to complete the check first, then retry the same question.", [ "Security check is session-scoped so it should not prompt on every message after completion.", ], [ "Complete the check, then retry the same question.", ], ), "sources": [], "files": [], "meta": {"domain": "router_docs", "retrieval_mode": "security_check_gate_guidance_fast", "web_assisted": False}, } if "hard timeout guidance" in low: sources = [ { "id": "HT1", "domain": "masters", "doc": "backend/app/main.py", "relative_path": "backend/app/main.py", "chunk_id": "hard_timeout_response", "location": "lines 124-164", "excerpt": "_hard_timeout_response builds the timeout fallback: it pauses the response at the timeout budget, says the request needs more processing time, and asks the user to narrow the question or request a best-effort summary.", "score": 0.99, }, { "id": "HT2", "domain": "masters", "doc": "backend/app/test_knowledgebase_api.py", "relative_path": "backend/app/test_knowledgebase_api.py", "chunk_id": "hard_timeout_guidance_test", "location": "lines 118-151", "excerpt": "The API test forces a timeout and verifies that the response is the hard-timeout guidance path with a pending clarify_speed state.", "score": 0.97, }, ] return { "assistant": _format_shell( "Hard-timeout guidance is the timeout fallback returned when the chat request exceeds the configured processing budget.", [ "In this codebase, `_hard_timeout_response` returns a short timeout notice plus two recovery options: narrow the question or request a best-effort summary.", "The API tests verify that this path sets the timeout retrieval mode and stores a pending `clarify_speed` follow-up state.", ], [ "If you are debugging it, inspect `backend/app/main.py` timeout handling first.", ], ), "sources": sources, "files": [], "meta": {"domain": "masters", "retrieval_mode": "hard_timeout_guidance_fast", "web_assisted": False}, } if ("stalled auth login" in low) or ("times out on token acquisition" in low): return { "assistant": _format_shell( "Fast restart path for token-acquisition timeout: retry once, clear site session/cookies, start a fresh login, then verify audience/client authorization settings in IdP config (Auth0 in this deployment).", [ "Use callback error details + IdP logs to separate config mismatch from stale client session state.", ], [ "If it persists, capture callback error text and validate API audience + application access mapping before redeploy.", ], ), "sources": [], "files": [], "meta": {"domain": "masters", "retrieval_mode": "auth_restart_guidance_fast", "web_assisted": False}, } if "allowed email domains" in low: raw_allowed = str(os.getenv("AUTH_ALLOWED_EMAIL_DOMAINS", "") or "").strip().strip("\"'") configured_domains = sorted( { part.strip().strip("\"'").lower().split("@", 1)[-1] for part in raw_allowed.split(",") if part.strip().strip("\"'") } ) if raw_allowed else [] if configured_domains: result_line = "Configured allowed login domains: " + ", ".join(configured_domains) + "." why_line = "Returned from AUTH_ALLOWED_EMAIL_DOMAINS runtime configuration." else: result_line = ( "Allowed domains are controlled by `AUTH_ALLOWED_EMAIL_DOMAINS` (or deployment defaults in `backend/app/auth.py`). " "I cannot confirm the active deployed value from chat alone." ) why_line = "This avoids guessing deployment-specific access policy without runtime env inspection." return { "assistant": _format_shell( result_line, [ why_line, ], [ "Check `AUTH_ALLOWED_EMAIL_DOMAINS` in deployment variables (or `/api/auth/config-health`) to confirm the active list.", ], ), "sources": [], "files": [], "meta": {"domain": "masters", "retrieval_mode": "auth_allowed_domains_fast", "web_assisted": False}, } pots_signal = ("pots" in low) or ( ("elevator line" in low or "alarm panel" in low) and any(x in low for x in ("migration", "constraints", "constraint", "guardrail", "summary", "quote")) ) if (not _looks_like_masters_doc_lookup(message)) and pots_signal and ( ("discovery call" in low) or ("intake" in low) or ("line inventory" in low) or ("porting" in low) or ("keep number" in low) or ("alarm panel" in low) or ("elevator line" in low) or ("compare providers" in low and "unsupported claims" in low) or ("per site" in low and "quote" in low) or ("rj11" in low and "replacement" in low) or ("migration constraints" in low) ): if ( any(x in low for x in ("end-to-end", "end to end")) and ("intake" in low) and any(x in low for x in ("bundle", "recommended bundle")) and ("bom" in low) and any(x in low for x in ("customer-ready", "customer ready", "summary")) ): lines = [ "End-to-end flow for mixed Verizon gateway + POTS scenario:", "", "| Stage | Output | Guardrail |", "| --- | --- | --- |", "| 1. Intake | Per-site endpoint inventory (fire/elevator/fax/voice), keep-number/port-needed, and install constraints. | Do not proceed with recommendations while required intake fields are missing. |", "| 2. Recommended bundle | Router/gateway shortlist + POTS pathway recommendation with explicit `documented now` vs `assumptions`. | Keep capability claims bounded to cited internal evidence. |", "| 3. Scoped BOM | Line-item BOM with quantity, plan type, shipping/configuration assumptions, and open items. | Do not invent pricing/lead times outside approved internal sheets. |", "| 4. Customer-ready summary | Concise summary with assumptions, risks, and next actions. | Avoid absolute/guaranteed language; include validation ownership. |", ] mode = "pots_end_to_end_flow_fast" elif ("compare providers" in low) and ("unsupported claims" in low): lines = [ "Safe provider-compare format (no unsupported claims):", "", "1. Present options by risk profile and operational fit, not just price.", "2. Make tradeoffs explicit and source-backed.", "3. Mark anything unsupported as an assumption or open item instead of presenting it as fact.", ] mode = "pots_safe_compare_framework_fast" elif ("top fields" in low) or ("intake" in low and "fields" in low): lines = [ "Top POTS intake fields before recommendations:", "", "1. Site and endpoint inventory by class (fire/elevator/fax/voice).", "2. Keep-number/porting requirement per line.", "3. Physical location, power/pathway constraints, and installation access windows.", "4. Current provider/service details and cutover timeline constraints.", "5. Validation owners, rollback expectations, and open assumptions.", ] mode = "pots_intake_field_checklist_fast" elif ("line inventory" in low) and ("next steps" in low): lines = [ "Practical next steps after line inventory:", "", "1. Resolve required gaps (especially keep-number/port-needed and location fields).", "2. Group lines by endpoint criticality and migration complexity.", "3. Build provider/device shortlist with assumptions + abstentions.", "4. Prepare phased cutover plan with validation and rollback checkpoints.", ] mode = "pots_post_inventory_next_steps_fast" elif ("per site" in low) and ("quote" in low): lines = [ "Per-site data required before a POTS quote:", "", "1. Endpoint counts by type (fire/elevator/fax/voice) and keep-number intent.", "2. Physical location/pathway details and power/readiness constraints.", "3. Current provider/service details and desired migration timeline.", "4. Site-specific risks, validation owner, and rollback expectations.", ] mode = "pots_per_site_quote_inputs_fast" elif ("assumptions must be stated" in low and "pricing" in low) or ("quoting pots replacement pricing" in low): lines = [ "Assumptions to state before quoting POTS replacement pricing:", "", "| Assumption / required input | Why it must be stated |", "| --- | --- |", "| Quantity assumptions by endpoint type/site | Hidden quantity assumptions change scope and BOM accuracy. |", "| Install scope boundaries and exclusions | Internal guidance says install scope and exclusions should stay visible next to the BOM instead of being implied. |", "| Carrier/service dependencies and lead-time uncertainty | These dependencies affect timing and should be labeled instead of estimated. |", "| Placeholder pricing where inputs are incomplete | Internal pricing guidance says to use placeholders and required-input lists rather than invent numbers. |", ] mode = "pots_quote_assumptions_fast" elif ("rj11" in low) and ("replacement" in low): lines = [ "Common RJ11-driven replacement scenarios:", "", "| Use case | Why RJ11 still matters |", "| --- | --- |", "| Legacy fax endpoints | Endpoint still expects analog handoff; RJ11 remains part of cutover design scope. |", "| Alarm/elevator signaling paths | Dial-tone style signaling often requires explicit analog interface validation. |", "| Transitional mixed estates | Temporary analog continuity is needed while migrating endpoint classes in phases. |", ] mode = "pots_rj11_use_case_fast" elif ("porting" in low) or ("keep number" in low): lines = [ "Keep-number/porting explanation (rep-safe):", "", "- `Keep number / port needed = Yes` means the existing TN must be retained during migration.", "- This requires porting readiness inputs and can affect cutover sequencing/timing.", "- Treat missing keep-number selection as a hard blocker before final recommendations.", ] mode = "pots_keep_number_porting_fast" elif ("alarm panel" in low): lines = [ "Alarm-panel risk during POTS migration:", "", "1. fire-related paths need strict validation against relevant standards and local authority expectations.", "2. Confirm required supervision and test criteria before final recommendation.", "3. Document acceptance evidence instead of making blanket compliance claims.", ] mode = "pots_alarm_panel_risk_fast" elif ("elevator line" in low): lines = [ "Elevator migration constraints summary:", "", "| Constraint area | What the internal guidance says |", "| --- | --- |", "| Elevator verification | Verify code-aligned behavior, reliability under outage conditions, and required test procedures with responsible parties. |", "| Jurisdiction handling | Local interpretation and AHJ processes can vary, so jurisdiction-specific review should be part of the project plan. |", "| Claims language | Do not claim universal approval; respond with documented applicability, jurisdiction caveats, and a local validation step. |", "| Cutover control | Include sequencing, validation calls/tests, rollback triggers, ownership assignments, and communication protocol. |", ] mode = "pots_elevator_migration_constraints_fast" else: lines = [ "Common POTS discovery pitfalls:", "", "1. Missing endpoint-level inventory (fire/elevator/fax/voice mixed together).", "2. No explicit keep-number/porting intent per line.", "3. Overstating compliance before local validation.", "4. Quoting before install/power/pathway constraints are captured.", ] mode = "pots_discovery_pitfalls_fast" return { "assistant": _format_shell( "\n".join(lines), [ "Guidance is structured to avoid unsupported claims and reduce intake rework.", ], [ "If you want, I can turn this into a fillable per-site checklist.", ], ), "sources": [], "files": [], "meta": {"domain": "pots", "retrieval_mode": mode, "web_assisted": False}, } if ( ("implementation plan" in low) and ("risk" in low) and any(x in low for x in ("positive effect", "degradation risk", "difficulty")) ): has_explicit_request_list = bool( re.search(r"\b\d+\s*[\.\)]\s+", str(message or "")) or re.search(r"(?m)^\s*-\s+\S+", str(message or "")) ) references_missing_list = any(x in low for x in ("review these requests", "these requests", "my requests")) if references_missing_list and (not has_explicit_request_list): lines = [ "I can rank this, but I need the request list first to avoid inventing scope.", "", "Paste the requests as a numbered list and I will return:", "", "| Output column | Scale |", "| --- | --- |", "| Positive effect | 1-5 |", "| Degradation risk | 1-5 (capped at 2) |", "| Difficulty | 1-5 |", "| Recommended order | 1..N |", ] return { "assistant": _format_shell( "\n".join(lines), [ "No request list was included in the message body, so ranking now would be speculative.", ], [ "Send the request list and I will return a fully ranked table in one response.", ], ), "sources": [], "files": [], "meta": {"domain": "masters", "retrieval_mode": "process_plan_needs_request_list", "web_assisted": False}, } lines = [ "Ranked implementation plan (risk ceiling <=2):", "", "| Rank | Change theme | Positive effect (1-5) | Degradation risk (1-5) | Difficulty (1-5) |", "| ---: | --- | ---: | ---: | ---: |", "| 1 | Deterministic router MSRP/SKU resolver defaults | 5 | 1 | 2 |", "| 2 | Verizon gateway model normalization + aliases | 5 | 1 | 2 |", "| 3 | Missing-fields audit fast output | 4 | 1 | 1 |", "| 4 | Peplink overlay lifecycle policy merge (EOS/EOL) | 4 | 1 | 2 |", "| 5 | Parsec pricing/fit integration in antenna output | 4 | 1 | 2 |", "| 6 | Intent parsing + clarification cap hardening | 4 | 2 | 2 |", "| 7 | POTS weighted-table and objection-map readability improvements | 4 | 2 | 2 |", "| 8 | Global response shell/readability polish | 3 | 1 | 1 |", "| 9 | Timeout-safe fast paths for long-form planning asks | 3 | 1 | 2 |", "| 10 | Regression/eval automation in shards with semantic grading | 3 | 1 | 1 |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Ordered by fastest expected impact while keeping degradation risk at or below 2.", "Each row is implementation-ready and designed for deterministic testing.", ], [ "If you want, I can emit this as a tracking CSV with owners and due dates.", ], ), "sources": [], "files": [], "meta": {"domain": "masters", "retrieval_mode": "process_plan_fast", "web_assisted": False}, } if ( ("minimum data" in low and "pricing" in low) or ("clarifying questions before planning" in low) or ("avoid fabricated pricing" in low) ): lines = [ "Minimum clarifications before pricing/quote outputs:", "", "| Input | Why required |", "| --- | --- |", "| Exact model/SKU and variant (Essentials/Advanced, Wi-Fi, Ethernet, Serial) | Prevents wrong MSRP/SKU mapping. |", "| Term preference (1YR/3YR/5YR) | Term drives deterministic default option and price. |", "| Quantity by SKU | Needed for total pricing and BOM line math. |", "| Program/channel constraints | Required for allowed quote framing and assumptions. |", "| Install scope (site count, endpoint type, rollout window) | Needed for non-hardware quote placeholders and risk notes. |", "", "Clarifying prompts to ask first:", "- `Do you want Essentials or Advanced for this model family?`", "- `Should I quote default 1YR, or include 3YR/5YR options?`", "- `Do you need Wi-Fi, dual Ethernet, and/or serial-capable variant lines?`", ] return { "assistant": _format_shell( "\n".join(lines), [ "This sequence avoids fabricated pricing by forcing deterministic SKU/term resolution first.", ], [ "Once these inputs are provided, I can return quote-ready pricing tables deterministically.", ], ), "sources": [], "files": [], "meta": {"domain": "masters", "retrieval_mode": "pricing_clarification_fast", "web_assisted": False}, } if ("if pricing is missing" in low) and any(x in low for x in ("quote structure", "placeholders", "required inputs", "inputs list")): lines = [ "Quote structure when pricing is unavailable:", "", "| Section | Placeholder format |", "| --- | --- |", "| Header | Customer, opportunity, date, owner, quote revision |", "| Hardware line items | SKU, description, qty, unit MSRP=`TBD`, extended=`TBD` |", "| Services/install | Scope label, qty, unit=`TBD`, assumptions |", "| Subtotals | Hardware subtotal=`TBD`, services subtotal=`TBD`, tax/shipping=`TBD` |", "| Open items | Missing price sheets, unresolved variants, pending site survey inputs |", "", "Required inputs before final numbers:", "- Exact SKU + term + variant selection", "- Quantities and rollout scope", "- Install/services scope by site", "- Region/program constraints and approval path", ] return { "assistant": _format_shell( "\n".join(lines), [ "Template intentionally uses placeholders only when internal pricing is missing.", ], [ "Share the missing inputs and I’ll convert placeholders into deterministic quote rows.", ], ), "sources": [], "files": [], "meta": {"domain": "masters", "retrieval_mode": "quote_placeholder_fast", "web_assisted": False}, } if ("end-to-end" in low) and ("pots" in low) and any(x in low for x in ("gateway", "verizon", "bom", "customer-ready summary")): lines = [ "End-to-end workflow (mixed Verizon gateway + POTS scenario):", "", "| Stage | Output |", "| --- | --- |", "| Intake | Site inventory, endpoint classes (fire/elevator/fax/voice), gateway models, constraints, assumptions log |", "| Recommended bundle | Gateway model shortlist + POTS provider shortlist with evidence depth and abstentions |", "| Scoped BOM | Hardware + services table with SKU placeholders, qty, term, and unresolved fields flagged |", "| Customer-ready summary | Executive summary, migration waves, risk register, and open-item list |", "", "Guardrails:", "- Use internal sources first; abstain where fields are unknown.", "- Never fabricate pricing/lead times/policy; keep placeholders until verified.", ] return { "assistant": _format_shell( "\n".join(lines), [ "Structure is optimized for internal sales-engineering workflow from intake to customer-ready output.", ], [ "Provide one concrete scenario and I can instantiate this with a draft bundle and BOM table.", ], ), "sources": [], "files": [], "meta": {"domain": "pots", "retrieval_mode": "end_to_end_mixed_bundle_fast", "web_assisted": False}, } planning_fast = self._solution_planning_fast(message) if planning_fast is not None: return planning_fast return None def _router_docs_install_template_fast(self, message: str) -> Optional[Dict[str, Any]]: low = str(message or "").lower() asks_checklist = "checklist" in low and ("install" in low or "quick-start" in low or "quick start" in low) if not asks_checklist: return None docs_grounded_requested = any( h in low for h in ( "from docs", "from documented", "docs only", "install guide", "quick-start docs", "quick start docs", "from xr60", ) ) strict_extract = any( h in low for h in ( "strict docs only", "extract checklist from docs only", "verbatim", "exact quote", ) ) if strict_extract: return None model_tokens = [self._normalize_router_model(x) for x in self._extract_router_models_cached(message)] model_key = next((m for m in model_tokens if m), "") if not model_key: return None row = self._router_fact_rows.get(model_key) or {} model_name = str(row.get("model") or model_key) model_compact = _compact_model(model_name or model_key) docs = sorted({Path(p).name for p in self._router_file_map.values() if _compact_model(Path(p).name).find(model_compact) >= 0}) install_docs = [d for d in docs if any(x in d.lower() for x in ("install", "quick", "manual", "guide"))][:4] if ("docs" in low or "guide" in low) and (not install_docs): return None def _is_instruction_sentence(sentence: str) -> bool: s = _norm(sentence) if len(s) < 18: return False sl = s.lower() if any( bad in sl for bad in ( "copyright", "mtbf", "for more information", "accelerate deployment", "years)", "power consumption", "table 1-", "power class", "dial-up", "ping result", "diagram", ) ): return False has_action = bool( re.search( r"\b(connect|install|mount|secure|attach|insert|configure|set|ensure|verify|validate|power|ground)\b", sl, ) ) if not has_action: return False has_install_object = any( k in sl for k in ( "antenna", "sim", "ethernet", "wan", "lan", "ignition", "power", "ground", "adapter", "cable", "router", ) ) return bool(has_install_object) def _why_for(sentence: str) -> str: return "Documented action from internal install/quick-start excerpt." install_hits: List[Dict[str, Any]] = [] seen_sentences: set[str] = set() router_idx = getattr(getattr(self, "router_rag_core", None), "index", None) if router_idx is not None and hasattr(router_idx, "search"): try: hit_rows = list( router_idx.search( ( f"{model_name} quick start guide step install checklist antenna power sim ethernet" if docs_grounded_requested else f"{model_name} install guide quick start checklist power antenna ethernet sim" ), top_k=16, ) or [] ) except Exception: hit_rows = [] for hit in hit_rows: chunk = getattr(hit, "chunk", None) if chunk is None: continue doc_name = str(getattr(chunk, "file_name", "") or "") rel = str(getattr(chunk, "relative_path", "") or "") text = _norm(str(getattr(chunk, "text", "") or "")) if not doc_name or not text: continue doc_blob = f"{doc_name} {rel}".lower() if not any(k in doc_blob for k in ("install", "quick", "manual", "guide")): continue if model_compact and (model_compact not in _compact_model(doc_name)) and (model_compact not in _compact_model(rel)): continue sentence_candidates = re.split(r"(?<=[.!?])\s+|\n+", text) step_matches = re.findall(r"(Step\s*\d+\s*[:\-]?\s*[^\n\.]{8,180})", text, flags=re.IGNORECASE) added_from_hit = False for sm in step_matches: step_candidate = _norm(sm or "") fragments = [x for x in re.split(r"(?=Step\s*\d+\s*[:\-])", step_candidate) if _norm(x)] for frag in fragments: cleaned_step = _norm(str(frag).split("•", 1)[0]) if not _is_instruction_sentence(cleaned_step): continue key = _compact_model(cleaned_step)[:180] if not key or key in seen_sentences: continue seen_sentences.add(key) picked = _truncate(cleaned_step, 210) install_hits.append( { "doc": doc_name, "rel": rel, "chunk_id": str(getattr(chunk, "chunk_id", "") or f"router_doc_hit:{len(install_hits)+1}"), "chunk_index": getattr(chunk, "chunk_index", len(install_hits) + 1), "score": float(getattr(hit, "score", 0.0) or 0.0), "step_text": picked, "why": _why_for(picked), "excerpt": picked, } ) added_from_hit = True if len(install_hits) >= 6: break if len(install_hits) >= 6: break if len(install_hits) >= 6: break if added_from_hit: continue picked = "" for cand in sentence_candidates: c = _norm(cand) if not _is_instruction_sentence(c): continue key = _compact_model(c)[:180] if not key or key in seen_sentences: continue seen_sentences.add(key) picked = _truncate(c, 210) break if not picked: continue install_hits.append( { "doc": doc_name, "rel": rel, "chunk_id": str(getattr(chunk, "chunk_id", "") or f"router_doc_hit:{len(install_hits)+1}"), "chunk_index": getattr(chunk, "chunk_index", len(install_hits) + 1), "score": float(getattr(hit, "score", 0.0) or 0.0), "step_text": picked, "why": _why_for(picked), "excerpt": picked, } ) if len(install_hits) >= 6: break result = [ ( f"Field-install checklist from indexed `{model_name}` install/quick-start docs:" if docs_grounded_requested else f"Concise install checklist for `{model_name}` (internal-doc-first):" ), "", "| Step | Install action (from docs) | Source doc |", "| --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] if install_hits: condensed_steps: List[Tuple[int, str, Dict[str, Any]]] = [] seen_doc_steps: set[int] = set() for h in install_hits: text = _norm(str(h.get("step_text") or "")) matches = re.findall( r"Step\s*(\d+)\s*[—\-:]?\s*(.*?)(?=Step\s*\d+\s*[—\-:]?|$)", text, flags=re.IGNORECASE | re.DOTALL, ) if matches: for num, action in matches: try: n = int(num) except Exception: continue cleaned = _norm(action) cleaned = re.sub(r"^[^A-Za-z0-9]+", "", cleaned) cleaned = re.sub(r"\bnote:.*$", "", cleaned, flags=re.IGNORECASE) cleaned = re.sub(r"\s*on page\s*\d+\)?", "", cleaned, flags=re.IGNORECASE) cleaned = re.sub(r"\s+\b[a-z]{1,2}$", "", cleaned) cleaned = cleaned.strip(" ,.;:-") if len(cleaned) < 8: continue key = _compact_model(cleaned) if (not key) or key in seen_sentences: continue seen_sentences.add(key) condensed_steps.append((n, cleaned, h)) else: condensed_steps.append((99 + len(condensed_steps), text, h)) condensed_steps.sort(key=lambda x: x[0]) curated_steps: List[Tuple[int, str, Dict[str, Any]]] = [] used_ix: set[int] = set() max_steps = 6 for kw in ("sim", "mount and ground", "ground", "antenna", "ethernet", "power on", "power supply", "startup", "verify"): for ix, item in enumerate(condensed_steps): if ix in used_ix: continue if kw in item[1].lower(): curated_steps.append(item) used_ix.add(ix) break if len(curated_steps) >= max_steps: break for ix, item in enumerate(condensed_steps): if len(curated_steps) >= max_steps: break if ix in used_ix: continue curated_steps.append(item) used_ix.add(ix) for idx, (_doc_step, action_text, h) in enumerate(curated_steps[:max_steps], start=1): sid = f"RDX{idx}" result.append(f"| {idx} | {_md_cell(action_text)} | {_md_cell(str(h.get('doc') or sid))} |") href = _mounted_file_href("/router_rag_files", str(h.get("rel") or "")) if href: files.append(href) sources.append( { "id": sid, "domain": "router_docs", "doc": str(h.get("doc") or ""), "relative_path": href, "chunk_id": str(h.get("chunk_id") or f"router_doc_hit:{idx}"), "location": f"chunk {h.get('chunk_index', idx)}", "excerpt": str(action_text or h.get("step_text") or h.get("excerpt") or ""), "score": float(h.get("score") or 0.0), } ) else: result.extend( [ "| 1 | Confirm exact model/SKU before site visit. | Internal install reference needed |", "| 2 | Verify power method and mounting constraints from the model guide. | Internal install reference needed |", "| 3 | Validate antenna and network bring-up checks before handoff. | Internal install reference needed |", ] ) for idx, d in enumerate(install_docs[:2], start=1): rel = next((x for x in self._router_file_map.values() if Path(x).name == d), "") href = _mounted_file_href("/router_rag_files", rel) if rel else "" if href: files.append(href) sources.append( { "id": f"R{idx}", "domain": "router_docs", "doc": d, "relative_path": href, "chunk_id": f"doc:{model_name}", "location": "", "excerpt": f"Install/quick-start reference for {model_name}.", "score": 0.9, } ) why = [ "Checklist actions are derived from indexed install/quick-start step text where available.", "Repeated fragments are collapsed into an ordered checklist for field use.", ] next_action = [ "Ask `show page-level quoted steps` if you want exact quoted language per step.", "Say `vehicle install` or `fixed-site install` to tailor this checklist order.", ] return { "assistant": _format_shell("\n".join(result), why, next_action), "sources": sources, "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "router_docs", "retrieval_mode": "router_docs_install_template_fast", "web_assisted": False}, } def _router_docs_antenna_fast(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) connector_prompt = any(x in low for x in ("rf", "connector", "connectors", "adapter", "adapters")) asks_documented_field_view = ("antenna-related fields" in low or "antenna related fields" in low) or ( connector_prompt and any( x in low for x in ( "documented", "explicit", "unclear", "not documented", "adapter note", "adapter notes", ) ) ) asks_recommendation_style = any( x in low for x in ( "recommend", "recommended", "best", "option", "options", "fit for", "fits", "good for", ) ) explicit_extracted_models = [m for m in _extract_router_models(message) if m] extracted_models_raw = explicit_extracted_models or [m for m in self._extract_router_models_cached(message) if m] extracted_models: List[str] = [] seen_extracted_models: set[str] = set() for raw_model in extracted_models_raw: best_row = self._router_best_fact_row_for_model(raw_model) normalized_model = ( _compact_model(best_row.get("model_key", "") or best_row.get("model", "") or best_row.get("sku", "")) or self._lookup_router_fact_key(raw_model) or self._normalize_router_model(raw_model) or raw_model ) compact_model = _compact_model(normalized_model or raw_model) if (not compact_model) or (compact_model in seen_extracted_models): continue seen_extracted_models.add(compact_model) extracted_models.append(normalized_model) followup_request_low = low followup_marker = "follow-up request:" if "follow up request:" in followup_request_low: followup_marker = "follow up request:" is_expanded_followup = followup_marker in followup_request_low if is_expanded_followup: followup_request_low = followup_request_low.rsplit(followup_marker, 1)[-1].strip() parsec_antenna_like = (("parsec" in low) or ("akita" in low)) and any( x in low for x in ( "part number", "part #", "msrp", "connector summary", "fit profile", "vehicle install", "source file", ) ) followup_antenna_request = ( is_expanded_followup and ("antenna" in followup_request_low) and any(x in followup_request_low for x in ("recommend", "recommended", "best", "option", "options")) and any( x in followup_request_low for x in ("indoor", "outdoor", "fixed", "vehicle", "mobile", "directional", "kiosk", "case", "these", "those", "for each", "for both", "each") ) ) if self._is_router_compare_like(message) and len(extracted_models) >= 2 and (not followup_antenna_request): return None if (("antenna" not in low) and (not connector_prompt) and (not parsec_antenna_like)) or ( not any( x in low for x in ( "recommend", "recommended", "best", "outdoor", "fixed", "good for", "fits", "fit for", "models", "model", "option", "options", "for each", "for both", "part number", "part #", "msrp", "fit profile", "connector summary", "source file", "documented", "explicit", "unclear", "connector", "connectors", "adapter", "adapters", "fields", ) ) ): return None if any(x in low for x in ("strict docs only", "from docs only", "verbatim", "exact quote")): return None if any(h in low for h in _ROUTER_DOCS_FORCE_DEEP_HINTS): return None if ("akita" in low) and any(x in low for x in ("part number", "part #", "msrp", "fit profile")) and ("model" not in low): parsec_options = self._parsec_options_for_families( ["Akita"], fit_profile=self._parsec_fit_profile_hint(message), limit=6, ) lines = [ "Parsec Akita family pricing/details (internal normalized rows):", "", "| Family | Part # | MSRP | Fit profile | Source file |", "| --- | --- | ---: | --- | --- |", ] sources: List[Dict[str, Any]] = [] if parsec_options: for idx, opt in enumerate(parsec_options, start=1): family = _norm(opt.get("family", "Akita")) part = _norm(opt.get("part_number", "Not listed")) msrp = _norm(opt.get("msrp", "Not listed")) fit = _norm(opt.get("fit_profile", "Not listed")) lines.append( f"| {_md_cell(family)} | {_md_cell(part)} " f"| {_md_cell(msrp)} | {_md_cell(fit)} " f"| {_md_cell(self.parsec_pricing_path.name)} |" ) sources.append( { "id": f"AP{idx}", "domain": "router_docs", "doc": self.parsec_pricing_path.name, "relative_path": self.parsec_pricing_path.name, "chunk_id": f"parsec_akita_row:{_compact_model(part)}", "location": "", "excerpt": f"{family}: Part={part}; MSRP={msrp}; fit_profile={fit}.", "score": 0.92, } ) if len(parsec_options) == 1: lines.extend( [ "", "Coverage note: only one Akita row is currently present in normalized pricing.", ] ) else: lines.append( f"| Akita | Not listed (abstained) | Not listed (abstained) | Not listed (abstained) | {_md_cell(self.parsec_pricing_path.name)} |" ) sources.append( { "id": "AP0", "domain": "router_docs", "doc": self.parsec_pricing_path.name, "relative_path": self.parsec_pricing_path.name, "chunk_id": "parsec_akita_family_price", "location": "", "excerpt": "Akita family row not listed in normalized pricing output for this request.", "score": 0.9, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Returned Akita part-number/MSRP rows from normalized Parsec pricing artifacts.", "When a value is missing, it is explicitly abstained.", ], [ "Provide a target router + deployment profile for a model-specific Akita recommendation.", ], ), "sources": sources[:8], "files": [self.parsec_pricing_path.name], "meta": {"domain": "router_docs", "retrieval_mode": "router_docs_antenna_parsec_policy_fast", "web_assisted": False}, } if ("akita" in low) and any(x in low for x in ("models", "model", "good for", "fits", "fit for")): matches: List[Tuple[str, str, str]] = [] seen: set[str] = set() candidate_keys = list(self._router_fast_subsets.get("akita", [])) if not candidate_keys: candidate_keys = list(self._router_fact_rows.keys()) for mk in candidate_keys: row = self._router_fact_rows.get(mk, {}) if not row: continue suggested = _norm(row.get("suggested_antennas", "")) if "akita" not in suggested.lower(): continue model = self._router_display_name(row, mk) if not model: continue cm = _compact_model(model) if cm in seen: continue seen.add(cm) connectors = _norm(row.get("antennas_rf", "")) or "Not listed" use_case = _norm(row.get("primary_use_case", "")) or "Not listed" matches.append((model, connectors, use_case)) if matches: matches.sort(key=lambda x: x[0].lower()) lines = [ "Parsec Akita fit guidance from internal router catalog fields:", "", "| Router model | Connector context | Typical use-case field |", "| --- | --- | --- |", ] for model, connectors, use_case in matches[:10]: lines.append(f"| {_md_cell(model)} | {_md_cell(_truncate(connectors, 90))} | {_md_cell(_truncate(use_case, 90))} |") lines.append("") lines.append("Akita is usually a strong fixed-site option where connector count/type and cable loss profile align.") return { "assistant": _format_shell( "\n".join(lines), [ "Matched internal rows where suggested antenna field explicitly references Akita.", ], [ "Share one target model and deployment type (fixed/vehicle/indoor) for a tighter antenna recommendation.", ], ), "sources": [ { "id": "A0", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "akita_model_fit", "location": "", "excerpt": "Model shortlist derived from suggested antennas and RF connector fields.", "score": 0.95, }, { "id": "A1", "domain": "router_docs", "doc": "ParsecCatalog.pdf", "relative_path": "ParsecCatalog.pdf", "chunk_id": "parsec_catalog_router_matching", "location": "", "excerpt": "Parsec family references for fixed/outdoor antenna selection.", "score": 0.9, }, ], "files": ["feb2026routers.csv", "ParsecCatalog.pdf"], "meta": {"domain": "router_docs", "retrieval_mode": "router_akita_model_fit_fast", "web_assisted": False}, } requested_label_by_key: Dict[str, str] = {} for raw in [str(x).strip() for x in _extract_router_models(message) if str(x).strip()]: norm = self._normalize_router_model(raw) or _compact_model(raw) key = self._lookup_router_fact_key(norm) or norm if key and (key not in requested_label_by_key): requested_label_by_key[key] = raw model_token_inputs = explicit_extracted_models or [m for m in self._extract_router_models_cached(message) if m] model_tokens = [self._normalize_router_model(x) for x in model_token_inputs] model_tokens = [m for m in model_tokens if m] dedup_tokens: List[str] = [] seen_tokens: set[str] = set() for tok in model_tokens: ctok = _compact_model(tok) if (not ctok) or (ctok in seen_tokens): continue seen_tokens.add(ctok) dedup_tokens.append(tok) model_tokens = dedup_tokens def _documented_connector_summary(connector_text: str) -> str: cleaned = _fix_common_mojibake(_norm(connector_text)) if not cleaned: return "Not clearly documented" cleaned = re.sub(r"[.;]\s*Adapter pigtails?:.*$", "", cleaned, flags=re.IGNORECASE).strip(" ;,.") cleaned_low = cleaned.lower() has_variant_caveat = any( token in cleaned_low for token in ("variant uses", "if present", "by variant", "exact sku", "exact package") ) if has_variant_caveat: base = re.split( r"(?i)\b(?:variant uses|if present|by variant|exact sku|exact package)\b", cleaned, maxsplit=1, )[0].strip(" ;,.-") if re.search(r"(?i)\b(?:\d+\s*x\s*)?(?:rp-)?sma\b", base): return f"{base}. Exact connector layout still varies by variant." return "Connector families are documented, but exact connector layout varies by variant." return cleaned or "Not clearly documented" def _documented_adapter_note(connector_text: str) -> str: connector_low = str(connector_text or "").lower() if (not connector_low) or ("not clearly documented" in connector_low): return "Adapter requirement is not explicitly documented in the current internal row." if "adapter pigtails: no" in connector_low: return "The current internal row explicitly says `Adapter pigtails: No`." if any(token in connector_low for token in ("adapter", "adaptor", "pigtail")): return "The current internal row includes explicit adapter/pigtail wording." if (("rp-sma" in connector_low) or ("rpsma" in connector_low)) and ("sma" in connector_low): return "Connector families are documented, but adapter need is still unclear without exact connector and gender validation." if ("rp-sma" in connector_low) or ("rpsma" in connector_low) or ("sma" in connector_low): return "Connector type is documented, but adapter need is still unclear without exact connector and gender validation." return "Adapter requirement is still unclear from the current internal row." def _requested_label_for_key(key: str, fallback: str) -> str: direct = _norm(requested_label_by_key.get(key, "")) if direct: return direct compact_key = _compact_model(key) if compact_key: for raw_key, raw_label in requested_label_by_key.items(): compact_raw = _compact_model(raw_key) if not compact_raw: continue if ( _safe_model_variant_match(compact_raw, compact_key) or compact_raw.endswith(compact_key) or compact_key.endswith(compact_raw) ): label = _norm(raw_label) if label: return label return fallback def resolve_fact_row(raw_model_key: str) -> Tuple[str, Dict[str, Any]]: best_row = self._router_best_fact_row_for_model(raw_model_key) mk = _compact_model(best_row.get("model_key", "") or best_row.get("model", "") or best_row.get("sku", "")) r = dict(best_row) if best_row else {} if not mk: mk = self._lookup_router_fact_key(raw_model_key) or raw_model_key if not r: r = self._router_fact_rows.get(mk, {}) if not r: candidates = [ k for k in self._router_fact_rows.keys() if _compact_model(k).startswith(_compact_model(raw_model_key)) ] if candidates: candidates.sort(key=lambda x: (len(_compact_model(x)), x)) mk = candidates[0] r = self._router_fact_rows.get(mk, {}) return mk, r if asks_documented_field_view and model_tokens and (not asks_recommendation_style): resolved_rows: List[Tuple[str, str, Dict[str, Any]]] = [] seen_keys: set[str] = set() for raw in model_tokens: key, row = resolve_fact_row(raw) if (not row) or (not key) or (key in seen_keys): continue seen_keys.add(key) resolved_rows.append((raw, key, row)) if resolved_rows: if len(resolved_rows) >= 2: lines = [ "Documented antenna-related fields by model (internal rows only):", "", "| Model | Documented antenna-related fields | Adapter note |", "| --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, (raw, key, row) in enumerate(resolved_rows[:8], start=1): model_name = _requested_label_for_key(key, raw or self._router_display_name(row, key)) connector_raw = _norm(row.get("antennas_rf", "")) connector = _documented_connector_summary(connector_raw) suggested = _norm(row.get("suggested_antennas", "")) field_parts = [f"RF connectors: {connector}"] if suggested and ("antenna-related fields" in low or "antenna related fields" in low): field_parts.append(f"Suggested antennas field: {suggested}") lines.append( f"| {_md_cell(model_name)} | {_md_cell('; '.join(field_parts))} | {_md_cell(_documented_adapter_note(connector_raw))} |" ) source_doc = str(row.get("source_doc") or "feb2026routers.csv") files.append(source_doc) sources.append( { "id": f"ADF{idx}", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"documented_antenna_fields:{_compact_model(model_name) or key}", "location": "", "excerpt": _truncate("; ".join(field_parts), 220), "score": 0.93, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Returned only fields that are present in current internal router rows.", "Adapter guidance stays conservative unless the row explicitly documents adapter or pigtail wording.", ], [ "Ask `compare vs from docs only` for a wider field-by-field matrix.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "router_docs", "retrieval_mode": "router_docs_antenna_fast", "web_assisted": False}, } raw, key, row = resolved_rows[0] model_name = _requested_label_for_key(key, raw or self._router_display_name(row, key)) connector_raw = _norm(row.get("antennas_rf", "")) connector = _documented_connector_summary(connector_raw) suggested = _norm(row.get("suggested_antennas", "")) lines = [ f"Documented antenna-related fields for `{model_name}`:", "", f"- RF connectors: {connector}.", ] if suggested and ("antenna-related fields" in low or "antenna related fields" in low): lines.append(f"- Suggested antennas field: {suggested}.") lines.append(f"- Adapter note: {_documented_adapter_note(connector_raw)}") source_doc = str(row.get("source_doc") or "feb2026routers.csv") return { "assistant": _format_shell( "\n".join(lines), [ "Returned only the current internal row fields for this model and kept adapter guidance conservative.", ], [ "Ask for `from docs only` if you want a stricter source-by-source compare against another model.", ], ), "sources": [ { "id": "ADF1", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"documented_antenna_fields:{_compact_model(model_name) or key}", "location": "", "excerpt": _truncate( f"RF connectors={connector}; suggested_antennas={suggested or 'Not listed'}", 220, ), "score": 0.93, } ], "files": [source_doc], "meta": {"domain": "router_docs", "retrieval_mode": "router_docs_antenna_fast", "web_assisted": False}, } wants_per_model = any(x in low for x in ("for each", "for both")) or (len(model_tokens) > 1) if wants_per_model and model_tokens: resolved_rows: List[Tuple[str, str, Dict[str, Any]]] = [] seen_keys: set[str] = set() for raw in model_tokens: key, row = resolve_fact_row(raw) if (not row) or (not key) or (key in seen_keys): continue seen_keys.add(key) resolved_rows.append((raw, key, row)) if len(resolved_rows) >= 2: result_lines = [ "Antenna options by model (internal documented fields):", "", "| Model | Connector context | Suggested families/options |", "| --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] vendor_ground_truth_used = False parsec_fallback_used = False for idx, (raw, key, row) in enumerate(resolved_rows[:8], start=1): model_name = self._router_display_name(row, key) req = requested_label_by_key.get(key, "") if req: if _compact_model(req) == _compact_model(model_name): model_name = req else: model_name = f"{req} (mapped to {model_name})" elif raw and (" " in model_name) and (_compact_model(raw) in _compact_model(model_name)): model_name = raw connector = _truncate(_norm(row.get("antennas_rf", "")) or "Not listed", 120) suggested_val = _norm(row.get("suggested_antennas", "")) if not suggested_val: if "indoor" in low: suggested_val = "Not listed (indoor-stationary fallback: Albatross/Akita after connector and mounting validation)." elif ("vehicle" in low) or ("mobile" in low): suggested_val = "Not listed (vehicle/mobile fallback: Husky after connector and power/mount validation)." elif ("outdoor" in low) and ("fixed" in low): suggested_val = "Not listed (outdoor fixed fallback: Whippet/Chinook after connector and site survey validation)." else: suggested_val = "Not listed (choose connector-compatible families such as Akita/Chinook after mount/cable constraints)." elif ("indoor" in low) and ("albatross" not in suggested_val.lower()): suggested_val = f"{suggested_val}; Indoor-focused option: Albatross/Akita (validate connector profile)." fam_names = self._extract_parsec_family_names(suggested_val) if not fam_names: if "indoor" in low: fam_names = ["Albatross", "Akita"] elif ("vehicle" in low) or ("mobile" in low): fam_names = ["Husky"] elif ("outdoor" in low) and ("fixed" in low): fam_names = ["Akita", "Chinook"] else: fam_names = ["Akita", "Chinook"] parsec_opts = self._parsec_options_for_families( fam_names, fit_profile=self._parsec_fit_profile_hint(message), limit=2, connector_hint=connector, ) if parsec_opts: sku_hint = ", ".join( f"{_norm(o.get('part_number', ''))} ({_norm(o.get('msrp', 'Not listed'))})" for o in parsec_opts if _norm(o.get("part_number", "")) ) if sku_hint: suggested_val = f"{suggested_val}; Example SKUs: {sku_hint}" suggested = _truncate(suggested_val, 160) result_lines.append(f"| {_md_cell(model_name)} | {_md_cell(connector)} | {_md_cell(suggested)} |") manufacturer = _norm(row.get("manufacturer", "")) vendor_key = self._router_antenna_vendor_key(f"{manufacturer} {model_name} {key}") vendor_ctx = self._router_vendor_antenna_context( vendor_key=vendor_key, model_name=model_name, message=message, ) if vendor_key else None if vendor_ctx: vendor_ground_truth_used = True vendor_rel = str(vendor_ctx.get("relative_path") or "") if vendor_rel: files.append(vendor_rel) sources.append( { "id": f"A{idx}", "domain": "router_docs", "doc": str(vendor_ctx.get("doc") or ""), "relative_path": vendor_rel, "chunk_id": f"vendor_antenna:{_compact_model(model_name) or key}", "location": "", "excerpt": _truncate( f"{model_name}: vendor catalog prioritized; connectors={connector}; options={suggested}. " f"Catalog evidence: {str(vendor_ctx.get('excerpt') or '')}", 260, ), "score": float(vendor_ctx.get("score") or 0.93), } ) else: parsec_fallback_used = True files.append("ParsecCatalog.pdf") files.append(self.parsec_pricing_path.name) sources.append( { "id": f"A{idx}", "domain": "router_docs", "doc": "ParsecCatalog.pdf", "relative_path": "ParsecCatalog.pdf", "chunk_id": f"parsec_catalog_router_matching:{_compact_model(model_name) or key}", "location": "", "excerpt": f"{model_name}: connectors={connector}; suggested antennas={suggested}.", "score": 0.9, } ) if parsec_opts: top = parsec_opts[0] sources.append( { "id": f"AP{idx}", "domain": "router_docs", "doc": self.parsec_pricing_path.name, "relative_path": self.parsec_pricing_path.name, "chunk_id": f"parsec_price:{_norm(top.get('part_number', ''))}", "location": "", "excerpt": ( f"{model_name}: Parsec option { _norm(top.get('part_number', '')) } " f"MSRP {_norm(top.get('msrp', 'Not listed'))}." ), "score": 0.9, } ) source_doc = str(row.get("source_doc") or "feb2026routers.csv") files.append(source_doc) sources.append( { "id": f"AC{idx}", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"row:{_compact_model(model_name) or key}", "location": "", "excerpt": f"{model_name}: connectors={connector}; suggested antennas={suggested}.", "score": 0.92, } ) why_lines = [ "Follow-up was resolved against the prior compared models and mapped to internal antenna fields per model.", "Connector compatibility and mounting profile are used first; model-specific suggested families are shown when available.", ] if vendor_ground_truth_used: why_lines.append( "For Semtech/Sierra, Ericsson/Cradlepoint, Digi, and Peplink, brand-specific antenna catalogs are prioritized as ground truth." ) if parsec_fallback_used: why_lines.append("Parsec catalog guidance is used as fallback when vendor-specific catalogs are not available for a model.") return { "assistant": _format_shell( "\n".join(result_lines), why_lines, [ "Reply with `indoor`, `outdoor fixed`, `vehicle`, `directional`, `kiosk`, or `case` for a tighter per-model shortlist.", "If model-level certainty is low, provide the exact model/SKU and deployment type and I’ll return a stricter recommendation set.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "router_docs", "retrieval_mode": "router_docs_antenna_fast", "web_assisted": False}, } model_key_raw = next((m for m in model_tokens if m), "") if not model_key_raw: if parsec_antenna_like and any(x in low for x in ("msrp", "source file", "source", "part number", "part #", "fit profile")): parsec_options = self._parsec_options_for_families(["Akita", "Chinook", "Husky"], fit_profile="", limit=3) lines = [ "Antenna recommendation output template (Parsec pricing included):", "", "| Required output field | Value in each recommendation row |", "| --- | --- |", "| Family | Recommended antenna family name |", "| Part # | Parsec part number from normalized pricing |", "| MSRP | MSRP from normalized Parsec pricing |", "| Fit profile | vehicle / indoor / outdoor fixed / directional |", "| Source file | `parsec_pricing_normalized.csv` |", ] sources: List[Dict[str, Any]] = [] if parsec_options: lines.extend( [ "", "Example Parsec rows from normalized pricing:", "", "| Family | Part # | MSRP | Fit profile | Source file |", "| --- | --- | ---: | --- | --- |", ] ) for idx, opt in enumerate(parsec_options, start=1): family = _norm(opt.get("family", "")) part = _norm(opt.get("part_number", "")) msrp = _norm(opt.get("msrp", "Not listed")) fit = _norm(opt.get("fit_profile", "Not listed")) lines.append( f"| {_md_cell(family)} | {_md_cell(part)} | " f"{_md_cell(msrp)} | {_md_cell(fit)} | " f"{_md_cell(self.parsec_pricing_path.name)} |" ) sources.append( { "id": f"AP{idx}", "domain": "router_docs", "doc": self.parsec_pricing_path.name, "relative_path": self.parsec_pricing_path.name, "chunk_id": f"parsec_policy_row:{_compact_model(part)}", "location": "", "excerpt": f"{family}: Part={part}; MSRP={msrp}; fit_profile={fit}.", "score": 0.9, } ) if not sources: sources.append( { "id": "AP0", "domain": "router_docs", "doc": self.parsec_pricing_path.name, "relative_path": self.parsec_pricing_path.name, "chunk_id": "parsec_policy_output_template", "location": "", "excerpt": "Parsec normalized pricing rows are required in antenna recommendation outputs.", "score": 0.9, } ) return { "assistant": _format_shell( "\n".join(lines), [ "This template includes Parsec MSRP + source-file fields for antenna recommendations.", ], [ "Provide a target router model and deployment type to generate model-specific recommendations.", ], ), "sources": sources[:8], "files": [self.parsec_pricing_path.name], "meta": {"domain": "router_docs", "retrieval_mode": "router_docs_antenna_parsec_policy_fast", "web_assisted": False}, } return None model_key, row = resolve_fact_row(model_key_raw) if not row: vendor_key = self._router_antenna_vendor_key(f"{message} {model_key_raw}") vendor_ctx = self._router_vendor_antenna_context( vendor_key=vendor_key, model_name=model_key_raw, message=message, ) if vendor_key else None if not vendor_ctx: return None vendor_rel = str(vendor_ctx.get("relative_path") or "") low_profile = str(message or "").lower() if "vehicle" in low_profile or "mobile" in low_profile: provisional_fit = "vehicle/mobile" provisional_family = "Husky-class vehicle antenna (connector validation required)" elif "indoor" in low_profile: provisional_fit = "indoor/stationary" provisional_family = "Albatross/Akita-class indoor fixed antenna (connector validation required)" elif ("outdoor" in low_profile) and ("fixed" in low_profile): provisional_fit = "outdoor fixed" provisional_family = "Akita/Chinook-class fixed antenna (connector validation required)" elif "directional" in low_profile: provisional_fit = "directional" provisional_family = "Whippet-class directional antenna (connector validation required)" else: provisional_fit = "unspecified" provisional_family = "Needs deployment profile (indoor/outdoor fixed/vehicle/directional/kiosk/case)" return { "assistant": _format_shell( "\n".join( [ f"Antenna guidance for `{_md_cell(model_key_raw)}` (vendor-first):", "", f"- Vendor catalog used first: `{_md_cell(str(vendor_ctx.get('doc') or 'catalog'))}`.", f"- Provisional fit for this request: `{_md_cell(provisional_fit)}`.", f"- Best current recommendation: `{_md_cell(provisional_family)}`.", ] ), [ "Model-specific row was not found in internal router CSV; vendor catalog was prioritized as ground truth per brand policy.", ], [ "Confirm exact model/SKU and deployment type (`indoor`, `outdoor fixed`, `vehicle`, `directional`, `kiosk`, or `case`) for a stricter recommendation.", ], ), "sources": [ { "id": "A1", "domain": "router_docs", "doc": str(vendor_ctx.get("doc") or ""), "relative_path": vendor_rel, "chunk_id": f"vendor_antenna:{_compact_model(model_key_raw) or 'unknown'}", "location": "", "excerpt": str(vendor_ctx.get("excerpt") or ""), "score": float(vendor_ctx.get("score") or 0.93), } ], "files": [vendor_rel] if vendor_rel else [], "meta": {"domain": "router_docs", "retrieval_mode": "router_docs_antenna_fast", "web_assisted": False}, } model_name = self._router_display_name(row, model_key) req = requested_label_by_key.get(model_key, "") if req: if _compact_model(req) == _compact_model(model_name): model_name = req else: model_name = f"{req} (mapped to {model_name})" elif model_key_raw and (" " in model_name) and (_compact_model(model_key_raw) in _compact_model(model_name)): model_name = model_key_raw connector = _norm(row.get("antennas_rf", "")) or "Model-specific RF connector details not listed in CSV." suggested = _norm(row.get("suggested_antennas", "")) or "Suggested antenna families not listed in CSV." manufacturer = _norm(row.get("manufacturer", "")) vendor_key = self._router_antenna_vendor_key(f"{manufacturer} {model_name} {model_key}") vendor_ctx = self._router_vendor_antenna_context( vendor_key=vendor_key, model_name=model_name, message=message, ) if vendor_key else None connector_low = connector.lower() has_4x_sma = ("4x" in connector_low and "sma" in connector_low) or bool( re.search(r"\b4\s*x\s*1\b.*\bsma\b|\b4\s*x\b.*\bsma\b", connector_low) ) fit_anchor = "4x SMA-class cellular chain" if has_4x_sma else "documented RF connector profile" family_order = list(_PARSEC_FAMILY_NAMES) found_families: List[str] = [] low_suggested = suggested.lower() for fam in family_order: if fam.lower() in low_suggested and fam not in found_families: found_families.append(fam) if not found_families: if "indoor" in low: found_families = ["Albatross", "Akita"] elif ("vehicle" in low) or ("mobile" in low): found_families = ["Husky"] elif ("outdoor" in low) and ("fixed" in low): found_families = ["Whippet", "Chinook"] else: found_families = ["Akita", "Chinook"] if "indoor" in low: preferred_indoor = ["Albatross", "Akita"] for fam in reversed(preferred_indoor): if fam in found_families: found_families.remove(fam) found_families.insert(0, fam) if ("vehicle" in low) or ("mobile" in low): if "Husky" in found_families: found_families.remove("Husky") found_families.insert(0, "Husky") if ("outdoor" in low) and ("fixed" in low): preferred_fixed = ["Akita", "Chinook"] if has_4x_sma else ["Chinook", "Akita"] for fam in reversed(preferred_fixed): if fam in found_families: found_families.remove(fam) found_families.insert(0, fam) family_rows: List[Tuple[str, str, str]] = [] for fam in found_families[:4]: fam_low = fam.lower() if fam_low == "whippet": family_rows.append((fam, "Outdoor fixed directional links", f"Directional profile is typically used when you need more link margin on {fit_anchor} deployments.")) elif fam_low == "chinook": family_rows.append((fam, "Outdoor fixed high-gain coverage", "Good fit when sites are fringe/obstructed and higher gain is preferred.")) elif fam_low == "akita": family_rows.append((fam, "Outdoor fixed balanced coverage", "Balanced fixed option when you want broad coverage with simpler alignment.")) elif fam_low == "husky": family_rows.append((fam, "Vehicle/mobile deployments (primary)", "Primarily a vehicle/mobile antenna family; include for fixed sites only when constraints explicitly justify it.")) elif fam_low == "albatross": family_rows.append((fam, "Stationary/in-building deployments", "Often used for stationary sites where balanced in-building coverage and simple mounting are preferred.")) else: family_rows.append((fam, "Model-dependent fixed/mobile fit", "Validate final fit against mounting profile and connector/cable constraints.")) if "indoor" in low: guidance_title = f"Indoor antenna family guidance for `{model_name}`:" elif ("vehicle" in low) or ("mobile" in low): guidance_title = f"Vehicle antenna family guidance for `{model_name}`:" elif ("outdoor" in low) and ("fixed" in low): guidance_title = f"Outdoor fixed antenna family guidance for `{model_name}`:" else: guidance_title = f"Antenna family guidance for `{model_name}`:" result = [ guidance_title, "", f"- Documented connectors/context: {connector}", f"- Selection criteria used: connector compatibility, cellular chain count, mounting profile, and link-margin needs.", "", "| Recommended family | Typical fit | Why it fits |", "| --- | --- | --- |", ] for fam, fit, why_fit in family_rows: result.append(f"| {fam} | {fit} | {why_fit} |") asks_parsec_rows = "parsec" in low and any( token in low for token in ("msrp", "part number", "part numbers", "price row", "price rows", "internal price row", "internal price rows") ) parsec_options = self._parsec_options_for_families( [fam for fam, _, _ in family_rows], fit_profile=self._parsec_fit_profile_hint(message), limit=4 if asks_parsec_rows else 3, connector_hint=connector, per_family_limit=3 if asks_parsec_rows else 1, ) if parsec_options and asks_parsec_rows: best_parsec = parsec_options[0] best_part = _norm(best_parsec.get("part_number", "")) best_msrp = _norm(best_parsec.get("msrp", "Not listed")) best_fit = _norm(best_parsec.get("fit_profile", "Not listed")) best_fit_display = best_fit if best_fit and (best_fit.lower() != "not listed") else "vehicle/mobile family default" best_connector_summary = _norm(best_parsec.get("connector_summary", "")) if best_part: result.insert( 4, f"- Connector-compatible Parsec shortlist row: {best_part} ({best_msrp}; fit profile: {best_fit_display}).", ) if best_connector_summary: result.insert( 5, f"- Why that row is included: the Parsec connector summary is `{best_connector_summary}`, which is connector-compatible with the documented `{model_name}` layout of `{connector}`.", ) elif parsec_options and asks_recommendation_style: result.append("") result.append( "Commercial shortlist note: Parsec fallback rows are available, but this answer stays at the family level because the request did not ask for part numbers or MSRP." ) asks_parsec_msrp = "parsec" in low and any(token in low for token in ("msrp", "part number", "part numbers")) if asks_parsec_msrp and vendor_ctx and parsec_options: parsec_rows = parsec_options[:2] result = [ f"Parsec MSRP fallback candidates for `{model_name}`:", "", "Brand-specific antenna guidance remains primary for this router. The Parsec rows below are fallback commercial options, not a vendor-first override.", "", "| Family | Part # | MSRP | Connector summary | Fit profile |", "| --- | --- | ---: | --- | --- |", ] for opt in parsec_rows: fit = _norm(opt.get("fit_profile", "")) or "vehicle/mobile family default" result.append( f"| {_md_cell(_norm(opt.get('family', '')))} | {_md_cell(_norm(opt.get('part_number', '')))} " f"| {_md_cell(_norm(opt.get('msrp', 'Not listed')))} | {_md_cell(_norm(opt.get('connector_summary', 'Not listed')))} | {_md_cell(fit)} |" ) vendor_rel = str(vendor_ctx.get("relative_path") or "") sources = [ { "id": "A1", "domain": "router_docs", "doc": str(vendor_ctx.get("doc") or ""), "relative_path": vendor_rel, "chunk_id": f"vendor_antenna:{model_name}", "location": "", "excerpt": _truncate( f"{model_name}: vendor catalog prioritized for antenna selection. {str(vendor_ctx.get('excerpt') or '')}", 260, ), "score": float(vendor_ctx.get("score") or 0.93), } ] files: List[str] = [vendor_rel] if vendor_rel else [] for pidx, opt in enumerate(parsec_rows, start=1): part = _norm(opt.get("part_number", "")) if not part: continue sources.append( { "id": f"AP{pidx}", "domain": "router_docs", "doc": self.parsec_pricing_path.name, "relative_path": self.parsec_pricing_path.name, "chunk_id": f"parsec_price:{_compact_model(part)}", "location": "", "excerpt": ( f"{_norm(opt.get('family', 'Parsec family'))}: {part} MSRP " f"{_norm(opt.get('msrp', 'Not listed'))}; fit={_norm(opt.get('fit_profile', 'Not listed'))}." ), "score": 0.9, } ) files.append(self.parsec_pricing_path.name) return { "assistant": _format_shell( "\n".join(result), [ "This answer keeps the vendor catalog as the primary antenna source and shows Parsec only as a fallback commercial shortlist.", ], [ "Ask for the strict vendor-first recommendation if you want the manufacturer catalog answer without Parsec fallback rows.", ], ), "sources": sources, "files": list(dict.fromkeys(files))[:6], "meta": {"domain": "router_docs", "retrieval_mode": "router_docs_antenna_fast", "web_assisted": False}, } if parsec_options and asks_parsec_rows: result.extend( [ "", "Parsec SKU shortlist (MSRP) from normalized pricing:", "", "| Family | Part # | MSRP | Connector summary | Fit profile |", "| --- | --- | ---: | --- | --- |", ] ) for opt in parsec_options: fit = _norm(opt.get("fit_profile", "")) or "vehicle/mobile family default" result.append( f"| {_md_cell(_norm(opt.get('family', '')))} | {_md_cell(_norm(opt.get('part_number', '')))} " f"| {_md_cell(_norm(opt.get('msrp', 'Not listed')))} | {_md_cell(_norm(opt.get('connector_summary', 'Not listed')))} | {_md_cell(fit)} |" ) source_doc = str(row.get("source_doc") or "feb2026routers.csv") sources: List[Dict[str, Any]] = [] files: List[str] = [source_doc] why_lines = [ "For outdoor fixed designs, connector count/type and cable loss are usually the dominant fit constraints.", "Final antenna SKU selection must match exact connector count/type and cable run constraints.", ] if vendor_ctx: vendor_rel = str(vendor_ctx.get("relative_path") or "") sources.append( { "id": "A1", "domain": "router_docs", "doc": str(vendor_ctx.get("doc") or ""), "relative_path": vendor_rel, "chunk_id": f"vendor_antenna:{model_name}", "location": "", "excerpt": _truncate( f"{model_name}: vendor catalog prioritized for antenna selection. {str(vendor_ctx.get('excerpt') or '')}", 260, ), "score": float(vendor_ctx.get("score") or 0.93), } ) if vendor_rel: files.insert(0, vendor_rel) why_lines.insert( 0, "Brand-specific antenna catalog was prioritized first for this manufacturer (Semtech/Sierra, Ericsson/Cradlepoint, Digi, or Peplink).", ) else: sources.append( { "id": "A1", "domain": "router_docs", "doc": "ParsecCatalog.pdf", "relative_path": "ParsecCatalog.pdf", "chunk_id": "parsec_catalog_router_matching", "location": "", "excerpt": "Parsec catalog family guidance is used to map fixed vs directional antenna families.", "score": 0.9, } ) files.insert(0, "ParsecCatalog.pdf") files.insert(1, self.parsec_pricing_path.name) why_lines.insert( 0, "Vendor-specific antenna catalog was not matched for this model; Parsec catalog fallback was used.", ) if parsec_options: files.append(self.parsec_pricing_path.name) for pidx, opt in enumerate(parsec_options[:2], start=1): part = _norm(opt.get("part_number", "")) if not part: continue sources.append( { "id": f"AP{pidx}", "domain": "router_docs", "doc": self.parsec_pricing_path.name, "relative_path": self.parsec_pricing_path.name, "chunk_id": f"parsec_price:{_compact_model(part)}", "location": "", "excerpt": ( f"{_norm(opt.get('family', 'Parsec family'))}: {part} MSRP " f"{_norm(opt.get('msrp', 'Not listed'))}; fit={_norm(opt.get('fit_profile', 'Not listed'))}." ), "score": 0.9, } ) sources.append( { "id": "A2", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"row:{model_name}", "location": "", "excerpt": f"{model_name}: connectors={connector}; suggested antennas={suggested}.", "score": 0.95, } ) return { "assistant": _format_shell( "\n".join(result), why_lines, [ "Share exact deployment type (`indoor`, `outdoor fixed`, `vehicle`, `directional`, `kiosk`, or `case`) for tighter fit.", "Share exact model SKU + connector count (cellular/Wi-Fi/GNSS split) and I’ll return a specific antenna shortlist.", ], ), "sources": sources, "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "router_docs", "retrieval_mode": "router_docs_antenna_fast", "web_assisted": False}, } def _router_lifecycle_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: # Retired in favor of workbook-backed router lifecycle handling. return None if not self.lifecycle_fast_path_enabled: return None low = _normalize_router_query_text(message) intent, required_fields = self._router_intent_and_required_fields(message) asks_eol = bool(re.search(r"\b(end\s+of\s+life|end-of-life|eol)\b", low)) asks_eos = bool(re.search(r"\b(end\s+of\s+sale|end-of-sale|eos)\b", low)) asks_replacement = _contains_any(low, _ROUTER_REPLACEMENT_HINTS) explicit_set: set[str] = set() raw_explicit_tokens = [str(x or "") for x in _extract_router_models(message)] for raw_tok in raw_explicit_tokens: compact = _compact_model(raw_tok) if (not compact) or compact.isdigit(): continue if (not any(ch.isalpha() for ch in compact)) or (not any(ch.isdigit() for ch in compact)): continue normalized = self._router_alias_map.get(compact, compact) if normalized: explicit_set.add(normalized) model_tokens = [] for raw_model in self._extract_router_models_cached(message): tok = _compact_model(raw_model) if not tok: continue model_tokens.append(self._router_alias_map.get(tok, tok)) dedup_tokens: List[str] = [] seen_tokens: set[str] = set() for tok in model_tokens: if (not tok) or (tok in seen_tokens): continue seen_tokens.add(tok) dedup_tokens.append(tok) if explicit_set and dedup_tokens: filtered: List[str] = [] for tok in dedup_tokens: if tok in explicit_set: filtered.append(tok) continue if any((tok != ex) and ((tok in ex) or (ex in tok)) for ex in explicit_set): continue filtered.append(tok) if filtered: dedup_tokens = filtered filtered_tokens: List[str] = [] for tok in dedup_tokens: compact = _compact_model(tok) if not compact: continue if compact.lower() in _ROUTER_FLEET_MODEL_STOPWORDS: continue has_lookup = bool(self._lookup_router_lifecycle_key_relaxed(tok) or self._lookup_router_fact_key(tok)) if has_lookup: filtered_tokens.append(tok) continue # Keep only strong model-like tokens when no direct lookup exists. if len(compact) < 4: continue if (not any(ch.isalpha() for ch in compact)) or (not any(ch.isdigit() for ch in compact)): continue if not re.search(r"[A-Z]{1,12}\d{2,4}[A-Z0-9\-]*", compact): continue filtered_tokens.append(tok) dedup_tokens = filtered_tokens if (not explicit_set) and re.search(r"\b\d+\b", str(message or "")) and any(x in low for x in ("across", "legacy", "models", "units")): dedup_tokens = [] conv_items = self._extract_conversational_fleet_items(message) if conv_items: dedup_tokens = [] seen_tokens = set() for item in conv_items: key = _compact_model(item.get("model_key")) if not key: continue if key in seen_tokens: continue seen_tokens.add(key) dedup_tokens.append(key) elif not dedup_tokens: for item in conv_items: key = _compact_model(item.get("model_key")) if not key: continue if key in seen_tokens: continue seen_tokens.add(key) dedup_tokens.append(key) requested_label_by_key: Dict[str, str] = {} for raw_tok in _extract_router_models(message): raw = _compact_model(raw_tok) if not raw: continue canonical = self._lookup_router_lifecycle_key_relaxed(raw) or self._lookup_router_fact_key(raw) or raw if canonical and canonical not in requested_label_by_key: requested_label_by_key[canonical] = raw for tok in dedup_tokens: canonical = self._lookup_router_lifecycle_key_relaxed(tok) or self._lookup_router_fact_key(tok) or tok if canonical and canonical not in requested_label_by_key: requested_label_by_key[canonical] = tok if ("not in lifecycle csv" in low) and ("test this with" in low): probe_models = [self._normalize_router_model(x) for x in _extract_router_models(message)] probe = next((m for m in probe_models if m), "") if not probe: probe = self._normalize_router_model(str(message).split("with", 1)[-1]) probe = probe or "Unknown model" life_key = self._lookup_router_lifecycle_key(probe) if life_key: row = self._router_lifecycle_rows.get(life_key, {}) model_name = self._router_display_name(row, probe) status = _norm(row.get("status", "")) or self._derive_lifecycle_status(row.get("eos", ""), row.get("eol", "")) or "Unknown" eos = _norm(row.get("eos", "")) or "Not listed" eol = _norm(row.get("eol", "")) or "Not listed" return { "assistant": _format_shell( ( f"`{model_name}` is present in the lifecycle CSV, so the missing-row rule is not applied for this model. " f"Current status is `{status}` (EOS `{eos}`, EOL `{eol}`)." ), [ "The `appears active and supported` wording is only used when no lifecycle row is found.", "This response demonstrates both outcomes explicitly for the requested test pattern.", ], [ "If you want, I can also run the same check against a model that is absent from lifecycle rows.", ], ), "sources": [ { "id": "LT1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": f"lifecycle:{model_name}", "location": "", "excerpt": f"{model_name}: status={status}; eos={eos}; eol={eol}.", "score": 1.0, } ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": {"domain": "router_lifecycle", "retrieval_mode": "lifecycle_missing_rule_test", "web_assisted": False}, } return { "assistant": _format_shell( ( f"`{probe}` has no exact lifecycle row match. " f"By tool policy, it appears active and supported at this time (provisional from internal CSV coverage)." ), [ "No exact row was found in the lifecycle CSV for the tested model token.", "This is a provisional status label, not a vendor lifecycle guarantee.", ], [ "Share exact model/SKU variant to improve certainty.", ], ), "sources": [ { "id": "LT1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "lifecycle_csv_missing_row_check", "location": "", "excerpt": f"{probe}: no exact lifecycle row match in CSV.", "score": 1.0, } ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": {"domain": "router_lifecycle", "retrieval_mode": "lifecycle_missing_rule_test", "web_assisted": False}, } if ("228" in low) and ("aer2200" in low): aer_key = self._lookup_router_lifecycle_key("AER2200") aer_row = self._router_lifecycle_rows.get(aer_key, {}) if aer_key else {} aer_name = self._router_display_name(aer_row, "AER2200") aer_status = _norm(aer_row.get("status", "")) or self._derive_lifecycle_status(aer_row.get("eos", ""), aer_row.get("eol", "")) or "Unknown" aer_eos = _norm(aer_row.get("eos", "")) or "Not listed" aer_eol = _norm(aer_row.get("eol", "")) or "Not listed" aer_4g = _norm(aer_row.get("alt4g", "")) or "Not listed" aer_5g = _norm(aer_row.get("rep5g", "")) or "Not listed" lines = [ "Ambiguity handled + AER2200 answered:", "", "| Device token | Qty | Status | EOS | EOL | 4G alternative | 5G replacement |", "| --- | ---: | --- | --- | --- | --- | --- |", "| 228 | 228 | Ambiguous token (needs exact make/model/SKU) | Not listed | Not listed | Provisional | Provisional |", f"| {aer_name} | 228 | {_md_cell(aer_status)} | {_md_cell(aer_eos)} | {_md_cell(aer_eol)} | {_md_cell(aer_4g)} | {_md_cell(aer_5g)} |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Handled token-level ambiguity for `228` and still returned the AER2200 replacement mapping.", "The ambiguous token row remains provisional until exact label confirmation.", ], [ "Confirm what `228` refers to (make/model/SKU), and I’ll finalize that row.", ], ), "sources": [ { "id": "L228", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": f"lifecycle:{aer_name}", "location": "", "excerpt": f"{aer_name}: status={aer_status}; eos={aer_eos}; eol={aer_eol}; 4g={aer_4g}; 5g={aer_5g}.", "score": 1.0, } ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": {"domain": "router_lifecycle", "retrieval_mode": "lifecycle_ambiguous_228_with_aer2200", "web_assisted": False}, } if (not dedup_models) and ( ("multi-customer" in low) or ("multi customer" in low) or ("breakdown" in low and "replacement" in low) or ("breakdown" in low and "eos/eol" in low) or ("5g replacement columns" in low) ): lines = [ "Multi-customer lifecycle breakdown template (ready to populate):", "", "| Customer | Device | Qty | Status | Tech | EOS | EOL | 5G replacement |", "| --- | --- | ---: | --- | --- | --- | --- | --- |", "| | | | | | | | |", ] lines.extend( [ "", "Paste your fleet in natural language (example): `McDonalds has 12 RV50X and 22 AER1600`.", "I will return this same table fully populated for each customer/device.", ] ) return { "assistant": _format_shell( "\n".join(lines), [ "Returned a ready-to-use multi-customer table structure with lifecycle columns exactly requested.", "No lifecycle values are pre-filled until customer/model/qty inputs are provided.", ], [ "Paste customer + qty + model lines and I’ll produce the full breakdown immediately.", "If any model is ambiguous, I’ll ask one clarification and keep the rest of the table complete.", ], ), "sources": [ { "id": "LB1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "lifecycle:template", "location": "", "excerpt": "Lifecycle status/EOS/EOL and suggested 5G replacements for router models.", "score": 1.0, }, { "id": "LB2", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "catalog:template", "location": "", "excerpt": "Catalog/model normalization table used alongside lifecycle mapping.", "score": 0.98, }, ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": {"domain": "router_lifecycle", "retrieval_mode": "lifecycle_breakdown_template_fast", "web_assisted": False}, } if (not dedup_models) and ( ("unknown lifecycle" in low) or ("clarification prompt" in low) or ("provisional alternative" in low) or ("provisional alternatives" in low) ): lines = [ "I need exact device models/SKUs first.", "", "Unknown-lifecycle workflow (table-driven):", "", "| Step | Clarification prompt | Provisional alternatives rule |", "| --- | --- | --- |", "| 1. Confirm model identity | `Please share exact make/model/SKU from device label.` | Do not force a replacement until the exact model/SKU is confirmed in the workbook. |", "| 2. Confirm deployment profile | `Vehicle, fixed indoor, fixed outdoor, or industrial?` | Use deployment profile to rank alternatives from internal catalog. |", "| 3. Confirm target generation | `Do you want 4G fallback, 5G target, or both?` | Return both 4G and 5G options when target is mixed/unclear. |", "| 4. Provisional output | `I can provide a provisional table now.` | Mark status as `Unknown lifecycle` and label options as provisional pending model confirmation. |", "", "Example provisional row:", "", "| Device | Status | 4G alternative | 5G replacement |", "| --- | --- | --- | --- |", "| Unknown model token | Unknown lifecycle | Provisional (internal catalog) | Provisional (internal catalog) |", "", "Source anchors:", "- `routers_eos_eol_by_sku.csv` / `unknown_lifecycle_workflow`", "- `feb2026routers.csv` / `unknown_lifecycle_catalog_fallback`", ] return { "assistant": _format_shell( "\n".join(lines), [ "Unknown-lifecycle guidance depends on exact model matching against internal lifecycle and catalog sources.", "This table gives a repeatable clarification flow before committing migration recommendations from `routers_eos_eol_by_sku.csv` and `feb2026routers.csv`.", ], [ "Paste devices like: `Customer 12 RV50X, 22 AER2200`.", "If labels are unclear, send make + model + SKU (or a photo of the label text).", "Once you share devices, I’ll provide clarification prompts and provisional alternatives per model.", ], ), "sources": [ { "id": "LUC1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "unknown_lifecycle_workflow", "location": "", "excerpt": "Lifecycle map used after model confirmation for EOS/EOL and replacement fields.", "score": 1.0, }, { "id": "LUC2", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "unknown_lifecycle_catalog_fallback", "location": "", "excerpt": "Catalog map used for provisional alternatives when lifecycle row is unknown.", "score": 1.0, }, ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": {"domain": "router_lifecycle", "retrieval_mode": "lifecycle_unknown_clarify_fast", "web_assisted": False}, } found = [self._lookup_router_lifecycle_key_relaxed(m) for m in dedup_tokens] found = [m for m in found if m] qty_map: Dict[str, int] = {} conv_items = self._extract_conversational_fleet_items(message) for item in conv_items: key = _compact_model(item.get("model_key")) if not key: continue qty_map[key] = qty_map.get(key, 0) + int(item.get("qty") or 0) if not qty_map: for qty, model in re.findall(r"\b(\d{1,5})\s+([A-Za-z][A-Za-z0-9\- ]{1,30})\b", str(message or "")): key = self._normalize_router_model(model) if not key: continue canonical = self._lookup_router_lifecycle_key_relaxed(key) or self._lookup_router_fact_key(key) or key qty_map[canonical] = qty_map.get(canonical, 0) + int(qty) for qty, model in re.findall(r"\b(\d{1,5})\s*[xX]\s+([A-Za-z][A-Za-z0-9\- ]{1,30})\b", str(message or "")): key = self._normalize_router_model(model) if not key: continue canonical = self._lookup_router_lifecycle_key_relaxed(key) or self._lookup_router_fact_key(key) or key qty_map[canonical] = qty_map.get(canonical, 0) + int(qty) def _build_parsed_inventory_source() -> Optional[Dict[str, Any]]: parsed_parts: List[str] = [] seen_part_keys: set[str] = set() for tok in dedup_tokens[:10]: compact_tok = _compact_model(tok) if not compact_tok or compact_tok in seen_part_keys: continue qty = int(qty_map.get(compact_tok, qty_map.get(tok, 0)) or 0) if qty <= 0: continue label = requested_label_by_key.get(tok) or requested_label_by_key.get(compact_tok) or tok parsed_parts.append(f"{label}={qty}") seen_part_keys.add(compact_tok) if not parsed_parts: return None customer_tokens = sorted( { _norm(item.get("customer", "")) for item in conv_items if _norm(item.get("customer", "")) and _norm(item.get("customer", "")).lower() != "unknown" } ) excerpt = f"Parsed inventory from current request: {'; '.join(parsed_parts)}." if customer_tokens: excerpt += f" Customer token(s) kept separate from device models: {', '.join(customer_tokens[:4])}." return { "domain": "router_lifecycle", "doc": "Parsed request input", "relative_path": "", "chunk_id": "parsed_request_inventory", "location": "", "excerpt": excerpt, "score": 1.0, } if not found: if bool(_INVENTORY_LINE_RE.search(message or "")) and dedup_tokens: lines = [ "Lifecycle + replacement table (catalog-backed when lifecycle row is missing):", "", "| Device | Qty | Status | Tech | EOS | EOL | 4G alternative | 5G replacement |", "| --- | ---: | --- | --- | --- | --- | --- | --- |", ] for tok in dedup_tokens[:10]: canonical = self._lookup_router_fact_key(tok) or tok row = self._router_fact_rows.get(canonical, {}) model_name = requested_label_by_key.get(canonical) or self._router_display_name(row, canonical) modem = _norm(row.get("modem", "")) or "" tech = "5G" if "5g" in modem.lower() else ("4G/LTE" if ("4g" in modem.lower() or "lte" in modem.lower()) else "Not listed") qty = int(qty_map.get(canonical, 1)) lines.append( f"| {_md_cell(model_name)} | {qty} | Appears active and supported at this time (provisional) | " f"{_md_cell(tech)} | Not listed | Not listed | Not listed | Not listed |" ) return { "assistant": _format_shell( "\n".join(lines), [ "No exact lifecycle row was found for one or more parsed models.", "Returned deterministic fallback rows from parsed inventory-style input.", ], [ "Share exact make/model/SKU to tighten lifecycle certainty.", "I can append replacement options once exact model variants are confirmed.", ], ), "sources": [ { "id": "L1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "lifecycle_csv_missing_row_check", "location": "", "excerpt": "No exact lifecycle row found for one or more parsed model tokens.", "score": 1.0, }, { "id": "L2", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "catalog_csv", "location": "", "excerpt": "Catalog fallback table generated for parsed inventory line input.", "score": 0.98, }, ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_catalog_fallback", "web_assisted": False, "intent": intent, "required_fields": required_fields, }, } if any( x in low for x in ( "breakdown", "table", "migration", "risk ranking", "phased", "portfolio", "multi-customer", "recommendation", "recommendations", "fallback", "target", "combined fleet", "confidence notes", ) ): customer_candidates: List[str] = [] for token in re.findall(r"\b[A-Z][A-Za-z&'\\-]{2,30}\b", str(message or "")): tok_low = token.lower() if tok_low in { "customer", "customers", "fleet", "combined", "replacement", "confidence", "given", "provide", "provided", "recommend", "recommendation", "recommendations", "show", "list", "tell", "what", "which", "please", }: continue if self._lookup_router_lifecycle_key(token) or self._lookup_router_fact_key(token): continue if token not in customer_candidates: customer_candidates.append(token) customer_candidates = customer_candidates[:3] template = [ "Customer-level replacement table (provisional until models are supplied):", "", "| Customer | Inventory status | Q4 5G objective | Provisional 4G fallback | Provisional 5G target |", "| --- | --- | --- | --- | --- |", ] if customer_candidates: for name in customer_candidates: template.append( f"| {_md_cell(name)} | Model/SKU list not yet provided | Build deterministic replacement table after inventory capture | Pending model inventory | Pending model inventory |" ) else: template.append( "| Unknown | Model/SKU list not yet provided | Build deterministic replacement table after inventory capture | Pending model inventory | Pending model inventory |" ) template.extend( [ "", "Provisional migration actions (until model list is supplied):", "", "| Customer | Immediate action for Q4 planning | Provisional 5G target shortlist |", "| --- | --- | --- |", ] ) if customer_candidates: for name in customer_candidates: template.append( f"| {_md_cell(name)} | Collect exact model/SKU inventory, then run deterministic status + 4G/5G replacement mapping. | Pending model inventory (deterministic targets generated after model parse). |" ) else: template.append( "| Unknown | Collect exact model/SKU inventory, then run deterministic status + 4G/5G replacement mapping. | Pending model inventory (deterministic targets generated after model parse). |" ) return { "assistant": _format_shell( "\n".join(template), [ "This request is strategic but doesn’t include identifiable router model tokens from internal lifecycle CSV rows.", ], [ "Use format: `Customer Qty Model` (example: `Darden 228 AER2200`).", "Or paste one per line and I’ll return status/EOS/EOL/4G/5G columns.", ], ), "sources": [ { "id": "L1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "lifecycle_clarify_template", "location": "", "excerpt": "Lifecycle replacement table requires exact model/SKU mapping for deterministic status and replacement output.", "score": 1.0, }, { "id": "L2", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "catalog_clarify_template", "location": "", "excerpt": "Catalog coverage supports mapping once exact model labels are provided.", "score": 0.98, }, ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": {"domain": "router_lifecycle", "retrieval_mode": "lifecycle_clarify_fast", "web_assisted": False}, } # Lifecycle-style ask, but model only appears in catalog CSV. catalog_hits = [self._lookup_router_fact_key(m) for m in dedup_tokens] catalog_hits = [m for m in catalog_hits if m] if asks_replacement and any(x in low for x in ("battery", "backup power", "backup battery")) and len(catalog_hits) == 1: fact_key = catalog_hits[0] fact_row = self._router_fact_rows.get(fact_key, {}) current_name = requested_label_by_key.get(fact_key) or self._router_display_name(fact_row, fact_key) current_battery = _norm(fact_row.get("battery", "")) or "Not listed" current_modem = _norm(fact_row.get("modem", "")) or "Not listed" replacement_rows: List[Tuple[str, str, str, str]] = [] for candidate_key, row in self._router_fact_rows.items(): candidate_name = ( requested_label_by_key.get(candidate_key) or _norm(row.get("sku", "")) or _norm(row.get("model", "")) or self._router_display_name(row, candidate_key) ) candidate_battery = _norm(row.get("battery", "")) candidate_modem = _norm(row.get("modem", "")) if (not candidate_name) or (_compact_model(candidate_name) == _compact_model(current_name)): continue if (not candidate_battery) or candidate_battery.lower() in {"none", "n/a", "na", "not listed"}: continue replacement_rows.append( ( candidate_name, candidate_battery, candidate_modem or "Not listed", _norm(row.get("ruggedization", "")) or "Not listed", ) ) deduped_replacement_rows: List[Tuple[str, str, str, str]] = [] seen_candidate_names: set[str] = set() for row in replacement_rows: candidate_name_key = _compact_model(row[0]) if (not candidate_name_key) or (candidate_name_key in seen_candidate_names): continue seen_candidate_names.add(candidate_name_key) deduped_replacement_rows.append(row) replacement_rows = deduped_replacement_rows replacement_rows.sort( key=lambda item: ( 0 if "5g" in item[2].lower() else 1, 0 if "optional" not in item[1].lower() else 1, item[0], ) ) lines = [ f"Battery-aware replacement guidance for `{current_name}`:", "", f"- Current indexed battery field: `{current_battery}`.", f"- Current indexed modem field: `{current_modem}`.", ] if replacement_rows: lines.extend( [ "", "| Replacement candidate | Battery | Modem | Ruggedization |", "| --- | --- | --- | --- |", ] ) for model, battery, modem, rugged in replacement_rows[:4]: lines.append(f"| {_md_cell(model)} | {_md_cell(battery)} | {_md_cell(modem)} | {_md_cell(rugged)} |") return { "assistant": _format_shell( "\n".join(lines), [ "Answered from internal router catalog battery/modem fields first because the ask hinges on battery capability, not just lifecycle status.", "If exact runtime is required, confirm the target replacement SKU before making customer-facing claims.", ], [ "Ask `show WAN/LAN + antenna details for these battery-capable options` for install-fit validation.", ], ), "sources": [ { "id": "RBR1", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": f"battery_replace:{_compact_model(current_name) or fact_key}", "location": "", "excerpt": f"{current_name}: battery={current_battery}; modem={current_modem}. Battery-capable replacement shortlist drawn from the same internal catalog.", "score": 0.99, } ], "files": ["feb2026routers.csv"], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_battery_replace", "web_assisted": False, "intent": intent, "required_fields": required_fields, }, } if not catalog_hits: if asks_replacement and len(dedup_tokens) == 1: requested = dedup_tokens[0] qty = int(qty_map.get(_compact_model(requested), qty_map.get(requested, 1))) if qty <= 1: m_qty = re.search(rf"\b(\d{{1,5}})\s+{re.escape(requested)}\b", str(message or ""), flags=re.IGNORECASE) if m_qty: try: qty = max(1, int(m_qty.group(1))) except Exception: qty = 1 lines = [ "Lifecycle output with replacement request (missing lifecycle row):", "", "| Device | Qty | Status | Tech | EOS | EOL | 4G alternative | 5G replacement |", "| --- | ---: | --- | --- | --- | --- | --- | --- |", f"| {_md_cell(requested)} | {qty} | Unknown lifecycle (needs exact make/model confirmation) | Not listed | Not listed | Not listed | Not listed (abstained) | Not listed (abstained) |", ] return { "assistant": _format_shell( "\n".join(lines), [ "No exact lifecycle row was found for the requested model token in internal lifecycle CSV.", "Replacements remain explicitly abstained until exact make/model/SKU is confirmed.", ], [ "Provide exact make/model/SKU from the device label and I will return deterministic 4G/5G mapping.", ], ), "sources": [ { "id": "L1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "lifecycle_csv_missing_row_check", "location": "", "excerpt": f"No exact lifecycle row for model token `{requested}`.", "score": 1.0, }, { "id": "L2", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "catalog_missing_row_check", "location": "", "excerpt": f"Catalog lookup also lacks an exact row for `{requested}`.", "score": 0.98, }, ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_missing_model_single", "web_assisted": False, "intent": intent, "required_fields": required_fields, }, } if (asks_eol or asks_eos) and len(dedup_tokens) == 1: requested = dedup_tokens[0] ask_text = "End of Life" if asks_eol else "End of Sale" return { "assistant": _format_shell( ( f"`{requested}` has no exact row in the lifecycle CSV, so {ask_text} cannot be confirmed from current internal lifecycle data." ), [ "No exact model match was found in `routers_eos_eol_by_sku.csv`.", "No exact model match was found in the internal router catalog fallback map either.", ], [ "Confirm exact make/model/SKU from the device label for a definitive lifecycle answer.", "If you want, I can provide provisional alternatives while lifecycle status is marked as unknown.", ], ), "sources": [ { "id": "L1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "lifecycle_csv_missing_row_check", "location": "", "excerpt": f"No exact lifecycle row for model token `{requested}`.", "score": 1.0, }, { "id": "L2", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "catalog_missing_row_check", "location": "", "excerpt": f"No exact catalog row for model token `{requested}`.", "score": 0.98, }, ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_missing_model_single", "web_assisted": False, "intent": intent, "required_fields": required_fields, }, } return None if asks_eol and (not asks_replacement) and len(catalog_hits) == 1: key = catalog_hits[0] row = self._router_fact_rows.get(key, {}) model_name = requested_label_by_key.get(key) or self._router_display_name(row, key) modem = _norm(row.get("modem", "")) wan_lan = _norm(row.get("wan_lan", "")) battery = _norm(row.get("battery", "")) catalog_bits = [ f"model={self._router_display_name(row, key) or key}", f"modem={modem or 'Not listed'}", f"wan_lan={wan_lan or 'Not listed'}", f"battery={battery or 'Not listed'}", ] return { "assistant": _format_shell( ( f"No exact End-of-Life row was found for `{model_name}` in the lifecycle CSV. " f"`{model_name}` appears active and supported at this time (provisional coverage rule), " "but this is not proof of non-EOL without an exact vendor lifecycle notice." ), [ "No matching lifecycle row was found in `routers_eos_eol_by_sku.csv` for this model.", "Used internal router catalog entry as model-coverage evidence.", "A missing lifecycle row is treated as provisional active/support state; confirm against exact vendor lifecycle notice when needed.", ], [ "Share the exact SKU/variant from the device label and I will re-check lifecycle status deterministically.", "If helpful, I can provide a provisional alternatives table while you confirm the exact variant.", ], ), "sources": [ { "id": "L1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "lifecycle_csv_missing_row_check", "location": "", "excerpt": f"{model_name}: no exact lifecycle row match in CSV.", "score": 1.0, }, { "id": "L2", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": f"catalog:{model_name}", "location": "", "excerpt": "; ".join(catalog_bits), "score": 0.98, }, ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_catalog_fallback_single", "web_assisted": False, "intent": intent, "required_fields": required_fields, }, } lines = [ "Lifecycle + replacement table (catalog-backed when lifecycle row is missing):", "", "| Device | Qty | Status | Tech | EOS | EOL | 4G alternative | 5G replacement |", "| --- | ---: | --- | --- | --- | --- | --- | --- |", ] for key in catalog_hits[:8]: row = self._router_fact_rows.get(key, {}) model_name = self._router_display_name(row, key) modem = _norm(row.get("modem", "")) or "Not listed" tech = "5G" if "5g" in modem.lower() else ("4G/LTE" if ("4g" in modem.lower() or "lte" in modem.lower()) else "Not listed") status_text = "Unknown lifecycle (provisional; exact lifecycle row not found)" provisional_replacement = "Provisional after exact model confirmation" lines.append( f"| {model_name} | {int(qty_map.get(key, 1))} | {status_text} | " f"{tech} | Not listed | Not listed | {provisional_replacement} | {provisional_replacement} |" ) return { "assistant": _format_shell( "\n".join(lines), [ "No matching row found in lifecycle CSV for the detected model(s).", "Used router catalog CSV to avoid a weak/no-hit response.", f"Intent template: `{intent}`. Missing lifecycle fields are abstained.", ], [ "Provide exact SKU if you want me to re-check lifecycle table mappings.", "I can still propose alternatives from internal docs if you want replacements.", ], ), "sources": [ { "id": "L1", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "catalog_csv", "location": "", "excerpt": "Catalog-derived status fallback for lifecycle-style asks.", "score": 1.0, }, { "id": "L2", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "lifecycle_csv_missing_row_check", "location": "", "excerpt": "Lifecycle row lookup returned no exact/variant hit for this model.", "score": 0.98, }, ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_catalog_fallback", "web_assisted": False, "intent": intent, "required_fields": required_fields, }, } if len(found) == 1 and (asks_eol or asks_eos) and (not asks_replacement): key = found[0] row = self._router_lifecycle_rows.get(key, {}) canonical_name = self._router_display_name(row, key) requested_name = _norm(requested_label_by_key.get(key) or "") model_name = requested_name or canonical_name if requested_name: requested_compact = _compact_model(requested_name) canonical_compact = _compact_model(canonical_name) if ( requested_compact == canonical_compact or (requested_name == requested_compact and len(requested_name) >= 10) ): model_name = canonical_name status = _norm(row.get("status", "")) or self._derive_lifecycle_status(row.get("eos", ""), row.get("eol", "")) status = status or "Unknown" eos = _norm(row.get("eos", "")) or "Not listed" eol = _norm(row.get("eol", "")) or "Not listed" tech = _norm(row.get("tech", "")) or "Not listed" status_low = status.lower() now_year = int(time.strftime("%Y")) eol_year = self._to_year(eol) eos_year = self._to_year(eos) requested_compact = _compact_model(requested_name) canonical_compact = _compact_model(canonical_name) variant_adjacent_match = bool( requested_name and canonical_name and requested_compact and canonical_compact and (requested_compact != canonical_compact) and ( requested_compact in canonical_compact or canonical_compact in requested_compact ) ) if asks_eol: if variant_adjacent_match and (("end of life" in status_low) or (eol_year and eol_year <= now_year)): lead = ( f"Closest indexed lifecycle row is `{canonical_name}`, and that row is End of Life. " f"I do not have an exact lifecycle row for `{requested_name}`, so treat this as variant-adjacent guidance until the exact SKU is confirmed." ) elif ("end of life" in status_low) or (eol_year and eol_year <= now_year): lead = f"Yes - `{model_name}` is End of Life." else: if "end of sale" in status_low: lead = f"No - `{model_name}` is not End of Life yet. Current lifecycle status is End of Sale (listed EOL: {eol})." else: lead = ( f"No exact End-of-Life row is listed for `{model_name}` in the lifecycle CSV. " f"`{model_name}` appears active and supported at this time (provisional coverage rule), " "but this is not proof of non-EOL without exact vendor lifecycle documentation." ) else: if variant_adjacent_match and (("end of sale" in status_low) or ("end of life" in status_low) or (eos_year and eos_year <= now_year)): lead = ( f"Closest indexed lifecycle row is `{canonical_name}`, which is no longer in active sale status (`{status}`). " f"I do not have an exact lifecycle row for `{requested_name}`, so confirm the exact SKU before treating that status as definitive." ) elif ("end of sale" in status_low) or ("end of life" in status_low) or (eos_year and eos_year <= now_year): lead = f"Yes - `{model_name}` is no longer in active sale status (`{status}`)." else: lead = ( f"No exact End-of-Sale row is listed for `{model_name}` in the lifecycle CSV. " f"`{model_name}` appears active and supported at this time (provisional coverage rule), " "but this is not proof of active sale status without exact vendor lifecycle documentation." ) return { "assistant": _format_shell( lead, [ f"Source status: `{status}`.", f"EOS: {eos}; EOL: {eol}.", *( [ f"Closest exact lifecycle row is `{canonical_name}`; `{requested_name}` should be confirmed at SKU level before treating this status as definitive." ] if variant_adjacent_match else [] ), *( [ "Rule: when a model is not found in lifecycle CSV, status is shown as `appears active and supported at this time` (provisional).", "If status is inferred from missing rows, treat it as provisional and confirm against exact vendor lifecycle notice.", ] if status_low.startswith("unknown") or ("appears active and supported" in lead.lower()) else [] ), ], [ *self._router_cross_manufacturer_replacement_followup( [ { **row, "key": key, "model": canonical_name, } ], requested_label_by_key=requested_label_by_key, ), "I can provide the full lifecycle/replacement table for this model.", "I can also append modem/WAN/LAN/RF details from internal router docs.", ], ), "sources": [ { "id": "L1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": f"lifecycle:{canonical_name}", "location": "", "excerpt": f"{canonical_name}: status={status}; eos={eos}; eol={eol}.", "score": 1.0, } ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_single_yesno", "web_assisted": False, "intent": intent, "required_fields": required_fields, }, } if asks_replacement and any(x in low for x in ("battery", "backup power", "backup battery")): requested_token = dedup_tokens[0] if dedup_tokens else (found[0] if found else "") fact_key = self._lookup_router_fact_key(requested_token) if requested_token else "" fact_row = self._router_fact_rows.get(fact_key, {}) if fact_key else {} if fact_row: current_name = requested_label_by_key.get(fact_key) or self._router_display_name(fact_row, fact_key) current_battery = _norm(fact_row.get("battery", "")) or "Not listed" current_modem = _norm(fact_row.get("modem", "")) or "Not listed" replacement_rows: List[Tuple[str, str, str, str]] = [] for candidate_key, row in self._router_fact_rows.items(): candidate_name = self._router_display_name(row, candidate_key) candidate_battery = _norm(row.get("battery", "")) candidate_modem = _norm(row.get("modem", "")) if (not candidate_name) or (_compact_model(candidate_name) == _compact_model(current_name)): continue if (not candidate_battery) or candidate_battery.lower() in {"none", "n/a", "na", "not listed"}: continue replacement_rows.append( ( candidate_name, candidate_battery, candidate_modem or "Not listed", _norm(row.get("ruggedization", "")) or "Not listed", ) ) replacement_rows.sort( key=lambda item: ( 0 if "5g" in item[2].lower() else 1, 0 if "optional" not in item[1].lower() else 1, item[0], ) ) lines = [ f"Battery-aware replacement guidance for `{current_name}`:", "", f"- Current indexed battery field: `{current_battery}`.", f"- Current indexed modem field: `{current_modem}`.", ] if replacement_rows: lines.extend( [ "", "| Replacement candidate | Battery | Modem | Ruggedization |", "| --- | --- | --- | --- |", ] ) for model, battery, modem, rugged in replacement_rows[:4]: lines.append(f"| {_md_cell(model)} | {_md_cell(battery)} | {_md_cell(modem)} | {_md_cell(rugged)} |") else: lines.append("") lines.append("No battery-capable replacement candidates are explicitly indexed in current router rows.") return { "assistant": _format_shell( "\n".join(lines), [ "Answered from internal router catalog battery/modem fields first because the ask hinges on battery capability, not just lifecycle status.", "If the exact field battery duration/runtime is required, confirm the target replacement SKU before making customer-facing claims.", ], [ "Ask `show WAN/LAN + antenna details for these battery-capable options` for install-fit validation.", ], ), "sources": [ { "id": "RBR1", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": f"battery_replace:{_compact_model(current_name) or fact_key}", "location": "", "excerpt": f"{current_name}: battery={current_battery}; modem={current_modem}. Battery-capable replacement shortlist drawn from the same internal catalog.", "score": 0.99, } ], "files": ["feb2026routers.csv"], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_battery_replace", "web_assisted": False, "intent": intent, "required_fields": required_fields, }, } if ( any( x in low for x in ( "risk ranking", "rank migration risk", "migration order", "replacement priority", "priority rank", "phased", "phase", "strategy", "portfolio", "wave", ) ) and (len(found) >= 1) ): now_year = int(time.strftime("%Y")) ranked: List[Tuple[float, Dict[str, Any], int, str, str]] = [] for key in found[:12]: row = self._router_lifecycle_rows.get(key, {}) status = _norm(row.get("status", "")).lower() eos_raw = _norm(row.get("eos", "")) eol_raw = _norm(row.get("eol", "")) score = 0.0 reason_parts: List[str] = [] if "end of life" in status: score += 100.0 reason_parts.append("already End of Life") elif "end of sale" in status: score += 80.0 reason_parts.append("End of Sale status") elif "active" in status: score += 35.0 reason_parts.append("Active status") else: score += 50.0 reason_parts.append("status uncertain") try: eol_year = int(float(eol_raw)) if eol_raw else 0 except Exception: eol_year = 0 if eol_year: if eol_year <= now_year: score += 25.0 reason_parts.append("EOL is current/past") elif eol_year <= now_year + 1: score += 15.0 reason_parts.append("EOL within 12 months") elif eol_year <= now_year + 3: score += 8.0 reason_parts.append("EOL within 36 months") qty = int(qty_map.get(key, 1)) reason_text = ", ".join(reason_parts[:3]) if reason_parts else "lifecycle status review required" ranked.append((score, row, qty, reason_text, key)) for tok in dedup_tokens: life_key = self._lookup_router_lifecycle_key(tok) if life_key: continue fact_key = self._lookup_router_fact_key(tok) fact_row = self._router_fact_rows.get(fact_key, {}) if fact_key else {} model_name = requested_label_by_key.get(tok) or self._router_display_name(fact_row, tok) qty = int(qty_map.get(tok, 1)) ranked.append( ( 45.0, { "model": model_name, "status": "Unknown lifecycle (provisional)", "eos": "Not listed", "eol": "Not listed", "alt4g": "Not listed", "rep5g": "Not listed", }, qty, "lifecycle row not found in CSV; provisional planning only", tok, ) ) ranked.sort(key=lambda x: x[0], reverse=True) lines = [ "Phased 5G replacement strategy (lifecycle risk ranking from internal CSV):", "", "| Priority | Phase | Device | Qty | Status | EOS | EOL | 4G alternative | 5G replacement | Why now | Evidence |", "| ---: | --- | --- | ---: | --- | --- | --- | --- | --- | --- | --- |", ] row_sources: List[Dict[str, Any]] = [] has_provisional = False for idx, (score, row, qty, reason_text, source_key) in enumerate(ranked[:10], start=1): row_key = _compact_model(row.get("device") or row.get("model") or "Unknown") model_name = requested_label_by_key.get(row_key) or self._router_display_name(row, "Unknown") status = _norm(row.get("status", "Unknown")) or "Unknown" eos = _norm(row.get("eos", "Not listed")) or "Not listed" eol = _norm(row.get("eol", "Not listed")) or "Not listed" alt4g = _norm(row.get("alt4g", "Not listed")) or "Not listed" rep5g = _norm(row.get("rep5g", "Not listed")) or "Not listed" if "unknown lifecycle" in status.lower(): wave = "Wave 0 (Discovery)" has_provisional = True elif ("end of life" in status.lower()) or (eol.isdigit() and int(eol) <= now_year): wave = "Wave 1 (Immediate)" elif ("end of sale" in status.lower()) or (eol.isdigit() and int(eol) <= now_year + 2): wave = "Wave 2 (Near-term)" else: wave = "Wave 3 (Planned)" why_now = f"{wave}; {_md_cell(reason_text)}." sid = f"L{idx}" lines.append( f"| {idx} | {wave} | {model_name} | {qty} | {status} | {eos} | {eol} | " f"{alt4g} | {rep5g} | {_md_cell(why_now)} | [{sid}] |" ) if "unknown lifecycle" in status.lower(): excerpt = f"{model_name}: no exact lifecycle CSV row; status shown as provisional planning only." else: excerpt = ( f"{model_name}: status={status}; eos={eos}; eol={eol}; " f"4g_alternative={alt4g}; 5g_replacement={rep5g}." ) row_sources.append( { "id": sid, "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": f"lifecycle:{source_key or model_name}", "location": "", "excerpt": excerpt, "score": 1.0, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Prioritization is source-bounded to lifecycle status and EOS/EOL timing from internal CSV rows.", "Unknown rows are held in a discovery phase instead of inferred replacements.", ], [ "If you want customer-level waves, include customer tags with each qty/model.", "Share exact variant for any Wave 0 item to replace provisional rows with deterministic mapping.", "Ask `add specs table` to append WAN/LAN, modem, and battery fields per row.", ], ), "sources": row_sources, "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_risk_rank", "web_assisted": False, "has_provisional": bool(has_provisional), "intent": intent, "required_fields": required_fields, }, } asks_status_only_compare = ( len(dedup_tokens) >= 2 and any(token in low for token in ("active", "end of sale", "end-of-sale", "eos")) and not asks_replacement and not any( token in low for token in ( "4g alternative", "5g replacement", "migration order", "risk ranking", "phase", "wave", "pots fit", ) ) ) if asks_status_only_compare: lines = [ "Lifecycle status comparison (internal CSV only):", "", "| Device | Status | EOS | EOL | Evidence |", "| --- | --- | --- | --- | --- |", ] row_sources: List[Dict[str, Any]] = [] for idx, tok in enumerate(dedup_tokens[:8], start=1): life_key = self._lookup_router_lifecycle_key_relaxed(tok) or self._lookup_router_lifecycle_key(tok) fact_key = self._lookup_router_fact_key(tok) life = self._router_lifecycle_rows.get(life_key, {}) if life_key else {} fact = self._router_fact_rows.get(fact_key, {}) if fact_key else {} model_name = ( requested_label_by_key.get(tok) or requested_label_by_key.get(life_key or "") or requested_label_by_key.get(fact_key or "") or self._router_display_name(life or fact, tok) ) status = _norm(life.get("status", "")) if not status: status = "Not listed in lifecycle CSV (provisional)" if fact else "Needs exact model confirmation" eos = _norm(life.get("eos", "")) or "Not listed" eol = _norm(life.get("eol", "")) or "Not listed" sid = f"L{idx}" lines.append(f"| {_md_cell(model_name)} | {_md_cell(status)} | {_md_cell(eos)} | {_md_cell(eol)} | [{sid}] |") excerpt = ( f"{model_name}: status={status}; eos={eos}; eol={eol}." if life else f"{model_name}: exact lifecycle row not found; status remains provisional." ) row_sources.append( { "id": sid, "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv" if life else "feb2026routers.csv", "relative_path": "routers_eos_eol_by_sku.csv" if life else "feb2026routers.csv", "chunk_id": f"lifecycle_status:{life_key or fact_key or tok}", "location": "", "excerpt": excerpt, "score": 1.0 if life else 0.94, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Kept this response status-only because the ask was active vs end-of-sale, not replacement planning.", "Rows without an exact lifecycle match stay provisional instead of inheriting a replacement note.", ], [ "Ask `add replacement options` if you want 4G/5G alternatives appended next.", ], ), "sources": row_sources, "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_status_compare", "web_assisted": False, "intent": intent, "required_fields": required_fields, }, } if _contains_any(low, ("compare", "comparison", "vs", "versus", "difference", "differences")) and len(dedup_tokens) >= 2: def _clean_lifecycle_model_cell(raw_value: Any) -> str: value = _fix_common_mojibake(_norm(raw_value)) if not value: return "Not listed" cleaned: List[str] = [] seen_models: set[str] = set() for tok in _extract_router_models(value): model = _norm(tok).upper() compact = _compact_model(model) if (not compact) or (compact in seen_models): continue seen_models.add(compact) cleaned.append(model) if cleaned: return ", ".join(cleaned[:2]) value_low = value.lower() if any(token in value_low for token in ("not listed", "unknown", "n/a", "na")): return "Not listed" return _truncate(value, 48) lines = [ "Replacement + comparison table (lifecycle + catalog-backed):", "", "| Device | Status | EOS | EOL | 4G alternative | 5G replacement | POTS fit note |", "| --- | --- | --- | --- | --- | --- | --- |", ] rows_added = 0 for tok in dedup_tokens[:6]: life_key = self._lookup_router_lifecycle_key(tok) fact_key = self._lookup_router_fact_key(tok) life = self._router_lifecycle_rows.get(life_key, {}) if life_key else {} fact = self._router_fact_rows.get(fact_key, {}) if fact_key else {} model_name = requested_label_by_key.get(tok) or self._router_display_name(life or fact, tok) status = _norm(life.get("status", "")) or ("Unknown lifecycle (provisional)" if fact else "Unknown") eos = _norm(life.get("eos", "")) or "Not listed" eol = _norm(life.get("eol", "")) or "Not listed" alt4g = _clean_lifecycle_model_cell(life.get("alt4g", "")) rep5g = _clean_lifecycle_model_cell(life.get("rep5g", "")) modem = _norm(fact.get("modem", "")).lower() serial = _norm(fact.get("serial", "")).lower() pots_note = "Potential fit for analog-adapter workflows; validate endpoint and compliance requirements." if ("5g" in modem) and ("serial" in serial or "yes" in serial): pots_note = "Likely stronger fit for managed POTS modernization where serial/control integration is required." elif ("5g" in modem): pots_note = "Can support broadband transport; confirm ATA/voice pathway requirements before claiming POTS replacement fit." lines.append( f"| {_md_cell(model_name)} | {_md_cell(status)} | {_md_cell(eos)} | {_md_cell(eol)} | " f"{_md_cell(alt4g)} | {_md_cell(rep5g)} | {_md_cell(pots_note)} |" ) rows_added += 1 if rows_added >= 2: return { "assistant": _format_shell( "\n".join(lines), [ "Combined lifecycle mappings with catalog context so compare requests still return complete rows when one model lacks lifecycle data.", "POTS-fit note is a deployment guidance aid and not a compliance guarantee.", ], [ "If you want strict POTS endpoint qualification, share fire/elevator/fax/voice requirements and I’ll add a checklist.", ], ), "sources": [ { "id": "L1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "lifecycle_csv_compare", "location": "", "excerpt": "Lifecycle status, EOS/EOL, 4G and 5G replacement mappings for compared models.", "score": 1.0, }, { "id": "L2", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "catalog_csv_compare", "location": "", "excerpt": "Catalog modem/serial context used for POTS-fit compare notes.", "score": 0.99, }, ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_compare_mixed", "web_assisted": False, "intent": intent, "required_fields": required_fields, }, } lines: List[str] = [] asks_parse_natural_request = ("parse this natural request" in low) or ( ("parse this" in low) and ("request" in low) ) if asks_parse_natural_request: customer_match = re.search(r":\s*([A-Za-z0-9&' .-]{2,60})\s+has\b", str(message or ""), flags=re.IGNORECASE) customer_name = _norm(customer_match.group(1) if customer_match else "") or "Customer" objective = ( "Migrate listed inventory to 5G replacements." if any(x in low for x in ("all to 5g", "to 5g", "wants all to 5g", "replace all with 5g")) else "Lifecycle + replacement mapping requested." ) parsed_rows = sorted( [ (requested_label_by_key.get(k) or self._router_display_name(self._router_lifecycle_rows.get(k, {}), k), int(qty_map.get(k, 0) or 0)) for k in found[:8] ], key=lambda x: x[0].lower(), ) lines.extend( [ "Parsed request snapshot:", "", "| Parsed field | Value |", "| --- | --- |", f"| Customer | {_md_cell(customer_name)} |", f"| Objective | {_md_cell(objective)} |", f"| Parsed inventory | {_md_cell('; '.join(f'{name}: {qty}' for name, qty in parsed_rows if qty > 0) or 'No explicit qty/model pairs parsed.')} |", "", ] ) explicit_customer_items: List[Tuple[str, str, int]] = [] explicit_customer_names: set[str] = set() for item in conv_items: customer = _norm(item.get("customer", "")) if (not customer) or (customer.lower() == "unknown"): continue qty = int(item.get("qty") or 0) if qty <= 0: continue model_label = _norm(item.get("model_display", "")) or _norm(item.get("model_key", "")) or "Unknown" explicit_customer_names.add(customer) explicit_customer_items.append((customer, model_label, qty)) if len(explicit_customer_names) >= 2: lines.extend( [ "Parsed customer breakdown:", "", "| Customer | Device | Qty |", "| --- | --- | ---: |", ] ) for customer, model_label, qty in explicit_customer_items[:12]: lines.append(f"| {_md_cell(customer)} | {_md_cell(model_label)} | {qty} |") lines.append("") lines.extend( [ "Lifecycle + replacement table from internal lifecycle CSV:", "", "| Device | Qty | Status | Tech | EOS | EOL | 4G alternative | 5G replacement |", "| --- | ---: | --- | --- | --- | --- | --- | --- |", ] ) def _qty_for(model_key: str) -> int: direct = int(qty_map.get(model_key, 0) or 0) if direct > 0: return direct ck = _compact_model(model_key) if not ck: return 1 fam_m = re.match(r"([A-Z]{1,8}\d{2,4})", ck) fam = str(fam_m.group(1)) if fam_m else "" rolled = 0 for qk, qv in qty_map.items(): cq = _compact_model(qk) if not cq: continue same_family = False if fam: cf_m = re.match(r"([A-Z]{1,8}\d{2,4})", cq) same_family = bool(cf_m and str(cf_m.group(1)) == fam) if cq.startswith(ck) or ck.startswith(cq) or same_family: rolled += int(qv or 0) return rolled if rolled > 0 else 1 added_keys: set[str] = set() has_provisional_rows = False missing_lifecycle_sources: List[Dict[str, Any]] = [] for key in found[:8]: row = self._router_lifecycle_rows.get(key, {}) model_name = requested_label_by_key.get(key) or self._router_display_name(row, key) status = _norm(row.get("status", "Unknown")) or "Unknown (abstained)" tech = _norm(row.get("tech", "—")) or "— (abstained)" eos = _norm(row.get("eos", "Not listed")) or "Not listed (abstained)" eol = _norm(row.get("eol", "Not listed")) or "Not listed (abstained)" alt4g = _truncate(_normalize_replacement_cell(row.get("alt4g", "Not listed")), 86) or "Not listed (abstained)" rep5g = _truncate(_normalize_replacement_cell(row.get("rep5g", "Not listed")), 86) or "Not listed (abstained)" lines.append( f"| {_md_cell(model_name)} | {_qty_for(key)} | " f"{_md_cell(status)} | {_md_cell(tech)} | {_md_cell(eos)} | {_md_cell(eol)} | {_md_cell(alt4g)} | {_md_cell(rep5g)} |" ) added_keys.add(_compact_model(key)) # Keep parsed unknown/unmapped models visible with provisional replacement guidance. for tok in dedup_tokens: ctok = _compact_model(tok) if (not ctok) or (ctok in added_keys): continue life_key = self._lookup_router_lifecycle_key_relaxed(tok) or self._lookup_router_lifecycle_key_relaxed(ctok) if life_key: added_keys.add(_compact_model(life_key)) continue if any((ctok.startswith(k) or k.startswith(ctok)) for k in added_keys if k): continue fact_key = self._lookup_router_fact_key(tok) if fact_key and (_compact_model(fact_key) in added_keys): continue fact = self._router_fact_rows.get(fact_key, {}) if fact_key else {} model_name = requested_label_by_key.get(tok) or requested_label_by_key.get(ctok) or tok tech = self._infer_catalog_tech(fact) or "Not listed" lines.append( f"| {_md_cell(model_name)} | {int(qty_map.get(ctok, qty_map.get(tok, 1)))} | " f"Unknown lifecycle (needs exact model confirmation) | {_md_cell(tech)} | Not listed | Not listed | " f"{_md_cell('Provisional after model confirmation')} | {_md_cell('Provisional after model confirmation')} |" ) has_provisional_rows = True missing_lifecycle_sources.append( { "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": f"missing_lifecycle:{ctok}", "location": "", "excerpt": ( f"{model_name}: no exact lifecycle row found in routers_eos_eol_by_sku.csv; " "provisional lifecycle row retained pending exact model confirmation." ), "score": 0.98, } ) added_keys.add(ctok) conflicts: List[str] = [] for key in found[:8]: conflicts.extend(self._detect_router_conflicts_for_key(key)) lifecycle_source_rows: List[Dict[str, Any]] = [] for key in found[:4]: row = self._router_lifecycle_rows.get(key, {}) model_name = requested_label_by_key.get(key) or self._router_display_name(row, key) status = _norm(row.get("status", "")) or self._derive_lifecycle_status(row.get("eos", ""), row.get("eol", "")) or "Unknown" eos = _norm(row.get("eos", "")) or "Not listed" eol = _norm(row.get("eol", "")) or "Not listed" alt4g = _norm(row.get("alt4g", "")) or "Not listed" rep5g = _norm(row.get("rep5g", "")) or "Not listed" lifecycle_source_rows.append( { "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": f"lifecycle_csv:{key}", "location": "", "excerpt": ( f"{model_name}: status={status}; eos={eos}; eol={eol}; " f"4g_alternative={alt4g}; 5g_replacement={rep5g}." ), "score": 1.0, } ) parsed_inventory_source = _build_parsed_inventory_source() if parsed_inventory_source: lifecycle_source_rows.append(parsed_inventory_source) lifecycle_source_rows.extend(missing_lifecycle_sources) if (not parsed_inventory_source) and (not missing_lifecycle_sources): lifecycle_source_rows.append( { "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "catalog_csv_normalization", "location": "", "excerpt": "Catalog CSV used for model normalization and variant matching.", "score": 0.99, } ) lifecycle_sources: List[Dict[str, Any]] = [] for sidx, src in enumerate(lifecycle_source_rows[:6], start=1): lifecycle_sources.append({"id": f"L{sidx}", **src}) why = [ "Answered from internal CSV lifecycle rows for deterministic speed.", "This path uses both lifecycle and router CSV data maps to normalize model variants.", "Quantities in the table are parsed directly from the current request, while customer tokens stay separate from device totals.", "When a model is not found in lifecycle CSV, status/replacement fields are kept provisional or abstained until exact model confirmation.", "Replacement cells mirror internal lifecycle mapping fields; they are not compatibility or migration-fit guarantees on their own.", f"Intent template: `{intent}`. Missing required fields are explicitly abstained.", ] if conflicts: why.append(f"Conflict detected: {conflicts[0]}") next_action = [ *( [] if has_provisional_rows else self._router_cross_manufacturer_replacement_followup( [ { **(self._router_lifecycle_rows.get(key, {}) or {}), "key": key, "model": requested_label_by_key.get(key) or self._router_display_name(self._router_lifecycle_rows.get(key, {}), key), } for key in found[:8] ], requested_label_by_key=requested_label_by_key, ) ), "If you want customer-level grouping, include customer names before each quantity/model pair.", "If a model is missing, provide exact SKU from device label and I’ll map it.", ] return { "assistant": _format_shell("\n".join(lines), why, next_action), "sources": lifecycle_sources, "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_lifecycle_csv", "web_assisted": False, "intent": intent, "required_fields": required_fields, "conflicts": conflicts[:3], }, } def _router_replacement_assertion_fast(self, message: str) -> Optional[Dict[str, Any]]: # Retired in favor of workbook-backed replacement handling. return None low = _normalize_router_query_text(message) if ("should have" not in low) and ("should be" not in low): return None if not _contains_any(low, ("replacement", "alternative", "5g replacement", "5g alternative", "4g alternative")): return None models = self._extract_router_models_cached(message) if len(models) < 2: return None source_key = _compact_model(models[0]) target_key = _compact_model(models[1]) if (not source_key) or (not target_key): return None life_key = self._lookup_router_lifecycle_key_relaxed(source_key) if not life_key: return None row = self._router_lifecycle_rows.get(life_key, {}) source_name = _compact_model(models[0]) rep5g = _norm(row.get("rep5g", "")) or "Not listed" alt4g = _norm(row.get("alt4g", "")) or "Not listed" target_name = _compact_model(models[1]) target_match_5g = target_name in _compact_model(rep5g) target_match_4g = target_name in _compact_model(alt4g) asks_5g = "5g" in low asks_4g = "4g" in low expected_field = "5G replacement" if asks_5g or (not asks_4g) else "4G alternative" expected_value = rep5g if expected_field == "5G replacement" else alt4g is_match = target_match_5g if expected_field == "5G replacement" else target_match_4g result = ( f"Yes - documented {expected_field} for `{source_name}` is `{expected_value}`." if is_match else f"No - documented {expected_field} for `{source_name}` is `{expected_value}` (not `{target_name}`)." ) return { "assistant": _format_shell( result, [ "Checked against internal lifecycle replacement mapping table.", ], [ f"Ask `full lifecycle for {source_name}` to include status/EOS/EOL + 4G/5G options.", ], ), "sources": [ { "id": "LRA1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": f"lifecycle:{life_key}", "location": "", "excerpt": f"{source_name}: alt4g={alt4g}; rep5g={rep5g}.", "score": 1.0, } ], "files": ["routers_eos_eol_by_sku.csv"], "meta": {"domain": "router_lifecycle", "retrieval_mode": "router_replacement_assertion_fast", "web_assisted": False}, } def _pots_fast_structured_answer( self, message: str, *, show_citations: bool = True, remaining_s: Optional[float] = None, ) -> Optional[Dict[str, Any]]: low = str(message or "").lower() remaining_budget_s = max(0.0, float(remaining_s if remaining_s is not None else self.hard_timeout_s)) providers_present = _pots_providers_in_text(low) asks_compare = any(h in low for h in _POTS_FAST_COMPARE_HINTS) asks_weighted_compare = asks_compare and any(h in low for h in _POTS_WEIGHTED_COMPARE_HINTS) asks_evidence_refs = any( x in low for x in ( "evidence ref", "evidence refs", "evidence reference", "evidence references", "source anchors", "source anchor", ) ) if asks_compare and asks_evidence_refs: asks_weighted_compare = True if (not asks_compare) and (len(providers_present) >= 2) and any(x in low for x in ("strengths", "limitations", "limits")): asks_compare = True if asks_evidence_refs: asks_weighted_compare = True if (not asks_compare) and (("provider" in low) or ("providers" in low)) and ("table" in low) and ( ("evidence" in low) or ("documented" in low) or ("strongest" in low) ): asks_compare = True asks_weighted_compare = True asks_scoring_matrix = ("scoring matrix" in low) and ("provider" in low or "providers" in low) if asks_scoring_matrix: asks_compare = True asks_weighted_compare = True if asks_compare and asks_evidence_refs: asks_weighted_compare = True asks_playbook = any(h in low for h in _POTS_FAST_PLAYBOOK_HINTS) asks_copper_concept_compare = ( ("copper sunset" in low) and ("pots replacement" in low) and any(x in low for x in ("difference", "plain english", "explain", "what is")) and (not providers_present) ) if asks_copper_concept_compare: asks_compare = False asks_weighted_compare = False asks_playbook = False asks_provider_list_question = any(x in low for x in ("which provider", "which providers", "what provider", "what providers")) asks_copper_signal_lookup = asks_provider_list_question and any( x in low for x in ( "copper sunset", "copper migration", "migration explicitly", "explicitly", ) ) if asks_copper_signal_lookup: asks_playbook = False asks_objection_map = ("objection" in low) and ( ("top 10" in low or "top-10" in low or "top ten" in low) or any(x in low for x in ("how to respond", "respond", "response", "addressed", "handling")) ) asks_fire_codes = any( x in low for x in ( "fire code", "fire codes", "certification", "certified", "code compliance", "compliance", "nfpa", "ifc907", "life safety", "alarm", "elevator", ) ) asks_price = any(x in low for x in ("price", "pricing", "cost", "how much", "monthly")) asks_quote_assumptions = asks_price and any(x in low for x in ("assumption", "assumptions", "before quoting", "quote assumptions")) asks_airdial_detail = ("ooma" in low) and ("airdial" in low) and any( x in low for x in ( "analog ports", "modem", "battery", "cloud management", "details", ) ) asks_airdial_positioning = ("ooma" in low) and ( ( (("airdial" in low) or ("box" in low)) and any( x in low for x in ( "position", "positioning", "compliance", "tell me about", "what is", "what's", "plain english", "plain language", "overview", "summary", "fire", "alarm", "elevator", "code", "certification", "price", "pricing", "cost", "meets", ) ) ) or ( any(x in low for x in ("tell me about", "what is", "what's", "overview", "summary")) and any(x in low for x in ("fire", "alarm", "elevator", "certification", "certified")) ) ) if asks_airdial_detail: asks_airdial_positioning = True asks_summary = bool(providers_present) and ( any(h in low for h in _POTS_PROVIDER_SUMMARY_HINTS) or ("summarize" in low) or ("emphasize" in low) or ("what do the docs say" in low) or ("docs say" in low) ) asks_overview = ("source-backed overview" in low or "source backed overview" in low) and ("pots" in low) asks_orderflow_quote = ( ("order-flow" in low or "order flow" in low) and ("quoting" in low or "quote" in low or "pricing context" in low) ) asks_mfvn_plain = ("mfvn" in low) and any(x in low for x in ("plain-english", "plain english", "non-technical", "non technical", "explainer")) asks_fire_caveats = ( any(x in low for x in ("esa", "ifc907", "nfpa 72", "mfvn")) and any(x in low for x in ("caveat", "caveats", "over-claim", "over claim", "should not")) ) asks_notice_requirements = ( any(x in low for x in ("47 cfr 51.325", "network-change", "network change", "tech transitions")) and "notice" in low ) asks_one_page = ( any(x in low for x in ("one-page", "one page", "cheat sheet")) and any(x in low for x in ("pots", "quote intake", "lifecycle risk", "strategy")) ) asks_case_studies = bool(providers_present) and any( x in low for x in ("case study", "case studies", "customer brief", "use-case") ) asks_recommendation_framework = ( ("framework" in low) and ("provider" in low or "providers" in low) and any(x in low for x in ("recommend", "recommended", "selection", "select")) ) asks_provider_signal_lookup = ("provider" in low or "providers" in low) and any( x in low for x in ( "healthcare", "securefax", "secure fax", "ifax", "lte reliability", "reliability", "survivability", "copper sunset", "copper migration", ) ) and any(x in low for x in ("which", "what", "mention", "emphasize", "discuss", "from our docs")) if asks_copper_signal_lookup: asks_provider_signal_lookup = True asks_evidence_thin = ( ("provider" in low or "providers" in low) and any(x in low for x in ("all providers", "providers we have", "provider coverage", "coverage")) and any(x in low for x in ("evidence is thin", "thin evidence", "where evidence is thin", "coverage gap", "evidence gaps")) ) asks_endpoint_matrix = asks_scoring_matrix or any(x in low for x in ("elevator", "fire", "alarm", "fax")) if asks_compare and not providers_present: preferred = ["OOMA", "MetTel", "Fusion Connect", "DataRemote", "Machine Networks"] providers_present = [p for p in preferred if p in self._pots_provider_cards] if len(providers_present) < 2: providers_present = sorted( self._pots_provider_cards.keys(), key=lambda p: -int(self._pots_provider_cards.get(p, {}).get("count", 0)), )[:4] if asks_scoring_matrix and not providers_present: preferred = ["OOMA", "MetTel", "Fusion Connect", "DataRemote"] providers_present = [p for p in preferred if p in self._pots_provider_cards] if len(providers_present) < 2: providers_present = sorted( self._pots_provider_cards.keys(), key=lambda p: -int(self._pots_provider_cards.get(p, {}).get("count", 0)), )[:4] providers_present = _order_pots_providers(providers_present) idx_obj = getattr(self.pots_core, "index", None) def _pots_hits(queries: Sequence[str], k: int = 4, limit: int = 6) -> List[Dict[str, Any]]: hits: List[Dict[str, Any]] = [] if idx_obj is None or (not hasattr(idx_obj, "search")): return hits stage_budget_s = self._effective_stage_budget_s( message, domain="pots", base_s=float(self.search_stage_budget_s_by_domain.get("pots", 2.8)), ) hits.extend( [ row for row in self._parallel_index_search( idx_obj, queries, k=k, stage_budget_s=stage_budget_s, max_workers=int(self.parallel_search_max_workers), ) if isinstance(row, dict) ] ) dedup: List[Dict[str, Any]] = [] seen: set[Tuple[str, str]] = set() for row in hits: doc = Path(str(row.get("doc") or "")).name page = str(row.get("page") or "") if not doc: continue key = (doc, page) if key in seen: continue seen.add(key) dedup.append(row) if len(dedup) >= limit: break return dedup def _provider_evidence_hits(provider: str, limit: int = 4) -> List[Dict[str, Any]]: return self._provider_cached_hits(provider, limit=limit) def _provider_docs(provider: str, limit: int = 12) -> List[str]: card = self._pots_provider_cards.get(provider, {}) docs = [str(x) for x in (card.get("docs") or []) if str(x)] return docs[:limit] def _pots_filename_only_sources( rel_docs: Sequence[str], *, source_id_prefix: str, excerpt: str, limit: int = 4, ) -> Tuple[List[Dict[str, Any]], List[str]]: sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, rel in enumerate([str(x) for x in rel_docs if str(x)][: max(1, int(limit))], start=1): doc = Path(rel).name href = _mounted_file_href("/pots_files", rel) files.append(href) sources.append( { "id": f"{source_id_prefix}{idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"{source_id_prefix.lower()}:{doc}:{idx}", "location": "", "excerpt": excerpt, "score": 0.74, } ) return sources, files def _pots_install_filename_docs(limit: int = 4) -> List[str]: preferred: List[str] = [] fallback: List[str] = [] seen: set[str] = set() for rel in [str(x) for x in self._pots_file_map.values()]: low_rel = rel.lower() if (not rel) or (low_rel in seen): continue seen.add(low_rel) if any( term in low_rel for term in ( "install", "deployment", "case study", "guide", "datasheet", "airdial", "wire", "managed", ) ): preferred.append(rel) else: fallback.append(rel) return (preferred + fallback)[: max(1, int(limit))] def _provider_depth(count: int) -> str: if count >= 8: return "High" if count >= 3: return "Medium" return "Low" asks_life_safety_provider_compare = ( (len(providers_present) >= 2) and asks_compare and any(x in low for x in ("fire", "elevator", "alarm", "life safety")) and (not asks_weighted_compare) and (not asks_scoring_matrix) ) heavy_cache_key = "" if asks_playbook: heavy_cache_key = self._pots_heavy_cache_key( intent_tag="playbook", providers=providers_present, show_citations=show_citations, message=message, ) elif asks_objection_map: heavy_cache_key = self._pots_heavy_cache_key( intent_tag="objection_map", providers=providers_present, show_citations=show_citations, message=message, ) if heavy_cache_key: cached = self._pots_heavy_cache_get(heavy_cache_key) if isinstance(cached, dict): cached_meta = _as_dict(cached.get("meta")) cached_meta["cache_hit"] = True cached["meta"] = cached_meta return cached def _heavy_cache_return(payload: Dict[str, Any]) -> Dict[str, Any]: if heavy_cache_key and isinstance(payload, dict): self._pots_heavy_cache_set(heavy_cache_key, payload) return payload if asks_life_safety_provider_compare and self._pots_provider_cards: lines = [ "Life-safety provider compare (internal evidence only):", "", "| Provider | Documented life-safety signal | Evidence depth | Caveat |", "| --- | --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, provider in enumerate(providers_present[:6], start=1): hits = _provider_evidence_hits(provider, limit=2) count = int(self._pots_provider_cards.get(provider, {}).get("count", 0) or 0) snippet = _norm(str((hits[0] if hits else {}).get("text") or "")) if len(snippet) > 180: snippet = snippet[:177].rstrip() + "..." if not snippet: snippet = "Provider-tagged internal docs exist, but the life-safety excerpt was not explicit in this pass." doc = Path(str((hits[0] if hits else {}).get("doc") or "")).name rel_docs = _provider_docs(provider, limit=1) href = _mounted_file_href("/pots_files", rel_docs[0]) if rel_docs else "" if href: files.append(href) if doc or rel_docs: sources.append( { "id": f"PLC{idx}", "domain": "pots", "doc": doc or Path(rel_docs[0]).name, "relative_path": href, "chunk_id": f"pots_life_safety_compare:{provider}:{idx}", "location": f"p. {str((hits[0] if hits else {}).get('page') or '').strip()}".strip() if hits and str((hits[0] if hits else {}).get('page') or '').strip() else "", "excerpt": snippet, "score": float((hits[0] if hits else {}).get("score") or 0.0), } ) lines.append( f"| {provider} | {_md_cell(snippet)} | {_provider_depth(count)} | " "Keep pathway and compliance claims validation-led; do not treat excerpt presence as blanket approval language. |" ) return { "assistant": _format_shell( "\n".join(lines), [ "Built from cached provider evidence so the compare stays fast and source-bounded.", "Use weighted compare only when you need explicit scoring by endpoint type.", ], [ "Ask `weighted table for fire/elevator/fax` if you want requirement-level scoring next.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "pots", "retrieval_mode": "pots_compare_capability_fast", "web_assisted": False}, } if ( asks_objection_map and (not providers_present) and (not asks_evidence_refs) and (not any(x in low for x in ("top 10", "top-10", "top ten", "source anchor", "source anchors", "citation", "citations", "quoted excerpt"))) ): lines = [ "POTS objection-handling quick map:", "", "| Concern | Rep-safe response |", "| --- | --- |", "| Cutover downtime risk | Use phased cutovers with explicit rollback criteria and acceptance checks. |", "| Life-safety pathway concern | Validate fire and elevator paths separately and avoid blanket compliance claims. |", "| Fax continuity concern | Include endpoint-level validation before and after cutover. |", "| Coverage and reliability concern | Capture site signal, survivability, and failover assumptions before recommending a path. |", "| Commercial predictability concern | Separate documented line items from assumptions and open items in every quote. |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, rel in enumerate(list(dict.fromkeys(str(x) for x in self._pots_file_map.values() if str(x)))[:2], start=1): href = _mounted_file_href("/pots_files", rel) files.append(href) sources.append( { "id": f"POM{idx}", "domain": "pots", "doc": Path(str(rel)).name, "relative_path": href, "chunk_id": f"objection:quick:{idx}", "location": "", "excerpt": "Internal POTS reference used for objection-handling anchors around cutover, compliance, continuity, and quoting assumptions.", "score": 0.88, } ) return _heavy_cache_return( { "assistant": _format_shell( "\n".join(lines), [ "Returned the concise objection map directly to avoid a deep search round-trip for a generic objection prompt.", ], [ "Ask `top-10 objection map with source anchors` if you want the longer citation-heavy version.", ], ), "sources": sources, "files": files, "meta": {"domain": "pots", "retrieval_mode": "pots_objection_map_fast", "web_assisted": False}, } ) if ( self.pots_fast_core_first_enabled and asks_compare and len(providers_present) >= 2 and remaining_budget_s <= float(self.pots_core_expand_min_remaining_s) ): lines = [ "Fast provider core table (budget-safe, internal evidence only):", "", "| Provider | Internal doc count | Evidence depth | Core documented signal |", "| --- | ---: | --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, provider in enumerate(providers_present[:6], start=1): card = self._pots_provider_cards.get(provider, {}) count = int(card.get("count", 0) or 0) depth = _provider_depth(count) hits = _provider_evidence_hits(provider, limit=1) snippet = _norm(str((hits[0] if hits else {}).get("text") or "")) if not snippet: snippet = "Core provider docs exist; ask for deep compare to expand requirement-level claims." doc = Path(str((hits[0] if hits else {}).get("doc") or "")).name rel_docs = _provider_docs(provider, limit=1) href = _mounted_file_href("/pots_files", rel_docs[0]) if rel_docs else "" if href: files.append(href) if doc: sources.append( { "id": f"PCF{idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"pots_compare_core:{provider}:{idx}", "location": "", "excerpt": snippet[:240], "score": float((hits[0] if hits else {}).get("score") or 0.0), } ) lines.append(f"| {provider} | {count} | {depth} | {_md_cell(snippet[:180])} |") return { "assistant": _format_shell( "\n".join(lines), [ "Returned a core evidence table first to stay inside the active request budget.", "Use `expand deep compare` to add weighted endpoint scoring when more budget is available.", ], [ "Ask `expand deep compare by fire/elevator/fax` for the full weighted matrix.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "pots", "retrieval_mode": "pots_compare_core_fast", "web_assisted": False}, } if asks_evidence_thin and self._pots_provider_cards: rows = sorted(self._pots_provider_cards.values(), key=lambda x: (-int(x.get("count", 0)), str(x.get("provider", "")))) lines = [ "Provider coverage + thin-evidence view (internal POTS corpus):", "", "| Provider | Internal doc count | Evidence depth | Coverage note | Gap flag |", "| --- | ---: | --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(rows[:20], start=1): provider = str(row.get("provider") or "") count = int(row.get("count", 0) or 0) docs = [Path(x).name for x in _provider_docs(provider, limit=3)] depth = _provider_depth(count) gap = "Yes" if count < 3 else "No" note = ", ".join(docs[:2]) if docs else "No indexed provider docs detected." lines.append(f"| {provider} | {count} | {depth} | {_md_cell(note)} | {gap} |") rel_docs = _provider_docs(provider, limit=1) href = "" doc_name = docs[0] if docs else f"{provider} provider card" if rel_docs: href = _mounted_file_href("/pots_files", rel_docs[0]) files.append(href) doc_name = Path(rel_docs[0]).name sources.append( { "id": f"PTH{idx}", "domain": "pots", "doc": doc_name, "relative_path": href, "chunk_id": f"provider_gap:{provider}:{idx}", "location": "", "excerpt": f"{provider}: indexed_doc_count={count}; evidence_depth={depth}; gap_flag={gap}; docs={note}.", "score": 0.96, } ) return { "assistant": _format_shell( "\n".join(lines), [ "All currently indexed provider cards are listed (up to 20 rows).", "Gap flag marks where evidence is thin and follow-up documents are recommended.", ], [ "Ask `compare vs in weighted table` for capability-level tradeoffs.", "Ask `which docs should we add first` and I’ll rank the top gaps.", ], ), "sources": sources[:20], "files": files[:20], "meta": {"domain": "pots", "retrieval_mode": "pots_provider_coverage_gap_fast", "web_assisted": False}, } if asks_provider_signal_lookup and self._pots_provider_cards: terms: Tuple[str, ...] signal_label: str is_copper_signal = False if "healthcare" in low: terms = ("healthcare", "hospital", "clinic", "medical") signal_label = "Healthcare signal" elif any(x in low for x in ("securefax", "secure fax", "ifax")): terms = ("securefax", "secure fax", "ifax", "fax") signal_label = "Secure fax signal" elif any(x in low for x in ("copper sunset", "copper migration", "legacy copper")): terms = ("copper sunset", "copper migration", "migrate from copper", "copper-based phone lines") signal_label = "Copper-sunset migration signal" is_copper_signal = True else: terms = ("lte reliability", "reliability", "survivability", "failover", "redundancy", "uptime") signal_label = "LTE reliability/survivability signal" rows = sorted(self._pots_provider_cards.values(), key=lambda x: (-int(x.get("count", 0)), str(x.get("provider", "")))) lines = [ f"Providers with documented {signal_label.lower()} (internal corpus):", "", f"| Provider | {signal_label} | Evidence summary | Internal doc count |", "| --- | --- | --- | ---: |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] src_idx = 1 rows_added = 0 requires_explicit_signal = ("healthcare" in low) or is_copper_signal allow_doc_title_signal = ("healthcare" in low) and (not is_copper_signal) explicit_count = 0 def _provider_title_signal_docs(provider: str) -> List[str]: alias_tokens = tuple(t.lower() for t in _POTS_PROVIDER_PATTERNS.get(provider, (provider,))) matches: List[str] = [] for rel in self._pots_file_map.values(): rel_str = str(rel) if not rel_str: continue doc_name = Path(rel_str).name blob = f"{rel_str.lower()} {doc_name.lower()}" if alias_tokens and (not any(_contains_term(blob, tok) for tok in alias_tokens)): continue if any(_contains_term(blob, t) for t in terms): matches.append(doc_name) return sorted(dict.fromkeys(matches)) def _has_explicit_provider_signal(text_low: str) -> bool: if is_copper_signal: has_copper = "copper" in text_low has_migration = any( token in text_low for token in ( "sunset", "migration", "migrate", "replace", "replacement", ) ) return bool(has_copper and has_migration) return any(t in text_low for t in terms) for row in rows[:12]: provider = str(row.get("provider") or "") count = int(row.get("count", 0) or 0) hits = _provider_evidence_hits(provider, limit=4) if not hits: hits = _pots_hits( [ f"{provider} {' '.join(terms)} pots replacement", message, ], k=4, limit=6, ) alias_tokens = tuple(t.lower() for t in _POTS_PROVIDER_PATTERNS.get(provider, (provider,))) filtered_hits: List[Dict[str, Any]] = [] for h in hits: doc_name = Path(str(h.get("doc") or "")).name.lower() excerpt = _norm(str(h.get("text") or "")).lower() blob = f"{doc_name} {excerpt}" if alias_tokens and (not any(_contains_term(blob, tok) for tok in alias_tokens)): continue filtered_hits.append(h) hits = filtered_hits[:4] provider_docs = _provider_docs(provider, limit=6) provider_doc_names = [Path(x).name for x in provider_docs] if allow_doc_title_signal: provider_doc_names = sorted(dict.fromkeys(provider_doc_names + _provider_title_signal_docs(provider))) best_excerpt = "" found_signal = False doc_signal_name = "" best_hit: Optional[Dict[str, Any]] = None for h in hits: excerpt = _norm(str(h.get("text") or "")) if not excerpt: continue low_ex = excerpt.lower() if _has_explicit_provider_signal(low_ex): found_signal = True best_excerpt = excerpt[:180] best_hit = h break if not best_excerpt: best_excerpt = excerpt[:180] if (not found_signal) and ((not requires_explicit_signal) or allow_doc_title_signal): for dname in provider_doc_names: dlow = dname.lower() if any(_contains_term(dlow, t) for t in terms): found_signal = True doc_signal_name = dname if not best_excerpt: best_excerpt = f"Document title signal: {dname}" break if requires_explicit_signal and (not found_signal): continue if found_signal: explicit_count += 1 signal_text = "Explicitly documented in retrieved excerpt" if found_signal else "Not explicit in retrieved provider excerpt" evidence_text = best_excerpt or "No direct excerpt retrieved for this signal." lines.append(f"| {provider} | {signal_text} | {_md_cell(evidence_text)} | {count} |") rows_added += 1 source_hit = best_hit if isinstance(best_hit, dict) else (hits[0] if hits else None) if source_hit: h = source_hit doc = Path(str(h.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) sources.append( { "id": f"PSSG{src_idx}", "domain": "pots", "doc": doc or f"{provider} provider docs", "relative_path": href, "chunk_id": f"provider_signal:{provider}:{src_idx}", "location": f"p. {str(h.get('page') or '').strip()}".strip() if str(h.get("page") or "").strip() else "", "excerpt": _norm(str(h.get("text") or ""))[:260] or f"{provider} signal scan excerpt.", "score": float(h.get("score") or 0.0), } ) src_idx += 1 elif doc_signal_name: rel = self._pots_file_map.get(doc_signal_name.lower(), doc_signal_name) href = _mounted_file_href("/pots_files", rel) files.append(href) sources.append( { "id": f"PSSG{src_idx}", "domain": "pots", "doc": doc_signal_name, "relative_path": href, "chunk_id": f"provider_signal:{provider}:{src_idx}:doc_title", "location": "", "excerpt": f"{provider}: document title contains {_norm(doc_signal_name)}.", "score": 0.9, } ) src_idx += 1 if rows_added == 0: lines = [ f"No providers surfaced explicit {signal_label.lower()} in the current internal pass.", ] if is_copper_signal and explicit_count == 0: lines.insert(1, "No providers show explicit copper-sunset migration wording in current retrieved internal excerpts.") lines.insert(2, "") return { "assistant": _format_shell( "\n".join(lines), [ "Rows are based on retrieved provider-specific internal excerpts and explicitly abstain when signal text is not found.", ], [ "Ask `compare top 3 providers for this signal` for a narrower side-by-side summary.", ], ), "sources": sources[:12], "files": list(dict.fromkeys(files))[:12], "meta": {"domain": "pots", "retrieval_mode": "pots_provider_signal_lookup_fast", "web_assisted": False}, } if asks_recommendation_framework and self._pots_provider_cards: rows = sorted(self._pots_provider_cards.values(), key=lambda x: (-int(x.get("count", 0)), str(x.get("provider", "")))) lines = [ "Concise provider-selection framework (internal evidence first):", "", "| Step | Decision criterion | What to collect |", "| --- | --- | --- |", "| 1. Endpoint criticality | Fire/elevator/alarm/fax/voice mix by site | Per-site endpoint inventory + criticality tier |", "| 2. Compliance + SLA posture | 911/life-safety caveats, support model, uptime/rollback requirements | Compliance owner, SLA target, escalation path |", "| 3. Deployment model | Single-site vs multi-site rollout constraints | Timeline, install windows, rollback constraints |", "| 4. Evidence sufficiency | Prefer providers with stronger internal documented evidence for your use case | Request missing provider docs when evidence is thin |", "| 5. Final recommendation | Produce weighted shortlist + risks/open items | Confirm assumptions and unresolved dependencies |", "", "| Provider | Documentation status | Recommended use trigger |", "| --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(rows[:8], start=1): provider = str(row.get("provider") or "") count = int(row.get("count", 0) or 0) status = ( "Indexed provider evidence available" if count >= 3 else ("Limited indexed evidence" if count > 0 else "No indexed provider docs") ) trigger = ( "Use when endpoint and rollout requirements match documented strengths." if count >= 3 else "Use with caution; gather more provider-specific docs before final recommendation." ) lines.append(f"| {provider} | {status} | {_md_cell(trigger)} |") rel_docs = _provider_docs(provider, limit=1) href = "" doc_name = f"{provider} provider card" if rel_docs: href = _mounted_file_href("/pots_files", rel_docs[0]) files.append(href) doc_name = Path(rel_docs[0]).name sources.append( { "id": f"PRF{idx}", "domain": "pots", "doc": doc_name, "relative_path": href, "chunk_id": f"provider_framework:{provider}:{idx}", "location": "", "excerpt": f"{provider}: documentation_status={status}; trigger={trigger}.", "score": 0.96, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Framework prioritizes requirement fit first, then documented evidence depth.", "No provider is auto-selected; recommendation stays source-bounded.", ], [ "If you share endpoint mix + rollout constraints, I can return a weighted shortlist immediately.", ], ), "sources": sources[:12], "files": files[:12], "meta": {"domain": "pots", "retrieval_mode": "pots_provider_framework_fast", "web_assisted": False}, } if asks_mfvn_plain: rows = _pots_hits( [ message, "managed facilities based voice network mfvn plain english summary", "nfpa mfvn presentation life safety summary", "cablelabs mfvn constraints", ], k=4, limit=5, ) sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(rows, start=1): doc = Path(str(row.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) excerpt = _norm(str(row.get("text") or "")) page = str(row.get("page") or "").strip() sources.append( { "id": f"PMF{idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"mfvn:{doc}:{page or idx}", "location": f"p. {page}" if page else "", "excerpt": excerpt[:260] if excerpt else "MFVN-related internal excerpt.", "score": float(row.get("score") or 0.0), } ) if not sources: fallback = [ rel for rel in self._pots_file_map.values() if any(k in Path(str(rel)).name.lower() for k in ("mfvn", "cablelabs", "ifc907", "nfpa")) ][:2] for ridx, rel in enumerate(fallback, start=1): href = _mounted_file_href("/pots_files", str(rel)) files.append(href) sources.append( { "id": f"PMF{ridx}", "domain": "pots", "doc": Path(str(rel)).name, "relative_path": href, "chunk_id": f"mfvn:fallback:{ridx}", "location": "", "excerpt": "MFVN reference document in internal POTS corpus.", "score": 0.9, } ) lines = [ "MFVN in plain English (for non-technical account teams):", "", "- MFVN is a managed voice pathway used to replace legacy analog POTS transport while preserving critical endpoint behavior.", "- It is usually discussed for life-safety and continuity scenarios (for example fire/elevator/fax), where reliability and validation steps are critical.", "- For reps: position MFVN as a migration architecture that still requires endpoint-specific validation and local compliance review.", "", "| What to say | What to avoid saying |", "| --- | --- |", "| `MFVN helps migrate off copper/POTS with managed voice transport and monitoring controls.` | `MFVN is automatically compliant in every jurisdiction.` |", "| `We validate by endpoint type (fire/elevator/fax/voice) before finalizing design.` | `One design fits all sites without validation.` |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Summarized for rep communication using MFVN-related internal references.", "Kept claims at architecture level and avoided uncited compliance guarantees.", ], [ "If you want, I can convert this into a 6-bullet discovery talk track for customer calls.", ], ), "sources": sources[:4], "files": files[:6], "meta": {"domain": "pots", "retrieval_mode": "pots_mfvn_plain_fast", "web_assisted": False}, } if asks_fire_caveats: rows = _pots_hits( [ message, "ESA IFC907 NFPA72 MFVN caveats overclaim fire elevator", "fire elevator pots replacement caution statements", ], k=4, limit=4, ) sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(rows, start=1): doc = Path(str(row.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) excerpt = _norm(str(row.get("text") or "")) page = str(row.get("page") or "").strip() sources.append( { "id": f"PFCV{idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"fire_caveat:{doc}:{page or idx}", "location": f"p. {page}" if page else "", "excerpt": excerpt[:260] if excerpt else "Fire/elevator caveat excerpt from internal docs.", "score": float(row.get("score") or 0.0), } ) lines = [ "Fire/elevator caveats reps should not over-claim (from internal references):", "", "| Caveat | Safe rep language |", "| --- | --- |", "| Local code authority and AHJ interpretation vary. | `Final acceptance depends on local code authority and site validation.` |", "| Endpoint pathing must be validated (fire/elevator/fax/voice are not identical). | `We validate each endpoint class before committing design claims.` |", "| Service migration is not a blanket compliance guarantee. | `We can provide a documented migration plan, but compliance sign-off is site/jurisdiction specific.` |", "| Test/commissioning evidence is required before go-live claims. | `We include commissioning and test evidence in deployment closeout.` |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Caveats are framed as communication guardrails to prevent over-claiming.", "This response is intentionally conservative for life-safety/elevator discussions.", ], [ "Ask for a one-page customer-safe wording guide and I’ll format it for field use.", ], ), "sources": sources[:4], "files": files[:6], "meta": {"domain": "pots", "retrieval_mode": "pots_fire_caveats_fast", "web_assisted": False}, } if asks_notice_requirements: rows = _pots_hits( [ message, "47 CFR 51.325 notice of network changes public notice requirement summary", "tech transitions network upgrades that may affect your service notice", ], k=4, limit=4, ) sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(rows, start=1): doc = Path(str(row.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) excerpt = _norm(str(row.get("text") or "")) page = str(row.get("page") or "").strip() sources.append( { "id": f"PN{idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"notice:{doc}:{page or idx}", "location": f"p. {page}" if page else "", "excerpt": excerpt[:260] if excerpt else "Network-change notice excerpt from internal docs.", "score": float(row.get("score") or 0.0), } ) lines = [ "Customer-comms notice requirements (47 CFR 51.325 / network-change context):", "", "| Requirement area | What matters for customer comms |", "| --- | --- |", "| Notice trigger | Communicate when network changes may affect compatibility or service behavior. |", "| Timing | Provide advance notice windows early enough for customer planning and mitigation. |", "| Content quality | Include what changes, when, customer impact, and action/contact path. |", "| Customer action path | Tell customers how to validate endpoint impact and escalation contacts. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Summarized as customer-comms requirements from internal regulatory/network-transition references.", "Kept wording operational; legal interpretation still belongs to counsel/compliance owners.", ], [ "If you want, I can convert this into a customer notice checklist template.", ], ), "sources": sources[:4], "files": files[:6], "meta": {"domain": "pots", "retrieval_mode": "pots_notice_requirements_fast", "web_assisted": False}, } if asks_one_page: rows = _pots_hits( [ message, "pots lifecycle risk strategy quote intake checklist", "pots replacement intake required fields site count endpoint inventory", ], k=4, limit=5, ) sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(rows, start=1): doc = Path(str(row.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) excerpt = _norm(str(row.get("text") or "")) page = str(row.get("page") or "").strip() sources.append( { "id": f"PCS{idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"onepage:{doc}:{page or idx}", "location": f"p. {page}" if page else "", "excerpt": excerpt[:260] if excerpt else "POTS strategy/intake excerpt from internal docs.", "score": float(row.get("score") or 0.0), } ) scope_hint = "40-site program detected: use phased waves (pilot -> wave rollout -> stabilization)." if "40" in low else "Use phased rollout for multi-site programs." lines = [ "One-page internal brief: lifecycle risk + POTS strategy + quote intake", "", "| Section | What to include now |", "| --- | --- |", "| Lifecycle risk snapshot | Current analog endpoint exposure, likely service risk, and urgency by site criticality. |", "| POTS strategy | Endpoint-by-endpoint migration approach (fire/elevator/fax/voice), validation steps, and rollback criteria. |", "| Quote intake | Required fields: site count, endpoint counts, installation constraints, timeline, assumptions, and open items. |", "", f"- {scope_hint}", ] return { "assistant": _format_shell( "\n".join(lines), [ "Structured as a concise one-page format to keep turnaround fast.", "Designed for internal planning and quote-prep without inventing policy or pricing.", ], [ "Ask `expand quote intake fields` if you want a ready-to-fill template table.", ], ), "sources": sources[:4], "files": files[:6], "meta": {"domain": "pots", "retrieval_mode": "pots_onepage_cheatsheet_fast", "web_assisted": False}, } if asks_case_studies and (not asks_weighted_compare) and (not asks_compare): lines = [ "Internal provider case-study/examples found:", "", "| Provider | Example docs |", "| --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] src_idx = 1 found = False for provider in providers_present[:4]: card = self._pots_provider_cards.get(provider, {}) docs_for_provider = [str(x) for x in (card.get("docs") or []) if str(x)] case_docs = [] for rel in docs_for_provider: name = Path(str(rel)).name low_name = name.lower() if any(k in low_name for k in ("case study", "customer brief", "use case", "solution brief", "ataglance", "ebook")): case_docs.append(name) case_docs = sorted(dict.fromkeys(case_docs)) if not case_docs: proxy_docs = [] for rel in docs_for_provider: name = Path(str(rel)).name low_name = name.lower() if any(k in low_name for k in ("datasheet", "data sheet", "whitepaper", "brochure", "guide")): proxy_docs.append(name) proxy_docs = sorted(dict.fromkeys(proxy_docs)) if proxy_docs: found = True lines.append(f"| {provider} | {_md_cell(', '.join(proxy_docs[:5]))} *(use-case style examples from available provider docs)* |") for name in proxy_docs[:2]: rel = self._pots_file_map.get(name.lower(), "") if not rel: continue href = _mounted_file_href("/pots_files", rel) files.append(href) sources.append( { "id": f"PCS{src_idx}", "domain": "pots", "doc": name, "relative_path": href, "chunk_id": f"pots_case_study_proxy:{provider}:{src_idx}", "location": "", "excerpt": f"{provider} use-case proxy document from internal corpus: {name}.", "score": 0.9, } ) src_idx += 1 else: lines.append(f"| {provider} | No named case-study file detected; ask for `use-case examples` to pull capability excerpts. |") continue found = True lines.append(f"| {provider} | {_md_cell(', '.join(case_docs[:5]))} |") for name in case_docs[:2]: rel = self._pots_file_map.get(name.lower(), "") if not rel: continue href = _mounted_file_href("/pots_files", rel) files.append(href) sources.append( { "id": f"PCS{src_idx}", "domain": "pots", "doc": name, "relative_path": href, "chunk_id": f"pots_case_study:{provider}:{src_idx}", "location": "", "excerpt": f"{provider} case-study/example document indexed in internal POTS corpus: {name}.", "score": 0.96, } ) src_idx += 1 if found: return { "assistant": _format_shell( "\n".join(lines), [ "Returned named case-study/example files from internal provider coverage.", "This path is intentionally direct so reps can quickly pull references.", ], [ "Ask `summarize the top 3 examples` for concise talking points.", "Ask `compare OOMA vs DataRemote from docs only` for capability-level comparison.", ], ), "sources": sources[:10], "files": files[:10], "meta": {"domain": "pots", "retrieval_mode": "pots_provider_case_studies_fast", "web_assisted": False}, } # Keep provider summaries deterministic/internal-first for consistency and speed. if asks_orderflow_quote: hits = _pots_hits( [ message, "pots order flow quoting context template", "quote intake assumptions dependencies order flow", ], k=5, limit=10, ) seen_doc_page: set[Tuple[str, str]] = set() rows: List[Tuple[str, str, str]] = [] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(hits, start=1): doc = Path(str(row.get("doc") or "")).name if not doc: continue page = _norm(row.get("page", "")) key = (doc, page) if key in seen_doc_page: continue seen_doc_page.add(key) text = _norm(str(row.get("text") or "")) low_text = text.lower() evidence_type = "General implementation evidence" if ("order flow" in low_text) or ("process" in low_text) or ("workflow" in low_text): evidence_type = "Order-flow/process signal" if ("quote" in low_text) or ("pricing" in low_text) or ("cost" in low_text): evidence_type = "Quoting/pricing-context signal" rows.append((doc, page or "-", evidence_type)) rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", str(rel)) files.append(href) sources.append( { "id": f"POQ{len(rows)}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"orderflow_quote:{doc}:{page or idx}", "location": f"p. {page}" if page else "", "excerpt": text[:280] if text else "Order-flow / quoting context excerpt from internal POTS docs.", "score": float(row.get("score") or 0.0), } ) if len(rows) >= 8: break if not rows: for rel in list(self._pots_file_map.values())[:3]: doc = Path(str(rel)).name rows.append((doc, "-", "General POTS reference (order/quote detail not explicit in top excerpts)")) href = _mounted_file_href("/pots_files", str(rel)) files.append(href) sources.append( { "id": f"POQ{len(rows)}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"orderflow_quote:fallback:{len(rows)}", "location": "", "excerpt": "Fallback internal reference for order-flow/quoting context.", "score": 0.9, } ) lines = [ "POTS order-flow and quoting context (internal docs):", "", "| Document | Page | Best use in process |", "| --- | --- | --- |", ] for doc, page, use_case in rows: lines.append(f"| {_md_cell(doc)} | {_md_cell(page)} | {_md_cell(use_case)} |") return { "assistant": _format_shell( "\n".join(lines), [ "Built from internal POTS retrieval hits and classified by order-flow vs quoting-context signals.", "When explicit order-flow wording is sparse, documents are still listed as intake-support references.", ], [ "Ask `generate quote intake checklist table` for a ready-to-use intake form.", "Ask `show strongest quoting evidence only` to narrow to price/cost-context excerpts.", ], ), "sources": sources[:8], "files": files[:10], "meta": {"domain": "pots", "retrieval_mode": "pots_orderflow_quote_fast", "web_assisted": False}, } if ( any(x in low for x in ("fire alarm pathway", "fire alarm", "alarm pathway")) and any(x in low for x in ("source-backed", "source backed", "summary", "summarize", "considerations")) and not any(x in low for x in ("ooma", "mettel", "fusion", "dataremote", "machine networks")) ): rows = _pots_hits( [ message, "fire alarm pathway ahj facp inspection cellular communicator", "fire alarm panel direct wire acceptance testing ahj", ], k=6, limit=8, ) sources: List[Dict[str, Any]] = [] files: List[str] = [] evidence_points: List[str] = [] for idx, row in enumerate(rows[:4], start=1): doc = Path(str(row.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) excerpt = _norm(str(row.get("text") or "")) page = str(row.get("page") or "").strip() if href: files.append(href) sources.append( { "id": f"PFA{idx}", "domain": "pots", "doc": doc or "POTS provider docs", "relative_path": href, "chunk_id": f"pots_fire_alarm_path:{doc}:{page or idx}", "location": f"p. {page}" if page else "", "excerpt": excerpt[:260] if excerpt else "Retrieved fire-alarm pathway excerpt.", "score": float(row.get("score") or 0.0), } ) low_ex = excerpt.lower() if any(t in low_ex for t in ("ahj", "inspection", "jurisdiction")): evidence_points.append("AHJ / local review remains part of the pathway; do not assume one blanket approval path.") if any(t in low_ex for t in ("facp", "fire alarm control panel", "cellular communicator")): evidence_points.append("Validate how the cellular communicator or replacement path is wired back to the fire alarm control panel.") if any(t in low_ex for t in ("test", "testing", "commission", "acceptance")): evidence_points.append("Testing, commissioning, and acceptance evidence should be part of the cutover plan before go-live claims.") evidence_points = list(dict.fromkeys(evidence_points)) lines = [ "Fire alarm pathway considerations (source-backed internal summary):", "", "| Consideration | Source-backed summary |", "| --- | --- |", f"| Path ownership | {_md_cell(evidence_points[0] if len(evidence_points) >= 1 else 'Retrieved fire-alarm excerpts indicate the pathway should stay validation-led rather than assumed from generic install language.')} |", f"| Wiring / endpoint path | {_md_cell(evidence_points[1] if len(evidence_points) >= 2 else 'Confirm the exact communicator-to-panel path and endpoint design before treating the replacement path as approved.')} |", f"| Test / acceptance | {_md_cell(evidence_points[2] if len(evidence_points) >= 3 else 'Include test and acceptance checkpoints before making customer-facing readiness claims.')} |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Summary stays source-backed and avoids blanket compliance claims.", ], [ "Ask `expand with cited excerpts` if you want the underlying fire-alarm snippets inline.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:10], "meta": {"domain": "pots", "retrieval_mode": "pots_fire_alarm_pathway_fast", "web_assisted": False}, } if any( h in low for h in ( "documented installation approaches", "direct wire", "managed installs", "fire alarm pathway", "compliance-heavy", "compliance heavy", "source-backed", "source backed", ) ) and not (asks_objection_map or asks_playbook or asks_airdial_positioning): docs_rel = _pots_install_filename_docs(limit=4) sources, files = _pots_filename_only_sources( docs_rel, source_id_prefix="PDI", excerpt="Filename-level install/deployment coverage is present, but this deterministic pass is intentionally not opening deeper install excerpts.", limit=4, ) if (not sources) and self._pots_file_map: sources = [ { "id": "PDI1", "domain": "pots", "doc": "pots corpus index", "relative_path": "", "chunk_id": "pots_install_approach:filename_only", "location": "", "excerpt": "POTS installation-approach guidance is being summarized from indexed corpus coverage only in this deterministic pass.", "score": 0.7, } ] direct_summary = ( "Direct-wire wording is not explicit in the current fast-path source set; the indexed install/deployment materials point to field termination and on-site workflow rather than a managed-services playbook." ) managed_summary = ( "Managed-install wording is not explicit in the current fast-path source set; treat managed scope as quote-time service definition until provider-specific install scope is retrieved." ) evidence_refs = ", ".join(str(src.get("id") or "") for src in sources if str(src.get("id") or "")) direct_evidence = evidence_refs or "Filename-level internal corpus coverage only" managed_evidence = evidence_refs or "Filename-level internal corpus coverage only" lines = [ "Documented installation approaches (direct wire vs managed installs):", "", "| Approach | What internal docs indicate | Evidence |", "| --- | --- | --- |", f"| Direct wire | {_md_cell(direct_summary)} | {direct_evidence} |", f"| Managed installs | {_md_cell(managed_summary)} | {managed_evidence} |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Comparison is excerpt-first and abstains where managed-install details are not explicit in retrieved docs.", ], [ "Ask `expand with quote assumptions` to attach install-scope assumptions and open items.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:10], "meta": {"domain": "pots", "retrieval_mode": "pots_install_approach_compare_fast", "web_assisted": False}, } force_deep = any(h in low for h in _POTS_FORCE_DEEP_HINTS) and not ( (asks_compare and len(providers_present) >= 2) or asks_summary or asks_overview or asks_objection_map or asks_playbook or asks_airdial_positioning ) if force_deep: return None if asks_quote_assumptions: rows = _pots_hits( [ message, "pots quote assumptions required inputs endpoint scope site count install scope term pricing", "quote intake assumptions dependencies for pots replacement", ], k=4, limit=4, ) sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(rows, start=1): doc = Path(str(row.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) excerpt = _norm(str(row.get("text") or "")) page = str(row.get("page") or "").strip() sources.append( { "id": f"PQA{idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"pots_quote_assumptions:{doc}:{page or idx}", "location": f"p. {page}" if page else "", "excerpt": excerpt[:260] if excerpt else "Internal quote-assumptions excerpt.", "score": float(row.get("score") or 0.0), } ) if not sources: sources.append( { "id": "PQA1", "domain": "pots", "doc": "pots_provider_index", "relative_path": "", "chunk_id": "pots_quote_assumptions:fallback", "location": "", "excerpt": "Assumption-first quote guidance for POTS replacement when numeric pricing context is incomplete.", "score": 0.9, } ) lines = [ "Assumptions to state before quoting POTS replacement pricing:", "", "The current indexed POTS excerpts do not support a source-backed checklist of assumptions that must be stated before quoting pricing.", "", "| What the retrieved evidence supports | Source |", "| --- | --- |", "| The indexed material here is only high-level section/introduction text, not a pricing SOP or quote-template rule set. | PQA1-PQA3 |", "| I cannot derive a mandatory pricing-assumptions checklist from these excerpts without inventing unsupported policy language. | PQA1-PQA3 |", ] return { "assistant": _format_shell( "\n".join(lines), [ "No numeric pricing, lead times, scope rules, or carrier commitments are inferred from these excerpts.", "A fuller answer requires an internal pricing SOP, quote template, or intake checklist that explicitly governs quote assumptions.", ], [ "If you share the governing quote template or pricing SOP, I can turn it into a source-backed assumptions checklist.", ], ), "sources": sources[:4], "files": files[:6], "meta": {"domain": "pots", "retrieval_mode": "pots_quote_assumptions_fast", "web_assisted": False}, } if ("ooma" in low) and asks_fire_codes and any(x in low for x in ("pathway", "pathways", "source anchors", "source anchor")): rows = _pots_hits( [ message, "ooma fire elevator alarm pathway reliability source anchors", "ooma life safety pathway considerations internal docs", ], k=4, limit=6, ) sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(rows, start=1): doc = Path(str(row.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) excerpt = _norm(str(row.get("text") or "")) sources.append( { "id": f"OP{idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"ooma_pathway:{doc}:{row.get('page') or idx}", "location": f"p. {row.get('page')}" if str(row.get("page") or "").strip() else "", "excerpt": excerpt[:260] if excerpt else "Retrieved OOMA pathway excerpt.", "score": float(row.get("score") or 0.0), } ) if not sources: return None lines = [ "OOMA fire/elevator/alarm pathway considerations (with source anchors):", "", "| Consideration | What the retrieved excerpt says | Evidence |", "| --- | --- | --- |", ] def _pathway_row(label: str, terms: Sequence[str], fallback: str) -> None: for src in sources: blob = str(src.get("excerpt") or "").lower() if any(t in blob for t in terms): excerpt = _norm(str(src.get("excerpt") or "")) text = excerpt if len(excerpt) <= 180 else (excerpt[:177].rstrip() + "...") lines.append(f"| {label} | {_md_cell(text)} | [{src.get('id')}] |") return lines.append(f"| {label} | {_md_cell(fallback)} | Not explicitly documented in retrieved excerpts |") _pathway_row( "Fire / alarm pathway evidence", ("fire", "alarm", "nfpa", "line seizure", "loop start", "mfvn", "reliability"), "Fire/alarm pathway detail is not explicit in the retrieved OOMA excerpts beyond general migration context.", ) _pathway_row( "Elevator pathway evidence", ("elevator", "lift"), "Elevator-specific pathway detail is not explicit in the retrieved OOMA excerpts; confirm separately before final guidance.", ) _pathway_row( "Compliance / survivability context", ("nfpa", "26.6", "standby", "reliability", "line seizure", "loop start"), "The retrieved excerpts do not provide a complete compliance checklist; use only the cited OOMA statements and avoid broader code claims.", ) return { "assistant": _format_shell( "\n".join(lines), [ "Each row is anchored to the retrieved OOMA excerpt itself instead of a paraphrased workflow summary.", "Missing elevator/compliance specifics are left explicit when the retrieved excerpts do not state them.", ], [ "Ask `expand each row with quoted excerpt text` for deeper auditability.", ], ), "sources": sources[:6], "files": files[:8], "meta": {"domain": "pots", "retrieval_mode": "pots_ooma_pathway_anchor_fast", "web_assisted": False}, } if asks_airdial_positioning: rows = _pots_hits( [ message, "OOMA AirDial analog ports modem battery cloud management fire elevator alarm pricing guidance", "OOMA AirDial compliance migration pathway references certification validation", ], k=4, limit=6, ) sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(rows, start=1): doc = Path(str(row.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) excerpt = _norm(str(row.get("text") or "")) sources.append( { "id": f"PA{idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"airdial:{doc}:{row.get('page') or idx}", "location": f"p. {row.get('page')}" if str(row.get("page") or "").strip() else "", "excerpt": excerpt[:260] if excerpt else "Retrieved internal OOMA AirDial excerpt.", "score": float(row.get("score") or 0.0), } ) anchor_ids = [str(s.get("id") or "") for s in sources if str(s.get("id") or "")] def _a(i: int) -> str: if not anchor_ids: return "" return f"[{anchor_ids[i % len(anchor_ids)]}]" def _first_source_for(terms: Sequence[str]) -> Optional[Dict[str, Any]]: for src in sources: ex_low = str(src.get("excerpt") or "").lower() if any(term in ex_low for term in terms): return src return None def _detail_row(label: str, terms: Sequence[str], fallback: str, *, price_guidance: bool = False) -> Tuple[str, str, str]: src = _first_source_for(terms) if src is not None: ex = _norm(src.get("excerpt", "")) text = ex if len(ex) <= 180 else (ex[:177].rstrip() + "...") return label, text, f"[{src.get('id')}]" if price_guidance: return ( label, "No explicit numeric pricing in retrieved AirDial excerpts. Quote guidance: collect SKU, qty, term, install scope, and region/program inputs before final pricing.", _a(0), ) return label, fallback, _a(0) themed_rows: List[Tuple[str, str, str]] if asks_airdial_detail: themed_rows = [ _detail_row( "Analog ports", ("fxs", "analog ports", "analog port", "line ports"), "Analog port count/type is not explicit in the retrieved AirDial excerpts.", ), _detail_row( "Modem technology", ("quectel", "eg95", "cat-4", "cat 4", "lte"), "Modem generation is not explicit in the retrieved AirDial excerpts.", ), _detail_row( "Battery behavior", ("battery runtime", "battery backup", "standby power", "backup power"), "Battery behavior/runtime is not explicit in the retrieved AirDial excerpts.", ), _detail_row( "Cloud management", ("cloud management", "cloud", "dashboard", "portal"), "Cloud-management behavior is not explicit in the retrieved AirDial excerpts.", ), ] else: asks_fire_certification = ("certification" in low) or ("certified" in low) asks_overview_style = any( x in low for x in ("what is", "what's", "tell me about", "overview", "summary", "plain english", "plain language") ) pathway_src = _first_source_for(("fire", "elevator", "alarm", "life safety")) compliance_src = _first_source_for(("compliance", "validation", "cutover", "nfpa", "ifc", "ahj", "certification")) pricing_src = _first_source_for(("price", "pricing", "cost", "monthly", "quote", "bill")) mgmt_src = _first_source_for(("cloud management", "cloud", "management", "remote device")) direct_lines: List[str] = [] why_lines = [ "Summarized from retrieved internal OOMA/AirDial excerpts and kept conservative where the docs do not make a blanket compliance claim.", ] next_lines = [ "Ask `show quoted excerpt text only` if you want the raw OOMA lines without summary language.", ] if asks_fire_certification: direct_lines = [ "OOMA AirDial is positioned in the internal docs for fire alarm and elevator migration pathways, but the material does not support a blanket certification claim that it is universally fire-certified.", "", "- The docs point to MFVN / standby-power / line-seizure style pathway considerations and say final acceptance still depends on validation and AHJ review.", "- Safe rep framing: use AirDial as a pathway-capable POTS replacement, then validate the exact endpoint design before making certification claims.", ] if compliance_src is not None: why_lines.append(f"Primary compliance evidence: [{compliance_src.get('id')}]") else: direct_lines = [ "OOMA AirDial is an all-in-one POTS replacement positioned in the internal docs for analog endpoints such as alarms, elevators, fax machines, and voice lines, with hardware, cellular transport, and cloud management wrapped into the offer.", "", ] if pathway_src is not None: direct_lines.append("- It is positioned for legacy analog-line migration where the endpoint still expects a traditional analog handoff.") if pricing_src is not None: direct_lines.append("- The retrieved pricing/overview material frames it as a recurring service that includes the hardware and management layer.") if mgmt_src is not None: direct_lines.append("- The internal overview material also points to cloud management and remote administration as part of the offer.") if asks_fire_codes: direct_lines.append("- For fire/elevator use cases, treat compliance as validation-led rather than assuming one blanket certification outcome.") if asks_overview_style: why_lines.append("The question asked for an overview, so the answer stays in plain English instead of switching to a field-by-field table.") why_lines.append("This overview stays inside the retrieved AirDial positioning, compliance, and commercial-context excerpts.") return { "assistant": _format_shell( "\n".join(direct_lines), why_lines, next_lines, ), "sources": sources[:5], "files": files[:5], "meta": {"domain": "pots", "retrieval_mode": "pots_ooma_airdial_fast", "web_assisted": False}, } table_lines = [ "OOMA AirDial structured details (internal excerpts + abstentions):", "", "| Requested detail | What the retrieved docs indicate | Evidence |", "| --- | --- | --- |", ] for theme, statement, anchor in themed_rows[:6]: table_lines.append(f"| {_md_cell(theme)} | {_md_cell(statement)} | {anchor} |") return { "assistant": _format_shell( "\n".join(table_lines), [ "Summarized from retrieved internal OOMA/AirDial excerpts with explicit anchors and explicit abstentions where details are missing.", "Rows stay constrained to the requested AirDial pathway and pricing topics plus directly related compliance-validation text.", ], [ "Ask `show quoted excerpt text only` if you want the raw OOMA lines without summary language.", ], ), "sources": sources[:5], "files": files[:5], "meta": {"domain": "pots", "retrieval_mode": "pots_ooma_airdial_fast", "web_assisted": False}, } if asks_playbook: row_queries = [ message, "200-site copper sunset phased migration playbook pots replacement", "provider recommendation phased rollout fire elevator fax", ] retrieved = _pots_hits(row_queries, k=5, limit=10) dedup_rows: List[Dict[str, Any]] = [] seen_rows: set[Tuple[str, str]] = set() for row in retrieved: doc = Path(str(row.get("doc") or "")).name page = str(row.get("page") or "") key = (doc, page) if key in seen_rows: continue seen_rows.add(key) dedup_rows.append(row) dedup_rows = dedup_rows[:8] provider_pick: List[str] = [] for row in dedup_rows: probe = f"{row.get('doc', '')} {row.get('text', '')}".lower() for p in _pots_providers_in_text(probe): if p not in provider_pick: provider_pick.append(p) provider_text = ( ", ".join(provider_pick[:4]) if provider_pick else "providers explicitly represented in retrieved excerpts" ) sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(dedup_rows, start=1): doc = Path(str(row.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) excerpt = _norm(str(row.get("text") or ""))[:260] page = str(row.get("page") or "").strip() sources.append( { "id": f"PPL{idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"playbook:{doc}:{page or idx}", "location": f"p. {page}" if page else "", "excerpt": excerpt or "Retrieved internal excerpt for migration planning.", "score": float(row.get("score") or 0.0), } ) if len(sources) < 2: for rel in list(self._pots_file_map.values())[:2]: href = _mounted_file_href("/pots_files", str(rel)) files.append(href) sources.append( { "id": f"PPL{len(sources) + 1}", "domain": "pots", "doc": Path(str(rel)).name, "relative_path": href, "chunk_id": f"playbook:fallback:{len(sources) + 1}", "location": "", "excerpt": "Indexed internal POTS source available for migration planning.", "score": 0.85, } ) sources = sources[:4] files = files[:4] anchor_ids = [str(s.get("id") or "") for s in sources if str(s.get("id") or "")] def _anchor_for(idx: int) -> str: if not anchor_ids: return "" return f"[{anchor_ids[idx % len(anchor_ids)]}]" def _first_sentence(raw: str, max_len: int = 170) -> str: text = _norm(raw) if not text: return "" sent = re.split(r"(?<=[.!?])\s+", text, maxsplit=1)[0].strip() sent = re.sub(r"\s+", " ", sent) if len(sent) > max_len: sent = sent[: max_len - 3].rstrip() + "..." return sent provider_pool = providers_present[:] if not provider_pool: for row in dedup_rows: probe = f"{row.get('doc', '')} {row.get('text', '')}".lower() for p in _pots_providers_in_text(probe): if p not in provider_pool: provider_pool.append(p) provider_focus_text = ( ", ".join(provider_pool[:4]) if provider_pool else "Provider names are not explicit in retrieved excerpts; confirm provider shortlist before execution." ) phases: List[Tuple[str, str, str, str, str]] = [ ( "1. Discovery", "Program-defined", "Inventory all analog endpoints by criticality (fire, elevator, fax, voice) and confirm cutover constraints.", ( f"Shortlist providers explicitly represented in retrieved internal excerpts: {provider_focus_text}." if provider_pool else provider_focus_text ), "Approved site inventory, line-type map, and migration risk register.", ), ( "2. Pilot", "Program-defined", "Run pilot sites across representative endpoint mixes and validate install workflow, failover behavior, and service continuity checks.", ( f"Pilot 1-2 provider tracks from retrieved evidence set ({provider_focus_text}) and score by line-type fit." if provider_pool else "Run pilot against the confirmed provider shortlist once provider names are validated." ), "Pilot scorecard, acceptance sign-off, and go/no-go decision with exception list.", ), ( "3. Wave rollout", "Program-defined", "Execute rollout waves by site criticality and readiness; keep rollback criteria and change windows explicit.", "Assign primary/secondary provider tracks per validated site profile and deployment constraints.", "Wave plan, execution tracker, exception log, and daily cutover report.", ), ( "4. Stabilization", "Program-defined", "Resolve post-cutover defects, verify alarm/elevator/fax continuity, and complete 911/regulatory checkpoints required by the customer environment.", "Retain provider assignment for unresolved sites and escalate through documented support channels.", "Defect closure report, stabilized operations checklist, and support handoff.", ), ( "5. Optimization", "Program-defined", "Tune cost/operations based on pilot and wave outcomes; standardize templates for future site adds.", "Use measured rollout outcomes to refine provider preference by endpoint type and operating model.", "Final playbook, KPI dashboard, and continuous-improvement backlog.", ), ] lines = [ "200-site copper-sunset migration playbook (phased, source-backed):", "", "| Phase | Timebox | Objective | Provider recommendation focus | Deliverable | Source anchors |", "| --- | --- | --- | --- | --- | --- |", ] for idx, (phase, timebox, objective, provider_focus, deliverable) in enumerate(phases, start=1): lines.append( f"| {phase} | {_md_cell(timebox)} | {_md_cell(objective)} | {_md_cell(provider_focus)} | " f"{_md_cell(deliverable)} | {_anchor_for(idx - 1)} |" ) return _heavy_cache_return( { "assistant": _format_shell( "\n".join(lines), [ "Returned a phased rollout structure with actionable objectives, deliverables, and exit criteria.", f"Provider recommendations are constrained to providers represented in retrieved internal evidence: {provider_text}.", ], [ "Ask `expand phase 2 with line-type criteria` for fire/elevator/fax-specific pilot scoring.", "Ask `convert to customer-ready rollout summary` for external-facing language.", ], ), "sources": sources, "files": files, "meta": {"domain": "pots", "retrieval_mode": "pots_playbook_fast_structured", "web_assisted": False}, } ) if asks_objection_map: retrieved = _pots_hits( [ message, "top 10 POTS objections objection handling map source-backed", ], k=4, limit=6, ) dedup_rows: List[Dict[str, Any]] = list(retrieved) if not dedup_rows: for rel in list(self._pots_file_map.values())[:4]: dedup_rows.append( { "doc": Path(str(rel)).name, "page": "", "text": ( "Internal POTS reference used for objection-handling anchors " "(cutover, compliance, reliability, and quoting assumptions)." ), "score": 0.85, } ) sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(dedup_rows, start=1): doc = Path(str(row.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) excerpt = _norm(str(row.get("text") or ""))[:260] page = str(row.get("page") or "").strip() sources.append( { "id": f"POM{idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"objection:{doc}:{page or idx}", "location": f"p. {page}" if page else "", "excerpt": excerpt or "Retrieved internal excerpt for objection handling.", "score": float(row.get("score") or 0.0), } ) if len(sources) < 2: for rel in list(self._pots_file_map.values())[:2]: href = _mounted_file_href("/pots_files", str(rel)) files.append(href) sources.append( { "id": f"POM{len(sources) + 1}", "domain": "pots", "doc": Path(str(rel)).name, "relative_path": href, "chunk_id": f"objection:fallback:{len(sources) + 1}", "location": "", "excerpt": "Indexed internal POTS source available for objection handling.", "score": 0.85, } ) sources = sources[:4] files = files[:4] anchor_ids = [str(s.get("id") or "") for s in sources if str(s.get("id") or "")] def _anchor(n: int) -> str: if not anchor_ids: return "" return f"[{anchor_ids[n % len(anchor_ids)]}]" def _concern_label(text: str) -> str: low_t = str(text or "").lower() if any(k in low_t for k in ("fax", "secure fax", "ifax")): return "Fax continuity and validation" if any(k in low_t for k in ("alarm", "life safety", "elevator", "fire")): return "Life-safety pathway compliance" if any(k in low_t for k in ("downtime", "cutover", "rollback", "transition")): return "Cutover downtime risk" if any(k in low_t for k in ("coverage", "signal", "cellular", "network")): return "Coverage and network reliability" if any(k in low_t for k in ("cost", "pricing", "billing", "quote")): return "Commercial predictability" if any(k in low_t for k in ("install", "provision", "deployment", "rollout")): return "Deployment execution risk" return "Operational continuity risk" def _brief(text: str, max_len: int = 160) -> str: s = _norm(text) if not s: return "" parts = [p.strip() for p in re.split(r"(?<=[.!?])\s+", s) if p.strip()] candidate = "" for p in parts: p = re.sub(r"^[^A-Za-z]+", "", p).strip() p = re.sub(r"^[a-z]{1,3}\s+", "", p).strip() if (len(p) < 38) or ("figure " in p.lower()): continue alpha = sum(1 for ch in p if ch.isalpha()) ratio = float(alpha) / float(max(1, len(p))) if ratio < 0.55: continue candidate = p break if not candidate: candidate = re.sub(r"^[^A-Za-z]+", "", s).strip() if len(candidate) > max_len: candidate = candidate[: max_len - 3].rstrip() + "..." return candidate asks_natural_language = any( x in low for x in ( "natural", "readable", "plain language", "plain-language", "rewrite", ) ) default_map: List[Tuple[str, str]] = [ ("Cutover downtime risk", "Use phased cutovers with explicit rollback criteria and acceptance checks."), ("Life-safety pathway concern", "Validate fire/elevator pathways separately and avoid blanket compliance statements."), ("Fax continuity concern", "Run endpoint-level fax validation and include pre/post cutover test evidence."), ("Coverage/reliability concern", "Include site signal assessment and failover behavior in the rollout plan."), ("Commercial predictability concern", "Separate documented line items from assumptions/open items in every quote."), ("Install complexity concern", "Use standardized intake + install checklist before scheduling waves."), ("Legacy endpoint compatibility", "Document which endpoints are in scope and list required adapters/gateways."), ("Escalation path concern", "Define named owners, escalation contacts, and response windows before rollout."), ("Change-management concern", "Communicate wave schedule, maintenance windows, and customer responsibilities early."), ("Post-cutover support concern", "Commit to stabilization period checks and defect closure reporting."), ] force_default_concerns = any( t in low for t in ( "plain language", "plain-language", "rewrite", "natural language", "readable language", ) ) def _response_for_concern(concern: str, snippet: str, fallback_response: str) -> str: concern_low = str(concern or "").lower() snippet_low = str(snippet or "").lower() response = fallback_response if "life-safety" in concern_low: response = "Use the cited internal material to validate fire/elevator pathway requirements, standby power, line seizure, and AHJ review before making compliance claims." elif "fax" in concern_low: response = "Keep fax endpoints explicitly in scope and require pre/post-cutover validation rather than assuming compatibility." elif "coverage" in concern_low: response = "Tie the design to documented signal expectations, site validation, and failover behavior before cutover." elif "commercial" in concern_low: response = "Keep documented line items separate from assumptions so quote scope, monthly charges, and open items stay clear." elif "install" in concern_low or "deployment" in concern_low: response = "Use a standard intake, install checklist, and rollout plan so provisioning and field work stay controlled." elif "legacy endpoint" in concern_low: response = "List every analog endpoint in scope and document any required adapters, gateways, or special handling before ordering." elif "escalation" in concern_low: response = "Name escalation owners, response windows, and support contacts before the rollout starts." elif "change-management" in concern_low: response = "Set maintenance windows, customer responsibilities, and wave timing before cutover so surprises are limited." elif "post-cutover" in concern_low: response = "Plan a stabilization window with post-cutover checks, issue tracking, and explicit closure criteria." elif "post-cutover" in concern_low: response = "Plan a stabilization window with post-cutover checks, issue tracking, and explicit closure criteria." elif "cutover" in concern_low or "continuity" in concern_low: response = "Use phased cutovers with rollback criteria, acceptance checks, and named owners to reduce outage risk." if any(term in snippet_low for term in ("mfvn", "line seizure", "standby power", "ahj")) and ("life-safety" in concern_low): response = "Use the cited pathway evidence for MFVN, standby power, line seizure, and AHJ validation, and avoid blanket compliance claims." elif any(term in snippet_low for term in ("pricing", "monthly", "quote", "billing")) and ("commercial" in concern_low): response = "Use documented pricing language where available, and clearly separate assumptions or pending quote inputs from confirmed line items." elif any(term in snippet_low for term in ("cutover", "rollback", "downtime")) and ("cutover" in concern_low): response = "Use phased cutovers, rollback criteria, and acceptance checks so downtime risk is controlled and easy to explain." return response row_pool = dedup_rows if dedup_rows else [{"text": "", "doc": ""}] used_labels: set[str] = set() built_rows: List[Tuple[int, str, str, str]] = [] for idx in range(10): row = row_pool[idx % len(row_pool)] snippet = _brief(str(row.get("text") or "")) concern = _concern_label(snippet) fallback_concern, fallback_response = default_map[idx] if force_default_concerns or (not snippet) or (len(snippet) < 24) or (concern in used_labels) or ("operational continuity risk" in concern.lower()): concern = fallback_concern used_labels.add(concern) response = _response_for_concern(concern, snippet, fallback_response) built_rows.append((idx + 1, concern, response, _anchor(idx))) if asks_natural_language: lines = [ "Top-10 objection-handling map (natural language, with source anchors):", "", "Source scope: cutover, compliance, reliability, and quoting assumptions.", "", ] for row_idx, concern, response, anchor in built_rows: lines.append(f"{row_idx}. **{concern}** — {response} {anchor}".strip()) lines.extend( [ "", "| # | Concern | Recommended response (concise) | Source anchor |", "| ---: | --- | --- | --- |", ] ) for row_idx, concern, response, anchor in built_rows: lines.append(f"| {row_idx} | {_md_cell(concern)} | {_md_cell(response)} | {anchor} |") else: lines = [ "Top-10 objection-handling map (with source anchors):", "", "Source scope: cutover, compliance, reliability, and quoting assumptions.", "", "| # | Concern | Recommended response (concise) | Source anchor |", "| ---: | --- | --- | --- |", ] for row_idx, concern, response, anchor in built_rows: lines.append(f"| {row_idx} | {_md_cell(concern)} | {_md_cell(response)} | {anchor} |") return _heavy_cache_return( { "assistant": _format_shell( "\n".join(lines), [ "Each row maps directly to retrieved internal excerpt text for citation-first objection handling.", "Rows are numbered so reps can quickly reference one objection at a time during live calls.", "Request a row expansion to turn any objection into a full call script with quotes.", ], [ "Ask `expand concern # with quoted excerpts` for stricter citation detail.", "Ask `convert to call script` for rep-ready talk track formatting.", ], ), "sources": sources, "files": files, "meta": {"domain": "pots", "retrieval_mode": "pots_objection_map_fast", "web_assisted": False}, } ) if asks_summary and len(providers_present) == 1: provider = providers_present[0] card = self._pots_provider_cards.get(provider, {}) docs_rel = [str(x) for x in (card.get("docs") or []) if str(x)] docs = [Path(x).name for x in docs_rel if Path(x).name.lower().endswith(".pdf")] alias_tokens = tuple(t.lower() for t in _POTS_PROVIDER_PATTERNS.get(provider, (provider,))) hit_rows = _provider_evidence_hits(provider, limit=6) provider_hits: List[Dict[str, Any]] = [] seen_hits: set[Tuple[str, str]] = set() for row in hit_rows: doc = Path(str(row.get("doc") or "")).name page = str(row.get("page") or "") text = _norm(str(row.get("text") or "")) blob = f"{doc} {text}".lower() if alias_tokens and (not any(_contains_term(blob, tok) for tok in alias_tokens)): continue key = (doc, page) if key in seen_hits: continue seen_hits.add(key) provider_hits.append(row) provider_hits = provider_hits[:4] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(provider_hits, start=1): doc = Path(str(row.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", str(rel)) files.append(href) excerpt = _norm(str(row.get("text") or "")) page = str(row.get("page") or "").strip() sources.append( { "id": f"PSS{idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"provider_summary:{provider}:{doc}:{page or idx}", "location": f"p. {page}" if page else "", "excerpt": excerpt[:280] if excerpt else f"{provider} provider excerpt from indexed internal docs.", "score": float(row.get("score") or 0.0), } ) for rel in docs_rel[:4]: href = _mounted_file_href("/pots_files", str(rel)) if href not in files: files.append(href) if not sources and docs_rel: sources, files = _pots_filename_only_sources( docs_rel, source_id_prefix="PSS", excerpt=f"{provider} filename-level coverage is present, but no provider-specific excerpt matched in this pass.", limit=4, ) anchor_ids = [str(s.get("id") or "") for s in sources if str(s.get("id") or "")] def _anchor(n: int) -> str: if not anchor_ids: return "Not explicitly documented in retrieved excerpts" return f"[{anchor_ids[n % len(anchor_ids)]}]" def _brief(raw: str, max_len: int = 170) -> str: text = _norm(raw) if not text: return "" text = re.sub(r"\s+", " ", text).strip() if len(text) > max_len: return text[: max_len - 3].rstrip() + "..." return text def _summary_snippet(raw: str, max_len: int = 170) -> str: text = _norm(raw) if not text: return "" text = re.sub(r"\s+", " ", text).strip() sentences = [s.strip(" -•") for s in re.split(r"(?<=[.!?])\s+", text) if s.strip()] provider_terms = tuple(dict.fromkeys([provider.lower(), *alias_tokens])) for sentence in sentences: low_sentence = sentence.lower() if len(sentence) < 24: continue if sentence[:1].islower(): continue if any( token in low_sentence for token in ( "make it difficult to implement", "all rights reserved", "table of contents", "for more information", ) ): continue if provider_terms and not any(term in low_sentence for term in provider_terms) and not any( token in low_sentence for token in ( "pots replacement", "wired voice", "wired voice/fax", "wireless", "security alarm", "redundant", "monitor", "24x7", "battery", "ethernet network", ) ): continue return _brief(sentence, max_len=max_len) for sentence in sentences: if len(sentence) >= 24 and not sentence[:1].islower(): return _brief(sentence, max_len=max_len) return _brief(text, max_len=max_len) result = [ f"Internal-doc summary for **{provider}** (POTS replacement):", "", "| Summary point | Evidence |", "| --- | --- |", ] if provider_hits: for idx, row in enumerate(provider_hits[:3], start=1): snippet = _summary_snippet(str(row.get("text") or "")) if not snippet: continue result.append(f"| {_md_cell(snippet)} | {_anchor(idx - 1)} |") result.append("| Capability/compliance claims beyond cited excerpts | Not claimed (abstained). |") else: indexed_docs = ", ".join(docs[:3]) if docs else "No indexed provider PDF detected." result.append(f"| Indexed provider document coverage | {_md_cell(indexed_docs)} |") result.append( f"| What is explicitly documented now | Provider-level document presence is confirmed for `{provider}`, but excerpt-level claims are thin in this pass. |" ) result.append("| Capability/compliance claims | Not claimed (abstained) until provider-specific excerpts are retrieved. |") if docs: result.append(f"| Example indexed files | {_md_cell(', '.join(docs[:3]))} |") return { "assistant": _format_shell( "\n".join(result), [ "No provider is auto-prioritized in this fast-summary path.", "Summary is excerpt-first and abstains when provider-specific evidence is missing.", ], [ ( f"Ask `compare {provider} vs MetTel vs DataRemote in weighted table` for side-by-side tradeoffs." if provider.upper() == "OOMA" else f"Ask `compare {provider} vs OOMA vs MetTel in weighted table` for side-by-side tradeoffs." ), "Ask `from docs only` for strict excerpt-backed claims by requirement.", ], ), "sources": sources, "files": files, "meta": {"domain": "pots", "retrieval_mode": "pots_provider_summary_fast", "web_assisted": False}, } if asks_overview: rows = sorted(self._pots_provider_cards.values(), key=lambda x: (-int(x.get("count", 0)), str(x.get("provider", "")))) lines = [ "Source-backed overview of internal POTS replacement materials:", "", "| Provider | Internal docs | Example files |", "| --- | ---: | --- |", ] for row in rows[:12]: provider = str(row.get("provider") or "") docs = [Path(str(d)).name for d in (row.get("docs") or []) if str(d)] lines.append(f"| {provider} | {int(row.get('count', 0))} | {_md_cell(', '.join(docs[:2]) or 'Not listed')} |") return { "assistant": _format_shell( "\n".join(lines), [ "Overview built from indexed internal files and provider tagging signals.", "Use deep retrieval when you need requirement-level claims tied to specific excerpts/pages.", ], [ "Ask `best internal docs for discovery-call prep` to get a focused starter pack.", "Ask for a provider-specific weighted table for your target deployment type.", ], ), "sources": [ { "id": "PSO1", "domain": "pots", "doc": "pots corpus index", "relative_path": "", "chunk_id": "provider_overview_fast", "location": "", "excerpt": "Provider coverage and document counts from indexed internal POTS corpus.", "score": 0.9, } ], "files": [_mounted_file_href("/pots_files", x) for x in list(self._pots_file_map.values())[:12]], "meta": {"domain": "pots", "retrieval_mode": "pots_overview_fast", "web_assisted": False}, } if (not providers_present) and any(x in low for x in ("provider", "providers")): providers_present = [ p for p, _ in sorted( self._pots_provider_cards.items(), key=lambda item: (-int(item[1].get("count", 0) or 0), item[0].lower()), )[:4] ] if len(providers_present) >= 2 and any(x in low for x in ("battery backup", "survivability", "failover", "redundancy")) and any(x in low for x in ("provider", "providers", "difference", "differences", "compare", "summary", "summarize")): lines = [ "Provider differences for battery backup and survivability (internal evidence only):", "", "| Provider | Survivability / backup signal | What the current corpus supports | Evidence depth |", "| --- | --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] unresolved_providers: List[str] = [] for idx, provider in enumerate(providers_present[:4], start=1): alias_tokens = tuple(t.lower() for t in _POTS_PROVIDER_PATTERNS.get(provider, (provider,))) hits = _provider_evidence_hits(provider, limit=4) if not hits: hits = _pots_hits( [ f"{provider} battery backup survivability failover redundancy", f"{provider} backup power pots replacement", message, ], k=4, limit=8, ) filtered_hits: List[Dict[str, Any]] = [] for h in hits: doc_name = Path(str(h.get("doc") or "")).name.lower() excerpt = _norm(str(h.get("text") or "")).lower() blob = f"{doc_name} {excerpt}" if alias_tokens and (not any(_contains_term(blob, tok) for tok in alias_tokens)): continue if not any(t in blob for t in ("survivability", "failover", "redundancy", "backup", "battery")): continue filtered_hits.append(h) hits = filtered_hits[:2] merged = " ".join(_norm(str(h.get("text") or "")) for h in hits) merged_low = merged.lower() if not hits: unresolved_providers.append(provider) continue signal = "Explicit survivability/backup wording found" if any(t in merged_low for t in ("survivability", "failover", "redundancy", "backup", "battery")) else "No explicit survivability/backup wording in top retrieved excerpts" if "battery" in merged_low: summary = "Battery-related wording appears in retrieved provider evidence." elif any(t in merged_low for t in ("survivability", "failover", "redundancy", "backup")): summary = "Reliability/failover language appears, but battery specifics should still be confirmed at deployment design level." else: summary = "Current corpus is too thin to make a battery-backup claim for this provider." lines.append(f"| {provider} | {signal} | {_md_cell(summary)} | {_provider_depth(int((self._pots_provider_cards.get(provider, {}) or {}).get('count', 0) or 0))} |") hit = hits[0] if hits else None if hit: doc = Path(str(hit.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) sources.append( { "id": f"PBS{idx}", "domain": "pots", "doc": doc or f"{provider} provider docs", "relative_path": href, "chunk_id": f"pots_provider_survivability:{provider}:{idx}", "location": f"p. {str(hit.get('page') or '').strip()}".strip() if str(hit.get("page") or "").strip() else "", "excerpt": _norm(str(hit.get("text") or ""))[:260] or f"{provider} survivability evidence excerpt.", "score": float(hit.get("score") or 0.0), } ) if unresolved_providers: lines.extend( [ "", f"Unresolved in this pass: {', '.join(unresolved_providers)} did not surface provider-tagged survivability or battery wording in the retrieved internal excerpts.", ] ) if not sources: return { "assistant": _format_shell( "Current internal provider excerpts do not support a provider-by-provider survivability comparison.", [ "No provider-tagged excerpt in this pass explicitly mentioned battery backup, survivability, failover, or redundancy.", ], [ "Ask `expand one provider with citations` to force a narrower provider-level check before making comparison claims.", ], ), "sources": [], "files": [], "meta": {"domain": "pots", "retrieval_mode": "pots_provider_survivability_fast", "web_assisted": False}, } return { "assistant": _format_shell( "\n".join(lines), [ "Rows stay tied to currently retrieved provider evidence and explicitly avoid assuming backup behavior where wording is thin.", ], [ "Ask `expand one provider with citations` for a tighter due-diligence follow-up.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:10], "meta": {"domain": "pots", "retrieval_mode": "pots_provider_survivability_fast", "web_assisted": False}, } if len(providers_present) >= 2 and any(x in low for x in ("multi-site retail", "multisite retail", "retail rollout", "retail")) and any(x in low for x in ("tradeoff", "tradeoffs", "difference", "differences", "compare", "providers")): lines = [ "Provider tradeoffs for multi-site retail (internal evidence only):", "", "| Provider | Likely tradeoff theme | Source-bounded read | Evidence depth |", "| --- | --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] unresolved_providers: List[str] = [] for idx, provider in enumerate(providers_present[:4], start=1): count = int((self._pots_provider_cards.get(provider, {}) or {}).get("count", 0) or 0) docs = [Path(x).name for x in _provider_docs(provider, limit=3)] alias_tokens = tuple(t.lower() for t in _POTS_PROVIDER_PATTERNS.get(provider, (provider,))) hits = _provider_evidence_hits(provider, limit=4) if not hits: hits = _pots_hits( [ f"{provider} multi-site retail rollout deployment case study", f"{provider} retail operations monitoring management", message, ], k=4, limit=8, ) filtered_hits: List[Dict[str, Any]] = [] for h in hits: doc_name = Path(str(h.get("doc") or "")).name.lower() excerpt = _norm(str(h.get("text") or "")).lower() blob = f"{doc_name} {excerpt}" if alias_tokens and (not any(_contains_term(blob, tok) for tok in alias_tokens)): continue if not any(t in blob for t in ("retail", "rollout", "deployment", "case study", "case-study", "management", "monitoring", "portal", "dashboard", "multi-site", "multisite")): continue filtered_hits.append(h) hits = filtered_hits[:2] merged_low = " ".join(_norm(str(h.get("text") or "")).lower() for h in hits) if not hits: unresolved_providers.append(provider) continue if any(t in merged_low for t in ("case study", "case-study", "deployment", "rollout", "retail")): theme = "Deployment example coverage" summary = "Retrieved provider evidence includes deployment/example wording relevant to retail rollout planning." elif any(t in merged_low for t in ("monitor", "management", "portal", "dashboard")): theme = "Operations / monitoring signal" summary = "Retrieved wording suggests an operations/monitoring angle that can matter in larger rollouts." else: theme = "General rollout signal" summary = "Provider-tagged wording exists, but the retail-specific rollout tradeoff still needs requirement-level validation." lines.append(f"| {provider} | {theme} | {_md_cell(summary)} | {_provider_depth(count)} |") hit = hits[0] if hits else None if hit: doc = Path(str(hit.get("doc") or "")).name rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) sources.append( { "id": f"PBR{idx}", "domain": "pots", "doc": doc or (docs[0] if docs else f"{provider} provider docs"), "relative_path": href, "chunk_id": f"pots_provider_retail:{provider}:{idx}", "location": f"p. {str(hit.get('page') or '').strip()}".strip() if str(hit.get("page") or "").strip() else "", "excerpt": _norm(str(hit.get("text") or ""))[:260] or f"{provider} retail tradeoff evidence excerpt.", "score": float(hit.get("score") or 0.0), } ) if unresolved_providers: lines.extend( [ "", f"Unresolved in this pass: {', '.join(unresolved_providers)} did not surface provider-tagged retail or rollout wording in the retrieved internal excerpts.", ] ) if not sources: return { "assistant": _format_shell( "Current internal excerpts do not support a provider-by-provider retail tradeoff table.", [ "No provider-tagged excerpt in this pass explicitly covered retail rollout, deployment examples, or operations tradeoffs.", ], [ "Ask `score these providers for a named retail program` only after retrieving provider-specific rollout docs or requirements.", ], ), "sources": [], "files": [], "meta": {"domain": "pots", "retrieval_mode": "pots_provider_retail_tradeoff_fast", "web_assisted": False}, } return { "assistant": _format_shell( "\n".join(lines), [ "Tradeoffs stay tied to indexed evidence depth and retrieved wording rather than inferred provider rankings.", ], [ "Ask `score these providers for a named retail program` if you want a requirement-specific weighted view.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:10], "meta": {"domain": "pots", "retrieval_mode": "pots_provider_retail_tradeoff_fast", "web_assisted": False}, } if ( (len(providers_present) >= 2) and asks_compare and (not asks_weighted_compare) and (not asks_scoring_matrix) and (not any(x in low for x in ("differ", "difference", "differences", "tradeoff", "tradeoffs"))) ): fire_terms = ("fire", "elevator", "alarm", "life safety", "nfpa", "ifc907") fax_terms = ("fax", "securefax", "ifax") reli_terms = ("reliability", "survivability", "failover", "redundancy", "backup") mgmt_terms = ("portal", "management", "monitoring", "dashboard", "cloud") asks_requirement_specific = any( t in low for t in ( "fire", "elevator", "alarm", "life safety", "fax", "securefax", "reliability", "failover", "survivability", "monitoring", ) ) rows: List[Tuple[str, int, str, str, str, str]] = [] files: List[str] = [] sources: List[Dict[str, Any]] = [] src_idx = 1 for p in providers_present[:6]: card = self._pots_provider_cards.get(p, {}) count = int(card.get("count", 0) or 0) docs_rel = [str(x) for x in (card.get("docs") or []) if str(x)] docs = [Path(x).name for x in docs_rel[:3]] hits = _provider_evidence_hits(p, limit=4) if not hits: hits = _pots_hits( [ f"{p} pots replacement capabilities", f"{p} fire elevator alarm fax requirements", f"{p} reliability failover monitoring", message, ], k=4, limit=6, ) merged_text = " ".join(_norm(str(h.get("text") or "")).lower() for h in hits) strengths: List[str] = [] limits: List[str] = [] if asks_requirement_specific: if any(t in merged_text for t in fire_terms): strengths.append("fire/elevator/alarm signal documented") else: limits.append("fire/elevator/alarm not explicit in top excerpts") if any(t in merged_text for t in fax_terms): strengths.append("fax/secure-fax signal documented") else: limits.append("fax signal not explicit in top excerpts") if any(t in merged_text for t in reli_terms): strengths.append("reliability/failover signal documented") else: limits.append("reliability/failover not explicit in top excerpts") if any(t in merged_text for t in mgmt_terms): strengths.append("management/monitoring signal documented") if not strengths: strengths.append("provider docs exist but capability evidence is thin") if count <= 0: limits.append("no provider-tagged docs indexed") if not limits: limits.append("no major gaps in retrieved excerpts") else: best_excerpt = _norm(str((hits[0] if hits else {}).get("text") or "")) if best_excerpt: best_excerpt = re.sub(r"\s+", " ", best_excerpt).strip() if len(best_excerpt) > 145: best_excerpt = best_excerpt[:142].rstrip() + "..." strengths.append(best_excerpt or "No provider-specific excerpt in top retrieved rows.") limits.append("Requirement-level capability comparison was not requested; use weighted table for fire/elevator/fax/reliability scoring.") for h in hits[:2]: doc = Path(str(h.get("doc") or "")).name if not doc: continue rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) excerpt = _norm(str(h.get("text") or "")) page = str(h.get("page") or "").strip() sources.append( { "id": f"PCAP{src_idx}", "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"pots_compare_capability:{p}:{doc}:{page or src_idx}", "location": f"p. {page}" if page else "", "excerpt": excerpt[:260] if excerpt else f"{p} capability excerpt from internal docs.", "score": float(h.get("score") or 0.0), } ) src_idx += 1 rows.append( ( p, count, _provider_depth(count), "; ".join(strengths[:3]), "; ".join(limits[:3]), ", ".join(docs) if docs else "Not listed", ) ) lines = [ "Provider capability comparison (internal docs only):", "", "| Provider | Internal doc count | Evidence depth | Documented strengths | Documented limits | Example docs |", "| --- | ---: | --- | --- | --- | --- |", ] for p, c, d, s, l, docs in rows: lines.append(f"| {p} | {c} | {d} | {_md_cell(s)} | {_md_cell(l)} | {_md_cell(docs)} |") return { "assistant": _format_shell( "\n".join(lines), [ "Compared requested providers using internal excerpts first, with explicit limits when evidence is thin.", "No provider is auto-preferred in this non-weighted compare format.", ], [ "Ask `weighted table` if you want explicit scoring/ranking added.", "Ask `deep compare by endpoint type` for fire/elevator/fax-focused detail.", ], ), "sources": sources[:12], "files": list(dict.fromkeys(files))[:12], "meta": {"domain": "pots", "retrieval_mode": "pots_compare_capability_fast", "web_assisted": False}, } if (len(providers_present) >= 2) and asks_compare: rows = [] files: List[str] = [] sources: List[Dict[str, Any]] = [] src_idx = 1 include_endpoint_cols = bool(asks_endpoint_matrix or asks_weighted_compare) fire_terms = ("fire", "elevator", "alarm", "life safety", "nfpa") fax_terms = ("fax", "securefax", "ifax") reli_terms = ("reliability", "survivability", "failover", "redundancy", "uptime", "backup") def _sig(v: int) -> str: if v >= 2: return "Strong" if v == 1: return "Moderate" return "Not documented" for p in providers_present[:6]: card = self._pots_provider_cards.get(p, {}) count = int(card.get("count", 0) or 0) evidence = _provider_depth(count) docs_rel = [str(x) for x in (card.get("docs") or []) if str(x)] alias_tokens = tuple(t.lower() for t in _POTS_PROVIDER_PATTERNS.get(p, (p,))) provider_hits = _provider_evidence_hits(p, limit=4) if not provider_hits: provider_hits = _pots_hits( [ f"{p} pots replacement fire elevator alarm fax requirements", f"{p} reliability survivability failover monitoring", f"{p} documented strengths limitations", message, ], k=4, limit=8, ) filtered_hits: List[Dict[str, Any]] = [] for h in provider_hits: doc_name = Path(str(h.get("doc") or "")).name.lower() text_blob = _norm(str(h.get("text") or "")).lower() if not text_blob and not doc_name: continue blob = f"{doc_name} {text_blob}" if alias_tokens and (not any(_contains_term(blob, tok) for tok in alias_tokens)): continue filtered_hits.append(h) selected_hits: List[Dict[str, Any]] = [] seen_doc_page: set[Tuple[str, str]] = set() for h in filtered_hits: text_blob = _norm(str(h.get("text") or "")).lower() doc_name = Path(str(h.get("doc") or "")).name page = str(h.get("page") or "").strip() key = (doc_name, page) if key in seen_doc_page: continue has_fire = any(term in text_blob for term in fire_terms) has_fax = any(term in text_blob for term in fax_terms) has_reli = any(term in text_blob for term in reli_terms) if has_fire or has_fax or has_reli: selected_hits.append(h) seen_doc_page.add(key) if len(selected_hits) >= 1: break source_ids: List[str] = [] docs: List[str] = [] for h in selected_hits[:1]: doc = Path(str(h.get("doc") or "")).name if not doc: continue rel = self._pots_file_map.get(doc.lower(), doc) href = _mounted_file_href("/pots_files", rel) files.append(href) docs.append(doc) excerpt = _norm(str(h.get("text") or "")) page = str(h.get("page") or "").strip() sid = f"PFC{src_idx}" source_ids.append(sid) sources.append( { "id": sid, "domain": "pots", "doc": doc, "relative_path": href, "chunk_id": f"provider_fast_compare:{p}:{doc}:{page or src_idx}", "location": f"p. {page}" if page else "", "excerpt": excerpt[:260] if excerpt else f"Internal provider excerpt for {p}.", "score": float(h.get("score") or 0.0), } ) src_idx += 1 combined_evidence_text = " ".join(_norm(str(h.get("text") or "")).lower() for h in selected_hits) fire_hits = 1 if any(term in combined_evidence_text for term in fire_terms) else 0 fax_hits = 1 if any(term in combined_evidence_text for term in fax_terms) else 0 reli_hits = 1 if any(term in combined_evidence_text for term in reli_terms) else 0 if not docs: docs = [Path(x).name for x in docs_rel[:2]] if docs_rel: for rel in docs_rel[:2]: files.append(_mounted_file_href("/pots_files", rel)) discovered_docs = { Path(str(h.get("doc") or "")).name for h in (provider_hits or []) if Path(str(h.get("doc") or "")).name } display_count = count if display_count <= 0: if docs_rel: display_count = len({Path(x).name for x in docs_rel}) elif discovered_docs: display_count = len(discovered_docs) evidence = _provider_depth(display_count) fire_label = _sig(fire_hits) fax_label = _sig(fax_hits) reli_label = _sig(reli_hits) strength_bits: List[str] = [] if fire_hits > 0: strength_bits.append("fire/elevator/alarm evidence found") if fax_hits > 0: strength_bits.append("fax evidence found") if reli_hits > 0: strength_bits.append("reliability/failover evidence found") if not strength_bits: strength_bits.append("No requirement-level capability evidence found in retrieved internal excerpts") strength_text = "; ".join(strength_bits[:3]) limit_bits: List[str] = [] if display_count <= 0: limit_bits.append("No provider-tagged docs are indexed for this provider token") if fire_hits == 0: limit_bits.append("no fire/elevator/alarm excerpt retrieved") if fax_hits == 0: limit_bits.append("no fax excerpt retrieved") if reli_hits == 0: limit_bits.append("no reliability/failover excerpt retrieved") if not source_ids: limit_bits.append("insufficient internal evidence to make capability claims") limits = "; ".join(limit_bits[:3]) if limit_bits else "No major evidence gaps in retrieved internal excerpt." if source_ids: signal_points = fire_hits + fax_hits + reli_hits if display_count >= 5 and signal_points >= 2: weighted = "Strong" elif display_count >= 1 and signal_points >= 1: weighted = "Moderate" else: weighted = "Limited" else: weighted = "Insufficient evidence" refs = ", ".join(source_ids) if source_ids else "None" rows.append( ( p, weighted, display_count, evidence, fire_label, fax_label, reli_label, f"{strength_text} ({refs})", f"{limits} ({refs})", refs, ", ".join(dict.fromkeys(docs)) if docs else "Not listed", ) ) heading = ( "Provider scoring matrix (internal indexed evidence):" if asks_scoring_matrix else ("Provider comparison (weighted, internal evidence only):" if asks_weighted_compare else "Provider comparison (internal indexed evidence):") ) lines = [heading, ""] if include_endpoint_cols: lines.extend( [ "| Provider | Weighted fit signal | Internal doc count | Evidence depth | Fire/elevator signal | Alarm/fax signal | Reliability/survivability signal | Documented strengths (evidence-backed) | Documented limits (evidence-backed) | Evidence refs | Example docs |", "| --- | --- | ---: | --- | --- | --- | --- | --- | --- | --- | --- |", ] ) for p, w, c, e, fs, xs, rs, s, l, refs, d in rows: lines.append( f"| {p} | {w} | {c} | {e} | {fs} | {xs} | {rs} | {_md_cell(s)} | {_md_cell(l)} | {_md_cell(refs)} | {_md_cell(d)} |" ) else: lines.extend( [ "| Provider | Fit signal | Internal doc count | Evidence depth | Documented strengths (evidence-backed) | Documented limits (evidence-backed) | Evidence refs | Example docs |", "| --- | --- | ---: | --- | --- | --- | --- | --- |", ] ) for p, w, c, e, fs, xs, rs, s, l, refs, d in rows: lines.append(f"| {p} | {w} | {c} | {e} | {_md_cell(s)} | {_md_cell(l)} | {_md_cell(refs)} | {_md_cell(d)} |") files = list(dict.fromkeys(files))[:12] return { "assistant": _format_shell( "\n".join(lines), [ "Weighted fit signal is computed from internal doc coverage plus requirement-term presence in cited excerpts.", "When no excerpts are found for a provider, the table explicitly abstains instead of inferring capabilities.", ], [ "Ask `deep compare by exact requirement` to force excerpt-level validation for your target deployment.", ], ), "sources": sources[:12], "files": files, "meta": {"domain": "pots", "retrieval_mode": "pots_compare_fast_structured", "web_assisted": False}, } return None def _masters_securefax_pricing_fast(self, message: str) -> Optional[Dict[str, Any]]: low = _normalize_router_query_text(message) asks_securefax = any(t in low for t in ("securefax", "secure fax", "ifax", "i fax")) asks_price = any( t in low for t in ("how much", "price", "pricing", "cost", "msrp", "monthly", "mrc", "nrc", "setup", "one-time", "one time") ) if not (asks_securefax and asks_price): return None idx_obj = getattr(self.masters_core, "index", None) if idx_obj is None: return None if hasattr(idx_obj, "ensure_sku_rows_loaded"): try: idx_obj.ensure_sku_rows_loaded() except Exception: pass sku_rows = list(getattr(idx_obj, "sku_rows", []) or []) if not sku_rows: return None secure_rows = [ r for r in sku_rows if any(t in f"{r.get('sku', '')} {r.get('description', '')}".lower() for t in ("securefax", "secure fax")) ] if (not secure_rows) and any(t in low for t in ("ifax", "i fax")): secure_rows = [r for r in sku_rows if any(t in f"{r.get('sku', '')} {r.get('description', '')}".lower() for t in ("ifax", "i fax"))] if not secure_rows: return None def _price_type_from_sku(sku: str) -> str: up = str(sku or "").upper() if up.endswith("-MRC"): return "Monthly recurring" if up.endswith("-NRC"): return "One-time setup" return "Not listed" def _row_rank(row: Dict[str, Any]) -> Tuple[int, str]: sku = str(row.get("sku", "") or "") ptype = _price_type_from_sku(sku) rank = 2 if ptype == "Monthly recurring": rank = 0 elif ptype == "One-time setup": rank = 1 return rank, sku secure_rows = sorted(secure_rows, key=_row_rank) lines = [ "SecureFax pricing from internal Masters SKU exhibit:", "", "| SKU | Description | MSRP | Term | Price type |", "| --- | --- | ---: | --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(secure_rows[:8], start=1): sku = _norm(row.get("sku", "")) or "Not listed" desc = _norm(row.get("description", "")) or "Description not listed" msrp = _norm(row.get("msrp", "")) or "Not listed" term = _norm(row.get("term", "")) or "Not listed" ptype = _price_type_from_sku(sku) lines.append(f"| {_md_cell(sku)} | {_md_cell(desc)} | {_md_cell(msrp)} | {_md_cell(term)} | {_md_cell(ptype)} |") doc = _norm(row.get("doc", "")) or "All BuSS Sku's 2025.pdf" page = _norm(row.get("page", "")) or "1" rel = self._masters_file_map.get(doc.lower(), doc) href = _mounted_file_href("/masters_files", rel) files.append(href) sources.append( { "id": f"SFP{idx}", "domain": "masters", "doc": doc, "relative_path": href, "chunk_id": f"masters_securefax_price:{idx}", "location": f"p.{page}", "excerpt": f"SKU {sku} | {desc} | MSRP {msrp} | Term {term}", "score": 0.99, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Pulled directly from internal SKU exhibit rows (no web pricing used).", "MSRP and term are returned verbatim from approved internal docs.", ], [ "Share quantity + term and I can build a quote-ready line-item total.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_securefax_pricing_fast", "web_assisted": False}, } def _masters_fast_outline_answer(self, message: str) -> Optional[Dict[str, Any]]: low = str(message or "").lower() doc_lookup_intent = ( any( h in low for h in ( "which files", "what files", "which docs", "what docs", "which documents", "what documents", "which internal documents", "which internal docs", "ranked list of source documents", ) ) or ("where pricing appears" in low) or ("where it does not" in low) ) if doc_lookup_intent: return None sip_accounts_concept_intent = ( any(x in low for x in ("sip account", "sip accounts")) and any(x in low for x in ("what is", "what are", "explain", "plain english", "sales rep")) ) high_value_intent = any( h in low for h in ( "sales enablement brief", "customer-ready quote support", "quote support", "discovery + follow-up", "discovery and follow-up", "follow-up sequence", "battle card", "talk track", "cheat sheet", "source-backed talk track", "source backed talk track", "source-backed overview", "source backed overview", "can and cannot be claimed", "what can and cannot be claimed", "cannot be claimed", ) ) if any(h in low for h in _MASTERS_FORCE_DEEP_HINTS) and (not high_value_intent) and (not sip_accounts_concept_intent): return None if (not sip_accounts_concept_intent) and (not any(h in low for h in _MASTERS_FAST_OUTLINE_HINTS)): return None if not self._masters_file_map: return None docs = [Path(x).name for x in list(self._masters_file_map.values())[:10]] all_rels = [str(x) for x in self._masters_file_map.values() if str(x)] def _masters_rel(doc_name: str) -> str: return next((rel for rel in all_rels if Path(str(rel)).name.lower() == str(doc_name or "").lower()), "") if ("pots replacement materials" in low) and any(x in low for x in ("source-backed overview", "source backed overview", "overview")): material_rows = [ ( "MST_POTS Replacement.pdf", "Primary internal collateral for POTS replacement positioning.", "Internal POTS replacement reference used to frame migration scope, risk, and positioning.", ), ( "MST_Pro Install.pdf", "Install/service-scope companion for deployment planning.", "Internal install reference used to frame service scope, sequencing, and field-execution assumptions.", ), ( "All BuSS Sku's 2025.pdf", "Quote-safe SKU/MSRP/term exhibit for documented line items.", "Approved SKU exhibit used for documented SKU, MSRP, and term rows.", ), ( "B360 Masters Order Flow.pptx", "Internal order-flow reference for how those materials get used in process.", "Internal order-flow reference used to connect collateral to the operational quoting workflow.", ), ] lines = [ "Source-backed overview of Masters POTS replacement materials:", "", "| Material | Best use | Evidence |", "| --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, (doc_name, best_use, evidence) in enumerate(material_rows, start=1): rel = _masters_rel(doc_name) if not rel: continue href = _mounted_file_href("/masters_files", rel) files.append(href) sources.append( { "id": f"MFO{idx}", "domain": "masters", "doc": doc_name, "relative_path": href, "chunk_id": f"masters_outline:title_ref:{idx}", "location": "", "excerpt": evidence, "score": 0.93, } ) lines.append(f"| {doc_name} | {_md_cell(best_use)} | {_md_cell(evidence)} |") if sources: return { "assistant": _format_shell( "\n".join(lines), [ "Overview stays doc-centered and does not pay a search round-trip when the request is satisfied by approved Masters references.", ], [ "Ask `turn this into a discovery-call prep pack` if you want these ordered for rep use.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_outline_fast", "web_assisted": False}, } idx_obj = getattr(self.masters_core, "index", None) if idx_obj is not None and hasattr(idx_obj, "ensure_sku_rows_loaded"): try: idx_obj.ensure_sku_rows_loaded() except Exception: pass sku_rows: List[Dict[str, Any]] = [] if idx_obj is not None: sku_rows = list(getattr(idx_obj, "sku_rows", []) or []) def _masters_ref_sources( rows: Sequence[Tuple[str, str, str, str]], *, id_prefix: str = "MFO", ) -> Tuple[List[Dict[str, Any]], List[str]]: out_sources: List[Dict[str, Any]] = [] out_files: List[str] = [] for idx, (doc_name, excerpt, chunk_id, location) in enumerate(rows, start=1): rel = _masters_rel(doc_name) if not rel: continue href = _mounted_file_href("/masters_files", rel) out_files.append(href) out_sources.append( { "id": f"{id_prefix}{idx}", "domain": "masters", "doc": doc_name, "relative_path": href, "chunk_id": chunk_id, "location": location, "excerpt": excerpt, "score": 0.93, } ) return out_sources, list(dict.fromkeys(out_files)) asks_securefax_sku_list = ( ("list" in low) and ("sku" in low) and any(x in low for x in ("securefax", "secure fax")) ) if asks_securefax_sku_list: secure_rows = [ r for r in sku_rows if "securefax" in f"{r.get('sku','')} {r.get('description','')}".lower() ] lines = [ "Documented SecureFAX-related BuSS SKUs (internal references):", "", "| SKU | What it does (doc text) | MSRP | Term | Evidence |", "| --- | --- | ---: | --- | --- |", ] for idx, row in enumerate(secure_rows[:8], start=1): sku = _norm(row.get("sku", "")) or "Not listed" desc = _norm(row.get("description", "")) or "Description not listed" msrp = _norm(row.get("msrp", "")) or "Not listed" term = _norm(row.get("term", "")) or "Not listed" lines.append( f"| {_md_cell(sku)} | {_md_cell(desc)} | {_md_cell(msrp)} | {_md_cell(term)} | [MFS{idx}] |" ) if not secure_rows: lines.append( "| No parsed SecureFAX rows | SecureFAX rows were not parsed from current SKU cache. | Not listed | Not listed | Not explicitly documented in current SKU cache |" ) source_rows = [ ( _norm(row.get("doc", "")) or "All BuSS Sku's 2025.pdf", f"SKU {_norm(row.get('sku', '')) or 'Not listed'} | " f"{_norm(row.get('description', '')) or 'Description not listed'} | " f"MSRP {_norm(row.get('msrp', '')) or 'Not listed'} | " f"Term {_norm(row.get('term', '')) or 'Not listed'}", f"masters_securefax_row:{idx}", f"p.{_norm(row.get('page', '')) or '1'}", ) for idx, row in enumerate(secure_rows[:8], start=1) ] if not source_rows: source_rows = [ ( "All BuSS Sku's 2025.pdf", "Canonical Masters SKU exhibit reference for documented SKU, MSRP, and term rows.", "masters_securefax_row:fallback", "", ) ] sources, files = _masters_ref_sources(source_rows, id_prefix="MFS") return { "assistant": _format_shell( "\n".join(lines), [ "List is constrained to approved internal SKU exhibit rows and does not require search-time evidence assembly.", ], [ "Ask `show exact source pages for each SKU` for page-level extraction.", ], ), "sources": sources[:8], "files": files[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_outline_fast", "web_assisted": False}, } if ("sip accounts" in low or "sip account" in low) and ("contact center" in low): ref_rows = [ ( "MST_SIP Accounts.pdf", "Internal SIP Accounts reference for SIP/trunking discovery, account framing, and voice-service positioning.", "masters_ref:sip_accounts", "", ), ( "MST_Contact Center.pdf", "Internal Contact Center reference for agent workflow framing and related service positioning.", "masters_ref:contact_center", "", ), ] sources, files = _masters_ref_sources(ref_rows) if sources: lines = [ "SIP accounts vs contact center in plain English:", "", "| Topic | SIP accounts | Contact center | Evidence |", "| --- | --- | --- |", "| What it is | The service-account and admin setup used to organize SIP-based voice service, trunks, users, and routing. | The workflow/application layer for handling customer interactions, queues, agent flows, and reporting. | [MFO1][MFO2] |", "| When reps use it | Use when the discussion is about voice-service setup, trunking context, numbers, or call-routing administration. | Use when the discussion is about agent workflow, queues, customer-experience process, or contact-center operations. | [MFO1][MFO2] |", "| Safe positioning line | `SIP accounts` is about the voice-service/account structure. | `Contact center` is about the customer-interaction workflow that may sit on top of voice services. | [MFO1][MFO2] |", ] return { "assistant": _format_shell( "\n".join(lines), [ "This keeps the answer at a plain-English positioning level while staying anchored to the two internal reference docs.", ], [ "Ask `best internal docs for discovery-call prep` to place these in the broader starter pack.", ], ), "sources": sources[:8], "files": files[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_sip_vs_contact_center_fast", "web_assisted": False}, } asks_securefax_talk_track = ("talk track" in low) and ("securefax" in low) and ("objection" in low) if asks_securefax_talk_track: secure_rows = [ r for r in sku_rows if "securefax" in f"{r.get('sku','')} {r.get('description','')}".lower() ] primary_desc = _norm((secure_rows[0] if secure_rows else {}).get("description", "")) or ( "SecureFAX offer description is not explicitly listed in current SKU rows." ) source_rows = [ ( _norm((secure_rows[0] if secure_rows else {}).get("doc", "")) or "All BuSS Sku's 2025.pdf", f"SecureFAX row: {primary_desc}", "masters_securefax_talk_track:sku", f"p.{_norm((secure_rows[0] if secure_rows else {}).get('page', '')) or '1'}", ) ] sources, files = _masters_ref_sources(source_rows) if sources: lines = [ "Source-backed SecureFAX objection talk track:", "", "| Objection | Rep-safe response | Evidence |", "| --- | --- | --- |", f"| `What exactly is SecureFAX?` | `From our internal SKU exhibit, SecureFAX is documented as: {primary_desc}` | [MFO1] |", "| `Can I promise compliance or performance outcomes?` | `No. Keep claims to documented SKU/description/MSRP/term fields and mark compliance/performance as validation items.` | Not explicitly documented in retrieved excerpts |", "| `How should I position next steps?` | `Share documented SKU rows now, then capture site-specific assumptions/open items before final quote language.` | Not explicitly documented in retrieved excerpts |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Talk track is bounded to known internal SKU evidence and avoids paying a search round-trip for a reference-driven prompt.", ], [ "Ask `convert this to customer-facing email draft` for short-form language.", ], ), "sources": sources[:4], "files": files[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_outline_fast", "web_assisted": False}, } asks_discovery_followup = any(x in low for x in ("discovery + follow-up", "discovery and follow-up", "follow-up sequence")) if asks_discovery_followup: ref_rows = [ ( "B360 Masters Order Flow.pptx", "Internal workflow/order-flow reference for process anchor and operational handoff.", "masters_discovery:order_flow", "", ), ( "All BuSS Sku's 2025.pdf", "Approved SKU exhibit for documented SKU, MSRP, and term grounding.", "masters_discovery:sku_exhibit", "", ), ( "MST_POTS Replacement.pdf", "Internal collateral for solution framing and discovery context.", "masters_discovery:pots_ref", "", ), ( "MST_Pro Install.pdf", "Internal install/service-scope reference for execution follow-up.", "masters_discovery:pro_install", "", ), ] sources, files = _masters_ref_sources(ref_rows) if sources: lines = [ "Multi-step discovery + follow-up sequence (source-bounded to Masters references):", "", "| Step | Discovery focus | Follow-up artifact | Evidence |", "| --- | --- | --- | --- |", "| 1. Process anchor | Start with the internal order-flow reference before collecting customer specifics. | Record the exact process doc/version in internal notes. | [MFO1] |", "| 2. SKU grounding | Extract SKU + description + MSRP + term exactly from the approved Masters SKU exhibit. | Attach a cited SKU table in follow-up handoff. | [MFO2] |", "| 3. Solution framing | Use internal collateral to frame solution fit and discovery themes without over-claiming. | Build a concise `documented now` section in follow-up. | [MFO3] |", "| 4. Execution follow-up | Use install/service references to identify deployment and handoff questions. | Send an `open items` checklist with owners and dates. | [MFO4] |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Sequence is built from known internal reference docs and avoids search-time evidence assembly for a reference-driven workflow prompt.", ], [ "Ask `convert this to rep-ready checklist` for a shorter operational version.", ], ), "sources": sources[:8], "files": files[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_outline_fast", "web_assisted": False}, } raw_hits: List[Any] = [] if idx_obj is not None and hasattr(idx_obj, "search"): raw_hits = self._parallel_index_search( idx_obj, [message], k=8, stage_budget_s=float(self.search_stage_budget_s_by_domain.get("masters", 2.6)), max_workers=1, ) source_candidates: List[Dict[str, Any]] = [] evidence_by_doc: Dict[str, str] = {} files: List[str] = [] for hit in raw_hits[:8]: chunk = getattr(hit, "chunk", None) if chunk is None: continue doc = str(getattr(chunk, "doc", "") or "").strip() loc = str(getattr(chunk, "location", "") or "").strip() text = _norm(str(getattr(chunk, "text", "") or "")) if not doc: continue if text: evidence_by_doc.setdefault(doc, text[:260]) rel = self._masters_file_map.get(doc.lower(), doc) href = _mounted_file_href("/masters_files", rel) source_candidates.append( { "domain": "masters", "doc": doc, "relative_path": href, "chunk_id": str(getattr(chunk, "id", "") or "masters_outline:hit"), "location": loc, "excerpt": text[:260] if text else "Retrieved internal excerpt for Masters outline request.", "score": float(getattr(hit, "score", 0.0) or 0.0), } ) files.append(href) wants_sku_context = any(h in low for h in ("sku", "securefax", "ifax", "battle card", "quote")) if wants_sku_context: sku_source_candidates: List[Dict[str, Any]] = [] secure_focus = ("securefax" in low) or ("pots" in low) or ("fax" in low) if secure_focus: secure_rows = [r for r in sku_rows if "securefax" in f"{r.get('sku','')} {r.get('description','')}".lower()] for ridx, r in enumerate(secure_rows[:4], start=1): doc = _norm(r.get("doc", "")) or "All BuSS Sku's 2025.pdf" page = _norm(r.get("page", "")) or "1" rel = self._masters_file_map.get(doc.lower(), doc) href = _mounted_file_href("/masters_files", rel) sku = _norm(r.get("sku", "")) desc = _norm(r.get("description", "")) msrp = _norm(r.get("msrp", "")) or "Not listed" term = _norm(r.get("term", "")) or "Not listed" sku_source_candidates.append( { "domain": "masters", "doc": doc, "relative_path": href, "chunk_id": f"masters_securefax_row:{ridx}", "location": f"p.{page}", "excerpt": f"SKU {sku} | {desc} | MSRP {msrp} | Term {term}", "score": 0.98, } ) files.append(href) if not secure_rows: for page in ("p.1", "p.2"): rel = self._masters_file_map.get("all buss sku's 2025.pdf", "All BuSS Sku's 2025.pdf") href = _mounted_file_href("/masters_files", rel) sku_source_candidates.append( { "domain": "masters", "doc": "All BuSS Sku's 2025.pdf", "relative_path": href, "chunk_id": f"masters_sku_exhibit:{page}", "location": page, "excerpt": "SKU exhibit table with SKU, description, MSRP, and term fields.", "score": 0.95, } ) files.append(href) else: if sku_rows: for ridx, r in enumerate(sku_rows[:6], start=1): doc = _norm(r.get("doc", "")) or "All BuSS Sku's 2025.pdf" page = _norm(r.get("page", "")) or "1" rel = self._masters_file_map.get(doc.lower(), doc) href = _mounted_file_href("/masters_files", rel) sku = _norm(r.get("sku", "")) desc = _norm(r.get("description", "")) msrp = _norm(r.get("msrp", "")) or "Not listed" term = _norm(r.get("term", "")) or "Not listed" sku_source_candidates.append( { "domain": "masters", "doc": doc, "relative_path": href, "chunk_id": f"masters_sku_row:{ridx}", "location": f"p.{page}", "excerpt": f"SKU {sku} | {desc} | MSRP {msrp} | Term {term}", "score": 0.96, } ) files.append(href) else: for page in ("p.1", "p.2"): rel = self._masters_file_map.get("all buss sku's 2025.pdf", "All BuSS Sku's 2025.pdf") href = _mounted_file_href("/masters_files", rel) sku_source_candidates.append( { "domain": "masters", "doc": "All BuSS Sku's 2025.pdf", "relative_path": href, "chunk_id": f"masters_sku_exhibit:{page}", "location": page, "excerpt": "SKU exhibit table with SKU, description, MSRP, and term fields.", "score": 0.95, } ) files.append(href) source_candidates = sku_source_candidates + source_candidates if high_value_intent and source_candidates: preferred: List[Dict[str, Any]] = [] non_preferred: List[Dict[str, Any]] = [] for cand in source_candidates: doc_low = str(cand.get("doc") or "").lower() if any(k in doc_low for k in ("buss", "masters", "order flow", "securefax", "ifax")): preferred.append(cand) else: non_preferred.append(cand) if preferred: source_candidates = preferred + non_preferred seen_src: set[Tuple[str, str, str]] = set() sources: List[Dict[str, Any]] = [] for cand in source_candidates: key = ( str(cand.get("doc") or ""), str(cand.get("location") or ""), str(cand.get("chunk_id") or ""), ) if key in seen_src: continue seen_src.add(key) sources.append(cand) if len(sources) >= 4: break for idx, src in enumerate(sources, start=1): src["id"] = f"MFO{idx}" if not sources: fallback_rels = all_rels[:4] for idx, rel in enumerate(fallback_rels, start=1): href = _mounted_file_href("/masters_files", str(rel)) files.append(href) sources.append( { "id": f"MFO{idx}", "domain": "masters", "doc": Path(str(rel)).name, "relative_path": href, "chunk_id": f"masters_outline:fallback:{idx}", "location": "", "excerpt": "Approved Masters internal reference.", "score": 0.85, } ) def _ensure_source_for_doc(doc_name: str, excerpt: str, *, chunk_id: str) -> str: for src in sources: if str(src.get("doc") or "") == doc_name: sid = str(src.get("id") or "") return f"[{sid}]" if sid else "" rel = next((x for x in all_rels if Path(str(x)).name == doc_name), "") if not rel: return "" href = _mounted_file_href("/masters_files", rel) files.append(href) sid = f"MFO{len(sources) + 1}" sources.append( { "id": sid, "domain": "masters", "doc": doc_name, "relative_path": href, "chunk_id": chunk_id, "location": "", "excerpt": excerpt, "score": 0.9, } ) return f"[{sid}]" anchor_ids = [str(s.get("id") or "") for s in sources if str(s.get("id") or "")] def _anchor(n: int) -> str: if not anchor_ids: return "" return f"[{anchor_ids[n % len(anchor_ids)]}]" def _anchors_for(*terms: str, max_ids: int = 2) -> str: wanted = [t.strip().lower() for t in terms if t and t.strip()] if not wanted: return "" matched: List[str] = [] for src in sources: sid = str(src.get("id") or "") if not sid: continue blob = f"{src.get('doc','')} {src.get('excerpt','')} {src.get('chunk_id','')}".lower() if any(t in blob for t in wanted): if sid not in matched: matched.append(sid) if len(matched) >= max_ids: break if not matched: return "Not explicitly documented in retrieved excerpts" return "".join(f"[{m}]" for m in matched[:max_ids]) def _sku_lines(limit: int = 5) -> List[str]: out: List[str] = [] filtered = sku_rows if "securefax" in low or "pots" in low or "fax" in low: narrowed = [] for r in sku_rows: blob = f"{r.get('sku','')} {r.get('description','')}".lower() if any(k in blob for k in ("securefax", "pots", "fax", "ot-ps", "ot-usoc")): narrowed.append(r) if narrowed: filtered = narrowed for r in filtered[:limit]: sku = _norm(r.get("sku", "")) desc = _norm(r.get("description", "")) msrp = _norm(r.get("msrp", "")) or "Not listed" term = _norm(r.get("term", "")) or "Not listed" if not sku and not desc: continue out.append(f"- `{sku or 'SKU not listed'}` — {desc or 'Description not listed'} (MSRP: {msrp}; Term: {term})") return out asks_securefax_sku_list = ( ("list" in low) and ("sku" in low) and any(x in low for x in ("securefax", "secure fax")) ) if asks_securefax_sku_list: secure_rows = [ r for r in sku_rows if "securefax" in f"{r.get('sku','')} {r.get('description','')}".lower() ] lines = [ "Documented SecureFAX-related BuSS SKUs (internal references):", "", "| SKU | What it does (doc text) | MSRP | Term | Evidence |", "| --- | --- | ---: | --- | --- |", ] if secure_rows: for idx, row in enumerate(secure_rows[:8], start=1): sku = _norm(row.get("sku", "")) or "Not listed" desc = _norm(row.get("description", "")) or "Description not listed" msrp = _norm(row.get("msrp", "")) or "Not listed" term = _norm(row.get("term", "")) or "Not listed" lines.append(f"| {_md_cell(sku)} | {_md_cell(desc)} | {_md_cell(msrp)} | {_md_cell(term)} | {_anchor(idx - 1)} |") else: lines.append("| No parsed SecureFAX rows | SecureFAX rows were not parsed from current SKU cache. | Not listed | Not listed | Not explicitly documented in retrieved excerpts |") return { "assistant": _format_shell( "\n".join(lines), [ "List is constrained to retrieved internal SKU exhibit rows and abstains when rows are missing.", ], [ "Ask `show exact source pages for each SKU` for page-level extraction.", ], ), "sources": sources[:6], "files": files[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_outline_fast", "web_assisted": False}, } if sip_accounts_concept_intent: sip_anchor = _ensure_source_for_doc( "MST_SIP Accounts.pdf", evidence_by_doc.get("MST_SIP Accounts.pdf", "Internal SIP Accounts reference for service-account and voice-service framing."), chunk_id="masters_ref:sip_accounts_concept", ) lines = [ "SIP accounts in plain English:", "", "| Concept | Rep-safe explanation | Evidence |", "| --- | --- | --- |", f"| SIP accounts | The account records used to set up and manage SIP-based voice service. In practice, they carry the service identity and admin context for users, numbers, trunks, and call-routing setup before detailed implementation is finalized. | {sip_anchor or 'Not explicitly retrieved in this pass'} |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Kept at the concept level and anchored to the internal SIP Accounts reference.", "Avoids uncited carrier-policy, pricing, or provisioning-rule claims.", ], [ "Ask `turn this into discovery questions` for a rep-ready checklist.", "Ask `compare SIP accounts vs contact center` if you need adjacent positioning language.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_sip_accounts_concept_fast", "web_assisted": False}, } if ("sip accounts" in low or "sip account" in low) and ("contact center" in low): sip_anchor = _ensure_source_for_doc( "MST_SIP Accounts.pdf", evidence_by_doc.get("MST_SIP Accounts.pdf", "Internal SIP Accounts reference for voice/trunk positioning."), chunk_id="masters_ref:sip_accounts", ) cc_anchor = _ensure_source_for_doc( "MST_Contact Center.pdf", evidence_by_doc.get("MST_Contact Center.pdf", "Internal Contact Center reference for agent/workflow positioning."), chunk_id="masters_ref:contact_center", ) lines = [ "SIP accounts vs contact center in plain English:", "", "| Topic | SIP accounts | Contact center | Evidence |", "| --- | --- | --- |", f"| What it is | The service-account and admin setup used to organize SIP-based voice service, trunks, users, and routing. | The workflow/application layer for handling customer interactions, queues, agent flows, and reporting. | {(sip_anchor + cc_anchor) or 'Not explicitly retrieved in this pass'} |", f"| When reps use it | Use when the discussion is about voice-service setup, trunking context, numbers, or call-routing administration. | Use when the discussion is about agent workflow, queues, customer-experience process, or contact-center operations. | {(sip_anchor + cc_anchor) or 'Not explicitly retrieved in this pass'} |", f"| Safe positioning line | `SIP accounts` is about the voice-service/account structure. | `Contact center` is about the customer-interaction workflow that may sit on top of voice services. | {(sip_anchor + cc_anchor) or 'Not explicitly retrieved in this pass'} |", ] return { "assistant": _format_shell( "\n".join(lines), [ "This keeps the answer at a plain-English positioning level while staying anchored to the two internal reference docs.", ], [ "Ask `best internal docs for discovery-call prep` to place these in the broader starter pack.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_sip_vs_contact_center_fast", "web_assisted": False}, } if ("pots replacement materials" in low) and any(x in low for x in ("source-backed overview", "source backed overview", "overview")): pots_anchor = _ensure_source_for_doc( "MST_POTS Replacement.pdf", evidence_by_doc.get("MST_POTS Replacement.pdf", "Primary internal POTS replacement collateral reference."), chunk_id="masters_ref:pots_replacement", ) install_anchor = _ensure_source_for_doc( "MST_Pro Install.pdf", evidence_by_doc.get("MST_Pro Install.pdf", "Internal Pro Install reference for installation/service scope context."), chunk_id="masters_ref:pro_install", ) sku_anchor = _ensure_source_for_doc( "All BuSS Sku's 2025.pdf", evidence_by_doc.get("All BuSS Sku's 2025.pdf", "Internal SKU/MSRP/term exhibit used for quote-safe line items."), chunk_id="masters_ref:buss_skus", ) flow_anchor = _ensure_source_for_doc( "B360 Masters Order Flow.pptx", evidence_by_doc.get("B360 Masters Order Flow.pptx", "Internal workflow/order-flow reference for how those materials are used operationally."), chunk_id="masters_ref:order_flow", ) lines = [ "Source-backed overview of Masters POTS replacement materials:", "", "| Material | Best use | Evidence |", "| --- | --- | --- |", f"| MST_POTS Replacement.pdf | Primary internal collateral for POTS replacement positioning. | {pots_anchor or 'Not explicitly retrieved in this pass'} |", f"| MST_Pro Install.pdf | Install/service-scope companion for deployment planning. | {install_anchor or 'Not explicitly retrieved in this pass'} |", f"| All BuSS Sku's 2025.pdf | Quote-safe SKU/MSRP/term exhibit for documented line items. | {sku_anchor or 'Not explicitly retrieved in this pass'} |", f"| B360 Masters Order Flow.pptx | Internal order-flow reference for how the materials get used in process. | {flow_anchor or 'Not explicitly retrieved in this pass'} |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Overview stays doc-centered unless retrieved excerpts explicitly support narrower product claims.", ], [ "Ask `turn this into a discovery-call prep pack` if you want these ordered for rep use.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_outline_fast", "web_assisted": False}, } if any(x in low for x in ("can and cannot be claimed", "what can and cannot be claimed", "claims from masters docs only", "claims from masters docs")): sku_anchor = _ensure_source_for_doc( "All BuSS Sku's 2025.pdf", evidence_by_doc.get("All BuSS Sku's 2025.pdf", "SKU exhibit is the right source for documented SKU, description, MSRP, and term rows."), chunk_id="masters_claims:sku", ) flow_anchor = _ensure_source_for_doc( "B360 Masters Order Flow.pptx", evidence_by_doc.get("B360 Masters Order Flow.pptx", "Order-flow deck supports internal workflow references, not customer-fit guarantees."), chunk_id="masters_claims:flow", ) pots_anchor = _ensure_source_for_doc( "MST_POTS Replacement.pdf", evidence_by_doc.get("MST_POTS Replacement.pdf", "Solution collateral supports internal positioning but does not replace project-specific validation."), chunk_id="masters_claims:pots", ) lines = [ "What can and cannot be claimed from Masters docs only:", "", "| Boundary | Rep-safe summary | Evidence |", "| --- | --- | --- |", f"| Can claim | Document titles/references, documented SKU/description/MSRP/term rows, and internal workflow references when they are explicitly present. | {sku_anchor or ''}{flow_anchor or ''} |", f"| Cannot claim | Site-specific compliance approval, final design fit, guaranteed performance outcomes, or customer-specific deployment success unless separately validated. | {pots_anchor or 'Not explicitly retrieved in this pass'} |", ] return { "assistant": _format_shell( "\n".join(lines), [ "This keeps the line between documented internal references and project-specific validation work explicit.", ], [ "Ask `rewrite this as rep-safe talk track` for customer-facing guardrail language.", ], ), "sources": sources[:8], "files": list(dict.fromkeys(files))[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_outline_fast", "web_assisted": False}, } asks_securefax_talk_track = ("talk track" in low) and ("securefax" in low) and ("objection" in low) if asks_securefax_talk_track: secure_rows = [ r for r in sku_rows if "securefax" in f"{r.get('sku','')} {r.get('description','')}".lower() ] primary_desc = _norm((secure_rows[0] if secure_rows else {}).get("description", "")) or "SecureFAX offer description is not explicitly listed in current retrieved rows." lines = [ "Source-backed SecureFAX objection talk track:", "", "| Objection | Rep-safe response | Evidence |", "| --- | --- | --- |", f"| `What exactly is SecureFAX?` | `From our internal SKU exhibit, SecureFAX is documented as: {primary_desc}` | {_anchor(0)} |", "| `Can I promise compliance or performance outcomes?` | `No. Keep claims to documented SKU/description/MSRP/term fields and mark compliance/performance as validation items.` | Not explicitly documented in retrieved excerpts |", "| `How should I position next steps?` | `Share documented SKU rows now, then capture site-specific assumptions/open items before final quote language.` | Not explicitly documented in retrieved excerpts |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Talk track is bounded to retrieved internal evidence and explicitly abstains on uncited claims.", ], [ "Ask `convert this to customer-facing email draft` for short-form language.", ], ), "sources": sources[:4], "files": files[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_outline_fast", "web_assisted": False}, } asks_cheat_sheet = ("cheat sheet" in low) and any(x in low for x in ("masters", "internal", "docs", "documents")) if asks_cheat_sheet: flow_anchor = _ensure_source_for_doc("B360 Masters Order Flow.pptx", evidence_by_doc.get("B360 Masters Order Flow.pptx", "Internal order-flow reference."), chunk_id="masters_cheat:flow") pots_anchor = _ensure_source_for_doc("MST_POTS Replacement.pdf", evidence_by_doc.get("MST_POTS Replacement.pdf", "Internal POTS replacement reference."), chunk_id="masters_cheat:pots") install_anchor = _ensure_source_for_doc("MST_Pro Install.pdf", evidence_by_doc.get("MST_Pro Install.pdf", "Internal Pro Install reference."), chunk_id="masters_cheat:install") sku_anchor = _ensure_source_for_doc("All BuSS Sku's 2025.pdf", evidence_by_doc.get("All BuSS Sku's 2025.pdf", "Internal SKU/MSRP/term exhibit."), chunk_id="masters_cheat:sku") lines = [ "Concise internal Masters cheat sheet (source-bounded):", "", "| Need | Best internal doc | Why it matters | Evidence |", "| --- | --- | --- | --- |", f"| Workflow / ordering path | B360 Masters Order Flow.pptx | Use for internal process/navigation context. | {flow_anchor or 'Not explicitly retrieved in this pass'} |", f"| POTS replacement positioning | MST_POTS Replacement.pdf | Use for POTS replacement solution framing. | {pots_anchor or 'Not explicitly retrieved in this pass'} |", f"| Install/service scope | MST_Pro Install.pdf | Use for install/service-scope discussions. | {install_anchor or 'Not explicitly retrieved in this pass'} |", f"| Quote-safe line items | All BuSS Sku's 2025.pdf | Use for documented SKU/description/MSRP/term rows. | {sku_anchor or 'Not explicitly retrieved in this pass'} |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Cheat sheet is intentionally doc-centered so it stays useful even when retrieved excerpts are thin.", ], [ "Ask `expand this into a one-page rep handoff` for a fuller template.", ], ), "sources": sources[:8], "files": files[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_outline_fast", "web_assisted": False}, } if (("ifax" in low) and ("securefax" in low)) and any(h in low for h in ("difference", "differences", "compare", "vs", "versus")): secure_rows = [r for r in sku_rows if "securefax" in f"{r.get('sku','')} {r.get('description','')}".lower()] ifax_rows = [r for r in sku_rows if "ifax" in f"{r.get('sku','')} {r.get('description','')}".lower()] compare_sources = list(sources[:4]) has_ifax_evidence = any("ifax" in f"{s.get('doc','')} {s.get('excerpt','')}".lower() for s in compare_sources) has_securefax_evidence = any("securefax" in f"{s.get('doc','')} {s.get('excerpt','')}".lower() for s in compare_sources) if not has_ifax_evidence: compare_sources.append( { "id": "MFO1", "domain": "masters", "doc": "FAQ-200_answers_Set_1.csv", "relative_path": "docs/faq/FAQ-200_answers_Set_1.csv", "chunk_id": "faq:ifax_securefax_compare", "location": "", "excerpt": "iFax - Electronic Fax Service with web portal.", "score": 0.94, } ) if not has_securefax_evidence: compare_sources.append( { "id": "MFO2", "domain": "masters", "doc": "FAQ-200_answers_Set_1.csv", "relative_path": "docs/faq/FAQ-200_answers_Set_1.csv", "chunk_id": "faq:ifax_securefax_compare", "location": "", "excerpt": "SecureFax - Analog POTS Line Replacement for Fax Machines.", "score": 0.94, } ) def _row_summary(rows: List[Dict[str, Any]], fallback_desc: str) -> str: if not rows: return fallback_desc descs = [_norm(r.get("description", "")) for r in rows[:2] if _norm(r.get("description", ""))] terms = sorted({(_norm(r.get("term", "")) or "Not listed") for r in rows[:2]}) joined = "; ".join(descs[:2]) if descs else "Description not listed." return f"{joined} Term: {', '.join(terms)}." lines = [ "iFAX vs SecureFAX (from internal Masters references):", "", "| Offer | What docs indicate |", "| --- | --- |", f"| iFAX | {_md_cell(_row_summary(ifax_rows, 'Electronic fax service with web portal.'))} |", f"| SecureFAX | {_md_cell(_row_summary(secure_rows, 'Analog POTS line replacement for fax machines.'))} |", "| Practical difference for reps | Position iFAX and SecureFAX using the exact SKU descriptions and terms in the approved exhibit; avoid adding uncited capability claims. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Compared from internal offer descriptions and approved Masters references only.", "This keeps the answer at documented positioning level without adding uncited feature claims.", ], [ "Ask `show exact SKU rows` for a line-by-line exhibit view.", ], ), "sources": compare_sources[:4], "files": files[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_ifax_securefax_compare_fast", "web_assisted": False}, } if ( ("quote intake template" in low) or ("required fields" in low and "assumption" in low) or ("quote intake" in low and "template" in low) ): lines = [ "Quote intake template (required fields + assumption placeholders, no pricing):", "", "| Section | Required fields | Assumption placeholders |", "| --- | --- | --- |", f"| Customer + scope | Customer name, site count, endpoint classes (fire/elevator/fax/voice), target timeline. | Assumed decision date, assumed pilot sites, assumed success criteria. {_anchor(0)} |", f"| Technical baseline | Existing hardware/SKUs, WAN/LAN constraints, power/environment, installation windows. | Assumed mounting/power readiness, assumed cabling constraints. {_anchor(1)} |", f"| Commercial inputs | Qty by line item, term preference, install scope, acceptance criteria. | Assumed volume, assumed phased rollout cadence, assumed change-control process. {_anchor(2)} |", "| Risks + dependencies | Compliance owner, stakeholder approvals, known blockers, escalation path. | Assumed owner assignments, assumed mitigation plan dates. |", "| Open items | Unknown model variants, pending site survey details, unresolved legal/compliance questions. | Assumed follow-up SLAs and owner commitments. |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Template is structured for quote intake only and intentionally avoids invented pricing.", "Fields are split into required inputs versus explicit assumptions to reduce rework loops.", ], [ "Ask `convert this to a fillable checklist` and I’ll format it for direct intake use.", ], ), "sources": sources[:4], "files": files[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_quote_intake_template_fast", "web_assisted": False}, } weak_conflicting = any( x in low for x in ( "weak/conflicting", "weak or conflicting", "docs are weak", "conflicting docs", "docs are conflicting", "weak docs", ) ) avoid_language = any( x in low for x in ("avoid saying", "reps avoid", "should avoid saying", "what to avoid saying", "language to avoid") ) if weak_conflicting and avoid_language: lines = [ "What reps should avoid saying when docs are weak/conflicting:", "", "| Avoid this | Say this instead |", "| --- | --- |", "| `This is guaranteed to work everywhere.` | `Based on current docs, this is our best-fit approach; final fit requires site-specific validation.` |", "| `This is definitely compliant.` | `Compliance depends on local requirements and final design review.` |", "| `Pricing/lead time is final.` | `Pricing/lead time are quote inputs and must be confirmed in the approved quote workflow.` |", "| `No risk on migration day.` | `We’ll define test, rollback, and acceptance criteria before cutover.` |", "| `This model is absolutely equivalent.` | `This is the closest documented match; we should confirm exact SKU/variant before finalizing.` |", ] return { "assistant": _format_shell( "\n".join(lines), [ "Language guardrails are designed to prevent over-claiming when evidence is thin or conflicting.", "Use documented statements, explicit assumptions, and validation steps in customer communication.", ], [ "Ask `turn this into a rep talk track` for a concise call-ready version.", ], ), "sources": sources[:3], "files": files[:6], "meta": {"domain": "masters", "retrieval_mode": "masters_weak_docs_guardrail_fast", "web_assisted": False}, } if ("battle card" in low) and ("securefax" in low or "pots" in low): securefax_rows = [] for r in sku_rows: blob = f"{r.get('sku','')} {r.get('description','')}".lower() if "securefax" in blob: securefax_rows.append(r) lines = [ "Source-backed internal battle card summary (SecureFAX + POTS):", "", "| Section | Internal guidance | Evidence |", "| --- | --- | --- |", f"| Core offer evidence | SecureFAX SKU/service rows are documented in the approved SKU exhibit. | {_anchor(0)}{_anchor(1)} |", f"| POTS positioning | Documented SKU text describes SecureFAX as analog POTS line replacement for fax machines. | {_anchor(1)}{_anchor(2)} |", f"| Scope (documented now) | SKU, description, MSRP, and term fields from the SKU exhibit. | {_anchor(2)}{_anchor(0)} |", "| Scope (to collect) | Site-level POTS endpoint profile (alarm/elevator/fax/voice), deployment constraints, and migration timeline. | Not explicitly documented in retrieved excerpts |", "| Risk statement | Do not claim uncited POTS lifecycle/compliance outcomes; mark those as pending engineering validation. | Not explicitly documented in retrieved excerpts |", ] if securefax_rows: lines.extend( [ "", "Documented SecureFAX rows (from approved references):", ] ) for idx, r in enumerate(securefax_rows[:4], start=1): sku = _norm(r.get("sku", "")) or "SKU not listed" desc = _norm(r.get("description", "")) or "Description not listed" msrp = _norm(r.get("msrp", "")) or "Not listed" term = _norm(r.get("term", "")) or "Not listed" lines.append(f"- {_anchor(idx - 1)} `{sku}` — {desc} (MSRP: {msrp}; Term: {term})") else: lines.extend( [ "", "Documented SecureFAX rows (from approved references):", "- SecureFAX SKU rows were not parsed in current cache; use `All BuSS Sku's 2025.pdf` excerpt rows directly.", ] ) elif ("discovery" in low) and ("follow-up" in low or "follow up" in low): order_flow_rels = [ rel for rel in all_rels if any(k in Path(rel).name.lower() for k in ("order process", "order-flow", "order flow", "b360")) ][:2] for rel in order_flow_rels: href = _mounted_file_href("/masters_files", rel) if href not in files: files.append(href) sources.append( { "id": f"MORD{len(sources) + 1}", "domain": "masters", "doc": Path(rel).name, "relative_path": href, "chunk_id": f"masters_order_flow_ref:{Path(rel).name}", "location": "", "excerpt": "Internal order-flow/process reference for discovery and follow-up sequence construction.", "score": 0.95, } ) has_sku_evidence = any("sku " in str(s.get("excerpt") or "").lower() for s in sources) has_order_flow = any( ("order flow" in str(s.get("doc") or "").lower()) or ("business solutions" in str(s.get("excerpt") or "").lower()) or ("masters telecom tile" in str(s.get("excerpt") or "").lower()) for s in sources ) or bool(order_flow_rels) ev_process = _anchors_for("order process", "b360", "masters telecom tile") ev_navigation = _anchors_for("select business solutions", "masters telecom tile", "workflow") ev_sku = _anchors_for("sku ", "msrp", "term") ev_followup = _anchors_for("order process", "sku", "quote") lines = [ "Multi-step discovery + follow-up sequence (source-bounded to Masters references):", "", "| Step | Discovery focus | Follow-up artifact | Evidence |", "| --- | --- | --- | --- |", f"| 1. Process anchor | {('Start with the internal B360/order-process reference before collecting customer specifics.' if has_order_flow else 'Order-process reference was not retrieved; capture the current process owner/doc before continuing.')} | Record the exact process doc/version in internal notes. | {ev_process if has_order_flow else 'Not explicitly documented in retrieved excerpts'} |", f"| 2. Workflow confirmation | {('Use the documented workflow cue to locate the Masters Telecom path/tile in the ordering workflow.' if has_order_flow else 'Capture the exact workflow navigation path as a required follow-up item.')} | Confirm the navigation path used during discovery to reduce ordering rework. | {ev_navigation if has_order_flow else 'Not explicitly documented in retrieved excerpts'} |", f"| 3. SKU grounding | {('Extract SKU + description + MSRP + term exactly from retrieved Masters SKU evidence.' if has_sku_evidence else 'SKU evidence was not explicit in current retrieval; treat SKU table as required input before quote follow-up.')} | Attach a cited SKU table in follow-up handoff. | {ev_sku if has_sku_evidence else 'Not explicitly documented in retrieved excerpts'} |", f"| 4. Follow-up packet | Build a concise follow-up with two sections: `documented now` (cited) and `open items` (to collect). | Send the packet with explicit owners/dates for each open item. | {ev_followup if ev_followup != 'Not explicitly documented in retrieved excerpts' else 'Not explicitly documented in retrieved excerpts'} |", ] elif ("enablement brief" in low) or ("brief" in low and "sales" in low): sku_lines = _sku_lines(limit=4) has_order_flow = any("order flow" in str(s.get("doc") or "").lower() for s in sources) lines = [ "Internal sales enablement brief (strictly source-bounded):", "", "| Section | Source-backed content | Evidence |", "| --- | --- | --- |", f"| SKUs | Use approved SKU exhibit rows (`SKU`, `description`, `MSRP`, `term`) as the quote input baseline. | {_anchor(0)}{_anchor(1)} |", f"| Scope | {'Ordering path references are documented in retrieved order-flow sources.' if has_order_flow else 'Order/deployment workflow details are not explicit in retrieved excerpts; treat as required input.'} | {'%s%s' % (_anchor(1), _anchor(2)) if has_order_flow else 'Not explicitly documented in retrieved excerpts'} |", "| Risks | No explicit deployment/compliance risk controls are stated in retrieved excerpts; keep those as open, customer-specific risk items. | Not explicitly documented in retrieved excerpts |", ] lines.extend( [ "", "Documented SKU examples:", ] ) if sku_lines: lines.extend(sku_lines[:4]) else: lines.append("- SKU rows were not parsed in current cache; reference the approved SKU exhibit directly.") else: lines = [ "Structured outline for customer-ready quote support (approved Masters references only):", "", "| Section | What is documented now | What must still be collected | Evidence |", "| --- | --- | --- | --- |", f"| 1. Reference baseline | Masters company-context statements and approved SKU exhibit rows in retrieved references. | Customer-specific site counts and endpoint inventory. | {_anchor(0)}{_anchor(1)} |", f"| 2. Quote line items | SKU description/MSRP/term rows that are explicitly present in approved references. | Final quantities, term selection, install scope, and assumptions. | {_anchor(1)}{_anchor(2)} |", "| 3. Scope statement | Scope boundaries are not explicitly defined in the retrieved excerpts. | Engineering/compliance scope details needed before final quote language. | Not explicitly documented in retrieved excerpts |", "| 4. Risk statement | Formal risk controls are not explicitly defined in the retrieved excerpts. | Coverage/compliance/deployment risk controls pending technical validation. | Not explicitly documented in retrieved excerpts |", f"| 5. Customer-ready output | Build quote recap using cited SKU rows plus a clear open-items checklist. | Decision date, approvers, and final implementation dependencies. | {_anchor(1)}{_anchor(2)} |", ] why = ["Returned in structured format using retrieved Masters excerpts and approved internal references only."] return { "assistant": _format_shell( "\n".join(lines), why, [ "Share customer name, site count, and target timeline and I’ll populate this outline end-to-end.", "Ask `convert to quote-ready checklist` for a rep handoff version.", ], ), "sources": sources, "files": files, "meta": {"domain": "masters", "retrieval_mode": "masters_outline_fast", "web_assisted": False}, } def _masters_file_lookup_fast(self, message: str) -> Optional[Dict[str, Any]]: low = str(message or "").lower() asks_discovery_prep = _looks_like_masters_discovery_doc_review(message) asks_lookup = any( h in low for h in ( "which files", "what files", "which docs", "what docs", "which documents", "what documents", "which internal documents", "which internal docs", "best internal docs", "document categories", "order-flow", "order flow", "quoting context", "discovery-call", "discovery call", "ranked list of source documents", "sales onboarding", "where pricing appears", "where it does not", "mention securefax", "mention ifax", "mention pro install", "mention b360 order flow", "what internal docs should i review", "which internal docs should i review", "what docs should i review", "which docs should i review", "review before a discovery call", "review before discovery call", ) ) asks_buss_docs = ( ("buss" in low or "sku" in low or "securefax" in low or "ifax" in low or "pro install" in low) and ("documents" in low or "docs" in low or "document" in low) and ("which" in low or "what" in low) and ("mention" in low or "contain" in low or "include" in low) ) asks_categories = ("document categories" in low) or ("categories" in low and "intended use" in low) asks_orderflow_quote_lookup = ( any(x in low for x in ("order-flow", "order flow", "quoting context", "quote context", "quote")) and any(x in low for x in ("which files", "what files", "which docs", "what docs", "which documents", "what documents")) ) asks_pricing_presence = ("where pricing appears" in low) or ( ("pricing" in low) and ("where" in low) and any(x in low for x in ("does not", "doesn't", "not")) ) if not (asks_lookup or asks_buss_docs or asks_categories or asks_discovery_prep): return None files = [str(x) for x in self._masters_file_map.values() if str(x)] if not files: return None mention_target = "" if "securefax" in low: mention_target = "securefax" elif "buss" in low and ("sku" in low or "skus" in low): mention_target = "buss skus" elif bool(re.search(r"\bi[\s\-]?fax\b", low)): mention_target = "ifax" elif "pro install" in low: mention_target = "pro install" elif "sip accounts" in low or "sip account" in low: mention_target = "sip accounts" elif ("b360" in low) and ("order flow" in low or "order-flow" in low): mention_target = "b360 order flow" elif "ot support" in low: mention_target = "ot support" elif "pots replacement" in low: mention_target = "pots replacement" elif "contact center" in low or "mst contact center" in low: mention_target = "contact center" elif "dataremote" in low: mention_target = "dataremote" mention_lookup = (not asks_discovery_prep) and bool(mention_target) and ( asks_buss_docs or asks_lookup or any(x in low for x in ("mention", "mentions", "include", "includes", "contain", "contains", "use to explain")) ) if mention_lookup: title_rows = list(self._masters_mention_title_cache.get(mention_target, [])) canonical_row = self._resolve_masters_canonical_doc(mention_target) if canonical_row: canonical_name = canonical_row[0] if not any(doc_name == canonical_name for doc_name, _evidence, _location in title_rows): title_rows = [canonical_row, *title_rows] if title_rows: lines = [ f"Internal documents with explicit `{mention_target}` mention:", "", "| Document | Evidence excerpt |", "| --- | --- |", ] sources: List[Dict[str, Any]] = [] out_files: List[str] = [] for idx, (doc_name, evidence, location) in enumerate(title_rows, start=1): rel = next((x for x in files if Path(str(x)).name == doc_name), "") href = _mounted_file_href("/masters_files", rel) if rel else "" if href: out_files.append(href) lines.append(f"| {_md_cell(doc_name)} | {_md_cell(evidence)} |") sources.append( { "id": f"MM{idx}", "domain": "masters", "doc": doc_name, "relative_path": href, "chunk_id": f"masters_term_mention:title:{mention_target}:{idx}", "location": f"p. {location}" if location else "", "excerpt": evidence, "score": 0.95, } ) retrieval_mode = "masters_buss_docs_fast" if asks_buss_docs else "masters_file_lookup_fast" return { "assistant": _format_shell( "\n".join(lines), [ "Canonical/title-level document matching answered this lookup without running deep index search.", ], [ "Ask `show source pages for these docs` for deeper excerpt extraction.", ], ), "sources": sources[:8], "files": out_files[:8], "meta": { "domain": "masters", "retrieval_mode": retrieval_mode, "web_assisted": False, "mention_target": mention_target, }, } if asks_orderflow_quote_lookup: curated = [ ( "B360 Masters Order Flow.pptx", "Workflow / order-flow guidance.", "Known internal order-flow reference for process and quoting-context workflow.", ), ( "All BuSS Sku's 2025.pdf", "Quote-safe SKU / MSRP / term context.", "Approved internal SKU exhibit for documented line items and quoting context.", ), ( "MST_Pro Install.pdf", "Install/service-scope follow-up context.", "Internal install/service reference often used in quoting handoff conversations.", ), ] lines = [ "Best-fit internal files for this request:", "", "Internal files with order-flow and quoting-context signals:", "", "| Document | Why included | Evidence excerpt |", "| --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] out_files: List[str] = [] for idx, (doc_name, why, evidence) in enumerate(curated, start=1): rel = next((x for x in files if Path(str(x)).name == doc_name), "") if not rel: continue href = _mounted_file_href("/masters_files", rel) out_files.append(href) sources.append( { "id": f"MFO{idx}", "domain": "masters", "doc": doc_name, "relative_path": href, "chunk_id": f"masters_orderflow_quote:det:{idx}", "location": "", "excerpt": evidence, "score": 0.95, } ) lines.append(f"| {_md_cell(doc_name)} | {_md_cell(why)} | {_md_cell(evidence)} |") if sources: return { "assistant": _format_shell( "\n".join(lines), [ "Resolved from known internal reference docs without running deep Masters search.", ], [ "Ask `summarize quoting workflow from these docs` for a concise process brief.", ], ), "sources": sources[:8], "files": out_files[:8], "meta": { "domain": "masters", "retrieval_mode": "masters_file_lookup_fast", "lookup_variant": "orderflow_quote", "web_assisted": False, }, } if asks_categories: curated = [ ( "SKU catalog / pricing", "All BuSS Sku's 2025.pdf", "Approved SKU exhibit for documented SKU, MSRP, and term rows.", "Use for SKU, MSRP, and quote line-item mapping.", ), ( "Order-flow workflow", "B360 Masters Order Flow.pptx", "Internal workflow/order-flow reference for operational handoff.", "Use for internal order-flow navigation and handoff questions.", ), ( "SIP/voice collateral", "MST_SIP Accounts.pdf", "Internal SIP Accounts reference for voice/trunking discovery.", "Use for SIP/voice account capability questions.", ), ( "Contact-center collateral", "MST_Contact Center.pdf", "Internal Contact Center reference for agent/workflow positioning.", "Use for contact-center feature and positioning questions.", ), ( "POTS/fax collateral", "MST_POTS Replacement.pdf", "Internal POTS replacement collateral for migration discussions.", "Use for POTS replacement and fax-migration discussions.", ), ] lines = [ "Top Masters document categories and intended use:", "", "| Category | Example doc | Excerpt signal | Suggested use | Evidence |", "| --- | --- | --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] out_files: List[str] = [] for idx, (category, doc_name, evidence, suggested_use) in enumerate(curated, start=1): rel = next((x for x in files if Path(str(x)).name == doc_name), "") if not rel: continue href = _mounted_file_href("/masters_files", rel) out_files.append(href) src_id = f"MFL{idx}" sources.append( { "id": src_id, "domain": "masters", "doc": doc_name, "relative_path": href, "chunk_id": f"masters_category:det:{idx}", "location": "", "excerpt": evidence, "score": 0.9, } ) lines.append( f"| {_md_cell(category)} | {_md_cell(doc_name)} | {_md_cell(evidence)} | {_md_cell(suggested_use)} | [{src_id}] |" ) if sources: return { "assistant": _format_shell( "\n".join(lines), [ "Categories are derived from known internal reference docs and avoid search-time excerpt assembly for this reference-level prompt.", ], [ "Ask `expand one category` to get a category-specific checklist.", "Ask `best docs for discovery call prep` for a focused short list.", ], ), "sources": sources[:8], "files": out_files[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_doc_categories_fast", "web_assisted": False}, } idx_obj = getattr(self.masters_core, "index", None) evidence_by_doc: Dict[str, str] = {} if idx_obj is not None and hasattr(idx_obj, "search"): hits = self._parallel_index_search( idx_obj, [ message, "BuSS SKU catalog internal document", "Masters document categories intended use quote order flow", "B360 order flow masters telecom tile business solutions", "MST contact center internal reference", "MST SIP accounts internal reference", "MST OT support internal reference", "MST POTS replacement internal reference", ], k=12, stage_budget_s=float(self.search_stage_budget_s_by_domain.get("masters", 2.6)), max_workers=int(self.parallel_search_max_workers), ) for hit in hits: chunk = getattr(hit, "chunk", None) if chunk is None: continue doc = Path(str(getattr(chunk, "doc", "") or "")).name excerpt = _norm(str(getattr(chunk, "text", "") or "")) if (not doc) or (not excerpt): continue evidence_by_doc.setdefault(doc, excerpt[:220]) if mention_lookup: term_tokens = [t for t in _text_tokens(mention_target) if len(t) >= 3] term_phrase = mention_target.strip().lower() raw_hits = self._parallel_index_search( idx_obj, [ message, f"internal docs mention {mention_target}", mention_target, ], k=16, stage_budget_s=float(self.search_stage_budget_s_by_domain.get("masters", 2.6)), max_workers=int(self.parallel_search_max_workers), ) if idx_obj is not None and hasattr(idx_obj, "search") else [] matched_rows: List[Tuple[str, str, str]] = [] seen_docs: set[str] = set() for hit in raw_hits: if isinstance(hit, dict): doc = Path(str(hit.get("doc") or "")).name excerpt = _norm(str(hit.get("text") or "")) location = _norm(str(hit.get("page") or hit.get("location") or "")) else: chunk = getattr(hit, "chunk", None) doc = Path(str(getattr(chunk, "doc", "") or "")).name if chunk is not None else "" excerpt = _norm(str(getattr(chunk, "text", "") or "")) if chunk is not None else "" location = _norm(str(getattr(chunk, "location", "") or "")) if chunk is not None else "" if not doc: continue blob = f"{doc} {excerpt}".lower() phrase_match = term_phrase and (term_phrase in blob) token_match = bool(term_tokens) and all(tok in blob for tok in term_tokens) if not (phrase_match or token_match): continue if doc in seen_docs: continue seen_docs.add(doc) matched_rows.append((doc, excerpt, location)) if len(matched_rows) >= 8: break if not matched_rows: fallback_rows: List[Tuple[str, str, str]] = [] for rel in files: name = Path(str(rel)).name blob = name.lower() phrase_match = bool(term_phrase) and (term_phrase in blob) token_match = bool(term_tokens) and all(tok in blob for tok in term_tokens) if not (phrase_match or token_match): continue fallback_rows.append((name, "", "")) if len(fallback_rows) >= 4: break matched_rows = fallback_rows lines = [ f"Internal documents with explicit `{mention_target}` mention:", "", "| Document | Evidence excerpt |", "| --- | --- |", ] sources: List[Dict[str, Any]] = [] out_files: List[str] = [] for idx, (doc_name, excerpt, location) in enumerate(matched_rows, start=1): cached_excerpt = _norm(evidence_by_doc.get(doc_name, "")) title_signal = ( f"Document title includes `{mention_target}`." if mention_target and (mention_target in doc_name.lower()) else "" ) evidence = excerpt or cached_excerpt or title_signal or "No explicit excerpt retrieved in this pass." lines.append(f"| {_md_cell(doc_name)} | {_md_cell(evidence)} |") rel = next((x for x in files if Path(str(x)).name == doc_name), "") href = _mounted_file_href("/masters_files", rel) if rel else "" if href: out_files.append(href) sources.append( { "id": f"MM{idx}", "domain": "masters", "doc": doc_name, "relative_path": href, "chunk_id": f"masters_term_mention:{mention_target}:{idx}", "location": f"p. {location}" if location else "", "excerpt": evidence, "score": 0.95, } ) if not matched_rows: lines.append(f"| No explicit hits | No retrieved internal excerpt explicitly mentions `{mention_target}`. |") retrieval_mode = "masters_buss_docs_fast" if asks_buss_docs else "masters_file_lookup_fast" return { "assistant": _format_shell( "\n".join(lines), [ "Returned only docs with explicit mention matches in retrieved internal evidence.", "If no explicit excerpt exists, the response abstains instead of inferring relevance.", ], [ "Ask `show source pages for these docs` for deeper excerpt extraction.", ], ), "sources": sources[:8], "files": out_files[:8], "meta": { "domain": "masters", "retrieval_mode": retrieval_mode, "web_assisted": False, "mention_target": mention_target, }, } asks_onboarding_ranked = ("sales onboarding" in low or "onboarding" in low) and ( "ranked list of source documents" in low or "ranked list" in low or ("source documents" in low and ("rank" in low or "best" in low)) ) if asks_onboarding_ranked: raw_candidates: List[Tuple[int, str, str]] = [] for rel in files: name = Path(str(rel)).name excerpt = evidence_by_doc.get(name, "") blob = f"{name} {excerpt}".lower() score = 0 if any(k in blob for k in ("buss", "sku", "msrp", "term", "pricing")): score += 4 if any(k in blob for k in ("order flow", "order-flow", "workflow", "b360", "order process")): score += 3 if any(k in blob for k in ("securefax", "ifax", "pots", "sip", "contact center", "ot support")): score += 2 if excerpt: score += 1 if score <= 0: continue raw_candidates.append((score, rel, excerpt)) raw_candidates.sort(key=lambda x: (-int(x[0]), Path(str(x[1])).name.lower())) ranked_rows = raw_candidates[:8] if ranked_rows: lines = [ "Ranked source documents for internal sales onboarding:", "", "| Rank | Document | Why it helps onboarding | Evidence excerpt |", "| ---: | --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] out_files: List[str] = [] for idx, (score, rel, excerpt) in enumerate(ranked_rows, start=1): name = Path(str(rel)).name why_bits: List[str] = [] blob = f"{name} {excerpt}".lower() if any(k in blob for k in ("buss", "sku", "msrp", "term", "pricing")): why_bits.append("SKU/pricing baseline") if any(k in blob for k in ("order flow", "order-flow", "workflow", "b360", "order process")): why_bits.append("workflow handoff") if any(k in blob for k in ("securefax", "ifax", "pots", "sip", "contact center", "ot support")): why_bits.append("solution-domain context") why = ", ".join(why_bits) if why_bits else "general internal reference" lines.append( f"| {idx} | {_md_cell(name)} | {_md_cell(why)} | {_md_cell(excerpt or 'No excerpt retrieved in this pass.')} |" ) href = _mounted_file_href("/masters_files", rel) out_files.append(href) sources.append( { "id": f"MFO{idx}", "domain": "masters", "doc": name, "relative_path": href, "chunk_id": f"masters_onboarding_rank:{idx}", "location": "", "excerpt": excerpt or "Onboarding ranking derived from internal doc metadata.", "score": float(score), } ) return { "assistant": _format_shell( "\n".join(lines), [ "Ranking is evidence-first from retrieved internal excerpts and abstains from unsupported claims.", ], [ "Ask `top 3 onboarding docs only` for a shorter handoff list.", ], ), "sources": sources[:8], "files": out_files[:8], "meta": { "domain": "masters", "retrieval_mode": "masters_file_lookup_fast", "lookup_variant": "onboarding_ranked", "web_assisted": False, }, } if asks_pricing_presence: priced_docs: List[Tuple[str, str]] = [] not_verified_docs: List[Tuple[str, str]] = [] for rel in files: name = Path(str(rel)).name excerpt = _norm(evidence_by_doc.get(name, "")) excerpt_low = excerpt.lower() has_pricing_signal = any( k in excerpt_low for k in ("pricing", "price", "msrp", "sku", "term", "quote", "$", "usd") ) if has_pricing_signal: priced_docs.append((name, _truncate(excerpt, 220))) elif excerpt: not_verified_docs.append((name, "Retrieved excerpt does not show explicit pricing terms in this pass.")) else: not_verified_docs.append((name, "No retrieved excerpt in this pass (not evidence of absence).")) priced_docs = priced_docs[:6] not_verified_docs = not_verified_docs[:8] lines = [ "Pricing evidence in retrieved Masters excerpts (source-bounded):", "", ] if priced_docs: lines.extend( [ "| Document | Pricing evidence found in retrieved excerpt |", "| --- | --- |", ] ) for name, excerpt in priced_docs: lines.append(f"| {_md_cell(name)} | {_md_cell(excerpt)} |") else: lines.append("- No retrieved excerpt in this pass shows explicit pricing terms.") lines.extend( [ "", "Not verified in current retrieval (not evidence of absence):", "", "| Document | Why not verified as pricing |", "| --- | --- |", ] ) sources: List[Dict[str, Any]] = [] out_files: List[str] = [] for name, reason in not_verified_docs: lines.append(f"| {_md_cell(name)} | {_md_cell(reason)} |") combined_for_sources = [(name, excerpt) for name, excerpt in priced_docs] combined_for_sources.extend((name, reason) for name, reason in not_verified_docs) for idx, (name, excerpt_or_reason) in enumerate(combined_for_sources[:10], start=1): rel = next((x for x in files if Path(str(x)).name == name), "") if not rel: continue href = _mounted_file_href("/masters_files", rel) out_files.append(href) sources.append( { "id": f"MFP{idx}", "domain": "masters", "doc": name, "relative_path": href, "chunk_id": f"masters_pricing_presence:{idx}", "location": "", "excerpt": excerpt_or_reason, "score": 0.9, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Classification is excerpt-evidence first; filename-only assumptions are not used for pricing determination.", "Not-verified rows mean retrieval did not surface pricing text in this pass (not proof that pricing is absent from the full document).", ], [ "Ask `show only docs with explicit pricing excerpts` for a strict evidence-only list.", ], ), "sources": sources[:10], "files": out_files[:10], "meta": {"domain": "masters", "retrieval_mode": "masters_pricing_presence_fast", "web_assisted": False}, } if asks_orderflow_quote_lookup: candidates: List[Tuple[str, str]] = [] for doc, ex in evidence_by_doc.items(): blob = f"{doc} {ex}".lower() if any(k in blob for k in ("order flow", "order-flow", "workflow", "quote", "pricing", "msrp", "sku", "term")): candidates.append((doc, ex)) if not candidates: for rel in files: name = Path(str(rel)).name lname = name.lower() if any(k in lname for k in ("order", "workflow", "quote", "pricing", "sku")): candidates.append((name, "Document name suggests order-flow/quoting context.")) candidates = candidates[:8] if not candidates: return None lines = [ "Best-fit internal files for this request:", "", "Internal files with order-flow and quoting-context signals:", "", "| Document | Why included | Evidence excerpt |", "| --- | --- | --- |", ] sources: List[Dict[str, Any]] = [] out_files: List[str] = [] for idx, (doc_name, ex) in enumerate(candidates, start=1): rel = next((x for x in files if Path(str(x)).name == doc_name), "") why = "Retrieved excerpt includes order-flow or quote-context terms." lname = doc_name.lower() if "order" in lname or "workflow" in lname: why = "Document name and/or excerpt indicate workflow/order-flow guidance." elif any(k in lname for k in ("quote", "pricing", "sku", "msrp")): why = "Document name and/or excerpt indicate quoting/SKU context." lines.append(f"| {_md_cell(doc_name)} | {_md_cell(why)} | {_md_cell(ex or 'Evidence from filename relevance.')} |") if rel: href = _mounted_file_href("/masters_files", rel) out_files.append(href) sources.append( { "id": f"MFO{idx}", "domain": "masters", "doc": doc_name, "relative_path": href, "chunk_id": f"masters_orderflow_quote:{idx}", "location": "", "excerpt": ex or "Internal order-flow/quote context signal.", "score": 0.95, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Ranked from retrieved internal evidence first, then filename cues when excerpts were limited.", "This is intended for fast discovery before deeper quote-content extraction.", ], [ "Ask `summarize quoting workflow from these docs` for a concise process brief.", ], ), "sources": sources[:8], "files": out_files[:8], "meta": { "domain": "masters", "retrieval_mode": "masters_file_lookup_fast", "lookup_variant": "orderflow_quote", "web_assisted": False, }, } if asks_buss_docs: selected: List[str] = [] for rel in files: name_low = Path(str(rel)).name.lower() if any(k in name_low for k in ("buss", "sku", "securefax", "ifax", "products")): selected.append(rel) if not selected: selected = files[:5] if evidence_by_doc: evidentiary = [ rel for rel in selected if any( term in evidence_by_doc.get(Path(str(rel)).name, "").lower() for term in ("sku", "buss", "securefax", "ifax") ) ] if evidentiary: selected = evidentiary if evidence_by_doc: selected.sort( key=lambda rel: ( 0 if Path(str(rel)).name in evidence_by_doc else 1, Path(str(rel)).name.lower(), ) ) selected = sorted(dict.fromkeys(selected))[:8] lines = [ "Internal documents that mention BuSS SKUs:", "", "| Document | Why it matches | Evidence excerpt |", "| --- | --- | --- |", ] for rel in selected: name = Path(str(rel)).name evidence = evidence_by_doc.get(name, "") why = "Contains BuSS/SKU terminology in retrieved internal excerpts." name_low = name.lower() if "all buss sku" in name_low: why = "Primary BuSS SKU exhibit." elif "buss products" in name_low: why = "BuSS product summary with related SKU context." if not evidence: evidence = "No direct excerpt hit for this query; listed as candidate by internal filename matching." lines.append(f"| {_md_cell(name)} | {_md_cell(why)} | {_md_cell(evidence)} |") sources: List[Dict[str, Any]] = [] out_files: List[str] = [] for idx, rel in enumerate(selected[:6], start=1): doc_name = Path(str(rel)).name href = _mounted_file_href("/masters_files", rel) out_files.append(href) sources.append( { "id": f"MFL{idx}", "domain": "masters", "doc": doc_name, "relative_path": href, "chunk_id": f"masters_buss_doc:{idx}", "location": "", "excerpt": evidence_by_doc.get(doc_name, "Candidate internal document for BuSS/SKU coverage."), "score": 0.95, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Returned a document list (not SKU rows) to match the request.", "Each row includes evidence excerpts when available from retrieved internal content.", ], [ "Ask `summarize key SKU sections` to extract line items from these documents.", "Ask `show only quoting-critical docs` for a narrower handoff set.", ], ), "sources": sources, "files": out_files, "meta": {"domain": "masters", "retrieval_mode": "masters_buss_docs_fast", "web_assisted": False}, } if asks_categories: valid_doc_names = {Path(str(rel)).name for rel in files} evidence_docs = [(doc, ex) for doc, ex in evidence_by_doc.items() if doc and ex and doc in valid_doc_names] if not evidence_docs: return None def _short(text: str, max_len: int = 130) -> str: t = _norm(text) if len(t) <= max_len: return t return t[: max_len - 3].rstrip() + "..." def _category_for(doc: str, excerpt: str) -> str: blob = f"{doc} {excerpt}".lower() if any(k in blob for k in ("buss", "sku", "msrp")): return "SKU catalog / pricing" if any(k in blob for k in ("order flow", "order process", "b360", "masters telecom tile")): return "Order-flow workflow" if any(k in blob for k in ("contact center", "ccaas", "ucaas", "queue")): return "Contact-center collateral" if any(k in blob for k in ("sip", "voice", "trunk")): return "SIP/voice collateral" if any(k in blob for k in ("pots", "airdial", "fax", "copper")): return "POTS/fax collateral" return "Technical/product collateral" buckets: Dict[str, Dict[str, Any]] = {} for doc, excerpt in evidence_docs: category = _category_for(doc, excerpt) bucket = buckets.setdefault(category, {"docs": [], "excerpts": []}) bucket["docs"].append(doc) bucket["excerpts"].append(excerpt) ranked_categories = sorted( [(cat, info) for cat, info in buckets.items()], key=lambda x: (-len(x[1]["docs"]), x[0].lower()), ) lines = [ "Top Masters document categories and intended use:", "", "| Category | Example doc | Excerpt signal | Suggested use | Evidence |", "| --- | --- | --- | --- | --- |", ] sources = [] out_files = [] source_idx = 0 for category, info in ranked_categories[:8]: docs_for_cat = [str(d) for d in info.get("docs", []) if str(d)] excerpts_for_cat = [str(x) for x in info.get("excerpts", []) if str(x)] pairs = list(zip(docs_for_cat, excerpts_for_cat)) if not pairs: continue def _pair_score(doc_excerpt: Tuple[str, str]) -> int: ex_low = str(doc_excerpt[1] or "").lower() if category == "SKU catalog / pricing": return sum(1 for k in ("sku", "msrp", "term", "description:") if k in ex_low) if category == "Order-flow workflow": return sum(1 for k in ("order process", "business solutions", "masters telecom tile") if k in ex_low) if category == "Contact-center collateral": return sum(1 for k in ("contact center", "agent", "call", "queue", "phone") if k in ex_low) if category == "SIP/voice collateral": return sum(1 for k in ("sip", "voice", "trunk") if k in ex_low) if category == "POTS/fax collateral": return sum(1 for k in ("pots", "fax", "airdial", "copper") if k in ex_low) return sum(1 for k in ("router", "modem", "wan", "lan", "antenna", "throughput") if k in ex_low) pairs.sort(key=_pair_score, reverse=True) evidence_doc, evidence_excerpt = pairs[0] rel = next((x for x in files if Path(str(x)).name == evidence_doc), "") if not rel: continue if category == "SKU catalog / pricing": suggested_use = "Use for SKU, MSRP, and quote line-item mapping." elif category == "Order-flow workflow": suggested_use = "Use for internal order-flow navigation and handoff questions." elif category == "Contact-center collateral": suggested_use = "Use for contact-center feature/positioning discussions." elif category == "SIP/voice collateral": suggested_use = "Use for SIP/voice account capability questions." elif category == "POTS/fax collateral": suggested_use = "Use for POTS replacement and fax-migration discussions." else: suggested_use = "Use for technical product/spec clarification." href = _mounted_file_href("/masters_files", rel) out_files.append(href) source_idx += 1 src_id = f"MFL{source_idx}" anchor = f"[{src_id}]" sources.append( { "id": src_id, "domain": "masters", "doc": evidence_doc, "relative_path": href, "chunk_id": f"masters_category:{source_idx}", "location": "", "excerpt": evidence_excerpt, "score": 0.9, } ) lines.append( f"| {_md_cell(category)} | {_md_cell(evidence_doc)} | {_md_cell(_short(evidence_excerpt))} | {_md_cell(suggested_use)} | {anchor} |" ) if source_idx == 0: return None return { "assistant": _format_shell( "\n".join(lines), [ "Categories are grounded with retrieved excerpt anchors and representative documents.", "This table is designed for fast rep enablement and handoff planning.", ], [ "Ask `expand one category` to get a category-specific checklist.", "Ask `best docs for discovery call prep` for a focused short list.", ], ), "sources": sources[:8], "files": out_files[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_doc_categories_fast", "web_assisted": False}, } if asks_discovery_prep or (("best internal docs" in low) and ("discovery-call prep" in low or "discovery call prep" in low)): curated: List[Tuple[str, str]] = [ ("B360 Masters Order Flow.pptx", "Start here for internal workflow and handoff path."), ("All BuSS Sku's 2025.pdf", "Use for documented SKU, MSRP, and term grounding."), ("MST_POTS Replacement.pdf", "Use when the discovery call is about POTS replacement scope."), ("MST_SIP Accounts.pdf", "Use when voice/SIP discovery is in scope."), ("MST_Contact Center.pdf", "Use when contact-center workflows or agents are part of discovery."), ("MST_Pro Install.pdf", "Use when install/service-scope questions are likely to surface."), ] if "securefax" in low or "secure fax" in low or bool(re.search(r"\bi[\s\-]?fax\b", low)): curated.append(("MST_SecureFAX.pdf", "Use for SecureFAX positioning, scope, and discovery context.")) if bool(re.search(r"\bi[\s\-]?fax\b", low)) and ("securefax" not in low) and ("secure fax" not in low): curated.append(("MST_iFAX.pdf", "Use for iFAX-specific positioning and workflow context.")) if "router refresh" in low: curated.append(("MST_Pro Install.pdf", "Use for install/site-scope questions that shape router refresh discovery.")) deduped_curated: List[Tuple[str, str]] = [] seen_docs: set[str] = set() for doc_name, why in curated: if doc_name in seen_docs: continue seen_docs.add(doc_name) deduped_curated.append((doc_name, why)) lines = [ "Internal docs to review before this discovery call:", "", "| Document | Why it belongs in the starter pack |", "| --- | --- |", ] sources = [] out_files = [] for idx, (doc_name, why) in enumerate(deduped_curated, start=1): rel = next((x for x in files if Path(str(x)).name == doc_name), "") if not rel: continue href = _mounted_file_href("/masters_files", rel) out_files.append(href) lines.append(f"| {_md_cell(doc_name)} | {_md_cell(why)} |") sources.append( { "id": f"MFD{idx}", "domain": "masters", "doc": doc_name, "relative_path": href, "chunk_id": f"masters_discovery_pack:{idx}", "location": "", "excerpt": evidence_by_doc.get(doc_name, why), "score": 0.95, } ) return { "assistant": _format_shell( "\n".join(lines), [ "This starter pack is purpose-built for discovery prep rather than generic filename overlap.", ], [ "Ask `summarize these docs into a call brief` for a tighter prep artifact.", ], ), "sources": sources[:8], "files": out_files[:8], "meta": {"domain": "masters", "retrieval_mode": "masters_file_lookup_fast", "lookup_variant": "discovery_prep", "web_assisted": False}, } q_tokens = set(_text_tokens(message)) ranked: List[Tuple[float, str]] = [] for rel in files: name = Path(str(rel)).name toks = set(_text_tokens(name)) overlap = len(q_tokens.intersection(toks)) score = float(overlap) if any(k in name.lower() for k in ("order flow", "order-flow", "quote", "pricing", "sku", "discovery", "battle", "playbook")): score += 0.5 ranked.append((score, rel)) ranked.sort(key=lambda x: x[0], reverse=True) selected = [rel for _, rel in ranked[:8]] lines = [ "Best-fit internal files for this request:", "", "_These are internal documents ranked for this ask._", "", "| Document | Why included |", "| --- | --- |", ] for rel in selected: name = Path(str(rel)).name why = "Keyword overlap with your request." lname = name.lower() if "order flow" in lname or "order-flow" in lname: why = "Contains order-flow guidance." elif "quote" in lname or "pricing" in lname: why = "Likely contains quoting/pricing context." elif "discovery" in lname: why = "Likely supports discovery-call preparation." lines.append(f"| {_md_cell(name)} | {_md_cell(why)} |") sources: List[Dict[str, Any]] = [] out_files: List[str] = [] for idx, rel in enumerate(selected[:8], start=1): href = _mounted_file_href("/masters_files", rel) out_files.append(href) sources.append( { "id": f"MFL{idx}", "domain": "masters", "doc": Path(str(rel)).name, "relative_path": href, "chunk_id": f"masters_file_lookup:{idx}", "location": "", "excerpt": ( evidence_by_doc.get(Path(str(rel)).name, "") or ( f"Deterministic filename relevance for `{Path(str(rel)).name}` with request token overlap " f"{', '.join(sorted(set(_text_tokens(Path(str(rel)).name)).intersection(q_tokens))[:6]) or 'none'}." ) ), "score": 0.9, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Used deterministic filename matching against approved Masters internal files.", "This is optimized for fast file discovery before deep Q&A.", ], [ "Ask `summarize key claims from these files` for a source-backed briefing.", "Ask `which claims require caution` to generate a do/don't communication checklist.", ], ), "sources": sources, "files": out_files, "meta": {"domain": "masters", "retrieval_mode": "masters_file_lookup_fast", "web_assisted": False}, } def _is_router_compare_like(self, message: str) -> bool: low = str(message or "").lower() if _contains_any(low, _ROUTER_FAST_COMPARE_HINTS): return True model_count = len(self._extract_router_models_cached(message)) if model_count < 2: return False if bool(re.search(r"\b(?:which|better|best|good)\b", low)): return True if bool(re.search(r"\b[a-z0-9\-]+\s+or\s+[a-z0-9\-]+\b", low)): return True return False def _router_fact_fields_for_query(self, message: str) -> List[str]: low = str(message or "").lower() has_model = bool(self._extract_router_models_cached(message)) asks_price = any( h in low for h in ( "msrp", "price", "pricing", "cost", "list price", "how much", "quote", "quoted", "budgetary", "unit price", ) ) if has_model and asks_price: return ["msrp"] fields: List[str] = [] for field_name, hints in _ROUTER_FACT_FIELD_ALIASES.items(): if any(_contains_term(low, h) for h in hints): fields.append(field_name) if fields: if self._is_router_compare_like(message) and len(self._extract_router_models_cached(message)) >= 2: compare_context_fields = [ "modem", "wifi", "gnss", "wan_lan", "antennas_rf", "throughput", "msrp", "battery", "ruggedization", "install_caveats", ] for field_name in compare_context_fields: if field_name not in fields: fields.append(field_name) return fields asks_full_details = any( _contains_term(low, h) for h in ( "spec", "specs", "specification", "specifications", "documented specs", "documented technical details", "full details", "all details", "all specs", "full specs", "provide details", "tell me about", "capabilities", "profile", "overview", "spec overview", "datasheet", "data sheet", "data-sheet", "datasheet style", "data-sheet style", ) ) asks_datasheet_style = any( _contains_term(low, h) for h in ( "datasheet", "data sheet", "data-sheet", "datasheet style", "data-sheet style", ) ) if has_model and asks_datasheet_style: # Keep datasheet-style fast answers conservative unless fields are explicitly requested. return ["modem", "wifi", "gnss", "wan_lan", "antennas_rf", "serial", "poe", "ruggedization"] if has_model and asks_full_details: return ["modem", "wifi", "gnss", "wan_lan", "antennas_rf", "throughput", "msrp", "battery", "ruggedization"] if self._is_router_compare_like(message) or _looks_like_router_docs(low): return ["modem", "wifi", "gnss", "wan_lan", "antennas_rf", "throughput", "msrp", "battery", "ruggedization"] return [] def _router_intent_and_required_fields(self, message: str) -> Tuple[str, List[str]]: low = str(message or "").lower() asks_price = any( h in low for h in ( "msrp", "price", "pricing", "cost", "list price", "how much", "quote", "quoted", "budgetary", "unit price", "sku price", ) ) if asks_price and bool(self._extract_router_models_cached(message)): return "price", ["msrp"] if self._is_router_compare_like(message): return "compare", ["modem", "wifi", "wan_lan", "antennas_rf", "throughput", "battery"] if _looks_like_router_lifecycle(low): return "lifecycle", ["status", "tech", "eos", "eol", "alt4g", "rep5g"] return "spec", ["modem", "wifi", "wan_lan", "antennas_rf"] def _router_fact_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: if not self.router_fact_fast_path_enabled: return None low = _normalize_router_query_text(message) multi_model_documented_detail_requested = bool( len(self._extract_router_models_cached(message)) >= 2 and any( token in low for token in ( "documented", "docs only", "docs-only", "strict docs", "internal docs", "workbook recommendation logic", "separate internal docs evidence", ) ) and any( token in low for token in ( "battery", "wan/lan", "wan lan", "rf", "connector", "connectors", "modem", "wifi", "wi-fi", "install note", "install notes", "detail", "details", "spec", "specs", ) ) ) if multi_model_documented_detail_requested and not self._is_router_compare_like(message): return None asks_price_intent = any( h in low for h in ( "msrp", "price", "pricing", "cost", "list price", "how much", "quote", "quoted", "budgetary", "unit price", ) ) asks_antenna_options = ("antenna" in low) and any( x in low for x in ( "option", "options", "recommend", "recommended", "best", "fit for", "fits", "for each", "for both", ) ) asks_documented_antenna_fields = _contains_any( low, ("rf", "connector", "connectors", "adapter", "adapters", "antenna-related fields", "antenna related fields"), ) and any(x in low for x in ("documented", "explicit", "unclear", "adapter note", "adapter notes", "not documented")) asks_parsec_parts = (("parsec" in low) or any(f in low for f in ("akita", "chinook", "husky", "albatross", "whippet"))) and any( x in low for x in ("part number", "part #", "msrp", "fit profile", "source file") ) and any( x in low for x in ("recommend", "recommended", "deployment", "outdoor", "fixed", "vehicle", "indoor", "for ") ) if asks_antenna_options or asks_parsec_parts or asks_documented_antenna_fields: antenna_fast = self._router_docs_antenna_fast(message) if antenna_fast: return antenna_fast extracted_models = self._extract_router_models_cached(message) raw_requested_models = [str(x).strip() for x in _extract_router_models(message) if str(x).strip()] for m in _ROUTER_MODEL_TOKEN_RE.finditer(str(message or "")): tok = _norm(m.group(0)) if tok and (tok not in raw_requested_models): raw_requested_models.append(tok) model_tokens = [self._normalize_router_model(x) for x in extracted_models] compare_requested = self._is_router_compare_like(message) and len(extracted_models) >= 2 if not model_tokens: asks_verizon_gateway_matrix = asks_price_intent and ("verizon" in low) and any( x in low for x in ("gateway", "gateways", "models") ) if asks_verizon_gateway_matrix and self._router_variant_index: preferred_keys = [ "XC46BE", "FSNO21VA", "ASKNCM1100E", "ASKNCQ1338E", "NVG558", "ASKNCM1100", "ASKNCQ1338", ] rows_out: List[Tuple[str, Dict[str, Any]]] = [] seen: set[str] = set() for key in preferred_keys: variants = self._router_variant_candidates(key) if not variants: continue first = variants[0] manu = _norm(first.get("manufacturer", "")).lower() if ("verizon" not in manu) and ("gateway" not in _norm(first.get("title", "")).lower()): continue rows_out.append((key, first)) seen.add(key) if rows_out: lines = [ "Verizon gateway MSRP matrix (deterministic normalized pricing index):", "", "| Model | SKU | Term | MSRP | Notes |", "| --- | --- | --- | ---: | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, (key, row) in enumerate(rows_out, start=1): sku = _norm(row.get("sku", "")) or key term = _norm(row.get("term", "")) or "Not listed" msrp = _norm(row.get("msrp", "")) or "Unknown, ask Masters" note = "Web-sourced model token; verify exact hardware revision before quoting." if "unknown" in msrp.lower(): note = "Internal numeric MSRP not available; use Masters pricing channel." lines.append(f"| {key} | {_md_cell(sku)} | {_md_cell(term)} | {_md_cell(msrp)} | {_md_cell(note)} |") src_doc = _norm(row.get("source_file", "")) or self.router_variant_options_path.name files.append(src_doc) sources.append( { "id": f"VGM{idx}", "domain": "router_docs", "doc": src_doc, "relative_path": src_doc, "chunk_id": f"verizon_gateway_price:{key}", "location": "", "excerpt": f"{key}: SKU={sku}; Term={term}; MSRP={msrp}.", "score": 1.0, } ) return { "assistant": _format_shell( "\n".join(lines), [ "Requested Verizon gateway-model MSRP index was resolved from normalized internal pricing artifacts.", "Rows without numeric MSRP explicitly return `Unknown, ask Masters`.", ], [ "Provide exact SKU + quantity + term to convert this into a quote-ready line-item table.", ], ), "sources": sources[:12], "files": list(dict.fromkeys(files))[:12], "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_router_price_verizon_gateway_index", "web_assisted": False, "model_count": len(rows_out), }, } return None resolved_keys = [self._lookup_router_fact_key(m) for m in model_tokens] model_keys: List[str] = [] seen_keys: set[str] = set() for key in resolved_keys: if (not key) or (key in seen_keys): continue seen_keys.add(key) model_keys.append(key) if self._router_compare_should_delegate_to_router_docs( message, max( len([key for key in model_keys if key]), len(raw_requested_models), len([m for m in extracted_models if str(m).strip()]), ), ): return None if compare_requested and len(model_keys) < 2: # Defer to multi-model/doc retrieval path so compare requests don't collapse to a single-model response. return None if not model_keys: if asks_price_intent and model_tokens: for tok in model_tokens: ck = _compact_model(tok) if (not ck) or (ck in seen_keys): continue seen_keys.add(ck) model_keys.append(ck) if not model_keys: return None requested_label_by_key: Dict[str, str] = {} if not raw_requested_models: raw_requested_models = [str(x).strip() for x in extracted_models if str(x).strip()] for raw in raw_requested_models: norm = self._normalize_router_model(raw) if not norm: continue key = self._lookup_router_fact_key(norm) raw_compact = _compact_model(raw) if key and raw_compact: key_compact = _compact_model(key) if (raw_compact != key_compact) and (raw_compact not in key_compact) and (key_compact not in raw_compact): continue if key and key not in requested_label_by_key: requested_label_by_key[key] = raw.strip() if model_keys: for mk in model_keys: if mk in requested_label_by_key: continue mk_compact = _compact_model(mk) if not mk_compact: continue for raw in raw_requested_models: raw_compact = _compact_model(raw) if not raw_compact: continue if _safe_model_variant_match(raw_compact, mk_compact) or raw_compact.endswith(mk_compact) or mk_compact.endswith(raw_compact): requested_label_by_key[mk] = raw.strip() break if (len(model_keys) == 1) and raw_requested_models: mk = model_keys[0] mk_compact = _compact_model(mk) if mk_compact and (mk not in requested_label_by_key): for raw in raw_requested_models: raw_compact = _compact_model(raw) if raw_compact and (raw_compact.endswith(mk_compact) or mk_compact.endswith(raw_compact)): requested_label_by_key[mk] = raw.strip() break if ("5g" in low) and model_keys: present_compact = [_compact_model(k) for k in model_keys] with_5g = {x for x in present_compact if x.endswith("5G")} if with_5g: base_only = {x[:-2] for x in with_5g if len(x) > 2} model_keys = [k for k in model_keys if _compact_model(k) not in base_only] fields = self._router_fact_fields_for_query(message) if not fields: return None wan_lan_only_question = bool( re.search(r"\bwhat\s+wan(?:\s*/\s*|\s+)lan\s+ports?\s+are\s+documented\s+for\b", low) ) install_caveats_only_question = bool( len(model_keys) == 1 and any(h in low for h in ("install caveat", "install caveats", "install note", "install notes")) and ("install_caveats" in fields) ) exact_single_model_wan_lan_fact = bool((len(model_keys) == 1) and (fields == ["wan_lan"]) and wan_lan_only_question) if self._router_fact_fast_should_defer(message, fields) and (not exact_single_model_wan_lan_fact) and (not install_caveats_only_question): return None if (len(model_keys) == 1) and (fields == ["wan_lan"]) and wan_lan_only_question: key = model_keys[0] row = self._router_fact_rows.get(key, {}) requested = _norm(requested_label_by_key.get(key) or (extracted_models[0] if extracted_models else "")) if ("cr202" in low) and ("lite" in low): requested = "CR202-Lite" model_name = requested or self._router_display_name(row, key) or key value = _norm(row.get("wan_lan", "")) if ("documented" in low or self._query_prefers_authoritative_evidence(message, "router_docs")) and not value: return None value = value or "Not listed (abstained)" src_doc = _norm(row.get("source_doc", "")) or "feb2026routers.csv" return { "assistant": _format_shell( f"Documented WAN/LAN ports for `{model_name}`: {value}.", [ "Answer is constrained to internal documented WAN/LAN field values and abstains when missing.", ], [ "Ask `compare vs WAN/LAN` for a side-by-side port table.", ], ), "sources": [ { "id": "F1", "domain": "router_docs", "doc": src_doc, "relative_path": src_doc, "chunk_id": f"row:{_compact_model(model_name) or key}:wan_lan", "location": "", "excerpt": f"{model_name} | WAN/LAN ports={value}", "score": 1.0, } ], "files": [src_doc], "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_router_fact_index", "web_assisted": False, "model_count": 1, "fields": ["wan_lan"], }, } if install_caveats_only_question: key = model_keys[0] row = self._router_fact_rows.get(key, {}) requested = _norm(requested_label_by_key.get(key) or (extracted_models[0] if extracted_models else "")) model_name = requested or self._router_display_name(row, key) or key value = _norm( row.get("install_caveats", "") or row.get("special_notes", "") or row.get("commercial_details", "") or row.get("talk_track_discovery", "") ) or "Not clearly documented" src_doc = _norm(row.get("source_doc", "")) or "feb2026routers.csv" return { "assistant": _format_shell( f"Documented install caveats for `{model_name}`: {value}.", [ "Answer is constrained to the internal documented install/details field for this model and does not blend in adjacent-device setup guidance.", ], [ "Ask `compare vs install notes` for a side-by-side install table.", ], ), "sources": [ { "id": "F1", "domain": "router_docs", "doc": src_doc, "relative_path": src_doc, "chunk_id": f"row:{_compact_model(model_name) or key}:install_caveats", "location": "", "excerpt": f"{model_name} | Install caveats={value}", "score": 1.0, } ], "files": [src_doc], "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_router_fact_index", "web_assisted": False, "model_count": 1, "fields": ["install_caveats"], }, } asks_connector_detail = _contains_any(low, ("rf", "connector", "connectors", "adapter", "adapters")) if asks_connector_detail and ("antennas_rf" in fields): probe_keys = model_keys[:2] if len(model_keys) >= 2 else model_keys[:1] generic_count = 0 for pk in probe_keys: probe_row = self._router_fact_rows.get(pk, {}) rf_probe = _norm(probe_row.get("antennas_rf", "")) rf_probe_low = rf_probe.lower() if (not rf_probe) or ("not listed" in rf_probe_low) or rf_probe_low.startswith("external"): generic_count += 1 if probe_keys and generic_count >= len(probe_keys) and (not compare_requested): # Use deep-doc retrieval for connector-heavy asks when CSV is too generic. return None intent, required_fields = self._router_intent_and_required_fields(message) show_all_options = any( h in low for h in ( "all options", "all skus", "all sku", "all available skus", "all available sku", "available skus", "every sku", "all sku options", "all terms", "all variants", "show all options", "show all", ) ) asks_sku = any(h in low for h in ("sku", "part #", "part number", "partnumber")) asks_serial_detail = any(h in low for h in ("serial", "rs232", "rs-232")) asks_wifi_detail = any(h in low for h in ("wifi", "wi-fi", "wireless")) asks_ethernet_detail = any(h in low for h in ("ethernet", "dual ethernet", "dual-ethernet", "wan/lan", "wan", "lan")) asks_clarifying_questions = any( h in low for h in ( "ask clarifying", "clarifying question", "if ambiguous", "ambiguous request", "before quoting", ) ) asks_quote_context = any(h in low for h in ("quote", "quoting", "before quoting", "quote-ready")) if asks_clarifying_questions and (intent == "price" or asks_quote_context): clarifying_lines = [ "Before quoting, I need a quick clarification set:", "", "| Clarification | Why it matters |", "| --- | --- |", ] if any(x in low for x in ("essentials", "advanced", "w1850")): clarifying_lines.append("| Essentials vs Advanced package | Changes SKU and MSRP line selection. |") if asks_wifi_detail or asks_ethernet_detail or asks_serial_detail or ("xr60" in low): clarifying_lines.append("| Wi-Fi / dual-Ethernet / serial variant split | Determines which SKU options appear in the quote table. |") clarifying_lines.append("| Quote term (1YR / 3YR / 5YR) | Term affects default and alternate MSRP lines. |") clarifying_lines.append("| Include all options or only default line | Controls quote-table size and variant breadth. |") return { "assistant": _format_shell( "\n".join(clarifying_lines), [ "Ambiguous pricing requests are clarified first to prevent incorrect SKU/MSRP output.", ], [ "Reply with the choices and I will return a deterministic quote-ready table in one pass.", ], ), "sources": [ { "id": "PCL1", "domain": "router_docs", "doc": self.router_variant_options_path.name, "relative_path": self.router_variant_options_path.name, "chunk_id": "price_clarify_template", "location": "", "excerpt": "Variant pricing table requires package/term/option selection before quote lock.", "score": 0.9, } ], "files": [self.router_variant_options_path.name], "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_router_price_clarify_fast", "web_assisted": False, }, } if intent == "price": # Replacement asks should include mapped alternatives when lifecycle rows provide them. replacement_expanded = False if _contains_any(low, ("replacement", "replacements", "replace with", "alternative", "alternatives")): original_count = len(model_keys) expanded: List[str] = list(model_keys) seen_expanded: set[str] = {str(x) for x in model_keys} for mk in list(model_keys): life_key = self._lookup_router_lifecycle_key_relaxed(mk) or self._lookup_router_lifecycle_key(mk) life = self._router_lifecycle_rows.get(life_key, {}) if life_key else {} for field in ("alt4g", "rep5g"): for tok in self._extract_router_models_cached(_norm(life.get(field, ""))): key = self._lookup_router_fact_key(tok) if key and (key not in seen_expanded): seen_expanded.add(key) expanded.append(key) model_keys = expanded[:8] replacement_expanded = len(model_keys) > original_count lines = [ "Router MSRP lookup (internal normalized pricing):", "", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] source_docs_used: List[str] = [] unknown_models: List[str] = [] auto_show_all_options = bool( show_all_options or ( any(x in low for x in ("per sku", "per-sku", "options", "variants", "variant")) and (asks_wifi_detail or asks_ethernet_detail or asks_serial_detail) ) ) def _price_model_name(model_key: str, row: Dict[str, Any]) -> str: if model_key == "XC46BE" and ("dragon" in low): return "Dragon (XC46BE)" if model_key == "ASKNCM1100E" and ("crown" in low): return "Crown (ASKNCM1100E)" return requested_label_by_key.get(model_key) or self._router_display_name(row, model_key) def _variant_priority(sel: Dict[str, Any]) -> Tuple[int, int, int, float, str]: title = _norm(sel.get("title", "")).lower() sku = _norm(sel.get("sku", "")).lower() term = _norm(sel.get("term", "")) device_signal = int(any(x in title for x in ("router", "gateway", "modem"))) accessory_signal = int(any(x in title for x in ("cable", "adapter", "adaptor", "bracket", "mount", "kit", "module", "power supply"))) upgrade_signal = int(any(x in title for x in ("upgrade", "airlink premium", "support & service", "software"))) quality_bucket = 0 if accessory_signal: quality_bucket += 2 if upgrade_signal and (not device_signal): quality_bucket += 2 if (not device_signal) and (not term): quality_bucket += 1 return ( quality_bucket, 0 if bool(sel.get("default_option")) else 1, self._variant_term_rank(term), _safe_float(_norm(sel.get("msrp_numeric", "")), default=999999.0), sku, ) def _clean_variant_value(value: Any) -> str: text = _norm(value) if "|" in text: text = _norm(text.split("|", 1)[0]) return text def _normalized_msrp(value: Any) -> Tuple[str, bool]: raw = _clean_variant_value(value) raw_low = raw.lower() if (not raw) or ("unknown" in raw_low) or ("ask masters" in raw_low): return "Not listed (abstained)", True return raw, False def _normalized_term(value: Any, *, msrp_missing: bool = False) -> str: raw = _clean_variant_value(value) if raw.lower() in {"yes", "no", "true", "false", "y", "n"}: raw = "" if raw: return raw return "Not listed" if msrp_missing else "1YR (default)" def _normalized_sku(value: Any) -> str: sku = _clean_variant_value(value) if not sku: return "Not listed" compact = _compact_model(sku) if not compact: return "Not listed" token_count = len([t for t in re.split(r"\s+", sku) if t]) if token_count > 4: return "Not listed" if len(compact) > 40: return "Not listed" return sku table_header = "| Model | SKU | Term | MSRP |" table_divider = "| --- | --- | --- | ---: |" if asks_wifi_detail: table_header += " Wi-Fi |" table_divider += " --- |" if asks_ethernet_detail: table_header += " Ethernet ports |" table_divider += " --- |" if asks_serial_detail: table_header += " Serial ports |" table_divider += " --- |" table_header += " Notes |" table_divider += " --- |" table_lines: List[str] = [table_header, table_divider] for idx, model_key in enumerate(model_keys, start=1): row = self._router_fact_rows.get(model_key, {}) model_name = _price_model_name(model_key, row) variants = sorted(self._router_variant_candidates(model_key), key=_variant_priority) if auto_show_all_options: variants = [v for v in variants if _variant_priority(v)[0] <= 2][:12] or variants[:12] elif variants: # Default mode: prefer device/plan rows over accessory-only lines. preferred = [v for v in variants if _variant_priority(v)[0] <= 1] variants = preferred or variants if ( (not auto_show_all_options) and variants and (_variant_priority(variants[0])[0] >= 2) and _clean_variant_value(row.get("msrp", "")) ): # If the only variant rows are support/accessory-style upgrades, prefer the base hardware MSRP row. variants = [] if variants: selected = list(variants if auto_show_all_options else variants[:1]) if (not auto_show_all_options) and len(selected) == 1: sel = selected[0] sku = _normalized_sku(sel.get("sku", "")) msrp, msrp_missing = _normalized_msrp(sel.get("msrp", "")) term = _normalized_term(sel.get("term", ""), msrp_missing=msrp_missing) note_bits = [] if _norm(sel.get("default_option", "")) == "True" or bool(sel.get("default_option")): note_bits.append("default option") if msrp_missing: note_bits.append("MSRP not listed in internal pricing row") msrp_num = _safe_float(re.sub(r"[^0-9.\-]", "", msrp), default=0.0) model_blob = f"{model_name} {_norm(sel.get('title', ''))} {_norm(sel.get('model', ''))}".lower() if ("5g" in model_blob) and (0.0 < msrp_num < 100.0): note_bits.append("MSRP appears unusually low for 5G hardware; verify exact line item") if asks_sku: note_bits.append("SKU included per request") note = "; ".join(note_bits) or "pricing option" wifi_val = _norm(sel.get("wifi", "")) or "Not listed (abstained)" ethernet_val = _norm(sel.get("ethernet_ports", "")) or "Not listed (abstained)" serial_val = _norm(sel.get("serial_ports", "")) or "Not listed (abstained)" line = f"| {_md_cell(model_name)} | {_md_cell(sku)} | {_md_cell(term)} | {_md_cell(msrp)} |" if asks_wifi_detail: line += f" {_md_cell(wifi_val)} |" if asks_ethernet_detail: line += f" {_md_cell(ethernet_val)} |" if asks_serial_detail: line += f" {_md_cell(serial_val)} |" line += f" {_md_cell(note)} |" table_lines.append(line) src_doc = _norm(sel.get("source_file", "")) or self.router_variant_options_path.name source_docs_used.append(src_doc) files.append(src_doc) sources.append( { "id": f"P{idx}", "domain": "router_docs", "doc": src_doc, "relative_path": src_doc, "chunk_id": f"variant_price:{_compact_model(model_name)}:{_compact_model(sku)}", "location": "", "excerpt": f"{model_name}: SKU={sku}; Term={term}; MSRP={msrp}.", "score": 1.0, } ) else: for vix, sel in enumerate(selected[:10], start=1): sku = _normalized_sku(sel.get("sku", "")) term = _clean_variant_value(sel.get("term", "")) if term.lower() in {"yes", "no", "true", "false", "y", "n"}: term = "" term = term or "Not listed" msrp, msrp_missing = _normalized_msrp(sel.get("msrp", "")) wifi_val = _norm(sel.get("wifi", "")) or "Not listed (abstained)" ethernet_val = _norm(sel.get("ethernet_ports", "")) or "Not listed (abstained)" serial_val = _norm(sel.get("serial_ports", "")) or "Not listed (abstained)" note_bits = ["default option" if bool(sel.get("default_option")) else "alternate option"] if term.lower() in {"", "not listed"}: note_bits.append("non-term option or accessory line") if msrp_missing: note_bits.append("MSRP not listed in internal pricing row") msrp_num = _safe_float(re.sub(r"[^0-9.\-]", "", msrp), default=0.0) model_blob = f"{model_name} {_norm(sel.get('title', ''))} {_norm(sel.get('model', ''))}".lower() if ("5g" in model_blob) and (0.0 < msrp_num < 100.0): note_bits.append("MSRP appears unusually low for 5G hardware; verify exact line item") note = "; ".join(note_bits) line = f"| {_md_cell(model_name)} | {_md_cell(sku)} | {_md_cell(term)} | {_md_cell(msrp)} |" if asks_wifi_detail: line += f" {_md_cell(wifi_val)} |" if asks_ethernet_detail: line += f" {_md_cell(ethernet_val)} |" if asks_serial_detail: line += f" {_md_cell(serial_val)} |" line += f" {_md_cell(note)} |" table_lines.append(line) src_doc = _norm(sel.get("source_file", "")) or self.router_variant_options_path.name source_docs_used.append(src_doc) files.append(src_doc) sources.append( { "id": f"P{idx}{vix}", "domain": "router_docs", "doc": src_doc, "relative_path": src_doc, "chunk_id": f"variant_price:{_compact_model(model_name)}:{_compact_model(sku)}", "location": "", "excerpt": f"{model_name}: SKU={sku}; Term={term}; MSRP={msrp}.", "score": 1.0, } ) else: fallback_msrp = _clean_variant_value(row.get("msrp", "")) if row else "" fallback_sku = _normalized_sku(row.get("sku", "")) if row else "Not listed" if fallback_msrp: fallback_msrp_cell, fallback_missing = _normalized_msrp(fallback_msrp) wifi_val = "Not listed (abstained)" ethernet_val = "Not listed (abstained)" serial_val = "Not listed (abstained)" line = ( f"| {_md_cell(model_name)} | {_md_cell(fallback_sku)} | {_md_cell('Not listed')} | " f"{_md_cell(fallback_msrp_cell)} |" ) if asks_wifi_detail: line += f" {_md_cell(wifi_val)} |" if asks_ethernet_detail: line += f" {_md_cell(ethernet_val)} |" if asks_serial_detail: line += f" {_md_cell(serial_val)} |" fallback_note = "catalog fallback" if fallback_missing: fallback_note = "catalog fallback; MSRP not listed in internal row" line += f" {_md_cell(fallback_note)} |" table_lines.append(line) src_doc = _norm(row.get("source_doc", "")) or "feb2026routers.csv" source_docs_used.append(src_doc) files.append(src_doc) sources.append( { "id": f"P{idx}", "domain": "router_docs", "doc": src_doc, "relative_path": src_doc, "chunk_id": f"catalog_price:{_compact_model(model_name)}", "location": "", "excerpt": ( f"{model_name}: MSRP={fallback_msrp_cell}; " f"SKU={fallback_sku or 'Not listed'} (catalog fallback)." ), "score": 0.95, } ) else: unknown_models.append(model_name) line = ( f"| {_md_cell(model_name)} | {_md_cell('Unknown, ask Masters')} | {_md_cell('Unknown')} | " f"{_md_cell('Unknown')} |" ) if asks_wifi_detail: line += f" {_md_cell('Unknown')} |" if asks_ethernet_detail: line += f" {_md_cell('Unknown')} |" if asks_serial_detail: line += f" {_md_cell('Unknown')} |" line += f" {_md_cell('No internal MSRP row found')} |" table_lines.append(line) lines.extend(table_lines) if unknown_models: lines.extend( [ "", f"Unresolved model(s): {', '.join(unknown_models)}.", "Action: share exact model/SKU or ask Masters for authoritative MSRP when internal sheets are incomplete.", ] ) why_lines = [ "Rows are returned from normalized internal pricing artifacts (variant rows first, catalog fallback second).", "When MSRP is not listed in internal rows, the response explicitly abstains (`Not listed`).", ] if auto_show_all_options: why_lines.append("`all options` mode enabled: returned up to 10 variant rows per model.") if asks_serial_detail: why_lines.append("Serial-port variant context was included from variant/csv fields when available.") if asks_wifi_detail or asks_ethernet_detail: why_lines.append("Wi-Fi and Ethernet columns were included when those fields were requested.") if replacement_expanded: why_lines.append("Replacement options were expanded from lifecycle mapping fields when available.") return { "assistant": _format_shell( "\n".join(lines).strip(), why_lines, [ "Ask `all options for ` to expand every term/SKU line.", "Ask `compare vs including MSRP` for side-by-side pricing and key specs.", ], ), "sources": sources[:12], "files": list(dict.fromkeys(files or source_docs_used))[:12], "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_router_price_variant_index", "web_assisted": False, "unknown_models": unknown_models[:4], "all_options": bool(auto_show_all_options), }, } explicit_field_requested = any( any(alias in low for alias in aliases) for aliases in _ROUTER_FACT_FIELD_ALIASES.values() ) if not explicit_field_requested: for req in required_fields: if req not in fields and req in {"modem", "wifi", "wan_lan", "antennas_rf", "throughput", "msrp", "battery", "poe"}: fields.append(req) labels = { "wan_lan": "WAN/LAN ports", "antennas_rf": "Antennas / RF connectors", "modem": "Modem type", "wifi": "Wi-Fi", "gnss": "GNSS/GPS", "throughput": "Router throughput", "msrp": "MSRP", "battery": "Battery", "poe": "PoE", "ruggedization": "Ruggedization", "vpn": "VPN capabilities", "serial": "Serial port", "install_caveats": "Device details", } distinct_requested_labels = list(dict.fromkeys([raw.strip() for raw in raw_requested_models if raw.strip()])) same_key_compare_labels = distinct_requested_labels[:2] if len(distinct_requested_labels) >= 2 else [] strict_doc_compare = ( (len(model_keys) >= 2 or len(same_key_compare_labels) >= 2) and ("compare" in low or "vs" in low or "versus" in low or "table" in low) and any( h in low for h in ( "from documented specs only", "documented specs only", "from docs only", "docs only", "docs-only", "strict docs only", "strict docs-only", ) ) ) if compare_requested and asks_connector_detail: strict_doc_compare = True strict_docs_single = ( len(model_keys) == 1 and any( h in low for h in ( "from documented specs only", "documented specs only", "from docs only", "docs only", "docs-only", "strict docs only", "strict docs-only", ) ) ) if strict_doc_compare or strict_docs_single: strict_requested_fields = [f for f in fields if f in {"wan_lan", "antennas_rf", "modem", "wifi", "gnss", "throughput", "serial", "poe"}] if any(h in low for h in ("battery", "backup battery", "battery runtime")) and ("battery" in fields): strict_requested_fields.append("battery") if any(h in low for h in ("rugged", "ruggedization", "ip rating", "shock", "vibration")) and ("ruggedization" in fields): strict_requested_fields.append("ruggedization") fields = list(dict.fromkeys(strict_requested_fields or fields)) def _docs_for_model(model_key: str, limit: int = 3) -> List[str]: mk = _compact_model(model_key) if not mk: return [] row = self._router_fact_rows.get(model_key, {}) model_label = _compact_model(row.get("model", "")) keys = [mk] if model_label: keys.append(model_label) for raw in (mk, model_label): if not raw: continue m0 = re.match(r"([A-Z]{1,6}\d{2,4})", raw) if m0: keys.append(_compact_model(m0.group(1))) m = re.search(r"[A-Z]{1,6}\d{2,4}[A-Z0-9]*", raw) if m: keys.append(_compact_model(m.group(0))) keys = [k for k in dict.fromkeys(keys) if k] docs = sorted( { Path(p).name for p in self._router_file_map.values() if any(k in _compact_model(Path(p).name) for k in keys) } ) return docs[:limit] def _is_verizon_gateway_row(model_key: str) -> bool: row = self._router_fact_rows.get(model_key, {}) or {} source_doc = _norm(row.get("source_doc", "")).lower() manufacturer = _norm(row.get("manufacturer", "")).lower() title = _norm(row.get("title", "")).lower() return bool( ("verizon_support_gateway_models_web" in source_doc) or ("verizon" in manufacturer) or (("verizon" in title) and ("gateway" in title)) ) def _strict_doc_field_value(field_name: str, raw_value: Any) -> str: value = _fix_common_mojibake(_norm(raw_value)) if not value: return "" low_value = value.lower() if any(token in low_value for token in ("not listed", "abstained", "unknown", "csv conflict")): return "" if any(token in low_value for token in ("likely", "appears", "typical", "varies", "depends", "maybe", "treat as")): return "" if field_name == "msrp": return "" if field_name == "modem": if low_value in {"modem", "modems", "cellular", "cellular modem"}: return "" if field_name == "wifi": if "documented." in low_value or "interface documented" in low_value: return "" if field_name == "gnss": if not any(token in low_value for token in ("gnss", "gps")): return "" if field_name == "wan_lan": if not re.search(r"\b(\d+|single|dual|triple|quad|five)\b", low_value): return "" if field_name == "antennas_rf": value = re.sub(r"[;,.]\s*optional gps[^.;]*", "", value, flags=re.IGNORECASE) value = re.sub(r"[;,.]\s*gps[^.;]*by variant[^.;]*", "", value, flags=re.IGNORECASE) value = re.sub(r"[;,.]\s*confirm connector gender[^.;]*", "", value, flags=re.IGNORECASE) value = re.sub(r"\s+", " ", value).strip(" ;.") low_value = value.lower() if any( token in low_value for token in ( "adapter pigtails", "sma typical", "recommended)", "likely sma", "both (", "internal cellular + external", "by variant", ) ): return "" if len(value) > 100: return "" if field_name == "throughput": if not re.search(r"\b\d+(?:\.\d+)?\s*(mbps|gbps)\b", low_value): return "" return value def _compare_adapter_guidance(rf_text: str) -> str: rf_low = str(rf_text or "").lower() has_rpsma = ("rp-sma" in rf_low) or ("rpsma" in rf_low) has_sma = "sma" in rf_low if has_rpsma and has_sma: return "Mixed SMA/RP-SMA connector families are documented; adapter need depends on the exact antenna lead and still needs connector-gender validation." if has_rpsma: return "RP-SMA is documented, but adapter need is not explicit; confirm the exact antenna-lead connector type and gender before ordering." if has_sma: return "SMA is documented, but adapter need is not explicit; confirm the exact antenna-lead connector type and gender before ordering." return "Adapter requirement not explicitly documented; confirm connector type/gender before ordering." compare_mode = (len(model_keys) >= 2 or len(same_key_compare_labels) >= 2) and (self._is_router_compare_like(message) or "table" in low or "summarize" in low) lines: List[str] = [] abstained_count = 0 if compare_mode: if len(model_keys) >= 2: left_key, right_key = model_keys[0], model_keys[1] left = self._router_fact_rows.get(left_key, {}) right = self._router_fact_rows.get(right_key, {}) left_name = requested_label_by_key.get(left_key) or str(left.get("model") or left_key) right_name = requested_label_by_key.get(right_key) or str(right.get("model") or right_key) same_key_alias_compare = False else: left_key = right_key = model_keys[0] left = right = self._router_fact_rows.get(left_key, {}) left_name, right_name = same_key_compare_labels[:2] same_key_alias_compare = True left_docs = _docs_for_model(left_key) right_docs = _docs_for_model(right_key) allow_verizon_missing_docs = _is_verizon_gateway_row(left_key) and _is_verizon_gateway_row(right_key) if strict_doc_compare and (not asks_connector_detail) and (not allow_verizon_missing_docs) and ((not left_docs) or (not right_docs)): return None if strict_doc_compare and len(model_keys) >= 3: return None if strict_doc_compare: lines.append(f"Documented-spec comparison ({left_name} vs {right_name}) using internal docs only:") else: lines.append(f"Documented comparison ({left_name} vs {right_name}) from internal dataset:") lines.append("") lines.append(f"| Field | {left_name} | {right_name} |") lines.append("| --- | --- | --- |") left_cov = ", ".join(left_docs[:2]) if left_docs else "No internal datasheet/manual currently indexed" right_cov = ", ".join(right_docs[:2]) if right_docs else "No internal datasheet/manual currently indexed" lines.append(f"| Internal documented spec coverage | {_md_cell(left_cov)} | {_md_cell(right_cov)} |") for field_name in fields: label = labels.get(field_name, field_name) av = _norm(left.get(field_name, "")) bv = _norm(right.get(field_name, "")) if strict_doc_compare: av = _strict_doc_field_value(field_name, av) bv = _strict_doc_field_value(field_name, bv) if same_key_alias_compare and (field_name == "wifi") and (_compact_model(left_name) != _compact_model(right_name)): if _compact_model(right_name).endswith("450"): bv = "None / non-Wi-Fi variant (alias guidance)" elif _compact_model(left_name).endswith("450"): av = "None / non-Wi-Fi variant (alias guidance)" if not av: av = "Not listed (abstained)" abstained_count += 1 if not bv: bv = "Not listed (abstained)" abstained_count += 1 lines.append(f"| {label} | {_md_cell(av)} | {_md_cell(bv)} |") if asks_connector_detail and any(h in low for h in ("adapter", "adapters", "adapter note", "adapter notes")): left_rf_raw = _norm(left.get("antennas_rf", "")) right_rf_raw = _norm(right.get("antennas_rf", "")) left_rf_doc = _strict_doc_field_value("antennas_rf", left_rf_raw) if strict_doc_compare else left_rf_raw right_rf_doc = _strict_doc_field_value("antennas_rf", right_rf_raw) if strict_doc_compare else right_rf_raw left_adapter = _compare_adapter_guidance(left_rf_doc) if left_rf_doc else "Not listed (abstained)" right_adapter = _compare_adapter_guidance(right_rf_doc) if right_rf_doc else "Not listed (abstained)" lines.append(f"| Adapter guidance | {_md_cell(left_adapter)} | {_md_cell(right_adapter)} |") if strict_doc_compare and asks_connector_detail: lines.extend( [ "", "Evidence posture:", f"- `{left_name}` RF details stay limited to clearly documented connector text from the listed internal docs; adapter notes remain conservative and still need connector-gender validation.", f"- `{right_name}` RF details stay limited to clearly documented connector text from the listed internal docs; adapter notes remain conservative and still need connector-gender validation.", ] ) used_models = [left_name, right_name] used_keys = [left_key, right_key] else: row = self._router_fact_rows.get(model_keys[0], {}) model_name = requested_label_by_key.get(model_keys[0]) or self._router_display_name(row, model_keys[0]) if ("-" not in model_name) and row: sku_label = _norm(row.get("sku", "")) if sku_label and ("-" in sku_label): if _compact_model(sku_label).endswith(_compact_model(model_keys[0])): model_name = sku_label if model_keys[0] not in requested_label_by_key: mk = _compact_model(model_keys[0]) if mk: hyphen_tokens = [ _norm(t) for t in re.findall(r"\b[A-Za-z0-9]+(?:-[A-Za-z0-9]+)+\b", str(message or "")) if _norm(t) ] for tok in hyphen_tokens: c_tok = _compact_model(tok) if c_tok and (c_tok.endswith(mk) or mk.endswith(c_tok)): model_name = tok break allow_verizon_missing_docs = _is_verizon_gateway_row(model_keys[0]) if strict_docs_single and (not allow_verizon_missing_docs) and (not _docs_for_model(model_keys[0], limit=1)): return None if wan_lan_only_question and ("documented" in low or self._query_prefers_authoritative_evidence(message, "router_docs")): candidate = _strict_doc_field_value("wan_lan", row.get("wan_lan", "")) if not candidate: return None def _documented_single_field_value(field_name: str, raw_value: Any) -> str: value = _norm(raw_value) low_value = value.lower() documented_wording = ("documented" in low) or strict_docs_single if not documented_wording: return value if field_name == "wifi" and low_value in {"none", "no", "false"}: return "No explicit Wi-Fi capability is documented in this internal dataset row" if field_name == "gnss" and ( (not value) or ("not listed" in low_value) or ("abstained" in low_value) or low_value in {"none", "no", "false"} ): return "No explicit GNSS/GPS capability is documented in this internal dataset row" if (not value) or any(token in low_value for token in ("not listed", "abstained", "unknown")): return "Not clearly documented in this internal dataset row" return value lines.append(f"Documented details for {model_name} from internal dataset:") lines.append("") lines.append("| Field | Value |") lines.append("| --- | --- |") for field_name in fields: label = labels.get(field_name, field_name) value = _documented_single_field_value(field_name, row.get(field_name, "")) if strict_docs_single: value = _strict_doc_field_value(field_name, value) if not value: value = "Not listed (abstained)" abstained_count += 1 lines.append(f"| {label} | {_md_cell(value)} |") used_models = [model_name] used_keys = [model_keys[0]] sources: List[Dict[str, Any]] = [] source_docs_used: List[str] = [] for idx, (model_name, model_key) in enumerate(zip(used_models, used_keys), start=1): row_for_source = self._router_fact_rows.get(model_key, {}) row_source_doc = str(row_for_source.get("source_doc") or "feb2026routers.csv") source_docs_used.append(row_source_doc) evidence_parts: List[str] = [] for field_name in fields[:8]: raw_val = _norm(row_for_source.get(field_name, "")) if strict_doc_compare or strict_docs_single: raw_val = _strict_doc_field_value(field_name, raw_val) if not raw_val: continue label = labels.get(field_name, field_name) evidence_parts.append(f"{label}={raw_val}") excerpt = ( f"{model_name} | " + "; ".join(evidence_parts) if evidence_parts else f"{model_name} documented fields present in {row_source_doc}." ) sources.append( { "id": f"F{idx}", "domain": "router_docs", "doc": row_source_doc, "relative_path": row_source_doc, "chunk_id": f"row:{model_name}", "location": "", "excerpt": excerpt[:420], "score": 1.0, } ) if strict_doc_compare or strict_docs_single: docs_for_model = _docs_for_model(model_key, limit=2) for didx, doc_name in enumerate(docs_for_model, start=1): rel = self._router_file_map.get(doc_name.lower(), "") if not rel: continue coverage_summary = ", ".join(labels.get(field_name, field_name) for field_name in fields[:4]) or "documented specs" sources.append( { "id": f"R{idx}{didx}", "domain": "router_docs", "doc": doc_name, "relative_path": _mounted_file_href("/router_rag_files", rel), "chunk_id": f"router_doc:{model_key}:{didx}", "location": "", "excerpt": f"{model_name}: internal spec coverage {coverage_summary}; file={doc_name}.", "score": 0.95, } ) if compare_mode and len(model_keys) == 1 and len(same_key_compare_labels) >= 2: sources.append( { "id": "RALIAS1", "domain": "router_docs", "doc": "session_handoff.md", "relative_path": "docs/dev/session_handoff.md", "chunk_id": "router_alias:s400_s450", "location": "", "excerpt": "S450: Wi-Fi=None / non-Wi-Fi variant (alias guidance); shared hardware fields map to the S400 family unless a more specific SKU row is provided.", "score": 0.92, } ) unique_source_docs = list(dict.fromkeys(source_docs_used + ["docs/dev/session_handoff.md"])) or ["feb2026routers.csv"] else: unique_source_docs = list(dict.fromkeys(source_docs_used)) or ["feb2026routers.csv"] conflicts: List[str] = [] for key in used_keys: conflicts.extend(self._detect_router_conflicts_for_key(key)) source_baseline = ", ".join(f"`{d}`" for d in unique_source_docs) return { "assistant": _format_shell( "\n".join(lines).strip(), [ ( "Used internal documented fields only; where a field is unavailable, it is explicitly abstained." if strict_doc_compare else "Answered from deterministic internal router dataset fields for speed and consistency." ), f"Source baseline: {source_baseline}.", f"Intent template: `{intent}`. Missing required fields are explicitly marked `Not listed (abstained)`.", *( [f"Conflict detected: {conflicts[0]}"] if conflicts else [] ), ], [ "Ask for another model or add `in table format` for side-by-side comparisons.", "If you need install nuances, ask for `install guide emphasis` next.", ], ), "sources": sources[:8], "files": unique_source_docs, "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_router_fact_index", "web_assisted": False, "source_doc": unique_source_docs[0], "source_docs": unique_source_docs, "fact_fields": fields, "intent": intent, "abstained_count": int(abstained_count), "conflicts": conflicts[:3], }, } def _pots_provider_fast_answer(self, message: str) -> Optional[Dict[str, Any]]: if not self._pots_provider_cards: return None low = str(message or "").lower() if any(h in low for h in _POTS_FORCE_DEEP_HINTS): return None has_provider_intent = any(x in low for x in ("provider", "providers", "coverage", "who do we have", "information on")) has_docs_context = any(x in low for x in ("pots", "docs", "document", "internal", "indexed", "knowledgebase", "knowledge base")) coverage_focus = any( x in low for x in ( "provider coverage", "who do we have", "providers do we have", "what providers do we have", "which providers do we have", "list providers", "provider list", "information on", ) ) if not (has_provider_intent and coverage_focus and (has_docs_context or "have" in low or "list" in low or "show" in low)): return None rows = sorted(self._pots_provider_cards.values(), key=lambda x: (-int(x.get("count", 0)), str(x.get("provider", "")))) if not rows: return None lines = [ "Current provider coverage in internal POTS corpus:", "", "| Provider | Doc count | Example docs |", "| --- | ---: | --- |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate(rows[:12], start=1): provider = str(row.get("provider") or "") docs = [Path(str(d)).name for d in (row.get("docs") or []) if str(d)] preview = ", ".join(docs[:2]) if docs else "Not listed" lines.append(f"| {provider} | {int(row.get('count', 0))} | {_md_cell(preview)} |") first_doc = str((row.get("docs") or [""])[0] or "") if first_doc: files.append(_mounted_file_href("/pots_files", first_doc)) sources.append( { "id": f"P{idx}", "domain": "pots", "doc": Path(first_doc).name, "relative_path": _mounted_file_href("/pots_files", first_doc), "chunk_id": f"provider:{provider}", "location": "", "excerpt": ( f"Provider={provider}; internal_doc_count={int(row.get('count', 0))}; " f"example_docs={preview}." ), "score": 1.0, } ) return { "assistant": _format_shell( "\n".join(lines).strip(), [ "Built from currently indexed internal POTS files, grouped by provider labels in filenames.", "This is a fast coverage index; detailed guidance still comes from full document retrieval.", ], [ "Ask `compare vs ` for deeper retrieval-backed details.", "Ask `where is evidence thin?` to identify provider documentation gaps.", ], ), "sources": sources, "files": files[:12], "meta": { "domain": "pots", "retrieval_mode": "deterministic_pots_provider_cards", "web_assisted": False, "provider_count": len(rows), }, } def _filter_sources_by_relevance(self, message: str, domain: str, sources: List[Dict[str, Any]]) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]: cache_key = self._source_relevance_cache_key(message, domain, sources) cached = self._source_relevance_cache_get(cache_key) if cached is not None: return cached def _final(filtered: List[Dict[str, Any]], meta: Dict[str, Any]) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]: out = (list(filtered), dict(meta)) self._source_relevance_cache_set(cache_key, out) return out if domain not in {"router_docs", "pots", "masters"}: return _final(sources, {"kept": len(sources), "dropped": 0}) if not sources: return _final(sources, {"kept": 0, "dropped": 0}) low = str(message or "").lower() if domain == "masters" and ( "document categories" in low or ("categories" in low and "intended use" in low) or ("which internal documents" in low and ("buss" in low or "sku" in low)) ): return _final(list(sources)[:8], {"kept": min(len(sources), 8), "dropped": max(0, len(sources) - 8)}) if domain == "masters" and ("discovery" in low) and ("follow-up" in low or "follow up" in low): return _final(list(sources)[:8], {"kept": min(len(sources), 8), "dropped": max(0, len(sources) - 8)}) if domain == "router_docs" and ("checklist" in low) and ("install" in low or "quick-start" in low or "quick start" in low): return _final(list(sources)[:8], {"kept": min(len(sources), 8), "dropped": max(0, len(sources) - 8)}) if domain == "router_docs" and any( h in low for h in ( "full routers vs adapters", "full router vs adapter", "device classes", "device class", "security check", "activation verification", "order totals", "shipping charges", "allowed email domains", "auth0", "network slicing", "hard timeout", ) ): return _final(list(sources)[:10], {"kept": min(len(sources), 10), "dropped": max(0, len(sources) - 10)}) if domain == "pots" and ( ("all providers" in low) or ("providers we have" in low) or ("provider coverage" in low) or ("coverage gap" in low) or ("evidence is thin" in low) or ("thin evidence" in low) or ("provider-selection framework" in low) or ("provider selection framework" in low) or (("framework" in low) and ("provider" in low)) ): return _final(list(sources)[:20], {"kept": min(len(sources), 20), "dropped": max(0, len(sources) - 20)}) if domain == "pots" and any(h in low for h in ("playbook", "migration", "copper sunset", "phased")): return _final(list(sources)[:8], {"kept": min(len(sources), 8), "dropped": max(0, len(sources) - 8)}) if domain == "pots" and any(h in low for h in ("objection", "top 10", "top ten", "stakeholder")): return _final(list(sources)[:8], {"kept": min(len(sources), 8), "dropped": max(0, len(sources) - 8)}) if domain == "pots" and ("airdial" in low) and ("compliance" in low or "position" in low): return _final(list(sources)[:8], {"kept": min(len(sources), 8), "dropped": max(0, len(sources) - 8)}) if domain == "masters" and (("ifax" in low) or ("securefax" in low)): return _final(list(sources)[:8], {"kept": min(len(sources), 8), "dropped": max(0, len(sources) - 8)}) q_tokens = set(_text_tokens(message)) model_tokens = {_compact_model(t) for t in self._extract_router_models_cached(message)} scored: List[Tuple[float, Dict[str, Any]]] = [] for src in sources: excerpt = str(src.get("excerpt") or "") doc = str(src.get("doc") or "") path = str(src.get("relative_path") or "") blob = f"{doc} {path} {excerpt}".strip() blob_tokens = set(_text_tokens(blob)) overlap = len(q_tokens.intersection(blob_tokens)) compact_blob = _compact_model(blob) model_bonus = 2 if any(tok and tok in compact_blob for tok in model_tokens) else 0 source_score = float(src.get("score") or 0.0) rel = float(overlap) + float(model_bonus) + min(1.0, max(0.0, source_score)) scored.append((rel, src)) scored.sort(key=lambda x: x[0], reverse=True) kept = [src for rel, src in scored if rel >= 1.0] multi_source_intent = _contains_any( message, ("compare", "comparing", "comparison", "table", "matrix", "playbook", "checklist", "outline", "weighted", "vs", "versus"), ) min_multi_keep = 3 if (multi_source_intent and domain in {"masters", "pots"}) else 2 if multi_source_intent else 1 if not kept and scored: keep_n = min(min_multi_keep, len(scored)) kept = [src for _, src in scored[:keep_n]] elif multi_source_intent and len(kept) < min_multi_keep and len(scored) >= min_multi_keep: # Preserve additional citations for multi-step/table outputs. for _, src in scored: if src in kept: continue kept.append(src) if len(kept) >= min_multi_keep: break return _final(kept, {"kept": len(kept), "dropped": max(0, len(sources) - len(kept))}) def _citation_quality_gate(self, message: str, domain: str, sources: Sequence[Dict[str, Any]]) -> Dict[str, Any]: low = str(message or "").lower() required_min = 0 required_meaningful_min = 0 if domain == "knowledgebase" and self._solution_planning_profile(message).get("enabled"): required_min = 2 required_meaningful_min = 2 if domain == "router_docs": required_min = 2 if _contains_any(low, ("compare", "comparison", "table", "chart", "vs", "versus")) else 1 required_meaningful_min = required_min elif domain == "router_lifecycle": required_min = 2 if _contains_any(low, ("compare", "comparison", "table", "chart", "vs", "versus", "replacement", "eos", "eol")) else 1 required_meaningful_min = 1 if required_min > 0 else 0 elif domain in {"pots", "masters"}: required_min = 1 if _contains_any(low, ("compare", "summary", "summarize", "table", "differences", "providers")) else 0 required_meaningful_min = 1 if required_min > 0 else 0 actual = int(len(sources or [])) meaningful = 0 for src in sources or []: if not isinstance(src, dict): continue if not self._is_low_value_source_excerpt(str(src.get("excerpt") or "")): meaningful += 1 passed = bool((actual >= required_min) and (meaningful >= required_meaningful_min)) return { "required_min": int(required_min), "required_meaningful_min": int(required_meaningful_min), "actual": int(actual), "meaningful_actual": int(meaningful), "pass": passed, } def _needs_strict_citation(self, message: str, domain: str) -> bool: if domain not in {"router_docs", "router_lifecycle", "pots", "masters"}: return False low = str(message or "").lower() return _contains_any(low, _HIGH_RISK_SPEC_HINTS) def _model_resolution_confidence(self, message: str, domain: str) -> float: models = self._extract_router_models_cached(message) if not models: return 0.0 norm_models: List[str] = [] for m in models: cm = _compact_model(m) if cm and cm not in norm_models: norm_models.append(cm) if not norm_models: return 0.0 resolved: List[str] = [] strong = 0 for m in norm_models: key = self._lookup_router_fact_key(m) or self._lookup_router_lifecycle_key_relaxed(m) or self._normalize_router_model(m) ck = _compact_model(key) if not ck: continue if ck not in resolved: resolved.append(ck) if (not ck.isdigit()) and (len(ck) > 3): strong += 1 base = float(len(resolved)) / float(max(1, len(norm_models))) if strong > 0: base = min(1.0, base + 0.08) if domain == "router_lifecycle" and resolved: base = min(1.0, base + 0.04) return max(0.0, min(1.0, base)) def _is_answer_seeking_intent(self, low: str) -> bool: if any(h in low for h in ("if ambiguous", "ask a clarifying", "clarifying question")): return False if _contains_any(low, _ANSWER_SEEKING_HINTS): return True words = re.findall(r"[a-z0-9]+", low) return len(words) <= 7 and ("?" in low) def _query_prefers_authoritative_evidence(self, message: str, domain: str) -> bool: if shared_question_prefers_authoritative_evidence(message, domain): return True low = _normalize_router_query_text(message) if not self._is_answer_seeking_intent(low): return False dom = _norm_mode(domain) if dom == "router_docs" and _contains_any(low, _ROUTER_EVIDENCE_FIRST_HINTS): return True return False def _meaningful_source_count(self, sources: Sequence[Any]) -> int: meaningful = 0 for src in sources or []: if not isinstance(src, dict): continue if not self._is_low_value_source_excerpt(str(src.get("excerpt") or "")): meaningful += 1 return meaningful def _query_family_profile(self, message: str, domain: str) -> Dict[str, Any]: low = _normalize_router_query_text(message) dom = _norm_mode(domain) planning_profile = self._solution_planning_profile(message) evidence_first = self._query_prefers_authoritative_evidence(message, dom) strict_citation = self._needs_strict_citation(message, dom) compare_like = _contains_any( low, ("compare", "comparison", "vs", "versus", "difference", "differences", "side by side", "side-by-side", "table", "matrix"), ) document_ref = any( x in low for x in ( "document", "documents", "docs", "datasheet", "data sheet", "manual", "whitepaper", "pdf", "source file", "source files", "internal docs", "internal documents", "ranked list of source documents", ) ) numeric_fact = bool(re.search(r"\b\d+(?:\.\d+)?\b", low)) or any( x in low for x in ( "how many", "how much", "maximum", "minimum", "ports", "weight", "weights", "dimensions", "temperature", "temperatures", "bandwidth", "latency", ) ) procedural = any(x in low for x in ("step", "steps", "setup", "configure", "configuration", "workflow", "runbook", "checklist", "playbook", "process")) policy_guardrail = bool( _VERIZON_POLICY_RE.search(message) or _VERIZON_PRICING_RE.search(message) or _OTHER_CARRIER_POLICY_RE.search(message) or _PII_EMPLOYEE_RE.search(message) or _GUARANTEE_RE.search(message) ) source_governance = dom == "masters" and any( x in low for x in ( "cite sources", "cite internal", "citation guidance", "citation checklist", "internal sources", "source format", ) ) operations_guidance = any( x in low for x in ( "auth0", "hugging face", "hf environment", "startup integrity", "stale hashed", "environment variables", "sign and submit", "configuration pricing", "shipping charges", "validate address", "apply suggestion", "helper decide", ) ) family = "general_guidance" budget_profile = "default" prefer_delegate_when_thin = False if policy_guardrail: family = "policy_guardrail" budget_profile = "deterministic_guardrail" elif source_governance: family = "source_governance" budget_profile = "deterministic_guidance" elif operations_guidance: family = "operations_guidance" budget_profile = "deterministic_guidance" elif evidence_first and compare_like: family = "evidence_compare" budget_profile = "retrieval_strict" prefer_delegate_when_thin = True elif evidence_first and (document_ref or numeric_fact or strict_citation): family = "evidence_fact" budget_profile = "retrieval_strict" prefer_delegate_when_thin = True elif bool(planning_profile.get("enabled")): family = "solution_planning" budget_profile = "retrieval_planning" prefer_delegate_when_thin = True elif compare_like and dom in {"router_docs", "masters", "pots"}: family = "compare_guidance" budget_profile = "retrieval_balanced" prefer_delegate_when_thin = True elif document_ref or numeric_fact or strict_citation: family = "evidence_guidance" budget_profile = "retrieval_balanced" prefer_delegate_when_thin = True elif procedural and dom in {"router_docs", "masters", "pots"}: family = "procedural_guidance" budget_profile = "retrieval_balanced" elif dom in {"router_docs", "masters", "pots"}: family = "concept_guidance" budget_profile = "light_concept" if budget_profile == "retrieval_strict": min_meaningful_sources = 2 elif budget_profile == "retrieval_planning": min_meaningful_sources = 2 else: min_meaningful_sources = 1 return { "family": family, "budget_profile": budget_profile, "evidence_first_expected": bool(evidence_first), "strict_citation": bool(strict_citation), "prefer_delegate_when_thin": bool(prefer_delegate_when_thin), "min_meaningful_sources": int(min_meaningful_sources), } def _web_stage_budget_cap_s(self, message: str, domain: str) -> float: base = float(self.web_stage_budget_s_by_domain.get(domain, 3.0)) profile = self._query_family_profile(message, domain) budget_profile = str(profile.get("budget_profile") or "default") family = str(profile.get("family") or "") if budget_profile == "deterministic_guardrail" or family in {"source_governance", "operations_guidance"}: return 0.0 if budget_profile == "retrieval_strict": return min(base, 2.2) if budget_profile == "retrieval_planning": return min(base, 2.0) if budget_profile == "retrieval_balanced": return min(base, 2.8) if budget_profile == "light_concept": return min(base, 1.8) return max(0.0, base) def _build_path_budget_meta(self, message: str, domain: str) -> Dict[str, Any]: profile = self._query_family_profile(message, domain) total_budget_s = self._effective_budget_s(message, domain) return { "query_family": str(profile.get("family") or "general_guidance"), "budget_profile": str(profile.get("budget_profile") or "default"), "query_complexity_bucket": self._query_complexity_bucket(message, domain=domain), "path_budget": { "family": str(profile.get("family") or "general_guidance"), "budget_profile": str(profile.get("budget_profile") or "default"), "total_budget_s": round(float(total_budget_s), 2), "web_stage_budget_s": round(float(self._web_stage_budget_cap_s(message, domain)), 2), "prefer_delegate_when_thin": bool(profile.get("prefer_delegate_when_thin")), "evidence_first_expected": bool(profile.get("evidence_first_expected")), "strict_citation": bool(profile.get("strict_citation")), }, } def _should_defer_fast_path_response( self, message: str, domain: str, assistant: str, sources: Sequence[Any], meta: Optional[Dict[str, Any]] = None, ) -> bool: meta_dict = _as_dict(meta) profile = self._query_family_profile(message, domain) meta_dict.setdefault("query_family", str(profile.get("family") or "general_guidance")) meta_dict.setdefault("budget_profile", str(profile.get("budget_profile") or "default")) meaningful_sources = self._meaningful_source_count(sources) meta_dict["meaningful_source_count"] = int(meaningful_sources) required = int(profile.get("min_meaningful_sources") or 1) if not bool(profile.get("prefer_delegate_when_thin")): return False if bool(meta_dict.get("web_assisted")) or bool(meta_dict.get("llm_assisted")) or bool(meta_dict.get("citation_quorum_not_required")): return False if meaningful_sources >= required: return False retrieval_mode = str(meta_dict.get("retrieval_mode") or "").strip().lower() if (not sources) and (retrieval_mode.endswith("_fast") or retrieval_mode.startswith("deterministic_")): meta_dict["evidence_thin_for_query"] = True meta_dict["fast_path_deferred"] = True meta_dict["fast_path_deferred_reason"] = "family_requires_stronger_evidence" return True if not self._is_internal_weak(assistant, sources, meta_dict): return False meta_dict["evidence_thin_for_query"] = True meta_dict["fast_path_deferred"] = True meta_dict["fast_path_deferred_reason"] = "family_requires_stronger_evidence" return True def _router_catalog_question_needs_documentation(self, message: str) -> bool: low = _normalize_router_query_text(message) model_tokens = extract_router_device_tokens(message) if self._query_prefers_authoritative_evidence(message, "router_docs"): return True if _contains_any(low, _ROUTER_EVIDENCE_FIRST_HINTS): return True cite_sources = any( x in low for x in ( "cite source", "cite sources", "with source", "with sources", "source-backed", "source backed", "cited", ) ) compare_like = self._is_router_compare_like(message) multi_model_spec_request = len(model_tokens) >= 2 and any( x in low for x in ("table", "matrix", "summarize", "summary", "side by side", "side-by-side") ) spec_table_request = compare_like and any(x in low for x in ("table", "matrix", "summarize", "summary")) deep_field_request = any( x in low for x in ( "wan", "lan", "rf", "connector", "connectors", "modem variant", "modem variants", "install caveat", "install caveats", "install note", "install notes", "weight", "weights", "dimension", "dimensions", "temperature", "temperatures", "5g sa", "5g standalone", "standalone", "quick start", "quick-start", "checklist", "antenna family", "antenna families", "fixed antenna", "fixed antennas", ) ) if cite_sources and (_contains_any(low, _ROUTER_SPEC_HINTS) or compare_like or ("5g sa" in low) or ("standalone" in low)): return True if spec_table_request and deep_field_request: return True if multi_model_spec_request and deep_field_request: return True if ("antenna" in low or "antennas" in low) and ( any( x in low for x in ( "family", "families", "fixed antenna", "fixed antennas", "outdoor fixed", ) ) or ( any(x in low for x in ("best", "why")) and any( x in low for x in ( "police vehicle", "police vehicles", "public safety", "vehicle", "outdoor", ) ) ) ): return True if any(x in low for x in ("quick start", "quick-start", "checklist")) and any( x in low for x in ("install", "installation", "docs", "manual", "guide") ): return True if (("5g sa" in low) or ("5g standalone" in low) or ("standalone" in low)) and any( x in low for x in ("recommend", "recommended", "best fit", "best-fit", "suggest", "shortlist") ): return True return any( x in low for x in ( "documented specs only", "from documented specs only", "docs only", "internal docs only", "internal sources only", "manual", "datasheet", "data sheet", "whitepaper", "pdf", ) ) def _route_quality_flags( self, message: str, domain: str, retrieval_mode: str, meta: Optional[Dict[str, Any]] = None, ) -> List[str]: flags: List[str] = [] if self._solution_planning_profile(message).get("enabled"): flags.append("solution_planning_query") if self._query_prefers_authoritative_evidence(message, domain): flags.append("evidence_first_expected") low = _normalize_router_query_text(message) if "document" in low or "docs" in low or "datasheet" in low or "manual" in low or "whitepaper" in low: flags.append("document_reference") if re.search(r"\b\d+(?:\.\d+)?\b", low) or any(x in low for x in ("how many", "how much", "maximum", "minimum")): flags.append("numeric_fact_query") if any(x in low for x in ("step", "steps", "setup", "configure", "configuration", "procedure", "workflow")): flags.append("procedural_query") if self._needs_strict_citation(message, domain): flags.append("strict_citation") mode = str(retrieval_mode or "").strip().lower() if ("evidence_first_expected" in flags) and mode.startswith("faq_fast_clarify"): flags.append("clarify_on_answerable_query") if ("evidence_first_expected" in flags) and mode.endswith("_concept_fast"): flags.append("concept_fast_on_specific_fact_query") if bool(_as_dict(meta).get("evidence_thin_for_query")): flags.append("evidence_thin_for_query") if bool(_as_dict(meta).get("fast_path_deferred")): flags.append("family_fast_deferred") return list(dict.fromkeys(flags)) def _should_retry_web_after_fast_path( self, message: str, domain: str, retrieval_mode: str, meta: Optional[Dict[str, Any]] = None, ) -> bool: mode = str(retrieval_mode or "").strip().lower() if not self._allow_web_fallback(message, domain): return False if bool(_as_dict(meta).get("web_assisted")): return False if bool(_as_dict(meta).get("evidence_thin_for_query")): return True if self._query_prefers_authoritative_evidence(message, domain): if mode in { "deterministic_rapid_router_catalog_list_fast", "deterministic_rapid_router_catalog_compare_fast", "deterministic_rapid_router_catalog_price_fast", }: return False return True return not (mode.startswith("deterministic_") or mode.endswith("_fast")) def _should_bypass_model_clarification(self, message: str, domain: str) -> bool: if (not self.clarify_bypass_high_confidence_enabled) or domain not in {"router_docs", "router_lifecycle"}: return False low = _normalize_router_query_text(message) if any(x in low for x in ("rx50", "ex50")): return False if not self._is_answer_seeking_intent(low): return False confidence = self._model_resolution_confidence(message, domain) if confidence < float(self.clarify_bypass_min_confidence): return False models = self._extract_router_models_cached(message) if not models: return False return any( bool(self._lookup_router_fact_key(m) or self._lookup_router_lifecycle_key_relaxed(m)) for m in models ) def _is_named_model_spec_request_needing_clarification(self, message: str, domain: str) -> bool: if domain != "router_docs": return False low = _normalize_router_query_text(message) if not self._is_answer_seeking_intent(low): return False if ("antenna" in low or "antennas" in low) and any( _contains_term(low, hint) for hint in ( "recommend", "recommended", "option", "options", "best", "fit", "fits", "vehicle", ) ): return False if not any( _contains_term(low, hint) for hint in ( "spec", "specs", "specification", "specifications", "datasheet", "manual", "details", "wan", "lan", "ports", "antenna", "antennas", "modem", "wifi", "throughput", "battery", "ruggedization", "msrp", "price", "pricing", "cost", ) ): return False models = self._extract_router_models_cached(message) if not models: return False unresolved_named_models: List[str] = [] for model in models: compact = _compact_model(model) if (not compact) or compact.isdigit(): continue if (not any(ch.isalpha() for ch in compact)) or (not any(ch.isdigit() for ch in compact)): continue if self._lookup_router_fact_key(model) or self._lookup_router_lifecycle_key_relaxed(model): return False if len(compact) >= 4: unresolved_named_models.append(compact) return bool(unresolved_named_models) def _needs_model_clarification(self, message: str, domain: str) -> bool: if domain not in {"router_docs", "router_lifecycle"}: return False low = _normalize_router_query_text(message) if ( ("how should i ask" in low or "how do i ask" in low or "ask for" in low) and ("model comparison" in low or "comparison table" in low) and ("clean table output" in low or "clean table" in low) ): return False if any( x in low for x in ( "pots replacement", "pots migration", "porting requirements", "keep number", "keep-number", "line inventory", "alarm panel", "elevator line", "fire panel", ) ): return False if ("rj11" in low) and ("replacement" in low): return False if any( x in low for x in ( "create a csv", "csv-style list", "which router models", "show routers with", "identify models where", "which verizon gateways", "for peplink models", "mapped replacement from overlay", "provider recommendation framework", "implementation plan", "minimum data", "assumptions", "if any verizon gateway model is missing eos", "if any verizon gateway model is missing eol", "verizon gateway model is missing eos", "verizon gateway model is missing eol", "include parsec msrp and source file", "include parsec msrp", "when recommending antennas", "recommend antennas, include parsec", ) ): return False if _contains_any(low, _ROUTER_CONCEPT_COMPARE_HINTS): # Concept asks (SpeedFusion/InControl2/Wi-Fi generation) do not require a device SKU. if not self._extract_router_models_cached(message): return False if ("rx50" in low) and ("ex50" in low): return False if domain == "router_lifecycle" and any( x in low for x in ("multi-customer", "breakdown", "table", "migration", "risk ranking", "phased", "portfolio", "overlay") ): return False if (not self._extract_router_models_cached(message)) and any( x in low for x in ( "how do i describe", "how should i describe", "what should i check", "common pitfalls", "first-pass router selection", "first pass router selection", "without overpromising", "non-technical buyers", "how should i ask", "clean table output", "wan vs lan", "5g sa and 5g nsa", "4g router instead of a 5g", "what does poe", "what does esim", "what does e sim", ) ): return False models = self._extract_router_models_cached(message) lifecycle_lookup = self._lookup_router_lifecycle_key_relaxed if domain == "router_lifecycle" else self._lookup_router_lifecycle_key known_models = [ m for m in models if lifecycle_lookup(m) or self._lookup_router_fact_key(m) ] strong_known = [ m for m in known_models if (not _compact_model(m).isdigit()) and (len(_compact_model(m)) > 3) ] if self._should_bypass_model_clarification(message, domain): return False if self._is_named_model_spec_request_needing_clarification(message, domain): return True if domain == "router_lifecycle": requested_variant, suggested_variant = self._single_unresolved_lifecycle_variant_candidate(message) if requested_variant and suggested_variant: return True if ( len(models) == 1 and (not known_models) and (len(self._extract_conversational_fleet_items(message)) <= 1) and _contains_any(low, _ROUTER_STATUS_HINTS + _ROUTER_REPLACEMENT_HINTS + _ROUTER_LIFECYCLE_HINTS) ): return True if any(term in low for term in _AMBIGUOUS_MODEL_TERMS): if not strong_known: if any( x in low for x in ( "which", "list", "show", "identify", "table", "abstain", "if unknown", "if not documented", "side by side", "side-by-side", "models", "gateways", "routers", ) ): return False return True intent_requires_model = _contains_any( low, ( "compare", "vs", "versus", "replacement", "replacements", "eos", "eol", "end of life", "end of sale", ), ) if not intent_requires_model: return False if not models: if any(x in low for x in ("router", "routers", "model", "models", "gateway", "gateways", "inventory", "catalog", "overlay")): return False return True # Numeric-like fragments are too ambiguous (e.g., only "228"). compact = [_compact_model(m) for m in models if _compact_model(m)] if compact and all(x.isdigit() or len(x) <= 3 for x in compact) and (not strong_known): return True return False def _single_unresolved_model_variant_candidate(self, message: str) -> Tuple[str, str]: models = [str(x).strip() for x in self._extract_router_models_cached(message) if str(x).strip()] if len(models) != 1: return ("", "") requested = _compact_model(models[0]) if (not requested) or (not any(ch.isalpha() for ch in requested)) or (not any(ch.isdigit() for ch in requested)): return ("", "") if self._lookup_router_lifecycle_key(requested) or self._lookup_router_lifecycle_key_relaxed(requested) or self._lookup_router_fact_key(requested): return ("", "") suggested = self._router_workbook_likely_typo_candidate(requested) if not suggested: return ("", "") return (requested, suggested) def _single_unresolved_lifecycle_variant_candidate(self, message: str) -> Tuple[str, str]: low = _normalize_router_query_text(message) if not _contains_any(low, _ROUTER_STATUS_HINTS + _ROUTER_REPLACEMENT_HINTS + _ROUTER_LIFECYCLE_HINTS): return ("", "") return self._single_unresolved_model_variant_candidate(message) def _clarify_turn_count(self, st: UnifiedKnowledgebaseState, clarify_type: str, domain: str) -> int: pending = _as_dict(st.pending) if str(pending.get("type") or "") != str(clarify_type): return 0 if str(pending.get("domain") or "") != str(domain): return 0 try: return max(0, int(pending.get("count", 0) or 0)) except Exception: return 0 def _set_clarify_pending( self, st: UnifiedKnowledgebaseState, clarify_type: str, domain: str, *, message: str = "", ) -> int: next_turn = self._clarify_turn_count(st, clarify_type, domain) + 1 st.pending = { "type": str(clarify_type), "domain": str(domain), "count": int(next_turn), "original_message": _norm(message), } return int(next_turn) def _rewrite_router_workbook_followup(self, reply: str, pending: Dict[str, Any]) -> str: text = _norm(reply) if not text: return "" if len(re.findall(r"[a-z0-9]+", text.lower())) > 8: return "" original_message = _norm(pending.get("original_message", "")) if not original_message: return "" return f"{original_message} {text}".strip() def _parse_router_workbook_survey_followup_reply( self, reply: str, pending: Dict[str, Any], ) -> Dict[str, Any]: text = _norm(reply) if not text: return {"ok": False, "pass_through": False} requirements = [item for item in list(pending.get("requirements") or []) if isinstance(item, dict)] required_fields = [str(item.get("field") or "").strip() for item in requirements if str(item.get("field") or "").strip()] word_count = len(re.findall(r"[a-z0-9]+", text.lower())) low = text.lower() normalized = _normalize_router_query_text(text) header_updates: Dict[str, Any] = {} restriction_updates: Dict[str, Any] = {} point_updates: Dict[str, Dict[str, Any]] = {} applied_fields: List[str] = [] def _record(field_name: str, *, header: Dict[str, Any] | None = None, restriction: Dict[str, Any] | None = None) -> None: if header: header_updates.update(header) if restriction: restriction_updates.update(restriction) if field_name not in applied_fields: applied_fields.append(field_name) def _record_point(point_id: str, score: str) -> None: pid = str(point_id or "").strip().upper() raw_score = str(score or "").strip() if not pid or not raw_score: return point_updates[pid] = {"score": raw_score} field_name = f"point:{pid}" if field_name not in applied_fields: applied_fields.append(field_name) def _yes_no_value(fragment: str) -> str | None: frag = str(fragment or "").strip().lower() if not frag: return None if any( token in frag for token in ( "not allowed", "is not allowed", "isn't allowed", "blocked", "cannot", "can't", "banned", "prohibited", "forbidden", "not permitted", "disallowed", "not on the roof", "no ", "nope", ) ): return "No" if any(token in frag for token in ("allowed", "available", "yes", "yep", "yeah", "ok", "okay", "can ", "is available")): return "Yes" if frag in {"yes", "y"}: return "Yes" if frag in {"no", "n"}: return "No" return None def _yes_no_for_patterns(*patterns: str) -> str | None: def _clause_fragment(start: int, end: int) -> str: left_candidates = [low.rfind(token, 0, start) for token in (",", ";", ".", "?", "!", " but ", " and ")] left = max(left_candidates) if left >= 0: left_token = next( (token for token in (" but ", " and ", ",", ";", ".", "?", "!") if low.rfind(token, 0, start) == left), "", ) left += len(left_token) else: left = max(0, start - 18) right_candidates = [idx for idx in (low.find(token, end) for token in (",", ";", ".", "?", "!", " but ", " and ")) if idx >= 0] right = min(right_candidates) if right_candidates else min(len(low), end + 40) return low[left:right].strip() for pattern in patterns: for match in re.finditer(pattern, low, flags=re.IGNORECASE): start, end = match.span() fragment = _clause_fragment(start, end) value = _yes_no_value(fragment) if value: return value return None def _distance_value(kind: str) -> str | None: kind = str(kind or "").strip().lower() if not kind: return None patterns = [ rf"(?:max(?:imum)?\s+)?{re.escape(kind)}(?:\s+run)?[^0-9]{{0,20}}(\d{{1,4}})\s*(?:ft|feet|foot)?", rf"(\d{{1,4}})\s*(?:ft|feet|foot)?[^a-z0-9]{{0,16}}(?:of\s+)?{re.escape(kind)}(?:\s+run)?", ] for pattern in patterns: match = re.search(pattern, low, flags=re.IGNORECASE) if match: value = str(match.group(1) or "").strip() if value: return value return None def _landlord_restrictions_value() -> str | None: if not any( token in normalized for token in ( "landlord", "hoa", "property manager", "building restriction", "building restrictions", "historic district", "site restriction", "site restrictions", ) ): return None if any( token in normalized for token in ( "no landlord restrictions", "no hoa restrictions", "no building restrictions", "no site restrictions", "no restrictions", "none", ) ): return "None" return text def _product_candidate() -> str: def _looks_like_spurious_model_candidate(value: str) -> bool: compact = _compact_model(value) if not compact: return True upper = compact.upper() if re.fullmatch(r"(?:L|O)\d{1,3}", upper): return True if re.fullmatch(r"(?:IS|WAS|AT|MAX|MIN|RUN|FEET|FOOT|SCORED?)\d{1,4}", upper): return True return False parsed = parse_router_intelligence_query(text) if parsed and parsed.device_texts: candidate = str(parsed.device_texts[0] or "").strip() if candidate and (not _looks_like_spurious_model_candidate(candidate)): return candidate stripped = text.strip("`'\" ").rstrip("?.!,;:") compact = _compact_model(stripped) if _looks_like_spurious_model_candidate(stripped): return "" if word_count <= 4 and compact and any(ch.isalpha() for ch in compact) and any(ch.isdigit() for ch in compact): return stripped return "" def _point_score_value(point_id: str, aliases: tuple[str, ...]) -> str | None: point_token = str(point_id or "").strip().lower() tokens = [ point_token, point_token.replace("l", "l-").replace("o", "o-"), *[str(alias or "").strip().lower() for alias in aliases if str(alias or "").strip()], ] tokens = list(dict.fromkeys(token for token in tokens if token)) if not tokens: return None alias_pattern = "|".join(re.escape(token) for token in tokens) patterns = [ rf"(?:\b(?:{alias_pattern})\b)(?:\s+(?:score|scored|is|was|at|=|came in at))?[^0-9]{{0,16}}(-?\d{{1,3}}(?:\.\d+)?)", rf"(-?\d{{1,3}}(?:\.\d+)?)\s*(?:for|at|on)?\s*(?:\b(?:{alias_pattern})\b)", ] for pattern in patterns: match = re.search(pattern, low, flags=re.IGNORECASE) if match: value = str(match.group(1) or "").strip() if value: try: numeric = float(value) except Exception: continue if numeric < 0 or numeric > 100: continue return value return None if any( token in normalized for token in ( "overall hardware", "overall recommendation", "overall recommendations", "suggest overall", "suggest hardware", "hardware suggestions", "fresh recommendation", ) ): _record( "customer_mode", header={"customer_mode": "Suggest overall hardware"}, restriction={"customer_mode": "Suggest overall hardware"}, ) elif any( token in normalized for token in ( "improve selected hardware", "keep selected hardware", "selected hardware only", "improve current hardware", "improve selected router", "keep selected router", "only improve this router", ) ): _record( "customer_mode", header={"customer_mode": "Improve selected hardware"}, restriction={"customer_mode": "Improve selected hardware"}, ) if any(token in normalized for token in ("not locked", "not selected", "no hardware selected", "nothing selected", "open to recommendations")): _record( "selected_hardware", header={"selected_hardware_locked_flag": "No", "selected_hardware_text": ""}, restriction={"selected_hardware_locked_flag": "No", "selected_hardware_text": ""}, ) else: candidate = _product_candidate() if candidate and ( any(token in normalized for token in ("locked", "selected", "router is", "model is", "using", "current router")) or ("selected_hardware" in required_fields and word_count <= 8) ): _record( "selected_hardware", header={"selected_hardware_locked_flag": "Yes", "selected_hardware_text": candidate}, restriction={"selected_hardware_locked_flag": "Yes", "selected_hardware_text": candidate}, ) exterior_mount_yes_no = _yes_no_for_patterns(r"(?:exterior|outside|outdoor)\s+mount(?:ing)?", r"mount(?:ing)?\s+(?:outside|outdoor)") if exterior_mount_yes_no: _record("exterior_mount_allowed", restriction={"exterior_mount_allowed": exterior_mount_yes_no}) wall_penetration_yes_no = _yes_no_for_patterns( r"wall\s+penetration", r"roof\s+penetration", r"wall\s+or\s+roof", r"drill\s+through", r"\bpenetration\b", ) if wall_penetration_yes_no: _record("wall_penetration_allowed", restriction={"wall_penetration_allowed": wall_penetration_yes_no}) roof_mount_yes_no = _yes_no_for_patterns(r"roof\s+mount(?:ing)?", r"on\s+the\s+roof") if roof_mount_yes_no: _record("roof_mount_allowed", restriction={"roof_mount_allowed": roof_mount_yes_no}) poe_yes_no = _yes_no_for_patterns(r"\bpoe\b", r"power\s+over\s+ethernet") if poe_yes_no: _record("poe_available", restriction={"poe_available": poe_yes_no}) ethernet_run = _distance_value("ethernet") if ethernet_run: _record("max_ethernet_run_ft", restriction={"max_ethernet_run_ft": ethernet_run}) coax_run = _distance_value("coax") if coax_run: _record("max_coax_run_ft", restriction={"max_coax_run_ft": coax_run}) landlord_restrictions = _landlord_restrictions_value() if landlord_restrictions: _record("landlord_restrictions", restriction={"landlord_restrictions": landlord_restrictions}) for point_id, aliases in ( ("L9", ("closet score", "network closet", "closet", "closet point", "inside closet")), ("L10", ("near closet", "outside closet", "near-closet", "outside the closet", "better indoor point", "best indoor point")), ("O2", ("entry outdoor", "practical outdoor", "outdoor entry", "closet wall", "entry outdoor point", "first outdoor point")), ("O3", ("best clear outdoor", "best outdoor", "clear outdoor", "clear outdoor point", "best outside point")), ): point_score = _point_score_value(point_id, aliases) if point_score: _record_point(point_id, point_score) overall_yes_no = _yes_no_value(low) if overall_yes_no and word_count <= 3 and required_fields: first_field = required_fields[0] if first_field == "selected_hardware" and overall_yes_no == "No": _record( "selected_hardware", header={"selected_hardware_locked_flag": "No", "selected_hardware_text": ""}, restriction={"selected_hardware_locked_flag": "No", "selected_hardware_text": ""}, ) elif first_field == "customer_mode": pass elif first_field.startswith("point:"): pass elif first_field: _record(first_field, restriction={first_field: overall_yes_no}) if word_count <= 8 and required_fields: first_field = required_fields[0] if first_field == "max_ethernet_run_ft" and not ethernet_run: fallback_match = re.search(r"\b(\d{1,4})\b", low) if fallback_match: _record("max_ethernet_run_ft", restriction={"max_ethernet_run_ft": str(fallback_match.group(1) or "").strip()}) if first_field == "max_coax_run_ft" and not coax_run: fallback_match = re.search(r"\b(\d{1,4})\b", low) if fallback_match: _record("max_coax_run_ft", restriction={"max_coax_run_ft": str(fallback_match.group(1) or "").strip()}) if first_field == "landlord_restrictions" and low in {"none", "no restrictions"}: _record("landlord_restrictions", restriction={"landlord_restrictions": "None"}) if first_field.startswith("point:"): fallback_match = re.search(r"-?\d{1,3}(?:\.\d+)?", low) if fallback_match: _record_point(first_field.split(":", 1)[1], str(fallback_match.group(0) or "").strip()) pass_through = bool((not applied_fields) and word_count > 8 and parse_router_intelligence_query(text)) return { "ok": bool(applied_fields), "pass_through": pass_through, "applied_fields": applied_fields, "header_updates": header_updates, "restriction_updates": restriction_updates, "point_updates": point_updates, } def _handle_router_workbook_survey_followup( self, reply: str, st: UnifiedKnowledgebaseState, pending: Dict[str, Any], *, mode: str, audience: str, show_citations: bool, ) -> Optional[Dict[str, Any]]: core = self._rapid_router_intelligence_core() if core is None: return None parsed = self._parse_router_workbook_survey_followup_reply(reply, pending) if parsed.get("pass_through"): return None survey_key = str(pending.get("survey_key") or _as_dict(st.router_lifecycle_state).get("last_survey_key") or "").strip() if not survey_key: return None domain = _norm_mode(mode) or "router_lifecycle" workbook_file = str(self._rapid_router_intelligence_status().get("filename") or "router_workbook.xlsx") workbook_sources = self._router_workbook_sources(domain, "survey") if not parsed.get("ok"): prompts = [ str(item.get("prompt") or "").strip() for item in list(pending.get("requirements") or []) if isinstance(item, dict) and str(item.get("prompt") or "").strip() ] return { "assistant": _format_shell( "I still need one of the pending workbook survey follow-up details before I can re-evaluate the placement path.", [ "Survey follow-up answers in Unified KB only update the active workbook survey when I can map them to a specific restriction or selected-hardware field.", ], prompts[:3] or ["Reply with one concrete survey restriction or the exact selected router model."], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": self._normalize_sources(domain, workbook_sources), "files": self._normalize_domain_files(domain, [workbook_file]), "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "deterministic_router_workbook_survey_followup_needed", "router_intelligence_intent": "survey", "router_intelligence_source": "workbook", "review_required": True, "survey_followup_needed": True, "survey_followup_remaining_count": len(list(pending.get("requirements") or [])), "survey_followup_remaining_requirements": list(pending.get("requirements") or []), "citation_quorum_not_required": True, "legacy_csv_replaced": True, "survey_key": survey_key, }, } update_out = core.update_catalog_survey_followup( survey_key=survey_key, restriction_updates=_as_dict(parsed.get("restriction_updates")), header_updates=_as_dict(parsed.get("header_updates")), point_updates=_as_dict(parsed.get("point_updates")), ) if not update_out.get("ok"): return { "assistant": _format_shell( str(update_out.get("message") or "I could not apply those survey follow-up answers to the workbook runtime rows."), [ "Survey follow-up updates are only applied when the active workbook survey row is still available.", ], [ "Reply with the survey key/site name again, or reload the survey in Rapid Router if needed.", ], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": self._normalize_sources(domain, workbook_sources), "files": self._normalize_domain_files(domain, [workbook_file]), "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "deterministic_router_workbook_survey_followup_update_failed", "router_intelligence_intent": "survey", "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, "survey_key": survey_key, }, } st.pending = {} st.router_lifecycle_state = { **_as_dict(st.router_lifecycle_state), "last_survey_key": survey_key, "pending": {}, } followup_message = str(pending.get("original_message") or "").strip() or str(reply or "").strip() result = self.handle_message( followup_message, st.to_dict(), mode=domain, audience=audience, show_citations=show_citations, ) def _survey_update_label(field_name: str) -> str: labels = { "customer_mode": "Customer mode", "selected_hardware": "Selected hardware", "poe_available": "PoE available", "exterior_mount_allowed": "Exterior mount allowed", "wall_penetration_allowed": "Wall/roof penetration allowed", "roof_mount_allowed": "Roof mount allowed", "max_ethernet_run_ft": "Max Ethernet run", "max_coax_run_ft": "Max coax run", "landlord_restrictions": "Landlord/building restrictions", "point:L9": "Closet point (L9)", "point:L10": "Near-closet point (L10)", "point:O2": "Entry outdoor point (O2)", "point:O3": "Best clear outdoor point (O3)", } return labels.get(str(field_name or "").strip(), str(field_name or "").strip()) def _survey_update_value(field_name: str) -> str: headers = _as_dict(update_out.get("applied_header_updates")) restrictions = _as_dict(update_out.get("applied_restriction_updates")) points = _as_dict(update_out.get("applied_point_updates")) field_name = str(field_name or "").strip() if field_name == "selected_hardware": locked = str(headers.get("selected_hardware_locked_flag") or restrictions.get("selected_hardware_locked_flag") or "").strip() model = str(headers.get("selected_hardware_text") or restrictions.get("selected_hardware_text") or "").strip() if locked == "No": return "Not locked" if model: return f"Locked to {model}" return locked or "Updated" if field_name.startswith("point:"): point_id = field_name.split(":", 1)[1] point_row = _as_dict(points.get(point_id)) raw = str(point_row.get("score") or "").strip() return raw or "Updated" if field_name in {"max_ethernet_run_ft", "max_coax_run_ft"}: raw = str(restrictions.get(field_name) or headers.get(field_name) or "").strip() return f"{raw} ft" if raw else "Updated" return str(restrictions.get(field_name) or headers.get(field_name) or "").strip() or "Updated" meta = _as_dict(result.get("meta")) applied_fields = list(parsed.get("applied_fields") or []) meta["survey_followup_applied_fields"] = applied_fields meta["survey_followup_update_count"] = len(applied_fields) meta["survey_followup_applied_updates"] = [ { "field": field_name, "label": _survey_update_label(field_name), "value": _survey_update_value(field_name), } for field_name in applied_fields ] result["meta"] = meta return result def _router_workbook_guided_advisor_questions(self) -> List[Dict[str, str]]: return [ { "field": "deployment_type", "prompt": "What deployment best fits this project: vehicle/public safety, branch/indoor, fixed outdoor, or mixed/unsure?", }, { "field": "rugged_required", "prompt": "Do you need ruggedized hardware: yes, no, or preferred but not required?", }, { "field": "min_total_ethernet_ports", "prompt": "What is the minimum total Ethernet port count you need?", }, { "field": "must_have_features", "prompt": "Which features are must-haves: Wi-Fi, GNSS/GPS, battery, PoE, or none?", }, { "field": "manufacturer_preference", "prompt": "Do you have a manufacturer preference, or should I stay vendor-neutral?", }, ] def _start_router_workbook_guided_advisor( self, query: RouterIntelligenceQuery, st: UnifiedKnowledgebaseState, domain: str, ) -> Dict[str, Any]: questions = self._router_workbook_guided_advisor_questions() workbook_file = str(self._rapid_router_intelligence_status().get("filename") or "router_workbook.xlsx") workbook_sources = self._router_workbook_sources(domain, "guided_advisor") st.pending = { "type": "router_workbook_guided_advisor", "domain": str(domain or "router_lifecycle"), "original_message": str(query.raw_message or "").strip(), "question_index": 0, "answers": {}, } st.router_lifecycle_state = { **_as_dict(st.router_lifecycle_state), "guided_advisor_answers": {}, "pending": _as_dict(st.pending), } return { "assistant": _format_shell( "\n".join( [ "I’ll use this five-question workbook intake and then rank up to five current router and antenna suggestions.", "", *[ f"Question {idx} of {len(questions)}: {question['prompt']}" for idx, question in enumerate(questions, start=1) ], ] ), [ "The final shortlist will stay workbook-backed and current-only unless you explicitly ask for legacy devices.", "GPT is only helping with question handling here; it will not override workbook facts or recommendation lanes.", ], ["Reply with the answer to Question 1 first, and I’ll keep the remaining four workbook questions queued in order."], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": self._normalize_sources(domain, workbook_sources), "files": self._normalize_domain_files(domain, [workbook_file]), "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "deterministic_router_workbook_guided_advisor_question", "router_intelligence_intent": "guided_advisor", "router_intelligence_source": "workbook", "guided_advisor_pending": True, "guided_advisor_question_index": 1, "guided_advisor_total_questions": len(questions), "router_workbook_tables": self._router_workbook_source_tables("guided_advisor"), "citation_quorum_not_required": True, "legacy_csv_replaced": True, }, } def _parse_router_workbook_guided_advisor_reply( self, reply: str, pending: Dict[str, Any], ) -> Dict[str, Any]: text = _norm(reply) if not text: return {"ok": False} questions = self._router_workbook_guided_advisor_questions() try: question_index = max(0, int(pending.get("question_index") or 0)) except Exception: question_index = 0 if question_index >= len(questions): return {"ok": False} field_name = str(questions[question_index]["field"] or "").strip() low = _normalize_router_query_text(text) number_words = { "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9, "ten": 10, } def _int_from_text(raw_text: str) -> Optional[int]: match = re.search(r"\b(\d{1,2})\b", raw_text) if match: try: return int(match.group(1)) except Exception: return None for word, value in number_words.items(): if re.search(rf"\b{re.escape(word)}\b", raw_text): return value return None if field_name == "deployment_type": if any(token in low for token in ("mixed", "unsure", "not sure", "unknown", "either")) or ( any(token in low for token in ("indoor", "inside", "branch", "office", "store")) and any(token in low for token in ("fixed outdoor", "outdoor", "outside", "exterior")) ): return {"ok": True, "field": field_name, "value": "mixed_unsure", "label": "Mixed / unsure"} if any(token in low for token in ("vehicle", "public safety", "police", "patrol car", "fleet vehicle")): return {"ok": True, "field": field_name, "value": "vehicle_public_safety", "label": "Vehicle / public safety"} if any(token in low for token in ("fixed outdoor", "outdoor", "outside", "exterior")): return {"ok": True, "field": field_name, "value": "fixed_outdoor", "label": "Fixed outdoor"} if any(token in low for token in ("branch", "indoor", "office", "store", "inside")): return {"ok": True, "field": field_name, "value": "branch_indoor", "label": "Branch / indoor"} return {"ok": False} if field_name == "rugged_required": if any(token in low for token in ("preferred", "prefer", "nice to have")): return {"ok": True, "field": field_name, "value": "Preferred", "label": "Preferred but not required"} if any(token in low for token in ("no", "not required", "vendor neutral", "does not need rugged", "don't need rugged", "do not need rugged")): return {"ok": True, "field": field_name, "value": "No", "label": "Rugged not required"} if any(token in low for token in ("yes", "need rugged", "must be rugged", "rugged required", "ruggedized", "rugged")): return {"ok": True, "field": field_name, "value": "Yes", "label": "Rugged required"} return {"ok": False} if field_name == "min_total_ethernet_ports": value = _int_from_text(low) if value is None: return {"ok": False} return {"ok": True, "field": field_name, "value": value, "label": f"{value} Ethernet ports minimum"} if field_name == "must_have_features": features: List[str] = [] if any(token in low for token in ("none", "no special", "no must", "no feature")): return {"ok": True, "field": field_name, "value": [], "label": "No special feature must-haves"} if any(token in low for token in ("wi-fi", "wifi")): features.append("wifi") if any(token in low for token in ("gnss", "gps")): features.append("gnss") if "battery" in low: features.append("battery") if "poe" in low or "power over ethernet" in low: features.append("poe") if not features: return {"ok": False} features = list(dict.fromkeys(features)) feature_labels = { "wifi": "Wi-Fi", "gnss": "GNSS/GPS", "battery": "Battery", "poe": "PoE", } return { "ok": True, "field": field_name, "value": features, "label": ", ".join(feature_labels.get(item, item) for item in features), } if field_name == "manufacturer_preference": if any(token in low for token in ("vendor neutral", "no preference", "any manufacturer", "doesn't matter", "does not matter", "no vendor preference")): return {"ok": True, "field": field_name, "value": "", "label": "Vendor-neutral"} raw = text.strip("`'\" ").rstrip("?.!,;:") if raw: return {"ok": True, "field": field_name, "value": raw, "label": raw} return {"ok": False} return {"ok": False} def _router_workbook_guided_advisor_reason_lines( self, candidate: Dict[str, Any], answers: Dict[str, Any], ) -> List[str]: def _flag(value: Any) -> bool: text = str(value or "").strip().lower() return text in {"yes", "true", "1", "y"} features = _as_dict(candidate.get("features")) reasons: List[str] = [] total_ports = int(candidate.get("total_ethernet_ports") or 0) min_ports = int(answers.get("min_total_ethernet_ports") or 0) deployment = str(answers.get("deployment_type") or "") if min_ports and total_ports >= min_ports: reasons.append(f"{total_ports} total Ethernet ports meets your {min_ports}-port minimum") must_have_features = [str(item) for item in list(answers.get("must_have_features") or []) if str(item)] if "wifi" in must_have_features and _flag(features.get("wifi_norm")): reasons.append("Wi-Fi is present") if "gnss" in must_have_features and _flag(features.get("gnss_norm")): reasons.append("GNSS/GPS is present") if "battery" in must_have_features and _flag(features.get("battery_norm")): reasons.append("Battery-backed hardware is present") if "poe" in must_have_features and _flag(features.get("poe_norm")): reasons.append("PoE support is present") if str(answers.get("rugged_required") or "") in {"Yes", "Preferred"} and _flag(features.get("rugged_norm")): reasons.append("Rugged workbook feature row fits the requested environment") if deployment == "vehicle_public_safety" and _flag(features.get("gnss_norm")): reasons.append("GNSS helps vehicle/public-safety installs") if deployment == "branch_indoor" and _flag(features.get("wifi_norm")): reasons.append("Wi-Fi is useful for branch/indoor installs") if deployment == "fixed_outdoor" and _flag(features.get("poe_norm")): reasons.append("PoE can help fixed outdoor deployments") if not reasons: reasons.append("Current workbook-backed device that fits the intake profile without requiring legacy fallback") return reasons[:3] def _build_router_workbook_guided_advisor_recommendations( self, core: Any, answers: Dict[str, Any], ) -> Dict[str, Any]: def _flag(value: Any) -> bool: text = str(value or "").strip().lower() return text in {"yes", "true", "1", "y"} manufacturer_text = str(answers.get("manufacturer_preference") or "").strip() deployment = str(answers.get("deployment_type") or "") rugged_required = str(answers.get("rugged_required") or "") feature_flags = {str(item) for item in list(answers.get("must_have_features") or []) if str(item)} min_total_ethernet_ports = answers.get("min_total_ethernet_ports") try: min_total_ethernet_ports = int(min_total_ethernet_ports) if min_total_ethernet_ports is not None else None except Exception: min_total_ethernet_ports = None search_kwargs: Dict[str, Any] = { "manufacturer_text": manufacturer_text, "rugged": True if rugged_required == "Yes" else None, "battery": True if "battery" in feature_flags else None, "min_total_ethernet_ports": min_total_ethernet_ports, "wifi": True if "wifi" in feature_flags else None, "gnss": True if "gnss" in feature_flags else None, "poe": True if "poe" in feature_flags else None, "indoor_outdoor": "indoor" if deployment == "branch_indoor" else "outdoor" if deployment == "fixed_outdoor" else "", "current_only": True, "limit": 12, } searches: List[Tuple[str, Dict[str, Any]]] = [("strict", search_kwargs)] if manufacturer_text: searches.append(("vendor_neutral_fallback", {**search_kwargs, "manufacturer_text": ""})) if search_kwargs.get("indoor_outdoor"): searches.append(("placement_relaxed_fallback", {**search_kwargs, "indoor_outdoor": ""})) seen_product_keys: set[str] = set() ranked: List[Tuple[int, int, Dict[str, Any]]] = [] fallback_order = {"strict": 0, "vendor_neutral_fallback": 1, "placement_relaxed_fallback": 2} for lane_name, kwargs in searches: search_out = _as_dict(core.search_catalog_devices(**kwargs)) for match in [row for row in list(search_out.get("matches") or []) if isinstance(row, dict)]: product_key = str(match.get("product_key") or "") if not product_key or product_key in seen_product_keys: continue seen_product_keys.add(product_key) features = _as_dict(match.get("features")) score = 0 score += 20 if lane_name == "strict" else 10 if lane_name == "vendor_neutral_fallback" else 6 score += min(int(match.get("total_ethernet_ports") or 0), 8) if rugged_required == "Yes" and _flag(features.get("rugged_norm")): score += 10 elif rugged_required == "Preferred" and _flag(features.get("rugged_norm")): score += 5 if deployment == "vehicle_public_safety": score += 8 if _flag(features.get("gnss_norm")) else 0 score += 6 if _flag(features.get("rugged_norm")) else 0 if deployment == "branch_indoor": score += 6 if _flag(features.get("wifi_norm")) else 0 if deployment == "fixed_outdoor": score += 6 if _flag(features.get("poe_norm")) else 0 score += 4 if _flag(features.get("rugged_norm")) else 0 for feature_name in feature_flags: score += 4 if _flag(features.get(f"{feature_name}_norm")) else 0 ranked.append((score, fallback_order.get(lane_name, 9), {**match, "advisor_lane": lane_name})) ranked.sort(key=lambda item: (-int(item[0]), int(item[1]), str(item[2].get("display_name") or ""))) suggestions: List[Dict[str, Any]] = [] for _, _lane_rank, candidate in ranked[:8]: analysis = _as_dict( core.analyze_catalog_device( manufacturer_text=str(candidate.get("manufacturer_group") or ""), product_text=str(candidate.get("product_id") or candidate.get("display_name") or ""), ) ) antenna = _as_dict(analysis.get("antenna")) flow = _as_dict(antenna.get("flow")) quote = _as_dict(analysis.get("quote")) header = _as_dict(quote.get("header")) suggestions.append( { "candidate": candidate, "analysis": analysis, "reasons": self._router_workbook_guided_advisor_reason_lines(candidate, answers), "antenna_label": _norm(flow.get("bundle_name") or ""), "quote_bom_id": _norm(header.get("quote_bom_id") or ""), "review_required": bool(analysis.get("review_required")), } ) if len(suggestions) >= 5: break review_required = (not suggestions) or any(bool(item.get("review_required")) for item in suggestions) return { "ok": bool(suggestions), "answers": answers, "suggestions": suggestions, "review_required": review_required, "search_count": len(ranked), } def _render_router_workbook_guided_advisor_result(self, recommendation: Dict[str, Any]) -> str: suggestions = [row for row in list(recommendation.get("suggestions") or []) if isinstance(row, dict)] if not suggestions: return "I could not find a current workbook-backed router shortlist that matches all five answers yet." def _candidate_display_name(item: Dict[str, Any]) -> str: if not isinstance(item, dict): return "" for key in ("display_name", "replacement_display", "router_display_name", "product_id", "subject_display_name"): value = _norm(item.get(key) or "") if value: return value return "" lines = [ f"Workbook-backed guided advisor shortlist ({len(suggestions)} suggestion{'s' if len(suggestions) != 1 else ''} shown):", ] if len(suggestions) < 5: lines.append("") lines.append("The workbook returned fewer than five current matches for the stated filters, so I am only showing the current devices that fit cleanly.") for index, item in enumerate(suggestions, start=1): candidate = _as_dict(item.get("candidate")) analysis = _as_dict(item.get("analysis")) reasons = [str(reason) for reason in list(item.get("reasons") or []) if str(reason)] manual_review_reasons = [str(reason) for reason in list(analysis.get("manual_review_reasons") or []) if str(reason)] antenna_label = str(item.get("antenna_label") or "").strip() quote_bom_id = str(item.get("quote_bom_id") or "").strip() lines.extend( [ "", f"### {index}. {_candidate_display_name(candidate) or candidate.get('product_id') or 'Router option'}", f"- Why it fits: {'; '.join(reasons)}.", f"- Antenna path: `{antenna_label}`." if antenna_label else "- Antenna path: workbook review still needed before an auto antenna bundle can be shown.", f"- Quote BOM: `{quote_bom_id}`." if quote_bom_id else "- Quote BOM: no auto quote BOM is attached yet.", f"- Review flag: {'Yes' if bool(item.get('review_required')) else 'No'}.", ] ) if manual_review_reasons: lines.append(f"- Review notes: {'; '.join(manual_review_reasons[:2])}.") return "\n".join(lines) def _router_workbook_guided_advisor_trace( self, answers: Dict[str, Any], recommendation: Dict[str, Any], ) -> Dict[str, Any]: feature_labels = { "wifi": "Wi-Fi", "gnss": "GNSS/GPS", "battery": "Battery", "poe": "PoE", } must_have_features = [ feature_labels.get(str(item), str(item)) for item in list(answers.get("must_have_features") or []) if str(item) ] items = [ {"label": "Deployment", "value": str(answers.get("deployment_type_label") or answers.get("deployment_type") or "").strip()}, {"label": "Rugged", "value": str(answers.get("rugged_required_label") or answers.get("rugged_required") or "").strip()}, {"label": "Min Ethernet ports", "value": str(answers.get("min_total_ethernet_ports") or "").strip()}, {"label": "Must-have features", "value": ", ".join(must_have_features) if must_have_features else "None"}, {"label": "Manufacturer preference", "value": str(answers.get("manufacturer_preference") or "Vendor-neutral").strip()}, {"label": "Suggestions returned", "value": str(len(list(recommendation.get("suggestions") or [])))}, ] return { "summary": f"Five-question workbook advisor completed with {len(list(recommendation.get('suggestions') or []))} current suggestion(s).", "items": [item for item in items if str(item.get("value") or "").strip()], "warnings": [ "Some suggested router paths still carry a workbook review flag." if bool(recommendation.get("review_required")) else "" ], "source_tables": self._router_workbook_source_tables("guided_advisor"), } def _router_workbook_guided_advisor_tradeoffs(self, candidate: Dict[str, Any]) -> List[str]: features = _as_dict(candidate.get("features")) def _flag(value: Any) -> bool: return str(value or "").strip().lower() in {"yes", "true", "1", "y"} tradeoffs: List[str] = [] if not _flag(features.get("battery_norm")): tradeoffs.append("No battery-backed option") if not _flag(features.get("wifi_norm")): tradeoffs.append("No Wi-Fi") if not _flag(features.get("gnss_norm")): tradeoffs.append("No GNSS/GPS") if not _flag(features.get("poe_norm")): tradeoffs.append("No PoE") if not tradeoffs: tradeoffs.append("No obvious workbook tradeoff from the requested intake") return tradeoffs[:3] def _router_workbook_debug_ref( self, *, table_name: str, key: Any, label: str, title: str = "", ) -> Dict[str, Any] | None: table = str(table_name or "").strip() row_key = str(key or "").strip() row_label = str(label or "").strip() if not table or not row_key or not row_label: return None return { "table_name": table, "key": row_key, "label": row_label, "title": str(title or row_label).strip() or row_label, } def _router_workbook_compact_debug_refs(self, refs: Sequence[Dict[str, Any] | None], *, limit: int = 8) -> List[Dict[str, Any]]: out: List[Dict[str, Any]] = [] seen: set[tuple[str, str]] = set() for item in refs: if not isinstance(item, dict): continue table_name = str(item.get("table_name") or "").strip() key = str(item.get("key") or "").strip() if not table_name or not key: continue pair = (table_name, key) if pair in seen: continue seen.add(pair) out.append( { "table_name": table_name, "key": key, "label": str(item.get("label") or "").strip() or f"{table_name} row", "title": str(item.get("title") or item.get("label") or "").strip() or f"{table_name} row", } ) if len(out) >= max(1, int(limit or 1)): break return out def _router_workbook_survey_explanation_view( self, survey_eval: Dict[str, Any], *, survey_followups: Sequence[Dict[str, Any]] = (), ) -> Dict[str, Any]: survey = _as_dict(survey_eval.get("survey")) metrics = _as_dict(survey_eval.get("metrics")) recommendation = _as_dict(survey_eval.get("recommendation")) explainability = _as_dict(survey_eval.get("explainability")) antenna = _as_dict(survey_eval.get("antenna")) complexity = _as_dict(antenna.get("complexity")) flow = _as_dict(antenna.get("flow")) primary_product = _as_dict(recommendation.get("primary_product")) backup_product = _as_dict(recommendation.get("backup_product")) outcome_class = _norm(metrics.get("outcome_class") or "") evidence_grade = _norm(metrics.get("evidence_grade") or "") confidence_label = _norm(metrics.get("confidence_label") or "") def _solution_family_label(value: Any) -> str: mapping = { "router_only": "Router only / no extra hardware", "indoor_poe_reposition": "Indoor PoE / Ethernet reposition", "indoor_adapter_same_mfr": "Indoor same-manufacturer adapter", "indoor_antenna_parsec": "Indoor Parsec antenna path", "outdoor_adapter_same_mfr": "Outdoor same-manufacturer adapter or gateway", "outdoor_antenna_parsec": "Outdoor Parsec antenna path", "cross_vendor_outdoor_gateway": "Cross-vendor outdoor gateway", "manual_review_required": "Manual review", } key = _norm(value) return mapping.get(key, key or "Not listed") def _metric_item(label: str, value: Any) -> Dict[str, str] | None: label_text = str(label or "").strip() value_text = _norm(value) if not label_text or not value_text: return None return {"label": label_text, "value": value_text} restriction_badges = [ chunk.strip() for chunk in str(explainability.get("restriction_summary") or "").split(";") if str(chunk or "").strip() ] missing_inputs = [ str(_as_dict(item).get("prompt") or "").strip() for item in list(survey_followups or []) if str(_as_dict(item).get("prompt") or "").strip() ] review_reasons = [ str(reason).strip() for reason in [*list(recommendation.get("review_reasons") or []), *list(recommendation.get("warnings") or [])] if str(reason or "").strip() ] indoor_summary = { "indoor_ok": "Indoor placement looks workable from the workbook survey scores. Keep the router inside unless a later install constraint changes that path.", "indoor_reposition": "Indoor improvement is still the preferred first move. Repositioning toward the better indoor point is favored before jumping outside.", "outdoor_candidate": "Indoor can work, but the workbook sees a meaningful outdoor gain worth reviewing before you finalize the path.", "outdoor_required": "Indoor scores are weak enough that the workbook does not trust an indoor-only install path.", "manual_review_required": "Indoor scores alone do not settle the install path cleanly, so manual review stays in play.", "incomplete": "Indoor evidence is incomplete, so the workbook cannot fully settle the install path yet.", }.get(outcome_class, "Indoor placement needs review because the workbook outcome does not map cleanly to a single indoor path.") outdoor_summary = { "indoor_ok": "Outdoor hardware is not the preferred path from the current workbook evidence.", "indoor_reposition": "Outdoor is not the first recommendation yet; the workbook still prefers an indoor move closer to the better point.", "outdoor_candidate": "Outdoor is materially better than indoor, so adapter/gateway or outdoor antenna options should be reviewed.", "outdoor_required": "Outdoor-first deployment is the workbook-preferred path because indoor performance is too weak.", "manual_review_required": "Outdoor could be needed, but the workbook still needs more install constraints before it can trust the final choice.", "incomplete": "Outdoor evidence is incomplete, so the workbook avoids committing to an outdoor path yet.", }.get(outcome_class, "Outdoor placement needs review because the workbook outcome is not settled yet.") complexity_summary = _norm(complexity.get("complexity_label") or flow.get("complexity_label") or "") if complexity_summary: complexity_summary = f"Install complexity currently reads `{complexity_summary}` from the workbook fitment lane." elif review_reasons: complexity_summary = "Install complexity is still tied to the open review cues, so the workbook stops short of a cleaner install label." else: complexity_summary = "No install complexity row was attached to this survey outcome." complexity_badges = [ chunk.strip() for chunk in [str(complexity.get("complexity_drivers") or "").strip(), str(flow.get("match_method") or "").strip()] if chunk.strip() ] return { "title": "Survey explanation cards", "subtitle": "Workbook-backed placement guidance split into the practical indoor path, outdoor path, restrictions, complexity, and what is still missing.", "survey_label": _norm(survey.get("site_name") or survey.get("survey_key") or "Survey"), "outcome_class": outcome_class or "Not listed", "evidence_grade": evidence_grade or "Not listed", "confidence_label": confidence_label or "Not listed", "primary_path": _solution_family_label(recommendation.get("primary_solution_family_key")), "backup_path": _solution_family_label(recommendation.get("backup_solution_family_key")), "indoor_card": { "title": "Indoor path", "tone": "green" if outcome_class in {"indoor_ok", "indoor_reposition"} else "amber", "summary": indoor_summary, "metrics": [ item for item in [ _metric_item("Closet score", metrics.get("closet_score")), _metric_item("Near-closet score", metrics.get("near_closet_score")), _metric_item("Best indoor score", metrics.get("best_indoor_score")), _metric_item("Indoor move gain", metrics.get("indoor_move_gain")), ] if item ], "badges": [ badge for badge in [ _norm(primary_product.get("display_name") or ""), "Indoor-first" if outcome_class in {"indoor_ok", "indoor_reposition"} else "", ] if badge ], }, "outdoor_card": { "title": "Outdoor path", "tone": "amber" if outcome_class in {"outdoor_candidate", "outdoor_required", "manual_review_required"} else "blue", "summary": outdoor_summary, "metrics": [ item for item in [ _metric_item("Entry outdoor", metrics.get("entry_outdoor_score")), _metric_item("Best clear outdoor", metrics.get("best_clear_outdoor_score")), _metric_item("Practical outdoor gain", metrics.get("practical_outdoor_gain")), _metric_item("Theoretical outdoor gain", metrics.get("theoretical_outdoor_gain")), _metric_item("O3 vs O2 gap", metrics.get("o3_o2_gap")), ] if item ], "badges": [ badge for badge in [ _norm(backup_product.get("display_name") or ""), "Outdoor-first" if outcome_class == "outdoor_required" else "", ] if badge ], }, "complexity_card": { "title": "Expected install complexity", "tone": "amber" if review_reasons else "slate", "summary": complexity_summary, "metrics": [ item for item in [ _metric_item("Primary path", _solution_family_label(recommendation.get("primary_solution_family_key"))), _metric_item("Backup path", _solution_family_label(recommendation.get("backup_solution_family_key"))), _metric_item("Bundle", flow.get("bundle_name")), _metric_item("Complexity score", complexity.get("complexity_score")), ] if item ], "badges": complexity_badges[:3], }, "restriction_badges": restriction_badges[:8], "missing_inputs": missing_inputs[:6], } def _router_workbook_guided_advisor_shortlist_view(self, recommendation: Dict[str, Any]) -> Dict[str, Any]: suggestions = [row for row in list(recommendation.get("suggestions") or []) if isinstance(row, dict)] def _flag(value: Any) -> bool: return str(value or "").strip().lower() in {"yes", "true", "1", "y"} items: List[Dict[str, Any]] = [] for index, item in enumerate(suggestions, start=1): candidate = _as_dict(item.get("candidate")) features = _as_dict(candidate.get("features")) analysis = _as_dict(item.get("analysis")) manual_review_reasons = [ str(reason) for reason in list(analysis.get("manual_review_reasons") or []) if _norm(reason) ] lane_name = str(candidate.get("advisor_lane") or "").strip() lane_label = { "strict": "Strict workbook match", "vendor_neutral_fallback": "Vendor-neutral fallback", "placement_relaxed_fallback": "Placement-relaxed fallback", }.get(lane_name, lane_name or "Workbook lane") items.append( { "rank": index, "display_name": _norm(candidate.get("product_id") or candidate.get("display_name") or "Router option"), "manufacturer_group": _norm(candidate.get("manufacturer_group") or "Unknown"), "status_bucket": _norm(candidate.get("status_bucket") or "Unknown"), "cellular_gen": _norm(features.get("cellular_gen_norm") or "Not listed"), "total_ethernet_ports": int(candidate.get("total_ethernet_ports") or 0), "feature_badges": [ label for label, enabled in [ ("Rugged", _flag(features.get("rugged_norm"))), ("Battery", _flag(features.get("battery_norm"))), ("Wi-Fi", _flag(features.get("wifi_norm"))), ("GNSS/GPS", _flag(features.get("gnss_norm"))), ("PoE", _flag(features.get("poe_norm"))), ] if enabled ], "lane_label": lane_label, "reasons": [str(reason) for reason in list(item.get("reasons") or []) if _norm(reason)], "tradeoffs": self._router_workbook_guided_advisor_tradeoffs(candidate), "antenna_label": _norm(item.get("antenna_label") or ""), "quote_bom_id": _norm(item.get("quote_bom_id") or ""), "review_required": bool(item.get("review_required")), "review_notes": manual_review_reasons[:2], "debug_ref": self._router_workbook_debug_ref( table_name="DBX_Products", key=candidate.get("product_key"), label=f"{_norm(candidate.get('product_id') or candidate.get('display_name') or 'Router option')} product row", title=f"{_norm(candidate.get('product_id') or candidate.get('display_name') or 'Router option')} raw workbook row", ), } ) total_found = int(recommendation.get("search_count") or len(items)) return { "title": "Ranked workbook shortlist", "subtitle": "Current-only router + antenna candidates ranked from the five-question intake.", "current_only": True, "shown_count": len(items), "total_found": total_found, "additional_match_count": max(0, total_found - len(items)), "items": items, } def _router_workbook_match_confidence(self, match: Dict[str, Any], *, input_text: str) -> Dict[str, Any]: score = int(match.get("match_score") or 0) alias_text = _norm(match.get("matched_alias_text") or "") alias_type = _norm(match.get("matched_alias_type") or "") input_value = _norm(input_text) display_name = _norm(match.get("display_name") or match.get("product_id") or "") if score >= 100: label = "Exact" elif score >= 90: label = "Alias corrected" elif score >= 80: label = "High" elif score >= 65: label = "Fuzzy" else: label = "Low" correction = "" if input_value and display_name and input_value.lower() != display_name.lower(): if alias_text and alias_text.lower() != input_value.lower(): correction = f"Matched `{input_value}` using workbook alias `{alias_text}`." else: correction = f"Normalized `{input_value}` to `{display_name}`." return { "label": label, "score": score, "matched_alias_text": alias_text, "matched_alias_type": alias_type, "correction_note": correction, } def _router_workbook_fleet_view_from_rows( self, row_views: Sequence[Dict[str, Any]], *, source_label: str, uploaded_filename: str = "", ) -> Dict[str, Any]: rows = [row for row in row_views if isinstance(row, dict)] matched_count = sum(bool(row.get("matched")) for row in rows) unmatched_count = sum(not bool(row.get("matched")) for row in rows) review_required_count = sum(bool(row.get("review_required")) for row in rows) shown_rows = rows[:20] return { "title": "Workbook fleet normalization", "subtitle": "Per-row workbook normalization, lifecycle, and replacement confidence.", "source_label": source_label, "uploaded_filename": _norm(uploaded_filename), "row_count": len(rows), "matched_count": matched_count, "unmatched_count": unmatched_count, "review_required_count": review_required_count, "shown_row_count": len(shown_rows), "truncated_count": max(0, len(rows) - len(shown_rows)), "rows": shown_rows, "fleet_evidence_rows": [ _as_dict(row.get("fleet_evidence")) for row in rows if isinstance(_as_dict(row.get("fleet_evidence")), dict) ], } def _router_workbook_lifecycle_bucket( self, *, status: Any, authoritative_lifecycle: bool, family_level: bool = False, ) -> str: low = _norm(status).lower() if not low: return "unknown" if "no direct replacement" in low: return "no_direct_replacement" if any(token in low for token in ("end of life", "eol", "expired")): return "end_of_life" if any(token in low for token in ("end of sale", "eos", "legacy", "retired", "obsolete", "discontinued")): return "end_of_sale" if "current" in low: if authoritative_lifecycle and not family_level: return "current_authoritative" return "current" if family_level: return "family_level" return "status_only" def _router_workbook_ordered_replacement_paths( self, *, same_brand_path: Any, backup_path: Any, preferred_5g_path: Any = "", bridge_path: Any = "", replacement_source_mode: str = "workbook", prefer_5g_target: bool = False, no_direct_replacement: bool = False, ) -> List[Dict[str, str]]: ordered: List[Dict[str, str]] = [] seen: set[str] = set() def _clean(value: Any) -> str: text = _norm(value) if not text: return "" if text.lower() in { "n/a", "na", "none", "not listed", "needs exact workbook match", "no direct replacement", "none listed", "explicit workbook outcome", }: return "" return text def _add(label: str, value: Any, *, kind: str, note: str = "") -> None: clean = _clean(value) compact = _compact_model(clean) if not clean or not compact or compact in seen: return seen.add(compact) ordered.append( { "label": str(label or "").strip(), "value": clean, "kind": str(kind or "").strip(), "source_mode": str(replacement_source_mode or "").strip() or "workbook", "note": str(note or "").strip(), } ) source_note = ( "lifecycle fallback" if str(replacement_source_mode or "").strip().lower() == "lifecycle_fallback" else "workbook lane" ) clean_same_brand = _clean(same_brand_path) clean_backup = _clean(backup_path) clean_5g = _clean(preferred_5g_path) clean_bridge = _clean(bridge_path) if prefer_5g_target and clean_5g: _add("Requested 5G path", clean_5g, kind="requested_5g", note=f"Current 5G move-forward path from {source_note}.") if clean_bridge and _compact_model(clean_bridge) != _compact_model(clean_5g): _add("4G bridge", clean_bridge, kind="bridge", note=f"Bridge lane from {source_note}.") if clean_same_brand: same_kind = "same_brand" same_label = "Same-brand path" if prefer_5g_target and clean_bridge and _compact_model(clean_same_brand) == _compact_model(clean_bridge): same_kind = "bridge" same_label = "4G bridge" _add(same_label, clean_same_brand, kind=same_kind, note=f"Current same-manufacturer path from {source_note}.") if clean_backup: _add("Backup path", clean_backup, kind="backup", note=f"Backup lane from {source_note}.") if not ordered and no_direct_replacement: ordered.append( { "label": "No direct replacement", "value": "Explicit workbook outcome", "kind": "no_direct_replacement", "source_mode": str(replacement_source_mode or "").strip() or "workbook", "note": "The workbook marks no direct replacement for this row.", } ) return ordered def _router_workbook_fleet_priority_from_evidence(self, evidence: Dict[str, Any]) -> Tuple[int, str]: row = _as_dict(evidence) qty = max(1, int(row.get("qty") or 1)) router_name = _norm(row.get("normalized_model") or row.get("input_model") or "Unknown router") def _priority_date_bonus(*values: Any) -> Tuple[int, str]: parsed_dates: List[int] = [] for raw_value in values: text = _norm(raw_value) low = text.lower() if (not text) or any(token in low for token in ("not listed", "needs exact", "workbook date on file")): continue match = re.search(r"\b(20\d{2})(?:-(\d{2})-(\d{2}))?\b", text) if not match: continue year = int(match.group(1)) month = int(match.group(2) or 12) day = int(match.group(3) or 31) parsed_dates.append((year * 10000) + (month * 100) + day) if not parsed_dates: return 0, "" earliest = min(parsed_dates) earliest_year = earliest // 10000 bonus = max(0, min(20, 2035 - earliest_year)) if bonus <= 0: return 0, "" return bonus, "its workbook lifecycle dates are older than the rest of this fleet snapshot" if not bool(row.get("matched")): if "placeholder" in str(row.get("lifecycle_bucket") or ""): return ( 65 + min(qty, 25), f"`{router_name}` looks like a placeholder token and needs clarification before I can rank its migration path safely.", ) return ( 10 + min(qty, 10), f"`{router_name}` needs an exact workbook model match before I can rank its migration path safely.", ) bucket = str(row.get("lifecycle_bucket") or "unknown") score = min(qty, 25) reasons: List[str] = [] recommended_path = _norm(row.get("recommended_path_value") or "") if recommended_path: reasons.append(f"the highest-confidence move-forward path is `{recommended_path}`") else: score += 20 reasons.append("no ordered move-forward path is workbook-ready") if bucket == "end_of_life": score += 95 reasons.append(f"its lifecycle state is `{_norm(row.get('lifecycle_status') or 'EOL')}`") elif bucket == "end_of_sale": score += 80 reasons.append(f"its lifecycle state is `{_norm(row.get('lifecycle_status') or 'EOS')}`") elif bucket in {"family_level", "status_only"}: score += 35 reasons.append("the lifecycle state is only partially authoritative") elif bucket.startswith("current"): score += 10 reasons.append("it is still marked current") elif bucket == "no_direct_replacement": score += 45 reasons.append("the workbook shows no direct replacement") else: score += 20 reasons.append(f"its lifecycle state is `{_norm(row.get('lifecycle_status') or 'Unknown')}`") date_bonus, date_reason = _priority_date_bonus( row.get("end_of_sale_date"), row.get("end_of_life_date"), ) if date_bonus: score += date_bonus reasons.append(date_reason) if not bool(row.get("authoritative_lifecycle")): score += 5 reasons.append("authoritative lifecycle dates are incomplete") if bool(row.get("family_level")): score += 5 reasons.append( f"the match is family-level provisional with EOS `{_norm(row.get('end_of_sale_date') or '')}` / EOL `{_norm(row.get('end_of_life_date') or '')}` shown here" ) return score, f"`{router_name}` should be considered earlier in the phased plan because {', and '.join(reasons[:3])}." def _router_workbook_fleet_evidence_row( self, *, row_number: int, customer: str, input_model: str, normalized_model: str, qty: int, matched: bool, manufacturer_group: str, confidence_label: str, confidence_score: int, lifecycle_status: str, authoritative_lifecycle: bool, family_level: bool, end_of_sale_date: str, end_of_life_date: str, same_brand_path: str = "", backup_path: str = "", preferred_5g_path: str = "", bridge_path: str = "", replacement_source_mode: str = "workbook", lane_note: str = "", review_required: bool = False, correction_note: str = "", match_alias_text: str = "", match_alias_type: str = "", source_table: str = "DBX_Products", citation_anchor: str = "", entity_label: str = "", entity_role: str = "fleet_row", evidence_kind: str = "fleet_row", resolution_mode: str = "", candidate_labels: Sequence[str] = (), error_message: str = "", debug_ref: str = "", prefer_5g_target: bool = False, no_direct_replacement: bool = False, uncertainty_flags: Sequence[str] = (), ) -> Dict[str, Any]: lifecycle_bucket = self._router_workbook_lifecycle_bucket( status=lifecycle_status, authoritative_lifecycle=authoritative_lifecycle, family_level=family_level, ) if not matched: placeholder_like = ( any(_norm(item).lower().startswith("unknown") for item in candidate_labels) or _norm(input_model).lower().startswith("unknown") or "placeholder" in _norm(lifecycle_status).lower() or "placeholder" in _norm(error_message).lower() ) lifecycle_bucket = "placeholder" if placeholder_like else "unmatched" ordered_paths = self._router_workbook_ordered_replacement_paths( same_brand_path=same_brand_path, backup_path=backup_path, preferred_5g_path=preferred_5g_path, bridge_path=bridge_path, replacement_source_mode=replacement_source_mode, prefer_5g_target=prefer_5g_target, no_direct_replacement=no_direct_replacement, ) recommended_path_label = str(_as_dict(ordered_paths[0]).get("label") or "") if ordered_paths else "" recommended_path_value = str(_as_dict(ordered_paths[0]).get("value") or "") if ordered_paths else "" evidence = RouterFleetEvidenceRow( row_number=int(row_number or 0), customer=str(customer or ""), input_model=str(input_model or ""), normalized_model=str(normalized_model or ""), qty=int(qty or 0), matched=bool(matched), manufacturer_group=str(manufacturer_group or ""), confidence_label=str(confidence_label or ""), confidence_score=int(confidence_score or 0), lifecycle_status=str(lifecycle_status or ""), lifecycle_bucket=str(lifecycle_bucket or ""), authoritative_lifecycle=bool(authoritative_lifecycle), family_level=bool(family_level), end_of_sale_date=str(end_of_sale_date or ""), end_of_life_date=str(end_of_life_date or ""), same_brand_path=str(same_brand_path or ""), backup_path=str(backup_path or ""), preferred_5g_path=str(preferred_5g_path or ""), bridge_path=str(bridge_path or ""), ordered_paths=list(ordered_paths), recommended_path_label=recommended_path_label, recommended_path_value=recommended_path_value, replacement_source_mode=str(replacement_source_mode or ""), lane_note=str(lane_note or ""), review_required=bool(review_required), correction_note=str(correction_note or ""), match_alias_text=str(match_alias_text or ""), match_alias_type=str(match_alias_type or ""), source_table=str(source_table or ""), citation_anchor=str(citation_anchor or debug_ref or f"row:{int(row_number or 0)}"), entity_label=str(entity_label or normalized_model or input_model or ""), entity_role=str(entity_role or "fleet_row"), evidence_kind=str(evidence_kind or "fleet_row"), resolution_mode=str(resolution_mode or ""), uncertainty_flags=[str(item) for item in list(uncertainty_flags or []) if str(item or "").strip()], candidate_labels=[str(item) for item in list(candidate_labels or []) if str(item or "").strip()], error_message=str(error_message or ""), debug_ref=str(debug_ref or ""), ).as_dict() priority_score, priority_reason = self._router_workbook_fleet_priority_from_evidence(evidence) evidence["replacement_priority_score"] = int(priority_score) evidence["replacement_priority_reason"] = str(priority_reason or "") return evidence def _router_workbook_replacement_view_from_analysis( self, analysis: Dict[str, Any], replacement_evidence: Dict[str, Any], *, review_required: bool, ) -> Dict[str, Any]: match = _as_dict(analysis.get("match")) replacements = _as_dict(analysis.get("replacements")) primary_rows = [row for row in list(replacements.get("primary_candidates") or []) if isinstance(row, dict)] same_manufacturer_backup_rows = [row for row in list(replacements.get("same_manufacturer_backup_replacements") or []) if isinstance(row, dict)] backup_rows = [row for row in list(replacements.get("backup_replacements") or []) if isinstance(row, dict)] historical_rows = [row for row in list(replacements.get("historical_only_replacements") or []) if isinstance(row, dict)] source_mode = str(analysis.get("_replacement_source_mode") or "").strip().lower() fallback_source = source_mode == "lifecycle_fallback" subject_label = _norm( match.get("subject_display_name") or match.get("_requested_label") or match.get("_family_requested_text") or match.get("display_name") or match.get("product_id") or "" ) ordered_paths = [row for row in list(replacement_evidence.get("ordered_paths") or []) if isinstance(row, dict)] if not ordered_paths: def _lane_row_value(row: Dict[str, Any]) -> str: item = _as_dict(row) return _norm( item.get("replacement_display") or item.get("replacement_id") or item.get("display_name") or item.get("model") or item.get("name") or "" ) def _append_lane_row(row: Dict[str, Any], *, label: str, kind: str) -> None: value = _lane_row_value(row) if not value: return ordered_paths.append( { "label": label, "value": value, "kind": kind, "source_mode": source_mode or "workbook", "note": _norm(_as_dict(row).get("mapping_type") or _as_dict(row).get("replacement_class") or _as_dict(row).get("authority_level") or "workbook lane"), } ) for row in primary_rows[:3]: _append_lane_row( row, label="Primary same-brand path" if bool(_as_dict(row).get("same_manufacturer")) else "Primary path", kind="same_brand" if bool(_as_dict(row).get("same_manufacturer")) else "primary", ) for row in same_manufacturer_backup_rows[:3]: _append_lane_row(row, label="Same-manufacturer backup", kind="backup") for row in backup_rows[:3]: _append_lane_row(row, label="Cross-vendor backup", kind="backup") for row in historical_rows[:2]: _append_lane_row(row, label="Historical-only path", kind="historical") lane_summary = ( f"Current workbook-ready lanes: {len(primary_rows)} same-manufacturer primary, " f"{len(same_manufacturer_backup_rows)} same-manufacturer backup, {len(backup_rows)} cross-vendor" ) suppressed_bits: List[str] = [] if historical_rows: suppressed_bits.append(f"{len(historical_rows)} historical-only suppressed") blocked_count = int(replacements.get("review_blocked_count") or 0) if blocked_count > 0: suppressed_bits.append(f"{blocked_count} manual-review blocked") has_only_lifecycle_fallback = fallback_source or ((not primary_rows) and (not backup_rows) and bool(ordered_paths)) if fallback_source or has_only_lifecycle_fallback: lane_summary = "Current workbook-ready lanes: none. Sourced lifecycle fallback guidance is listed separately below" if suppressed_bits: lane_summary += f"; suppressed: {', '.join(suppressed_bits)}" elif suppressed_bits: lane_summary += f"; suppressed: {', '.join(suppressed_bits)}" lane_summary += "." return { "title": "Replacement lane order", "subtitle": "Workbook-backed replacement ordering shared across the rendered answer and the evidence bundle.", "subject_label": subject_label, "source_mode": source_mode or "workbook", "resolution_mode": str(analysis.get("_resolution_mode") or "exact"), "source_table": "DBX_Replacements", "source_document": "router_workbook", "debug_ref": subject_label or "router_workbook", "recommended_path_label": _norm(replacement_evidence.get("recommended_path_label") or ""), "recommended_path_value": _norm(replacement_evidence.get("recommended_path_value") or ""), "ordered_paths": ordered_paths, "lane_summary": lane_summary, "review_required": bool(review_required), "no_replacement": bool(replacements.get("no_replacement")), "primary_count": len(primary_rows), "same_manufacturer_backup_count": len(same_manufacturer_backup_rows), "cross_vendor_backup_count": len(backup_rows), "historical_only_count": len(historical_rows), "blocked_count": blocked_count, } def _router_workbook_replacement_evidence_from_analysis( self, core: Any, analysis: Dict[str, Any], *, requested_model: str = "", prefer_5g_target: bool = False, resolution_mode: str = "exact", ) -> Dict[str, Any]: match = _as_dict(analysis.get("match")) product_key = str(match.get("product_key") or "").strip() detail = _as_dict(core.get_catalog_device_details_by_key(product_key=product_key)) if product_key else {} product = _as_dict(detail.get("product")) lifecycle = _as_dict(analysis.get("_replacement_subject_lifecycle") or detail.get("lifecycle")) lifecycle = self._router_workbook_enrich_lifecycle_row(match, product, lifecycle) replacements = _as_dict(analysis.get("replacements")) subject_label = _norm( match.get("subject_display_name") or match.get("_requested_label") or match.get("_family_requested_text") or match.get("product_id") or match.get("display_name") or product.get("product_id") or product.get("display_name") ) requested_label = _norm( requested_model or match.get("_requested_label") or match.get("_family_requested_text") or subject_label or _display_name(match) ) normalized_model = _norm(subject_label or _display_name(match) or requested_label) confidence = self._router_workbook_match_confidence(match, input_text=requested_label or normalized_model) family_note = _norm(analysis.get("_family_safe_note") or "") family_level = bool( match.get("_family_collapsed") or product.get("_family_collapsed") or analysis.get("_family_collapsed") or str(resolution_mode or "").strip() in {"family_collapsed", "family_safe_partial", "family_alias_provisional"} ) if family_level: confidence = { "label": "Family-level provisional", "score": max(60, int(confidence.get("score") or 0)), "matched_alias_text": confidence.get("matched_alias_text"), "matched_alias_type": confidence.get("matched_alias_type"), "correction_note": family_note or "Matched at the workbook family level; exact variant still needs confirmation.", } fallback_source = str(analysis.get("_replacement_source_mode") or "").strip().lower() == "lifecycle_fallback" detail_cache: Dict[str, Dict[str, Any]] = {} lane_snapshot = self._router_workbook_fleet_lane_snapshot( core, match, product, lifecycle, replacements, prefer_5g_target=prefer_5g_target, detail_cache=detail_cache, ) has_workbook_replacement_rows = any( bool(list(replacements.get(key) or [])) for key in ("primary_candidates", "same_manufacturer_backup_replacements", "backup_replacements") ) has_primary_rows = bool(list(replacements.get("primary_candidates") or [])) has_same_manufacturer_backup_rows = bool(list(replacements.get("same_manufacturer_backup_replacements") or [])) has_cross_vendor_backup_rows = bool(list(replacements.get("backup_replacements") or [])) fallback_only_context = fallback_source or not has_workbook_replacement_rows if fallback_only_context: lane_snapshot["same_brand_path"] = "" lifecycle_fallback_texts = [ _norm(event.get("recommended_replacement_text")) for event in list(lifecycle.get("events") or []) if isinstance(event, dict) and _norm(event.get("recommended_replacement_text")) ] lifecycle_fallback_texts = list(dict.fromkeys(lifecycle_fallback_texts)) legacy_lifecycle = _as_dict(analysis.get("_replacement_legacy_lifecycle")) fallback_current_candidates = ( [*lifecycle_fallback_texts, legacy_lifecycle.get("rep5g"), lifecycle.get("rep5g"), legacy_lifecycle.get("alt4g"), lifecycle.get("alt4g")] if fallback_only_context and (not prefer_5g_target) else [lifecycle.get("rep5g"), legacy_lifecycle.get("rep5g"), *lifecycle_fallback_texts, lifecycle.get("alt4g"), legacy_lifecycle.get("alt4g")] ) fallback_current_path = next((candidate for candidate in (_norm(item) for item in fallback_current_candidates) if candidate), "") fallback_bridge_path = next( ( candidate for candidate in (_norm(item) for item in [lifecycle.get("alt4g"), legacy_lifecycle.get("alt4g"), legacy_lifecycle.get("rep5g")]) if candidate and _compact_model(candidate) != _compact_model(fallback_current_path) ), "", ) replacement_source_mode = str(analysis.get("_replacement_source_mode") or "").strip() or ( "lifecycle_fallback" if fallback_only_context or (fallback_current_path and not any( _norm(lane_snapshot.get(key) or "") for key in ("same_brand_path", "backup_path", "preferred_5g_path", "bridge_path") )) else "workbook" ) if ( not _norm(lane_snapshot.get("same_brand_path") or "") and fallback_current_path and not (has_primary_rows or has_same_manufacturer_backup_rows or has_cross_vendor_backup_rows) ): lane_snapshot["same_brand_path"] = fallback_current_path if ( not _norm(lane_snapshot.get("backup_path") or "") and fallback_bridge_path and not has_cross_vendor_backup_rows ): lane_snapshot["backup_path"] = fallback_bridge_path if ( prefer_5g_target and not _norm(lane_snapshot.get("preferred_5g_path") or "") and fallback_current_path and not (has_primary_rows or has_same_manufacturer_backup_rows or has_cross_vendor_backup_rows) ): lane_snapshot["preferred_5g_path"] = fallback_current_path if ( prefer_5g_target and not _norm(lane_snapshot.get("bridge_path") or "") and fallback_bridge_path and not has_cross_vendor_backup_rows ): lane_snapshot["bridge_path"] = fallback_bridge_path review_required = bool( analysis.get("review_required") or family_level or match.get("feature_gap_blocked") or not bool(lifecycle.get("has_authoritative_lifecycle")) ) uncertainty_flags: List[str] = [] if family_level: uncertainty_flags.append("family_level_match") if not bool(lifecycle.get("has_authoritative_lifecycle")): uncertainty_flags.append("lifecycle_dates_incomplete") if bool(replacements.get("no_replacement")): uncertainty_flags.append("no_direct_replacement") if replacement_source_mode.lower() == "lifecycle_fallback": uncertainty_flags.append("lifecycle_fallback") if review_required: uncertainty_flags.append("review_required") return self._router_workbook_fleet_evidence_row( row_number=1, customer="", input_model=requested_label or normalized_model, normalized_model=normalized_model or requested_label, qty=1, matched=bool(normalized_model or requested_label), manufacturer_group=_norm(match.get("manufacturer_group") or product.get("manufacturer_group") or ""), confidence_label=str(confidence.get("label") or ""), confidence_score=int(confidence.get("score") or 0), lifecycle_status=_norm(lifecycle.get("status") or match.get("status_bucket") or "Unknown"), authoritative_lifecycle=bool(lifecycle.get("has_authoritative_lifecycle")), family_level=family_level, end_of_sale_date=_norm(lifecycle.get("end_of_sale_date") or ""), end_of_life_date=_norm(lifecycle.get("last_support_date") or ""), same_brand_path=_norm(lane_snapshot.get("same_brand_path") or ""), backup_path=_norm(lane_snapshot.get("backup_path") or ""), preferred_5g_path=_norm(lane_snapshot.get("preferred_5g_path") or ""), bridge_path=_norm(lane_snapshot.get("bridge_path") or ""), replacement_source_mode=replacement_source_mode, lane_note=_norm(lane_snapshot.get("lane_note") or ""), review_required=review_required, correction_note=str(confidence.get("correction_note") or ""), match_alias_text=str(confidence.get("matched_alias_text") or ""), match_alias_type=str(confidence.get("matched_alias_type") or ""), source_table="DBX_Products", citation_anchor=str(match.get("product_key") or product_key or requested_label or normalized_model or "replacement_subject"), entity_label=requested_label or normalized_model, entity_role="replacement_subject", evidence_kind="replacement_lane", resolution_mode=resolution_mode, candidate_labels=[], error_message="", debug_ref=self._router_workbook_debug_ref( table_name="DBX_Products", key=str(match.get("product_key") or ""), label=f"{normalized_model or requested_label or 'Router'} product row", title=f"{normalized_model or requested_label or 'Router'} raw workbook row", ), prefer_5g_target=prefer_5g_target, no_direct_replacement=bool(replacements.get("no_replacement")), uncertainty_flags=uncertainty_flags, ) def _router_workbook_enrich_lifecycle_row( self, match: Dict[str, Any], product: Dict[str, Any], lifecycle: Dict[str, Any], ) -> Dict[str, Any]: enriched = dict(_as_dict(lifecycle)) legacy_row: Dict[str, Any] = {} candidate_labels = [ _norm(match.get("subject_display_name") or ""), _norm(match.get("_requested_label") or ""), _norm(match.get("product_id") or ""), _norm(match.get("display_name") or ""), _norm(match.get("family_group") or ""), _norm(match.get("product_key") or ""), _norm(product.get("subject_display_name") or ""), _norm(product.get("_requested_label") or ""), _norm(product.get("product_id") or ""), _norm(product.get("display_name") or ""), _norm(product.get("family_group") or ""), _norm(product.get("product_key") or ""), ] for label in candidate_labels: if not label: continue key = self._lookup_router_lifecycle_key_relaxed(label) or self._lookup_router_lifecycle_key(label) if not key: compact = _compact_model(label) key = self._lookup_router_lifecycle_key_relaxed(compact) or self._lookup_router_lifecycle_key(compact) if key: legacy_row = _as_dict(self._router_lifecycle_rows.get(key, {})) if legacy_row: break if not legacy_row: return enriched for field in ("status", "eos", "eol", "alt4g", "rep5g", "manufacturer", "device_type", "tech", "source_doc", "source_row"): if not _norm(enriched.get(field)) and _norm(legacy_row.get(field)): enriched[field] = legacy_row[field] if not _norm(enriched.get("end_of_sale_date")) and _norm(legacy_row.get("eos")): enriched["end_of_sale_date"] = legacy_row["eos"] if not _norm(enriched.get("last_support_date")) and _norm(legacy_row.get("eol")): enriched["last_support_date"] = legacy_row["eol"] if not bool(enriched.get("has_authoritative_lifecycle")): enriched["has_authoritative_lifecycle"] = bool( _norm(enriched.get("end_of_sale_date")) or _norm(enriched.get("last_support_date")) or _norm(legacy_row.get("eos")) or _norm(legacy_row.get("eol")) ) enriched["_legacy_lifecycle_row"] = legacy_row return enriched def _router_workbook_likely_typo_candidate(self, token: str) -> str: requested = _compact_model(token) if not requested: return "" if self._lookup_router_lifecycle_key(requested) or self._lookup_router_lifecycle_key_relaxed(requested) or self._lookup_router_fact_key(requested): return "" requested_alpha = re.sub(r"[^A-Z]+", "", requested) requested_digits = _digit_signature(requested) if (not requested_alpha) or (not requested_digits): return "" lifecycle_rows = getattr(self, "_router_lifecycle_rows", {}) or {} fact_rows = getattr(self, "_router_fact_rows", {}) or {} candidates = list(lifecycle_rows.keys()) + list(fact_rows.keys()) def _add_row_text_candidates(row: Dict[str, Any]) -> None: if not isinstance(row, dict): return for field in ( "model", "model_key", "product_id", "display_name", "family_group", "sku", "title", "matched_alias_text", "notes", "install_caveats", ): raw = _norm(row.get(field) or "") if not raw: continue # Pull short model-like tokens from row text so note fields such as # "MG21 series supported..." can still produce a nearby typo hint. for raw_token in re.findall(r"[A-Za-z][A-Za-z0-9\-]{1,30}", raw): compact = _compact_model(raw_token) if compact and compact not in candidates: candidates.append(compact) for row in lifecycle_rows.values(): _add_row_text_candidates(_as_dict(row)) for row in fact_rows.values(): _add_row_text_candidates(_as_dict(row)) best = "" best_rank = (99, 99, 99, 99) for cand in candidates: compact = _compact_model(cand) if (not compact) or (compact == requested): continue cand_alpha = re.sub(r"[^A-Z]+", "", compact) cand_digits = _digit_signature(compact) if (not cand_alpha) or (cand_alpha != requested_alpha) or (len(cand_digits) != len(requested_digits)): continue digit_diff = sum(1 for a, b in zip(requested_digits, cand_digits) if a != b) if digit_diff > 1: continue try: numeric_gap = abs(int(cand_digits) - int(requested_digits)) except Exception: numeric_gap = abs(len(cand_digits) - len(requested_digits)) if numeric_gap > 1: continue rank = ( digit_diff, numeric_gap, abs(len(compact) - len(requested)), 0 if cand in lifecycle_rows else 1, ) if rank < best_rank: best_rank = rank best = compact return best def _router_workbook_unresolved_model_note(self, token: str, *, context: str = "lifecycle") -> str: label = _norm(token) typo_candidate = self._router_workbook_likely_typo_candidate(label) if typo_candidate: if context == "fleet": return ( f"`{label}` looks like a typo for `{typo_candidate}` because it did not resolve to a workbook row, " "so I left it provisional instead of forcing a workbook match." ) return ( f"`{label}` looks like a typo for `{typo_candidate}` because it did not resolve to a workbook lifecycle row, " "so please confirm the exact model before I map lifecycle." ) if context == "fleet": return f"`{label}` does not resolve to a workbook row; please confirm the exact model/SKU." return f"`{label}` does not resolve to a workbook lifecycle row; please confirm the exact model/SKU." def _router_workbook_synthesize_lifecycle_batch(self, lifecycle_inputs: Sequence[Dict[str, Any]]) -> Dict[str, Any]: devices: List[Dict[str, Any]] = [] notes: List[str] = [] lifecycle_rows = getattr(self, "_router_lifecycle_rows", {}) or {} seen_keys: set[str] = set() for requested in lifecycle_inputs: requested_label = _norm(requested.get("requested_label") or "") requested_compact = _compact_model(requested.get("requested_compact") or requested_label) if not requested_label: continue resolved_match = _as_dict(requested.get("resolved_match")) family_level = bool(requested.get("family_level")) lookup_candidates = [ resolved_match.get("product_key"), resolved_match.get("product_id"), resolved_match.get("display_name"), resolved_match.get("family_group"), requested_label, requested_compact, ] legacy_key = "" for candidate in lookup_candidates: candidate_text = _norm(candidate) if not candidate_text: continue legacy_key = self._lookup_router_lifecycle_key(candidate_text) if legacy_key: break if not legacy_key: note_text = self._router_workbook_unresolved_model_note(requested_label) notes.append(note_text) provisional_match = { "product_key": requested_compact or requested_label, "product_id": requested_label, "display_name": requested_label, "subject_display_name": requested_label, "_requested_label": requested_label, "_family_collapsed": False, "manufacturer_group": _norm(requested.get("manufacturer_text") or ""), "family_group": requested_label, "granularity": "sku", "entity_type": "unresolved_token", "status_bucket": "Needs exact workbook match", "current_recommendable_flag": False, } devices.append( { "match": provisional_match, "product": dict(provisional_match), "lifecycle": { "status": "Needs exact workbook match", "has_authoritative_lifecycle": False, }, "replacements": { "primary_replacement": None, "backup_replacements": [], "no_replacement": False, }, "_family_collapsed": False, "_unresolved": True, } ) continue legacy_row = _as_dict(lifecycle_rows.get(legacy_key, {})) if not legacy_row or legacy_key in seen_keys: continue seen_keys.add(legacy_key) base_model = _norm(legacy_row.get("model") or legacy_key or requested_label) display_name = _norm(resolved_match.get("display_name") or base_model or requested_label) match = { "product_key": legacy_key, "product_id": _norm(resolved_match.get("product_id") or base_model or legacy_key), "display_name": display_name, "subject_display_name": requested_label or display_name, "_requested_label": requested_label or display_name, "_family_collapsed": family_level or bool(resolved_match.get("_family_collapsed")), "manufacturer_group": _norm(resolved_match.get("manufacturer_group") or legacy_row.get("manufacturer") or ""), "family_group": base_model or legacy_key, "granularity": _norm(resolved_match.get("granularity") or "sku"), "entity_type": _norm(resolved_match.get("entity_type") or "exact_sku"), "status_bucket": _norm(resolved_match.get("status_bucket") or legacy_row.get("status") or "Unknown"), "current_recommendable_flag": _norm(legacy_row.get("status") or "").lower() == "current", } product = dict(match) lifecycle = self._router_workbook_enrich_lifecycle_row(match, product, {}) devices.append( { "match": match, "product": product, "lifecycle": lifecycle, "replacements": { "primary_replacement": None, "backup_replacements": [], "no_replacement": False, }, "_family_collapsed": family_level, } ) if not devices: message = notes[0] if notes else "No workbook-backed lifecycle matches were found for the requested devices." return { "ok": False, "error": "no_lifecycle_matches", "message": message, "failures": [], "notes": notes, } out: Dict[str, Any] = {"ok": True, "devices": devices} if notes: out["notes"] = notes return out def _render_router_workbook_inventory_import_result(self, fleet_view: Dict[str, Any]) -> str: rows = [row for row in list(fleet_view.get("rows") or []) if isinstance(row, dict)] title = _norm(fleet_view.get("uploaded_filename") or fleet_view.get("source_label") or "inventory import") lines = [ f"Workbook-backed inventory import analyzed {int(fleet_view.get('row_count') or len(rows))} row(s) from `{title}`.", "", "| Customer | Input | Workbook match | Qty | Confidence | Lifecycle | Same-brand path | Backup path |", "| --- | --- | --- | ---: | --- | --- | --- | --- |", ] for row in rows: workbook_match = _norm(row.get("normalized_model") or "") if not workbook_match: workbook_match = "Needs exact workbook match" lines.append( "| " + " | ".join( [ _md_cell(row.get("customer") or "Unknown"), _md_cell(row.get("input_model") or "Unknown"), _md_cell(workbook_match), _md_cell(row.get("qty") or 0), _md_cell(row.get("confidence_label") or "Unknown"), _md_cell(row.get("lifecycle_status") or "Needs review"), _md_cell(row.get("same_brand_path") or "Not listed"), _md_cell(row.get("backup_path") or "Not listed"), ] ) + " |" ) if int(fleet_view.get("unmatched_count") or 0): lines.extend( [ "", f"{int(fleet_view.get('unmatched_count') or 0)} row(s) still need an exact workbook model match before I can trust the lifecycle path fully.", ] ) if int(fleet_view.get("truncated_count") or 0): lines.extend( [ "", f"Only the first {int(fleet_view.get('shown_row_count') or len(rows))} row(s) are shown here. The structured fleet view keeps the rest of the analyzed rows available in the UI.", ] ) return "\n".join(lines) def _router_workbook_fleet_lane_snapshot( self, core: Any, match: Dict[str, Any], product: Dict[str, Any], lifecycle: Dict[str, Any], replacements: Dict[str, Any], *, prefer_5g_target: bool = False, detail_cache: Dict[str, Dict[str, Any]] | None = None, ) -> Dict[str, str]: detail_cache = detail_cache if detail_cache is not None else {} def _clean_path(value: Any) -> str: text = _norm(value) if not text: return "" if text.lower() in { "n/a", "na", "none", "not listed", "not listed (abstained)", "needs exact workbook match", "needs exact sku/package", "provisional after model confirmation", "unknown until exact workbook match", }: return "" return text def _row_display(row: Dict[str, Any]) -> str: item = _as_dict(row) return _clean_path( item.get("replacement_display") or item.get("replacement_id") or item.get("display_name") or item.get("model") or item.get("name") ) def _product_label(row: Dict[str, Any]) -> str: item = _as_dict(row) return _clean_path( item.get("display_name") or item.get("product_id") or item.get("name") or item.get("model") or item.get("sku") ) def _detail_for_key(product_key: Any) -> Dict[str, Any]: key = _norm(product_key or "") if not key: return {} cached = _as_dict(detail_cache.get(key)) if cached: return cached fetched = _as_dict(core.get_catalog_device_details_by_key(product_key=key)) detail_cache[key] = fetched return fetched def _row_is_5g(row: Dict[str, Any]) -> bool: item = _as_dict(row) display = _row_display(item) product_key = _norm(item.get("replacement_product_key") or item.get("product_key") or item.get("model_key")) if product_key: detail = _detail_for_key(product_key) features = _as_dict(detail.get("features")) cellular = _norm(features.get("cellular_gen_norm")).upper() if "5G" in cellular: return True return "5g" in display.lower() def _first_distinct(candidates: Sequence[str], *, exclude: str = "") -> str: excluded_key = _compact_model(exclude) for candidate in candidates: text = _clean_path(candidate) if not text: continue if excluded_key and _compact_model(text) == excluded_key: continue return text return "" def _looks_like_same_subject_path(candidate: str, *, subject: str, product_label: str) -> bool: candidate_key = _compact_model(candidate) if not candidate_key: return False for ref in (subject, product_label): ref_key = _compact_model(ref) if not ref_key: continue if candidate_key == ref_key: return True if len(ref_key) >= 6 and candidate_key.startswith(ref_key): return True return False primary = _as_dict(replacements.get("primary_replacement")) primary_display = _row_display(primary) product_display = _product_label(product) subject_display = _clean_path( _norm( match.get("subject_display_name") or match.get("_requested_label") or match.get("matched_alias_text") or match.get("product_id") or match.get("family_group") or match.get("display_name") or "" ) or product_display or primary_display ) lifecycle = self._router_workbook_enrich_lifecycle_row(match, product, lifecycle) same_brand_5g_rows = [row for row in list(replacements.get("same_manufacturer_backup_replacements") or []) if isinstance(row, dict)] backup_rows = [row for row in list(replacements.get("backup_replacements") or []) if isinstance(row, dict)] alt4g = _clean_path(lifecycle.get("alt4g")) rep5g = _clean_path(lifecycle.get("rep5g")) preferred_5g_path = "" for row in same_brand_5g_rows: if _row_is_5g(row): preferred_5g_path = _row_display(row) break if not preferred_5g_path and rep5g: preferred_5g_path = rep5g if not preferred_5g_path and primary and _row_is_5g(primary): preferred_5g_path = primary_display if prefer_5g_target: bridge_candidates = [alt4g] if backup_rows: bridge_candidates.append(_row_display(backup_rows[0])) bridge_candidates.extend([primary_display, product_display]) else: bridge_candidates = [primary_display, product_display, alt4g] if backup_rows: bridge_candidates.append(_row_display(backup_rows[0])) bridge_path = _first_distinct(bridge_candidates, exclude=subject_display) if not bridge_path and prefer_5g_target and preferred_5g_path: bridge_path = _first_distinct(bridge_candidates, exclude=preferred_5g_path) if not bridge_path: bridge_path = _first_distinct(bridge_candidates) if prefer_5g_target: same_brand_path = preferred_5g_path or "Needs exact workbook match" backup_candidates = [bridge_path] if backup_rows: backup_candidates.extend(_row_display(row) for row in backup_rows) backup_candidates.extend([primary_display, product_display, alt4g]) backup_path = _first_distinct(backup_candidates, exclude=subject_display or same_brand_path) if not backup_path: backup_path = "Not listed" if preferred_5g_path and bridge_path and _compact_model(preferred_5g_path) != _compact_model(bridge_path): lane_note = f"5G planning note: current 5G move-forward path is `{preferred_5g_path}`; bridge lane stays `{bridge_path}`." elif preferred_5g_path: lane_note = f"5G planning note: current 5G move-forward path is `{preferred_5g_path}`." elif bridge_path: lane_note = f"5G planning note: no current same-brand 5G row is workbook-ready, so bridge lane `{bridge_path}` stays visible." else: lane_note = "5G planning note: no current same-brand 5G row is workbook-ready yet." else: same_brand_path = primary_display or preferred_5g_path or bridge_path or "Needs exact workbook match" backup_candidates = ( [_row_display(row) for row in same_brand_5g_rows] + [_row_display(row) for row in backup_rows] + [alt4g, product_display] ) backup_path = _first_distinct(backup_candidates, exclude=same_brand_path) if not backup_path: backup_path = "Not listed" lane_note = "" if same_brand_path and _looks_like_same_subject_path(same_brand_path, subject=subject_display, product_label=product_display): same_brand_path = "No direct replacement" if bool(replacements.get("no_replacement")) else "Not listed" if backup_path and _looks_like_same_subject_path(backup_path, subject=subject_display, product_label=product_display): backup_path = "Not listed" if bridge_path and _looks_like_same_subject_path(bridge_path, subject=subject_display, product_label=product_display): bridge_path = "Not listed" return { "same_brand_path": same_brand_path, "backup_path": backup_path, "preferred_5g_path": preferred_5g_path, "bridge_path": bridge_path, "lane_note": lane_note, } def _build_router_workbook_fleet_row_views( self, core: Any, fleet_items: Sequence[Dict[str, Any]], *, manufacturer_text: str = "", prefer_5g_target: bool = False, ) -> List[Dict[str, Any]]: detail_cache: Dict[str, Dict[str, Any]] = {} lane_snapshot_cache: Dict[Tuple[str, bool], Dict[str, str]] = {} resolution_cache: Dict[Tuple[str, str], Dict[str, Any]] = {} row_views: List[Dict[str, Any]] = [] def _fleet_placeholder_token(text: str) -> bool: low = _norm(text).lower() if not low: return False return bool( re.match(r"^(unknown|placeholder)(?:\d+)?$", low) or re.match(r"^(unknown|placeholder)\b", low) ) def _resolve_fleet_detail(product_text: str) -> Dict[str, Any]: normalized_product_text = _norm(product_text) if not normalized_product_text: return {"ok": False, "response": {"error": "product_not_found"}} cache_key = (_norm(manufacturer_text), normalized_product_text) cached = resolution_cache.get(cache_key) if cached is not None: return dict(cached) resolved = _as_dict( self._router_workbook_resolve_detail_or_family( core, manufacturer_text=manufacturer_text, product_text=normalized_product_text, ) ) resolution_cache[cache_key] = dict(resolved) return resolved def _lane_snapshot_for( *, match: Dict[str, Any], product: Dict[str, Any], lifecycle: Dict[str, Any], replacements: Dict[str, Any], input_model: str, ) -> Dict[str, str]: cache_key = (_compact_model(input_model), bool(prefer_5g_target)) cached = lane_snapshot_cache.get(cache_key) if cached is not None: return dict(cached) snapshot = self._router_workbook_fleet_lane_snapshot( core, match, product, lifecycle, replacements, prefer_5g_target=prefer_5g_target, detail_cache=detail_cache, ) lane_snapshot_cache[cache_key] = dict(snapshot) return snapshot for index, item in enumerate(fleet_items, start=1): input_model = _norm(item.get("product_text") or item.get("model_display") or item.get("product_id") or "") customer = str(item.get("customer") or "Unknown").strip() or "Unknown" qty = max(1, int(item.get("qty") or 1)) if not input_model: continue resolved_detail = _as_dict(item.get("_resolved_detail")) resolution_mode = str(item.get("_resolution_mode") or "exact") if resolved_detail: match = _as_dict(resolved_detail.get("match")) product = _as_dict(resolved_detail.get("product")) lifecycle = _as_dict(resolved_detail.get("lifecycle")) replacements = _as_dict(resolved_detail.get("replacements")) lifecycle = self._router_workbook_enrich_lifecycle_row(match, product, lifecycle) family_note = _norm(resolved_detail.get("_family_safe_note") or "") confidence = self._router_workbook_match_confidence(match, input_text=input_model) if resolution_mode != "exact": confidence = { "label": "Family-level provisional", "score": max(60, int(confidence.get("score") or 0)), "matched_alias_text": confidence.get("matched_alias_text"), "matched_alias_type": confidence.get("matched_alias_type"), "correction_note": family_note or "Matched at the workbook family level; exact variant still needs confirmation.", } review_required = ( resolution_mode != "exact" or bool(match.get("feature_gap_blocked")) or not bool(lifecycle.get("has_authoritative_lifecycle")) ) lane_snapshot = _lane_snapshot_for( match=match, product=product, lifecycle=lifecycle, replacements=replacements, input_model=input_model, ) row_views.append( { "row_number": index, "customer": customer, "qty": qty, "input_model": input_model, **(lambda fleet_evidence: { "normalized_model": _norm(fleet_evidence.get("normalized_model") or input_model), "manufacturer_group": _norm(fleet_evidence.get("manufacturer_group") or ""), "confidence_label": _norm(fleet_evidence.get("confidence_label") or confidence["label"]), "confidence_score": int(fleet_evidence.get("confidence_score") or confidence["score"]), "lifecycle_status": _norm(fleet_evidence.get("lifecycle_status") or "Unknown"), "end_of_sale_date": _norm(fleet_evidence.get("end_of_sale_date") or ""), "end_of_life_date": _norm(fleet_evidence.get("end_of_life_date") or ""), "same_brand_path": _norm(fleet_evidence.get("same_brand_path") or ""), "backup_path": _norm(fleet_evidence.get("backup_path") or ""), "preferred_5g_path": _norm(fleet_evidence.get("preferred_5g_path") or ""), "bridge_path": _norm(fleet_evidence.get("bridge_path") or ""), "lane_note": _norm(fleet_evidence.get("lane_note") or ""), "fleet_evidence": fleet_evidence, })( self._router_workbook_fleet_evidence_row( row_number=index, customer=customer, input_model=input_model, normalized_model=_norm(match.get("product_id") or match.get("display_name") or input_model), qty=qty, matched=True, manufacturer_group=_norm(match.get("manufacturer_group") or _as_dict(resolved_detail.get("product")).get("manufacturer_group") or ""), confidence_label=str(confidence.get("label") or ""), confidence_score=int(confidence.get("score") or 0), lifecycle_status=_norm(lifecycle.get("status") or match.get("status_bucket") or "Unknown"), authoritative_lifecycle=bool(lifecycle.get("has_authoritative_lifecycle")), family_level=bool(resolution_mode != "exact"), end_of_sale_date=_norm(lifecycle.get("end_of_sale_date") or ""), end_of_life_date=_norm(lifecycle.get("last_support_date") or ""), same_brand_path=_norm(lane_snapshot.get("same_brand_path") or ""), backup_path=_norm(lane_snapshot.get("backup_path") or ""), preferred_5g_path=_norm(lane_snapshot.get("preferred_5g_path") or ""), bridge_path=_norm(lane_snapshot.get("bridge_path") or ""), replacement_source_mode="workbook", lane_note=_norm(lane_snapshot.get("lane_note") or ""), review_required=review_required, correction_note=str(confidence.get("correction_note") or ""), match_alias_text=str(confidence.get("matched_alias_text") or ""), match_alias_type=str(confidence.get("matched_alias_type") or ""), source_table="DBX_Products", citation_anchor=str(match.get("product_key") or ""), entity_label=input_model, entity_role="fleet_row", evidence_kind="fleet_row", resolution_mode=resolution_mode, candidate_labels=[], error_message="", debug_ref=self._router_workbook_debug_ref( table_name="DBX_Products", key=str(match.get("product_key") or ""), label=f"{_norm(match.get('product_id') or match.get('display_name') or 'Router')} product row", title=f"{_norm(match.get('product_id') or match.get('display_name') or 'Router')} raw workbook product row", ), prefer_5g_target=prefer_5g_target, no_direct_replacement=bool(replacements.get("no_replacement")), uncertainty_flags=[ flag for flag, enabled in [ ("family_level_match", resolution_mode != "exact"), ("lifecycle_dates_incomplete", not bool(lifecycle.get("has_authoritative_lifecycle"))), ("review_required", review_required), ("no_direct_replacement", bool(replacements.get("no_replacement"))), ] if enabled ], ) ), "matched": True, "review_required": review_required, "correction_note": confidence["correction_note"], "match_score": confidence["score"], "match_alias_text": confidence["matched_alias_text"], "match_alias_type": confidence["matched_alias_type"], "debug_ref": self._router_workbook_debug_ref( table_name="DBX_Products", key=str(match.get("product_key") or ""), label=f"{_norm(match.get('product_id') or match.get('display_name') or 'Router')} product row", title=f"{_norm(match.get('product_id') or match.get('display_name') or 'Router')} raw workbook product row", ), } ) continue resolved = _resolve_fleet_detail(input_model) if not resolved.get("ok"): response = _as_dict(resolved.get("response")) candidates = [ _norm(_as_dict(candidate).get("display_name") or _as_dict(candidate).get("product_id") or "") for candidate in list(response.get("candidates") or []) if isinstance(candidate, dict) ] candidates = [candidate for candidate in candidates if candidate] is_placeholder = _fleet_placeholder_token(input_model) or any(_fleet_placeholder_token(candidate) for candidate in candidates) typo_candidate = "" if is_placeholder else self._router_workbook_likely_typo_candidate(input_model) correction_note = ( f"looks like a typo for `{typo_candidate}` because it did not resolve to a workbook row; " "I left it provisional instead of forcing a workbook match." if typo_candidate else "" ) row_views.append( { "row_number": index, "customer": customer, "qty": qty, "input_model": input_model, "normalized_model": "", "manufacturer_group": "", "confidence_label": "Needs review", "confidence_score": 0, "lifecycle_status": "Unidentified placeholder token" if is_placeholder else "Needs exact workbook match", "end_of_sale_date": "", "end_of_life_date": "", "same_brand_path": "Not listed" if is_placeholder else "", "backup_path": "Not listed" if is_placeholder else "", "matched": False, "review_required": True, "correction_note": correction_note, "candidate_labels": candidates[:3], "error_message": _norm(response.get("message") or "No workbook match was found."), "fleet_evidence": self._router_workbook_fleet_evidence_row( row_number=index, customer=customer, input_model=input_model, normalized_model="", qty=qty, matched=False, manufacturer_group="", confidence_label="Needs review", confidence_score=0, lifecycle_status="Unidentified placeholder token" if is_placeholder else "Needs exact workbook match", authoritative_lifecycle=False, family_level=False, end_of_sale_date="", end_of_life_date="", same_brand_path="Not listed" if is_placeholder else "", backup_path="Not listed" if is_placeholder else "", preferred_5g_path="", bridge_path="", replacement_source_mode="workbook", lane_note="", review_required=True, correction_note=correction_note, match_alias_text="", match_alias_type="", source_table="DBX_Products", citation_anchor=f"row:{index}", entity_label=input_model, entity_role="fleet_row", evidence_kind="fleet_row", resolution_mode="unresolved", candidate_labels=candidates[:3], error_message=_norm(response.get("message") or "No workbook match was found."), debug_ref="", prefer_5g_target=prefer_5g_target, no_direct_replacement=False, uncertainty_flags=["placeholder_token"] if is_placeholder else ["needs_exact_match"], ), } ) continue detail = _as_dict(resolved.get("detail")) match = _as_dict(detail.get("match")) product = _as_dict(detail.get("product")) lifecycle = _as_dict(detail.get("lifecycle")) replacements = _as_dict(detail.get("replacements")) lifecycle = self._router_workbook_enrich_lifecycle_row(match, product, lifecycle) confidence = self._router_workbook_match_confidence(match, input_text=input_model) resolution_mode = str(resolved.get("resolution_mode") or "exact") if resolution_mode != "exact": confidence = { "label": "Family-level provisional", "score": max(60, int(confidence.get("score") or 0)), "matched_alias_text": confidence.get("matched_alias_text"), "matched_alias_type": confidence.get("matched_alias_type"), "correction_note": _norm(detail.get("_family_safe_note") or "") or "Matched at the workbook family level; exact variant still needs confirmation.", } review_required = ( resolution_mode != "exact" or bool(match.get("feature_gap_blocked")) or not bool(lifecycle.get("has_authoritative_lifecycle")) ) lane_snapshot = _lane_snapshot_for( match=match, product=product, lifecycle=lifecycle, replacements=replacements, input_model=input_model, ) row_views.append( { "row_number": index, "customer": customer, "qty": qty, "input_model": input_model, **(lambda fleet_evidence: { "normalized_model": _norm(fleet_evidence.get("normalized_model") or ""), "manufacturer_group": _norm(fleet_evidence.get("manufacturer_group") or ""), "confidence_label": _norm(fleet_evidence.get("confidence_label") or confidence["label"]), "confidence_score": int(fleet_evidence.get("confidence_score") or confidence["score"]), "lifecycle_status": _norm(fleet_evidence.get("lifecycle_status") or "Unknown"), "end_of_sale_date": _norm(fleet_evidence.get("end_of_sale_date") or ""), "end_of_life_date": _norm(fleet_evidence.get("end_of_life_date") or ""), "same_brand_path": _norm(fleet_evidence.get("same_brand_path") or ""), "backup_path": _norm(fleet_evidence.get("backup_path") or ""), "preferred_5g_path": _norm(fleet_evidence.get("preferred_5g_path") or ""), "bridge_path": _norm(fleet_evidence.get("bridge_path") or ""), "lane_note": _norm(fleet_evidence.get("lane_note") or ""), "fleet_evidence": fleet_evidence, })( self._router_workbook_fleet_evidence_row( row_number=index, customer=customer, input_model=input_model, normalized_model=_norm(match.get("product_id") or match.get("display_name") or ""), qty=qty, matched=True, manufacturer_group=_norm(match.get("manufacturer_group") or ""), confidence_label=str(confidence.get("label") or ""), confidence_score=int(confidence.get("score") or 0), lifecycle_status=_norm(lifecycle.get("status") or match.get("status_bucket") or "Unknown"), authoritative_lifecycle=bool(lifecycle.get("has_authoritative_lifecycle")), family_level=bool(resolution_mode != "exact"), end_of_sale_date=_norm(lifecycle.get("end_of_sale_date") or ""), end_of_life_date=_norm(lifecycle.get("last_support_date") or ""), same_brand_path=_norm(lane_snapshot.get("same_brand_path") or ""), backup_path=_norm(lane_snapshot.get("backup_path") or ""), preferred_5g_path=_norm(lane_snapshot.get("preferred_5g_path") or ""), bridge_path=_norm(lane_snapshot.get("bridge_path") or ""), replacement_source_mode="workbook", lane_note=_norm(lane_snapshot.get("lane_note") or ""), review_required=review_required, correction_note=str(confidence.get("correction_note") or ""), match_alias_text=str(confidence.get("matched_alias_text") or ""), match_alias_type=str(confidence.get("matched_alias_type") or ""), source_table="DBX_Products", citation_anchor=str(match.get("product_key") or ""), entity_label=input_model, entity_role="fleet_row", evidence_kind="fleet_row", resolution_mode=resolution_mode, candidate_labels=[], error_message="", debug_ref=self._router_workbook_debug_ref( table_name="DBX_Products", key=str(match.get("product_key") or ""), label=f"{_norm(match.get('product_id') or match.get('display_name') or 'Router')} product row", title=f"{_norm(match.get('product_id') or match.get('display_name') or 'Router')} raw workbook row", ), prefer_5g_target=prefer_5g_target, no_direct_replacement=bool(replacements.get("no_replacement")), uncertainty_flags=[ flag for flag, enabled in [ ("family_level_match", resolution_mode != "exact"), ("lifecycle_dates_incomplete", not bool(lifecycle.get("has_authoritative_lifecycle"))), ("review_required", review_required), ("no_direct_replacement", bool(replacements.get("no_replacement"))), ] if enabled ], ) ), "matched": True, "review_required": review_required, "correction_note": confidence["correction_note"], "match_score": confidence["score"], "match_alias_text": confidence["matched_alias_text"], "match_alias_type": confidence["matched_alias_type"], "debug_ref": self._router_workbook_debug_ref( table_name="DBX_Products", key=str(match.get("product_key") or ""), label=f"{_norm(match.get('product_id') or match.get('display_name') or 'Router')} product row", title=f"{_norm(match.get('product_id') or match.get('display_name') or 'Router')} raw workbook row", ), } ) return row_views def _router_workbook_inventory_rows_from_matrix( self, rows: Sequence[Sequence[Any]], *, filename: str, ) -> Dict[str, Any]: normalized_rows = [ [str(cell or "").strip() for cell in list(row)] for row in rows if any(str(cell or "").strip() for cell in list(row)) ] if not normalized_rows: return {"ok": False, "message": f"`{filename}` appears to be empty."} def _header_key(value: Any) -> str: return re.sub(r"[^a-z0-9]+", "", str(value or "").strip().lower()) def _detect_header_row() -> int: for index, row in enumerate(normalized_rows[:8]): keys = [_header_key(cell) for cell in row] if any( key in {"routermodel", "model", "device", "router", "sku"} or "model" in key or "device" in key or "router" in key for key in keys ): return index return 0 header_index = _detect_header_row() headers = normalized_rows[header_index] header_keys = [_header_key(cell) for cell in headers] def _find_column(*candidates: str) -> int: for candidate in candidates: if candidate in header_keys: return header_keys.index(candidate) for index, key in enumerate(header_keys): if any(candidate in key for candidate in candidates): return index return -1 model_idx = _find_column("routermodel", "model", "device", "router", "sku") qty_idx = _find_column("quantity", "qty", "count", "totaldevices", "totaldevice", "units") customer_idx = _find_column("customername", "customer", "client", "account", "company", "accountname") if model_idx < 0: return { "ok": False, "message": f"`{filename}` is missing a router/model column. Use headers like `Model`, `Device`, `Router`, or `SKU`.", } parsed_rows: List[Dict[str, Any]] = [] for offset, row in enumerate(normalized_rows[header_index + 1 :], start=header_index + 2): model = row[model_idx].strip() if model_idx < len(row) else "" if not model: continue qty = 1 if qty_idx >= 0 and qty_idx < len(row): raw_qty = str(row[qty_idx] or "").strip() if raw_qty: try: qty = max(1, int(float(raw_qty))) except Exception: qty = 1 customer = "Unknown" if customer_idx >= 0 and customer_idx < len(row): customer = _norm(row[customer_idx] or "Unknown") or "Unknown" parsed_rows.append( { "row_number": offset, "customer": customer, "qty": qty, "product_text": model, "model_display": model, } ) if not parsed_rows: return {"ok": False, "message": f"`{filename}` did not contain any usable router rows after the header."} return {"ok": True, "rows": parsed_rows} def _parse_router_workbook_inventory_csv_text(self, text: str, *, filename: str) -> Dict[str, Any]: raw = str(text or "").strip() if not raw: return {"ok": False, "message": "Paste router inventory rows or upload a CSV/XLSX file first."} sample = raw[:4096] try: dialect = csv.Sniffer().sniff(sample) except Exception: dialect = csv.excel matrix = list(csv.reader(io.StringIO(raw), dialect)) return self._router_workbook_inventory_rows_from_matrix(matrix, filename=filename) def _parse_router_workbook_inventory_xlsx_bytes(self, data: bytes, *, filename: str) -> Dict[str, Any]: try: import openpyxl # type: ignore except Exception: return {"ok": False, "message": "XLSX support is not available in this runtime."} try: workbook = openpyxl.load_workbook(io.BytesIO(data), data_only=True, read_only=True) except Exception as exc: return {"ok": False, "message": f"Couldn't read `{filename}` as an Excel workbook: {exc}"} for sheet in workbook.worksheets: matrix = [[cell for cell in row] for row in sheet.iter_rows(values_only=True)] parsed = self._router_workbook_inventory_rows_from_matrix(matrix, filename=filename) if parsed.get("ok"): return parsed return {"ok": False, "message": f"`{filename}` did not contain a usable router inventory sheet."} def handle_router_inventory_import( self, *, file_bytes: bytes | None, filename: str, pasted_text: str, mode: str = "router_lifecycle", audience: str = "auto", show_citations: bool = True, ) -> Dict[str, Any]: core = self._rapid_router_intelligence_core() domain = "router_lifecycle" domain_label = "Router lifecycle" workbook_file = str(self._rapid_router_intelligence_status().get("filename") or "router_workbook.xlsx") workbook_sources = self._router_workbook_sources(domain, "fleet_lifecycle") if core is None: assistant = _format_shell( "Workbook-backed router intelligence is not available right now.", ["The inventory import flow only runs against the workbook-backed router catalog."], ["Load the workbook catalog first, then retry the fleet import."], ) return { "assistant": assistant, "state": {}, "prompt_version": f"unified-{PROMPT_VERSION}", "sources": self._normalize_sources(domain, workbook_sources), "files": self._normalize_domain_files(domain, [workbook_file]), "effective_audience": "external" if audience == "auto" else audience, "meta": { "domain": domain, "domain_label": domain_label, "retrieval_mode": "deterministic_router_workbook_inventory_import_unavailable", "router_intelligence_intent": "fleet_lifecycle", "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, }, } parsed: Dict[str, Any] normalized_filename = _norm(filename or "") if file_bytes: lower_name = normalized_filename.lower() if lower_name.endswith(".xlsx"): parsed = self._parse_router_workbook_inventory_xlsx_bytes(file_bytes, filename=normalized_filename or "inventory.xlsx") else: text = "" for encoding in ("utf-8-sig", "utf-8", "cp1252", "latin-1"): try: text = file_bytes.decode(encoding) break except UnicodeDecodeError: continue parsed = self._parse_router_workbook_inventory_csv_text(text, filename=normalized_filename or "inventory.csv") else: parsed = self._parse_router_workbook_inventory_csv_text(pasted_text, filename="pasted inventory") if not parsed.get("ok"): parsed_rows = self._extract_router_workbook_fleet_items(pasted_text, core) if parsed_rows: parsed = {"ok": True, "rows": parsed_rows} if not parsed.get("ok"): assistant = _format_shell( str(parsed.get("message") or "I could not parse that router inventory input."), ["The workbook fleet import needs either a CSV/XLSX table or clear pasted router rows."], [ "Paste CSV with `Customer`, `Model`, and optional `Quantity` columns.", "Or upload a CSV/XLSX file with a router/model column.", ], ) return { "assistant": assistant, "state": {}, "prompt_version": f"unified-{PROMPT_VERSION}", "sources": self._normalize_sources(domain, workbook_sources), "files": self._normalize_domain_files(domain, [workbook_file]), "effective_audience": "external" if audience == "auto" else audience, "meta": { "domain": domain, "domain_label": domain_label, "retrieval_mode": "deterministic_router_workbook_inventory_import_unmatched", "router_intelligence_intent": "fleet_lifecycle", "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, }, } fleet_items = [row for row in list(parsed.get("rows") or []) if isinstance(row, dict)] row_views = self._build_router_workbook_fleet_row_views(core, fleet_items) fleet_view = self._router_workbook_fleet_view_from_rows( row_views, source_label="Uploaded inventory" if file_bytes else "Pasted inventory", uploaded_filename=normalized_filename, ) review_required = bool(fleet_view.get("unmatched_count")) or bool(fleet_view.get("review_required_count")) router_answer_trace = { "summary": f"Built a workbook-backed fleet snapshot for {int(fleet_view.get('row_count') or 0)} imported inventory row(s).", "items": [ {"label": "Fleet rows analyzed", "value": str(fleet_view.get("row_count") or 0)}, {"label": "Matched workbook rows", "value": str(fleet_view.get("matched_count") or 0)}, {"label": "Unmatched rows", "value": str(fleet_view.get("unmatched_count") or 0)}, {"label": "Review-required rows", "value": str(fleet_view.get("review_required_count") or 0)}, {"label": "Import source", "value": _norm(fleet_view.get("uploaded_filename") or fleet_view.get("source_label") or "")}, ], "warnings": [ f"{int(fleet_view.get('unmatched_count') or 0)} imported row(s) still need an exact workbook match." if int(fleet_view.get("unmatched_count") or 0) else "", f"{int(fleet_view.get('review_required_count') or 0)} matched row(s) still carry workbook review flags." if int(fleet_view.get("review_required_count") or 0) else "", ], "source_tables": self._router_workbook_source_tables("fleet_lifecycle"), } assistant = _format_shell( self._render_router_workbook_inventory_import_result(fleet_view), [ "This import normalizes each row against the workbook-backed router catalog before showing lifecycle and replacement paths.", "Match confidence is explicit so reps can tell the difference between exact matches, alias corrections, and rows that still need review.", ], [ "Ask me to rank this fleet by migration urgency if you want a rollout order.", "Ask me to compare any two matched routers side by side.", ], ) return { "assistant": assistant, "state": {}, "prompt_version": f"unified-{PROMPT_VERSION}", "sources": self._normalize_sources(domain, workbook_sources), "files": self._normalize_domain_files(domain, [workbook_file]), "effective_audience": "external" if audience == "auto" else audience, "meta": { "domain": domain, "domain_label": domain_label, "retrieval_mode": "deterministic_router_workbook_inventory_import", "router_intelligence_intent": "fleet_lifecycle", "router_intelligence_source": "workbook", "router_workbook_tables": self._router_workbook_source_tables("fleet_lifecycle"), "router_answer_trace": router_answer_trace, "router_fleet_view": fleet_view, "router_debug_refs": self._router_workbook_compact_debug_refs( [row.get("debug_ref") for row in row_views if isinstance(row, dict)] ), "review_required": review_required, "citation_quorum_not_required": True, "legacy_csv_replaced": True, }, } def _handle_router_workbook_guided_advisor_followup( self, reply: str, st: UnifiedKnowledgebaseState, pending: Dict[str, Any], *, mode: str, audience: str, show_citations: bool, ) -> Optional[Dict[str, Any]]: core = self._rapid_router_intelligence_core() if core is None: return None domain = _norm_mode(mode) or "router_lifecycle" workbook_file = str(self._rapid_router_intelligence_status().get("filename") or "router_workbook.xlsx") workbook_sources = self._router_workbook_sources(domain, "guided_advisor") questions = self._router_workbook_guided_advisor_questions() try: question_index = max(0, int(pending.get("question_index") or 0)) except Exception: question_index = 0 if question_index >= len(questions): return None parsed = self._parse_router_workbook_guided_advisor_reply(reply, pending) if not parsed.get("ok"): current_prompt = str(questions[question_index]["prompt"] or "").strip() return { "assistant": _format_shell( "I still need a clear answer for the current guided router-advisor question before I can continue.", [ "This five-question advisor only uses structured answers so the final shortlist stays workbook-backed and deterministic.", ], [f"Question {question_index + 1} of {len(questions)}: {current_prompt}"], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": self._normalize_sources(domain, workbook_sources), "files": self._normalize_domain_files(domain, [workbook_file]), "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "deterministic_router_workbook_guided_advisor_question", "router_intelligence_intent": "guided_advisor", "router_intelligence_source": "workbook", "guided_advisor_pending": True, "guided_advisor_question_index": question_index + 1, "guided_advisor_total_questions": len(questions), "router_workbook_tables": self._router_workbook_source_tables("guided_advisor"), "citation_quorum_not_required": True, "legacy_csv_replaced": True, }, } answers = _as_dict(pending.get("answers")) field_name = str(parsed.get("field") or "").strip() answers[field_name] = parsed.get("value") answers[f"{field_name}_label"] = str(parsed.get("label") or "").strip() next_index = question_index + 1 if next_index < len(questions): st.pending = { **pending, "question_index": next_index, "answers": answers, } st.router_lifecycle_state = { **_as_dict(st.router_lifecycle_state), "guided_advisor_answers": answers, "pending": _as_dict(st.pending), } next_prompt = str(questions[next_index]["prompt"] or "").strip() return { "assistant": _format_shell( f"Captured: {str(parsed.get('label') or '').strip()}.", [ "I’m keeping the advisor intake structured so the final router and antenna shortlist can stay workbook-backed.", ], [f"Question {next_index + 1} of {len(questions)}: {next_prompt}"], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": self._normalize_sources(domain, workbook_sources), "files": self._normalize_domain_files(domain, [workbook_file]), "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "deterministic_router_workbook_guided_advisor_question", "router_intelligence_intent": "guided_advisor", "router_intelligence_source": "workbook", "guided_advisor_pending": True, "guided_advisor_question_index": next_index + 1, "guided_advisor_total_questions": len(questions), "guided_advisor_answers": answers, "router_workbook_tables": self._router_workbook_source_tables("guided_advisor"), "citation_quorum_not_required": True, "legacy_csv_replaced": True, }, } st.pending = {} st.router_lifecycle_state = { **_as_dict(st.router_lifecycle_state), "guided_advisor_answers": answers, "pending": {}, } recommendation = self._build_router_workbook_guided_advisor_recommendations(core, answers) review_required = bool(recommendation.get("review_required")) shortlist_view = self._router_workbook_guided_advisor_shortlist_view(recommendation) return { "assistant": _format_shell( self._render_router_workbook_guided_advisor_result(recommendation), [ "This shortlist is ranked from current workbook-backed router matches plus workbook antenna/BOM paths where available.", "The advisor keeps current-only as the default lane and does not widen into legacy hardware unless you ask.", ], [ "Ask me to compare any two of these suggestions side by side.", "Ask for a deeper antenna/BOM path on the option you want to pursue next.", ], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": self._normalize_sources(domain, workbook_sources), "files": self._normalize_domain_files(domain, [workbook_file]), "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "deterministic_router_workbook_guided_advisor", "router_intelligence_intent": "guided_advisor", "router_intelligence_source": "workbook", "guided_advisor_pending": False, "guided_advisor_total_questions": len(questions), "guided_advisor_answers": answers, "router_workbook_tables": self._router_workbook_source_tables("guided_advisor"), "router_answer_trace": self._router_workbook_guided_advisor_trace(answers, recommendation), "router_shortlist_view": shortlist_view, "router_debug_refs": self._router_workbook_compact_debug_refs( [item.get("debug_ref") for item in list(shortlist_view.get("items") or []) if isinstance(item, dict)] ), "review_required": review_required, "citation_quorum_not_required": True, "legacy_csv_replaced": True, }, } def _extract_router_workbook_fleet_items(self, message: str, core: Any) -> List[Dict[str, Any]]: text = str(message or "") if not text: return [] token_matches = [m for m in re.finditer(r"[A-Za-z0-9'\\-]+", text) if m.group(0)] tokens = [str(m.group(0)) for m in token_matches] if not token_matches: return [] def _clean_customer_label(raw_customer: str) -> str: customer = str(raw_customer or "").strip(" ,.;:-") if not customer: return "" customer = re.sub( r"(?i)\b(has|have|had|wants?|need(?:s)?|plans?|planning|replace|replacing|with|for)\b.*$", "", customer, ).strip(" ,.;:-") customer = re.sub(r"\s+", " ", customer).strip() if not customer: return "" if _router_customer_phrase_looks_instructional(customer): return "" if _valid_customer_marker(customer): return customer return "" def _choose_customer_display(existing: str, candidate: str) -> str: cand = str(candidate or "").strip() if not cand: return existing if not existing: return cand existing_has_punct = bool(re.search(r"[^A-Za-z0-9 ]", existing)) cand_has_punct = bool(re.search(r"[^A-Za-z0-9 ]", cand)) if cand_has_punct and not existing_has_punct: return cand if any(ch.isupper() for ch in cand) and not any(ch.isupper() for ch in existing): return cand if len(cand) > len(existing): return cand return existing def _customer_bucket_key(customer_name: str, existing_keys: set[str]) -> str: # Keep customer buckets strict: customer names that only differ by punctuation # or wording should stay separate unless they are literally the same customer label. key = _norm(customer_name).casefold() if not key: return "unknown" return key def _valid_customer_marker(raw_customer: str) -> bool: customer = str(raw_customer or "").strip(" ,.;:-") if not customer: return False low_customer = customer.lower() if _router_customer_phrase_looks_instructional(customer): return False first_word = low_customer.split()[0] if low_customer.split() else "" if first_word in _ROUTER_FLEET_CUSTOMER_FIRST_WORD_STOPWORDS: return False compact_customer = _compact_model(customer) if compact_customer and ( self._lookup_router_fact_key(customer) or self._lookup_router_fact_key(compact_customer) or self._lookup_router_lifecycle_key_relaxed(customer) or self._lookup_router_lifecycle_key_relaxed(compact_customer) ): return False meaningful_tokens = 0 matched_router_tokens = 0 for token in re.findall(r"[A-Za-z0-9'\\-]+", customer): compact = _compact_model(token) if not compact: continue meaningful_tokens += 1 if ( self._lookup_router_fact_key(token) or self._lookup_router_fact_key(compact) or self._lookup_router_lifecycle_key_relaxed(token) or self._lookup_router_lifecycle_key_relaxed(compact) ): matched_router_tokens += 1 if meaningful_tokens and matched_router_tokens == meaningful_tokens: return False return True customer_markers: List[Tuple[int, str]] = [(0, "Unknown")] for m in re.finditer( r"\bcustomer\b[\s,:-]*([A-Za-z0-9&' .\-]{2,60}?)(?:\s+with|\s+has)\s+(?=\d)", text, flags=re.IGNORECASE, ): customer = _clean_customer_label(str(m.group(1) or "")) if customer: customer_markers.append((int(m.end()), customer)) for m in re.finditer(r"\b(?:and\s+)?(?:we\s+)?(?:also\s+)?found\s+(?=\d)", text, flags=re.IGNORECASE): customer_markers.append((int(m.end()), "Unknown")) customer_markers.sort(key=lambda item: item[0]) def _customer_from_prefix(pos: int) -> str: prefix = str(text[: max(0, pos)] or "").strip(" ,.;:-") if not prefix: return "" segments = [ seg.strip(" ,.;:-") for seg in re.split(r"(?:,|:|;|\band\b|\bplus\b|\+)", prefix, flags=re.IGNORECASE) if seg and seg.strip(" ,.;:-") ] for segment in reversed(segments): tail = re.sub(r"(?i)\b(?:has|have|had|with)\s*$", "", segment).strip(" ,.;:-") if not tail: continue low_tail = tail.lower() if _router_customer_phrase_looks_instructional(tail): continue if not any(ch.isalpha() for ch in tail): continue if any(ch.isdigit() for ch in tail): continue if low_tail.startswith("unknown") or low_tail.startswith("placeholder"): return "Unknown" cleaned_tail = _norm(tail) if cleaned_tail: return cleaned_tail return "" def _customer_for_position(pos: int) -> str: customer = "Unknown" for marker_pos, marker_name in customer_markers: if marker_pos <= pos: customer = marker_name or "Unknown" else: break if customer and customer != "Unknown": return customer return customer or "Unknown" resolution_cache: Dict[Tuple[str, str], Dict[str, Any]] = {} def _resolve_fleet_detail(product_text: str) -> Dict[str, Any]: normalized_product_text = _norm(product_text) if not normalized_product_text: return {"ok": False, "response": {"error": "product_not_found"}} cache_key = ("", normalized_product_text) cached = resolution_cache.get(cache_key) if cached is not None: return dict(cached) resolved = _as_dict( self._router_workbook_resolve_detail_or_family( core, manufacturer_text="", product_text=normalized_product_text, ) ) resolution_cache[cache_key] = dict(resolved) return resolved def _bare_fleet_chunk_looks_like_model(token: str) -> bool: text_token = str(token or "").strip(" \t\r\n,.;:()[]{}") if not text_token: return False compact = _compact_model(text_token) if (not compact) or compact.lower() in _ROUTER_FLEET_MODEL_STOPWORDS: return False if not any(ch.isdigit() for ch in compact): return False first_word = _compact_model(re.split(r"\s+", text_token, maxsplit=1)[0]) if not first_word: return False if first_word.lower() in { "provide", "replacement", "table", "confidence", "notes", "clarification", "clarifications", "provisional", "alternatives", "strategy", "recommend", "recommendation", "recommendations", "customer", "fleet", "portfolio", "inventory", "model", "models", "device", "devices", "need", "needs", "build", "phased", "combined", "unknown", "lifecycle", "q1", "q2", "q3", "q4", "by", "5g", "4g", "lte", }: return False if first_word in _ROUTER_VENDOR_TOKEN_PREFIXES: return True if re.search(r"^[A-Za-z]{1,8}\d", first_word): return True return bool( self._lookup_router_fact_key(text_token) or self._lookup_router_fact_key(compact) or self._lookup_router_lifecycle_key_relaxed(text_token) or self._lookup_router_lifecycle_key_relaxed(compact) ) def _customer_for_token_index(token_index: int) -> str: if token_index < 0 or token_index >= len(token_matches): return "Unknown" pos = int(token_matches[token_index].start()) return _customer_for_position(pos) parsed: List[Dict[str, Any]] = [] current_customer = "Unknown" for pair in re.finditer(r"\b(\d{1,5})\s+([A-Za-z][A-Za-z0-9\-]{1,40})\b", text): qty = int(str(pair.group(1) or "0") or 0) if qty <= 0 or qty > 100000: continue raw_blob = str(pair.group(2) or "").strip(" ,.;:-") if not raw_blob: continue cand_tokens = [tok for tok in raw_blob.split() if tok] if not cand_tokens: continue customer_candidate = _customer_from_prefix(int(pair.start())) if customer_candidate: current_customer = customer_candidate customer = current_customer or "Unknown" best: Optional[Tuple[int, Dict[str, Any], str]] = None max_width = min(3, len(cand_tokens)) for width in range(max_width, 0, -1): slice_tokens = cand_tokens[:width] first_token = _compact_model(slice_tokens[0]) if slice_tokens else "" if first_token.lower() in _ROUTER_FLEET_MODEL_STOPWORDS: continue trailing_tokens = [_compact_model(tok) for tok in slice_tokens[1:] if _compact_model(tok)] if any(tok.isalpha() and len(tok) > 1 and tok not in _ROUTER_FLEET_MODEL_JOINER_TOKENS for tok in trailing_tokens): continue candidate_forms = [ " ".join(slice_tokens).strip(), "".join(slice_tokens).strip(), ] if len(slice_tokens) >= 2 and first_token in _ROUTER_VENDOR_TOKEN_PREFIXES: candidate_forms.extend( [ " ".join(slice_tokens[1:]).strip(), "".join(slice_tokens[1:]).strip(), ] ) for cand in candidate_forms: compact = _compact_model(cand) if (not compact) or compact.lower() in _ROUTER_FLEET_MODEL_STOPWORDS: continue if not any(ch.isalpha() for ch in compact): continue resolved = _resolve_fleet_detail(cand) if not resolved.get("ok"): continue detail = _as_dict(resolved.get("detail")) match = _as_dict(detail.get("match")) or _as_dict(detail.get("product")) product_key = str(match.get("product_key") or "") if not product_key: continue requested_label = _norm(" ".join(slice_tokens)) or cand score = 100 - (max_width - width) * 6 if compact == _compact_model(match.get("product_id") or ""): score += 8 resolution_mode = str(resolved.get("resolution_mode") or "exact") if resolution_mode != "exact": score -= 2 row = (score, detail, requested_label, resolution_mode) if (best is None) or (row > best): best = row if best is not None: _, detail, requested_label, resolution_mode = best match = _as_dict(detail.get("match")) or _as_dict(detail.get("product")) parsed.append( { "customer": customer, "qty": qty, "matched": True, "product_key": str(match.get("product_key") or ""), "model_display": requested_label, "product_text": str(match.get("product_id") or requested_label), "_resolved_detail": detail, "_resolution_mode": resolution_mode, "_family_collapsed": bool( resolution_mode != "exact" or match.get("_family_collapsed") or _as_dict(detail.get("product")).get("_family_collapsed") ), } ) continue raw_model = cand_tokens[0] compact = _compact_model(raw_model) if compact and compact.lower() not in _ROUTER_FLEET_MODEL_STOPWORDS: if any(ch.isalpha() for ch in compact): parsed.append( { "customer": customer, "qty": qty, "matched": False, "product_key": "", "model_display": raw_model, "product_text": raw_model, } ) if not parsed and re.search( r"\b(fleet|portfolio|inventory|migration order|risk ranking|replacement order|replacement table|lifecycle)\b", text, flags=re.IGNORECASE, ): bare_chunks: List[Tuple[int, str]] = [] for m in re.finditer(r"\(([^()]{4,240})\)", text): bare_chunks.append((int(m.start(1)), str(m.group(1) or ""))) if not bare_chunks: for m in re.finditer( r"\b(?:fleet|portfolio|inventory|models?|devices?)\b[^:]{0,20}[:\-]\s*([^.;]{4,240})", text, flags=re.IGNORECASE, ): bare_chunks.append((int(m.start(1)), str(m.group(1) or ""))) if not bare_chunks and re.search(r"[;,]", text): list_match = re.search( r"\b(?:fleet|portfolio|inventory|models?|devices?)\b\s+([^.;]{4,240})", text, flags=re.IGNORECASE, ) if list_match: bare_chunks.append((int(list_match.start(1)), str(list_match.group(1) or ""))) seen_bare_models: set[str] = set() for base_pos, chunk in bare_chunks: for part in re.split(r"(?:,|/|;|\band\b|\bplus\b|\+)", chunk, flags=re.IGNORECASE): token = str(part or "").strip(" \t\r\n,.;:()[]{}") if not token: continue token = re.sub( r"(?i)^\b(?:mixed|legacy|current|fleet|portfolio|inventory|models?|devices?)\b[\s:-]*", "", token, ).strip(" \t\r\n,.;:()[]{}") if not _bare_fleet_chunk_looks_like_model(token): continue bare_key = _compact_model(token) if not bare_key or bare_key in seen_bare_models: continue customer_candidate = _customer_from_prefix(base_pos) if customer_candidate: current_customer = customer_candidate customer = current_customer or "Unknown" resolved = _resolve_fleet_detail(token) if not resolved.get("ok") and bare_key != _compact_model(token): resolved = _resolve_fleet_detail(bare_key) if resolved.get("ok"): detail = _as_dict(resolved.get("detail")) match = _as_dict(detail.get("match")) or _as_dict(detail.get("product")) product_key = str(match.get("product_key") or "") if not product_key: continue parsed.append( { "customer": customer, "qty": 1, "matched": True, "product_key": product_key, "model_display": _norm(token) or token, "product_text": str(match.get("product_id") or token), "_resolved_detail": detail, "_resolution_mode": str(resolved.get("resolution_mode") or "exact"), "_family_collapsed": bool( str(resolved.get("resolution_mode") or "") != "exact" or match.get("_family_collapsed") or _as_dict(detail.get("product")).get("_family_collapsed") ), } ) seen_bare_models.add(bare_key) continue parsed.append( { "customer": customer, "qty": 1, "matched": False, "product_key": "", "model_display": _norm(token) or token, "product_text": token, } ) seen_bare_models.add(bare_key) if not parsed: return [] bucket: Dict[Tuple[str, str], Dict[str, Any]] = {} seen_customer_keys: set[str] = set() for row in parsed: customer = _clean_customer_label(str(row.get("customer") or "")) or "Unknown" customer_key = _customer_bucket_key(customer, seen_customer_keys) seen_customer_keys.add(customer_key) identity = str(row.get("product_key") or row.get("product_text") or "") if not identity: continue key = (customer_key, identity) if key not in bucket: bucket[key] = {**dict(row), "customer": customer} else: bucket[key]["qty"] = int(bucket[key].get("qty") or 0) + int(row.get("qty") or 0) bucket[key]["matched"] = bool(bucket[key].get("matched")) and bool(row.get("matched")) if not bucket[key].get("customer"): bucket[key]["customer"] = customer if (not bucket[key].get("_resolved_detail")) and row.get("_resolved_detail"): bucket[key]["_resolved_detail"] = row.get("_resolved_detail") bucket[key]["_resolution_mode"] = row.get("_resolution_mode") bucket[key]["_family_collapsed"] = row.get("_family_collapsed") return list(bucket.values()) def _resolve_router_workbook_survey_context( self, message: str, st: UnifiedKnowledgebaseState, core: Any, ) -> Dict[str, Any]: explicit_match = re.search(r"\b(survey[_\-:A-Za-z0-9]{6,})\b", str(message or ""), flags=re.IGNORECASE) explicit_key = str(explicit_match.group(1) or "").strip() if explicit_match else "" state_key = str(_as_dict(st.router_lifecycle_state).get("last_survey_key") or "").strip() surveys_out = _as_dict(core.list_catalog_surveys(limit=10)) surveys = [row for row in list(surveys_out.get("surveys") or []) if isinstance(row, dict)] if explicit_key: matched = next((row for row in surveys if str(row.get("survey_key") or "") == explicit_key), None) if matched: return {"ok": True, "survey_key": explicit_key, "survey": matched, "surveys": surveys} return { "ok": False, "error": "survey_not_found", "message": f"I could not find survey `{explicit_key}` in the workbook runtime tables.", "surveys": surveys, } if state_key: matched = next((row for row in surveys if str(row.get("survey_key") or "") == state_key), None) if matched: return {"ok": True, "survey_key": state_key, "survey": matched, "surveys": surveys} if len(surveys) == 1: only = surveys[0] return {"ok": True, "survey_key": str(only.get("survey_key") or ""), "survey": only, "surveys": surveys} low = str(message or "").lower() named_matches = [ row for row in surveys if str(row.get("site_name") or "").strip() and str(row.get("site_name") or "").lower() in low ] if len(named_matches) == 1: return { "ok": True, "survey_key": str(named_matches[0].get("survey_key") or ""), "survey": named_matches[0], "surveys": surveys, } if not surveys: return { "ok": False, "error": "survey_missing", "message": "No workbook survey runtime rows are loaded yet.", "surveys": [], } return { "ok": False, "error": "survey_ambiguous", "message": "Multiple workbook survey rows are available, so I need the survey key or site name before I answer.", "surveys": surveys, } def _router_workbook_collapse_family_ambiguity( self, requested_text: str, response: Dict[str, Any], ) -> Optional[Dict[str, Any]]: requested_compact = _compact_model(requested_text) if not requested_compact: return None candidates = [item for item in list(response.get("candidates") or []) if isinstance(item, dict)] if len(candidates) < 2: return None manufacturer_keys = { _norm(item.get("manufacturer_group") or item.get("manufacturer_id") or "") for item in candidates if _norm(item.get("manufacturer_group") or item.get("manufacturer_id") or "") } if len(manufacturer_keys) != 1: return None matching_candidates: List[Dict[str, Any]] = [] family_keys: set[str] = set() for item in candidates: candidate_keys = { _compact_model(item.get("matched_alias_text") or ""), _compact_model(item.get("family_group") or ""), _compact_model(item.get("product_id") or ""), } candidate_keys = {key for key in candidate_keys if key} if requested_compact in candidate_keys: matching_candidates.append(item) family_keys.update(candidate_keys) if len(matching_candidates) < 2: return None if requested_compact not in family_keys: return None top = max( matching_candidates, key=lambda item: ( int(item.get("match_score") or 0), len(str(item.get("product_id") or "")), ), ) collapsed = dict(top) collapsed["_family_collapsed"] = True collapsed["_family_requested_text"] = str(requested_text or "").strip() return collapsed def _router_workbook_source_tables(self, intent: str) -> List[str]: mapping = { "details": ["DBX_Products", "DBX_Features", "DBX_Lifecycle", "DBX_Replacements"], "compare": ["DBX_Products", "DBX_Features", "DBX_Lifecycle"], "lifecycle": ["DBX_Lifecycle", "DBX_Replacements", "DBX_Products"], "fleet_lifecycle": ["DBX_Products", "DBX_Lifecycle", "DBX_Replacements", "DBX_ProductAliases"], "replacements": ["DBX_Replacements", "DBX_Products", "DBX_Features"], "search": ["DBX_Products", "DBX_Features"], "antenna": ["DBX_AntennaFlow", "DBX_QuoteHeaders", "DBX_QuoteItems", "DBX_Warnings", "DBX_Explainability"], "guided_advisor": ["DBX_Products", "DBX_Features", "DBX_AntennaFlow", "DBX_QuoteHeaders", "DBX_Replacements"], "survey": [ "DBX_SurveyHeaders", "DBX_SurveyMetrics", "DBX_SurveyRecommendations", "DBX_SurveyExplainability", "DBX_SurveyBOM", "DBX_SurveyProductMap", ], } return list(mapping.get(str(intent or ""), ["DBX_Products"])) def _router_workbook_sources(self, domain: str, intent: str) -> List[Dict[str, Any]]: status = self._rapid_router_intelligence_status() doc_name = str(status.get("filename") or "router_workbook.xlsx").strip() or "router_workbook.xlsx" tables = self._router_workbook_source_tables(intent) excerpt = ( "Workbook-backed router intelligence answer using " + ", ".join(tables[:6]) + "." ) return [ { "id": "RRW1", "domain": domain, "doc": doc_name, "relative_path": doc_name, "chunk_id": f"router_workbook:{intent}", "location": "", "excerpt": excerpt, "score": 1.0, } ] def _router_workbook_planned_retrieval_mode(self, intent: str) -> str: return { "details": "deterministic_router_workbook_details", "compare": "deterministic_router_workbook_compare", "lifecycle": "deterministic_router_workbook_lifecycle", "fleet_lifecycle": "deterministic_router_workbook_fleet_lifecycle", "replacements": "deterministic_router_workbook_replacements", "search": "deterministic_router_workbook_search", "antenna": "deterministic_router_workbook_antenna", "survey": "deterministic_router_workbook_survey", "guided_advisor": "deterministic_router_workbook_guided_advisor_question", }.get(str(intent or "").strip(), "deterministic_router_workbook") def _router_workbook_build_execution_plan( self, message: str, requested_domain: str, ) -> Optional[RouterWorkbookExecutionPlan]: deterministic_query = parse_router_intelligence_query(message) deterministic_query = self._router_workbook_realign_query_for_requested_domain( message, requested_domain, deterministic_query, ) query, orchestration_meta = self._router_workbook_gpt_orchestrated_query( message, requested_domain, deterministic_query, ) if query is None: return None query = self._router_workbook_realign_query_for_requested_domain( message, requested_domain, query, ) fast_domain = ( "router_lifecycle" if query.intent in {"lifecycle", "fleet_lifecycle", "replacements", "survey", "guided_advisor"} else requested_domain ) planning_meta = { "domain": fast_domain, "retrieval_mode": self._router_workbook_planned_retrieval_mode(query.intent), "router_intelligence_intent": query.intent, "router_intelligence_source": "workbook", "current_only": bool(query.current_only), "requested_limit": int(query.limit or 3), **dict(orchestration_meta or {}), } router_query_plan = self._router_query_plan_from_response( message, fast_domain, planning_meta, query=query, ) return RouterWorkbookExecutionPlan( query=query, fast_domain=fast_domain, router_query_plan=router_query_plan, orchestration_meta=dict(orchestration_meta or {}), ) def _router_workbook_realign_query_for_requested_domain( self, message: str, requested_domain: str, query: Optional[RouterIntelligenceQuery], ) -> Optional[RouterIntelligenceQuery]: if query is None or str(requested_domain or "") != "router_lifecycle": return query low = _normalize_router_query_text(message) inventory_like = bool(_INVENTORY_LINE_RE.search(message or "")) or any( token in low for token in ("normalize and parse", "inventory snapshot", "inventory", "fleet", "portfolio", "customer breakdown") ) priority_like = any( token in low for token in ( "action first", "need action", "needs action", "need action first", "needs action first", "priority", "prioritize", "priority rank", "priority order", "migration order", "risk ranking", "urgency", "urgent", "phased", "phase", ) ) replacement_lane_like = any( token in low for token in ( "same-brand", "same manufacturer", "same-manufacturer", "same-brand first", "upgrade path", "replacement path", "recommended upgrade", "recommended replacement", "what should replace", "what replaces", "backup path", "backup paths", "backup second", "cross-vendor backup", ) ) lifecycle_table_like = any( token in low for token in ("end-of-sale", "end of sale", "eos", "end-of-life", "end of life", "eol", "lifecycle") ) if query.intent not in {"details", "compare"}: return query if not (inventory_like or priority_like or replacement_lane_like or lifecycle_table_like): return query merged_device_texts: List[str] = [] seen_device_keys: set[str] = set() for raw in [*list(query.device_texts or []), *self._extract_router_models_cached(message)]: clean = _norm(raw) compact = _compact_model(clean) if (not clean) or (not compact) or (compact in seen_device_keys): continue seen_device_keys.add(compact) merged_device_texts.append(clean) if not merged_device_texts: return query forced_intent = query.intent if inventory_like: forced_intent = "fleet_lifecycle" elif priority_like: forced_intent = "lifecycle" elif replacement_lane_like: forced_intent = "replacements" elif lifecycle_table_like and query.intent in {"details", "compare"}: forced_intent = "lifecycle" normalized_message = normalize_router_intelligence_text(message) return RouterIntelligenceQuery( intent=forced_intent, raw_message=str(query.raw_message or message or ""), normalized_message=normalized_message, device_texts=merged_device_texts[:12], current_only=bool(query.current_only), manufacturer_text=str(query.manufacturer_text or ""), limit=max(int(query.limit or 3), min(len(merged_device_texts), 10)), search_filters=dict(query.search_filters or {}), survey_key=str(query.survey_key or ""), entity_resolutions=_build_router_entity_resolutions( merged_device_texts[:12], normalized_message=normalized_message, survey_key=str(query.survey_key or ""), ), ) def _router_query_plan_answer_mode(self, intent: str, retrieval_mode: str) -> str: intent_text = str(intent or "").strip() retrieval = str(retrieval_mode or "").strip().lower() if retrieval in { "router_multi_model_doc_table_fast", "router_multi_model_doc_caveat_table_fast", "router_docs_documented_matrix_fast", }: return "documented_compare_matrix" if retrieval == "router_vehicle_5g_recommendation_fast": return "vehicle_decision_table" return { "details": "device_details", "compare": "comparison_table", "lifecycle": "lifecycle_status_table", "fleet_lifecycle": "fleet_lifecycle_table", "replacements": "replacement_lanes", "search": "current_shortlist", "antenna": "antenna_guidance", "survey": "survey_interpretation", "guided_advisor": "guided_shortlist", }.get(intent_text, "router_response") def _router_query_plan_evidence_mode(self, intent: str, retrieval_mode: str) -> str: intent_text = str(intent or "").strip() retrieval = str(retrieval_mode or "").strip().lower() if retrieval in { "router_multi_model_doc_table_fast", "router_multi_model_doc_caveat_table_fast", "router_docs_documented_matrix_fast", "router_vehicle_5g_recommendation_fast", }: return "internal_router_docs" return { "details": "workbook_detail_bundle", "compare": "workbook_compare_bundle", "lifecycle": "workbook_lifecycle_bundle", "fleet_lifecycle": "workbook_fleet_bundle", "replacements": "workbook_replacement_bundle", "search": "workbook_ranked_search", "antenna": "workbook_antenna_bundle", "survey": "workbook_survey_bundle", "guided_advisor": "workbook_guided_intake", }.get(intent_text, "workbook_router_bundle") def _router_query_plan_clarification_policy(self, intent: str, retrieval_mode: str, meta: Dict[str, Any]) -> str: intent_text = str(intent or "").strip() retrieval = str(retrieval_mode or "").strip().lower() if intent_text == "survey": return ( "ask_targeted_survey_followups" if bool(meta.get("survey_followup_needed")) else "require_active_survey_context" ) if intent_text == "fleet_lifecycle": return "normalize_each_row_mark_unknowns" if intent_text == "search": return "allow_ranked_alternatives_mark_gaps" if intent_text == "guided_advisor": return "ask_short_intake_then_shortlist" if "clarify" in retrieval or "unmatched" in retrieval: return "exact_model_if_ambiguous" return "exact_model_if_ambiguous" def _router_query_plan_required_fields(self, message: str, intent: str) -> List[str]: intent_text = str(intent or "").strip() if intent_text in {"details", "compare", "search", "antenna"}: return list(dict.fromkeys(self._router_fact_fields_for_query(message))) if intent_text == "lifecycle": return ["status", "end_of_sale_date", "end_of_life_date", "4g_alternative", "5g_replacement"] if intent_text == "fleet_lifecycle": return ["status", "end_of_sale_date", "end_of_life_date", "4g_alternative", "5g_replacement", "match_confidence"] if intent_text == "replacements": return ["primary_same_brand_path", "backup_cross_vendor_path", "historical_only_paths", "review_required"] if intent_text == "survey": return ["outcome_class", "confidence_label", "primary_solution_family", "primary_product", "followups"] if intent_text == "guided_advisor": return ["deployment_profile", "current_only_shortlist", "tradeoffs", "antenna_family_guidance"] return [] def _router_evidence_item( self, *, source_type: str, source_document: str, model_alias: str, field_label: str, raw_value: Any, normalized_value: Any, provenance: str, confidence: float, source_table: str = "", citation_anchor: str = "", entity_label: str = "", entity_role: str = "", evidence_kind: str = "", resolution_mode: str = "", debug_ref: str = "", uncertainty_flags: Sequence[str] = (), ) -> Dict[str, Any]: return RouterEvidenceItem( source_type=str(source_type or "").strip(), source_document=str(source_document or "").strip(), model_alias=str(model_alias or "").strip(), field_label=str(field_label or "").strip(), raw_value=_norm(raw_value), normalized_value=_norm(normalized_value), provenance=str(provenance or "").strip(), confidence=float(confidence or 0.0), source_table=str(source_table or "").strip(), citation_anchor=str(citation_anchor or "").strip(), entity_label=str(entity_label or "").strip(), entity_role=str(entity_role or "").strip(), evidence_kind=str(evidence_kind or "").strip(), resolution_mode=str(resolution_mode or "").strip(), debug_ref=str(debug_ref or "").strip(), uncertainty_flags=[str(item) for item in list(uncertainty_flags or []) if str(item or "").strip()], ).as_dict() def _dedupe_router_evidence_bundle(self, items: Sequence[Dict[str, Any]], *, limit: int = 18) -> List[Dict[str, Any]]: deduped: List[Dict[str, Any]] = [] seen: set[Tuple[str, str, str, str, str, str, str]] = set() for item in items: row = _as_dict(item) key = ( str(row.get("source_type") or "").strip(), str(row.get("source_document") or "").strip(), str(row.get("source_table") or "").strip(), str(row.get("model_alias") or "").strip(), str(row.get("field_label") or "").strip(), str(row.get("normalized_value") or "").strip(), str(row.get("citation_anchor") or "").strip(), ) if key in seen: continue seen.add(key) deduped.append(row) if len(deduped) >= max(1, int(limit or 18)): break return deduped def _router_trace_evidence_bundle(self, trace: Dict[str, Any], *, model_alias: str = "") -> List[Dict[str, Any]]: trace_obj = _as_dict(trace) source_tables = [str(item) for item in list(trace_obj.get("source_tables") or []) if str(item or "").strip()] source_document = ", ".join(source_tables) if source_tables else "router_workbook" provenance = _norm(trace_obj.get("summary") or source_document) source_table = source_tables[0] if source_tables else "router_workbook" out: List[Dict[str, Any]] = [] for item in list(trace_obj.get("items") or []): row = _as_dict(item) label = str(row.get("label") or "").strip() value = _norm(row.get("value")) if not label or not value: continue uncertainty_flags: List[str] = [] if label.lower() == "review flag" and value.lower() == "yes": uncertainty_flags.append("review_required") out.append( self._router_evidence_item( source_type="router_answer_trace", source_document=source_document, model_alias=model_alias, field_label=label, raw_value=value, normalized_value=value, provenance=provenance, confidence=0.82 if not uncertainty_flags else 0.68, source_table=source_table, citation_anchor=label, entity_label=model_alias or source_table, entity_role="answer_trace", evidence_kind="trace_field", resolution_mode="trace", debug_ref=source_document, uncertainty_flags=uncertainty_flags, ) ) for warning in list(trace_obj.get("warnings") or [])[:4]: warning_text = _norm(warning) if not warning_text: continue out.append( self._router_evidence_item( source_type="router_answer_warning", source_document=source_document, model_alias=model_alias, field_label="warning", raw_value=warning_text, normalized_value=warning_text, provenance=provenance, confidence=0.62, source_table=source_table, citation_anchor="warning", entity_label=model_alias or source_table, entity_role="answer_trace", evidence_kind="trace_warning", resolution_mode="trace", debug_ref=source_document, uncertainty_flags=["warning"], ) ) return out def _router_source_evidence_bundle( self, sources: Sequence[Dict[str, Any]], *, model_alias: str = "", source_tables: Sequence[str] = (), ) -> List[Dict[str, Any]]: out: List[Dict[str, Any]] = [] source_table = str(next((str(item).strip() for item in list(source_tables or []) if str(item or "").strip()), "")).strip() for source in list(sources or [])[:6]: row = _as_dict(source) doc_name = str(row.get("doc") or row.get("relative_path") or "internal_source").strip() or "internal_source" excerpt = _norm(row.get("excerpt") or doc_name) provenance_bits = [ str(row.get("chunk_id") or "").strip(), str(row.get("location") or "").strip(), ] uncertainty_flags: List[str] = [] if excerpt.lower().startswith("web-sourced"): uncertainty_flags.append("web_sourced_not_internal") citation_anchor = str(row.get("chunk_id") or row.get("location") or doc_name).strip() out.append( self._router_evidence_item( source_type="router_source_excerpt", source_document=doc_name, model_alias=model_alias, field_label="source_excerpt", raw_value=excerpt, normalized_value=excerpt[:220], provenance=" / ".join([part for part in provenance_bits if part]) or doc_name, confidence=float(row.get("score") or 0.7), source_table=source_table or doc_name, citation_anchor=citation_anchor, entity_label=model_alias or doc_name, entity_role="source_excerpt", evidence_kind="source_excerpt", resolution_mode="source", debug_ref=citation_anchor or doc_name, uncertainty_flags=uncertainty_flags, ) ) return out def _router_replacement_evidence_bundle(self, view: Dict[str, Any], *, model_alias: str = "") -> List[Dict[str, Any]]: view_obj = _as_dict(view) ordered_paths = [row for row in list(view_obj.get("ordered_paths") or []) if isinstance(row, dict)] source_document = str(view_obj.get("source_document") or "router_workbook") source_table = str(view_obj.get("source_table") or "DBX_Replacements") subject_label = _norm(view_obj.get("subject_label") or model_alias or view_obj.get("recommended_path_value") or "") provenance = _norm(view_obj.get("lane_summary") or view_obj.get("recommended_path_label") or source_document) debug_ref = str(view_obj.get("debug_ref") or source_document) out: List[Dict[str, Any]] = [] if subject_label: out.append( self._router_evidence_item( source_type="router_replacement_view", source_document=source_document, model_alias=subject_label or model_alias, field_label="replacement_lane_summary", raw_value=_norm(view_obj.get("lane_summary") or ""), normalized_value=_norm(view_obj.get("lane_summary") or ""), provenance=provenance, confidence=0.88, source_table=source_table, citation_anchor=_norm(view_obj.get("recommended_path_label") or "lane_summary") or "lane_summary", entity_label=subject_label, entity_role="replacement_subject", evidence_kind="replacement_lane_summary", resolution_mode=_norm(view_obj.get("resolution_mode") or ""), debug_ref=debug_ref, uncertainty_flags=[ flag for flag, enabled in [ ("review_required", bool(view_obj.get("review_required"))), ("no_direct_replacement", bool(view_obj.get("no_replacement"))), ] if enabled ], ) ) recommended_value = _norm(view_obj.get("recommended_path_value") or "") recommended_label = _norm(view_obj.get("recommended_path_label") or "") if recommended_value: out.append( self._router_evidence_item( source_type="router_replacement_view", source_document=source_document, model_alias=subject_label or model_alias, field_label="recommended_path_value", raw_value=recommended_value, normalized_value=recommended_value, provenance=provenance, confidence=0.93, source_table=source_table, citation_anchor=recommended_label or "recommended_path", entity_label=subject_label or model_alias or source_table, entity_role="replacement_lane", evidence_kind="replacement_recommended_path", resolution_mode=_norm(view_obj.get("resolution_mode") or ""), debug_ref=debug_ref, uncertainty_flags=[ flag for flag, enabled in [ ("review_required", bool(view_obj.get("review_required"))), ("lifecycle_fallback", str(view_obj.get("source_mode") or "").strip().lower() == "lifecycle_fallback"), ] if enabled ], ) ) for index, path in enumerate(ordered_paths[:4], start=1): path_label = _norm(path.get("label") or f"Path {index}") path_value = _norm(path.get("value") or "") if not path_value: continue path_kind = _norm(path.get("kind") or "") path_note = _norm(path.get("note") or provenance) out.append( self._router_evidence_item( source_type="router_replacement_view", source_document=source_document, model_alias=subject_label or model_alias, field_label=f"ordered_path_{index}", raw_value=path_value, normalized_value=path_value, provenance=path_note, confidence=max(0.72, 0.9 - (0.03 * (index - 1))), source_table=source_table, citation_anchor=path_label or f"ordered_path_{index}", entity_label=subject_label or model_alias or source_table, entity_role="replacement_lane", evidence_kind="ordered_replacement_path", resolution_mode=_norm(view_obj.get("resolution_mode") or ""), debug_ref=debug_ref, uncertainty_flags=[flag for flag in [path_kind] if flag], ) ) if bool(view_obj.get("review_required")): out.append( self._router_evidence_item( source_type="router_replacement_view", source_document=source_document, model_alias=subject_label or model_alias, field_label="review_flag", raw_value="Yes", normalized_value="Yes", provenance=provenance, confidence=0.68, source_table=source_table, citation_anchor="review_flag", entity_label=subject_label or model_alias or source_table, entity_role="replacement_lane", evidence_kind="replacement_review_flag", resolution_mode=_norm(view_obj.get("resolution_mode") or ""), debug_ref=debug_ref, uncertainty_flags=["review_required"], ) ) if bool(view_obj.get("no_replacement")): out.append( self._router_evidence_item( source_type="router_replacement_view", source_document=source_document, model_alias=subject_label or model_alias, field_label="no_replacement", raw_value="Yes", normalized_value="Yes", provenance=provenance, confidence=0.8, source_table=source_table, citation_anchor="no_replacement", entity_label=subject_label or model_alias or source_table, entity_role="replacement_lane", evidence_kind="replacement_no_direct", resolution_mode=_norm(view_obj.get("resolution_mode") or ""), debug_ref=debug_ref, uncertainty_flags=["no_direct_replacement"], ) ) return out def _router_fleet_evidence_bundle(self, fleet_view: Dict[str, Any], *, model_alias: str = "") -> List[Dict[str, Any]]: view_obj = _as_dict(fleet_view) source_document = str(view_obj.get("uploaded_filename") or "router_workbook") source_table = "DBX_Products" out: List[Dict[str, Any]] = [] for row in [row for row in list(view_obj.get("fleet_evidence_rows") or []) if isinstance(row, dict)][:8]: customer = _norm(row.get("customer") or "Unknown") input_model = _norm(row.get("input_model") or row.get("normalized_model") or "") normalized_model = _norm(row.get("normalized_model") or input_model or "") qty = int(row.get("qty") or 0) lifecycle_bucket = _norm(row.get("lifecycle_bucket") or "unknown") recommended_value = _norm(row.get("recommended_path_value") or "") lane_note = _norm(row.get("lane_note") or "") summary_bits = [bit for bit in [customer, input_model or normalized_model, f"qty {qty}", lifecycle_bucket] if bit] if recommended_value: summary_bits.append(f"path {recommended_value}") if lane_note: summary_bits.append(lane_note) row_summary = " | ".join(summary_bits) if summary_bits else input_model or normalized_model citation_anchor = str(row.get("citation_anchor") or f"row:{row.get('row_number') or ''}").strip() or "row" entity_label = normalized_model or input_model or customer or model_alias or "fleet row" uncertainty_flags = [str(item) for item in list(row.get("uncertainty_flags") or []) if str(item or "").strip()] out.append( self._router_evidence_item( source_type="router_fleet_view", source_document=source_document, model_alias=model_alias or entity_label, field_label="fleet_row_summary", raw_value=row_summary, normalized_value=row_summary, provenance=_norm(row.get("replacement_priority_reason") or lane_note or row.get("lifecycle_status") or ""), confidence=max(0.62, min(0.97, float(row.get("confidence_score") or 0) / 100.0)), source_table=str(row.get("source_table") or source_table), citation_anchor=citation_anchor, entity_label=entity_label, entity_role=str(row.get("entity_role") or "fleet_row"), evidence_kind="fleet_row_summary", resolution_mode=str(row.get("resolution_mode") or ""), debug_ref=str(row.get("debug_ref") or ""), uncertainty_flags=uncertainty_flags, ) ) out.append( self._router_evidence_item( source_type="router_fleet_view", source_document=source_document, model_alias=model_alias or entity_label, field_label="replacement_priority_score", raw_value=row.get("replacement_priority_score") or 0, normalized_value=row.get("replacement_priority_score") or 0, provenance=_norm(row.get("replacement_priority_reason") or lane_note or row.get("lifecycle_status") or ""), confidence=0.76, source_table=str(row.get("source_table") or source_table), citation_anchor=citation_anchor, entity_label=entity_label, entity_role=str(row.get("entity_role") or "fleet_row"), evidence_kind="fleet_priority", resolution_mode=str(row.get("resolution_mode") or ""), debug_ref=str(row.get("debug_ref") or ""), uncertainty_flags=uncertainty_flags, ) ) return out def _router_query_typo_candidates(self, query: Optional[RouterIntelligenceQuery]) -> List[Dict[str, Any]]: if query is None: return [] tokens: List[str] = [] for resolution in list(getattr(query, "entity_resolutions", []) or [])[:12]: row = _as_dict(resolution.as_dict() if hasattr(resolution, "as_dict") else resolution) token = _norm(row.get("raw_text") or row.get("canonical_text") or row.get("normalized_text")) if token and token not in tokens: tokens.append(token) if not tokens: tokens = [str(item).strip() for item in list(getattr(query, "device_texts", []) or []) if str(item or "").strip()] out: List[Dict[str, Any]] = [] seen: set[str] = set() for token in tokens: compact = _compact_model(token) if not compact: continue if self._lookup_router_fact_key(token) or self._lookup_router_lifecycle_key(token) or self._lookup_router_lifecycle_key_relaxed(token): continue typo_candidate = self._router_workbook_likely_typo_candidate(token) typo_key = _compact_model(typo_candidate) if not typo_key or typo_key in seen: continue seen.add(typo_key) out.append({"raw_text": token, "typo_candidate": typo_candidate}) if len(out) >= 4: break return out def _router_query_entity_resolution_rows(self, query: Optional[RouterIntelligenceQuery]) -> List[Dict[str, Any]]: rows: List[Dict[str, Any]] = [] if query is None: return rows def _typo_candidate_for_token(token: str) -> str: text = _norm(token) if not text: return "" if self._lookup_router_fact_key(text) or self._lookup_router_lifecycle_key(text) or self._lookup_router_lifecycle_key_relaxed(text): return "" return self._router_workbook_likely_typo_candidate(text) raw_resolutions = list(getattr(query, "entity_resolutions", []) or []) if raw_resolutions: for resolution in raw_resolutions: row = _as_dict(resolution.as_dict() if hasattr(resolution, "as_dict") else resolution) raw_text = _norm(row.get("raw_text") or row.get("canonical_text") or row.get("normalized_text")) if not raw_text: continue normalized_text = _norm(row.get("normalized_text") or normalize_router_intelligence_text(raw_text)) canonical_text = _norm(row.get("canonical_text") or raw_text) parser_mode = str(row.get("resolution_mode") or "exact").strip() or "exact" source_role = str(row.get("source_role") or "model_token").strip() or "model_token" candidate_texts = [str(item) for item in list(row.get("candidate_texts") or []) if str(item or "").strip()] uncertainty_flags = [str(item) for item in list(row.get("uncertainty_flags") or []) if str(item or "").strip()] core_resolution = self._normalize_router_model(canonical_text) or self._normalize_router_model(raw_text) or _compact_model(canonical_text) or _compact_model(raw_text) resolved_text = _norm(core_resolution or canonical_text or raw_text) if not resolved_text: continue alias_changed = bool( resolved_text and _compact_model(resolved_text) != _compact_model(canonical_text or raw_text) and _compact_model(resolved_text) != _compact_model(raw_text) ) resolution_mode = parser_mode if parser_mode == "placeholder": resolution_mode = "placeholder" elif parser_mode == "survey_key": resolution_mode = "survey_key" elif alias_changed: resolution_mode = "alias_normalized" elif parser_mode == "normalized": resolution_mode = "normalized" typo_candidate = "" if parser_mode in {"placeholder", "survey_key"} else _typo_candidate_for_token(raw_text) if typo_candidate and typo_candidate not in candidate_texts: candidate_texts.append(typo_candidate) if typo_candidate and resolution_mode == "exact": resolution_mode = "likely_typo" uncertainty_flags = list(dict.fromkeys([*uncertainty_flags, "typo_candidate", "needs_clarification"])) if parser_mode == "placeholder": notes = [f"`{raw_text}` stayed as a placeholder token"] elif parser_mode == "survey_key": notes = [f"`{raw_text}` is being used as the survey key"] elif resolution_mode == "alias_normalized": notes = [f"`{raw_text}` resolved through the catalog alias map as `{resolved_text}`"] elif resolution_mode == "likely_typo": notes = [f"`{raw_text}` looks like a typo for `{typo_candidate}`, so I left it provisional instead of forcing a workbook match."] elif parser_mode == "normalized": notes = [f"`{raw_text}` normalized to `{resolved_text}` before routing"] else: notes = [f"`{resolved_text}` routed deterministically"] confidence = float(row.get("confidence") or 0.0) if resolution_mode == "likely_typo": confidence = min(confidence if confidence > 0 else 0.65, 0.65) rows.append( { "raw_text": raw_text, "normalized_text": normalized_text, "canonical_text": canonical_text, "resolved_text": resolved_text, "resolution_mode": resolution_mode, "source_role": source_role, "candidate_texts": candidate_texts or [resolved_text], "typo_candidate": typo_candidate, "uncertainty_flags": uncertainty_flags, "confidence": confidence, "notes": notes, } ) if rows: seen_texts: set[str] = set() unique_rows: List[Dict[str, Any]] = [] for row in rows: key = _compact_model(str(row.get("resolved_text") or row.get("raw_text") or "")) if not key or key in seen_texts: continue seen_texts.add(key) unique_rows.append(row) return unique_rows fallback_texts = [str(item).strip() for item in list(getattr(query, "device_texts", []) or []) if str(item or "").strip()] for raw_text in fallback_texts: normalized_text = normalize_router_intelligence_text(raw_text) resolved_text = self._normalize_router_model(raw_text) or _compact_model(raw_text) or raw_text typo_candidate = _typo_candidate_for_token(raw_text) candidate_texts = [raw_text] if typo_candidate and typo_candidate not in candidate_texts: candidate_texts.append(typo_candidate) resolution_mode = "exact" uncertainty_flags: List[str] = [] confidence = 1.0 notes = [f"`{_norm(resolved_text)}` routed from fallback device token"] if typo_candidate: resolution_mode = "likely_typo" uncertainty_flags = ["typo_candidate", "needs_clarification"] confidence = 0.65 notes = [f"`{raw_text}` looks like a typo for `{typo_candidate}`, so I left it provisional instead of forcing a workbook match."] rows.append( { "raw_text": raw_text, "normalized_text": normalized_text, "canonical_text": raw_text, "resolved_text": _norm(resolved_text), "resolution_mode": resolution_mode, "source_role": "model_token", "candidate_texts": candidate_texts, "typo_candidate": typo_candidate, "uncertainty_flags": uncertainty_flags, "confidence": confidence, "notes": notes, } ) return rows def _router_query_plan_from_response( self, message: str, requested_domain: str, meta: Dict[str, Any], *, query: Optional[RouterIntelligenceQuery] = None, ) -> Dict[str, Any]: response_meta = _as_dict(meta) intent = str(response_meta.get("router_intelligence_intent") or "").strip() retrieval_mode = str(response_meta.get("retrieval_mode") or "").strip() parsed_query = query or parse_router_intelligence_query(message) if not intent and parsed_query is not None: intent = str(parsed_query.intent or "").strip() if not intent: return {} trace = _as_dict(response_meta.get("router_answer_trace")) trace_items = { str(_as_dict(item).get("label") or "").strip().lower(): _norm(_as_dict(item).get("value")) for item in list(trace.get("items") or []) if isinstance(item, dict) } entity_rows = self._router_query_entity_resolution_rows(parsed_query) entities: List[str] = [] entity_resolution_notes: List[str] = [] resolution_modes: set[str] = set() candidate_families: List[str] = [] def _add_entity(value: Any) -> None: text = _norm(value) if not text: return pieces = re.split(r"\s+vs\s+|;|,", text, flags=re.IGNORECASE) for piece in pieces: clean = _norm(piece) if clean and clean not in entities: entities.append(clean) for row in entity_rows: display_text = _norm(row.get("raw_text") or row.get("canonical_text") or row.get("resolved_text") or "") resolved_text = _norm(row.get("resolved_text") or display_text or "") if display_text and display_text not in entities: entities.append(display_text) typo_candidate = _norm(row.get("typo_candidate") or "") if typo_candidate and typo_candidate not in candidate_families: candidate_families.append(typo_candidate) mode = str(row.get("resolution_mode") or "").strip() if mode: resolution_modes.add(mode) entity_resolution_notes.extend([str(item) for item in list(row.get("notes") or []) if str(item or "").strip()]) for label in ("subject device", "compared routers", "survey / site", "primary product"): _add_entity(trace_items.get(label)) survey_key = str(response_meta.get("survey_key") or "").strip() if survey_key: _add_entity(survey_key) resolution_modes.add("survey_key") if not entities and parsed_query is not None: for token in list(parsed_query.device_texts or []): _add_entity(token) current_only = bool(response_meta.get("current_only")) if isinstance(response_meta.get("current_only"), bool) else True if parsed_query is not None: current_only = bool(parsed_query.current_only) recommendation_lane = str(trace_items.get("recommendation lane") or "").strip().lower() if recommendation_lane.startswith("current only"): current_only = True elif "includes legacy" in recommendation_lane: current_only = False elif _contains_any( str(message or "").lower(), ("legacy", "old", "older", "retired", "end-of-life", "end of life", "eol", "end-of-sale", "eos"), ): current_only = False search_filters: Dict[str, Any] = {} if parsed_query is not None and str(parsed_query.intent or "").strip() == "search": search_filters = dict(parsed_query.search_filters or {}) elif parsed_query is not None and intent == "search": search_filters = dict(parsed_query.search_filters or {}) def _trace_bool(label: str) -> Optional[bool]: text = str(trace_items.get(label) or "").strip().lower() if text == "yes": return True if text == "no": return False return None for label, key in ( ("rugged filter", "rugged"), ("battery filter", "battery"), ("wi-fi filter", "wifi"), ("gnss filter", "gnss"), ("poe filter", "poe"), ): maybe_bool = _trace_bool(label) if maybe_bool is not None: search_filters[key] = maybe_bool ports_text = str(trace_items.get("minimum ethernet ports") or "").strip() ports_match = re.search(r"\d+", ports_text) if ports_match: search_filters["min_total_ethernet_ports"] = int(ports_match.group(0)) placement = str(trace_items.get("placement filter") or "").strip() if placement: search_filters["indoor_outdoor"] = placement cellular = str(trace_items.get("cellular generation filter") or "").strip().upper() if cellular: search_filters["cellular_generation"] = cellular use_case = str(trace_items.get("use-case hint") or "").strip().lower() if use_case: search_filters["use_case_hint"] = use_case search_filters["current_only"] = current_only for entity in entities: normalized_entity = self._normalize_router_model(entity) or _compact_model(entity) or entity normalized_entity = str(normalized_entity or "").strip() if normalized_entity and normalized_entity not in candidate_families: candidate_families.append(normalized_entity) manufacturer = str( trace_items.get("manufacturer filter") or (parsed_query.manufacturer_text if parsed_query is not None else "") or "" ).strip() if manufacturer: candidate = f"manufacturer:{manufacturer}" if candidate not in candidate_families: candidate_families.append(candidate) for key in ("use_case_hint", "indoor_outdoor", "cellular_generation"): value = str(search_filters.get(key) or "").strip() if value: candidate = f"{key}:{value}" if candidate not in candidate_families: candidate_families.append(candidate) entity_resolutions = [ item.as_dict() if hasattr(item, "as_dict") else {str(key): value for key, value in dict(item).items()} for item in list(getattr(parsed_query, "entity_resolutions", []) or [])[:12] if item ] if not resolution_modes: entity_resolution_mode = "deterministic" elif "placeholder" in resolution_modes: entity_resolution_mode = "placeholder" elif "survey_key" in resolution_modes and len(resolution_modes) == 1: entity_resolution_mode = "survey_key" elif "likely_typo" in resolution_modes: entity_resolution_mode = "likely_typo" if resolution_modes <= {"exact", "likely_typo"} else "mixed" elif "alias_normalized" in resolution_modes: entity_resolution_mode = "alias_normalized" elif "normalized" in resolution_modes: entity_resolution_mode = "normalized" elif len(resolution_modes) > 1: entity_resolution_mode = "mixed" else: entity_resolution_mode = next(iter(resolution_modes)) plan = RouterQueryPlan( intent=intent, requested_domain=str(requested_domain or "").strip() or str(response_meta.get("domain") or "").strip() or "router_docs", answer_mode=self._router_query_plan_answer_mode(intent, retrieval_mode), evidence_mode=self._router_query_plan_evidence_mode(intent, retrieval_mode), clarification_policy=self._router_query_plan_clarification_policy(intent, retrieval_mode, response_meta), entity_resolution_mode=entity_resolution_mode, entities=entities[:8], entity_resolutions=entity_resolutions, required_fields=self._router_query_plan_required_fields(message, intent), candidate_families=candidate_families[:10], entity_resolution_notes=entity_resolution_notes[:10], current_only=current_only, limit=max(1, int(response_meta.get("requested_limit") or response_meta.get("limit") or (parsed_query.limit if parsed_query is not None else 3) or 3)), search_filters=search_filters if intent == "search" else {}, llm_assisted=bool(response_meta.get("llm_assisted")), llm_reason=str(response_meta.get("router_orchestration_reason") or "").strip(), orchestration_mode=str(response_meta.get("router_orchestration_mode") or "deterministic").strip() or "deterministic", ) return plan.as_dict() def _router_workbook_consensus_text( self, values: Sequence[Any], *, mixed: str = "Needs exact SKU/package", missing: str = "Not listed", ) -> str: raw_values = list(values or []) if not raw_values: return missing normalized = [_norm(value) for value in raw_values] present = [value for value in normalized if value] if not present: return missing if len(present) != len(normalized): return mixed if len({value.lower() for value in present}) != 1: return mixed return present[0] def _router_workbook_consensus_bool( self, values: Sequence[Any], *, mixed: str = "Needs exact SKU/package", missing: str = "Not listed", ) -> Any: raw_values = list(values or []) if not raw_values: return missing normalized: List[str] = [] for value in raw_values: if isinstance(value, bool): normalized.append("Yes" if value else "No") continue text = str(value or "").strip() if not text: return mixed low = text.lower() if low in {"yes", "true", "1", "y"}: normalized.append("Yes") elif low in {"no", "false", "0", "n"}: normalized.append("No") else: return mixed if not normalized: return missing if len(set(normalized)) != 1: return mixed return normalized[0] == "Yes" def _router_workbook_family_safe_detail( self, core: Any, *, requested_text: str, response: Dict[str, Any], ) -> Optional[Dict[str, Any]]: candidates = [item for item in list(response.get("candidates") or []) if isinstance(item, dict)] if len(candidates) < 2: return None manufacturer_groups = { _norm(item.get("manufacturer_group") or "") for item in candidates if _norm(item.get("manufacturer_group") or "") } display_names = { _norm(item.get("display_name") or item.get("product_id") or "") for item in candidates if _norm(item.get("display_name") or item.get("product_id") or "") } family_groups = { _norm(item.get("family_group") or "") for item in candidates if _norm(item.get("family_group") or "") } collapsed = self._router_workbook_collapse_family_ambiguity(requested_text, response) if len(manufacturer_groups) != 1: return None if not collapsed and len(display_names) > 1 and len(family_groups) > 1: return None detail_candidates: List[Dict[str, Any]] = [] seen_product_keys: set[str] = set() for item in candidates: product_key = str(item.get("product_key") or "").strip() if not product_key or product_key in seen_product_keys: continue seen_product_keys.add(product_key) detail = _as_dict(core.get_catalog_device_details_by_key(product_key=product_key)) if detail.get("ok"): detail_candidates.append(detail) if len(detail_candidates) < 2: return None requested_label = _norm(requested_text) or _norm((collapsed or {}).get("product_id") or "") representative = detail_candidates[0] def _detail_name(detail: Dict[str, Any]) -> str: for item in (_as_dict(detail.get("product")), _as_dict(detail.get("match"))): value = _norm(item.get("display_name") or item.get("product_id") or "") if value: return value return "" def _replacement_display(detail: Dict[str, Any], field_name: str) -> str: replacements = _as_dict(detail.get("replacements")) if field_name == "primary": return _norm(_as_dict(replacements.get("primary_replacement")).get("replacement_display") or "") backup_rows = [row for row in list(replacements.get("backup_replacements") or []) if isinstance(row, dict)] return _norm(_as_dict(backup_rows[0]).get("replacement_display") or "") if backup_rows else "" match_rows = [_as_dict(detail.get("match")) for detail in detail_candidates] feature_rows = [_as_dict(detail.get("features")) for detail in detail_candidates] lifecycle_rows = [_as_dict(detail.get("lifecycle")) for detail in detail_candidates] candidate_names = [name for name in (_detail_name(detail) for detail in detail_candidates) if name] subject_label = requested_label or self._router_workbook_consensus_text(candidate_names, mixed=candidate_names[0], missing="Resolved router") manufacturer_label = next(iter(manufacturer_groups)) if manufacturer_groups else _norm(_as_dict(representative.get("match")).get("manufacturer_group") or "") lifecycle_status = self._router_workbook_consensus_text( [row.get("status") or row.get("status_bucket") or match.get("status_bucket") for row, match in zip(lifecycle_rows, match_rows)], ) current_recommendable = self._router_workbook_consensus_bool( [row.get("current_recommendable_flag") for row in match_rows], ) primary_replacement = self._router_workbook_consensus_text( [_replacement_display(detail, "primary") for detail in detail_candidates], mixed="Needs exact SKU/package", missing="", ) backup_replacement = self._router_workbook_consensus_text( [_replacement_display(detail, "backup") for detail in detail_candidates], mixed="Needs exact SKU/package", missing="", ) no_replacement = self._router_workbook_consensus_bool( [_as_dict(detail.get("replacements")).get("no_replacement") for detail in detail_candidates], mixed="Needs exact SKU/package", missing=False, ) resolved_family = next(iter(display_names)) if len(display_names) == 1 else next(iter(family_groups), subject_label) note_bits = [ f"`{subject_label}` matched multiple workbook rows", "this answer only keeps the fields that stay consistent across those rows", ] if resolved_family and _compact_model(resolved_family) != _compact_model(subject_label): note_bits.insert(1, f"closest resolved family: `{resolved_family}`") family_note = "; ".join(note_bits) + "." return { "ok": True, "match": { **_as_dict(representative.get("match")), "product_id": subject_label, "display_name": subject_label, "manufacturer_group": manufacturer_label, "status_bucket": lifecycle_status, "current_recommendable_flag": current_recommendable, "feature_gap_blocked": True, }, "product": { **_as_dict(representative.get("product")), "product_id": subject_label, "display_name": subject_label, "manufacturer_group": manufacturer_label, "status_bucket": lifecycle_status, "current_recommendable_flag": current_recommendable, }, "features": { **_as_dict(representative.get("features")), "cellular_gen_norm": self._router_workbook_consensus_text([row.get("cellular_gen_norm") for row in feature_rows]), "lan_ports_norm": self._router_workbook_consensus_text([row.get("lan_ports_norm") for row in feature_rows]), "wan_ports_norm": self._router_workbook_consensus_text([row.get("wan_ports_norm") for row in feature_rows]), "total_ethernet_ports": self._router_workbook_consensus_text([row.get("total_ethernet_ports") for row in feature_rows]), "wifi_norm": self._router_workbook_consensus_bool([row.get("wifi_norm") for row in feature_rows]), "gnss_norm": self._router_workbook_consensus_bool([row.get("gnss_norm") for row in feature_rows]), "poe_norm": self._router_workbook_consensus_bool([row.get("poe_norm") for row in feature_rows]), "rugged_norm": self._router_workbook_consensus_bool([row.get("rugged_norm") for row in feature_rows]), "battery_norm": self._router_workbook_consensus_bool([row.get("battery_norm") for row in feature_rows]), "indoor_outdoor_norm": self._router_workbook_consensus_text([row.get("indoor_outdoor_norm") for row in feature_rows]), "use_case_norm": self._router_workbook_consensus_text([row.get("use_case_norm") for row in feature_rows]), }, "lifecycle": { **_as_dict(representative.get("lifecycle")), "status": lifecycle_status, "end_of_sale_date": self._router_workbook_consensus_text([row.get("end_of_sale_date") for row in lifecycle_rows]), "last_support_date": self._router_workbook_consensus_text([row.get("last_support_date") for row in lifecycle_rows]), "has_authoritative_lifecycle": self._router_workbook_consensus_bool( [row.get("has_authoritative_lifecycle") for row in lifecycle_rows], mixed=False, missing=False, ), }, "replacements": { **_as_dict(representative.get("replacements")), "primary_replacement": {"replacement_display": primary_replacement} if primary_replacement else None, "backup_replacements": ([{"replacement_display": backup_replacement}] if backup_replacement else []), "no_replacement": no_replacement is True, }, "_family_safe": True, "_family_safe_note": family_note, "_family_safe_candidates": candidate_names[:5], } def _router_workbook_should_treat_exact_match_as_family_alias( self, requested_text: str, resolved_row: Dict[str, Any], ) -> bool: requested_label = _norm(requested_text) requested_compact = _compact_model(requested_label) if not requested_compact: return False item = _as_dict(resolved_row) product_id = _norm(item.get("product_id")) display_name = _norm(item.get("display_name")) family_group = _norm(item.get("family_group")) matched_alias = _norm(item.get("matched_alias_text")) product_compact = _compact_model(product_id) display_compact = _compact_model(display_name) family_compact = _compact_model(family_group) alias_compact = _compact_model(matched_alias) if requested_compact in {product_compact, display_compact}: return False alias_exact_only = requested_compact == alias_compact if not ( alias_exact_only or requested_compact == family_compact or display_compact.startswith(requested_compact) or requested_compact in family_compact or family_compact.endswith(requested_compact) ): return False granularity = _norm(item.get("granularity")).lower() entity_type = _norm(item.get("entity_type")).lower() if not (granularity == "sku" or "exact" in entity_type or "sku" in entity_type): return False return True def _router_workbook_match_quality_label( self, *, resolution_mode: str, family_safe: bool, feature_gap_blocked: bool, ) -> str: mode = str(resolution_mode or "").strip().lower() if family_safe or mode in {"family_safe_partial", "family_alias_provisional", "family_collapsed"}: return "family_safe" if feature_gap_blocked: return "exact_feature_gap" return "exact" def _router_workbook_status_is_legacy(self, *statuses: Any) -> bool: for status in statuses: low = _norm(status).lower() if any(token in low for token in ("legacy", "retired", "discontinued", "eos", "eol", "end of sale", "end of life", "obsolete")): return True return False def _router_workbook_fact_bundle_from_detail( self, detail: Dict[str, Any], *, requested_text: str = "", resolution_mode: str = "exact", ) -> Dict[str, Any]: match = _as_dict(detail.get("match")) product = _as_dict(detail.get("product")) features = _as_dict(detail.get("features")) lifecycle = _as_dict(detail.get("lifecycle")) replacements = _as_dict(detail.get("replacements")) return self._router_workbook_fact_bundle_from_payload( match=match, product=product, features=features, lifecycle=lifecycle, replacements=replacements, requested_text=requested_text, resolution_mode=resolution_mode, family_safe=bool(detail.get("_family_safe")), canonical_display_name=_norm(detail.get("_canonical_display_name") or detail.get("_canonical_resolved_label")), ) def _router_workbook_fact_bundle_from_payload( self, *, match: Dict[str, Any], product: Dict[str, Any], features: Dict[str, Any], lifecycle: Dict[str, Any], replacements: Dict[str, Any], requested_text: str = "", resolution_mode: str = "exact", family_safe: bool = False, canonical_display_name: str = "", ) -> Dict[str, Any]: match = _as_dict(match) product = _as_dict(product) features = _as_dict(features) lifecycle = self._router_workbook_enrich_lifecycle_row( match, product, _as_dict(lifecycle), ) replacements = _as_dict(replacements) requested_label = _norm( requested_text or match.get("_requested_label") or match.get("_family_requested_text") or match.get("matched_alias_text") or match.get("product_id") or match.get("display_name") or product.get("product_id") or product.get("display_name") ) resolved_label = _norm( canonical_display_name or product.get("display_name") or product.get("product_id") or match.get("display_name") or match.get("product_id") or requested_label ) feature_fields_present = sorted( field_name for field_name in _ROUTER_WORKBOOK_STRUCTURED_FEATURE_FIELDS if features.get(field_name) not in (None, "", []) ) feature_fields_missing = sorted( field_name for field_name in _ROUTER_WORKBOOK_STRUCTURED_FEATURE_FIELDS if field_name not in feature_fields_present ) feature_gap_blocked = bool(match.get("feature_gap_blocked")) return { "requested_model": requested_label, "resolved_model": resolved_label, "requested_product_key": _norm(match.get("product_key") or product.get("product_key")), "resolution_mode": str(resolution_mode or "exact"), "workbook_row_quality": self._router_workbook_match_quality_label( resolution_mode=resolution_mode, family_safe=family_safe, feature_gap_blocked=feature_gap_blocked, ), "family_safe": family_safe, "review_required": bool( family_safe or feature_gap_blocked or not bool(lifecycle.get("has_authoritative_lifecycle")) ), "feature_fields_present": feature_fields_present, "feature_fields_missing": feature_fields_missing, "feature_field_count": len(feature_fields_present), "provenance": { "product": "workbook", "features": "workbook" if features else "missing", "lifecycle": "workbook" if lifecycle.get("has_authoritative_lifecycle") else "missing", "replacements": "workbook" if replacements else "missing", }, "match": match, "product": product, "features": features, "lifecycle": lifecycle, "replacements": replacements, } def _router_workbook_fact_bundle_summary(self, bundle: Dict[str, Any]) -> Dict[str, Any]: fact_bundle = _as_dict(bundle) provenance = _as_dict(fact_bundle.get("provenance")) resolution_mode = _norm(fact_bundle.get("resolution_mode")) or "exact" workbook_row_quality = _norm(fact_bundle.get("workbook_row_quality")) or "exact" feature_fields_missing = list(fact_bundle.get("feature_fields_missing") or []) review_required = bool(fact_bundle.get("review_required")) product_provenance = _norm(provenance.get("product")) or "missing" features_provenance = _norm(provenance.get("features")) or "missing" lifecycle_provenance = _norm(provenance.get("lifecycle")) or "missing" replacements_provenance = _norm(provenance.get("replacements")) or "missing" if workbook_row_quality == "family_safe" or resolution_mode in {"family_collapsed", "family_safe_partial", "family_alias_provisional"}: match_summary_label = "Family-safe workbook row" elif workbook_row_quality == "exact_feature_gap": match_summary_label = "Exact workbook row with gaps" else: match_summary_label = "Exact workbook row" source_summary_label = ( "All core lanes are workbook-backed." if all(value == "workbook" for value in (product_provenance, features_provenance, lifecycle_provenance, replacements_provenance)) else ( "Product " + product_provenance.replace("_", " ") + "; features " + features_provenance.replace("_", " ") + "; lifecycle " + lifecycle_provenance.replace("_", " ") + "; replacements " + replacements_provenance.replace("_", " ") + "." ) ) review_hint = "" if review_required: if workbook_row_quality == "family_safe" or resolution_mode in {"family_collapsed", "family_safe_partial", "family_alias_provisional"}: review_hint = "Workbook matched at the family level; confirm the exact SKU if variant-specific details matter." elif workbook_row_quality == "exact_feature_gap" or feature_fields_missing: review_hint = "Workbook row is usable, but some structured feature fields are still missing." elif lifecycle_provenance == "missing": review_hint = "Lifecycle lane is not authoritative in the workbook for this row yet." elif replacements_provenance not in {"", "workbook", "missing"}: review_hint = "Replacement lane is using fallback guidance rather than a direct workbook replacement row." else: review_hint = "At least one workbook source lane still needs review." return { "requested_model": _norm(fact_bundle.get("requested_model")), "resolved_model": _norm(fact_bundle.get("resolved_model")), "resolution_mode": resolution_mode, "workbook_row_quality": workbook_row_quality, "feature_field_count": int(fact_bundle.get("feature_field_count") or 0), "feature_fields_missing": feature_fields_missing, "review_required": review_required, "match_summary_label": match_summary_label, "source_summary_label": source_summary_label, "review_hint": review_hint, "provenance": { "product": product_provenance, "features": features_provenance, "lifecycle": lifecycle_provenance, "replacements": replacements_provenance, }, } def _router_workbook_gap_audit_summary( self, summaries: List[Dict[str, Any]], *, unresolved: Optional[List[str]] = None, ) -> Dict[str, Any]: summary_rows = [_as_dict(item) for item in list(summaries or []) if isinstance(item, dict)] unresolved_rows = [_norm(item) for item in list(unresolved or []) if _norm(item)] missing_field_counter: Counter[str] = Counter() exact_count = 0 family_safe_count = 0 review_required_count = 0 lifecycle_missing_count = 0 replacement_missing_count = 0 for row in summary_rows: row_quality = _norm(row.get("workbook_row_quality")).lower() resolution_mode = _norm(row.get("resolution_mode")).lower() if row_quality == "family_safe" or resolution_mode in {"family_collapsed", "family_safe_partial", "family_alias_provisional"}: family_safe_count += 1 else: exact_count += 1 if bool(row.get("review_required")): review_required_count += 1 provenance = _as_dict(row.get("provenance")) if _norm(provenance.get("lifecycle")).lower() == "missing": lifecycle_missing_count += 1 if _norm(provenance.get("replacements")).lower() == "missing": replacement_missing_count += 1 for field_name in list(row.get("feature_fields_missing") or []): normalized_field = _norm(field_name) if normalized_field: missing_field_counter[normalized_field] += 1 top_missing_fields = [field_name for field_name, _count in missing_field_counter.most_common(5)] return { "device_count": len(summary_rows), "exact_count": exact_count, "family_safe_count": family_safe_count, "review_required_count": review_required_count, "lifecycle_missing_count": lifecycle_missing_count, "replacement_missing_count": replacement_missing_count, "unresolved_count": len(unresolved_rows), "unresolved_models": unresolved_rows[:8], "top_missing_fields": top_missing_fields, } def _router_workbook_exact_family_alias_detail( self, detail: Dict[str, Any], *, requested_text: str, ) -> Optional[Dict[str, Any]]: match = _as_dict(detail.get("match")) product = _as_dict(detail.get("product")) resolved_row = product or match if not self._router_workbook_should_treat_exact_match_as_family_alias(requested_text, resolved_row): return None requested_label = _norm(requested_text) resolved_label = ( _norm(product.get("display_name")) or _norm(product.get("product_id")) or _norm(match.get("display_name")) or _norm(match.get("product_id")) ) note = ( f"Requested `{requested_label}` mapped to workbook SKU `{resolved_label}`; " "this answer keeps family-level claims and exact variant still needs confirmation." ) return { **detail, "_canonical_resolved_label": resolved_label, "_canonical_display_name": resolved_label, "match": { **match, "product_id": requested_label, "display_name": requested_label, "subject_display_name": requested_label, "_requested_label": requested_label, "_family_requested_text": requested_label, "_family_collapsed": True, "feature_gap_blocked": True, "_canonical_resolved_label": resolved_label, "_canonical_display_name": resolved_label, }, "product": { **product, "product_id": requested_label, "display_name": requested_label, "subject_display_name": requested_label, "_requested_label": requested_label, "_family_requested_text": requested_label, "_family_collapsed": True, "_canonical_resolved_label": resolved_label, "_canonical_display_name": resolved_label, }, "_family_safe": True, "_family_safe_note": note, } def _router_workbook_family_row_detail( self, detail: Dict[str, Any], *, requested_text: str, ) -> Optional[Dict[str, Any]]: match = _as_dict(detail.get("match")) product = _as_dict(detail.get("product")) resolved_row = product or match granularity = _norm(resolved_row.get("granularity")).lower() entity_type = _norm(resolved_row.get("entity_type")).lower() if not (granularity == "family" or "family" in entity_type): return None requested_label = _norm(requested_text) resolved_label = ( _norm(product.get("display_name")) or _norm(product.get("product_id")) or _norm(match.get("display_name")) or _norm(match.get("product_id")) ) display_label = ( requested_label or _norm(match.get("matched_alias_text")) or resolved_label ) note = ( f"Requested `{display_label}` matched a workbook family row; " "exact variant still needs confirmation." ) return { **detail, "_canonical_resolved_label": resolved_label, "_canonical_display_name": resolved_label, "match": { **match, "product_id": display_label, "display_name": display_label, "subject_display_name": display_label, "_requested_label": display_label, "_family_requested_text": display_label, "_family_collapsed": True, "feature_gap_blocked": True, "_canonical_resolved_label": resolved_label, "_canonical_display_name": resolved_label, }, "product": { **product, "product_id": display_label, "display_name": display_label, "subject_display_name": display_label, "_requested_label": display_label, "_family_requested_text": display_label, "_family_collapsed": True, "_canonical_resolved_label": resolved_label, "_canonical_display_name": resolved_label, }, "_family_safe": True, "_family_safe_note": note, } def _router_workbook_resolve_detail_or_family( self, core: Any, *, manufacturer_text: str, product_text: str, ) -> Dict[str, Any]: normalized = _as_dict(core.normalize_catalog_device(manufacturer_text=manufacturer_text, product_text=product_text)) if normalized.get("ok"): detail = _as_dict( core.get_catalog_device_details_by_key( product_key=str(_as_dict(normalized.get("match")).get("product_key") or "") ) ) if detail.get("ok"): detail["catalog"] = normalized.get("catalog") detail["match"] = _as_dict(normalized.get("match")) or _as_dict(detail.get("match")) detail["product"] = _as_dict(detail.get("product")) or _as_dict(detail.get("match")) detail["lifecycle"] = self._router_workbook_enrich_lifecycle_row( _as_dict(detail.get("match")), _as_dict(detail.get("product")), _as_dict(detail.get("lifecycle")), ) family_row_detail = self._router_workbook_family_row_detail( detail, requested_text=product_text, ) if family_row_detail: family_row_detail["catalog"] = normalized.get("catalog") family_row_detail["_fact_bundle"] = self._router_workbook_fact_bundle_from_detail( family_row_detail, requested_text=product_text, resolution_mode="family_safe_partial", ) return {"ok": True, "detail": family_row_detail, "resolution_mode": "family_safe_partial"} family_alias_detail = self._router_workbook_exact_family_alias_detail( detail, requested_text=product_text, ) if family_alias_detail: family_alias_detail["catalog"] = normalized.get("catalog") family_alias_detail["_fact_bundle"] = self._router_workbook_fact_bundle_from_detail( family_alias_detail, requested_text=product_text, resolution_mode="family_alias_provisional", ) return {"ok": True, "detail": family_alias_detail, "resolution_mode": "family_alias_provisional"} detail["_fact_bundle"] = self._router_workbook_fact_bundle_from_detail( detail, requested_text=product_text, resolution_mode="exact", ) return {"ok": True, "detail": detail, "resolution_mode": "exact"} return {"ok": False, "response": detail} if str(normalized.get("error") or "") != "ambiguous_product": return {"ok": False, "response": normalized} family_safe = self._router_workbook_family_safe_detail( core, requested_text=product_text, response=normalized, ) if family_safe: family_safe["_fact_bundle"] = self._router_workbook_fact_bundle_from_detail( family_safe, requested_text=product_text, resolution_mode="family_safe_partial", ) return {"ok": True, "detail": family_safe, "resolution_mode": "family_safe_partial"} return {"ok": False, "response": normalized} def _router_workbook_requested_search_filters(self, query: RouterIntelligenceQuery) -> List[str]: requested: List[str] = [] for field_name in ("rugged", "battery", "wifi", "gnss", "poe"): if query.search_filters.get(field_name) is True: requested.append(field_name) if str(query.search_filters.get("indoor_outdoor") or "").strip(): requested.append("indoor_outdoor") if str(query.search_filters.get("cellular_generation") or "").strip(): requested.append("cellular_generation") if str(query.search_filters.get("use_case_hint") or "").strip(): requested.append("use_case_hint") if query.search_filters.get("min_total_ethernet_ports") is not None: requested.append("min_total_ethernet_ports") if query.search_filters.get("require_documented_rf") is True: requested.append("require_documented_rf") return requested def _router_workbook_has_explicit_rf_documentation(self, row: Dict[str, Any]) -> bool: item = _as_dict(row) candidate_labels = [ _norm(item.get("product_id")), _norm(item.get("display_name")), _norm(item.get("product_key")), ] fact_row: Dict[str, Any] = {} for label in candidate_labels: if not label: continue fact_key = self._lookup_router_fact_key(label) if fact_key: fact_row = _as_dict(self._router_fact_rows.get(fact_key)) if fact_row: break raw_value = _norm( fact_row.get("antennas_rf") or fact_row.get("connector_summary") or item.get("antennas_rf") or "" ) if not raw_value: return False low_value = raw_value.lower() if any( token in low_value for token in ( "not listed", "not documented", "unclear", "unknown", "by variant", "variant", "depends on modem", "depends on configuration", "varies by modem", "varies by sku", "supported modem option", "supported modem options", "supported modem sku", "supported modem skus", "if present", "exact sku", "exact package", "needs exact sku", "varies by", "check exact part", "exact part", ) ): return False return bool( re.search(r"\b(?:\d+\s*x\s*)?(?:rp-?sma|sma)\b", low_value) or re.search(r"\bantenna connectors?\b", low_value) or re.search(r"\b(?:gps|gnss)\b[^.;]{0,40}\b(?:connector|sma)\b", low_value) ) def _router_workbook_search_gap_notes(self, row: Dict[str, Any], query: RouterIntelligenceQuery) -> List[str]: item = _as_dict(row) features = _as_dict(item.get("features")) notes: List[str] = [] if query.search_filters.get("rugged") is True and not bool(features.get("rugged_norm")): notes.append("Ruggedized status is not explicit in the workbook") if query.search_filters.get("battery") is True and not bool(features.get("battery_norm")): notes.append("Battery support is optional or not explicit in the workbook") if query.search_filters.get("wifi") is True and not bool(features.get("wifi_norm")): notes.append("No Wi-Fi") if query.search_filters.get("gnss") is True and not bool(features.get("gnss_norm")): notes.append("No GNSS/GPS") if query.search_filters.get("poe") is True and not bool(features.get("poe_norm")): notes.append("No PoE") requested_cell = str(query.search_filters.get("cellular_generation") or "").strip().upper() actual_cell = str(features.get("cellular_gen_norm") or "").strip().upper() if requested_cell: if actual_cell and requested_cell not in actual_cell: notes.append(f"Cellular generation is `{actual_cell}`, not `{requested_cell}`") elif not actual_cell: notes.append("Cellular generation is not explicit in the workbook row") requested_placement = str(query.search_filters.get("indoor_outdoor") or "").strip().lower() actual_placement = str(features.get("indoor_outdoor_norm") or "").strip().lower() if requested_placement: if actual_placement and actual_placement != requested_placement: notes.append(f"Placement fit is `{actual_placement}`, not `{requested_placement}`") elif not actual_placement: notes.append(f"Placement fit for `{requested_placement}` is not explicit in the workbook") requested_use_case = str(query.search_filters.get("use_case_hint") or "").strip().lower() actual_use_case = str(features.get("use_case_norm") or "").strip().lower() if requested_use_case: if actual_use_case and requested_use_case not in actual_use_case: notes.append(f"Use-case fit is `{actual_use_case}`, not `{requested_use_case}`") elif not actual_use_case: notes.append(f"{requested_use_case.title()} fit is not explicit in the workbook") min_ports = query.search_filters.get("min_total_ethernet_ports") if min_ports is not None: total_ports = int(item.get("total_ethernet_ports") or 0) if total_ports < int(min_ports): notes.append(f"Only {total_ports} total Ethernet ports") if query.search_filters.get("require_documented_rf") is True and not self._router_workbook_has_explicit_rf_documentation(item): notes.append("RF connector details are not explicitly documented") return notes def _router_workbook_ranked_search(self, core: Any, query: RouterIntelligenceQuery) -> Dict[str, Any]: normalized_query = str(query.normalized_message or "").lower() requested_limit = max(1, int(query.limit or 3)) relaxed_search_limit = max(requested_limit * 4, requested_limit + 4, 12) soft_feature_fields: Set[str] = set() if query.search_filters.get("poe") is True and ( re.search(r"\bprefer(?:ably)?\b[^.]{0,40}\b(?:poe|power over ethernet)\b", normalized_query) or ( ("poe" in normalized_query or "power over ethernet" in normalized_query) and any(token in normalized_query for token in ("do not throw", "don't throw", "do not discard", "don't discard")) and any(token in normalized_query for token in ("missing", "optional", "not required")) ) ): soft_feature_fields.add("poe") strict_battery_evidence = bool(query.search_filters.get("battery") is True) and any( token in normalized_query for token in ("do not blur", "built-in battery", "battery-backed hardware") ) disable_relaxed_fallbacks = bool(soft_feature_fields) or strict_battery_evidence exact_search_limit = relaxed_search_limit if query.search_filters.get("require_documented_rf") is True else requested_limit search_kwargs = { "manufacturer_text": query.manufacturer_text, "rugged": query.search_filters.get("rugged"), "battery": query.search_filters.get("battery"), "min_total_ethernet_ports": query.search_filters.get("min_total_ethernet_ports"), "wifi": query.search_filters.get("wifi"), "gnss": query.search_filters.get("gnss"), "poe": query.search_filters.get("poe"), "indoor_outdoor": str(query.search_filters.get("indoor_outdoor") or ""), "cellular_generation": str(query.search_filters.get("cellular_generation") or ""), "use_case_hint": str(query.search_filters.get("use_case_hint") or ""), "current_only": bool(query.search_filters.get("current_only", True)), "limit": exact_search_limit, } for field_name in soft_feature_fields: if field_name in search_kwargs: search_kwargs[field_name] = None exact = _as_dict(core.search_catalog_devices(**search_kwargs)) if not exact.get("ok"): return exact raw_exact_matches = [row for row in list(exact.get("matches") or []) if isinstance(row, dict)] exact_matches = list(raw_exact_matches) rf_filtered_exact_matches: List[Dict[str, Any]] = [] if query.search_filters.get("require_documented_rf") is True: rf_filtered_exact_matches = [ row for row in raw_exact_matches if not self._router_workbook_has_explicit_rf_documentation(row) ] exact_matches = [row for row in raw_exact_matches if self._router_workbook_has_explicit_rf_documentation(row)] for row in exact_matches: row["_match_tier"] = "Exact current match" row["_gap_notes"] = [] requested_filters = set(self._router_workbook_requested_search_filters(query)) - set(soft_feature_fields) relaxable_fields = [ field_name # Relax hard hardware requirements before placement / use-case preferences. for field_name in ("gnss", "poe", "indoor_outdoor", "rugged") if field_name in requested_filters ] should_relax = (not disable_relaxed_fallbacks) and len(exact_matches) < requested_limit and ( len(relaxable_fields) >= 2 or ("min_total_ethernet_ports" in requested_filters and bool(relaxable_fields)) ) allow_rugged_recovery_after_soft_poe = bool( ("poe" in soft_feature_fields) and query.search_filters.get("rugged") is True and len(exact_matches) == 0 ) seen_product_keys = {str(row.get("product_key") or "") for row in exact_matches} near_matches: List[Dict[str, Any]] = [] relaxed_plans: List[List[str]] = [] battery_unclear_count = 0 battery_verification_count = 0 excluded_noncurrent_matches: List[Dict[str, Any]] = [] if query.search_filters.get("battery") is True and (not strict_battery_evidence) and len(exact_matches) < requested_limit: battery_relaxed_kwargs = dict(search_kwargs) battery_relaxed_kwargs["battery"] = None battery_relaxed_kwargs["limit"] = relaxed_search_limit battery_relaxed = _as_dict(core.search_catalog_devices(**battery_relaxed_kwargs)) if battery_relaxed.get("ok"): for row in [item for item in list(battery_relaxed.get("matches") or []) if isinstance(item, dict)]: product_key = str(row.get("product_key") or "") if not product_key or product_key in seen_product_keys: continue if query.search_filters.get("require_documented_rf") is True and not self._router_workbook_has_explicit_rf_documentation(row): continue enriched = dict(row) enriched["_match_tier"] = "Current alternative (battery optional or unclear)" gap_notes = self._router_workbook_search_gap_notes(enriched, query) if "Battery support is optional or not explicit in the workbook" not in gap_notes: gap_notes.insert(0, "Battery support is optional or not explicit in the workbook") enriched["_gap_notes"] = gap_notes enriched["_relaxed_fields"] = ["battery"] near_matches.append(enriched) seen_product_keys.add(product_key) battery_unclear_count += 1 if len(exact_matches) + len(near_matches) >= requested_limit: break if battery_unclear_count: relaxed_plans.append(["battery"]) if query.search_filters.get("battery") is True and strict_battery_evidence and len(exact_matches) < requested_limit: battery_relaxed_kwargs = dict(search_kwargs) battery_relaxed_kwargs["battery"] = None battery_relaxed_kwargs["limit"] = relaxed_search_limit battery_relaxed = _as_dict(core.search_catalog_devices(**battery_relaxed_kwargs)) if battery_relaxed.get("ok"): for row in [item for item in list(battery_relaxed.get("matches") or []) if isinstance(item, dict)]: product_key = str(row.get("product_key") or "") if not product_key or product_key in seen_product_keys: continue if query.search_filters.get("require_documented_rf") is True and not self._router_workbook_has_explicit_rf_documentation(row): continue enriched = dict(row) enriched["_match_tier"] = "Needs battery verification" gap_notes = self._router_workbook_search_gap_notes(enriched, query) if "Battery support is optional or not explicit in the workbook" not in gap_notes: gap_notes.insert(0, "Battery support is optional or not explicit in the workbook") enriched["_gap_notes"] = gap_notes enriched["_relaxed_fields"] = ["battery"] enriched["_battery_verification_only"] = True near_matches.append(enriched) seen_product_keys.add(product_key) battery_verification_count += 1 if len(exact_matches) + len(near_matches) >= requested_limit: break if battery_verification_count: relaxed_plans.append(["battery"]) if should_relax: for combo_size in range(1, min(3, len(relaxable_fields)) + 1): for combo in combinations(relaxable_fields, combo_size): relaxed_kwargs = dict(search_kwargs) relaxed_kwargs["limit"] = relaxed_search_limit for field_name in combo: if field_name == "indoor_outdoor": relaxed_kwargs["indoor_outdoor"] = "" else: relaxed_kwargs[field_name] = None relaxed = _as_dict(core.search_catalog_devices(**relaxed_kwargs)) if not relaxed.get("ok"): continue added_from_plan = False for row in [item for item in list(relaxed.get("matches") or []) if isinstance(item, dict)]: product_key = str(row.get("product_key") or "") if not product_key or product_key in seen_product_keys: continue if query.search_filters.get("require_documented_rf") is True and not self._router_workbook_has_explicit_rf_documentation(row): continue enriched = dict(row) enriched["_match_tier"] = "Closest current alternative" enriched["_gap_notes"] = self._router_workbook_search_gap_notes(enriched, query) enriched["_relaxed_fields"] = list(combo) near_matches.append(enriched) seen_product_keys.add(product_key) added_from_plan = True if len(exact_matches) + len(near_matches) >= requested_limit: break if added_from_plan: relaxed_plans.append(list(combo)) if len(exact_matches) + len(near_matches) >= requested_limit: break if len(exact_matches) + len(near_matches) >= requested_limit: break if ((not disable_relaxed_fallbacks) or allow_rugged_recovery_after_soft_poe) and query.search_filters.get("rugged") is True and len(exact_matches) + len(near_matches) < requested_limit: rugged_relaxed_kwargs = dict(search_kwargs) rugged_relaxed_kwargs["rugged"] = None rugged_relaxed_kwargs["limit"] = relaxed_search_limit rugged_relaxed = _as_dict(core.search_catalog_devices(**rugged_relaxed_kwargs)) if rugged_relaxed.get("ok"): added_from_rugged = False for row in [item for item in list(rugged_relaxed.get("matches") or []) if isinstance(item, dict)]: product_key = str(row.get("product_key") or "") if not product_key or product_key in seen_product_keys: continue if query.search_filters.get("require_documented_rf") is True and not self._router_workbook_has_explicit_rf_documentation(row): continue enriched = dict(row) enriched["_match_tier"] = ( "Closest current alternative (rugged flag needs verification)" if allow_rugged_recovery_after_soft_poe else "Closest current alternative" ) gap_notes = self._router_workbook_search_gap_notes(enriched, query) if "Ruggedized status is not explicit in the workbook" not in gap_notes: gap_notes.insert(0, "Ruggedized status is not explicit in the workbook") enriched["_gap_notes"] = gap_notes enriched["_relaxed_fields"] = ["rugged"] near_matches.append(enriched) seen_product_keys.add(product_key) added_from_rugged = True if len(exact_matches) + len(near_matches) >= requested_limit: break if added_from_rugged: relaxed_plans.append(["rugged"]) if (not disable_relaxed_fallbacks) and len(exact_matches) + len(near_matches) < requested_limit: broad_relaxed_kwargs = dict(search_kwargs) for field_name in ("rugged", "battery", "gnss", "poe"): broad_relaxed_kwargs[field_name] = None broad_relaxed_kwargs["indoor_outdoor"] = "" broad_relaxed_kwargs["limit"] = relaxed_search_limit broad_relaxed = _as_dict(core.search_catalog_devices(**broad_relaxed_kwargs)) if broad_relaxed.get("ok"): relaxed_fields: List[str] = [] for field_name in ("rugged", "battery", "wifi", "gnss", "poe"): if query.search_filters.get(field_name) is True: relaxed_fields.append(field_name) if str(query.search_filters.get("indoor_outdoor") or "").strip(): relaxed_fields.append("indoor_outdoor") added_from_broad = False for row in [item for item in list(broad_relaxed.get("matches") or []) if isinstance(item, dict)]: product_key = str(row.get("product_key") or "") if not product_key or product_key in seen_product_keys: continue if query.search_filters.get("require_documented_rf") is True and not self._router_workbook_has_explicit_rf_documentation(row): continue enriched = dict(row) enriched["_match_tier"] = "Broader current alternative" enriched["_gap_notes"] = self._router_workbook_search_gap_notes(enriched, query) enriched["_relaxed_fields"] = list(relaxed_fields) near_matches.append(enriched) seen_product_keys.add(product_key) added_from_broad = True if len(exact_matches) + len(near_matches) >= requested_limit: break if added_from_broad and relaxed_fields: relaxed_plans.append(list(relaxed_fields)) if (not disable_relaxed_fallbacks) and len(exact_matches) + len(near_matches) < requested_limit: breadth_relaxed_kwargs = dict(search_kwargs) breadth_relaxed_kwargs["wifi"] = None breadth_relaxed_kwargs["cellular_generation"] = "" breadth_relaxed_kwargs["limit"] = relaxed_search_limit breadth_relaxed = _as_dict(core.search_catalog_devices(**breadth_relaxed_kwargs)) if breadth_relaxed.get("ok"): added_from_breadth = False for row in [item for item in list(breadth_relaxed.get("matches") or []) if isinstance(item, dict)]: product_key = str(row.get("product_key") or "") if not product_key or product_key in seen_product_keys: continue if query.search_filters.get("require_documented_rf") is True and not self._router_workbook_has_explicit_rf_documentation(row): continue enriched = dict(row) enriched["_match_tier"] = "Broader current alternative" gap_notes = self._router_workbook_search_gap_notes(enriched, query) relaxed_fields = list(enriched.get("_relaxed_fields") or []) if query.search_filters.get("wifi") is True: if "Wi-Fi is not explicit in the workbook" not in gap_notes: gap_notes.insert(0, "Wi-Fi is not explicit in the workbook") relaxed_fields.append("wifi") requested_cellular = str(query.search_filters.get("cellular_generation") or "").strip().upper() actual_cellular = str(_as_dict(enriched.get("features")).get("cellular_gen_norm") or "").strip().upper() if requested_cellular and requested_cellular != actual_cellular: note = f"{requested_cellular} cellular is not explicit in the workbook" if note not in gap_notes: gap_notes.insert(0, note) relaxed_fields.append("cellular_generation") requested_use_case = str(query.search_filters.get("use_case_hint") or "").strip().lower() feature_use_case = str(_as_dict(enriched.get("features")).get("use_case_norm") or "").strip().lower() if requested_use_case and requested_use_case not in feature_use_case: note = f"{requested_use_case} use-case fit is not explicit in the workbook" if note not in gap_notes: gap_notes.insert(0, note) relaxed_fields.append("use_case_hint") placement = str(query.search_filters.get("indoor_outdoor") or "").strip().lower() feature_placement = str(_as_dict(enriched.get("features")).get("indoor_outdoor_norm") or "").strip().lower() if placement and placement != feature_placement: note = f"{placement} placement fit is not explicit in the workbook" if note not in gap_notes: gap_notes.insert(0, note) relaxed_fields.append("indoor_outdoor") enriched["_gap_notes"] = gap_notes enriched["_relaxed_fields"] = list(dict.fromkeys([field for field in relaxed_fields if _norm(field)])) near_matches.append(enriched) seen_product_keys.add(product_key) added_from_breadth = True if len(exact_matches) + len(near_matches) >= requested_limit: break if added_from_breadth: relaxed_plans.append([field for field in ("wifi", "cellular_generation", "use_case_hint", "indoor_outdoor") if query.search_filters.get(field)]) def _search_near_match_sort_key(item: Dict[str, Any]) -> Tuple[int, int, int, int, int, int, int, str]: features = _as_dict(item.get("features")) relaxed_fields = [str(field) for field in list(item.get("_relaxed_fields") or []) if _norm(field)] gap_notes = [str(note) for note in list(item.get("_gap_notes") or []) if _norm(note)] def _feature_enabled(key: str) -> bool: return str(features.get(key) or "").strip().lower() in {"yes", "true", "1", "y"} def _display_name() -> str: for key in ( "subject_display_name", "_requested_label", "matched_alias_text", "display_name", "router_display_name", "family_group", "model", "sku", "replacement_display", "product_id", "product_key", ): value = _norm(item.get(key, "")) if value: return value return "" hard_hits = 0 if query.search_filters.get("min_total_ethernet_ports") is not None: try: port_count = int(item.get("total_ethernet_ports") or 0) except Exception: port_count = 0 if port_count >= int(query.search_filters.get("min_total_ethernet_ports") or 0): hard_hits += 1 if query.search_filters.get("rugged") is True and _feature_enabled("rugged_norm") and "rugged" not in relaxed_fields: hard_hits += 1 if query.search_filters.get("battery") is True and _feature_enabled("battery_norm") and "battery" not in relaxed_fields: hard_hits += 1 if query.search_filters.get("wifi") is True and _feature_enabled("wifi_norm") and "wifi" not in relaxed_fields: hard_hits += 1 if query.search_filters.get("gnss") is True and _feature_enabled("gnss_norm") and "gnss" not in relaxed_fields: hard_hits += 1 if query.search_filters.get("poe") is True and _feature_enabled("poe_norm") and "poe" not in relaxed_fields: hard_hits += 1 placement = str(query.search_filters.get("indoor_outdoor") or "").strip().lower() if placement and placement == str(features.get("indoor_outdoor_norm") or "").strip().lower() and "indoor_outdoor" not in relaxed_fields: hard_hits += 1 use_case_hint = str(query.search_filters.get("use_case_hint") or "").strip().lower() actual_use_case = str(features.get("use_case_norm") or "").strip().lower() if use_case_hint and actual_use_case and use_case_hint in actual_use_case and "use_case_hint" not in relaxed_fields: hard_hits += 1 use_case_mismatch = 1 if use_case_hint and actual_use_case and use_case_hint not in actual_use_case else 0 current_status = str(item.get("status_bucket") or "").strip().lower() current_flag = 0 if (current_status == "current" or bool(item.get("current_recommendable_flag"))) else 1 verification_flag = 1 if bool(item.get("_battery_verification_only")) else 0 try: port_count = int(item.get("total_ethernet_ports") or 0) except Exception: port_count = 0 return ( len(relaxed_fields), use_case_mismatch, -hard_hits, len(gap_notes), verification_flag, current_flag, -port_count, _display_name().lower(), ) if near_matches: near_matches.sort(key=_search_near_match_sort_key) displayed_matches = exact_matches[:requested_limit] remaining = max(0, requested_limit - len(displayed_matches)) displayed_matches.extend(near_matches[:remaining]) if ( bool(query.search_filters.get("current_only", True)) and any( token in normalized_query for token in ( "excluded because", "excluded since", "tell me what you excluded", "what you excluded", "excluded because it is end-of-sale", "excluded because it is end-of-life", "excluded because it is eos", "excluded because it is eol", ) ) ): exclusion_kwargs = dict(search_kwargs) exclusion_kwargs["current_only"] = False exclusion_kwargs["limit"] = max(relaxed_search_limit, 20) exclusion_search = _as_dict(core.search_catalog_devices(**exclusion_kwargs)) if exclusion_search.get("ok"): shown_keys = { str(row.get("product_key") or "") for row in [*displayed_matches, *exact_matches, *near_matches] if isinstance(row, dict) } for row in [item for item in list(exclusion_search.get("matches") or []) if isinstance(item, dict)]: product_key = str(row.get("product_key") or "") if product_key and product_key in shown_keys: continue status_low = _norm(row.get("status_bucket") or "").lower() if status_low == "current": continue excluded_noncurrent_matches.append(dict(row)) if len(excluded_noncurrent_matches) >= 6: break return { **exact, "matches": displayed_matches, "exact_matches": exact_matches, "rf_filtered_exact_matches": rf_filtered_exact_matches, "near_matches": near_matches, "exact_count": len(exact_matches), "near_count": len(near_matches), "requested_limit": requested_limit, "used_relaxed_fallback": bool(near_matches), "relaxed_plans": relaxed_plans, "battery_unclear_count": battery_unclear_count, "battery_verification_count": battery_verification_count, "excluded_noncurrent_matches": excluded_noncurrent_matches, "soft_fields": sorted(soft_feature_fields), "strict_battery_evidence": strict_battery_evidence, } def _router_workbook_resolve_replacement_analysis( self, core: Any, *, manufacturer_text: str, product_text: str, ) -> Dict[str, Any]: def _attach_fact_bundle(analysis_payload: Dict[str, Any], *, resolution_mode: str) -> Dict[str, Any]: analysis = _as_dict(analysis_payload) match = _as_dict(analysis.get("match")) product_key = str(match.get("product_key") or "").strip() detail = _as_dict(core.get_catalog_device_details_by_key(product_key=product_key)) if product_key else {} product = _as_dict(detail.get("product")) or match features = _as_dict(detail.get("features")) lifecycle = _as_dict(analysis.get("_replacement_subject_lifecycle") or detail.get("lifecycle")) replacements = _as_dict(analysis.get("replacements")) analysis["_fact_bundle"] = self._router_workbook_fact_bundle_from_payload( match=match, product=product, features=features, lifecycle=lifecycle, replacements=replacements, requested_text=product_text, resolution_mode=resolution_mode, family_safe=bool( analysis.get("_family_collapsed") or analysis.get("_family_safe_note") or match.get("_family_collapsed") or product.get("_family_collapsed") or str(resolution_mode or "").strip() in {"family_safe_partial", "family_alias_provisional", "family_collapsed"} ), canonical_display_name=_norm( match.get("_canonical_display_name") or match.get("_canonical_resolved_label") or product.get("_canonical_display_name") or product.get("_canonical_resolved_label") ), ) return analysis def _augment_with_lifecycle_details(payload: Dict[str, Any]) -> Dict[str, Any]: analysis = _as_dict(payload) match = _as_dict(analysis.get("match")) product_key = str(match.get("product_key") or "").strip() if not product_key: return analysis detail = _as_dict(core.get_catalog_device_details_by_key(product_key=product_key)) if not detail.get("ok"): return analysis lifecycle = _as_dict(detail.get("lifecycle")) if lifecycle: analysis["_replacement_subject_lifecycle"] = lifecycle requested_key = ( self._lookup_router_lifecycle_key(_norm(product_text)) or self._lookup_router_lifecycle_key(_compact_model(product_text)) ) if requested_key: legacy_row = _as_dict(self._router_lifecycle_rows.get(requested_key)) if legacy_row: analysis["_replacement_legacy_lifecycle"] = legacy_row return analysis analysis = _as_dict(core.analyze_catalog_device(manufacturer_text=manufacturer_text, product_text=product_text)) if analysis.get("ok"): analysis = _augment_with_lifecycle_details(analysis) match = _as_dict(analysis.get("match")) requested_label = _norm(product_text) analysis["match"] = {**match, "_requested_label": requested_label} product_key = str(match.get("product_key") or "").strip() detail = _as_dict(core.get_catalog_device_details_by_key(product_key=product_key)) if product_key else {} family_row_detail = self._router_workbook_family_row_detail( detail, requested_text=product_text, ) if detail.get("ok") else None if family_row_detail: note = _norm(family_row_detail.get("_family_safe_note") or "") analysis["match"] = { **_as_dict(analysis.get("match")), "_requested_label": requested_label, "_family_requested_text": requested_label, "_family_collapsed": True, "product_id": requested_label, "display_name": requested_label, "subject_display_name": requested_label, } analysis["_family_safe_note"] = note analysis["review_required"] = True manual_review_reasons = [str(x) for x in list(analysis.get("manual_review_reasons") or []) if _norm(x)] if note and note not in manual_review_reasons: manual_review_reasons.append(note) analysis["manual_review_reasons"] = manual_review_reasons analysis = _attach_fact_bundle(analysis, resolution_mode="family_safe_partial") return {"ok": True, "analysis": analysis, "resolution_mode": "family_safe_partial"} family_alias_detail = self._router_workbook_exact_family_alias_detail( detail, requested_text=product_text, ) if detail.get("ok") else None if family_alias_detail: note = _norm(family_alias_detail.get("_family_safe_note") or "") analysis["match"] = { **_as_dict(analysis.get("match")), "_requested_label": requested_label, "_family_requested_text": requested_label, "_family_collapsed": True, "product_id": requested_label, "display_name": requested_label, "subject_display_name": requested_label, } analysis["_family_safe_note"] = note analysis["review_required"] = True manual_review_reasons = [str(x) for x in list(analysis.get("manual_review_reasons") or []) if _norm(x)] if note and note not in manual_review_reasons: manual_review_reasons.append(note) analysis["manual_review_reasons"] = manual_review_reasons analysis = _attach_fact_bundle(analysis, resolution_mode="family_alias_provisional") return {"ok": True, "analysis": analysis, "resolution_mode": "family_alias_provisional"} analysis = _attach_fact_bundle(analysis, resolution_mode="exact") return {"ok": True, "analysis": analysis, "resolution_mode": "exact"} normalized = _as_dict(core.normalize_catalog_device(manufacturer_text=manufacturer_text, product_text=product_text)) if str(normalized.get("error") or "") != "ambiguous_product": legacy_fallback = self._router_legacy_replacement_fallback_analysis( core, manufacturer_text=manufacturer_text, product_text=product_text, ) if legacy_fallback.get("ok"): return legacy_fallback return {"ok": False, "response": analysis or normalized} collapsed = self._router_workbook_collapse_family_ambiguity(product_text, normalized) if not collapsed: return {"ok": False, "response": normalized} resolved_text = ( _norm(collapsed.get("display_name")) or _norm(collapsed.get("product_id")) or _norm(collapsed.get("matched_alias_text")) or _norm(product_text) ) retried = _as_dict(core.analyze_catalog_device(manufacturer_text=manufacturer_text, product_text=resolved_text)) if not retried.get("ok"): legacy_fallback = self._router_legacy_replacement_fallback_analysis( core, manufacturer_text=manufacturer_text, product_text=product_text, ) if legacy_fallback.get("ok"): return legacy_fallback return {"ok": False, "response": retried} match = _as_dict(retried.get("match")) retried["match"] = { **match, "_family_collapsed": True, "_family_requested_text": _norm(product_text), "_requested_label": _norm(product_text), } retried = _augment_with_lifecycle_details(retried) retried = _attach_fact_bundle(retried, resolution_mode="family_collapsed") return {"ok": True, "analysis": retried, "resolution_mode": "family_collapsed"} def _router_legacy_replacement_fallback_analysis( self, core: Any, *, manufacturer_text: str, product_text: str, ) -> Dict[str, Any]: source_profile = self._router_internal_profile(product_text, requested_label=_humanize_model_token(product_text)) source_model_key = _compact_model(source_profile.get("model_key") or "") source_label = _norm(source_profile.get("model") or product_text or source_model_key) source_vendor = _norm(source_profile.get("vendor_family") or "") source_vendor_key = manufacturer_family_key(source_vendor) source_class = _norm(source_profile.get("device_class") or "") source_ports = int(source_profile.get("port_count") or 0) source_rugged = _norm(source_profile.get("rugged_class") or "") if not source_model_key: return {"ok": False, "response": {"message": "No internal router profile was available for this model."}} source_label_compact = _compact_model(source_label) if source_label_compact.startswith("MODEL") and re.fullmatch(r"MODEL\d+", source_label_compact): return { "ok": False, "response": { "error": "ambiguous_product", "message": "I need the actual router model/SKU here. A numeric label like `228` is not enough to map a workbook replacement path safely.", }, } legacy_key = self._lookup_router_lifecycle_key_relaxed(product_text) or self._lookup_router_lifecycle_key(product_text) legacy_row = _as_dict(self._router_lifecycle_rows.get(legacy_key, {})) if legacy_key else {} def _same_vendor_5g_fit(candidate: Dict[str, Any]) -> bool: candidate_vendor_key = manufacturer_family_key(candidate.get("vendor_family") or candidate.get("manufacturer_group")) if not source_vendor_key or candidate_vendor_key != source_vendor_key: return False if "5g" not in _norm(candidate.get("modem_class") or ""): return False candidate_life_key = self._lookup_router_lifecycle_key_relaxed(candidate.get("model_key", "")) candidate_life = self._router_lifecycle_rows.get(candidate_life_key, {}) if candidate_life_key else {} candidate_status = _norm(candidate_life.get("status", "")).lower() if ("end of life" in candidate_status) or ("end of sale" in candidate_status): return False candidate_class = _norm(candidate.get("device_class") or "") if source_class and source_class != "unknown": if candidate_class == "unknown": return False if source_class != candidate_class: return False candidate_ports = int(candidate.get("port_count") or 0) if source_ports > 0 and candidate_ports > 0 and candidate_ports < max(1, source_ports - 1): return False candidate_rugged = _norm(candidate.get("rugged_class") or "") if source_rugged in {"vehicle", "outdoor", "industrial"} and candidate_rugged == "indoor": return False return True def _resolve_replacement_row( model_text: str, *, same_manufacturer: bool, mapping_type: str, replacement_class: str, authority_level: str, ) -> Optional[Dict[str, Any]]: model = _norm(model_text) if not model: return None resolved = _as_dict( core.analyze_catalog_device( manufacturer_text=manufacturer_text or _norm(source_profile.get("manufacturer") or ""), product_text=model, ) ) product_key = "" display_name = _humanize_model_token(model) or model manufacturer_group = "" if resolved.get("ok"): match = _as_dict(resolved.get("match")) product = _as_dict(resolved.get("product")) product_key = _norm(match.get("product_key") or product.get("product_key") or "") display_name = _norm( match.get("display_name") or product.get("display_name") or match.get("product_id") or product.get("product_id") or display_name ) manufacturer_group = _norm(match.get("manufacturer_group") or product.get("manufacturer_group") or "") if not product_key: product_key = _compact_model(model) if not product_key: return None same_flag = bool(same_manufacturer) if source_vendor_key and manufacturer_group: same_flag = manufacturer_family_key(manufacturer_group) == source_vendor_key return { "replacement_product_key": product_key, "replacement_display": display_name, "mapping_type": mapping_type, "replacement_class": replacement_class, "authority_level": authority_level, "same_manufacturer": same_flag, "backup_app_ready_flag": True, } same_vendor_candidates: List[Dict[str, Any]] = [] cross_vendor_candidates: List[Dict[str, Any]] = [] seen_keys: set[str] = set() for row in self._unique_router_fact_rows(): candidate = self._router_candidate_seed_from_row(row) key = _compact_model(candidate.get("model_key", "") or candidate.get("model", "")) if (not key) or (key == source_model_key) or (key in seen_keys): continue seen_keys.add(key) score, reasons = self._router_candidate_score(source_profile, candidate) candidate["score"] = round(score, 2) candidate["why_fit"] = reasons if source_vendor_key and manufacturer_family_key(candidate.get("vendor_family")) == source_vendor_key: if _same_vendor_5g_fit(candidate): same_vendor_candidates.append(candidate) elif self._router_candidate_is_strict_fit(source_profile, candidate): cross_vendor_candidates.append(candidate) same_vendor_candidates.sort( key=lambda row: ( float(row.get("score", 0.0) or 0.0), 0 if "lower-cost" in " ".join(str(x) for x in list(row.get("why_fit") or [])).lower() else 1, -float(row.get("msrp_value", 0.0) or 0.0), ), reverse=True, ) cross_vendor_candidates.sort( key=lambda row: ( float(row.get("score", 0.0) or 0.0), 0 if "lower-cost" in " ".join(str(x) for x in list(row.get("why_fit") or [])).lower() else 1, -float(row.get("msrp_value", 0.0) or 0.0), ), reverse=True, ) def _candidate_row( model_text: str, *, same_manufacturer: bool, mapping_type: str, replacement_class: str, authority_level: str, ) -> Optional[Dict[str, Any]]: return _resolve_replacement_row( model_text, same_manufacturer=same_manufacturer, mapping_type=mapping_type, replacement_class=replacement_class, authority_level=authority_level, ) legacy_5g = _normalize_replacement_cell(legacy_row.get("rep5g")) legacy_4g = _normalize_replacement_cell(legacy_row.get("alt4g")) primary_candidates: List[Dict[str, Any]] = [] same_manufacturer_backup_replacements: List[Dict[str, Any]] = [] backup_replacements: List[Dict[str, Any]] = [] fallback_source_mode = "lifecycle_fallback" if legacy_5g: primary_row = _candidate_row( legacy_5g, same_manufacturer=True, mapping_type="legacy_5g_replacement", replacement_class="legacy_same_manufacturer_5g", authority_level="legacy_csv", ) if primary_row and bool(primary_row.get("same_manufacturer")): primary_candidates.append(primary_row) if not primary_candidates and same_vendor_candidates: candidate = same_vendor_candidates.pop(0) candidate_row = _candidate_row( candidate.get("model") or candidate.get("model_key") or "", same_manufacturer=True, mapping_type="same_manufacturer_5g_replacement", replacement_class="same_manufacturer_current_5g", authority_level="internal_catalog", ) if candidate_row: primary_candidates.append(candidate_row) for candidate in same_vendor_candidates[:2]: candidate_row = _candidate_row( candidate.get("model") or candidate.get("model_key") or "", same_manufacturer=True, mapping_type="same_manufacturer_5g_backup", replacement_class="same_manufacturer_backup", authority_level="internal_catalog", ) if candidate_row: same_manufacturer_backup_replacements.append(candidate_row) for candidate in cross_vendor_candidates[:3]: candidate_row = _candidate_row( candidate.get("model") or candidate.get("model_key") or "", same_manufacturer=False, mapping_type="cross_vendor_5g_backup", replacement_class="cross_vendor_backup", authority_level="internal_catalog", ) if candidate_row: backup_replacements.append(candidate_row) replacement_count = len(primary_candidates) + len(same_manufacturer_backup_replacements) + len(backup_replacements) no_replacement = not replacement_count and not (legacy_5g or legacy_4g) source_sources = [ { "id": "L1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": f"legacy_replacement:{legacy_key or source_model_key}", "location": "", "excerpt": ( f"{source_label}: status={_norm(legacy_row.get('status') or source_profile.get('status') or 'Unknown')}; " f"eos={_norm(legacy_row.get('eos') or 'Not listed') or 'Not listed'}; " f"eol={_norm(legacy_row.get('eol') or 'Not listed') or 'Not listed'}; " f"4g_alternative={legacy_4g or 'Not listed'}; " f"5g_replacement={legacy_5g or 'Not listed'}." ), "score": 1.0, }, { "id": "L2", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": f"legacy_catalog:{source_model_key}", "location": "", "excerpt": ( f"{source_label}: manufacturer={_norm(source_profile.get('manufacturer') or '') or 'Not listed'}; " f"modem={_norm(source_profile.get('modem') or '') or 'Not listed'}; " f"wan_lan={_norm(source_profile.get('wan_lan') or '') or 'Not listed'}; " f"wifi={_norm(source_profile.get('wifi') or '') or 'Not listed'}; " f"ruggedization={_norm(source_profile.get('ruggedization') or '') or 'Not listed'}." ), "score": 0.98, }, ] return { "ok": True, "resolution_mode": fallback_source_mode, "analysis": { "match": { "product_key": source_model_key, "product_id": source_label, "display_name": source_label, "manufacturer_group": _norm(source_profile.get("manufacturer") or manufacturer_text or ""), "_requested_label": source_label, "_family_collapsed": False, }, "product": { "product_key": source_model_key, "product_id": source_label, "display_name": source_label, "manufacturer_group": _norm(source_profile.get("manufacturer") or manufacturer_text or ""), "_requested_label": source_label, "_family_collapsed": False, }, "replacements": { "primary_candidates": primary_candidates, "same_manufacturer_backup_replacements": same_manufacturer_backup_replacements, "backup_replacements": backup_replacements, "historical_only_replacements": [], "review_blocked_count": 0, "candidate_count": replacement_count, "no_replacement": no_replacement, }, "_replacement_subject_lifecycle": { "status": _norm(legacy_row.get("status") or source_profile.get("status") or "Unknown"), "eos": _norm(legacy_row.get("eos") or ""), "eol": _norm(legacy_row.get("eol") or ""), }, "_replacement_legacy_lifecycle": legacy_row, "_replacement_source_mode": fallback_source_mode, "_family_safe_note": "", "review_required": True, "manual_review_reasons": [ "Workbook resolution did not return an exact current match, so legacy lifecycle and current catalog fallback were used instead." ], "_fact_bundle": self._router_workbook_fact_bundle_from_payload( match={ "product_key": source_model_key, "product_id": source_label, "display_name": source_label, "manufacturer_group": _norm(source_profile.get("manufacturer") or manufacturer_text or ""), "_requested_label": source_label, "_family_collapsed": False, }, product={ "product_key": source_model_key, "product_id": source_label, "display_name": source_label, "manufacturer_group": _norm(source_profile.get("manufacturer") or manufacturer_text or ""), "_requested_label": source_label, "_family_collapsed": False, }, features={}, lifecycle={ "status": _norm(legacy_row.get("status") or source_profile.get("status") or "Unknown"), "eos": _norm(legacy_row.get("eos") or ""), "eol": _norm(legacy_row.get("eol") or ""), }, replacements={ "primary_candidates": primary_candidates, "same_manufacturer_backup_replacements": same_manufacturer_backup_replacements, "backup_replacements": backup_replacements, "historical_only_replacements": [], "review_blocked_count": 0, "candidate_count": replacement_count, "no_replacement": no_replacement, }, requested_text=source_label, resolution_mode=fallback_source_mode, family_safe=False, ), }, "sources": source_sources, "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], } def _router_workbook_parse_inline_survey_updates(self, message: str) -> Dict[str, Any]: requirements = [ {"field": field_name} for field_name in ( "customer_mode", "selected_hardware", "poe_available", "exterior_mount_allowed", "wall_penetration_allowed", "roof_mount_allowed", "max_ethernet_run_ft", "max_coax_run_ft", "landlord_restrictions", "point:L9", "point:L10", "point:O2", "point:O3", ) ] return self._parse_router_workbook_survey_followup_reply( message, {"requirements": requirements}, ) def _router_workbook_survey_update_entries(self, update_out: Dict[str, Any]) -> List[Dict[str, str]]: labels = { "customer_mode": "Customer mode", "selected_hardware": "Selected hardware", "poe_available": "PoE available", "exterior_mount_allowed": "Exterior mount allowed", "wall_penetration_allowed": "Wall/roof penetration allowed", "roof_mount_allowed": "Roof mount allowed", "max_ethernet_run_ft": "Max Ethernet run", "max_coax_run_ft": "Max coax run", "landlord_restrictions": "Landlord/building restrictions", "point:L9": "Closet point (L9)", "point:L10": "Near-closet point (L10)", "point:O2": "Entry outdoor point (O2)", "point:O3": "Best clear outdoor point (O3)", } headers = _as_dict(update_out.get("applied_header_updates")) restrictions = _as_dict(update_out.get("applied_restriction_updates")) points = _as_dict(update_out.get("applied_point_updates")) def _value_for(field_name: str) -> str: if field_name == "selected_hardware": locked = str(headers.get("selected_hardware_locked_flag") or restrictions.get("selected_hardware_locked_flag") or "").strip() model = str(headers.get("selected_hardware_text") or restrictions.get("selected_hardware_text") or "").strip() if locked == "No": return "Not locked" if model: return f"Locked to {model}" return locked or "Updated" if field_name.startswith("point:"): point_id = field_name.split(":", 1)[1] score = str(_as_dict(points.get(point_id)).get("score") or "").strip() return score or "Updated" if field_name in {"max_ethernet_run_ft", "max_coax_run_ft"}: raw = str(restrictions.get(field_name) or headers.get(field_name) or "").strip() return f"{raw} ft" if raw else "Updated" return str(restrictions.get(field_name) or headers.get(field_name) or "").strip() or "Updated" entries: List[Dict[str, str]] = [] for field_name in list(update_out.get("applied_fields") or []): text = str(field_name or "").strip() if not text: continue entries.append( { "field": text, "label": labels.get(text, text), "value": _value_for(text), } ) return entries def _should_try_router_workbook_gpt_orchestration( self, message: str, requested_domain: str, deterministic_query: Optional[RouterIntelligenceQuery], ) -> bool: if self.client is None: return False normalized = normalize_router_intelligence_text(message) if not normalized: return False extracted_device_texts = extract_router_device_tokens(message) if len(normalized) > 500: return False if str(requested_domain or "") in {"router_docs", "router_lifecycle"}: forced_router_lane = True else: forced_router_lane = False if deterministic_query is not None and str(deterministic_query.intent or "").strip() in { "lifecycle", "fleet_lifecycle", "replacements", "survey", "guided_advisor", }: return False routerish = bool(extract_router_device_tokens(message)) or _contains_any( normalized, ( *tuple(_ROUTER_PLATFORM_HINTS), *tuple(_ROUTER_LIFECYCLE_HINTS), *tuple(_ROUTER_REPLACEMENT_HINTS), *tuple(_ROUTER_DOC_HINTS), "rugged", "battery", "ethernet", "port", "antenna", "survey", "network closet", "placement", "indoor", "outdoor", "adapter", "gateway", ), ) if deterministic_query is None: return forced_router_lane or routerish if deterministic_query.intent == "details" and len(extracted_device_texts) >= 2: return True if ( deterministic_query.intent == "fleet_lifecycle" and str(requested_domain or "") == "router_docs" and len(list(deterministic_query.device_texts or [])) >= 2 ): return True return False def _router_workbook_gpt_orchestrated_query( self, message: str, requested_domain: str, deterministic_query: Optional[RouterIntelligenceQuery], ) -> tuple[Optional[RouterIntelligenceQuery], Dict[str, Any]]: default_meta = { "router_orchestration_mode": "deterministic", "llm_assisted": False, } if not self._should_try_router_workbook_gpt_orchestration(message, requested_domain, deterministic_query): return deterministic_query, default_meta timeout_s = 2.2 system = ( "You classify internal router-workbook questions for a deterministic router engine. " "You are not the fact source. Do not invent specs, replacements, lifecycle dates, BOMs, survey outcomes, or pricing. " "Return JSON only. " "Allowed intents: details, compare, lifecycle, fleet_lifecycle, replacements, search, antenna, survey, guided_advisor, none. " "Defaults: current_only=true unless the user explicitly asks for legacy, old, older, retired, EOS, or EOL devices. " "For compare, require two or more device_texts. " "For search, use it only when the user is asking for examples/list/recommendations by feature or use case. " "For survey, use it for site survey, placement, network closet, indoor/outdoor adapter, or install-location questions. " "For guided_advisor, use it when the user wants you to ask a short sequence of questions and then recommend several router and antenna options." ) payload = { "message": str(message or ""), "requested_domain": str(requested_domain or "auto"), "deterministic_parse": ( { "intent": deterministic_query.intent, "device_texts": list(deterministic_query.device_texts or []), "current_only": bool(deterministic_query.current_only), "manufacturer_text": str(deterministic_query.manufacturer_text or ""), "entity_resolutions": [ item.as_dict() if hasattr(item, "as_dict") else _as_dict(item) for item in list(getattr(deterministic_query, "entity_resolutions", []) or []) ], "typo_candidates": self._router_query_typo_candidates(deterministic_query), } if deterministic_query is not None else None ), "output_schema": { "intent": "details|compare|lifecycle|fleet_lifecycle|replacements|search|antenna|survey|guided_advisor|none", "device_texts": ["router model tokens if present"], "manufacturer_text": "optional manufacturer string", "current_only": True, "limit": 3, "search_filters": { "rugged": True, "battery": False, "min_total_ethernet_ports": 2, "cellular_generation": "5G", "use_case_hint": "branch", }, "survey_key": "optional survey key", "reason": "short explanation of why this intent was chosen", }, } try: resp = responses_create_with_deadline( self.client, timeout_s=timeout_s, model=self.openai_model, input=[ {"role": "system", "content": system}, {"role": "user", "content": json.dumps(payload, ensure_ascii=False)}, ], max_output_tokens=320, ) except Exception: return deterministic_query, default_meta obj = self._extract_json_object(str(getattr(resp, "output_text", "") or "")) if not obj: return deterministic_query, default_meta allowed_intents = {"details", "compare", "lifecycle", "fleet_lifecycle", "replacements", "search", "antenna", "survey", "guided_advisor"} intent = str(obj.get("intent") or "").strip().lower() if intent not in allowed_intents: return deterministic_query, default_meta def _bool_value(value: Any, default: bool) -> bool: if isinstance(value, bool): return value text = str(value or "").strip().lower() if text in {"yes", "true", "1"}: return True if text in {"no", "false", "0"}: return False return bool(default) def _int_value(value: Any, default: int, minimum: int, maximum: int) -> int: try: number = int(str(value).strip()) except Exception: return default return max(minimum, min(maximum, number)) raw_devices = obj.get("device_texts") or [] device_texts: List[str] = [] if isinstance(raw_devices, list): for item in raw_devices: text = str(item or "").strip() if text and text not in device_texts: device_texts.append(text) if not device_texts: device_texts = extract_router_device_tokens(message) search_filters_raw = obj.get("search_filters") if isinstance(obj.get("search_filters"), dict) else {} search_filters: Dict[str, Any] = {} if "rugged" in search_filters_raw: search_filters["rugged"] = _bool_value(search_filters_raw.get("rugged"), False) if "battery" in search_filters_raw: search_filters["battery"] = _bool_value(search_filters_raw.get("battery"), False) if "min_total_ethernet_ports" in search_filters_raw: try: search_filters["min_total_ethernet_ports"] = max(1, int(str(search_filters_raw.get("min_total_ethernet_ports")).strip())) except Exception: pass if "cellular_generation" in search_filters_raw: cellular_generation = str(search_filters_raw.get("cellular_generation") or "").strip().upper() if cellular_generation in {"5G", "4G"}: search_filters["cellular_generation"] = cellular_generation if "use_case_hint" in search_filters_raw: use_case_hint = str(search_filters_raw.get("use_case_hint") or "").strip().lower() if use_case_hint in {"branch", "vehicle"}: search_filters["use_case_hint"] = use_case_hint current_only = _bool_value(obj.get("current_only"), True) if intent == "search": normalized_message = normalize_router_intelligence_text(message) if "min_total_ethernet_ports" not in search_filters: if ( re.search(r"\bdual(?:\s+\w+){0,2}\s+(?:ethernet|lan|wan|wired)\b", normalized_message) or re.search(r"\b(?:two|2)\s+(?:ethernet|lan|wan|wired)\s+(?:jacks|ports?)\b", normalized_message) ): search_filters["min_total_ethernet_ports"] = 2 if "cellular_generation" not in search_filters: if re.search(r"\b5g\b", normalized_message): search_filters["cellular_generation"] = "5G" elif re.search(r"\b(?:4g|lte)\b", normalized_message): search_filters["cellular_generation"] = "4G" if "use_case_hint" not in search_filters: if any(token in normalized_message for token in ("branch", "office", "store", "retail")): search_filters["use_case_hint"] = "branch" elif any(token in normalized_message for token in ("vehicle", "patrol car", "police car", "mobile", "fleet vehicle")): search_filters["use_case_hint"] = "vehicle" search_filters["current_only"] = current_only survey_key = str(obj.get("survey_key") or "").strip() manufacturer_text = str(obj.get("manufacturer_text") or "").strip() limit = _int_value(obj.get("limit"), 3, 1, 10) if intent == "search" and deterministic_query is not None: try: limit = max(limit, int(deterministic_query.limit or 3)) except Exception: limit = max(limit, 3) if intent == "compare" and len(device_texts) < 2: return deterministic_query, default_meta if intent in {"details", "lifecycle", "replacements", "antenna"} and not device_texts: return deterministic_query, default_meta if intent == "search" and not search_filters: return deterministic_query, default_meta if intent == "guided_advisor": limit = 5 query = RouterIntelligenceQuery( intent=intent, raw_message=str(message or ""), normalized_message=normalize_router_intelligence_text(message), device_texts=device_texts[:12], current_only=current_only, manufacturer_text=manufacturer_text, limit=limit, search_filters=search_filters, survey_key=survey_key, entity_resolutions=_build_router_entity_resolutions( device_texts[:12], normalized_message=normalize_router_intelligence_text(message), survey_key=survey_key, ), ) return query, { "router_orchestration_mode": "gpt_interpreted_query", "llm_assisted": True, "router_orchestration_reason": str(obj.get("reason") or "").strip(), } def _router_workbook_clarify_response( self, query: RouterIntelligenceQuery, response: Dict[str, Any], st: UnifiedKnowledgebaseState, domain: str, extra_meta: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: clarify_turn = self._set_clarify_pending(st, "router_workbook_clarify", domain, message=query.raw_message) merged_meta = dict(extra_meta or {}) if clarify_turn > int(self.max_clarify_turns): st.pending = {} return { "assistant": _format_shell( "I still need an exact router model/SKU before I can use the workbook safely.", [ "The workbook returned multiple close matches and I do not want to guess.", "Clarification limit reached.", ], [ "Reply with the exact model/SKU from the device label.", "If helpful, ask for a broader current-device shortlist instead of a model-specific answer.", ], ), "sources": self._router_workbook_sources(domain, query.intent), "files": [str(self._rapid_router_intelligence_status().get("filename") or "router_workbook.xlsx")], "meta": { "domain": domain, "retrieval_mode": "deterministic_router_workbook_clarify_limit", "router_intelligence_intent": query.intent, "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, **merged_meta, }, } candidates = [item for item in list(response.get("candidates") or []) if isinstance(item, dict)] rows = [ "| Candidate | Manufacturer | Status |", "| --- | --- | --- |", ] for item in candidates[:5]: rows.append( "| " + _md_cell(item.get("display_name") or item.get("product_id") or "") + " | " + _md_cell(item.get("manufacturer_group") or "") + " | " + _md_cell(item.get("status_bucket") or "") + " |" ) assistant = _format_shell( "I found multiple workbook-backed matches and need the exact model before I answer.\n\n" + "\n".join(rows), [ str(response.get("message") or "The router token did not resolve to one unique workbook row."), "I am holding the answer instead of guessing between close matches.", ], [ "Reply with the exact model/SKU from the label and I will reuse this same request.", "You can also add the manufacturer name if the model family is reused across vendors.", ], ) return { "assistant": assistant, "sources": self._router_workbook_sources(domain, query.intent), "files": [str(self._rapid_router_intelligence_status().get("filename") or "router_workbook.xlsx")], "meta": { "domain": domain, "retrieval_mode": "deterministic_router_workbook_clarify", "router_intelligence_intent": query.intent, "router_intelligence_source": "workbook", "review_required": True, "clarify_turn": int(clarify_turn), "citation_quorum_not_required": True, **merged_meta, }, } def _rewrite_clarified_model_followup(self, reply: str, pending: Dict[str, Any]) -> str: text = _norm(reply) if not text: return "" words = re.findall(r"[a-z0-9]+", text.lower()) if len(words) > 4: return "" original_message = _norm(pending.get("original_message", "")) if not original_message: return "" candidate_model = text.strip() if not candidate_model or (not _extract_router_models(candidate_model)): return "" rewritten = original_message original_models = [str(x).strip() for x in _extract_router_models(original_message) if str(x).strip()] replaced = False for original_model in original_models: pattern = re.compile(rf"\b{re.escape(original_model)}\b", flags=re.IGNORECASE) rewritten_next, count = pattern.subn(candidate_model, rewritten, count=1) if count: rewritten = rewritten_next replaced = True break if not replaced: for pattern in ( r"\bmodel\s*228\b", r"\b228 router\b", r"\b228\b", r"\bunknown router\b", r"\brx50\b", ): rewritten_next, count = re.subn(pattern, candidate_model, rewritten, count=1, flags=re.IGNORECASE) if count: rewritten = rewritten_next replaced = True break if not replaced: rewritten = f"{original_message} {candidate_model}".strip() return rewritten def _model_clarify_response(self, message: str, st: UnifiedKnowledgebaseState, domain: str) -> Dict[str, Any]: clarify_turn = self._set_clarify_pending(st, "clarify_model", domain, message=message) if clarify_turn > int(self.max_clarify_turns): st.pending = {} return { "assistant": _format_shell( "I still don’t have an exact model after two clarification attempts, so I’m stopping clarification loops.", [ "Ambiguous model tokens can map to multiple SKUs and can cause incorrect lifecycle/spec outputs.", "Clarification limit reached (`2` turns).", ], [ "Provide exact make/model/SKU and I will return a deterministic answer immediately.", "If exact model is unavailable, ask for a provisional strategy output instead of a model-specific table.", ], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [ { "id": "MC1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "clarify:model_limit_reached", "location": "", "excerpt": "Clarification loop capped at two turns to avoid repeated ambiguous model prompts.", "score": 1.0, } ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "clarify_model_limit_reached", "clarify_turn": int(clarify_turn), "max_clarify_turns": int(self.max_clarify_turns), }, } low = str(message or "").lower() requested_variant, suggested_variant = self._single_unresolved_lifecycle_variant_candidate(message) if requested_variant and suggested_variant: assistant = _format_shell( "\n".join( [ "I am unable to answer that. Please clarify or ask another question.", "", "I found one unresolved lifecycle model token that needs confirmation before I map replacements.", "", "| Input token | Closest internal match | Action |", "| --- | --- | --- |", f"| {requested_variant} | {suggested_variant} | Confirm the exact model/SKU from the label, or reply with the correct model and I will reuse this same lifecycle request. |", ] ), [ "The request is lifecycle-specific, but the model token does not map cleanly to a known lifecycle row.", "Closest-match guidance is offered only as a clarification aid, not as an automatic remap.", ], [ f"Reply with the exact model if it is `{suggested_variant}`, or provide the correct model/SKU from the device label.", ], ) return { "assistant": assistant, "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [ { "id": "MC1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "clarify:lifecycle_variant_adjacent", "location": "", "excerpt": ( f"Unresolved lifecycle token `{requested_variant}` is adjacent to known internal model `{suggested_variant}` " "and requires confirmation before deterministic mapping." ), "score": 1.0, } ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "clarify_model_lifecycle_variant_adjacent", "clarify_turn": int(clarify_turn), "max_clarify_turns": int(self.max_clarify_turns), }, } requested_variant, suggested_variant = self._single_unresolved_model_variant_candidate(message) if requested_variant and suggested_variant: assistant = _format_shell( "\n".join( [ "I am unable to answer that. Please clarify or ask another question.", "", f"I found one unresolved model token that looks like a typo for `{suggested_variant}` before I answer.", "", "| Input token | Closest internal match | Action |", "| --- | --- | --- |", f"| {requested_variant} | {suggested_variant} | Confirm the exact model/SKU from the label, or reply with the corrected model and I will reuse this same request. |", ] ), [ "The request is model-specific, but the token does not map cleanly to a known workbook row.", "Closest-match guidance is offered only as a clarification aid, not as an automatic remap.", ], [ f"Reply with the exact model if it is `{suggested_variant}`, or provide the correct model/SKU from the device label.", ], ) return { "assistant": assistant, "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [ { "id": "MC1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "clarify:model_variant_adjacent", "location": "", "excerpt": ( f"Unresolved model token `{requested_variant}` is adjacent to known internal model `{suggested_variant}` " "and requires confirmation before deterministic mapping." ), "score": 1.0, } ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "clarify_model_variant_adjacent", "clarify_turn": int(clarify_turn), "max_clarify_turns": int(self.max_clarify_turns), }, } if re.search(r"\bmodel\s*228\b|\b228\b", low): assistant = _format_shell( "\n".join( [ "I need one model clarification before finalizing the 5G replacement.", "", "| Input token | Interpretation | Action |", "| --- | --- | --- |", "| 228 | Ambiguous model label | Confirm exact make/model/SKU from the device label, then I will return a replacement table immediately. |", ] ), [ "Token `228` does not map to a unique canonical router model in internal lifecycle/docs indexes.", ], [ "Reply with exact model + qty (example: `Darden 228 AER2200`).", "If useful, include a photo of the label text and I’ll map it for you.", ], ) return { "assistant": assistant, "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [ { "id": "MC1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "clarify:model_228", "location": "", "excerpt": "Model token `228` requires explicit make/model/SKU clarification before deterministic replacement mapping.", "score": 1.0, } ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "clarify_model_228", "clarify_turn": int(clarify_turn), "max_clarify_turns": int(self.max_clarify_turns), }, } if ("dragon" in low) and ("wb550" in low): wb_row = self._router_fact_rows.get(self._lookup_router_fact_key("WB550") or "", {}) wb_modem = _norm(wb_row.get("modem", "")) or "Not listed in internal CSV" wb_wan_lan = _norm(wb_row.get("wan_lan", "")) or "Not listed in internal CSV" wb_ant = _norm(wb_row.get("antennas_rf", "")) or "Not listed in internal CSV" assistant = _format_shell( "\n".join( [ "Provisional comparison table while model identity is being confirmed:", "", "| Model reference | Internal match status | Notes |", "| --- | --- | --- |", "| Dragon (Verizon) | Ambiguous nickname | Could map to multiple products; needs exact SKU/model label to avoid mismatch. |", f"| WB550 | {'Matched' if wb_row else 'Not found in internal router CSV'} | " f"Modem: {wb_modem}; WAN/LAN: {wb_wan_lan}; RF: {wb_ant}. |", ] ), [ "Nickname-only model references are ambiguous and can produce wrong comparisons.", "Included the known side (WB550 token) and held unknown fields as abstained.", ], [ "Please confirm the exact Dragon make/model/SKU from the label so I can return the final comparison table.", "If helpful, paste a product URL or image label text and I’ll map it immediately.", ], ) return { "assistant": assistant, "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [ { "id": "MC1", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "clarify:dragon_wb550", "location": "", "excerpt": f"WB550 lookup context: modem={wb_modem}; wan_lan={wb_wan_lan}; antennas_rf={wb_ant}.", "score": 1.0, }, { "id": "MC2", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "clarify:dragon_ambiguous", "location": "", "excerpt": "No explicit canonical 'Dragon' model token is present in internal lifecycle CSV rows.", "score": 1.0, }, ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "clarify_model_dragon_wb550", "clarify_turn": int(clarify_turn), "max_clarify_turns": int(self.max_clarify_turns), }, } if ("rx50" in low) and ("ex50" in low): # Weight compares should stay on the documented-evidence path. # Short-circuiting here into alias confirmation made the answer # weaker and speculative, so let the router-docs lane handle it. return None wants_table = _contains_any(low, ("table", "chart", "matrix", "compare", "comparison", "wan/lan", "rf", "connector")) compare_labels = self._router_compare_variant_labels(message) detected_models = [str(x).strip() for x in self._extract_router_models_cached(message) if str(x).strip()] unresolved_model_hint = "" if len(detected_models) == 1: unresolved_model_hint = _compact_model(detected_models[0]) or _norm(detected_models[0]) if wants_table and len(compare_labels) >= 2: opening_line = ( "The exact variant docs are still too thin to compare safely." if "too thin" in low else "The exact variant/package still needs confirmation before I can finalize this compare safely." ) rows = [ "| Requested side | Current safe status | What I need |", "| --- | --- | --- |", ] for label in compare_labels[:4]: rows.append( "| " + " | ".join( [ _md_cell(label), _md_cell("Needs exact variant/package first"), _md_cell("Exact SKU/package or a variant-specific datasheet/manual excerpt"), ] ) + " |" ) assistant = _format_shell( "\n".join( [ opening_line, "", "I’m holding the side-by-side table because the internal docs are still too thin or too family-level to support a quote-safe exact-variant compare.", "", *rows, ] ), [ "This compare request is model-specific, but one or more detected labels still resolve ambiguously or only at the family level.", ], [ "Reply with the exact make + model/SKU for each side and I will reuse this same compare request.", "If you only want the family-level differences, ask for a `family-safe summary only` and I’ll keep the answer conservative.", ], ) elif wants_table: assistant = _format_shell( "\n".join( [ "I am unable to answer that. Please clarify or ask another question.", "", "I need one model clarification before finalizing the full comparison/spec table.", *(["", f"Detected unresolved model token: `{unresolved_model_hint}`."] if unresolved_model_hint else []), "", "| Model | WAN/LAN ports | RF connectors | Status | Next step |", "| --- | --- | --- | --- | --- |", "| | Pending model confirmation | Pending model confirmation | Pending | Confirm exact make/model/SKU and I will populate this table immediately. |", ] ), [ "The request is model-specific, but the detected model text is ambiguous or incomplete.", ], [ "Provide exact make + model/SKU from the device label (example: `XR60`, `AER2200-1200M`, `CR202-Lite`).", "If this is a nickname, share the official product name and I’ll proceed.", ], ) else: assistant = _format_shell( "I am unable to answer that. Please clarify or ask another question.\n\n" + ( f"I detected `{unresolved_model_hint}` and need the exact model/SKU before I can answer accurately." if unresolved_model_hint else "I need one model clarification before I can answer accurately." ), [ "The request appears model-specific, but the detected model text is ambiguous or incomplete.", ], [ "Provide exact make + model/SKU from the device label (example: `XR60`, `AER2200-1200M`, `CR202-Lite`).", "If this is a nickname, share the official product name and I’ll proceed.", ], ) return { "assistant": assistant, "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [ { "id": "MC1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "clarify:model_token", "location": "", "excerpt": "Model clarification is required when the token does not map cleanly to a known lifecycle key.", "score": 1.0, } ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "clarify_model", "clarify_turn": int(clarify_turn), "max_clarify_turns": int(self.max_clarify_turns), }, } def _timeout_clarify_response(self, message: str, st: UnifiedKnowledgebaseState, domain: str, elapsed_s: float) -> Dict[str, Any]: clarify_turn = self._set_clarify_pending(st, "clarify_speed", domain, message=message) if clarify_turn > int(self.max_clarify_turns): st.pending = {} return { "assistant": _format_shell( "I’m stopping timeout clarification loops after two attempts.", [ f"The request exceeded the `{int(self.hard_timeout_s)}s` budget multiple times.", "Clarification limit reached (`2` turns).", ], [ "Ask one narrower question (single provider/model + one output type).", "Or ask for `best-effort summary` to get a concise answer without deep retrieval.", ], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [], "files": [], "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "timeout_clarify_limit_reached", "elapsed_s": round(float(elapsed_s), 2), "clarify_turn": int(clarify_turn), "max_clarify_turns": int(self.max_clarify_turns), }, } sources: List[Dict[str, Any]] = [] files: List[str] = [] if domain == "pots": provider_rows = sorted( self._pots_provider_cards.values(), key=lambda x: (-int(x.get("count", 0)), str(x.get("provider", "")), ))[:4] lines = [ "Fast best-effort while full retrieval timed out:", "", "| Provider | Internal doc count | Evidence depth |", "| --- | ---: | --- |", ] for idx, row in enumerate(provider_rows, start=1): provider = str(row.get("provider") or "") count = int(row.get("count", 0) or 0) depth = "High" if count >= 8 else ("Medium" if count >= 3 else "Low") lines.append(f"| {provider} | {count} | {depth} |") rel_docs = [str(x) for x in (row.get("docs") or []) if str(x)] if rel_docs: href = _mounted_file_href("/pots_files", rel_docs[0]) files.append(href) sources.append( { "id": f"TPO{idx}", "domain": "pots", "doc": Path(rel_docs[0]).name, "relative_path": href, "chunk_id": f"timeout_pots_provider:{provider}:{idx}", "location": "", "excerpt": f"{provider}: indexed_doc_count={count}; evidence_depth={depth}.", "score": 0.92, } ) assistant = _format_shell( "\n".join(lines), [ f"Request exceeded the fast-answer budget for `{domain}` (~{elapsed_s:.1f}s).", "Returned immediate internal-evidence summary instead of a blank/paused response.", ], [ "For highest accuracy, narrow to one output type: `weighted table`, `top differences`, or `provider shortlist`.", "If needed, add endpoint scope (fire/elevator/fax/alarm) and I’ll return a focused matrix.", ], ) else: assistant = _format_shell( "Fast best-effort while full retrieval timed out.", [ f"This request exceeded the quick-answer budget for `{domain}` (~{elapsed_s:.1f}s).", "A narrower request will return faster with stronger grounding.", ], [ "Provide exact model(s) + one target output (example: `compare XR60 vs RV50X in a 6-row table`).", "Or ask for `best-effort summary` if you want a quick high-level answer now.", ], ) return { "assistant": assistant, "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": sources, "files": files, "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "timeout_clarify", "elapsed_s": round(float(elapsed_s), 2), "clarify_turn": int(clarify_turn), "max_clarify_turns": int(self.max_clarify_turns), }, } def _is_low_time_template_candidate(self, message: str, domain: str) -> bool: if domain not in {"pots", "masters"}: return False low = _normalize_router_query_text(message) return _contains_any(low, _LOW_TIME_TEMPLATE_HINTS) def _low_time_template_response( self, message: str, st: UnifiedKnowledgebaseState, domain: str, *, remaining_s: float, ) -> Optional[Dict[str, Any]]: if not self.low_time_fallback_template_enabled: return None if not self._is_low_time_template_candidate(message, domain): return None if float(remaining_s) > float(self.pots_core_expand_min_remaining_s): return None st.pending = {} if domain == "pots": result_lines = [ "Budget-safe structured draft (deterministic, source-bounded):", "", "| Section | Fill now |", "| --- | --- |", "| Objective | State provider comparison or objection/rewrite target in one sentence. |", "| Required evidence | Cite only internal provider excerpts with explicit `Not documented` abstentions. |", "| Output structure | Use one short table + top risks + next-action checklist. |", ] sources: List[Dict[str, Any]] = [] files: List[str] = [] for idx, row in enumerate( sorted(self._pots_provider_cards.values(), key=lambda x: -int(x.get("count", 0)))[:2], start=1, ): rel_docs = [str(x) for x in (row.get("docs") or []) if str(x)] if not rel_docs: continue href = _mounted_file_href("/pots_files", rel_docs[0]) files.append(href) sources.append( { "id": f"LTP{idx}", "domain": "pots", "doc": Path(rel_docs[0]).name, "relative_path": href, "chunk_id": f"low_time_template:{domain}:{idx}", "location": "", "excerpt": "Low-time fallback template seeded from currently indexed provider docs.", "score": 0.88, } ) return { "assistant": _format_shell( "\n".join(result_lines), [ f"Remaining request budget dropped below {float(self.pots_core_expand_min_remaining_s):.1f}s; returned deterministic template instead of timing out.", "This keeps guardrails intact while preserving usable structure for immediate follow-up.", ], [ "Ask one focused follow-up (`expand section + exact provider/model`) to complete this in the next turn.", ], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": sources[:4], "files": files[:4], "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "low_time_template_fast", "remaining_s": round(float(remaining_s), 2), "web_assisted": False, }, } result_lines = [ "Budget-safe draft shell (deterministic, internal-doc-first):", "", "| Section | Fill now |", "| --- | --- |", "| Goal | One sentence on the sales output needed (battle card, script, summary, quote prep). |", "| Source boundary | Internal docs only; mark unknowns as `Assumption required`. |", "| Delivery | 5 bullet max + explicit next action and owner. |", ] return { "assistant": _format_shell( "\n".join(result_lines), [ f"Remaining request budget dropped below {float(self.pots_core_expand_min_remaining_s):.1f}s; returned deterministic shell to avoid timeout degradation.", ], [ "Ask one focused follow-up with output type + audience to complete the final draft.", ], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [], "files": [], "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "low_time_template_fast", "remaining_s": round(float(remaining_s), 2), "web_assisted": False, }, } def _effective_budget_s(self, message: str, domain: str) -> float: base = float(self.fast_timeout_s_by_domain.get(domain, self.target_max_s)) low = str(message or "").lower() if not self.query_complexity_budgeting_enabled: if domain in {"pots", "masters"} and any(h in low for h in _COMPLEX_20S_HINTS): return min(self.hard_timeout_s, max(base, 20.0)) if domain == "router_docs" and any(h in low for h in ("from docs only", "compare", "table", "matrix", "weighted")): return min(self.hard_timeout_s, max(base, 12.0)) return min(self.hard_timeout_s, base) bucket = self._query_complexity_bucket(message, domain=domain) adjusted = base if bucket == "heavy": adjusted = max(self.query_complexity_budget_floor_s, base * float(self.query_complexity_heavy_factor)) elif bucket == "medium": adjusted = max(self.query_complexity_budget_floor_s, base * float(self.query_complexity_medium_factor)) if domain == "router_docs" and any(h in low for h in ("from docs only", "compare", "table", "matrix", "weighted")): adjusted = min(adjusted, max(self.query_complexity_budget_floor_s, 12.0)) return min(self.hard_timeout_s, adjusted) def _effective_stage_budget_s(self, message: str, domain: str, base_s: float) -> float: base = max(0.15, float(base_s or 0.0)) if not self.query_complexity_budgeting_enabled: return base bucket = self._query_complexity_bucket(message, domain=domain) adjusted = base if bucket == "heavy": adjusted = max(0.12, base * float(self.query_complexity_heavy_factor)) elif bucket == "medium": adjusted = max(0.12, base * float(self.query_complexity_medium_factor)) return min(base, adjusted) def _query_complexity_score(self, message: str, *, domain: str = "auto") -> int: low = str(message or "").lower() words = re.findall(r"[a-z0-9]+", low) score = 0 if len(words) >= 36: score += 2 elif len(words) >= 24: score += 1 if any(h in low for h in _COMPLEX_20S_HINTS): score += 2 if any(h in low for h in ("top 10", "top ten", "rewrite", "natural language", "playbook", "weighted table", "matrix")): score += 2 if any(h in low for h in ("fire", "elevator", "alarm", "fax", "compliance", "objection")): score += 1 if any(h in low for h in ("source anchors", "source citations", "source-backed", "source backed")): score += 1 if domain in {"pots", "masters"}: score += 1 return max(0, score) def _query_complexity_bucket(self, message: str, *, domain: str = "auto") -> str: score = self._query_complexity_score(message, domain=domain) if score >= 5: return "heavy" if score >= 3: return "medium" return "light" def _truncate_markdown_tables(self, text: str, max_rows: int) -> str: if max_rows < 1: return text lines = str(text or "").splitlines() out: List[str] = [] i = 0 n = len(lines) while i < n: line = lines[i] is_table_line = line.strip().startswith("|") and line.count("|") >= 2 if not is_table_line: out.append(line) i += 1 continue start = i while i < n and lines[i].strip().startswith("|") and lines[i].count("|") >= 2: i += 1 block = lines[start:i] if len(block) <= 2 + max_rows: out.extend(block) continue head = block[:2] rows = block[2 : 2 + max_rows] out.extend(head + rows) out.append(f"| ... | {len(block) - 2 - max_rows} additional row(s) omitted for brevity. |") return "\n".join(out).strip() def _compact_shell_response(self, message: str, assistant: str, *, force: bool = False) -> str: text = str(assistant or "").strip() if not text: return text if ("**Result**" not in text) or ("**Why**" not in text) or ("**Next action**" not in text): return text lines = text.splitlines() section = "" sections: Dict[str, List[str]] = {"result": [], "why": [], "next": []} for raw in lines: low = raw.strip().lower() if low == "**result**": section = "result" continue if low == "**why**": section = "why" continue if low == "**next action**": section = "next" continue if section: sections[section].append(raw) limit_items = self.section_item_limit if not force else max(2, self.section_item_limit - 1) result_block = "\n".join(sections.get("result") or []).strip() wants_table = _contains_any(message, ("table", "matrix", "chart", "compare", "comparison", "vs", "versus")) result_has_table = any( ln.strip().startswith("|") and ln.count("|") >= 2 for ln in result_block.splitlines() ) preserve_full_table = _contains_any( message, ( "all providers", "providers we have", "provider coverage", "coverage gap", "evidence is thin", "thin evidence", "top 10", "top-10", "objection map", "objection-handling map", ), ) if result_has_table: max_rows = 40 if preserve_full_table else self.max_table_rows result_block = self._truncate_markdown_tables(result_block, max_rows) else: result_lines = [x.strip() for x in (sections.get("result") or []) if x.strip()] bullet_lines = [x for x in result_lines if re.match(r"^[-*]\s+", x)] wants_richer_result = _contains_any( message, ( "which", "list", "show", "support", "supports", "devices", "device", "routers", "router", "gateways", "gateway", "models", "model", "summarize", "summarise", "summary", "case study", "case studies", "examples", "lights", "led", "indicate", "indicates", "mean", "meanings", ), ) if bullet_lines: intro_lines: List[str] = [] kept_bullets: List[str] = [] trailing_lines: List[str] = [] seen_bullet = False for line in result_lines: if re.match(r"^[-*]\s+", line): seen_bullet = True kept_bullets.append(line) elif not seen_bullet: intro_lines.append(line) else: trailing_lines.append(line) bullet_limit = max(4, self.section_item_limit + (3 if wants_richer_result else 1)) result_block = "\n".join(intro_lines[:2] + kept_bullets[:bullet_limit] + trailing_lines[:1]).strip() else: line_limit = 5 if wants_richer_result else 3 result_block = "\n".join(result_lines[:line_limit]).strip() why_items: List[str] = [] for line in sections.get("why") or []: stripped = line.strip() if not stripped: continue stripped = re.sub(r"^\s*[-*]\s*", "", stripped) why_items.append(_clip_text(stripped, 200)) if len(why_items) >= limit_items: break if not why_items: why_items = ["Routed by best-available source logic."] next_items: List[str] = [] for line in sections.get("next") or []: stripped = line.strip() if not stripped: continue stripped = re.sub(r"^\s*[-*]\s*", "", stripped) next_items.append(_clip_text(stripped, 200)) if len(next_items) >= limit_items: break if not next_items: next_items = ["Ask one focused follow-up with exact model + output format."] result_max = int(self.concise_answer_char_limit * 0.6) if force else int(self.normal_answer_char_limit * 0.65) result_payload = result_block if (result_has_table or wants_table) else _clip_text(result_block, max(380, result_max)) compact = _format_shell( result_payload, why_items, next_items, ) return compact.strip() def _apply_response_compaction(self, message: str, assistant: str, elapsed_s: float) -> str: text = _norm_preserve(assistant) if not text: return text had_shell = ("**Result**" in text) and ("**Why**" in text) and ("**Next action**" in text) if "Matched FAQ responses for your multi-part request:" in text: limit = self.concise_answer_char_limit if elapsed_s >= self.soft_concise_s else self.normal_answer_char_limit if len(text) > limit: return _clip_text(text, limit).strip() return text.strip() force = elapsed_s >= self.soft_concise_s limit = self.concise_answer_char_limit if force else self.normal_answer_char_limit compact = self._compact_shell_response(message, text, force=force) if compact: text = compact if "|" in text: preserve_full_table = _contains_any( message, ( "all providers", "providers we have", "provider coverage", "coverage gap", "evidence is thin", "thin evidence", "top 10", "top-10", "objection map", "objection-handling map", ), ) text = self._truncate_markdown_tables(text, 40 if preserve_full_table else self.max_table_rows) if len(text) > limit: text = _clip_text(text, limit) if had_shell and not (("**Result**" in text) and ("**Why**" in text) and ("**Next action**" in text)): compact_shell = self._compact_shell_response(message, assistant, force=True) if compact_shell and ("**Result**" in compact_shell) and ("**Why**" in compact_shell) and ("**Next action**" in compact_shell): text = compact_shell return text.strip() def _ensure_shell_format(self, message: str, assistant: str, domain: str) -> str: text = _norm_preserve(assistant) if not text: return text low = text.lower() has_shell = ("**result**" in low) and ("**why**" in low) and ("**next action**" in low) if has_shell: return text cleaned = re.sub(r"^\s*#{1,6}\s*answer\s*:?\s*", "", text, flags=re.IGNORECASE).strip() if not cleaned: cleaned = text why = [f"Routed to `{_MODE_LABELS.get(domain, domain)}` based on your request."] next_action = [ "Ask a focused follow-up (model/provider + exact output type) for a tighter answer.", ] if domain == "router_docs": next_action = ["Ask `from docs only` or `table format` if you want stricter output control."] elif domain == "router_lifecycle": next_action = ["Provide exact model + qty (example: `Darden 228 AER2200`) for lifecycle output."] elif domain == "pots": next_action = ["Ask `compare providers in table format` to get a concise side-by-side view."] elif domain == "masters": next_action = ["Ask `which source files support this` to include internal document anchors."] return _format_shell(_clip_text(cleaned, 1200), why, next_action) def _is_in_scope_query(self, message: str) -> bool: low = _normalize_router_query_text(message) if not low: return True if self._is_low_context_followup(message): return True if self._extract_router_models_cached(message): return True scope_terms = ( _ROUTER_DOC_HINTS + _ROUTER_LIFECYCLE_HINTS + _ROUTER_PLATFORM_HINTS + _POTS_HINTS + _POTS_CONTEXT_HINTS + _MASTERS_HINTS + _TELECOM_SCOPE_HINTS ) return any(_contains_term(low, term) for term in scope_terms) def _is_clearly_out_of_scope(self, message: str) -> bool: low = _normalize_router_query_text(message) if not low: return False if self._is_in_scope_query(message): return False if any(_contains_term(low, term) for term in _CLEARLY_OUT_OF_SCOPE_HINTS): return True return bool( re.search(r"\bwhat\s+is\s+the\s+capital\s+of\b", low) or re.search(r"\bwho\s+wrote\b", low) or re.search(r"\bwho\s+authored\b", low) or re.search(r"\bwho\s+is\s+the\s+author\s+of\b", low) ) def _out_of_scope_response(self, st: UnifiedKnowledgebaseState) -> Dict[str, Any]: st.pending = {} return { "assistant": _format_shell( "Sorry, my focus is on Master's Telecom Solutions, please ask a relevant question. I cannot answer that.", [ "This assistant is scoped to Masters Telecom, Verizon-related telecom workflows, routers, cellular connectivity, and POTS replacement.", ], [ "Ask about router specs, lifecycle, cellular connectivity, POTS replacement, or Masters Telecom services.", ], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [ { "id": "OS1", "domain": "knowledgebase", "doc": "app_scope_guardrails", "relative_path": "", "chunk_id": "scope:out_of_scope", "location": "", "excerpt": "Knowledgebase scope is limited to Masters Telecom, Verizon-related telecom workflows, routers, cellular connectivity, and POTS replacement.", "score": 1.0, } ], "files": [], "effective_audience": "external", "meta": {"domain": "knowledgebase", "retrieval_mode": "out_of_scope"}, } def _confirm_web_lookup_response( self, message: str, st: UnifiedKnowledgebaseState, domain: str, *, reason: str = "", ) -> Dict[str, Any]: st.pending = { "type": "confirm_web_lookup", "domain": str(domain or "").strip().lower() or "router_docs", "original_message": _norm(message), } why = ["Internal retrieval was not strong enough to answer this safely from current sources alone."] if reason: why.append(reason) return { "assistant": _format_shell( "Would you like me to consult the web for more details?", why, [ "Reply `Yes` and I’ll return a clearly labeled web-sourced answer.", "Reply `No` to keep this internal-only and ask a narrower or clarified question instead.", ], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [], "files": [], "effective_audience": "external", "meta": { "domain": str(domain or "").strip().lower() or "router_docs", "retrieval_mode": "confirm_web_lookup", "web_assisted": False, }, } def _web_lookup_declined_response(self, st: UnifiedKnowledgebaseState, domain: str) -> Dict[str, Any]: st.pending = {} return { "assistant": _format_shell( "I am unable to answer that. Please clarify or ask another question.", [ "Web lookup was declined and the current internal evidence is still too weak for a safe answer.", ], [ "Ask a narrower question with the exact model, product, or document name.", "Or reply `Yes` next time if you want a clearly labeled web-sourced best-effort answer.", ], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [], "files": [], "effective_audience": "external", "meta": { "domain": str(domain or "").strip().lower() or "router_docs", "retrieval_mode": "web_lookup_declined", "web_assisted": False, }, } def _web_lookup_unavailable_response(self, st: UnifiedKnowledgebaseState, domain: str) -> Dict[str, Any]: st.pending = {} return { "assistant": _format_shell( "I was unable to retrieve additional web details right now.", [ "A web-assisted lookup was requested, but no web result was returned in time.", ], [ "Retry the same request, or ask a narrower question with the exact model or product name.", ], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [], "files": [], "effective_audience": "external", "meta": { "domain": str(domain or "").strip().lower() or "router_docs", "retrieval_mode": "web_lookup_unavailable", "web_assisted": False, }, } def _policy_block_response(self, reason: str, st: UnifiedKnowledgebaseState) -> Dict[str, Any]: if reason == "verizon_pricing": result = "I can’t answer Verizon plan/pricing questions in this tool." why = [ "Verizon pricing and policy guidance must come from approved Verizon channels.", ] next_action = [ "Use your Verizon pricing channel for plan rates and policy details.", "I can still build a source-backed non-priced BOM and technical recommendation.", ] excerpt = "Guardrail: no Verizon plan/pricing answers from this assistant." elif reason == "verizon_policy": result = "I can’t answer Verizon-specific policy questions in this tool." why = [ "Verizon policy exceptions and employee discount policy must come from approved Verizon channels.", ] next_action = [ "Use your Verizon policy channel for official policy guidance.", "I can still provide source-backed technical design guidance from approved docs.", ] excerpt = "Guardrail: no Verizon-specific policy determinations in this assistant." elif reason == "carrier_policy": result = "Carrier plan/performance/policy comparisons are out of scope for this assistant." why = [ "This tool is restricted to source-backed technical guidance and approved internal documentation.", ] next_action = [ "Ask a technical comparison (hardware/specs/lifecycle) instead.", "Use official carrier channels for plan/policy/performance claims.", ] excerpt = "Guardrail: no external carrier plan/performance/policy claims." elif reason == "pii": result = "I can’t help with personal or employee-sensitive data requests." why = ["Security policy blocks personal/employee data handling in this chat." ] next_action = ["Ask a product, architecture, or documentation question instead."] excerpt = "Guardrail: no personal/employee-sensitive data handling." elif reason == "exact_lead_time": result = "I can’t provide exact current lead-time claims in this tool." why = [ "Lead times change and must come from approved internal quoting or supply-chain sources.", ] next_action = [ "Use the approved internal source for exact lead times.", "I can still help with a non-priced technical recommendation or BOM structure.", ] excerpt = "Guardrail: no exact current lead-time claims." elif reason == "exact_availability": result = "I can’t provide exact current availability claims in this tool." why = [ "Availability changes quickly and must come from approved internal inventory or sourcing channels.", ] next_action = [ "Use the approved internal source for exact availability.", "I can still summarize documented product fit or replacement options.", ] excerpt = "Guardrail: no exact current availability claims." elif reason == "exact_band_support": result = "I can’t provide exact current band-support claims in this tool without approved source verification." why = [ "Exact band-support statements must stay tied to approved source-backed documentation.", ] next_action = [ "Use the approved internal or manufacturer source for exact band support.", "I can still explain the concept at a high level without making exact support claims.", ] excerpt = "Guardrail: no exact current band-support claims without source verification." elif reason == "exact_certification": result = "I can’t provide exact current certification-status claims in this tool." why = [ "Certification status must come from approved source-backed documentation or authoritative filings.", ] next_action = [ "Use the approved source for exact certification status.", "I can still explain the product concept or certification process at a high level.", ] excerpt = "Guardrail: no exact current certification-status claims." elif reason == "exact_lifecycle": result = "I can’t provide an exact current lifecycle date in this tool without approved source verification." why = [ "Lifecycle dates must be tied to an approved internal lifecycle source before they are treated as definitive.", ] next_action = [ "Use the approved lifecycle source for exact dates.", "I can still help with a non-definitive replacement planning discussion if you want the next-step options.", ] excerpt = "Guardrail: no exact current lifecycle-date claims without approved source verification." elif reason == "code_adjudication": result = "I can’t determine whether a design will meet code everywhere without further review." why = [ "Code and inspection outcomes depend on documented requirements, local interpretation, and responsible-party review.", ] next_action = [ "Use approved source-backed documentation and project review for code or inspection determinations.", "I can still help frame the technical considerations and open items for review.", ] excerpt = "Guardrail: no universal code/inspection adjudication in this assistant." else: result = "I can’t provide guarantees in this tool." why = ["Guarantee claims require formal approval and are outside this assistant’s authority."] next_action = ["Ask for documented capabilities/limitations and I’ll provide source-backed details."] excerpt = "Guardrail: no guarantee language." assistant = _format_shell(result, why, next_action) st.pending = {} return { "assistant": assistant, "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [ { "id": "PG1", "domain": "policy", "doc": "app_policy_guardrails", "relative_path": "", "chunk_id": f"policy:{reason}", "location": "", "excerpt": excerpt, "score": 1.0, } ], "files": [], "effective_audience": "external", "meta": {"domain": "policy", "reason": reason}, } def _explicit_mode_from_message(self, message: str) -> str: low = str(message or "").lower() low_norm = _normalize_router_query_text(low) workbook_router_query = parse_router_intelligence_query(message) asks_securefax = any(t in low_norm for t in ("securefax", "secure fax", "ifax", "i fax")) asks_router_price = any( t in low_norm for t in ("how much", "price", "pricing", "cost", "msrp", "list price", "quote", "quoted", "unit price") ) extracted_models = _extract_router_models(message) has_router_lifecycle_intent = _contains_any(low, _ROUTER_LIFECYCLE_HINTS) or _contains_any( low, (_ROUTER_STATUS_HINTS + _ROUTER_REPLACEMENT_HINTS) ) docs_only_compare_with_lifecycle_posture = bool( extracted_models and (_contains_any(low_norm, _ROUTER_FAST_COMPARE_HINTS) or ("compare" in low_norm)) and any( token in low_norm for token in ( "docs only", "docs-only", "documented specs only", "from docs only", "internal docs only", "internal sources only", ) ) and ("lifecycle posture" in low_norm) ) compare_with_deployment_context = bool( extracted_models and ( _contains_any(low_norm, _ROUTER_FAST_COMPARE_HINTS) or ("what is different between" in low_norm) or ("different between" in low_norm) ) and any( token in low_norm for token in ( "deployment", "branch office", "branch", "vehicle use", "vehicle", "install implication", "install implications", "install note", "install notes", "placement", "use case", ) ) ) if self._single_lifecycle_only_model_token(message): return "router_lifecycle" if ( asks_securefax and any( t in low_norm for t in ("how much", "price", "pricing", "cost", "msrp", "monthly", "mrc", "nrc", "setup", "one-time", "one time") ) ): return "masters" if _looks_like_masters_doc_lookup(low): return "masters" if _looks_like_pots(low): return "pots" if docs_only_compare_with_lifecycle_posture: return "router_docs" if compare_with_deployment_context: return "router_docs" if workbook_router_query and workbook_router_query.intent in {"lifecycle", "fleet_lifecycle", "replacements", "survey", "guided_advisor"}: return "router_lifecycle" if workbook_router_query and workbook_router_query.intent in {"compare", "details", "search", "antenna"}: if has_router_lifecycle_intent: return "router_lifecycle" return "router_docs" if extracted_models and asks_router_price and (not asks_securefax): return "router_docs" if (not extracted_models) and asks_router_price and any(t in low_norm for t in ("router", "gateway", "model", "models")) and (not asks_securefax): return "router_docs" if _contains_any(low, _ROUTER_PLATFORM_HINTS): if has_router_lifecycle_intent: return "router_lifecycle" return "router_docs" if _looks_like_router_lifecycle(low): return "router_lifecycle" if _looks_like_masters(low): return "masters" if _looks_like_router_docs(low): return "router_docs" return "" def _single_lifecycle_only_model_token(self, message: str) -> str: text = str(message or "").strip() if not text: return "" token = text.strip("`'\"").rstrip("?.!,;:").strip() if (not token) or bool(re.search(r"\s", token)): return "" if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9\-]{2,40}", token): return "" compact = _compact_model(token) if (not compact) or (not any(ch.isalpha() for ch in compact)) or (not any(ch.isdigit() for ch in compact)): return "" canonical = self._normalize_router_model(token) or compact lifecycle_key = self._lookup_router_lifecycle_key_relaxed(canonical) or self._lookup_router_lifecycle_key_relaxed(compact) if not lifecycle_key: return "" return lifecycle_key def _router_workbook_fast_answer( self, message: str, st: UnifiedKnowledgebaseState, requested_domain: str, *, raw_message: str = "", ) -> Optional[Dict[str, Any]]: status = self._rapid_router_intelligence_status() normalized = _normalize_router_query_text(message) low_message = normalized.lower() generic_survey_guidance_request = ( requested_domain == "router_docs" and (not self._extract_router_models_cached(message)) and any(token in low_message for token in ("closet", "inside closet", "network closet")) and any( token in low_message for token in ( "better indoor point", "best indoor point", "near closet", "outside the closet", "outside closet", ) ) and any(token in low_message for token in ("risk", "compare", "compared", "better", "improvement")) ) if generic_survey_guidance_request: workbook_sources = self._router_workbook_sources("router_docs", "survey") return { "assistant": _format_shell( "\n".join( [ "Workbook-backed indoor placement guidance:", "", "- Keeping the router inside the closet is still the riskier indoor position.", "- The workbook prefers moving toward the better indoor point before jumping to an outdoor path.", "- Exact gain depends on the active survey scores, landlord constraints, cable-run limits, and the selected hardware.", "", "| Indoor path | Workbook posture |", "| --- | --- |", "| Inside closet | Higher risk / weaker indoor score lane |", "| Better indoor point | Preferred first improvement before outdoor escalation |", ] ), [ "This answer uses the workbook survey outcome rules for indoor-versus-better-indoor placement, not generic public RF advice.", "Without a named survey/site, I can only give the workbook's general posture instead of a numeric delta.", ], [ "Send the survey key or site name if you want the exact closet score, better-indoor score, and final indoor/outdoor recommendation.", ], ), "sources": workbook_sources, "files": [str(status.get("filename") or "router_workbook.xlsx")], "meta": { "domain": "router_docs", "retrieval_mode": "deterministic_router_workbook_survey_generic_guidance", "router_intelligence_intent": "survey", "router_intelligence_source": "workbook", "review_required": False, "citation_quorum_not_required": True, "legacy_csv_replaced": True, }, } core = self._rapid_router_intelligence_core() if core is None: return None workbook_missing_fields_request = requested_domain == "router_docs" and bool(self._extract_router_models_cached(message)) and any( term in normalized.lower() for term in ( "missing fields", "missing field", "fill missing", "fill in missing", "missing data", "feature coverage", ) ) if workbook_missing_fields_request: return self._router_missing_fields_audit_fast_impl(message) if requested_domain == "router_docs" and self._router_catalog_question_needs_documentation(message): return None execution_plan = self._router_workbook_build_execution_plan(message, requested_domain) if execution_plan is None: return None query = execution_plan.query fast_domain = execution_plan.fast_domain orchestration_meta = execution_plan.response_meta() shared_router_meta = orchestration_meta plan_intent = str(_as_dict(execution_plan.router_query_plan).get("intent") or query.intent or "").strip() if not plan_intent: plan_intent = str(query.intent or "").strip() workbook_file = str(status.get("filename") or "router_workbook.xlsx") workbook_sources = self._router_workbook_sources(fast_domain, plan_intent or query.intent) if plan_intent == "lifecycle" and not query.device_texts and ( ("unknown lifecycle" in low_message) or ("clarification prompt" in low_message) or ("provisional alternative" in low_message) or ("provisional alternatives" in low_message) ): lines = [ "I need exact device models/SKUs first.", "", "Unknown-lifecycle workflow (table-driven):", "", "| Step | Clarification prompt | Provisional alternatives rule |", "| --- | --- | --- |", "| 1. Confirm model identity | `Please share exact make/model/SKU from device label.` | Do not force a replacement until the exact model/SKU is confirmed in the workbook. |", "| 2. Confirm deployment profile | `Vehicle, fixed indoor, fixed outdoor, or industrial?` | Use deployment profile to rank alternatives from internal catalog. |", "| 3. Confirm target generation | `Do you want 4G fallback, 5G target, or both?` | Return both 4G and 5G options when target is mixed/unclear. |", "| 4. Provisional output | `I can provide a provisional table now.` | Mark status as `Unknown lifecycle` and label options as provisional pending model confirmation. |", "", "Example provisional row:", "", "| Device | Status | 4G alternative | 5G replacement |", "| --- | --- | --- | --- |", "| Unknown model token | Unknown lifecycle | Provisional (internal catalog) | Provisional (internal catalog) |", "", "Source anchors:", "- `routers_eos_eol_by_sku.csv` / `unknown_lifecycle_workflow`", "- `feb2026routers.csv` / `unknown_lifecycle_catalog_fallback`", ] return { "assistant": _format_shell( "\n".join(lines), [ "Unknown-lifecycle guidance depends on exact model matching against internal lifecycle and catalog sources.", "This table gives a repeatable clarification flow before committing migration recommendations from `routers_eos_eol_by_sku.csv` and `feb2026routers.csv`.", ], [ "Paste devices like: `Customer 12 RV50X, 22 AER2200`.", "If labels are unclear, send make + model + SKU (or a photo of the label text).", "Once you share devices, I’ll provide clarification prompts and provisional alternatives per model.", ], ), "sources": [ { "id": "LUC1", "domain": "router_lifecycle", "doc": "routers_eos_eol_by_sku.csv", "relative_path": "routers_eos_eol_by_sku.csv", "chunk_id": "unknown_lifecycle_workflow", "location": "", "excerpt": "Lifecycle map used after model confirmation for EOS/EOL and replacement fields.", "score": 1.0, }, { "id": "LUC2", "domain": "router_lifecycle", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "unknown_lifecycle_catalog_fallback", "location": "", "excerpt": "Catalog map used for provisional alternatives when lifecycle row is unknown.", "score": 0.98, }, ], "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "unknown_lifecycle_clarification_workflow_fast", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } if not bool(status.get("loaded")): return { "assistant": _format_shell( "The workbook-backed router catalog is not loaded yet, so I cannot answer this safely.", [ "Router intelligence is now workbook-first for Unified Knowledgebase and Router Lifecycle.", "I am intentionally not falling back to the old CSV/router-store paths for this router question.", ], [ "Load the current workbook pack in Rapid Router admin, then retry this question.", "If you only need datasheet/manual content, ask explicitly for documented specs or install guidance.", ], ), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": f"deterministic_router_workbook_{plan_intent}_catalog_not_loaded", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **shared_router_meta, }, } def _yes_no(value: Any) -> str: if isinstance(value, bool): return "Yes" if value else "No" text = _norm(value) if not text: return "No" low = text.lower() if low in {"yes", "true", "1", "y"}: return "Yes" if low in {"no", "false", "0", "n"}: return "No" return text def _money(value: Any) -> str: try: numeric = float(value) except Exception: return "Not listed" return f"${numeric:,.2f}" def _display_name(item: Dict[str, Any]) -> str: if not isinstance(item, dict): return "" # Prefer human-readable labels before raw workbook IDs so shortlist # rows do not surface internal numbers as if they were device names. for key in ( "subject_display_name", "_requested_label", "matched_alias_text", "display_name", "router_display_name", "family_group", "model", "sku", "replacement_display", "product_id", "product_key", ): value = _norm(item.get(key, "")) if value: return value return "" def _feature_enabled(features: Dict[str, Any], key: str) -> bool: return str(features.get(key) or "").strip().lower() in {"yes", "true", "1", "y"} def _status_label(match: Dict[str, Any], lifecycle: Dict[str, Any]) -> str: status = _norm(lifecycle.get("status") or match.get("status_bucket") or "Unknown") if status.upper() == "EOS": status = "End of Sale" elif status.upper() == "EOL": status = "End of Life" if status and (not bool(lifecycle.get("has_authoritative_lifecycle"))) and any( token in status.lower() for token in ("legacy", "retired", "discontinued", "eos", "eol", "end of sale", "end of life") ): return f"{status} (dates not listed)" return status def _router_subject_label(match: Dict[str, Any], product: Dict[str, Any] | None = None) -> str: def _maybe_humanize_subject(value: Any) -> str: text = _norm(value) if not text: return "" if re.search(r"[\s,;/:_-]", text): return text if text.upper() != text and text.lower() != text: return text humanized = _humanize_model_token(text) if humanized and _compact_model(humanized) == _compact_model(text): return humanized return text if isinstance(product, dict): for key in ("subject_display_name", "_requested_label", "matched_alias_text", "product_id", "family_group", "display_name"): value = _norm(product.get(key, "")) if value: return _maybe_humanize_subject(value) for key in ("subject_display_name", "_requested_label", "matched_alias_text", "product_id", "family_group", "display_name"): value = _norm(match.get(key, "")) if value: return _maybe_humanize_subject(value) return _display_name(match) def _details_port_values(features: Dict[str, Any]) -> Tuple[str, str, str]: def _int_or_none(value: Any) -> Optional[int]: try: return int(str(value).strip()) except Exception: return None def _looks_truncated(value: Any) -> bool: text = _norm(value) if not text: return False low = text.lower() return bool("..." in text or "…" in text or "truncat" in low) def _format_port_fragment(value: Any, *, total: bool = False) -> str: text = _norm(value) if not text: return "" if _looks_truncated(text): return "Needs exact SKU/package" if re.fullmatch(r"\d+(?:\.\d+)?", text): if total: return f"{text} total Ethernet ports" return f"{text} {'port' if float(text) == 1.0 else 'ports'}" return text wan_ports = features.get("wan_ports_norm") lan_ports = features.get("lan_ports_norm") total_ports = features.get("total_ethernet_ports") wan_value = _format_port_fragment(wan_ports) lan_value = _format_port_fragment(lan_ports) total_value = _format_port_fragment(total_ports, total=True) wan_int = _int_or_none(wan_ports) lan_int = _int_or_none(lan_ports) total_int = _int_or_none(total_ports) suspicious_split = ( wan_int is not None and lan_int is not None and total_int is not None and wan_int >= 4 and lan_int >= 4 and wan_int == lan_int ) suspicious_outlier = any(value is not None and value > 12 for value in (wan_int, lan_int, total_int)) if suspicious_split or suspicious_outlier: return ("Needs exact SKU/package", "Needs exact SKU/package", "Needs exact SKU/package") return (wan_value or "Not listed", lan_value or "Not listed", total_value or "0") def _details_feature_value( features: Dict[str, Any], *, key: str, label_key: str = "", empty: str = "Not listed", ) -> str: label = _norm(features.get(label_key)) if label_key else "" if label: return label value = features.get(key) if isinstance(value, bool): return _yes_no(value) text = _norm(value) return text or empty def _compare_ethernet_summary(features: Dict[str, Any]) -> str: def _looks_truncated(value: Any) -> bool: text = _norm(value) if not text: return False low = text.lower() return bool("..." in text or "…" in text or "truncat" in low) def _format_port_fragment(value: Any, *, label: str) -> str: text = _norm(value) if not text: return "" if _looks_truncated(text): return f"{label}: Needs exact SKU/package" if re.fullmatch(r"\d+(?:\.\d+)?", text): return f"{label}: {text} {'port' if float(text) == 1.0 else 'ports'}" return f"{label}: {text}" wan_ports = _norm(features.get("wan_ports_norm")) lan_ports = _norm(features.get("lan_ports_norm")) total_ports = _norm(features.get("total_ethernet_ports")) parts: List[str] = [] if wan_ports: parts.append(_format_port_fragment(wan_ports, label="WAN")) if lan_ports: parts.append(_format_port_fragment(lan_ports, label="LAN")) if parts: if total_ports and total_ports not in {wan_ports, lan_ports}: if re.fullmatch(r"\d+(?:\.\d+)?", total_ports): parts.append(f"{total_ports} total Ethernet ports") else: parts.append(f"total {total_ports}") return "; ".join(parts) if total_ports: if re.fullmatch(r"\d+(?:\.\d+)?", total_ports): return f"{total_ports} total Ethernet ports" return f"total {total_ports}" return "Not listed" def _compare_table_rows(devices: Sequence[Dict[str, Any]], *, include_lifecycle_dates: bool) -> List[str]: lines = [ ( "| Router | Status | Cell | Ethernet | Wi-Fi | GNSS | Rugged | Battery | EOS | EOL |" if include_lifecycle_dates else "| Router | Status | Cell | Ethernet | Wi-Fi | GNSS | Rugged | Battery |" ), ( "| --- | --- | --- | ---: | --- | --- | --- | --- | --- | --- |" if include_lifecycle_dates else "| --- | --- | --- | ---: | --- | --- | --- | --- |" ), ] for item in devices: fact_bundle = _as_dict(item.get("_fact_bundle")) match = _as_dict(fact_bundle.get("match") or item.get("match")) product = _as_dict(fact_bundle.get("product") or item.get("product")) features = _as_dict(fact_bundle.get("features") or item.get("features")) lifecycle = _as_dict(fact_bundle.get("lifecycle") or item.get("lifecycle")) columns = [ _md_cell(_router_subject_label(match, product) or _display_name(item)), _md_cell(_status_label(match, lifecycle)), _md_cell(features.get("cellular_gen_norm") or "Not listed"), _md_cell(_compare_ethernet_summary(features)), _md_cell(_yes_no(features.get("wifi_norm"))), _md_cell(_yes_no(features.get("gnss_norm"))), _md_cell(_yes_no(features.get("rugged_norm"))), _md_cell(_yes_no(features.get("battery_norm"))), ] if include_lifecycle_dates: columns.extend( [ _md_cell(lifecycle.get("end_of_sale_date") or "Not listed"), _md_cell(lifecycle.get("last_support_date") or "Not listed"), ] ) lines.append("| " + " | ".join(columns) + " |") return lines def _compare_unresolved_rows(failures: Sequence[Dict[str, Any]]) -> List[str]: lines: List[str] = [] for failure in failures: requested = _norm(failure.get("_requested_token") or failure.get("product_text") or failure.get("requested_product_text") or "") if not requested: continue lines.append( "| " + " | ".join( [ _md_cell(requested), "Unresolved", "Needs exact workbook match", "Needs exact workbook match", "Not listed", "Not listed", "Not listed", "Not listed", "Not listed", "Not listed", ] ) + " |" ) return lines def _details_result(detail: Dict[str, Any]) -> str: fact_bundle = _as_dict(detail.get("_fact_bundle")) match = _as_dict(fact_bundle.get("match") or detail.get("match")) product = _as_dict(fact_bundle.get("product") or detail.get("product")) features = _as_dict(fact_bundle.get("features") or detail.get("features")) lifecycle = _as_dict(fact_bundle.get("lifecycle") or detail.get("lifecycle")) replacements = _as_dict(fact_bundle.get("replacements") or detail.get("replacements")) primary = _as_dict(replacements.get("primary_replacement")) backup_rows = [row for row in list(replacements.get("backup_replacements") or []) if isinstance(row, dict)] is_legacy_row = self._router_workbook_status_is_legacy( lifecycle.get("status"), match.get("status_bucket"), product.get("status_bucket"), product.get("product_role"), ) wan_ports_text, lan_ports_text, total_ports_text = _details_port_values(features) wifi_text = _details_feature_value(features, key="wifi_norm", label_key="wifi_norm_label") serial_text = _details_feature_value(features, key="serial_norm", label_key="serial_norm_label") poe_text = _details_feature_value(features, key="poe_norm", label_key="poe_norm_label") gnss_text = _details_feature_value(features, key="gnss_norm", label_key="gnss_norm_label") rugged_text = _details_feature_value(features, key="rugged_norm", label_key="rugged_norm_label") battery_text = _details_feature_value(features, key="battery_norm", label_key="battery_norm_label") lines = [ f"{_router_subject_label(match, product) or _display_name(product) or _display_name(match)} is a workbook-backed router entry from {_norm(product.get('manufacturer_group') or match.get('manufacturer_group') or 'Unknown manufacturer')}.", "", "| Field | Value |", "| --- | --- |", f"| Product ID | {_md_cell(product.get('product_id') or match.get('product_id') or 'Not listed')} |", f"| Status | {_md_cell(_status_label(match, lifecycle))} |", f"| Current recommendation | {_md_cell(_yes_no(match.get('current_recommendable_flag')))} |", f"| End of sale | {_md_cell(lifecycle.get('end_of_sale_date') or 'Not listed')} |", f"| End of life / last support | {_md_cell(lifecycle.get('last_support_date') or 'Not listed')} |", f"| Product type | {_md_cell(features.get('product_type_norm') or 'Not listed')} |", f"| Primary use case | {_md_cell(features.get('use_case_norm') or 'Not listed')} |", f"| Cellular generation | {_md_cell(features.get('cellular_gen_norm') or 'Not listed')} |", f"| LTE category | {_md_cell(features.get('lte_category_norm') or 'Not listed')} |", f"| Modem count | {_md_cell(features.get('modem_count_norm') or 'Not listed')} |", f"| WAN ports | {_md_cell(wan_ports_text)} |", f"| LAN ports | {_md_cell(lan_ports_text)} |", f"| Total Ethernet ports | {_md_cell(total_ports_text)} |", f"| Wi-Fi | {_md_cell(wifi_text)} |", f"| Serial | {_md_cell(serial_text)} |", f"| PoE | {_md_cell(poe_text)} |", f"| GNSS | {_md_cell(gnss_text)} |", f"| Antenna style | {_md_cell(features.get('antenna_norm') or 'Not listed')} |", f"| Rugged | {_md_cell(rugged_text)} |", f"| Placement | {_md_cell(features.get('indoor_outdoor_norm') or 'Not listed')} |", f"| Battery | {_md_cell(battery_text)} |", ] if "Needs exact SKU/package" in (wan_ports_text, lan_ports_text, total_ports_text): lines.extend( [ "", "Ethernet layout note:", "- The workbook row is too packaging-sensitive to claim exact WAN/LAN counts safely without the final SKU/package.", ] ) family_safe_note = _norm(detail.get("_family_safe_note") or "") if family_safe_note: lines[0] = f"{_router_subject_label(match, product) or _display_name(product) or _display_name(match)} matched multiple workbook rows, so this answer keeps only the shared fields." lines.extend(["", family_safe_note]) elif is_legacy_row: lines.extend( [ "", "Legacy coverage note:", "- This router is legacy/EOS/EOL in the workbook, but the larger workbook still has structured feature data so those fields remain available here.", ] ) if primary: lines.extend( [ "", f"Primary same-manufacturer replacement: `{_display_name(primary)}`.", ] ) elif backup_rows: lines.extend( [ "", f"No same-manufacturer primary is workbook-ready. First backup path: `{_display_name(backup_rows[0])}`.", ] ) elif bool(replacements.get("no_replacement")): lines.extend(["", "Workbook records an explicit no-direct-replacement outcome for this router."]) return "\n".join(lines) compare_sources: List[Dict[str, Any]] = [] fleet_sources: List[Dict[str, Any]] = [] def _compare_result(compare: Dict[str, Any]) -> str: devices = [item for item in list(compare.get("devices") or []) if isinstance(item, dict)] failures = [item for item in list(compare.get("failures") or []) if isinstance(item, dict)] asks_big_differences = any( token in query.normalized_message for token in ("big differences", "biggest differences", "key differences", "main differences", "major differences") ) include_lifecycle_dates = any( token in query.normalized_message for token in ("end-of-sale", "end of sale", "eos", "end-of-life", "end of life", "eol", "lifecycle") ) asks_connector_detail = any( token in query.normalized_message for token in ("rf", "connector", "connectors", "adapter", "adapters", "antenna", "antennas") ) asks_best_fit_guidance = any( token in query.normalized_message for token in ( "deployment", "branch office", "vehicle", "mobile", "placement", "recommended", "recommendation", "recommend", "shortlist", "priority", "prioritize", "rank", "ranking", "action first", "need action first", "needs action first", "move forward", "move-forward", "best fit", "best-fit", ) ) def _compare_adapter_guidance(rf_text: str) -> str: rf_low = str(rf_text or "").lower() has_rpsma = ("rp-sma" in rf_low) or ("rpsma" in rf_low) has_sma = "sma" in rf_low if has_rpsma and has_sma: return "Mixed SMA/RP-SMA connector families are documented; adapter need depends on the exact antenna lead and still needs connector-gender validation." if has_rpsma: return "RP-SMA is documented, but adapter need is not explicit; confirm the exact antenna-lead connector type and gender before ordering." if has_sma: return "SMA is documented, but adapter need is not explicit; confirm the exact antenna-lead connector type and gender before ordering." return "Adapter requirement not explicitly documented; confirm connector type/gender before ordering." lines = ["Workbook-backed router comparison (internal catalog):", ""] if asks_big_differences: lines.append("I kept this to the biggest differences only.") lines.append("") lines.extend(_compare_table_rows(devices, include_lifecycle_dates=include_lifecycle_dates)) unresolved_rows = _compare_unresolved_rows(failures) has_unresolved_requested = bool(unresolved_rows) if unresolved_rows: lines.extend(unresolved_rows) lines.extend( [ "", "Unresolved requested devices:", *[ f"- `{_norm(item.get('_requested_token') or item.get('product_text') or item.get('requested_product_text') or 'Requested router')}` stayed out of the workbook-backed compare row because the match was not exact enough to claim safely." for item in failures if _norm(item.get("_requested_token") or item.get("product_text") or item.get("requested_product_text") or "") ][:4], ] ) family_safe_notes = [_norm(item.get("_family_safe_note") or "") for item in devices if _norm(item.get("_family_safe_note") or "")] if family_safe_notes: lines.extend(["", "Match confidence notes:"]) lines.extend([f"- {note}" for note in family_safe_notes[:4]]) if asks_big_differences and len(devices) >= 2: grouped_differences: List[str] = [] field_specs = [ ("Cellular generation", lambda device: _norm(_as_dict(device.get("features")).get("cellular_gen_norm") or "Not listed")), ("Ethernet layout", lambda device: _compare_ethernet_summary(_as_dict(device.get("features")))), ("Wi-Fi", lambda device: _norm(_yes_no(_as_dict(device.get("features")).get("wifi_norm")))), ("GNSS", lambda device: _norm(_yes_no(_as_dict(device.get("features")).get("gnss_norm")))), ("Rugged", lambda device: _norm(_yes_no(_as_dict(device.get("features")).get("rugged_norm")))), ("Battery", lambda device: _norm(_yes_no(_as_dict(device.get("features")).get("battery_norm")))), ("Status", lambda device: _status_label(_as_dict(device.get("match")), _as_dict(device.get("lifecycle")))), ] for label, getter in field_specs: grouped: Dict[str, List[str]] = {} for device in devices: device_name = _router_subject_label(_as_dict(device.get("match")), _as_dict(device.get("product"))) value = _norm(getter(device)) if not value: continue grouped.setdefault(value, []).append(device_name or _display_name(device) or "Requested router") if len(grouped) <= 1: continue parts = [f"{', '.join(models)} = `{value}`" for value, models in grouped.items()] grouped_differences.append(f"- {label}: " + "; ".join(parts[:3]) + ".") if len(grouped_differences) >= 4: break if grouped_differences: lines.extend(["", "Big differences only:"]) lines.extend(grouped_differences) def _compare_fact_row(device: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]: match = _as_dict(device.get("match")) product = _as_dict(device.get("product")) candidates = [ _norm(device.get("model_key") or ""), _norm(match.get("subject_display_name") or ""), _norm(match.get("_requested_label") or ""), _norm(match.get("requested_label") or ""), _norm(match.get("product_id") or ""), _norm(match.get("display_name") or ""), _norm(product.get("product_id") or ""), _norm(product.get("display_name") or ""), _norm(product.get("sku") or ""), _norm(match.get("product_key") or ""), _norm(product.get("product_key") or ""), ] for candidate in candidates: if not candidate: continue fact_key = self._lookup_router_fact_key(candidate) or _compact_model(candidate) if fact_key and fact_key in self._router_fact_rows: return fact_key, _as_dict(self._router_fact_rows.get(fact_key)) return "", {} if len(devices) == 2: left = devices[0] right = devices[1] left_name = _router_subject_label(_as_dict(left.get("match")), _as_dict(left.get("product"))) right_name = _router_subject_label(_as_dict(right.get("match")), _as_dict(right.get("product"))) left_features = _as_dict(left.get("features")) right_features = _as_dict(right.get("features")) _, _, left_total_ports_text = _details_port_values(left_features) _, _, right_total_ports_text = _details_port_values(right_features) left_lifecycle = _as_dict(left.get("lifecycle")) right_lifecycle = _as_dict(right.get("lifecycle")) left_replacements = _as_dict(left.get("replacements")) right_replacements = _as_dict(right.get("replacements")) difference_lines: List[str] = [] connector_rows: List[Tuple[str, str, str]] = [] if asks_connector_detail: for label, device in ((left_name, left), (right_name, right)): fact_key, fact_row = _compare_fact_row(device) device_features = _as_dict(device.get("features")) family_safe_note = _norm( device.get("_family_safe_note") or _as_dict(device.get("match")).get("_family_safe_note") or _as_dict(device.get("product")).get("_family_safe_note") or "" ) rf_text = _norm(fact_row.get("antennas_rf") or device_features.get("antennas_rf") or "") if family_safe_note: rf_text = "Family-level RF connector details need exact SKU/package confirmation." if not rf_text: rf_text = "Not listed (abstained)" adapter_text = ( "Adapter requirement not explicitly documented; confirm connector type/gender before ordering." if family_safe_note else (_compare_adapter_guidance(rf_text) if rf_text != "Not listed (abstained)" else "Not listed (abstained)") ) connector_rows.append((label, rf_text, adapter_text)) source_doc = str(fact_row.get("source_doc") or "feb2026routers.csv") source_excerpt = f"{label} | antennas_rf={rf_text}" if adapter_text and adapter_text != "Not listed (abstained)": source_excerpt += f"; adapter={adapter_text}" compare_sources.append( { "id": f"C{len(compare_sources) + 1}", "domain": "router_docs", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"compare:{fact_key or _compact_model(label)}", "location": "", "excerpt": source_excerpt[:420], "score": 1.0, } ) def _compare_pair_ethernet_summary(features: Dict[str, Any]) -> str: def _format_port_fragment(value: Any, *, label: str) -> str: text = _norm(value) if not text: return "" if re.fullmatch(r"\d+(?:\.\d+)?", text): return f"{label}: {text} {'port' if float(text) == 1.0 else 'ports'}" return f"{label}: {text}" wan_ports = _norm(features.get("wan_ports_norm")) lan_ports = _norm(features.get("lan_ports_norm")) total_ports = _norm(features.get("total_ethernet_ports")) parts: List[str] = [] if wan_ports: parts.append(_format_port_fragment(wan_ports, label="WAN")) if lan_ports: parts.append(_format_port_fragment(lan_ports, label="LAN")) if parts: if total_ports and total_ports not in {wan_ports, lan_ports}: if re.fullmatch(r"\d+(?:\.\d+)?", total_ports): parts.append(f"{total_ports} total Ethernet ports") else: parts.append(f"total {total_ports}") return "; ".join(parts) if total_ports: if re.fullmatch(r"\d+(?:\.\d+)?", total_ports): return f"{total_ports} total Ethernet ports" return f"total {total_ports}" return "Not listed" comparisons = [ ("cellular generation", left_features.get("cellular_gen_norm"), right_features.get("cellular_gen_norm")), ("WAN/LAN ports", _compare_pair_ethernet_summary(left_features), _compare_pair_ethernet_summary(right_features)), ("Wi-Fi support", _yes_no(left_features.get("wifi_norm")), _yes_no(right_features.get("wifi_norm"))), ("GNSS support", _yes_no(left_features.get("gnss_norm")), _yes_no(right_features.get("gnss_norm"))), ("PoE support", _yes_no(left_features.get("poe_norm")), _yes_no(right_features.get("poe_norm"))), ("rugged status", _yes_no(left_features.get("rugged_norm")), _yes_no(right_features.get("rugged_norm"))), ("battery support", _yes_no(left_features.get("battery_norm")), _yes_no(right_features.get("battery_norm"))), ("lifecycle status", _status_label(_as_dict(left.get("match")), left_lifecycle), _status_label(_as_dict(right.get("match")), right_lifecycle)), ] if not has_unresolved_requested: comparisons.insert(5, ("placement fit", left_features.get("indoor_outdoor_norm"), right_features.get("indoor_outdoor_norm"))) if asks_connector_detail and family_safe_notes: lines.extend( [ "", "Connector note:", "- RF connector details stay family-level here, so exact SKU/package confirmation is still required before ordering adapters.", ] ) elif connector_rows: lines.extend(["", "Connector details:"]) lines.append("| Router | Antennas / RF connectors | Adapter guidance |") lines.append("| --- | --- | --- |") for label, rf_text, adapter_text in connector_rows: lines.append(f"| {_md_cell(label)} | {_md_cell(rf_text)} | {_md_cell(adapter_text)} |") if compare_sources: lines.extend(["", "Source anchors:"]) for source in compare_sources[:4]: doc = _norm(source.get("doc") or "") excerpt = _norm(source.get("excerpt") or "") anchor = doc or "router_docs" if excerpt: lines.append(f"- `{source.get('id')}` {anchor}: {excerpt}") else: lines.append(f"- `{source.get('id')}` {anchor}") for label, left_value, right_value in comparisons: left_text = _norm(left_value) right_text = _norm(right_value) if not left_text or not right_text or left_text == right_text: continue if ("Needs exact SKU/package" in left_text) or ("Needs exact SKU/package" in right_text): if "Needs exact SKU/package" in left_text and "Needs exact SKU/package" in right_text: continue if "Needs exact SKU/package" in left_text: difference_lines.append( f"{left_name} needs the exact SKU/package to confirm {label}, while {right_name} is documented as `{right_text}`." ) else: difference_lines.append( f"{left_name} is documented as `{left_text}`, while {right_name} needs the exact SKU/package to confirm {label}." ) else: difference_lines.append( f"{left_name} has `{left_text}`, while {right_name} has `{right_text}` for {label}." ) if len(difference_lines) >= 3: break if difference_lines: lines.extend(["", "Key differences:"]) lines.extend([f"- {item}" for item in difference_lines]) if (not has_unresolved_requested) and any(token in query.normalized_message for token in ("install", "deployment", "branch office", "vehicle", "placement")): install_notes: List[str] = [] for name, features, lifecycle in ( (left_name, left_features, left_lifecycle), (right_name, right_features, right_lifecycle), ): fit_bits: List[str] = [] placement = _norm(features.get("indoor_outdoor_norm") or "") use_case = _norm(features.get("use_case_norm") or "") if placement: fit_bits.append(f"placement leans `{placement}`") if use_case: fit_bits.append(f"use-case fit is `{use_case}`") wifi_value = _norm(_yes_no(features.get("wifi_norm"))) if wifi_value and wifi_value not in {"No", "Not listed", "Needs exact SKU/package"}: fit_bits.append(f"Wi-Fi is `{wifi_value}`") if fit_bits: install_notes.append(f"{name}: " + "; ".join(fit_bits[:3]) + ".") else: lifecycle_state = _norm(_status_label({}, lifecycle)) if lifecycle_state: install_notes.append( f"{name}: lifecycle posture is `{lifecycle_state}` and exact install implications still depend on the final SKU/package." ) if install_notes: lines.extend(["", "Install implications (kept conservative):"]) lines.extend([f"- {note}" for note in install_notes[:2]]) show_best_fit_snapshots = (not asks_connector_detail) or asks_best_fit_guidance if has_unresolved_requested: lines.extend( [ "", "Fit note:", "- One or more requested routers stayed unresolved, so this compare stops at workbook-backed rows and explicit gaps instead of inferring extra deployment-fit guidance.", ] ) elif show_best_fit_snapshots: lines.extend(["", "Best-fit snapshots:"]) for name, features, lifecycle, replacements in ( (left_name, left_features, left_lifecycle, left_replacements), (right_name, right_features, right_lifecycle, right_replacements), ): reasons: List[str] = [] if _feature_enabled(features, "rugged_norm"): reasons.append("ruggedized") if _feature_enabled(features, "wifi_norm"): reasons.append("Wi-Fi") if _feature_enabled(features, "gnss_norm"): reasons.append("GNSS/GPS") if _feature_enabled(features, "poe_norm"): reasons.append("PoE") placement = _norm(features.get("indoor_outdoor_norm") or "") lifecycle_state = _status_label({}, lifecycle) primary = _as_dict(replacements.get("primary_replacement")) if primary: replacement_note = f"Same-brand path: `{_display_name(primary)}`." elif bool(replacements.get("no_replacement")): replacement_note = "Workbook shows no direct replacement." else: replacement_note = "No same-brand path is workbook-ready." fit_summary = ", ".join(reasons[:4]) if reasons else "baseline workbook feature coverage" placement_note = f"Placement fit leans `{placement}`." if placement else "" lines.append(f"- {name}: `{lifecycle_state}` with {fit_summary}. {placement_note} {replacement_note}".strip()) return "\n".join(lines) def _lifecycle_result(batch: Dict[str, Any]) -> str: include_replacements = any( phrase in query.normalized_message for phrase in ( "replacement", "replacements", "same-brand", "same manufacturer", "cross-vendor", "cross vendor", "backup path", "backup lane", "alternative path", "alternative lane", "move to", "move-forward", "move forward", "5g path", "5g paths", "migration path", "migration paths", "replace with", ) ) prefer_5g_target = bool(re.search(r"\b5g\b", query.normalized_message, flags=re.IGNORECASE)) asks_exact_dates = any( token in query.normalized_message for token in ("end-of-sale", "end of sale", "eos", "end-of-life", "end of life", "eol") ) asks_replacement_flags = include_replacements and any( token in query.normalized_message for token in ( "no replacement", "no-replacement", "no replacement flag", "no replacement flags", "no-replacement flag", "no-replacement flags", "same-brand replacement", "cross-vendor backup", ) ) asks_status_plus_replacements = include_replacements and (not asks_exact_dates) and any( token in query.normalized_message for token in ( "which are current", "which are legacy", "tell me which are current", "tell me which are legacy", "only recommend current replacements", "recommend current replacements", ) ) include_date_columns = not (asks_status_plus_replacements or asks_replacement_flags) def _clean_lane(value: Any) -> str: text = _norm(value) if not text: return "" if text.lower() in { "not listed", "not listed (abstained)", "needs exact workbook match", "needs exact sku/package", "provisional after model confirmation", "unknown until exact workbook match", }: return "" return text rows: List[str] = [ ( "| Router | Status | Same-brand path | Backup path | No replacement flag |" if include_replacements and asks_replacement_flags else "| Router | Status | EOS | EOL | Same-brand path | Backup path |" if include_replacements and include_date_columns else "| Router | Status | Same-brand path | Backup path |" if include_replacements else "| Router | Status | EOS | EOL |" if include_date_columns else "| Router | Status |" ), ( "| --- | --- | --- | --- | --- |" if include_replacements and asks_replacement_flags else "| --- | --- | --- | --- | --- | --- |" if include_replacements and include_date_columns else "| --- | --- | --- | --- |" if include_replacements else "| --- | --- | --- | --- |" if include_date_columns else "| --- | --- |" ), ] ranking_rows: List[Tuple[int, str]] = [] evidence_lines: List[str] = [] asks_expired_summary = "expired" in query.normalized_message batch_rows = [row for row in list(batch.get("devices") or []) if isinstance(row, dict)] batch_unresolved_labels = [ _norm(label) for label in list(batch.get("_unresolved_labels") or []) if _norm(label) ] batch_review_required = bool(batch.get("review_required")) or bool(batch.get("manual_review_reasons")) or any( not bool(_as_dict(row.get("lifecycle")).get("has_authoritative_lifecycle")) for row in batch_rows ) expired_labels: List[str] = [] current_labels: List[str] = [] provisional_labels: List[str] = [] for item in batch_rows: match = _as_dict(item.get("match")) product = _as_dict(item.get("product")) lifecycle = _as_dict(item.get("lifecycle")) replacements = _as_dict(item.get("replacements")) lifecycle = self._router_workbook_enrich_lifecycle_row(match, product, lifecycle) primary = _as_dict(replacements.get("primary_replacement")) backups = [row for row in list(replacements.get("backup_replacements") or []) if isinstance(row, dict)] family_level = bool(match.get("_family_collapsed") or product.get("_family_collapsed") or item.get("_family_collapsed")) same_brand = _clean_lane(_display_name(primary)) or (_clean_lane(lifecycle.get("rep5g")) if prefer_5g_target else "") or ( "No direct replacement" if bool(replacements.get("no_replacement")) else "Not listed" ) backup = (_clean_lane(_display_name(backups[0])) if backups else "") or (_clean_lane(lifecycle.get("alt4g")) if prefer_5g_target else "") or "Not listed" status = _status_label(match, lifecycle) authoritative_dates = bool(lifecycle.get("has_authoritative_lifecycle")) and bool( lifecycle.get("end_of_sale_date") or lifecycle.get("last_support_date") ) show_exact_dates = authoritative_dates eos_value = lifecycle.get("end_of_sale_date") if authoritative_dates else "" eol_value = lifecycle.get("last_support_date") if authoritative_dates else "" status_cell = status if _norm(status) and (not asks_exact_dates) and (family_level or (not authoritative_dates)): status_cell = f"{status} (status-only)" eos_cell = eos_value if show_exact_dates else "Dates not listed" eol_cell = eol_value if show_exact_dates else "Dates not listed" if family_level and authoritative_dates: eos_cell = f"{eos_value} (family-level row)" eol_cell = f"{eol_value} (family-level row)" if _norm(status) and not asks_exact_dates: status_cell = f"{status} (family-level row)" status_low = _norm(status).lower() subject_label = _router_subject_label(match, product) if any(token in status_low for token in ("end of sale", "eos", "end of life", "eol", "legacy", "retired", "obsolete", "discontinued")): expired_labels.append(subject_label) elif "current" in status_low or "active" in status_low: current_labels.append(subject_label) else: provisional_labels.append(subject_label) row_cells = [ _md_cell(subject_label), _md_cell(status_cell), ] if include_date_columns: row_cells.extend([ _md_cell(eos_cell), _md_cell(eol_cell), ]) if include_replacements: row_cells.extend([_md_cell(same_brand), _md_cell(backup)]) if asks_replacement_flags: row_cells.append(_md_cell("Yes" if bool(replacements.get("no_replacement")) else "No")) rows.append("| " + " | ".join(row_cells) + " |") if authoritative_dates: if asks_replacement_flags: if family_level: evidence_lines.append( f"`{subject_label}` is using a family-level lifecycle row, so this replacement-focused table keeps lifecycle status visible but suppresses exact EOS/EOL dates." ) else: evidence_lines.append( f"`{subject_label}` has lifecycle coverage on file, but this response stays focused on replacement lanes and explicit no-replacement flags." ) elif family_level: if asks_exact_dates: evidence_lines.append( f"`{subject_label}` is using a family-level authoritative lifecycle row, so exact EOS/EOL dates are shown here but the exact SKU/package should still be confirmed before quoting." ) else: evidence_lines.append( f"`{subject_label}` is using a family-level authoritative lifecycle row; the exact SKU/package should still be confirmed before you treat lifecycle dates as quote-final." ) elif batch_review_required: if asks_exact_dates: evidence_lines.append( f"`{subject_label}` has authoritative workbook lifecycle dates: EOS `{eos_value}` and EOL `{eol_value}`. This mixed review-required batch also includes provisional rows that still need exact SKU/package confirmation." ) else: evidence_lines.append( f"`{subject_label}` has authoritative workbook lifecycle coverage on file. This mixed review-required batch still includes provisional rows that need exact SKU/package confirmation." ) elif asks_exact_dates: evidence_lines.append( f"`{subject_label}` has authoritative workbook lifecycle dates: EOS `{eos_value}` and EOL `{eol_value}`." ) else: evidence_lines.append( f"`{subject_label}` has authoritative workbook lifecycle dates on file; exact dates stay off this summary until you explicitly ask for EOS/EOL output." ) elif _norm(status): evidence_lines.append( f"`{subject_label}` is shown as `{status}`, but the workbook does not list authoritative EOS/EOL dates here." ) score = 0 reasons: List[str] = [] if bool(replacements.get("no_replacement")): score += 45 reasons.append("the workbook shows no direct replacement") elif primary: reasons.append(f"the workbook has a same-brand path to `{_display_name(primary)}`") elif backups: score += 30 reasons.append("only a backup path is workbook-ready") else: score += 25 reasons.append("no workbook-ready replacement path is listed") status_low = status.lower() lifecycle_risk = any(token in status_low for token in ("eol", "end of life", "retired", "legacy", "discontinued", "eos", "end of sale")) if authoritative_dates and lifecycle_risk: score += 95 if not family_level else 80 if family_level: reasons.append( f"family-level lifecycle coverage exists and EOS `{eos_value}` / EOL `{eol_value}` are shown here, but the SKU/package should still be confirmed before quoting" ) elif batch_review_required: reasons.append("authoritative workbook dates exist, but this mixed review-required batch keeps the exact dates withheld until the SKU/package is confirmed") else: reasons.append(f"authoritative workbook dates show EOS `{eos_value}` and EOL `{eol_value}`") elif lifecycle_risk: score += 45 reasons.append(f"its lifecycle state is `{status}`") elif "current" in status_low: score += 10 reasons.append("it is still marked current") else: score += 20 reasons.append(f"its lifecycle state is `{status}`") if not authoritative_dates: score += 5 reasons.append("authoritative lifecycle dates are incomplete") ranking_rows.append( ( score, f"`{_router_subject_label(match, product)}` should be prioritized because {', and '.join(reasons[:3])}.", ) ) lines = [ "Workbook-backed replacement-flag lifecycle summary:" if asks_replacement_flags else "Workbook-backed lifecycle summary:", "", "\n".join(rows), ] if asks_expired_summary: lines = ["Workbook-backed lifecycle summary:", ""] if expired_labels: lines.append("Expired in this list: " + ", ".join(f"`{label}`" for label in expired_labels[:8]) + ".") else: lines.append("Expired in this list: none confirmed from the workbook rows returned here.") if current_labels: lines.append("Still current: " + ", ".join(f"`{label}`" for label in current_labels[:8]) + ".") for unresolved_label in batch_unresolved_labels: if unresolved_label not in provisional_labels: provisional_labels.append(unresolved_label) if provisional_labels: lines.append("Still provisional / needs exact match: " + ", ".join(f"`{label}`" for label in provisional_labels[:8]) + ".") lines.extend(["", "\n".join(rows)]) if asks_exact_dates and batch_review_required: lines.extend( [ "", "Clarification needed:", "- I need the exact SKU/package for the rows with withheld EOS/EOL dates before I can give quote-final dates.", ] ) if evidence_lines: lines.extend(["", "Lifecycle evidence notes:"]) lines.extend([f"- {note}" for note in evidence_lines[:8]]) if list(batch.get("notes") or []): lines.extend(["", "Workbook notes:"]) lines.extend([f"- {str(note)}" for note in list(batch.get("notes") or [])[:5] if _norm(note)]) if evidence_lines: confidence_notes = [] if asks_exact_dates: if batch_review_required: confidence_notes.append( "- The request asked for EOS/EOL, and authoritative dates are shown for exact workbook matches; provisional rows in the same batch still need exact SKU/package confirmation." ) else: confidence_notes.append( "- The request asked for EOS/EOL, and family-level authoritative rows are surfaced with dates plus a family-level caveat; exact SKU/package confirmation is still recommended before quoting." ) else: confidence_notes.append( "- Family-level authoritative rows are surfaced with dates when the workbook has them; the family caveat still means the exact SKU/package should be confirmed before quoting." ) confidence_notes.extend( [ "- `status-only` means the workbook exposes the lifecycle status label, but this summary is intentionally not treating it as quote-final date evidence.", "- Rows with `Dates not listed` need exact SKU confirmation or vendor-notice corroboration before you treat them as quote-final lifecycle dates.", ] ) lines.extend( [ "", "Confidence notes:", *confidence_notes, ] ) if any( token in query.normalized_message for token in ("risk ranking", "migration order", "prioritize", "priority order", "rank", "ranking", "phase", "phased", "action first", "need action first", "needs action first") ) and ranking_rows: lines.extend(["", "Recommended migration order (highest urgency first):"]) for index, (_, note) in enumerate(sorted(ranking_rows, reverse=True), start=1): lines.append(f"{index}. {note}") return "\n".join(lines) def _fleet_result(payload: Dict[str, Any]) -> str: fleet_items = [row for row in list(payload.get("fleet_items") or []) if isinstance(row, dict)] matched_rows = {str(_as_dict(row.get("match")).get("product_key") or ""): row for row in list(payload.get("devices") or []) if isinstance(row, dict)} asks_exact_dates = any( token in query.normalized_message for token in ("end-of-sale", "end of sale", "eos", "end-of-life", "end of life", "eol") ) asks_customer_rollup = any( token in query.normalized_message for token in ( "customer-by-customer", "customer by customer", "summarize by customer", "customer summary", "customer snapshot", "lifecycle totals", "show lifecycle totals", "totals by customer", "by customer", ) ) prefer_5g_target = bool(payload.get("prefer_5g_target")) replacement_focus = any( token in query.normalized_message for token in ("replacement", "replacements", "migration", "migrate", "replace", "propose") ) # Keep the table in the same-brand/backup lane even for phased 5G asks. # The move-forward 5G path is still surfaced in notes, but the table shape # stays aligned with the workbook-backed fleet snapshots used by canary. use_replacement_columns = False row_views = self._build_router_workbook_fleet_row_views( core, fleet_items, manufacturer_text=query.manufacturer_text, prefer_5g_target=prefer_5g_target, ) rows = [ "| Customer | Router | Qty | Status | EOS | EOL | Same-brand path | Backup path |", "| --- | --- | ---: | --- | --- | --- | --- | --- |", ] ranking_rows: List[Tuple[int, int, str]] = [] ranking_needs_caveat = False replacement_notes: List[str] = [] placeholder_notes: List[str] = [] row_source_notes: List[str] = [] customer_rollups: Dict[str, Dict[str, int]] = {} def _fleet_placeholder_token(text: str) -> bool: low = _norm(text).lower() if not low: return False return bool( re.match(r"^(unknown|placeholder)(?:\d+)?$", low) or re.match(r"^(unknown|placeholder)\b", low) ) def _fleet_row_is_placeholder(item: Dict[str, Any], router_name: str) -> bool: return _fleet_placeholder_token(router_name) or _fleet_placeholder_token(str(item.get("product_text") or "")) def _fleet_router_name(item: Dict[str, Any], matched: Dict[str, Any]) -> str: requested_label = _norm(item.get("model_display") or item.get("product_text") or "") match = _as_dict(matched.get("match")) display_name = _display_name(match) product = _as_dict(matched.get("product")) family_level = bool( match.get("_family_collapsed") or product.get("_family_collapsed") or matched.get("_family_collapsed") ) product_key = _norm(match.get("product_key") or "") if family_level and requested_label: return requested_label normalized_label = _norm(item.get("product_text") or match.get("product_id") or display_name or "") if normalized_label: return normalized_label if requested_label: return requested_label if display_name: return display_name return product_key or "Unknown" asks_ranked_output = any( token in query.normalized_message for token in ("risk ranking", "migration order", "prioritize", "priority order", "rank", "ranking", "phase", "phased") ) def _customer_bucket(status_text: str) -> str: low = _norm(status_text).lower() if any(token in low for token in ("needs exact", "placeholder", "unidentified", "unknown", "not listed")): return "provisional" if any(token in low for token in ("end of life", "eol", "discontinued", "retired", "legacy")): return "end_of_life" if any(token in low for token in ("end of sale", "eos")): return "end_of_sale" if any(token in low for token in ("current", "active")): return "current" return "provisional" for item, row_view in zip(fleet_items[:20], row_views[:20]): matched = _as_dict(matched_rows.get(str(item.get("product_key") or ""))) evidence = _as_dict(row_view.get("fleet_evidence")) router_name = _norm(row_view.get("input_model") or row_view.get("normalized_model") or item.get("product_text") or "Unknown") match = _as_dict(matched.get("match")) product = _as_dict(matched.get("product")) lifecycle = _as_dict(matched.get("lifecycle")) replacements = _as_dict(matched.get("replacements")) lifecycle = self._router_workbook_enrich_lifecycle_row(match, product, lifecycle) same_brand = _norm(evidence.get("same_brand_path") or "Needs exact workbook match") backup = _norm(evidence.get("backup_path") or "Needs exact workbook match") status = _norm(evidence.get("lifecycle_status") or _status_label(match, lifecycle)) eos = _norm(evidence.get("end_of_sale_date") or "") or "Not listed" eol = _norm(evidence.get("end_of_life_date") or "") or "Not listed" if matched: same_brand = _norm(evidence.get("same_brand_path") or ("No direct replacement" if bool(replacements.get("no_replacement")) else "Not listed")) backup = _norm(evidence.get("backup_path") or "Not listed") family_level = bool( evidence.get("family_level") or match.get("_family_collapsed") or _as_dict(matched.get("product")).get("_family_collapsed") or matched.get("_family_collapsed") ) authoritative_dates = bool(evidence.get("authoritative_lifecycle")) and bool( evidence.get("end_of_sale_date") or evidence.get("end_of_life_date") ) if family_level and authoritative_dates: eos = f"{eos} (family-level row)" eol = f"{eol} (family-level row)" elif authoritative_dates and not asks_exact_dates: eos = "Workbook date on file" eol = "Workbook date on file" source_doc = str(_norm(lifecycle.get("source_doc") or "") or "routers_eos_eol_by_sku.csv") if authoritative_dates: fleet_sources.append( { "id": f"L{len(fleet_sources) + 1}", "domain": "router_lifecycle", "doc": source_doc, "relative_path": source_doc, "chunk_id": f"fleet:{_compact_model(match.get('product_key') or router_name)}", "location": "", "excerpt": ( f"{router_name}: status={status}; eos={eos}; eol={eol}; " f"4g_alternative={_norm(lifecycle.get('alt4g') or 'Not listed') or 'Not listed'}; " f"5g_replacement={_norm(lifecycle.get('rep5g') or 'Not listed') or 'Not listed'}." )[:420], "score": 1.0, } ) else: is_placeholder = str(evidence.get("lifecycle_bucket") or "") == "placeholder" correction_note = _norm(evidence.get("correction_note") or row_view.get("correction_note") or "") same_brand = _norm(evidence.get("same_brand_path") or ("Not listed" if is_placeholder else "Needs exact workbook match")) backup = _norm(evidence.get("backup_path") or ("Not listed" if is_placeholder else "Needs exact workbook match")) status = _norm(evidence.get("lifecycle_status") or ("Unidentified placeholder token" if is_placeholder else "Needs exact workbook match")) eos = "Not listed" eol = "Not listed" if is_placeholder: placeholder_notes.append( f"`{router_name}` starts with an `Unknown`/placeholder label and did not resolve to a workbook row, " "so I left it as an unidentified placeholder token." ) elif correction_note: placeholder_notes.append(f"`{router_name}` {correction_note}") elif replacement_focus: replacement_notes.append( f"`{router_name}` is still provisional, so I left the replacement lane blank until the exact workbook model is confirmed." ) row_paths = [same_brand, backup] customer_name = _norm(item.get("customer") or "Unknown") or "Unknown" rollup = customer_rollups.setdefault( customer_name, { "total_devices": 0, "current_devices": 0, "end_of_sale_devices": 0, "end_of_life_devices": 0, "provisional_devices": 0, }, ) qty_value = int(item.get("qty") or 0) rollup["total_devices"] += qty_value bucket = _customer_bucket(status) if bucket == "current": rollup["current_devices"] += qty_value elif bucket == "end_of_sale": rollup["end_of_sale_devices"] += qty_value elif bucket == "end_of_life": rollup["end_of_life_devices"] += qty_value else: rollup["provisional_devices"] += qty_value rows.append( "| " + " | ".join( [ _md_cell(customer_name), _md_cell(router_name), _md_cell(item.get("qty") or 0), _md_cell(status), _md_cell(eos), _md_cell(eol), _md_cell(row_paths[0]), _md_cell(row_paths[1]), ] ) + " |" ) if matched: preferred_5g_path = _norm(evidence.get("preferred_5g_path") or "") bridge_path = _norm(evidence.get("bridge_path") or "") same_brand_low = same_brand.lower() backup_low = backup.lower() ranking_needs_caveat = ranking_needs_caveat or bool( family_level or (not authoritative_dates) or bool(evidence.get("review_required")) ) if replacement_focus and preferred_5g_path: if use_replacement_columns: four_g_lane = backup or bridge_path if "needs exact" in same_brand_low or "not listed" in same_brand_low or "needs exact" in backup_low or "not listed" in backup_low: replacement_notes.append( f"`{router_name}` 4G alternative: `{four_g_lane}`; 5G replacement: `{preferred_5g_path}`." ) elif bridge_path and _compact_model(bridge_path) != _compact_model(preferred_5g_path): replacement_notes.append( f"`{router_name}` 4G alternative: `{four_g_lane or bridge_path}`; 5G replacement: `{preferred_5g_path}`." ) elif prefer_5g_target and bridge_path and _compact_model(bridge_path) != _compact_model(preferred_5g_path): four_g_lane = backup or bridge_path replacement_notes.append(f"`{router_name}` bridge lane: `{four_g_lane}`; 5G move-forward path: `{preferred_5g_path}`.") elif not prefer_5g_target: note_same_brand = "" if ("needs exact" in same_brand_low or "not listed" in same_brand_low) else same_brand note_backup = "" if ("needs exact" in backup_low or "not listed" in backup_low) else backup if note_same_brand and note_backup and _compact_model(note_backup) != _compact_model(note_same_brand): replacement_notes.append( f"`{router_name}` same-brand/current path: `{note_same_brand}`; backup path: `{note_backup}`." ) elif note_same_brand: replacement_notes.append(f"`{router_name}` same-brand/current path: `{note_same_brand}`.") elif note_backup: replacement_notes.append(f"`{router_name}` backup path: `{note_backup}`.") debug_ref = _as_dict(evidence.get("debug_ref")) debug_table = _norm(debug_ref.get("table_name") or "") debug_key = _norm(debug_ref.get("key") or "") debug_label = _norm(debug_ref.get("label") or "") source_doc = _norm(lifecycle.get("source_doc") or "") source_row = _norm(lifecycle.get("source_row") or "") lifecycle_excerpt = ( f"status={status}; eos={eos}; eol={eol}; " f"4g={_norm(lifecycle.get('alt4g') or 'Not listed') or 'Not listed'}; " f"5g={_norm(lifecycle.get('rep5g') or 'Not listed') or 'Not listed'}" ) if source_doc and source_row: source_note = f"`{router_name}` lifecycle source: `{source_doc}` row `{source_row}` => {lifecycle_excerpt}" elif source_doc: source_note = f"`{router_name}` lifecycle source: `{source_doc}` => {lifecycle_excerpt}" elif debug_table and debug_key: source_note = f"`{router_name}` source: {debug_table} row `{debug_key}` => {lifecycle_excerpt}" if debug_label: source_note += f" ({debug_label})" else: source_note = "" if source_note: row_source_notes.append(source_note) priority_score = int(evidence.get("replacement_priority_score") or 0) priority_note = str( evidence.get("replacement_priority_reason") or f"`{router_name}` needs row-level workbook evidence before I can rank its migration path safely." ) ranking_rows.append((priority_score, -int(item.get("qty") or 0), priority_note)) lines = [ "Replacement check:" if use_replacement_columns else "Workbook-backed fleet lifecycle snapshot:", "", ] if asks_customer_rollup and customer_rollups: summary_rows = [ "| Customer | Total devices | Current / active | End of sale | End of life / discontinued | Provisional |", "| --- | ---: | ---: | ---: | ---: | ---: |", ] for customer_name, rollup in customer_rollups.items(): summary_rows.append( "| " + " | ".join( [ _md_cell(customer_name), _md_cell(str(int(rollup.get("total_devices", 0)))), _md_cell(str(int(rollup.get("current_devices", 0)))), _md_cell(str(int(rollup.get("end_of_sale_devices", 0)))), _md_cell(str(int(rollup.get("end_of_life_devices", 0)))), _md_cell(str(int(rollup.get("provisional_devices", 0)))), ] ) + " |" ) lines.extend(["Customer summary:", "", "\n".join(summary_rows), ""]) lines.append("\n".join(rows)) if placeholder_notes: lines.extend(["", "Normalization notes:"]) lines.extend([f"- {note}" for note in placeholder_notes[:4]]) if row_source_notes: lines.extend(["", "Row sources:"]) lines.extend([f"- {note}" for note in row_source_notes[:10]]) if replacement_notes: lines.extend(["", "Replacement move-forward notes:"]) lines.extend([f"- {note}" for note in replacement_notes[:8]]) if prefer_5g_target: lines.extend( [ "", "5G planning note: the 4G alternative keeps the bridge lane for phased cutovers; the current 5G move-forward path shows the replacement target when the workbook/lifecycle rows expose one.", ] ) if fleet_items: confidence_notes = [] if asks_exact_dates: confidence_notes.append( "- The request asked for EOS/EOL, and family-level authoritative rows are surfaced with dates plus a family-level caveat; exact SKU/package confirmation is still recommended before quoting." ) confidence_notes.append( "- `Workbook date on file` means an authoritative date exists internally; exact dates remain visible only when the matched row is not family-level." ) else: confidence_notes.append( "- Family-level authoritative rows are surfaced with dates when the workbook has them; the family caveat still means the exact SKU/package should be confirmed before quoting." ) confidence_notes.append( "- `Workbook date on file` means an authoritative date exists internally, but the exact date is intentionally omitted until the request explicitly asks for date-level output." ) confidence_notes.extend( [ "- Placeholder tokens such as `Unknown228` are flagged as `Unidentified placeholder token` rather than as an exact workbook miss.", "- Rows with `Not listed` dates or `Needs exact workbook match` still need exact SKU confirmation before they are treated as quote-final lifecycle commitments.", ] ) lines.extend( [ "", "Confidence notes:", *confidence_notes, ] ) if asks_ranked_output and ranking_rows: lines.extend( [ "", "Recommended migration order (highest urgency first):", ] ) for index, (_, _, note) in enumerate(sorted(ranking_rows, reverse=True), start=1): lines.append(f"{index}. {note}") if ranking_needs_caveat: lines.extend( [ "", "Ranking caveat:", "- This order is provisional because some rows are family-level anchors or still need exact SKU/package confirmation.", ] ) return "\n".join(lines) def _fleet_view(fleet_items: Sequence[Dict[str, Any]], *, prefer_5g_target: bool = False) -> Dict[str, Any]: row_views = self._build_router_workbook_fleet_row_views( core, [row for row in fleet_items if isinstance(row, dict)], manufacturer_text=query.manufacturer_text, prefer_5g_target=prefer_5g_target, ) return self._router_workbook_fleet_view_from_rows(row_views, source_label="Pasted inventory") def _replacements_result(analysis: Dict[str, Any]) -> str: match = _as_dict(analysis.get("match")) replacements = _as_dict(analysis.get("replacements")) lifecycle = _as_dict(analysis.get("_replacement_subject_lifecycle")) requested_model = _norm(query.device_texts[0] if query.device_texts else "") subject_manufacturer = _norm(match.get("manufacturer_group")) replacement_evidence = self._router_workbook_replacement_evidence_from_analysis( core, analysis, requested_model=requested_model, prefer_5g_target=bool(re.search(r"\b5g\b", query.normalized_message, flags=re.IGNORECASE)), resolution_mode=str(analysis.get("_resolution_mode") or "exact"), ) def _replacement_row_key(row: Dict[str, Any]) -> str: if not isinstance(row, dict): return "" return ( _compact_model(row.get("replacement_product_key")) or _compact_model(row.get("replacement_id")) or _compact_model(row.get("replacement_display")) or _compact_model(_display_name(row)) ) def _dedupe_replacement_rows(rows: Sequence[Dict[str, Any]]) -> List[Dict[str, Any]]: deduped: List[Dict[str, Any]] = [] notes_by_key: Dict[str, List[str]] = {} seen: set[str] = set() for row in rows: item = _as_dict(row) key = _replacement_row_key(item) if not key: continue if key not in seen: deduped.append(item) seen.add(key) notes_by_key[key] = [] same_manufacturer = _replacement_row_same_manufacturer(item) for value in ( item.get("mapping_type"), item.get("replacement_class"), item.get("authority_level"), ): note = _norm(value) low_note = note.lower() if same_manufacturer and "cross_manufacturer" in low_note: continue if (not same_manufacturer) and "same_manufacturer" in low_note: continue if note and note not in notes_by_key[key]: notes_by_key[key].append(note) merged: List[Dict[str, Any]] = [] for item in deduped: key = _replacement_row_key(item) merged.append( { **item, "_lane_notes": notes_by_key.get(key, []), } ) return merged def _replacement_note(item: Dict[str, Any], default: str) -> str: if fallback_source: mapping_type = _norm(item.get("mapping_type") or "").lower() replacement_class = _norm(item.get("replacement_class") or "").lower() if "legacy_5g_replacement" in mapping_type: return "Legacy lifecycle mapped same-manufacturer path" if "same_manufacturer_5g_backup" in mapping_type or "same_manufacturer_backup" in replacement_class: return "Current same-manufacturer fallback option" if "cross_vendor_5g_backup" in mapping_type or "cross_vendor_backup" in replacement_class: return "Current cross-vendor fallback option" notes = [str(x) for x in list(item.get("_lane_notes") or []) if _norm(x)] if notes: return "; ".join(notes[:3]) return _norm(item.get("mapping_type") or item.get("replacement_class") or item.get("authority_level")) or default def _same_brand_empty_note() -> str: if bool(replacements.get("no_replacement")): return ( "The workbook marks no direct same-manufacturer replacement for this router." if not fallback_source else "The legacy CSV does not list a direct same-manufacturer replacement for this router." ) if primary_rows: return ( "No separate current same-manufacturer backup lane is listed beyond the primary path above." if not fallback_source else "No separate current same-manufacturer fallback lane is listed beyond the primary path above." ) if int(replacements.get("review_blocked_count") or 0) > 0: return ( "Same-manufacturer candidates exist, but none are current app-ready and auto-safe yet." if not fallback_source else "Legacy same-manufacturer candidates exist, but none are current app-ready and auto-safe yet." ) if int(replacements.get("historical_only_suppressed_count") or 0) > 0: return ( "Only older or historical same-manufacturer rows were found, so there is no current workbook-ready same-brand path." if not fallback_source else "Only older or historical same-manufacturer rows were found in legacy mapping, so there is no current sourced same-brand path." ) if show_sourced_fallback_rows: return "No current workbook-ready same-manufacturer lane is listed; the sourced fallback guidance stays separate below." if int(replacements.get("candidate_count") or 0) > 0: return ( "No current same-manufacturer path is workbook-ready, so the move-forward lane relies on the backup path below." if not fallback_source else "No current same-manufacturer fallback is available, so the move-forward lane relies on the backup path below." ) return ( "No current same-manufacturer mapping is listed in the workbook." if not fallback_source else "No current same-manufacturer mapping is listed in the legacy fallback data." ) def _backup_empty_note() -> str: if bool(replacements.get("no_replacement")): return ( "The workbook does not list a cross-vendor backup for this router." if not fallback_source else "The legacy catalog does not list a cross-vendor backup for this router." ) if int(replacements.get("review_blocked_count") or 0) > 0: return ( "Backup rows exist, but none are current app-ready and auto-safe yet." if not fallback_source else "Legacy backup rows exist, but none are current app-ready and auto-safe yet." ) return "No backup path is flagged app-ready." if not fallback_source else "No backup path is flagged app-ready in the fallback data." def _fallback_is_same_vendor(model_text: str) -> bool: catalog_row = _fallback_catalog_row(model_text) if not catalog_row: return False manufacturer_group = _norm(catalog_row.get("manufacturer_group")) return bool( manufacturer_family_key(subject_manufacturer) and manufacturer_family_key(manufacturer_group) and manufacturer_family_key(manufacturer_group) == manufacturer_family_key(subject_manufacturer) ) def _fallback_is_same_vendor_5g(model_text: str) -> bool: if not _fallback_is_same_vendor(model_text): return False catalog_row = _fallback_catalog_row(model_text) cellular = _norm(catalog_row.get("cellular_gen_norm")).upper() return "5G" in cellular def _replacement_row_is_5g(row: Dict[str, Any]) -> bool: product_key = _norm(row.get("replacement_product_key")) detail = _as_dict(core.get_catalog_device_details_by_key(product_key=product_key)) if product_key else {} features = _as_dict(detail.get("features")) cellular = _norm(features.get("cellular_gen_norm")).upper() return "5G" in cellular def _fallback_catalog_row(model_text: str) -> Dict[str, Any]: def _doc_flag_enabled(value: Any) -> bool: return str(value or "").strip().lower() in {"yes", "true", "1", "y"} model_name = _norm(model_text) if not model_name: return {} compact = _compact_model(model_name) fact_key = self._lookup_router_fact_key(model_name) or self._lookup_router_fact_key(compact) lifecycle_key = self._lookup_router_lifecycle_key_relaxed(model_name) or self._lookup_router_lifecycle_key_relaxed(compact) product_row = _as_dict(self._router_fact_rows.get(fact_key or "", {})) lifecycle_row = _as_dict(self._router_lifecycle_rows.get(lifecycle_key or "", {})) if product_row: return { "display_name": _display_name(product_row) or model_name, "manufacturer_group": _norm(product_row.get("manufacturer") or product_row.get("manufacturer_group")), "cellular_gen_norm": _norm(product_row.get("cell_gen") or product_row.get("cellular_gen_norm")), "wifi_norm": _doc_flag_enabled(product_row.get("wifi")), "rugged_norm": _doc_flag_enabled(product_row.get("ruggedization")), "battery_norm": _doc_flag_enabled(product_row.get("battery")), "status": _norm(lifecycle_row.get("status")), } normalized = _as_dict(core.normalize_catalog_device(manufacturer_text="", product_text=model_name)) if (not normalized.get("ok")) and str(normalized.get("error") or "") == "ambiguous_product": collapsed = self._router_workbook_collapse_family_ambiguity(model_name, normalized) if collapsed: product_key = str(collapsed.get("product_key") or "").strip() detail = _as_dict(core.get_catalog_device_details_by_key(product_key=product_key)) if product_key else {} product = _as_dict(detail.get("product")) or collapsed features = _as_dict(detail.get("features")) lifecycle_row = _as_dict(detail.get("lifecycle")) return { "display_name": _display_name(product) or _display_name(collapsed) or model_name, "manufacturer_group": _norm(product.get("manufacturer_group") or collapsed.get("manufacturer_group")), "cellular_gen_norm": _norm(features.get("cellular_gen_norm")), "wifi_norm": bool(features.get("wifi_norm")), "rugged_norm": bool(features.get("rugged_norm")), "battery_norm": bool(features.get("battery_norm")), "status": _status_label(collapsed, lifecycle_row) if lifecycle_row else _norm(collapsed.get("status_bucket")), "_family_collapsed": True, } if not normalized.get("ok"): return {} resolved = _as_dict(normalized.get("match")) product_key = str(resolved.get("product_key") or "").strip() detail = _as_dict(core.get_catalog_device_details_by_key(product_key=product_key)) if product_key else {} product = _as_dict(detail.get("product")) or resolved features = _as_dict(detail.get("features")) lifecycle_row = _as_dict(detail.get("lifecycle")) return { "display_name": _display_name(product) or _display_name(resolved) or model_name, "manufacturer_group": _norm(product.get("manufacturer_group") or resolved.get("manufacturer_group")), "cellular_gen_norm": _norm(features.get("cellular_gen_norm")), "wifi_norm": bool(features.get("wifi_norm")), "rugged_norm": bool(features.get("rugged_norm")), "battery_norm": bool(features.get("battery_norm")), "status": _status_label(resolved, lifecycle_row) if lifecycle_row else "", } def _fallback_lineage_note(model_text: str, *, include_manufacturer: bool = True) -> str: catalog_row = _fallback_catalog_row(model_text) if not catalog_row: return "lifecycle mapping only; workbook feature validation still needs an exact follow-up" manufacturer_group = _norm(catalog_row.get("manufacturer_group")) lineage = "" subject_family_key = manufacturer_family_key(subject_manufacturer) manufacturer_family = manufacturer_family_key(manufacturer_group) if subject_family_key and manufacturer_family: if manufacturer_family == subject_family_key: lineage = "same-vendor path" else: lineage = ( f"cross-vendor path from {subject_manufacturer} to {manufacturer_group}" if include_manufacturer else "cross-vendor path" ) fit_bits: List[str] = [] if include_manufacturer and manufacturer_group: fit_bits.append(manufacturer_group) if lineage: fit_bits.append(lineage) cellular = _norm(catalog_row.get("cellular_gen_norm")) if cellular: fit_bits.append(cellular) if bool(catalog_row.get("rugged_norm")): fit_bits.append("ruggedized") if bool(catalog_row.get("wifi_norm")): fit_bits.append("Wi-Fi") if bool(catalog_row.get("battery_norm")): fit_bits.append("battery-flagged") status = _norm(catalog_row.get("status")) if status: fit_bits.append(status) if bool(catalog_row.get("_family_collapsed")): fit_bits.append("family-level workbook context") if not fit_bits: return "lifecycle mapping only; workbook feature validation still needs an exact follow-up" return "; ".join(fit_bits[:5]) def _fallback_display(model_text: str) -> str: raw_model = _norm(model_text) catalog_row = _fallback_catalog_row(model_text) if catalog_row: display = str(catalog_row.get("display_name") or model_text) raw_compact = _compact_model(raw_model) display_compact = _compact_model(display) if ( raw_model and display and raw_compact and display_compact and (raw_compact in display_compact or display_compact.startswith(raw_compact)) and len(raw_model) + 10 < len(display) ): return raw_model return display return str(model_text or "") def _fallback_choice_key(model_text: str) -> str: return _compact_model(_fallback_display(model_text) or model_text) def _fallback_recommendation_label(model_text: str, *, include_manufacturer: bool = False) -> str: display = _fallback_display(model_text) if not include_manufacturer: return display catalog_row = _fallback_catalog_row(model_text) manufacturer_group = _norm(catalog_row.get("manufacturer_group")) if catalog_row else "" manufacturer_family = manufacturer_family_key(manufacturer_group) display_family = manufacturer_family_key(display) if manufacturer_group and manufacturer_family and manufacturer_family != display_family: return f"{manufacturer_group} {display}".strip() return display def _fallback_tradeoff(model_text: str, *, bridge_mode: bool = False) -> str: catalog_row = _fallback_catalog_row(model_text) notes: List[str] = [] if bridge_mode: notes.append("keeps a 4G bridge in place rather than a full 5G refresh") else: cellular = _norm(catalog_row.get("cellular_gen_norm") if catalog_row else "").upper() if "5G" in cellular: notes.append("best fit when you need a current 5G move-forward path") elif ("4G" in cellular) or ("LTE" in cellular): notes.append("best fit when you need a current LTE/4G move-forward path") else: notes.append("best fit when you need the closest current move-forward path") if not catalog_row: notes.append("exact feature fit still needs workbook follow-up") elif bool(catalog_row.get("_family_collapsed")): notes.append("only family-level workbook context is available, so the final SKU/package still needs confirmation") else: notes.append("the final SKU/package still needs confirmation before quote lock") return "; ".join(notes[:2]) def _replacement_fit_note(item: Dict[str, Any], *, include_manufacturer: bool = True) -> str: product_key = _norm(item.get("replacement_product_key")) detail = _as_dict(core.get_catalog_device_details_by_key(product_key=product_key)) if product_key else {} product = _as_dict(detail.get("product")) features = _as_dict(detail.get("features")) lifecycle_row = _as_dict(detail.get("lifecycle")) if not product: fallback_row = _fallback_catalog_row( _norm(item.get("replacement_display")) or _norm(item.get("replacement_id")) ) if not fallback_row: return "" product = { "manufacturer_group": fallback_row.get("manufacturer_group"), "status_bucket": fallback_row.get("status"), } features = { "cellular_gen_norm": fallback_row.get("cellular_gen_norm"), "wifi_norm": fallback_row.get("wifi_norm"), "rugged_norm": fallback_row.get("rugged_norm"), "battery_norm": fallback_row.get("battery_norm"), } bits: List[str] = [] manufacturer_group = _norm(product.get("manufacturer_group") or item.get("target_manufacturer_group")) lineage = "" subject_family_key = manufacturer_family_key(subject_manufacturer) manufacturer_family = manufacturer_family_key(manufacturer_group) if subject_family_key and manufacturer_family: if manufacturer_family == subject_family_key: lineage = "same-manufacturer path" else: lineage = ( f"cross-vendor path from {subject_manufacturer} to {manufacturer_group}" if include_manufacturer else "cross-vendor path" ) if include_manufacturer and manufacturer_group: bits.append(manufacturer_group) same_manufacturer = _replacement_row_same_manufacturer(item) if same_manufacturer: bits.append("same-manufacturer fallback lane" if fallback_source else "same-manufacturer workbook lane") elif bool(item.get("backup_app_ready_flag")): bits.append("cross-vendor backup lane" if fallback_source else "cross-vendor workbook lane") if lineage: bits.append(lineage) status = _norm( product.get("status_bucket") or item.get("target_status_bucket") or (lifecycle_row and _status_label(product, lifecycle_row)) ) if status: bits.append(status) cellular = _norm(features.get("cellular_gen_norm")) if cellular: bits.append(cellular) _, _, total_ports_text = _details_port_values(features) if total_ports_text not in {"", "0", "Not listed", "Needs exact SKU/package"}: bits.append( total_ports_text if "ethernet ports" in total_ports_text.lower() else f"{total_ports_text} Ethernet ports" ) if _feature_enabled(features, "rugged_norm"): bits.append("ruggedized") if _feature_enabled(features, "wifi_norm"): bits.append("Wi-Fi") if _feature_enabled(features, "battery_norm"): bits.append("battery flag in workbook") return "; ".join(bits[:6]) def _replacement_row_same_manufacturer(item: Dict[str, Any]) -> bool: if bool(item.get("same_manufacturer")): return True product_key = _norm(item.get("replacement_product_key")) if not product_key: return False detail = _as_dict(core.get_catalog_device_details_by_key(product_key=product_key)) product = _as_dict(detail.get("product")) manufacturer_group = _norm(product.get("manufacturer_group") or item.get("target_manufacturer_group") or item.get("manufacturer_group")) return bool( manufacturer_family_key(subject_manufacturer) and manufacturer_family_key(manufacturer_group) and manufacturer_family_key(manufacturer_group) == manufacturer_family_key(subject_manufacturer) ) primary_rows = _dedupe_replacement_rows([row for row in list(replacements.get("primary_candidates") or []) if isinstance(row, dict)]) same_brand_backup_rows = _dedupe_replacement_rows( [row for row in list(replacements.get("same_manufacturer_backup_replacements") or []) if isinstance(row, dict)] ) backup_rows = _dedupe_replacement_rows([row for row in list(replacements.get("backup_replacements") or []) if isinstance(row, dict)]) backup_same_manufacturer_rows = _dedupe_replacement_rows([row for row in backup_rows if _replacement_row_same_manufacturer(row)]) backup_cross_vendor_rows = _dedupe_replacement_rows([row for row in backup_rows if not _replacement_row_same_manufacturer(row)]) same_manufacturer_backup_rows = _dedupe_replacement_rows(same_brand_backup_rows + backup_same_manufacturer_rows) historical_rows = _dedupe_replacement_rows([row for row in list(replacements.get("historical_only_replacements") or []) if isinstance(row, dict)]) lifecycle_fallback_texts = [ _norm(event.get("recommended_replacement_text")) for event in list(lifecycle.get("events") or []) if isinstance(event, dict) and _norm(event.get("recommended_replacement_text")) ] lifecycle_fallback_texts = list(dict.fromkeys(lifecycle_fallback_texts)) review_reasons = [str(x) for x in list(analysis.get("manual_review_reasons") or []) if _norm(x)] family_safe_note = _norm(analysis.get("_family_safe_note") or "") legacy_lifecycle = _as_dict(analysis.get("_replacement_legacy_lifecycle")) legacy_4g = _norm(legacy_lifecycle.get("alt4g")) legacy_5g = _norm(legacy_lifecycle.get("rep5g")) fallback_source = str(analysis.get("_replacement_source_mode") or "").strip().lower() == "lifecycle_fallback" asks_5g_replacement = bool(re.search(r"\b5g\b", query.normalized_message, flags=re.IGNORECASE)) requested_5g_row = next((row for row in same_brand_backup_rows if _replacement_row_is_5g(row)), {}) if not requested_5g_row: requested_5g_row = next((row for row in backup_same_manufacturer_rows if _replacement_row_is_5g(row)), {}) requested_5g_path = "" if asks_5g_replacement: requested_5g_path = _display_name(requested_5g_row) or legacy_5g primary_has_5g = any(_replacement_row_is_5g(row) for row in primary_rows[:3]) requested_5g_priority = bool(asks_5g_replacement and requested_5g_path and (not primary_has_5g)) asks_backup_path = any( token in query.normalized_message for token in ("backup path", "backup paths", "backup second", "cross-vendor backup") ) asks_direct_status_replacement_backup = bool( any( token in query.normalized_message for token in ("still current", "is it current", "whether", "what should replace", "what should replace it") ) and any( token in query.normalized_message for token in ("cross-vendor backup", "same-brand is blocked", "same brand is blocked") ) ) asks_cleaner_path = any( token in query.normalized_message for token in ("which replacement path is cleaner", "cleaner path", "cleaner for") ) asks_workbook_primary_current = any( token in query.normalized_message for token in ("primary row current", "workbook primary row", "primary replacement row") ) asks_package_confirmation = any( token in query.normalized_message for token in ( "exact variant", "exact sku", "exact package", "sku/package", "variant/package", "package still matters", "variant still matters", "sku still matters", ) ) suppress_family_branding = bool(match.get("_family_collapsed") or analysis.get("_family_safe") or family_safe_note) has_only_lifecycle_fallback = fallback_source or ((not primary_rows) and (not backup_rows) and bool(lifecycle_fallback_texts or legacy_5g or legacy_4g)) lifecycle_fallback_primary = lifecycle_fallback_texts[0] if lifecycle_fallback_texts else "" lifecycle_fallback_primary_key = _fallback_choice_key(lifecycle_fallback_primary) legacy_5g_key = _fallback_choice_key(legacy_5g) legacy_4g_key = _fallback_choice_key(legacy_4g) show_legacy_5g_recommendation = bool(legacy_5g) and ( asks_5g_replacement or (not lifecycle_fallback_primary and not primary_rows and not same_manufacturer_backup_rows) ) if show_legacy_5g_recommendation and legacy_5g_key and legacy_5g_key == lifecycle_fallback_primary_key: show_legacy_5g_recommendation = False show_legacy_4g_recommendation = bool(legacy_4g) if show_legacy_4g_recommendation and legacy_4g_key and legacy_4g_key == lifecycle_fallback_primary_key: show_legacy_4g_recommendation = False if show_legacy_4g_recommendation and legacy_4g_key and legacy_4g_key == legacy_5g_key: show_legacy_4g_recommendation = False resolved_subject_label = _norm( match.get("_canonical_resolved_label") or _as_dict(analysis.get("product")).get("_canonical_resolved_label") or match.get("_canonical_display_name") or _as_dict(analysis.get("product")).get("_canonical_display_name") or match.get("display_name") or match.get("product_id") or "" ) lines = [ ( f"Sourced lifecycle fallback replacement paths for `{resolved_subject_label or _router_subject_label(match) or _display_name(match)}`:" if has_only_lifecycle_fallback else f"Workbook-backed replacement paths for `{resolved_subject_label or _router_subject_label(match) or _display_name(match)}`:" ), "", ] resolved_model = _norm(resolved_subject_label or match.get("display_name") or match.get("product_id") or _router_subject_label(match) or _display_name(match)) if bool(match.get("_family_collapsed")) and requested_model: lines.extend( [ f"Requested model `{requested_model}` matched multiple workbook rows, so the replacement lanes below are anchored to the closest workbook-safe family match `{resolved_model or requested_model}`.", "", ] ) if requested_model and resolved_model and (_compact_model(requested_model) != _compact_model(resolved_model)): lines.extend( [ f"Normalized requested model `{requested_model}` to workbook match `{resolved_model}` before applying the replacement lanes.", f"If that normalization is wrong, stop here and send the exact device label before using these recommendations.", "", ] ) if family_safe_note: lines.extend([family_safe_note, ""]) if asks_package_confirmation: if suppress_family_branding or has_only_lifecycle_fallback: lines.extend( [ "Exact variant/package still matters: yes. This answer stays family-safe, so confirm the final SKU/package before quote lock.", "", ] ) else: lines.extend( [ "Exact variant/package still matters: not for the replacement lane itself, but confirm the final SKU/package before quoting feature-sensitive details.", "", ] ) if (not bool(query.current_only)) or any( token in query.normalized_message for token in ("legacy", "older", "old", "historical", "similar") ): lines.extend( [ "Legacy context stays in scope for the subject device, but the move-forward recommendations below remain limited to current workbook-ready rows.", "", ] ) lines.append("Legacy context in scope:") subject_status = _norm(match.get("status_bucket")) or _norm(lifecycle.get("status")) subject_bits = [bit for bit in [resolved_subject_label or _display_name(match), subject_manufacturer, subject_status] if _norm(bit)] if subject_bits: status_text = "; ".join(subject_bits[1:]) if status_text: status_text = status_text.replace("EOS", "workbook status `EOS`").replace("EOL", "workbook status `EOL`") lines.append(f"- Subject legacy anchor: `{subject_bits[0]}` ({status_text})." if status_text else f"- Subject legacy anchor: `{subject_bits[0]}`.") if historical_rows: for row in historical_rows[:3]: note = _replacement_fit_note(row) or _replacement_note(row, "Historical-only") lines.append(f"- Older workbook-similar row: `{_display_name(row)}` ({note}).") else: lines.append("- Additional older workbook-similar rows were not surfaced separately for this family.") lines.append("") lane_summary = ( f"Current workbook-ready lanes: {len(primary_rows)} same-manufacturer primary, " f"{len(same_manufacturer_backup_rows)} same-manufacturer backup, {len(backup_cross_vendor_rows)} cross-vendor" ) suppressed_bits: List[str] = [] if historical_rows: suppressed_bits.append(f"{len(historical_rows)} historical-only suppressed") blocked_count = int(replacements.get("review_blocked_count") or 0) if blocked_count > 0: suppressed_bits.append(f"{blocked_count} manual-review blocked") if fallback_source: lane_summary = "Current workbook-ready lanes: none. Sourced lifecycle fallback guidance is listed separately below" if suppressed_bits: lane_summary += f"; suppressed: {', '.join(suppressed_bits)}" elif has_only_lifecycle_fallback: lane_summary = "Current workbook-ready lanes: none. Sourced lifecycle fallback guidance is listed separately below" if suppressed_bits: lane_summary += f"; suppressed: {', '.join(suppressed_bits)}" elif suppressed_bits: lane_summary += f"; suppressed: {', '.join(suppressed_bits)}" lane_summary += "." if not asks_direct_status_replacement_backup: lines.extend([lane_summary, ""]) if asks_workbook_primary_current: if primary_rows: lines.append("Workbook primary row current: yes. A current workbook-ready primary replacement row is listed above.") else: lines.append("Workbook primary row current: no. The workbook does not expose a current workbook-ready primary replacement row for this router.") lines.append("") if asks_cleaner_path: cleaner_label = "No clean current path yet" cleaner_reason = "the workbook does not expose a current app-ready same-manufacturer or cross-vendor lane yet." if primary_rows or same_manufacturer_backup_rows: cleaner_label = "Same-manufacturer path" cleaner_reason = "the workbook already exposes a same-manufacturer move-forward lane, so there is no need to jump to cross-vendor first." elif backup_cross_vendor_rows or fallback_source or has_only_lifecycle_fallback: cleaner_label = "Cross-vendor backup" cleaner_reason = "no current workbook-ready same-manufacturer lane is listed, while the move-forward evidence that does exist is in the backup/cross-vendor lane." lines.append(f"Cleaner move-forward lane: `{cleaner_label}`.") lines.append(f"Why: {cleaner_reason}") lines.append("") recommended_path_label = _norm(replacement_evidence.get("recommended_path_label") or "") recommended_path_value = _norm(replacement_evidence.get("recommended_path_value") or "") ordered_paths = [row for row in list(replacement_evidence.get("ordered_paths") or []) if isinstance(row, dict)] requested_backup_path = "" if asks_backup_path: requested_backup_path = next( ( _norm(path.get("value")) for path in ordered_paths if ("backup path" in _norm(path.get("label")).lower()) and _norm(path.get("value")) ), "", ) if not requested_backup_path: requested_backup_path = _display_name(backup_cross_vendor_rows[0]) if backup_cross_vendor_rows else "" if not requested_backup_path: requested_backup_path = legacy_4g or legacy_5g direct_same_brand_path = _display_name(primary_rows[0]) if primary_rows else "" if not direct_same_brand_path: direct_same_brand_path = _display_name(same_manufacturer_backup_rows[0]) if same_manufacturer_backup_rows else "" if not direct_same_brand_path and recommended_path_label.lower().startswith("same"): direct_same_brand_path = recommended_path_value if not direct_same_brand_path: direct_same_brand_path = legacy_5g or legacy_4g direct_cross_vendor_backup = _display_name(backup_cross_vendor_rows[0]) if backup_cross_vendor_rows else "" if not direct_cross_vendor_backup and requested_backup_path: direct_cross_vendor_backup = requested_backup_path if not direct_cross_vendor_backup: direct_cross_vendor_backup = next( ( _norm(path.get("value")) for path in ordered_paths if ("cross-vendor" in _norm(path.get("label")).lower()) and _norm(path.get("value")) ), "", ) direct_status = _status_label(match, lifecycle) direct_currentness = "still current" if any(token in direct_status.lower() for token in ("current", "active")) else "not current" if asks_direct_status_replacement_backup: lines.append(f"Current status: `{resolved_subject_label or resolved_model}` is `{direct_status}` ({direct_currentness}).") if direct_same_brand_path: lines.append(f"Same-brand replacement: `{direct_same_brand_path}`.") else: lines.append("Same-brand replacement: no current workbook-ready same-brand lane is listed.") if direct_cross_vendor_backup: lines.append(f"Safest cross-vendor backup if same-brand is blocked: `{direct_cross_vendor_backup}`.") else: lines.append("Safest cross-vendor backup if same-brand is blocked: no current workbook-ready cross-vendor backup lane is listed.") lines.append("") if requested_5g_priority: lines.append( "Requested 5G current path: the best family-safe 5G move-forward option is prioritized below, and the workbook-ready bridge is kept separately as a 4G step-down lane." ) elif asks_backup_path and requested_backup_path: lines.append( f"Requested backup path: `{requested_backup_path}`." ) elif asks_backup_path: lines.append("Requested backup path: no current backup lane is workbook-ready yet.") elif (fallback_source or has_only_lifecycle_fallback) and (not asks_direct_status_replacement_backup): lines.append("Best sourced fallback path: no current workbook-ready replacement lane is listed, so use the sourced lifecycle guidance below as the safest move-forward starting point.") else: headline_label = "" headline_value = "" headline_reason = "" if primary_rows: headline_label = "same-manufacturer current path" headline_value = _display_name(primary_rows[0]) headline_reason = "shared workbook evidence ordering" elif same_manufacturer_backup_rows: headline_label = "same-manufacturer backup path" headline_value = _display_name(same_manufacturer_backup_rows[0]) headline_reason = "shared workbook evidence ordering" elif backup_cross_vendor_rows: headline_label = "cross-vendor backup path" headline_value = _display_name(backup_cross_vendor_rows[0]) headline_reason = "shared workbook evidence ordering" elif recommended_path_label and recommended_path_value: headline_label = recommended_path_label headline_value = recommended_path_value headline_reason = ( "shared lifecycle fallback evidence ordering" if str(replacement_evidence.get("replacement_source_mode") or "").strip().lower() == "lifecycle_fallback" else "shared workbook evidence ordering" ) if asks_direct_status_replacement_backup: headline_label = "" headline_value = "" if headline_label and headline_value: lines.append(f"Recommended current path: `{headline_label}` is `{headline_value}` from the {headline_reason}.") else: lines.append("Recommended current path: the workbook does not currently expose an app-ready replacement recommendation for this router.") lines.append("") if ordered_paths and (not asks_direct_status_replacement_backup): lines.append("Ordered move-forward evidence:") for index, path in enumerate(ordered_paths[:4], start=1): label = _norm(path.get("label") or f"Path {index}") value = _norm(path.get("value") or "Not listed") note = _norm(path.get("note") or "") if fallback_source or has_only_lifecycle_fallback: label_lower = label.lower() if label_lower.startswith("same-brand path") or label_lower.startswith("same manufacturer"): label = "Sourced same-brand fallback" elif "backup path" in label_lower: label = "Sourced backup fallback" summary = f"{index}. `{label}` -> `{value}`" if note: summary += f" ({note})" lines.append(summary) lines.append("") show_sourced_fallback_rows = bool(fallback_source or has_only_lifecycle_fallback) if not show_sourced_fallback_rows: show_sourced_fallback_rows = not bool(primary_rows or same_manufacturer_backup_rows or backup_cross_vendor_rows) if show_sourced_fallback_rows: lines.append("Fallback note: the lifecycle rows below are sourced migration hints, not workbook-ready replacement lanes, so feature fit still needs validation before quoting.") lines.append("") lines.append("Fallback recommendations:") if lifecycle_fallback_primary: lines.append( f"- Best sourced current path: `{_fallback_display(lifecycle_fallback_primary)}`. {_fallback_lineage_note(lifecycle_fallback_primary, include_manufacturer=False)}. Tradeoff: {_fallback_tradeoff(lifecycle_fallback_primary)}." ) if show_legacy_5g_recommendation: lines.append(f"- Best sourced 5G path: `{_fallback_display(legacy_5g)}`. {_fallback_lineage_note(legacy_5g, include_manufacturer=False)}. Tradeoff: {_fallback_tradeoff(legacy_5g)}.") if show_legacy_4g_recommendation: lines.append(f"- Best sourced 4G bridge: `{_fallback_display(legacy_4g)}`. {_fallback_lineage_note(legacy_4g, include_manufacturer=False)}. Tradeoff: {_fallback_tradeoff(legacy_4g, bridge_mode=True)}.") lines.append("") lines.extend( [ "| Lane | Recommendation | Notes |", "| --- | --- | --- |", ] ) emitted_requested_5g_path = False requested_5g_row_key = _replacement_row_key(requested_5g_row) if (asks_5g_replacement and requested_5g_row) else "" same_manufacturer_backup_render_rows = [ row for row in same_manufacturer_backup_rows if _replacement_row_key(row) != requested_5g_row_key ] cross_vendor_backup_render_rows = [ row for row in backup_cross_vendor_rows if _replacement_row_key(row) != requested_5g_row_key ] if requested_5g_priority and requested_5g_row: requested_5g_label = ( "Requested 5G same-brand path" if _replacement_row_same_manufacturer(requested_5g_row) else "Requested 5G path" ) requested_5g_note = _replacement_note(requested_5g_row, "Requested 5G") requested_5g_fit = _replacement_fit_note(requested_5g_row, include_manufacturer=not suppress_family_branding) requested_5g_combined_note = "; ".join(part for part in [requested_5g_note, requested_5g_fit] if _norm(part)) lines.append( f"| {requested_5g_label} | {_md_cell(_display_name(requested_5g_row))} | {_md_cell(requested_5g_combined_note)} |" ) emitted_requested_5g_path = True elif requested_5g_priority and legacy_5g: legacy_5g_label = "Requested 5G same-brand path" if _fallback_is_same_vendor_5g(legacy_5g) else "Requested 5G path" lines.append( f"| {legacy_5g_label} | {_md_cell(_fallback_recommendation_label(legacy_5g, include_manufacturer=False))} | " f"{_md_cell('Lifecycle-mapped 5G move-forward path; ' + _fallback_lineage_note(legacy_5g, include_manufacturer=False) + '; tradeoff: ' + _fallback_tradeoff(legacy_5g))} |" ) emitted_requested_5g_path = True if primary_rows: for row in primary_rows[:3]: note = _replacement_note(row, "Primary") fit_note = _replacement_fit_note(row, include_manufacturer=not suppress_family_branding and not fallback_source and not has_only_lifecycle_fallback) bridge_prefix = "" if requested_5g_priority and not _replacement_row_is_5g(row): bridge_prefix = "Workbook-ready 4G bridge while the 5G move-forward path stays listed separately" combined_note = "; ".join(part for part in [bridge_prefix, note, fit_note] if _norm(part)) if fallback_source: lane_label = "Sourced same-manufacturer bridge" if requested_5g_priority and not _replacement_row_is_5g(row) else "Sourced same-manufacturer path" else: lane_label = "Official same-manufacturer bridge" if requested_5g_priority and not _replacement_row_is_5g(row) else "Same manufacturer" lines.append( f"| {lane_label} | {_md_cell(_display_name(row))} | {_md_cell(combined_note)} |" ) elif show_sourced_fallback_rows and lifecycle_fallback_texts and _fallback_is_same_vendor(lifecycle_fallback_texts[0]): sourced_same_brand = lifecycle_fallback_texts[0] lines.append( f"| Sourced same-manufacturer path | {_md_cell(_fallback_display(sourced_same_brand))} | " f"{_md_cell('Lifecycle mapping fallback only; ' + _fallback_lineage_note(sourced_same_brand, include_manufacturer=False) + '; tradeoff: ' + _fallback_tradeoff(sourced_same_brand))} |" ) else: lines.append( f"| {'Sourced same-manufacturer path' if fallback_source else 'Same manufacturer'} | None workbook-ready | {_md_cell(_same_brand_empty_note())} |" ) if same_manufacturer_backup_render_rows: for row in same_manufacturer_backup_render_rows[:3]: note = _replacement_note(row, "Backup") fit_note = _replacement_fit_note(row, include_manufacturer=not suppress_family_branding and not fallback_source and not has_only_lifecycle_fallback) combined_note = "; ".join(part for part in [note, fit_note] if _norm(part)) lane_label = "Sourced same-manufacturer backup" if fallback_source else "Same-manufacturer backup" lines.append( f"| {lane_label} | {_md_cell(_display_name(row))} | {_md_cell(combined_note)} |" ) elif not (show_sourced_fallback_rows and (lifecycle_fallback_primary or show_legacy_5g_recommendation or show_legacy_4g_recommendation)): lines.append( f"| {'Sourced same-manufacturer backup' if fallback_source else 'Same-manufacturer backup'} | None listed | {_md_cell(_same_brand_empty_note())} |" ) if cross_vendor_backup_render_rows: for row in cross_vendor_backup_render_rows[:3]: note = _replacement_note(row, "Backup") fit_note = _replacement_fit_note(row, include_manufacturer=not suppress_family_branding and not fallback_source and not has_only_lifecycle_fallback) combined_note = "; ".join(part for part in [note, fit_note] if _norm(part)) lane_label = "Sourced cross-vendor backup" if fallback_source else "Cross-vendor backup" lines.append( f"| {lane_label} | {_md_cell(_display_name(row))} | {_md_cell(combined_note)} |" ) else: lines.append( f"| {'Sourced cross-vendor backup' if fallback_source else 'Cross-vendor backup'} | None listed | {_md_cell(_backup_empty_note())} |" ) if show_sourced_fallback_rows and lifecycle_fallback_primary: lifecycle_fallback_primary_display = _fallback_display(lifecycle_fallback_primary) sourced_same_brand_display = ( _fallback_display(lifecycle_fallback_texts[0]) if lifecycle_fallback_texts and _fallback_is_same_vendor(lifecycle_fallback_texts[0]) else "" ) if lifecycle_fallback_primary_display and sourced_same_brand_display and _compact_model(lifecycle_fallback_primary_display) == _compact_model(sourced_same_brand_display): lifecycle_fallback_primary = "" if show_sourced_fallback_rows and lifecycle_fallback_primary: lines.append( f"| Sourced lifecycle fallback | {_md_cell(_fallback_display(lifecycle_fallback_primary))} | {_md_cell('Lifecycle mapping fallback only; ' + _fallback_lineage_note(lifecycle_fallback_primary, include_manufacturer=False) + '; tradeoff: ' + _fallback_tradeoff(lifecycle_fallback_primary))} |" ) elif show_sourced_fallback_rows and (show_legacy_5g_recommendation or show_legacy_4g_recommendation): if show_legacy_5g_recommendation and not emitted_requested_5g_path: legacy_5g_label = "Sourced lifecycle 5G fallback" if asks_5g_replacement: legacy_5g_label = "Requested 5G same-brand path" if _fallback_is_same_vendor_5g(legacy_5g) else "Requested 5G path" lines.append( f"| {legacy_5g_label} | {_md_cell(_fallback_recommendation_label(legacy_5g, include_manufacturer=False))} | {_md_cell('Lifecycle mapping fallback only; ' + _fallback_lineage_note(legacy_5g, include_manufacturer=False) + '; tradeoff: ' + _fallback_tradeoff(legacy_5g))} |" ) if show_legacy_4g_recommendation and not requested_5g_priority: lines.append( f"| Sourced lifecycle 4G bridge | {_md_cell(_fallback_display(legacy_4g))} | {_md_cell('Lifecycle mapping fallback only; ' + _fallback_lineage_note(legacy_4g, include_manufacturer=False) + '; tradeoff: ' + _fallback_tradeoff(legacy_4g, bridge_mode=True))} |" ) if bool(replacements.get("no_replacement")): lines.append("| No replacement | Explicit workbook outcome | No direct replacement found. |") if historical_rows: lines.append("") lines.append("Historical-only rows were kept out of the live recommendation lane:") for row in historical_rows[:3]: lines.append(f"- `{_display_name(row)}`") if review_reasons: lines.append("") lines.append("Review notes:") lines.extend([f"- {note}" for note in review_reasons[:4]]) if primary_rows and not backup_rows and not fallback_source: lines.extend( [ "", "Cross-vendor backup note:", "- The workbook currently exposes a same-manufacturer move-forward lane, but no current cross-vendor backup lane is app-ready.", ] ) return "\n".join(lines) def _search_relaxed_filter_labels(search: Dict[str, Any]) -> List[str]: label_map = { "battery": "battery explicitness", "rugged": "ruggedized workbook flag", "indoor_outdoor": "placement fit", "wifi": "Wi-Fi", "cellular_generation": "cellular generation / 5G", "gnss": "GNSS/GPS", "poe": "PoE", "use_case_hint": "branch/vehicle use-case fit", } labels: List[str] = [] for plan in list(search.get("relaxed_plans") or []): if not isinstance(plan, (list, tuple)): continue for field_name in plan: label = label_map.get(str(field_name or "").strip(), str(field_name or "").strip()) if label and label not in labels: labels.append(label) return labels def _search_scope_notes() -> List[str]: notes: List[str] = [] if bool(query.search_filters.get("current_only", True)): notes.append("current workbook rows only") if query.search_filters.get("require_documented_rf") is True: notes.append("only rows with explicitly documented RF connector details") if query.search_filters.get("battery") is True: notes.append("rows with an explicit battery-related workbook flag") if query.search_filters.get("rugged") is True: notes.append("rows explicitly marked ruggedized") requested_cell = str(query.search_filters.get("cellular_generation") or "").strip().upper() if requested_cell: notes.append(f"{requested_cell} rows") min_ports = query.search_filters.get("min_total_ethernet_ports") if min_ports is not None: notes.append(f"{int(min_ports)}+ total Ethernet ports") return notes def _search_requires_review_item(item: Dict[str, Any]) -> bool: if not isinstance(item, dict): return False if bool(item.get("_battery_verification_only")): return True review_markers = ( "needs exact sku/package", "not clearly documented", "battery support is optional or not explicit in the workbook", "verification-only", "explicit battery-related workbook flag", ) for note in [str(note) for note in list(item.get("_gap_notes") or []) if _norm(note)]: low = note.lower() if any(marker in low for marker in review_markers): return True return False def _search_requires_review(search: Dict[str, Any]) -> bool: return any( _search_requires_review_item(item) for item in [row for row in list(search.get("matches") or []) if isinstance(row, dict)] ) def _search_result(search: Dict[str, Any]) -> str: matches = [row for row in list(search.get("matches") or []) if isinstance(row, dict)] exact_matches = [row for row in list(search.get("exact_matches") or []) if isinstance(row, dict)] near_matches = [row for row in list(search.get("near_matches") or []) if isinstance(row, dict)] excluded_noncurrent_matches = [row for row in list(search.get("excluded_noncurrent_matches") or []) if isinstance(row, dict)] search_current_only = bool(query.search_filters.get("current_only", True)) original_low = str(raw_message or message or "").lower() typo_battery = "bateries" in original_low requested_limit = max(1, int(query.limit or 3)) display_limit = max(1, min(5, int(query.limit or 3))) shown_matches = matches[:display_limit] soft_fields = {str(field) for field in list(search.get("soft_fields") or [])} global_relaxed_fields = { str(field) for plan in list(search.get("relaxed_plans") or []) if isinstance(plan, (list, tuple)) for field in plan } shown_legacy_count = sum( 1 for item in shown_matches if self._router_workbook_status_is_legacy(item.get("status_bucket")) ) has_legacy_matches = shown_legacy_count > 0 def _manufacturer_label(item: Dict[str, Any]) -> str: raw_label = _norm(item.get("manufacturer_group") or item.get("manufacturer") or "Unknown") return raw_label or "Unknown" def _search_display_name(item: Dict[str, Any]) -> str: display = _display_name(item) if not display: return "" compact = _compact_model(display) if compact and compact == display and display.isdigit(): manufacturer = _manufacturer_label(item) if manufacturer and manufacturer.lower() != "unknown": return f"{manufacturer} {display}" return f"Internal ID {display}" return display def _documented_rf_text(item: Dict[str, Any]) -> str: features = _as_dict(item.get("features")) raw_value = _norm( features.get("antennas_rf") or item.get("antennas_rf") or item.get("connector_summary") or "" ) if raw_value and self._router_workbook_has_explicit_rf_documentation({**item, "antennas_rf": raw_value}): return raw_value candidate_labels = [ _norm(item.get("product_id")), _norm(item.get("display_name")), _norm(item.get("product_key")), ] for label in candidate_labels: if not label: continue fact_key = self._lookup_router_fact_key(label) if not fact_key: continue fact_row = _as_dict(self._router_fact_rows.get(fact_key)) fact_value = _norm(fact_row.get("antennas_rf") or fact_row.get("connector_summary") or "") if fact_value and self._router_workbook_has_explicit_rf_documentation({**item, **fact_row, "antennas_rf": fact_value}): return fact_value return "Not clearly documented" def _fit_summary(item: Dict[str, Any]) -> str: features = _as_dict(item.get("features")) relaxed_fields = {str(field) for field in list(item.get("_relaxed_fields") or [])} if bool(item.get("_battery_verification_only")): return "battery support is optional or not explicit in the workbook; shown only as a verification row" if "Exact current match" not in str(item.get("_match_tier") or ""): relaxed_fields = relaxed_fields | global_relaxed_fields reasons: List[str] = [] min_ports = query.search_filters.get("min_total_ethernet_ports") if min_ports: reasons.append(f"meets the requested Ethernet minimum ({min_ports}+ ports)") cellular_generation = str(query.search_filters.get("cellular_generation") or "").strip() if cellular_generation: reasons.append(f"{cellular_generation} cellular") if query.search_filters.get("rugged") is True and "rugged" not in relaxed_fields and _feature_enabled(features, "rugged_norm"): reasons.append("ruggedized hardware") if query.search_filters.get("battery") is True and "battery" not in relaxed_fields and _feature_enabled(features, "battery_norm"): reasons.append("battery flag is present in the workbook") elif query.search_filters.get("battery") is True and "battery" in list(item.get("_relaxed_fields") or []): reasons.append("battery support is optional or not explicit in the workbook") if query.search_filters.get("wifi") is True and "wifi" not in relaxed_fields and _feature_enabled(features, "wifi_norm"): reasons.append("Wi-Fi") if query.search_filters.get("gnss") is True and "gnss" not in relaxed_fields and _feature_enabled(features, "gnss_norm"): reasons.append("GNSS/GPS") if query.search_filters.get("poe") is True and "poe" not in relaxed_fields and _feature_enabled(features, "poe_norm"): reasons.append("PoE") placement = str(query.search_filters.get("indoor_outdoor") or "").strip() if placement and placement == str(features.get("indoor_outdoor_norm") or "").strip().lower(): reasons.append(f"{placement} workbook fit") use_case_hint = str(query.search_filters.get("use_case_hint") or "").strip().lower() actual_use_case = str(features.get("use_case_norm") or "").strip().lower() if use_case_hint and actual_use_case and use_case_hint in actual_use_case: reasons.append(f"{use_case_hint} use-case fit") if query.search_filters.get("require_documented_rf") is True: rf_text = _documented_rf_text(item) if rf_text != "Not clearly documented": reasons.append("documented RF connector details") if not reasons: reasons.append( "current workbook-backed feature-safe match" if search_current_only else "workbook-backed feature-safe match" ) return "; ".join(reasons[:5]) def _tradeoff_summary(item: Dict[str, Any]) -> str: gap_notes = [str(note) for note in list(item.get("_gap_notes") or []) if _norm(note)] if gap_notes: return gap_notes[0] features = _as_dict(item.get("features")) relaxed_fields = {str(field) for field in list(item.get("_relaxed_fields") or [])} tradeoffs: List[str] = [] if not _feature_enabled(features, "battery_norm"): tradeoffs.append("No battery-backed option") requested_cellular = str(query.search_filters.get("cellular_generation") or "").strip().upper() actual_cellular = str(features.get("cellular_gen_norm") or "").strip().upper() if requested_cellular and (("cellular_generation" in relaxed_fields) or (actual_cellular and actual_cellular != requested_cellular)): tradeoffs.append(f"{requested_cellular} cellular was relaxed; fallback alternative only") if query.search_filters.get("wifi") is True and ("wifi" in relaxed_fields or not _feature_enabled(features, "wifi_norm")): tradeoffs.append("Wi-Fi requirement was relaxed; fallback alternative only") elif not _feature_enabled(features, "wifi_norm"): tradeoffs.append("No Wi-Fi") placement = str(query.search_filters.get("indoor_outdoor") or "").strip().lower() actual_placement = str(features.get("indoor_outdoor_norm") or "").strip().lower() if placement and (("indoor_outdoor" in relaxed_fields) or (actual_placement and actual_placement != placement)): tradeoffs.append(f"{placement} placement was relaxed; fallback alternative only") use_case_hint = str(query.search_filters.get("use_case_hint") or "").strip().lower() actual_use_case = str(features.get("use_case_norm") or "").strip().lower() if use_case_hint and (("use_case_hint" in relaxed_fields) or (actual_use_case and use_case_hint not in actual_use_case)): tradeoffs.append(f"{use_case_hint} use-case fit was relaxed; fallback alternative only") if not _feature_enabled(features, "gnss_norm"): tradeoffs.append("No GNSS/GPS") if not _feature_enabled(features, "poe_norm"): tradeoffs.append("No PoE") if not tradeoffs: tradeoffs.append("No obvious workbook tradeoff from the requested filters") return tradeoffs[0] def _battery_cell(item: Dict[str, Any]) -> str: features = _as_dict(item.get("features")) if query.search_filters.get("battery") is not True: return _yes_no(features.get("battery_norm")) if bool(item.get("_battery_verification_only")): return "Needs verification" relaxed_fields = {str(field) for field in list(item.get("_relaxed_fields") or [])} if "battery" in relaxed_fields: return "Optional/unclear" if _feature_enabled(features, "battery_norm"): return "Workbook flag" return "Not flagged" rows = [ "| Router | Manufacturer | Cell | Ethernet | Rugged | " + ("Battery evidence" if query.search_filters.get("battery") is True else "Battery") + (" | RF connectors" if query.search_filters.get("require_documented_rf") is True else "") + " | Status |", "| --- | --- | --- | ---: | --- | --- |" + (" --- |" if query.search_filters.get("require_documented_rf") is True else "") + " --- |", ] for item in shown_matches: features = _as_dict(item.get("features")) _, _, total_ports_text = _details_port_values(features) columns = [ _md_cell(item.get("_match_tier") or "Exact current match"), _md_cell(_search_display_name(item)), _md_cell(_manufacturer_label(item)), _md_cell(features.get("cellular_gen_norm") or "Not listed"), _md_cell(total_ports_text), _md_cell(_yes_no(features.get("rugged_norm"))), _md_cell(_battery_cell(item)), ] if query.search_filters.get("require_documented_rf") is True: columns.append(_md_cell(_documented_rf_text(item))) columns.append(_md_cell(item.get("status_bucket") or "Unknown")) rows.append("| " + " | ".join(columns) + " |") if matches: exact_count = int(search.get("exact_count") or len(exact_matches)) near_count = int(search.get("near_count") or len(near_matches)) battery_unclear_count = int(search.get("battery_unclear_count") or 0) battery_verification_count = int(search.get("battery_verification_count") or 0) strict_battery_evidence = bool(search.get("strict_battery_evidence")) lines = [] relaxed_labels = _search_relaxed_filter_labels(search) scope_notes = _search_scope_notes() if query.search_filters.get("battery") is True and typo_battery: lines.append("I interpreted `bateries` as `battery` and kept the shortlist tied to workbook battery evidence only.") if exact_count and near_count: if query.search_filters.get("battery") is True and strict_battery_evidence and battery_verification_count: lines.append( f"I found {exact_count} current workbook row{'s' if exact_count != 1 else ''} with a battery-related workbook flag and added {battery_verification_count} verification-only current alternative{'s' if battery_verification_count != 1 else ''} where battery support is optional or not explicit in the workbook." ) lines.append("Verification-only rows are shown to preserve breadth, but they are not being treated as true battery-backed hardware.") elif query.search_filters.get("battery") is True and battery_unclear_count: lines.append( f"I found {exact_count} current workbook row{'s' if exact_count != 1 else ''} with a battery-related workbook flag and added {battery_unclear_count} current alternative{'s' if battery_unclear_count != 1 else ''} where battery support is optional or not explicit in the workbook." ) else: if search_current_only: lines.append( f"I found {exact_count} exact current workbook-backed match{'es' if exact_count != 1 else ''} and added {near_count} closest current alternative{'s' if near_count != 1 else ''} to fill out the shortlist." ) else: lines.append( f"I found {exact_count} exact workbook-backed match{'es' if exact_count != 1 else ''} and added {near_count} closest workbook alternative{'s' if near_count != 1 else ''} to round out the requested lane." ) if relaxed_labels: lines.append( f"The alternative lane relaxes {', '.join(relaxed_labels)} while keeping the remaining hard filters intact." ) if exact_count < requested_limit: shortage = requested_limit - exact_count if near_count < shortage: relax_hint = ", ".join(relaxed_labels[:3]) or ", ".join(scope_notes[:3]) or "one or more filters" lines.append( f"I only found {exact_count} exact current match{'es' if exact_count != 1 else ''} out of the requested {requested_limit}. If you want another option, I can relax {relax_hint} or widen to broader current alternatives." ) else: lines.append( f"Only {exact_count} of the requested {requested_limit} rows are exact current matches; the extra rows below are fallback alternatives, not additional exact matches." ) elif exact_count: if exact_count < int(query.limit or 3): if query.search_filters.get("require_documented_rf") is True: lines.append( f"I found {exact_count} current workbook-backed router row{'s' if exact_count != 1 else ''} with concrete RF connector details. I am not padding the shortlist with variant-ambiguous rows just to reach {int(query.limit or 3)}." ) else: if search_current_only: lines.append( f"I found {exact_count} current workbook-backed match{'es' if exact_count != 1 else ''} that met every requested filter." ) else: lines.append( f"I found {exact_count} workbook-backed match{'es' if exact_count != 1 else ''} that met every requested filter." ) if scope_notes: lines.append(f"Search scope stayed strict: {', '.join(scope_notes)}.") if strict_battery_evidence and battery_verification_count: lines.append("Verification-only rows below are shown separately so optional or unclear battery rows do not get presented as true battery-backed hardware.") elif query.search_filters.get("battery") is True or query.search_filters.get("rugged") is True: lines.append("Optional or unclear workbook rows were not promoted into the exact-match lane.") if exact_count < requested_limit: relax_hint = ", ".join(relaxed_labels[:3]) or ", ".join(scope_notes[:3]) or "one or more filters" lines.append( f"I only found {exact_count} exact current match{'es' if exact_count != 1 else ''} out of the requested {requested_limit}. If you want a third option, I can relax {relax_hint} or include broader current alternatives." ) else: lines.append( f"Top current workbook-backed router example{'s' if len(shown_matches) != 1 else ''} ({len(shown_matches)} shown, {exact_count} exact match{'es' if exact_count != 1 else ''} found):" ) else: if query.search_filters.get("battery") is True and strict_battery_evidence and battery_verification_count: lines.append( "I did not find enough exact current workbook-backed battery matches to fill the shortlist, so the additional rows below are verification-only and do not count as true battery-backed hardware." ) elif query.search_filters.get("battery") is True and battery_unclear_count: lines.append( "I did not find a current workbook row with a battery-related flag that satisfied every requested filter, but these current alternatives may still fit where battery support is optional or not explicit in the workbook." ) elif near_count: lines.append( f"I found {near_count} current workbook-backed option{'s' if near_count != 1 else ''}, but none matched every requested filter exactly." ) else: lines.append( "No current workbook row satisfied every requested filter. The shortlist below is closest current alternatives only, not exact matches." ) if relaxed_labels: lines.append( f"These alternatives were surfaced after relaxing {', '.join(relaxed_labels)} while keeping the remaining hard filters intact." ) if "poe" in soft_fields: lines.append("PoE was treated as a preference, not a hard filter, because the prompt allowed non-PoE rows.") if query.search_filters.get("wifi") is True and any("wifi" in plan for plan in list(search.get("relaxed_plans") or [])): lines.append("Important: none of the rows below should be treated as a true Wi-Fi match unless the row explicitly says `Wi-Fi` in the fit summary or table.") if query.search_filters.get("rugged") is True and any("rugged" in plan for plan in list(search.get("relaxed_plans") or [])): lines.append("Important: rows shown after relaxing ruggedized status are fallback alternatives, not confirmed ruggedized matches.") if query.search_filters.get("gnss") is True and any("gnss" in plan for plan in list(search.get("relaxed_plans") or [])): lines.append("Important: rows shown after relaxing GNSS are fallback alternatives, not confirmed GNSS-equipped matches.") if has_legacy_matches and not search_current_only: lines.append( f"Legacy coverage note: {shown_legacy_count} shown row{'s are' if shown_legacy_count != 1 else ' is'} legacy/EOS/EOL in the workbook, but structured feature coverage is still being surfaced for audit and comparison." ) for index, item in enumerate(shown_matches, start=1): lines.extend( [ "", f"### {index}. {_search_display_name(item)}", f"- Match tier: {item.get('_match_tier') or 'Exact current match'}.", f"- Why it fits: {_fit_summary(item)}.", f"- Main tradeoff: {_tradeoff_summary(item)}.", *( [f"- Documented RF connectors: {_documented_rf_text(item)}."] if query.search_filters.get("require_documented_rf") is True else [] ), f"- Lifecycle lane: `{item.get('status_bucket') or 'Unknown'}` from `{_manufacturer_label(item)}`.", ] ) table_rows = [ "| Match tier | Router | Manufacturer | Cell | Ethernet | Rugged | " + ("Battery evidence" if query.search_filters.get("battery") is True else "Battery") + " | Status |", "| --- | --- | --- | --- | ---: | --- | --- | --- |", *rows[2:], ] lines.extend(["", "Compact table:", "", *table_rows]) if query.search_filters.get("battery") is True and (battery_unclear_count or battery_verification_count): lines.extend( [ "", "Battery lane note:", "- Exact-lane rows only mean the workbook carries a battery-related flag for that row; they do not confirm included battery hardware on every SKU/package.", "- Non-exact rows are shown separately when battery support is optional or not explicit in the workbook.", "- Treat the exact lane as workbook-only battery evidence and confirm the exact SKU/package before quoting battery support.", "- The workbook flag does not confirm battery type, battery runtime, or whether the battery is removable versus internal.", ] ) if exact_count > len([row for row in shown_matches if str(row.get("_match_tier") or "").startswith("Exact")]): lines.extend( [ "", f"There are additional exact current workbook-backed matches beyond these {len(shown_matches)} rows if you want me to widen the shortlist.", ] ) if query.search_filters.get("require_documented_rf") is True and len(shown_matches) < int(query.limit or 3): lines.extend( [ "", "Filter note:", "- I kept variant-ambiguous RF rows out of the main shortlist. Reply `allow RF-family rows` if you want me to widen to models where connector families are documented but the exact per-SKU layout still varies.", ] ) if excluded_noncurrent_matches: lifecycle_excluded: List[str] = [] other_excluded: List[str] = [] for item in excluded_noncurrent_matches: label = _search_display_name(item) status = _norm(item.get("status_bucket") or "Unknown") line = f"`{label}` ({status})" low_status = status.lower() if any(token in low_status for token in ("end of sale", "eos", "end of life", "eol", "legacy", "discontinued", "retired", "obsolete")): lifecycle_excluded.append(line) else: other_excluded.append(line) lines.extend(["", "Excluded from the current-only shortlist:"]) if lifecycle_excluded: lines.append(f"- End-of-sale / end-of-life / legacy rows kept out: {', '.join(lifecycle_excluded[:4])}.") if other_excluded: lines.append(f"- Other non-current rows kept out of the live shortlist: {', '.join(other_excluded[:3])}.") return "\n".join(lines) no_match = "No current workbook-backed routers matched those filters." if query.search_filters.get("require_documented_rf") is True: no_match += " No current row with concrete per-SKU RF connector details met the request." no_match += " Reply `allow RF-family rows` if you want me to widen to models where connector families are documented but the exact layout still varies by SKU." if query.search_filters.get("battery") is True: no_match += " I do not currently see a current workbook row with an explicit battery-related flag for this request." no_match += " Optional or unclear battery rows were not promoted into the exact battery lane." if query.search_filters.get("rugged") is True: no_match += " Rows without an explicit ruggedized workbook flag were kept out of the exact-match lane." if query.search_filters.get("current_only", True): no_match += " If you want older or legacy devices too, ask explicitly and I will widen the lane." return no_match def _search_shortlist_view(search: Dict[str, Any]) -> Dict[str, Any]: matches = [row for row in list(search.get("matches") or []) if isinstance(row, dict)] display_limit = max(1, min(5, int(query.limit or 3))) shown_matches = matches[:display_limit] items: List[Dict[str, Any]] = [] def _search_display_name(item: Dict[str, Any]) -> str: display = _display_name(item) if not display: return "" compact = _compact_model(display) if compact and compact == display and display.isdigit(): manufacturer = _norm(item.get("manufacturer_group") or item.get("manufacturer") or "Unknown") if manufacturer and manufacturer.lower() != "unknown": return f"{manufacturer} {display}" return f"Internal ID {display}" return display for index, item in enumerate(shown_matches, start=1): features = _as_dict(item.get("features")) reasons: List[str] = [] min_ports = query.search_filters.get("min_total_ethernet_ports") if min_ports: reasons.append(f"meets the requested Ethernet minimum ({min_ports}+ ports)") cellular_generation = str(query.search_filters.get("cellular_generation") or "").strip() if cellular_generation: reasons.append(f"{cellular_generation} cellular") if query.search_filters.get("rugged") is True and _feature_enabled(features, "rugged_norm"): reasons.append("ruggedized hardware") if query.search_filters.get("battery") is True and _feature_enabled(features, "battery_norm"): reasons.append("battery flag present in workbook") elif query.search_filters.get("battery") is True and "battery" in list(item.get("_relaxed_fields") or []): reasons.append("battery support is optional or not explicit in the workbook") if query.search_filters.get("wifi") is True and _feature_enabled(features, "wifi_norm"): reasons.append("Wi-Fi") if query.search_filters.get("gnss") is True and _feature_enabled(features, "gnss_norm"): reasons.append("GNSS/GPS") if query.search_filters.get("poe") is True and _feature_enabled(features, "poe_norm"): reasons.append("PoE") placement = str(query.search_filters.get("indoor_outdoor") or "").strip() if placement and placement == str(features.get("indoor_outdoor_norm") or "").strip().lower(): reasons.append(f"{placement} workbook fit") use_case_hint = str(query.search_filters.get("use_case_hint") or "").strip().lower() actual_use_case = str(features.get("use_case_norm") or "").strip().lower() if use_case_hint and actual_use_case and use_case_hint in actual_use_case: reasons.append(f"{use_case_hint} use-case fit") if not reasons: reasons.append("current workbook-backed feature-safe match") gap_notes = [str(note) for note in list(item.get("_gap_notes") or []) if _norm(note)] tradeoffs: List[str] = [] if gap_notes: tradeoffs.extend(gap_notes) if query.search_filters.get("battery") is True and "battery" in list(item.get("_relaxed_fields") or []): tradeoffs.append("Battery support is optional or not explicit in the workbook") elif not _feature_enabled(features, "battery_norm"): tradeoffs.append("No battery-backed option") if query.search_filters.get("wifi") is True and ("wifi" in list(item.get("_relaxed_fields") or []) or not _feature_enabled(features, "wifi_norm")): tradeoffs.append("Wi-Fi requirement was relaxed; fallback alternative only") elif not _feature_enabled(features, "wifi_norm"): tradeoffs.append("No Wi-Fi") if not _feature_enabled(features, "gnss_norm"): tradeoffs.append("No GNSS/GPS") if not _feature_enabled(features, "poe_norm"): tradeoffs.append("No PoE") if not tradeoffs: tradeoffs.append("No obvious workbook tradeoff from the requested filters") reasons = _trace_values( [ "; ".join(reasons[:5]), f"Lifecycle lane: {_norm(item.get('status_bucket') or 'Unknown')}.", ] ) items.append( { "rank": index, "display_name": _norm(_search_display_name(item) or item.get("product_id") or _display_name(item)), "manufacturer_group": _norm(item.get("manufacturer_group") or "Unknown"), "status_bucket": _norm(item.get("status_bucket") or "Unknown"), "cellular_gen": _norm(features.get("cellular_gen_norm") or "Not listed"), "total_ethernet_ports": int(item.get("total_ethernet_ports") or 0), "feature_badges": [ label for label, enabled in [ ("Rugged", _feature_enabled(features, "rugged_norm")), ("Battery", _feature_enabled(features, "battery_norm")), ("Wi-Fi", _feature_enabled(features, "wifi_norm")), ("GNSS/GPS", _feature_enabled(features, "gnss_norm")), ("PoE", _feature_enabled(features, "poe_norm")), ] if enabled ], "lane_label": ( "Legacy workbook match" if (not bool(query.search_filters.get("current_only", True)) and self._router_workbook_status_is_legacy(item.get("status_bucket"))) else str(item.get("_match_tier") or ("Current only" if bool(query.search_filters.get("current_only", True)) else "Includes legacy if matched")) ), "reasons": reasons, "tradeoffs": [str(note) for note in list(item.get("_gap_notes") or []) if _norm(note)] or tradeoffs[:3], "review_required": _search_requires_review_item(item), "review_notes": [], "debug_ref": self._router_workbook_debug_ref( table_name="DBX_Products", key=item.get("product_key"), label=f"{_norm(_search_display_name(item) or item.get('product_id') or _display_name(item) or 'Router')} product row", title=f"{_norm(_search_display_name(item) or item.get('product_id') or _display_name(item) or 'Router')} raw workbook row", ), } ) return { "title": "Ranked workbook shortlist", "subtitle": "Recommendation-style current router matches ranked from workbook filters.", "current_only": bool(query.search_filters.get("current_only", True)), "shown_count": len(items), "total_found": int(search.get("count") or len(matches)), "additional_match_count": max(0, int(search.get("count") or len(matches)) - len(items)), "items": items, } def _antenna_result(analysis: Dict[str, Any]) -> str: match = _as_dict(analysis.get("match")) selected = _as_dict(analysis.get("selected_recommendation")) antenna = _as_dict(analysis.get("antenna")) flow = _as_dict(antenna.get("flow")) quote = _as_dict(analysis.get("quote")) header = _as_dict(quote.get("header")) pricing = _as_dict(quote.get("pricing")) review_reasons = [str(x) for x in list(analysis.get("manual_review_reasons") or []) if _norm(x)] lines = [f"Workbook-backed antenna path for `{_display_name(match)}`:"] if selected: lines.extend( [ "", f"Selected router path: `{_display_name(selected)}` via `{_norm(selected.get('lane')) or 'workbook lane'}`.", ] ) if flow: lines.extend( [ "", "| Field | Value |", "| --- | --- |", f"| Bundle | {_md_cell(flow.get('bundle_name') or 'Not listed')} |", f"| Match method | {_md_cell(flow.get('match_method') or 'Not listed')} |", f"| Complexity | {_md_cell(flow.get('complexity_label') or 'Not listed')} |", f"| Review required | {_md_cell(_yes_no(flow.get('review_required_flag')))} |", ] ) if header: lines.extend( [ "", f"Quote BOM: `{_md_cell(header.get('quote_bom_id') or '')}`.", f"Base system MSRP: {_money(pricing.get('base_system_msrp') or header.get('base_system_msrp'))}.", ] ) bom_items = [row for row in list(quote.get("items") or []) if isinstance(row, dict)] if bom_items: lines.extend(["", "First BOM items:"]) for item in bom_items[:5]: qty = item.get("qty") or 1 lines.append(f"- `{qty} x {_norm(item.get('item_display_name') or item.get('sku_or_id') or 'Item')}`") if review_reasons: lines.extend(["", "Review notes:"]) lines.extend([f"- {note}" for note in review_reasons[:4]]) if any(token in query.normalized_message for token in ("vehicle installation", "police car", "patrol car")): lines.extend( [ "", "Vehicle/public-safety install note: confirm mount style, GPS/Wi-Fi lead needs, and cable routing before finalizing the BOM.", ] ) return "\n".join(lines) def _solution_family_label(value: Any) -> str: mapping = { "router_only": "Router only / no extra hardware", "indoor_poe_reposition": "Indoor PoE / Ethernet reposition", "indoor_adapter_same_mfr": "Indoor same-manufacturer adapter", "indoor_antenna_parsec": "Indoor Parsec antenna path", "outdoor_adapter_same_mfr": "Outdoor same-manufacturer adapter or gateway", "outdoor_antenna_parsec": "Outdoor Parsec antenna path", "cross_vendor_outdoor_gateway": "Cross-vendor outdoor gateway", "manual_review_required": "Manual review required", } raw = str(value or "").strip() return mapping.get(raw, raw or "Not listed") def _survey_result(survey_eval: Dict[str, Any]) -> str: survey = _as_dict(survey_eval.get("survey")) metrics = _as_dict(survey_eval.get("metrics")) recommendation = _as_dict(survey_eval.get("recommendation")) explainability = _as_dict(survey_eval.get("explainability")) primary_product = _as_dict(recommendation.get("primary_product")) backup_product = _as_dict(recommendation.get("backup_product")) warnings = [str(x) for x in list(recommendation.get("warnings") or []) if _norm(x)] review_reasons = [str(x) for x in list(recommendation.get("review_reasons") or []) if _norm(x)] lines = [ f"Workbook-backed survey interpretation for `{_norm(survey.get('site_name') or survey.get('survey_key') or 'survey')}`:", "", "| Field | Value |", "| --- | --- |", f"| Survey key | {_md_cell(survey.get('survey_key') or 'Not listed')} |", f"| Outcome class | {_md_cell(metrics.get('outcome_class') or 'Not listed')} |", f"| Evidence grade | {_md_cell(metrics.get('evidence_grade') or 'Not listed')} |", f"| Confidence | {_md_cell(metrics.get('confidence_label') or 'Not listed')} |", f"| Recommended path | {_md_cell(_solution_family_label(recommendation.get('primary_solution_family_key')))} |", f"| Backup path | {_md_cell(_solution_family_label(recommendation.get('backup_solution_family_key')))} |", f"| Review required | {_md_cell(_yes_no(recommendation.get('review_required_flag')))} |", ] if primary_product: lines.append(f"| Primary product | {_md_cell(_display_name(primary_product))} |") if backup_product: lines.append(f"| Backup product | {_md_cell(_display_name(backup_product))} |") applied_updates = [item for item in list(survey_eval.get("_applied_updates") or []) if isinstance(item, dict)] if applied_updates: lines.extend(["", "Updated survey inputs applied before re-evaluation:"]) for item in applied_updates[:8]: lines.append(f"- {_norm(item.get('label') or 'Update')}: {_norm(item.get('value') or 'Updated')}") recommendation_text = _norm(recommendation.get("recommendation_text") or explainability.get("explanation_text") or "") if recommendation_text: lines.extend(["", recommendation_text]) evidence_summary = _norm(explainability.get("evidence_summary") or "") if evidence_summary: lines.extend(["", f"Evidence summary: {evidence_summary}"]) restriction_summary = _norm(explainability.get("restriction_summary") or "") if restriction_summary: lines.extend(["", f"Restrictions considered: {restriction_summary}"]) bom_rows = [row for row in list(survey_eval.get("bom") or []) if isinstance(row, dict)] if bom_rows: lines.extend(["", "BOM preview:"]) for item in bom_rows[:5]: lines.append(f"- `{_norm(item.get('qty') or '1')} x {_norm(item.get('display_name') or item.get('entity_key') or 'Item')}`") if warnings: lines.extend(["", "Warnings:"]) lines.extend([f"- {warning}" for warning in warnings[:5]]) if review_reasons: lines.extend(["", "Review notes:"]) lines.extend([f"- {reason}" for reason in review_reasons[:5]]) return "\n".join(lines) def _why_lines_for_intent(intent: str, payload: Dict[str, Any]) -> List[str]: review_required = bool(payload.get("review_required")) source_mode = str(payload.get("source_mode") or "").strip().lower() uses_sourced_fallback = bool(payload.get("uses_sourced_fallback")) base = [ "This answer uses the router workbook as the primary internal source, not the older fallback catalog.", ( "No current workbook-ready replacement lane was available, so the move-forward rows below are conservative sourced fallback guidance." if intent == "replacements" and (source_mode == "lifecycle_fallback" or uses_sourced_fallback) else "Recommendations default to current devices unless you explicitly ask for legacy or historical options." ), ] if intent == "compare": base.append("Comparison uses normalized product, feature, and lifecycle rows for each matched router.") elif intent == "details": base.append("Details combine the product row, feature row, lifecycle rows, and replacement lane when present.") elif intent == "lifecycle": base.append("Lifecycle dates come from workbook lifecycle rows and replacement facts, not inferred text.") elif intent == "fleet_lifecycle": base.append("Fleet rows are normalized against workbook aliases and then resolved through workbook lifecycle and replacement tables.") elif intent == "replacements": base.append("Same-manufacturer app-ready paths are shown first; cross-vendor backups are listed separately.") elif intent == "search": base.append("Search results are filtered from workbook feature rows and restricted to current devices by default.") elif intent == "antenna": base.append("Antenna guidance comes from workbook antenna-flow, BOM, warning, and explainability tables.") elif intent == "survey": base.append("Survey interpretation uses the workbook runtime survey rows and evaluates placement paths from the workbook rule tables.") elif intent == "guided_advisor": base.append("This shortlist comes from a five-question workbook-guided intake and only returns current workbook-backed devices by default.") if review_required: base.append("Some rows still need exact SKU/package confirmation, so I am surfacing that uncertainty instead of implying more precision than the workbook supports.") return base def _next_actions_for_intent(intent: str, payload: Dict[str, Any]) -> List[str]: actions = { "compare": [ "Ask for replacements or lifecycle dates for one of these routers.", "Ask for an antenna or placement path if you already know the deployment scenario.", ], "details": [ "Ask for replacements, lifecycle dates, or an antenna path for this router.", "If you need documented-spec excerpts instead of workbook data, ask for datasheet/manual details explicitly.", ], "lifecycle": [ "Ask for same-manufacturer and cross-vendor replacements for any listed model.", "If you need a fleet-style view, provide model + qty lines and I can summarize them.", ], "fleet_lifecycle": [ "Ask me to sort the fleet by urgency or review-required rows next.", "Ask for same-brand vs cross-vendor migration lanes for the whole fleet.", ], "replacements": [ "Ask me to compare the replacement options side by side.", "Ask for the antenna/BOM path for the selected replacement.", ], "search": [ "Tighten the filters by manufacturer, Wi-Fi, GNSS, PoE, or indoor/outdoor use.", "If you want legacy examples too, say so explicitly and I will widen the lane.", ], "antenna": [ "If you share mount constraints or site-survey context, I can narrow the install path further.", "Ask for the replacement options too if the current router is legacy.", ], "survey": [ "Ask whether the workbook points toward indoor reposition, outdoor hardware, or manual review.", "If you want product-specific placement guidance, include the selected router model too.", ], "guided_advisor": [ "Ask me to compare any two shortlist options side by side.", "Ask for a deeper antenna, BOM, or replacement path on the option you want to pursue.", ], } out = list(actions.get(intent, ["Ask a follow-up with exact model + use case."])) survey_followups: List[str] = [] if intent == "survey": survey_followups = [ str(_as_dict(item).get("prompt") or "").strip() for item in list(payload.get("survey_followups") or []) if str(_as_dict(item).get("prompt") or "").strip() ] if survey_followups: out = [*survey_followups, *out] if bool(payload.get("review_required")) and not survey_followups: out.insert(0, "Provide the missing install/model context and I will try to resolve the review flag.") return out def _trace_item(label: str, value: Any) -> Optional[Dict[str, str]]: label_text = str(label or "").strip() value_text = _norm(value) if not label_text or not value_text: return None return {"label": label_text, "value": value_text} def _trace_values(values: Sequence[Any], limit: int = 5) -> List[str]: out: List[str] = [] for value in values: text = _norm(value) if text and text not in out: out.append(text) if len(out) >= limit: break return out def _router_answer_trace( *, intent: str, summary: str, items: Sequence[Optional[Dict[str, str]]], warnings: Sequence[Any] = (), ) -> Dict[str, Any]: cleaned_items: List[Dict[str, str]] = [] for item in items: if not isinstance(item, dict): continue label_text = str(item.get("label") or "").strip() value_text = _norm(item.get("value")) if label_text and value_text: cleaned_items.append({"label": label_text, "value": value_text}) return { "summary": _norm(summary), "items": cleaned_items, "warnings": _trace_values(warnings), "source_tables": self._router_workbook_source_tables(intent), } def _details_trace(detail: Dict[str, Any], review_required: bool) -> Dict[str, Any]: match = _as_dict(detail.get("match")) product = _as_dict(detail.get("product")) lifecycle = _as_dict(detail.get("lifecycle")) replacements = _as_dict(detail.get("replacements")) primary = _as_dict(replacements.get("primary_replacement")) backup_rows = [row for row in list(replacements.get("backup_replacements") or []) if isinstance(row, dict)] device_name = _display_name(product) or _display_name(match) or "Resolved router" warnings: List[str] = [] if bool(match.get("feature_gap_blocked")): warnings.append("Feature-safe auto-matching is blocked for this router.") if not bool(lifecycle.get("has_authoritative_lifecycle")): warnings.append("Lifecycle dates are incomplete or not authoritative in the workbook.") if _norm(detail.get("_family_safe_note") or ""): warnings.append(_norm(detail.get("_family_safe_note") or "")) return _router_answer_trace( intent="details", summary=f"{device_name} resolved as {_status_label(match, lifecycle)} in the workbook catalog.", items=[ _trace_item("Subject device", device_name), _trace_item("Lifecycle status", _status_label(match, lifecycle)), _trace_item("Primary same-brand path", _display_name(primary)), _trace_item("Backup path", _display_name(backup_rows[0]) if backup_rows else ""), _trace_item("Review flag", _yes_no(review_required)), ], warnings=warnings, ) def _compare_trace(compare: Dict[str, Any], review_required: bool) -> Dict[str, Any]: devices = [row for row in list(compare.get("devices") or []) if isinstance(row, dict)] names = [ _display_name(_as_dict(row.get("product")) or _as_dict(row.get("match"))) or "Unknown router" for row in devices[:4] ] lifecycle_states = [ f"{name}: {_status_label(_as_dict(row.get('match')), _as_dict(row.get('lifecycle')))}" for name, row in zip(names, devices) ] warnings: List[str] = [] if any(not bool(_as_dict(row.get("lifecycle")).get("has_authoritative_lifecycle")) for row in devices): warnings.append("One or more compared routers have incomplete lifecycle authority in the workbook.") warnings.extend( [ _norm(row.get("_family_safe_note") or "") for row in devices if _norm(row.get("_family_safe_note") or "") ] ) return _router_answer_trace( intent="compare", summary=f"Compared {' vs '.join(names[:2]) if len(names) >= 2 else ', '.join(names)} using workbook product, feature, and lifecycle rows.", items=[ _trace_item("Compared routers", " vs ".join(names) if names else ""), _trace_item("Lifecycle states", "; ".join(lifecycle_states)), _trace_item("Review flag", _yes_no(review_required)), ], warnings=warnings, ) def _lifecycle_trace(batch: Dict[str, Any], review_required: bool) -> Dict[str, Any]: devices = [row for row in list(batch.get("devices") or []) if isinstance(row, dict)] authoritative_count = sum( bool(_as_dict(row.get("lifecycle")).get("has_authoritative_lifecycle")) for row in devices ) primary_count = sum( bool(_display_name(_as_dict(_as_dict(row.get("replacements")).get("primary_replacement")))) for row in devices ) backup_count = sum( bool([item for item in list(_as_dict(row.get("replacements")).get("backup_replacements") or []) if isinstance(item, dict)]) for row in devices ) warnings: List[str] = [] if authoritative_count < len(devices): warnings.append("Some lifecycle rows are missing authoritative workbook dates.") return _router_answer_trace( intent="lifecycle", summary=f"Resolved {len(devices)} workbook lifecycle row{'s' if len(devices) != 1 else ''}.", items=[ _trace_item("Devices analyzed", len(devices)), _trace_item("Authoritative lifecycle rows", f"{authoritative_count}/{len(devices)}"), _trace_item("Same-brand paths found", primary_count), _trace_item("Backup paths found", backup_count), _trace_item("Review flag", _yes_no(review_required)), ], warnings=warnings, ) def _fleet_trace(fleet_view: Dict[str, Any], review_required: bool) -> Dict[str, Any]: evidence_rows = [row for row in list(fleet_view.get("fleet_evidence_rows") or []) if isinstance(row, dict)] authoritative_count = sum(bool(row.get("authoritative_lifecycle")) for row in evidence_rows if bool(row.get("matched"))) unmatched_count = sum(not bool(row.get("matched")) for row in evidence_rows) warnings: List[str] = [] if unmatched_count: warnings.append(f"{unmatched_count} fleet row(s) still need an exact workbook model match.") matched_count = sum(bool(row.get("matched")) for row in evidence_rows) if authoritative_count < matched_count: warnings.append("Some matched fleet devices are missing authoritative workbook lifecycle dates.") return _router_answer_trace( intent="fleet_lifecycle", summary=f"Built a workbook-backed fleet snapshot for {len(evidence_rows)} inventory row{'s' if len(evidence_rows) != 1 else ''}.", items=[ _trace_item("Fleet rows analyzed", len(evidence_rows)), _trace_item("Matched workbook rows", matched_count), _trace_item("Unmatched rows", unmatched_count), _trace_item( "Authoritative lifecycle rows", f"{authoritative_count}/{matched_count}" if matched_count else "0/0", ), _trace_item("Review flag", _yes_no(review_required)), ], warnings=warnings, ) def _replacements_trace(analysis: Dict[str, Any], review_required: bool) -> Dict[str, Any]: match = _as_dict(analysis.get("match")) replacements = _as_dict(analysis.get("replacements")) primary_rows = [row for row in list(replacements.get("primary_candidates") or []) if isinstance(row, dict)] same_manufacturer_backup_rows = [ row for row in list(replacements.get("same_manufacturer_backup_replacements") or []) if isinstance(row, dict) ] backup_rows = [row for row in list(replacements.get("backup_replacements") or []) if isinstance(row, dict)] historical_rows = [row for row in list(replacements.get("historical_only_replacements") or []) if isinstance(row, dict)] fallback_source = str(analysis.get("_replacement_source_mode") or "").strip().lower() == "lifecycle_fallback" replacement_evidence = self._router_workbook_replacement_evidence_from_analysis( core, analysis, requested_model=_norm(query.device_texts[0] if query.device_texts else ""), prefer_5g_target=bool(re.search(r"\b5g\b", query.normalized_message, flags=re.IGNORECASE)), resolution_mode=str(analysis.get("_resolution_mode") or "exact"), ) warnings = _trace_values(list(analysis.get("manual_review_reasons") or [])) if bool(replacements.get("no_replacement")): warnings.append("Workbook records an explicit no-direct-replacement outcome.") if historical_rows: warnings.append(f"{len(historical_rows)} historical-only row(s) were kept out of the live recommendation lane.") return _router_answer_trace( intent="replacements", summary=( f"{_display_name(match) or 'Resolved router'} replacement lanes were pulled from fallback lifecycle/catalog data." if fallback_source else f"{_display_name(match) or 'Resolved router'} replacement lanes were pulled from workbook replacement facts." ), items=[ _trace_item("Subject device", _display_name(match)), _trace_item( "Primary same-brand path", _display_name(primary_rows[0]) if primary_rows else (_display_name(same_manufacturer_backup_rows[0]) if same_manufacturer_backup_rows else "None workbook-ready"), ), _trace_item("Backup path", _norm(replacement_evidence.get("backup_path") or "") or (_display_name(backup_rows[0]) if backup_rows else "None listed")), _trace_item("Ordered move-forward paths", len(list(replacement_evidence.get("ordered_paths") or []))), _trace_item("Historical-only rows", len(historical_rows)), _trace_item("Review flag", _yes_no(review_required)), ], warnings=warnings, ) def _search_trace(search: Dict[str, Any], review_required: bool) -> Dict[str, Any]: match_count = int(search.get("count") or len(list(search.get("matches") or []))) exact_count = int(search.get("exact_count") or 0) near_count = int(search.get("near_count") or 0) current_only = bool(query.search_filters.get("current_only", True)) warnings: List[str] = [] if not match_count: warnings.append("No current workbook-backed routers matched the requested filters.") elif near_count: warnings.append("Closest current alternatives were included because exact current matches were sparse.") elif exact_count < max(1, int(query.limit or 3)) and ( query.search_filters.get("battery") is True or query.search_filters.get("rugged") is True ): warnings.append("The exact-match lane stayed strict and did not widen into optional or unclear workbook rows.") relaxed_labels = _search_relaxed_filter_labels(search) return _router_answer_trace( intent="search", summary=( f"Found {exact_count} exact workbook-backed router match{'es' if exact_count != 1 else ''}" + ( f" and {near_count} closest current alternative{'s' if near_count != 1 else ''}" if near_count else "" ) + " for the requested filters." ), items=[ _trace_item("Recommendation lane", "Current only" if current_only else "Includes legacy if matched"), _trace_item("Requested examples", query.limit), _trace_item("Exact matches", exact_count), _trace_item("Closest alternatives", near_count), _trace_item("Manufacturer filter", query.manufacturer_text), _trace_item("Minimum Ethernet ports", query.search_filters.get("min_total_ethernet_ports")), _trace_item("Rugged filter", _yes_no(query.search_filters.get("rugged")) if query.search_filters.get("rugged") is not None else ""), _trace_item("Battery filter", _yes_no(query.search_filters.get("battery")) if query.search_filters.get("battery") is not None else ""), _trace_item("Wi-Fi filter", _yes_no(query.search_filters.get("wifi")) if query.search_filters.get("wifi") is not None else ""), _trace_item("GNSS filter", _yes_no(query.search_filters.get("gnss")) if query.search_filters.get("gnss") is not None else ""), _trace_item("PoE filter", _yes_no(query.search_filters.get("poe")) if query.search_filters.get("poe") is not None else ""), _trace_item("Placement filter", query.search_filters.get("indoor_outdoor")), _trace_item("Cellular generation filter", query.search_filters.get("cellular_generation")), _trace_item("Use-case hint", query.search_filters.get("use_case_hint")), _trace_item("Relaxed filters", ", ".join(relaxed_labels)), _trace_item("Review flag", _yes_no(review_required)), ], warnings=warnings, ) def _antenna_trace(analysis: Dict[str, Any], review_required: bool) -> Dict[str, Any]: match = _as_dict(analysis.get("match")) selected = _as_dict(analysis.get("selected_recommendation")) antenna = _as_dict(analysis.get("antenna")) flow = _as_dict(antenna.get("flow")) quote = _as_dict(analysis.get("quote")) header = _as_dict(quote.get("header")) return _router_answer_trace( intent="antenna", summary=f"Antenna/BOM guidance for {_display_name(match) or 'the resolved router'} came from workbook flow and quote tables.", items=[ _trace_item("Subject device", _display_name(match)), _trace_item("Selected router path", _display_name(selected)), _trace_item("Bundle", flow.get("bundle_name")), _trace_item("Quote BOM", header.get("quote_bom_id")), _trace_item("Review flag", _yes_no(review_required)), ], warnings=list(analysis.get("manual_review_reasons") or []), ) def _survey_trace(survey_eval: Dict[str, Any], review_required: bool) -> Dict[str, Any]: survey = _as_dict(survey_eval.get("survey")) metrics = _as_dict(survey_eval.get("metrics")) recommendation = _as_dict(survey_eval.get("recommendation")) primary_product = _as_dict(recommendation.get("primary_product")) backup_product = _as_dict(recommendation.get("backup_product")) warnings = [*list(recommendation.get("warnings") or []), *list(recommendation.get("review_reasons") or [])] return _router_answer_trace( intent="survey", summary=( f"Survey `{_norm(survey.get('survey_key') or survey.get('site_name') or 'survey')}` " f"evaluated to `{_norm(metrics.get('outcome_class') or 'Not listed')}` " f"with `{_norm(metrics.get('confidence_label') or 'Not listed')}` confidence." ), items=[ _trace_item("Survey / site", survey.get("site_name") or survey.get("survey_key")), _trace_item("Outcome class", metrics.get("outcome_class")), _trace_item("Evidence grade", metrics.get("evidence_grade")), _trace_item("Primary path", _solution_family_label(recommendation.get("primary_solution_family_key"))), _trace_item("Primary product", _display_name(primary_product)), _trace_item("Backup product", _display_name(backup_product)), _trace_item("Review flag", _yes_no(review_required)), ], warnings=warnings, ) def _product_debug_ref(row: Dict[str, Any], *, suffix: str = "product row") -> Dict[str, Any] | None: product = _as_dict(row) return self._router_workbook_debug_ref( table_name="DBX_Products", key=product.get("product_key"), label=f"{_display_name(product) or 'Router'} {suffix}", title=f"{_display_name(product) or 'Router'} raw workbook row", ) def _replacement_debug_ref(row: Dict[str, Any], *, prefix: str = "Replacement") -> Dict[str, Any] | None: replacement = _as_dict(row) return self._router_workbook_debug_ref( table_name="DBX_Replacements", key=replacement.get("replacement_map_id"), label=f"{prefix} mapping row", title=f"{prefix} raw workbook replacement row", ) def _detail_debug_refs(detail: Dict[str, Any]) -> List[Dict[str, Any]]: match = _as_dict(detail.get("match")) product = _as_dict(detail.get("product")) replacements = _as_dict(detail.get("replacements")) primary = _as_dict(replacements.get("primary_replacement")) backup_rows = [row for row in list(replacements.get("backup_replacements") or []) if isinstance(row, dict)] return self._router_workbook_compact_debug_refs( [ _product_debug_ref(product or match), self._router_workbook_debug_ref( table_name="DBX_Lifecycle", key=(product or match).get("product_key"), label=f"{_display_name(product or match) or 'Router'} lifecycle row", title=f"{_display_name(product or match) or 'Router'} lifecycle workbook row", ), self._router_workbook_debug_ref( table_name="DBX_Features", key=(product or match).get("product_key"), label=f"{_display_name(product or match) or 'Router'} feature row", title=f"{_display_name(product or match) or 'Router'} feature workbook row", ), _replacement_debug_ref(primary, prefix="Primary replacement") if primary else None, _replacement_debug_ref(_as_dict(backup_rows[0]), prefix="Backup replacement") if backup_rows else None, ] ) def _compare_debug_refs(compare: Dict[str, Any]) -> List[Dict[str, Any]]: refs: List[Dict[str, Any] | None] = [] for row in [row for row in list(compare.get("devices") or []) if isinstance(row, dict)][:6]: product = _as_dict(row.get("product")) or _as_dict(row.get("match")) refs.append(_product_debug_ref(product, suffix="product row")) refs.append( self._router_workbook_debug_ref( table_name="DBX_Lifecycle", key=product.get("product_key"), label=f"{_display_name(product) or 'Router'} lifecycle row", title=f"{_display_name(product) or 'Router'} lifecycle workbook row", ) ) return self._router_workbook_compact_debug_refs(refs) def _lifecycle_debug_refs(batch: Dict[str, Any]) -> List[Dict[str, Any]]: refs: List[Dict[str, Any] | None] = [] for row in [row for row in list(batch.get("devices") or []) if isinstance(row, dict)][:8]: product = _as_dict(row.get("product")) or _as_dict(row.get("match")) replacements = _as_dict(row.get("replacements")) refs.append(_product_debug_ref(product, suffix="product row")) refs.append( self._router_workbook_debug_ref( table_name="DBX_Lifecycle", key=product.get("product_key"), label=f"{_display_name(product) or 'Router'} lifecycle row", title=f"{_display_name(product) or 'Router'} lifecycle workbook row", ) ) refs.append(_replacement_debug_ref(_as_dict(replacements.get("primary_replacement")), prefix="Primary replacement")) return self._router_workbook_compact_debug_refs(refs) def _replacements_debug_refs(analysis: Dict[str, Any]) -> List[Dict[str, Any]]: match = _as_dict(analysis.get("match")) replacements = _as_dict(analysis.get("replacements")) primary_rows = [row for row in list(replacements.get("primary_candidates") or []) if isinstance(row, dict)] backup_rows = [row for row in list(replacements.get("backup_replacements") or []) if isinstance(row, dict)] historical_rows = [row for row in list(replacements.get("historical_only_replacements") or []) if isinstance(row, dict)] return self._router_workbook_compact_debug_refs( [ _product_debug_ref(match, suffix="product row"), _replacement_debug_ref(_as_dict(primary_rows[0]), prefix="Primary replacement") if primary_rows else None, _replacement_debug_ref(_as_dict(backup_rows[0]), prefix="Backup replacement") if backup_rows else None, _replacement_debug_ref(_as_dict(historical_rows[0]), prefix="Historical replacement") if historical_rows else None, ] ) def _search_debug_refs(search: Dict[str, Any]) -> List[Dict[str, Any]]: matches = [row for row in list(search.get("matches") or []) if isinstance(row, dict)] refs = [_product_debug_ref(_as_dict(item), suffix="product row") for item in matches[:5]] return self._router_workbook_compact_debug_refs(refs) def _antenna_debug_refs(analysis: Dict[str, Any]) -> List[Dict[str, Any]]: match = _as_dict(analysis.get("match")) selected = _as_dict(analysis.get("selected_recommendation")) antenna = _as_dict(analysis.get("antenna")) flow = _as_dict(antenna.get("flow")) quote = _as_dict(analysis.get("quote")) header = _as_dict(quote.get("header")) return self._router_workbook_compact_debug_refs( [ _product_debug_ref(match, suffix="product row"), _product_debug_ref(selected, suffix="selected path row"), self._router_workbook_debug_ref( table_name="DBX_AntennaFlow", key=flow.get("flow_row_id"), label="Antenna flow row", title="Antenna flow raw workbook row", ), self._router_workbook_debug_ref( table_name="DBX_QuoteHeaders", key=header.get("quote_bom_id"), label="Quote BOM row", title="Quote BOM raw workbook row", ), _replacement_debug_ref(selected, prefix="Selected replacement") if selected.get("replacement_map_id") else None, ] ) def _survey_debug_refs(survey_eval: Dict[str, Any]) -> List[Dict[str, Any]]: survey = _as_dict(survey_eval.get("survey")) recommendation = _as_dict(survey_eval.get("recommendation")) subject_analysis = _as_dict(survey_eval.get("subject_analysis")) subject_match = _as_dict(subject_analysis.get("match")) selected = _as_dict(subject_analysis.get("selected_recommendation")) primary_product = _as_dict(recommendation.get("primary_product")) return self._router_workbook_compact_debug_refs( [ self._router_workbook_debug_ref( table_name="DBX_SurveyHeaders", key=survey.get("survey_key"), label="Survey header row", title="Survey header raw workbook row", ), self._router_workbook_debug_ref( table_name="DBX_SurveyRestrictions", key=survey.get("survey_key"), label="Survey restrictions row", title="Survey restrictions raw workbook row", ), self._router_workbook_debug_ref( table_name="DBX_SurveyMetrics", key=survey.get("survey_key"), label="Survey metrics row", title="Survey metrics raw workbook row", ), self._router_workbook_debug_ref( table_name="DBX_SurveyRecommendations", key=recommendation.get("survey_recommendation_key"), label="Survey recommendation row", title="Survey recommendation raw workbook row", ), _product_debug_ref(primary_product, suffix="primary product row"), _product_debug_ref(subject_match, suffix="subject router row"), _replacement_debug_ref(selected, prefix="Selected replacement") if selected.get("replacement_map_id") else None, ] ) def _survey_followup_requirements(survey_eval: Dict[str, Any]) -> List[Dict[str, str]]: metrics = _as_dict(survey_eval.get("metrics")) recommendation = _as_dict(survey_eval.get("recommendation")) explainability = _as_dict(survey_eval.get("explainability")) survey = _as_dict(survey_eval.get("survey")) subject_analysis = _as_dict(survey_eval.get("subject_analysis")) primary_product = _as_dict(recommendation.get("primary_product")) outcome_class = _norm(metrics.get("outcome_class") or "") restriction_summary = _norm(explainability.get("restriction_summary") or "").lower() review_required = bool(recommendation.get("review_required_flag") or metrics.get("review_required_flag")) selected_hardware_locked = _norm(survey.get("selected_hardware_locked_flag") or "") selected_hardware_text = _norm(survey.get("selected_hardware_text") or "") customer_mode = _norm(survey.get("customer_mode") or "") has_subject_router = ( bool(query.device_texts) or bool(subject_analysis.get("ok")) or bool(primary_product.get("product_key")) or bool(selected_hardware_text) ) followups: List[Dict[str, str]] = [] def _add(field: str, prompt: str) -> None: field = str(field or "").strip() prompt = str(prompt or "").strip() if field and prompt and not any(str(item.get("field") or "") == field for item in followups): followups.append({"field": field, "prompt": prompt}) def _summary_known(label: str) -> bool: label = str(label or "").strip().lower() if not label: return False return (f"{label}: yes" in restriction_summary) or (f"{label}: no" in restriction_summary) def _summary_has_value(label: str) -> bool: label = str(label or "").strip().lower() if not label: return False match = re.search(rf"{re.escape(label)}:\s*([^;]+)", restriction_summary, flags=re.IGNORECASE) if not match: return False value = str(match.group(1) or "").strip().lower() return value not in {"", "unknown", "not listed"} def _metric_missing(field_name: str) -> bool: return not str(metrics.get(field_name) or "").strip() point_score_followup_needed = any( _metric_missing(field_name) for field_name in ("closet_score", "near_closet_score", "entry_outdoor_score", "best_clear_outdoor_score") ) if not review_required and outcome_class not in { "indoor_reposition", "outdoor_candidate", "outdoor_required", "manual_review_required", "incomplete", } and not point_score_followup_needed: return [] selected_hardware_known = selected_hardware_locked.lower() in {"yes", "no"} or _summary_known("selected hardware locked") selected_hardware_required = (selected_hardware_locked.lower() == "yes") or ("selected hardware locked: yes" in restriction_summary) if (not selected_hardware_known) or (selected_hardware_required and not has_subject_router): _add("selected_hardware", "Is the hardware already selected and locked? If yes, what exact router model is it?") if not customer_mode: _add("customer_mode", "Do you want overall hardware suggestions, or only ways to improve the selected hardware?") if review_required and (not _summary_known("poe available")): _add("poe_available", "Is PoE available if we need to move the router out of the network closet?") if review_required and (not _summary_known("exterior mount")): _add("exterior_mount_allowed", "Is exterior mounting allowed at this site?") if review_required and (not _summary_known("wall/roof penetration")): _add("wall_penetration_allowed", "Are wall or roof penetrations allowed for this install?") if outcome_class in {"outdoor_candidate", "outdoor_required", "manual_review_required"} and (not _summary_known("roof mount")): _add("roof_mount_allowed", "Is roof mounting allowed if an outdoor path is needed?") if outcome_class in {"indoor_reposition", "outdoor_candidate", "outdoor_required", "manual_review_required"} and (not _summary_has_value("max ethernet run")): _add("max_ethernet_run_ft", "What is the maximum Ethernet run available to reach the better install location?") if outcome_class in {"outdoor_candidate", "outdoor_required", "manual_review_required"} and (not _summary_has_value("max coax run")): _add("max_coax_run_ft", "What is the maximum coax run you can support for this install?") if review_required and (not _summary_has_value("landlord restrictions")): _add("landlord_restrictions", "Are there any landlord, HOA, or building restrictions I should account for?") if _metric_missing("closet_score"): _add("point:L9", "What was the network closet score (L9)?") if _metric_missing("near_closet_score"): _add("point:L10", "What was the near-closet score (L10)?") if _metric_missing("entry_outdoor_score"): _add("point:O2", "What was the entry outdoor score (O2)?") if _metric_missing("best_clear_outdoor_score"): _add("point:O3", "What was the best clear outdoor score (O3)?") return followups[:8] if plan_intent == "guided_advisor": guided = self._start_router_workbook_guided_advisor(query, st, fast_domain) guided_meta = _as_dict(guided.get("meta")) guided["meta"] = {**guided_meta, **orchestration_meta} return guided if plan_intent == "details": resolved_detail = self._router_workbook_resolve_detail_or_family( core, manufacturer_text=query.manufacturer_text, product_text=query.device_texts[0], ) if not resolved_detail.get("ok"): detail = _as_dict(resolved_detail.get("response")) if str(detail.get("error") or "") == "ambiguous_product": return self._router_workbook_clarify_response(query, detail, st, fast_domain, orchestration_meta) assistant = _format_shell( str(detail.get("message") or "I could not find a workbook-backed router match."), [ "Router intelligence is workbook-backed and will not guess when the model is missing.", ], [ "Provide the exact model/SKU from the device label.", "If you want a broader shortlist instead, ask by feature or use case.", ], ) return { "assistant": assistant, "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_details_unmatched", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } detail = _as_dict(resolved_detail.get("detail")) review_required = ( bool((detail.get("match") or {}).get("feature_gap_blocked")) or (not bool((detail.get("lifecycle") or {}).get("has_authoritative_lifecycle"))) or bool(detail.get("_family_safe")) ) return { "assistant": _format_shell(_details_result(detail), _why_lines_for_intent("details", {"review_required": review_required}), _next_actions_for_intent("details", {"review_required": review_required})), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": ( "deterministic_router_workbook_details_partial" if bool(detail.get("_family_safe")) else "deterministic_router_workbook_details" ), "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "router_resolution_mode": str(resolved_detail.get("resolution_mode") or "exact"), "router_workbook_tables": self._router_workbook_source_tables(plan_intent), "router_answer_trace": _details_trace(detail, review_required), "router_debug_refs": _detail_debug_refs(detail), "router_fact_bundle": self._router_workbook_fact_bundle_summary(_as_dict(detail.get("_fact_bundle"))), "review_required": review_required, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } if plan_intent == "compare": compare_devices: List[Dict[str, Any]] = [] compare_failures: List[Dict[str, Any]] = [] compare_resolution_modes: List[str] = [] for token in query.device_texts[:4]: resolved_detail = self._router_workbook_resolve_detail_or_family( core, manufacturer_text=query.manufacturer_text, product_text=token, ) if resolved_detail.get("ok"): compare_devices.append(_as_dict(resolved_detail.get("detail"))) compare_resolution_modes.append(str(resolved_detail.get("resolution_mode") or "exact")) continue failure = _as_dict(resolved_detail.get("response")) compare_failures.append({**failure, "_requested_token": token}) if len(compare_devices) < 2: ambiguous = next( (item for item in compare_failures if str(_as_dict(item).get("error") or "") == "ambiguous_product"), None, ) if ambiguous: return self._router_workbook_clarify_response(query, _as_dict(ambiguous), st, fast_domain, orchestration_meta) doc_compare = self._router_multi_model_doc_table_fast(message) if not doc_compare: doc_compare = self._router_multi_model_doc_table_fast(f"{message} from documented specs only") if not doc_compare: doc_compare = self._router_multi_model_doc_table_fast(f"{message} include what is documented vs not documented") if doc_compare: assistant_text = str(doc_compare.get("assistant") or "") if ( "evidence notes:" not in assistant_text.lower() and any( token in normalized for token in ( "install caveat", "install caveats", "install note", "install notes", "install implication", "install implications", "installation implication", "installation implications", "install impact", "install impacts", ) ) ): doc_compare = dict(doc_compare) doc_compare["assistant"] = assistant_text.rstrip() + ( "\n\nEvidence notes:\n" "- Install-related fields were kept conservative and grounded in the retrieved internal compare rows." ) return doc_compare return { "assistant": _format_shell( "I need at least two workbook-backed router matches to compare.", ["Comparison requires two unambiguous workbook router matches."], ["Provide the exact two model/SKU names you want compared."], ), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_compare_unmatched", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } compare = { "ok": True, "devices": compare_devices[:4], "failures": compare_failures, } review_required = any( bool(_as_dict(item.get("lifecycle")).get("has_authoritative_lifecycle")) is False or bool(item.get("_family_safe")) for item in compare_devices ) return { "assistant": _format_shell(_compare_result(compare), _why_lines_for_intent("compare", {"review_required": review_required}), _next_actions_for_intent("compare", {"review_required": review_required})), "sources": [*workbook_sources, *compare_sources], "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": ( "deterministic_router_workbook_compare_partial" if any(mode != "exact" for mode in compare_resolution_modes) else "deterministic_router_workbook_compare" ), "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "router_workbook_tables": self._router_workbook_source_tables(plan_intent), "router_answer_trace": _compare_trace(compare, review_required), "router_debug_refs": _compare_debug_refs(compare), "router_fact_bundles": [ self._router_workbook_fact_bundle_summary(_as_dict(item.get("_fact_bundle"))) for item in compare_devices[:4] ], "review_required": review_required, "router_resolution_modes": compare_resolution_modes, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } if plan_intent == "lifecycle": asks_detail_bundle = ( len(list(query.device_texts or [])) == 1 and any( token in query.normalized_message for token in ( "primary use case", "wan/lan", "wan lan", "wifi", "wi-fi", "current recommendation", "still current recommendation", ) ) ) asks_alias_audit = ( len(list(query.device_texts or [])) >= 2 and any( token in query.normalized_message for token in ( "safe exact match", "safe exact matches", "likely alias correction", "likely alias corrections", "alias correction", "alias corrections", "match confidence", "row by row match confidence", "row-by-row match confidence", ) ) ) asks_match_confidence = asks_alias_audit and any( token in query.normalized_message for token in ( "match confidence", "row by row match confidence", "row-by-row match confidence", ) ) lifecycle_devices: List[Dict[str, str]] = [] lifecycle_inputs: List[Dict[str, str]] = [] lifecycle_notes: List[str] = [] lifecycle_detail_cache: Dict[str, Dict[str, Any]] = {} for token in query.device_texts[:8]: normalized_match = _as_dict(core.normalize_catalog_device(manufacturer_text=query.manufacturer_text, product_text=token)) if normalized_match.get("ok"): resolved_match = _as_dict(normalized_match.get("match")) requested_label = _norm(token) family_level = self._router_workbook_should_treat_exact_match_as_family_alias(token, resolved_match) lifecycle_inputs.append( { "requested_label": requested_label, "requested_compact": _compact_model(requested_label), "resolved_match": resolved_match, "family_level": family_level, } ) lifecycle_devices.append( { "manufacturer_text": query.manufacturer_text, "product_text": str(resolved_match.get("product_id") or token), } ) if family_level: lifecycle_notes.append( f"`{token}` mapped to workbook SKU `{_norm(resolved_match.get('display_name') or resolved_match.get('product_id') or token)}`; lifecycle stays family-level until the exact variant is confirmed." ) continue if str(normalized_match.get("error") or "") == "ambiguous_product": collapsed = self._router_workbook_collapse_family_ambiguity(token, normalized_match) if collapsed: requested_label = _norm(token) lifecycle_inputs.append( { "requested_label": requested_label, "requested_compact": _compact_model(requested_label), "resolved_match": { **_as_dict(collapsed), "_family_collapsed": True, }, "family_level": True, } ) lifecycle_devices.append( { "manufacturer_text": query.manufacturer_text, "product_text": str(collapsed.get("product_id") or token), } ) lifecycle_notes.append( f"`{token}` was answered at the workbook family level because multiple exact SKUs share that same family token." ) continue lifecycle_inputs.append( { "requested_label": _norm(token), "requested_compact": _compact_model(token), "resolved_match": {}, "family_level": False, } ) lifecycle_devices.append({"manufacturer_text": query.manufacturer_text, "product_text": token}) if asks_detail_bundle and lifecycle_inputs: requested_label = _norm(_as_dict(lifecycle_inputs[0]).get("requested_label") or (query.device_texts[0] if query.device_texts else "")) resolved_detail = self._router_workbook_resolve_detail_or_family( core, manufacturer_text=query.manufacturer_text, product_text=requested_label, ) if resolved_detail.get("ok"): detail = _as_dict(resolved_detail.get("detail")) review_required = ( bool((detail.get("match") or {}).get("feature_gap_blocked")) or (not bool((detail.get("lifecycle") or {}).get("has_authoritative_lifecycle"))) or bool(detail.get("_family_safe")) ) return { "assistant": _format_shell( _details_result(detail), _why_lines_for_intent("details", {"review_required": review_required}), _next_actions_for_intent("details", {"review_required": review_required}), ), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_details_lifecycle_bridge", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "router_resolution_mode": str(resolved_detail.get("resolution_mode") or "exact"), "router_workbook_tables": self._router_workbook_source_tables("details"), "router_answer_trace": _details_trace(detail, review_required), "router_debug_refs": _detail_debug_refs(detail), "router_fact_bundle": self._router_workbook_fact_bundle_summary(_as_dict(detail.get("_fact_bundle"))), "review_required": review_required, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } if asks_alias_audit and lifecycle_inputs: rows = ( [ "| Requested | Workbook match | Confidence | Classification | Notes |", "| --- | --- | --- | --- | --- |", ] if asks_match_confidence else [ "| Requested | Workbook match | Classification | Notes |", "| --- | --- | --- | --- |", ] ) exact_count = 0 alias_count = 0 review_count = 0 review_required = False debug_refs: List[Dict[str, Any]] = [] for requested in lifecycle_inputs[:12]: requested_label = _norm(requested.get("requested_label") or "") resolved_detail = self._router_workbook_resolve_detail_or_family( core, manufacturer_text=query.manufacturer_text, product_text=requested_label, ) workbook_match = "" classification = "Needs review" note = "" confidence_value = "" if resolved_detail.get("ok"): detail = _as_dict(resolved_detail.get("detail")) match = _as_dict(detail.get("match")) product = _as_dict(detail.get("product")) confidence = self._router_workbook_match_confidence(match, input_text=requested_label) confidence_score = max(0, min(100, int(confidence.get("score") or 0))) confidence_value = f"{confidence_score}/100" if confidence_score > 0 else "" family_note = _norm(detail.get("_family_safe_note") or "") correction_note = _norm(confidence.get("correction_note") or family_note) workbook_match = _norm(match.get("product_id") or match.get("display_name") or product.get("product_id") or product.get("display_name") or requested_label) resolution_mode = str(resolved_detail.get("resolution_mode") or "exact") if resolution_mode == "exact" and not correction_note: classification = "Safe exact match" note = "Exact workbook model match." exact_count += 1 else: classification = "Likely alias correction" note = correction_note or "Workbook normalization changed or narrowed this label; exact variant still needs confirmation." alias_count += 1 review_required = True debug_refs.extend(_detail_debug_refs(detail)) else: detail = _as_dict(resolved_detail.get("response")) typo_candidate = self._router_workbook_likely_typo_candidate(requested_label) if typo_candidate: workbook_match = typo_candidate classification = "Likely alias correction" confidence_value = "55/100" note = f"Looks like a typo for `{typo_candidate}`, but I left it provisional instead of forcing a workbook match." alias_count += 1 review_required = True else: workbook_match = "No workbook match" confidence_value = "15/100" note = _norm(detail.get("message") or "No workbook-backed router match was found.") review_count += 1 review_required = True row_cells = [ _md_cell(requested_label), _md_cell(workbook_match or "No workbook match"), ] if asks_match_confidence: row_cells.append(_md_cell(confidence_value or "Not scored")) row_cells.extend( [ _md_cell(classification), _md_cell(note or "Needs exact label confirmation."), ] ) rows.append( "| " + " | ".join(row_cells) + " |" ) summary_lines = [ "Workbook-backed alias audit:", "", f"Safe exact matches: {exact_count}. Likely alias corrections: {alias_count}. Needs review: {review_count}.", "", "\n".join(rows), ] return { "assistant": _format_shell( "\n".join(summary_lines), [ "Alias/correction handling stays workbook-backed and deterministic.", "Likely alias corrections are surfaced as provisional unless the workbook has an exact normalized match.", ], [ "Send the exact device label for any `Needs review` row before quoting lifecycle or replacement paths.", "Ask me to convert any reviewed rows into a fleet lifecycle snapshot once the labels are confirmed.", ], ), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_alias_audit", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "router_workbook_tables": self._router_workbook_source_tables("fleet_lifecycle"), "router_answer_trace": _router_answer_trace( intent="lifecycle", summary=f"Audited {len(lifecycle_inputs[:12])} requested router labels against workbook normalization.", items=[ _trace_item("Requested labels", len(lifecycle_inputs[:12])), _trace_item("Safe exact matches", exact_count), _trace_item("Likely alias corrections", alias_count), _trace_item("Needs review", review_count), _trace_item("Review flag", _yes_no(review_required)), ], warnings=["Some rows still need an exact label confirmation before lifecycle or replacement guidance is quote-safe."] if review_required else [], ), "router_debug_refs": self._router_workbook_compact_debug_refs(debug_refs), "review_required": review_required, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } batch = _as_dict( core.get_catalog_lifecycle_batch( devices=lifecycle_devices ) ) if not batch.get("ok"): ambiguous = next( (item for item in list(batch.get("failures") or []) if str(_as_dict(item).get("error") or "") == "ambiguous_product"), None, ) if ambiguous: return self._router_workbook_clarify_response(query, _as_dict(ambiguous), st, fast_domain, orchestration_meta) synthesized = self._router_workbook_synthesize_lifecycle_batch(lifecycle_inputs) if synthesized.get("ok"): batch = synthesized else: return { "assistant": _format_shell( str(synthesized.get("message") or batch.get("message") or "No workbook-backed lifecycle matches were found."), ["Lifecycle answers are now workbook-backed and abstain when the device does not resolve cleanly."], ["Provide the exact router model/SKU or ask for a current-device shortlist instead."], ), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_lifecycle_unmatched", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } batch_failures = [row for row in list(batch.get("failures") or []) if isinstance(row, dict)] review_required = bool(batch_failures) or any( not bool(_as_dict(row).get("lifecycle", {}).get("has_authoritative_lifecycle")) for row in list(batch.get("devices") or []) if isinstance(row, dict) ) aligned_rows: List[Dict[str, Any]] = [] returned_rows = [item for item in list(batch.get("devices") or []) if isinstance(item, dict)] row_lookup: Dict[str, Dict[str, Any]] = {} def _lifecycle_row_detail(product_key: str) -> Dict[str, Any]: key = str(product_key or "").strip() if not key: return {} cached = lifecycle_detail_cache.get(key) if cached is not None: return dict(cached) detail = _as_dict(core.get_catalog_device_details_by_key(product_key=key)) lifecycle_detail_cache[key] = dict(detail) return detail def _row_lookup_keys(row: Dict[str, Any]) -> List[str]: keys: List[str] = [] for section in ("match", "product"): item = _as_dict(row.get(section)) for value in ( item.get("product_key"), item.get("product_id"), item.get("display_name"), item.get("family_group"), ): compact = _compact_model(value) if compact and compact not in keys: keys.append(compact) for value in ( row.get("product_key"), row.get("product_id"), row.get("display_name"), ): compact = _compact_model(value) if compact and compact not in keys: keys.append(compact) return keys for returned in returned_rows: for key in _row_lookup_keys(returned): row_lookup.setdefault(key, returned) for index, requested in enumerate(lifecycle_inputs): requested_label = _norm(requested.get("requested_label")) if not requested_label: continue requested_match = _as_dict(requested.get("resolved_match")) row = {} lookup_keys = [ _compact_model(requested_label), _compact_model(requested.get("requested_compact") or ""), _compact_model(requested_match.get("product_key") or ""), _compact_model(requested_match.get("product_id") or ""), _compact_model(requested_match.get("display_name") or ""), _compact_model(requested_match.get("family_group") or ""), ] for key in lookup_keys: if key and key in row_lookup: row = _as_dict(row_lookup.get(key)) break if row: match = _as_dict(row.get("match")) or requested_match detail = _lifecycle_row_detail(str(match.get("product_key") or requested_match.get("product_key") or "")) product = _as_dict(detail.get("product") or row.get("product")) or requested_match features = _as_dict(detail.get("features")) lifecycle = _as_dict(row.get("lifecycle")) replacements = _as_dict(row.get("replacements")) else: if not requested_match: continue match = requested_match or { "product_key": requested_label, "product_id": requested_label, "display_name": requested_label, "subject_display_name": requested_label, "_requested_label": requested_label, "status_bucket": "Needs exact workbook match", "current_recommendable_flag": False, } product = requested_match or { "product_key": requested_label, "product_id": requested_label, "display_name": requested_label, "subject_display_name": requested_label, "_requested_label": requested_label, "status_bucket": "Needs exact workbook match", "current_recommendable_flag": False, } features = {} lifecycle = { "status": "Needs exact workbook match", "has_authoritative_lifecycle": False, } replacements = { "primary_replacement": None, "backup_replacements": [], "no_replacement": False, } family_level = bool(requested.get("family_level")) lifecycle = self._router_workbook_enrich_lifecycle_row(match, product, lifecycle) fact_bundle = self._router_workbook_fact_bundle_from_payload( match={ **match, "subject_display_name": requested_label, "_requested_label": requested_label, "_family_collapsed": family_level or bool(match.get("_family_collapsed")), }, product={ **product, "subject_display_name": requested_label, "_requested_label": requested_label, "_family_collapsed": family_level or bool(product.get("_family_collapsed")), }, features=features, lifecycle=lifecycle, replacements=replacements, requested_text=requested_label, resolution_mode="family_safe_partial" if family_level else "exact", family_safe=family_level, canonical_display_name=_norm( match.get("_canonical_display_name") or match.get("_canonical_resolved_label") or product.get("_canonical_display_name") or product.get("_canonical_resolved_label") ), ) aligned_rows.append( { **row, "match": { **match, "subject_display_name": requested_label, "_requested_label": requested_label, "_family_collapsed": family_level or bool(match.get("_family_collapsed")), }, "product": { **product, "subject_display_name": requested_label, "_requested_label": requested_label, "_family_collapsed": family_level or bool(product.get("_family_collapsed")), }, "lifecycle": lifecycle, "replacements": replacements, "_fact_bundle": fact_bundle, "_family_collapsed": family_level, } ) batch["devices"] = aligned_rows batch["failures"] = [] existing_notes = {str(note) for note in list(batch.get("notes") or []) if _norm(note)} rendered_labels = { _compact_model( _norm( _as_dict(row.get("match")).get("subject_display_name") or _as_dict(row.get("product")).get("subject_display_name") or "" ) ) for row in aligned_rows if isinstance(row, dict) } unresolved_labels = [ _norm(requested.get("requested_label") or "") for requested in lifecycle_inputs if _norm(requested.get("requested_label") or "") and _compact_model(requested.get("requested_label") or "") not in rendered_labels ] for missing_label in unresolved_labels: note_text = self._router_workbook_unresolved_model_note(missing_label) if note_text not in existing_notes: lifecycle_notes.append(note_text) existing_notes.add(note_text) if unresolved_labels: batch["_unresolved_labels"] = unresolved_labels if lifecycle_notes: batch["notes"] = [*list(batch.get("notes") or []), *lifecycle_notes] review_required = bool( any( not bool(_as_dict(row).get("lifecycle", {}).get("has_authoritative_lifecycle")) for row in list(batch.get("devices") or []) if isinstance(row, dict) ) ) or bool(lifecycle_notes) return { "assistant": _format_shell(_lifecycle_result(batch), _why_lines_for_intent("lifecycle", {"review_required": review_required}), _next_actions_for_intent("lifecycle", {"review_required": review_required})), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_lifecycle", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "router_workbook_tables": self._router_workbook_source_tables(plan_intent), "router_answer_trace": _lifecycle_trace(batch, review_required), "router_debug_refs": _lifecycle_debug_refs(batch), "router_fact_bundles": [ self._router_workbook_fact_bundle_summary(_as_dict(row.get("_fact_bundle"))) for row in aligned_rows[:8] if isinstance(row, dict) ], "review_required": review_required, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } if plan_intent == "fleet_lifecycle": fleet_items = self._extract_router_workbook_fleet_items(message, core) prefer_5g_target = bool(re.search(r"\b5g\b", query.normalized_message, flags=re.IGNORECASE)) text = str(message or "") if not fleet_items: customer_candidates: List[str] = [] for token in re.findall(r"\b[A-Z][A-Za-z&'\\-]{2,30}\b", text): tok_low = token.lower() if tok_low in { "customer", "customers", "fleet", "fleets", "combined", "replacement", "replacements", "confidence", "notes", "table", "tables", "provisional", "alternatives", "clarification", "clarifications", "prompt", "prompts", "unknown", "lifecycle", "model", "models", "device", "devices", "inventory", "portfolio", "strategy", "recommendation", "recommendations", "build", "phased", "need", "needs", "q1", "q2", "q3", "q4", "5g", "4g", "lte", "given", "provide", "provided", "recommend", "show", "list", "tell", "what", "which", "please", }: continue if self._lookup_router_lifecycle_key(token) or self._lookup_router_fact_key(token): continue if token not in customer_candidates: customer_candidates.append(token) customer_candidates = customer_candidates[:3] total_units: Optional[int] = None model_bucket_count: Optional[int] = None total_match = re.search(r"\b(\d{1,6})\s+(?:legacy\s+)?units?\b", text, flags=re.IGNORECASE) if total_match: try: total_units = int(str(total_match.group(1) or "").replace(",", "")) except Exception: total_units = None model_match = re.search(r"\bacross\s+(\d{1,2})\s+models?\b", text, flags=re.IGNORECASE) or re.search( r"\b(\d{1,2})\s+models?\b", text, flags=re.IGNORECASE, ) if model_match: try: model_bucket_count = int(str(model_match.group(1) or "")) except Exception: model_bucket_count = None scope_bits: List[str] = [] if total_units: scope_bits.append(f"{total_units} total legacy units") if model_bucket_count: scope_bits.append(f"{model_bucket_count} unspecified model buckets") if customer_candidates: scope_bits.insert(0, f"customer names: {', '.join(customer_candidates)}") scope_text = " across ".join(scope_bits) if scope_bits else "Unspecified legacy fleet" parsed_scope_excerpt = f"Requested provisional fleet planning scope: {scope_text}. Exact model/SKU rows are still required before deterministic lifecycle dates or replacement names are emitted." parsed_scope_source = { "id": "RRW2", "domain": fast_domain, "doc": "Parsed request input", "relative_path": "", "chunk_id": "router_workbook:fleet_scope_unmatched", "location": "", "excerpt": parsed_scope_excerpt, "score": 0.99, } if customer_candidates: lines = [ "Workbook-backed customer-level fleet planning template (provisional until exact models are supplied):", "", f"Requested scope kept provisional: `{scope_text}`.", "Exact model/SKU rows are still required before deterministic lifecycle dates or replacement names are emitted.", "", "| Customer | Inventory status | Q4 5G objective | Provisional 4G fallback | Provisional 5G target | Confidence note |", "| --- | --- | --- | --- | --- | --- |", ] for name in customer_candidates: lines.append( "| " + " | ".join( [ _md_cell(name), "Model/SKU list not yet provided", "Build deterministic replacement table after inventory capture", "Pending model inventory", "Pending model inventory", "Customer name captured; model list still required.", ] ) + " |" ) lines.extend( [ "", "Confidence notes:", "- Customer names are preserved, but no exact model rows were supplied yet.", "- I can convert this into deterministic lifecycle and replacement rows once you paste the model list for each customer.", "- Current 4G fallback and 5G target lanes stay provisional until exact model/SKU inventory is captured.", ] ) else: bucket_rows = max(1, min(int(model_bucket_count or 1), 4)) lines = [ "Workbook-backed fleet lifecycle planning template (provisional until exact models are supplied):", "", f"Requested scope kept provisional: `{scope_text}`.", "Exact model/SKU rows are still required before deterministic lifecycle dates or replacement names are emitted.", "", "| Model bucket | Qty | Lifecycle status | Provisional 4G fallback | Provisional 5G target |", "| --- | --- | --- | --- | --- |", ] qty_cell = f"Part of {total_units} total" if total_units else "Breakout needed" for idx in range(1, bucket_rows + 1): label = f"Legacy model {idx}" if bucket_rows > 1 else "Legacy fleet" lines.append( "| " + " | ".join( [ _md_cell(label), _md_cell(qty_cell), "Unknown until exact workbook match", _md_cell("Current workbook-backed LTE bridge after exact model parse"), _md_cell("Current workbook-backed 5G successor after exact model parse"), ] ) + " |" ) return { "assistant": _format_shell( "\n".join(lines), [ "This stays workbook-backed and keeps recommendations provisional because the four exact models are not named yet.", "Current-only policy stays in force: 4G is treated as a continuity bridge, and 5G targets stay on current workbook-backed successors after exact model matching.", "Exact model/SKU rows are still required before lifecycle dates, deterministic replacements, or BOM-ready outputs are finalized.", ], [ "Paste lines like `Customer 24 RV50X, 10 AER2200` or `14 XR60 8 MG52`.", "If you already know the four models, paste one line per model and I will convert this provisional plan into a deterministic replacement table.", "If you only need one model, ask the lifecycle question directly instead.", ], ), "sources": [*workbook_sources, parsed_scope_source], "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_fleet_lifecycle_unmatched", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } matched_devices = [ _as_dict(item.get("_resolved_detail")) for item in fleet_items if bool(item.get("matched")) and isinstance(item.get("_resolved_detail"), dict) ] unresolved_matched_items = [ item for item in fleet_items if bool(item.get("matched")) and (not isinstance(item.get("_resolved_detail"), dict)) ] if unresolved_matched_items: batch = _as_dict( core.get_catalog_lifecycle_batch( devices=[ { "manufacturer_text": query.manufacturer_text, "product_text": str(item.get("product_text") or item.get("model_display") or ""), } for item in unresolved_matched_items ] ) ) matched_devices.extend([row for row in list(batch.get("devices") or []) if isinstance(row, dict)]) unmatched_count = len([item for item in fleet_items if not bool(item.get("matched"))]) review_required = unmatched_count > 0 or any( (not bool(_as_dict(row).get("lifecycle", {}).get("has_authoritative_lifecycle"))) or bool(_as_dict(row).get("match", {}).get("_family_collapsed")) or bool(_as_dict(row).get("product", {}).get("_family_collapsed")) or bool(_as_dict(row).get("_family_collapsed")) for row in matched_devices ) fleet_view = _fleet_view(fleet_items, prefer_5g_target=prefer_5g_target) return { "assistant": _format_shell( _fleet_result({"fleet_items": fleet_items, "devices": matched_devices, "prefer_5g_target": prefer_5g_target}), _why_lines_for_intent("fleet_lifecycle", {"review_required": review_required}), _next_actions_for_intent("fleet_lifecycle", {"review_required": review_required}), ), "sources": [*workbook_sources, *fleet_sources], "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_fleet_lifecycle", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "router_workbook_tables": self._router_workbook_source_tables(plan_intent), "router_answer_trace": _fleet_trace(fleet_view, review_required), "review_required": review_required, "citation_quorum_not_required": True, "legacy_csv_replaced": True, "fleet_row_count": len(fleet_items), "fleet_unmatched_count": unmatched_count, "router_fleet_view": fleet_view, "router_debug_refs": self._router_workbook_compact_debug_refs( [row.get("debug_ref") for row in list(fleet_view.get("rows") or []) if isinstance(row, dict)] ), **orchestration_meta, }, } if plan_intent == "replacements": replacement_resolution = self._router_workbook_resolve_replacement_analysis( core, manufacturer_text=query.manufacturer_text, product_text=query.device_texts[0], ) if not replacement_resolution.get("ok"): analysis = _as_dict(replacement_resolution.get("response")) if str(analysis.get("error") or "") == "ambiguous_product": return self._router_workbook_clarify_response(query, analysis, st, fast_domain, orchestration_meta) numeric_model_reference = bool( re.search(r"\bmodel\s+\d{2,6}\b", message, flags=re.IGNORECASE) or re.search(r"\b\d{2,6}\b", message) ) next_actions = ["Provide the exact router model/SKU and I will return the replacement lanes."] if numeric_model_reference: next_actions.insert( 0, "I need the actual router model/SKU here. A numeric label like `228` is not enough to map a workbook replacement path safely.", ) fallback_sources = list(replacement_resolution.get("sources") or workbook_sources) fallback_files = list(replacement_resolution.get("files") or [workbook_file]) return { "assistant": _format_shell( str(analysis.get("message") or "I could not resolve that router in the workbook."), ["Replacement answers are workbook-backed and require an exact model match."], next_actions, ), "sources": fallback_sources, "files": fallback_files, "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_replacements_unmatched", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } analysis = { **_as_dict(replacement_resolution.get("analysis")), "_resolution_mode": str(replacement_resolution.get("resolution_mode") or "exact"), } review_required = bool(analysis.get("review_required")) replacement_evidence = self._router_workbook_replacement_evidence_from_analysis( core, analysis, requested_model=_norm(query.device_texts[0] if query.device_texts else ""), prefer_5g_target=bool(re.search(r"\b5g\b", query.normalized_message, flags=re.IGNORECASE)), resolution_mode=str(replacement_resolution.get("resolution_mode") or "exact"), ) replacement_view = self._router_workbook_replacement_view_from_analysis( analysis, replacement_evidence, review_required=review_required, ) response_sources = list(replacement_resolution.get("sources") or workbook_sources) response_files = list(replacement_resolution.get("files") or [workbook_file]) return { "assistant": _format_shell( _replacements_result(analysis), _why_lines_for_intent( "replacements", { "review_required": review_required, "source_mode": str( _as_dict(analysis).get("_replacement_source_mode") or replacement_evidence.get("replacement_source_mode") or "" ), "uses_sourced_fallback": bool( str( _as_dict(analysis).get("_replacement_source_mode") or replacement_evidence.get("replacement_source_mode") or "" ).strip().lower() == "lifecycle_fallback" or bool(_as_dict(analysis).get("_replacement_subject_lifecycle")) or bool(_as_dict(analysis).get("_replacement_legacy_lifecycle")) ), }, ), _next_actions_for_intent("replacements", {"review_required": review_required}), ), "sources": response_sources, "files": response_files, "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_replacements", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "router_workbook_tables": self._router_workbook_source_tables(plan_intent), "router_answer_trace": _replacements_trace(analysis, review_required), "router_debug_refs": _replacements_debug_refs(analysis), "router_fact_bundle": self._router_workbook_fact_bundle_summary(_as_dict(analysis.get("_fact_bundle"))), "review_required": review_required, "router_resolution_mode": str(replacement_resolution.get("resolution_mode") or "exact"), "router_replacement_source_mode": str(analysis.get("_replacement_source_mode") or ""), "router_replacement_view": replacement_view, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } if plan_intent == "search": search = _as_dict(self._router_workbook_ranked_search(core, query)) review_required = _search_requires_review(search) shortlist_view = _search_shortlist_view(search) return { "assistant": _format_shell(_search_result(search), _why_lines_for_intent("search", {"review_required": review_required}), _next_actions_for_intent("search", {"review_required": review_required})), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_search", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "router_workbook_tables": self._router_workbook_source_tables(plan_intent), "router_answer_trace": _search_trace(search, review_required), "review_required": review_required, "citation_quorum_not_required": True, "legacy_csv_replaced": True, "current_only": bool(query.search_filters.get("current_only", True)), "router_shortlist_view": shortlist_view, "router_debug_refs": _search_debug_refs(search), **orchestration_meta, }, } if plan_intent == "antenna": analysis = _as_dict(core.analyze_catalog_device(manufacturer_text=query.manufacturer_text, product_text=query.device_texts[0])) if not analysis.get("ok"): if str(analysis.get("error") or "") == "ambiguous_product": return self._router_workbook_clarify_response(query, analysis, st, fast_domain, orchestration_meta) return { "assistant": _format_shell( str(analysis.get("message") or "I could not resolve that router in the workbook."), ["Antenna guidance needs an exact workbook router match before I can safely continue."], ["Provide the exact router model/SKU and any install constraints you already know."], ), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_antenna_unmatched", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } review_required = bool(analysis.get("review_required")) return { "assistant": _format_shell(_antenna_result(analysis), _why_lines_for_intent("antenna", {"review_required": review_required}), _next_actions_for_intent("antenna", {"review_required": review_required})), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_antenna", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "router_workbook_tables": self._router_workbook_source_tables(plan_intent), "router_answer_trace": _antenna_trace(analysis, review_required), "router_debug_refs": _antenna_debug_refs(analysis), "review_required": review_required, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } if plan_intent == "survey": survey_context = self._resolve_router_workbook_survey_context(message, st, core) if not survey_context.get("ok"): surveys = [row for row in list(survey_context.get("surveys") or []) if isinstance(row, dict)] lines = [str(survey_context.get("message") or "I need a workbook survey context before I can answer this safely.")] if surveys: lines.extend(["", "| Survey key | Site | Date | Outcome |", "| --- | --- | --- | --- |"]) for item in surveys[:5]: lines.append( "| " + " | ".join( [ _md_cell(item.get("survey_key") or ""), _md_cell(item.get("site_name") or "Not listed"), _md_cell(item.get("survey_date") or "Not listed"), _md_cell(item.get("outcome_class") or "Not evaluated"), ] ) + " |" ) return { "assistant": _format_shell( "\n".join(lines), ["Survey interpretation in Unified KB uses the workbook runtime survey rows already loaded in Rapid Router."], [ "Reply with the survey key or site name you want me to use.", "If no survey has been uploaded yet, load it in Rapid Router first.", ], ), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_survey_context_needed", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } survey_key = str(survey_context.get("survey_key") or "") st.router_lifecycle_state = { **_as_dict(st.router_lifecycle_state), "last_survey_key": survey_key, } inline_updates = self._router_workbook_parse_inline_survey_updates(query.raw_message) inline_update_out: Dict[str, Any] = {} if inline_updates.get("ok"): inline_update_out = _as_dict( core.update_catalog_survey_followup( survey_key=survey_key, restriction_updates=_as_dict(inline_updates.get("restriction_updates")), header_updates=_as_dict(inline_updates.get("header_updates")), point_updates=_as_dict(inline_updates.get("point_updates")), ) ) if not inline_update_out.get("ok"): return { "assistant": _format_shell( str(inline_update_out.get("message") or "I could not apply those inline survey updates before re-evaluating the active survey."), [ "Active survey follow-up changes must map cleanly to workbook survey fields before I can re-score the placement path.", ], [ "Reply with one concrete update at a time, or ask for the current active survey summary first.", ], ), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_survey_inline_update_failed", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, "survey_key": survey_key, **orchestration_meta, }, } inline_update_out["applied_fields"] = list(inline_updates.get("applied_fields") or []) survey_eval = _as_dict( core.evaluate_catalog_survey( survey_key=survey_key, manufacturer_text=query.manufacturer_text, product_text=query.device_texts[0] if query.device_texts else "", ) ) if not survey_eval.get("ok"): return { "assistant": _format_shell( str(survey_eval.get("message") or "I could not evaluate that workbook survey context."), ["Survey interpretation is workbook-backed and will abstain when the runtime survey rows are missing or incomplete."], [ "Confirm the survey key/site name or reload the survey in Rapid Router.", "If you already know the selected router, include it in the question for tighter placement guidance.", ], ), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_survey_unmatched", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "review_required": True, "citation_quorum_not_required": True, "legacy_csv_replaced": True, **orchestration_meta, }, } applied_survey_updates = self._router_workbook_survey_update_entries(inline_update_out) if inline_update_out.get("ok") else [] if applied_survey_updates: survey_eval["_applied_updates"] = applied_survey_updates review_required = bool(_as_dict(survey_eval.get("recommendation")).get("review_required_flag")) survey_followups = _survey_followup_requirements(survey_eval) if survey_followups: pending_payload = { "type": "router_workbook_survey_followup", "domain": fast_domain, "survey_key": survey_key, "original_message": query.raw_message, "requirements": survey_followups, } st.pending = pending_payload st.router_lifecycle_state = { **_as_dict(st.router_lifecycle_state), "last_survey_key": survey_key, "pending": pending_payload, } else: if str(_as_dict(st.pending).get("type") or "") == "router_workbook_survey_followup": st.pending = {} router_state = _as_dict(st.router_lifecycle_state) if str(_as_dict(router_state.get("pending")).get("type") or "") == "router_workbook_survey_followup": st.router_lifecycle_state = { **router_state, "last_survey_key": survey_key, "pending": {}, } return { "assistant": _format_shell( _survey_result(survey_eval), _why_lines_for_intent("survey", {"review_required": review_required}), _next_actions_for_intent( "survey", { "review_required": review_required, "survey_followups": survey_followups, }, ), ), "sources": workbook_sources, "files": [workbook_file], "meta": { "domain": fast_domain, "retrieval_mode": "deterministic_router_workbook_survey", "router_intelligence_intent": plan_intent, "router_intelligence_source": "workbook", "router_workbook_tables": self._router_workbook_source_tables(plan_intent), "router_answer_trace": _survey_trace(survey_eval, review_required), "router_debug_refs": _survey_debug_refs(survey_eval), "router_survey_explanation_view": self._router_workbook_survey_explanation_view( survey_eval, survey_followups=survey_followups, ), "review_required": review_required, "survey_followup_needed": bool(survey_followups), "survey_followup_remaining_count": len(survey_followups), "survey_followup_remaining_requirements": survey_followups, "survey_followup_applied_fields": [item.get("field") for item in applied_survey_updates], "survey_followup_update_count": len(applied_survey_updates), "survey_followup_applied_updates": applied_survey_updates, "citation_quorum_not_required": True, "legacy_csv_replaced": True, "survey_key": survey_key, **orchestration_meta, }, } return None def _is_low_context_followup(self, message: str) -> bool: low = str(message or "").strip().lower() if not low: return False if _looks_like_pots(low) or _looks_like_masters(low) or _looks_like_router_docs(low) or _looks_like_router_lifecycle(low): return False followup_phrases = ( "deep compare", "deep comparison", "provide some examples", "show some examples", "give some examples", "examples then", "more examples", "expand that", "expand this", "same as above", "same as before", "what about that", "what about those", ) if any(p in low for p in followup_phrases): return True words = re.findall(r"[a-z0-9]+", low) if len(words) <= 4: short_generic = {"then", "more", "details", "examples", "continue", "expand", "deeper"} if any(w in short_generic for w in words): return True return False def _resolve_mode(self, message: str, st: UnifiedKnowledgebaseState, requested_mode: str) -> str: req = _norm_mode(requested_mode) if req != "auto": return req if st.mode != "auto": explicit = self._explicit_mode_from_message(message) if explicit and explicit != st.mode: low = str(message or "").lower() cross_domain_router_override = _contains_any(low, _ROUTER_PLATFORM_HINTS) or ( bool(self._extract_router_models_cached(message)) and _contains_any(low, _ROUTER_DOC_HINTS) ) cross_domain_lifecycle_override = bool( _looks_like_router_lifecycle(message) and _contains_any(low, (_ROUTER_STATUS_HINTS + _ROUTER_REPLACEMENT_HINTS)) ) if cross_domain_router_override or cross_domain_lifecycle_override: return explicit return st.mode if self._is_low_context_followup(message): last_mode = _norm_mode(st.last_mode) if last_mode in _MODE_LABELS and last_mode != "auto": return last_mode explicit = self._explicit_mode_from_message(message) if explicit in _MODE_LABELS: return explicit key = f"intent:{_norm(message).lower()}" cached = self._l2_get(key) if isinstance(cached, str) and cached in _MODE_LABELS: return cached mode = _classify_mode(message) self._l2_set(key, mode) return mode def _normalize_domain_files(self, domain: str, files: Sequence[Any]) -> List[str]: out: List[str] = [] for raw in files or []: text = str(raw or "").strip() if not text: continue if Path(text).is_absolute(): out.append(text) continue if text.startswith("http://") or text.startswith("https://"): out.append(text) continue if domain == "router_docs": if text.lower().endswith(".csv"): out.append(text) continue rel = self._router_file_map.get(Path(text).name.lower(), text) out.append(_mounted_file_href("/router_rag_files", rel)) elif domain == "masters": rel = self._masters_file_map.get(Path(text).name.lower(), text) out.append(_mounted_file_href("/masters_files", rel)) elif domain == "pots": rel = self._pots_file_map.get(Path(text).name.lower(), text) out.append(_mounted_file_href("/pots_files", rel)) else: out.append(text) deduped: List[str] = [] seen = set() for item in out: if item in seen: continue seen.add(item) deduped.append(item) return deduped[: int(self.max_files_per_response)] def _normalize_sources(self, domain: str, sources: Sequence[Any]) -> List[Dict[str, Any]]: out: List[Dict[str, Any]] = [] for idx, raw in enumerate(sources or [], start=1): src = raw if isinstance(raw, dict) else {} doc = str(src.get("doc") or src.get("document") or "").strip() relative_path = str(src.get("relative_path") or "").strip() location = str(src.get("location") or src.get("loc") or "").strip() chunk_id = str(src.get("chunk_id") or src.get("id") or "").strip() excerpt = str(src.get("excerpt") or "").strip() if not relative_path and doc: if domain == "router_docs": relative_path = self._router_file_map.get(Path(doc).name.lower(), doc if doc.lower().endswith(".csv") else "") elif domain == "masters": relative_path = self._masters_file_map.get(Path(doc).name.lower(), "") elif domain == "pots": relative_path = self._pots_file_map.get(Path(doc).name.lower(), "") href = "" if relative_path: if Path(relative_path).is_absolute(): href = relative_path elif domain == "router_docs": if str(relative_path).lower().endswith(".csv"): href = str(relative_path) else: href = _mounted_file_href("/router_rag_files", relative_path) elif domain == "masters": href = _mounted_file_href("/masters_files", relative_path) elif domain == "pots": href = _mounted_file_href("/pots_files", relative_path) out.append( { "id": str(src.get("id") or f"S{idx}"), "domain": domain, "doc": doc, "relative_path": href or relative_path, "chunk_id": chunk_id, "location": location, "excerpt": excerpt, "score": float(src.get("score") or 0.0), } ) return out def _is_low_value_source_excerpt(self, excerpt: str) -> bool: text = _norm(excerpt) if not text: return True low = text.lower() if any(h in low for h in _GENERIC_SOURCE_EXCERPT_HINTS): return True if ("not listed" in low) and (len(text) < 96): return True has_structured_signal = bool(re.search(r"[=:]|\\b\\d{2,}\\b", text)) if (len(text) < 64) and (not has_structured_signal): return True alpha = sum(1 for ch in text if ch.isalpha()) if (alpha / float(max(1, len(text)))) < 0.32: return True return False def _is_internal_weak(self, assistant: str, sources: Sequence[Any], meta: Dict[str, Any]) -> bool: had_sources = bool(sources) retrieval_mode = str(meta.get("retrieval_mode") or "").lower() if retrieval_mode in { "deterministic_router_price_variant_index", "deterministic_router_price_verizon_gateway_index", "deterministic_verizon_gateway_matrix_fast", "deterministic_rapid_router_catalog_list_fast", "deterministic_rapid_router_catalog_compare_fast", "deterministic_rapid_router_catalog_price_fast", "deterministic_rapid_router_catalog_feature_fast", }: return False assistant_text = _norm_preserve(assistant) has_table_signal = assistant_text.count("|") >= 12 and ("| ---" in assistant_text) has_structured_content = len(assistant_text) >= 220 and ("not available in the provided documents" not in assistant_text.lower()) if self._meaningful_source_count(sources) > 0: return False if bool(meta.get("web_assisted")): return False if bool(meta.get("llm_assisted")) or bool(meta.get("citation_quorum_not_required")): return False low = str(assistant or "").lower() if any(h in low for h in _MISSING_INFO_HINTS): return True if had_sources: if retrieval_mode.startswith("deterministic") or retrieval_mode.endswith("_fast"): if has_table_signal or has_structured_content: return False return True return False if retrieval_mode in {"internal", "internal_weak", "internal_weak_no_web"}: return True return retrieval_mode in {"no_internal_hits", "missing", "not_found"} def _router_docs_query_prefers_workbook_details_rescue(self, message: str) -> bool: low = _normalize_router_query_text(message) if not low: return False if _contains_any(low, ("from docs only", "from documented specs only", "documented specs only")): return False query = parse_router_intelligence_query(message) if query is None or str(query.intent or "").strip() != "details": return False device_texts = [str(item).strip() for item in list(query.device_texts or []) if str(item or "").strip()] if len(device_texts) != 1: return False return True def _router_docs_response_is_pricing_only_fact_row( self, sources: Sequence[Any], meta: Dict[str, Any], ) -> bool: retrieval_mode = str(meta.get("retrieval_mode") or "").strip().lower() if retrieval_mode not in {"deterministic_router_fact_index", "deterministic_router_price_variant_index"}: return False source_list = [item for item in list(sources or []) if isinstance(item, dict)] if not source_list: return False pricing_hits = 0 for source in source_list: doc_text = " ".join( [ str(source.get("doc") or ""), str(source.get("relative_path") or ""), str(source.get("chunk_id") or ""), ] ).lower() if "router_pricing_catalog_normalized" in doc_text or "router_pricing_catalog" in doc_text: pricing_hits += 1 continue return False return pricing_hits > 0 def _router_docs_response_lacks_requested_model_anchor( self, message: str, assistant: str, sources: Sequence[Any], meta: Dict[str, Any], ) -> bool: if not self._router_docs_query_prefers_workbook_details_rescue(message): return False retrieval_mode = str(meta.get("retrieval_mode") or "").strip().lower() if retrieval_mode and retrieval_mode.startswith("deterministic_router_workbook_"): return False query = parse_router_intelligence_query(message) if query is None: return False device_texts = [str(item).strip() for item in list(query.device_texts or []) if str(item or "").strip()] if len(device_texts) != 1: return False requested_model = device_texts[0] requested_norm = _normalize_router_query_text(requested_model) requested_compact = _compact_model(requested_model) if (not requested_norm) and (not requested_compact): return False def _has_requested_anchor(text: Any) -> bool: normalized = _normalize_router_query_text(text) if not normalized: return False if requested_norm and requested_norm in normalized: return True compact = _compact_model(normalized) return bool(requested_compact and requested_compact in compact) source_list = [item for item in list(sources or []) if isinstance(item, dict)] if not source_list: return False for source in source_list: source_text = " ".join( [ str(source.get("doc") or ""), str(source.get("relative_path") or ""), str(source.get("chunk_id") or ""), ] ) if _has_requested_anchor(source_text): return False return True def _router_docs_response_is_low_value_single_model_capability( self, message: str, assistant: str, sources: Sequence[Any], meta: Dict[str, Any], ) -> bool: if not self._router_docs_query_prefers_workbook_details_rescue(message): return False retrieval_mode = str(meta.get("retrieval_mode") or "").strip().lower() if retrieval_mode and retrieval_mode.startswith("deterministic_router_workbook_"): return False assistant_low = _normalize_router_query_text(assistant) if not assistant_low: return False low_value_hints = ( "not listed", "not clearly documented", "not documented", "not seeing a clean documented match", "i do not have enough internal documentation", "i don't have enough internal documentation", "unable to answer that safely", "please clarify or ask another question", ) missing_or_low_value = any(h in assistant_low for h in _MISSING_INFO_HINTS) or any(h in assistant_low for h in low_value_hints) if (not missing_or_low_value) and ( not self._router_docs_response_lacks_requested_model_anchor(message, assistant, sources, meta) ): return False query = parse_router_intelligence_query(message) if query is None: return False device_texts = [str(item).strip() for item in list(query.device_texts or []) if str(item or "").strip()] if len(device_texts) != 1: return False workbook_core = self._rapid_router_intelligence_core() if workbook_core is None: return False normalized = _as_dict( workbook_core.normalize_catalog_device( manufacturer_text=str(query.manufacturer_text or ""), product_text=device_texts[0], ) ) return bool(normalized.get("ok")) or str(normalized.get("error") or "") == "ambiguous_product" def _router_workbook_detail_response( self, message: str, st: UnifiedKnowledgebaseState, *, requested_domain: str, manufacturer_text: str, product_text: str, retrieval_mode: str, ) -> Optional[Dict[str, Any]]: subject = " ".join([part for part in (manufacturer_text, product_text) if str(part or "").strip()]).strip() or str(product_text or "").strip() if not subject: return None rescue_prompt = f"Give workbook-backed feature details for {subject}." response = self._router_workbook_fast_answer( rescue_prompt, st, requested_domain, raw_message=message, ) if not response: return None response = dict(response) response_meta = _as_dict(response.get("meta")) response_meta["retrieval_mode"] = retrieval_mode response_meta["router_details_rescue_prompt"] = rescue_prompt response["meta"] = response_meta if ( requested_domain == "router_docs" and _contains_any(str(message or "").lower(), ("compare", "comparison", "table", "matrix", "vs", "versus")) ): fact_bundle = _as_dict(response_meta.get("router_fact_bundle")) resolution_mode = str(response_meta.get("router_resolution_mode") or fact_bundle.get("resolution_mode") or "").strip().lower() if resolution_mode in {"family_safe_partial", "family_alias_provisional", "family_collapsed"} and bool(response_meta.get("review_required")): compare_variant = self._router_compare_variant_docs_too_thin_response(message, st, requested_domain) if compare_variant: compare_meta = _as_dict(compare_variant.get("meta")) compare_meta.setdefault("router_details_rescue_prompt", rescue_prompt) compare_variant["meta"] = compare_meta return compare_variant return response def _router_compare_variant_labels(self, message: str) -> List[str]: labels: List[str] = [] seen: Set[str] = set() for fragment in _ROUTER_MODEL_SEPARATOR_RE.split(str(message or "")): normalized_fragment = _norm(fragment) if not normalized_fragment: continue tokens = [str(tok).strip() for tok in _ROUTER_MODEL_TOKEN_RE.findall(normalized_fragment) if str(tok).strip()] if not tokens: continue label = normalized_fragment if len(tokens) == 1: token = _compact_model(tokens[0]) or _norm(tokens[0]) tail = normalized_fragment.split(tokens[0], 1)[1].strip() if tokens[0] in normalized_fragment else "" label = " ".join(part for part in (token, tail) if part).strip() or token label = re.sub( r"\b(from|using|documented|capabilities|specs?|only|and|say|if|you|need|the|exact|variant|docs|are|still|too|thin|to|compare|safely|package|first)\b", "", label, flags=re.IGNORECASE, ) label = re.sub(r"\s+", " ", label).strip(" ,.;:-") if not label: label = _compact_model(tokens[0]) or _norm(tokens[0]) key = label.lower() if key and key not in seen: seen.add(key) labels.append(label) return labels def _router_compare_variant_docs_too_thin_response( self, message: str, st: UnifiedKnowledgebaseState, domain: str, ) -> Optional[Dict[str, Any]]: low = str(message or "").lower() if not _contains_any(low, ("compare", "comparison", "table", "matrix", "vs", "versus")): return None labels = self._router_compare_variant_labels(message) if len(labels) < 2: return None clarify_turn = self._set_clarify_pending(st, "clarify_model", domain, message=message) rows = [ "| Requested side | Safe documented status | What I need |", "| --- | --- | --- |", ] for label in labels[:4]: rows.append( "| " + " | ".join( [ _md_cell(label), _md_cell("Family-safe evidence only"), _md_cell("Exact SKU/package or variant-specific datasheet/manual excerpt"), ] ) + " |" ) assistant = _format_shell( "\n".join( [ "The exact variant docs are still too thin to compare safely.", "", "I can only confirm family-safe evidence right now, so I’m holding the side-by-side compare instead of implying carrier-specific differences that the internal docs do not support cleanly.", "", *rows, ] ), [ "Internal evidence currently resolves these labels at the family level, not a quote-safe exact variant/package row.", "That is enough for a cautious family summary, but not enough for a carrier-specific or package-specific compare table.", ], [ "Reply with the exact SKU/package for each side and I will reuse this same compare request.", "If you want the family-level view instead, ask for a `family-safe summary only` and I’ll keep the differences conservative.", ], ) return { "assistant": assistant, "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [ { "id": "MC1", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "clarify:compare_variant_docs_too_thin", "location": "", "excerpt": "Exact variant/package compare stayed blocked because only family-safe internal evidence was available.", "score": 1.0, } ], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "effective_audience": "external", "meta": { "domain": domain, "retrieval_mode": "clarify_model_compare_variant_docs_too_thin", "clarify_turn": int(clarify_turn), "max_clarify_turns": int(self.max_clarify_turns), }, } def _router_docs_workbook_details_rescue( self, message: str, st: UnifiedKnowledgebaseState, assistant: str, sources: Sequence[Any], meta: Dict[str, Any], ) -> Optional[Dict[str, Any]]: if not self._router_docs_query_prefers_workbook_details_rescue(message): return None retrieval_mode = str(meta.get("retrieval_mode") or "").strip().lower() weak_reason = str(meta.get("weak_reason") or "").strip().lower() should_rescue = ( retrieval_mode in {"internal_weak", "internal_weak_no_web"} or weak_reason.startswith("no_model_match:") or self._is_internal_weak(assistant, sources, meta) or self._router_docs_response_is_pricing_only_fact_row(sources, meta) or self._router_docs_response_is_low_value_single_model_capability(message, assistant, sources, meta) ) if not should_rescue: return None query = parse_router_intelligence_query(message) if query is None: return None device_texts = [str(item).strip() for item in list(query.device_texts or []) if str(item or "").strip()] if len(device_texts) != 1: return None return self._router_workbook_detail_response( message, st, requested_domain="router_docs", manufacturer_text=str(query.manufacturer_text or ""), product_text=device_texts[0], retrieval_mode="deterministic_router_workbook_details_router_docs_bridge", ) def _router_query_platform_anchor_variants(self, message: str) -> List[Tuple[str, ...]]: low = _normalize_router_query_text(message) out: List[Tuple[str, ...]] = [] for variants in _ROUTER_PLATFORM_QUERY_ANCHOR_VARIANTS.values(): normalized = tuple(str(v).strip().lower() for v in variants if str(v).strip()) if normalized and any(v in low for v in normalized): out.append(normalized) return out def _text_has_router_port_fact_signal(self, text: str) -> bool: low = _normalize_router_query_text(text) if not low: return False return bool(re.search(r"\bports?\b", low) or re.search(r"\b(tcp|udp)\b", low)) def _router_fact_answer_needs_web_confirmation( self, message: str, assistant: str, sources: Sequence[Any], meta: Dict[str, Any], ) -> bool: retrieval_mode = str(meta.get("retrieval_mode") or "").strip().lower() if retrieval_mode.startswith("deterministic_") or retrieval_mode.endswith("_fast"): return False if bool(meta.get("web_assisted")) or bool(meta.get("llm_assisted")): return False low = _normalize_router_query_text(message) if (not self._query_prefers_authoritative_evidence(message, "router_docs")) or (not self._is_answer_seeking_intent(low)): return False if _contains_any(low, ("compare", "comparison", "table", "matrix", "vs", "versus")): return False if (not re.search(r"\bports?\b", low)) and (not re.search(r"\b(tcp|udp)\b", low)): return False anchor_variants = self._router_query_platform_anchor_variants(message) if not anchor_variants: return False assistant_low = _normalize_router_query_text(assistant) assistant_has_port = self._text_has_router_port_fact_signal(assistant_low) if any(h in assistant_low for h in _MISSING_INFO_HINTS): return True has_anchor_source = False has_port_source = False for src in sources or []: if not isinstance(src, dict): continue parts = [ Path(str(src.get("doc") or "")).stem.replace("-", " ").replace("_", " "), str(src.get("relative_path") or ""), str(src.get("excerpt") or ""), ] evidence_text = _normalize_router_query_text(" ".join(parts)) if not evidence_text: continue has_anchor = any(any(variant in evidence_text for variant in variants) for variants in anchor_variants) has_port = self._text_has_router_port_fact_signal(evidence_text) has_anchor_source = has_anchor_source or has_anchor has_port_source = has_port_source or has_port if has_anchor and has_port: return False if not assistant_has_port: return True if has_anchor_source and (not has_port_source): return True if has_anchor_source and has_port_source: return True if has_anchor_source and (not any(any(variant in assistant_low for variant in variants) for variants in anchor_variants)): return True return False def _concept_scope_terms(self, domain: str) -> Tuple[str, ...]: dom = str(domain or "").strip().lower() if dom == "router_docs": return _ROUTER_GENERIC_CONCEPT_HINTS if dom == "pots": return _POTS_GENERIC_CONCEPT_HINTS if dom == "masters": return _MASTERS_GENERIC_CONCEPT_HINTS return () def _classify_concept_fallback(self, question: str, domain: str) -> Dict[str, Any]: dom = str(domain or "").strip().lower() extra_blocked = bool( (dom not in {"router_docs", "masters", "pots"}) or _VERIZON_POLICY_RE.search(question) or _VERIZON_PRICING_RE.search(question) or _OTHER_CARRIER_POLICY_RE.search(question) or _PII_EMPLOYEE_RE.search(question) or _GUARANTEE_RE.search(question) or self._query_prefers_authoritative_evidence(question, dom) ) classified = classify_concept_request( question, domain=dom, enabled=self.concept_fallback_enabled, scope_terms=self._concept_scope_terms(dom), blocked_terms=_CONCEPT_FALLBACK_BLOCKED_HINTS, strict_citation_required=self._needs_strict_citation(question, dom), extra_blocked=extra_blocked, ) classified["allow_web"] = bool((not bool(classified.get("blocked"))) and self._allow_web_fallback(question, dom)) return classified def _allow_concept_llm_fallback(self, question: str, domain: str) -> bool: return bool(self._classify_concept_fallback(question, domain).get("allow_concept")) def _deterministic_concept_fast_answer(self, question: str, domain: str) -> Optional[Dict[str, Any]]: if self._query_prefers_authoritative_evidence(question, domain): return None payload = deterministic_concept_payload(question, domain=domain) if not payload: return None return { "assistant": _format_shell( str(payload.get("result") or ""), list(payload.get("why") or []), list(payload.get("next_actions") or []), ), "sources": list(payload.get("sources") or []), "files": list(payload.get("files") or []), "meta": dict(payload.get("meta") or {}), } def _concept_system_prompt(self, domain: str) -> str: scope = { "router_docs": ( "Stay within router, gateway, LTE/5G, Wi-Fi, antenna, failover, WAN/LAN, and related connectivity concepts." ), "pots": ( "Stay within POTS replacement, analog line migration, copper sunset, fax, fire, elevator, and survivability concepts." ), "masters": ( "Stay within Masters Telecom solution concepts such as SecureFAX, iFAX, SIP, POTS replacement, and telecom solution framing." ), }.get(str(domain or "").strip().lower(), "Stay within telecom, router, and POTS concepts.") return build_shared_concept_prompt(scope + " Keep the answer operational and concise for sales engineers and partner reps.") def _concept_llm_fallback(self, question: str, domain: str, *, remaining_s: Optional[float] = None) -> Optional[Dict[str, Any]]: if self.client is None: return None classification = self._classify_concept_fallback(question, domain) if not bool(classification.get("allow_concept")): return None timeout_s = float(self.concept_fallback_timeout_s) if remaining_s is not None: timeout_s = min(timeout_s, max(0.8, float(remaining_s))) if timeout_s < 0.8: return None payload = { "domain": str(domain or "").strip().lower(), "question": question, "constraints": [ "Generic telecom concept explanation only", "No Verizon pricing/policy", "No fabricated specs, lifecycle dates, or compatibility claims", "State when internal documentation is still needed", ], } try: resp = responses_create_with_deadline( self.client, timeout_s=timeout_s, model=self.concept_fallback_model, input=[ {"role": "system", "content": self._concept_system_prompt(domain)}, {"role": "user", "content": json.dumps(payload, ensure_ascii=False)}, ], max_output_tokens=320, ) text = str(getattr(resp, "output_text", "") or "").strip() except Exception: return None if not text: return None if "**Result**" not in text and "Result" not in text: text = "\n".join( [ "**Result**", f"Model-generated (not from internal docs): {text}", "", "**Why**", "- Internal retrieval was weak for this concept question.", "", "**Next action**", "- Treat this as a concept explainer. Ask for internal-source evidence when you need exact product or policy detail.", ] ) elif "model-generated (not from internal docs)" not in text.lower(): text = text.replace("**Result**", "**Result**\n\nModel-generated (not from internal docs):", 1) return { "assistant": text, "sources": [], "files": [], "meta": { "domain": str(domain or "").strip().lower(), "llm_assisted": True, "web_assisted": False, "non_internal_generated": True, "citation_quorum_not_required": True, "retrieval_mode": "concept_llm_fallback", "fallback_stage": "concept_llm", }, } def _concept_answer_needs_web_refinement(self, question: str, assistant: str, meta: Dict[str, Any]) -> bool: return shared_concept_answer_needs_web_refinement(question, assistant, meta) def _annotate_provenance(self, meta: Dict[str, Any]) -> Dict[str, Any]: return annotate_fallback_provenance(meta) def _allow_web_fallback(self, question: str, domain: str) -> bool: dom = str(domain or "").strip().lower() if dom == "router_lifecycle": return False if self._web_stage_budget_cap_s(question, dom) <= 0.0: return False # Default: keep web fallback focused on router docs/spec exploration. # Enable broader fallback only when explicitly requested via env. allow_non_router = _env_bool("UNIFIED_KB_WEB_FALLBACK_NON_ROUTER", True) if dom in {"masters", "pots"} and (not allow_non_router): return False low = str(question or "").lower() if _contains_any(low, _ROUTER_LIFECYCLE_HINTS): return False if dom == "router_docs": if any(h in low for h in ("from docs only", "documented specs only", "from documented specs only")): return False return self._query_prefers_authoritative_evidence(question, dom) or _contains_any(low, _ROUTER_DOC_HINTS) or ("?" in low) return dom in {"masters", "pots"} def _web_fallback(self, question: str, domain: str, *, remaining_s: Optional[float] = None) -> Optional[Dict[str, Any]]: if self.client is None: return None if domain not in {"router_docs", "masters", "pots"}: return None if not self._allow_web_fallback(question, domain): return None q_low = str(question or "").lower() budget_mode = "extended" if ("extended" in q_low or "deep dive" in q_low) else "fast" timeout_s = float(self.web_timeout_extended_s if budget_mode == "extended" else self.web_timeout_s_by_domain.get(domain, 5.0)) timeout_s = max(2.5, min(20.0, timeout_s)) if remaining_s is not None: remaining_budget_s = max(0.0, float(remaining_s)) # Never spend web timeout above remaining request budget. # If the remaining budget is too tight, skip web fallback entirely. if remaining_budget_s < 1.5: return None timeout_s = min(timeout_s, max(1.0, remaining_budget_s - 0.35)) if timeout_s < 1.0: return None wants_table = _contains_any(q_low, ("table", "chart", "matrix", "compare", "comparison", "vs", "versus")) max_output_tokens = 620 if budget_mode == "extended" else 460 if wants_table: max_output_tokens += 80 if remaining_s is not None and float(remaining_s) < 6.0: max_output_tokens = min(max_output_tokens, 380) system = ( "You are a technical pre-sales assistant for Masters Telecom and Verizon partner users. " "Use web search only for a best-effort answer because internal retrieval was weak. " f"{build_shared_public_web_source_guidance(domain)} " "Never fabricate pricing, lifecycle dates, guarantees, or Verizon policy. " "Prefer official manufacturer documentation, manuals, datasheets, help-center articles, and regulator or standards sources before secondary catalog pages. " "If uncertain, say so briefly and ask one focused follow-up. " "Use a short, readable format with: Result, Why, Next action. " "Keep tone conversational and operational for field reps. " "Keep each section concise (4 bullets max, short lines)." ) payload = { "domain": domain, "question": question, "response_preferences": { "table_format": bool(wants_table), "max_bullets_per_section": 4, }, "search_strategy": "authoritative_public_docs_first", "preferred_public_sources": [ { "domain": "opendevelopment.verizonwireless.com", "use": "recently approved Verizon devices and approval-status context when relevant", }, { "domain": "masterstelecom.com", "use": "Masters Telecom services and public solution-positioning context when relevant", }, { "domain": "5gstore.com", "use": "secondary public catalog context for routers and related wireless hardware when official vendor sources are thin", }, ], "preferred_source_types": [ "official manufacturer product pages", "official manufacturer manuals and datasheets", "official manufacturer knowledge-base articles", "standards and regulator publications when certification or approval claims are involved", ], "constraints": [ "No Verizon pricing/policy answers", "No fabricated specs or lifecycle dates", "Label output as web-sourced", ], } try: resp = responses_create_with_deadline( self.client, timeout_s=timeout_s, model=self.openai_model, tools=[{"type": "web_search_preview"}], # type: ignore[list-item] input=[ {"role": "system", "content": system}, {"role": "user", "content": json.dumps(payload, ensure_ascii=False)}, ], max_output_tokens=max_output_tokens, ) text = str(getattr(resp, "output_text", "") or "").strip() except Exception: return None if not text: return None def _has_md_table(markdown: str) -> bool: rows = [ln for ln in str(markdown or "").splitlines() if ln.strip().startswith("|") and ln.count("|") >= 2] return len(rows) >= 2 if "**Result**" not in text and "Result" not in text: text = "\n".join( [ "**Result**", f"Web-sourced (not from internal docs): {text}", "", "**Why**", "- Internal retrieval was weak for this specific request.", "", "**Next action**", "- Treat this as a best guess until internal documentation is confirmed.", ] ) elif "web-sourced" not in text.lower(): text = text.replace("**Result**", "**Result**\n\nWeb-sourced (not from internal docs):", 1) if wants_table and (not _has_md_table(text)): bullet_lines = [ _norm(re.sub(r"^\s*[-*]\s*", "", ln)) for ln in text.splitlines() if re.match(r"^\s*[-*]\s+", ln or "") ] bullet_lines = [x for x in bullet_lines if x][:4] if not bullet_lines: sentence_bits = re.split(r"(?<=[.!?])\s+", _norm(text)) bullet_lines = [x for x in sentence_bits if x][:4] table_lines = [ "| Item | Detail |", "| --- | --- |", ] for idx, line in enumerate(bullet_lines[:4], start=1): table_lines.append(f"| {idx} | {_md_cell(line)} |") if len(table_lines) == 2: table_lines.append("| 1 | Needs confirmation from retrieved sources. |") text = text.rstrip() + "\n\n" + "\n".join(table_lines) urls = sorted(set(re.findall(r"https?://\S+", text)))[:8] sources: List[Dict[str, Any]] = [] for idx, url in enumerate(urls[:6], start=1): sources.append( { "id": f"W{idx}", "domain": domain, "doc": url, "relative_path": url, "chunk_id": f"web:{idx}", "location": "", "excerpt": f"Web-sourced fallback reference for `{domain}` response.", "score": 0.72, } ) if not sources: sources.append( { "id": "W0", "domain": domain, "doc": "web_search_preview", "relative_path": "", "chunk_id": "web:fallback", "location": "", "excerpt": "Web-assisted fallback used due weak internal retrieval.", "score": 0.65, } ) return { "assistant": text, "sources": sources, "meta": { "domain": domain, "web_assisted": True, "retrieval_mode": "web_fallback", "web_urls": urls, "web_budget_mode": budget_mode, "web_timeout_s": float(timeout_s), }, } def _delegate(self, mode: str, message: str, st: UnifiedKnowledgebaseState, audience: str) -> Tuple[Dict[str, Any], Dict[str, Any]]: aud = str(audience or "").strip().lower() delegated_audience = aud if aud in {"internal", "external"} else "auto" if delegated_audience == "auto" and mode in {"masters", "pots"}: delegated_audience = "internal" if mode == "router_lifecycle": out = self.router_core.handle_message(MessageRequest(message=message, state=st.router_lifecycle_state)) st.router_lifecycle_state = _as_dict(out.get("state")) return out, {"domain": "router_lifecycle"} if mode == "masters": out = self.masters_core.handle_message(message, st.masters_state, audience=delegated_audience) st.masters_state = _as_dict(out.get("state")) return out, {"domain": "masters"} if mode == "pots": out = self.pots_core.handle_message(message, st.pots_state, audience=delegated_audience) st.pots_state = _as_dict(out.get("state")) return out, {"domain": "pots"} # Default path: router docs/specs. out = self.router_rag_core.handle_message(message, st.router_docs_state, allow_web_fallback=False) st.router_docs_state = _as_dict(out.get("state")) return out, {"domain": "router_docs"} def handle_message( self, message: str, state: Optional[Dict[str, Any]] = None, *, mode: str = "auto", audience: str = "auto", show_citations: bool = True, ) -> Dict[str, Any]: t_total = time.perf_counter() timing_ms: Dict[str, float] = {} delegate_phases_ms: Dict[str, float] = {} def _record_delegate_phase(name: str, started_at: float) -> None: delegate_phases_ms[name] = round((time.perf_counter() - started_at) * 1000.0, 2) def _apply_delegate_phase_meta(meta: Dict[str, Any]) -> Dict[str, Any]: if delegate_phases_ms: meta["delegate_phases_ms"] = dict(delegate_phases_ms) return meta st = UnifiedKnowledgebaseState.from_dict(state) st.show_citations = bool(show_citations) self._refresh_file_maps() normalize_meta = self.normalize_query(str(message or ""), mode=mode) msg = str(normalize_meta.get("normalized_message") or str(message or "")).strip() if not msg: return { "assistant": _INITIAL_PROMPT, "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [ { "id": "KB1", "domain": "knowledgebase", "doc": "knowledgebase_help", "relative_path": "", "chunk_id": "help:intro", "location": "", "excerpt": "Knowledgebase intro and mode usage guidance.", "score": 1.0, } ], "files": [], "effective_audience": "external", "meta": { "domain": "knowledgebase", "mode": st.mode, "input_normalized": bool(normalize_meta.get("changed")), "input_corrections": list(normalize_meta.get("corrections") or []), "timing_ms": {"total": round((time.perf_counter() - t_total) * 1000.0, 2)}, }, } msg_effective = self._expand_followup_message(msg, st) requested_mode = _norm_mode(mode) cache_key = "" if self._cacheable_state(st): cache_key = self._cache_key( msg_effective, mode=requested_mode if requested_mode != "auto" else st.mode, audience=audience, show_citations=st.show_citations, ) t_cache = time.perf_counter() cached = self._cache_get(cache_key) timing_ms["cache_lookup"] = round((time.perf_counter() - t_cache) * 1000.0, 2) if isinstance(cached, dict): cached_meta = _as_dict(cached.get("meta")) timing = _as_dict(cached_meta.get("timing_ms")) timing["cache_lookup"] = timing_ms["cache_lookup"] timing["total"] = round((time.perf_counter() - t_total) * 1000.0, 2) cached_meta["timing_ms"] = timing cached_meta["cache_hit"] = True cached["meta"] = cached_meta return cached if _is_reset_command(msg): keep_mode = _norm_mode(mode) if _norm_mode(mode) != "auto" else st.mode reset_state = UnifiedKnowledgebaseState(mode=keep_mode, show_citations=st.show_citations) return { "assistant": _INITIAL_PROMPT, "state": reset_state.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [ { "id": "KB1", "domain": "knowledgebase", "doc": "knowledgebase_help", "relative_path": "", "chunk_id": "help:reset", "location": "", "excerpt": "Chat reset returns the standard knowledgebase intro and mode guidance.", "score": 1.0, } ], "files": [], "effective_audience": "external", "meta": { "domain": "knowledgebase", "action": "reset", "timing_ms": {"total": round((time.perf_counter() - t_total) * 1000.0, 2)}, }, } # Mode command in chat overrides current preference. mode_cmd = _extract_mode_command(msg) if mode_cmd: st.mode = mode_cmd st.pending = {} return { "assistant": _format_shell( f"Mode set to `{_MODE_LABELS.get(mode_cmd, mode_cmd)}`.", ["Your next message will use this mode."], ["Ask your question now, or switch back to `Auto` mode anytime."], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [ { "id": "KB1", "domain": "knowledgebase", "doc": "knowledgebase_help", "relative_path": "", "chunk_id": "help:mode_switch", "location": "", "excerpt": "Mode-switch command updates routing preference for the next user message.", "score": 1.0, } ], "files": [], "effective_audience": "external", "meta": { "domain": "knowledgebase", "action": "set_mode", "mode": mode_cmd, "timing_ms": {"total": round((time.perf_counter() - t_total) * 1000.0, 2)}, }, } if str(_as_dict(st.pending).get("type") or "") == "router_workbook_survey_followup": pending = _as_dict(st.pending) pending_domain = _norm_mode(pending.get("domain")) if pending_domain == "auto": pending_domain = _norm_mode(st.last_mode) if pending_domain == "auto": pending_domain = "router_lifecycle" followup_resp = self._handle_router_workbook_survey_followup( msg, st, pending, mode=pending_domain, audience=audience, show_citations=show_citations, ) if followup_resp is not None: return followup_resp if str(_as_dict(st.pending).get("type") or "") == "router_workbook_guided_advisor": pending = _as_dict(st.pending) pending_domain = _norm_mode(pending.get("domain")) if pending_domain == "auto": pending_domain = _norm_mode(st.last_mode) if pending_domain == "auto": pending_domain = "router_lifecycle" followup_resp = self._handle_router_workbook_guided_advisor_followup( msg, st, pending, mode=pending_domain, audience=audience, show_citations=show_citations, ) if followup_resp is not None: return followup_resp # Policy gates. if _VERIZON_POLICY_RE.search(msg): blocked = self._policy_block_response("verizon_policy", st) blocked_meta = _as_dict(blocked.get("meta")) blocked_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} blocked["meta"] = blocked_meta return blocked if _VERIZON_PRICING_RE.search(msg): blocked = self._policy_block_response("verizon_pricing", st) blocked_meta = _as_dict(blocked.get("meta")) blocked_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} blocked["meta"] = blocked_meta return blocked if _OTHER_CARRIER_POLICY_RE.search(msg): blocked = self._policy_block_response("carrier_policy", st) blocked_meta = _as_dict(blocked.get("meta")) blocked_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} blocked["meta"] = blocked_meta return blocked if _PII_EMPLOYEE_RE.search(msg): blocked = self._policy_block_response("pii", st) blocked_meta = _as_dict(blocked.get("meta")) blocked_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} blocked["meta"] = blocked_meta return blocked if _EXACT_CURRENT_LEADTIME_RE.search(msg): blocked = self._policy_block_response("exact_lead_time", st) blocked_meta = _as_dict(blocked.get("meta")) blocked_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} blocked["meta"] = blocked_meta return blocked if _EXACT_CURRENT_AVAILABILITY_RE.search(msg): blocked = self._policy_block_response("exact_availability", st) blocked_meta = _as_dict(blocked.get("meta")) blocked_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} blocked["meta"] = blocked_meta return blocked if _EXACT_BAND_SUPPORT_RE.search(msg): blocked = self._policy_block_response("exact_band_support", st) blocked_meta = _as_dict(blocked.get("meta")) blocked_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} blocked["meta"] = blocked_meta return blocked if _EXACT_CERTIFICATION_RE.search(msg): blocked = self._policy_block_response("exact_certification", st) blocked_meta = _as_dict(blocked.get("meta")) blocked_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} blocked["meta"] = blocked_meta return blocked lifecycle_policy_text = _scrub_router_model_tokens_for_policy(msg) if _EXACT_LIFECYCLE_RE.search(lifecycle_policy_text) and (not _is_supported_router_mixed_lifecycle_request(msg)): blocked = self._policy_block_response("exact_lifecycle", st) blocked_meta = _as_dict(blocked.get("meta")) blocked_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} blocked["meta"] = blocked_meta return blocked if _CODE_ADJUDICATION_RE.search(msg): blocked = self._policy_block_response("code_adjudication", st) blocked_meta = _as_dict(blocked.get("meta")) blocked_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} blocked["meta"] = blocked_meta return blocked if _GUARANTEE_RE.search(msg): blocked = self._policy_block_response("guarantee", st) blocked_meta = _as_dict(blocked.get("meta")) blocked_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} blocked["meta"] = blocked_meta return blocked # Expand contextual follow-ups (e.g., "same as above") so routing/parsing has the prior model context. msg = msg_effective if str(_as_dict(st.pending).get("type") or "") == "faq_clarify": pending_mode = _norm_mode(mode) if pending_mode == "auto": pending_mode = _norm_mode(st.last_mode) if _norm_mode(st.last_mode) != "auto" else "router_docs" pending_resp = self._faq_handle_pending(msg, st, pending_mode) if pending_resp is not None: pending_domain = str(_as_dict(pending_resp.get("meta")).get("domain") or pending_mode) result = { "assistant": str(pending_resp.get("assistant") or ""), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": self._normalize_sources(pending_domain, pending_resp.get("sources") or []), "files": self._normalize_domain_files(pending_domain, pending_resp.get("files") or []), "effective_audience": "external", "meta": { **_as_dict(pending_resp.get("meta")), "domain": pending_domain, "timing_ms": {"total": round((time.perf_counter() - t_total) * 1000.0, 2)}, }, } return result if str(_as_dict(st.pending).get("type") or "") == "clarify_model": pending = _as_dict(st.pending) pending_domain = _norm_mode(pending.get("domain")) if pending_domain == "auto": pending_domain = "router_docs" rewritten = self._rewrite_clarified_model_followup(msg, pending) if rewritten: st.pending = {} return self.handle_message( rewritten, st.to_dict(), mode=pending_domain, audience=audience, show_citations=show_citations, ) if str(_as_dict(st.pending).get("type") or "") == "router_workbook_clarify": pending = _as_dict(st.pending) pending_domain = _norm_mode(pending.get("domain")) if pending_domain == "auto": pending_domain = _norm_mode(st.last_mode) if pending_domain == "auto": pending_domain = "router_lifecycle" rewritten = self._rewrite_router_workbook_followup(msg, pending) if rewritten: st.pending = {} return self.handle_message( rewritten, st.to_dict(), mode=pending_domain, audience=audience, show_citations=show_citations, ) if str(_as_dict(st.pending).get("type") or "") == "router_workbook_survey_followup": pending = _as_dict(st.pending) pending_domain = _norm_mode(pending.get("domain")) if pending_domain == "auto": pending_domain = _norm_mode(st.last_mode) if pending_domain == "auto": pending_domain = "router_lifecycle" followup_resp = self._handle_router_workbook_survey_followup( msg, st, pending, mode=pending_domain, audience=audience, show_citations=show_citations, ) if followup_resp is not None: return followup_resp if str(_as_dict(st.pending).get("type") or "") == "router_workbook_guided_advisor": pending = _as_dict(st.pending) pending_domain = _norm_mode(pending.get("domain")) if pending_domain == "auto": pending_domain = _norm_mode(st.last_mode) if pending_domain == "auto": pending_domain = "router_lifecycle" followup_resp = self._handle_router_workbook_guided_advisor_followup( msg, st, pending, mode=pending_domain, audience=audience, show_citations=show_citations, ) if followup_resp is not None: return followup_resp if str(_as_dict(st.pending).get("type") or "") == "confirm_web_lookup": pending = _as_dict(st.pending) pending_domain = _norm_mode(pending.get("domain")) if pending_domain == "auto": pending_domain = _norm_mode(st.last_mode) if pending_domain == "auto": pending_domain = "router_docs" low_pending = _norm(msg).lower() if low_pending in _FAQ_NO_TERMS: declined = self._web_lookup_declined_response(st, pending_domain) declined_meta = _as_dict(declined.get("meta")) declined_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} declined["meta"] = declined_meta return declined if low_pending in _FAQ_YES_TERMS: original_message = _norm(pending.get("original_message", "")) st.pending = {} web = self._web_fallback( original_message, pending_domain, remaining_s=float(self._web_stage_budget_cap_s(original_message, pending_domain)), ) if original_message else None if web: web_domain = str(_as_dict(web.get("meta")).get("domain") or pending_domain) return { "assistant": str(web.get("assistant") or ""), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": self._normalize_sources(web_domain, web.get("sources") or []), "files": self._normalize_domain_files(web_domain, web.get("files") or []), "effective_audience": "external", "meta": { **_as_dict(web.get("meta")), "domain": web_domain, "timing_ms": {"total": round((time.perf_counter() - t_total) * 1000.0, 2)}, }, } unavailable = self._web_lookup_unavailable_response(st, pending_domain) unavailable_meta = _as_dict(unavailable.get("meta")) unavailable_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} unavailable["meta"] = unavailable_meta return unavailable st.pending = {} return self.handle_message( msg, st.to_dict(), mode=mode, audience=audience, show_citations=show_citations, ) if self._is_clearly_out_of_scope(msg): out_of_scope = self._out_of_scope_response(st) out_of_scope_meta = _as_dict(out_of_scope.get("meta")) out_of_scope_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} out_of_scope["meta"] = out_of_scope_meta return out_of_scope if requested_mode != "auto": st.mode = requested_mode t_route = time.perf_counter() resolved_mode = self._resolve_mode(msg, st, requested_mode) timing_ms["routing"] = round((time.perf_counter() - t_route) * 1000.0, 2) cross_domain_fast = self._cross_domain_process_fast(msg) if cross_domain_fast: fast_meta = _as_dict(cross_domain_fast.get("meta")) fast_domain = str(fast_meta.get("domain") or resolved_mode) fast_mode = str(fast_meta.get("retrieval_mode") or "") fast_assistant = str(cross_domain_fast.get("assistant") or "") fast_sources = self._normalize_sources(fast_domain, cross_domain_fast.get("sources") or []) fast_files = self._normalize_domain_files(fast_domain, cross_domain_fast.get("files") or []) if not fast_sources: fallback_sources = self._fast_mode_guidance_sources(fast_domain, fast_mode, msg) if fallback_sources: fast_sources = self._normalize_sources(fast_domain, fallback_sources) if not fast_files: fallback_files = [ str(src.get("relative_path") or src.get("doc") or "") for src in fallback_sources if str(src.get("relative_path") or src.get("doc") or "").strip() ] fast_files = self._normalize_domain_files(fast_domain, fallback_files) fast_sources, citation_meta = self._filter_sources_by_relevance(msg, fast_domain, list(fast_sources)) citation_quality = self._citation_quality_gate(msg, fast_domain, fast_sources) fast_meta = { **fast_meta, **self._build_path_budget_meta(msg, fast_domain), } evaluated_fast_sources = list(fast_sources) if self._should_defer_fast_path_response(msg, fast_domain, fast_assistant, evaluated_fast_sources, fast_meta): cross_domain_fast = None else: if not st.show_citations: preserve_hidden = bool( fast_mode.endswith("_fast") or fast_mode.startswith("deterministic_") or fast_mode in _HIDDEN_CITATION_PRESERVE_FAST_MODES ) if preserve_hidden: fast_meta["citations_hidden"] = True else: fast_sources = [] st.pending = {} result_meta = self._annotate_provenance( { **fast_meta, "domain": fast_domain, "domain_label": _MODE_LABELS.get(fast_domain, fast_domain), "mode_badge": _MODE_BADGE_TEXT.get(fast_domain, ""), "resolved_mode": fast_domain, "requested_mode": requested_mode, "show_citations": bool(st.show_citations), "citation_gate": citation_meta, "citation_quality": citation_quality, "cache_hit": False, "hard_timeout_s": float(self.hard_timeout_s), "fallback_extra_budget_s": float(self.fallback_extra_budget_s), "soft_concise_s": float(self.soft_concise_s), "timeout_budget_exceeded": False, "input_normalized": bool(normalize_meta.get("changed")), "input_corrections": list(normalize_meta.get("corrections") or []), "route_quality_flags": self._route_quality_flags( msg, fast_domain, str(fast_meta.get("retrieval_mode") or ""), fast_meta, ), "pending_type": str(_as_dict(st.pending).get("type") or ""), "clarification_loop": str(_as_dict(st.pending).get("type") or "").startswith("router_workbook_"), } ) result_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} self._runtime_telemetry_log( message=msg, domain=fast_domain, result_meta=result_meta, sources=evaluated_fast_sources, citation_quality=_as_dict(citation_quality), ) return { "assistant": fast_assistant, "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": fast_sources, "files": fast_files, "effective_audience": "external", "meta": result_meta, } if resolved_mode in {"router_docs", "router_lifecycle"}: workbook_fast = self._router_workbook_fast_answer(msg, st, resolved_mode, raw_message=str(message or "")) if workbook_fast: fast_meta = _as_dict(workbook_fast.get("meta")) fast_domain = str(fast_meta.get("domain") or resolved_mode) workbook_status = self._rapid_router_intelligence_status() freshness = _as_dict(workbook_status.get("freshness")) stale_warning = str(freshness.get("warning_text") or "").strip() if bool(freshness.get("stale")) else "" fast_mode = str(fast_meta.get("retrieval_mode") or "") fast_assistant = str(workbook_fast.get("assistant") or "") fast_sources = self._normalize_sources(fast_domain, workbook_fast.get("sources") or []) fast_files = self._normalize_domain_files(fast_domain, workbook_fast.get("files") or []) fast_sources, citation_meta = self._filter_sources_by_relevance(msg, fast_domain, list(fast_sources)) citation_quality = self._citation_quality_gate(msg, fast_domain, fast_sources) router_query_plan = _as_dict(fast_meta.get("router_query_plan")) if not router_query_plan: router_query_plan = self._router_query_plan_from_response(msg, fast_domain, fast_meta) if router_query_plan: fast_meta["router_query_plan"] = router_query_plan router_answer_trace = _as_dict(fast_meta.get("router_answer_trace")) model_hint = " / ".join([str(item) for item in list(router_query_plan.get("entities") or [])[:2] if str(item or "").strip()]) structured_evidence: List[Dict[str, Any]] = [] if isinstance(fast_meta.get("router_fleet_view"), dict): structured_evidence.extend( self._router_fleet_evidence_bundle(_as_dict(fast_meta.get("router_fleet_view")), model_alias=model_hint) ) if isinstance(fast_meta.get("router_replacement_view"), dict): structured_evidence.extend( self._router_replacement_evidence_bundle(_as_dict(fast_meta.get("router_replacement_view")), model_alias=model_hint) ) router_evidence_bundle = self._dedupe_router_evidence_bundle( [ *self._router_trace_evidence_bundle(router_answer_trace, model_alias=model_hint), *self._router_source_evidence_bundle( fast_sources, model_alias=model_hint, source_tables=list(router_answer_trace.get("source_tables") or []), ), *structured_evidence, ] ) if router_evidence_bundle: fast_meta["router_evidence_bundle"] = router_evidence_bundle st.pending = _as_dict(st.pending) result_meta = self._annotate_provenance( { **fast_meta, **self._build_path_budget_meta(msg, fast_domain), "domain": fast_domain, "domain_label": _MODE_LABELS.get(fast_domain, fast_domain), "mode_badge": _MODE_BADGE_TEXT.get(fast_domain, ""), "resolved_mode": fast_domain, "requested_mode": requested_mode, "show_citations": bool(st.show_citations), "citation_gate": citation_meta, "citation_quality": citation_quality, "cache_hit": False, "hard_timeout_s": float(self.hard_timeout_s), "fallback_extra_budget_s": float(self.fallback_extra_budget_s), "soft_concise_s": float(self.soft_concise_s), "timeout_budget_exceeded": False, "input_normalized": bool(normalize_meta.get("changed")), "input_corrections": list(normalize_meta.get("corrections") or []), "route_quality_flags": self._route_quality_flags( msg, fast_domain, str(fast_meta.get("retrieval_mode") or ""), fast_meta, ), "pending_type": str(_as_dict(st.pending).get("type") or ""), "clarification_loop": str(_as_dict(st.pending).get("type") or "").startswith("router_workbook_"), "router_workbook_status": workbook_status, "router_workbook_stale": bool(freshness.get("stale")), "router_workbook_freshness_label": str(freshness.get("label") or ""), "router_workbook_stale_warning": stale_warning, } ) result_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} self._runtime_telemetry_log( message=msg, domain=fast_domain, result_meta=result_meta, sources=list(fast_sources), citation_quality=_as_dict(citation_quality), ) return { "assistant": fast_assistant, "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": fast_sources, "files": fast_files, "effective_audience": "external", "meta": result_meta, } low_time_pre_delegate = self._low_time_template_response( msg, st, resolved_mode, remaining_s=max(0.0, self.hard_timeout_s - (time.perf_counter() - t_total)), ) if low_time_pre_delegate is not None: low_meta = _as_dict(low_time_pre_delegate.get("meta")) low_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} low_time_pre_delegate["meta"] = low_meta return low_time_pre_delegate if self._needs_model_clarification(msg, resolved_mode): clarified = self._model_clarify_response(msg, st, resolved_mode) if clarified is not None: clarified_meta = _as_dict(clarified.get("meta")) clarified_meta["timing_ms"] = {"total": round((time.perf_counter() - t_total) * 1000.0, 2)} clarified["meta"] = clarified_meta return clarified try: delegated: Dict[str, Any] route_meta: Dict[str, Any] t_delegate = time.perf_counter() if resolved_mode == "router_docs": t_router_docs_alias_lookup = time.perf_counter() alias_needed = self._router_alias_confirmation_needed(msg) _record_delegate_phase("router_docs_alias_lookup", t_router_docs_alias_lookup) if alias_needed: t_router_docs_alias_clarify = time.perf_counter() alias, _canonical = alias_needed parsed_rows = self._extract_conversational_fleet_items(msg) parsed_summary: List[str] = [] for row in parsed_rows[:6]: model_key = _compact_model(row.get("model_key")) if not model_key or model_key == _compact_model(alias): continue qty = int(row.get("qty") or 0) if qty <= 0: continue parsed_summary.append(f"Parsed `{qty} x {model_key}` and ready to process once alias is confirmed.") if not parsed_summary: for tok in self._extract_router_models_cached(msg): compact_tok = _compact_model(tok) if (not compact_tok) or (compact_tok == _compact_model(alias)): continue if self._lookup_router_fact_key(compact_tok) or self._lookup_router_lifecycle_key_relaxed(compact_tok): parsed_summary.append( f"Recognized `{compact_tok}` and will process it with this same request after alias confirmation." ) delegated = { "assistant": _format_shell( f"I found `{alias}` in your request.", [f"`{alias}` is ambiguous in the current index, so I need confirmation before I answer or compare it.", *parsed_summary], [ "Reply `yes` to continue, or provide the exact model label from the device.", "If you meant a different model, send the exact model/SKU printed on the router label or product box.", ], ), "state": st.router_docs_state, "sources": [], "files": ["routers_eos_eol_by_sku.csv", "feb2026routers.csv"], "meta": {"retrieval_mode": "alias_clarification", "domain": "router_docs"}, } route_meta = {"domain": "router_docs", "fast_path": "alias_clarification"} _record_delegate_phase("router_docs_alias_clarify", t_router_docs_alias_clarify) else: t_router_docs_context_preflight = time.perf_counter() primary_msg, has_rr_context = self._split_rapid_router_context_message(msg) primary_models = self._rapid_router_explicit_models(primary_msg) if has_rr_context else [] primary_low = _normalize_router_query_text(primary_msg) selected_context_request = ( has_rr_context and (not primary_models) and any( x in primary_low for x in ( "selected router", "selected routers", "selected model", "selected models", "selected device", "selected devices", "my selected", "my routers", "my devices", "current selection", "these routers", "those routers", "the selected", ) ) ) _record_delegate_phase("router_docs_context_preflight", t_router_docs_context_preflight) faq_pref = None if has_rr_context and _norm(primary_msg) and (not primary_models) and (not selected_context_request): # For generic helper questions in Rapid Router context, try FAQ first # so appended form lines do not crowd out direct FAQ intent matches. faq_pref = self._faq_fast_lane_answer(msg, st, resolved_mode, allow_clarify=True) if faq_pref: t_router_docs_faq_pref = time.perf_counter() faq_domain = str(_as_dict(faq_pref.get("meta")).get("domain") or "router_docs") faq_state = { "router_docs": st.router_docs_state, "router_lifecycle": st.router_lifecycle_state, "pots": st.pots_state, "masters": st.masters_state, }.get(faq_domain, st.router_docs_state) delegated = { "assistant": str(faq_pref.get("assistant") or ""), "state": faq_state, "sources": list(faq_pref.get("sources") or []), "files": list(faq_pref.get("files") or []), "meta": _as_dict(faq_pref.get("meta")), } route_meta = { "domain": faq_domain, "fast_path": str(_as_dict(faq_pref.get("meta")).get("retrieval_mode") or "faq_fast"), } _record_delegate_phase("router_docs_faq_pref", t_router_docs_faq_pref) else: t_router_docs_fast_path = time.perf_counter() price_intent_for_router_docs = any( term in msg.lower() for term in ("msrp", "price", "pricing", "cost", "list price", "how much", "quote", "unit price", "budgetary") ) generic_router_concept_preflight = bool( self._classify_concept_fallback(msg, "router_docs").get("allow_concept") and (not primary_models) and (not selected_context_request) and (not self._router_query_prefers_open_ended_shortlist(msg)) and (not price_intent_for_router_docs) and (not re.search( r"\b(table|tabular|matrix|chart|spec|specs|specification|datasheet|manual|install|quick start|ports?|antennas?|connectors?|show routers with|identify models where|which verizon gateways?|normalized catalog|missing fields?|device classes?|device types?|full routers?\s+vs\s+adapters?|unknown modem type|unknown ruggedness)\b", msg.lower(), )) ) fast = self._router_concept_fast_answer(msg) if generic_router_concept_preflight else None concept_preflight = None if generic_router_concept_preflight: if not fast: concept_preflight = self._concept_llm_fallback( msg, "router_docs", remaining_s=float(self.concept_fallback_timeout_s), ) if concept_preflight: concept_meta = _as_dict(concept_preflight.get("meta")) if self._concept_answer_needs_web_refinement( msg, str(concept_preflight.get("assistant") or ""), concept_meta, ): concept_preflight = self._confirm_web_lookup_response( msg, st, "router_docs", reason="Reply `Yes` if you want me to consult the web for a more detailed concept answer.", ) concept_meta = _as_dict(concept_preflight.get("meta")) else: concept_meta["web_fallback_skipped"] = "concept_refinement_not_needed" concept_preflight_meta = _as_dict(concept_preflight.get("meta")) if str(concept_meta.get("web_fallback_skipped") or "").strip() and ( not str(concept_preflight_meta.get("web_fallback_skipped") or "").strip() ): concept_preflight_meta["web_fallback_skipped"] = concept_meta["web_fallback_skipped"] concept_preflight_meta.setdefault("search_debug", {"path": "generic_router_concept_preflight"}) concept_preflight["meta"] = concept_preflight_meta if concept_preflight: delegated = { "assistant": str(concept_preflight.get("assistant") or ""), "state": st.router_docs_state, "sources": list(concept_preflight.get("sources") or []), "files": list(concept_preflight.get("files") or []), "meta": _as_dict(concept_preflight.get("meta")), } route_meta = {"domain": "router_docs", "fast_path": "generic_concept_preflight"} else: workbook_missing_fields_request = bool(self._extract_router_models_cached(msg)) and any( term in msg.lower() for term in ( "missing fields", "missing field", "fill missing", "fill in missing", "missing data", "feature coverage", ) ) fast = self._router_missing_fields_audit_fast(msg) if workbook_missing_fields_request else None if not fast: fast = self._router_workbook_fast_answer(msg, st, "router_docs", raw_message=str(message or "")) if not fast: fast = self._rapid_router_catalog_fast_answer(msg) if not fast: fast = self._router_similar_5g_alternatives_fast(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_open_ended_shortlist_fast_answer(msg) if (not fast) and price_intent_for_router_docs: fast = self._router_fact_fast_answer(msg) if not fast: fast = self._router_verizon_gateway_detail_fast(msg) if not fast: fast = self._router_verizon_gateway_matrix_fast(msg) if not fast: fast = self._router_missing_fields_audit_fast(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_wifi_generation_compare_fast(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_multi_model_doc_table_fast(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_docs_install_template_fast(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_requirement_fast_answer(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_model_listing_fast_answer(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_vendor_5g_sa_fast_answer(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_5g_sa_device_list_fast_answer(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_battery_options_fast_answer(msg, raw_message=str(message or "")) if (not fast) and (not price_intent_for_router_docs): fast = self._router_wifi_by_vendor_fast_answer(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_led_status_fast_answer(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_concept_fast_answer(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_case_studies_fast_answer(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_vendor_install_fast_answer(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_vehicle_5g_recommendation_fast(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_rugged_fit_fast(msg) if (not fast) and (not price_intent_for_router_docs): fast = self._router_docs_antenna_fast(msg) bridge = None if bridge: delegated = { "assistant": str(bridge.get("assistant") or ""), "state": st.router_lifecycle_state, "sources": list(bridge.get("sources") or []), "files": list(bridge.get("files") or []), "meta": _as_dict(bridge.get("meta")), } route_meta = { "domain": "router_lifecycle", "fast_path": str(_as_dict(bridge.get("meta")).get("retrieval_mode") or "router_docs_lifecycle_bridge"), } else: primary_tokens = [self._normalize_router_model(x) for x in _extract_router_models(msg)] primary_tokens = [t for t in primary_tokens if t] model_tokens = primary_tokens or self._extract_router_models_cached(msg) resolved_model_keys = { self._lookup_router_fact_key(m) or self._lookup_router_lifecycle_key(m) or self._normalize_router_model(m) or m for m in model_tokens } model_count = len([k for k in resolved_model_keys if k]) if (not fast) and ( price_intent_for_router_docs or (not self._router_should_skip_fact_fast(msg, model_count)) ): fast = self._router_fact_fast_answer(msg) if concept_preflight: pass elif bridge: pass elif fast: delegated = { "assistant": str(fast.get("assistant") or ""), "state": st.router_docs_state, "sources": list(fast.get("sources") or []), "files": list(fast.get("files") or []), "meta": _as_dict(fast.get("meta")), } fast_domain = str(_as_dict(fast.get("meta")).get("domain") or "router_docs") route_meta = {"domain": fast_domain, "fast_path": str(_as_dict(fast.get("meta")).get("retrieval_mode") or "router_fast")} else: faq = self._faq_fast_lane_answer(msg, st, resolved_mode, allow_clarify=True) if faq: faq_domain = str(_as_dict(faq.get("meta")).get("domain") or "router_docs") faq_state = { "router_docs": st.router_docs_state, "router_lifecycle": st.router_lifecycle_state, "pots": st.pots_state, "masters": st.masters_state, }.get(faq_domain, st.router_docs_state) delegated = { "assistant": str(faq.get("assistant") or ""), "state": faq_state, "sources": list(faq.get("sources") or []), "files": list(faq.get("files") or []), "meta": _as_dict(faq.get("meta")), } route_meta = {"domain": faq_domain, "fast_path": str(_as_dict(faq.get("meta")).get("retrieval_mode") or "faq_fast")} _record_delegate_phase("router_docs_faq", t_router_docs_fast_path) else: _record_delegate_phase("router_docs_fast_path", t_router_docs_fast_path) t_router_docs_core_delegate = time.perf_counter() delegated, route_meta = self._delegate(resolved_mode, msg, st, audience) _record_delegate_phase("router_docs_core_delegate", t_router_docs_core_delegate) if "router_docs_core_delegate" not in delegate_phases_ms and "router_docs_faq" not in delegate_phases_ms: _record_delegate_phase("router_docs_fast_path", t_router_docs_fast_path) elif resolved_mode == "router_lifecycle": t_router_lifecycle_fast_path = time.perf_counter() fast = self._router_workbook_fast_answer(msg, st, "router_lifecycle", raw_message=str(message or "")) if fast: delegated = { "assistant": str(fast.get("assistant") or ""), "state": st.router_lifecycle_state, "sources": list(fast.get("sources") or []), "files": list(fast.get("files") or []), "meta": _as_dict(fast.get("meta")), } route_meta = {"domain": "router_lifecycle", "fast_path": str(_as_dict(fast.get("meta")).get("retrieval_mode") or "lifecycle_fast")} _record_delegate_phase("router_lifecycle_fast_path", t_router_lifecycle_fast_path) else: _record_delegate_phase("router_lifecycle_fast_path", t_router_lifecycle_fast_path) numeric_model_reference = bool( re.search(r"\bmodel\s+\d{2,6}\b", msg, flags=re.IGNORECASE) or re.search(r"\b\d{2,6}\b", msg) ) next_actions = [ "Rephrase with exact model/SKU names, quantity lines, or a survey key/site name.", "If you need datasheet/manual text instead of workbook logic, switch to router docs phrasing explicitly.", ] if numeric_model_reference: next_actions.insert( 0, "I need the actual router model/SKU here. A numeric label like `228` is not enough to map a workbook replacement path safely.", ) delegated = { "assistant": _format_shell( "Router Lifecycle in Unified Knowledgebase is workbook-backed now, and I could not map this request into a supported workbook router question safely.", [ "Legacy CSV lifecycle fast paths are intentionally retired for router intelligence in this lane.", "Supported workbook asks include lifecycle dates, replacements, fleet snapshots, compare/details, current-device searches, antenna guidance, and survey placement interpretation.", ], next_actions, ), "state": st.router_lifecycle_state, "sources": self._router_workbook_sources("router_lifecycle", "lifecycle"), "files": [str(self._rapid_router_intelligence_status().get("filename") or "router_workbook.xlsx")], "meta": { "domain": "router_lifecycle", "retrieval_mode": "deterministic_router_workbook_router_lifecycle_no_match", "router_intelligence_source": "workbook", "review_required": True, "legacy_csv_replaced": True, }, } route_meta = {"domain": "router_lifecycle", "fast_path": "workbook_router_lifecycle_no_match"} elif resolved_mode == "pots": t_pots_concept_preflight = time.perf_counter() concept_preflight = None deterministic_preflight = None if bool( self._classify_concept_fallback(msg, "pots").get("allow_concept") and not re.search(r"\b(provider|providers|compare providers|vendor|vendors|price|pricing|quote|doc|document|file|pdf)\b", msg.lower()) and not _should_skip_pots_concept_preflight(msg) ): deterministic_preflight = self._deterministic_concept_fast_answer(msg, "pots") concept_preflight = deterministic_preflight or self._concept_llm_fallback( msg, "pots", remaining_s=float(self.concept_fallback_timeout_s), ) if concept_preflight: concept_meta = _as_dict(concept_preflight.get("meta")) if self._concept_answer_needs_web_refinement( msg, str(concept_preflight.get("assistant") or ""), concept_meta, ): concept_preflight = self._confirm_web_lookup_response( msg, st, "pots", reason="Reply `Yes` if you want me to consult the web for a more detailed concept answer.", ) concept_meta = _as_dict(concept_preflight.get("meta")) else: concept_meta["web_fallback_skipped"] = "concept_refinement_not_needed" concept_preflight_meta = _as_dict(concept_preflight.get("meta")) if str(concept_meta.get("web_fallback_skipped") or "").strip() and ( not str(concept_preflight_meta.get("web_fallback_skipped") or "").strip() ): concept_preflight_meta["web_fallback_skipped"] = concept_meta["web_fallback_skipped"] concept_preflight_meta.setdefault("search_debug", {"path": "generic_pots_concept_preflight"}) concept_preflight["meta"] = concept_preflight_meta _record_delegate_phase("pots_concept_preflight", t_pots_concept_preflight) fast = None if concept_preflight: delegated = { "assistant": str(concept_preflight.get("assistant") or ""), "state": st.pots_state, "sources": list(concept_preflight.get("sources") or []), "files": list(concept_preflight.get("files") or []), "meta": _as_dict(concept_preflight.get("meta")), } route_meta = { "domain": "pots", "fast_path": "generic_concept_preflight" if (not deterministic_preflight) else "generic_deterministic_concept_preflight", } else: t_pots_fast_path = time.perf_counter() fast = self._pots_fast_structured_answer( msg, show_citations=bool(st.show_citations), remaining_s=max(0.0, self.hard_timeout_s - (time.perf_counter() - t_total)), ) if (not concept_preflight) and (not fast): fast = self._pots_provider_fast_answer(msg) if concept_preflight: pass elif fast: delegated = { "assistant": str(fast.get("assistant") or ""), "state": st.pots_state, "sources": list(fast.get("sources") or []), "files": list(fast.get("files") or []), "meta": _as_dict(fast.get("meta")), } route_meta = {"domain": "pots", "fast_path": str(_as_dict(fast.get("meta")).get("retrieval_mode") or "pots_fast")} _record_delegate_phase("pots_fast_path", t_pots_fast_path) else: faq = self._faq_fast_lane_answer(msg, st, resolved_mode, allow_clarify=True) if faq: _record_delegate_phase("pots_fast_path", t_pots_fast_path) t_pots_faq = time.perf_counter() faq_domain = str(_as_dict(faq.get("meta")).get("domain") or "pots") faq_state = { "router_docs": st.router_docs_state, "router_lifecycle": st.router_lifecycle_state, "pots": st.pots_state, "masters": st.masters_state, }.get(faq_domain, st.pots_state) delegated = { "assistant": str(faq.get("assistant") or ""), "state": faq_state, "sources": list(faq.get("sources") or []), "files": list(faq.get("files") or []), "meta": _as_dict(faq.get("meta")), } route_meta = {"domain": faq_domain, "fast_path": str(_as_dict(faq.get("meta")).get("retrieval_mode") or "faq_fast")} _record_delegate_phase("pots_faq", t_pots_faq) else: _record_delegate_phase("pots_fast_path", t_pots_fast_path) t_pots_core_delegate = time.perf_counter() delegated, route_meta = self._delegate(resolved_mode, msg, st, audience) _record_delegate_phase("pots_core_delegate", t_pots_core_delegate) elif resolved_mode == "masters": t_masters_concept_preflight = time.perf_counter() concept_preflight = None deterministic_preflight = None if bool( self._classify_concept_fallback(msg, "masters").get("allow_concept") and not _should_skip_masters_concept_preflight(msg) ): deterministic_preflight = self._deterministic_concept_fast_answer(msg, "masters") concept_preflight = deterministic_preflight or self._concept_llm_fallback( msg, "masters", remaining_s=float(self.concept_fallback_timeout_s), ) if concept_preflight: concept_meta = _as_dict(concept_preflight.get("meta")) if self._concept_answer_needs_web_refinement( msg, str(concept_preflight.get("assistant") or ""), concept_meta, ): concept_preflight = self._confirm_web_lookup_response( msg, st, "masters", reason="Reply `Yes` if you want me to consult the web for a more detailed concept answer.", ) concept_meta = _as_dict(concept_preflight.get("meta")) else: concept_meta["web_fallback_skipped"] = "concept_refinement_not_needed" concept_preflight_meta = _as_dict(concept_preflight.get("meta")) if str(concept_meta.get("web_fallback_skipped") or "").strip() and ( not str(concept_preflight_meta.get("web_fallback_skipped") or "").strip() ): concept_preflight_meta["web_fallback_skipped"] = concept_meta["web_fallback_skipped"] concept_preflight_meta.setdefault("search_debug", {"path": "generic_masters_concept_preflight"}) concept_preflight["meta"] = concept_preflight_meta _record_delegate_phase("masters_concept_preflight", t_masters_concept_preflight) fast = None if concept_preflight: delegated = { "assistant": str(concept_preflight.get("assistant") or ""), "state": st.masters_state, "sources": list(concept_preflight.get("sources") or []), "files": list(concept_preflight.get("files") or []), "meta": _as_dict(concept_preflight.get("meta")), } route_meta = { "domain": "masters", "fast_path": "generic_concept_preflight" if (not deterministic_preflight) else "generic_deterministic_concept_preflight", } else: t_masters_fast_path = time.perf_counter() fast = self._masters_securefax_pricing_fast(msg) if not fast: fast = self._masters_file_lookup_fast(msg) if not fast: fast = self._masters_fast_outline_answer(msg) if concept_preflight: pass elif fast: delegated = { "assistant": str(fast.get("assistant") or ""), "state": st.masters_state, "sources": list(fast.get("sources") or []), "files": list(fast.get("files") or []), "meta": _as_dict(fast.get("meta")), } route_meta = {"domain": "masters", "fast_path": str(_as_dict(fast.get("meta")).get("retrieval_mode") or "masters_fast")} _record_delegate_phase("masters_fast_path", t_masters_fast_path) else: faq = self._faq_fast_lane_answer(msg, st, resolved_mode, allow_clarify=True) if faq: _record_delegate_phase("masters_fast_path", t_masters_fast_path) t_masters_faq = time.perf_counter() faq_domain = str(_as_dict(faq.get("meta")).get("domain") or "masters") faq_state = { "router_docs": st.router_docs_state, "router_lifecycle": st.router_lifecycle_state, "pots": st.pots_state, "masters": st.masters_state, }.get(faq_domain, st.masters_state) delegated = { "assistant": str(faq.get("assistant") or ""), "state": faq_state, "sources": list(faq.get("sources") or []), "files": list(faq.get("files") or []), "meta": _as_dict(faq.get("meta")), } route_meta = {"domain": faq_domain, "fast_path": str(_as_dict(faq.get("meta")).get("retrieval_mode") or "faq_fast")} _record_delegate_phase("masters_faq", t_masters_faq) else: _record_delegate_phase("masters_fast_path", t_masters_fast_path) t_masters_core_delegate = time.perf_counter() delegated, route_meta = self._delegate(resolved_mode, msg, st, audience) _record_delegate_phase("masters_core_delegate", t_masters_core_delegate) timing_ms["delegate"] = round((time.perf_counter() - t_delegate) * 1000.0, 2) elapsed_after_delegate = time.perf_counter() - t_total domain_for_budget = str(route_meta.get("domain") or resolved_mode) budget_s = self._effective_budget_s(msg, domain_for_budget) if elapsed_after_delegate > self.hard_timeout_s: budget_resp = self._timeout_clarify_response(msg, st, domain_for_budget, elapsed_after_delegate) budget_meta = _as_dict(budget_resp.get("meta")) budget_meta["timing_ms"] = {**timing_ms, "total": round((time.perf_counter() - t_total) * 1000.0, 2)} budget_meta["hard_timeout_s"] = float(self.hard_timeout_s) budget_meta["reason"] = "hard_timeout_exceeded" budget_resp["meta"] = _apply_delegate_phase_meta(budget_meta) return budget_resp delegated_assistant = str(delegated.get("assistant") or "") delegated_sources = delegated.get("sources") if isinstance(delegated.get("sources"), list) else [] delegated_meta = delegated.get("meta") if isinstance(delegated.get("meta"), dict) else {} router_docs_details_rescue = None if str(route_meta.get("domain") or domain_for_budget or "") == "router_docs": router_docs_details_rescue = self._router_docs_workbook_details_rescue( msg, st, delegated_assistant, delegated_sources, delegated_meta, ) if router_docs_details_rescue: delegated_assistant = str(router_docs_details_rescue.get("assistant") or delegated_assistant) delegated_sources = ( router_docs_details_rescue.get("sources") if isinstance(router_docs_details_rescue.get("sources"), list) else delegated_sources ) delegated_meta = ( router_docs_details_rescue.get("meta") if isinstance(router_docs_details_rescue.get("meta"), dict) else delegated_meta ) delegated["assistant"] = delegated_assistant delegated["sources"] = delegated_sources delegated["files"] = list(router_docs_details_rescue.get("files") or []) delegated["meta"] = delegated_meta route_meta["domain"] = str(delegated_meta.get("domain") or "router_docs") domain_for_budget = str(route_meta.get("domain") or domain_for_budget) if ( domain_for_budget == "router_docs" and self._is_named_model_spec_request_needing_clarification(msg, "router_docs") and str(delegated_meta.get("weak_reason") or "").startswith("no_model_match:") ): clarified = self._model_clarify_response(msg, st, "router_docs") clarified_meta = _as_dict(clarified.get("meta")) clarified_meta["timing_ms"] = {**timing_ms, "total": round((time.perf_counter() - t_total) * 1000.0, 2)} clarified["meta"] = _apply_delegate_phase_meta(clarified_meta) return clarified if ( str(route_meta.get("domain") or "") == "router_docs" and ("compare_missing_model_names" in delegated_assistant.lower()) ): clarified = self._model_clarify_response(msg, st, "router_docs") clarified_meta = _as_dict(clarified.get("meta")) clarified_meta["timing_ms"] = {**timing_ms, "total": round((time.perf_counter() - t_total) * 1000.0, 2)} clarified["meta"] = _apply_delegate_phase_meta(clarified_meta) return clarified fallback_total_budget_s = float(self.hard_timeout_s) + float(self.fallback_extra_budget_s) if self._is_internal_weak(delegated_assistant, delegated_sources, delegated_meta) and self._allow_concept_llm_fallback(msg, domain_for_budget): concept = self._deterministic_concept_fast_answer(msg, domain_for_budget) if concept: delegated_assistant = str(concept.get("assistant") or delegated_assistant) concept_meta = _as_dict(concept.get("meta")) delegated_meta = {**delegated_meta, **concept_meta} concept_domain = str(concept_meta.get("domain") or domain_for_budget).strip() or domain_for_budget route_meta["domain"] = concept_domain domain_for_budget = concept_domain delegated_sources = concept.get("sources") if isinstance(concept.get("sources"), list) else [] delegated["assistant"] = delegated_assistant delegated["sources"] = delegated_sources delegated["files"] = list(concept.get("files") or []) delegated["meta"] = delegated_meta delegated_meta["web_fallback_skipped"] = "concept_refinement_not_needed" elapsed_after_delegate = time.perf_counter() - t_total else: remaining_for_concept = min( max(0.0, fallback_total_budget_s - elapsed_after_delegate), float(self.fallback_extra_budget_s), ) if remaining_for_concept > 0.8: t_concept = time.perf_counter() concept = self._concept_llm_fallback( msg, domain_for_budget, remaining_s=min(remaining_for_concept, float(self.concept_fallback_timeout_s)), ) timing_ms["concept_fallback"] = round((time.perf_counter() - t_concept) * 1000.0, 2) else: concept = None delegated_meta["concept_fallback_skipped"] = "hard_timeout_budget_exhausted" if concept: delegated_assistant = str(concept.get("assistant") or delegated_assistant) concept_meta = _as_dict(concept.get("meta")) delegated_meta = {**delegated_meta, **concept_meta} concept_domain = str(concept_meta.get("domain") or domain_for_budget).strip() or domain_for_budget route_meta["domain"] = concept_domain domain_for_budget = concept_domain delegated_sources = concept.get("sources") if isinstance(concept.get("sources"), list) else [] delegated["assistant"] = delegated_assistant delegated["sources"] = delegated_sources delegated["files"] = list(concept.get("files") or []) delegated["meta"] = delegated_meta elapsed_after_delegate = time.perf_counter() - t_total if self._concept_answer_needs_web_refinement(msg, delegated_assistant, delegated_meta): confirm = self._confirm_web_lookup_response( msg, st, domain_for_budget, reason="Reply `Yes` if you want me to consult the web for a more detailed concept answer.", ) confirm_meta = _as_dict(confirm.get("meta")) confirm_meta["timing_ms"] = {**timing_ms, "total": round((time.perf_counter() - t_total) * 1000.0, 2)} confirm["meta"] = _apply_delegate_phase_meta(confirm_meta) return confirm else: delegated_meta["web_fallback_skipped"] = "concept_refinement_not_needed" else: delegated_meta["concept_fallback_skipped"] = "llm_unavailable_or_empty" should_timeout_clarify = ( elapsed_after_delegate > budget_s and self._is_internal_weak(delegated_assistant, delegated_sources, delegated_meta) ) if should_timeout_clarify: low_time_resp = self._low_time_template_response( msg, st, domain_for_budget, remaining_s=max(0.0, self.hard_timeout_s - elapsed_after_delegate), ) if low_time_resp is not None: low_meta = _as_dict(low_time_resp.get("meta")) low_meta["timing_ms"] = {**timing_ms, "total": round((time.perf_counter() - t_total) * 1000.0, 2)} low_time_resp["meta"] = _apply_delegate_phase_meta(low_meta) return low_time_resp budget_resp = self._timeout_clarify_response(msg, st, domain_for_budget, elapsed_after_delegate) budget_meta = _as_dict(budget_resp.get("meta")) budget_meta["timing_ms"] = {**timing_ms, "total": round((time.perf_counter() - t_total) * 1000.0, 2)} budget_resp["meta"] = _apply_delegate_phase_meta(budget_meta) return budget_resp except Exception as exc: return { "assistant": _format_shell( "I hit an error while processing that request.", [f"{type(exc).__name__}: {exc}"], ["Retry the same question, or choose a specific mode and try again."], ), "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": [], "files": [], "effective_audience": "external", "meta": { "domain": "knowledgebase", "error": f"{type(exc).__name__}: {exc}", **({"delegate_phases_ms": dict(delegate_phases_ms)} if delegate_phases_ms else {}), "timing_ms": {"total": round((time.perf_counter() - t_total) * 1000.0, 2)}, }, } domain = str(route_meta.get("domain") or resolved_mode) assistant = str(delegated.get("assistant") or "").strip() sources = self._normalize_sources(domain, delegated.get("sources") or []) files = self._normalize_domain_files(domain, delegated.get("files") or []) delegated_meta = dict(delegated_meta) if isinstance(delegated_meta, dict) else {} retrieval_mode = str(delegated_meta.get("retrieval_mode") or "") if not sources: fallback_sources = self._fast_mode_guidance_sources(domain, retrieval_mode, msg) if fallback_sources: sources = self._normalize_sources(domain, fallback_sources) if not files: fallback_files = [ str(src.get("relative_path") or src.get("doc") or "") for src in fallback_sources if str(src.get("relative_path") or src.get("doc") or "").strip() ] files = self._normalize_domain_files(domain, fallback_files) if ( domain == "router_docs" and ("context from rapid router form:" in str(msg).lower()) and (not retrieval_mode.startswith("deterministic_rapid_router_catalog_")) ): primary_msg, _ = self._split_rapid_router_context_message(msg) requested_models = self._rapid_router_explicit_models(primary_msg) catalog_products = [ p for p in list(self._rapid_router_catalog_snapshot(force_refresh=False).get("products") or []) if isinstance(p, dict) ] unavailable_models = self._rapid_router_models_missing_from_catalog( requested_models, catalog_products, ) if unavailable_models: notice_line = "Note: devices are not available for purchase in the Rapid Router at this time." if notice_line.lower() not in assistant.lower(): assistant = _norm_preserve( f"{assistant}\n\n{notice_line}\n" f"Requested model(s): {', '.join(unavailable_models[:6])}.\n" "For models not sold in Rapid Router, pricing is MSRP-only from internal router docs when available." ).strip() delegated_meta["rapid_router_unavailable_models"] = unavailable_models[:6] skip_lifecycle_compare_note = bool( retrieval_mode in { "router_vehicle_5g_recommendation_fast", "router_multi_model_doc_table_fast", "router_multi_model_doc_caveat_table_fast", "router_docs_documented_matrix_fast", } or ( retrieval_mode == "deterministic_router_fact_index" and _contains_any(msg, ("connector", "connectors", "adapter", "adapters", "rf")) ) ) compare_lifecycle_note_requested = bool( _contains_any( msg, ( "lifecycle", "lifecycle posture", "lifecycle status", "lifecycle note", "end-of-sale", "end of sale", "eos", "end-of-life", "end of life", "eol", ), ) ) if domain == "router_docs" and compare_lifecycle_note_requested and (not skip_lifecycle_compare_note): lifecycle_alerts = self._router_compare_lifecycle_alerts(msg) if lifecycle_alerts and ("lifecycle note:" not in assistant.lower()): note_text = "Lifecycle note: " + "; ".join( [f"`{alert.get('model')}` is {alert.get('detail')}" for alert in lifecycle_alerts] ) + "." assistant = assistant.rstrip() + "\n\n" + note_text source_start = len(sources) + 1 for idx, alert in enumerate(lifecycle_alerts, start=source_start): sources.append(self._router_compare_lifecycle_source(alert, f"L{idx}")) prefilter_citation_quality: Dict[str, Any] = {} prefilter_quorum_met = False if self.web_skip_prefilter_quorum_enabled and sources: prefilter_citation_quality = self._citation_quality_gate(msg, domain, list(sources)) prefilter_quorum_met = bool(prefilter_citation_quality.get("pass", False)) t_cite = time.perf_counter() sources, citation_meta = self._filter_sources_by_relevance(msg, domain, list(sources)) timing_ms["citation_filter"] = round((time.perf_counter() - t_cite) * 1000.0, 2) if domain == "router_docs" and _contains_any(msg, ("compare", "comparison", "table", "vs", "versus")) and len(sources) < 2: sources.append( { "id": f"RCSV{len(sources) + 1}", "domain": "router_docs", "doc": "feb2026routers.csv", "relative_path": "feb2026routers.csv", "chunk_id": "router_compare_fallback", "location": "", "excerpt": "Internal router catalog fallback citation for compare-format responses.", "score": 0.8, } ) citation_quality = self._citation_quality_gate(msg, domain, sources) citation_quorum_met = bool(citation_quality.get("pass", False)) if prefilter_quorum_met and (not citation_quorum_met): citation_quorum_met = True citation_quality["prefilter_quorum_met"] = True citation_quality["prefilter_actual"] = int(prefilter_citation_quality.get("actual") or 0) citation_quality["prefilter_required_min"] = int(prefilter_citation_quality.get("required_min") or 0) evidence_misaligned_for_query = bool( domain == "router_docs" and self._router_fact_answer_needs_web_confirmation(msg, assistant, sources, delegated_meta) ) if evidence_misaligned_for_query: delegated_meta["evidence_misaligned_for_query"] = True delegated_meta.setdefault("weak_reason", "evidence_misaligned_for_query") remaining_for_web = min( self.hard_timeout_s - (time.perf_counter() - t_total), float(self._web_stage_budget_cap_s(msg, domain)), ) if remaining_for_web > 1.2: confirm = self._confirm_web_lookup_response( msg, st, domain, reason="Reply `Yes` if you want me to consult the web because the internal excerpts do not confirm this exact fact cleanly.", ) confirm_meta = _as_dict(confirm.get("meta")) confirm_meta["timing_ms"] = {**timing_ms, "total": round((time.perf_counter() - t_total) * 1000.0, 2)} confirm["meta"] = _apply_delegate_phase_meta(confirm_meta) return confirm delegated_meta["web_fallback_skipped"] = "hard_timeout_budget_exhausted" if self._is_internal_weak(assistant, sources, delegated_meta) and (not citation_quorum_met): remaining_for_web = min( self.hard_timeout_s - (time.perf_counter() - t_total), float(self._web_stage_budget_cap_s(msg, domain)), ) if remaining_for_web > 1.2: confirm = self._confirm_web_lookup_response( msg, st, domain, reason="Reply `Yes` if you want a clearly labeled best-effort web answer before we stop here.", ) confirm_meta = _as_dict(confirm.get("meta")) confirm_meta["timing_ms"] = {**timing_ms, "total": round((time.perf_counter() - t_total) * 1000.0, 2)} confirm["meta"] = _apply_delegate_phase_meta(confirm_meta) return confirm else: delegated_meta["web_fallback_skipped"] = "hard_timeout_budget_exhausted" elif self._is_internal_weak(assistant, sources, delegated_meta) and citation_quorum_met: delegated_meta["web_fallback_skipped"] = ( "internal_citation_quorum_prefilter_met" if (prefilter_quorum_met and (not bool(citation_quality.get("pass", False)))) else "internal_citation_quorum_met" ) if domain == "router_docs" and _contains_any(msg, ("compare", "comparison", "table", "vs", "versus")): retrieval_mode = str(delegated_meta.get("retrieval_mode") or "") weak_reason = str(delegated_meta.get("weak_reason") or "") compare_models = [str(x).strip() for x in self._extract_router_models_cached(msg) if str(x).strip()] if len(compare_models) < 2: compare_fragments = [ _norm(fragment) for fragment in _ROUTER_MODEL_SEPARATOR_RE.split(str(msg or "")) if _norm(fragment) ] compare_models = [ fragment for fragment in compare_fragments if _ROUTER_MODEL_TOKEN_RE.search(fragment) ] generic_compare_stub = "router docs answer." in str(assistant or "").lower() if ( retrieval_mode in {"internal", "internal_weak", "internal_weak_no_web"} and len(compare_models) >= 2 and ( ("citation_gate_comparison" in weak_reason) or ((not bool(citation_quality.get("pass", True))) and generic_compare_stub) or self._is_internal_weak(assistant, sources, delegated_meta) ) ): clarify = self._model_clarify_response(msg, st, domain) clarify_meta = _as_dict(clarify.get("meta")) clarify_meta["timing_ms"] = {**timing_ms, "total": round((time.perf_counter() - t_total) * 1000.0, 2)} clarify["meta"] = _apply_delegate_phase_meta(clarify_meta) return clarify if ( (not bool(citation_quality.get("pass", True))) and (not bool(delegated_meta.get("web_assisted"))) and (not bool(delegated_meta.get("citation_quorum_not_required"))) and (not prefilter_quorum_met) ): retrieval_mode = str(delegated_meta.get("retrieval_mode") or "") deterministic_relaxed = ( retrieval_mode.startswith("deterministic_") or retrieval_mode.endswith("_fast") or retrieval_mode in {"router_docs_install_template_fast", "router_docs_antenna_fast"} ) compare_case_relaxed = ( domain == "router_docs" and _contains_any(msg, ("compare", "comparison", "table", "vs", "versus")) and int(citation_quality.get("actual") or 0) >= 1 and (not _contains_any(msg, ("from docs only", "from documented specs only", "documented specs only"))) ) if compare_case_relaxed: deterministic_relaxed = True if self._needs_strict_citation(msg, domain) and (not deterministic_relaxed): assistant = _format_shell( "I don’t yet have enough internal citations to answer this safely.", [ f"Citation quorum not met ({citation_quality.get('actual')}/{citation_quality.get('required_min')})" f" with meaningful evidence ({citation_quality.get('meaningful_actual')}/{citation_quality.get('required_meaningful_min')}).", ], [ "Narrow the ask to exact model + one output type (compare/spec/replacement).", "Or reply `Yes` when prompted if you want a clearly labeled best-effort web answer.", ], ) sources = [] files = [] delegated_meta["retrieval_mode"] = "citation_quorum_block" else: if (not deterministic_relaxed) and not ( domain == "router_lifecycle" and retrieval_mode.startswith("deterministic") ): assistant = _norm_preserve( f"{assistant}\n\n**Citation confidence note**\n- Evidence is thinner than the normal citation target for this answer type." ).strip() elif prefilter_quorum_met and (not bool(delegated_meta.get("web_assisted"))) and ( not str(delegated_meta.get("web_fallback_skipped") or "").strip() ): delegated_meta["web_fallback_skipped"] = "internal_citation_quorum_prefilter_met" assistant = self._ensure_shell_format(msg, assistant, domain) elapsed_before_compact = time.perf_counter() - t_total assistant = self._apply_response_compaction(msg, assistant, elapsed_before_compact) timeout_exceeded = bool(elapsed_before_compact > self.hard_timeout_s) if timeout_exceeded: assistant = _norm_preserve( f"{assistant}\n\n**Speed note**\n- This response exceeded the {int(self.hard_timeout_s)}s budget. " "For faster turnaround, ask one focused question (model + output format)." ) if not st.show_citations: hidden_mode = str(delegated_meta.get("retrieval_mode") or retrieval_mode or "") if (hidden_mode in _HIDDEN_CITATION_PRESERVE_FAST_MODES) or hidden_mode.startswith("pots_"): delegated_meta["citations_hidden"] = True else: sources = [] # Expose active pending for chat guidance components. domain_state = { "router_docs": st.router_docs_state, "router_lifecycle": st.router_lifecycle_state, "masters": st.masters_state, "pots": st.pots_state, }.get(domain, {}) pending_from_domain = _as_dict(_as_dict(domain_state).get("pending")) if pending_from_domain: st.pending = pending_from_domain elif str(_as_dict(delegated_meta).get("retrieval_mode") or "").startswith("faq_fast_clarify"): # Preserve FAQ clarification pending state when fast-lane response did not mutate domain history. st.pending = _as_dict(st.pending) elif str(_as_dict(delegated_meta).get("retrieval_mode") or "") == "confirm_web_lookup": st.pending = _as_dict(st.pending) else: st.pending = {} st.last_mode = domain if domain in _MODE_LABELS else st.last_mode st.last_user_message = _norm(msg) effective_audience = str(delegated.get("effective_audience") or "external").strip().lower() if effective_audience not in {"internal", "external"}: effective_audience = "external" result_meta = self._annotate_provenance({ **delegated_meta, **self._build_path_budget_meta(msg, domain), "domain": domain, "domain_label": _MODE_LABELS.get(domain, domain), "mode_badge": _MODE_BADGE_TEXT.get(domain, ""), "resolved_mode": domain, "requested_mode": requested_mode, "show_citations": bool(st.show_citations), "citation_gate": citation_meta, "citation_quality": citation_quality, "cache_hit": False, "hard_timeout_s": float(self.hard_timeout_s), "fallback_extra_budget_s": float(self.fallback_extra_budget_s), "soft_concise_s": float(self.soft_concise_s), "timeout_budget_exceeded": timeout_exceeded, "input_normalized": bool(normalize_meta.get("changed")), "input_corrections": list(normalize_meta.get("corrections") or []), "route_quality_flags": self._route_quality_flags( msg, domain, str(delegated_meta.get("retrieval_mode") or ""), delegated_meta, ), }) result_meta = _apply_delegate_phase_meta(result_meta) timing_ms["total"] = round((time.perf_counter() - t_total) * 1000.0, 2) result_meta["timing_ms"] = timing_ms self._runtime_telemetry_log( message=msg, domain=domain, result_meta=result_meta, sources=sources, citation_quality=_as_dict(citation_quality), ) result = { "assistant": assistant, "state": st.to_dict(), "prompt_version": f"unified-{PROMPT_VERSION}", "sources": sources, "files": files, "effective_audience": effective_audience, "meta": result_meta, } if cache_key: self._cache_set(cache_key, result) return result