root 3a9881d36f feat: Profile-System für unterschiedliche Use-Cases (Sitzungsprotokoll, später Arzt-Diktat etc.)
Mac-Worker:
- profiles/meeting.yaml — extrahiert die bisherigen Prompts + DOCX-Layout
- core/profiles.py — YAML-Loader mit Cache und Fallback
- ollama_client.summarize/make_protocol nehmen profile_name
- docx_export: generischer Renderer aus profile.docx (meta + sections mit
  Typ text/list/tasks/transcript)
- /api/profiles listet verfügbare Profile
- pyyaml als Dependency

LXC:
- Job.profile + User.default_profile (Migration: ALTER TABLE)
- /api/profiles proxy mit 60s-Cache und Fallback
- Upload-Form akzeptiert profile (Server-Default: user.default_profile)
- Pipeline gibt Profile bei summarize/protocol/docx an Mac weiter
- PATCH /api/auth/me — User kann Standard-Profil ändern

Frontend:
- Profile-Dropdown im Upload (nur sichtbar wenn ≥2 Profile)
- Settings-Karte "Mein Standard-Profil" (nur wenn ≥2 Profile)
- Job-Zeile zeigt grünes Profile-Tag (nur wenn relevant)

Neue Profile = neue YAML-Datei in mac-worker/profiles/ — kein Code-Deploy
nötig, der Mac-Worker liest sie beim Start ein.
2026-05-14 03:11:30 +00:00

85 lines
2.4 KiB
Python

"""Lädt Verarbeitungs-Profile aus mac-worker/profiles/*.yaml.
Ein Profil definiert:
- Prompts für Summary und Protocol
- DOCX-Layout (Meta-Zeilen + Sektionen mit Typ)
Sind keine Profile vorhanden, wirft get() FileNotFoundError beim ersten Zugriff.
"""
from __future__ import annotations
import logging
from dataclasses import dataclass, field
from pathlib import Path
import yaml
log = logging.getLogger("mac-worker.profiles")
PROFILES_DIR = Path(__file__).resolve().parent.parent.parent / "profiles"
DEFAULT_PROFILE_NAME = "meeting"
@dataclass
class Profile:
name: str
display_name: str
language: str = "de"
summary_prompt: str = ""
protocol_prompt: str = ""
docx: dict = field(default_factory=dict)
_cache: dict[str, Profile] = {}
def _load() -> dict[str, Profile]:
if _cache:
return _cache
if not PROFILES_DIR.exists():
log.warning("Profile-Verzeichnis %s existiert nicht", PROFILES_DIR)
return _cache
for f in sorted(PROFILES_DIR.glob("*.yaml")):
try:
data = yaml.safe_load(f.read_text(encoding="utf-8")) or {}
p = Profile(
name=data["name"],
display_name=data.get("display_name", data["name"]),
language=data.get("language", "de"),
summary_prompt=data.get("summary_prompt", ""),
protocol_prompt=data.get("protocol_prompt", ""),
docx=data.get("docx") or {},
)
_cache[p.name] = p
log.info("Profil geladen: %s (%s)", p.name, p.display_name)
except Exception as e: # noqa: BLE001
log.error("Profil %s konnte nicht geladen werden: %s", f.name, e)
return _cache
def list_all() -> list[dict]:
"""Zur Auslieferung über /api/profiles."""
return [
{"name": p.name, "display_name": p.display_name, "language": p.language}
for p in _load().values()
]
def get(name: str | None) -> Profile:
profiles = _load()
if not profiles:
raise FileNotFoundError(
f"Keine Profile gefunden in {PROFILES_DIR} — bitte mind. eine YAML-Datei anlegen."
)
if name and name in profiles:
return profiles[name]
if DEFAULT_PROFILE_NAME in profiles:
return profiles[DEFAULT_PROFILE_NAME]
return next(iter(profiles.values()))
def reload() -> None:
"""Cache leeren (z. B. für Tests oder Hot-Reload via SIGHUP)."""
_cache.clear()