feat: Quality-Filter für KI-Pipeline & History (#24)
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:
parent
9210d051a8
commit
9ec774e956
52
CLAUDE.md
52
CLAUDE.md
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,))
|
||||||
|
|
|
||||||
|
|
@ -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'],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user