Compare commits

..

No commits in common. "67b508f09bc213916dce77392cbbb4f50481c1e9" and "fc2905f9da7f8addb3c170f03420d015f086809a" have entirely different histories.

20 changed files with 33 additions and 3643 deletions

View File

@ -12,8 +12,7 @@ Dieser Ordner ist der **primäre Orientierungspunkt** für Claude Code / Cursor-
| 2 | **`rules/DOCUMENTATION.md`** Ablage- und Dokumentationsregeln |
| 3 | `rules/ARCHITECTURE.md`, `rules/CODING_RULES.md`, `rules/LESSONS_LEARNED.md` |
| 4 | Issue-Landkarte: **`.claude/docs/GITEA_ISSUES_INDEX.md`** |
| 5 | **Jinkendo Foundation** (Designprinzipien 19): **`docs/jinkendo-foundation/design-principles/README.md`** |
| 6 | **Universal CSV Import** (Modul/Executor/Vorlagen): **`docs/technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md`** (unter `.claude/`) |
| 5 | **Universal CSV Import** (Modul/Executor/Vorlagen): **`docs/technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md`** (unter `.claude/`) |
Themen mit UI/Nav/PWA: siehe `../docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md` (im **Projekt**-`docs/`, nicht hier).
@ -26,7 +25,6 @@ Themen mit UI/Nav/PWA: siehe `../docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md` (im
├── README.md ← Diese Datei
├── rules/ ← Verbindliche Regeln (versioniert, wenn konfiguriert)
├── docs/ ← Spezifikationen + Arbeitspapiere
│ ├── jinkendo-foundation/ ← Foundation Produktfamilie (Designprinzipien)
│ ├── functional/ ← Fachlich (WAS)
│ ├── technical/ ← Technisch (WIE)
│ ├── architecture/ ← Querschnitt

View File

@ -20,7 +20,6 @@ _Dieser Ordner `.claude/docs/` ist per `.gitignore`-Ausnahme **versioniert** (Sp
├── ROADMAP.md ← Strategische Phasen (03)
├── CLEANUP_PLAN.md ← Historie Bereinigung März 2026
├── prompts/ ← Exportierte Prompt-Artefakte (JSON)
├── jinkendo-foundation/ ← Foundation Produktfamilie (Designprinzipien)
├── functional/ ← Fachliche Spezifikationen (WAS)
├── technical/ ← Technische Spezifikationen & Referenz (WIE)
├── working/ ← Arbeitspapiere, Analysen, Session-Snapshots
@ -56,7 +55,6 @@ _Dieser Ordner `.claude/docs/` ist per `.gitignore`-Ausnahme **versioniert** (Sp
| Dashboard-Widgets | `technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md` | Widget-Katalog + Registrierung (siehe Guide) |
| Training Profiler / Resolver | `technical/TRAINING_PROFILE_RESOLVER_LAYER1.md`, `functional/TRAINING_TYPE_PROFILES.md` | Resolver-Module wie im Guide genannt |
| Universal CSV Import | `technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md` | `backend/csv_parser/`, `routers/csv_import.py`, `routers/admin_csv_templates.py` |
| **Designprinzipien (Produktfamilie)** | **`jinkendo-foundation/design-principles/README.md`** | Querschnittsmuster; Foundation für Schwester-Apps |
| Aktivität Produktionsreife | `technical/ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md` (+ EAV-Guide) | `backend/data_layer/activity_session_metrics.py`, `activity_metrics.py`, CSV-Orchestrierung |
| Mitgliedschaft / Features | `technical/MEMBERSHIP_SYSTEM.md`, `architecture/FEATURE_ENFORCEMENT.md` | `backend/auth.py`, Feature-Logging, Router mit Enforcement |
@ -94,16 +92,6 @@ _Dieser Ordner `.claude/docs/` ist per `.gitignore`-Ausnahme **versioniert** (Sp
## Technische Spezifikationen (`technical/`)
### Jinkendo Foundation (Produktfamilie)
| Dokument | Inhalt |
|----------|--------|
| **[jinkendo-foundation/README.md](jinkendo-foundation/README.md)** | Einstieg Foundation-Ordner |
| **[design-principles/README.md](jinkendo-foundation/design-principles/README.md)** | Index: 9 Designprinzipien, Lesereihenfolge, Übergabe-Checkliste |
| `design-principles/*_DESIGN_PRINCIPLES.md` | Einzeldokumente (#1#9) |
### Referenz & Agent-Guides (`technical/`)
| Dokument | Thema |
|----------|--------|
| `AGGREGATION_METHODS.md` | Aggregation |
@ -195,4 +183,4 @@ Siehe [`audit/README.md`](./audit/README.md).
---
**Letzte Aktualisierung:** 4. Juli 2026 (Jinkendo Foundation, Designprinzipien nach `jinkendo-foundation/`)
**Letzte Aktualisierung:** 9. April 2026 (Universal CSV Agent-Guide, Abgleich-Tabelle)

View File

@ -1,46 +0,0 @@
# Jinkendo Foundation
**Stand:** 2026-07-04
**Zweck:** Geteilte **Architektur-Foundation** für die Jinkendo-Produktfamilie (Mitai, Miken, Ikigai, Shinkan, …) — unabhängig von app-spezifischer Domänenlogik.
Dieser Ordner enthält **keine** Mitai-Fachspecs und **keine** operativen Agent-Guides zu einzelnen Features. Die Referenzimplementierung bleibt in **Mitai Jinkendo**; die Foundation beschreibt **übertragbare Muster**.
---
## Inhalt
| Pfad | Beschreibung |
|------|--------------|
| **[design-principles/README.md](./design-principles/README.md)** | **Einstieg:** Index aller 9 Designprinzipien-Dokumente, Lesereihenfolge, Übergabe-Checkliste |
| `design-principles/*_DESIGN_PRINCIPLES.md` | Einzeldokumente (#1 Prompt Engine … #9 Migration & Deploy) |
---
## Mitai-spezifische Implementierung
Agent-Guides, API-Referenz und Domänen-Specs liegen weiterhin unter:
- `.claude/docs/technical/` — WIE (Implementierung)
- `.claude/docs/functional/` — WAS (Fachlich)
- `docs/issues/` — Issue-Epics
Die Designprinzipien verlinken dorthin, wo Mitai als **Beispiel** dient.
---
## Apps der Familie
| Domain | App |
|--------|-----|
| mitai.jinkendo.de | Körper-Tracking (Referenz) |
| miken.jinkendo.de | Meditation |
| ikigai.jinkendo.de | Lebenssinn |
| shinkan.jinkendo.de | Kampfsport |
---
## Pflege
Neue Querschnittsmuster → neues Dokument unter `design-principles/` + Eintrag im [Index](./design-principles/README.md).
Regeln zur Ablage: [DOCUMENTATION.md](../../rules/DOCUMENTATION.md)

View File

@ -1,326 +0,0 @@
# Auth & Session Designprinzipien (Extraktion)
**Status:** Analyse / Arbeitspapier
**Stand:** 2026-07-04
**Geltungsbereich:** Authentifizierung, Session-Management, rollenbasierte API-Zugriffe — kein Mandanten-/SSO-System, keine Zahlungs-Auth
**Serie:** Designprinzipien für Produktfamilie · Dokument 5 von n
**Vorgänger:** [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md)
**Kernkomponenten:**
| Bereich | Pfade |
|---------|-------|
| Auth-Kern | `backend/auth.py` |
| Auth-Endpoints | `backend/routers/auth.py` |
| Profile | `backend/routers/profiles.py` |
| Frontend | `frontend/src/context/AuthContext.jsx`, `ProfileContext.jsx`, `utils/api.js` |
| DB | `profiles`, `sessions` |
| Architektur-Regeln | `.claude/rules/ARCHITECTURE.md`, `CLAUDE.md` § Auth |
| Vision (nicht implementiert) | `CENTRAL_SUBSCRIPTION_SYSTEM.md` (SSO/JWT) |
---
## Modul
**Auth & Session**
Server-seitige, token-basierte Authentifizierung mit FastAPI-Dependencies — **Identität und Rolle**, getrennt von Feature-Entitlements und fachlicher Logik.
---
## Fachliche Verantwortung
Das Modul übernimmt:
1. **Identität** — Wer ist eingeloggt? (`profiles` + Passwort/bcrypt)
2. **Session** — Opaque Token in `sessions`, Ablaufzeit, Logout
3. **API-Gate**`require_auth`, `require_admin`, `require_auth_flexible`
4. **Passwort-Lifecycle** — Hash, Verify, Reset, Registrierung, E-Mail-Verifizierung
5. **Rollen**`profiles.role`: `user` \| `admin` (grobbinsenartig)
Es übernimmt **nicht**:
- Feature-Limits / Tier (→ Feature & Entitlement System, gleiche `auth.py`-Datei aber logisch getrennt)
- Mandanten-Isolation / Org-Workspaces
- OAuth/SSO/JWT (nur Vision)
- Authorization auf Datensatzebene (Row-Level Security)
### Was Mitai **ist** vs. **nicht ist**
| Mitai | Produktfamilien-Muster |
|-------|------------------------|
| 1 Login = 1 Profil (E-Mail) | ✅ Account-Modell |
| Historisch Multi-Profil auf einer Instanz | ⚠️ Legacy (`/profiles`, `X-Profile-Id`) |
| Self-hosted Einzelinstanz | ✅ Kein Multi-Tenant-SaaS |
| Session-Token in DB | ✅ Server-side Session Store |
| Zentrale Jinkendo-Auth (Vision) | ❌ nicht gebaut |
---
## Administrierte vs. code-definierte Konfiguration
| Konfiguration | Speicherort | Administrierbar? |
|---------------|-------------|------------------|
| Nutzer-Stammdaten, Rolle | `profiles` | Admin (User-Verwaltung) / Self-Service |
| Session-Laufzeit | `profiles.session_days` (Default 30) | Profil/Admin |
| Passwort-Hash | `profiles.pin_hash` | Nutzer (change pin) |
| E-Mail-Verifizierung | `email_verified`, Token-Felder | System |
| Trial-Ende | `trial_ends_at` | System bei Registrierung |
| SMTP | Env (`SMTP_*`, `APP_URL`) | Deploy |
| Rate Limits Login/Register | Code (`5/min`, `3/hour`) | Code |
**Hardcodiert:** bcrypt, Token-Länge (`secrets.token_urlsafe(32)`), Rollen-Enum (`user`/`admin`), Header-Name `X-Auth-Token`.
---
## Session- und Auth-Flow
```
Login (email + password)
→ verify_pin (bcrypt | legacy SHA256)
→ optional bcrypt upgrade
→ INSERT sessions (token, profile_id, expires_at)
→ Client: localStorage bodytrack_token
Request
→ Header X-Auth-Token (api.js / AuthContext)
→ get_session(token) JOIN profiles
→ require_auth → session dict (profile_id, role, …)
Logout
→ DELETE sessions WHERE token=…
→ Client: localStorage clear
```
**Sonderfall:** `require_auth_flexible` — Token via Header **oder** Query `ssetoken` (SSE, `<img>`, Downloads).
---
## Rollen
| Rolle | Mechanismus | Typische Rechte |
|-------|-------------|-----------------|
| **user** | `profiles.role = 'user'` | Eigene Daten, Features nach Tier |
| **admin** | `require_admin` | Admin-Shell, Prompts, User, System |
Kein feingranulares RBAC (keine Permission-Matrix). Admin ist Binär-Schalter.
---
## Designprinzipien
### 1. FastAPI-Dependencies als Auth-Gate
| | |
|---|---|
| **Prinzip** | Jeder geschützte Endpoint nutzt `session: dict = Depends(require_auth)` als **separaten** Parameter — nie in `Header()` eingebettet. |
| **Begründung** | Verhindert ungeschützte Endpoints durch falsche Parameter-Signatur. |
| **Quelle** | `CLAUDE.md` § Kritische Regeln; `auth.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nicht linter-erzwungen; Legacy-Endpoints existieren. |
### 2. Server-side opaque Sessions
| | |
|---|---|
| **Prinzip** | Token ist zufällig, in DB gespeichert; Validierung über `sessions` + Ablauf — kein JWT mit Client-Claims. |
| **Begründung** | Revocation (Logout), kein Trust in Client-Payload; einfaches Modell für Self-Hosted. |
| **Quelle** | `sessions` Tabelle; `make_token()`, `get_session()` |
| **Tragfähigkeit** | **hoch** (Single-App, Self-Hosted) |
| **Einschränkung** | Skalierung multi-node braucht shared session store; SSO-Familie braucht anderes Modell. |
### 3. profile_id aus Session, nicht aus Client
| | |
|---|---|
| **Prinzip** | Autoritative Identität für neue Endpoints: `session['profile_id']` — Client darf Profil nicht wählen. |
| **Begründung** | Verhindert IDOR (Zugriff auf fremde Profile). |
| **Quelle** | `routers/goals.py`, `routers/prompts.py`; Architektur-Intent in `CLAUDE.md` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Legacy `get_pid(x_profile_id)` akzeptiert `X-Profile-Id` **ohne** Session-Abgleich — siehe Nicht übernehmen. |
### 4. bcrypt mit Legacy-Migration
| | |
|---|---|
| **Prinzip** | Passwörter mit bcrypt; SHA256-Legacy beim Login erkannt und transparent auf bcrypt upgraded. |
| **Begründung** | Kein Big-Bang-Migration; sichere Hashes ohne Nutzer-Zwangs-Reset. |
| **Quelle** | `verify_pin()`, Login in `routers/auth.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Upgrade nur bei erfolgreichem Login. |
### 5. Rate Limiting auf Auth-Endpoints
| | |
|---|---|
| **Prinzip** | Login, Register, Forgot-Password, Resend-Verification mit `slowapi`-Limits (IP-basiert). |
| **Begründung** | Brute-Force- und Abuse-Schutz. |
| **Quelle** | `routers/auth.py` (`5/minute`, `3/hour`); `main.py` Limiter |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | IP-only; kein account-based lockout. |
### 6. Keine E-Mail-Enumeration bei sensiblen Flows
| | |
|---|---|
| **Prinzip** | Forgot-Password und Resend-Verification liefern generische Erfolgsmeldung, auch wenn E-Mail unbekannt. |
| **Begründung** | Privacy; erschwert Account-Scraping. |
| **Quelle** | `password_reset_request`, `resend_verification` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Register sagt „E-Mail bereits registriert“ (Enumeration möglich). |
### 7. E-Mail-Verifizierung vor voller Nutzung
| | |
|---|---|
| **Prinzip** | Self-Register setzt `email_verified=FALSE`; Verify-Endpoint aktiviert + Auto-Login-Session. |
| **Begründung** | Valide Kontaktadresse; Spam-Reduktion. |
| **Quelle** | `register`, `verify_email` in `routers/auth.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nicht überall im Backend erzwungen (Login ohne verified check?). |
### 8. Flexible Auth für technische Clients
| | |
|---|---|
| **Prinzip** | `require_auth_flexible`: gleiche Session-Validierung via Header oder `?ssetoken=` für SSE/Bilder. |
| **Begründung** | Browser-APIs ohne Custom Headers. |
| **Quelle** | `auth.py`; Prompt SSE `/execute-stream` |
| **Tragfähigkeit** | **mittelhoch** |
| **Einschränkung** | Token in URL kann in Logs/Referrer leaken — kurze Sessions / HTTPS Pflicht. |
### 9. Zentraler API-Client mit Token-Injektion
| | |
|---|---|
| **Prinzip** | Frontend: `api.js` injiziert `X-Auth-Token` automatisch — kein scattered `fetch` ohne Auth. |
| **Begründung** | Konsistenz; eine Stelle für Token-Handling. |
| **Quelle** | `utils/api.js``hdrs()`; `getToken()` aus AuthContext |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Einzelne Komponenten umgehen noch `api.js` (SettingsPage, EmailSettings). |
### 10. Auth getrennt von Authorization (Features)
| | |
|---|---|
| **Prinzip** | `require_auth` = identifiziert; `check_feature_access` = berechtigt für Aktion — nacheinander im Router. |
| **Begründung** | Klare Schichten; Auth-Modul nicht mit Tier-Logik vermischen (auch wenn Datei `auth.py` beides enthält). |
| **Quelle** | `FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md`; Router-Muster |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Legacy Profil-Flags `ai_enabled`, `export_enabled` parallel zum Feature-System. |
### 11. Admin-Gate im Frontend und Backend
| | |
|---|---|
| **Prinzip** | Backend: `require_admin`; Frontend: `RequireAdmin` + `isAdmin` aus Session-Rolle. |
| **Begründung** | UX-Navigation + API-Sicherheit (Frontend allein reicht nicht). |
| **Quelle** | `RequireAdmin.jsx`; `require_admin()` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Einzelne Routen (Workflow-Editor) ohne Frontend-Admin-Gate. |
### 12. Session-Kontext im Frontend
| | |
|---|---|
| **Prinzip** | `AuthProvider` hält `{ token, profile_id, role, profile }`; App setzt `setProfileId(session.profile_id)` für API. |
| **Begründung** | Single React-Tree für Login-State; Re-Validate via `/auth/me` beim Start. |
| **Quelle** | `AuthContext.jsx`; `App.jsx` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | `ProfileContext` lädt alle Profile — Multi-Profil-UX Rest; Session-Profil ist Kanon. |
---
## Nicht übernehmen
1. **`get_pid(X-Profile-Id)` ohne Session-Bindung** — Client kann fremde `profile_id` senden; IDOR-Risiko. Kanon: immer `session['profile_id']` oder explizite Admin-Impersonation mit Audit.
2. **Profile-CRUD nur mit `require_auth`**`/profiles` listet alle Nutzer für jeden Authentifizierten (Kommentar „admin“, kein `require_admin`). Für Familien-Architektur: strikte Admin-Gates.
3. **Dual-System Profil-Flags vs. Features**`ai_enabled`, `export_enabled`, `ai_limit_day` in Session-Query neben v9c Feature-Registry.
4. **localStorage-Key-Inkonsistenz**`bodytrack_token` vs. `mitai-jinkendo_active_profile` (historischer App-Name).
5. **Direktes `fetch` ohne `api.js`** — umgeht Token-/Error-Konvention.
6. **Reset-Token in `sessions`-Tabelle**`reset_{token}` mischt Session-Typen in einer Tabelle; OK für MVP, für Familie: getrennte Token-Typen/Tabellen.
7. **Kein JWT/SSO trotz Produktfamilien-Vision**`CENTRAL_SUBSCRIPTION_SYSTEM.md` beschreibt `auth.jinkendo.de` — Mitai-Implementierung ist **nicht** das Zielbild für Cross-App-SSO.
8. **Multi-Profil-Haushalt ohne klares Modell** — Legacy Multi-Profile auf einer Instanz vs. 1 Account = 1 Profil; für neue Apps Modell explizit wählen.
9. **Role als einziges RBAC** — reicht für Admin/User, nicht für feingranulare Permissions.
10. **Session-Query mit veralteten Profil-Spalten**`get_session` SELECT enthält Legacy-Felder statt nur Identität + Rolle.
11. **Fehlende erzwungene E-Mail-Verified-Prüfung** — Registrierung setzt Flag, Login prüft es nicht offensichtlich.
12. **Debug-Print in Auth-Modul**`print("[AUTH.PY] Module loaded…")` in Produktionscode.
---
## Abgrenzung zu anderen Serien-Dokumenten
| Thema | Dokument |
|-------|----------|
| Tier, Limits, Quotas | [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) |
| Zentrale SSO/Abo-Vision | [CENTRAL_SUBSCRIPTION_SYSTEM.md](../../technical/CENTRAL_SUBSCRIPTION_SYSTEM.md) |
| API-First / Router | `ARCHITECTURE.md` §1 |
---
## Modul-Inventar (Ist-Stand)
```
backend/
├── auth.py # Session, require_*, Feature-Access (v9c)
└── routers/
├── auth.py # login, logout, register, verify, reset
└── profiles.py # CRUD, get_pid (Legacy)
frontend/src/
├── context/AuthContext.jsx
├── context/ProfileContext.jsx
├── layouts/RequireAdmin.jsx
└── utils/api.js # Token-Injektion
DB:
├── profiles # Identität, Rolle, Hash, Tier, Trial
└── sessions # token → profile_id, expires_at
```
**Endpoints (Auswahl):**
| Endpoint | Auth |
|----------|------|
| `POST /api/auth/login` | Public + Rate limit |
| `POST /api/auth/logout` | Token optional |
| `GET /api/auth/me` | require_auth |
| `POST /api/auth/register` | Public + Rate limit |
| `GET /api/auth/verify/{token}` | Public |
---
## Verwandte Dokumentation
- Feature-Entitlements: [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md)
- Architektur-Regeln Auth: `CLAUDE.md`, `.claude/rules/ARCHITECTURE.md`
- GUI Admin-Guard: `docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md`
- SSO-Vision: [CENTRAL_SUBSCRIPTION_SYSTEM.md](../../technical/CENTRAL_SUBSCRIPTION_SYSTEM.md)
---
## Geplante Folgedokumente (Serie)
| # | Modul | Status |
|---|-------|--------|
| 1 | Prompt Engine | ✅ |
| 2 | Data Layer | ✅ |
| 3 | Feature & Entitlement | ✅ |
| 4 | Registry-/Plugin-Muster | ✅ |
| 5 | Auth & Session | ✅ dieses Dokument |
| 6 | Universal Import | ✅ `UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md` |
| 7 | Dashboard Widgets | ✅ |
| 8 | Navigation / IA | ✅ `NAVIGATION_IA_DESIGN_PRINCIPLES.md` |
| 9 | Migration & Deploy | ✅ |

View File

@ -1,363 +0,0 @@
# Dashboard Widgets Designprinzipien (Extraktion)
**Status:** Analyse / Arbeitspapier
**Stand:** 2026-07-04
**Geltungsbereich:** Konfigurierbare Übersicht (Widget-Katalog, Layout, Entitlements, Frontend-Registry) — keine Chart-/Metrik-Berechnung
**Serie:** Designprinzipien für Produktfamilie · Dokument 7 von n
**Vorgänger:** [UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md](./UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md)
**Kernkomponenten:**
| Bereich | Pfade |
|---------|-------|
| Katalog (SSoT) | `backend/widget_catalog.py` |
| Layout-Schema | `backend/dashboard_layout_schema.py` |
| Config-Validierung | `backend/dashboard_widget_config.py` |
| Entitlements | `backend/dashboard_widget_entitlements.py`, `widget_feature_requirements_db.py` |
| Produkt-Standard | `backend/system_dashboard_product_default.py` |
| HTTP | `backend/routers/app_dashboard.py` |
| Frontend-Registry | `frontend/src/widgetSystem/dashboardWidgetRegistry.jsx` |
| Registrierung | `frontend/src/widgetSystem/registerDashboardWidgets.js` |
| Layout-Editor | `frontend/src/pages/DashboardConfigurePage.jsx` |
| Fehler-Isolation | `frontend/src/widgetSystem/WidgetErrorBoundary.jsx` |
| Leitfaden | `DASHBOARD_WIDGETS_AGENT_GUIDE.md` |
| Registry-Meta | [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md) |
---
## Modul
**Dashboard Widgets**
Erweiterbares System für **konfigurierbare Startübersicht**: Backend-Katalog definiert erlaubte Widget-IDs; Nutzer speichern Reihenfolge, Ein/Aus und optionale `config` pro Profil; Frontend rendert über eine lokale Komponenten-Registry.
---
## Fachliche Verantwortung
Das Modul übernimmt:
1. **Widget-Katalog** — IDs, Titel, Beschreibung, optionale Feature-Anforderung (`requires_feature`).
2. **Layout-Persistenz**`profiles.dashboard_layout` (JSON v1: `{ version, widgets[] }`).
3. **Validierung** — Erlaubte IDs, keine Duplikate, max. 32 Widgets, mindestens eines aktiv.
4. **Pro-Widget-Config** — Whitelist pro Widget-ID; Normalisierung beim Speichern.
5. **Standard-Layouts** — Code-Fallback (`DEFAULT_PRODUCT_DASHBOARD_WIDGET_IDS`), Admin-Override (`system_config`), Lab-Template (`DEFAULT_LAB_WIDGET_IDS`).
6. **Entitlements**`allowed` im Katalog; Layout bereinigt bei fehlender Berechtigung.
7. **Frontend-Rendering** — Registry mappt Katalog-ID → React-Komponente + Props aus `layoutEntry.config`.
8. **Nutzer-Konfigurator** — „Übersicht anpassen“ (Sortieren, Toggle, Config-Editoren).
Es übernimmt **nicht**:
- Berechnung von KPIs, Charts, Scores (→ Data Layer + Chart-Endpoints, siehe [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md))
- Tier-/Subscription-Logik in Widgets (→ Feature System, siehe [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md))
- Prompt-/KI-Ausführung (Widget zeigt nur UI; Pipeline läuft über eigene API)
### Datenfluss (Happy Path)
```
WIDGET_CATALOG (Backend)
→ GET /api/app/widgets/catalog (+ allowed via check_feature_access)
→ GET /api/app/dashboard-layout
→ coalesce_effective_layout (Profil oder Standard)
→ merge_missing_catalog_widgets (neue IDs anhängen)
→ apply_entitlements_to_layout_dict
→ Frontend: ensureDashboardWidgetsRegistered()
→ WidgetRenderer: enabled widgets → mapProps(layoutEntry.config) → Component
→ PUT /api/app/dashboard-layout (Pydantic + Entitlements + speichern)
```
### Layout-Eintrag (Struktur)
| Feld | Bedeutung |
|------|-----------|
| `id` | Muss in `WIDGET_CATALOG` existieren |
| `enabled` | Sichtbar auf der Übersicht |
| `config` | Optional; nur für whitelisted Widgets mit Inhalt erlaubt |
---
## Administrierte vs. code-definierte Konfiguration
| Konfiguration | Speicherort | Wer pflegt? |
|---------------|-------------|-------------|
| Widget-IDs, Metadaten, Default-Aktivierung | `widget_catalog.py` | Entwickler |
| Produkt-Standard-Layout (live) | `system_config.dashboard_product_default` | Admin |
| Produkt-Standard (Fallback) | `DEFAULT_PRODUCT_DASHBOARD_WIDGET_IDS` | Entwickler |
| Lab-/Editor-Standard | `DEFAULT_LAB_WIDGET_IDS` | Entwickler |
| Nutzer-Layout | `profiles.dashboard_layout` | Nutzer |
| Feature-Gate (Katalog) | `requires_feature` pro Eintrag | Entwickler |
| Feature-Gate (Override) | `widget_feature_requirements` + Marker | Admin |
| Config-Schema pro Widget | `dashboard_widget_config.py` | Entwickler |
| React-Komponente | `registerDashboardWidgets.js` | Entwickler |
---
## Designprinzipien
### 1. Backend-Katalog als Single Source of Truth für IDs
| | |
|---|---|
| **Prinzip** | `WIDGET_CATALOG` ist die einzige autoritative Liste erlaubter Widget-IDs; `ALLOWED_WIDGET_IDS` wird daraus abgeleitet — nicht manuell duplizieren. |
| **Begründung** | Layout-Validator, API und Default-Layouts bleiben synchron; unbekannte IDs werden beim PUT abgewiesen. |
| **Quelle** | `widget_catalog.py`; Agent-Guide §4 |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Frontend-Registry ist zweite manuelle Bindung (kein Build-Time-Gate). |
### 2. Dual Registry: Backend-Kanon + Frontend-Komponentenbindung
| | |
|---|---|
| **Prinzip** | Jede Katalog-ID braucht einen Eintrag in `registerDashboardWidget({ id, Component, mapProps })`; idempotent via `ensureDashboardWidgetsRegistered()`. |
| **Begründung** | React-Komponenten können nicht im Python-Katalog leben; explizite Zuordnung hält Bundle tree-shakeable. |
| **Quelle** | `registerDashboardWidgets.js`, `dashboardWidgetRegistry.jsx` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Fehlende Registrierung → Laufzeit „Unbekanntes Widget“, kein CI-Fail. |
### 3. Layout als versioniertes Profil-JSON
| | |
|---|---|
| **Prinzip** | Nutzer-Layout in `profiles.dashboard_layout`; Schema `version: 1`, Liste `{ id, enabled, config? }`. |
| **Begründung** | Pro Profil anpassbar; Reset auf NULL → System-Standard. |
| **Quelle** | `DashboardLayoutPayload`, `app_dashboard.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nur v1; Schema-Evolution braucht Migrationspfad. |
### 4. Validierung an der API-Grenze (Pydantic)
| | |
|---|---|
| **Prinzip** | Jeder GET/PUT-Pfad normalisiert über `DashboardLayoutPayload`: Duplikat-IDs, unbekannte IDs, leeres Layout (kein enabled) → Fehler. |
| **Begründung** | Keine korrupten Layouts in der DB; Frontend kann auf gültige Struktur vertrauen. |
| **Quelle** | `dashboard_layout_schema.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Ungültiges gespeichertes Layout → Fallback auf Standard (`coalesce_effective_layout`). |
### 5. Config nur für explizit whitelisted Widgets
| | |
|---|---|
| **Prinzip** | `WIDGETS_ALLOWING_CONFIG`: Widgets **ohne** Eintrag dürfen nur leere `config` haben; sonst Validierungsfehler. |
| **Begründung** | Verhindert unkontrollierte JSON-Blobs und stille Ignorierung unbekannter Keys. |
| **Quelle** | `dashboard_widget_config.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Pro Widget heterogene Schemas (chart_days vs. KPI-Tiles vs. show_*-Booleans). |
### 6. Strikte Config-Keys (Whitelist, Normalisierung)
| | |
|---|---|
| **Prinzip** | Unbekannte Keys in `config` werden abgelehnt; bekannte Keys typgeprüft und normalisiert (z. B. `chart_days` 790, KPI max. 9 Kacheln). |
| **Begründung** | Vorhersagbares Verhalten; Editor und Backend stimmen überein. |
| **Quelle** | `_validate_chart_days_only`, `_validate_kpi_board_config`, History-Viz-Defaults |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Frontend-Normalizer (`bodyChartDays.js`, `*VizConfig.js`) teils parallel — Abweichungsrisiko. |
### 7. Config-Größenlimit
| | |
|---|---|
| **Prinzip** | `MAX_WIDGET_CONFIG_JSON_BYTES` (3072) — keine großen Blobs in Layout-JSON. |
| **Begründung** | DB-Spalte und API-Payload bleiben schlank; Config = Präferenzen, nicht Datenspeicher. |
| **Quelle** | `dashboard_widget_config.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | — |
### 8. Katalog-Erweiterung ohne Layout-Reset
| | |
|---|---|
| **Prinzip** | `merge_missing_catalog_widgets` hängt neue Katalog-IDs ans bestehende Layout an (`enabled: false`). |
| **Begründung** | Nutzer müssen nach Deploy nicht resetten; „Übersicht anpassen“ zeigt neue Optionen. |
| **Quelle** | `dashboard_layout_schema.py`; Agent-Guide |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Reihenfolge neuer Widgets immer am Ende. |
### 9. Mehrere Standard-Layouts (Produkt vs. Lab vs. Admin)
| | |
|---|---|
| **Prinzip** | **Produkt:** `get_product_default_base_dict` (DB-Override oder `DEFAULT_PRODUCT_DASHBOARD_WIDGET_IDS`). **Lab:** `lab_default_layout_dict` für Editor/Reset. **Nutzer:** eigenes JSON oder NULL. |
| **Begründung** | Onboarding-Default getrennt von Entwickler-/Lab-Template; Admin kann Produkt-Standard ohne Deploy ändern. |
| **Quelle** | `system_dashboard_product_default.py`, `widget_catalog.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Feldname `lab_default_layout` historisch irreführend (Servertemplate, nicht nur Lab). |
### 10. Entitlements zentral, Widgets konsumieren nur `allowed`
| | |
|---|---|
| **Prinzip** | Sichtbarkeit über `check_feature_access` in `widget_id_allowed`; Katalog liefert `allowed` pro Zeile. Widgets/React duplizieren **keine** Tier-Logik. |
| **Begründung** | Eine Wahrheit für „darf angezeigt werden“; spätere Feature-Cluster ohne Widget-Refactor. |
| **Quelle** | `dashboard_widget_entitlements.py`; Agent-Guide §0 |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Inhalts-Endpoints (Charts, KI) brauchen **eigenes** Feature-Gate (Defense in Depth). |
### 11. Layout-Persistenz bereinigt nicht erlaubte Widgets
| | |
|---|---|
| **Prinzip** | `apply_entitlements_to_layout_dict`: bei fehlender Berechtigung `enabled: false`; mindestens `welcome` bleibt aktiv. GET und PUT wenden an. |
| **Begründung** | Keine „gespeichert aber nie sichtbar“-Zombies; Downgrade/Tier-Wechsel degradieren gracefully. |
| **Quelle** | `dashboard_widget_entitlements.py`, `app_dashboard.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Policy ist deaktivieren, nicht entfernen — IDs bleiben im JSON. |
### 12. DB-Override für Widget-Feature-Anforderungen
| | |
|---|---|
| **Prinzip** | Katalog-`requires_feature` ist Default; Admin kann per `dashboard_widget_requirement_custom` + `widget_feature_requirements` überschreiben (AND-Semantik). |
| **Begründung** | Runtime-Anpassung ohne Code-Deploy; Marker-Zeile trennt Custom von Fallback. |
| **Quelle** | `widget_feature_requirements_db.py`, Migration 041 |
| **Tragfähigkeit** | **mittelhoch** |
| **Einschränkung** | Zwei Quellen (Code + DB) — Dokumentation und Admin-UI nötig. |
### 13. mapProps: Layout-Config → Komponenten-Props
| | |
|---|---|
| **Prinzip** | Registry-Eintrag mappt `ctx.layoutEntry.config` auf typisierte Props (`chartDays`, `kpiConfig`, `bodyHistoryVizConfig`, …). |
| **Begründung** | Widget-Komponenten bleiben layout-agnostisch; Normalisierung an einer Stelle pro ID. |
| **Quelle** | `registerDashboardWidgets.js` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Teilweise Normalisierung in Widget statt in `mapProps` (inkonsistent, aber dokumentiert). |
### 14. Refresh-Koordination über Context
| | |
|---|---|
| **Prinzip** | `refreshTick` + `requestRefresh()` im Render-Context; Widgets laden Daten bei Tick-Änderung neu; Aktionen (z. B. Schnelleingabe) rufen `requestRefresh`. |
| **Begründung** | Kein globales State-Monster; gezielte Invalidierung nach Capture. |
| **Quelle** | `dashboardWidgetRegistry.jsx`, Widget-Implementierungen |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Kein feingranulares Cache pro Widget. |
### 15. Fehler-Isolation pro Widget
| | |
|---|---|
| **Prinzip** | `WidgetErrorBoundary` um jede Instanz — Render-Fehler crashen nicht die ganze Übersicht. |
| **Begründung** | Robuste PWA; ein defektes Chart blockiert nicht Gewicht-Eingabe. |
| **Quelle** | `WidgetErrorBoundary.jsx` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Kein automatisches Retry/Reporting. |
### 16. Konfigurator filtert nach `allowed`
| | |
|---|---|
| **Prinzip** | `DashboardConfigurePage` blendet Widgets mit `allowed === false` aus der bearbeitbaren Liste aus. |
| **Begründung** | Nutzer sehen keine Optionen, die sie nicht nutzen dürfen (Agent-Guide A2). |
| **Quelle** | `DashboardConfigurePage.jsx` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Bereits gespeicherte disabled Einträge können im JSON verbleiben. |
### 17. Widgets konsumieren Data Layer, duplizieren keine Logik
| | |
|---|---|
| **Prinzip** | Chart-/KPI-Widgets rufen Chart-Endpoints bzw. API-Fassaden auf; Berechnungen leben in `data_layer/`, nicht in Widget-JS. |
| **Begründung** | Gleiche Zahlen wie Verlauf, KI-Platzhalter und Export. |
| **Quelle** | Layer-2b `*_history_viz`-Widgets; [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md) |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Legacy-Widgets unter `dashboard-widgets-legacy/` teils ältere Fetch-Pfade. |
### 18. Dedizierte Config-Editoren für komplexe Widgets
| | |
|---|---|
| **Prinzip** | Einfache `chart_days`: Set `CHART_DAYS_WIDGET_IDS` im Layout-Editor; komplexe Config: eigene Editor-Komponenten (`KpiBoardConfigEditor`, `*VizConfigEditor`). |
| **Begründung** | UX skaliert mit Config-Komplexität; Backend-Schema und Editor bleiben parallel pflegbar. |
| **Quelle** | `widgetSystem/*ConfigEditor.jsx`, Agent-Guide §3.4 |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Jedes neue komplexe Widget = Editor + Validator + Tests. |
---
## Nicht übernehmen
1. **Tier-Logik in React-Widgets** — nur `allowed` aus API; keine hardcodierten Plan-Namen.
2. **`ALLOWED_WIDGET_IDS` manuell pflegen** — immer aus Katalog ableiten.
3. **Config ohne Backend-Whitelist** — stille Ignorierung unbekannter Keys in Widgets.
4. **Nur UI-Gating ohne API-Absicherung** — Chart-/KI-/Export-Endpoints weiterhin `check_feature_access` (403).
5. **Frontend-Registry vergessen** — Katalog-Eintrag ohne `registerDashboardWidget` → Laufzeitfehler statt Build-Fail.
6. **Große Daten in `config`** — Layout ist Präferenzspeicher, kein Blob-Store (>3072 Bytes).
7. **Doppelte Widget-IDs im Layout** — Validator verbietet; Editor muss dasselbe erzwingen.
8. **Neue Katalog-IDs ohne `merge_missing_catalog_widgets`-Pfad** — Nutzer-Layouts veralten unsichtbar.
9. **Kompletter Katalog nur in DB** — schwer testbar; Code-Katalog + optionale Feature-Overrides ist das Muster.
10. **Evidence-Pflicht à la Placeholder-Registry** — 22 Metadatenfelder pro Widget wären Overkill; Tiefe an Risiko anpassen ([REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md)).
11. **Ein Default für alles** — Produkt-Onboarding, Lab-Template und Admin-Override haben unterschiedliche Zwecke; nicht vermischen.
12. **Fehlender Cross-Check Backend ↔ Frontend IDs** — empfohener Test/Gate fehlt im Ist-Stand; nicht als „optional“ ignorieren.
13. **Berechnungslogik im Widget** — KPIs/Scores gehören in Data Layer, nicht in `useEffect`-Mathe.
14. **Entitlements beim Speichern ablehnen statt deaktivieren** — Mitai wählt deaktivieren; Policy bewusst festlegen und dokumentieren.
---
## Modul-Inventar (Ist-Stand)
```
backend/
├── widget_catalog.py # WIDGET_CATALOG, DEFAULT_*_IDS
├── dashboard_layout_schema.py # Pydantic, merge_missing, defaults
├── dashboard_widget_config.py # WIDGETS_ALLOWING_CONFIG, Validatoren
├── dashboard_widget_entitlements.py # allowed, layout cleanup
├── widget_feature_requirements_db.py # Admin-Override
├── system_dashboard_product_default.py
└── routers/app_dashboard.py
frontend/src/
├── widgetSystem/
│ ├── dashboardWidgetRegistry.jsx
│ ├── registerDashboardWidgets.js
│ ├── layoutEditor.js
│ ├── bodyChartDays.js, *VizConfig.js
│ └── *ConfigEditor.jsx
├── components/dashboard-widgets/ # Produkt-Widgets
├── components/dashboard-widgets-legacy/ # ältere Kern-Widgets
└── pages/DashboardConfigurePage.jsx
DB:
├── profiles.dashboard_layout
├── system_config.dashboard_product_default
├── dashboard_widget_requirement_custom
└── widget_feature_requirements
```
**Katalog-Umfang:** ~24 Widget-IDs (Stand `widget_catalog.py`); ~13 mit konfigurierbarer `config`.
---
## Verwandte Dokumentation
- Agent-Guide (normativ): [DASHBOARD_WIDGETS_AGENT_GUIDE.md](../../technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md)
- Registry-Meta: [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md)
- Feature-Gates: [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md)
- Datenberechnung: [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md)
- Architektur §9: `.claude/rules/ARCHITECTURE.md`
---
## Geplante Folgedokumente (Serie)
| # | Modul | Status |
|---|-------|--------|
| 16 | … | ✅ |
| 7 | Dashboard Widgets | ✅ dieses Dokument |
| 8 | Navigation / IA | ✅ |
| 9 | Migration & Deploy | ✅ |

View File

@ -1,359 +0,0 @@
# Data Layer Designprinzipien (Extraktion)
**Status:** Analyse / Arbeitspapier
**Stand:** 2026-07-04
**Geltungsbereich:** Multi-Layer Data Architecture (Phase 0c, Issue #53) — keine Mitai-Gesamtarchitektur, keine konkrete Gesundheits-/Ernährungsfachlogik als Produktinhalt
**Serie:** Designprinzipien für Produktfamilie · Dokument 2 von n
**Vorgänger:** [PROMPT_ENGINE_DESIGN_PRINCIPLES.md](./PROMPT_ENGINE_DESIGN_PRINCIPLES.md)
**Kernkomponenten:**
| Bereich | Pfade |
|---------|-------|
| Metriken (Layer 1) | `backend/data_layer/*_metrics.py`, `scores.py`, `correlations.py` |
| Utilities | `backend/data_layer/utils.py` |
| Visualisierung (Layer 2b) | `*_chart_payloads.py`, `*_viz.py` |
| KI-Formatierung (Layer 2a-Hilfe) | `prompt_output_compact.py` |
| Persistenz-Orchestrierung | `activity_persistence_orchestrator.py`, `activity_session_metrics.py` |
| Konsumenten | `routers/charts.py`, `placeholder_resolver.py`, `routers/exportdata.py` |
| Leitfäden | `DATA_LAYER_EXTENSION_GUIDE.md`, `docs/issues/issue-53-phase-0c-multi-layer-architecture.md` |
| Architektur-Regel Import-Grenze | `.claude/rules/ARCHITECTURE.md` §8 |
---
## Modul
**Data Layer** (Phase 0c Multi-Layer Architecture, Issue #53)
Zentrale Schicht für **Datenabruf, Berechnung und strukturierte Aufbereitung** — ohne UI-Formatierung, ohne Prompt-Texte, ohne Chart.js-spezifische Ausgabe in den Kern-Metrik-Modulen.
---
## Fachliche Verantwortung
Der Data Layer ist die **Single Source of Truth für alle abgeleiteten Messwerte und Metriken**. Er übernimmt:
1. **Datenabruf** — Lesen aus PostgreSQL (profile-scoped), optional mit Quality-Filter.
2. **Berechnung** — Trends, Scores, Korrelationen, Aggregationen, Projektionen.
3. **Strukturierte Rückgabe** — Dicts/Listen mit numerischen Werten, Datumsfeldern, Metadaten (`confidence`, `data_points`).
4. **Konsumenten-Bereitstellung** — Charts (Layer 2b), KI-Platzhalter (Layer 2a via Resolver), Export, Router-Anreicherung.
Er übernimmt **nicht**:
- CSV-Parsing und Feld-Mapping (Import-Schicht)
- Prompt-Template-Auflösung (Prompt Engine)
- React-Rendering oder Frontend-Berechnungen
- Autorisierung / Feature-Limits (Auth-Schicht)
### Schichtenmodell (Multi-Layer)
```
┌─────────────────────────────────────────────────────────┐
│ Layer 0: Persistenz (PostgreSQL) │
│ weight_log, nutrition_log, activity_log, sleep_log, … │
└──────────────────────────┬──────────────────────────────┘
┌──────────────────────────▼──────────────────────────────┐
│ Layer 1: DATA LAYER (Metriken) │
│ Strukturierte Daten · confidence · data_points │
│ KEINE formatierten Strings · KEINE Chart.js-Objekte │
└──────────────┬───────────────────────┬──────────────────┘
│ │
▼ ▼
┌──────────────────────────┐ ┌────────────────────────────┐
│ Layer 2a: KI / Prompts │ │ Layer 2b: Visualisierung │
│ placeholder_resolver │ │ *_chart_payloads, *_viz │
│ prompt_output_compact │ │ routers/charts.py │
└──────────────────────────┘ └────────────────────────────┘
```
### Administrierte vs. code-definierte Konfiguration
| Was | Wo | Administrierbar? |
|-----|-----|------------------|
| Berechnungslogik (Formeln, Fenster) | `data_layer/*.py` | ❌ Code + Review |
| Confidence-Schwellen | `data_layer/utils.py` | ❌ Code |
| Goal Mode / Focus Weights | DB (`profiles`, `user_focus_area_weights`) | ✅ Nutzer/Admin |
| Quality Filter (Profil) | DB (`profiles`) | ✅ Admin |
| Chart-Zeitfenster | Query-Parameter an API | ✅ Request |
| Referenzwerte (persönlich) | DB + `reference_values.py` | ✅ Nutzer |
| EAV Session Metrics | DB (`training_*_parameter`) | ✅ Admin |
**Bewusst nicht hardcodiert in Routern:** Metrik-Berechnungen — Router delegieren an Data Layer.
**Hardcodiert (Code):** Domänen-Module, Confidence-Regeln, Schwellen pro Metrik-Typ, TDEE-Fallback-Logik, Chart-Payload-Struktur.
### Trennung: Metriken · Chart-Payloads · KI-Formatierung · Persistenz
| Schicht | Module | Verantwortung |
|---------|--------|---------------|
| **Metriken** | `body_metrics.py`, `nutrition_metrics.py`, … | Reine Berechnung, strukturierte Dicts |
| **Chart-Payloads** | `nutrition_chart_payloads.py`, `correlation_chart_payloads.py`, … | Chart.js-kompatible `{ labels, datasets, metadata }` aus Layer-1-Daten |
| **Viz-Bundles** | `body_viz.py`, `fitness_viz.py`, … | Zusammengesetzte Dashboard-/History-Pakete für Frontend |
| **KI-Kompaktierung** | `prompt_output_compact.py` | Token-sparende Zahlen/JSON für Platzhalter |
| **Interpretation** | `*_interpretation.py`, `vital_signs_assessment.py` | Textliche Einordnung (WHO-Klassen etc.) — Grenze zu Layer 2a |
| **Persistenz-Orchestrator** | `activity_persistence_orchestrator.py` | Schreibpfade REST/CSV → DB + Nebenwirkungen (EAV, Eval) |
### Konsumenten (wer ruft den Data Layer auf?)
| Konsument | Muster |
|-----------|--------|
| `routers/charts.py` | Layer-1-Funktion + Chart-Payload-Builder |
| `placeholder_resolver.py` | Layer-1 → Formatierung/JSON für `{{placeholders}}` |
| `routers/exportdata.py` | `enrich_sessions_with_metrics`, `serialize_dates` |
| `routers/activity.py`, `csv_import.py` | `activity_persistence_orchestrator` (Schreiben) |
| `prompt_executor.execute_prompt_with_data` | ⚠️ teils Roh-SQL parallel zum Data Layer (Legacy) |
### Rollen
Der Data Layer hat **keine eigene Admin-UI**. Konfiguration erfolgt indirekt:
- **Admin:** Training-Parameter, Attributprofile, Referenzwert-Typen, Quality-Filter
- **Nutzer:** Profildaten, Referenzwerte, Focus-Area-Gewichte (beeinflussen Scores)
- **Entwickler:** Neue Funktionen in `data_layer/` nach Extension Guide
---
## Designprinzipien
### 1. Single Source of Truth für Berechnungen
| | |
|---|---|
| **Prinzip** | Jede Metrik wird **einmal** in `data_layer/` berechnet; Charts, KI und Export konsumieren dieselbe Funktion. |
| **Begründung** | Verhindert divergierende Zahlen zwischen Dashboard, Analyse und KI-Ausgabe. |
| **Quelle** | Issue #53 Executive Summary; `nutrition_chart_payloads.py` Kommentar „identisch zu GET /api/charts/energy-balance“ |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nicht alle Pfade migriert (`insights._prepare_template_vars`, `execute_prompt_with_data` Roh-SQL). |
### 2. Layer 1 liefert strukturierte Daten, keine formatierten Strings
| | |
|---|---|
| **Prinzip** | Kern-Metrik-Funktionen geben Dicts mit `float`/`int`/`date` zurück — **keine** Strings mit Einheiten („86,1 kg“). |
| **Begründung** | Formatierung ist konsumentenspezifisch (DE-Locale, Chart-Achsen, KI-Token). |
| **Quelle** | `data_layer/__init__.py` Docstring: „NO FORMATTING. NO STRINGS WITH UNITS.“ |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | `placeholder_resolver` und `*_interpretation` Module formatieren teils direkt — Grenze Layer 1/2a nicht überall scharf. |
### 3. Pflicht-Metadaten: confidence + data_points
| | |
|---|---|
| **Prinzip** | Jede Metrik-Funktion liefert mindestens `confidence` (`high`\|`medium`\|`low`\|`insufficient`) und `data_points`. |
| **Begründung** | UI/KI können Datenqualität kommunizieren; Debugging und Monitoring vereinfacht. |
| **Quelle** | `DATA_LAYER_EXTENSION_GUIDE.md` § Pflicht-Felder; `calculate_confidence()` in `utils.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nicht runtime-validiert; Disziplin per Code-Review. |
### 4. Confidence nach Metrik-Typ und Zeitfenster
| | |
|---|---|
| **Prinzip** | Schwellen unterscheiden `general`, `correlation`, `trend` und Fensterlänge (7d / 28d / 90d). |
| **Begründung** | Korrelationen brauchen mehr Paare; Trends messen Abdeckung (% der Tage). |
| **Quelle** | `data_layer/utils.py``calculate_confidence()` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Schwellen global hardcodiert, nicht pro Metrik konfigurierbar. |
### 5. Domänen-Module statt Monolith
| | |
|---|---|
| **Prinzip** | Ein Python-Modul pro fachlichem Bereich (`body_metrics`, `nutrition_metrics`, …), max. ~500 Zeilen, dann Split. |
| **Begründung** | Wartbarkeit, klare Ownership, parallele Entwicklung. |
| **Quelle** | `DATA_LAYER_EXTENSION_GUIDE.md` § Modul-Struktur |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Einige Module deutlich >500 Zeilen (Phase-0c-Wachstum). |
### 6. Layer 2b: Chart-Payloads als Adapter
| | |
|---|---|
| **Prinzip** | Chart.js-Strukturen leben in dedizierten `*_chart_payloads.py` / `*_viz.py`, nicht in Metrik-Modulen. |
| **Begründung** | Gleiche Metrik, verschiedene Visualisierungen; API-Endpoints bleiben dünn. |
| **Quelle** | `nutrition_chart_payloads.py`; `routers/charts.py` Imports |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Teilweise noch SQL-Duplikation in Payload-Buildern neben Layer-1-Aufruf. |
### 7. Layer 2a-Hilfe: KI-spezifische Kompaktierung getrennt
| | |
|---|---|
| **Prinzip** | Token-Reduktion für LLM-Kontext (`compact_float_for_prompt`, `compact_json_payload_for_prompts`) ist eigenes Modul, nicht in Metrik-Kern. |
| **Begründung** | KI hat andere Anforderungen als Charts (Präzision vs. Token-Kosten). |
| **Quelle** | `prompt_output_compact.py`; Tests in `tests/test_prompt_output_compact.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nur für KI-Pfad; Charts nutzen eigene Rundung. |
### 8. Import-Grenze: Ingest vs. Interpretation
| | |
|---|---|
| **Prinzip** | CSV-Import macht Mapping + Typkonvertierung + Duplikatlogik — **keine** fachliche Auswertung beim Insert. |
| **Begründung** | Semantik gehört in Layer 1+, sonst versteckte Business-Logik in Import-Adaptern. |
| **Quelle** | `ARCHITECTURE.md` §8; Issue #53 |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Legacy-Adapter (Apple-Schlaf-Aggregat, dedizierte Import-Endpoints) noch aktiv. |
### 9. Persistenz-Orchestrator für Schreibpfade
| | |
|---|---|
| **Prinzip** | Alle Schreibwege eines Domänenobjekts (REST, CSV, Legacy) laufen durch **einen** Orchestrator mit Nebenwirkungen (EAV, Evaluation). |
| **Begründung** | Konsistente Duplikat-Erkennung, Registry-Felder, keine divergierenden Insert-Logiken. |
| **Quelle** | `activity_persistence_orchestrator.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Bisher vor allem Aktivität; andere Domänen noch direkt in Routern. |
### 10. Registry als Feld-Kanon (Activity)
| | |
|---|---|
| **Prinzip** | Erlaubte persistierbare Felder für CSV/REST leiten sich aus `module_registry` ab, nicht aus Router-Hardcoding. |
| **Begründung** | Single Source of Truth für Import-Mappings und DB-Updates. |
| **Quelle** | `activity_data_canon.py`, `activity_persistence_orchestrator.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nur Activity vollständig; andere Module noch klassische Spalten-CRUD. |
### 11. EAV-Anreicherung als Read-Layer
| | |
|---|---|
| **Prinzip** | Session-Metriken (EAV) werden beim **Lesen** angereichert (`enrich_sessions_with_metrics`), nicht pro Consumer dupliziert. |
| **Begründung** | Ein Merge-Kanon für Liste, Detail, Export, Platzhalter. |
| **Quelle** | `activity_session_metrics.py`; `ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Domänenspezifisch (Training); Muster übertragbar. |
### 12. Scores als composable Layer
| | |
|---|---|
| **Prinzip** | Composite Scores (`scores.py`) kombinieren Domänen-Metriken mit nutzer-spezifischen Focus Weights — keine Score-Logik in Routern. |
| **Begründung** | Goal-Mode-/Focus-abhängige Gewichtung zentral, für KI und Dashboard gleich. |
| **Quelle** | `data_layer/scores.py`; Phase-0b-Fokus-System |
| **Tragfähigkeit** | **mittelhoch** |
| **Einschränkung** | Eng an Mitai-Zielsystem gekoppelt; Muster „gewichtete Composite Scores“ ist generisch. |
### 13. API-First: Router delegieren, rechnen nicht
| | |
|---|---|
| **Prinzip** | `routers/charts.py` und ähnliche Endpoints rufen Data-Layer-Funktionen auf und mappen auf HTTP — keine Trend-Berechnung im Router. |
| **Begründung** | Testbarkeit; Frontend ohne Business-Logik. |
| **Quelle** | `ARCHITECTURE.md` §1.2 API-First |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | `charts.py` ist groß (2246+ Zeilen) — viel Adapter-Code, aber Berechnung delegiert. |
### 14. serialize_dates / safe_float als Querschnitt
| | |
|---|---|
| **Prinzip** | JSON/API-Serialisierung (Dates, Decimal) zentral in `utils.py`, nicht pro Modul neu erfunden. |
| **Begründung** | PostgreSQL-Typen (DATE, DECIMAL) konsistent für API und Export. |
| **Quelle** | `data_layer/utils.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | — |
### 15. Extension Guide als verbindlicher Entwicklungsvertrag
| | |
|---|---|
| **Prinzip** | Neue Metriken folgen Template (Retrieve → Confidence → Early Return → Calculate → Return) und werden in `__init__.py` exportiert. |
| **Begründung** | Einheitliche Struktur für 97+ Funktionen und wachsende Codebase. |
| **Quelle** | `DATA_LAYER_EXTENSION_GUIDE.md` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Guide und Ist-Code divergieren teils (Modulgröße, `goals.py` noch nicht in `__init__`). |
---
## Nicht übernehmen
Muster, die sich nicht bewährt haben oder zu produktspezifisch sind:
1. **Berechnungslogik in `placeholder_resolver.py`** — Phase-0b-Legacy; Resolver soll nur formatieren/aggregieren, nicht rechnen.
2. **Paralleler Roh-SQL-Kontext in `prompt_executor.execute_prompt_with_data`** — lädt Modul-Rohdaten per SQL, obwohl Layer 1 existiert; zweite Wahrheit.
3. **Legacy Insights-Pfad (`insights._prepare_template_vars`)** — eigene Variablen-Vorbereitung ohne Data Layer.
4. **Import mit fachlicher Interpretation** — Apple-Schlaf-Aggregat und ähnliche Adapter verstecken Semantik im Ingest (Gitea #69).
5. **Monolithische Router mit Inline-Berechnung** — vor Phase 0c; gelegentlich noch Reste in nicht migrierten Pfaden.
6. **Interpretation vermischt mit Layer 1**`*_interpretation.py` liefert teils fertige Texte; für Familien-Architektur klar als Layer 2a/2b markieren oder auslagern.
7. **SQL-Duplikation in Chart-Payloads** — manche Payload-Builder führen eigene Queries statt ausschließlich Layer-1-Ergebnisse zu visualisieren.
8. **Hardcodierte Confidence global** — funktioniert, aber nicht pro Metrik/Domäne konfigurierbar; Skalierung in Multi-Tenant-Produktfamilie prüfen.
9. **Domänen-Module als Produktinhalt**`body_metrics`, TDEE, WHR etc. sind Mitai-spezifisch; **Schichtenmodell** übernehmen, **Formeln** nicht blind kopieren.
10. **Fehlende runtime-Validierung des Return-Schemas**`confidence`/`data_points` per Konvention, nicht per TypedDict/Pydantic erzwungen.
11. **Uneinheitliche Schreib-Orchestrierung** — nur Activity hat `persistence_orchestrator`; andere Domänen noch fragmentiert.
12. **Riesige Einzeldateien** — einige Metrik-Module >>500 Zeilen widersprechen eigenem Extension Guide.
---
## Modul-Inventar (Ist-Stand)
```
backend/data_layer/
├── Kern-Metriken (Layer 1)
│ ├── body_metrics.py
│ ├── nutrition_metrics.py
│ ├── activity_metrics.py
│ ├── recovery_metrics.py
│ ├── health_metrics.py
│ ├── scores.py
│ └── correlations.py
├── Visualisierung (Layer 2b)
│ ├── *_chart_payloads.py (nutrition, recovery, correlation)
│ └── *_viz.py (body, nutrition, fitness, recovery, history_overview)
├── KI / Format (Layer 2a-Nähe)
│ ├── prompt_output_compact.py
│ └── *_interpretation.py
├── Persistenz / EAV
│ ├── activity_persistence_orchestrator.py
│ ├── activity_session_metrics.py
│ └── activity_data_canon.py
├── Querschnitt
│ ├── utils.py
│ ├── reference_values.py
│ └── nutrition_body_merge.py
└── __init__.py (Exports)
```
**Konsumenten-Endpoints (Auswahl):** 20+ Chart-Endpoints in `routers/charts.py` (E1E5, A1A8, R1R5, C1C4).
---
## Verwandte Dokumentation
- Issue #53 Abschluss: [issue-53-phase-0c-multi-layer-architecture.md](../../../../docs/issues/issue-53-phase-0c-multi-layer-architecture.md)
- Extension Guide: [DATA_LAYER_EXTENSION_GUIDE.md](../../technical/DATA_LAYER_EXTENSION_GUIDE.md)
- Fachliche Datenarchitektur: [DATA_ARCHITECTURE.md](../../functional/DATA_ARCHITECTURE.md)
- Import-Grenze: [ARCHITECTURE.md](../../../rules/ARCHITECTURE.md) §8
- Platzhalter-Anbindung: [PLACEHOLDER_REGISTRY_FRAMEWORK.md](../../technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md)
- Prompt Engine (Konsument Layer 2a): [PROMPT_ENGINE_DESIGN_PRINCIPLES.md](./PROMPT_ENGINE_DESIGN_PRINCIPLES.md)
- Feature & Entitlement: [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md)
---
## Geplante Folgedokumente (Serie)
| # | Modul | Datei (geplant) |
|---|-------|-----------------|
| 1 | Prompt Engine | ✅ `PROMPT_ENGINE_DESIGN_PRINCIPLES.md` |
| 2 | Data Layer | ✅ dieses Dokument |
| 3 | Feature & Entitlement System | ✅ `FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md` |
| 4 | Registry-/Plugin-Muster | ✅ `REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md` |
| 5 | Auth & Session | ✅ `AUTH_SESSION_DESIGN_PRINCIPLES.md` |
| 6 | Universal Import | ✅ `UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md` |
| 7 | Dashboard Widgets | ✅ `DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md` |
| 8 | Navigation / IA | ✅ `NAVIGATION_IA_DESIGN_PRINCIPLES.md` |
| 9 | Migration & Deploy | ✅ `MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md` |

View File

@ -1,370 +0,0 @@
# Feature & Entitlement System Designprinzipien (Extraktion)
**Status:** Analyse / Arbeitspapier
**Stand:** 2026-07-04
**Geltungsbereich:** Membership-, Tier- und Feature-Limit-System (v9c) — keine Mitai-Domänenlogik, kein zentrales SSO/Stripe (Vision)
**Serie:** Designprinzipien für Produktfamilie · Dokument 3 von n
**Vorgänger:** [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md)
**Kernkomponenten:**
| Bereich | Pfade |
|---------|-------|
| Entitlement-Auflösung | `backend/auth.py` (`get_effective_tier`, `check_feature_access`, `increment_feature_usage`) |
| Monitoring | `backend/feature_logger.py` |
| Nutzer-API | `backend/routers/subscription.py`, `backend/routers/features.py` |
| Admin | `routers/tiers_mgmt.py`, `tier_limits.py`, `coupons.py`, `access_grants.py`, `user_restrictions.py` |
| Widget-Gating | `backend/dashboard_widget_entitlements.py`, `widget_feature_requirements_db.py` |
| Frontend | `UsageBadge.jsx`, Feature-Usage in Seiten (z. B. `Analysis.jsx`, `WeightPage`) |
| Doku | `MEMBERSHIP_SYSTEM.md`, `FEATURE_ENFORCEMENT.md`, `CENTRAL_SUBSCRIPTION_SYSTEM.md` (Vision) |
---
## Modul
**Feature & Entitlement System** (Membership v9c)
Zentrale Schicht für **„Darf dieser Nutzer diese Funktion wie oft nutzen?“** — unabhängig von Auth (Identität) und unabhängig von fachlicher Business-Logik in Routern.
---
## Fachliche Verantwortung
Das System übernimmt:
1. **Feature-Registry** — Deklarative Liste aller limitierbaren Produktfunktionen mit Metadaten.
2. **Tier-Auflösung** — Effektiver Tarif eines Profils (Basis-Tier + zeitliche Grants).
3. **Limit-Auflösung** — Pro Feature: Override → Tier-Limit → Feature-Default.
4. **Usage-Tracking** — Zähler für Count-Features mit optionalem Reset (daily/monthly/never).
5. **Enforcement** — HTTP 403 bei Überschreitung; Frontend-Vorschaum via Badges.
6. **Beobachtbarkeit** — Strukturiertes JSON-Logging aller Access-Checks.
7. **Promotionen** — Coupons → Access Grants (temporäre Tier-Elevation, Pause/Resume).
Es übernimmt **nicht**:
- Login, Session, Passwort (Auth-Modul)
- Zahlungsabwicklung / Stripe (geplant, `CENTRAL_SUBSCRIPTION_SYSTEM.md`)
- Mandanten-Isolation (Org/Workspace) — Entitlements sind **profile-scoped**
- Inhaltliche Berechtigung pro Datensatz (nur Feature-Gates)
### Zwei Entscheidungsebenen
| Ebene | Frage | Funktion |
|-------|--------|----------|
| **Tier** | Welcher Tarif gilt? | `get_effective_tier()` |
| **Feature** | Darf Feature X genutzt werden (wie oft)? | `check_feature_access()` |
Tier beeinflusst Feature-Limits über `tier_limits`; User-Overrides können Limits unabhängig vom Tier setzen.
### Administrierte Konfigurationen
| Konfiguration | Speicherort | Admin-UI |
|---------------|-------------|----------|
| Feature-Definitionen | `features` | Admin Features |
| Tier-Stufen | `tiers` | Admin Tiers |
| Tier × Feature Matrix | `tier_limits` | Admin Tier Limits |
| User-Overrides | `user_feature_restrictions` | Admin User Restrictions |
| Coupons | `coupons`, `coupon_redemptions` | Admin Coupons |
| Temporäre Tier-Grants | `access_grants` | (via Coupon/Admin) |
| Widget → Feature Mapping | `widget_feature_requirements`, Katalog | Admin Widget Features |
| Usage-Zähler | `user_feature_usage` | (automatisch) |
**Nicht hardcodiert:** Limits pro Tier, Feature-Metadaten, Coupon-Parameter, User-Overrides.
**Hardcodiert (Code):** Feature-IDs in Routern (`'ai_calls'`, `'weight_entries'`, …), Reset-Berechnung, 4-Phasen-Muster, 11 initial registrierte Features.
### Auflösungs-Hierarchien
**Effektiver Tier** (`get_effective_tier`):
1. Aktiver `access_grants`-Eintrag (`is_active`, `valid_from`/`valid_until`)
2. Fallback: `profiles.tier`
**Feature-Limit** (`check_feature_access` → `_check_impl`):
1. `user_feature_restrictions.limit_value` (höchste Priorität)
2. `tier_limits` für effektiven Tier
3. `features.default_limit`
**Limit-Semantik:**
| `limit_type` | Bedeutung |
|--------------|-----------|
| `count` | Zählbares Kontingent; `used < limit` |
| `boolean` | An/Aus; `limit == 1` erlaubt, `0` gesperrt |
| `limit_value` | Bedeutung |
|---------------|-----------|
| `NULL` | Unbegrenzt |
| `0` | Deaktiviert |
| `> 0` | Kontingent oder Boolean „an“ |
### Rollen
| Rolle | Darf |
|-------|------|
| **Admin** | Features/Tiers/Limits/Coupons/Restrictions CRUD; alle Nutzer-Overrides |
| **Nutzer** | Eigene Subscription/Usage lesen (`/subscription/me`, `/features/usage`); keine Limit-Änderung |
Enforcement gilt für alle authentifizierten Nutzer gleich — Admins haben keine automatische Bypass-Logik in `check_feature_access`.
### Versionierung, Freigabe, Test
| Mechanismus | Status |
|-------------|--------|
| 4-Phasen-Rollout (Monitor → UI → Enforce) | ✅ dokumentiert & angewendet |
| JSON-Log `feature-usage.log` | ✅ Phase 2 Monitoring |
| DB-Migration v9c für Schema | ✅ |
| Automatisierte Enforcement-Tests pro Router | ⚠️ teilweise (Widgets getestet) |
| Zentrale Policy „jeder Endpoint muss checken“ | ❌ nicht erzwungen |
---
## Designprinzipien
### 1. Feature-Registry statt hardcodierter Limits
| | |
|---|---|
| **Prinzip** | Jedes limitierbare Produkt-Feature ist Zeile in `features` — neue Features ohne Schema-Migration für Limits. |
| **Begründung** | Admin-UI, Usage-API und Backend-Checks teilen dieselbe ID und Metadaten. |
| **Quelle** | `MEMBERSHIP_SYSTEM.md` § Feature-Registry; `routers/features.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Feature-IDs müssen trotzdem in Router-Code referenziert werden. |
### 2. Eine Auflösungsfunktion für Entitlements
| | |
|---|---|
| **Prinzip** | Alle Backend- und Widget-Checks rufen `check_feature_access(profile_id, feature_id)` auf. |
| **Begründung** | Keine duplizierte Tier/Limit-Logik in Routern, Widgets oder Frontend. |
| **Quelle** | `auth.py`; `dashboard_widget_entitlements.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nicht alle Endpoints nutzen es (z. B. `/prompts/execute` fehlt). |
### 3. Getrennte Tier- und Feature-Auflösung
| | |
|---|---|
| **Prinzip** | `get_effective_tier()` für Tarif; `check_feature_access()` für konkretes Feature — Tier ist Input, nicht Output der Feature-Prüfung. |
| **Begründung** | Temporäre Grants heben Tier an; User-Override kann einzelnes Feature unabhängig anpassen. |
| **Quelle** | `auth.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | `get_effective_tier` im Code einfacher als in `MEMBERSHIP_SYSTEM.md` (kein `tier_locked`, Trial nicht in Tier-Funktion). |
### 4. Prioritäts-Kette für Limits
| | |
|---|---|
| **Prinzip** | User-Override > Tier-Limit > Feature-Default — explizit und dokumentiert. |
| **Begründung** | Support/Beta-Fälle ohne Tier-Wechsel; vorhersehbares Verhalten. |
| **Quelle** | `_check_impl()` in `auth.py`; `MEMBERSHIP_SYSTEM.md` § Zugriffs-Hierarchie |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | `user_feature_restrictions.enabled` im Schema, aber nicht in `_check_impl` ausgewertet. |
### 5. Count vs. Boolean als zwei Feature-Klassen
| | |
|---|---|
| **Prinzip** | Zählbare Aktionen (`count` + Usage) vs. Schalter-Features (`boolean`, kein Counter). |
| **Begründung** | Pipeline-An/Aus vs. monatliche KI-Calls — unterschiedliche UX und Backend-Logik. |
| **Quelle** | `features.limit_type`; `FEATURE_ENFORCEMENT.md` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Boolean-Features nutzen `limit_value` 0/1 — leicht mit Count zu verwechseln. |
### 6. Reset-Perioden für Count-Features
| | |
|---|---|
| **Prinzip** | `reset_period`: `never` \| `daily` \| `monthly` — Counter-Reset in `check_feature_access` bei abgelaufenem `reset_at`. |
| **Begründung** | Monats-Kontingente vs. Lifetime-Limits in einem Modell. |
| **Quelle** | `auth.py``_calculate_next_reset()` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Reset beim Check, nicht per Cron — Edge Cases bei seltenem Zugriff. |
### 7. Usage nur bei neuen Entitäten incrementieren
| | |
|---|---|
| **Prinzip** | `increment_feature_usage()` nur nach **INSERT**, nicht nach UPDATE/Upsert-Deduplikat. |
| **Begründung** | Limits messen „neue Nutzung“, nicht Bearbeitung bestehender Daten. |
| **Quelle** | `FEATURE_ENFORCEMENT.md` § Wichtige Regeln |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Bulk-Import muss explizit zählen; Fehler anfällig. |
### 8. Vier-Phasen-Rollout (Observe before Enforce)
| | |
|---|---|
| **Prinzip** | Phase 1 Cleanup → 2 Logging → 3 Frontend-Badges → 4 HTTP 403. |
| **Begründung** | Limits einführen ohne blind Nutzer zu blockieren; Daten für Limit-Kalibrierung. |
| **Quelle** | `FEATURE_ENFORCEMENT.md` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Disziplin pro Feature; kein zentraler Feature-Flag pro Endpoint-Phase. |
### 9. Strukturiertes Feature-Logging
| | |
|---|---|
| **Prinzip** | Jeder Check: `log_feature_usage(profile_id, feature_id, access, action)` → JSON in `feature-usage.log`. |
| **Begründung** | Audit, Debugging, Kalibrierung — auch wenn noch nicht enforced. |
| **Quelle** | `feature_logger.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Log-Pfad `/app/logs` container-spezifisch. |
### 10. Defense in Depth: API 403 + Frontend-Gate
| | |
|---|---|
| **Prinzip** | Backend blockiert autoritativ; Frontend zeigt `UsageBadge`, deaktiviert Buttons, Tooltip bei Limit. |
| **Begründung** | UX (frühes Feedback) + Sicherheit (API nicht umgehbar via curl). |
| **Quelle** | `FEATURE_ENFORCEMENT.md`; `UsageBadge.jsx` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Frontend-Gate optional pro Seite; nicht generisch erzwungen. |
### 11. Nutzer-Usage-API ohne Code-Änderung bei neuen Features
| | |
|---|---|
| **Prinzip** | `GET /features/usage` iteriert alle aktiven `features` und ruft `check_feature_access` pro Zeile. |
| **Begründung** | Neues DB-Feature erscheint automatisch in Quota-Übersicht. |
| **Quelle** | `routers/features.py``get_feature_usage()` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Frontend muss Feature-ID kennen, um Badge zu binden. |
### 12. Access Grants für temporäre Tier-Elevation
| | |
|---|---|
| **Prinzip** | Coupons/Admin erzeugen `access_grants`; effektiver Tier steigt zeitlich begrenzt. |
| **Begründung** | Promotions, Partner (Wellpass), Trials ohne permanente Tier-Änderung. |
| **Quelle** | `access_grants`; `routers/coupons.py` (Pause/Resume) |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Coupon-Stacking-Logik komplex; dokumentiert vs. Code prüfen bei Neuentwicklung. |
### 13. Entitlements als Querschnitt für UI-Module (Widgets)
| | |
|---|---|
| **Prinzip** | Dashboard-Widgets mappen auf `features.id`; Katalog liefert `allowed` pro Profil. |
| **Begründung** | Tier-Logik nicht in React-Widgets duplizieren (`DASHBOARD_WIDGETS_AGENT_GUIDE` §0). |
| **Quelle** | `dashboard_widget_entitlements.py`; `ARCHITECTURE.md` §9 |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Widget-Sichtbarkeit ≠ API-Schutz — Chart-Endpoints brauchen eigenes Gating (A4). |
### 14. Admin-konfigurierbare Tier × Feature Matrix
| | |
|---|---|
| **Prinzip** | `tier_limits` trennt Tier-Definition von Limits; Tiers ohne hardcodierte Spalten pro Feature. |
| **Begründung** | Neue Tiers/Preise ohne Code-Deploy der Limit-Logik. |
| **Quelle** | `MEMBERSHIP_SYSTEM.md` § Tier-System; `tier_limits` Tabelle |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Tier-Namen in Seed-Daten (`free`, `premium`, …) — erweiterbar, aber Konvention. |
### 15. NULL = unlimited, 0 = disabled
| | |
|---|---|
| **Prinzip** | Einheitliche Semantik für Limit-Werte in allen Schichten. |
| **Begründung** | Vermeidet Sonderfälle „-1 means unlimited“; klare Admin-UI. |
| **Quelle** | `_check_impl()` in `auth.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | SQL NULL vs. Python None — konsistent, aber in UI erklärungsbedürftig. |
---
## Registrierte Features (Referenz)
| Feature ID | Typ | Reset | Typische Aktion |
|------------|-----|-------|-----------------|
| `weight_entries` | count | never | Gewicht anlegen |
| `circumference_entries` | count | never | Umfang anlegen |
| `caliper_entries` | count | never | Caliper anlegen |
| `activity_entries` | count | monthly | Training anlegen/import |
| `nutrition_entries` | count | monthly | Ernährung anlegen/import |
| `photos` | count | monthly | Foto hochladen |
| `ai_calls` | count | monthly | KI-Analyse |
| `ai_pipeline` | boolean | — | Pipeline-Analyse |
| `data_export` | count | monthly | Export/PDF |
| `data_import` | count | monthly | ZIP/Universal-Import |
**Enforcement-Lücken (Ist):** `routers/prompts.py` (`/execute`, `/execute-stream`) ohne `check_feature_access` — Legacy `insights.py` hat Enforcement für `ai_calls`/`ai_pipeline`.
---
## Nicht übernehmen
1. **Dokumentations-Drift**`MEMBERSHIP_SYSTEM.md` („Enforcement deaktiviert“) vs. `FEATURE_ENFORCEMENT.md` (Phase 4 komplett) vs. Ist-Code; bei Neuentwicklung einen Kanon festlegen.
2. **Unvollständige Tier-Auflösung** — Doku beschreibt `tier_locked`, Trial-in-Tier; Code nutzt primär Grants + `profiles.tier`. Trial (`trial_ends_at`) eher UI-Banner als Tier-Engine.
3. **Feature-IDs in Routern verstreut** — kein zentraler Endpoint-Registry-Eintrag „welcher Router prüft welches Feature“.
4. **Check und Increment nicht atomar** — Race bei parallelen Requests möglich; kein DB-Level Locking.
5. **Legacy Profil-Spalten parallel**`ai_enabled`, `ai_limit_day`, `export_enabled` in Sessions-Query neben Feature-System.
6. **Frontend ohne Backend-Gate** — reine UI-Deaktivierung ohne 403 ist unsicher (manche Seiten nur teilweise umgesetzt).
7. **Boolean via limit_value 0/1** — funktioniert, aber für Familien-Architektur explizites `enabled`-Flag oder Capability-Tokens erwägen.
8. **Unused Schema-Felder**`user_feature_restrictions.enabled` nicht in Auflösung eingebunden.
9. **App-lokales Abo ohne Zahlungsanbindung** — Stripe/SSO nur Vision (`CENTRAL_SUBSCRIPTION_SYSTEM.md`); nicht als fertiges Familien-Muster übernehmen.
10. **Profile als Entitlement-Subject** — kein Org/Mandant; Multi-App-Familie braucht separates Identity/Subscription-Boundary.
11. **Self-hosted Tier als Sonderfall**`selfhosted` ist Deploy-Modell, kein generisches SaaS-Tier-Muster.
12. **Increment-Schleifen bei Bulk**`for _ in range(new_entries): increment_feature_usage()` — ineffizient; batch-Inkrement besser.
---
## Modul-Inventar (Ist-Stand)
```
backend/
├── auth.py # get_effective_tier, check_feature_access, increment_feature_usage
├── feature_logger.py # JSON-Logging
├── dashboard_widget_entitlements.py # Widget allowed + Layout-Sanitisierung
├── widget_feature_requirements_db.py
└── routers/
├── subscription.py # /me, /usage, /limits (Nutzer)
├── features.py # Admin CRUD + /usage, /check-access
├── tiers_mgmt.py, tier_limits.py
├── coupons.py, access_grants.py
└── user_restrictions.py
frontend/src/components/
└── UsageBadge.jsx # Quota-Anzeige (Phase 3)
```
**DB (v9c):** `features`, `tiers`, `tier_limits`, `user_feature_restrictions`, `user_feature_usage`, `coupons`, `coupon_redemptions`, `access_grants`, `user_activity_log`
---
## Verwandte Dokumentation
- Membership-Detail: [MEMBERSHIP_SYSTEM.md](../../technical/MEMBERSHIP_SYSTEM.md)
- Enforcement-Howto: [FEATURE_ENFORCEMENT.md](../../architecture/FEATURE_ENFORCEMENT.md)
- Vision Produktfamilie: [CENTRAL_SUBSCRIPTION_SYSTEM.md](../../technical/CENTRAL_SUBSCRIPTION_SYSTEM.md)
- Widget-Gating: [DASHBOARD_WIDGETS_AGENT_GUIDE.md](../../technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md) §0
- Prompt Engine (Enforcement-Lücke): [PROMPT_ENGINE_DESIGN_PRINCIPLES.md](./PROMPT_ENGINE_DESIGN_PRINCIPLES.md)
---
## Geplante Folgedokumente (Serie)
| # | Modul | Status |
|---|-------|--------|
| 1 | Prompt Engine | ✅ |
| 2 | Data Layer | ✅ |
| 3 | Feature & Entitlement | ✅ dieses Dokument |
| 4 | Registry-/Plugin-Muster | ✅ |
| 5 | Auth & Session | ✅ |
| 6 | Universal Import | ✅ |
| 7 | Dashboard Widgets | ✅ |
| 8 | Navigation / IA | ✅ |
| 9 | Migration & Deploy | ✅ `MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md` |

View File

@ -1,369 +0,0 @@
# Migration & Deploy Designprinzipien (Extraktion)
**Status:** Analyse / Arbeitspapier
**Stand:** 2026-07-04
**Geltungsbereich:** DB-Migrationen, Container-Startup, CI/CD-Deploy — keine Anwendungsdomäne
**Serie:** Designprinzipien für Produktfamilie · Dokument 9 von n (Abschluss)
**Vorgänger:** [NAVIGATION_IA_DESIGN_PRINCIPLES.md](./NAVIGATION_IA_DESIGN_PRINCIPLES.md)
**Kernkomponenten:**
| Bereich | Pfade |
|---------|-------|
| DB-Init & Migrationen | `backend/db_init.py`, `backend/startup.sh` |
| Migrationen | `backend/migrations/XXX_*.sql` |
| Basis-Schema (Greenfield) | `backend/schema.sql` |
| Tracking | Tabelle `schema_migrations` |
| Compose Prod/Dev | `docker-compose.yml`, `docker-compose.dev-env.yml` |
| CI/CD | `.gitea/workflows/deploy-dev.yml`, `deploy-prod.yml`, `test.yml` |
| Versionierung | `backend/version.py` (`APP_VERSION`, `DB_SCHEMA_VERSION`, `MODULE_VERSIONS`) |
| Doku (operativ) | `MIGRATIONS.md` |
| Architektur-Regeln | `.claude/rules/ARCHITECTURE.md` §2, §7 |
---
## Modul
**Migration & Deploy**
Automatische **PostgreSQL-Schema-Evolution** beim Container-Start plus **Git-getriebene Deploy-Pipeline** (develop → Dev, main → Prod) auf selbst-gehosteter Infrastruktur (Docker auf Raspberry Pi).
---
## Fachliche Verantwortung
Das Modul übernimmt:
1. **Schema-Migrationen** — Nummerierte SQL-Dateien, idempotent wo möglich, getrackt in `schema_migrations`.
2. **Startup-Orchestrierung** — Postgres ready → Schema/Migrationen → optional SQLite-Import → Uvicorn.
3. **Umgebungstrennung** — Dev (`3099`/`8099`) vs. Prod (`3002`/`8002`), getrennte DBs/Volumes.
4. **Deploy-Automatisierung** — Push auf Branch → Runner → `git reset --hard``docker compose build --no-cache` → Health-Check.
5. **Post-Deploy-Tests** — Pytest/Lint/Frontend-Build gegen **deployed** Container auf dem Runner.
6. **Versions-Metadaten** — App-/Modul-Version und dokumentierte `DB_SCHEMA_VERSION`.
Es übernimmt **nicht**:
- Fachliche Datenberechnungen (→ Data Layer)
- Automatisches Downgrade/Rollback von Schema
- Blue-Green oder Multi-Region-Deploy
### Deploy-Pipeline (Happy Path)
```
Entwickler: commit → push develop
→ Gitea Runner: deploy-dev.yml
→ cd /home/lars/docker/bodytrack-dev
→ git fetch + reset --hard origin/develop
→ docker compose -f docker-compose.dev-env.yml build --no-cache && up -d
→ backend startup.sh → db_init.py (Migrationen)
→ curl localhost:8099/api/auth/status
→ test.yml (push + nach Deploy): pytest im Container, py_compile, npm run build
Prod: PR develop → main → deploy-prod.yml (Port 8002, bodytrack/)
```
---
## Administrierte vs. code-definierte Konfiguration
| Konfiguration | Speicherort | Wer pflegt? |
|---------------|-------------|-------------|
| Migration-SQL | `backend/migrations/` | Entwickler |
| Welche Migrationen angewendet | `schema_migrations` (DB) | Automatisch |
| Greenfield-Basis | `schema.sql` | Entwickler (selten) |
| Compose/Ports/Env | `docker-compose*.yml`, `.env` auf Server | Betrieb |
| Deploy-Workflow | `.gitea/workflows/*.yml` | Entwickler |
| App-Version / Changelog | `backend/version.py` | Entwickler (pro Release) |
| Prod-Geheimnisse | Server-`.env`, nicht im Repo | Betrieb |
---
## Designprinzipien
### 1. Migrationen beim Container-Start ( nicht manuell in Prod)
| | |
|---|---|
| **Prinzip** | `startup.sh` ruft `db_init.py` auf **bevor** Uvicorn startet; pending Migrationen werden automatisch angewendet. |
| **Begründung** | Kein vergessenes Schema-Update; Deploy und DB-Stand bleiben gekoppelt. |
| **Quelle** | `startup.sh`, `db_init.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Fehlgeschlagene Migration blockiert API-Start (`sys.exit(1)`). |
### 2. Nummeriertes Datei-Pattern als Gate
| | |
|---|---|
| **Prinzip** | Nur `\d{3}_*.sql` wird ausgeführt (z. B. `054_activity_session_metrics_eav.sql`); alles andere wird ignoriert. |
| **Begründung** | Sortierbare Reihenfolge; Ad-hoc-Skripte (`check_features.sql`, `v9c_*.sql`) verunreinigen nicht den Lauf. |
| **Quelle** | `run_migrations()` Regex |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Legacy-Dateien ohne Nummer liegen noch im Ordner (historischer Ballast). |
### 3. Tracking-Tabelle als Single Source of „applied“
| | |
|---|---|
| **Prinzip** | `schema_migrations(filename)` — jede erfolgreiche Datei genau einmal eingetragen; Pending = Dateien minus Applied. |
| **Begründung** | Idempotenter Startup; wiederholter Container-Start wendet nichts doppelt an. |
| **Quelle** | `ensure_migration_table`, `apply_migration` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Kein checksum — geänderte Datei nach Apply wird nicht erneut ausgeführt. |
### 4. Alphabetische Reihenfolge = Migrations-Reihenfolge
| | |
|---|---|
| **Prinzip** | `sorted(glob)` — dreistellige Präfixe (`001`, `054`, `061`) definieren die Apply-Order. |
| **Begründung** | Einfach, git-freundlich, keine separate Migrations-Registry. |
| **Quelle** | `run_migrations()` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nummern-Kollisionen oder nachträgliches Einfügen erfordern Disziplin (immer nächste freie Nummer). |
### 5. Fail-Fast bei Migrationsfehler
| | |
|---|---|
| **Prinzip** | Schlägt eine Migration fehl → kein Commit in Tracking (bei Exception vor INSERT), Prozess exit 1, Container unhealthy. |
| **Begründung** | API läuft nicht mit halb angewendetem Schema. |
| **Quelle** | `apply_migration`, `main` in `db_init.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Manueller Recovery-Prozess nötig (siehe MIGRATIONS.md Rollback). |
### 6. Greenfield: schema.sql, Bestand: nur Migrationen
| | |
|---|---|
| **Prinzip** | Existiert `profiles` nicht → einmalig `schema.sql` laden; danach nur noch nummerierte Migrationen. |
| **Begründung** | Frische Instanz schnell bootstrapped; langlebige DBs evolvieren incremental. |
| **Quelle** | `check_table_exists`, `load_schema` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | `schema.sql` kann hinter Migrationen zurückfallen wenn nicht gepflegt. |
### 7. Idempotente DDL bevorzugen
| | |
|---|---|
| **Prinzip** | `ADD COLUMN IF NOT EXISTS`, `CREATE INDEX IF NOT EXISTS`, defensive UPDATEs — Migration soll mehrfach ausführbar sein ohne Schaden. |
| **Begründung** | Recovery nach partiellem Apply; manuelles Re-Run sicherer. |
| **Quelle** | `MIGRATIONS.md` Best Practices |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nicht alle Änderungen sind idempotent (DROP, irreversible Datenmigration). |
### 8. Kein psql-Meta in Migrationsdateien
| | |
|---|---|
| **Prinzip** | Nur SQL — kein `\echo`, `\i`, `\connect`; Ausführung via psycopg2, nicht interaktiv. |
| **Begründung** | Parser/Runner versteht nur SQL-Statements. |
| **Quelle** | `MIGRATIONS.md`, `apply_migration` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | — |
### 9. Schema-Änderung = nummerierte Migration (nie ad-hoc in Prod)
| | |
|---|---|
| **Prinzip** | Neue Tabellen/Spalten **nur** via `backend/migrations/XXX_*.sql`; nicht direkt in laufender Prod-DB editieren. |
| **Begründung** | Reproduzierbarkeit Dev→Prod; Review im Git-Diff. |
| **Quelle** | ARCHITECTURE.md, CLAUDE.md |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Agent-Regel — technisch nicht erzwungen. |
### 10. DB_SCHEMA_VERSION als dokumentierter Marker
| | |
|---|---|
| **Prinzip** | `backend/version.py``DB_SCHEMA_VERSION` bei Schema-Änderung manuell bumpen (Format z. B. `YYYYMMDD` + Suffix). |
| **Begründung** | API `/api/version` und Changelog zeigen Schema-Stand unabhängig von App-Minor. |
| **Quelle** | ARCHITECTURE.md §2.6 |
| **Tragfähigkeit** | **mittelhoch** |
| **Einschränkung** | Nicht automatisch aus `schema_migrations` abgeleitet — Drift möglich. |
### 11. Branch → Umgebung (develop / main)
| | |
|---|---|
| **Prinzip** | `develop` → Dev-Deploy automatisch; `main` → Prod-Deploy automatisch; Prod nur nach expliziter Freigabe/Merge. |
| **Begründung** | Klare Promotion; Dev als Integrationsumgebung. |
| **Quelle** | Workflows, CLAUDE.md Deployment |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Kein Staging-Branch zwischen Dev und Prod. |
### 12. Deploy-Arbeitskopie = exakt Remote-Branch
| | |
|---|---|
| **Prinzip** | Runner: `git fetch` + `git reset --hard origin/<branch>` — keine `pull`-Merge-Konflikte, kein schmutziger `package-lock` auf dem Pi. |
| **Begründung** | Reproduzierbarer Deploy-Baum; Fix aus GUI-IA-Abnahme 2026-04-05. |
| **Quelle** | `deploy-prod.yml`, `deploy-dev.yml` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Lokale Hotfixes auf dem Server werden beim Deploy überschrieben. |
### 13. Immutabler Build pro Deploy (`--no-cache`)
| | |
|---|---|
| **Prinzip** | `docker compose build --no-cache` bei jedem Deploy — frisches Image aus Dockerfile + Repo-Stand. |
| **Begründung** | Keine veralteten Layer; Migrationen und Code garantiert im Image. |
| **Quelle** | Deploy-Workflows |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Langsamere Deploys; kein Registry-basiertes Image-Promotion. |
### 14. Health-Check nach Deploy
| | |
|---|---|
| **Prinzip** | Nach `up -d`: kurz warten, dann `curl -sf …/api/auth/status` (8099 Dev / 8002 Prod). |
| **Begründung** | Minimale Smoke-Verification dass API antwortet (inkl. DB-Init durchlaufen). |
| **Quelle** | Deploy-Workflows |
| **Tragfähigkeit** | **mittelhoch** |
| **Einschränkung** | Prüft nicht fachliche Endpoints oder Migration-Inhalt. |
### 15. Persistente Volumes für Daten und Fotos
| | |
|---|---|
| **Prinzip** | Postgres-Daten, `/app/data`, `/app/photos` in benannten/external Volumes — überleben Container-Rebuild. |
| **Begründung** | Deploy = neues Image, nicht Datenverlust. |
| **Quelle** | `docker-compose*.yml` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Volume-Backup/Restore ist Betriebsaufgabe außerhalb Repo. |
### 16. Postgres Healthcheck vor Backend-Start
| | |
|---|---|
| **Prinzip** | `depends_on: condition: service_healthy` — Backend startet erst wenn DB `pg_isready`. |
| **Begründung** | `wait_for_postgres` in db_init ist zweite Absicherung; reduziert Race beim ersten Start. |
| **Quelle** | Compose-Files |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | — |
### 17. Tests gegen deployed Stack (Self-Hosted Runner)
| | |
|---|---|
| **Prinzip** | `test.yml` führt pytest **im laufenden Backend-Container** auf dem Pi aus, nicht in isolierter GitHub-Cloud. |
| **Begründung** | Tests laufen gegen echte Dev/Prod-Compose-Umgebung des Projekts. |
| **Quelle** | `.gitea/workflows/test.yml` |
| **Tragfähigkeit** | **mittel** |
| **Einschränkung** | Prod-Deploy triggert Tests auf Prod-Pfad — Risiko wenn Tests schreibend; `-m 'not slow'` begrenzt Laufzeit. |
### 18. Feste Ports pro Umgebung
| | |
|---|---|
| **Prinzip** | Dev `3099/8099`, Prod `3002/8002` — nicht ändern (Reverse Proxy/Fritz!Box hängen daran). |
| **Begründung** | Externe URLs (`dev.mitai.jinkendo.de`, `mitai.jinkendo.de`) stabil. |
| **Quelle** | CLAUDE.md, Compose |
| **Tragfähigkeit** | **hoch** (betriebsspezifisch) |
| **Einschränkung** | Andere Projekte brauchen eigene Port-Matrix. |
### 19. Prod-Schutz: Deploy nur über Git
| | |
|---|---|
| **Prinzip** | Keine direkten Prod-Container-/DB-Schreibzugriffe für Automation; Prod-Änderung = Merge `main` → Workflow. |
| **Begründung** | Audit-Trail, Review, keine Drift. |
| **Quelle** | ARCHITECTURE.md §7.1, `/deploy` Command |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Menschlicher SSH-Zugriff bleibt möglich — Prozess, nicht Technik. |
### 20. Versions-Bump als Release-Disziplin
| | |
|---|---|
| **Prinzip** | Jede lieferbare Änderung: `APP_VERSION`, betroffene `MODULE_VERSIONS`, `CHANGELOG` in `version.py`; bei Schema auch `DB_SCHEMA_VERSION`. |
| **Begründung** | `/api/version`, Support, Korrelation Deploy ↔ Code. |
| **Quelle** | ARCHITECTURE.md §2.5, `deploy.md` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Frontend-`version.js` in Spec erwähnt, im Repo teils nicht vorhanden — Dual-Bump unvollständig. |
---
## Nicht übernehmen
1. **Unnummerierte Migrationsdateien**`v9c_*.sql`, `check_*.sql` werden nicht auto-applied; nicht als Vorbild.
2. **Migration-Datei nach Apply ändern** — Tracking verhindert Re-Run; neue Nummer statt Edit.
3. **Automatischer Downgrade** — nicht implementiert; Rollback manuell + Tracking-Eintrag löschen.
4. **Direktes Schema in Prod** — immer Git-Migration + Deploy.
5. **Breaking DROP ohne Koordination** — App-Code und Migration in einem Release.
6. **psql-Metacommands in `.sql`** — bricht Python-Runner.
7. **`git pull` auf Deploy-Server** — Merge-Schmutz; `reset --hard` ist das Muster.
8. **Prod-Deploy ohne Dev-Validierung** — develop-First ist implizite Policy.
9. **Schema-Drift ohne `DB_SCHEMA_VERSION`-Bump** — dokumentarische Lücke.
10. **Cached Docker-Build als Default** — Mitai wählt Reproduzierbarkeit über Geschwindigkeit.
11. **Migrationen außerhalb Container-Start vergessen** — manuelles psql in Prod als Normalfall.
12. **Hardcoded Seed-Daten in Migration** — produktive User/Secrets nicht in SQL.
13. **Port-Änderung „nebenbei“** — Infrastruktur-Kopplung.
14. **Tests nur lokal, nie auf Runner-Stack** — Mitai testet bewusst post-deploy im Pi-Container (Trade-off verstehen).
15. **Transaktionssteuerung in SQL-Datei** — Runner committet pro Datei; komplexe multi-step Rollbacks nicht eingebaut.
---
## Modul-Inventar (Ist-Stand)
```
backend/
├── db_init.py # wait, schema, run_migrations, sqlite import
├── startup.sh # db_init → uvicorn
├── schema.sql # Greenfield
├── migrations/ # 001061+ nummeriert (+ Legacy ohne Nummer)
└── version.py # APP_VERSION, DB_SCHEMA_VERSION, MODULE_VERSIONS
docker-compose.yml # Prod: 3002/8002
docker-compose.dev-env.yml # Dev: 3099/8099
.gitea/workflows/
├── deploy-dev.yml # push develop
├── deploy-prod.yml # push main
└── test.yml # pytest, lint, npm build on Pi
Server (Pi):
/home/lars/docker/bodytrack-dev/ # develop
/home/lars/docker/bodytrack/ # main
```
**Migrationen (Stand):** 60+ nummerierte Dateien (`001` … `061`); höchste Nummer im Repo prüfen vor neuer Migration.
---
## Verwandte Dokumentation
- Operativ: [MIGRATIONS.md](../../technical/MIGRATIONS.md)
- Architektur: `.claude/rules/ARCHITECTURE.md` §2 (Versionierung), §7 (Prod-Schutz)
- Deploy-Command: `.claude/commands/deploy.md`, `merge-to-prod.md`
- Import/Migration-Grenze: [UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md](./UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md)
- Auth auf Prod: [AUTH_SESSION_DESIGN_PRINCIPLES.md](./AUTH_SESSION_DESIGN_PRINCIPLES.md)
---
## Serie Übersicht (abgeschlossen)
| # | Modul | Status |
|---|-------|--------|
| 1 | Prompt Engine | ✅ `PROMPT_ENGINE_DESIGN_PRINCIPLES.md` |
| 2 | Data Layer | ✅ `DATA_LAYER_DESIGN_PRINCIPLES.md` |
| 3 | Feature & Entitlement | ✅ `FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md` |
| 4 | Registry / Plugin (Meta) | ✅ `REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md` |
| 5 | Auth & Session | ✅ `AUTH_SESSION_DESIGN_PRINCIPLES.md` |
| 6 | Universal Import | ✅ `UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md` |
| 7 | Dashboard Widgets | ✅ `DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md` |
| 8 | Navigation / IA | ✅ `NAVIGATION_IA_DESIGN_PRINCIPLES.md` |
| 9 | Migration & Deploy | ✅ dieses Dokument |

View File

@ -1,346 +0,0 @@
# Navigation & Informationsarchitektur Designprinzipien (Extraktion)
**Status:** Analyse / Arbeitspapier
**Stand:** 2026-07-04
**Geltungsbereich:** App-Navigation, Bereichs-Shells, Admin-IA, Responsive Shell — keine Seiteninhalte oder Domänenlogik
**Serie:** Designprinzipien für Produktfamilie · Dokument 8 von n
**Vorgänger:** [DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md](./DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md)
**Kernkomponenten:**
| Bereich | Pfade |
|---------|-------|
| Hauptnavigation | `frontend/src/config/appNav.js` |
| Erfassung | `frontend/src/config/captureNav.js`, `layouts/CaptureShell.jsx` |
| Einstellungen | `frontend/src/config/settingsNav.js`, `layouts/SettingsShell.jsx` |
| Admin | `frontend/src/config/adminNav.js`, `layouts/AdminShell.jsx`, `RequireAdmin.jsx` |
| KI-Analyse (Kategorien) | `frontend/src/config/analysisCategories.js`, `pages/Analysis.jsx` |
| Routing | `frontend/src/App.jsx` |
| Desktop-Sidebar | `frontend/src/components/DesktopSidebar.jsx` |
| Responsive CSS | `frontend/src/app.css` (`--nav-h`, `.bottom-nav`, `.analysis-split`, `.desktop-sidebar`) |
| Abnahme-Doku | `docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md` |
| Responsive-Spec | `.claude/docs/functional/RESPONSIVE_UI.md` |
---
## Modul
**Navigation & Informationsarchitektur (IA)**
Schichtenmodell für die PWA: **eine primäre Hauptnavigation** (67 Bereiche), darunter **Bereichs-Shells** mit eigener Sub-Navigation, getrennte **Admin-Realm**, **Auth-Gates** und **ein Breakpoint** für Mobile vs. Desktop.
---
## Fachliche Verantwortung
Das Modul übernimmt:
1. **Hauptnav-SSoT** — Reihenfolge, Labels, Icons, Admin-Sichtbarkeit (`getMainNavItems`).
2. **Routing-Struktur** — Welche URL gehört zu welchem Bereich (Übersicht, Erfassen, Verlauf, Ziele, Analyse, Einstellungen, Admin).
3. **Sub-Navigation pro Bereich** — Capture-Hub, Settings-Tabs, Admin-Gruppen, Analyse-Kategorien.
4. **Layout-Muster** — Bottom-Nav (mobil), Sidebar (Desktop), `analysis-split` für tiefe Bereiche.
5. **Zugriffskontrolle (UI)**`RequireAdmin`, Admin-Link nur bei `role === 'admin'`.
6. **Active-State** — Nested Routes (Erfassung unter `/capture`, Admin unter `/admin/*`).
7. **PWA-Tauglichkeit** — Safe Area, Scrollbare Bottom-Nav, Content-Padding.
Es übernimmt **nicht**:
- Backend-Autorisierung (→ [AUTH_SESSION_DESIGN_PRINCIPLES.md](./AUTH_SESSION_DESIGN_PRINCIPLES.md))
- Feature-Entitlements in der Nav (Tier-Gates an Endpoints/Widgets, nicht an jedem NavLink)
- Inhaltliche Tab-Logik innerhalb von Verlauf/Analyse (Seiten concern)
### IA-Modell (Nutzerperspektive)
| Ebene | Mental Model | Beispiel-Routen |
|-------|--------------|-----------------|
| **Primär** | Wo bin ich in der App? | `/`, `/capture`, `/history`, `/goals`, `/analysis`, `/settings` |
| **Sekundär (Shell)** | Was mache ich in diesem Bereich? | `/weight`, `/admin/g/features`, `/settings/dashboard-layout` |
| **Tertiär (Seite)** | Tabs/Filter innerhalb einer Maske | Verlauf-Tabs, Analyse-Kategorien |
### Strategisch vs. taktisch (Ziele)
| Ebene | Ort | Zweck |
|-------|-----|-------|
| **Strategisch** | `/goals` (Hauptnav) | Ziele definieren, Prioritäten, Focus Areas |
| **Taktisch** | `/custom-goals` (Erfassung) | Tägliche Ist-Werte für eigene Ziele |
| **Auswertung** | `/history` | Trends, Charts, Vergleiche |
---
## Administrierte vs. code-definierte Konfiguration
| Konfiguration | Speicherort | Wer pflegt? |
|---------------|-------------|-------------|
| Hauptnav-Reihenfolge & Labels | `appNav.js` | Entwickler |
| Erfassungs-Kacheln & Shell-Nav | `captureNav.js` | Entwickler |
| Admin-Gruppen & Hub-Karten | `adminNav.js` | Entwickler |
| Settings-Subnav | `settingsNav.js` | Entwickler |
| Analyse-Kategorie-Reihenfolge | `analysisCategories.js` | Entwickler |
| KI-Prompt-Kategorien (Runtime) | DB `ai_prompts.category` | Admin (Prompts) |
| React-Routes | `App.jsx` | Entwickler (muss zu Nav-Configs passen) |
---
## Designprinzipien
### 1. Eine Quelle für die Hauptnavigation
| | |
|---|---|
| **Prinzip** | `getMainNavItems(isAdmin)` liefert dieselbe Item-Liste für **Bottom-Nav** und **Desktop-Sidebar** — keine parallelen Hardcodings. |
| **Begründung** | Reihenfolge und Labels bleiben synchron; Admin-Conditional an einer Stelle. |
| **Quelle** | `appNav.js`, `App.jsx`, `DesktopSidebar.jsx` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Active-State-Logik ist in zwei Dateien dupliziert (`navItemActive` / `sidebarLinkActive`). |
### 2. Feste primäre IA-Reihenfolge (Produkt-Story)
| | |
|---|---|
| **Prinzip** | Übersicht → Erfassen → Verlauf → **Ziele** → Analyse → Einstellungen → [Admin] — spiegelt Nutzerfluss: sehen → eingeben → auswerten → steuern → interpretieren → konfigurieren. |
| **Begründung** | Ziele als eigener Hauptpunkt (nicht unter Analyse versteckt); klare Trennung Capture vs. History vs. Analysis. |
| **Quelle** | `GUI_IA_ADMIN_NAV_2026-04-05.md`; `appNav.js` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Produkt-spezifisch; andere Apps können andere Reihenfolge brauchen. |
### 3. Config-Dateien pro Bereich (Nav-as-Data)
| | |
|---|---|
| **Prinzip** | Sub-Navigation lebt in dedizierten Config-Modulen (`captureNav`, `adminNav`, `settingsNav`, `analysisCategories`) — Shell-Komponenten iterieren nur. |
| **Begründung** | Neue Erfassungsmaske = Eintrag in Config + Route; kein Nav-HTML in jeder Page. |
| **Quelle** | `captureNav.js` Kommentar „Pfade müssen mit Routes übereinstimmen“ |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Kein Build-Time-Check Config ↔ Routes. |
### 4. Bereichs-Shells für tiefe Navigation
| | |
|---|---|
| **Prinzip** | Capture, Settings und Admin nutzen **Shell-Layouts** mit `<Outlet />`; Nutzer wechselt Sub-Bereiche ohne Hauptnav zu verlassen. |
| **Begründung** | Erfassung hat 12+ Masken — wären als Hauptnav-Einträge unbrauchbar. |
| **Quelle** | `CaptureShell`, `SettingsShell`, `AdminShell` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Verlauf und Analyse haben eigene Tab-Muster (kein gemeinsames Shell-Config). |
### 5. Wiederverwendbares `analysis-split`-Layout
| | |
|---|---|
| **Prinzip** | Admin, Settings und KI-Analyse teilen CSS-Muster: mobil horizontale Chips, Desktop linke Spalte + `__main` für Inhalt. |
| **Begründung** | Ein visuelles Muster für „Kategorie links, Arbeit rechts“; weniger UI-Drift. |
| **Quelle** | `AdminShell.jsx`, `SettingsShell.jsx`, `Analysis.jsx`, `app.css` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Capture nutzt eigenes `capture-shell` (Emoji-Icons, Hub-Kacheln). |
### 6. Admin: Gruppen in der Shell, Seiten über Hub
| | |
|---|---|
| **Prinzip** | Shell-Nav zeigt nur **Admin-Gruppen** (+ Übersicht); konkrete Seiten als Karten auf `/admin/g/:groupId`. |
| **Begründung** | Skaliert bei wachsender Admin-Oberfläche; keine 20er-Sidebar. |
| **Quelle** | `adminNav.js` (`ADMIN_GROUPS`, `getAdminShellNavEntries`) |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Ein Klick mehr als flache Nav; bewusster Trade-off. |
### 7. Admin als eigener Realm
| | |
|---|---|
| **Prinzip** | `/admin/*` hinter `RequireAdmin`; kein Admin-Block mehr in Einstellungen; Profil-Anlage nur Admin → Benutzerverwaltung. |
| **Begründung** | Trennung Nutzer- vs. Betreiber-Kontext; weniger Verwechslung. |
| **Quelle** | `RequireAdmin.jsx`, `GUI_IA_ADMIN_NAV_2026-04-05.md` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | UI-Guard ersetzt nicht Backend-`require_admin` auf APIs. |
### 8. Route-Guard mit Nutzer-Feedback
| | |
|---|---|
| **Prinzip** | Nicht-Admin auf `/admin` → Redirect `/` mit `state.adminDenied`; Dashboard zeigt Hinweis. |
| **Begründung** | Stilles Scheitern vermeiden; klare Erwartung. |
| **Quelle** | `RequireAdmin.jsx`, `Dashboard.jsx` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | — |
### 9. Nested Active-State für Section-Prefixes
| | |
|---|---|
| **Prinzip** | Custom Active-Logik: `/capture` aktiv bei allen Erfassungs-Pfaden; `/admin` bei gesamten Admin-Baum; `/goals` mit `end: true` (exakt). |
| **Begründung** | React-Router `end` allein reicht für Section-Gruppen nicht. |
| **Quelle** | `navItemActive`, `adminShellEntryIsActive` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Neue Section-Prefixes brauchen explizite Regel. |
### 10. Erfassungs-Hub + direkte Deep-Links
| | |
|---|---|
| **Prinzip** | `/capture` = Kachel-Hub; jede Maske auch direkt erreichbar (`/weight`, …); Shell-Nav immer sichtbar. |
| **Begründung** | Onboarding über Hub; Power-User/Dashboard-Links springen direkt. |
| **Quelle** | `CaptureHub`, `CAPTURE_HUB_TILES` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Hub und Shell-Nav listen dieselben Ziele (Doppelpflege). |
### 11. Einstellungen: nur aktives Profil
| | |
|---|---|
| **Prinzip** | Settings = Self-Service für **aktives** Profil (Name, E-Mail, Avatar, Quality-Filter); keine Profil-Liste für Endnutzer. |
| **Begründung** | Multi-Profil-Verwaltung ist Admin-Aufgabe; reduziert Komplexität. |
| **Quelle** | `SettingsPage.jsx`, IA-Doku |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Session-bound Profile-Id-Schwäche bleibt Backend-Thema. |
### 12. Settings-Subnav für Layout & Export
| | |
|---|---|
| **Prinzip** | Konfiguration schwerer Features (Dashboard-Layout, PDF-Berichte, Referenzwerte) als eigene Settings-Routen unter Shell — nicht in „Allgemein“ verstecken. |
| **Begründung** | Entspricht [DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md](./DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md) (Nutzer-Konfigurator). |
| **Quelle** | `settingsNav.js` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Admin-Dashboard-Default liegt unter `/admin/...` (getrennte Rolle). |
### 13. KI-Analyse: Ergebnis im Hauptspalt
| | |
|---|---|
| **Prinzip** | Neue Analyse-Ergebnisse rendern in `analysis-split__main`, nicht in der Kategorie-Nav — Nav bleibt wählbar. |
| **Begründung** | Lange Ergebnisse verdrängen sonst die Prompt-Auswahl (Mobile). |
| **Quelle** | `GUI_IA_ADMIN_NAV_2026-04-05.md`, `Analysis.jsx` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | — |
### 14. Ein Breakpoint Mobile / Desktop (1024px)
| | |
|---|---|
| **Prinzip** | `< 1024px`: Bottom-Nav + Mobile-Header; `≥ 1024px`: Desktop-Sidebar, Bottom-Nav ausgeblendet, breiterer Content. **Kein** separates Tablet-Layout. |
| **Begründung** | Einfache Spec, PWA-first; iPad im Portrait = Mobile-Verhalten. |
| **Quelle** | `RESPONSIVE_UI.md`, `app.css` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Große Phones und kleine Tablets identisch behandelt. |
### 15. PWA Safe Area für Bottom-Navigation
| | |
|---|---|
| **Prinzip** | `--nav-h`, `--nav-pad-top`, `env(safe-area-inset-bottom)` auf `.bottom-nav`; Content-`padding-bottom` inkl. Nav-Höhe; horizontal scrollbare Nav bei vielen Items. |
| **Begründung** | iPhone Home-Indicator und Notch — kein Clipping, kein verdeckter Content. |
| **Quelle** | `app.css`, IA-Doku |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Safe Area nur auf Nav/Content-Padding, nicht global überall. |
### 16. Auth-Routen außerhalb der App-Shell
| | |
|---|---|
| **Prinzip** | Login, Register, Verify, Reset-Password rendern **ohne** Bottom-Nav/Sidebar — minimale Vollbild-Cards. |
| **Begründung** | Keine Navigation ohne Session; klarer Fokus. |
| **Quelle** | `App.jsx` (early returns vor `AppShell`) |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Public Routes nicht zentral in einer Route-Config. |
### 17. Rollen-sichtbare Nav-Einträge (UI only)
| | |
|---|---|
| **Prinzip** | Admin-Link erscheint nur wenn `isAdmin`; Backend schützt APIs separat. |
| **Begründung** | Progressive disclosure; normale Nutzer sehen keinen toten Link. |
| **Quelle** | `getMainNavItems(isAdmin)` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Security nicht durch Ausblenden ersetzt. |
### 18. Deep-Link-State für Verlauf
| | |
|---|---|
| **Prinzip** | Nav zu `/history` setzt optional `state: { tab: 'overview' }` — konsistenter Einstieg von Hauptnav. |
| **Begründung** | Verlauf merkt sich Tabs; Hauptnav soll nicht zufälligen alten Tab öffnen. |
| **Quelle** | `App.jsx`, `DesktopSidebar.jsx`, `History.jsx` |
| **Tragfähigkeit** | **mittel** |
| **Einschränkung** | Nur für History implementiert, nicht app-weit. |
---
## Nicht übernehmen
1. **Hauptnav an mehreren Stellen hardcoden** — immer `appNav.js`.
2. **Admin-Funktionen in Einstellungen** — eigener `/admin`-Bereich.
3. **Alle Erfassungsmasken in die Bottom-Nav** — Shell + Hub skaliert.
4. **Alle Admin-Seiten in der Shell-Sidebar** — Hub-Gruppen-Muster beibehalten.
5. **Nav-Config ohne Route-Pflege** — jeder neue Pfad: Config + `App.jsx` + ggf. Active-State.
6. **UI-Admin-Guard ohne Backend-Guard**`RequireAdmin` ist UX, APIs brauchen `require_admin`.
7. **Zwei Tablet-/Desktop-Breakpoints** — Mitai: ein Cut bei 1024px.
8. **Safe Area ignorieren** — PWA auf iOS bricht sonst an Bottom-Nav.
9. **Profil-Liste für Endnutzer in Settings** — Multi-Profil = Admin.
10. **Feature-Tier-Logik in Nav-Komponenten** — Entitlements an Widgets/APIs ([FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md)).
11. **Inkonsistente Layout-Muster pro Bereich** — wo `analysis-split` passt, nicht neues Ad-hoc-Layout erfinden.
12. **Orphan-Routes ohne Nav-Ergänzung** — z. B. `/subscription`, `/workflow-editor/:id` existieren außerhalb Haupt-IA; bewusst dokumentieren, nicht unkontrolliert multiply.
13. **Active-State nur per Router-Default** — Section-Prefixes (`/capture/*`, `/admin/*`) brauchen explizite Regeln.
14. **Analyse-Ergebnisse in der Nav-Spalte** — verdrängt Prompt-Auswahl auf Mobile.
---
## Modul-Inventar (Ist-Stand)
```
frontend/src/config/
├── appNav.js # Hauptnav (6 + Admin)
├── captureNav.js # Erfassungs-Hub + Shell
├── settingsNav.js # Settings-Subnav
├── adminNav.js # ADMIN_GROUPS, Shell-Entries
└── analysisCategories.js # KI-Analyse-Gruppen
frontend/src/layouts/
├── CaptureShell.jsx
├── SettingsShell.jsx
├── AdminShell.jsx
└── RequireAdmin.jsx
frontend/src/components/
└── DesktopSidebar.jsx
frontend/src/App.jsx # Routes + Bottom-Nav + Auth-Gates
frontend/src/app.css # Shell, split, safe-area, 1024px breakpoint
```
**Hauptnav (7 Einträge mit Admin):** Übersicht · Erfassen · Verlauf · Ziele · Analyse · Einstellungen · Admin
**Admin-Gruppen (8):** users · features · subscription · training · goals · prompts · system
---
## Verwandte Dokumentation
- Abnahme-Stand: [GUI_IA_ADMIN_NAV_2026-04-05.md](../../../../docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md)
- Responsive-Spec: [RESPONSIVE_UI.md](../../functional/RESPONSIVE_UI.md)
- Auth/Session: [AUTH_SESSION_DESIGN_PRINCIPLES.md](./AUTH_SESSION_DESIGN_PRINCIPLES.md)
- Dashboard-Konfigurator: [DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md](./DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md)
- Gitea #30 (Responsive UI, teilweise erledigt)
---
## Geplante Folgedokumente (Serie)
| # | Modul | Status |
|---|-------|--------|
| 17 | … | ✅ |
| 8 | Navigation / IA | ✅ dieses Dokument |
| 9 | Migration & Deploy | ✅ |

View File

@ -1,305 +0,0 @@
# Prompt Engine Designprinzipien (Extraktion)
**Status:** Analyse / Arbeitspapier
**Stand:** 2026-07-04
**Geltungsbereich:** Modul „Prompt Engine“ (Unified Prompt System, Issue #28) — keine Mitai-Gesamtarchitektur, keine Domänenlogik (Gesundheit, Ernährung, Messwerte)
**Serie:** Designprinzipien für Produktfamilie · Dokument 1 von n
**Kernkomponenten:**
| Bereich | Pfade |
|---------|-------|
| Executor | `backend/prompt_executor.py`, `backend/workflow_executor.py` |
| Platzhalter | `backend/placeholder_resolver.py`, `backend/placeholder_registry.py`, `backend/placeholder_registrations/` |
| API | `backend/routers/prompts.py`, `backend/routers/workflows.py` |
| Admin-UI | `frontend/src/pages/AdminPromptsPage.jsx`, `UnifiedPromptModal.jsx`, `WorkflowEditorPage.jsx` |
| Fachliche Spec | `.claude/docs/functional/AI_PROMPTS.md` |
| Platzhalter-Governance | `.claude/docs/technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md`, `docs/PLACEHOLDER_GOVERNANCE.md` |
---
## Modul
**Prompt Engine** (Unified Prompt System, Issue #28)
Backend-Kern: `prompt_executor.py`, `placeholder_resolver.py`, `placeholder_registry` / `placeholder_registrations/`, `workflow_executor.py`
API: `routers/prompts.py`
Admin-UI: `AdminPromptsPage`, `UnifiedPromptModal`, `WorkflowEditorPage`
---
## Fachliche Verantwortung
Die Prompt Engine ist die **zentrale Ausführungs- und Konfigurationsschicht für KI-Analysen**. Sie übernimmt:
1. **Prompt-Orchestrierung** — Laden aktiver Prompt-Definitionen aus der DB und Ausführung als `base` (Einzelprompt), `pipeline` (mehrstufig) oder `workflow` (Graph).
2. **Kontextaufbereitung** — Befüllen von Platzhaltern mit profilbezogenen Daten (Resolver → Data Layer).
3. **LLM-Aufruf** — Einheitlicher OpenRouter-Call über injizierte Callback-Funktion.
4. **Ergebnisbehandlung** — JSON-Validierung, strukturierte Container (Fragenergänzungen), Debug-Metadaten, optionales Speichern in `ai_insights`.
5. **Admin-Konfiguration** — CRUD für Prompts/Workflows, Import/Export, Vorschau und Test ohne Produktions-Ausführung.
### Administrierte Konfigurationen
| Konfiguration | Speicherort | Inhalt |
|---------------|-------------|--------|
| Prompt-Metadaten | `ai_prompts` | `name`, `slug`, `category`, `active`, `sort_order`, `display_name` |
| Templates | `ai_prompts` | `template` |
| Pipeline-Stages | `ai_prompts.stages` (JSONB) | Stages mit `inline` / `reference` |
| Workflow-Graphen | `ai_prompts.graph_data` | Knoten, Kanten, Metadaten |
| Output-Regeln | `ai_prompts` | `output_format`, `output_schema` |
| Fragenergänzungen | `ai_prompts.question_augmentations` | Optionale Standard-Fragen (Hybridmodell: Knoten > Prompt) |
| System-Reset | `ai_prompts` | `is_system_default`, `default_template` |
| Legacy-Pipeline-Configs | `pipeline_configs` | Module, Zeiträume, Stage-Slugs (parallel zum Unified System) |
| Workflow-Fragenkatalog | `workflow_question_catalog` | Fragetypen, Templates, Normalisierung |
### Bewusst nicht hardcodiert
- Prompt-Texte, Pipeline-Zusammensetzung, Workflow-Topologie
- Kategorie, Sichtbarkeit (`active`), Sortierung
- Output-Format und Schema pro Prompt
- Referenz vs. Inline in Pipeline-Stages
### Hardcodiert (Code / Env)
- Platzhalter-Definitionen und Resolver (`PLACEHOLDER_MAP`, Registry)
- LLM-Modell (`OPENROUTER_MODEL`)
- Default-Module und -Zeiträume in `/prompts/execute`
- Domänen-Kategorien im Frontend (`analysisCategories.js`)
- Meta-Prompts für Generate/Optimize (Admin-Tooling)
### Trennung: Template · Platzhalter · Kontext · Workflow
| Schicht | Ort | Rolle |
|---------|-----|-------|
| **Templates** | `ai_prompts.template`, `stages`, Knoten-Templates im Graph | Was an die KI geht |
| **Platzhalter** | `placeholder_resolver` + Registry | Semantische API-Keys `{{key}}`, Resolver-Funktionen |
| **Kontextdaten** | `execute_prompt_with_data` + Resolver → `data_layer/` | Werte für Platzhalter |
| **Workflows** | `graph_data` + `workflow_executor` | Ausführungsgraph, Verzweigung, Join, Aggregation |
### Durchsetzung: keine Sonderlogik außerhalb der Engine
**Konzeptionell:** Ein Executor (`execute_prompt` → `execute_prompt_with_data`) als Single Entry Point.
**Praktisch unvollständig:** Legacy-Pfade in `insights.py` (`/insights/run`, `/insights/pipeline`) mit eigener Template-Logik (`_prepare_template_vars`, `_render_template`) und direkten LLM-Calls; `History.jsx` nutzt noch `runInsight`. Kein technischer Guard (Lint/Policy), nur Konvention.
### Rollen und Berechtigungen
| Rolle | Darf |
|-------|------|
| **Admin** (`require_admin`) | Prompts/Workflows/Pipeline-Configs CRUD, Import/Export, Reset-to-default, Generate/Optimize, Platzhalter-Metadaten-ZIP |
| **Nutzer** (`require_auth`) | Aktive Prompts listen (ohne Pipeline-Slugs), ausführen (`/prompts/execute`), Preview, Platzhalter-Katalog, eigene Werte exportieren |
Workflow-Editor-Route (`/workflow-editor/:id`) ist nicht hinter `RequireAdmin`; Schreib-APIs sind admin-geschützt.
### Versionierung, Freigabe, Test
| Mechanismus | Status |
|-------------|--------|
| Prompt-Versionsverlauf in DB | ❌ Overwrite |
| Reset-to-default für System-Prompts | ✅ `is_system_default` + `default_template` |
| JSON Import/Export (Dev→Prod) | ✅ `/export-all`, `/import` |
| Admin-Test mit Debug | ✅ `debug=true`, UnifiedPromptModal |
| Preview ohne LLM | ✅ `POST /preview` |
| Platzhalter-Deprecation-Prozess | 📄 dokumentiert, nicht runtime-erzwungen |
| Formales Freigabe-Workflow | ❌ |
| Executor-E2E-Tests | ⚠️ punktuell (Modifier, Output-Compact) |
---
## Designprinzipien
### 1. Single Executor für Prompt-Ausführung
| | |
|---|---|
| **Prinzip** | Alle KI-Analysen laufen über `execute_prompt` / `execute_prompt_with_data`. |
| **Begründung** | Einheitliche Platzhalter-Auflösung, Debug, JSON-Validierung, Speicher-Metadaten. |
| **Quelle** | `backend/prompt_executor.py`; `POST /api/prompts/execute`; `Analysis.jsx``executeUnifiedPromptStream` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Legacy `insights.py` und teils `History.jsx` umgehen den Executor noch. |
### 2. Konfigurierbare Prompt-Bibliothek statt fest verdrahteter Texte
| | |
|---|---|
| **Prinzip** | Prompt-Inhalte und Workflows liegen in `ai_prompts`, nicht im Anwendungscode. |
| **Begründung** | Admins können Analysen anpassen, duplizieren, deaktivieren, ohne Deploy. |
| **Quelle** | Migration 020; `UnifiedPromptCreate`/`Update` in `models.py`; `AdminPromptsPage.jsx` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Parallel existieren noch `pipeline_configs` und hardcodierte Default-Module/Zeiträume. |
### 3. Drei Prompt-Typen mit klarer Verantwortung
| | |
|---|---|
| **Prinzip** | `base` = wiederverwendbarer Baustein; `pipeline` = sequenzielle Stages; `workflow` = Graph mit Verzweigung. |
| **Begründung** | Komposition ohne Copy-Paste; Reference-Prompts in Pipelines (`source: 'reference'`). |
| **Quelle** | `execute_prompt()` Typ-Verzweigung; `StagePromptCreate` in `models.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Pipeline-Stages laufen sequentiell, obwohl konzeptionell „parallel“; `workflow_definitions` und `ai_prompts.graph_data` doppelt. |
### 4. Platzhalter als API-Verträge (Registry)
| | |
|---|---|
| **Prinzip** | Platzhalter sind registrierte, dokumentierte Verträge — keine freien Prompt-Hilfsvariablen. |
| **Begründung** | Konsistenz für Injektion, GUI-Picker, Export, Validierung. |
| **Quelle** | `PLACEHOLDER_REGISTRY_FRAMEWORK.md`; `docs/PLACEHOLDER_GOVERNANCE.md`; `import placeholder_registrations` in `main.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Duplikat `PLACEHOLDER_MAP` in `placeholder_resolver.py` neben Registry; Metadaten teils noch Legacy. |
### 5. Trennung Template (Was) vs. Resolver (Daten)
| | |
|---|---|
| **Prinzip** | Templates enthalten nur `{{keys}}`; Berechnung liegt in Resolver/Data Layer. |
| **Begründung** | Prompt-Autoren ändern Text, nicht Berechnungslogik. |
| **Quelle** | `resolve_placeholders()` in `prompt_executor.py`; Registry-Felder `resolver_function`, `data_layer_function` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | `execute_prompt_with_data` lädt zusätzlich Roh-SQL pro Modul — zweite Kontext-Schicht. |
### 6. Layer-1-Daten vs. Layer-2a-Prompt-Injektion
| | |
|---|---|
| **Prinzip** | Berechnungen in `data_layer/`; Prompt Engine konsumiert nur formatierte Werte. |
| **Begründung** | Single Source of Truth für Charts, Platzhalter, KI. |
| **Quelle** | Phase-0c-Architektur; Registry-Felder `data_layer_module` / `layer_1_decision` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Legacy `_prepare_template_vars` in `insights.py` umgeht Data Layer. |
### 7. Transparenz durch Debug- und Preview-Modus
| | |
|---|---|
| **Prinzip** | Aufgelöste/unaufgelöste Platzhalter, Final-Prompt und Stage-Outputs sind inspizierbar; Preview ohne LLM. |
| **Begründung** | Admin kann Prompts testen und Wertetabelle/Expertenmodus speisen. |
| **Quelle** | `debug`-Parameter; `/preview`; `UnifiedPromptModal` Test-Button; `ai_insights.metadata` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Debug-Daten in Responses können groß/sensibel sein; kein separates Staging. |
### 8. Wiederverwendbare Base-Prompts via Reference
| | |
|---|---|
| **Prinzip** | Pipeline-Stages referenzieren Slugs statt Templates zu duplizieren. |
| **Begründung** | Ein Baustein, mehrere Workflows; zentral wartbar. |
| **Quelle** | `source == 'reference'` in `execute_pipeline_prompt()` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Keine Referenz-Versionierung; Änderung am Base-Prompt wirkt sofort auf alle Referenzen. |
### 9. Strukturierte LLM-Ausgaben per Output-Format
| | |
|---|---|
| **Prinzip** | Pro Prompt/Prompt-Def: `output_format: text\|json`, optional `output_schema`; Pipeline-Outputs als Stage-Keys im Kontext. |
| **Begründung** | Maschinenlesbare Zwischenergebnisse für Multi-Stage und Wertetabelle. |
| **Quelle** | `validate_json_output()`; Stage `output_key` in Pipeline |
| **Tragfähigkeit** | **mittel** |
| **Einschränkung** | JSON-Schema-Validierung ist TODO (`jsonschema`); Markdown-Unwrap als Heuristik. |
### 10. Admin-only Konfiguration, User-only Ausführung
| | |
|---|---|
| **Prinzip** | Schreibende Prompt-/Workflow-Operationen nur mit `require_admin`. |
| **Begründung** | Produktions-Prompts sind Systemkonfiguration, nicht Nutzerdaten. |
| **Quelle** | `require_admin` in `routers/prompts.py`; `RequireAdmin` für `/admin/prompts` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | `/workflow-editor/:id` ohne Frontend-Admin-Gate; `/prompts/execute` ohne `check_feature_access` (Legacy-Pfad in `insights.py` hat Enforcement). |
### 11. Import/Export als Umgebungs-Sync
| | |
|---|---|
| **Prinzip** | Prompt-Sätze als JSON exportierbar/importierbar (Dev→Prod). |
| **Begründung** | Konfiguration versionierbar in Git, nicht in der App-DB. |
| **Quelle** | `GET /export-all`, `POST /import` in `routers/prompts.py`; Admin-UI Buttons |
| **Tragfähigkeit** | **mittel** |
| **Einschränkung** | Kein Diff, keine Merge-Strategie, kein Rollback; Overwrite-Flag manuell. |
### 12. Workflow-Erweiterung: Graph + Fragenergänzungen + Signale
| | |
|---|---|
| **Prinzip** | Workflows als Knoten/Kanten-Graph; optionale Fragen am Knoten; Normalisierung/Logic/Join als Engine-Schicht. |
| **Begründung** | Bedingte, verzweigte Analysen jenseits linearer Pipelines. |
| **Quelle** | `workflow_executor.py`; Migration 034; `question_augmenter.py` |
| **Tragfähigkeit** | **mittel** |
| **Einschränkung** | Hohe Komplexität; zwei Speicherorte (`graph_data` vs. `workflow_definitions`); Jinja2 im Workflow-Pfad zusätzlich zu `{{}}`-Resolver. |
### 13. Platzhalter-Modifier für KI-Kontext
| | |
|---|---|
| **Prinzip** | `{{key\|d}}` (Wert + Beschreibung), `{{key\|x}}` (Erklärung ohne Zahl) über Katalog-Metadaten. |
| **Begründung** | Prompts können Kontext für das Modell reichhaltiger machen ohne Template-Duplikate. |
| **Quelle** | `resolve_placeholders()` Modifier-Logik; `get_placeholder_catalog()` |
| **Tragfähigkeit** | **mittel** |
| **Einschränkung** | Modifier-Syntax ad hoc; Katalog-Pflicht für sinnvolle `\|x`-Nutzung. |
### 14. System-Prompt-Reset statt DB-Versionierung
| | |
|---|---|
| **Prinzip** | Shipped Prompts mit `is_system_default` + `default_template`; Admin-Reset auf Original. |
| **Begründung** | Schutz vor irreversiblen Fehlkonfigurationen ohne vollständiges Versionsmodell. |
| **Quelle** | Migration 019; `POST /{prompt_id}/reset-to-default` |
| **Tragfähigkeit** | **mittel** |
| **Einschränkung** | Nur ein Default-Snapshot; keine Historie benutzerdefinierter Änderungen. |
### 15. Governance für Platzhalter-Änderungen
| | |
|---|---|
| **Prinzip** | Breaking Changes nur über Deprecation + Replacement; semantische Verträge dokumentiert. |
| **Begründung** | Prompts in Produktion brechen nicht still. |
| **Quelle** | `docs/PLACEHOLDER_GOVERNANCE.md` §4 |
| **Tragfähigkeit** | **mittel** (prozessual) |
| **Einschränkung** | Prozess in Doku, nicht im Runtime erzwungen; Checkliste verweist noch auf Legacy-Dateien. |
---
## Nicht übernehmen
Muster, die sich nicht bewährt haben oder zu produktspezifisch sind — bei Neuentwicklung vermeiden:
1. **Parallele Ausführungspfade** — Legacy `insights.py` (`/insights/run`, `/insights/pipeline`) mit eigener Template-Engine und LLM-Calls neben `prompt_executor`; Frontend-Split (`Analysis` vs. `History`).
2. **Doppelte Metadaten für Platzhalter**`PLACEHOLDER_MAP`, Registry, `placeholder_metadata_complete.py` und Katalog-Fallbacks parallel; erzeugt Sync-Risiko (114 Keys müssen deckungsgleich bleiben).
3. **Zwei Pipeline-Modelle gleichzeitig**`pipeline_configs` (3 fixe Stages) und Unified `type=pipeline` in `ai_prompts`; Migration 020 migriert, Tabelle bleibt aktiv.
4. **Zwei Workflow-Speicher**`workflow_definitions.graph` und `ai_prompts.graph_data`; unklare Single Source of Truth.
5. **Roh-SQL-Kontextladung im Executor**`execute_prompt_with_data` lädt Modul-Rohdaten per SQL, obwohl Resolver/Data Layer existieren; Domänenwissen in der Engine.
6. **Hardcodierte Execute-Defaults** — Module/Zeiträume in `/execute` fest verdrahtet statt aus Prompt-/Pipeline-Konfiguration.
7. **Fehlende Feature-Enforcement-Konsistenz**`check_feature_access` auf Legacy-Insights, nicht auf `/prompts/execute`.
8. **„Parallel“ als sequentiell implementiert** — Pipeline-Stages kommentiert als parallel, Code sequentiell; irreführendes Modell.
9. **Unvollständige Output-Validierung** — JSON-Parse + Markdown-Unwrap, Schema-Check auskommentiert/TODO.
10. **Workflow-Editor ohne klares Admin-Gate in Routing** — Schreib-API geschützt, UI-Route für alle Authentifizierten erreichbar.
11. **Domänen-spezifische Hardcodings in der Engine** — Kategorien (`körper`, `ernährung`, …), deutsche Meta-Prompts für Generate/Optimize, Fitness-Kontext in Prompt-Generator.
12. **Kein integriertes Prompt-Versions- und Freigabemodell** — Overwrite + JSON-Export ersetzt keine Revision/Review/Publish-Pipeline.
13. **Issue #51 (Seitenzuordnung) nicht umgesetzt** — Prompt-Verfügbarkeit kontextuell nicht konfigurierbar; alles über zentrale Analyse-Seite.
14. **Globales LLM-Modell per Env**`workflow_executor` übergibt Modell pro Call, `call_openrouter` ignoriert es; keine prompt-spezifische Modellwahl trotz API-Shape.
---
## Verwandte Dokumentation
- Fachliche Spec: [AI_PROMPTS.md](../../functional/AI_PROMPTS.md)
- Platzhalter-Registry: [PLACEHOLDER_REGISTRY_FRAMEWORK.md](../../technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md)
- Platzhalter-Governance: [PLACEHOLDER_GOVERNANCE.md](../../../../docs/PLACEHOLDER_GOVERNANCE.md)
- Issue #28 (Unified Prompt System): abgeschlossen, siehe `CLAUDE.md`
- Issue #51 (Prompt-Seitenzuordnung): [issue-51-prompt-page-assignment.md](../../../../docs/issues/issue-51-prompt-page-assignment.md)
- **Serie (abgeschlossen):** [Index](./README.md) · [Jinkendo Foundation](../README.md) · Dokumente #1#9

View File

@ -1,136 +0,0 @@
# Designprinzipien Index (Jinkendo Foundation)
**Teil von:** [Jinkendo Foundation](../README.md)
**Status:** Arbeitspapier / Übergabe
**Stand:** 2026-07-04
**Zweck:** Zentraler Einstieg für die aus Mitai extrahierte Serie **tragfähiger Designprinzipien** — für neue Apps der Jinkendo-Produktfamilie oder vergleichbare Self-Hosted-PWAs.
**Nicht enthalten:** Mitai-Gesamtarchitektur, Domänenlogik (Gesundheit/Ernährung), Kairo-/Framework-Empfehlungen.
**Ablage:** `.claude/docs/jinkendo-foundation/design-principles/` · Regeln: [DOCUMENTATION.md](../../../rules/DOCUMENTATION.md)
---
## Wofür diese Serie?
Mitai Jinkendo implementiert wiederkehrende **Querschnittsmuster** (Prompt-Ausführung, Data Layer, Entitlements, Registries, Auth, Import, Dashboard, Navigation, Deploy). Die neun Dokumente destillieren daraus:
- **Was** übertragbar ist (Prinzip + Begründung + Tragfähigkeit)
- **Was** bewusst nicht kopiert werden soll („Nicht übernehmen“)
- **Wo** im Code nachgeschaut werden kann (Pfade, Agent-Guides)
Jedes Dokument ist **eigenständig lesbar**; dieser Index ordnet Abhängigkeiten und Lese-Reihenfolge.
---
## Dokumente (9/9)
| # | Modul | Datei | Kernidee (1 Satz) |
|---|-------|-------|-------------------|
| 1 | Prompt Engine | [PROMPT_ENGINE_DESIGN_PRINCIPLES.md](./PROMPT_ENGINE_DESIGN_PRINCIPLES.md) | Ein Executor, Pipeline/Workflow-Typen, Platzhalter über Registry — keine Raw-Template-Ausführung. |
| 2 | Data Layer | [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md) | Layer 0→1→2: Single Source of Truth für Berechnungen; Charts und KI nutzen dieselbe Schicht. |
| 3 | Feature & Entitlement | [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) | Zentrale `check_feature_access`, DB-Registry, 4-Phasen-Rollout — Enforcement an der API. |
| 4 | Registry / Plugin (Meta) | [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md) | Drei Registry-Muster (Platzhalter, Widgets, CSV): SSoT, Validierung an der Grenze, Runtime-Registrierung. |
| 5 | Auth & Session | [AUTH_SESSION_DESIGN_PRINCIPLES.md](./AUTH_SESSION_DESIGN_PRINCIPLES.md) | Server-Sessions, `require_auth` als Depends — bekannte Schwäche: Profile-Header ohne Session-Bindung. |
| 6 | Universal Import | [UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md](./UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md) | Modul-Registry + Executor + Vorlagen; Ingest ≠ Interpretation; SAVEPOINT pro Zeile. |
| 7 | Dashboard Widgets | [DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md](./DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md) | Backend-Katalog, Profil-Layout, Config-Whitelist, Entitlements — Dual Registry mit Frontend. |
| 8 | Navigation / IA | [NAVIGATION_IA_DESIGN_PRINCIPLES.md](./NAVIGATION_IA_DESIGN_PRINCIPLES.md) | `appNav` als SSoT, Shells für tiefe Bereiche, Admin-Hub, ein Breakpoint 1024px. |
| 9 | Migration & Deploy | [MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md](./MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md) | `XXX_*.sql` beim Container-Start; develop/main → Dev/Prod; fail-fast, kein Auto-Rollback. |
---
## Empfohlene Lesereihenfolge
### Schnellüberblick (30 Min)
1. [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md) — Meta-Muster für viele Module
2. [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md) — Daten vs. Darstellung
3. [MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md](./MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md) — Betrieb & Schema-Evolution
### Vollständige Implementierung (neues Produkt)
```
Foundation: (9) Migration & Deploy → (5) Auth → (3) Feature & Entitlement
Daten: (2) Data Layer → (6) Universal Import
Erweiterung: (4) Registry Meta → (1) Prompt Engine → (7) Dashboard Widgets
Oberfläche: (8) Navigation / IA
```
### Nur ein Modul nachbauen
| Ziel | Lese zuerst | Dann |
|------|-------------|------|
| KI-Analysen | #1 Prompt Engine | #2 Data Layer, #4 Registry |
| Charts & KPIs | #2 Data Layer | #7 Dashboard Widgets |
| Freemium / Limits | #3 Feature & Entitlement | #7 Widgets (Gating) |
| CSV-Import | #6 Universal Import | #2 Data Layer, #4 Registry |
| Admin-PWA | #8 Navigation / IA | #3, #5 |
---
## Querschnittsthemen (über alle Docs)
| Thema | Primär | Ergänzend |
|-------|--------|-----------|
| Single Source of Truth | #2 Data Layer | #4 Registry, #7 Widget-Katalog, #6 Modul-Registry |
| Validierung an der Grenze | #4 Registry | #7 Layout-Pydantic, #6 Template-Validator |
| Feature-Gating | #3 Entitlement | #7 Widget `allowed`, #6 Import-Limits |
| Dual Registry (Backend + Frontend) | #4 Registry | #7 `registerDashboardWidgets`, Platzhalter-UI |
| Idempotenz / Recovery | #9 Migration | #6 SAVEPOINT, Migration `IF NOT EXISTS` |
| Bekannte Lücken dokumentieren | alle „Nicht übernehmen“ | z. B. IDOR Profile-Header (#5), fehlender Cross-Check Widget-IDs (#7) |
---
## Verwandte normative Docs (Mitai-spezifisch)
Diese Agent-Guides sind **Implementierungsdetail**; die Designprinzipien-Serie ist **extrahiertes Muster**:
| Thema | Agent-Guide / Spec |
|-------|-------------------|
| Platzhalter | [PLACEHOLDER_REGISTRY_FRAMEWORK.md](../../technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md) |
| Data Layer erweitern | [DATA_LAYER_EXTENSION_GUIDE.md](../../technical/DATA_LAYER_EXTENSION_GUIDE.md) |
| CSV Import | [UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md](../../technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md) |
| Dashboard Widgets | [DASHBOARD_WIDGETS_AGENT_GUIDE.md](../../technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md) |
| GUI / Admin / Nav | [GUI_IA_ADMIN_NAV_2026-04-05.md](../../../../docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md) |
| Migrationen (operativ) | [MIGRATIONS.md](../../technical/MIGRATIONS.md) |
| Membership | [MEMBERSHIP_SYSTEM.md](../../technical/MEMBERSHIP_SYSTEM.md), [FEATURE_ENFORCEMENT.md](../../architecture/FEATURE_ENFORCEMENT.md) |
| Architektur-Regeln | [ARCHITECTURE.md](../../../rules/ARCHITECTURE.md) |
---
## Übergabe-Checkliste (neues Produkt in der Familie)
Nutze diese Liste beim Start eines Schwester-Projekts — **Prinzipien ja, Mitai-Pfade nein**:
```
[ ] Deploy: nummerierte SQL-Migrationen + Tracking-Tabelle + Startup vor App
[ ] Auth: Session server-side; Depends(require_auth); Admin-Route-Guard
[ ] Entitlements: eine check_feature_access-Funktion; keine Tier-Logik in UI-Widgets
[ ] Data Layer: Berechnungen nicht in Router/React duplizieren
[ ] Registry: neue erweiterbare IDs nur über zentralen Katalog + Validierung
[ ] Import (falls CSV): Modul-Registry + Vorlagen + Import-Grenze (keine Scores beim Insert)
[ ] Dashboard (falls konfigurierbar): Backend-Katalog + Layout-JSON + allowed-Flag
[ ] Navigation: eine appNav-SSoT; Shells für >6 Top-Level-Bereiche
[ ] Prompt/KI (falls): ein Executor; Platzhalter-Registry; kein Raw-Template an LLM
[ ] Dokumentieren: pro Modul „Nicht übernehmen“ aus Mitai-Lücken mitnehmen
```
---
## Pflege
| Aktion | Wo |
|--------|-----|
| Neues Querschnittsmodul extrahiert | Neues `*_DESIGN_PRINCIPLES.md` + Zeile in Tabelle oben |
| Mitai-Implementierung ändert Muster | Betroffenes Einzeldokument + ggf. Querschnittstabelle hier |
| Nur Mitai-Bugfix ohne Muster-Änderung | Agent-Guides / Code; Designprinzipien optional |
---
## Changelog Index
| Datum | Änderung |
|-------|----------|
| 2026-07-04 | Index angelegt; Serie 19 abgeschlossen |
| 2026-07-04 | Nach `jinkendo-foundation/design-principles/` verschoben |

View File

@ -1,315 +0,0 @@
# Registry- & Plugin-Muster Designprinzipien (Extraktion)
**Status:** Analyse / Arbeitspapier
**Stand:** 2026-07-04
**Geltungsbereich:** Querschnittsmuster für erweiterbare Registries — drei Implementierungen in Mitai (Platzhalter, Dashboard-Widgets, CSV-Import)
**Serie:** Designprinzipien für Produktfamilie · Dokument 4 von n
**Vorgänger:** [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md)
**Die drei Registries:**
| Registry | Backend-Kanon | Frontend-/Runtime-Registry | Leitfaden |
|----------|---------------|----------------------------|-----------|
| **Platzhalter** | `placeholder_registry.py` + `placeholder_registrations/` | `PLACEHOLDER_MAP` in `placeholder_resolver.py` | `PLACEHOLDER_REGISTRY_FRAMEWORK.md` |
| **Dashboard-Widgets** | `widget_catalog.py` | `registerDashboardWidgets.js``dashboardWidgetRegistry.jsx` | `DASHBOARD_WIDGETS_AGENT_GUIDE.md` |
| **CSV-Import-Module** | `csv_parser/module_registry.py` | — (Executor + Admin-UI konsumieren API) | `UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md` |
---
## Modul
**Registry- & Plugin-Muster** (Meta-Schicht)
Wiederkehrendes Architekturmuster: **Zentral registrierte, ID-basierte Erweiterungspunkte** mit Metadaten, Validierung und getrennten Konsumenten (GUI, API, Batch). Kein einzelnes Runtime-Modul — ein **Familien-Designpattern**, das in Mitai dreimal konkret umgesetzt ist.
---
## Fachliche Verantwortung
Registries übernehmen:
1. **Autoritative ID-Liste** — Was existiert, was ist erlaubt, in welcher Reihenfolge (optional).
2. **Metadaten für Mensch & Maschine** — Titel, Beschreibung, Typen, Abhängigkeiten, semantische Verträge.
3. **Validierung** — Unbekannte IDs werden abgelehnt; Konfigurationen gegen Kanon geprüft.
4. **Entkopplung** — Implementierung (Resolver, React-Komponente, Import-Executor) registriert sich an den Kanon, nicht umgekehrt.
5. **Erweiterbarkeit ohne Schema-Explosion** — Neue Einträge über Code-Registrierung (+ ggf. DB-Overrides), nicht über neue DB-Spalten pro Feature.
Registries übernehmen **nicht**:
- Fachliche Berechnung (→ Data Layer)
- Entitlement-Auflösung (→ Feature System; Widgets *referenzieren* Features)
- Auth / Mandanten
### Gemeinsames Strukturschema
```
┌─────────────────────────────────────────────────────────┐
│ REGISTRY (Kanon) │
│ ID + Metadaten + optionale Policies/Abhängigkeiten │
└────────────────────┬────────────────────────────────────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
Implementierung Validierung Konsumenten
(Resolver/ (Schema/ (GUI-Picker,
Component/ Tests) API, Executor)
Executor)
```
### Vergleich der drei Implementierungen
| Aspekt | Platzhalter | Dashboard-Widgets | CSV-Import |
|--------|-------------|-------------------|------------|
| **Primär-ID** | `key` (snake_case) | `id` (snake_case) | Modul-Name (`nutrition`, `activity`, …) |
| **Kanon-Speicher** | Python Singleton + Cluster-Module | Python-Liste `WIDGET_CATALOG` | Python-Dict `MODULE_DEFINITIONS` |
| **Metadaten-Tiefe** | Sehr hoch (22+ Felder, Evidence) | Mittel (title, description, requires_feature) | Hoch (fields, types, duplicate_key, aggregates) |
| **Runtime-Registry** | `register_placeholder()` beim Import | `registerDashboardWidget()` idempotent | Keine — Executor liest Dict |
| **Frontend-Spiegel** | PlaceholderPicker, Admin-Prompt-Modal | `ensureDashboardWidgetsRegistered()` | Admin CSV Template Editor |
| **Validierung** | `metadata.validate()`, Governance-Docs | Pydantic Layout + `validate_widget_entry_config` | `validate_field_mappings`, `validate_csv_template` |
| **Tests** | `test_placeholder_metadata.py`, … | `test_widget_catalog.py` | `test_template_validator.py`, … |
| **DB-Override** | Nein (nur Code) | Ja (`widget_feature_requirements`, Layout pro Profil) | Ja (Vorlagen, Nutzer-Mappings) |
| **Entitlements** | Indirekt (Data/Features) | `requires_feature``check_feature_access` | Feature-Limits beim Import |
---
## Designprinzipien (übergreifend)
### 1. Single Source of Truth für erlaubte IDs
| | |
|---|---|
| **Prinzip** | Jede erweiterbare Einheit hat **eine** autoritative ID-Liste; Router und UI duplizieren keine Feld-/Widget-/Platzhalter-Listen. |
| **Begründung** | Verhindert „funktioniert in der UI, scheitert in der API“ und umgekehrt. |
| **Quelle** | `module_registry.py` Kommentar; `widget_catalog.py`; `PLACEHOLDER_REGISTRY_FRAMEWORK.md` §2.3 |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Platzhalter: paralleles `PLACEHOLDER_MAP` neben Registry. |
### 2. ID-Stabilität als Vertrag
| | |
|---|---|
| **Prinzip** | IDs/Keys sind stabile API-Verträge; Umbenennung = neuer Key + Deprecation, nicht stilles Rename. |
| **Begründung** | Prompts, Layouts und CSV-Vorlagen referenzieren IDs persistent. |
| **Quelle** | `docs/PLACEHOLDER_GOVERNANCE.md` §4.24.3; Widget-Layout in `profiles.dashboard_layout` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nicht überall runtime-erzwungen (Platzhalter-Governance prozessual). |
### 3. Metadaten getrennt von Implementierung
| | |
|---|---|
| **Prinzip** | Registry speichert **Was** (ID, Beschreibung, Typ, Policies); Implementierung lebt in separaten Modulen. |
| **Begründung** | GUI, Export, Validierung und Docs können Metadaten nutzen ohne Resolver/Component zu laden. |
| **Quelle** | `PlaceholderMetadata` Dataclass; `WidgetCatalogEntry`; `MODULE_DEFINITIONS.fields` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Platzhalter bindet `_resolver_func` optional an Metadata-Objekt. |
### 4. Zwei-Phasen-Registrierung (Backend-Kanon + Runtime-Binding)
| | |
|---|---|
| **Prinzip** | Phase A: Kanon definiert IDs und Metadaten. Phase B: Implementierung registriert sich (Platzhalter-Cluster-Import, `registerDashboardWidget`, Executor nutzt Modul-Def). |
| **Begründung** | Backend bleibt autoritativ; Frontend/plugins können nachziehen, Tests können Lücken finden. |
| **Quelle** | `import placeholder_registrations` in `main.py`; `ensureDashboardWidgetsRegistered()` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Fehlende Frontend-Registrierung zeigt „Unbekanntes Widget“ — kein Build-Time-Fail. |
### 5. Auto-Registration via Package-Import
| | |
|---|---|
| **Prinzip** | Side-Effect-Import eines Packages triggert vollständige Registrierung (`placeholder_registrations/__init__.py`). |
| **Begründung** | Keine vergessene manuelle Registrierungsliste in `main.py` pro Eintrag. |
| **Quelle** | `placeholder_registrations/__init__.py`; `main.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Import-Reihenfolge und zirkuläre Imports beachten. |
### 6. Validierung am Registry-Rand
| | |
|---|---|
| **Prinzip** | Unbekannte Keys/Widget-IDs/Feld-Mappings werden an Registry-Grenzen abgewiesen, nicht erst in der Business-Logik. |
| **Begründung** | Frühes, klares Fehlerbild für Admins und Entwickler. |
| **Quelle** | `DashboardWidgetEntry` + `ALLOWED_WIDGET_IDS`; `validate_field_mappings()`; `get_unknown_placeholders()` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Prompt-Templates können unbekannte Platzhalter erst zur Laufzeit offenbaren. |
### 7. Konsumenten-Agnostik
| | |
|---|---|
| **Prinzip** | Dieselbe Registry bedient mehrere Konsumenten (KI, Charts, Export, Admin-Browser) ohne duplizierte Metadaten. |
| **Begründung** | DRY für Beschreibungen, Kategorien, Beispielwerte. |
| **Quelle** | `PLACEHOLDER_REGISTRY_FRAMEWORK.md` §2.2; `get_placeholder_catalog()` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Legacy-Export-Pfade mergen noch „Registry + Legacy“. |
### 8. Erweiterungs-Checkliste statt Ad-hoc
| | |
|---|---|
| **Prinzip** | Jedes Registry hat dokumentierte Schritte A→G (Katalog-Eintrag, Validierung, Tests, Version-Bump). |
| **Begründung** | Agenten und Menschen erweitern konsistent; Review an Checkliste. |
| **Quelle** | `DASHBOARD_WIDGETS_AGENT_GUIDE.md` §2; `UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md` §2; `PLACEHOLDER_DEVELOPMENT_GUIDE.md` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Drei separate Guides — kein unified „Registry Agent Guide“. |
### 9. Tests auf Katalog-Konsistenz
| | |
|---|---|
| **Prinzip** | Automatisierte Tests prüfen Eindeutigkeit, Reihenfolge, Payload-Shape, ID-Abgleich Kanon ↔ abgeleitete Sets. |
| **Begründung** | Regression wenn Katalog wächst but Registry/Layout nicht mitzieht. |
| **Quelle** | `test_widget_catalog.py`; Placeholder-Metadata-Tests |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Kein Cross-Registry-Test „Frontend widget IDs == backend catalog“. |
### 10. Optionale Entitlement-Referenz im Kanon
| | |
|---|---|
| **Prinzip** | Registry-Einträge **referenzieren** Feature-IDs (`requires_feature`), lösen Entitlements aber nicht selbst auf. |
| **Begründung** | Tier-Logik bleibt in `check_feature_access`; Katalog bleibt deklarativ. |
| **Quelle** | `WidgetCatalogEntry.requires_feature`; `dashboard_widget_entitlements.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Platzhalter haben kein direktes `requires_feature` — Gating nur indirekt. |
---
## Designprinzipien (spezifisch pro Registry)
### Platzhalter-Registry
| # | Prinzip | Tragfähigkeit | Schwäche |
|---|---------|---------------|----------|
| P1 | **Semantischer Vertrag** (`semantic_contract`) pro Key — Platzhalter sind API, nicht Prompt-Hilfe | hoch | Viele Legacy-Keys mit schwachem Vertrag |
| P2 | **Evidence-Tagging** — Herkunft jedes Metadatenfelds nachvollziehbar | mittel | Pflegeaufwand |
| P3 | **Cluster-Module** — Registrierung nach Domäne (`nutrition_part_a`, `body_metrics`, …) | hoch | 114 Keys Sync mit `PLACEHOLDER_MAP` |
| P4 | **Data-Layer-Referenz** in Metadata (`data_layer_function`) — Bindung an Layer 1 | hoch | Nicht alle Keys vollständig verknüpft |
| P5 | **Singleton** `get_registry()` — globaler Kanon | hoch | Test-Isolation braucht Disziplin |
### Dashboard-Widget-Registry
| # | Prinzip | Tragfähigkeit | Schwäche |
|---|---------|---------------|----------|
| W1 | **Backend-Katalog = ALLOWED_WIDGET_IDS** — Layout-Schema leitet ab | hoch | Frontend-Registry manuell parallel |
| W2 | **`merge_missing_catalog_widgets`** — neue Katalog-IDs erscheinen im Layout ohne User-Reset | hoch | — |
| W3 | **Strikte `config`-Validierung** nur für whitelisted Widgets (`WIDGETS_ALLOWING_CONFIG`) | hoch | Config-Schemas pro Widget heterogen |
| W4 | **Graceful Degradation** — unregistrierte ID → Fehler-Karte, nicht Crash | mittel | Maskiert Deploy-Fehler |
| W5 | **WidgetErrorBoundary** pro Instanz | hoch | — |
### CSV-Modul-Registry
| # | Prinzip | Tragfähigkeit | Schwäche |
|---|---------|---------------|----------|
| C1 | **`MODULE_DEFINITIONS` = einzige Feldliste** — Router duplizieren nicht | hoch | Activity erweitert dynamisch um `training_parameters` |
| C2 | **Deklarative Duplikat-Strategie** (`duplicate_key`, `update`/`skip`) | hoch | Modul-spezifische Executor-Sonderfälle |
| C3 | **`import_row_processing`** — Aggregation in Registry, nicht im Router | hoch | Legacy-Defaults in Modul-Def |
| C4 | **`validate_field_mappings`** vor Persistenz | hoch | Nutzer-Mappings teils ohne volle Validator-Parität (#71) |
| C5 | **Persistenz-Orchestrator** liest Registry-Felder (`activity_persistence_orchestrator`) | hoch | Nur Activity voll ausgebaut |
---
## Anti-Pattern: Doppel-Registry
Mitai zeigt an **Platzhaltern** das Risiko explizit:
```
placeholder_registrations/ ──register──► PlaceholderRegistry (Metadata)
│ ▲
└── resolver in code ──► PLACEHOLDER_MAP (Runtime, 114 Keys)
```
**Regel für Produktfamilie:** Runtime-Auflösung soll Metadata-Registry **lesen**, nicht spiegeln.
Widgets sind näher am Ideal: Backend `WIDGET_CATALOG` ist Kanon; Frontend muss IDs manuell in `registerDashboardWidgets.js` binden — akzeptabel, aber testbar machen (Cross-Check).
---
## Nicht übernehmen
1. **Parallele Runtime-Maps**`PLACEHOLDER_MAP` + Registry; eine Quelle für Keys und Resolver-Pfad.
2. **Frontend-Registry ohne Build-/Test-Gate** — fehlende `registerDashboardWidget`-Einträge erst zur Laufzeit sichtbar.
3. **Metadaten-Duplikation in Export-Code** — hardcodierte Beschreibungen außerhalb der Registry.
4. **Registry-Einträge ohne Validierungs-Tests** — besonders bei 100+ Platzhaltern.
5. **Import-Feldlisten in Routern** — alles über `module_registry` (Mitai-Zielbild, noch nicht überall).
6. **Evidence-/Metadata-Pflicht für einfache Plugins** — 22 Felder für Widgets wären Overkill; **Metadaten-Tiefe an Risiko anpassen**.
7. **Dynamische Registry aus DB ohne Versionierung** — Widget-Feature-Overrides OK; kompletter Kanon nur in DB wäre schwer testbar.
8. **Registry ohne Deprecation-Pfad** — Breaking Key-Changes still (Platzhalter-Governance existiert, durchsetzen).
9. **Entitlements in Registry auflösen** — Widgets richtig: referenzieren; nicht Tier-Logik im Katalog.
10. **Schlaf-Modul leeres `fields: {}`** — Sondermodus (`import_mode`) statt sauberem Registry-Eintrag; technische Schuld.
---
## Entscheidungsmatrix: Welche Registry-Tiefe?
| Wenn … | dann Metadaten-Tiefe … | Beispiel |
|--------|------------------------|----------|
| Externe Verträge / KI / Compliance | Hoch (Vertrag, Evidence, Missing-Policy) | Platzhalter |
| UI-Plugin mit optionaler Config | Mittel (ID, title, config schema, feature ref) | Widgets |
| Daten-Ingest / Schema-Mapping | Hoch (Typen, keys, constraints) | CSV-Module |
| Internes Hilfsmodul | Minimal (ID + Handler-Ref) | — |
---
## Modul-Inventar (Querschnitt)
```
backend/
├── placeholder_registry.py
├── placeholder_registrations/ # Auto-import Cluster
├── placeholder_resolver.py # PLACEHOLDER_MAP (Legacy-Spiegel)
├── placeholder_registry_export.py
├── widget_catalog.py
├── dashboard_layout_schema.py
├── dashboard_widget_config.py
├── dashboard_widget_entitlements.py
├── widget_feature_requirements_db.py
└── csv_parser/
└── module_registry.py
frontend/src/
├── widgetSystem/
│ ├── registerDashboardWidgets.js
│ └── dashboardWidgetRegistry.jsx
└── components/workflow/panels/PlaceholderPicker.jsx
```
---
## Verwandte Dokumentation
- Platzhalter: [PLACEHOLDER_REGISTRY_FRAMEWORK.md](../../technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md), [PLACEHOLDER_GOVERNANCE.md](../../../../docs/PLACEHOLDER_GOVERNANCE.md)
- Widgets: [DASHBOARD_WIDGETS_AGENT_GUIDE.md](../../technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md)
- Import: [UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md](../../technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md)
- Entitlements (Widget-Gating): [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md)
- Data Layer (Platzhalter-Berechnung): [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md)
- Prompt Engine (Platzhalter-Konsument): [PROMPT_ENGINE_DESIGN_PRINCIPLES.md](./PROMPT_ENGINE_DESIGN_PRINCIPLES.md)
---
## Geplante Folgedokumente (Serie)
| # | Modul | Status |
|---|-------|--------|
| 1 | Prompt Engine | ✅ |
| 2 | Data Layer | ✅ |
| 3 | Feature & Entitlement | ✅ |
| 4 | Registry-/Plugin-Muster | ✅ dieses Dokument |
| 5 | Auth & Session | ✅ `AUTH_SESSION_DESIGN_PRINCIPLES.md` |
| 6 | Universal Import | ✅ |
| 7 | Dashboard Widgets | ✅ |
| 8 | Navigation / IA | ✅ |
| 9 | Migration & Deploy | ✅ |
*Hinweis:* Dokumente 6 und 7 vertiefen Einzel-Registries; dieses Meta-Dokument ist die übergreifende Extraktion.

View File

@ -1,325 +0,0 @@
# Universal CSV Import Designprinzipien (Extraktion)
**Status:** Analyse / Arbeitspapier
**Stand:** 2026-07-04
**Geltungsbereich:** Universal CSV Import (Issue #21) — Ingest/Mapping/Persistenz, keine Auswertungslogik
**Serie:** Designprinzipien für Produktfamilie · Dokument 6 von n
**Vorgänger:** [AUTH_SESSION_DESIGN_PRINCIPLES.md](./AUTH_SESSION_DESIGN_PRINCIPLES.md)
**Kernkomponenten:**
| Bereich | Pfade |
|---------|-------|
| Modul-Kanon | `backend/csv_parser/module_registry.py` |
| Ausführung | `backend/csv_parser/executor.py` |
| Parsing/Typen | `core.py`, `type_converter.py`, `field_units.py` |
| Aggregation | `import_row_processing.py` |
| Validierung | `template_validator.py` |
| Fehler-Hints | `import_errors.py` |
| Mapping-Vorschläge | `mapping_suggest.py` |
| Nutzer-API | `backend/routers/csv_import.py` |
| Admin-Vorlagen | `backend/routers/admin_csv_templates.py` |
| Persistenz-Orchestrator | `data_layer/activity_persistence_orchestrator.py` |
| Leitfaden | `UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md` |
| Import-Grenze | `.claude/rules/ARCHITECTURE.md` §8 |
---
## Modul
**Universal CSV Import**
Konfigurierbare Pipeline: **CSV-Datei → Feld-Mapping → Typkonvertierung → (optional Aggregation) → DB-Upsert** — mit Vorlagen, Audit-Log und row-level Fehlertoleranz.
---
## Fachliche Verantwortung
Das Modul übernimmt:
1. **Modul-Registry** — Welche Zieltabellen/Felder importierbar sind (Typen, Duplikat-Keys, Strategien).
2. **Vorlagen (Mappings)** — System-Templates (Admin) + Nutzer-Kopien (`csv_field_mappings`).
3. **Analyse** — Delimiter-Erkennung, Spalten-Signatur, Mapping-Vorschläge, Diagnose einzelner Zeilen.
4. **Ausführung** — Upsert pro Modul, `source=csv`, Statistik, `affected_ids`.
5. **Fehlertransparenz** — Row-level Errors mit `code`/`hint`; kein Silent-Fail der ganzen Transaktion.
6. **Audit**`csv_import_log` mit Status, Counts, betroffenen IDs.
7. **Limits** — Dateigröße/Zeilen aus `system_config`; Feature-Entitlements pro Modul.
Es übernimmt **nicht**:
- Fachliche Metriken / Scores (→ Data Layer, siehe [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md))
- Prompt-/KI-Logik
- Vollständiger Ersatz aller Legacy-Import-Endpoints (noch parallel)
### Pipeline (Happy Path)
```
Upload CSV
→ decode_raw_bytes + resolve_effective_csv_delimiter
→ Vorlage laden (csv_field_mappings)
→ validate (optional Admin) / feature check
→ run_universal_csv_import(cur, …) // eine Transaktion
→ build_row_after_mapping (type_converter)
→ aggregate_mapped_rows (import_row_processing)
→ UPSERT / activity_persistence_orchestrator
→ csv_import_log UPDATE + increment_feature_usage
```
### Unterstützte Module (Registry)
| Modul | Zieltabelle | Besonderheit |
|-------|-------------|--------------|
| `nutrition` | `nutrition_log` | Tages-Aggregation |
| `weight` | `weight_log` | Duplikat: profile + date |
| `activity` | `activity_log` | SAVEPOINT pro Zeile; EAV via Orchestrator |
| `vitals_baseline` | `vitals_baseline` | Tages-Aggregation |
| `blood_pressure` | `blood_pressure_log` | Composite `measured_at` |
| `sleep` | `sleep_log` | Legacy-Adapter `import_mode: apple_sleep_aggregate` |
---
## Administrierte vs. code-definierte Konfiguration
| Konfiguration | Speicherort | Wer pflegt? |
|---------------|-------------|-------------|
| Zielfelder, Typen, Duplikat-Keys | `MODULE_DEFINITIONS` | Entwickler (Code) |
| System-Vorlagen | `csv_field_mappings` (`is_system=true`) | Admin (+ Migration Seeds) |
| Nutzer-Mappings | `csv_field_mappings` (`profile_id`) | Nutzer (Kopie/Anpassung) |
| `field_mappings`, `type_conversions`, `import_row_processing` | JSONB in Vorlage | Admin/Nutzer |
| Import-Limits | `system_config.csv_import` | Admin |
| Delimiter-Sniffing-Heuristik | `core.py` | Code |
| Header-Aliases (Vorschläge) | `mapping_suggest.py` | Code |
**Bewusst nicht in Routern hardcodiert:** Feldlisten, Duplikat-Logik — nur Registry + Executor.
---
## Designprinzipien
### 1. Module Registry als Single Source of Truth
| | |
|---|---|
| **Prinzip** | Alle erlaubten Zielfelder, Typen und Duplikat-Keys leben in `MODULE_DEFINITIONS` — Router duplizieren nicht. |
| **Begründung** | Admin-UI, Validator, Executor und `/api/csv/modules` bleiben synchron. |
| **Quelle** | `module_registry.py`; Agent-Guide §1 |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Activity erweitert Felder dynamisch aus `training_parameters` (DB). |
### 2. Ingest vs. Interpretation (Import-Grenze)
| | |
|---|---|
| **Prinzip** | Import: Mapping + Typ/Einheit + Duplikat/Upsert. **Keine** fachliche Auswertung beim Insert. |
| **Begründung** | Semantik gehört in Data Layer; Import bleibt austauschbar und testbar. |
| **Quelle** | `ARCHITECTURE.md` §8; [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md) |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | `sleep_apple_import.py` ist Legacy-Adapter mit quellenspezifischer Logik. |
### 3. Vorlagen trennen Struktur von Datei
| | |
|---|---|
| **Prinzip** | `csv_field_mappings` speichert Modul, Delimiter, Header-Flag, Mappings, Conversions, Row-Processing — unabhängig vom Upload. |
| **Begründung** | Wiederverwendung (Apple Health, Omron, …); Nutzer wählt Vorlage statt jedes Mal neu zu mappen. |
| **Quelle** | Migration 042; Admin + User APIs |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nutzer-Kopien nicht immer durch `validate_csv_template` (#71). |
### 4. Effektives Trennzeichen aus Datei, nicht blind aus Vorlage
| | |
|---|---|
| **Prinzip** | `resolve_effective_csv_delimiter` — DE-Export (`;`) vs. EN-Vorlage (`,`) wird aus Header-Feldanzahl erkannt. |
| **Begründung** | Regionale CSV-Exporte brechen sonst das gesamte Mapping (eine Spalte). |
| **Quelle** | `core.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Heuristik, kein 100%-Garant für exotische Formate. |
### 5. Typ- und Einheiten-Konvertierung deklarativ
| | |
|---|---|
| **Prinzip** | `type_conversions` + `source_unit` in Vorlage; Logik in `type_converter` / `field_units`. |
| **Begründung** | kJ→kcal, Datumsformate, Dezimal-Komma ohne Code pro Quelle. |
| **Quelle** | `type_converter.py`, `field_units.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Falsche `source_unit` → DB-Overflow; `enrich_row_error` hilft nachträglich. |
### 6. Zeilen-Aggregation vor Upsert
| | |
|---|---|
| **Prinzip** | `import_row_processing` (group_by + aggregates) fasst mehrere CSV-Zeilen pro logischem Tag/Datensatz zusammen. |
| **Begründung** | Ernährung/Vitals: viele Rohzeilen → ein Tageseintrag. |
| **Quelle** | `import_row_processing.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Modul-Default als Legacy-Fallback wenn Vorlage leer; Admin „Format prüfen“ kann Processing auslassen. |
### 7. Ein Cursor, eine Transaktion, SAVEPOINT pro Zeile
| | |
|---|---|
| **Prinzip** | `run_universal_csv_import(cur, …)` nutzt **bestehenden** Cursor; bei Row-Fehlern SAVEPOINT + ROLLBACK TO, nicht ganze Xact abbrechen. |
| **Begründung** | PostgreSQL „transaction aborted“; partielle Imports mit Fehlerliste. |
| **Quelle** | `executor.py` (activity, vitals); `csv_import.py` SAVEPOINT `csv_import_exec` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nicht alle Module gleich implementiert; Disziplin pro Modul. |
### 8. Kein verschachteltes get_db im Importpfad
| | |
|---|---|
| **Prinzip** | FK-Auflösung (z. B. Trainingstyp) und Activity-Persistenz mit **demselben** `cur` wie der Import. |
| **Begründung** | Pool-Deadlocks, konsistente Transaktion. |
| **Quelle** | `_resolve_training_type_for_activity`; Agent-Guide §2 |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Lazy-Import aus Router in Executor (Kopplung). |
### 9. Strukturierte Fehler mit Hints
| | |
|---|---|
| **Prinzip** | `enrich_row_error()` mappt DB-/Parse-Fehler auf `code` + menschenlesbaren `hint`. |
| **Begründung** | Nutzer/Admin können Vorlagen korrigieren ohne PostgreSQL-Kenntnis. |
| **Quelle** | `import_errors.py`; Import-Response `error_details` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Heuristische String-Matches, nicht vollständig. |
### 10. Vorlagen-Validierung vor Persistenz (Admin)
| | |
|---|---|
| **Prinzip** | `validate_csv_template``{ valid, errors[], warnings[] }`; Admin Create/Update → HTTP 422 bei Fehlern. |
| **Begründung** | Fehler früh, nicht erst beim Nutzer-Import. |
| **Quelle** | `template_validator.py`; `admin_csv_templates.py` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Dry-Run / User-Mappings Lücken (#71). |
### 11. System- vs. User-Mappings (Permissions)
| | |
|---|---|
| **Prinzip** | `is_system=true`: nur Admin editierbar; Nutzer kopiert und passt eigene Zeile an. |
| **Begründung** | Shipped Templates schützen; Individualisierung erlauben. |
| **Quelle** | `permissions.py`; DB CHECK + Unique Indexes |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | — |
### 12. Import-Audit und Rollback-Vorbereitung
| | |
|---|---|
| **Prinzip** | Jeder Lauf schreibt `csv_import_log` mit Counts, `error_details`, `affected_ids` (PKs pro Tabelle). |
| **Begründung** | Nachvollziehbarkeit, spätere Bereinigung/Rollback, Erfolgsrate pro Vorlage. |
| **Quelle** | Migration 042; `csv_import_execute` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Automatischer Rollback-Button nicht überall umgesetzt. |
### 13. Feature-Entitlements an Import gebunden
| | |
|---|---|
| **Prinzip** | `data_import` global + modulspezifisch (`nutrition_entries`, …); Increment nur für **neue** Zeilen. |
| **Begründung** | Konsistent mit Membership-System. |
| **Quelle** | `csv_import.py` `_check_module_feature_access`, `increment_feature_usage` |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Bulk-Increment-Schleife ineffizient (wie Feature-Doc). |
### 14. Mapping-Vorschläge (Heuristik, nicht Autorität)
| | |
|---|---|
| **Prinzip** | `mapping_suggest.py` schlägt Spalten-Zuordnung aus Header-Aliases vor — Admin bestätigt. |
| **Begründung** | Schneller Editor-Start; Kanon bleibt menschlich/administrativ freigegeben. |
| **Quelle** | `_MODULE_HEADER_ALIASES` |
| **Tragfähigkeit** | **mittelhoch** |
| **Einschränkung** | Domänenspezifische Aliases hardcodiert (DE/EN). |
### 15. Persistenz-Orchestrator für komplexe Domänen
| | |
|---|---|
| **Prinzip** | Activity: nach Registry-Mapping → `activity_persistence_orchestrator` (Upsert + EAV + Eval-Hook). |
| **Begründung** | Gleiche Schreiblogik wie REST-API; kein divergierender CSV-Pfad. |
| **Quelle** | `activity_persistence_orchestrator.py`; [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md) §9 |
| **Tragfähigkeit** | **hoch** |
| **Einschränkung** | Nur Activity vollständig; andere Module direkt im Executor. |
---
## Nicht übernehmen
1. **Parallele Legacy-Import-Endpoints**`/api/nutrition/import-csv`, `/api/activity/import-csv` neben Universal-Pfad; neue Quellen nur über Universal + Vorlage (ARCHITECTURE §8.2).
2. **Quellenspezifische Aggregat-Logik im Import**`sleep_apple_import` als Dauerlösung; Ziel: mapping-nah + Layer 1 (Gitea #69).
3. **Feldlisten in Routern** — jede neue Spalte nur via `module_registry` + Migration.
4. **Verschachtelte DB-Connections im Executor** — Pool-Risiko; immer Caller-`cur` durchreichen.
5. **Transaktion ohne SAVEPOINT bei Multi-Row-Import** — ein Fehler killt gesamten Import + opaque „transaction aborted“.
6. **Blindes Vorlagen-Delimiter** — regionaler Export bricht Mapping.
7. **Nutzer-Mappings ohne Validierung**#71; Copy-from-System muss durch Validator.
8. **Dry-Run ohne `import_row_processing`** — Admin „Format prüfen“ unvollständig vs. echter Import.
9. **`source`-CHECK in DB vergessen** — Import setzt `csv`, Constraint muss Migration sein.
10. **NUMERIC-Overflow durch falsche Einheit** — Schema + `source_unit` + Migrationbreite gemeinsam planen.
11. **Interpretation/Auswertung beim Import** — Scores, TDEE, Training-Quality nicht in `executor.py`.
12. **Executor-Monolith ohne Modul-Split**`executor.py` wächst pro Modul; langfristig Executor-Strategie pro Registry-Key.
---
## Modul-Inventar (Ist-Stand)
```
backend/csv_parser/
├── module_registry.py # MODULE_DEFINITIONS
├── executor.py # run_universal_csv_import
├── core.py # decode, delimiter, limits
├── type_converter.py
├── field_units.py
├── import_row_processing.py
├── template_validator.py
├── import_errors.py
├── mapping_suggest.py
├── permissions.py
└── sleep_apple_import.py # Legacy-Adapter
backend/routers/
├── csv_import.py # Nutzer: modules, analyze, import, mappings
└── admin_csv_templates.py # Admin: CRUD + validate
DB:
├── csv_field_mappings
└── csv_import_log
```
---
## Verwandte Dokumentation
- Agent-Guide (normativ): [UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md](../../technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md)
- Registry-Meta: [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md)
- Import-Grenze: [ARCHITECTURE.md](../../../rules/ARCHITECTURE.md) §8
- Feature-Limits: [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md)
- Gitea #71: Dry-Run, User-Mapping-Validierung
---
## Geplante Folgedokumente (Serie)
| # | Modul | Status |
|---|-------|--------|
| 15 | … | ✅ |
| 6 | Universal Import | ✅ dieses Dokument |
| 7 | Dashboard Widgets | ✅ `DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md` |
| 8 | Navigation / IA | ✅ |
| 9 | Migration & Deploy | ✅ |

View File

@ -20,7 +20,6 @@
|-----|------|----------------|
| **Fachliche Spec (WAS)** | `.claude/docs/functional/` | Domäne, Use Cases, UX-Ziele, fachliche Datenarchitektur. **Keine** reine API-Parameterliste (→ technical). |
| **Technische Spec (WIE)** | `.claude/docs/technical/` | API-, DB-, Implementierungsmuster, Agent-Guides, Migrationen. |
| **Produktfamilie / Foundation** | `.claude/docs/jinkendo-foundation/` | Übertragbare Designprinzipien (nicht Mitai-Domäne); Einstieg `design-principles/README.md`. |
| **Architektur-Querschnitt** | `.claude/docs/architecture/` | Kurze Überblicke (z.B. Frontend-Baum), ergänzend zu technical. |
| **Arbeitspapier / Zwischenstand** | `.claude/docs/working/` | Analysen, Sessions, Migration-Notizen, **keine** langfristige Norm. Kann veraltet sein → Datum im Dokument. **Nicht** als alleinige „Wahrheit“ für Produkt zitieren. |
| **Audits & Matrizen** | `.claude/docs/audit/` | Zeitlich begrenzte Reviews, Reconciliation, Gitea-Vorlagen. |
@ -64,7 +63,6 @@ Diese Ordner sind **kein** Ersatz für `working/` oder `docs/issues/`, wenn das
```
.claude/README.md ← Einstieg Agent/Human
.claude/docs/README.md ← Spec-Katalog
.claude/docs/jinkendo-foundation/ ← Foundation Produktfamilie (Designprinzipien)
.claude/docs/functional/ ← WAS
.claude/docs/technical/ ← WIE
.claude/docs/working/ ← Arbeitspapiere / Analysen

View File

@ -11,7 +11,6 @@
> | **Universal CSV Import** (neues Modul / Executor / Vorlagen) | **`.claude/docs/technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md`** |
> | **GUI / IA / Admin / Nav / PWA-Leiste** | **`docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md`** |
> | **Dashboard-Widgets** (Katalog, Registrierung, `config`) | **`.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md`** |
> | **Designprinzipien Produktfamilie** (Serie 19, Foundation) | **`.claude/docs/jinkendo-foundation/design-principles/README.md`** |
> | **Agent-Einstieg** | **`.claude/README.md`** |
> | **Activity Session Metrics (EAV, Attributprofile)** | **`.claude/docs/technical/ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md`** |

View File

@ -135,7 +135,7 @@ def insert_activity_from_entry(cur, profile_id: str, eid: str, e: ActivityEntry)
hr_avg,hr_max,hr_min,distance_km,pace_min_per_km,cadence,avg_power,elevation_gain,
temperature_celsius,humidity_percent,avg_hr_percent,kcal_per_km,rpe,source,notes,
training_type_id,training_category,training_subcategory,created)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,CURRENT_TIMESTAMP)""",
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,CURRENT_TIMESTAMP)""",
(
eid,
profile_id,

View File

@ -10,24 +10,8 @@ matplotlib.use("Agg")
import matplotlib.pyplot as plt
def _safe_float_series(raw: list, n: int) -> list[float]:
out: list[float] = []
for i in range(n):
if i >= len(raw):
out.append(0.0)
continue
v = raw[i]
try:
out.append(float(v) if v is not None else 0.0)
except (TypeError, ValueError):
out.append(0.0)
return out
def _color_to_rgb(hex_or_rgba: str) -> tuple[float, float, float]:
s = (hex_or_rgba or "#333333").strip()
if s.startswith("#") and len(s) >= 9:
s = s[:7]
if s.startswith("#") and len(s) >= 7:
try:
r = int(s[1:3], 16) / 255.0
@ -59,87 +43,7 @@ def chart_payload_to_png(payload: dict[str, Any], fig_width_in: float = 6.2, fig
else:
ax.text(0.5, 0.5, "Keine Daten", ha="center", va="center", transform=ax.transAxes)
elif chart_type == "dual_axis_line" and datasets and labels:
ax2 = ax.twinx()
x = list(range(len(labels)))
step = max(1, len(labels) // 8)
h1, l1 = [], []
h2, l2 = [], []
for i, ds in enumerate(datasets):
y_raw = ds.get("data") or []
if not y_raw:
continue
y = _safe_float_series(y_raw, len(labels))
lab = ds.get("label") or f"Serie {i + 1}"
col = _color_to_rgb(str(ds.get("borderColor") or ds.get("backgroundColor") or "#333333"))
y_axis = str(ds.get("yAxisID") or "").lower()
use_right = y_axis in ("right", "weight", "y1")
use_left = not use_right
target = ax if use_left else ax2
linestyle = "--" if ds.get("borderDash") else "-"
(ln,) = target.plot(
x[: len(y)],
y,
label=lab,
color=col,
linewidth=1.8,
linestyle=linestyle,
marker="o",
markersize=2 if linestyle == "-" else 0,
)
if use_left:
h1.append(ln)
l1.append(lab)
else:
h2.append(ln)
l2.append(lab)
ax.set_xticks(x[::step])
ax.set_xticklabels([labels[j] for j in range(0, len(labels), step)], rotation=25, fontsize=7)
ax.grid(True, alpha=0.25)
ax.set_xmargin(0.02)
leg = h1 + h2
leg_l = l1 + l2
if leg:
ax.legend(leg, leg_l, loc="upper right", fontsize=7)
elif chart_type == "bar" and datasets and labels:
n = len(labels)
x = list(range(n))
stack_keys_nonnull = [ds.get("stack") for ds in datasets if (ds.get("data") or []) != []]
use_stacked = (
len(datasets) >= 2
and len(stack_keys_nonnull) == len(datasets)
and all(sk for sk in stack_keys_nonnull)
and len(set(stack_keys_nonnull)) == 1
)
if use_stacked:
bottom = [0.0] * n
for ds in datasets:
y_raw = ds.get("data") or []
yv = _safe_float_series(y_raw, n)
lab = ds.get("label") or "Serie"
col = _color_to_rgb(str(ds.get("backgroundColor") or ds.get("borderColor") or "#1D9E75"))
ax.bar(x, yv, bottom=bottom, label=lab, color=col, alpha=0.9, width=0.72)
bottom = [bottom[i] + yv[i] for i in range(n)]
ax.set_xticks(x)
ax.set_xticklabels(labels, rotation=30, fontsize=7)
else:
width = 0.8 / max(len(datasets), 1)
for i, ds in enumerate(datasets):
y_raw = ds.get("data") or []
yv = _safe_float_series(y_raw, n)
offset = (i - len(datasets) / 2 + 0.5) * width
xpos = [xj + offset for xj in x]
lab = ds.get("label") or f"Serie {i + 1}"
col = _color_to_rgb(str(ds.get("backgroundColor") or ds.get("borderColor") or "#1D9E75"))
ax.bar(xpos, yv, width=width * 0.9, label=lab, color=col, alpha=0.88)
ax.set_xticks(x)
ax.set_xticklabels(labels, rotation=30, fontsize=7)
ax.legend(loc="upper right", fontsize=7)
ax.grid(True, axis="y", alpha=0.25)
ax.set_xmargin(0.02)
elif chart_type in ("line", "scatter") and datasets:
elif chart_type in ("line", "bar", "scatter") and datasets:
x = range(len(labels)) if labels else []
for i, ds in enumerate(datasets):
y = ds.get("data") or []
@ -147,21 +51,31 @@ def chart_payload_to_png(payload: dict[str, Any], fig_width_in: float = 6.2, fig
continue
lab = ds.get("label") or f"Serie {i + 1}"
col = _color_to_rgb(str(ds.get("borderColor") or ds.get("backgroundColor") or "#1D9E75"))
ls = "--" if ds.get("borderDash") else "-"
if chart_type == "bar":
yv = y[: len(labels)] if labels else y
bg = ds.get("backgroundColor")
if isinstance(bg, list):
cols = [_color_to_rgb(str(c)) for c in bg[: len(yv)]]
else:
cols = [_color_to_rgb(str(bg or "#1D9E75"))] * len(yv)
ax.bar(list(range(len(yv))), yv, label=lab, color=cols[: len(yv)], alpha=0.88)
else:
ax.plot(
list(x)[: len(y)],
y,
label=lab,
color=col,
linewidth=1.6,
linestyle=ls,
marker="o",
markersize=2,
)
if labels:
if labels and chart_type != "bar":
step = max(1, len(labels) // 8)
ax.set_xticks(list(x)[::step])
ax.set_xticklabels([labels[j] for j in range(0, len(labels), step)], rotation=25, fontsize=7)
elif labels and chart_type == "bar":
ax.set_xticks(list(x))
ax.set_xticklabels(labels, rotation=30, fontsize=7)
ax.legend(loc="upper right", fontsize=7)
ax.grid(True, alpha=0.25)
ax.set_xmargin(0.02)

View File

@ -122,15 +122,6 @@ def _weight_series_payload(bundle_weight: dict[str, Any]) -> dict[str, Any] | No
"borderColor": "#378ADD",
}
)
if any(p.get("avg14") is not None for p in series):
datasets.append(
{
"label": "Ø 14T",
"data": [safe_float(p.get("avg14")) for p in series],
"borderColor": "#085041",
"borderDash": [6, 3],
}
)
return {"chart_type": "line", "data": {"labels": labels, "datasets": datasets}, "metadata": {"confidence": "high"}}
@ -154,97 +145,6 @@ def _line_payload_from_points(
}
def _donut_avg_to_pie_payload(donut: list[dict[str, Any]] | None) -> dict[str, Any] | None:
if not donut:
return None
labels = [str(x.get("name") or "") for x in donut]
values = [safe_float(x.get("value")) for x in donut]
colors = [str(x.get("color") or "#666666") for x in donut]
if not labels or not values or len(labels) != len(values):
return None
return {
"chart_type": "pie",
"data": {
"labels": labels,
"datasets": [{"data": values, "backgroundColor": colors}],
},
"metadata": {"confidence": "high"},
}
def _daily_macros_stacked_bar_payload(daily_macros: list[dict[str, Any]]) -> dict[str, Any] | None:
if len(daily_macros) < 2:
return None
labels = [str(d.get("date") or "") for d in daily_macros]
return {
"chart_type": "bar",
"data": {
"labels": labels,
"datasets": [
{
"label": "Protein (g)",
"data": [safe_float(d.get("Protein")) for d in daily_macros],
"backgroundColor": "#4a8f72",
"stack": "daily",
},
{
"label": "Fett (g)",
"data": [safe_float(d.get("Fett")) for d in daily_macros],
"backgroundColor": "#6e8eb8",
"stack": "daily",
},
{
"label": "KH (g)",
"data": [safe_float(d.get("KH")) for d in daily_macros],
"backgroundColor": "#c17d45",
"stack": "daily",
},
],
},
"metadata": {"confidence": "high"},
}
def _kcal_vs_weight_dual_payload(kw: dict[str, Any]) -> dict[str, Any] | None:
pts = kw.get("points") or []
if len(pts) < 2:
return None
labels = [str(p.get("date") or "") for p in pts]
tdee = kw.get("tdee_reference_kcal")
datasets: list[dict[str, Any]] = [
{
"label": "Ø Kalorien (7T)",
"data": [safe_float(p.get("kcal_avg")) for p in pts],
"borderColor": "#EA580C",
"yAxisID": "kcal",
},
]
if tdee is not None and float(safe_float(tdee) or 0) > 0:
td = float(tdee)
datasets.append(
{
"label": "TDEE (geschätzt)",
"data": [td] * len(labels),
"borderColor": "#888888",
"yAxisID": "kcal",
"borderDash": [5, 5],
}
)
datasets.append(
{
"label": "Gewicht (kg)",
"data": [safe_float(p.get("weight")) for p in pts],
"borderColor": "#2563EB",
"yAxisID": "weight",
}
)
return {
"chart_type": "dual_axis_line",
"data": {"labels": labels, "datasets": datasets},
"metadata": {"confidence": "high"},
}
def _append_body_bundle(story: list, styles: dict, profile_id: str, cfg: dict[str, Any]) -> None:
days = int(cfg.get("chart_days") or 30)
bundle = get_body_history_viz_bundle(profile_id, days)
@ -335,27 +235,17 @@ def _append_nutrition_bundle(story: list, styles: dict, profile_id: str, cfg: di
pl2 = charts.get("nutrition_adherence")
if pl2:
_add_chart_to_story(story, styles, pl2, "Ernährungs-Adherence")
if cfg.get("show_macro_daily_bars", False):
dm = bundle.get("daily_macros") or []
pl_dm = _daily_macros_stacked_bar_payload(dm)
if pl_dm:
_add_chart_to_story(story, styles, pl_dm, "Makroverteilung täglich (g) · Fokus Protein")
if cfg.get("show_macro_distribution_pair", False):
pie_pl = _donut_avg_to_pie_payload(bundle.get("donut_avg_pct"))
if pie_pl:
_add_chart_to_story(story, styles, pie_pl, "Ø Makro-Quote (% der Makro-kcal)")
if cfg.get("show_macro_distribution_pair", False) or cfg.get("show_macro_daily_bars", False):
wm = bundle.get("weekly_macro_chart")
if isinstance(wm, dict) and wm.get("chart_type"):
meta_wm = wm.get("metadata") or {}
if meta_wm.get("confidence") != "insufficient":
_add_chart_to_story(story, styles, wm, "Wöch. Makro-Verteilung (Anteil %)")
_add_chart_to_story(story, styles, wm, "Makros (wöchentlich)")
kw = bundle.get("kcal_vs_weight") or {}
if cfg.get("show_kcal_vs_weight", False) and kw.get("points"):
pl_kw = _kcal_vs_weight_dual_payload(kw)
if pl_kw:
_add_chart_to_story(story, styles, pl_kw, "Kalorien (Ø 7 Tage) vs. Gewicht")
pts = kw["points"]
if pts:
pl = _line_payload_from_points(pts, "date", "kcal", "kcal")
if pl:
_add_chart_to_story(story, styles, pl, "Kalorien vs. Zeit")
story.append(Spacer(1, 2 * mm))

View File

@ -1,88 +0,0 @@
"""Audit: INSERT %s-Platzhalter vs. Parameter-Anzahl in backend/."""
from __future__ import annotations
import ast
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def _count_tuple_elements(node: ast.AST) -> int | None:
if isinstance(node, ast.Tuple):
return len(node.elts)
if isinstance(node, ast.List):
return len(node.elts)
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
if node.func.attr in {"values", "keys"}:
return None
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
left = _count_tuple_elements(node.left)
right = _count_tuple_elements(node.right)
if left is not None and right is not None:
return left + right
if isinstance(node, ast.ListComp):
return None
return None
def audit_file(path: Path) -> list[str]:
issues: list[str] = []
try:
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
except SyntaxError:
return issues
for node in ast.walk(tree):
if not isinstance(node, ast.Call):
continue
func = node.func
if not (
isinstance(func, ast.Attribute)
and func.attr == "execute"
and isinstance(func.value, ast.Name)
and func.value.id == "cur"
):
continue
if len(node.args) < 2:
continue
sql_node, params_node = node.args[0], node.args[1]
sql = None
if isinstance(sql_node, ast.Constant) and isinstance(sql_node.value, str):
sql = sql_node.value
elif isinstance(sql_node, ast.JoinedStr):
continue
if not sql or "INSERT" not in sql.upper():
continue
if "VALUES" not in sql.upper():
continue
ph = sql.count("%s")
param_count = _count_tuple_elements(params_node)
if param_count is None:
continue
if ph != param_count:
rel = path.relative_to(ROOT)
issues.append(
f"{rel}:{node.lineno} INSERT placeholders={ph} params={param_count}"
)
return issues
def main() -> int:
issues: list[str] = []
for path in ROOT.rglob("*.py"):
if "venv" in path.parts or "__pycache__" in path.parts:
continue
issues.extend(audit_file(path))
if issues:
print("MISMATCHES:")
for issue in sorted(issues):
print(issue)
return 1
print("OK: keine INSERT-Platzhalter-Mismatches gefunden (statisch prüfbar).")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -1,49 +0,0 @@
"""Regression: INSERT-Platzhalter müssen zur Wert-Tuple passen."""
from data_layer.activity_persistence_orchestrator import (
insert_activity_csv_minimal,
insert_activity_from_entry,
)
from models import ActivityEntry
class _CaptureCursor:
def __init__(self):
self.captured = {}
def execute(self, sql, params):
self.captured["sql"] = sql
self.captured["params"] = params
def test_insert_activity_from_entry_smoke_with_mock_cursor():
cur = _CaptureCursor()
entry = ActivityEntry(date="2026-07-22", activity_type="Laufen")
insert_activity_from_entry(cur, "profile-1", "entry-1", entry)
assert cur.captured["params"] is not None
assert cur.captured["sql"].count("%s") == len(cur.captured["params"])
def test_insert_activity_csv_minimal_smoke_with_mock_cursor():
cur = _CaptureCursor()
insert_activity_csv_minimal(
cur,
"profile-1",
"entry-1",
date_iso="2026-07-22",
start_time=None,
end_time=None,
activity_type="Laufen",
duration_min=45,
kcal_active=None,
kcal_resting=None,
hr_avg=None,
hr_max=None,
distance_km=None,
training_type_id=1,
training_category="cardio",
training_subcategory=None,
source="csv",
)
assert cur.captured["params"] is not None
assert cur.captured["sql"].count("%s") == len(cur.captured["params"])