fix: Phase 0b - body_progress_score + placeholder formatting
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 14s

Fixed remaining placeholder calculation issues:

1. body_progress_score returning 0:
   - When start_value is NULL, query oldest weight from last 90 days
   - Prevents progress = 0% when start equals current

2. focus_areas_weighted_json empty:
   - Changed from goal_utils.get_focus_weights_v2() to scores.get_user_focus_weights()
   - Now uses same function as focus_area_weights_json

3. Implemented 5 TODO markdown formatting functions:
   - _format_goals_as_markdown() - table with progress bars
   - _format_focus_areas_as_markdown() - weighted list
   - _format_top_focus_areas() - top N by weight
   - _format_goals_behind() - lowest progress goals
   - _format_goals_on_track() - goals >= 50% progress

All placeholders should now return proper values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-28 12:34:24 +01:00
parent b09a7b200a
commit 8da577fe58
2 changed files with 130 additions and 14 deletions

View File

@ -395,11 +395,26 @@ def _score_weight_trend(profile_id: str) -> Optional[int]:
current = goal.get('current_value')
target = goal.get('target_value')
start = goal.get('start_value', current)
start = goal.get('start_value')
if None in [current, target, start]:
if None in [current, target]:
return None
# If no start_value, use oldest weight in last 90 days
if start is None:
with get_db() as conn:
cur = get_cursor(conn)
cur.execute("""
SELECT weight
FROM weight_log
WHERE profile_id = %s
AND date >= CURRENT_DATE - INTERVAL '90 days'
ORDER BY date ASC
LIMIT 1
""", (profile_id,))
row = cur.fetchone()
start = float(row['weight']) if row else current
# Progress percentage
progress_pct = calculate_goal_progress_pct(current, target, start)

View File

@ -677,9 +677,10 @@ def _get_focus_areas_weighted_json(profile_id: str) -> str:
"""Get focus areas with weights as JSON string"""
import json
try:
from goal_utils import get_focus_weights_v2, get_db, get_cursor
from calculations.scores import get_user_focus_weights
from goal_utils import get_db, get_cursor
weights = get_focus_weights_v2(get_db(), profile_id)
weights = get_user_focus_weights(profile_id)
# Get focus area details
with get_db() as conn:
@ -713,31 +714,131 @@ def _get_focus_areas_weighted_json(profile_id: str) -> str:
def _format_goals_as_markdown(profile_id: str) -> str:
"""Format goals as markdown table"""
# TODO: Implement
try:
from goal_utils import get_active_goals
goals = get_active_goals(profile_id)
if not goals:
return 'Keine Ziele definiert'
# Build markdown table
lines = ['| Ziel | Aktuell | Ziel | Progress | Status |', '|------|---------|------|----------|--------|']
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 '-'
status = '🎯' if goal.get('is_primary') else ''
lines.append(f"| {name} | {current} | {target} | {progress} | {status} |")
return '\n'.join(lines)
except Exception:
return 'Keine Ziele definiert'
def _format_focus_areas_as_markdown(profile_id: str) -> str:
"""Format focus areas as markdown"""
# TODO: Implement
try:
import json
weighted_json = _get_focus_areas_weighted_json(profile_id)
areas = json.loads(weighted_json)
if not areas:
return 'Keine Focus Areas aktiv'
# Build markdown list
lines = []
for area in areas:
name = area.get('name', 'Unbekannt')
weight = area.get('weight', 0)
lines.append(f"- **{name}**: {weight}%")
return '\n'.join(lines)
except Exception:
return 'Keine Focus Areas aktiv'
def _format_top_focus_areas(profile_id: str, n: int = 3) -> str:
"""Format top N focus areas as text"""
# TODO: Implement
try:
import json
weighted_json = _get_focus_areas_weighted_json(profile_id)
areas = json.loads(weighted_json)
if not areas:
return 'Keine Focus Areas definiert'
# Sort by weight descending and take top N
sorted_areas = sorted(areas, key=lambda x: x.get('weight', 0), reverse=True)[:n]
lines = []
for i, area in enumerate(sorted_areas, 1):
name = area.get('name', 'Unbekannt')
weight = area.get('weight', 0)
lines.append(f"{i}. {name} ({weight}%)")
return ', '.join(lines)
except Exception:
return 'nicht verfügbar'
def _format_goals_behind(profile_id: str, n: int = 3) -> str:
"""Format top N goals behind schedule"""
# TODO: Implement
try:
from goal_utils import get_active_goals
goals = get_active_goals(profile_id)
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]
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]
lines = []
for goal in sorted_goals:
name = goal.get('name') or goal.get('goal_type', 'Unbekannt')
progress = goal.get('progress_pct', 0)
lines.append(f"{name} ({progress}%)")
return ', '.join(lines)
except Exception:
return 'nicht verfügbar'
def _format_goals_on_track(profile_id: str, n: int = 3) -> str:
"""Format top N goals on track"""
# TODO: Implement
try:
from goal_utils import get_active_goals
goals = get_active_goals(profile_id)
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]
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]
lines = []
for goal in sorted_goals:
name = goal.get('name') or goal.get('goal_type', 'Unbekannt')
progress = goal.get('progress_pct', 0)
lines.append(f"{name} ({progress}%)")
return ', '.join(lines)
except Exception:
return 'nicht verfügbar'