wiki/z-archive/concepts/Billing-Konzept.md

1144 lines
46 KiB
Markdown

# Billing & Verrechnungssystem Konzept
## Übersicht
Dieses Dokument beschreibt das Konzept für ein Kosten- und Verrechnungssystem (Billing) für LLM-Nutzung in der PowerOn-Plattform. Das System ermöglicht die Abrechnung von AI-Services auf Mandanten- und Benutzerebene mit verschiedenen Abrechnungsmodellen.
## Grundprinzipien
### 1. Mandantenzentrierte Verrechnung
- **Alle Kosten** werden einem Mandanten zugeordnet
- Der **Mandant** ist die zentrale Abrechnungseinheit
- Benutzer können mehreren Mandanten angehören → Kosten werden pro Mandant getrennt erfasst
### 2. Kostenstellen-Hierarchie
```
Mandant (Kostenstelle)
├── Abrechnungsmodell (definiert durch SysAdmin)
├── Budget/Guthaben
├── Benutzer mit Zugriff
│ ├── User A → individuelle Nutzung
│ └── User B → individuelle Nutzung
└── Features mit LLM-Nutzung
├── Chat Playground
├── Chatbot (Feature-Instanzen)
└── Automation
```
### 3. LLM-Provider-Steuerung
- Benutzer können pro Workflow definieren, welche **AICore-Provider** verwendet werden
- Provider-Liste ist **dynamisch** basierend auf registrierten AICore-Plugins (Plug&Play)
- Zuordnung auf Level **AICore-Connector** (z.B. `anthropic`, `openai`, `perplexity`, `tavily`, `internal`, weitere zukünftig)
- Ermöglicht Kostenkontrolle und Compliance-Anforderungen
### 4. Transparente Kostenerfassung
- Jede AI-Operation wird mit Kosten erfasst (`priceCHF`)
- Echtzeit-Sichtbarkeit für Benutzer
- Monatliche Abrechnungsberichte
---
## Abrechnungsmodelle
### Übersicht der Modelle
| Modell | Beschreibung | Kostenstelle | Nutzung |
|--------|--------------|--------------|---------|
| `PREPAY_MANDATE` | Vorbezahltes Guthaben auf Mandantenebene | Mandant | Gemeinsames Budget für alle User |
| `PREPAY_USER` | Vorbezahltes Guthaben pro User | User (im Mandanten-Kontext) | Individuelles Budget |
| `UNLIMITED` | Keine Kostenlimitierung | - | Nur für interne Mandanten |
**Hinweis:** Das frühere Modell `CREDIT_POSTPAY` ist **nicht mehr** Teil des Produkts. Alte gespeicherte Werte werden beim Lesen auf `PREPAY_MANDATE` normalisiert (ohne DB-Migration).
### Modell: PREPAY_MANDATE
```
Mandant "SOHA Treuhand AG"
├── Abrechnungsmodell: PREPAY_MANDATE
├── Guthaben: 500.00 CHF
├── Warnschwelle: 50.00 CHF (10%)
└── Nutzer:
├── User A: verbraucht 120.50 CHF → Mandanten-Konto -120.50
└── User B: verbraucht 80.25 CHF → Mandanten-Konto -80.25
Restguthaben: 299.25 CHF
```
**Eigenschaften:**
- Mandant lädt Guthaben auf (via Admin oder Payment)
- Alle User des Mandanten teilen das Guthaben
- Bei Guthaben = 0 → AI-Features blockiert (mit Warnung)
- Warnschwelle konfigurierbar (Email-Benachrichtigung)
### Modell: PREPAY_USER
```
Mandant "ROOT" (Bootstrap-Mandant)
├── Abrechnungsmodell: PREPAY_USER
├── Startguthaben bei neuem User nur hier (Root): z. B. 5.00 CHF (defaultUserCredit)
└── Nutzer:
├── User A: Guthaben 10.00 CHF → verbraucht 3.50 → Rest 6.50 CHF
└── User B: Guthaben 25.00 CHF → verbraucht 12.00 → Rest 13.00 CHF
```
**Eigenschaften:**
- Jeder User hat eigenes Guthaben im Mandanten-Kontext
- **Automatisches** Startguthaben nur, wenn ein User dem **Root-Mandanten** neu zugeordnet wird; bei allen anderen Mandanten wird das Nutzerkonto mit 0 CHF angelegt (Aufladung z. B. per Admin/Stripe)
- User kann Guthaben aufladen
- Bei Guthaben = 0 → AI-Features blockiert für diesen User
### Modell: CREDIT_POSTPAY (entfernt)
Nicht mehr unterstützt. Siehe Hinweis in der Modell-Übersicht.
---
## Datenmodell
### Neue Tabellen
```python
# modules/datamodels/datamodelBilling.py (vereinfacht, Stand Implementierung)
class BillingAccount(BaseModel):
"""Abrechnungskonto pro Mandant oder User-Mandant-Kombination"""
id: str # UUID
mandateId: str # FK → Mandate
userId: Optional[str] # FK → User (nur bei PREPAY_USER)
accountType: str # MANDATE | USER
balance: float # Aktuelles Guthaben in CHF (Prepaid)
warningThreshold: float # Warnschwelle in CHF
lastWarningAt: Optional[datetime] # Letzte Warnung gesendet
enabled: bool # Account aktiv
class BillingTransaction(BaseModel):
"""Einzelne Transaktion (Aufladung, Verbrauch, Gutschrift)"""
id: str # UUID
accountId: str # FK → BillingAccount
transactionType: str # CREDIT | DEBIT | ADJUSTMENT
amount: float # Betrag in CHF (positiv)
description: str # Beschreibung
# Referenz zur Quelle
referenceType: Optional[str] # WORKFLOW | PAYMENT | ADMIN | SYSTEM
referenceId: Optional[str] # ID des referenzierten Objekts
# Kontext
workflowId: Optional[str] # Bei WORKFLOW-Verbrauch
featureInstanceId: Optional[str] # Bei Feature-Nutzung
aicoreProvider: Optional[str] # anthropic, openai, etc.
class BillingSettings(BaseModel):
"""Abrechnungseinstellungen pro Mandant"""
id: str # UUID
mandateId: str # FK → Mandate (UNIQUE)
billingModel: str # PREPAY_MANDATE | PREPAY_USER | UNLIMITED
# Konfiguration
defaultUserCredit: float # Startguthaben nur sinnvoll Root + PREPAY_USER (Bootstrap setzt z. B. 5 CHF); sonst typ. 0
warningThresholdPercent: float # Warnschwelle in % (z.B. 10)
# Benachrichtigungen
notifyEmails: List[str] # Email-Adressen für Monatsberichte
notifyOnWarning: bool # Email bei Unterschreitung Warnschwelle
class UsageStatistics(BaseModel):
"""Aggregierte Nutzungsstatistiken (für schnellen Abruf)"""
id: str # UUID
accountId: str # FK → BillingAccount
periodType: str # DAY | MONTH | YEAR
periodStart: date # Beginn der Periode
# Aggregierte Werte
totalCostCHF: float # Gesamtkosten in CHF
transactionCount: int # Anzahl Transaktionen
# Aufschlüsselung nach Provider
costByProvider: Dict[str, float] # {'anthropic': 12.50, 'openai': 8.30}
# Aufschlüsselung nach Feature
costByFeature: Dict[str, float] # {'playground': 15.00, 'automation': 5.80}
```
### Hinweis: Chat-Daten bleiben User-owned
**Wichtig:** Die Chat-Modelle (`ChatWorkflow`, `ChatStat`, `ChatMessage`, etc.) speichern **keine** `mandateId`. Sie sind "User-owned" und die Mandanten-Zuordnung erfolgt über RBAC-Filterung zur Laufzeit.
**Billing-Zuordnung:** Der Mandanten-Kontext wird **in der BillingTransaction** gespeichert, nicht im ChatWorkflow:
- Bei jedem AI-Call wird der `mandateId` aus dem Request-Context in der Transaktion gespeichert
- Die Verknüpfung Workflow → Mandant erfolgt über `BillingTransaction.workflowId`
- Statistiken werden aus den BillingTransactions aggregiert, nicht aus ChatStat
```python
# KEINE Änderung an ChatWorkflow erforderlich!
# ChatWorkflow bleibt "User-owned, no mandate context"
# Stattdessen: BillingTransaction speichert den Mandanten-Kontext
class BillingTransaction(BaseModel):
# ...
workflowId: Optional[str] # Referenz zum Workflow
featureInstanceId: Optional[str] # Feature-Instanz (z.B. chatplayground)
# mandateId kommt über BillingAccount
```
**Hinweis:** Die Provider-Steuerung erfolgt NICHT über Datenbank-Attribute, sondern über das RBAC-System (siehe Abschnitt "LLM-Provider als RBAC Resources").
### ER-Diagramm
```
┌─────────────────┐ ┌─────────────────────┐
│ Mandate │────<│ BillingSettings │
│ │ 1:1│ │
│ id │ │ mandateId │
│ name │ │ billingModel │
└────────┬────────┘ │ billingAddress │
│ └─────────────────────┘
│ 1:n
┌─────────────────────┐ ┌─────────────────────┐
│ BillingAccount │────<│ BillingTransaction │
│ │ 1:n │ │
│ mandateId │ │ accountId │
│ userId (optional) │ │ amount (CHF) │
│ balance (CHF) │ │ transactionType │
│ accountType │ │ workflowId │
└─────────────────────┘ └─────────────────────┘
│ 1:n RBAC Resources
▼ ┌─────────────────────┐
┌─────────────────────┐ │ resource.aicore.* │
│ UsageStatistics │ │ │
│ │ │ .anthropic │
│ accountId │ │ .openai │
│ periodType │ │ .perplexity │
│ totalCostCHF │ │ .tavily │
│ costByProvider │ │ .internal │
└─────────────────────┘ └─────────────────────┘
```
### LLM-Provider als RBAC Resources
Die Steuerung, welche LLM-Provider ein Benutzer verwenden darf, erfolgt über das RBAC-System:
**Resource-Namenskonvention:**
```
resource.aicore.{connectorType}
```
**Dynamische Registrierung:**
- Jedes AICore-Plugin registriert sich automatisch als RBAC-Resource
- Die Liste der verfügbaren Provider wird aus `ModelRegistry.getConnectors()` ermittelt
- Neue Plugins werden automatisch erkannt (Plug&Play)
**Beispiel aktuell registrierter Provider:**
- `resource.aicore.anthropic`
- `resource.aicore.openai`
- `resource.aicore.perplexity`
- `resource.aicore.tavily`
- `resource.aicore.internal`
- *(weitere zukünftig via Plug&Play)*
**Bootstrap-Berechtigungen (Mandate ROOT):**
- **SysAdmin:** Alle registrierten Provider (USE)
- **Admin:** Alle registrierten Provider (USE)
- **User:** Alle registrierten Provider (USE)
**Vorteile des RBAC-Ansatzes:**
- Zentrale Verwaltung über bestehende Rollen-Struktur
- Granulare Steuerung pro Mandant möglich
- Keine zusätzlichen Datenbank-Attribute erforderlich
- Konsistent mit bestehendem Berechtigungssystem
- Automatische Erweiterung bei neuen AICore-Plugins
---
## Backend-Integration
### 1. Billing Service
```python
# modules/services/serviceBilling/mainServiceBilling.py
class BillingService:
"""Zentrale Service-Klasse für Billing-Operationen"""
def __init__(self, currentUser: User, mandateId: str):
self.currentUser = currentUser
self.mandateId = mandateId
self._loadSettings()
def _loadSettings(self):
"""Lädt BillingSettings für den Mandanten"""
self.settings = getBillingSettings(self.mandateId)
def getAccount(self) -> BillingAccount:
"""Gibt das relevante Account zurück (Mandate oder User)"""
if self.settings.billingModel == 'PREPAY_USER':
return getOrCreateUserAccount(self.mandateId, self.currentUser.id)
else:
return getMandateAccount(self.mandateId)
def checkBalance(self, estimatedCost: float) -> BillingCheckResult:
"""Prüft ob genug Guthaben vorhanden ist"""
account = self.getAccount()
if self.settings.billingModel == 'UNLIMITED':
return BillingCheckResult(allowed=True)
if account.balance < estimatedCost:
return BillingCheckResult(
allowed=False,
reason='INSUFFICIENT_BALANCE',
currentBalance=account.balance,
requiredAmount=estimatedCost
)
return BillingCheckResult(allowed=True, currentBalance=account.balance)
def recordUsage(self, priceCHF: float, context: UsageContext) -> BillingTransaction:
"""Erfasst Nutzungskosten und erstellt Transaktion"""
account = self.getAccount()
transaction = BillingTransaction(
id=generateUuid(),
accountId=account.id,
transactionType='DEBIT',
amount=priceCHF,
description=f"AI Usage: {context.process}",
referenceType='WORKFLOW',
referenceId=context.workflowId,
workflowId=context.workflowId,
featureInstanceId=context.featureInstanceId,
aicoreProvider=context.provider
)
# Account-Balance aktualisieren (atomar in DB)
account.balance -= priceCHF
# Warnung prüfen
self._checkWarningThreshold(account)
# Statistiken aktualisieren
self._updateStatistics(transaction)
return transaction
def isProviderAllowed(self, provider: str) -> bool:
"""Prüft ob User Berechtigung für Provider hat via RBAC"""
resourceKey = f"resource.aicore.{provider}"
return self.rbacService.hasPermission(
user=self.currentUser,
mandateId=self.mandateId,
objectKey=resourceKey,
action='USE'
)
```
### 2. Integration in AICore
```python
# modules/aicore/aicoreModelSelector.py - Erweiterung
class ModelSelector:
def selectModel(
self,
operationType: str,
promptBytes: int,
processingMode: str = 'BASIC',
priority: str = 'BALANCED',
allowedProviders: Optional[List[str]] = None # NEU
) -> Optional[AiModel]:
"""
Wählt das beste Model basierend auf Kriterien.
NEU: Filtert nach erlaubten Providern.
"""
availableModels = self._getAvailableModels()
# NEU: Provider-Filter anwenden
if allowedProviders:
availableModels = [
m for m in availableModels
if m.connectorType in allowedProviders
]
# ... bestehende Selektionslogik ...
```
### 3. Integration in Workflow-Verarbeitung
```python
# modules/workflows/processing/workflowProcessor.py - Erweiterung
class WorkflowProcessor:
def __init__(self, ...):
# ... bestehende Initialisierung ...
# NEU: Billing Service initialisieren
self.billingService = BillingService(
currentUser=self.currentUser,
mandateId=self.mandateId
)
async def _processAiCall(self, ...):
"""Erweitert um Billing-Checks"""
# Provider-Check
if not self.billingService.isProviderAllowed(selectedModel.connectorType):
raise ProviderNotAllowedException(
f"Provider '{selectedModel.connectorType}' not allowed for this mandate"
)
# Balance-Check (mit Kostenschätzung)
estimatedCost = self._estimateCost(selectedModel, inputBytes)
balanceCheck = self.billingService.checkBalance(estimatedCost)
if not balanceCheck.allowed:
raise InsufficientBalanceException(
f"Insufficient balance. Current: {balanceCheck.currentBalance}, "
f"Required: {balanceCheck.requiredAmount}"
)
# AI-Aufruf durchführen
response = await self._callAi(...)
# NEU: Kosten erfassen
if response.priceCHF:
self.billingService.recordUsage(
priceCHF=response.priceCHF,
context=UsageContext(
workflowId=self.workflowId,
featureInstanceId=self.featureInstanceId,
provider=selectedModel.connectorType,
process=response.process
)
)
return response
```
### 4. Bootstrap: Root-Mandant Setup
```python
# modules/interfaces/interfaceBootstrap.py - Erweiterung
def _setupRootMandate():
"""Setup für Root-Mandant mit PREPAY_USER Modell"""
rootMandate = getOrCreateMandate(
name="ROOT",
description="System Root Mandate"
)
# Billing Settings für Root
billingSettings = BillingSettings(
id=generateUuid(),
mandateId=rootMandate.id,
billingModel='PREPAY_USER',
defaultUserCredit=5.0, # 5 CHF Startguthaben (siehe DEFAULT_USER_CREDIT_CHF im Code)
warningThresholdPercent=20,
notifyOnWarning=True
)
saveBillingSettings(billingSettings)
# RBAC: LLM-Provider Berechtigungen für alle Rollen
_setupAicoreRbacPermissions(rootMandate.id)
def _setupAicoreRbacPermissions(mandateId: str):
"""Erstellt RBAC-Berechtigungen für LLM-Provider (dynamisch)"""
# Provider dynamisch aus ModelRegistry laden (Plug&Play)
from modules.aicore.aicoreModelRegistry import modelRegistry
connectors = modelRegistry.discoverConnectors()
providers = [c.getConnectorType() for c in connectors]
roles = ['sysadmin', 'admin', 'user']
for role in roles:
for provider in providers:
createAccessRule(
mandateId=mandateId,
roleLabel=role,
context='RESOURCE',
item=f'resource.aicore.{provider}',
use=True
)
def _setupNewUserInRoot(user: User):
"""Erstellt Billing-Account für neuen User im Root-Mandanten"""
rootMandate = getRootMandate()
settings = getBillingSettings(rootMandate.id)
# User-Account erstellen
account = BillingAccount(
id=generateUuid(),
mandateId=rootMandate.id,
userId=user.id,
accountType='USER',
balance=settings.defaultUserCredit, # z. B. 5 CHF Default
warningThreshold=settings.defaultUserCredit * 0.2, # 20%
enabled=True
)
# Initiale Gutschrift-Transaktion
transaction = BillingTransaction(
id=generateUuid(),
accountId=account.id,
transactionType='CREDIT',
amount=settings.defaultUserCredit,
description='Initial credit for new user registration',
referenceType='SYSTEM'
)
saveAccount(account)
saveTransaction(transaction)
```
---
## API-Endpoints
### Billing Management (Admin)
```
# Mandanten-Billing-Einstellungen
GET /api/admin/billing/mandates/{mandateId}/settings
PUT /api/admin/billing/mandates/{mandateId}/settings
POST /api/admin/billing/mandates/{mandateId}/credit # Guthaben aufladen
# Transaktionen einsehen
GET /api/admin/billing/mandates/{mandateId}/transactions
GET /api/admin/billing/mandates/{mandateId}/accounts
```
### User-Billing (Self-Service)
```
# Eigenes Guthaben
GET /api/billing/balance # Aktueller Stand pro Mandant
GET /api/billing/balance/{mandateId} # Stand für spezifischen Mandanten
# Eigene Transaktionen
GET /api/billing/transactions # Alle eigenen Transaktionen
GET /api/billing/transactions/{mandateId} # Transaktionen pro Mandant
# Statistiken
GET /api/billing/statistics # Aggregierte Stats
GET /api/billing/statistics/{mandateId} # Stats pro Mandant
GET /api/billing/statistics/{mandateId}/chart # Chart-Daten (Tag/Monat/Jahr)
```
### Provider-Konfiguration
```
# Erlaubte Provider für Workflows
GET /api/billing/providers # Alle verfügbaren Provider
GET /api/billing/providers/{mandateId} # Erlaubte Provider für Mandant
# Workflow-spezifische Provider-Einstellung
PUT /api/chat/playground/{workflowId}/providers # Provider für Workflow setzen
PUT /api/automations/{automationId}/providers # Provider für Automation setzen
```
### Response-Beispiele
```json
// GET /api/billing/balance
{
"balances": [
{
"mandateId": "root-mandate-id",
"mandateName": "ROOT",
"billingModel": "PREPAY_USER",
"balance": 6.50,
"currency": "CHF",
"warningThreshold": 2.00,
"isWarning": false
},
{
"mandateId": "soha-mandate-id",
"mandateName": "SOHA Treuhand AG",
"billingModel": "PREPAY_MANDATE",
"balance": 299.25,
"currency": "CHF",
"warningThreshold": 50.00,
"isWarning": false
}
]
}
// GET /api/billing/statistics/root-mandate-id/chart?period=month&year=2026
{
"mandateId": "root-mandate-id",
"period": "month",
"year": 2026,
"currency": "CHF",
"data": [
{"label": "Jan", "totalCost": 12.50, "byProvider": {"anthropic": 10.00, "openai": 2.50}},
{"label": "Feb", "totalCost": 8.30, "byProvider": {"anthropic": 8.30}},
// ...
],
"totals": {
"totalCost": 45.80,
"byProvider": {"anthropic": 38.30, "openai": 7.50}
}
}
```
---
## UI-Seiten
**Billing wird als separater Navigation-Container gerendert:**
```
Static Block: BILLING
├── page.billing.dashboard → /billing
├── page.billing.transactions → /billing/transactions
├── page.billing.statistics → /billing/statistics/{mandateId}
└── page.billing.admin → /billing/admin (nur SysAdmin)
```
### 1. Billing Dashboard (User)
**Pfad:** `/billing`
**Funktionen:**
- Übersicht aller Mandanten mit Guthaben
- Restbudget für ROOT-Mandant prominent anzeigen
- Quick-Stats: Ausgaben heute/diese Woche/diesen Monat
```
┌─────────────────────────────────────────────────────────┐
│ MEIN GUTHABEN │
├─────────────────────────────────────────────────────────┤
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ ROOT │ │ Chart: Ausgaben pro Tag │ │
│ │ ────────────────│ │ ┌─┐ ┌─┐ ┌─┐ │ │
│ │ CHF 6.50 │ │ │█│ │█│ ┌─┐ │█│ ┌─┐ │ │
│ │ [████████░░] 65% │ │ │█│ │█│ │█│ │█│ │█│ ┌─┐ │ │
│ │ │ │ Mo Di Mi Do Fr Sa So │ │
│ │ Warnschwelle: 2 │ └──────────────────────────────┘ │
│ └──────────────────┘ │
│ │
│ MANDANTEN-ÜBERSICHT │
│ ┌──────────────────────────────────────────────────┐ │
│ │ SOHA Treuhand AG CHF 299.25 ████████████░░ │ │
│ │ Partner AG CHF 150.00 ██████████░░░░ │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 2. Detailansicht pro Mandant
**Pfad:** `/billing/statistics/{mandateId}`
**Funktionen:**
- Detaillierte Kostengrafik (Tag/Monat/Jahr umschaltbar)
- Aufschlüsselung nach Provider
- Aufschlüsselung nach Feature
- Transaktionshistorie
```
┌─────────────────────────────────────────────────────────┐
│ SOHA Treuhand AG - Kostenübersicht │
├─────────────────────────────────────────────────────────┤
│ Zeitraum: [Tag ▼] [Monat] [Jahr] Feb 2026 │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ [Bar-Chart: Kosten pro Tag im Februar] │ │
│ │ 25 ─┬─ (CHF) │ │
│ │ │ ██ │ │
│ │ 20 ─┤ ██ ██ │ │
│ │ │ ██ ██ ██ ██ │ │
│ │ 10 ─┤ ██ ██ ██ ██ ██ ██ │ │
│ │ │──██──██───██───██───██──██─────────────│ │
│ │ 1 5 10 15 20 25 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ AUFSCHLÜSSELUNG │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ Nach Provider │ │ Nach Feature │ │
│ │ Anthropic 70% │ │ Playground 60% │ │
│ │ OpenAI 25% │ │ Automation 30% │ │
│ │ Internal 5% │ │ Chatbot 10% │ │
│ └────────────────┘ └────────────────┘ │
│ │
│ LETZTE TRANSAKTIONEN │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 04.02.2026 14:32 AI Usage: Document Analysis │ │
│ │ anthropic -CHF 0.15 │ │
│ │ 04.02.2026 14:28 AI Usage: Chat Response │ │
│ │ anthropic -CHF 0.08 │ │
│ │ ... │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 3. Provider-Auswahl im Chat Playground
**Integration in:** `/workflows/playground`
**Funktionen:**
- Provider-Liste wird **dynamisch** aus registrierten AICore-Plugins geladen
- Zeigt nur Provider an, für die der User RBAC-Berechtigung hat
- Auswahl gilt für den nächsten AI-Call
- Preise werden aus den Plugin-Definitionen gelesen
```
┌─────────────────────────────────────────────────────────┐
│ Chat Playground │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────┐ │
│ │ EINSTELLUNGEN │ │
│ │ │ │
│ │ AI-Provider für nächsten Call: │ │
│ │ ○ Anthropic (Claude) ~CHF 0.020/1k tokens │ │
│ │ ● OpenAI (GPT-4) ~CHF 0.015/1k tokens │ │
│ │ ○ Perplexity (Web) ~CHF 0.008/1k tokens │ │
│ │ ○ Internal (Document) ~CHF 0.002/1k tokens │ │
│ │ │ │
│ │ Geschätzte Kosten: ~CHF 0.02 pro Nachricht │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ [Chat-Interface...] │
└─────────────────────────────────────────────────────────┘
```
### 4. Automation Editor (Modal mit Tabs)
**Integration in:** `/automations/{id}/edit`
Das Automation-Editor Modal wird in drei Tabs aufgeteilt:
**Tab-Struktur:**
1. **General** - Name, Beschreibung, Schedule, Status
2. **Modellierung** - Template, Placeholders, Preview
3. **LLM Modelle** - Provider-Auswahl (dynamisch aus AICore-Plugins, gefiltert nach RBAC)
```
┌─────────────────────────────────────────────────────────┐
│ Automation bearbeiten: "Täglicher Report" │
├─────────────────────────────────────────────────────────┤
│ [General] [Modellierung] [LLM Modelle] │
│ ───────────────────────────────────────────────────── │
│ TAB: LLM Modelle │
│ │
│ Verfügbare Provider (dynamisch, RBAC-gefiltert):
│ ○ Anthropic (Claude) CHF 0.020/1k tokens │
│ ● OpenAI (GPT-4) CHF 0.015/1k tokens │
│ ○ Perplexity (Web) CHF 0.008/1k tokens │
│ ○ Internal (Document) CHF 0.002/1k tokens │
│ │
│ Geschätzte Kosten pro Ausführung: ~CHF 0.05 │
│ Bei täglicher Ausführung: ~CHF 1.50/Monat │
│ │
│ [Speichern] [Abbrechen] │
└─────────────────────────────────────────────────────────┘
```
### 5. Admin: Billing-Verwaltung (nur SysAdmin)
**Pfad:** `/billing/admin`
**Funktionen:**
- Übersicht aller Mandanten mit Billing-Status
- Abrechnungsmodell ändern
- Guthaben manuell aufladen (Stripe-Vorbereitung für Zukunft)
- Email-Empfänger verwalten
```
┌─────────────────────────────────────────────────────────┐
│ BILLING ADMINISTRATION │
├─────────────────────────────────────────────────────────┤
│ MANDANTEN │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Mandant Modell Balance Status │ │
│ │ ──────────────────────────────────────────────── │ │
│ │ ROOT PREPAY_USER n/a ✓ │ │
│ │ SOHA Treuhand PREPAY_MANDATE CHF 299 ✓ │ │
│ │ Enterprise AG PREPAY_MANDATE CHF 1200 ✓ │ │
│ │ Test GmbH PREPAY_MANDATE CHF 5 ⚠ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ [+ Neuer Mandant] [Export Monatsreport] │
│ │
│ MANDANT BEARBEITEN: SOHA Treuhand AG │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Abrechnungsmodell: [PREPAY_MANDATE ▼] │ │
│ │ │ │
│ │ Warnschwelle: [10] % │ │
│ │ │ │
│ │ Benachrichtigungs-Emails: │ │
│ │ [admin@soha.ch, billing@soha.ch ] │ │
│ │ │ │
│ │ [Speichern] [Guthaben aufladen] │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
**Hinweis:** Provider-Berechtigungen werden über das RBAC-System verwaltet (Administration → Rollen).
---
## Email-Reports
### Monatsende-Report
**Trigger:** Automatisch am 1. jeden Monats (Cronjob)
**Empfänger:** `BillingSettings.notifyEmails` pro Mandant
**Template:**
```
Betreff: PowerOn Kostenreport Januar 2026 - SOHA Treuhand AG
Sehr geehrte Damen und Herren,
anbei erhalten Sie den monatlichen Kostenreport für Ihren Mandanten
"SOHA Treuhand AG" für den Zeitraum Januar 2026.
ZUSAMMENFASSUNG
───────────────────────────────────────────────────────
Gesamtkosten: CHF 156.80
Anzahl AI-Operationen: 423
Durchschnitt pro Operation: CHF 0.37
AUFSCHLÜSSELUNG NACH PROVIDER
───────────────────────────────────────────────────────
Anthropic (Claude) CHF 112.50 (72%)
OpenAI (GPT-4) CHF 38.30 (24%)
Internal CHF 6.00 (4%)
AUFSCHLÜSSELUNG NACH FEATURE
───────────────────────────────────────────────────────
Chat Playground CHF 95.00 (61%)
Automation CHF 45.80 (29%)
Chatbot (Feature) CHF 16.00 (10%)
TOP 5 NUTZER (nach Verbrauch)
───────────────────────────────────────────────────────
1. Max Mustermann CHF 48.50
2. Anna Schmidt CHF 35.20
3. Peter Weber CHF 28.90
4. Lisa Müller CHF 22.10
5. Thomas Meier CHF 12.30
KONTOSTAND
───────────────────────────────────────────────────────
Vorheriger Stand: CHF 456.05
Verbrauch: -CHF 156.80
───────────────────────────────────────────────────────
Aktueller Stand: CHF 299.25
Bei Fragen stehen wir Ihnen gerne zur Verfügung.
Freundliche Grüsse
PowerOn Platform
```
### Warnung bei niedriger Balance
**Trigger:** Balance unterschreitet `warningThreshold`
**Empfänger:** `BillingSettings.notifyEmails`
```
Betreff: ⚠️ PowerOn - Niedriges Guthaben für SOHA Treuhand AG
Ihr Guthaben für den Mandanten "SOHA Treuhand AG" hat die
Warnschwelle unterschritten.
Aktuelles Guthaben: CHF 45.00
Warnschwelle: CHF 50.00 (10%)
Bitte laden Sie Ihr Guthaben auf, um Unterbrechungen zu vermeiden.
[Guthaben aufladen →]
Freundliche Grüsse
PowerOn Platform
```
---
## Implementierungsschritte
### Phase 1: Datenmodell & Grundstruktur
1. **Neue Billing-Datenbank erstellen** (`poweron_billing`)
- `BillingAccount`, `BillingTransaction`, `BillingSettings`, `UsageStatistics` Tabellen
- Migration für bestehende Mandanten (Default: `UNLIMITED`)
2. **Keine Änderung an Chat-Modellen**
- Chat-Daten bleiben "User-owned" (kein mandateId in ChatWorkflow)
- Mandanten-Kontext wird in BillingTransaction gespeichert
3. **BillingService implementieren**
- Balance-Prüfung
- Transaktions-Erfassung mit mandateId aus Request-Context
- Provider-Validierung über RBAC
### Phase 2: Integration in Workflows
1. **Billing-Integration in WorkflowProcessor**
- `mandateId` und `featureInstanceId` aus Request-Context verwenden
- BillingTransaction bei jedem AI-Call erstellen (mit mandateId)
- Provider-Filter über RBAC bei Model-Selection
2. **Billing-Checks einbauen**
- Vor jedem AI-Call: Balance prüfen (BillingAccount des Mandanten)
- Nach jedem AI-Call: Kosten als BillingTransaction erfassen
3. **Error Handling**
- `InsufficientBalanceException`
- `ProviderNotAllowedException`
- User-freundliche Fehlermeldungen
### Phase 3: API-Endpoints
1. **User-Billing APIs**
- Balance-Abfrage
- Transaktions-Historie
- Statistiken mit Chart-Daten
2. **Admin-Billing APIs**
- Settings verwalten
- Guthaben aufladen
- Report-Export
### Phase 4: UI-Integration
1. **Billing Dashboard**
- Neue Seite: `/billing`
- Chart-Komponenten für Statistiken
2. **Provider-Auswahl**
- Chat Playground erweitern
- Automation-Editor erweitern
3. **Admin-Seiten**
- Billing-Verwaltung
- Mandanten-Konfiguration
### Phase 5: Bootstrap & Migration
1. **Root-Mandant Setup**
- `PREPAY_USER` Modell
- 5 CHF automatisches Startguthaben nur für neu im Root-Mandanten angelegte User (`DEFAULT_USER_CREDIT_CHF`)
- RBAC-Berechtigungen für alle LLM-Provider
2. **Bestehende User migrieren**
- Accounts erstellen
- Initiales Guthaben gutschreiben
3. **Bestehende Mandanten migrieren**
- Default-Settings erstellen
- `UNLIMITED` oder `PREPAY_MANDATE`
- RBAC-Berechtigungen für LLM-Provider erstellen
### Phase 6: Email-Reports
1. **Email-Templates erstellen**
- Monatsreport
- Balance-Warnung
2. **Scheduler einrichten**
- Monatsende-Cronjob
- Warnschwellen-Check
3. **Email-Service integrieren**
- Template-Rendering
- Email-Versand
---
## Entscheidungen
### Architektur-Entscheidungen
1. **Soll Chat Playground über Mandanten laufen?**
**Ja**, dies ist für konsistente Verrechnung notwendig. `ChatWorkflow.mandateId` wird aus dem Request-Context übernommen.
2. **Welche Services laufen noch nicht über Mandanten?**
`data.chat.*` und `data.files.*` sind aktuell `USER_OWNED`. Für Billing muss `ChatWorkflow` den Mandanten-Kontext speichern.
3. **Läuft Automation bereits über Mandanten?**
**Ja**, `AutomationDefinition.mandateId` existiert bereits.
4. **Provider-Steuerung**
→ Über RBAC-System als Resources (`resource.aicore.*`), NICHT als Datenbank-Attribute.
### Geschäftsentscheidungen
1. **Payment-Integration**
→ Stripe-Anbindung wird vorbereitet (zukünftig)
→ Aktuell: SysAdmin-Seite für manuelle Guthaben-Aufladung
2. **Preisgestaltung**
**50% Aufschlag** auf Provider-Kosten, direkt in AICore-Modellen definiert
→ Preise werden in CHF kalkuliert und gespeichert
3. **Währung**
**Alles in CHF**, keine Währungsumrechnung im System
→ Provider-Kosten werden intern auf CHF umgerechnet und mit 50% Aufschlag versehen
4. **Datenhaltung**
**10 Jahre** Aufbewahrungspflicht für Transaktionen
→ Automatische Archivierung/Löschung nach 10 Jahren
5. **GDPR/Datenschutz**
→ Transaktionsdaten nach 10 Jahren löschen
→ Gelöschte User: Nur ID-Referenz in Transaktionen (bereits anonym, da keine personenbezogenen Daten)
→ Daten im System belassen für Mandanten-Abrechnungen
### Preistabelle (50% Aufschlag) - Beispiele
Jedes AICore-Plugin definiert seine eigenen Preise via `calculatePriceCHF()`. Die Preise werden dynamisch aus den Plugins geladen.
| Provider | Provider-Kosten (ca.) | Interner Preis (CHF) |
|------------|----------------------|----------------------|
| Anthropic | $0.015/1k tokens | CHF 0.020/1k tokens |
| OpenAI | $0.010/1k tokens | CHF 0.015/1k tokens |
| Perplexity | $0.005/1k tokens | CHF 0.008/1k tokens |
| Tavily | $0.003/1k tokens | CHF 0.005/1k tokens |
| Internal | $0.001/1k tokens | CHF 0.002/1k tokens |
**Hinweis:** Preise werden direkt in den AICore-Plugin-Modellen definiert (`calculatePriceCHF`) und beinhalten bereits den 50% Aufschlag.
---
## Glossar
| Begriff | Beschreibung |
|---------|--------------|
| **AICore** | Plugin-System für LLM-Provider Integration |
| **Billing Account** | Konto für Guthaben/Saldo (pro Mandant oder User) |
| **parseBillingModelFromStoredValue** | Mappt unbekannte/legacy `billingModel`-Strings auf `PREPAY_MANDATE` |
| **Connector** | AICore-Plugin für einen spezifischen Provider |
| **Kostenstelle** | Mandant, dem Kosten zugeordnet werden |
| **Mandate** | Mandant/Tenant in der Multi-Tenant-Architektur |
| **Provider** | LLM-Anbieter (anthropic, openai, etc.) |
| **priceCHF** | Berechneter Preis einer AI-Operation in CHF (inkl. 50% Aufschlag) |
| **RBAC Resource** | Berechtigungs-Objekt für Provider-Zugriff (z.B. `resource.aicore.anthropic`) |
---
## Anhang: Wichtige Dateien
### Bestehend (zu erweitern)
```
gateway/modules/
├── aicore/
│ ├── aicoreModelSelector.py # Provider-Filter via RBAC
│ ├── aicorePluginAnthropic.py # Preise auf CHF mit 50% Aufschlag anpassen
│ ├── aicorePluginOpenai.py # Preise auf CHF mit 50% Aufschlag anpassen
│ ├── aicorePluginPerplexity.py # Preise auf CHF mit 50% Aufschlag anpassen
│ ├── aicorePluginTavily.py # Preise auf CHF mit 50% Aufschlag anpassen
│ └── aicoreBase.py # connectorType für Billing
├── datamodels/
│ └── datamodelUam.py # Mandate-Referenz (keine Änderung an Chat-Modellen!)
├── interfaces/
│ ├── interfaceBootstrap.py # Root-Mandant Billing Setup + RBAC für Provider
│ └── interfaceRbac.py # Resource-Berechtigungen für Provider
├── workflows/
│ └── processing/workflowProcessor.py # Balance-Checks + RBAC Provider-Check + BillingTransaction
└── features/
├── chatplayground/ # Billing-Integration (mandateId aus Context)
└── automation/ # Billing-Integration (mandateId bereits vorhanden)
```
### Neu zu erstellen
```
gateway/modules/
├── datamodels/
│ └── datamodelBilling.py # Neue Billing-Modelle
├── interfaces/
│ └── interfaceDbBilling.py # Billing CRUD-Operationen (poweron_billing DB)
├── services/
│ └── serviceBilling/
│ ├── mainServiceBilling.py # Zentrale Billing-Logik
│ └── subBillingReports.py # Report-Generierung
└── routes/
└── routeBilling.py # Billing API-Endpoints
frontend_nyla/src/
├── pages/
│ └── billing/ # Billing UI-Seiten
└── components/
└── billing/ # Billing-Komponenten (Charts, etc.)
```
---
## Implementierungs-Checkliste
### Vor der Implementierung zu klären
- [ ] **AICore Preise:** Aktuelle Preise sind in USD definiert. Müssen auf CHF umgestellt werden mit 50% Aufschlag.
- [ ] **Datenbank:** Neue `poweron_billing` Datenbank oder in `poweron_app` integrieren?
- [ ] **Email-Service:** Welcher Email-Provider wird für Billing-Benachrichtigungen verwendet?
### Phase 1: Backend Grundstruktur
- [x] `datamodelBilling.py` (BillingAccount, BillingTransaction, BillingSettings, UsageStatistics)
- [x] `interfaceDbBilling.py` (CRUD-Operationen)
- [x] Kein separates Migrations-Script nötig (Legacy-Werte werden beim Lesen normalisiert)
- [ ] AICore-Plugins: Preise von USD auf CHF umstellen (`calculatepriceCHF` mit echten CHF-Werten)
### Phase 2: Service-Layer
- [ ] `mainServiceBilling.py` implementieren (Balance-Check, Transaction-Recording, Provider-Check via RBAC)
- [ ] RBAC-Integration: `resource.aicore.*` Resources registrieren
- [ ] Bootstrap: Billing-Settings für ROOT-Mandant, RBAC-Berechtigungen für Provider
### Phase 3: Workflow-Integration
- [ ] `workflowProcessor.py`: BillingService integrieren
- [ ] Vor AI-Call: Balance prüfen, Provider-Berechtigung prüfen
- [ ] Nach AI-Call: BillingTransaction erstellen (mit mandateId aus Request-Context)
- [ ] Exception-Handling: InsufficientBalanceException, ProviderNotAllowedException
### Phase 4: API-Endpoints
- [ ] `routeBilling.py` erstellen
- [ ] User-Endpoints: Balance, Transactions, Statistics
- [ ] Admin-Endpoints: Settings, Credit-Aufladung, Mandanten-Übersicht
### Phase 5: Frontend (Nyla)
- [ ] Billing-Container in Navigation integrieren
- [ ] Dashboard-Seite: Guthaben-Übersicht, Charts
- [ ] Transaktions-Seite: Historie mit Filterung
- [ ] Admin-Seite: Mandanten-Verwaltung, Guthaben-Aufladung
- [ ] Chat Playground: Provider-Auswahl UI
- [ ] Automation Editor: Tab "LLM Modelle"
### Phase 6: Reports & Notifications
- [ ] Email-Templates erstellen (Monatsreport, Balance-Warnung)
- [ ] Scheduler für Monatsreport (1. jeden Monats)
- [ ] Balance-Warnung bei Unterschreitung der Schwelle
### Qualitätssicherung
- [ ] Unit-Tests für BillingService
- [ ] Integration-Tests für Workflow mit Billing
- [ ] E2E-Tests für Billing-UI
- [ ] Datenmigration für bestehende Mandanten testen