backend-lehrer (11 files): - llm_gateway/routes/schools.py (867 → 5), recording_api.py (848 → 6) - messenger_api.py (840 → 5), print_generator.py (824 → 5) - unit_analytics_api.py (751 → 5), classroom/routes/context.py (726 → 4) - llm_gateway/routes/edu_search_seeds.py (710 → 4) klausur-service (12 files): - ocr_labeling_api.py (845 → 4), metrics_db.py (833 → 4) - legal_corpus_api.py (790 → 4), page_crop.py (758 → 3) - mail/ai_service.py (747 → 4), github_crawler.py (767 → 3) - trocr_service.py (730 → 4), full_compliance_pipeline.py (723 → 4) - dsfa_rag_api.py (715 → 4), ocr_pipeline_auto.py (705 → 4) website (6 pages): - audit-checklist (867 → 8), content (806 → 6) - screen-flow (790 → 4), scraper (789 → 5) - zeugnisse (776 → 5), modules (745 → 4) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
188 lines
5.8 KiB
Python
188 lines
5.8 KiB
Python
"""
|
|
Recording API - Meeting Minutes Routes.
|
|
|
|
Generate, retrieve, and export KI-based meeting minutes.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from fastapi.responses import PlainTextResponse, HTMLResponse
|
|
|
|
from recording_helpers import (
|
|
_recordings_store,
|
|
_transcriptions_store,
|
|
_minutes_store,
|
|
log_audit,
|
|
)
|
|
|
|
router = APIRouter(tags=["Recordings"])
|
|
|
|
|
|
# ==========================================
|
|
# MEETING MINUTES ENDPOINTS
|
|
# ==========================================
|
|
|
|
@router.post("/{recording_id}/minutes")
|
|
async def generate_meeting_minutes(
|
|
recording_id: str,
|
|
title: Optional[str] = Query(None, description="Meeting-Titel"),
|
|
model: str = Query("breakpilot-teacher-8b", description="LLM Modell")
|
|
):
|
|
"""
|
|
Generiert KI-basierte Meeting Minutes aus der Transkription.
|
|
|
|
Nutzt das LLM Gateway (Ollama/vLLM) fuer lokale Verarbeitung.
|
|
"""
|
|
from meeting_minutes_generator import get_minutes_generator, MeetingMinutes
|
|
|
|
# Check recording exists
|
|
recording = _recordings_store.get(recording_id)
|
|
if not recording:
|
|
raise HTTPException(status_code=404, detail="Recording not found")
|
|
|
|
# Check transcription exists and is completed
|
|
transcription = next(
|
|
(t for t in _transcriptions_store.values() if t["recording_id"] == recording_id),
|
|
None
|
|
)
|
|
if not transcription:
|
|
raise HTTPException(status_code=400, detail="No transcription found. Please transcribe first.")
|
|
|
|
if transcription["status"] != "completed":
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Transcription not ready. Status: {transcription['status']}"
|
|
)
|
|
|
|
# Check if minutes already exist
|
|
existing = _minutes_store.get(recording_id)
|
|
if existing and existing.get("status") == "completed":
|
|
# Return existing minutes
|
|
return existing
|
|
|
|
# Get transcript text
|
|
transcript_text = transcription.get("full_text", "")
|
|
if not transcript_text:
|
|
raise HTTPException(status_code=400, detail="Transcription has no text content")
|
|
|
|
# Generate meeting minutes
|
|
generator = get_minutes_generator()
|
|
|
|
try:
|
|
minutes = await generator.generate(
|
|
transcript=transcript_text,
|
|
recording_id=recording_id,
|
|
transcription_id=transcription["id"],
|
|
title=title,
|
|
date=recording.get("recorded_at", "")[:10] if recording.get("recorded_at") else None,
|
|
duration_minutes=recording.get("duration_seconds", 0) // 60 if recording.get("duration_seconds") else None,
|
|
participant_count=recording.get("participant_count", 0),
|
|
model=model
|
|
)
|
|
|
|
# Store minutes
|
|
minutes_dict = minutes.model_dump()
|
|
minutes_dict["generated_at"] = minutes.generated_at.isoformat()
|
|
_minutes_store[recording_id] = minutes_dict
|
|
|
|
# Log action
|
|
log_audit(
|
|
action="minutes_generated",
|
|
recording_id=recording_id,
|
|
metadata={"model": model, "generation_time": minutes.generation_time_seconds}
|
|
)
|
|
|
|
return minutes_dict
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Minutes generation failed: {str(e)}")
|
|
|
|
|
|
@router.get("/{recording_id}/minutes")
|
|
async def get_meeting_minutes(recording_id: str):
|
|
"""
|
|
Ruft generierte Meeting Minutes ab.
|
|
"""
|
|
minutes = _minutes_store.get(recording_id)
|
|
if not minutes:
|
|
raise HTTPException(status_code=404, detail="No meeting minutes found. Generate them first with POST.")
|
|
|
|
return minutes
|
|
|
|
|
|
def _load_minutes(recording_id: str):
|
|
"""Load and convert stored minutes dict back to MeetingMinutes."""
|
|
from meeting_minutes_generator import MeetingMinutes
|
|
|
|
minutes_dict = _minutes_store.get(recording_id)
|
|
if not minutes_dict:
|
|
raise HTTPException(status_code=404, detail="No meeting minutes found")
|
|
|
|
minutes_dict_copy = minutes_dict.copy()
|
|
if isinstance(minutes_dict_copy.get("generated_at"), str):
|
|
minutes_dict_copy["generated_at"] = datetime.fromisoformat(minutes_dict_copy["generated_at"])
|
|
|
|
return MeetingMinutes(**minutes_dict_copy)
|
|
|
|
|
|
@router.get("/{recording_id}/minutes/markdown")
|
|
async def get_minutes_markdown(recording_id: str):
|
|
"""
|
|
Exportiert Meeting Minutes als Markdown.
|
|
"""
|
|
from meeting_minutes_generator import minutes_to_markdown
|
|
|
|
minutes = _load_minutes(recording_id)
|
|
markdown = minutes_to_markdown(minutes)
|
|
|
|
return PlainTextResponse(
|
|
content=markdown,
|
|
media_type="text/markdown",
|
|
headers={"Content-Disposition": f"attachment; filename=protokoll_{recording_id}.md"}
|
|
)
|
|
|
|
|
|
@router.get("/{recording_id}/minutes/html")
|
|
async def get_minutes_html(recording_id: str):
|
|
"""
|
|
Exportiert Meeting Minutes als HTML.
|
|
"""
|
|
from meeting_minutes_generator import minutes_to_html
|
|
|
|
minutes = _load_minutes(recording_id)
|
|
html = minutes_to_html(minutes)
|
|
|
|
return HTMLResponse(content=html)
|
|
|
|
|
|
@router.get("/{recording_id}/minutes/pdf")
|
|
async def get_minutes_pdf(recording_id: str):
|
|
"""
|
|
Exportiert Meeting Minutes als PDF.
|
|
|
|
Benoetigt WeasyPrint (pip install weasyprint).
|
|
"""
|
|
from meeting_minutes_generator import minutes_to_html
|
|
|
|
minutes = _load_minutes(recording_id)
|
|
html = minutes_to_html(minutes)
|
|
|
|
try:
|
|
from weasyprint import HTML
|
|
from fastapi.responses import Response
|
|
|
|
pdf_bytes = HTML(string=html).write_pdf()
|
|
|
|
return Response(
|
|
content=pdf_bytes,
|
|
media_type="application/pdf",
|
|
headers={"Content-Disposition": f"attachment; filename=protokoll_{recording_id}.pdf"}
|
|
)
|
|
except ImportError:
|
|
raise HTTPException(
|
|
status_code=501,
|
|
detail="PDF export not available. Install weasyprint: pip install weasyprint"
|
|
)
|