- .gitignore: .claude/docs, rules, commands tracken; settings.local weiter ignorieren - DOCUMENTATION.md: verbindliche Ablage functional/technical/working/issues - .claude/README.md: Agent-Einstieg; GITEA_ISSUES_INDEX aus MCP (Stand 2026-04-08) - Arbeitspapiere von docs/ nach .claude/docs/working/ verschoben - docs/MEMBERSHIP_SYSTEM.md als Stub; kanonisch technical/MEMBERSHIP_SYSTEM.md - CLAUDE.md Pflichtlektüre und Links angepasst; docs/README.md vereinfacht Made-with: Cursor
423 lines
14 KiB
Markdown
423 lines
14 KiB
Markdown
# Activity Quality Gates – Fachliches Konzept
|
||
|
||
**Issue:** #15
|
||
**Status:** Design Phase
|
||
**Erstellt:** 2026-03-23
|
||
|
||
---
|
||
|
||
## Problem-Statement
|
||
|
||
### Aktuelles Problem:
|
||
Apple Health und andere Tracker importieren **alle** Workouts, unabhängig von ihrer Qualität:
|
||
- 5-Minuten-Spaziergang → "Outdoor Run"
|
||
- 10-Minuten-Krafttraining ohne echte Belastung
|
||
- Versehentlich gestartete Workouts
|
||
- Aufwärm-Sessions ohne echtes Training
|
||
|
||
**Folgen:**
|
||
- KI-Analysen werden verfälscht ("Du trainierst täglich!" - aber nur 5min)
|
||
- Statistiken zeigen unrealistische Trainingsfrequenz
|
||
- Korrelationen (Training ↔ Erholung) werden unbrauchbar
|
||
- User verliert Vertrauen in die Auswertungen
|
||
|
||
---
|
||
|
||
## Fachliche Anforderungen
|
||
|
||
### Must-Have:
|
||
1. **Pro Trainingstyp** unterschiedliche Qualitätskriterien
|
||
2. **Automatische Bewertung** beim Import und bei manueller Eingabe
|
||
3. **Nicht-destruktiv** - keine Daten löschen, nur markieren
|
||
4. **Transparenz** - User sieht warum eine Aktivität als minderwertig gilt
|
||
5. **Opt-Out möglich** - User kann Quality Gates pro Aktivität überschreiben
|
||
|
||
### Nice-to-Have:
|
||
6. **Admin-Presets** für gängige Trainingstypen (Laufen, Krafttraining, etc.)
|
||
7. **User-spezifische Anpassung** (z.B. für Reha-Patienten andere Schwellwerte)
|
||
8. **Historische Nachbearbeitung** - bestehende Aktivitäten neu bewerten
|
||
|
||
---
|
||
|
||
## Lösungsansätze (Evaluation)
|
||
|
||
### Ansatz A: Quality Flag (Boolean)
|
||
```sql
|
||
ALTER TABLE activity_log ADD COLUMN is_valid BOOLEAN DEFAULT true;
|
||
```
|
||
|
||
**Pro:**
|
||
- Einfach zu implementieren
|
||
- Schnelle Queries (`WHERE is_valid = true`)
|
||
- Binäre Entscheidung: gültig oder nicht
|
||
|
||
**Contra:**
|
||
- ❌ Keine Abstufungen (was ist mit "grenzwertig"?)
|
||
- ❌ Kein Grund dokumentiert (warum ungültig?)
|
||
- ❌ Schwer erweiterbar
|
||
|
||
**Bewertung:** ⭐⭐☆☆☆ (zu simpel)
|
||
|
||
---
|
||
|
||
### Ansatz B: Quality Score (0-100)
|
||
```sql
|
||
ALTER TABLE activity_log ADD COLUMN quality_score INTEGER DEFAULT 100;
|
||
```
|
||
|
||
**Pro:**
|
||
- Abstufungen möglich (100 = perfekt, 0 = wertlos)
|
||
- Filterbar: `WHERE quality_score >= 70`
|
||
- Flexibel für zukünftige Erweiterungen
|
||
|
||
**Contra:**
|
||
- ❌ Score-Berechnung komplex (wie gewichten?)
|
||
- ❌ Kein Grund dokumentiert
|
||
- ❌ Schwellwert (70? 80?) willkürlich
|
||
|
||
**Bewertung:** ⭐⭐⭐☆☆ (besser, aber intransparent)
|
||
|
||
---
|
||
|
||
### Ansatz C: Validation Result (JSONB) ⭐ **EMPFEHLUNG**
|
||
```sql
|
||
ALTER TABLE activity_log ADD COLUMN quality_check JSONB DEFAULT NULL;
|
||
|
||
-- Beispiel-Daten:
|
||
{
|
||
"evaluated_at": "2026-03-23T10:30:00Z",
|
||
"passed": false,
|
||
"score": 45,
|
||
"reasons": [
|
||
{"rule": "duration_min", "expected": 15, "actual": 8, "passed": false},
|
||
{"rule": "avg_hr_min", "expected": 100, "actual": 95, "passed": false},
|
||
{"rule": "max_hr_min", "expected": 120, "actual": 125, "passed": true}
|
||
],
|
||
"override": null // User kann auf "valid" oder "invalid" setzen
|
||
}
|
||
```
|
||
|
||
**Pro:**
|
||
- ✅ Transparent: Jede Regel einzeln nachvollziehbar
|
||
- ✅ Erweiterbar: Neue Regeln einfach hinzufügbar
|
||
- ✅ Override-Mechanismus eingebaut
|
||
- ✅ Historisch: Wann wurde evaluiert?
|
||
- ✅ Flexibel: Score + Boolean + Details
|
||
|
||
**Contra:**
|
||
- Mehr Speicherplatz (aber marginal bei JSONB)
|
||
- Komplexere Queries (aber PostgreSQL JSONB ist schnell)
|
||
|
||
**Bewertung:** ⭐⭐⭐⭐⭐ **BESTE LÖSUNG**
|
||
|
||
---
|
||
|
||
## Empfohlene Lösung: Ansatz C (Validation Result)
|
||
|
||
### Datenmodell
|
||
|
||
#### 1. Training Types (Regel-Definition)
|
||
```sql
|
||
ALTER TABLE training_types ADD COLUMN quality_rules JSONB DEFAULT NULL;
|
||
|
||
-- Beispiel: Laufen
|
||
UPDATE training_types SET quality_rules = '{
|
||
"enabled": true,
|
||
"rules": {
|
||
"duration_min": {"min": 15, "weight": 3},
|
||
"avg_hr_min": {"min": 100, "weight": 2},
|
||
"max_hr_min": {"min": 120, "weight": 1},
|
||
"distance_km": {"min": 1.0, "weight": 1}
|
||
},
|
||
"pass_threshold": 0.6,
|
||
"description": "Mindestens 15min, Durchschnittspuls > 100"
|
||
}'::jsonb WHERE name_de = 'Laufen';
|
||
|
||
-- Beispiel: Krafttraining
|
||
UPDATE training_types SET quality_rules = '{
|
||
"enabled": true,
|
||
"rules": {
|
||
"duration_min": {"min": 20, "weight": 5},
|
||
"avg_hr_min": {"min": 90, "weight": 1}
|
||
},
|
||
"pass_threshold": 0.8,
|
||
"description": "Mindestens 20 Minuten"
|
||
}'::jsonb WHERE name_de = 'Krafttraining';
|
||
```
|
||
|
||
**Erklärung:**
|
||
- `enabled`: Quality Gates aktiv für diesen Typ?
|
||
- `rules`: Dictionary der Regeln mit Schwellwerten + Gewichtung
|
||
- `weight`: Wichtigkeit der Regel (1-5)
|
||
- `pass_threshold`: Mindest-Score (0.0-1.0) für "bestanden"
|
||
- `description`: User-freundliche Erklärung
|
||
|
||
#### 2. Activity Log (Validierungs-Ergebnis)
|
||
```sql
|
||
ALTER TABLE activity_log ADD COLUMN quality_check JSONB DEFAULT NULL;
|
||
```
|
||
|
||
**Struktur siehe Ansatz C oben.**
|
||
|
||
---
|
||
|
||
## Validierungs-Logik
|
||
|
||
### Backend-Funktion: `validate_activity_quality()`
|
||
|
||
```python
|
||
def validate_activity_quality(activity: dict, training_type: dict) -> dict:
|
||
"""
|
||
Evaluiert eine Aktivität gegen die Quality Rules des Trainingstyps.
|
||
|
||
Returns:
|
||
{
|
||
"evaluated_at": ISO timestamp,
|
||
"passed": bool,
|
||
"score": float (0.0-1.0),
|
||
"reasons": [
|
||
{"rule": str, "expected": value, "actual": value, "passed": bool}
|
||
],
|
||
"override": null | "valid" | "invalid"
|
||
}
|
||
"""
|
||
rules = training_type.get('quality_rules', {})
|
||
|
||
if not rules or not rules.get('enabled'):
|
||
return None # Keine Quality Gates aktiv
|
||
|
||
results = []
|
||
total_weight = 0
|
||
passed_weight = 0
|
||
|
||
for rule_name, rule_config in rules['rules'].items():
|
||
weight = rule_config.get('weight', 1)
|
||
total_weight += weight
|
||
|
||
actual_value = activity.get(rule_name)
|
||
expected_min = rule_config.get('min')
|
||
|
||
passed = actual_value is not None and actual_value >= expected_min
|
||
|
||
if passed:
|
||
passed_weight += weight
|
||
|
||
results.append({
|
||
"rule": rule_name,
|
||
"expected": expected_min,
|
||
"actual": actual_value,
|
||
"passed": passed
|
||
})
|
||
|
||
score = passed_weight / total_weight if total_weight > 0 else 1.0
|
||
passed = score >= rules.get('pass_threshold', 0.6)
|
||
|
||
return {
|
||
"evaluated_at": datetime.now().isoformat(),
|
||
"passed": passed,
|
||
"score": round(score, 2),
|
||
"reasons": results,
|
||
"override": None
|
||
}
|
||
```
|
||
|
||
### Wann wird validiert?
|
||
|
||
1. **Beim Import** (CSV, API)
|
||
- Automatisch nach INSERT
|
||
- Spalte `quality_check` wird befüllt
|
||
|
||
2. **Beim manuellen Anlegen**
|
||
- Automatisch nach INSERT
|
||
- Spalte `quality_check` wird befüllt
|
||
|
||
3. **Beim Ändern der Quality Rules** (Admin)
|
||
- Optionaler Batch-Job: Alle Aktivitäten neu evaluieren
|
||
|
||
4. **Beim User-Override**
|
||
- User setzt `quality_check.override = "valid"` oder `"invalid"`
|
||
|
||
---
|
||
|
||
## User Experience
|
||
|
||
### 1. Activity-Liste (User-Ansicht)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ Aktivitäten (März 2026) [Filter ▼] │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ 🏃 Laufen - 23. März 2026 │
|
||
│ 45 Minuten · Ø 142 bpm · 5.2 km │
|
||
│ ✅ Hochwertige Aktivität │
|
||
│ │
|
||
│ 🏃 Laufen - 22. März 2026 ⚠️ │
|
||
│ 8 Minuten · Ø 95 bpm · 0.8 km │
|
||
│ ⚠️ Niedrige Qualität - wird nicht gezählt │
|
||
│ [Details anzeigen ▼] │
|
||
│ │
|
||
│ → Dauer zu kurz (8 min < 15 min erforderlich) ❌ │
|
||
│ → Durchschnittspuls zu niedrig (95 < 100) ❌ │
|
||
│ → Maximalpuls OK (125 ≥ 120) ✅ │
|
||
│ │
|
||
│ [Als gültig markieren] [Als ungültig markieren] │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Filter-Optionen:**
|
||
- ☑ Hochwertige Aktivitäten
|
||
- ☑ Minderwertige Aktivitäten
|
||
- ☐ Nur in Statistiken gezählt
|
||
- ☐ Nur manuell überschrieben
|
||
|
||
### 2. Admin-UI (Quality Rules konfigurieren)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ Trainingstyp bearbeiten: Laufen 🏃 │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ Kategorie: Cardio │
|
||
│ Name (DE): Laufen │
|
||
│ Name (EN): Running │
|
||
│ │
|
||
│ ━━━ Quality Gates ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
|
||
│ │
|
||
│ ☑ Quality Gates aktiviert │
|
||
│ │
|
||
│ Mindest-Schwellwerte: │
|
||
│ │
|
||
│ • Dauer (Minuten) [15] min Gewicht: ●●●○○ │
|
||
│ • Ø Herzfrequenz [100] bpm Gewicht: ●●○○○ │
|
||
│ • Max. Herzfrequenz [120] bpm Gewicht: ●○○○○ │
|
||
│ • Distanz [1.0] km Gewicht: ●○○○○ │
|
||
│ │
|
||
│ Erfolgs-Schwelle: [60]% (gewichteter Score) │
|
||
│ │
|
||
│ Beschreibung (User-sichtbar): │
|
||
│ "Mindestens 15 Minuten, Durchschnittspuls > 100" │
|
||
│ │
|
||
│ [Speichern] [Abbrechen] [Alle Aktivitäten neu prüfen]│
|
||
│ │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 3. Dashboard / Statistiken
|
||
|
||
**Transparenz:**
|
||
```
|
||
📊 Training (März 2026)
|
||
15 Aktivitäten erfasst
|
||
12 hochwertig ✅
|
||
3 minderwertig ⚠️ (werden nicht gezählt)
|
||
|
||
[Details zu minderwertigen Aktivitäten anzeigen]
|
||
```
|
||
|
||
---
|
||
|
||
## Betroffene Bereiche (Impact Analysis)
|
||
|
||
### 1. Aktivitäten-Listen
|
||
- **Query-Änderung:**
|
||
```sql
|
||
-- Alt:
|
||
SELECT * FROM activity_log WHERE profile_id = %s
|
||
|
||
-- Neu (nur hochwertige):
|
||
SELECT * FROM activity_log
|
||
WHERE profile_id = %s
|
||
AND (quality_check IS NULL
|
||
OR quality_check->>'passed' = 'true'
|
||
OR quality_check->>'override' = 'valid')
|
||
```
|
||
|
||
### 2. KI-Pipeline
|
||
- **insights.py:** Filter bei Daten-Aggregation
|
||
- Nur hochwertige Aktivitäten für KI-Prompts
|
||
|
||
### 3. Charts / Statistiken
|
||
- **Training Type Distribution:** Nur hochwertige zählen
|
||
- **Korrelations-Charts:** Separate Kurven für "alle" vs "hochwertig"?
|
||
|
||
### 4. Activity Import (CSV)
|
||
- Nach INSERT: `validate_activity_quality()` aufrufen
|
||
- Quality Check speichern
|
||
|
||
---
|
||
|
||
## Offene Fragen (für Entscheidung)
|
||
|
||
### Frage 1: Rückwirkende Evaluierung?
|
||
**Option A:** Nur neue Aktivitäten evaluieren
|
||
**Option B:** Alle bestehenden Aktivitäten nachträglich evaluieren
|
||
|
||
**Empfehlung:** Option B mit Batch-Job (einmalig beim Rollout)
|
||
|
||
### Frage 2: Standard-Verhalten bei fehlenden Quality Rules?
|
||
**Option A:** Alle Aktivitäten gelten als hochwertig (NULL = valid)
|
||
**Option B:** Alle Aktivitäten gelten als minderwertig (NULL = invalid)
|
||
|
||
**Empfehlung:** Option A (konservativ, keine Daten-Verlust-Angst)
|
||
|
||
### Frage 3: User-Override-Berechtigung?
|
||
**Option A:** Jeder User kann eigene Aktivitäten überschreiben
|
||
**Option B:** Nur Admin kann überschreiben
|
||
|
||
**Empfehlung:** Option A (User kennt seinen Kontext am besten)
|
||
|
||
### Frage 4: Wie viele Default-Rules?
|
||
**Option A:** Nur für Top 5 Trainingstypen (Laufen, Krafttraining, Radfahren, Schwimmen, HIIT)
|
||
**Option B:** Für alle 29 Trainingstypen
|
||
|
||
**Empfehlung:** Option A (Rest kann Admin/User nachpflegen)
|
||
|
||
### Frage 5: Quality Check in Liste anzeigen?
|
||
**Option A:** Immer sichtbar (Badge/Icon)
|
||
**Option B:** Nur bei minderwertigen Aktivitäten
|
||
|
||
**Empfehlung:** Option B (weniger visuelles Rauschen)
|
||
|
||
---
|
||
|
||
## Implementierungs-Reihenfolge (Vorschlag)
|
||
|
||
### Phase 1: Foundation (MVP)
|
||
1. DB-Migration: `quality_rules` + `quality_check` Spalten
|
||
2. Backend: `validate_activity_quality()` Funktion
|
||
3. Backend: Validierung beim INSERT (activity.py)
|
||
4. Admin-UI: Quality Rules konfigurieren (basic)
|
||
|
||
### Phase 2: User Experience
|
||
5. Frontend: Badge/Warning in Activity-Liste
|
||
6. Frontend: Details-Ansicht (welche Regeln failed?)
|
||
7. Frontend: User-Override (Als gültig/ungültig markieren)
|
||
|
||
### Phase 3: Integration
|
||
8. KI-Pipeline: Filter für hochwertige Aktivitäten
|
||
9. Charts: Nur hochwertige zählen
|
||
10. Batch-Job: Bestehende Aktivitäten evaluieren
|
||
|
||
### Phase 4: Polish
|
||
11. Admin-UI: Presets für gängige Trainingstypen
|
||
12. Filter-Optionen in Activity-Liste
|
||
13. Dashboard: Statistik hochwertig vs. minderwertig
|
||
|
||
**Geschätzter Aufwand:** 4-6 Stunden (mit Tests)
|
||
|
||
---
|
||
|
||
## Nächste Schritte
|
||
|
||
1. **Entscheidung:** Offene Fragen klären
|
||
2. **Technisches Design:** DB-Schema + API-Endpoints spezifizieren
|
||
3. **Implementation:** Phase 1 starten
|
||
4. **Testing:** Mit echten Apple Health Daten testen
|
||
|
||
---
|
||
|
||
**Erstellt:** 2026-03-23
|
||
**Review:** Pending User-Feedback
|