feat: Quality-Filter für KI-Pipeline & History (#24)
All checks were successful
Deploy Development / deploy (push) Successful in 49s
Build Test / lint-backend (push) Successful in 1s
Build Test / build-frontend (push) Successful in 14s

Backend:
- insights.py: KI-Pipeline filtert activity_log nach quality_label
- Nur 'excellent', 'good', 'acceptable' (poor wird ausgeschlossen)
- NULL-Werte erlaubt (für alte Einträge vor Migration 014)

Frontend:
- History.jsx: Toggle "Nur qualitativ hochwertige Aktivitäten"
- Filter wirkt auf Activity-Statistiken, Charts, Listen
- Anzeige: X von Y Activities (wenn gefiltert)

Dokumentation:
- CLAUDE.md: Feature-Roadmap aktualisiert (Phase 0-2)

Closes #24

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-23 21:59:02 +01:00
parent 9210d051a8
commit 9ec774e956
3 changed files with 71 additions and 14 deletions

View File

@ -190,19 +190,47 @@ frontend/src/
## Feature-Roadmap ## Feature-Roadmap
> Vollständiges Backlog: `.claude/docs/BACKLOG.md` > 📋 **Detaillierte Roadmap:** `.claude/docs/ROADMAP.md` (Phasen 0-3, Timeline, Abhängigkeiten)
> Beim Implementieren: verlinkte Dok-Datei zuerst lesen! > 📚 **Vollständiges Backlog:** `.claude/docs/BACKLOG.md`
> 🎯 **Gitea Issues:** http://192.168.2.144:3000/Lars/mitai-jinkendo/issues
>
> **Beim Implementieren:** verlinkte Dok-Datei zuerst lesen!
| Version | Feature | Dokumentation | ### Aktuelle Entwicklung (Phase 0-2, ~10-13 Wochen)
|---------|---------|---------------|
| v9c | Membership (aktiv) | `technical/MEMBERSHIP_SYSTEM.md` ✅ | | Phase | Fokus | Dauer | Gitea Issues |
| v9d | Schlaf-Modul | `functional/SLEEP_MODULE.md` (ausstehend) | |-------|-------|-------|--------------|
| v9d | Trainingstypen + HF | `functional/TRAINING_TYPES.md` ✅ | | **Phase 0** | Infrastruktur (v9f) | 4-6 Wochen | #24, #28, #29, #30 |
| v9e | Ziele + Vitalwerte | `functional/GOALS_VITALS.md` (ausstehend) | | **Phase 1** | Foundation (Charts, Goals) | 2-3 Wochen | #26, #25 |
| v9f | KI-Prompt Flexibilisierung | `functional/AI_PROMPTS.md` ✅ | | **Phase 2** | Engagement (Korrelationen, KI) | 3-4 Wochen | #27, #25 |
| v9g | Meditation + Selbstwahrnehmung | `functional/MEDITATION.md` (ausstehend) | | **Phase 3** | Begleitung (Development Routes) | später | - |
| v9h | Connectoren + Stripe | ausstehend |
| — | Responsive UI | `functional/RESPONSIVE_UI.md` ✅ | **Phase 0 Issues:**
- #24: Quality-Filter (3h, Quick Win) ← **Start hier**
- #28: AI-Prompts Flexibilisierung (16-20h, kritisch)
- #29: Abilities-Matrix UI (6-8h)
- #30: Responsive UI (8-10h, parallel)
**Phase 1 Issues:**
- #26: Charts erweitern (8-10h)
- #25: Ziele-System Basis (10-12h)
**Phase 2 Issues:**
- #27: Korrelationen (6-8h)
- #25: Goals KI-Integration (4h)
### Versions-Übersicht
| Version | Feature | Dokumentation | Status |
|---------|---------|---------------|--------|
| v9c | Membership (aktiv) | `technical/MEMBERSHIP_SYSTEM.md` | ✅ Production |
| v9d | Schlaf-Modul | `functional/SLEEP_MODULE.md` | ✅ Production |
| v9d | Trainingstypen + HF | `functional/TRAINING_TYPES.md` | ✅ Production |
| v9e | Ziele + Vitalwerte | `functional/GOALS_VITALS.md` | 🔲 Phase 1 |
| v9f | KI-Prompt Flexibilisierung | `functional/AI_PROMPTS.md` | 🔲 Phase 0 |
| v9g | Meditation + Selbstwahrnehmung | `functional/MEDITATION.md` | 🔲 Phase 3 |
| v9h | Connectoren + Stripe | ausstehend | 🔲 Später |
| — | Responsive UI | `functional/RESPONSIVE_UI.md` | 🔲 Phase 0 |
## Deployment ## Deployment

View File

@ -75,7 +75,13 @@ def _get_profile_data(pid: str):
caliper = [r2d(r) for r in cur.fetchall()] caliper = [r2d(r) for r in cur.fetchall()]
cur.execute("SELECT * FROM nutrition_log WHERE profile_id=%s ORDER BY date DESC LIMIT 90", (pid,)) cur.execute("SELECT * FROM nutrition_log WHERE profile_id=%s ORDER BY date DESC LIMIT 90", (pid,))
nutrition = [r2d(r) for r in cur.fetchall()] nutrition = [r2d(r) for r in cur.fetchall()]
cur.execute("SELECT * FROM activity_log WHERE profile_id=%s ORDER BY date DESC LIMIT 90", (pid,)) # Quality-Filter: nur hochwertige Aktivitäten für KI-Analyse (Issue #24)
cur.execute("""
SELECT * FROM activity_log
WHERE profile_id=%s
AND (quality_label IN ('excellent', 'good', 'acceptable') OR quality_label IS NULL)
ORDER BY date DESC LIMIT 90
""", (pid,))
activity = [r2d(r) for r in cur.fetchall()] activity = [r2d(r) for r in cur.fetchall()]
# v9d Phase 2: Sleep, Rest Days, Vitals # v9d Phase 2: Sleep, Rest Days, Vitals
cur.execute("SELECT * FROM sleep_log WHERE profile_id=%s ORDER BY date DESC LIMIT 30", (pid,)) cur.execute("SELECT * FROM sleep_log WHERE profile_id=%s ORDER BY date DESC LIMIT 30", (pid,))

View File

@ -588,11 +588,16 @@ function NutritionSection({ nutrition, weights, profile, insights, onRequest, lo
// Activity Section // Activity Section
function ActivitySection({ activities, insights, onRequest, loadingSlug, filterActiveSlugs }) { function ActivitySection({ activities, insights, onRequest, loadingSlug, filterActiveSlugs }) {
const [period, setPeriod] = useState(30) const [period, setPeriod] = useState(30)
const [qualityFilter, setQualityFilter] = useState(false) // Issue #24: Quality-Filter Toggle
if (!activities?.length) return ( if (!activities?.length) return (
<EmptySection text="Noch keine Aktivitätsdaten." to="/activity" toLabel="Aktivität erfassen"/> <EmptySection text="Noch keine Aktivitätsdaten." to="/activity" toLabel="Aktivität erfassen"/>
) )
const cutoff = dayjs().subtract(period,'day').format('YYYY-MM-DD') const cutoff = dayjs().subtract(period,'day').format('YYYY-MM-DD')
const filtA = activities.filter(d=>period===9999||d.date>=cutoff) // Issue #24: Filter nach Datum UND Quality-Label
const filtA = activities.filter(d =>
(period===9999 || d.date>=cutoff) &&
(!qualityFilter || ['excellent', 'good', 'acceptable'].includes(d.quality_label))
)
const byDate={} const byDate={}
filtA.forEach(a=>{ byDate[a.date]=(byDate[a.date]||0)+(a.kcal_active||0) }) filtA.forEach(a=>{ byDate[a.date]=(byDate[a.date]||0)+(a.kcal_active||0) })
@ -620,6 +625,24 @@ function ActivitySection({ activities, insights, onRequest, loadingSlug, filterA
<div> <div>
<SectionHeader title="🏋️ Aktivität" to="/activity" toLabel="Alle Einträge" lastUpdated={activities[0]?.date}/> <SectionHeader title="🏋️ Aktivität" to="/activity" toLabel="Alle Einträge" lastUpdated={activities[0]?.date}/>
<PeriodSelector value={period} onChange={setPeriod}/> <PeriodSelector value={period} onChange={setPeriod}/>
{/* Issue #24: Quality-Filter Toggle */}
<div style={{marginBottom:12,display:'flex',alignItems:'center',gap:8}}>
<label style={{display:'flex',alignItems:'center',gap:6,fontSize:12,cursor:'pointer',
padding:'6px 10px',borderRadius:8,background:qualityFilter?'var(--accent-light)':'var(--surface2)',
border:`1px solid ${qualityFilter?'var(--accent)':'var(--border)'}`,
color:qualityFilter?'var(--accent)':'var(--text2)',transition:'all 0.2s'}}>
<input type="checkbox" checked={qualityFilter} onChange={e=>setQualityFilter(e.target.checked)}
style={{width:14,height:14,cursor:'pointer',accentColor:'var(--accent)'}}/>
<span style={{fontWeight:qualityFilter?600:400}}>Nur qualitativ hochwertige Aktivitäten</span>
</label>
{qualityFilter && (
<span style={{fontSize:11,color:'var(--text3)'}}>
({filtA.length} von {activities.filter(d=>period===9999||d.date>=cutoff).length})
</span>
)}
</div>
<div style={{display:'flex',gap:6,marginBottom:12}}> <div style={{display:'flex',gap:6,marginBottom:12}}>
{[['Trainings',filtA.length,'var(--text1)'],['Kcal',totalKcal,'#EF9F27'], {[['Trainings',filtA.length,'var(--text1)'],['Kcal',totalKcal,'#EF9F27'],
['Stunden',Math.round(totalMin/60*10)/10,'#378ADD'], ['Stunden',Math.round(totalMin/60*10)/10,'#378ADD'],