From befc310958e0490e03eb8ada08962498f58f0cbc Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 28 Mar 2026 12:43:54 +0100 Subject: [PATCH] fix: focus_areas column name + goal progress calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed 2 critical placeholder issues: 1. focus_areas_weighted_json was empty: - Query used 'area_key' but column is 'key' in focus_area_definitions - Changed to SELECT key, not area_key 2. Goal progress placeholders showed "nicht verfügbar": - progress_pct in goals table is NULL (not auto-calculated) - Added manual calculation in all 3 formatter functions: * _format_goals_as_markdown() - shows % in table * _format_goals_behind() - finds lowest progress * _format_goals_on_track() - finds >= 50% progress All placeholders should now return proper values. Co-Authored-By: Claude Opus 4.6 --- backend/placeholder_resolver.py | 100 +++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 14 deletions(-) diff --git a/backend/placeholder_resolver.py b/backend/placeholder_resolver.py index 5f194b4..5e23ffa 100644 --- a/backend/placeholder_resolver.py +++ b/backend/placeholder_resolver.py @@ -686,11 +686,11 @@ def _get_focus_areas_weighted_json(profile_id: str) -> str: with get_db() as conn: cur = get_cursor(conn) cur.execute(""" - SELECT area_key, name_de, name_en, category + SELECT key, name_de, name_en, category FROM focus_area_definitions WHERE is_active = true """) - definitions = {row['area_key']: row for row in cur.fetchall()} + definitions = {row['key']: row for row in cur.fetchall()} # Build weighted list result = [] @@ -726,12 +726,33 @@ def _format_goals_as_markdown(profile_id: str) -> str: for goal in goals: name = goal.get('name') or goal.get('goal_type', 'Unbekannt') - current = f"{goal.get('current_value', '-')}" - target = f"{goal.get('target_value', '-')}" - progress = f"{goal.get('progress_pct', '-')}%" if goal.get('progress_pct') else '-' + current = goal.get('current_value') + target = goal.get('target_value') + start = goal.get('start_value') + + # Calculate progress if possible + progress_str = '-' + if None not in [current, target, start]: + try: + current_f = float(current) + target_f = float(target) + start_f = float(start) + + if target_f == start_f: + progress_pct = 100 if current_f == target_f else 0 + else: + progress_pct = ((current_f - start_f) / (target_f - start_f)) * 100 + progress_pct = max(0, min(100, progress_pct)) + + progress_str = f"{int(progress_pct)}%" + except (ValueError, ZeroDivisionError): + progress_str = '-' + + current_str = f"{current}" if current is not None else '-' + target_str = f"{target}" if target is not None else '-' status = '🎯' if goal.get('is_primary') else '○' - lines.append(f"| {name} | {current} | {target} | {progress} | {status} |") + lines.append(f"| {name} | {current_str} | {target_str} | {progress_str} | {status} |") return '\n'.join(lines) except Exception: @@ -793,19 +814,43 @@ def _format_goals_behind(profile_id: str, n: int = 3) -> str: if not goals: return 'Keine Ziele definiert' - # Filter goals with progress_pct available, sort by lowest progress - goals_with_progress = [g for g in goals if g.get('progress_pct') is not None] + # Calculate progress for each goal + goals_with_progress = [] + for g in goals: + current = g.get('current_value') + target = g.get('target_value') + start = g.get('start_value') + + if None in [current, target, start]: + continue + + # Calculate progress percentage + try: + current = float(current) + target = float(target) + start = float(start) + + if target == start: + progress_pct = 100 if current == target else 0 + else: + progress_pct = ((current - start) / (target - start)) * 100 + progress_pct = max(0, min(100, progress_pct)) + + g['_calc_progress_pct'] = int(progress_pct) + goals_with_progress.append(g) + except (ValueError, ZeroDivisionError): + continue if not goals_with_progress: return 'Keine Ziele mit Fortschritt' # Sort by progress ascending (lowest first) and take top N - sorted_goals = sorted(goals_with_progress, key=lambda x: x.get('progress_pct', 0))[:n] + sorted_goals = sorted(goals_with_progress, key=lambda x: x.get('_calc_progress_pct', 0))[:n] lines = [] for goal in sorted_goals: name = goal.get('name') or goal.get('goal_type', 'Unbekannt') - progress = goal.get('progress_pct', 0) + progress = goal.get('_calc_progress_pct', 0) lines.append(f"{name} ({progress}%)") return ', '.join(lines) @@ -822,19 +867,46 @@ def _format_goals_on_track(profile_id: str, n: int = 3) -> str: if not goals: return 'Keine Ziele definiert' - # Filter goals with progress >= 50%, sort by highest progress - goals_on_track = [g for g in goals if g.get('progress_pct') is not None and g.get('progress_pct', 0) >= 50] + # Calculate progress for each goal + goals_with_progress = [] + for g in goals: + current = g.get('current_value') + target = g.get('target_value') + start = g.get('start_value') + + if None in [current, target, start]: + continue + + # Calculate progress percentage + try: + current = float(current) + target = float(target) + start = float(start) + + if target == start: + progress_pct = 100 if current == target else 0 + else: + progress_pct = ((current - start) / (target - start)) * 100 + progress_pct = max(0, min(100, progress_pct)) + + g['_calc_progress_pct'] = int(progress_pct) + goals_with_progress.append(g) + except (ValueError, ZeroDivisionError): + continue + + # Filter goals with progress >= 50% + goals_on_track = [g for g in goals_with_progress if g.get('_calc_progress_pct', 0) >= 50] if not goals_on_track: return 'Keine Ziele auf gutem Weg' # Sort by progress descending (highest first) and take top N - sorted_goals = sorted(goals_on_track, key=lambda x: x.get('progress_pct', 0), reverse=True)[:n] + sorted_goals = sorted(goals_on_track, key=lambda x: x.get('_calc_progress_pct', 0), reverse=True)[:n] lines = [] for goal in sorted_goals: name = goal.get('name') or goal.get('goal_type', 'Unbekannt') - progress = goal.get('progress_pct', 0) + progress = goal.get('_calc_progress_pct', 0) lines.append(f"{name} ({progress}%)") return ', '.join(lines)