# 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