"""Сборка полного пакета ответа и контекста заявки по CJM.""" from __future__ import annotations from app.config import settings from app.data.knowledge_base import get_colleague_cases_for_system from app.models import ColleagueCase, ResponsePackage, Source, TicketDraft def build_why_suggested( *, system: str | None, sources: list[Source], confidence: int, source_title: str | None = None, ) -> str: reasons: list[str] = [] if system: reasons.append(f"в запросе определена система «{system}»") if sources: reasons.append(f"в инструкциях найден материал «{sources[0].title}»") elif source_title: reasons.append(f"совпадение с документом «{source_title}»") if confidence < 70: reasons.append("уверенность ниже 70% — ответ может быть неполным") if not reasons: return "Ответ сформирован на основе доступных корпоративных инструкций." return "Мы предлагаем этот ответ, потому что " + "; ".join(reasons) + "." def build_warning(recommendation: str, cases: list[ColleagueCase]) -> str | None: if not cases: return None rec_lower = recommendation.lower() divergent_keywords = ( "переустанов", "другой браузер", "режим инкогнито", "перезапуск сервера", "администратор", ) for case in cases: outcome_lower = case.outcome.lower() if any(keyword in outcome_lower for keyword in divergent_keywords): if not any(keyword in rec_lower for keyword in divergent_keywords): return ( "В похожих случаях коллеги использовали обходные решения, которые могут " "отличаться от официальной инструкции. Рекомендуем сначала следовать " "регламенту, а при неудаче — оформить обращение." ) return ( "Опыт коллег приведён для ориентира. При расхождении с инструкцией " "приоритет у официального регламента." ) def build_ticket_description( query: str, system: str, *, conversation: list[dict[str, str]] | None = None, sources: list[Source] | None = None, colleague_cases: list[ColleagueCase] | None = None, recommendation: str = "", ) -> str: lines = [ f"Исходный вопрос: {query}", f"ИТ-система: {system}", ] if recommendation: lines.extend(["", "Рекомендация ассистента:", recommendation[:800]]) if conversation: lines.extend(["", "Диалог с ассистентом:"]) for turn in conversation[-8:]: role = "Сотрудник" if turn.get("role") == "user" else "Ассистент" text = turn.get("text", "").strip() if text: lines.append(f"- {role}: {text[:300]}") if sources: lines.extend(["", "Источники:"]) for src in sources[:4]: title = src.title if src.url: lines.append(f"- {title} ({src.url})") else: lines.append(f"- {title}") if colleague_cases: lines.extend(["", "Похожие случаи коллег:"]) for case in colleague_cases[:3]: lines.append(f"- {case.summary} → {case.outcome}") return "\n".join(lines) def ensure_escalation_draft( package: ResponsePackage, query: str, *, conversation: list[dict[str, str]] | None = None, ) -> ResponsePackage: """CJM §4.2: при уверенности ниже порога — черновик заявки всегда в пакете.""" if package.confidence >= settings.confidence_threshold: return package system = package.system or "Общее" cases = package.colleague_cases package.ticket_draft = enrich_ticket_draft( query, system, "Высокий", conversation=conversation, sources=package.sources, colleague_cases=cases, recommendation=package.recommendation, ) return package def enrich_ticket_draft( query: str, system: str, priority: str, *, conversation: list[dict[str, str]] | None = None, sources: list[Source] | None = None, colleague_cases: list[ColleagueCase] | None = None, recommendation: str = "", ) -> TicketDraft: cases = colleague_cases or get_colleague_cases_for_system(system) return TicketDraft( title=f"Вопрос по системе {system}", description=build_ticket_description( query, system, conversation=conversation, sources=sources, colleague_cases=cases, recommendation=recommendation, ), system=system, priority=priority, )