# 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 ```python # 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 `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: 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 ```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=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 ```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) - 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 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 - 10 CHF Startguthaben für neue User - 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) | | **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_billing` Datenbank oder in `poweron_app` integrieren? - [ ] **Email-Service:** Welcher Email-Provider wird für Billing-Benachrichtigungen verwendet? ### Phase 1: Backend Grundstruktur - [ ] `datamodelBilling.py` erstellen (BillingAccount, BillingTransaction, BillingSettings, UsageStatistics, BillingAddress) - [ ] `interfaceDbBilling.py` erstellen (CRUD-Operationen) - [ ] Datenbank-Migrations-Script erstellen - [ ] 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