feat: hybrid goal tracking - with/without target_date
Implements requested hybrid approach:
WITH target_date:
- Time-based deviation (actual vs. expected progress)
- Format: 'Zielgewicht (41%, +7% voraus)'
WITHOUT target_date:
- Simple progress percentage
- Format: 'Ruhepuls (100% erreicht)' or 'VO2max (0% erreicht)'
Sorting:
behind_schedule:
1. Goals with negative deviation (behind timeline)
2. Goals without date with progress < 50%
on_track:
1. Goals with positive deviation (ahead of timeline)
2. Goals without date with progress >= 50%
Kept debug logging for new hybrid logic validation.
This commit is contained in:
parent
0e89850df8
commit
dd395180a3
|
|
@ -898,25 +898,82 @@ def _format_goals_behind(profile_id: str, n: int = 3) -> str:
|
||||||
print(f"[DEBUG] → Exception: {type(e).__name__}: {e}")
|
print(f"[DEBUG] → Exception: {type(e).__name__}: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not goals_with_deviation:
|
# Also process goals WITHOUT target_date (simple progress)
|
||||||
return 'Keine Ziele mit Zeitvorgabe'
|
goals_without_date = []
|
||||||
|
print(f"[DEBUG] Processing goals without target_date for simple progress")
|
||||||
|
|
||||||
# Sort by deviation ascending (most negative first = most behind)
|
for g in goals:
|
||||||
# Only include goals that are actually behind (deviation < 0)
|
if g.get('target_date'):
|
||||||
behind_goals = [g for g in goals_with_deviation if g['_deviation'] < 0]
|
continue # Already processed above
|
||||||
|
|
||||||
if not behind_goals:
|
goal_name = g.get('name') or g.get('goal_type', 'Unknown')
|
||||||
return 'Alle Ziele im Zeitplan'
|
current = g.get('current_value')
|
||||||
|
target = g.get('target_value')
|
||||||
|
start = g.get('start_value')
|
||||||
|
|
||||||
sorted_goals = sorted(behind_goals, key=lambda x: x['_deviation'])[:n]
|
if None in [current, target, start]:
|
||||||
|
print(f"[DEBUG] Goal '{goal_name}' (no date): Skipped - missing values")
|
||||||
|
continue
|
||||||
|
|
||||||
|
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['_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 = []
|
||||||
|
for g in behind_with_date:
|
||||||
|
combined.append({
|
||||||
|
'goal': g,
|
||||||
|
'sort_key': g['_deviation'], # Negative deviation (worst first)
|
||||||
|
'has_date': True
|
||||||
|
})
|
||||||
|
for g in behind_without_date:
|
||||||
|
# Map progress to deviation-like scale: 0% = -100, 50% = -50
|
||||||
|
combined.append({
|
||||||
|
'goal': g,
|
||||||
|
'sort_key': g['_simple_progress'] - 100, # Convert to negative scale
|
||||||
|
'has_date': False
|
||||||
|
})
|
||||||
|
|
||||||
|
if not combined:
|
||||||
|
return 'Alle Ziele im Zeitplan oder erreicht'
|
||||||
|
|
||||||
|
# Sort by sort_key (most negative first)
|
||||||
|
sorted_combined = sorted(combined, key=lambda x: x['sort_key'])[:n]
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
for goal in sorted_goals:
|
for item in sorted_combined:
|
||||||
name = goal.get('name') or goal.get('goal_type', 'Unbekannt')
|
g = item['goal']
|
||||||
actual = goal['_actual_progress']
|
name = g.get('name') or g.get('goal_type', 'Unbekannt')
|
||||||
expected = goal['_expected_progress']
|
|
||||||
deviation = goal['_deviation']
|
if item['has_date']:
|
||||||
lines.append(f"{name} ({actual}% statt {expected}%, {deviation}%)")
|
actual = g['_actual_progress']
|
||||||
|
expected = g['_expected_progress']
|
||||||
|
deviation = g['_deviation']
|
||||||
|
lines.append(f"{name} ({actual}% statt {expected}%, {deviation}%)")
|
||||||
|
else:
|
||||||
|
progress = g['_simple_progress']
|
||||||
|
lines.append(f"{name} ({progress}% erreicht)")
|
||||||
|
|
||||||
return ', '.join(lines)
|
return ', '.join(lines)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -1014,25 +1071,81 @@ def _format_goals_on_track(profile_id: str, n: int = 3) -> str:
|
||||||
print(f"[DEBUG] → Exception: {type(e).__name__}: {e}")
|
print(f"[DEBUG] → Exception: {type(e).__name__}: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not goals_with_deviation:
|
# Also process goals WITHOUT target_date (simple progress)
|
||||||
return 'Keine Ziele mit Zeitvorgabe'
|
goals_without_date = []
|
||||||
|
print(f"[DEBUG] Processing goals without target_date for simple progress")
|
||||||
|
|
||||||
# Sort by deviation descending (most positive first = most ahead)
|
for g in goals:
|
||||||
# Only include goals that are ahead or on track (deviation >= 0)
|
if g.get('target_date'):
|
||||||
ahead_goals = [g for g in goals_with_deviation if g['_deviation'] >= 0]
|
continue # Already processed above
|
||||||
|
|
||||||
if not ahead_goals:
|
goal_name = g.get('name') or g.get('goal_type', 'Unknown')
|
||||||
|
current = g.get('current_value')
|
||||||
|
target = g.get('target_value')
|
||||||
|
start = g.get('start_value')
|
||||||
|
|
||||||
|
if None in [current, target, start]:
|
||||||
|
print(f"[DEBUG] Goal '{goal_name}' (no date): Skipped - missing values")
|
||||||
|
continue
|
||||||
|
|
||||||
|
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['_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 = []
|
||||||
|
for g in ahead_with_date:
|
||||||
|
combined.append({
|
||||||
|
'goal': g,
|
||||||
|
'sort_key': g['_deviation'], # Positive deviation (best first)
|
||||||
|
'has_date': True
|
||||||
|
})
|
||||||
|
for g in ahead_without_date:
|
||||||
|
# Map progress to deviation-like scale: 50% = 0, 100% = +50
|
||||||
|
combined.append({
|
||||||
|
'goal': g,
|
||||||
|
'sort_key': g['_simple_progress'] - 50, # Convert to positive scale
|
||||||
|
'has_date': False
|
||||||
|
})
|
||||||
|
|
||||||
|
if not combined:
|
||||||
return 'Keine Ziele im Zeitplan'
|
return 'Keine Ziele im Zeitplan'
|
||||||
|
|
||||||
sorted_goals = sorted(ahead_goals, key=lambda x: x['_deviation'], reverse=True)[:n]
|
# Sort by sort_key descending (most positive first)
|
||||||
|
sorted_combined = sorted(combined, key=lambda x: x['sort_key'], reverse=True)[:n]
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
for goal in sorted_goals:
|
for item in sorted_combined:
|
||||||
name = goal.get('name') or goal.get('goal_type', 'Unbekannt')
|
g = item['goal']
|
||||||
actual = goal['_actual_progress']
|
name = g.get('name') or g.get('goal_type', 'Unbekannt')
|
||||||
expected = goal['_expected_progress']
|
|
||||||
deviation = goal['_deviation']
|
if item['has_date']:
|
||||||
lines.append(f"{name} ({actual}%, +{deviation}% voraus)")
|
actual = g['_actual_progress']
|
||||||
|
deviation = g['_deviation']
|
||||||
|
lines.append(f"{name} ({actual}%, +{deviation}% voraus)")
|
||||||
|
else:
|
||||||
|
progress = g['_simple_progress']
|
||||||
|
lines.append(f"{name} ({progress}% erreicht)")
|
||||||
|
|
||||||
return ', '.join(lines)
|
return ', '.join(lines)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user