wiki/concepts/Billing-Konzept.md
2026-03-21 01:34:59 +01:00

46 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
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

# 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
# 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

# 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

# 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=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

// 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. Preisgestaltung50% Aufschlag auf Provider-Kosten, direkt in AICore-Modellen definiert → Preise werden in CHF kalkuliert und gespeichert

  3. WährungAlles in CHF, keine Währungsumrechnung im System → Provider-Kosten werden intern auf CHF umgerechnet und mit 50% Aufschlag versehen

  4. Datenhaltung10 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

  • datamodelBilling.py (BillingAccount, BillingTransaction, BillingSettings, UsageStatistics)
  • interfaceDbBilling.py (CRUD-Operationen)
  • 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