""" Goal Utilities - Abstraction Layer for Focus Weights This module provides an abstraction layer between goal modes and focus weights. This allows Phase 0b placeholders to work with the current simple goal_mode system, while enabling future v2.0 redesign (focus_areas table) without rewriting 120+ placeholders. Version History: - V1 (current): Maps goal_mode to predefined weights - V2 (future): Reads from focus_areas table with custom user weights Part of Phase 1: Quick Fixes + Abstraction Layer """ from typing import Dict from db import get_cursor def get_focus_weights(conn, profile_id: str) -> Dict[str, float]: """ Get focus area weights for a profile. This is an abstraction layer that will evolve: - V1 (now): Maps goal_mode → predefined weights - V2 (later): Reads from focus_areas table → custom user weights Args: conn: Database connection profile_id: User's profile ID Returns: Dict with focus weights (sum = 1.0): { 'weight_loss': 0.3, # Fat loss priority 'muscle_gain': 0.2, # Muscle gain priority 'strength': 0.25, # Strength training priority 'endurance': 0.25, # Cardio/endurance priority 'flexibility': 0.0, # Mobility priority 'health': 0.0 # General health maintenance } Example Usage in Phase 0b: weights = get_focus_weights(conn, profile_id) # Score calculation considers user's focus overall_score = ( body_score * weights['weight_loss'] + strength_score * weights['strength'] + cardio_score * weights['endurance'] ) """ cur = get_cursor(conn) # Fetch current goal_mode cur.execute( "SELECT goal_mode FROM profiles WHERE id = %s", (profile_id,) ) row = cur.fetchone() if not row: # Fallback: balanced health focus return { 'weight_loss': 0.0, 'muscle_gain': 0.0, 'strength': 0.0, 'endurance': 0.0, 'flexibility': 0.0, 'health': 1.0 } goal_mode = row['goal_mode'] or 'health' # V1: Predefined weight mappings per goal_mode # These represent "typical" focus distributions for each mode WEIGHT_MAPPINGS = { 'weight_loss': { 'weight_loss': 0.60, # Primary: fat loss 'endurance': 0.20, # Support: cardio for calorie burn 'muscle_gain': 0.0, # Not compatible 'strength': 0.10, # Maintain muscle during deficit 'flexibility': 0.05, # Minor: mobility work 'health': 0.05 # Minor: general wellness }, 'strength': { 'strength': 0.50, # Primary: strength gains 'muscle_gain': 0.40, # Support: hypertrophy 'endurance': 0.10, # Minor: work capacity 'weight_loss': 0.0, # Not compatible with strength focus 'flexibility': 0.0, 'health': 0.0 }, 'endurance': { 'endurance': 0.70, # Primary: aerobic capacity 'health': 0.20, # Support: cardiovascular health 'flexibility': 0.10, # Support: mobility for running 'weight_loss': 0.0, 'muscle_gain': 0.0, 'strength': 0.0 }, 'recomposition': { 'weight_loss': 0.30, # Equal: lose fat 'muscle_gain': 0.30, # Equal: gain muscle 'strength': 0.25, # Support: progressive overload 'endurance': 0.10, # Minor: conditioning 'flexibility': 0.05, # Minor: mobility 'health': 0.0 }, 'health': { 'health': 0.50, # Primary: general wellness 'endurance': 0.20, # Support: cardio health 'flexibility': 0.15, # Support: mobility 'strength': 0.10, # Support: functional strength 'weight_loss': 0.05, # Minor: maintain healthy weight 'muscle_gain': 0.0 } } return WEIGHT_MAPPINGS.get(goal_mode, WEIGHT_MAPPINGS['health']) def get_primary_focus(conn, profile_id: str) -> str: """ Get the primary focus area for a profile. Returns the focus area with the highest weight. Useful for UI labels and simple decision logic. Args: conn: Database connection profile_id: User's profile ID Returns: Primary focus area name (e.g., 'weight_loss', 'strength') """ weights = get_focus_weights(conn, profile_id) return max(weights.items(), key=lambda x: x[1])[0] def get_focus_description(focus_area: str) -> str: """ Get human-readable description for a focus area. Args: focus_area: Focus area key (e.g., 'weight_loss') Returns: German description for UI display """ descriptions = { 'weight_loss': 'Gewichtsreduktion & Fettabbau', 'muscle_gain': 'Muskelaufbau & Hypertrophie', 'strength': 'Kraftsteigerung & Performance', 'endurance': 'Ausdauer & aerobe Kapazität', 'flexibility': 'Beweglichkeit & Mobilität', 'health': 'Allgemeine Gesundheit & Erhaltung' } return descriptions.get(focus_area, focus_area) # Future V2 Implementation (commented out for reference): """ def get_focus_weights_v2(conn, profile_id: str) -> Dict[str, float]: '''V2: Read from focus_areas table with custom user weights''' cur = get_cursor(conn) cur.execute(''' SELECT weight_loss_pct, muscle_gain_pct, endurance_pct, strength_pct, flexibility_pct, health_pct FROM focus_areas WHERE profile_id = %s AND active = true LIMIT 1 ''', (profile_id,)) row = cur.fetchone() if not row: # Fallback to V1 behavior return get_focus_weights(conn, profile_id) # Convert percentages to weights (0-1 range) return { 'weight_loss': row['weight_loss_pct'] / 100.0, 'muscle_gain': row['muscle_gain_pct'] / 100.0, 'endurance': row['endurance_pct'] / 100.0, 'strength': row['strength_pct'] / 100.0, 'flexibility': row['flexibility_pct'] / 100.0, 'health': row['health_pct'] / 100.0 } """