# Technisches Datenmodell – Mitai Jinkendo

> **Quelle:** Development Database (dev.mitai.jinkendo.de)  
> **Generiert:** 2026-04-02  
> **PostgreSQL Version:** 16-alpine  
> **Anzahl Tabellen:** 43

---

## Inhaltsverzeichnis

1. [Kernmodule](#1-kernmodule)
2. [Tracking-Module](#2-tracking-module)
3. [KI & Analysen](#3-ki--analysen)
4. [Membership & Features](#4-membership--features)
5. [Ziele & Training](#5-ziele--training)
6. [System-Tabellen](#6-system-tabellen)
7. [Backup-Tabellen](#7-backup-tabellen)
8. [ER-Diagramm](#8-er-diagramm)

---

## 1. Kernmodule

### 1.1 profiles

**Beschreibung:** Nutzerstammdaten und Authentifizierung

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK, eindeutige Profil-ID |
| email | varchar | YES | NULL | E-Mail-Adresse (unique) |
| pin_hash | varchar(255) | NO | - | bcrypt-gehashtes Passwort |
| role | varchar(20) | YES | 'user' | Rolle (user, admin) |
| tier | text | YES | 'free' | Membership-Tier |
| created_at | timestamp | YES | now() | Erstellungsdatum |
| email_verified | boolean | YES | false | E-Mail-Verifizierung |
| verification_token | varchar(255) | YES | NULL | Token für E-Mail-Verifizierung |
| invited_by | uuid | YES | NULL | FK zu profiles.id (Einladung) |
| quality_filter_level | varchar(20) | YES | 'disabled' | Qualitätsfilter für Aktivitäten |

**Constraints:**
- PK: `id`
- UNIQUE: `email`
- FK: `invited_by` → `profiles(id)` ON DELETE NO ACTION
- CHECK: `tier IN ('free', 'plus', 'premium', 'trial')`

**Indizes:**
- `idx_profiles_email` (email) WHERE email IS NOT NULL
- `idx_profiles_tier` (tier)
- `idx_profiles_quality_filter` (quality_filter_level)
- `idx_profiles_verification_token` (verification_token) WHERE verification_token IS NOT NULL

**Beziehungen:**
- 1:N zu allen Tracking-Tabellen (profile_id)
- 1:N zu sessions
- 1:N zu ai_insights, ai_usage
- 1:N zu goals, training_phases
- 1:1 zu user_stats
- Self-Reference (invited_by)

---

### 1.2 sessions

**Beschreibung:** Authentifizierungs-Sessions

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| token | varchar(255) | NO | - | PK, Session-Token |
| profile_id | uuid | NO | - | FK zu profiles.id |
| expires_at | timestamp | NO | - | Ablaufdatum |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `token`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_sessions_profile_id` (profile_id)
- `idx_sessions_expires_at` (expires_at)

---

### 1.3 user_stats

**Beschreibung:** Aggregierte Nutzerstatistiken

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| profile_id | uuid | NO | - | PK, FK zu profiles.id |
| total_weight_entries | integer | YES | 0 | Anzahl Gewichtseinträge |
| total_circumference_entries | integer | YES | 0 | Anzahl Umfangseinträge |
| total_caliper_entries | integer | YES | 0 | Anzahl Caliper-Einträge |
| total_activity_entries | integer | YES | 0 | Anzahl Aktivitäten |
| total_nutrition_entries | integer | YES | 0 | Anzahl Ernährungseinträge |
| total_photos | integer | YES | 0 | Anzahl Fotos |
| total_ai_insights | integer | YES | 0 | Anzahl KI-Analysen |
| last_weight_date | date | YES | NULL | Letztes Gewichtsdatum |
| last_activity_date | date | YES | NULL | Letzte Aktivität |
| last_insight_date | timestamp | YES | NULL | Letzte KI-Analyse |
| updated_at | timestamp | YES | now() | Letztes Update |

**Constraints:**
- PK: `profile_id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

---

## 2. Tracking-Module

### 2.1 weight_log

**Beschreibung:** Gewichtsverlauf

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| date | date | NO | - | Messdatum |
| weight | numeric(5,2) | NO | - | Gewicht in kg |
| source | varchar(50) | YES | 'manual' | Datenquelle |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- UNIQUE: `profile_id, date`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_weight_log_profile_date` (profile_id, date DESC)
- `idx_weight_log_profile_date_unique` UNIQUE (profile_id, date)

---

### 2.2 circumference_log

**Beschreibung:** Umfangsmessungen (8 Messpunkte)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| date | date | NO | - | Messdatum |
| c_neck | numeric(5,2) | YES | NULL | Hals (cm) |
| c_chest | numeric(5,2) | YES | NULL | Brust (cm) |
| c_waist | numeric(5,2) | YES | NULL | Taille (cm) |
| c_hips | numeric(5,2) | YES | NULL | Hüfte (cm) |
| c_thigh | numeric(5,2) | YES | NULL | Oberschenkel (cm) |
| c_calf | numeric(5,2) | YES | NULL | Wade (cm) |
| c_biceps | numeric(5,2) | YES | NULL | Bizeps (cm) |
| c_forearm | numeric(5,2) | YES | NULL | Unterarm (cm) |
| source | varchar(50) | YES | 'manual' | Datenquelle |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_circumference_profile_date` (profile_id, date DESC)

---

### 2.3 caliper_log

**Beschreibung:** Hautfaltenmessungen (Körperfettanalyse)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| date | date | NO | - | Messdatum |
| chest | numeric(5,2) | YES | NULL | Brust (mm) |
| abdominal | numeric(5,2) | YES | NULL | Bauch (mm) |
| thigh | numeric(5,2) | YES | NULL | Oberschenkel (mm) |
| triceps | numeric(5,2) | YES | NULL | Trizeps (mm) |
| subscapular | numeric(5,2) | YES | NULL | Schulterblatt (mm) |
| suprailiac | numeric(5,2) | YES | NULL | Hüfte (mm) |
| midaxillary | numeric(5,2) | YES | NULL | Mittelachsel (mm) |
| body_fat_pct | numeric(5,2) | YES | NULL | Körperfett % (berechnet) |
| formula | varchar(20) | YES | NULL | Berechnungsformel |
| source | varchar(50) | YES | 'manual' | Datenquelle |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_caliper_profile_date` (profile_id, date DESC)

---

### 2.4 activity_log

**Beschreibung:** Trainings- und Aktivitätslog

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| date | date | NO | - | Aktivitätsdatum |
| start_time | time | YES | NULL | Startzeit |
| duration_minutes | integer | YES | NULL | Dauer (Minuten) |
| training_type_id | integer | YES | NULL | FK zu training_types.id |
| training_category | varchar(50) | YES | NULL | Kategorie |
| kcal | integer | YES | NULL | Kalorienverbrauch |
| avg_hr | integer | YES | NULL | Durchschnittliche HF |
| max_hr | integer | YES | NULL | Maximale HF |
| distance_km | numeric(6,2) | YES | NULL | Distanz (km) |
| description | text | YES | NULL | Beschreibung |
| source | varchar(50) | YES | 'manual' | Datenquelle |
| evaluation | jsonb | YES | NULL | Qualitätsbewertung |
| overall_score | integer | YES | NULL | Gesamtbewertung (0-100) |
| quality_label | varchar(20) | YES | NULL | Qualitätslabel |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE
- FK: `training_type_id` → `training_types(id)` ON DELETE NO ACTION

**Indizes:**
- `idx_activity_profile_date` (profile_id, date DESC)
- `idx_activity_training_type` (training_type_id)
- `idx_activity_training_category` (training_category)
- `idx_activity_quality_label` (quality_label) WHERE quality_label IS NOT NULL
- `idx_activity_overall_score` (overall_score DESC) WHERE overall_score IS NOT NULL
- `idx_activity_evaluation_passed` (evaluation->'rule_set_results'->'minimum_requirements'->>'passed') WHERE evaluation IS NOT NULL

---

### 2.5 nutrition_log

**Beschreibung:** Ernährungslog (täglich)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| date | date | NO | - | Datum |
| kcal | integer | YES | NULL | Kalorien |
| protein_g | numeric(6,2) | YES | NULL | Protein (g) |
| carbs_g | numeric(6,2) | YES | NULL | Kohlenhydrate (g) |
| fat_g | numeric(6,2) | YES | NULL | Fett (g) |
| fiber_g | numeric(6,2) | YES | NULL | Ballaststoffe (g) |
| water_l | numeric(4,2) | YES | NULL | Wasser (Liter) |
| source | varchar(50) | YES | 'manual' | Datenquelle |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_nutrition_profile_date` (profile_id, date DESC)

---

### 2.6 sleep_log

**Beschreibung:** Schlaf-Tracking mit JSONB-Segmenten (Deep, REM, Light, Awake)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| date | date | NO | - | Schlafdatum |
| sleep_start | timestamp | YES | NULL | Schlafbeginn |
| sleep_end | timestamp | YES | NULL | Schlafende |
| total_duration_minutes | integer | YES | NULL | Gesamtdauer (Minuten) |
| sleep_segments | jsonb | YES | NULL | Schlafphasen-Daten |
| quality_score | integer | YES | NULL | Schlafqualität (0-100) |
| source | varchar(50) | YES | 'manual' | Datenquelle |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**JSONB-Struktur `sleep_segments`:**
```json
{
  "deep": 120,     // Minuten
  "rem": 90,       // Minuten
  "light": 180,    // Minuten
  "awake": 15      // Minuten
}
```

**Constraints:**
- PK: `id`
- UNIQUE: `profile_id, date`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_sleep_profile_date` (profile_id, date DESC)
- `unique_sleep_per_day` UNIQUE (profile_id, date)

---

### 2.7 vitals_baseline

**Beschreibung:** Morgenmessung Vitalwerte (RHR, HRV, VO2 Max, SpO2, Atemfrequenz)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| date | date | NO | - | Messdatum |
| resting_hr | integer | YES | NULL | Ruhepuls (bpm) |
| hrv | integer | YES | NULL | HRV (ms) |
| vo2_max | numeric(5,2) | YES | NULL | VO2 Max (ml/kg/min) |
| spo2 | integer | YES | NULL | Sauerstoffsättigung (%) |
| respiratory_rate | integer | YES | NULL | Atemfrequenz (bpm) |
| source | varchar(50) | YES | 'manual' | Datenquelle |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- UNIQUE: `profile_id, date`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_vitals_baseline_profile_date` (profile_id, date DESC)
- `unique_baseline_per_day` UNIQUE (profile_id, date)

---

### 2.8 blood_pressure_log

**Beschreibung:** Blutdruckmessungen (mehrfach täglich mit Context-Tagging)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| measured_at | timestamp | NO | - | Messzeitpunkt |
| systolic | integer | NO | - | Systolisch (mmHg) |
| diastolic | integer | NO | - | Diastolisch (mmHg) |
| pulse | integer | YES | NULL | Puls (bpm) |
| context | varchar(50) | YES | NULL | Kontext (nüchtern, nach Essen, etc.) |
| irregular_heartbeat | boolean | YES | false | Unregelmäßiger Herzschlag |
| afib_detected | boolean | YES | false | Vorhofflimmern erkannt |
| source | varchar(50) | YES | 'manual' | Datenquelle |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- UNIQUE: `profile_id, measured_at`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_blood_pressure_profile_datetime` (profile_id, measured_at DESC)
- `idx_blood_pressure_context` (context) WHERE context IS NOT NULL
- `unique_bp_measurement` UNIQUE (profile_id, measured_at)

---

### 2.9 rest_days

**Beschreibung:** Multi-dimensionale Ruhetage (Kraft, Cardio, Entspannung)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| date | date | NO | - | Ruhetag-Datum |
| focus | varchar(50) | NO | - | Fokus (strength, cardio, relaxation) |
| rest_config | jsonb | YES | NULL | Detailkonfiguration |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**JSONB-Struktur `rest_config`:**
```json
{
  "strength_rest": true,
  "cardio_rest": false,
  "relaxation": true,
  "preset": "full_rest"
}
```

**Constraints:**
- PK: `id`
- UNIQUE: `profile_id, date, focus`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_rest_days_profile_date` (profile_id, date DESC)
- `idx_rest_days_focus` (focus)
- `idx_rest_days_config` GIN (rest_config)
- `unique_rest_day_per_focus` UNIQUE (profile_id, date, focus)

---

### 2.10 photos

**Beschreibung:** Progress-Fotos

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| date | date | NO | - | Fotodatum |
| filename | varchar(255) | NO | - | Dateiname |
| pose | varchar(50) | YES | NULL | Pose (front, side, back) |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_photos_profile_date` (profile_id, date DESC)

---

## 3. KI & Analysen

### 3.1 ai_prompts

**Beschreibung:** KI-Prompt-Definitionen (Unified Prompt System)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| slug | varchar(100) | NO | - | Eindeutiger Slug (unique) |
| title | varchar(255) | NO | - | Titel |
| type | varchar(20) | YES | 'base' | Typ (base, pipeline) |
| category | varchar(50) | YES | NULL | Kategorie |
| stages | jsonb | YES | NULL | Pipeline-Stages |
| output_format | varchar(20) | YES | NULL | Output-Format (json, text) |
| output_schema | jsonb | YES | NULL | JSON-Schema für Output |
| active | boolean | YES | true | Aktiv |
| sort_order | integer | YES | 0 | Sortierung |
| created_at | timestamp | YES | now() | Erstellungsdatum |
| updated_at | timestamp | YES | now() | Letztes Update |

**JSONB-Struktur `stages`:**
```json
[
  {
    "name": "Stage 1",
    "prompts": [
      {
        "type": "reference",
        "slug": "body_summary"
      },
      {
        "type": "inline",
        "template": "Analyse {{weight_latest}}...",
        "output_format": "json",
        "output_schema": {...}
      }
    ]
  }
]
```

**Constraints:**
- PK: `id`
- UNIQUE: `slug`

**Indizes:**
- `idx_ai_prompts_slug` (slug)
- `ai_prompts_slug_key` UNIQUE (slug)
- `idx_ai_prompts_type` (type)
- `idx_ai_prompts_category` (category)
- `idx_ai_prompts_active_sort` (active, sort_order)
- `idx_ai_prompts_stages` GIN (stages)

---

### 3.2 ai_insights

**Beschreibung:** Gespeicherte KI-Analysen

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| scope | varchar(100) | YES | NULL | Prompt-Slug |
| content | text | NO | - | KI-Antwort |
| metadata | jsonb | YES | NULL | Platzhalter-Werte |
| created | timestamp | YES | now() | Erstellungsdatum |

**JSONB-Struktur `metadata`:**
```json
{
  "placeholders": {
    "weight_latest": 85.2,
    "goal_weight": 80.0,
    "activity_days": 28
  },
  "categories": {
    "PROFIL": [...],
    "KÖRPER": [...]
  }
}
```

**Constraints:**
- PK: `id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_ai_insights_profile_scope` (profile_id, scope, created DESC)

---

### 3.3 ai_usage

**Beschreibung:** Tägliche KI-Nutzung (Rate Limiting)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| date | date | NO | - | Nutzungsdatum |
| count | integer | YES | 0 | Anzahl Calls |

**Constraints:**
- PK: `id`
- UNIQUE: `profile_id, date`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_ai_usage_profile_date` (profile_id, date)
- `ai_usage_profile_id_date_key` UNIQUE (profile_id, date)

---

### 3.4 pipeline_configs

**Beschreibung:** Pipeline-Konfigurationen (deprecated, ersetzt durch ai_prompts)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| name | varchar(100) | NO | - | Name (unique) |
| description | text | YES | NULL | Beschreibung |
| stages | jsonb | NO | - | Stage-Konfiguration |
| active | boolean | YES | true | Aktiv |
| is_default | boolean | YES | false | Standard-Pipeline |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- UNIQUE: `name`

**Indizes:**
- `idx_pipeline_configs_active` (active)
- `idx_pipeline_configs_default` (is_default) WHERE is_default = true
- `idx_pipeline_configs_single_default` UNIQUE (is_default) WHERE is_default = true
- `pipeline_configs_name_key` UNIQUE (name)

---

## 4. Membership & Features

### 4.1 tiers

**Beschreibung:** Membership-Tiers (free, plus, premium, trial)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | text | NO | - | PK (free, plus, premium, trial) |
| name | text | NO | - | Display-Name |
| description | text | YES | NULL | Beschreibung |
| price_monthly | numeric(10,2) | YES | NULL | Preis/Monat |
| price_yearly | numeric(10,2) | YES | NULL | Preis/Jahr |
| trial_days | integer | YES | NULL | Trial-Dauer (Tage) |
| is_active | boolean | YES | true | Aktiv |
| sort_order | integer | YES | 0 | Sortierung |

**Constraints:**
- PK: `id`

---

### 4.2 features

**Beschreibung:** Feature-Definitionen

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | text | NO | - | PK (feature_key) |
| name | text | NO | - | Display-Name |
| description | text | YES | NULL | Beschreibung |
| category | text | YES | NULL | Kategorie |

**Constraints:**
- PK: `id`

---

### 4.3 tier_limits

**Beschreibung:** Feature-Limits pro Tier

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| tier_id | text | NO | - | FK zu tiers.id |
| feature_id | text | NO | - | FK zu features.id |
| limit_value | integer | YES | NULL | Limit-Wert |
| limit_type | text | YES | NULL | Limit-Typ (daily, monthly, total) |

**Constraints:**
- PK: `id`
- UNIQUE: `tier_id, feature_id`
- FK: `tier_id` → `tiers(id)` ON DELETE CASCADE
- FK: `feature_id` → `features(id)` ON DELETE CASCADE

**Indizes:**
- `idx_tier_limits_tier` (tier_id)
- `idx_tier_limits_feature` (feature_id)
- `tier_limits_tier_id_feature_id_key` UNIQUE (tier_id, feature_id)

---

### 4.4 coupons

**Beschreibung:** Gutschein-Codes

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| code | varchar(50) | NO | - | Gutschein-Code (unique) |
| tier_id | text | YES | NULL | FK zu tiers.id |
| duration_days | integer | YES | NULL | Laufzeit (Tage) |
| max_uses | integer | YES | NULL | Max. Einlösungen |
| current_uses | integer | YES | 0 | Aktuelle Einlösungen |
| valid_from | timestamp | YES | NULL | Gültig ab |
| valid_until | timestamp | YES | NULL | Gültig bis |
| is_active | boolean | YES | true | Aktiv |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- UNIQUE: `code`
- FK: `tier_id` → `tiers(id)` ON DELETE SET NULL

**Indizes:**
- `idx_coupons_code` (code)
- `coupons_code_key` UNIQUE (code)

---

### 4.5 coupon_redemptions

**Beschreibung:** Gutschein-Einlösungen

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| coupon_id | uuid | NO | - | FK zu coupons.id |
| profile_id | uuid | NO | - | FK zu profiles.id |
| redeemed_at | timestamp | YES | now() | Einlösungsdatum |

**Constraints:**
- PK: `id`
- UNIQUE: `coupon_id, profile_id`
- FK: `coupon_id` → `coupons(id)` ON DELETE CASCADE
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_coupon_redemptions_profile` (profile_id)
- `coupon_redemptions_coupon_id_profile_id_key` UNIQUE (coupon_id, profile_id)

---

### 4.6 access_grants

**Beschreibung:** Zeitgebundene Tier-Zugriffe

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| tier_id | text | NO | - | FK zu tiers.id |
| granted_by | text | YES | NULL | Vergeben durch |
| coupon_id | uuid | YES | NULL | FK zu coupons.id |
| valid_from | timestamp | NO | - | Gültig ab |
| valid_until | timestamp | NO | - | Gültig bis |
| is_active | boolean | YES | true | Aktiv |
| paused_by | uuid | YES | NULL | Pausiert von |
| paused_at | timestamp | YES | NULL | Pausiert am |
| remaining_days | integer | YES | NULL | Verbleibende Tage |

**Constraints:**
- PK: `id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE
- FK: `tier_id` → `tiers(id)` ON DELETE CASCADE
- FK: `coupon_id` → `coupons(id)` ON DELETE SET NULL

**Indizes:**
- `idx_access_grants_profile` (profile_id, valid_until DESC)
- `idx_access_grants_active` (profile_id, is_active, valid_until DESC)

---

### 4.7 user_feature_restrictions

**Beschreibung:** Nutzer-spezifische Feature-Restriktionen

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| feature_id | text | NO | - | FK zu features.id |
| custom_limit | integer | YES | NULL | Custom Limit |
| is_disabled | boolean | YES | false | Feature deaktiviert |

**Constraints:**
- PK: `id`
- UNIQUE: `profile_id, feature_id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE
- FK: `feature_id` → `features(id)` ON DELETE CASCADE

**Indizes:**
- `idx_user_restrictions_profile` (profile_id)
- `user_feature_restrictions_profile_id_feature_id_key` UNIQUE (profile_id, feature_id)

---

### 4.8 user_feature_usage

**Beschreibung:** Feature-Nutzungszähler

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| feature_id | text | NO | - | FK zu features.id |
| usage_count | integer | YES | 0 | Nutzungszähler |
| last_used_at | timestamp | YES | NULL | Letzte Nutzung |
| reset_at | timestamp | YES | NULL | Reset-Zeitpunkt |

**Constraints:**
- PK: `id`
- UNIQUE: `profile_id, feature_id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE
- FK: `feature_id` → `features(id)` ON DELETE CASCADE

**Indizes:**
- `idx_user_usage_profile` (profile_id)
- `user_feature_usage_profile_id_feature_id_key` UNIQUE (profile_id, feature_id)

---

### 4.9 user_activity_log

**Beschreibung:** Audit-Log für Nutzeraktionen

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| action | text | NO | - | Aktion |
| details | jsonb | YES | NULL | Details |
| created | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_activity_log_profile` (profile_id, created DESC)
- `idx_activity_log_action` (action, created DESC)

---

## 5. Ziele & Training

### 5.1 goals

**Beschreibung:** Nutzer-definierte Ziele

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| title | varchar(255) | NO | - | Zieltitel |
| goal_type | varchar(50) | NO | - | Zieltyp (weight, body_fat, etc.) |
| category | varchar(50) | YES | NULL | Kategorie |
| start_date | date | YES | NULL | Startdatum |
| target_date | date | YES | NULL | Zieldatum |
| start_value | numeric(10,2) | YES | NULL | Startwert |
| target_value | numeric(10,2) | NO | - | Zielwert |
| current_value | numeric(10,2) | YES | NULL | Aktueller Wert |
| source_table | varchar(50) | YES | NULL | Datenquelle-Tabelle |
| source_column | varchar(50) | YES | NULL | Datenquelle-Spalte |
| status | varchar(20) | YES | 'active' | Status (active, completed, paused) |
| is_primary | boolean | YES | false | Primäres Ziel |
| priority | integer | YES | 0 | Priorität |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |
| updated_at | timestamp | YES | now() | Letztes Update |

**Constraints:**
- PK: `id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_goals_profile` (profile_id)
- `idx_goals_status` (profile_id, status)
- `idx_goals_primary` (profile_id, is_primary) WHERE is_primary = true
- `idx_goals_category_priority` (profile_id, category, priority)

---

### 5.2 goal_type_definitions

**Beschreibung:** Zieltyp-Definitionen (weight, body_fat, vo2max, etc.)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| type_key | varchar(50) | NO | - | Zieltyp-Key (unique) |
| display_name | varchar(100) | NO | - | Display-Name |
| category | varchar(50) | NO | - | Kategorie |
| unit | varchar(20) | YES | NULL | Einheit |
| description | text | YES | NULL | Beschreibung |
| is_active | boolean | YES | true | Aktiv |
| sort_order | integer | YES | 0 | Sortierung |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- UNIQUE: `type_key`

**Indizes:**
- `idx_goal_type_definitions_category` (category)
- `idx_goal_type_definitions_active` (is_active) WHERE is_active = true
- `goal_type_definitions_type_key_key` UNIQUE (type_key)

---

### 5.3 goal_focus_contributions

**Beschreibung:** M:N Relationship Goals ↔ Focus Areas

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| goal_id | uuid | NO | - | PK, FK zu goals.id |
| focus_area_id | integer | NO | - | PK, FK zu focus_area_definitions.id |
| contribution_weight | numeric(5,2) | YES | 100 | Gewichtung (%) |

**Constraints:**
- PK: `goal_id, focus_area_id`
- FK: `goal_id` → `goals(id)` ON DELETE CASCADE
- FK: `focus_area_id` → `focus_area_definitions(id)` ON DELETE CASCADE

**Indizes:**
- `idx_gfc_goal` (goal_id)
- `idx_gfc_focus_area` (focus_area_id)

---

### 5.4 goal_progress_log

**Beschreibung:** Tägliche Fortschrittswerte für Custom Goals

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| goal_id | uuid | NO | - | FK zu goals.id |
| profile_id | uuid | NO | - | FK zu profiles.id |
| date | date | NO | - | Datum |
| value | numeric(10,2) | NO | - | Wert |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- UNIQUE: `goal_id, date`
- FK: `goal_id` → `goals(id)` ON DELETE CASCADE
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_goal_progress_goal_date` (goal_id, date DESC)
- `idx_goal_progress_profile` (profile_id)
- `unique_progress_per_day` UNIQUE (goal_id, date)

---

### 5.5 focus_area_definitions

**Beschreibung:** Focus Area Definitionen (26 Bereiche in 7 Kategorien)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | serial | NO | - | PK |
| key | varchar(50) | NO | - | Focus Area Key (unique) |
| name | varchar(100) | NO | - | Display-Name |
| category | varchar(50) | NO | - | Kategorie |
| description | text | YES | NULL | Beschreibung |
| sort_order | integer | YES | 0 | Sortierung |
| is_active | boolean | YES | true | Aktiv |

**Constraints:**
- PK: `id`
- UNIQUE: `key`

**Indizes:**
- `idx_focus_area_key` (key)
- `idx_focus_area_category` (category)
- `focus_area_definitions_key_key` UNIQUE (key)

---

### 5.6 user_focus_area_weights

**Beschreibung:** User-spezifische Focus Area Gewichtungen

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| profile_id | uuid | NO | - | PK, FK zu profiles.id |
| focus_area_id | integer | NO | - | PK, FK zu focus_area_definitions.id |
| weight | numeric(5,2) | YES | 0 | Gewichtung (0-100) |
| updated_at | timestamp | YES | now() | Letztes Update |

**Constraints:**
- PK: `profile_id, focus_area_id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE
- FK: `focus_area_id` → `focus_area_definitions(id)` ON DELETE CASCADE

**Indizes:**
- `idx_user_focus_weights_profile` (profile_id)
- `idx_user_focus_weights_area` (focus_area_id)

---

### 5.7 user_focus_preferences

**Beschreibung:** User Focus Preferences (deprecated/legacy)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| focus_areas | jsonb | YES | NULL | Focus Areas (legacy) |
| active | boolean | YES | true | Aktiv |
| created_at | timestamp | YES | now() | Erstellungsdatum |
| updated_at | timestamp | YES | now() | Letztes Update |

**Constraints:**
- PK: `id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_focus_areas_profile_active` UNIQUE (profile_id) WHERE active = true

---

### 5.8 training_types

**Beschreibung:** Trainingstypen (29 Typen in 7 Kategorien)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | serial | NO | - | PK |
| name | varchar(100) | NO | - | Typname |
| category | varchar(50) | NO | - | Kategorie |
| color | varchar(7) | YES | NULL | Farbe (Hex) |
| icon | varchar(50) | YES | NULL | Icon-Name |
| description | text | YES | NULL | Beschreibung |
| abilities | jsonb | YES | NULL | Abilities-Matrix |
| profile | jsonb | YES | NULL | Profil-Einstellungen |

**JSONB-Struktur `abilities`:**
```json
{
  "kraft": 80,
  "ausdauer": 20,
  "beweglichkeit": 40,
  "schnelligkeit": 10,
  "koordination": 30,
  "balance": 20,
  "geist": 10
}
```

**Constraints:**
- PK: `id`

**Indizes:**
- `idx_training_types_category` (category)
- `idx_training_types_abilities` GIN (abilities)
- `idx_training_types_profile_enabled` (profile->'rule_sets'->'minimum_requirements'->>'enabled') WHERE profile IS NOT NULL

---

### 5.9 activity_type_mappings

**Beschreibung:** Lernendes Mapping-System (Workout-Name → Training Type)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| activity_type | varchar(255) | NO | - | Workout-Name (unique per profile) |
| training_type_id | integer | NO | - | FK zu training_types.id |
| profile_id | uuid | YES | NULL | NULL = global, sonst user-spezifisch |
| is_global | boolean | YES | true | Global oder user-spezifisch |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- UNIQUE: `activity_type, profile_id`
- FK: `training_type_id` → `training_types(id)` ON DELETE CASCADE

**Indizes:**
- `idx_activity_type_mappings_type` (activity_type)
- `idx_activity_type_mappings_profile` (profile_id)
- `unique_activity_type_per_profile` UNIQUE (activity_type, profile_id)

---

### 5.10 training_phases

**Beschreibung:** Trainingsphasen (Auto-Detection Framework)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| phase_type | varchar(50) | NO | - | Phasentyp |
| start_date | date | NO | - | Startdatum |
| end_date | date | YES | NULL | Enddatum |
| status | varchar(20) | YES | 'suggested' | Status |
| confidence | numeric(5,2) | YES | NULL | Confidence (0-100) |
| description | text | YES | NULL | Beschreibung |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_training_phases_profile` (profile_id)
- `idx_training_phases_status` (profile_id, status)
- `idx_training_phases_dates` (profile_id, start_date, end_date)

---

### 5.11 fitness_tests

**Beschreibung:** Standardisierte Fitness-Tests

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| test_date | date | NO | - | Testdatum |
| test_type | varchar(50) | NO | - | Testtyp |
| result_value | numeric(10,2) | NO | - | Ergebnis |
| result_unit | varchar(20) | YES | NULL | Einheit |
| norm_category | varchar(50) | YES | NULL | Norm-Kategorie |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_fitness_tests_profile` (profile_id)
- `idx_fitness_tests_type` (profile_id, test_type)
- `idx_fitness_tests_date` (profile_id, test_date)

---

### 5.12 training_parameters

**Beschreibung:** Globale Trainings-Parameter (Quality-Filter Settings)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| key | varchar(100) | NO | - | Parameter-Key (unique) |
| category | varchar(50) | NO | - | Kategorie |
| value | text | NO | - | Wert |
| description | text | YES | NULL | Beschreibung |
| is_active | boolean | YES | true | Aktiv |
| created_at | timestamp | YES | now() | Erstellungsdatum |
| updated_at | timestamp | YES | now() | Letztes Update |

**Constraints:**
- PK: `id`
- UNIQUE: `key`

**Indizes:**
- `idx_training_parameters_key` (key) WHERE is_active = true
- `idx_training_parameters_category` (category) WHERE is_active = true
- `training_parameters_key_key` UNIQUE (key)

---

### 5.13 weekly_goals

**Beschreibung:** Wöchentliche Ziele (legacy)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| week_start | date | NO | - | Wochenbeginn |
| goals | jsonb | YES | NULL | Ziele |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- UNIQUE: `profile_id, week_start`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `idx_weekly_goals_profile_week` (profile_id, week_start DESC)
- `unique_weekly_goal_per_profile` UNIQUE (profile_id, week_start)

---

## 6. System-Tabellen

### 6.1 schema_migrations

**Beschreibung:** Migrations-Tracking

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | serial | NO | - | PK |
| filename | varchar(255) | NO | - | Migrations-Dateiname (unique) |
| applied_at | timestamp | YES | now() | Anwendungsdatum |

**Constraints:**
- PK: `id`
- UNIQUE: `filename`

**Indizes:**
- `schema_migrations_filename_key` UNIQUE (filename)

---

### 6.2 app_settings

**Beschreibung:** Globale App-Einstellungen

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| key | varchar(100) | NO | - | PK, Setting-Key |
| value | text | YES | NULL | Setting-Value |
| updated_at | timestamp | YES | now() | Letztes Update |

**Constraints:**
- PK: `key`

---

## 7. Backup-Tabellen

### 7.1 pipeline_configs_backup_pre_020

**Beschreibung:** Backup vor Migration 020 (Unified Prompt System)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | YES | - | Original ID |
| name | varchar(100) | YES | - | Name |
| description | text | YES | NULL | Beschreibung |
| stages | jsonb | YES | - | Stages |
| active | boolean | YES | true | Aktiv |
| is_default | boolean | YES | false | Default |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Keine Constraints oder Indizes.**

---

### 7.2 vitals_log_backup_pre_015

**Beschreibung:** Backup vor Vitals-Refactoring (Migration 015)

| Spalte | Typ | Nullable | Default | Beschreibung |
|--------|-----|----------|---------|--------------|
| id | uuid | NO | uuid_generate_v4() | PK |
| profile_id | uuid | NO | - | FK zu profiles.id |
| date | date | NO | - | Messdatum |
| resting_hr | integer | YES | NULL | Ruhepuls |
| hrv | integer | YES | NULL | HRV |
| systolic | integer | YES | NULL | Systolisch |
| diastolic | integer | YES | NULL | Diastolisch |
| pulse | integer | YES | NULL | Puls |
| vo2_max | numeric(5,2) | YES | NULL | VO2 Max |
| spo2 | integer | YES | NULL | SpO2 |
| respiratory_rate | integer | YES | NULL | Atemfrequenz |
| source | varchar(50) | YES | 'manual' | Datenquelle |
| notes | text | YES | NULL | Notizen |
| created_at | timestamp | YES | now() | Erstellungsdatum |

**Constraints:**
- PK: `id`
- UNIQUE: `profile_id, date`
- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE

**Indizes:**
- `unique_vitals_per_day` UNIQUE (profile_id, date)

---

## 8. ER-Diagramm

### 8.1 Zentrale Entität: profiles

```
profiles (1) → (N) weight_log
profiles (1) → (N) circumference_log
profiles (1) → (N) caliper_log
profiles (1) → (N) activity_log
profiles (1) → (N) nutrition_log
profiles (1) → (N) sleep_log
profiles (1) → (N) vitals_baseline
profiles (1) → (N) blood_pressure_log
profiles (1) → (N) rest_days
profiles (1) → (N) photos
profiles (1) → (N) sessions
profiles (1) → (N) ai_insights
profiles (1) → (N) ai_usage
profiles (1) → (N) goals
profiles (1) → (N) goal_progress_log
profiles (1) → (N) training_phases
profiles (1) → (N) fitness_tests
profiles (1) → (N) access_grants
profiles (1) → (N) coupon_redemptions
profiles (1) → (N) user_feature_restrictions
profiles (1) → (N) user_feature_usage
profiles (1) → (N) user_activity_log
profiles (1) → (1) user_stats
profiles (1) → (N) user_focus_area_weights
profiles (1) → (N) user_focus_preferences
profiles (1) → (N) activity_type_mappings
profiles (1) → (N) weekly_goals
profiles (self) → (N) profiles (invited_by)
```

### 8.2 Trainings-Typen-Beziehungen

```
training_types (1) → (N) activity_log
training_types (1) → (N) activity_type_mappings
```

### 8.3 Membership-Beziehungen

```
tiers (1) → (N) tier_limits
tiers (1) → (N) coupons
tiers (1) → (N) access_grants

features (1) → (N) tier_limits
features (1) → (N) user_feature_restrictions
features (1) → (N) user_feature_usage

coupons (1) → (N) coupon_redemptions
coupons (1) → (N) access_grants
```

### 8.4 Ziele-Beziehungen

```
goals (1) → (N) goal_progress_log
goals (N) → (M) focus_area_definitions (via goal_focus_contributions)

focus_area_definitions (N) → (M) profiles (via user_focus_area_weights)
```

### 8.5 Kardinalitäten

| Beziehung | Typ | ON DELETE |
|-----------|-----|-----------|
| profiles → sessions | 1:N | CASCADE |
| profiles → weight_log | 1:N | CASCADE |
| profiles → activity_log | 1:N | CASCADE |
| profiles → goals | 1:N | CASCADE |
| profiles → access_grants | 1:N | CASCADE |
| training_types → activity_log | 1:N | NO ACTION |
| tiers → tier_limits | 1:N | CASCADE |
| goals → goal_focus_contributions | 1:N | CASCADE |
| focus_area_definitions → goal_focus_contributions | 1:N | CASCADE |

---

## 9. Datenbank-Statistiken

### 9.1 Tabellen-Übersicht

| Kategorie | Anzahl Tabellen |
|-----------|-----------------|
| Kernmodule | 3 (profiles, sessions, user_stats) |
| Tracking | 10 (weight, circumference, caliper, activity, nutrition, sleep, vitals_baseline, blood_pressure, rest_days, photos) |
| KI & Analysen | 4 (ai_prompts, ai_insights, ai_usage, pipeline_configs) |
| Membership | 9 (tiers, features, tier_limits, coupons, coupon_redemptions, access_grants, user_feature_restrictions, user_feature_usage, user_activity_log) |
| Ziele & Training | 13 (goals, goal_type_definitions, goal_progress_log, goal_focus_contributions, focus_area_definitions, user_focus_area_weights, user_focus_preferences, training_types, activity_type_mappings, training_phases, fitness_tests, training_parameters, weekly_goals) |
| System | 2 (schema_migrations, app_settings) |
| Backup | 2 (pipeline_configs_backup_pre_020, vitals_log_backup_pre_015) |
| **Gesamt** | **43** |

### 9.2 Foreign Keys

| Tabelle | Anzahl FKs |
|---------|------------|
| access_grants | 3 |
| activity_log | 2 |
| activity_type_mappings | 1 |
| ai_insights | 1 |
| ai_usage | 1 |
| blood_pressure_log | 1 |
| caliper_log | 1 |
| circumference_log | 1 |
| coupon_redemptions | 2 |
| coupons | 1 |
| fitness_tests | 1 |
| goal_focus_contributions | 2 |
| goal_progress_log | 2 |
| goals | 1 |
| nutrition_log | 1 |
| photos | 1 |
| profiles | 1 (self-reference) |
| rest_days | 1 |
| sessions | 1 |
| sleep_log | 1 |
| tier_limits | 2 |
| training_phases | 1 |
| user_activity_log | 1 |
| user_feature_restrictions | 2 |
| user_feature_usage | 2 |
| user_focus_area_weights | 2 |
| user_focus_preferences | 1 |
| user_stats | 1 |
| vitals_baseline | 1 |
| vitals_log_backup_pre_015 | 1 |
| weekly_goals | 1 |
| weight_log | 1 |
| **Gesamt** | **42** |

### 9.3 Indizes

| Typ | Anzahl |
|-----|--------|
| Primary Keys | 43 |
| Unique Constraints | 36 |
| B-Tree Indizes | 86 |
| GIN Indizes (JSONB) | 7 |
| Partial Indizes | 12 |
| **Gesamt** | **184** |

---

## 10. Wichtige Design-Patterns

### 10.1 Profile-ID Isolation

**Regel:** Jede Tabelle mit Nutzerdaten hat `profile_id` als Foreign Key.

**Vorteile:**
- Strikte Datenisolation pro Nutzer
- Kein Zugriff auf fremde Daten möglich
- Performante Queries durch Index auf profile_id

**Beispiel Query:**
```sql
SELECT * FROM weight_log
WHERE profile_id = '...' AND date >= '2026-01-01'
ORDER BY date DESC;
```

### 10.2 Source-Tracking

**Regel:** Tabellen mit Import-Daten haben `source` Spalte.

**Werte:**
- `'manual'` – manuell erfasst
- `'apple_health'` – Apple Health Import
- `'garmin'` – Garmin Import
- `'withings'` – Withings Import

**Regel bei Reimport:**
```sql
INSERT INTO sleep_log (...)
ON CONFLICT (profile_id, date)
DO UPDATE SET ... WHERE sleep_log.source != 'manual';
```

**Schutz:** Manuelle Einträge werden niemals überschrieben.

### 10.3 JSONB für flexible Daten

**Verwendung:**
- `ai_prompts.stages` – dynamische Pipeline-Stages
- `ai_insights.metadata` – Platzhalter-Werte
- `activity_log.evaluation` – Qualitätsbewertungen
- `sleep_log.sleep_segments` – Schlafphasen
- `rest_days.rest_config` – Ruhetag-Konfiguration
- `training_types.abilities` – Abilities-Matrix
- `training_parameters.value` – Parameter-Werte

**Indizes:**
- GIN-Indizes für JSONB-Suchen
- Partial-Indizes mit JSONB-Extraktion

**Beispiel:**
```sql
-- GIN Index für vollständige JSONB-Suche
CREATE INDEX idx_ai_prompts_stages ON ai_prompts USING gin (stages);

-- Partial Index für spezifischen JSONB-Wert
CREATE INDEX idx_training_types_profile_enabled
ON training_types (((profile->'rule_sets'->'minimum_requirements'->>'enabled')))
WHERE profile IS NOT NULL;
```

### 10.4 Composite Primary Keys

**M:N Beziehungen:**
- `goal_focus_contributions` (goal_id, focus_area_id)
- `user_focus_area_weights` (profile_id, focus_area_id)

**Zeitbasierte Uniqueness:**
- `weight_log` (profile_id, date)
- `sleep_log` (profile_id, date)
- `vitals_baseline` (profile_id, date)
- `blood_pressure_log` (profile_id, measured_at)

### 10.5 Soft Deletes & Status-Felder

**Status-Felder:**
- `goals.status` (active, completed, paused)
- `training_phases.status` (suggested, accepted, active, completed)
- `ai_prompts.active` (true/false)
- `coupons.is_active` (true/false)

**Keine Hard Deletes bei:**
- ai_insights (Historische Analysen behalten)
- goals (Abgeschlossene Ziele für Statistiken)
- training_phases (Phasen-Historie)

### 10.6 Auto-Updated Timestamps

**Pattern:**
```sql
created_at TIMESTAMP DEFAULT NOW()
updated_at TIMESTAMP DEFAULT NOW()
```

**Trigger für auto-update:**
```sql
CREATE TRIGGER update_timestamp
BEFORE UPDATE ON tabelle
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
```

---

## 11. Performance-Optimierungen

### 11.1 Index-Strategie

**Profile-ID + Date Indizes:**
```sql
idx_weight_log_profile_date (profile_id, date DESC)
idx_activity_profile_date (profile_id, date DESC)
idx_sleep_profile_date (profile_id, date DESC)
```

**Grund:** Häufigste Query-Pattern = "alle Einträge für User X, neuste zuerst"

### 11.2 Partial Indizes

**Beispiele:**
```sql
-- Nur aktive Prompts indizieren
CREATE INDEX idx_ai_prompts_active_sort
ON ai_prompts (active, sort_order);

-- Nur Einträge mit Quality-Label
CREATE INDEX idx_activity_quality_label
ON activity_log (quality_label)
WHERE quality_label IS NOT NULL;

-- Nur verifizierte E-Mails
CREATE INDEX idx_profiles_verification_token
ON profiles (verification_token)
WHERE verification_token IS NOT NULL;
```

**Vorteil:** Kleinere Index-Größe, schnellere Updates

### 11.3 Composite Indizes

**Multi-Column Indizes:**
```sql
idx_access_grants_active (profile_id, is_active, valid_until DESC)
idx_goals_category_priority (profile_id, category, priority)
idx_tier_limits_tier (tier_id, feature_id)
```

**Regel:** Häufigste WHERE-Klauseln in Index-Spalten-Reihenfolge

### 11.4 GIN-Indizes für JSONB

**JSONB-Suchen beschleunigen:**
```sql
CREATE INDEX idx_ai_prompts_stages ON ai_prompts USING gin (stages);
CREATE INDEX idx_training_types_abilities ON training_types USING gin (abilities);
CREATE INDEX idx_rest_days_config ON rest_days USING gin (rest_config);
```

**Verwendung:**
```sql
SELECT * FROM ai_prompts
WHERE stages @> '[{"name": "Stage 1"}]'::jsonb;
```

---

## 12. Migration-Historie

### Migrations-Log

| Migration | Datum | Beschreibung |
|-----------|-------|--------------|
| 001-010 | 2025-2026 | Basis-Tabellen (weight, circumference, caliper, nutrition, activity, photos, ai) |
| 011-012 | 2026-02 | Membership-System (tiers, features, access_grants) |
| 013-014 | 2026-03 | Sleep + Rest Days |
| 015 | 2026-03 | Vitals-Refactoring (vitals_baseline + blood_pressure_log) |
| 016-021 | 2026-03 | Training Types + Activity Mappings |
| 022 | 2026-03 | Goal System (goals, training_phases, fitness_tests) |
| 023-028 | 2026-03 | Goal System Extensions (auto-population, time-based tracking) |
| 029-030 | 2026-03 | Dynamic Focus Areas v2.0 (focus_area_definitions, user_focus_area_weights) |
| 031-032 | 2026-03 | Goal Focus Contributions (M:N Relationship) |

**Aktueller Stand:** Migration 032  
**Tracking-Tabelle:** `schema_migrations`

---

## 13. Bekannte Deprecated-Elemente

### 13.1 Deprecated Tabellen

| Tabelle | Ersetzt durch | Migration |
|---------|---------------|-----------|
| vitals_log | vitals_baseline + blood_pressure_log | 015 |
| pipeline_configs | ai_prompts (Unified System) | 020 |

**Backup-Tabellen behalten für Rollback-Szenarien.**

### 13.2 Deprecated Felder

| Tabelle | Feld | Grund |
|---------|------|-------|
| profiles | goal_mode | Ersetzt durch Dynamic Focus Areas |
| user_focus_preferences | focus_areas | Ersetzt durch user_focus_area_weights |

---

## 14. Datenintegrität

### 14.1 Foreign Key Constraints

**ON DELETE CASCADE:**
- Alle Tracking-Daten → profiles
- sessions → profiles
- goals → profiles
- access_grants → profiles

**ON DELETE SET NULL:**
- access_grants → coupons
- coupons → tiers

**ON DELETE NO ACTION:**
- activity_log → training_types
- profiles → profiles (invited_by)

### 14.2 Check Constraints

**Beispiele:**
```sql
-- profiles.tier
CHECK (tier IN ('free', 'plus', 'premium', 'trial'))

-- goals.status
CHECK (status IN ('active', 'completed', 'paused'))

-- training_phases.phase_type
CHECK (phase_type IN ('calorie_deficit', 'calorie_surplus', 'deload', 'maintenance', 'periodization'))
```

### 14.3 Unique Constraints

**Pro User + Datum:**
- `weight_log` (profile_id, date)
- `sleep_log` (profile_id, date)
- `vitals_baseline` (profile_id, date)
- `blood_pressure_log` (profile_id, measured_at)
- `rest_days` (profile_id, date, focus)

**Pro User + Feature:**
- `user_feature_restrictions` (profile_id, feature_id)
- `user_feature_usage` (profile_id, feature_id)
- `ai_usage` (profile_id, date)

**Global:**
- `profiles` (email)
- `ai_prompts` (slug)
- `coupons` (code)
- `focus_area_definitions` (key)

---

## 15. Nächste Schritte (Roadmap)

### Phase 0c: Data Layer & Charts ✅ COMPLETED
- ✅ Data Layer Migration (97 Funktionen)
- ✅ Chart Endpoints (20 Endpoints E1-E5, A1-A8, R1-R5, C1-C4)
- 🔲 Frontend Chart Integration (IN PROGRESS)

### Phase 1: Advanced Goals & Tracking
- 🔲 Goal Templates System
- 🔲 Automated Training Phase Detection
- 🔲 Goal-Aware Placeholders (120+)

### Phase 2: Integrations
- 🔲 Apple Health Connector (persistent sync)
- 🔲 Garmin Connect Integration
- 🔲 Withings API Integration
- 🔲 Oura Ring Support

### Phase 3: Advanced Features
- 🔲 Multi-Profile Support (Trainer → Clients)
- 🔲 Team/Group Features
- 🔲 Stripe Payment Integration
- 🔲 Advanced Correlations (Lag-Analysis)

---

## Anhang

### A. Tabellen-Index (alphabetisch)

```
access_grants
activity_log
activity_type_mappings
ai_insights
ai_prompts
ai_usage
app_settings
blood_pressure_log
caliper_log
circumference_log
coupon_redemptions
coupons
features
fitness_tests
focus_area_definitions
goal_focus_contributions
goal_progress_log
goal_type_definitions
goals
nutrition_log
photos
pipeline_configs
pipeline_configs_backup_pre_020
profiles
rest_days
schema_migrations
sessions
sleep_log
tier_limits
tiers
training_parameters
training_phases
training_types
user_activity_log
user_feature_restrictions
user_feature_usage
user_focus_area_weights
user_focus_preferences
user_stats
vitals_baseline
vitals_log_backup_pre_015
weekly_goals
weight_log
```

### B. Query-Beispiele

**Gewichtsverlauf (letzte 90 Tage):**
```sql
SELECT date, weight
FROM weight_log
WHERE profile_id = '...'
  AND date >= CURRENT_DATE - INTERVAL '90 days'
ORDER BY date DESC;
```

**Aktivitäten mit Quality-Label "high":**
```sql
SELECT a.date, a.description, t.name AS training_type, a.overall_score
FROM activity_log a
JOIN training_types t ON a.training_type_id = t.id
WHERE a.profile_id = '...'
  AND a.quality_label = 'high'
ORDER BY a.date DESC;
```

**Aktuelle Tier-Limits für User:**
```sql
SELECT f.name, tl.limit_value, tl.limit_type
FROM profiles p
JOIN tier_limits tl ON p.tier = tl.tier_id
JOIN features f ON tl.feature_id = f.id
WHERE p.id = '...';
```

**Schlafqualität (7 Tage):**
```sql
SELECT date,
       total_duration_minutes,
       (sleep_segments->>'deep')::int AS deep_minutes,
       (sleep_segments->>'rem')::int AS rem_minutes,
       quality_score
FROM sleep_log
WHERE profile_id = '...'
  AND date >= CURRENT_DATE - INTERVAL '7 days'
ORDER BY date DESC;
```

**Aktive Ziele mit Progress:**
```sql
SELECT title, goal_type, target_value, current_value,
       ROUND((current_value / target_value) * 100, 1) AS progress_pct
FROM goals
WHERE profile_id = '...'
  AND status = 'active'
ORDER BY priority DESC, created_at ASC;
```

---

**Ende des Datenmodells**
