mitai-jinkendo/.claude/docs/working/issue-21-universal-csv-parser-analysis.md
Lars 4a771f6a83
All checks were successful
Deploy Development / deploy (push) Successful in 50s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 15s
feat(csv-parser): Implement CSV import functionality with mapping and type conversion
- Added permissions for editing and deleting CSV field mappings.
- Created type converter for CSV cells to handle various data types.
- Implemented database migrations for CSV field mappings and import logs.
- Seeded initial system templates for nutrition and activity data imports.
- Developed admin endpoints for managing system CSV templates.
- Introduced user endpoints for CSV import analysis and mapping retrieval.
- Added tests for core CSV parser functionalities, including delimiter detection and value conversion.
2026-04-09 21:37:19 +02:00

1036 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Issue #21: Universeller CSV-Parser Anforderungsanalyse & Konzept
**Stand:** 2026-04-09
**Autor:** Claude Code Agent
**Status:** Konzeptphase (Wartet auf User-Approval)
---
## 1. Ausgangslage
### 1.1 Bestehende CSV-Import-Implementierungen
Aktuell existieren **4 separate CSV-Import-Funktionen**:
| Modul | Datei | Format | Besonderheiten |
|-------|-------|--------|----------------|
| **Nutrition** | `nutrition.py:34` | FDDB | Delimiter `;`, hardcoded Spalten, Aggregierung nach Tag |
| **Activity** | `activity.py:344` | Apple Health | **Lernendes Mapping** via `activity_type_mappings`, Update-or-Insert |
| **Blood Pressure** | `blood_pressure.py:293` | Omron | Multiple Spaltennamen-Varianten (DE/EN), Context-Tagging |
| **ZIP Import** | `importdata.py:30` | Eigenes Format | Profile.json + CSV-Bundle |
### 1.2 Gemeinsame Patterns (bereits vorhanden)
**Encoding-Detection:**
```python
try: text = raw.decode('utf-8')
except: text = raw.decode('latin-1')
if text.startswith('\ufeff'): text = text[1:] # BOM-Handling
```
**Duplikat-Erkennung:**
- Nutrition: `ON CONFLICT (profile_id, date) DO UPDATE`
- Activity: `SELECT WHERE profile_id=%s AND date=%s AND start_time=%s`
- Blood Pressure: Timestamp-basiert
**Type-Conversion** (scattered):
- Datumsformate: FDDB (`dd.mm.yyyy`), Apple Health (ISO), Omron (mehrere)
- Dezimaltrennzeichen: `,``.`
- Einheiten: kJ → kcal
**Fehlende Patterns:**
- Kein **einheitliches Mapping-System** (außer Activity)
- Kein **User-Interface für Mapping-Anpassung**
- Keine **automatische Format-Erkennung**
- Keine **Vorschläge für unbekannte Spalten**
---
## 2. Anforderungen (aus User-Request)
### 2.1 Funktionale Anforderungen
| # | Anforderung | Priorität |
|---|-------------|-----------|
| **F1** | **Universeller Parser:** Ein Parser für alle Module (Nutrition, Activity, Weight, Circumference, Caliper, Vitals, Sleep) | MUST |
| **F2** | **Lernendes System:** Automatische Erkennung bekannter CSV-Strukturen basierend auf Spalten-Signaturen | MUST |
| **F3** | **User-anpassbares Mapping:** UI zur manuellen Zuordnung von CSV-Spalten zu DB-Feldern | MUST |
| **F4** | **Intelligente Vorschläge:** System schlägt Mappings vor basierend auf Spalten-Namen, Sample-Daten, Statistiken | SHOULD |
| **F5** | **Type-Conversion:** Automatische Konvertierung von Datumsformaten, Dezimaltrennzeichen, Text→Zahl, Einheiten | MUST |
| **F6** | **Mapping-Persistenz:** Gespeicherte Mappings können wiederverwendet werden (pro User, pro Modul, global) | MUST |
| **F7** | **Format-Templates:** Vordefinierte Templates für bekannte Formate (FDDB, Apple Health, Omron, Garmin, etc.) | SHOULD |
| **F8** | **Validierung:** Vor-Import-Validierung mit Fehler-Report und Preview (erste 5 Zeilen) | SHOULD |
| **F9** | **Rollback:** Fehlerhafte Imports können rückgängig gemacht werden | NICE |
### 2.2 Nicht-funktionale Anforderungen
| # | Anforderung | Priorität |
|---|-------------|-----------|
| **NF1** | **Backward-Kompatibilität:** Bestehende CSV-Import-Endpoints bleiben funktionsfähig (Wrapper um neuen Parser) | MUST |
| **NF2** | **Performance:** Import von 1000 Zeilen < 5 Sekunden | SHOULD |
| **NF3** | **Erweiterbarkeit:** Neue Module/Felder können ohne Code-Änderung hinzugefügt werden (Registry-Pattern) | MUST |
| **NF4** | **Security:** User können nur eigene Mappings sehen/ändern (außer Admin) | MUST |
---
## 3. Datenmodell
### 3.1 Neue DB-Tabellen
#### **`csv_field_mappings`** (Zentrale Mapping-Registry)
```sql
CREATE TABLE csv_field_mappings (
id SERIAL PRIMARY KEY,
profile_id INTEGER REFERENCES profiles(id), -- NULL = System-Template
is_system BOOLEAN DEFAULT false, -- true = read-only Template
module VARCHAR(50) NOT NULL, -- 'nutrition', 'activity', etc.
mapping_name VARCHAR(100) NOT NULL, -- "FDDB Export", "Apple Health"
description TEXT, -- "Standard-Format für FDDB CSV-Exporte"
-- CSV-Signatur (für Auto-Detection)
column_signature TEXT[], -- Spalten-Namen (sortiert, normalisiert)
delimiter VARCHAR(10) DEFAULT ',', -- CSV-Delimiter
encoding VARCHAR(20) DEFAULT 'utf-8',
has_header BOOLEAN DEFAULT true,
-- Mapping-Definition (JSONB)
field_mappings JSONB NOT NULL, -- { "csv_column": "db_field" }
type_conversions JSONB, -- { "db_field": {"type": "date", "format": "dd.mm.yyyy"} }
-- Statistik (für Ranking)
usage_count INTEGER DEFAULT 0,
last_used_at TIMESTAMP,
success_rate FLOAT DEFAULT 1.0, -- Erfolgreiche Imports / Gesamt
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(profile_id, module, mapping_name),
CHECK (
-- System-Templates haben profile_id = NULL
(is_system = true AND profile_id IS NULL) OR
(is_system = false AND profile_id IS NOT NULL) OR
(is_system = false AND profile_id IS NULL)
)
);
CREATE INDEX idx_csv_mappings_lookup ON csv_field_mappings(module, profile_id);
CREATE INDEX idx_csv_mappings_signature ON csv_field_mappings USING GIN (column_signature);
CREATE INDEX idx_csv_mappings_system ON csv_field_mappings(is_system, module) WHERE is_system = true;
COMMENT ON TABLE csv_field_mappings IS 'Mapping-Registry: System-Templates (is_system=true) + User-Mappings (profile_id NOT NULL)';
COMMENT ON COLUMN csv_field_mappings.is_system IS 'System-Templates sind read-only und für alle User verfügbar';
COMMENT ON COLUMN csv_field_mappings.profile_id IS 'NULL = System-Template, NOT NULL = User-spezifisches Mapping';
```
**Beispiel-Entries:**
**System-Template (für alle User verfügbar):**
```json
{
"id": 1,
"profile_id": null,
"is_system": true,
"module": "nutrition",
"mapping_name": "FDDB Export (Standard)",
"description": "Standard-Format für FDDB.de CSV-Exporte (Deutsch)",
"column_signature": ["datum_tag_monat_jahr_stunde_minute", "fett_g", "kh_g", "kj", "protein_g"],
"delimiter": ";",
"encoding": "utf-8",
"has_header": true,
"field_mappings": {
"datum_tag_monat_jahr_stunde_minute": "date",
"kj": "kcal",
"fett_g": "fat_g",
"kh_g": "carbs_g",
"protein_g": "protein_g"
},
"type_conversions": {
"date": {
"type": "date",
"format": "dd.mm.yyyy HH:MM",
"extract": "date_only"
},
"kcal": {
"type": "float",
"source_unit": "kJ",
"target_unit": "kcal",
"conversion_factor": 0.239
},
"fat_g": {
"type": "float",
"decimal_separator": ","
}
},
"usage_count": 1523,
"success_rate": 0.99
}
```
**User-spezifisches Mapping (nur für User ID 42):**
```json
{
"id": 123,
"profile_id": 42,
"is_system": false,
"module": "nutrition",
"mapping_name": "Mein FDDB Export (angepasst)",
"description": "FDDB Export mit Notiz-Spalte",
"column_signature": ["datum_tag_monat_jahr_stunde_minute", "fett_g", "kh_g", "kj", "protein_g", "notiz"],
"delimiter": ";",
"encoding": "utf-8",
"has_header": true,
"field_mappings": {
"datum_tag_monat_jahr_stunde_minute": "date",
"kj": "kcal",
"fett_g": "fat_g",
"kh_g": "carbs_g",
"protein_g": "protein_g",
"notiz": "note"
},
"type_conversions": {
"date": {
"type": "date",
"format": "dd.mm.yyyy HH:MM",
"extract": "date_only"
},
"kcal": {
"type": "float",
"source_unit": "kJ",
"target_unit": "kcal",
"conversion_factor": 0.239
}
},
"usage_count": 8,
"success_rate": 1.0
}
```
#### **`csv_import_log`** (Import-Historie für Rollback)
```sql
CREATE TABLE csv_import_log (
id SERIAL PRIMARY KEY,
profile_id INTEGER REFERENCES profiles(id),
mapping_id INTEGER REFERENCES csv_field_mappings(id),
module VARCHAR(50) NOT NULL,
filename VARCHAR(255),
rows_total INTEGER,
rows_imported INTEGER,
rows_updated INTEGER,
rows_skipped INTEGER,
rows_errors INTEGER,
error_details JSONB, -- [{"row": 5, "error": "Invalid date"}]
started_at TIMESTAMP DEFAULT NOW(),
finished_at TIMESTAMP,
status VARCHAR(20) DEFAULT 'running', -- 'running', 'success', 'failed'
-- Für Rollback
affected_ids JSONB -- {"nutrition_log": [123, 456, ...]}
);
CREATE INDEX idx_csv_import_profile ON csv_import_log(profile_id, module);
```
### 3.2 System-Templates (Seed-Data)
**Bei Installation/Migration werden folgende System-Templates angelegt:**
#### **Nutrition (Ernährung)**
1. **FDDB Export (Standard)**
- Delimiter: `;`
- Encoding: `utf-8`
- Spalten: `datum_tag_monat_jahr_stunde_minute`, `kj`, `fett_g`, `kh_g`, `protein_g`
- Besonderheit: kJ kcal Konvertierung
2. **MyFitnessPal Export**
- Delimiter: `,`
- Encoding: `utf-8`
- Spalten: `Date`, `Calories`, `Carbohydrates (g)`, `Fat (g)`, `Protein (g)`
3. **Cronometer Export**
- Delimiter: `,`
- Encoding: `utf-8`
- Spalten: `Day`, `Energy (kcal)`, `Protein (g)`, `Net Carbs (g)`, `Fat (g)`
#### **Activity (Aktivität)**
1. **Apple Health Workout Export (English)**
- Delimiter: `,`
- Encoding: `utf-8`
- Spalten: `Workout Type`, `Start`, `End`, `Duration`, `Distance (km)`, `Active Energy (kcal)`, `Heart Rate Average (bpm)`
- Besonderheit: Automatisches Training-Type-Mapping
2. **Apple Health Workout Export (Deutsch)**
- Delimiter: `,`
- Encoding: `utf-8`
- Spalten: `Trainingsart`, `Start`, `Ende`, `Dauer`, `Strecke (km)`, `Aktive Energie (kcal)`, `Durchschnittliche Herzfrequenz (bpm)`
3. **Garmin Connect Export**
- Delimiter: `,`
- Encoding: `utf-8`
- Spalten: `Activity Type`, `Date`, `Time`, `Duration`, `Distance`, `Calories`, `Avg HR`
#### **Blood Pressure (Blutdruck)**
1. **Omron Export (Deutsch)**
- Delimiter: `,`
- Encoding: `utf-8`
- Spalten: `Datum`, `Zeit`, `Systolisch (mmHg)`, `Diastolisch (mmHg)`, `Puls (bpm)`
2. **Omron Export (English)**
- Delimiter: `,`
- Encoding: `utf-8`
- Spalten: `Date`, `Time`, `Systolic (mmHg)`, `Diastolic (mmHg)`, `Pulse (bpm)`
#### **Vitals (Vitalwerte)**
1. **Apple Health Vitals Export**
- Delimiter: `,`
- Encoding: `utf-8`
- Spalten: `Start`, `Resting Heart Rate (bpm)`, `Heart Rate Variability (ms)`, `Respiratory Rate (breaths/min)`, `Oxygen Saturation (%)`
#### **Weight (Gewicht)**
1. **Apple Health Weight Export**
- Delimiter: `,`
- Encoding: `utf-8`
- Spalten: `Start`, `Body Mass (kg)`
2. **Withings Export**
- Delimiter: `,`
- Encoding: `utf-8`
- Spalten: `Date`, `Weight (kg)`, `Body Fat (%)`, `Muscle Mass (kg)`
**GESAMT:** ~12-15 System-Templates initial
**Migration:** `backend/migrations/XXX_csv_parser_seed_templates.sql`
### 3.3 Modul-Registry (Backend Code)
**`backend/csv_parser/module_registry.py`**
Definiert für jedes Modul:
- Verfügbare DB-Felder
- Datentypen
- Validierung
- Erforderliche Felder
- Duplikat-Strategie
```python
MODULE_DEFINITIONS = {
"nutrition": {
"table": "nutrition_log",
"fields": {
"date": {"type": "date", "required": True},
"kcal": {"type": "float", "required": True, "min": 0, "max": 10000},
"protein_g": {"type": "float", "required": False, "min": 0},
"fat_g": {"type": "float", "required": False, "min": 0},
"carbs_g": {"type": "float", "required": False, "min": 0},
"note": {"type": "string", "required": False, "max_length": 500}
},
"duplicate_key": ["profile_id", "date"], # ON CONFLICT
"duplicate_strategy": "update" # "update" | "skip" | "error"
},
"activity": {
"table": "activity_log",
"fields": {
"date": {"type": "date", "required": True},
"start_time": {"type": "time", "required": False},
"activity_type": {"type": "string", "required": True},
"duration_min": {"type": "float", "required": True, "min": 0},
"kcal_active": {"type": "float", "required": False},
"distance_km": {"type": "float", "required": False},
"hr_avg": {"type": "int", "required": False, "min": 30, "max": 220}
},
"duplicate_key": ["profile_id", "date", "start_time"],
"duplicate_strategy": "update"
},
# ... weitere Module
}
```
---
## 4. Architektur
### 4.1 System-Komponenten
```
┌─────────────────────────────────────────────────────────────┐
│ Frontend (React) │
├─────────────────────────────────────────────────────────────┤
│ 1. CSV-Upload-Komponente │
│ - Datei-Upload + Format-Detection │
│ - Preview (erste 5 Zeilen) │
│ │
│ 2. Mapping-Editor │
│ - Spalten-zu-Feld-Zuordnung (Drag & Drop) │
│ - Type-Conversion-Konfiguration │
│ - Vorschau der konvertierten Werte │
│ │
│ 3. Mapping-Bibliothek │
│ - Gespeicherte Mappings anzeigen/auswählen │
│ - Templates (FDDB, Apple Health, etc.) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Backend (FastAPI) │
├─────────────────────────────────────────────────────────────┤
│ 1. CSV-Parser-Engine │
│ - Encoding-Detection (UTF-8, Latin-1, etc.) │
│ - Delimiter-Detection (`,` `;` `\t`) │
│ - Column-Signature-Berechnung │
│ │
│ 2. Mapping-Engine │
│ - Auto-Detection (Spalten → bekannte Mappings) │
│ - Intelligent Suggestions (Fuzzy-Match, Sample-Analyse) │
│ - Mapping-Persistenz (DB speichern/laden) │
│ │
│ 3. Type-Converter │
│ - Date-Parser (20+ Formate) │
│ - Number-Parser (Dezimaltrennzeichen, Tausender) │
│ - Unit-Converter (kJ↔kcal, km↔mi, etc.) │
│ - Text-Normalizer (Trim, Lowercase, etc.) │
│ │
│ 4. Validator │
│ - Type-Validation (INT, FLOAT, DATE, etc.) │
│ - Range-Validation (min/max) │
│ - Required-Field-Check │
│ - Custom-Validators pro Modul │
│ │
│ 5. Import-Executor │
│ - Batch-Insert mit Transaction │
│ - Duplikat-Handling (Update/Skip/Error) │
│ - Rollback bei Fehler │
│ - Progress-Tracking (für große Files) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL │
├─────────────────────────────────────────────────────────────┤
│ - csv_field_mappings (Mapping-Registry) │
│ - csv_import_log (Import-Historie) │
│ - nutrition_log, activity_log, ... (Daten-Tabellen) │
└─────────────────────────────────────────────────────────────┘
```
### 4.2 Workflow (Happy Path)
```
1. User wählt Datei
2. Frontend: POST /api/csv/analyze
- Datei hochladen
- Backend: Encoding + Delimiter erkennen
- Backend: Column-Signature berechnen
- Backend: Auto-Detection
1. Suche in User-Mappings (profile_id = current_user)
2. Suche in System-Templates (is_system = true)
3. Backend antwortet:
{
"detected_mapping": {
"id": 1,
"name": "FDDB Export (Standard)",
"is_system": true,
"confidence": 0.98,
"match_type": "exact_signature"
},
"columns": ["date", "kcal", "protein"],
"sample_rows": [...],
"suggestions": {
"date": ["date", "created_at"], // Vorschläge
"kcal": ["kcal", "energy"]
}
}
4. Frontend: Mapping-Editor
- User sieht: "System-Template erkannt: FDDB Export (Standard)"
- User kann Mapping anpassen (erstellt dann automatisch User-Copy)
- User testet Type-Conversion (Preview)
5. Frontend: POST /api/csv/import
{
"mapping_id": 1, // Verwende bestehendes Mapping, ODER:
"mapping": {...}, // Custom-Mapping
"module": "nutrition",
"save_mapping": true, // Als User-Mapping speichern?
"mapping_name": "MyFitnessPal Export"
}
6. Backend: Import ausführen
- Validierung
- Transaction starten
- Row-by-Row importieren
- Bei Fehler: Rollback
- Bei Erfolg: usage_count++ für verwendetes Mapping
7. Backend: Antwort
{
"success": true,
"imported": 100,
"updated": 5,
"skipped": 2,
"errors": [{"row": 7, "error": "Invalid date"}],
"import_log_id": 456 // Für Rollback
}
```
### 4.3 System-Templates vs. User-Mappings
**Hierarchie (Auto-Detection-Reihenfolge):**
1. **User-Mappings** (profile_id = current_user)
- Höchste Priorität
- Exact Match sofort verwenden
- Partial Match als Vorschlag
2. **System-Templates** (is_system = true, profile_id = NULL)
- Fallback wenn kein User-Mapping passt
- Read-only (User kann nicht ändern)
- User kann aber **Kopie erstellen** und anpassen
**Permissions:**
| Aktion | User-Mappings | System-Templates |
|--------|---------------|------------------|
| **Anzeigen** | Eigene | Alle |
| **Verwenden** | Eigene | Alle |
| **Erstellen** | Ja | Nur Admin/Migration |
| **Ändern** | Eigene | Nein (Kopie erstellen) |
| **Löschen** | Eigene | Nein |
| **Kopieren** | Ja | Ja User-Mapping |
**Workflow "System-Template anpassen":**
```
User wählt System-Template "FDDB Export (Standard)"
→ User ändert Mapping (z.B. fügt Spalte hinzu)
→ Frontend fragt: "System-Template kann nicht geändert werden.
Kopie erstellen? [Ja] [Abbrechen]"
→ User klickt [Ja]
→ Neue User-Mapping mit is_system=false, profile_id=current_user
```
---
## 5. Intelligente Features
### 5.1 Auto-Detection (Spalten-Signatur-Matching)
**Algorithmus:**
1. **Exakte Signatur:** Spalten-Namen (normalisiert, sortiert) 100% Match
```
["date", "kcal", "protein_g"] Mapping-ID 123
```
2. **Partial Match:** 70% Überlappung Vorschlag
```
CSV: ["date", "calories", "protein"]
DB: ["date", "kcal", "protein_g"]
Match: 66% Mapping-ID 123 als Vorschlag
```
3. **Fuzzy-Match:** Levenshtein-Distanz < 3
```
"Datum" "date" (Distance: 3)
"Kalorien" "kcal" (keine exakte Match)
```
### 5.2 Intelligente Vorschläge
**Sample-basierte Analyse:**
1. **Date-Detection:** Regex-Patterns für 20+ Formate
```python
SAMPLES = ["01.01.2024", "02.01.2024", "03.01.2024"]
Pattern: dd.mm.yyyy
Vorschlag: Spalte "Datum" Feld "date"
```
2. **Number-Detection:** Statistik über Sample-Werte
```python
SAMPLES = ["1500,5", "2000,3", "1800,0"]
Decimal-Separator: ","
Range: 1000-3000 passt zu "kcal"
```
3. **Unit-Detection:** Keyword-Search in Spalten-Namen
```python
"Active Energy (kJ)" Einheit: kJ Feld: kcal (mit Conversion)
```
### 5.3 Type-Conversion (20+ Formate)
**Date-Formate:**
```python
DATE_PATTERNS = [
"%Y-%m-%d", # 2024-01-15 (ISO)
"%d.%m.%Y", # 15.01.2024 (DE)
"%d/%m/%Y", # 15/01/2024 (UK)
"%m/%d/%Y", # 01/15/2024 (US)
"%Y-%m-%d %H:%M:%S", # Full timestamp
"%d.%m.%Y %H:%M", # FDDB format
# ... 15 weitere
]
```
**Number-Conversion:**
```python
def parse_number(value: str, decimal_sep=',', thousands_sep='.') -> float:
# "1.500,50" → 1500.50
value = value.replace(thousands_sep, '')
value = value.replace(decimal_sep, '.')
return float(value)
```
**Unit-Conversion:**
```python
UNIT_CONVERSIONS = {
("kJ", "kcal"): lambda x: x / 4.184,
("kcal", "kJ"): lambda x: x * 4.184,
("km", "mi"): lambda x: x * 0.621371,
("mi", "km"): lambda x: x * 1.60934,
("kg", "lb"): lambda x: x * 2.20462,
("lb", "kg"): lambda x: x * 0.453592,
}
```
---
## 6. API-Endpoints
### 6.1 Neue Endpoints
#### **POST /api/csv/analyze**
Analysiert hochgeladene CSV-Datei und schlägt Mappings vor.
**Request:**
```
Content-Type: multipart/form-data
file: <csv-file>
module: "nutrition"
```
**Response:**
```json
{
"encoding": "utf-8",
"delimiter": ";",
"columns": ["Datum", "Kalorien (kJ)", "Protein (g)", "Fett (g)"],
"sample_rows": [
{"Datum": "01.01.2024", "Kalorien (kJ)": "8000", "Protein (g)": "80", "Fett (g)": "60"},
{"Datum": "02.01.2024", "Kalorien (kJ)": "9000", "Protein (g)": "90", "Fett (g)": "70"}
],
"detected_mappings": [
{
"mapping_id": 123,
"mapping_name": "FDDB Export",
"confidence": 0.95,
"match_type": "exact_signature"
}
],
"suggestions": {
"Datum": {
"suggested_field": "date",
"confidence": 0.98,
"type": "date",
"detected_format": "dd.mm.yyyy",
"sample_conversions": ["2024-01-01", "2024-01-02"]
},
"Kalorien (kJ)": {
"suggested_field": "kcal",
"confidence": 0.85,
"type": "float",
"requires_conversion": true,
"source_unit": "kJ",
"target_unit": "kcal",
"sample_conversions": [1912.6, 2151.7]
}
},
"available_fields": {
"date": {"type": "date", "required": true},
"kcal": {"type": "float", "required": true, "min": 0, "max": 10000},
"protein_g": {"type": "float", "required": false},
"fat_g": {"type": "float", "required": false},
"carbs_g": {"type": "float", "required": false}
}
}
```
#### **POST /api/csv/import**
Führt Import mit bestätigtem Mapping aus.
**Request:**
```json
{
"file_data": "<base64-encoded-csv>", // Oder file_id aus /analyze
"module": "nutrition",
"mapping": {
"field_mappings": {
"Datum": "date",
"Kalorien (kJ)": "kcal",
"Protein (g)": "protein_g"
},
"type_conversions": {
"date": {"type": "date", "format": "dd.mm.yyyy"},
"kcal": {"type": "float", "source_unit": "kJ", "conversion_factor": 0.239}
}
},
"save_mapping": true,
"mapping_name": "FDDB Export 2024"
}
```
**Response:**
```json
{
"success": true,
"import_log_id": 456,
"stats": {
"total_rows": 100,
"imported": 95,
"updated": 3,
"skipped": 2,
"errors": 0
},
"error_details": [],
"duration_ms": 1234
}
```
#### **GET /api/csv/mappings**
Liste gespeicherter Mappings (User + System-Templates).
**Query-Params:**
- `module`: Filter nach Modul (optional)
**Response:**
```json
{
"system_templates": [
{
"id": 1,
"module": "nutrition",
"name": "FDDB Export (Standard)",
"description": "Standard-Format für FDDB.de CSV-Exporte",
"is_system": true,
"usage_count": 1523,
"success_rate": 0.99,
"created_at": "2024-01-01T00:00:00"
},
{
"id": 2,
"module": "activity",
"name": "Apple Health Workout Export",
"description": "Apple Health CSV-Export (English)",
"is_system": true,
"usage_count": 5043,
"success_rate": 0.98,
"created_at": "2024-01-01T00:00:00"
}
],
"user_mappings": [
{
"id": 123,
"module": "nutrition",
"name": "Mein FDDB Export (angepasst)",
"description": "FDDB mit Notizen",
"is_system": false,
"usage_count": 8,
"success_rate": 1.0,
"last_used_at": "2024-01-15T10:30:00",
"created_at": "2024-01-10T12:00:00"
}
]
}
```
**Sortierung:**
- System-Templates: nach `usage_count DESC` (beliebteste zuerst)
- User-Mappings: nach `last_used_at DESC` (neueste zuerst)
#### **POST /api/csv/mappings/{mapping_id}/copy**
Erstellt User-Kopie eines System-Templates (für Anpassungen).
**Response:**
```json
{
"new_mapping_id": 124,
"message": "Kopie erstellt: 'FDDB Export (Standard)' → 'FDDB Export (Standard) - Kopie'"
}
```
#### **DELETE /api/csv/mappings/{mapping_id}**
Löscht gespeichertes Mapping.
**Permissions:**
- User können nur **eigene** Mappings löschen (profile_id = current_user)
- System-Templates (is_system = true) können **nicht** gelöscht werden
- Admin kann alle löschen (außer System-Templates)
#### **POST /api/csv/rollback/{import_log_id}**
Macht einen Import rückgängig (löscht importierte Einträge).
**NICE-TO-HAVE:** Nur wenn Zeit bleibt.
### 6.2 Bestehende Endpoints (Wrapper)
Die bestehenden Endpoints **bleiben funktional** als dünner Wrapper:
```python
# backend/routers/nutrition.py
@router.post("/import-csv")
async def import_nutrition_csv(file: UploadFile, ...):
"""
LEGACY: FDDB-spezifischer Import (Backward-Kompatibilität).
Nutzt intern den Universal-Parser mit vordefiniertem FDDB-Template.
"""
# Wrapper um Universal-Parser:
from csv_parser import universal_import
mapping = get_predefined_mapping("nutrition", "fddb")
result = await universal_import(
file=file,
module="nutrition",
mapping=mapping,
profile_id=pid
)
# Legacy Response-Format beibehalten:
return {
"imported": result["stats"]["imported"],
"skipped": result["stats"]["skipped"]
}
```
---
## 7. Frontend-UI (Skizze)
### 7.1 CSV-Upload-Seite
```
┌─────────────────────────────────────────────────────────┐
│ Daten importieren CSV-Upload │
├─────────────────────────────────────────────────────────┤
│ │
│ Schritt 1: Datei hochladen │
│ ┌─────────────────────────────────────────────────┐ │
│ │ [📁 Datei auswählen] nutrition-export.csv │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ Schritt 2: Modul auswählen │
│ ○ Ernährung ○ Aktivität ○ Gewicht ○ Vitalwerte │
│ │
│ [Weiter →] │
└─────────────────────────────────────────────────────────┘
```
### 7.2 Mapping-Editor
```
┌──────────────────────────────────────────────────────────────┐
│ CSV-Import Mapping bearbeiten │
├──────────────────────────────────────────────────────────────┤
│ │
│ ✓ Format erkannt: FDDB Export (95% Übereinstimmung) │
│ │
│ Spalten-Zuordnung: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ CSV-Spalte → Datenbank-Feld │ │
│ ├────────────────────────────────────────────────────────┤ │
│ │ "Datum" → [date ▼] ✓ │ │
│ │ "Kalorien (kJ)" → [kcal ▼] ⚠️ │ │
│ │ └─ Umrechnung: kJ → kcal (÷4.184) │ │
│ │ "Protein (g)" → [protein_g ▼] ✓ │ │
│ │ "Fett (g)" → [fat_g ▼] ✓ │ │
│ │ "Produkt" → [—nicht zuordnen—] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Vorschau (erste 3 Zeilen): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ date │ kcal │ protein_g │ fat_g │ │ │
│ ├────────────────────────────────────────────────────────┤ │
│ │ 2024-01-01 │ 1912.6 │ 80.0 │ 60.0 │ │ │
│ │ 2024-01-02 │ 2151.7 │ 90.0 │ 70.0 │ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ☐ Mapping speichern als: [FDDB Export 2024________] │
│ │
│ [← Zurück] [Import starten →] │
└──────────────────────────────────────────────────────────────┘
```
### 7.3 Import-Fortschritt
```
┌─────────────────────────────────────────────────────────┐
│ CSV-Import läuft... │
├─────────────────────────────────────────────────────────┤
│ │
│ ████████████████████░░░░░░░░ 80% (80/100 Zeilen) │
│ │
│ ✓ 75 Einträge importiert │
│ ↻ 3 Einträge aktualisiert │
│ ⊗ 2 Fehler │
│ │
│ [Abbrechen] │
└─────────────────────────────────────────────────────────┘
```
---
## 8. Implementierungs-Phasen
### Phase 1: Foundation (Woche 1) **← START HIER**
**Ziel:** Parser-Engine + Modul-Registry + System-Templates
- [ ] **Migration:**
- `XXX_csv_parser_tables.sql` `csv_field_mappings`, `csv_import_log` Tabellen
- `XXX_csv_parser_seed_templates.sql` 12-15 System-Templates anlegen
- [ ] **Backend:**
- `csv_parser/core.py` Encoding/Delimiter-Detection
- `csv_parser/module_registry.py` Modul-Definitionen
- `csv_parser/type_converter.py` Date/Number/Unit-Converter (20+ Formate)
- `csv_parser/permissions.py` System-Template Read-Only-Check
- [ ] **Testing:** Unit-Tests für Type-Converter + System-Template-Seed
**Output:**
- Funktionierender Parser (ohne Auto-Detection, ohne UI)
- 12-15 System-Templates in DB verfügbar
- User können Templates laden (aber nicht ändern)
---
### Phase 2: Mapping-System (Woche 2)
**Ziel:** Auto-Detection + Mapping-Persistenz
- [ ] **Backend:**
- `csv_parser/mapping_engine.py` Auto-Detection, Fuzzy-Match
- `csv_parser/suggestions.py` Intelligente Vorschläge
- API: `/api/csv/analyze`, `/api/csv/mappings`, `/api/csv/mappings/{id}/copy`
- [ ] **Permissions:** System-Template Read-Only-Enforcement
- [ ] **Testing:**
- Auto-Detection-Tests mit realen CSV-Files (alle System-Templates)
- User vs. System Permissions (User kann nicht System-Template ändern)
- Copy-Workflow (System-Template User-Mapping)
**Output:**
- Auto-Detection funktioniert (User-Mappings > System-Templates)
- User können System-Templates kopieren und anpassen
- Permissions korrekt (Read-Only für System-Templates)
---
### Phase 3: Import-Executor + API (Woche 2-3)
**Ziel:** Import-Workflow komplett
- [ ] **Backend:**
- `csv_parser/executor.py` Batch-Insert, Validation, Rollback
- API: `/api/csv/import`, `/api/csv/mappings`
- [ ] **Migration:** Bestehende Import-Endpoints auf Wrapper umstellen
- [ ] **Testing:** End-to-End-Tests (Nutrition, Activity)
**Output:** Import funktioniert via API, Legacy-Endpoints funktional
---
### Phase 4: Frontend (Woche 3-4)
**Ziel:** User-Interface für Mapping-Editor
- [ ] **Frontend:**
- `CSVUploadPage.jsx` Upload + Modul-Auswahl
- `CSVMappingEditor.jsx` Spalten-zu-Feld-Zuordnung
- `CSVImportProgress.jsx` Fortschritts-Anzeige
- `CSVMappingLibrary.jsx` Gespeicherte Mappings anzeigen/auswählen
- [ ] **UX:** Drag & Drop für Spalten-Zuordnung
- [ ] **Testing:** E2E-Tests (Playwright)
**Output:** Vollständige UI, User kann eigene Mappings erstellen
---
### Phase 5: Rollout (Woche 4)
**Ziel:** Alle Module migriert, Legacy-Code entfernt
- [ ] Alle Module auf Universal-Parser migriert (Weight, Circumference, Caliper, Sleep)
- [ ] Legacy-Import-Code entfernt (nach Deprecation-Phase)
- [ ] Dokumentation aktualisiert
- [ ] Gitea Issue #21 geschlossen
---
## 9. Offene Fragen (für User-Approval)
1. **Scope:** Alle Module sofort oder schrittweise? (Empfehlung: Start mit Nutrition + Activity)
2. **Rollback:** Wichtig genug für Phase 1-3? Oder NICE-TO-HAVE?
3. **UI-Komplexität:** Drag & Drop oder simple Dropdowns? (Empfehlung: Dropdowns zuerst, D&D später)
4. **Performance:** Import-Limit pro File? (Empfehlung: 10.000 Zeilen, dann Batch-Upload)
5. **Migration:** Legacy-Endpoints sofort wrappen oder parallel laufen lassen?
---
## 10. Aufwandsschätzung
| Phase | Aufwand | Komponenten |
|-------|---------|-------------|
| **Phase 1** | 8-12h | Parser-Engine, Type-Converter, Migrations |
| **Phase 2** | 6-8h | Auto-Detection, Mapping-Engine, Suggestions |
| **Phase 3** | 8-10h | Import-Executor, API-Endpoints, Wrapper |
| **Phase 4** | 12-16h | Frontend UI (3-4 Komponenten) |
| **Phase 5** | 4-6h | Migration aller Module, Cleanup |
| **GESAMT** | **38-52h** | ~5-7 Arbeitstage |
**Kritischer Pfad:** Phase 1 → Phase 2 → Phase 3 (Backend muss komplett sein vor Frontend)
---
## 11. Risiken & Mitigations
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|--------|-------------------|--------|------------|
| **Date-Format-Vielfalt:** 20+ Formate schwer zu parsen | HOCH | MITTEL | Fallback auf Manual-Input, User kann Format angeben |
| **Performance:** Große Files (>10k Zeilen) langsam | MITTEL | MITTEL | Batch-Processing + Background-Job (Celery) |
| **Backward-Compatibility:** Legacy-Code bricht | NIEDRIG | HOCH | Parallel-Betrieb + Feature-Flag |
| **UX-Komplexität:** Mapping-Editor zu komplex | MITTEL | NIEDRIG | Wizard-Flow, Step-by-Step, gute Defaults |
---
## 12. Erfolgskriterien
**User kann CSV-File hochladen ohne Code-Kenntnisse**
**System erkennt bekannte Formate automatisch (≥80% Accuracy)**
**User kann eigene Mappings speichern und wiederverwenden**
**Import-Fehlerrate < 5% bei validen Daten**
**Performance: 1000 Zeilen in < 5 Sekunden**
**Alle bestehenden CSV-Imports funktionieren weiter (Wrapper)**
---
**Nächster Schritt:** User-Approval für Konzept + Start Phase 1 (Foundation)
**Geschätzter Start-to-Finish:** 5-7 Arbeitstage (bei Fokus-Arbeit ohne Unterbrechungen)