diff --git a/CLAUDE.md b/CLAUDE.md index 561efc7..edb0f0e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,7 +82,45 @@ frontend/src/ **Branch:** develop **Nächster Schritt:** Testing → Prod Deploy → Code Splitting → Phase 0b (120+ Platzhalter) -### Letzte Updates (27.03.2026 - Dynamic Focus Areas v2.0 Complete) 🆕 +### Updates (28.03.2026 - Goal System Enhancement Complete) 🆕 + +#### Auto-Population & Time-Based Tracking ✅ +- ✅ **Auto-Population von start_date/start_value:** + - Automatische Ermittlung aus erster historischer Messung (on/after Startdatum) + - Windowing-Logik: Findet nächste verfügbare Messung am oder nach gewähltem Datum + - Auto-Adjustment: Startdatum wird auf tatsächliches Messdatum gesetzt + - Funktioniert für alle Goal-Typen (weight, body_fat, lean_mass, vo2max, strength, bp, rhr) +- ✅ **Time-Based Tracking (Behind Schedule):** + - Linear Progress Model: expected = (elapsed_days / total_days) × 100 + - Deviation Calculation: actual_progress - expected_progress + - Negativ = behind schedule, Positiv = ahead of schedule + - User-Feedback: "Warum 'behind schedule'?" → Zeitbasierte Abweichung implementiert +- ✅ **Hybrid Goal Display:** + - Goals MIT target_date: Zeit-basierte Abweichung (±% voraus/zurück) + - Goals OHNE target_date: Einfacher Fortschritt (% erreicht) + - Kombinierte Sortierung für aussagekräftige Rankings + - Platzhalter: `{{top_3_goals_behind_schedule}}`, `{{top_3_goals_on_track}}` +- ✅ **Timeline Visualization:** + - Start → Ziel Datumsanzeige in Ziellisten + - Format: "Start: 92.0 kg (22.02.26) → Ziel: 85.0 kg (31.05.26)" + - Fortschrittsbalken mit Prozentanzeige + +#### Bug Fixes (28.03.2026) ✅ +- ✅ **PostgreSQL Date Arithmetic:** ORDER BY ABS(date - %s::date) statt EXTRACT(EPOCH) +- ✅ **JSON Date Serialization:** serialize_dates() für Python date → ISO strings +- ✅ **start_date nicht gespeichert:** update_goal() Logik komplett überarbeitet +- ✅ **start_date fehlte in SELECT:** get_active_goals() + get_goals_grouped() ergänzt +- ✅ **Edit-Form Datum-Fallback:** goal.start_date || '' statt || today +- ✅ **Behind Schedule Logik:** Von "lowest progress" zu "time-based deviation" +- ✅ **Fehlende created_at:** Backup-Datum für Goals ohne start_date + +#### Betroffene Dateien: +- `backend/routers/goals.py`: serialize_dates(), _get_historical_value_for_goal_type(), create_goal(), update_goal(), list_goals(), get_goals_grouped() +- `backend/goal_utils.py`: get_active_goals() SELECT ergänzt (start_date, created_at) +- `backend/placeholder_resolver.py`: _format_goals_behind(), _format_goals_on_track() komplett überarbeitet (hybrid logic) +- `frontend/src/pages/GoalsPage.jsx`: Timeline-Display, handleEditGoal() fix + +### Letzte Updates (27.03.2026 - Dynamic Focus Areas v2.0 Complete) #### Dynamic Focus Areas v2.0 System ✅ - ✅ **Migration 031-032:** Vollständiges dynamisches System diff --git a/backend/placeholder_resolver.py b/backend/placeholder_resolver.py index debc29f..c6db4cf 100644 --- a/backend/placeholder_resolver.py +++ b/backend/placeholder_resolver.py @@ -824,7 +824,6 @@ def _format_goals_behind(profile_id: str, n: int = 3) -> str: today = date.today() goals_with_deviation = [] - print(f"[DEBUG] _format_goals_behind: Processing {len(goals)} goals") for g in goals: goal_name = g.get('name') or g.get('goal_type', 'Unknown') @@ -834,16 +833,13 @@ def _format_goals_behind(profile_id: str, n: int = 3) -> str: start_date = g.get('start_date') target_date = g.get('target_date') - print(f"[DEBUG] Goal '{goal_name}': current={current}, target={target}, start={start}, start_date={start_date}, target_date={target_date}") # Skip if missing required values if None in [current, target, start]: - print(f"[DEBUG] → Skipped: Missing current/target/start") continue # Skip if no target_date (can't calculate time-based progress) if not target_date: - print(f"[DEBUG] → Skipped: No target_date") continue try: @@ -867,9 +863,7 @@ def _format_goals_behind(profile_id: str, n: int = 3) -> str: created_at = g.get('created_at') if created_at: start_dt = date.fromisoformat(str(created_at).split('T')[0]) - print(f"[DEBUG] → Using created_at as start_date: {start_dt}") else: - print(f"[DEBUG] → Skipped: No start_date and no created_at") continue # Can't calculate without start date target_dt = target_date if isinstance(target_date, date) else date.fromisoformat(str(target_date)) @@ -879,7 +873,6 @@ def _format_goals_behind(profile_id: str, n: int = 3) -> str: elapsed_days = (today - start_dt).days if total_days <= 0: - print(f"[DEBUG] → Skipped: Invalid date range (total_days={total_days})") continue # Invalid date range expected_progress_pct = (elapsed_days / total_days) * 100 @@ -892,15 +885,12 @@ def _format_goals_behind(profile_id: str, n: int = 3) -> str: g['_expected_progress'] = int(expected_progress_pct) g['_deviation'] = int(deviation) goals_with_deviation.append(g) - print(f"[DEBUG] → Added: actual={int(actual_progress_pct)}%, expected={int(expected_progress_pct)}%, deviation={int(deviation)}%") except (ValueError, ZeroDivisionError, TypeError) as e: - print(f"[DEBUG] → Exception: {type(e).__name__}: {e}") continue # Also process goals WITHOUT target_date (simple progress) goals_without_date = [] - print(f"[DEBUG] Processing goals without target_date for simple progress") for g in goals: if g.get('target_date'): @@ -912,7 +902,6 @@ def _format_goals_behind(profile_id: str, n: int = 3) -> str: start = g.get('start_value') if None in [current, target, start]: - print(f"[DEBUG] Goal '{goal_name}' (no date): Skipped - missing values") continue try: @@ -928,16 +917,13 @@ def _format_goals_behind(profile_id: str, n: int = 3) -> str: g['_simple_progress'] = int(progress_pct) goals_without_date.append(g) - print(f"[DEBUG] Goal '{goal_name}' (no date): Added with {int(progress_pct)}% progress") except (ValueError, ZeroDivisionError, TypeError) as e: - print(f"[DEBUG] Goal '{goal_name}' (no date): Exception - {e}") continue # Combine: Goals with negative deviation + Goals without date with low progress behind_with_date = [g for g in goals_with_deviation if g['_deviation'] < 0] behind_without_date = [g for g in goals_without_date if g['_simple_progress'] < 50] - print(f"[DEBUG] Behind with date: {len(behind_with_date)}, Behind without date: {len(behind_without_date)}") # Create combined list with sort keys combined = [] @@ -1002,7 +988,6 @@ def _format_goals_on_track(profile_id: str, n: int = 3) -> str: today = date.today() goals_with_deviation = [] - print(f"[DEBUG] _format_goals_on_track: Processing {len(goals)} goals") for g in goals: goal_name = g.get('name') or g.get('goal_type', 'Unknown') @@ -1012,16 +997,13 @@ def _format_goals_on_track(profile_id: str, n: int = 3) -> str: start_date = g.get('start_date') target_date = g.get('target_date') - print(f"[DEBUG] Goal '{goal_name}': current={current}, target={target}, start={start}, start_date={start_date}, target_date={target_date}") # Skip if missing required values if None in [current, target, start]: - print(f"[DEBUG] → Skipped: Missing current/target/start") continue # Skip if no target_date if not target_date: - print(f"[DEBUG] → Skipped: No target_date") continue try: @@ -1065,15 +1047,12 @@ def _format_goals_on_track(profile_id: str, n: int = 3) -> str: g['_expected_progress'] = int(expected_progress_pct) g['_deviation'] = int(deviation) goals_with_deviation.append(g) - print(f"[DEBUG] → Added: actual={int(actual_progress_pct)}%, expected={int(expected_progress_pct)}%, deviation={int(deviation)}%") except (ValueError, ZeroDivisionError, TypeError) as e: - print(f"[DEBUG] → Exception: {type(e).__name__}: {e}") continue # Also process goals WITHOUT target_date (simple progress) goals_without_date = [] - print(f"[DEBUG] Processing goals without target_date for simple progress") for g in goals: if g.get('target_date'): @@ -1085,7 +1064,6 @@ def _format_goals_on_track(profile_id: str, n: int = 3) -> str: start = g.get('start_value') if None in [current, target, start]: - print(f"[DEBUG] Goal '{goal_name}' (no date): Skipped - missing values") continue try: @@ -1101,16 +1079,13 @@ def _format_goals_on_track(profile_id: str, n: int = 3) -> str: g['_simple_progress'] = int(progress_pct) goals_without_date.append(g) - print(f"[DEBUG] Goal '{goal_name}' (no date): Added with {int(progress_pct)}% progress") except (ValueError, ZeroDivisionError, TypeError) as e: - print(f"[DEBUG] Goal '{goal_name}' (no date): Exception - {e}") continue # Combine: Goals with positive deviation + Goals without date with high progress ahead_with_date = [g for g in goals_with_deviation if g['_deviation'] >= 0] ahead_without_date = [g for g in goals_without_date if g['_simple_progress'] >= 50] - print(f"[DEBUG] Ahead with date: {len(ahead_with_date)}, Ahead without date: {len(ahead_without_date)}") # Create combined list with sort keys combined = [] diff --git a/backend/routers/goals.py b/backend/routers/goals.py index 8a7fb9a..5f99e0b 100644 --- a/backend/routers/goals.py +++ b/backend/routers/goals.py @@ -360,12 +360,10 @@ def list_goals(session: dict = Depends(require_auth)): """, (pid,)) goals = [r2d(row) for row in cur.fetchall()] - print(f"[DEBUG] Loaded {len(goals)} goals for profile {pid}") # Debug: Show first goal with dates if goals: first = goals[0] - print(f"[DEBUG] First goal dates: start_date={first.get('start_date')} (type: {type(first.get('start_date'))}), target_date={first.get('target_date')} (type: {type(first.get('target_date'))})") # Update current values for each goal for goal in goals: @@ -378,11 +376,6 @@ def list_goals(session: dict = Depends(require_auth)): # Serialize date objects to ISO format strings goals = serialize_dates(goals) - # Debug: Show ALL goals with their dates - print(f"[DEBUG] Returning {len(goals)} goals after serialization:") - for g in goals: - print(f" Goal {g.get('id')}: goal_type={g.get('goal_type')}, start_date={g.get('start_date')}, target_date={g.get('target_date')}") - return goals except Exception as e: @@ -469,14 +462,6 @@ def update_goal(goal_id: str, data: GoalUpdate, session: dict = Depends(require_ """Update existing goal""" pid = session['profile_id'] - # Debug logging - print(f"[DEBUG] update_goal called with:") - print(f" goal_id: {goal_id}") - print(f" start_date: {data.start_date} (type: {type(data.start_date)})") - print(f" start_value: {data.start_value}") - print(f" target_date: {data.target_date}") - print(f" target_value: {data.target_value}") - with get_db() as conn: cur = get_cursor(conn) @@ -549,9 +534,7 @@ def update_goal(goal_id: str, data: GoalUpdate, session: dict = Depends(require_ goal_row = cur.fetchone() if goal_row: goal_type = goal_row['goal_type'] - print(f"[DEBUG] Looking up historical value for {goal_type} on or after {requested_date}") historical_data = _get_historical_value_for_goal_type(conn, pid, goal_type, requested_date) - print(f"[DEBUG] Historical data result: {historical_data}") if historical_data is not None: # Use actual measurement date and value @@ -607,8 +590,6 @@ def update_goal(goal_id: str, data: GoalUpdate, session: dict = Depends(require_ params.extend([goal_id, pid]) update_sql = f"UPDATE goals SET {', '.join(updates)} WHERE id = %s AND profile_id = %s" - print(f"[DEBUG] UPDATE SQL: {update_sql}") - print(f"[DEBUG] UPDATE params: {params}") cur.execute(update_sql, tuple(params)) @@ -618,7 +599,6 @@ def update_goal(goal_id: str, data: GoalUpdate, session: dict = Depends(require_ FROM goals WHERE id = %s """, (goal_id,)) saved_goal = cur.fetchone() - print(f"[DEBUG] After UPDATE, goal in DB: {r2d(saved_goal)}") return {"message": "Ziel aktualisiert"} @@ -768,16 +748,13 @@ def _get_historical_value_for_goal_type(conn, profile_id: str, goal_type: str, t # Get goal type configuration config = get_goal_type_config(conn, goal_type) if not config: - print(f"[DEBUG] No config found for goal_type: {goal_type}") return None source_table = config.get('source_table') source_column = config.get('source_column') - print(f"[DEBUG] Config: table={source_table}, column={source_column}") if not source_table or not source_column: - print(f"[DEBUG] Missing source_table or source_column") return None # Query for value closest to target_date (±7 days window) @@ -803,13 +780,10 @@ def _get_historical_value_for_goal_type(conn, profile_id: str, goal_type: str, t """ params = (profile_id, target_date) - print(f"[DEBUG] Query: {query}") - print(f"[DEBUG] Params: {params}") cur.execute(query, params) row = cur.fetchone() - print(f"[DEBUG] Query result: {row}") if row: value = row[source_column] @@ -827,10 +801,8 @@ def _get_historical_value_for_goal_type(conn, profile_id: str, goal_type: str, t result_date = measurement_date result = {'value': result_value, 'date': result_date} - print(f"[DEBUG] Returning: {result}") return result - print(f"[DEBUG] No data found on or after {target_date}") return None except Exception as e: print(f"[ERROR] Failed to get historical value for {goal_type} on {target_date}: {e}")