From 31e823898032b9235e781b060577906b5a26d037 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Mon, 23 Mar 2026 22:11:22 +0100 Subject: [PATCH] assessment and unified-data-bar --- ...ate-Onboarding-und-Store-Konzept-REVIEW.md | 225 +++++++ ...ti-Mandate-Onboarding-und-Store-Konzept.md | 600 ++++++++++++++++++ market/langdock-markteinschaetzung-poweron.md | 227 +++++++ .../langdock-markteinschaetzung-poweron.pdf | Bin 0 -> 42613 bytes 4 files changed, 1052 insertions(+) create mode 100644 concepts/Multi-Mandate-Onboarding-und-Store-Konzept-REVIEW.md create mode 100644 concepts/Multi-Mandate-Onboarding-und-Store-Konzept.md create mode 100644 market/langdock-markteinschaetzung-poweron.md create mode 100644 market/langdock-markteinschaetzung-poweron.pdf diff --git a/concepts/Multi-Mandate-Onboarding-und-Store-Konzept-REVIEW.md b/concepts/Multi-Mandate-Onboarding-und-Store-Konzept-REVIEW.md new file mode 100644 index 0000000..d71bb69 --- /dev/null +++ b/concepts/Multi-Mandate-Onboarding-und-Store-Konzept-REVIEW.md @@ -0,0 +1,225 @@ +# Architektur-Review: Multi-Mandate Onboarding und Store-Konzept + +**Gegenstand:** [Multi-Mandate-Onboarding-und-Store-Konzept.md](./Multi-Mandate-Onboarding-und-Store-Konzept.md) v3, 2026-03-23 + +**Zweck:** Kritischer Review der **Zielarchitektur** — Idee, Konzept, Modelle, Datenflüsse und Konsistenz. Keine Bewertung von Migrations- oder Implementierungsdetails. + +--- + +## 1. Gesamtbewertung + +Das Konzept definiert eine kohärente Zielarchitektur mit sechs Entscheidungen, die sich gegenseitig stützen: Mandate-per-Registration schafft die Tenant-Grenze, der Unified Data Layer eliminiert Datensilos, die UDB macht den Data Layer navigierbar, die Neutralisierung sichert den Datenfluss ab, und das RAG mit Scope-Tagging bildet den Plattformkern. + +**Stärken:** + +- Die Architektur löst ein reales, messbares Problem (Shared Root, Datensilos, fehlender Upgrade-Pfad). +- Mandate-per-Registration mit explizitem Store-Kontext ist sauber durchdacht und vermeidet implizite Logik. +- Unified Data Layer mit RAG-Scopes ist das richtige Abstraktionsniveau — weder zu granular noch zu grob. +- Die UDB als featureübergreifende Komponente mit Chat-Baum, Drag-and-Drop-Kontext und Inline-Tagging ist ein klarer Differenzierungsfaktor, den kein Wettbewerber in dieser Form bietet. +- Neutralisierung mit Fail-Safe-Regel ("kein Fallback auf Originaldaten") ist die einzig vertretbare Design-Entscheidung für regulierte Branchen. +- Operation Type statt "spezielle LLMs" nutzt das bestehende Modellsystem sauber. + +--- + +## 2. Architektur: Mandate-per-Registration + +### 2.1 Stimmigkeit des Mandanten-Modells + +Die Dreiteilung `system` / `personal` / `company` ist klar und deckt die Kundenprofile ab. Der Upgrade-Pfad Personal → Company (Team einladen, Label ändern) ist elegant, weil er kein neues Benutzerkonto erfordert. + +**Offener Punkt:** Ist `mandateType` mutabel (Personal → Company bei Einladung) oder immutabel? Wenn mutabel: Es sollte keine Geschäftslogik hart an `personal` vs. `company` gebunden sein. Wenn immutabel: Der "Upgrade" ist fachlich unklar. Empfehlung: Mutabel, rein informativ — keine Feature-Gates auf Basis von `mandateType`. + +### 2.2 Redundanz `isSystem` vs. `mandateType=system` + +Beide drücken dasselbe aus. Empfehlung: Eines der beiden Felder eliminieren oder klar trennen — `isSystem` als operativer Delete-Schutz, `mandateType` als fachliche Semantik. + +### 2.3 Zwei Registrierungs-Flows: Vollständigkeit + +Die zwei Flows (Personal/Trial und Company/Standard) decken die Selbstregistrierung ab. Nicht behandelt: + +- **OAuth-Registrierung** (Google, MSAL): Kein Formular mit "Personal vs. Company". Lösung: Onboarding-Wizard nach erstem OAuth-Login. +- **Invitation-Flow**: User registriert sich via Einladung. Bekommt er ein eigenes Mandate UND Zugang zum Einladungs-Mandate? Das Konzept sagt ja — sinnvoll, da der User auch ausserhalb des einladenden Mandanten arbeiten können soll. + +### 2.4 Subscription-Timing + +Trial-Start ab Registrierung oder ab erstem Login? Bei Magic-Link-Flow vergehen Stunden. Empfehlung: `PENDING`-Status bei Registrierung, Aktivierung bei erstem Login. + +--- + +## 3. Feature Store: Expliziter Kontext und Orphan Control + +### 3.1 Expliziter Mandanten-Kontext + +Die Entscheidung "nie implizit, immer explizites `mandateId`" ist architektonisch sauber und eliminiert eine ganze Klasse von Mehrdeutigkeits-Bugs. Für Solo-Mandant-User (ein Mandat) ist die UX trivial; für Multi-Mandat-User ist die explizite Auswahl notwendig und richtig. + +### 3.2 Orphan Control: Architektonische Bewertung + +Das Prinzip "letzter User deaktiviert → Instanz wird gelöscht" ist konsequent und verhindert Kapazitäts-Leaks. Technisch muss die Prüfung atomar sein (Race Condition bei gleichzeitiger Deaktivierung), aber das ist ein Implementierungsdetail, kein Architekturproblem. + +### 3.3 Store-Aktivierung bei existierender Instanz + +Wenn zwei Admins desselben Mandanten dasselbe Feature aktivieren — was passiert? Das Konzept muss klären: Maximal **eine Instanz pro Feature pro Mandat** via Store (dann: zweiter Admin bekommt nur FeatureAccess, keine neue Instanz) oder mehrere Instanzen erlaubt (dann: andere UX-Logik). + +### 3.4 Store-UX für Nicht-Admins + +Ein eingeladener User ist kein Admin und kann nichts aktivieren. Sieht er den Store? Empfehlung: Store als read-only Feature-Katalog zeigen oder via Navigation-RBAC ausblenden. + +--- + +## 4. Unified Data Layer und RAG + +### 4.1 Architektonische Schlüssigkeit + +Die Entscheidung, Datensilos aufzulösen (CommCoach-Voice parallel zu Workspace, doppelte Sprachdefinitionen), ist richtig und überfällig. Der Unified Data Layer als mandantenweite Datenschicht mit Features als "Workflow-Oberflächen" ist das korrekte Zielbild. + +Die Beibehaltung von `featureInstanceId` und `_createdBy` als Herkunftsreferenzen (Quellenschutz) bei gleichzeitiger mandantenweiter Sichtbarkeit ist eine elegante Lösung: kein Breaking Change auf Datenebene, aber neue Zugriffsmuster via Scope. + +### 4.2 RAG-Scopes: Konzeptionelle Bewertung + +Die vier Scopes (`personal` / `featureInstance` / `mandate` / `global`) bilden eine vollständige Hierarchie: + +``` +global ⊃ mandate ⊃ featureInstance ⊃ personal +``` + +Dies ist sauber und erlaubt feingranulare Steuerung. Jeder Scope ist klar abgegrenzt, es gibt keine Überlappung. + +**Kritischer Punkt:** Der Scope `global` ist mächtig — eine falsch getaggte Datenquelle wäre plattformweit sichtbar. Das Konzept adressiert dies nicht explizit. Empfehlung: `scope=global` nur durch sysadmin setzbar, RBAC-geschützt. + +### 4.3 Datenquellen-Tagging: UX-Bewertung + +Das Inline-Tagging in der UDB (Scope-Symbol + Neutralisierungs-Symbol neben jeder Datenquelle) ist elegant gelöst. Kein Dialog, kein Workflow-Bruch. Der Unterschied zwischen "Browse Sources" (Katalog, keine Icons) und "Active Sources" (eingebunden, mit Icons) ist logisch und intuitiv. + +**Default `personal` + `neutralize=false`** ist der richtige Default: konservativ für Sichtbarkeit (nur der User), offen für Neutralisierung (User entscheidet bewusst). Der User muss aktiv handeln, um Daten breiter zu teilen — Privacy-by-Default. + +### 4.4 Fehlendes konkretes Datenmodell + +Das Konzept beschreibt den Unified Data Layer fachlich, aber nicht als Datenmodell. Offene Fragen: + +- Neue Entität `DataSource` mit `scope`, `neutralize`, `featureInstanceId`, `createdBy`? +- Oder Erweiterung bestehender Modelle um `scope` und `neutralize`? +- Wo lebt der RAG-Index physisch? Eigene DB? pgvector in `poweron_app`? +- Scope-Metadaten im Vektorspeicher: Wie effizient ist die Union-Query über vier Scopes? + +Empfehlung: Separates Konzeptdokument für Phase 2 mit konkretem Datenmodell. + +### 4.5 Store vs. Unified Data Layer: Semantische Verschiebung über die Phasen + +In Phase 1 erstellt der Store eine `FeatureInstance` (Instanz = Datengrenze). In Phase 3 sind Features reine Workflow-Oberflächen auf dem Unified Layer — was "erstellt" der Store dann? Die Instanz wird zum **UI-Scope** statt zum **Daten-Scope**. Das ist ein fundamentaler Bedeutungswandel von `FeatureInstance`, der im Phasenplan klarer werden sollte. + +--- + +## 5. Unified Data Bar (UDB) + +### 5.1 Architektonische Bewertung + +Die UDB als plattformweite, wiederverwendbare Komponente (extrahiert aus dem Workspace) ist die richtige Entscheidung. Sie löst drei Probleme gleichzeitig: + +1. **Datennavigation:** Ein Ort für alle Daten, Chats und Quellen. +2. **Cross-Feature-Kontext:** Drag-and-Drop von Chats/Files/Sources in Prompts ermöglicht featureübergreifende Analysen. +3. **Konsistenz:** Alle Features haben dieselbe Datensidebar, keine Duplizierung. + +### 5.2 Chat-Baum: Bewertung + +Die hierarchische Chat-Struktur (Feature-Instanzen als Top-Level, Chats darunter, feature-spezifische Substrukturen wie CommCoach → Coaching-Modul → Sessions) ist ein starkes Konzept. Es gibt dem User eine Übersicht, die heute nirgends existiert — weder in ChatGPT (flache Ordner), Langdock (flache Projects), noch in Copilot (pro-App-Silos). + +**Kritischer Punkt:** Bei vielen Feature-Instanzen und Hunderten von Chats kann der Baum unübersichtlich werden. Empfehlung: Suchfunktion im Chats-Tab und optionaler Flat-Mode (chronologische Liste ohne Hierarchie) als Alternative. + +### 5.3 Drag-and-Drop als Kontext-Steuerung + +Das Prinzip "jedes UDB-Element kann in den Prompt gezogen werden" ist mächtig und intuitiv. Die Use Cases (Cross-Feature-Analyse, Reporting über Instanzen hinweg, Coaching-Review) demonstrieren den Mehrwert klar. + +**Offener Punkt:** Was passiert mit dem Neutralisierungs-Flag, wenn ein neutralisierter Chat in einen Prompt gezogen wird? Wird der neutralisierte Text übergeben oder der Original-Text? Konsistent mit der Fail-Safe-Regel sollte immer die neutralisierte Version übergeben werden, wenn das Flag gesetzt ist. + +### 5.4 Wettbewerbsanalyse: Einordnung + +Die Wettbewerbstabelle ist präzise. PageSpace ist der nächste Vergleich (einheitlicher Baum), aber ohne Mandanten-Modell, RAG-Scopes und Neutralisierung. Die Kombination aus diesen vier Dimensionen (Hierarchischer Chat-Baum + Scope-Tagging + Neutralisierung + Multi-Tenant) ist tatsächlich ein Alleinstellungsmerkmal. + +**Risiko:** PageSpace und ähnliche Startups bewegen sich schnell. Das Fenster für die Differenzierung ist zeitlich begrenzt. Die UDB sollte in Phase 2 Priorität haben, nicht erst in Phase 3. + +--- + +## 6. Neutralisierung + +### 6.1 Architektonische Schlüssigkeit + +Die Zwei-Flag-Steuerung (Datenquelle + AI-Call) mit OR-Verknüpfung ist einfach und korrekt. Die Fail-Safe-Regel ("Neutralisierung fehlgeschlagen = Dokument nicht weitergeben") ist die einzig vertretbare Entscheidung für den Zielmarkt (regulierte Branchen). Kein Kompromiss hier ist richtig. + +### 6.2 Operation Type statt spezielle LLMs + +Die Nutzung des bestehenden Modellsystems (Operation Type `neutralization`) statt dedizierter "Neutralisierungs-LLMs" ist architektonisch sauber. Es nutzt die vorhandene Modellverwaltung und vermeidet eine parallele Infrastruktur. + +### 6.3 ChatWorkflow-Transparenz + +Die Entscheidung, neutralisierte Dokumente im ChatWorkflow-Objekt als sichtbare Dokumente mitzugeben, ist ein starker Trust-Mechanismus. Der User sieht, was rausgeht. Das ist nicht nur ein Feature, sondern ein Verkaufsargument für regulierte Branchen. + +### 6.4 Latenz-Betrachtung + +Die Pipeline hat drei Phasen mit unterschiedlicher Latenz-Charakteristik: + +| Phase | Zeitpunkt | Latenz-kritisch? | +|-------|-----------|-------------------| +| Datenquellen-Neutralisierung | Beim Einbinden ins RAG | Nein (asynchron, Batch) | +| Prompt-Neutralisierung | Live, bei jedem AI-Call | Ja — User wartet | +| Response-Re-Hydrierung | Post-Processing | Nein (String-Replacement aus Mapping-Tabelle) | + +Die Datenquellen-Neutralisierung (Phase 1) ist unkritisch, da sie beim Einbinden geschieht und nur die neutralisierte Version im RAG landet. Die Re-Hydrierung ist ein einfaches String-Replacement. + +**Kritisch ist die Prompt-Neutralisierung:** Wenn der User im Chat einen Kundennamen tippt und `requireNeutralization=true` gesetzt ist, muss der Prompt live neutralisiert werden. Das erfordert ein schnelles Modell. Empfehlung: Für Prompt-Neutralisierung ein leichtgewichtiges, lokales Modell oder Regex-basierte Vorfilterung evaluieren, um die Latenz unter 500ms zu halten. + +### 6.5 Platzhalter-Konsistenz + +Die Mapping-Tabelle muss über AI-Calls hinweg konsistent sein. `[PERSON_1]` muss immer "Max Müller" bedeuten, nicht einmal "Max Müller" und einmal "Anna Schmidt". Die Empfehlung: Mapping-Einträge persistent pro Datenquelle, nicht session-scoped. Bei AI-Calls wird das aktive Mapping als Kontext mitgegeben. + +### 6.6 Betriebsmodell für Modelle mit Operation Type `neutralization` + +Die Neutralisierung sieht **alle** sensiblen Daten im Klartext. Das Modell muss mindestens so vertrauenswürdig sein wie der Mandant selbst. Offene Frage: Self-hosted (GPU-Infrastruktur) oder CH-Provider mit DSG-konformem SLA? Das hat Infrastruktur- und Kostenimplikationen und sollte als eigene Entscheidung dokumentiert werden. + +--- + +## 7. Konsistenz und offene Architektur-Fragen + +| Thema | Frage | Empfehlung | +|-------|-------|-----------| +| `mandateType`-Mutabilität | Personal → Company: Änderung oder neues Mandate? | Mutabel, keine Geschäftslogik daran binden | +| OAuth-Registrierung | Wo wählt ein OAuth-User den Mandant-Typ? | Onboarding-Wizard nach erstem Login | +| Store: Existierende Instanz | Zweiter Admin aktiviert dasselbe Feature | Max. eine Store-Instanz pro Feature/Mandat; zweiter Admin bekommt FeatureAccess | +| Store in Phase 3 | Was "aktiviert" der Store, wenn Features nur UI-Oberflächen sind? | FeatureInstance wird UI-Scope, nicht Daten-Scope — im Phasenplan klären | +| Scope `global` | Wer darf plattformweite Daten erstellen? | Nur sysadmin, RBAC-geschützt | +| Drag-and-Drop + Neutralisierung | Was wird übergeben: Original oder neutralisierte Version? | Immer neutralisierte Version, wenn Flag gesetzt | +| Billing vs. RAG-Volumen | Wachsende Datenmenge = Infrastrukturkosten | Soft-Limit für RAG-Volumen pro Mandant (kein Billing, aber Kapazitätsparameter) | +| UDB-Skalierung | Hunderte Chats im Baum | Suchfunktion + optionaler Flat-Mode | +| Company-Onboarding | Was sieht ein Firmen-Admin nach Registrierung? | Definieren: autoCreate wie Personal, oder Feature-Auswahl-Wizard | +| Mandanten-Löschung | Kaskade: Instanzen → Daten → RAG → Mappings → Stripe | Im Konzept definieren | + +--- + +## 8. Fazit + +Die Zielarchitektur ist **konzeptionell stark und in sich konsistent**. Die sechs Entscheidungen greifen ineinander: + +``` +Mandate-per-Registration + → saubere Tenant-Grenze + → Own Instance im Store (expliziter Kontext) + → Unified Data Layer pro Mandant + → RAG mit Scope-Tagging als Plattformkern + → UDB als Navigation + Kontext-Steuerung + → Neutralisierung als Fail-Safe-Pipeline +``` + +Die **UDB mit hierarchischem Chat-Baum, Drag-and-Drop-Kontext, Scope-Tagging und Neutralisierung** ist das architektonische Herzstück und ein belegbares Alleinstellungsmerkmal. + +Die **Neutralisierungs-Pipeline** mit Fail-Safe-Regel und ChatWorkflow-Transparenz ist die richtige Antwort auf den Zielmarkt (regulierte Schweizer Branchen). + +Die wichtigsten **offenen Architektur-Fragen** (Tabelle oben) betreffen Edge Cases und Phasenübergänge, nicht die Kernentscheidungen. Das Fundament stimmt. + +**Empfehlung für nächste Konzeptdokumente:** + +1. **Unified Data Layer: Datenmodell** (Phase 2) — Entitäten, Scope-Persistenz, RAG-Index-Architektur +2. **Neutralisierung: Betriebsmodell** — Trusted-Model-Infrastruktur, Latenz-Budget, Platzhalter-Konsistenz +3. **UDB: Komponentenarchitektur** — Extraktion aus Workspace, State Management, Drag-and-Drop-API + +--- + +*Review-Version: 2026-03-23 v2 — Basierend auf Konzeptdokument v3. Fokus: Zielarchitektur, Idee, Konzept und Modelle.* diff --git a/concepts/Multi-Mandate-Onboarding-und-Store-Konzept.md b/concepts/Multi-Mandate-Onboarding-und-Store-Konzept.md new file mode 100644 index 0000000..2b85d41 --- /dev/null +++ b/concepts/Multi-Mandate-Onboarding-und-Store-Konzept.md @@ -0,0 +1,600 @@ +# Multi-Mandate Onboarding, Root-Mandant und Feature-Store — Konzept + +## Zweck + +Dieses Dokument beschreibt die **Ziel-Architektur** für Marktzugang und Mandantenmodell: + +- **Ein globales Benutzerkonto** (`User`) mit **mehreren Mandats-Zugehörigkeiten** (`UserMandate`) — unverändertes Multi-Mandate-Modell. +- **Subscriptions und Kapazität** bleiben **pro Mandant** gebunden (`MandateSubscription`); siehe [Mandanten-Subscription-Konzept.md](./Mandanten-Subscription-Konzept.md) und [Subscription-State-Machine.md](./Subscription-State-Machine.md). +- **Feature-Instanzen** pro Mandant; Zugriff über `FeatureAccess` und Rollen. +- **Unified Data Layer** als zentrale, mandantenweite Datenschicht mit integriertem RAG und Neutralisierung. + +**Abgrenzung:** Konkrete Implementierungsschritte (PRs, Skripte) werden hier nur skizziert; der Fokus liegt auf **fachlicher Einordnung** und **skalierbarer Trennung** von System-Mandant, Personal-Mandant und Firmen-Mandant. + +--- + +## Kundenbedürfnisse (zwei Profile) + +### Profil A — Firma (Team-Mandant) + +- Ein Mandant repräsentiert die **Organisation**. +- **Eine Subscription auf Mandanten-Stufe** (z. B. Standard monatlich/jährlich). +- **Mehrere Benutzer** arbeiten im gleichen Mandanten; weitere Personen per **Einladung**. +- **Eigene Feature-Instanzen** im Firmen-Mandanten (kein „Gemeinschafts-Pool" mit Fremddaten). + +### Profil B — Einzelperson (Solo-Mandant) + +- Registrierung ohne sofortige Firmenstruktur. +- **Eigenes Mandat** (logisch „Personal"), damit Subscription, Limits und Daten **klar diesem Mandanten** zugeordnet sind. +- Späterer Ausbau: **Team einladen**, Mandanten-Label/Name anpassen — wird fachlich zum **Firmen-Mandanten** (Profil A), ohne neues Benutzerkonto. + +--- + +## IST-Zustand (kurz, Stand Codebase-Analyse) + +| Thema | Aktuell | +|--------|---------| +| Identität | `User` ohne `mandateId`; Kontext pro Request über `X-Mandate-Id` / `X-Instance-Id` | +| Registrierung | `POST /api/local/register` erstellt **nur** den User; Mandats-Zuweisung folgt über Admin, Einladung oder Folgeaktionen | +| Root-Mandant | Bootstrap: `name=root`, `isSystem=true`, interne Subscription `ROOT`, Feature-Instanzen mit `autoCreateInstance` | +| Feature Store | **Shared-Instance-Pattern** in [routeStore.py](../../gateway/modules/routes/routeStore.py): eine gemeinsame `FeatureInstance` pro Store-Feature im **Root-Mandanten**; Aktivierung = `FeatureAccess` + User-Rolle; Datenisolation über `read="m"` / `_createdBy` | +| Dashboard | Keine Instanzen → Redirect auf `/store` ([Dashboard.tsx](../../frontend_nyla/src/pages/Dashboard.tsx)) | +| Datensilos | Jede Feature-Instanz hat eigene Daten (z. B. CommCoach eigene Voice-Definitionen, Workspace eigene Chats); Parallelstrukturen und Duplikation | + +**Kernproblem:** Endnutzer landen faktisch im **Root-Mandanten** über den Store. Subscription/Billing pro Kunde ist nicht sauber modellierbar; Upgrade-Pfad „Einzelperson → Firma" fehlt. Datensilos pro Feature verhindern Cross-Feature-Workflows und duplizieren Logik (Voice, Kontext, Dokumente). + +--- + +## Ziel-Architektur (Entscheidungen) + +1. **Root-Mandant nur technisch:** Nur System-/Betriebskonten (sysadmin-Benutzer). Keine Endkunden-Datenhaltung, **keine Feature-Instanzen** für Endkunden im Root. +2. **Jede Selbstregistrierung erzeugt ein eigenes Mandat** (Personal oder Company), inkl. Mandats-Admin-Rolle und passender initialer Subscription (Trial vs. Standard). +3. **Feature Store** mit explizitem Mandanten-Kontext und Orphan Control (siehe Abschnitt unten). +4. **Zwei Einstiege von der Homepage/Landing:** + - **„Kostenlos testen"** → Personal-Mandat + Trial-Plan (`TRIAL_7D`). + - **„Für Unternehmen"** → Company-Mandat + `STANDARD_MONTHLY` (inkl. Firmenname). +5. **Unified Data Layer (Variante B):** Zentrale mandantenweite Datenschicht mit RAG als Plattformkern; Features als spezialisierte Workflow-Oberflächen. +6. **Neutralisierung als Kerndisziplin:** Flag-basiert pro Datenquelle und pro AI-Call, mit Trusted-Model-Pipeline. + +--- + +## Datenmodell-Erweiterung + +Auf `Mandate` ([datamodelUam.py](../../gateway/modules/datamodels/datamodelUam.py)) ein fachlicher Typ: + +| Wert | Bedeutung | +|------|-----------| +| `system` | Root / nicht löschbar, nur Betrieb (sysadmin-User) | +| `personal` | Solo-Mandat (ein primärer „Owner"-Admin) | +| `company` | Organisations-Mandat (Team, Einladungen) | + +**Trennung `isSystem` vs. `mandateType`:** Beide Felder haben unterschiedliche Aufgaben und bleiben bestehen: + +- **`isSystem`** = operativer Schutz (Delete-Schutz, Bootstrap-Erkennung). Nur der Root-Mandant hat `isSystem=true`. +- **`mandateType`** = fachliche Semantik (Markt, Onboarding, UI, Reporting). Steuert keine Geschäftslogik oder Feature-Gates. + +**`mandateType` ist mutabel und rein informativ.** Ein Personal-Mandat kann zu `company` geändert werden, wenn der User sein Team erweitert (Einladungen, Name ändern). Es darf **keine Geschäftslogik** hart an `personal` vs. `company` gebunden sein — keine Feature-Gates, keine unterschiedliche Kapazitätsbehandlung auf Basis von `mandateType`. Der Typ dient der UI-Darstellung, dem Reporting und der Onboarding-Steuerung. + +**Default für bestehende Mandanten (Migration):** `company`, Root explizit `system`. + +--- + +## Onboarding-Flows (Zielbild) + +```mermaid +flowchart TD + subgraph entry [Homepage] + TrialCTA[Kostenlos testen] + CompanyCTA[Fuer Unternehmen] + end + + subgraph personalFlow [Personal] + TrialCTA --> RegP["Register type=personal"] + RegP --> MP[Mandate personal] + MP --> SubT[Subscription TRIAL_7D] + SubT --> ProvP[Feature-Instanzen im Mandat] + end + + subgraph companyFlow [Company] + CompanyCTA --> RegC["Register type=company"] + RegC --> MC[Mandate company + Firmenname] + MC --> SubS[Subscription STANDARD] + SubS --> ProvC[Feature-Instanzen im Mandat] + end + + subgraph invite [Parallel: Einladung] + Inv[Einladung annehmen] + Inv --> UM[UserMandate / FeatureAccess im Zielmandat] + end +``` + +### OAuth-Registrierung (Google, MSAL) + +Bei OAuth-Registrierung gibt es kein Formular mit „Personal vs. Company". Lösung: **Onboarding-Wizard nach erstem OAuth-Login**, der den User durch die Mandant-Typ-Auswahl führt. Der Wizard erscheint nur beim allerersten Login (kein Mandate vorhanden) und erstellt das Mandate mit derselben Provisionierungslogik wie die Formular-Registrierung. + +### Invitation-Flow + +Ein User, der sich via Einladung registriert, erhält **beides**: Zugang zum einladenden Mandanten (via `UserMandate` + `FeatureAccess`) **und** bei Bedarf ein eigenes Personal-Mandate. Der User soll auch ausserhalb des einladenden Mandanten arbeiten können. Das eigene Mandate wird nicht zwingend bei Einladungs-Annahme erstellt, sondern bei Bedarf (z. B. erste Store-Aktivierung ohne Admin-Mandate — siehe Feature Store). + +### Subscription-Timing + +Die Trial-Subscription startet nicht ab Registrierung, sondern ab **erstem Login**. Bei Magic-Link- oder OAuth-Flows vergehen zwischen Registrierung und erstem Login Stunden. Daher: `MandateSubscription` wird bei Registrierung mit Status `PENDING` erstellt. Bei erstem Login wird der Status auf `ACTIVE` gesetzt und die Trial-Periode beginnt. So verliert kein User Trial-Tage durch Registrierungs-Delays. + +### Backend-Orchestrierung + +Zentrale interne Provisionierung nach `createUser`: + +1. Mandat anlegen (mit kopierten System-Rollen, wie bei `createMandate`). +2. `UserMandate` + Mandats-Admin-Rolle. +3. `MandateSubscription` mit gewähltem `planKey`. +4. Für Features mit `autoCreateInstance`: Instanzen **im neuen Mandat** erzeugen + `FeatureAccess` mit Admin-Instanzrolle für den Owner. + +**RBAC-Hinweis:** `createMandate` prüft heute Berechtigungen; für öffentliche Registrierung ist eine Root-privilegierte interne Routine nötig (kein Zirkelschluss „User braucht Rechte, die er noch nicht hat"). + +--- + +## Feature Store: Own Instance Pattern + +### Prinzip: Expliziter Mandanten-Kontext — nie implizit + +Ein User ist Mitglied in **1..n Mandanten**. Daher darf die Store-Aktivierung **nie** ein Mandat erraten oder implizit wählen. Der Kontext muss **immer explizit** definiert sein: + +- **UI:** Der Store zeigt pro Feature die **Mandanten des Users** als Aktivierungsziel. Die Aktivierung erfolgt nicht einfach mit „Aktivieren", sondern mit dem **Namen des Ziel-Mandanten** (z. B. Dropdown oder Kachel-Auswahl bei Multi-Mandat-Usern; bei Solo-Mandant-Usern ist die Auswahl trivial, da nur ein Mandat existiert). +- **API:** Der Activate-Endpoint erhält immer ein **explizites `mandateId`** — keine Heuristik, kein „primäres Mandat". + +### Mehrere Instanzen desselben Features + +Es ist erlaubt, **mehrere Instanzen desselben Features** in einem Mandanten zu haben. Beispiel: Zwei separate Workspace-Instanzen für unterschiedliche Projekte oder Teams. Jede Aktivierung im Store erstellt eine neue `FeatureInstance` — auch wenn bereits eine Instanz desselben Feature-Typs existiert. Die Subscription-Kapazität (`maxFeatureInstances`) begrenzt die Gesamtzahl, nicht pro Feature-Typ. + +### Store-Zugang: Für alle User (Upselling) + +Der Store ist für **jeden User sichtbar** — nicht nur für Admins. Der Store ist ein zentrales Upselling-Instrument. + +**User mit Admin-Mandate:** Aktiviert Features direkt im gewählten Mandanten. + +**User ohne Admin-Mandate** (z. B. nur als Mitglied in fremden Mandanten eingeladen): Wenn dieser User ein Feature im Store aktiviert, wird **automatisch ein eigenes Personal-Mandate erstellt** (inkl. Subscription), und die Feature-Instanz wird in diesem neuen Mandanten angelegt. Damit kann jeder User über den Store zu einem eigenständigen Mandanten-Admin werden — ohne separaten Registrierungsprozess. + +### Berechtigungen + +Nur **Mandanten-Admins** können im Feature Store neue Features für ihren Mandanten aktivieren. Bei Aktivierung erhält der Admin die jeweilige **Admin-Instanzrolle** auf der neu erstellten `FeatureInstance`. Ein User ohne Admin-Mandate löst die Auto-Mandate-Erstellung aus (siehe oben) und wird automatisch Admin des neuen Mandanten. + +### Deaktivierung und Orphan Control + +„Deaktivieren" im Store bedeutet: Der User entfernt seinen eigenen `FeatureAccess` auf die Instanz. **Wenn der letzte User eine Feature-Instanz deaktiviert** (d. h. kein `FeatureAccess`-Record mehr verbleibt), wird die **Feature-Instanz gelöscht** (Orphan Control). Dies verhindert verwaiste Instanzen, die Subscription-Kapazität belegen. + +```mermaid +flowchart TD + UserDeactivate["User deaktiviert Feature"] --> RemoveAccess["FeatureAccess entfernen"] + RemoveAccess --> CheckOrphan{"Noch FeatureAccess-Records auf dieser Instanz?"} + CheckOrphan -->|Ja| Done["Instanz bleibt bestehen"] + CheckOrphan -->|Nein| DeleteInstance["FeatureInstance loeschen + Stripe-Quantity sync"] +``` + +### Technische Umsetzung + +- `POST /api/store/activate`: Body enthält `featureCode` + `mandateId`. Prüft: User ist Admin im Ziel-Mandat, Subscription-Kapazität (`maxFeatureInstances`). Erstellt `FeatureInstance` + `FeatureAccess` + Admin-Instanzrolle. +- `POST /api/store/deactivate`: Body enthält `featureCode` + `mandateId`. Entfernt eigenen `FeatureAccess`. Prüft Orphan-Bedingung; löscht Instanz wenn letzer Zugriff entfernt. + +Frontend: Texte in [Store.tsx](../../frontend_nyla/src/pages/Store.tsx) anpassen auf „Feature für deinen Mandanten aktivieren" mit expliziter Mandanten-Auswahl. + +--- + +## Homepage / Frontend + +- **Zwei CTAs** (auf Login- oder Landing-Route): Links nach `/register?type=personal` bzw. `/register?type=company`. +- **Register-Formular:** bei `company` zusätzlich **Firmenname**; API-Body um `registrationType` / `companyName` erweitern. +- **Einladungen:** unverändert [InvitePage](../../frontend_nyla/src/pages/InvitePage.tsx)-Flow; nach Annahme kann der User mehrere Mandate haben (bestehendes Modell). + +--- + +## Migration: Root-Mandant bereinigen + +### Logik (klar definiert) + +Die Migration läuft als **separates Modul/Script**, das im Bootstrap aufgerufen wird und später wieder entfernt werden kann. + +**Algorithmus:** + +``` +SCHRITT 1 — Feature-Daten migrieren (ALLE User, unabhängig von isSysAdmin): + +FÜR JEDEN User MIT FeatureAccess auf Root-Mandant-Instanzen: + + 1. Hat der User bereits ein eigenes Mandat (nicht Root)? + → JA: Ziel-Mandat = bestehendes Mandat + → NEIN: Neues Personal-Mandat erstellen (mandateType=personal, name=username) + System-Rollen kopieren (copySystemRolesToMandate) + UserMandate mit admin-Rolle im neuen Mandat erstellen + MandateSubscription erstellen (TRIAL_7D oder passender Plan) + 2. FÜR JEDE FeatureAccess des Users auf Root-Shared-Instanzen: + a. Neue FeatureInstance im Ziel-Mandat erstellen (falls nicht schon vorhanden) + b. Daten migrieren: Alle Records mit featureInstanceId=alte_Root_Instanz + UND _createdBy=userId → featureInstanceId auf neue Instanz umschreiben + c. FeatureAccess + Rolle auf neue Instanz erstellen + d. Alten FeatureAccess auf Root-Instanz entfernen + +SCHRITT 2 — Root-Mandant bereinigen: + + Alle Feature-Instanzen im Root-Mandanten entfernen (keine Endkunden-Instanzen mehr) + FÜR JEDEN User MIT UserMandate zum Root-Mandanten: + Hat der User isSysAdmin=true? + → JA: UserMandate bleibt (Systembetrieb) + → NEIN: UserMandate zum Root entfernen +``` + +**Wichtig:** Das `isSysAdmin`-Flag ist irrelevant für die Feature-Migration (Schritt 1). Es steuert System-Zugriff, nicht Daten-Zugriff. Ein sysadmin mit FeatureAccess auf Root-Instanzen bekommt seine Daten genauso migriert. Das Flag ist nur in Schritt 2 relevant: sysadmin-User behalten ihre Root-Mandant-Mitgliedschaft (für Systembetrieb), alle anderen verlieren sie. + +### Implementierung + +- **Ort:** `gateway/scripts/script_migrate_root_users.py` (oder `gateway/modules/migration/migrateRootUsers.py`) +- **Aufruf:** Einmalig im Bootstrap (`interfaceBootstrap.py`) nach `initRootMandateFeatures` eingehängt; nach erfolgreicher Migration wird ein Flag gesetzt (z. B. DB-Record), damit der Code nur einmal läuft. +- **Entfernung:** Nach Verifikation in Produktion kann das Modul und der Bootstrap-Aufruf entfernt werden. +- **Sicherheit:** Dry-Run-Modus als Parameter; Logging aller Aktionen; kein Hard-Delete ohne Verifikation. + +--- + +## Unified Data Layer (Entscheidung: Variante B) + +### Entscheidung + +**Variante B wird gewählt.** Die Feature-Datensilos (z. B. CommCoach mit eigenen Voice-Definitionen parallel zum AI Workspace, doppelte Sprachdefinitionen) zeigen bereits heute, dass die instanz-zentrierte Datenhaltung zu Duplikation und Fragmentierung führt. + +### Prinzip + +Der **Unified Data Layer** ist die zentrale mandantenweite Datenschicht. Features (Workspace, Automation, CommCoach, Trustee, ...) sind **spezialisierte Oberflächen/Workflows**, die auf denselben Datenpool zugreifen. + +``` +Mandate +└── Unified Data Layer (RAG Repository) + ├── Dokumente, Extraktionen, Wissensbasis, Voice-Definitionen + ├── Neutralisierungskonfiguration (zentral pro Mandat) + │ + ├── Workspace nutzt: Chat, Recherche, Dokumentenkontext + ├── Automation nutzt: Workflows mit Zugriff auf Mandantendaten + ├── CommCoach nutzt: Coaching mit geteiltem Kontext + Voice + └── Trustee nutzt: Fachlogik auf Mandantendaten +``` + +### Datenreferenzierung (bleibt erhalten) + +Daten behalten ihre bestehenden Referenzen: + +- **`featureInstanceId`:** Zeigt an, **welches Feature** die Daten erzeugt hat (Quellenschutz). Bleibt als Herkunftsreferenz erhalten. +- **`_createdBy`:** Zeigt an, **welcher User** die Daten erzeugt hat. Bleibt für RBAC und Zuordnung. +- **`mandateId`:** Implizit enthalten, da jede `FeatureInstance` einem Mandanten zugeordnet ist. Muss nicht redundant auf Daten-Records gespeichert werden. + +### Datenquellen-Tagging: Explizite Klassifikation durch den User + +Jede Datenquelle, die in den Unified Data Layer eingebunden wird, muss vom User **explizit getaggt** werden. Es gibt keine implizite Zuordnung. Der User entscheidet beim Einbinden über zwei Dimensionen: + +**1. Sichtbarkeits-Scope (wer darf diese Daten sehen/nutzen):** + +| Scope | Bedeutung | Wer hat Zugriff | +|-------|-----------|-----------------| +| `personal` | Private Daten des Users | Nur der erstellende User, in allen Features | +| `featureInstance` | Daten für eine spezifische Feature-Instanz | Alle User mit FeatureAccess auf diese Instanz | +| `mandate` | Mandantenweite Daten | Alle User im Mandanten, in allen Features | +| `global` | Plattformweite Daten | Alle User plattformweit (read-only) | + +**Einschränkung `scope=global`:** Dieser Scope ist mächtig — eine falsch getaggte Datenquelle wäre plattformweit sichtbar. Daher ist `scope=global` **ausschliesslich durch sysadmin-User setzbar** und RBAC-geschützt. Reguläre User und Mandanten-Admins können maximal `scope=mandate` setzen. + +**2. Neutralisierungs-Flag (`neutralize: true/false`):** + +Ob die Daten vor AI-Zugriff neutralisiert werden sollen (siehe Abschnitt Neutralisierung). + +**Tagging-Zeitpunkt:** Das Tagging erfolgt **beim Einbinden** der Datenquelle (Upload, Verlinkung, Import). Der Scope und das Neutralisierungs-Flag können jederzeit nachträglich geändert werden — Änderungen lösen eine Re-Indizierung im RAG aus. + +**UI-Konzept:** Einfach und unkompliziert direkt in der UDB gelöst — kein separater Dialog nötig. + +**Files-Tab** — Jede Datei zeigt Scope- und Neutralisierungs-Symbole inline: + +``` +FILES +───────────────────────────────────── +📄 Jahresabschluss_2025.pdf 👤 🔒 +📄 Firmenrichtlinien.docx 🏢 +📄 Meine_Notizen.txt 👤 +📄 Branchenreport.pdf 🏢 🔒 +───────────────────────────────────── +👤 = personal 👥 = instanz 🏢 = mandant 🔒 = neutralisiert +``` + +**Sources-Tab** — Die Datenquellen selbst (Mailkonten, Datenbanken, Konnektoren) haben keine Scope-/Neutralisierungs-Icons. Die Icons erscheinen erst auf den **aktiv eingebundenen Elementen** (Active Sources), weil der Scope pro eingebundenem Element definiert wird, nicht pro Quelle: + +``` +SOURCES +───────────────────────────────────── +ACTIVE PERSONAL SOURCES + ■ p.motsch@valueon.ch – Patrick Mo... x 👤 🔒 + +ACTIVE FEATURE SOURCES + ■ PowerOn Prod – TrusteeDataJournal... x 👥 + ■ PowerOn Prod – TrusteeDataJournal... x 👥 🔒 + +BROWSE SOURCES + ● p.motsch@valueon.ch + ● patrick.motsch@pamocreate.com + ● ag112559@gmail.com + +FEATURE DATA + ▸ Root + PowerOn Teams 4 tables + PowerOn Test 10 tables + ▸ VO-Team + CEO Com-Coach 10 tables + PowerOn Prod 10 tables +───────────────────────────────────── +``` + +**Logik:** Die Datenquellen unter "Browse Sources" und "Feature Data" sind **Katalog-Einträge** — sie zeigen, was verfügbar ist. Erst wenn der User eine Quelle **aktiviert** (in die Active-Liste zieht), erscheinen die Scope- und Neutralisierungs-Symbole, weil erst dann die Entscheidung getroffen wird, wie die Daten im RAG integriert werden. + +- **Klick auf das Scope-Symbol** (bei Active-Elementen): Wechselt den Scope zyklisch (personal → instanz → mandant) oder zeigt ein kleines Popover. +- **Klick auf das Neutralisierungs-Symbol:** Toggled `neutralize` on/off (🔒 / kein Symbol). +- **Default bei Aktivierung:** `personal` + `neutralize=false` — der User passt bei Bedarf mit einem Klick an. +- Die Symbole sind sofort sichtbar und verständlich, ohne dass der User einen Einstellungsdialog öffnen muss. + +### Datenmodell: Unified Data Layer + +Das RAG-System existiert bereits in der Plattform und kann ausgebaut werden. Die Erweiterung betrifft primär die Scope- und Neutralisierungs-Metadaten auf bestehenden Entitäten: + +``` +DataSource (Erweiterung bestehender Modelle) +├── id -- existiert +├── mandateId -- existiert (via FeatureInstance) +├── featureInstanceId -- existiert (Quellenschutz) +├── _createdBy -- existiert (User-Zuordnung) +├── scope -- NEU: "personal" | "featureInstance" | "mandate" | "global" +├── neutralize -- NEU: boolean (Flag pro Datenquelle) +└── neutralizationStatus -- NEU: "pending" | "completed" | "failed" | "not_required" + +NeutralizerMapping (existiert als DataNeutralizerAttributes, wird erweitert) +├── id -- Platzhalter-ID (z.B. [name.uuid]) +├── mandateId -- Mandanten-Zuordnung +├── featureInstanceId -- Quellenschutz +├── userId -- Ersteller +├── originalText -- Originalwert +├── patternType -- Typ (email, phone, name, address, iban, ...) +└── fileId -- Zugehörige Datei + +RAG-Index (Vektorspeicher, bestehende Infrastruktur) +├── Dokument-Embedding -- existiert +├── mandateId -- NEU als Filter-Metadatum +├── scope -- NEU als Filter-Metadatum +├── featureInstanceId -- NEU als Filter-Metadatum +├── userId -- NEU als Filter-Metadatum (für scope=personal) +└── isNeutralized -- NEU: nur neutralisierte Versionen wenn Flag gesetzt +``` + +Die bestehenden Datenmodelle (`ContentExtracted`, `ContentPart`, Konnektoren) bleiben unverändert. Die Scope- und Neutralisierungs-Felder werden als Erweiterung auf die Datenquellen-Ebene aufgesetzt. Der RAG-Index erhält Scope-Metadaten als Filterkriterien, um die Union-Query über die vier Scopes effizient auszuführen. + +### RAG als zentrales Repository — der Plattformkern + +Das RAG-System ist das Repository, das die Plattform ausmacht — ein klarer USP. Alle getaggten Datenquellen werden im RAG indiziert und stehen den Features als Kontextquelle zur Verfügung. + +Bei jeder RAG-Query wird der **effektive Scope** aus der Kombination der Sichtbarkeiten des anfragenden Users aufgebaut: + +``` +RAG-Query-Context = personal(userId) + ∪ featureInstance(instanceId) -- falls im Feature-Kontext + ∪ mandate(mandateId) + ∪ global() +``` + +Das bedeutet: Ein User im Workspace-Feature sieht bei einer RAG-Suche seine persönlichen Daten, die Daten der Workspace-Instanz, die Mandantendaten und globale Daten — automatisch zusammengeführt, entsprechend der Tags der Datenquellen. + +### UI-Komponente: Unified Data Bar (UDB) + +Der Unified Data Layer braucht eine **einheitliche UI-Komponente**, die in **allen Feature-Instanzen** verfügbar ist — vergleichbar mit dem Folder-/File-Tree, der heute im Workspace existiert. Diese Komponente heisst **Unified Data Bar (UDB)**. + +**Herkunft:** Die bestehende Sidebar im AI Workspace mit den Tabs **Chats**, **Files**, **Sources** ist der Ausgangspunkt. Diese Komponente wird aus dem Workspace extrahiert und als **plattformweite, wiederverwendbare UI-Komponente** bereitgestellt. + +**Tabs der UDB:** + +| Tab | Inhalt | Scope-Filter | +|-----|--------|-------------| +| **Chats** | Chat-Verläufe als Baumstruktur, gruppiert nach Feature-Instanzen | personal + featureInstance | +| **Files** | Dokumente, Uploads, Extraktionen | Alle Scopes (personal / featureInstance / mandate / global) | +| **Sources** | Eingebundene Datenquellen mit Scope- und Neutralisierungs-Tags | Alle Scopes | + +**Chats-Tab: Baumstruktur nach Feature-Instanzen** + +Der Chats-Tab rendert alle Chats des Users als **hierarchischer Baum**. Top-Level sind die Feature-Instanzen, darunter die Chats in dieser Instanz, ggf. mit feature-spezifischen Substrukturen: + +``` +CHATS +───────────────────────────────────── +▾ AI Workspace – Mein Workspace + 💬 Recherche Datenschutzgesetz + 💬 Report Q1 Entwurf + 💬 Brainstorming Produktstrategie +▾ CommCoach – CEO Coaching + ▾ Coaching-Modul + 💬 Verhandlungstechnik Session 3 + 💬 Feedback-Gespräch Vorbereitung + ▾ Dossier + 💬 Analyse Kommunikationsstil +▾ Trustee – Jahresabschluss 2025 + 💬 Bilanzprüfung Rückfragen + 💬 Steueroptimierung Szenarien +▸ Automation – Workflow Tests +───────────────────────────────────── +``` + +Diese Struktur gibt dem User eine **featureübergreifende Übersicht** aller seiner AI-Interaktionen an einem Ort. Per Klick navigiert er direkt in den Chat — die UDB wechselt dabei ggf. den Feature-Kontext. Die aktuelle Feature-Instanz ist hervorgehoben und expandiert; andere Instanzen sind zugeklappt, aber zugänglich. + +**Skalierung:** Bei vielen Feature-Instanzen und Hunderten von Chats kann der Baum unübersichtlich werden. Daher bietet der Chats-Tab zwei Zusatzfunktionen: + +- **Suchfunktion:** Volltextsuche über Chat-Titel und -Inhalte, filtert den Baum auf Treffer. +- **Flat Mode:** Optionale chronologische Liste (alle Chats ohne Hierarchie, sortiert nach letzter Aktivität) als Alternative zur Baumansicht. Umschaltbar per Toggle im Tab-Header. + +**Drag-and-Drop: Chats als Kontext im Prompt** + +Chats aus dem Chat-Baum können per **Drag-and-Drop in den Prompt-Bereich gezogen** werden. Dabei wird **nicht der Chat-Inhalt selbst** übergeben, sondern die **RAG-Daten, die zu diesem Chat gehören** (Dokumente, Extraktionen, Quellen). Das RAG wird nach den dem Chat zugeordneten Daten abgefragt und diese als Kontext für den aktuellen AI-Call bereitgestellt. Use Cases: + +- **Auswertung:** Mehrere Coaching-Sessions aus CommCoach in den Workspace ziehen → „Fasse die Fortschritte der letzten 5 Sessions zusammen." +- **Cross-Feature-Analyse:** Trustee-Chat + Workspace-Recherche zusammen auswerten → „Vergleiche die steuerlichen Szenarien mit den Recherche-Ergebnissen." +- **Reporting:** Chats aus verschiedenen Instanzen zusammenziehen → „Erstelle einen Wochenbericht aus diesen Interaktionen." +- **Coaching-Review:** CommCoach-Dossier-Chats als Input für eine Meta-Analyse → „Welche Kommunikationsmuster zeigen sich über die Sessions hinweg?" + +Dasselbe Prinzip gilt auch für **Files** und **Sources** aus den anderen Tabs — alle UDB-Elemente sind als Kontext in Prompts nutzbar. Die UDB wird damit zur zentralen **Kontext-Steuerung** für alle AI-Interaktionen. + +**Einbettung in Features:** + +``` +┌─────────────────────────────────────────────────────┐ +│ Feature-Instanz (z.B. Trustee, CommCoach, Workspace)│ +│ │ +│ ┌──────────┐ ┌──────────────────────────────────┐ │ +│ │ UDB │ │ │ │ +│ │ │ │ Feature-spezifischer Content │ │ +│ │ [Chats] │ │ (Workflows, Formulare, etc.) │ │ +│ │ [Files] │ │ │ │ +│ │ [Sources]│ │ │ │ +│ │ │ │ │ │ +│ └──────────┘ └──────────────────────────────────┘ │ +└─────────────────────────────────────────────────────┘ +``` + +**Verhalten:** + +- Die UDB zeigt immer die Daten **im Kontext der aktuellen Feature-Instanz** an, ergänzt um personal- und mandate-scope Daten. +- Der Chats-Tab zeigt den **gesamten Chat-Baum** über alle Feature-Instanzen des Users. +- Der User kann in der UDB Datenquellen einbinden (Upload, Scope/Neutralisierung taggen) — direkt aus dem Feature-Kontext heraus. +- Die UDB ist eine **gemeinsame Komponente** (`frontend_nyla/src/components/`), nicht pro Feature dupliziert. + +**Implikation:** Die bestehende Sidebar-Logik im AI Workspace (Chats, Files, Sources) muss in Phase 2 aus dem Workspace **herausgelöst** und als eigenständige, wiederverwendbare Komponente bereitgestellt werden. Features binden die UDB ein und erhalten automatisch Zugriff auf den Unified Data Layer. + +### Wettbewerbseinordnung: UDB als Differenzierungsmerkmal + +Eine Analyse bestehender AI-Plattformen (Stand März 2026) zeigt, dass **keine** eine vergleichbare featureübergreifende Daten- und Chat-Baumnavigation bietet: + +| Plattform | Chat-Organisation | Datenquellen | Cross-Feature-Navigation | +|-----------|-------------------|-------------|-------------------------| +| **ChatGPT (OpenAI)** | Flach: Ordner + Projects, ein Feature (Chat) | Dateien pro Chat oder pro Projekt | Kein Feature-Konzept; alles ist Chat | +| **Langdock** | "Projects" gruppieren Chats flach; Agents als separate Kategorie | Knowledge Folders (bis 1000 Files), 55+ Integrations | Chats und Agents getrennt; keine Baumstruktur über Feature-Grenzen | +| **PageSpace** | Alles ist eine "Page" in einem einheitlichen Baum (Docs, Channels, Agents) | Dateien als Pages im Baum; Kontext durch Platzierung | Nächster Vergleich: einheitlicher Baum, aber kein Mandanten-Modell, kein RAG-Scope-Tagging, keine Neutralisierung | +| **Microsoft Copilot** | Pro App (Word, Teams, etc.); keine übergreifende Chat-Historie | Microsoft Graph (SharePoint, OneDrive) | Kein übergreifender Chat-Baum; jede App hat eigene AI-Interaktion | +| **Google Gemini** | Flach: Chat-Liste in Gemini; separate AI in Docs/Sheets | Google Drive | Keine Feature-übergreifende Struktur | + +**Was PowerON mit der UDB anders macht:** + +1. **Feature-Instanz-Hierarchie im Chat-Baum:** Kein anderes Tool zeigt Chats gruppiert nach fachlichen Modulen (Treuhand, Coaching, Workspace, Automation) in einem navigierbaren Baum mit feature-spezifischen Substrukturen (z. B. CommCoach → Coaching-Modul → Sessions). +2. **Drei Dimensionen in einer Sidebar (Chats + Files + Sources):** Langdock und ChatGPT trennen Chat-Liste, Knowledge-Ordner und Integrationen in separate Bereiche. Die UDB vereint sie in einer Komponente mit durchgängigem Scope-Modell. +3. **Scope-Tagging pro Datenquelle:** Kein Wettbewerber bietet granulares Tagging (personal / instanz / mandant / global) mit visueller Inline-Steuerung. +4. **Neutralisierung als Inline-Flag:** Einzigartig — keine vergleichbare Plattform bietet Daten-Neutralisierung als pro-Quelle-Toggle. +5. **Multi-Tenant mit Mandanten-Isolation:** PageSpace hat den ähnlichsten Baum-Ansatz, aber ohne Mandantenmodell, ohne RBAC-Scopes und ohne Neutralisierung. + +Die UDB mit hierarchischem Chat-Baum, Scope-Tagging und Neutralisierung ist nach aktuellem Stand ein **Alleinstellungsmerkmal**. + +### Phasenplan + +1. **Phase 1 (jetzt):** Mandate-per-Registration und Own-Instance-Pattern umsetzen — schafft die richtige Mandanten-Grenze als Voraussetzung. Daten bleiben instanz-zentriert. `FeatureInstance` = **Datengrenze** (Instanz besitzt ihre Daten exklusiv). +2. **Phase 2:** UDB-Komponente aus dem Workspace extrahieren und als wiederverwendbare Plattformkomponente bereitstellen. Shared Data Scope pro Mandant einführen. Dateien, Extraktionen und Wissensbasis mandantenweit verfügbar machen. Doppelte Strukturen (z. B. Voice in CommCoach vs. Workspace) konsolidieren. `FeatureInstance` = **Datengrenze + mandantenweiter Zugriff** (Daten sind instanz-zugeordnet, aber mandantenweit les-/nutzbar via Scope). +3. **Phase 3 (Ziel):** Features sind reine Workflow-Oberflächen mit eingebetteter UDB. Der Unified Data Layer (inkl. Neutralisierung, RAG, Vektorspeicher) ist die zentrale Plattformkomponente. RAG-Scopes (personal/instance/mandate/global) sind vollständig implementiert. `FeatureInstance` = **UI-Scope** (die Instanz steuert nur noch, welche Workflow-Oberfläche der User sieht, nicht mehr die Datengrenze). + +**Semantische Verschiebung von `FeatureInstance`:** Die Bedeutung von `FeatureInstance` wandelt sich fundamental über die Phasen — von der Datengrenze (Phase 1) zum UI-Scope (Phase 3). In Phase 1 besitzt eine Instanz ihre Daten exklusiv. In Phase 3 ist der Unified Data Layer die Datengrenze, und die Instanz definiert nur noch den Workflow-Kontext. Dieser Wandel ist beabsichtigt und muss bei der Implementierung der Phasenübergänge berücksichtigt werden: Daten-Ownership verschiebt sich von der Instanz zum Mandanten. + +--- + +## Daten-Neutralisierung als Kerndisziplin + +### Prinzip: Flag-basierte Steuerung + +Die Neutralisierung wird über **zwei unabhängige Flags** gesteuert: + +1. **Flag auf der Datenquelle** (`neutralize: true/false`): Beim Einbinden einer Datenquelle in den Unified Data Layer legt der User fest, ob diese Daten neutralisiert werden sollen. Ist das Flag gesetzt, landen **ausschließlich neutralisierte Dokumente** im RAG — nie die Originale. +2. **Flag pro AI-Call** (`requireNeutralization: true/false`): Unabhängig von der Datenquelle kann ein Feature oder der User verlangen, dass ein spezifischer AI-Call nur mit neutralisierten Daten arbeitet (z. B. für User-Prompts, die sensible Daten enthalten). + +**Regel:** Wenn **mindestens eines** der beiden Flags gesetzt ist, werden die Daten **vor dem Senden an eine AI-Instanz** neutralisiert. + +**Fail-Safe-Regel:** Wenn die Neutralisierung **fehlschlägt** (Modell nicht erreichbar, Timeout, unvollständige Neutralisierung), wird das Dokument **nicht weitergegeben** — weder an das externe AI-Modell noch in den RAG-Index. Der AI-Call wird ohne dieses Dokument ausgeführt und der User erhält einen Hinweis, dass ein Dokument aus Datenschutzgründen nicht einbezogen werden konnte. Kein Fallback auf Originaldaten. + +### Zugelassene Modelle: Operation Type `neutralization` + +Es gibt **keine speziellen LLMs** für Neutralisierung. Die zugelassenen AI-Modelle in der Plattform haben einen **Operation Type**. Modelle mit dem Operation Type `neutralization` dürfen für Neutralisierungsaufgaben eingesetzt werden. Dies sind Modelle, denen die Plattform vertraut (z. B. lokal gehostet oder vertraglich abgesichert). Externe LLMs ohne diesen Operation Type sehen **nur neutralisierte Daten**. + +Die Operation-Type-Konfiguration wird zentral verwaltet und ist nicht vom User änderbar. + +### Neutralisierungs-Pipeline + +```mermaid +flowchart TD + Input["Eingabedaten (Dokument / Datenquelle)"] --> Resolve["Container aufloesen"] + Resolve --> TypeCheck{"Datentyp?"} + + TypeCheck -->|Text| NeutText["Modell mit opType=neutralization: Text neutralisieren"] + TypeCheck -->|Bild| NeutImage["Modell mit opType=neutralization: Bild → neutraler Text"] + TypeCheck -->|Media| Drop["Media droppen (kein AI-Zugriff)"] + + NeutText --> MappingTable["Platzhalter-Mapping-Tabelle: Original ↔ Platzhalter"] + NeutImage --> MappingTable + + MappingTable --> RAGIndex["Nur neutralisierte Dokumente → RAG-Index"] + MappingTable --> ChatWorkflow["Neutralisierte Dokumente im ChatWorkflow sichtbar"] + + RAGIndex --> AICall["AI-Call mit neutralisierten Daten"] + AICall --> AIResponse["AI-Response (mit Platzhaltern)"] + AIResponse --> Rehydrate["Platzhalter rueckwandeln via Mapping-Tabelle"] + Rehydrate --> UserOutput["Endergebnis fuer User"] +``` + +**Schritte im Detail:** + +1. **Container auflösen:** Dokumente (PDF, DOCX, XLSX, ...) werden in ihre Bestandteile zerlegt — Text, Bilder, Medien. Dies entspricht dem bestehenden Extraction-Pattern im Code. +2. **Media droppen:** Audio-/Video-Inhalte werden nicht an AI weitergegeben (Datenschutz-Default). +3. **Bilder extrahieren:** Modell mit `opType=neutralization` extrahiert Bildinhalte als neutralen beschreibenden Text (keine Gesichter, keine erkennbaren Merkmale). +4. **Text neutralisieren:** Modell mit `opType=neutralization` ersetzt personenbezogene und sensible Daten (Namen, Adressen, Kontonummern, etc.) durch **Platzhalter** (z. B. `[PERSON_1]`, `[ADRESSE_2]`, `[KONTO_3]`). +5. **Platzhalter-Mapping-Tabelle:** Die Zuordnung Original → Platzhalter wird in einer **Datenbanktabelle** pro Mandant gespeichert. Diese Tabelle ist die Grundlage für Rückwandlung und User-Einsicht. +6. **RAG-Index:** Bei neutralisierten Datenquellen landen **ausschließlich die neutralisierten Versionen** im RAG — die Originale werden nicht indiziert. +7. **AI-Verarbeitung:** Externe Modelle arbeiten ausschließlich mit neutralisierten Texten. +8. **Rückwandlung:** Die AI-Response wird über die Mapping-Tabelle re-hydriert — Platzhalter werden durch Originaldaten ersetzt, bevor der User das Ergebnis sieht. + +### Transparenz im ChatWorkflow + +Die neutralisierten Dokumente werden im **ChatWorkflow-Objekt als Dokumente mitgegeben**. Der User sieht damit bei jedem AI-Call, welche Daten tatsächlich an das externe Modell gesendet werden. Dies schafft Vertrauen und Nachvollziehbarkeit: + +- Im Chat-UI: Aufklappbarer Bereich „Gesendete Daten (neutralisiert)" mit den neutralisierten Dokumenten. +- Der User kann prüfen, ob die Neutralisierung korrekt und vollständig ist. +- Bei Problemen kann der User die Neutralisierung der Datenquelle anpassen oder deaktivieren. + +### User-Kontrolle + +- Der User kann seine **neutralisierten Daten einsehen** (Transparenz: was wurde wie neutralisiert). +- Der User kann Platzhalter-Mappings **löschen** (Einträge in der Mapping-Tabelle entfernen). +- Pro Datenquelle ist der Neutralisierungs-Status sichtbar (Badge/Icon im UI). + +### Automatisierung + +Die Neutralisierung läuft **vollautomatisch** in der Pipeline: + +- Beim **Einbinden** einer Datenquelle mit `neutralize=true`: Daten werden sofort neutralisiert und die Mapping-Tabelle befüllt. **Nur die neutralisierte Version** wird im RAG-Index gespeichert. +- Bei jedem **AI-Call** mit `requireNeutralization=true`: Die Eingabedaten des Calls durchlaufen die Pipeline vor dem Senden. +- **Batch-Re-Neutralisierung:** Bei Änderung des Neutralisierungs-Flags einer Datenquelle werden bestehende RAG-Einträge re-neutralisiert oder de-neutralisiert (Mapping-Tabelle und RAG-Index aktualisieren). + +--- + +## Verwandte Dokumente + +- [Mandanten-Subscription-Konzept.md](./Mandanten-Subscription-Konzept.md) — Lizenz & Kapazität pro Mandant +- [Subscription-State-Machine.md](./Subscription-State-Machine.md) — Statusübergänge +- [Billing-Konzept.md](./Billing-Konzept.md) — Verbrauch & Prepaid +- [Navigation-API-Konzept.md](./Navigation-API-Konzept.md) — Mandantenbaum in der UI + +--- + +## Referenz: zentrale Code-Stellen + +| Bereich | Pfad | +|---------|------| +| Registrierung | `gateway/modules/routes/routeSecurityLocal.py` | +| Store (IST) | `gateway/modules/routes/routeStore.py` | +| Bootstrap / Root | `gateway/modules/interfaces/interfaceBootstrap.py` | +| Mandat anlegen | `gateway/modules/interfaces/interfaceDbApp.py` (`createMandate`, `createUserMandate`) | +| Feature-Instanz | `gateway/modules/interfaces/interfaceFeatures.py` (`createFeatureInstance`) | +| Pläne | `gateway/modules/datamodels/datamodelSubscription.py` (`BUILTIN_PLANS`) | +| Dashboard / Store UI | `frontend_nyla/src/pages/Dashboard.tsx`, `frontend_nyla/src/pages/Store.tsx` | +| Migration (neu) | `gateway/scripts/script_migrate_root_users.py` (oder `gateway/modules/migration/`) | + +--- + +*Dokument-Version: 2026-03-23 v3 — Korrekturen: Neutralisierung via Operation Type (keine speziellen LLMs); nur neutralisierte Dokumente im RAG; Platzhalter-Mapping als Datenbanktabelle (kein Vault); ChatWorkflow-Transparenz; explizites Datenquellen-Tagging mit Scope und Neutralisierungs-Flag; Marktbeurteilung in separates Dokument ausgelagert.* diff --git a/market/langdock-markteinschaetzung-poweron.md b/market/langdock-markteinschaetzung-poweron.md new file mode 100644 index 0000000..cea69f3 --- /dev/null +++ b/market/langdock-markteinschaetzung-poweron.md @@ -0,0 +1,227 @@ +# Markteinschaetzung: Langdock (Berlin) vs. PowerON + +**Dokument:** Markt- und Wettbewerbsanalyse mit strategischer Empfehlung +**Stand:** 23. Maerz 2026 +**Bezug:** poweron.swiss, langdock.com, Codebase (`frontend_nyla`, `gateway`) + +--- + +## 1. Kurzfassung + +**Langdock** ist eine horizontale Enterprise-KI-Adoptionsplattform (Chat, Workflows, Agents, Integrationen, API) mit Fokus auf sichere, modellagnostische KI fuer das gesamte Unternehmen. Sitz Berlin, tausende Kunden, schnelles Wachstum. + +**PowerON** positioniert sich auf poweron.swiss als *"The Swiss Pocket Knife of AI for SMEs"* -- eine universelle KI-Plattform fuer Schweizer KMU mit DSGVO-Compliance und Schweizer Hosting. Im Code steckt deutlich mehr: eine mandantenfaehige SaaS-Plattform mit RBAC, Feature-Instanzen, Billing/Subscription, vertikalen Fachmodulen (Treuhand, Immobilien, CommCoach) und einem einzigartigen **Daten-Neutralisierungs-Feature**. + +**Kernbefund:** Die oeffentliche Positionierung von PowerON ueberschneidet sich staerker mit Langdock als noetig. Das Produkt hat genuegend einzigartige Tiefe, um sich klar abzugrenzen -- die Botschaft muss geschaerft werden. + +--- + +## 2. Langdock -- Produkt und Positionierung + +### 2.1 Unternehmen + +- Sitz Berlin, Gruendung 2023. +- Ueber 5.000 Kunden, Jahresumsatz oeffentlich kommuniziert im zweistelligen Millionenbereich. +- Wachstum primaer durch Word-of-Mouth, nicht aggressive Sales. + +### 2.2 Produktbausteine + +| Baustein | Inhalt | +|------------------|------------------------------------------------------| +| **Chat** | Modellagnostischer KI-Chat fuer breite Nutzung | +| **Workflows** | KI-gestuetzte Automatisierungen | +| **Agents** | Wiederkehrende Aufgaben mit angepasster KI | +| **Integrations** | Anbindung bestehender Business-Tools | +| **API** | Einbindung in eigene Anwendungen, mehrere Modelle | + +### 2.3 Differenzierung + +- **Modellagnostisch** -- kein Vendor-Lock-in. +- **EU-Hosting / DSGVO** -- Compliance-Story. +- **White-Label** -- firmeneigene Oberflaechen (z. B. Mercks "myGPT Suite"). +- **On-Premise** ab groesseren Seat-Zahlen. +- **Kein eigenes Foundation-Model** -- Wert liegt in Integration und Governance. + +--- + +## 3. PowerON -- Oeffentliche Positionierung (poweron.swiss) + +### 3.1 Claim und Zielgruppe + +- *"The Swiss Pocket Knife of AI for SMEs"* +- Zielgruppe: Schweizer KMU (1 bis 500+ Mitarbeitende). +- Kernversprechen: **100% Swiss Hosting, DSGVO-konform, alle LLMs anbindbar, kein Lock-in**. + +### 3.2 Beworbene Use Cases + +| Use Case | Beschreibung | +|----------------------------------|-----------------------------------------------------------------| +| Research with Context | Externe Recherche + interne Datenquellen kombiniert | +| Customer Support Automation | Ticket-Routing, Jira/ClickUp-Integration, kontextbezogene Antworten | +| Data Migration & Transformation | Sichere Datentransfers, Validierung, Privacy-Preserving | +| Report Generation | Automatisierte Berichte, Branding, zeitgesteuert | +| Compliance Monitoring | Scanning, Redaction, Audit Trail | + +### 3.3 Hervorgehobene technische Features + +1. **Keine Groessenbeschraenkungen** -- intelligentes Splitting. +2. **Automatische Datenschutzfunktion** (Neutralisierung) -- sensible Daten werden vor KI-Verarbeitung entfernt und danach reinseriert. +3. **Multi-Provider mit Failover** (BCM) -- OpenAI, Anthropic, Perplexity; automatisches Umschalten. +4. **Mandanten-Isolation** -- eigener Bereich pro Unternehmen, Logging. +5. **Integrationen** -- M365, Google Workspace, Jira, ClickUp, DB-Connections, REST API, Voice. + +### 3.4 Die Neutralisierungs-Story (Alleinstellung) + +Die Homepage beschreibt einen **5-Stufen-Prozess**: + +1. Scan & Detection -- Erkennung sensibler Inhalte. +2. Replace & Mark -- Pseudonymisierung mit verschluesselter Zuordnung. +3. Store or Delete -- Flexible Verwaltung der Secret Maps. +4. Clean Documents -- Sichere Weitergabe an LLMs. +5. Restore Tags -- Wiederherstellung nach Verarbeitung. + +Im Code existiert dafuer ein eigenes Feature-Modul (`neutralization`) mit Playground, Konfiguration und Attribut-Verwaltung. + +**Bewertung:** Dieses Feature ist in dieser Form bei Langdock nicht vorhanden. Es ist der staerkste technische Differenzierungspunkt. + +--- + +## 4. PowerON -- Realitaet im Code (ueber die Homepage hinaus) + +Die Codebase zeigt erheblich mehr Tiefe als die Website kommuniziert: + +### 4.1 Plattform-Kern + +- **Multi-Tenant / Mandanten:** Isolierte Bereiche mit eigenem Lifecycle. +- **RBAC:** Rollen, Access Rules (UI / Resource / Data-Kontext), Admin-Oberflaechen, Export. +- **Billing & Subscription:** Stripe-Integration, Plan-Management, Bootstrap. +- **Store / Feature-Instanzen:** Modulare Aktivierung pro Mandant. +- **Einladungen, Benachrichtigungen, Messaging** als Plattform-Services. +- **Identitaet:** Microsoft, Google, lokale Auth; SharePoint-Integration. +- **Compliance:** GDPR-Routen, Admin-Logs, Automation-Events. + +### 4.2 Feature-Module (nicht auf Homepage) + +| Code | Inhalt | +|-------------------|---------------------------------------------------------------| +| **trustee** | Treuhand/Buchhaltung: Positionen, Dokumente, Spesen, Scan | +| **realestate** | Immobilien mit Kartenansicht | +| **commcoach** | Kommunikations-Coach: Coaching-Sessions, Dossier | +| **teamsbot** | MS Teams Bot mit Session-Management | +| **workspace** | AI Workspace: Agent Start/Stop, Dateien, RAG, Voice, Edits | +| **chatbot** | Konversationen mit LangGraph, Streaming, AI-Core | +| **automation/2** | Workflow-Definitionen, Templates, Editor, Tasks | +| **neutralization**| Playground, Konfiguration, Attribute | + +### 4.3 Gap: Homepage vs. Code + +Die Website zeigt PowerON als **generische KI-Plattform**. Der Code offenbart eine **spezialisierte SaaS mit Branchen-Features, Abrechnungslogik und tiefer Mandantenfaehigkeit** -- davon erfaehrt der Besucher von poweron.swiss aktuell nichts. + +--- + +## 5. Ueberschneidungen PowerON vs. Langdock + +| Thema | Langdock | PowerON | +|-------------------------|-----------------------------------|-----------------------------------------------| +| Firmenweite KI-Nutzung | Kerngeschaeft | Workspace, Chatbot, Teamsbot | +| Chat & Konversationen | Chat-Produkt | Chatbot + Workspace-Editor | +| Workflows / Automation | Workflows | automation, automation2, chatworkflow | +| Agents / Tools | Agents | Workspace (Agent-Lifecycle, RAG, Voice) | +| Multi-Model | 40+ Modelle | OpenAI, Anthropic, Perplexity + Failover | +| Integrationen | Viele Standard-Integrationen | M365, Google, Jira, ClickUp, SharePoint, DB | +| Governance / Security | EU-Hosting, DSGVO | Schweizer Hosting, DSGVO, RBAC, Neutralisierung| + +**Die generische KI-Schicht ueberschneidet sich stark.** Wer nur "ChatGPT im Unternehmen" sucht, sieht PowerON und Langdock in der gleichen Kategorie -- wobei Langdock dort durch Marktgroesse und Bekanntheit dominiert. + +--- + +## 6. Klare Unterschiede (PowerON-Vorteile) + +| Dimension | PowerON | Langdock | +|----------------------------|----------------------------------------------|---------------------------------| +| **Daten-Neutralisierung** | 5-Stufen-Prozess, eigenes Feature | Nicht vorhanden | +| **Schweizer Hosting** | 100% Schweiz, lokale Datensouveraenitaet | EU (nicht CH-spezifisch) | +| **Mandanten-SaaS** | Feature-Instanzen, Billing, Subscription | Seat-basiertes Enterprise-Modell| +| **Vertikale Fachmodule** | Treuhand, Immobilien, CommCoach, ... | Keine vertikalen Module | +| **Zielmarkt** | Schweizer KMU (reguliert, datensensibel) | Enterprise global/EU | + +--- + +## 7. Strategische Empfehlung + +### 7.1 Kernproblem der aktuellen Positionierung + +Die Homepage positioniert PowerON als **generische KI-Plattform** -- genau dort, wo Langdock (und Microsoft Copilot, und Google Gemini for Workspace, und dutzende weitere) bereits mit massiv mehr Ressourcen, Brand und Kundenstamm unterwegs sind. + +Ein Drei-Personen-Team kann dieses Rennen nicht auf der generischen Ebene gewinnen. Jede Stunde, die in "wir sind auch ein KI-Chat mit Workflows" investiert wird, ist eine Stunde, in der Langdock mit tausenden Kunden und Millionen-Revenue den gleichen Pitch perfektioniert. + +### 7.2 Empfohlene Richtung: Vertikale KI-SaaS mit Neutralisierungs-Moat + +**PowerON sollte sich als vertikale, mandantenfaehige KI-SaaS positionieren -- nicht als generische KI-Plattform.** + +Die empfohlene Positionierung stuetzt sich auf drei Saeulen: + +**Saeule 1: Neutralisierung als Leitfeature ("Privacy-by-Architecture")** + +Die Daten-Neutralisierung ist das Feature, das kein Wettbewerber in dieser Form hat. Statt es als eines von fuenf technischen Features aufzulisten, sollte es das **zentrale Verkaufsargument** werden: + +- *"PowerON ist die einzige KI-Plattform, bei der LLMs niemals Ihre sensiblen Daten sehen -- garantiert durch einen 5-Stufen-Neutralisierungsprozess."* +- Besonders relevant fuer: Treuhand, Kanzleien, Gesundheitswesen, Finanzdienstleister, oeffentliche Verwaltung. +- Alle anderen Features (Chat, Automation, Workspace) werden **unter dem Neutralisierungs-Dach** vermarktet, nicht daneben. + +**Saeule 2: Branchenloesungen statt generische Plattform** + +Der Code enthaelt bereits Treuhand, Immobilien und CommCoach. Diese Module gehoeren sichtbar auf die Website und in den Pitch: + +- Dedizierte Landing Pages pro Branche: "PowerON fuer Treuhandbueros", "PowerON fuer Immobilienverwaltungen". +- Jede Branchenseite zeigt den konkreten Workflow: Dokument rein, Neutralisierung, KI-Verarbeitung, Ergebnis zurueck -- mit branchenspezifischem Vokabular. +- Der Feature-Store und das Mandantenmodell sind dabei die **technische Grundlage**, nicht der Marketing-Lead. + +**Saeule 3: Schweizer Vertrauensmarke** + +"100% Swiss" ist bereits auf der Homepage, aber es wird nicht konsequent durchdekliniert: + +- Schweizer KMU kaufen Vertrauen, nicht Features. Die Kombination **Schweizer Hosting + Neutralisierung + Branchenkenntnis** ist eine Trias, die Langdock strukturell nicht bieten kann. +- Referenzkunden aus regulierten Schweizer Branchen sind wichtiger als Feature-Listen. +- Zertifizierungen und Audits (sofern vorhanden) prominent zeigen. + +### 7.3 Was das konkret aendert + +| Heute (poweron.swiss) | Empfohlen | +|-------------------------------------------|------------------------------------------------------| +| "Swiss Pocket Knife of AI for SMEs" | "Sichere KI fuer regulierte Schweizer Branchen" | +| 5 generische Use Cases | 2-3 Branchenloesungen mit konkreten Workflows | +| Neutralisierung = eins von fuenf Features | Neutralisierung = Leitnarrative und Hauptdifferenzierung | +| Kein Hinweis auf Treuhand/Immo/Coach | Dedizierte Branchenseiten | +| Feature-Liste konkurriert mit Langdock | Feature-Tiefe als Branchenloesungs-Beweis | + +### 7.4 Wovon ich abrate + +1. **Breites Integrations-Rennen:** Langdock hat Dutzende Integrationen; dieses Rennen zu gewinnen ist mit kleinem Team unrealistisch. Besser: die vorhandenen Integrationen (M365, SharePoint, Google) **tief und zuverlaessig** halten und pro Branche gezielt erweitern (z. B. ABACUS/Bexio fuer Treuhand). +2. **"KI-Chat fuer alle":** Das ist Langdocks Kernmarkt. PowerON gewinnt nicht, indem es den gleichen Pitch macht, sondern indem es den Pitch **spezifischer** macht. +3. **Feature-Breite vor Feature-Tiefe:** Lieber drei Branchen exzellent bedienen als zehn Features halb fertig zeigen (vgl. `features.feature5.title`-Platzhalter auf der Homepage). + +### 7.5 Mittelfristiger Zielzustand (6-12 Monate) + +- **Startseite:** Leitnarrative = Neutralisierung + Schweiz + Branche. Demo-CTA pro Branche. +- **2-3 Referenzkunden** mit Case Study (Treuhand oder regulierter Sektor). +- **Workspace und Automation** als "KI-Motor unter der Haube" der Branchenloesungen, nicht als eigenstaendige generische Produkte. +- **Pricing** transparent pro Branchenpaket (nicht pro Seat wie Langdock), um sich auch im Geschaeftsmodell abzugrenzen. + +--- + +## 8. Zusammenfassung in einem Satz + +> PowerON sollte aufhoeren, Langdocks Spiel zu spielen ("generische KI fuer alle"), und stattdessen das eigene Spiel spielen: **sichere, neutralisierte KI fuer regulierte Schweizer Branchen** -- ein Markt, den Langdock strukturell nicht adressiert. + +--- + +## 9. Quellen und Methodik + +- **PowerON:** Homepage poweron.swiss (Stand Maerz 2026) sowie Codeanalyse gateway und frontend_nyla. +- **Langdock:** Oeffentliche Website langdock.com und Produktunterseiten, Presseartikel (Stand Maerz 2026). +- Keine vertraulichen Materialien verwendet. Fuer verbindliche Pricing- und SLA-Vergleiche ist Herstellerkontakt noetig. + +--- + +*Ende des Berichts* diff --git a/market/langdock-markteinschaetzung-poweron.pdf b/market/langdock-markteinschaetzung-poweron.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0178a7d105d72f0a618a6045279038065789a9a3 GIT binary patch literal 42613 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 F{|7R)54HdR literal 0 HcmV?d00001