my_voice_assistant_v3_jamulix/app/quota.py
Dieter Schlüter 5d022aaf63 feat(admin): Phase 3 — Laufzeit-Konfiguration ohne Server-Neustart
Neue Tabelle config_overrides in SQLite; RuntimeSettings-Wrapper liest
überschreibbare Felder mit 30s TTL-Cache aus der DB und fällt auf
.env-Werte zurück. dependencies.py und quota.py nutzen runtime_settings
als Default statt des statischen Settings-Singletons.

16 Felder überschreibbar: STT/LLM/TTS-Provider, LLM-Modelle, Stimmen,
Systemprompt, Temperatur, Tageskontingent, Normalisierung u.a.

Backend: GET/PUT/DELETE /api/admin/config/{key}
Admin-UI: neuer Tab "⚙ Einstellungen" mit Inline-Edit und Reset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 01:39:36 +02:00

42 lines
1.3 KiB
Python

"""Pro-Nutzer-Tageskontingent (Kostenkontrolle).
Limit aus Settings (`daily_request_limit`), pro Nutzer ueber `prefs.daily_request_limit`
ueberschreibbar. 0 bedeutet unbegrenzt.
"""
from app.metrics import metrics
class QuotaExceededError(Exception):
def __init__(self, limit: int, count: int):
self.limit = limit
self.count = count
super().__init__(f"Daily request limit reached ({count}/{limit})")
def effective_limit(user, cfg=None) -> int:
if cfg is None:
from app.runtime_config import runtime_settings
cfg = runtime_settings
pref = user.prefs.get("daily_request_limit") if user and user.prefs else None
if pref is not None:
try:
return int(pref)
except (TypeError, ValueError):
pass
return cfg.daily_request_limit
def enforce_quota(user, store, cfg=None) -> None:
"""Wirft QuotaExceededError, wenn das Tageslimit erreicht ist."""
limit = effective_limit(user, cfg)
if limit and limit > 0:
count = store.get_request_count(user.id)
if count >= limit:
metrics.inc("quota_exceeded_total")
raise QuotaExceededError(limit, count)
def record_usage(user, store, units: int = 0) -> None:
store.add_usage(user.id, units)
metrics.inc("turns_total")