[split-required] Split 700-870 LOC files across all services
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>
This commit is contained in:
187
backend-lehrer/recording_minutes.py
Normal file
187
backend-lehrer/recording_minutes.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
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"
|
||||
)
|
||||
Reference in New Issue
Block a user