Files

150 lines
5.3 KiB
Python

"""Сборка полного пакета ответа и контекста заявки по 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,
)