47 KiB
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 |
CREDIT_POSTPAY |
Nutzung auf Kredit mit Nachzahlung | Mandant (Rechnungsadresse erforderlich) | Monatsrechnung |
UNLIMITED |
Keine Kostenlimitierung | - | Nur für interne Mandanten |
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 für neue User: 10.00 CHF
└── 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
- Neuer User erhält Startguthaben (konfigurierbar)
- User kann Guthaben aufladen
- Bei Guthaben = 0 → AI-Features blockiert für diesen User
Modell: CREDIT_POSTPAY
Mandant "Enterprise AG"
├── Abrechnungsmodell: CREDIT_POSTPAY
├── Rechnungsadresse: Enterprise AG, Musterstrasse 1, 8000 Zürich
├── Kreditlimit: 2000.00 CHF (optional)
├── Aktueller Saldo: -450.00 CHF (Schulden)
└── Monatsende: Rechnung über 450.00 CHF
Eigenschaften:
- Nutzung ohne Vorauszahlung
- Monatliche Abrechnung
- Optionales Kreditlimit
- Für verifizierte Geschäftskunden
Datenmodell
Neue Tabellen
# modules/datamodels/datamodelBilling.py
class BillingAddress(BaseModel):
"""Rechnungsadresse für CREDIT_POSTPAY Mandanten"""
company: str # Firmenname
street: str # Strasse und Hausnummer
zip: str # PLZ
city: str # Ort
country: str # Land (Default: "CH")
vatNumber: Optional[str] # MwSt-Nummer (optional)
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/Saldo in CHF
creditLimit: Optional[float] # Kreditlimit in CHF (nur CREDIT_POSTPAY)
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 | CREDIT_POSTPAY | UNLIMITED
# Konfiguration
defaultUserCredit: float # Startguthaben in CHF für neue User (bei PREPAY_USER)
warningThresholdPercent: float # Warnschwelle in % (z.B. 10)
blockOnZeroBalance: bool # AI blockieren bei Guthaben = 0
# Rechnungsadresse (erforderlich für CREDIT_POSTPAY)
billingAddress: Optional[BillingAddress]
# 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
mandateIdaus 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
# 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.anthropicresource.aicore.openairesource.aicore.perplexityresource.aicore.tavilyresource.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
# 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:
if self.settings.blockOnZeroBalance:
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
# 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
# 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
# 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=10.0, # 10 CHF Startguthaben
warningThresholdPercent=20,
blockOnZeroBalance=True,
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, # 10 CHF
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
// 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:
- General - Name, Beschreibung, Schedule, Status
- Modellierung - Template, Placeholders, Preview
- 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)
- Rechnungsadresse verwalten (für CREDIT_POSTPAY)
- Email-Empfänger verwalten
┌─────────────────────────────────────────────────────────┐
│ BILLING ADMINISTRATION │
├─────────────────────────────────────────────────────────┤
│ MANDANTEN │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Mandant Modell Balance Status │ │
│ │ ──────────────────────────────────────────────── │ │
│ │ ROOT PREPAY_USER n/a ✓ │ │
│ │ SOHA Treuhand PREPAY_MANDATE CHF 299 ✓ │ │
│ │ Enterprise AG CREDIT_POSTPAY CHF -450 ✓ │ │
│ │ Test GmbH PREPAY_MANDATE CHF 5 ⚠ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ [+ Neuer Mandant] [Export Monatsreport] │
│ │
│ MANDANT BEARBEITEN: SOHA Treuhand AG │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Abrechnungsmodell: [PREPAY_MANDATE ▼] │ │
│ │ │ │
│ │ Warnschwelle: [10] % │ │
│ │ Blockieren bei 0: [✓] │ │
│ │ │ │
│ │ Rechnungsadresse (für CREDIT_POSTPAY): │ │
│ │ [Firma: ________________] [Strasse: _________] │ │
│ │ [PLZ: ____] [Ort: _______] [MwSt-Nr: ________] │ │
│ │ │ │
│ │ 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
-
Neue Billing-Datenbank erstellen (
poweron_billing)BillingAccount,BillingTransaction,BillingSettings,UsageStatisticsTabellen- Migration für bestehende Mandanten (Default:
UNLIMITED)
-
Keine Änderung an Chat-Modellen
- Chat-Daten bleiben "User-owned" (kein mandateId in ChatWorkflow)
- Mandanten-Kontext wird in BillingTransaction gespeichert
-
BillingService implementieren
- Balance-Prüfung
- Transaktions-Erfassung mit mandateId aus Request-Context
- Provider-Validierung über RBAC
Phase 2: Integration in Workflows
-
Billing-Integration in WorkflowProcessor
mandateIdundfeatureInstanceIdaus Request-Context verwenden- BillingTransaction bei jedem AI-Call erstellen (mit mandateId)
- Provider-Filter über RBAC bei Model-Selection
-
Billing-Checks einbauen
- Vor jedem AI-Call: Balance prüfen (BillingAccount des Mandanten)
- Nach jedem AI-Call: Kosten als BillingTransaction erfassen
-
Error Handling
InsufficientBalanceExceptionProviderNotAllowedException- User-freundliche Fehlermeldungen
Phase 3: API-Endpoints
-
User-Billing APIs
- Balance-Abfrage
- Transaktions-Historie
- Statistiken mit Chart-Daten
-
Admin-Billing APIs
- Settings verwalten
- Guthaben aufladen
- Report-Export
Phase 4: UI-Integration
-
Billing Dashboard
- Neue Seite:
/billing - Chart-Komponenten für Statistiken
- Neue Seite:
-
Provider-Auswahl
- Chat Playground erweitern
- Automation-Editor erweitern
-
Admin-Seiten
- Billing-Verwaltung
- Mandanten-Konfiguration
Phase 5: Bootstrap & Migration
-
Root-Mandant Setup
PREPAY_USERModell- 10 CHF Startguthaben für neue User
- RBAC-Berechtigungen für alle LLM-Provider
-
Bestehende User migrieren
- Accounts erstellen
- Initiales Guthaben gutschreiben
-
Bestehende Mandanten migrieren
- Default-Settings erstellen
UNLIMITEDoderPREPAY_MANDATE- RBAC-Berechtigungen für LLM-Provider erstellen
Phase 6: Email-Reports
-
Email-Templates erstellen
- Monatsreport
- Balance-Warnung
-
Scheduler einrichten
- Monatsende-Cronjob
- Warnschwellen-Check
-
Email-Service integrieren
- Template-Rendering
- Email-Versand
Entscheidungen
Architektur-Entscheidungen
-
Soll Chat Playground über Mandanten laufen? → Ja, dies ist für konsistente Verrechnung notwendig.
ChatWorkflow.mandateIdwird aus dem Request-Context übernommen. -
Welche Services laufen noch nicht über Mandanten? →
data.chat.*unddata.files.*sind aktuellUSER_OWNED. Für Billing mussChatWorkflowden Mandanten-Kontext speichern. -
Läuft Automation bereits über Mandanten? → Ja,
AutomationDefinition.mandateIdexistiert bereits. -
Provider-Steuerung → Über RBAC-System als Resources (
resource.aicore.*), NICHT als Datenbank-Attribute.
Geschäftsentscheidungen
-
Payment-Integration → Stripe-Anbindung wird vorbereitet (zukünftig) → Aktuell: SysAdmin-Seite für manuelle Guthaben-Aufladung
-
Preisgestaltung → 50% Aufschlag auf Provider-Kosten, direkt in AICore-Modellen definiert → Preise werden in CHF kalkuliert und gespeichert
-
Währung → Alles in CHF, keine Währungsumrechnung im System → Provider-Kosten werden intern auf CHF umgerechnet und mit 50% Aufschlag versehen
-
Datenhaltung → 10 Jahre Aufbewahrungspflicht für Transaktionen → Automatische Archivierung/Löschung nach 10 Jahren
-
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) |
| Billing Address | Rechnungsadresse, erforderlich für CREDIT_POSTPAY |
| 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_billingDatenbank oder inpoweron_appintegrieren? - Email-Service: Welcher Email-Provider wird für Billing-Benachrichtigungen verwendet?
Phase 1: Backend Grundstruktur
datamodelBilling.pyerstellen (BillingAccount, BillingTransaction, BillingSettings, UsageStatistics, BillingAddress)interfaceDbBilling.pyerstellen (CRUD-Operationen)- Datenbank-Migrations-Script erstellen
- AICore-Plugins: Preise von USD auf CHF umstellen (
calculatepriceCHFmit echten CHF-Werten)
Phase 2: Service-Layer
mainServiceBilling.pyimplementieren (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.pyerstellen- 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