diff --git a/Datenbank-Schema-%28KI-%C3%BCberpr%C3%BCfung-n%C3%B6tig%29.md b/Datenbank-Schema-%28KI-%C3%BCberpr%C3%BCfung-n%C3%B6tig%29.md
new file mode 100644
index 0000000..5e4a0cb
--- /dev/null
+++ b/Datenbank-Schema-%28KI-%C3%BCberpr%C3%BCfung-n%C3%B6tig%29.md
@@ -0,0 +1,491 @@
+# Datenbank
+
+Die App verwendet **SQLite** (über `better-sqlite3`). Das Schema wird an drei Stellen aufgebaut:
+
+| Datei | Funktion | Aufgabe |
+|---|---|---|
+| `src/db/schema.js` | `initializeSchema(db)` | Legt die Grundtabellen an |
+| `src/db/schema.js` | `runMigrations(db, { dropLegacyChats })` | Ergänzt fehlende Spalten/Tabellen in bestehenden Datenbanken, migriert Altdaten |
+| `src/db/attachments.js` | `initializeAttachments(db)` | Tabellen und Trigger für Bild-Uploads |
+
+**Wichtig:** Das vollständige Schema ergibt sich erst aus allen drei Funktionen zusammen.
+Die Tabelle `chat_message_versions` und die Spalten `chat_messages.sources` und
+`chat_messages.thinking` existieren z. B. nur in `runMigrations`.
+
+## Allgemeine Konventionen
+
+- **`PRAGMA foreign_keys = ON`**: SQLite prüft Fremdschlüssel nur mit dieser Einstellung.
+ Sie muss bei jeder Verbindung gesetzt werden.
+- **`PRAGMA journal_mode = WAL`**: Erlaubt gleichzeitiges Lesen während geschrieben wird.
+- **Booleans** werden als `INTEGER` gespeichert (`0` = false, `1` = true),
+ z. B. `is_pinned`, `is_shared`.
+- **JSON-Daten** werden als `TEXT` gespeichert (z. B. `retry_variants`, `sources`,
+ `custom_fields`, `tags`, `snapshot_json`) und im Code mit `JSON.parse` gelesen.
+- **Zeitstempel** werden mit `DEFAULT CURRENT_TIMESTAMP` in UTC gesetzt.
+- Spaltennamen in der DB sind `snake_case`, im JavaScript-Code `camelCase`
+ (siehe z. B. `attachmentMetadata`).
+
+## Übersicht der Bereiche
+
+| Bereich | Tabellen |
+|---|---|
+| Benutzer | `users`, `user_settings` |
+| Chats | `chat_sessions`, `chat_messages`, `chat_message_versions`, `chat_memories`, `persona_chat_state` |
+| Personas | `personas`, `persona_versions`, `prompt_templates` |
+| Marktplatz | `persona_market`, `persona_market_favorites`, `persona_market_ratings`, `persona_market_reports` |
+| Anhänge | `attachments`, `message_attachments`, `attachment_vision_analyses`, `upload_deletions` |
+
+## Beziehungsdiagramm
+
+Legende: **durchgezogene Linie** = echter Fremdschlüssel (von SQLite geprüft),
+**gestrichelte Linie** = nur logische Beziehung (Spalte verweist auf eine andere Tabelle,
+aber ohne `FOREIGN KEY`, wird also nicht von der DB geprüft).
+
+```mermaid
+erDiagram
+ users {
+ TEXT username PK
+ }
+ user_settings {
+ TEXT username PK, FK
+ INTEGER active_persona_id FK
+ INTEGER active_user_persona_id FK
+ }
+ chat_sessions {
+ INTEGER id PK
+ TEXT username
+ INTEGER assistant_persona_id FK
+ INTEGER user_persona_id FK
+ INTEGER source_chat_id FK
+ INTEGER branched_from_message_id FK
+ INTEGER active_leaf_message_id
+ }
+ chat_messages {
+ INTEGER id PK
+ INTEGER chat_id FK
+ INTEGER parent_message_id FK
+ INTEGER retry_prompt_message_id
+ }
+ chat_message_versions {
+ INTEGER id PK
+ INTEGER message_id FK
+ }
+ chat_memories {
+ INTEGER id PK
+ TEXT username FK
+ INTEGER chat_id FK
+ INTEGER assistant_persona_id FK
+ INTEGER user_persona_id FK
+ }
+ persona_chat_state {
+ INTEGER id PK
+ TEXT username FK
+ INTEGER chat_id FK, UK
+ INTEGER assistant_persona_id FK
+ INTEGER user_persona_id FK
+ }
+ personas {
+ INTEGER id PK
+ TEXT username
+ INTEGER source_market_id
+ }
+ persona_versions {
+ INTEGER id PK
+ INTEGER persona_id FK
+ }
+ prompt_templates {
+ INTEGER id PK
+ TEXT username
+ INTEGER persona_id FK
+ }
+ persona_market {
+ INTEGER id PK
+ INTEGER persona_id
+ TEXT creator_username
+ }
+ persona_market_favorites {
+ INTEGER market_id PK, FK
+ TEXT username PK
+ }
+ persona_market_ratings {
+ INTEGER market_id PK, FK
+ TEXT username PK
+ }
+ persona_market_reports {
+ INTEGER id PK
+ INTEGER market_id FK
+ TEXT reporter_username
+ }
+ attachments {
+ TEXT id PK
+ TEXT username FK
+ TEXT storage_key UK
+ }
+ message_attachments {
+ INTEGER message_id PK, FK
+ TEXT attachment_id PK, FK
+ }
+ attachment_vision_analyses {
+ TEXT attachment_id PK, FK
+ TEXT vision_model PK
+ INTEGER prompt_version PK
+ TEXT question PK
+ }
+ upload_deletions {
+ TEXT storage_key PK
+ }
+
+ users ||--o| user_settings : "hat"
+ users ||--o{ chat_memories : "besitzt"
+ users ||--o{ persona_chat_state : "besitzt"
+ users ||--o{ attachments : "lädt hoch"
+ users ||..o{ chat_sessions : "besitzt"
+ users ||..o{ personas : "besitzt"
+ users ||..o{ persona_market : "veröffentlicht"
+ users ||..o{ prompt_templates : "besitzt"
+
+ chat_sessions ||--o{ chat_messages : "enthält"
+ chat_sessions ||--o{ chat_memories : "hat"
+ chat_sessions ||--o| persona_chat_state : "hat"
+ chat_sessions |o--o{ chat_sessions : "Quelle für Branch"
+ chat_messages |o--o{ chat_sessions : "Abzweigpunkt"
+ chat_messages |o--o{ chat_messages : "Parent"
+ chat_messages ||--o{ chat_message_versions : "hat Versionen"
+ chat_messages ||--o{ message_attachments : "hat"
+ attachments ||--o{ message_attachments : "wird verwendet in"
+ attachments ||--o{ attachment_vision_analyses : "hat Analysen"
+ attachments ||..o| upload_deletions : "Trigger"
+
+ personas |o--o{ chat_sessions : "Assistant-/User-Persona"
+ personas |o--o{ chat_memories : "bezieht sich auf"
+ personas |o--o{ persona_chat_state : "bezieht sich auf"
+ personas |o--o{ user_settings : "aktive Persona"
+ personas ||--o{ persona_versions : "hat Versionen"
+ personas ||--o{ prompt_templates : "hat"
+ personas |o..o{ persona_market : "veröffentlicht als"
+ persona_market |o..o{ personas : "importiert als"
+ persona_market ||--o{ persona_market_favorites : "hat"
+ persona_market ||--o{ persona_market_ratings : "hat"
+ persona_market ||--o{ persona_market_reports : "hat"
+```
+
+### Lösch-Verhalten (ON DELETE)
+
+| Wird gelöscht | Folge |
+|---|---|
+| `users` | Löscht `user_settings`, `chat_memories`, `persona_chat_state`, `attachments` (CASCADE). **Nicht** automatisch: `chat_sessions`, `personas`, `prompt_templates`, Marktplatz-Einträge (kein FK) |
+| `chat_sessions` | Löscht alle `chat_messages`, `chat_memories`, `persona_chat_state` des Chats |
+| `chat_messages` | Löscht `chat_message_versions` und `message_attachments`. Kind-Nachrichten behalten ihren Platz, `parent_message_id` wird `NULL` |
+| `personas` | Löscht `persona_versions`, `prompt_templates`. In Chats, Memories und Settings wird der Verweis auf `NULL` gesetzt |
+| `persona_market` | Löscht Favoriten, Bewertungen und Meldungen |
+| `attachments` | Löscht `message_attachments`, `attachment_vision_analyses` und trägt die Datei in `upload_deletions` ein (Trigger) |
+
+## Tabellen
+
+### Benutzer
+
+#### `users`
+Registrierte Benutzer.
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `username` | TEXT | **PK** |
+| `password` | TEXT | Passwort-Hash |
+| `session_version` | INTEGER | Default `0`. Wird erhöht, um alle Sessions eines Benutzers ungültig zu machen |
+
+#### `user_settings`
+Einstellungen pro Benutzer (1:1 zu `users`).
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `username` | TEXT | **PK**, FK → `users` (CASCADE) |
+| `active_persona_id` | INTEGER | FK → `personas` (SET NULL), aktive Assistant-Persona |
+| `active_user_persona_id` | INTEGER | FK → `personas` (SET NULL), aktive User-Persona |
+
+### Chats
+
+#### `chat_sessions`
+Ein Chat bzw. eine Unterhaltung.
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `id` | INTEGER | **PK**, Autoincrement |
+| `username` | TEXT | Besitzer (logisch → `users`, kein FK) |
+| `title` | TEXT | Chat-Titel |
+| `assistant_persona_id` | INTEGER | FK → `personas` (SET NULL) |
+| `user_persona_id` | INTEGER | FK → `personas` (SET NULL) |
+| `folder_name` | TEXT | Ordner in der Seitenleiste |
+| `is_pinned` | INTEGER | Boolean, Default `0` |
+| `archived_at` | DATETIME | `NULL` = nicht archiviert |
+| `scenario_prompt` | TEXT | Szenario-Beschreibung |
+| `scenario_summary` | TEXT | Kurzfassung des Szenarios |
+| `context_summary` | TEXT | Automatische Zusammenfassung des bisherigen Verlaufs |
+| `context_summary_message_id` | INTEGER | Bis zu welcher Nachricht die Zusammenfassung reicht. Default `0` |
+| `active_leaf_message_id` | INTEGER | Letzte Nachricht des aktuell sichtbaren Zweigs (logisch → `chat_messages`) |
+| `source_chat_id` | INTEGER | FK → `chat_sessions` (SET NULL), Ursprungs-Chat bei Branches |
+| `branched_from_message_id` | INTEGER | FK → `chat_messages` (SET NULL), Nachricht, an der abgezweigt wurde |
+| `preferred_model` | TEXT | Bevorzugtes Modell für diesen Chat |
+| `temperature` | REAL | Modell-Parameter |
+| `context_length` | INTEGER | Modell-Parameter |
+| `response_length` | INTEGER | Modell-Parameter |
+| `system_instruction` | TEXT | Zusätzliche System-Anweisung |
+| `created_at`, `updated_at` | DATETIME | |
+
+#### `chat_messages`
+Einzelne Nachrichten. Nachrichten bilden über `parent_message_id` einen **Baum**, damit
+Verzweigungen (z. B. durch Regenerieren oder Bearbeiten) möglich sind.
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `id` | INTEGER | **PK**, Autoincrement |
+| `chat_id` | INTEGER | FK → `chat_sessions` (CASCADE) |
+| `role` | TEXT | z. B. `user`, `bot` |
+| `content` | TEXT | Nachrichtentext |
+| `thinking` | TEXT | Reasoning-Ausgabe des Modells (nur via Migration) |
+| `sources` | TEXT | Websuche-Quellen als JSON (nur via Migration) |
+| `model_name` | TEXT | Verwendetes Modell |
+| `retry_variants` | TEXT | Alternative Antworten als JSON |
+| `retry_active_index` | INTEGER | Aktuell angezeigte Variante. Default `0` |
+| `retry_retries_used` | INTEGER | Anzahl genutzter Wiederholungen. Default `0` |
+| `retry_prompt_message_id` | INTEGER | Zugehörige User-Nachricht (logisch → `chat_messages`) |
+| `delivery_status` | TEXT | Default `'complete'` |
+| `error_message` | TEXT | Fehlertext bei fehlgeschlagener Antwort |
+| `parent_message_id` | INTEGER | FK → `chat_messages` (SET NULL), Vorgänger im Baum |
+| `created_at` | DATETIME | |
+
+#### `chat_message_versions`
+Frühere Fassungen einer Nachricht (z. B. nach Bearbeiten oder Regenerieren).
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `id` | INTEGER | **PK** |
+| `message_id` | INTEGER | FK → `chat_messages` (CASCADE), indiziert |
+| `content`, `thinking`, `sources`, `model_name` | TEXT | Stand der Nachricht zu diesem Zeitpunkt |
+| `created_at` | DATETIME | |
+
+#### `chat_memories`
+Fakten, die sich das Modell innerhalb eines Chats merkt.
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `id` | INTEGER | **PK** |
+| `username` | TEXT | FK → `users` (CASCADE) |
+| `chat_id` | INTEGER | FK → `chat_sessions` (CASCADE) |
+| `assistant_persona_id`, `user_persona_id` | INTEGER | FK → `personas` (SET NULL) |
+| `fact` | TEXT | Der gemerkte Fakt |
+| `created_at`, `updated_at` | DATETIME | |
+
+#### `persona_chat_state`
+Rollenspiel-Zustand eines Chats (1:1 zu `chat_sessions`, wegen `UNIQUE` auf `chat_id`).
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `id` | INTEGER | **PK** |
+| `username` | TEXT | FK → `users` (CASCADE) |
+| `chat_id` | INTEGER | FK → `chat_sessions` (CASCADE), **UNIQUE** |
+| `assistant_persona_id`, `user_persona_id` | INTEGER | FK → `personas` (SET NULL) |
+| `relationship_notes`, `tone`, `current_location`, `goals`, `unresolved_threads`, `boundaries` | TEXT | Zustand der Unterhaltung |
+| `created_at`, `updated_at` | DATETIME | |
+
+### Personas
+
+#### `personas`
+Charaktere, entweder für den Assistenten oder für den Benutzer selbst.
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `id` | INTEGER | **PK** |
+| `username` | TEXT | Besitzer (logisch → `users`, kein FK) |
+| `name` | TEXT | |
+| `pronouns`, `appearance`, `background`, `details` | TEXT | Beschreibung |
+| `custom_fields` | TEXT | Eigene Felder als JSON |
+| `persona_type` | TEXT | `'assistant'` (Default) oder User-Persona |
+| `source_market_id` | INTEGER | Falls aus dem Marktplatz importiert (logisch → `persona_market`) |
+| `source_creator_username` | TEXT | Ursprünglicher Ersteller |
+| `example_dialogues` | TEXT | Beispieldialoge |
+| `created_at`, `updated_at` | DATETIME | |
+
+#### `persona_versions`
+Versionsverlauf einer Persona.
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `id` | INTEGER | **PK** |
+| `persona_id` | INTEGER | FK → `personas` (CASCADE) |
+| `username` | TEXT | Wer die Version erstellt hat |
+| `version_number` | INTEGER | Fortlaufende Nummer |
+| `snapshot_json` | TEXT | Vollständiger Stand der Persona als JSON |
+| `created_at` | DATETIME | |
+
+#### `prompt_templates`
+Wiederverwendbare Prompt-Vorlagen.
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `id` | INTEGER | **PK** |
+| `username` | TEXT | Besitzer, darf `NULL` sein (z. B. System-Vorlagen) |
+| `persona_id` | INTEGER | FK → `personas` (CASCADE), optional |
+| `name`, `description`, `category` | TEXT | |
+| `prompt_text` | TEXT | Der eigentliche Prompt |
+| `starter_text` | TEXT | Optionaler Einstiegstext |
+| `is_shared` | INTEGER | Boolean, Default `0` |
+| `created_at`, `updated_at` | DATETIME | |
+
+### Marktplatz
+
+#### `persona_market`
+Öffentlich geteilte Personas. Eine Kopie der Persona, damit spätere Änderungen am Original
+den Marktplatz-Eintrag nicht automatisch verändern.
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `id` | INTEGER | **PK** |
+| `persona_id` | INTEGER | Original-Persona (logisch → `personas`, kein FK) |
+| `creator_username` | TEXT | Ersteller |
+| `name`, `pronouns`, `appearance`, `background`, `details`, `example_dialogues` | TEXT | Kopie der Persona-Daten |
+| `persona_type` | TEXT | Default `'assistant'` |
+| `tags` | TEXT | Schlagwörter |
+| `usage_count` | INTEGER | Wie oft importiert. Default `0` |
+| `favorite_count` | INTEGER | Zwischengespeicherte Anzahl Favoriten |
+| `rating_total`, `rating_count` | INTEGER | Summe und Anzahl der Bewertungen (Durchschnitt = total / count) |
+| `moderation_status` | TEXT | Default `'approved'` |
+| `moderation_notes` | TEXT | |
+| `soft_deleted_at` | DATETIME | `NULL` = sichtbar. Einträge werden nicht wirklich gelöscht |
+| `created_at`, `updated_at` | DATETIME | |
+
+**UNIQUE** (`persona_id`, `creator_username`): Eine Persona kann pro Ersteller nur einmal veröffentlicht werden.
+
+#### `persona_market_favorites`
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `market_id` | INTEGER | **PK**, FK → `persona_market` (CASCADE) |
+| `username` | TEXT | **PK** |
+| `created_at` | DATETIME | |
+
+Zusammengesetzter PK: Jeder Benutzer kann einen Eintrag nur einmal favorisieren.
+
+#### `persona_market_ratings`
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `market_id` | INTEGER | **PK**, FK → `persona_market` (CASCADE) |
+| `username` | TEXT | **PK** |
+| `rating` | INTEGER | Bewertung |
+| `created_at`, `updated_at` | DATETIME | |
+
+Zusammengesetzter PK: Eine Bewertung pro Benutzer und Eintrag (kann aktualisiert werden).
+
+#### `persona_market_reports`
+Meldungen von Benutzern zur Moderation.
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `id` | INTEGER | **PK** |
+| `market_id` | INTEGER | FK → `persona_market` (CASCADE) |
+| `reporter_username` | TEXT | |
+| `reason` | TEXT | |
+| `details` | TEXT | |
+| `status` | TEXT | Default `'open'` |
+| `created_at`, `updated_at` | DATETIME | |
+
+### Anhänge
+
+#### `attachments`
+Hochgeladene Bilder. Die Datei selbst liegt im Upload-Verzeichnis, die DB speichert nur Metadaten.
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `id` | TEXT | **PK** |
+| `username` | TEXT | FK → `users` (CASCADE, auch ON UPDATE) |
+| `original_name` | TEXT | Ursprünglicher Dateiname |
+| `storage_key` | TEXT | **UNIQUE**, Dateiname im Speicher |
+| `mime_type` | TEXT | |
+| `size_bytes`, `width`, `height` | INTEGER | |
+| `status` | TEXT | `'temporary'` (Default) oder `'attached'` (per CHECK erzwungen) |
+| `created_at` | TEXT | |
+| `vision_model`, `vision_analysis`, `vision_analyzed_at` | TEXT | Einzelne Bildanalyse (siehe auch `attachment_vision_analyses`) |
+
+#### `message_attachments`
+Verknüpfungstabelle Nachricht ↔ Anhang (n:m).
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `message_id` | INTEGER | **PK**, FK → `chat_messages` (CASCADE) |
+| `attachment_id` | TEXT | **PK**, FK → `attachments` (CASCADE), indiziert |
+| `position` | INTEGER | Reihenfolge der Bilder in der Nachricht |
+
+#### `attachment_vision_analyses`
+Zwischengespeicherte Bildanalysen. Pro Bild, Modell, Prompt-Version und Frage wird
+das Ergebnis nur einmal berechnet.
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `attachment_id` | TEXT | **PK**, FK → `attachments` (CASCADE) |
+| `vision_model` | TEXT | **PK** |
+| `prompt_version` | INTEGER | **PK** |
+| `question` | TEXT | **PK** |
+| `analysis` | TEXT | Ergebnis |
+| `analyzed_at` | TEXT | |
+
+#### `upload_deletions`
+Warteschlange für Dateien, die vom Speicher gelöscht werden müssen.
+
+| Spalte | Typ | Hinweis |
+|---|---|---|
+| `storage_key` | TEXT | **PK** |
+
+### Lebenszyklus eines Anhangs
+
+Die Datenbank kann keine Dateien löschen. Deshalb tragen Trigger die zu löschenden
+Dateien in `upload_deletions` ein, und die App räumt sie danach im Dateisystem auf.
+
+```mermaid
+flowchart TD
+ A[Bild hochgeladen] --> B["attachments
status = temporary"]
+ B -->|Nachricht gesendet| C["status = attached
+ Eintrag in message_attachments"]
+ C -->|Nachricht oder Chat gelöscht| D[message_attachments-Zeile gelöscht]
+ D -->|"Trigger release_message_attachment
(keine weiteren Verweise)"| E[attachments-Zeile gelöscht]
+ B -->|temporärer Upload verworfen| E
+ E -->|Trigger queue_attachment_file_delete| F["storage_key in upload_deletions"]
+ F --> G[App löscht Datei vom Speicher]
+```
+
+**Trigger:**
+
+- **`release_message_attachment`**: Nach dem Löschen einer Verknüpfung wird der Anhang gelöscht,
+ wenn er `attached` ist und von keiner anderen Nachricht mehr verwendet wird.
+- **`queue_attachment_file_delete`**: Nach dem Löschen eines Anhangs wird dessen
+ `storage_key` in `upload_deletions` eingetragen.
+
+## Migrationen
+
+`runMigrations` ist so geschrieben, dass sie **beliebig oft** laufen kann:
+
+- `CREATE TABLE IF NOT EXISTS` legt nur fehlende Tabellen an.
+- `ensureColumn` liest die vorhandenen Spalten per `PRAGMA table_info` und führt
+ `ALTER TABLE ... ADD COLUMN` nur aus, wenn die Spalte fehlt.
+- `UPDATE ... WHERE ... IS NULL` füllt Default-Werte für alte Zeilen nach.
+
+**Einmalige Datenmigrationen:**
+
+- **Nachrichtenbaum:** Wenn `parent_message_id` neu angelegt wurde, wird jede Nachricht mit
+ ihrer Vorgängernachricht im selben Chat verknüpft (`LAG`-Fensterfunktion), und
+ `active_leaf_message_id` wird auf die letzte Nachricht gesetzt.
+- **Alte `chats`-Tabelle:** Existiert die frühere Tabelle `chats` und ist `chat_messages`
+ leer, werden alle alten Nachrichten pro Benutzer in einen Chat „Imported chat“ übernommen.
+ Mit `dropLegacyChats = true` (Config `DROP_LEGACY_CHATS`) wird `chats` danach gelöscht.
+
+## Bekannte Auffälligkeiten
+
+- **Fehlende Fremdschlüssel auf `users`:** `chat_sessions`, `personas`, `prompt_templates`,
+ `persona_versions` und die Marktplatz-Tabellen verweisen nur logisch auf `users`.
+ Beim Löschen eines Benutzers bleiben diese Daten erhalten, sofern der Code sie nicht
+ selbst löscht.
+- **Doppelte Tabellendefinitionen:** Mehrere Tabellen werden sowohl in `initializeSchema`
+ als auch in `runMigrations` angelegt. Wegen `IF NOT EXISTS` ist das harmlos, muss aber
+ bei Änderungen an beiden Stellen gepflegt werden.
+- **Zwei Orte für Bildanalysen:** `attachments.vision_analysis` und die Tabelle
+ `attachment_vision_analyses`. Prüfen, ob die Spalten in `attachments` noch verwendet werden.
+- **Uneinheitliche Zeitstempel-Typen:** Meist `DATETIME`, in den Anhang-Tabellen `TEXT`.
+ In SQLite technisch gleichwertig, aber inkonsistent.
+- **Bewertungen ohne Wertebereich:** `persona_market_ratings.rating` hat keinen `CHECK`,
+ der gültige Bereich wird nur im Code geprüft.
\ No newline at end of file