diff --git a/backend/placeholder_resolver.py b/backend/placeholder_resolver.py index e9359c3..aafec51 100644 --- a/backend/placeholder_resolver.py +++ b/backend/placeholder_resolver.py @@ -79,9 +79,19 @@ def get_nutrition_avg(profile_id: str, field: str, days: int = 30) -> str: with get_db() as conn: cur = get_cursor(conn) cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d') + + # Map field names to actual column names + field_map = { + 'protein': 'protein_g', + 'fat': 'fat_g', + 'carb': 'carbs_g', + 'kcal': 'kcal' + } + db_field = field_map.get(field, field) + cur.execute( - f"""SELECT AVG({field}) as avg FROM nutrition_log - WHERE profile_id=%s AND date >= %s AND {field} IS NOT NULL""", + f"""SELECT AVG({db_field}) as avg FROM nutrition_log + WHERE profile_id=%s AND date >= %s AND {db_field} IS NOT NULL""", (profile_id, cutoff) ) row = cur.fetchone() @@ -93,6 +103,105 @@ def get_nutrition_avg(profile_id: str, field: str, days: int = 30) -> str: return "nicht verfügbar" +def get_caliper_summary(profile_id: str) -> str: + """Get latest caliper measurements summary.""" + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + """SELECT bf_jpl, bf_katch, date FROM caliper_log + WHERE profile_id=%s AND bf_jpl IS NOT NULL + ORDER BY date DESC LIMIT 1""", + (profile_id,) + ) + row = r2d(cur.fetchone()) if cur.rowcount > 0 else None + + if not row: + return "keine Caliper-Messungen" + + return f"JPL: {row['bf_jpl']:.1f}% (Katch: {row['bf_katch']:.1f}% am {row['date']})" + + +def get_circ_summary(profile_id: str) -> str: + """Get latest circumference measurements summary.""" + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + """SELECT brust, taille, huefte, date FROM circumference_log + WHERE profile_id=%s + ORDER BY date DESC LIMIT 1""", + (profile_id,) + ) + row = r2d(cur.fetchone()) if cur.rowcount > 0 else None + + if not row: + return "keine Umfangsmessungen" + + parts = [] + if row.get('brust'): parts.append(f"Brust {row['brust']}cm") + if row.get('taille'): parts.append(f"Taille {row['taille']}cm") + if row.get('huefte'): parts.append(f"Hüfte {row['huefte']}cm") + + return f"{', '.join(parts)} ({row['date']})" if parts else "keine Daten" + + +def get_goal_weight(profile_id: str) -> str: + """Get goal weight from profile.""" + profile = get_profile_data(profile_id) + goal = profile.get('goal_weight') + return f"{goal:.1f}" if goal else "nicht gesetzt" + + +def get_goal_bf_pct(profile_id: str) -> str: + """Get goal body fat percentage from profile.""" + profile = get_profile_data(profile_id) + goal = profile.get('goal_bf_pct') + return f"{goal:.1f}" if goal else "nicht gesetzt" + + +def get_nutrition_days(profile_id: str, days: int = 30) -> str: + """Get number of days with nutrition data.""" + with get_db() as conn: + cur = get_cursor(conn) + cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d') + cur.execute( + """SELECT COUNT(DISTINCT date) as days FROM nutrition_log + WHERE profile_id=%s AND date >= %s""", + (profile_id, cutoff) + ) + row = cur.fetchone() + return str(row['days']) if row else "0" + + +def get_protein_ziel_low(profile_id: str) -> str: + """Calculate lower protein target based on current weight (1.6g/kg).""" + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + """SELECT weight FROM weight_log + WHERE profile_id=%s ORDER BY date DESC LIMIT 1""", + (profile_id,) + ) + row = cur.fetchone() + if row: + return f"{int(row['weight'] * 1.6)}" + return "nicht verfügbar" + + +def get_protein_ziel_high(profile_id: str) -> str: + """Calculate upper protein target based on current weight (2.2g/kg).""" + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + """SELECT weight FROM weight_log + WHERE profile_id=%s ORDER BY date DESC LIMIT 1""", + (profile_id,) + ) + row = cur.fetchone() + if row: + return f"{int(row['weight'] * 2.2)}" + return "nicht verfügbar" + + def get_activity_summary(profile_id: str, days: int = 14) -> str: """Get activity summary for recent period.""" with get_db() as conn: @@ -194,12 +303,19 @@ PLACEHOLDER_MAP: Dict[str, Callable[[str], str]] = { '{{weight_trend}}': get_weight_trend, '{{kf_aktuell}}': get_latest_bf, '{{bmi}}': lambda pid: calculate_bmi(pid), + '{{caliper_summary}}': get_caliper_summary, + '{{circ_summary}}': get_circ_summary, + '{{goal_weight}}': get_goal_weight, + '{{goal_bf_pct}}': get_goal_bf_pct, # Ernährung '{{kcal_avg}}': lambda pid: get_nutrition_avg(pid, 'kcal', 30), '{{protein_avg}}': lambda pid: get_nutrition_avg(pid, 'protein', 30), '{{carb_avg}}': lambda pid: get_nutrition_avg(pid, 'carb', 30), '{{fat_avg}}': lambda pid: get_nutrition_avg(pid, 'fat', 30), + '{{nutrition_days}}': lambda pid: get_nutrition_days(pid, 30), + '{{protein_ziel_low}}': get_protein_ziel_low, + '{{protein_ziel_high}}': get_protein_ziel_high, # Training '{{activity_summary}}': get_activity_summary,