Separate ASR (2 workers) from summary/RAG post-processing, add LightRAG chat API, batch upload fixes, and local model mounts for Docker deployment. Co-authored-by: Cursor <cursoragent@cursor.com>
146 lines
5.4 KiB
Python
146 lines
5.4 KiB
Python
"""Форматирование документа совещания для индексации в LightRAG."""
|
||
|
||
import json
|
||
from datetime import datetime
|
||
from typing import Any, Dict, List
|
||
|
||
from src.document import format_time
|
||
|
||
|
||
def format_meeting_document(
|
||
segments: List[Dict[str, Any]],
|
||
metadata: Dict[str, Any],
|
||
source_filename: str,
|
||
) -> str:
|
||
"""Собирает текстовый документ для вставки в LightRAG.
|
||
|
||
Сохраняет полную расшифровку + метаданные + извлечённые сущности.
|
||
"""
|
||
lines = []
|
||
lines.append("=== СОВЕЩАНИЕ ===")
|
||
lines.append(f"ID: {metadata.get('project', 'unknown')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}")
|
||
lines.append(f"Проект: {metadata.get('project', 'unknown')}")
|
||
lines.append(f"Раздел: {metadata.get('section', 'Общие вопросы')}")
|
||
lines.append(f"Тема: {metadata.get('topic', 'Не определена')}")
|
||
lines.append(f"Дата: {metadata.get('date', 'Не указана')}")
|
||
lines.append(f"Источник: {source_filename}")
|
||
|
||
participants = metadata.get("participants", [])
|
||
if participants:
|
||
lines.append(f"Участники: {', '.join(participants)}")
|
||
else:
|
||
# fallback — извлечь уникальных спикеров из сегментов
|
||
speakers = sorted({seg.get("speaker", "UNKNOWN") for seg in segments})
|
||
lines.append(f"Участники: {', '.join(speakers)}")
|
||
|
||
lines.append("")
|
||
lines.append("--- Метаданные ---")
|
||
summary = metadata.get("summary", "")
|
||
if summary:
|
||
lines.append(f"Summary: {summary}")
|
||
|
||
decisions = metadata.get("key_decisions", [])
|
||
if decisions:
|
||
lines.append("Решения:")
|
||
for i, d in enumerate(decisions, 1):
|
||
lines.append(f" {i}. {d}")
|
||
|
||
actions = metadata.get("action_items", [])
|
||
if actions:
|
||
lines.append("Action items:")
|
||
for a in actions:
|
||
who = a.get("who", "?")
|
||
what = a.get("what", "")
|
||
deadline = a.get("deadline", "")
|
||
dl = f" (до {deadline})" if deadline else ""
|
||
lines.append(f" - {who}: {what}{dl}")
|
||
|
||
lines.append("")
|
||
lines.append("--- Полная расшифровка ---")
|
||
for seg in segments:
|
||
ts = format_time(seg.get("start", 0.0))
|
||
speaker = seg.get("speaker", "UNKNOWN")
|
||
text = seg.get("text", "").strip()
|
||
if text:
|
||
lines.append(f"[{ts}] {speaker}: {text}")
|
||
|
||
return "\n".join(lines)
|
||
|
||
|
||
def format_summary_markdown(
|
||
metadata: Dict[str, Any],
|
||
brief: str,
|
||
source_filename: str,
|
||
) -> str:
|
||
"""Формирует markdown-файл краткого содержания совещания."""
|
||
lines = [
|
||
"# Краткое содержание совещания",
|
||
"",
|
||
f"**Проект:** {metadata.get('project', '—')} ",
|
||
f"**Раздел:** {metadata.get('section', '—')} ",
|
||
f"**Тема:** {metadata.get('topic', '—')} ",
|
||
f"**Дата:** {metadata.get('date') or '—'} ",
|
||
f"**Источник:** {source_filename}",
|
||
"",
|
||
]
|
||
|
||
participants = metadata.get("participants") or []
|
||
if participants:
|
||
lines.append(f"**Участники:** {', '.join(participants)}")
|
||
lines.append("")
|
||
|
||
lines.extend(["## Суть", "", brief.strip(), ""])
|
||
|
||
problems = metadata.get("problems") or []
|
||
if problems:
|
||
lines.append("## Ключевые вопросы и проблемы")
|
||
lines.append("")
|
||
for item in problems:
|
||
lines.append(f"- {item}")
|
||
lines.append("")
|
||
|
||
decisions = metadata.get("key_decisions") or []
|
||
if decisions:
|
||
lines.append("## Принятые решения")
|
||
lines.append("")
|
||
for item in decisions:
|
||
lines.append(f"- {item}")
|
||
lines.append("")
|
||
|
||
actions = metadata.get("action_items") or []
|
||
if actions:
|
||
lines.append("## Поручения")
|
||
lines.append("")
|
||
for action in actions:
|
||
who = action.get("who", "?")
|
||
what = action.get("what", "")
|
||
deadline = action.get("deadline")
|
||
dl = f" (до {deadline})" if deadline else ""
|
||
lines.append(f"- **{who}:** {what}{dl}")
|
||
lines.append("")
|
||
|
||
return "\n".join(lines).strip() + "\n"
|
||
|
||
|
||
def format_global_document(doc_text: str, metadata: Dict[str, Any]) -> str:
|
||
"""Формирует версию документа для глобального (межпроектного) индекса.
|
||
|
||
Добавляет явное указание проекта в начало, чтобы глобальный граф знал связь.
|
||
"""
|
||
header = f"""=== СОВЕЩАНИЕ (Проект: {metadata.get('project', 'unknown')}) ===
|
||
Раздел: {metadata.get('section', 'Общие вопросы')}
|
||
Тема: {metadata.get('topic', 'Не определена')}
|
||
"""
|
||
return header + "\n" + doc_text
|
||
|
||
|
||
def build_meeting_text_only(segments: List[Dict[str, Any]]) -> str:
|
||
"""Собирает plain text из сегментов (для отправки в classify_meeting)."""
|
||
lines = []
|
||
for seg in segments:
|
||
speaker = seg.get("speaker", "UNKNOWN")
|
||
text = seg.get("text", "").strip()
|
||
if text:
|
||
lines.append(f"{speaker}: {text}")
|
||
return "\n".join(lines)
|