# Database Health and Data Cleanup — Admin Page ## Beschreibung und Kontext SysAdmin-Seite unter Admin > System > "Datenbank-Gesundheit" mit zwei Views: 1. **Tabellenstatistiken** — Row Count, Size (MB), Index Size, Last Vacuum/Analyze fuer alle Tabellen in allen Datenbanken 2. **Orphan Cleanup** — Generische Erkennung verwaister Datensaetze (FK-Referenzen auf nicht-existierende Parent-Records) mit Cleanup-Buttons **Business-Treiber:** Beim Entfernen von Demo-Sets (und generell beim Loeschen von Mandanten/Features) bleiben verwaiste Daten im System. Aktuell gibt es keine Sicht auf den Zustand der Datenbanken und keine Moeglichkeit, Orphans systematisch zu erkennen und zu bereinigen. **Abhaengigkeiten:** - FormGeneratorTable mit Batch-Handling (wird parallel implementiert) - Bestehende Interface-Architektur (interfaceDb*, interfaceFeature*) **Risiko bei Nicht-Umsetzung:** Verwaiste Daten akkumulieren sich ueber die Zeit, belegen Speicher, und koennen zu Inkonsistenzen fuehren (z.B. Workflows ohne Mandate, Feature-Configs ohne Feature-Instanzen). ## Fokus und kritische Details ### Dynamische DB-Registry (Selbst-Registrierung) Jede Datenbank registriert sich **selbst** in ihrem Interface (`interfaceDb*.py` oder `interfaceFeature*.py`). Es gibt **keine** statische Liste aller Datenbanken. Neue DBs werden automatisch erkannt, entfernte DBs verschwinden. **Pattern:** Jedes Interface ruft beim Import oder bei der Initialisierung `_registerDatabase("poweron_xyz")` auf. Die Registry sammelt alle registrierten DBs dynamisch. ### Deklaratives FK-Registry auf Model-Ebene Jedes `*Id`-Feld das eine echte FK-Beziehung darstellt, bekommt ein `fk_target` in `json_schema_extra`: ```python mandateId: str = Field( ..., json_schema_extra={ ..., "fk_target": {"db": "poweron_app", "table": "Mandate"}, }, ) ``` Felder die **keine** DB-FK sind (Stripe-IDs, logische IDs, Graph-Node-IDs, polymorphe `referenceId`) bekommen **kein** `fk_target`. ### Cross-DB Orphan Detection Viele FK-Beziehungen gehen ueber Datenbank-Grenzen hinweg (z.B. `AutoWorkflow.featureInstanceId` in `poweron_graphicaleditor` referenziert `FeatureInstance` in `poweron_app`). Der Scanner muss Parent-IDs aus der Ziel-DB laden und dann im Child-DB pruefen. ### Performance - **Stats:** `pg_stat_user_tables` + `pg_total_relation_size` sind PostgreSQL-Katalog-Queries (kein Table-Scan). Ueber ~14 DBs mit je 5-15 Tabellen dauert der Call wenige Sekunden. - **Orphans:** Cross-DB-Checks koennen bei grossen Tabellen langsam sein. Ergebnisse werden gecacht (5 Min TTL). - **`n_live_tup`** ist ein Schaetzwert (aktualisiert nach ANALYZE/VACUUM). Fuer ein Health-Dashboard ausreichend. ## Ziel und Nicht-Ziele **Ziel:** - Dynamische DB-Registry die sich aus den Interfaces selbst befuellt - Auto-Registry fuer PowerOnModel-Subklassen (Tabellenname = Klassenname) - `fk_target` Annotationen auf allen echten FK-Feldern - Generischer Orphan-Scanner der FK-Graph aus Model-Metadaten aufbaut - Tabellenstatistiken via PostgreSQL-Katalog - SysAdmin-UI mit zwei Tabs (Stats + Orphans) und Clean-Buttons - Navigation-Eintrag unter Admin > System **Explizit NICHT:** - Keine automatische Bereinigung (nur manuell per Button) - Keine Schema-Migration oder Aenderung an bestehenden DB-Constraints - Kein Monitoring/Alerting (nur On-Demand-Scan) - Keine Aenderung an bestehenden Interface-Initialisierungen (nur Registrierungs-Call hinzufuegen) ## Betroffene Module - **Gateway:** `modules/shared/dbRegistry.py` (neu), `modules/shared/fkRegistry.py` (neu), `modules/system/databaseHealth.py` (neu), `modules/routes/routeAdminDatabaseHealth.py` (neu), `modules/datamodels/datamodelBase.py` (Model-Registry), ~20 Datamodel-Dateien (fk_target Annotationen), ~13 Interface-Dateien (DB-Registrierung) - **Frontend:** `AdminDatabaseHealthPage.tsx` (neu), Routing-Eintrag - **DB-Migration:** nein - **Andere Komponenten:** keine ## Entscheidungen | Datum | Entscheidung | Begruendung | |-------|-------------|------------| | 2026-04-13 | Dynamische DB-Registry statt statischer Liste | Interfaces registrieren sich selbst — bei Add/Remove von DBs muss die Registry nicht manuell gepflegt werden | | 2026-04-13 | `fk_target` in `json_schema_extra` statt DB-Constraints als Quelle | Ermoeglicht Cross-DB Orphan Detection (z.B. Greenfield → App DB); DB-Constraints existieren nur in poweron_app | | 2026-04-13 | `__init_subclass__` fuer Model-Registry | Automatisch, kein manuelles Registrieren noetig; jede PowerOnModel-Subklasse ist sofort im Registry | | 2026-04-13 | `pg_stat_user_tables` fuer Row Counts statt `SELECT COUNT(*)` | Schnell (Katalog-Query), ausreichend genau fuer Health-Dashboard | | 2026-04-13 | SysAdmin-only Zugriff | Datenbank-Gesundheit ist eine System-Funktion, nicht mandantenspezifisch | ## Umsetzungs-Checkliste ### Phase 1: Infrastruktur (Backend) **LLM: Opus 4.6** (Architektur-Entscheidungen, Cross-File-Refactoring) - [ ] **DB-Registry** (`modules/shared/dbRegistry.py`) - `registerDatabase(dbName: str, configPrefix: str = "DB")` — oeffentliche API, registriert eine DB - `_getRegisteredDatabases() -> Dict[str, str]` — intern, gibt alle registrierten DBs zurueck - `_getConnectorForDb(dbName: str) -> DatabaseConnector` — intern, Factory fuer Connector - Thread-safe (Lock oder atomare Operationen) - `registerDatabase` ohne `_` Prefix weil explizit als oeffentliche API fuer andere Module gedacht - [ ] **Selbst-Registrierung in allen Interfaces** (~13 Dateien) - Jedes `interfaceDb*.py` und `interfaceFeature*.py` ruft `registerDatabase()` auf Modul-Ebene auf - Kein Umbau der bestehenden Connector-Logik — nur ein zusaetzlicher Registrierungs-Call - [ ] **Model-Registry** (`modules/datamodels/datamodelBase.py`) - `_MODEL_REGISTRY: Dict[str, Type[PowerOnModel]]` — automatisch via `__init_subclass__` - `_getModelByTableName(tableName: str) -> Optional[Type[PowerOnModel]]` — Lookup ### Phase 2: FK-Annotationen (Backend) **LLM: Composer Fast** (repetitive Annotation-Arbeit, klares Pattern) - [ ] **fk_target auf allen FK-Feldern** (~80-90 Felder in ~20 Dateien) - `modules/datamodels/datamodelMembership.py` — UserMandate, FeatureAccess, Junctions - `modules/datamodels/datamodelRbac.py` — Role, AccessRule - `modules/datamodels/datamodelFeatures.py` — FeatureInstance - `modules/datamodels/datamodelChat.py` — ChatWorkflow, ChatMessage, ChatLog, ChatDocument - `modules/datamodels/datamodelFiles.py` — FileItem, FileFolder - `modules/datamodels/datamodelKnowledge.py` — FileContentIndex, ContentChunk, WorkflowMemory - `modules/datamodels/datamodelBilling.py` — BillingAccount, BillingTransaction, BillingSettings - `modules/datamodels/datamodelSubscription.py` — MandateSubscription - `modules/datamodels/datamodelInvitation.py` — Invitation - `modules/datamodels/datamodelDataSource.py` — DataSource - `modules/datamodels/datamodelFeatureDataSource.py` — FeatureDataSource - `modules/datamodels/datamodelUam.py` — Token, AuthEvent, UserConnection, UserVoicePreferences, UserNotification - `modules/features/graphicalEditor/datamodelFeatureGraphicalEditor.py` — AutoWorkflow, AutoVersion, AutoRun, AutoStepLog, AutoTask - `modules/features/trustee/datamodelFeatureTrustee.py` — alle Trustee-Models - `modules/features/workspace/datamodelFeatureWorkspace.py` — WorkspaceUserSettings - `modules/features/neutralization/datamodelFeatureNeutralizer.py` — DataNeutraliserConfig - `modules/features/realEstate/datamodelFeatureRealEstate.py` — Kanton, Parzelle, Projekt - `modules/datamodels/datamodelContent.py` — ContentChunk, ContentObject (falls vorhanden) - `modules/interfaces/interfaceDbManagement.py` — MessagingSubscription, Prompt (falls Models dort) ### Phase 3: FK-Discovery und Orphan-Scanner (Backend) **LLM: Opus 4.6** (komplexe Cross-DB-Logik, SQL-Generierung) - [ ] **FK-Discovery** (`modules/shared/fkRegistry.py`) - `_discoverFkRelationships() -> List[FkRelationship]` — scannt Model-Registry, liest `fk_target` - Cached nach erstem Scan (Models aendern sich nicht zur Laufzeit) - `FkRelationship` Dataclass: `sourceDb, sourceTable, sourceColumn, targetDb, targetTable` - [ ] **Orphan-Scanner** (`modules/system/databaseHealth.py`) - `_scanOrphans(dbFilter: Optional[str]) -> List[OrphanResult]` - Same-DB: `SELECT COUNT(*) WHERE col NOT IN (SELECT id FROM parent)` - Cross-DB: Parent-IDs laden, dann `WHERE col NOT IN (...)` - `_cleanOrphans(db, table, column) -> int` — loescht Orphans, gibt Count zurueck - `_getTableStats(dbFilter: Optional[str]) -> List[TableStats]` — pg_stat_user_tables Query ### Phase 4: API-Endpunkte (Backend) **LLM: Composer Fast** (Standard-Route-Pattern, klar definiert) - [ ] **Route** (`modules/routes/routeAdminDatabaseHealth.py`) - Prefix: `/api/admin/database-health` - `GET /stats` — Tabellenstatistiken (optional `?db=...`) - `GET /orphans` — Orphan-Scan (optional `?db=...`) - `POST /orphans/clean` — Cleanup Body: `{"db": "...", "table": "...", "column": "..."}` - `POST /orphans/clean-all` — Alle Orphans bereinigen - Alle Endpunkte: SysAdmin-only via `requireSysAdminRole` - [ ] **Router in app.py registrieren** ### Phase 5: Navigation (Backend) **LLM: Composer Fast** (einzelne Zeile in mainSystem.py) - [ ] **Navigation-Eintrag** in `modules/system/mainSystem.py` - Unter `admin-system-group`, order 98, sysAdminOnly - Icon: `FaDatabase`, Path: `/admin/database-health` ### Phase 6: Frontend **LLM: Opus 4.6** (neue Seite mit Tabs, FormGeneratorTable-Integration) - [ ] **AdminDatabaseHealthPage.tsx** mit zwei Tabs - Tab 1 "Statistiken": FormGeneratorTable mit DB, Tabelle, Rows, Total Size, Table Size, Index Size, Last Vacuum, Last Analyze. Sortierbar, Summary-Zeile oben. - Tab 2 "Orphan Cleanup": FormGeneratorTable mit DB, Tabelle, FK-Spalte, Referenz, Orphans, Total Rows, Clean-Button. Filter "Nur Probleme". Batch-Clean. - [ ] **Routing** in Frontend PAGE_REGISTRY - [ ] **i18n** — alle Labels mit `t()` taggen ### Querschnitt-Checks - [ ] API-Endpunkte: 4 neue (stats, orphans, clean, clean-all) - [ ] DB-Schema / Migration: nein - [ ] Frontend-Komponenten: 1 neue Seite (2 Tabs) - [ ] RBAC / Permissions: SysAdmin-only - [ ] Neutralisierung betroffen? nein - [ ] Navigation / Routing: 1 neuer Eintrag - [ ] Billing-Impact? nein ## Akzeptanzkriterien | # | Kriterium (Given-When-Then) | Prio | |---|---------------------------|------| | 1 | Given SysAdmin auf /admin/database-health, When Tab "Statistiken" geladen, Then werden alle Tabellen aller registrierten DBs mit Row Count und Size angezeigt | must | | 2 | Given SysAdmin auf Tab "Orphan Cleanup", When Scan ausgefuehrt, Then werden alle FK-Beziehungen mit Orphan-Count angezeigt | must | | 3 | Given Orphans in AutoWorkflow.featureInstanceId (Cross-DB), When "Clean" geklickt, Then werden die verwaisten AutoWorkflow-Zeilen geloescht und Count aktualisiert | must | | 4 | Given neue Feature-DB wird hinzugefuegt (z.B. poweron_newfeature), When Interface sich registriert und Scan laeuft, Then erscheint die neue DB automatisch in Stats und Orphan-Scan | must | | 5 | Given kein SysAdmin-User, When /admin/database-health aufgerufen, Then 403 Forbidden | must | | 6 | Given grosse Tabelle (>100k Rows), When Orphan-Scan laeuft, Then antwortet der Scan innerhalb von 30 Sekunden | should | | 7 | Given "Alle bereinigen" geklickt, When mehrere Tabellen Orphans haben, Then werden alle Orphans in allen Tabellen bereinigt und Summary angezeigt | should | ## Testplan | ID | AC | Art | Automatisiert | Methode | Status | |----|----|-----|--------------|---------|--------| | T1 | 1 | api | nein | Manuell: GET /stats, pruefen ob alle DBs und Tabellen erscheinen | pending | | T2 | 2 | api | nein | Manuell: Demo laden, Demo entfernen (ohne Cascade), GET /orphans pruefen | pending | | T3 | 3 | api | nein | Manuell: POST /orphans/clean fuer AutoWorkflow, dann GET /orphans erneut | pending | | T4 | 4 | unit | nein | Manuell: _registerDatabase() aufrufen, pruefen ob in _getRegisteredDatabases() | pending | | T5 | 5 | api | nein | Manuell: Non-SysAdmin Request, 403 pruefen | pending | | T6 | 7 | e2e | nein | Manuell: Mehrere Orphan-Typen erzeugen, "Alle bereinigen", Stats pruefen | pending | ## Links - Cursor-Plan: `.cursor/plans/data_cleanup_admin_page_0a610562.plan.md` - Demo-Config Bug-Fix: `gateway/modules/demoConfigs/investorDemo2026.py` (remove() Cascade) - deleteMandate Cascade-Fix: `gateway/modules/interfaces/interfaceDbApp.py` (_cascadeDeleteGraphicalEditorData) - Coding-Conventions: `wiki/d-guides/coding-conventions.md` ## Abschluss - [ ] b-reference/ aktualisiert (gateway/architecture.md — neuer Abschnitt "Database Health") - [ ] TOPICS.md aktualisiert (neues Thema "Database Health / Data Cleanup") - [ ] Dieses Dokument → z-archive/ verschoben