Block A: License-Check + Credits-Tracking fuer Enhance und Chat

- Neuer Helper charge_usage_to_tenant() in services/license_service.py:
  UPSERT in token_usage_monthly und Credits-Abzug aus licenses.credits_used.
  Wiederverwendbar fuer alle Claude-Call-Verursacher.
- Orchestrator: Inline-Buchungslogik (35 Zeilen) durch Helper-Aufruf ersetzt.
- routers/incidents.py POST /enhance-description: require_writable_license
  statt get_current_user, db_dependency hinzugefuegt, Credits-Buchung mit
  source="enhance" nach jedem Claude-Call.
- routers/chat.py POST /: analog require_writable_license + Credits-Buchung
  mit source="chat". _call_claude_chat() gibt jetzt zusaetzlich ClaudeUsage
  zurueck.

Abgelaufene/gesperrte Lizenzen koennen damit keine Haiku-Calls mehr ausloesen,
und alle Kosten werden konsistent auf Tenant-Ebene verbucht.
Dieser Commit ist enthalten in:
claude-dev
2026-04-23 17:49:32 +00:00
Ursprung c8a8e10020
Commit e8ac0d0c50
4 geänderte Dateien mit 119 neuen und 40 gelöschten Zeilen

Datei anzeigen

@@ -1528,38 +1528,9 @@ class AgentOrchestrator:
# Credits-Tracking: Monatliche Aggregation + Credits abziehen
if tenant_id and usage_acc.total_cost_usd > 0:
year_month = datetime.now(TIMEZONE).strftime('%Y-%m')
await db.execute("""
INSERT INTO token_usage_monthly
(organization_id, year_month, source, input_tokens, output_tokens,
cache_creation_tokens, cache_read_tokens, total_cost_usd, api_calls, refresh_count)
VALUES (?, ?, 'monitor', ?, ?, ?, ?, ?, ?, 1)
ON CONFLICT(organization_id, year_month, source) DO UPDATE SET
input_tokens = input_tokens + excluded.input_tokens,
output_tokens = output_tokens + excluded.output_tokens,
cache_creation_tokens = cache_creation_tokens + excluded.cache_creation_tokens,
cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
total_cost_usd = total_cost_usd + excluded.total_cost_usd,
api_calls = api_calls + excluded.api_calls,
refresh_count = refresh_count + 1,
updated_at = CURRENT_TIMESTAMP
""", (tenant_id, year_month,
usage_acc.input_tokens, usage_acc.output_tokens,
usage_acc.cache_creation_tokens, usage_acc.cache_read_tokens,
round(usage_acc.total_cost_usd, 7), usage_acc.call_count))
# Credits auf Lizenz abziehen
lic_cursor = await db.execute(
"SELECT cost_per_credit FROM licenses WHERE organization_id = ? AND status = 'active' ORDER BY id DESC LIMIT 1",
(tenant_id,))
lic = await lic_cursor.fetchone()
if lic and lic["cost_per_credit"] and lic["cost_per_credit"] > 0:
credits_consumed = usage_acc.total_cost_usd / lic["cost_per_credit"]
await db.execute(
"UPDATE licenses SET credits_used = COALESCE(credits_used, 0) + ? WHERE organization_id = ? AND status = 'active'",
(round(credits_consumed, 2), tenant_id))
from services.license_service import charge_usage_to_tenant
await charge_usage_to_tenant(db, tenant_id, usage_acc, source="monitor")
await db.commit()
logger.info(f"Credits: {round(credits_consumed, 1) if lic and lic['cost_per_credit'] else 0} abgezogen für Tenant {tenant_id}")
# Quellen-Discovery im Background starten
if unique_results: