-- Migration 031: Focus Area System v2.0 -- Date: 2026-03-27 -- Purpose: Dynamic, extensible focus areas with Many-to-Many goal contributions -- ============================================================================ -- Part 1: New Tables -- ============================================================================ -- Focus Area Definitions (dynamic, user-extensible) CREATE TABLE IF NOT EXISTS focus_area_definitions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), key VARCHAR(50) UNIQUE NOT NULL, -- e.g. 'strength', 'aerobic_endurance' name_de VARCHAR(100) NOT NULL, name_en VARCHAR(100), icon VARCHAR(10), description TEXT, category VARCHAR(50), -- 'body_composition', 'training', 'endurance', 'coordination', 'mental', 'recovery', 'health' is_active BOOLEAN DEFAULT true, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_focus_area_key ON focus_area_definitions(key); CREATE INDEX idx_focus_area_category ON focus_area_definitions(category); COMMENT ON TABLE focus_area_definitions IS 'Dynamic focus area registry - defines all available focus dimensions'; COMMENT ON COLUMN focus_area_definitions.key IS 'Unique identifier for programmatic access'; COMMENT ON COLUMN focus_area_definitions.category IS 'Grouping for UI display'; -- Many-to-Many: Goals contribute to Focus Areas CREATE TABLE IF NOT EXISTS goal_focus_contributions ( goal_id UUID NOT NULL REFERENCES goals(id) ON DELETE CASCADE, focus_area_id UUID NOT NULL REFERENCES focus_area_definitions(id) ON DELETE CASCADE, contribution_weight DECIMAL(5,2) DEFAULT 100.00 CHECK (contribution_weight >= 0 AND contribution_weight <= 100), created_at TIMESTAMP DEFAULT NOW(), PRIMARY KEY (goal_id, focus_area_id) ); CREATE INDEX idx_gfc_goal ON goal_focus_contributions(goal_id); CREATE INDEX idx_gfc_focus_area ON goal_focus_contributions(focus_area_id); COMMENT ON TABLE goal_focus_contributions IS 'Maps goals to focus areas with contribution weights (0-100%)'; COMMENT ON COLUMN goal_focus_contributions.contribution_weight IS 'How much this goal contributes to the focus area (0-100%)'; -- ============================================================================ -- Part 2: Rename existing focus_areas table -- ============================================================================ -- Old focus_areas table becomes user_focus_preferences ALTER TABLE focus_areas RENAME TO user_focus_preferences; -- Add reference to new focus_area_definitions (for future use) ALTER TABLE user_focus_preferences ADD COLUMN IF NOT EXISTS notes TEXT; COMMENT ON TABLE user_focus_preferences IS 'User-specific focus area weightings (legacy flat structure + new references)'; -- ============================================================================ -- Part 3: Seed Data - Basis Focus Areas -- ============================================================================ INSERT INTO focus_area_definitions (key, name_de, name_en, icon, category, description) VALUES -- Body Composition ('weight_loss', 'Gewichtsverlust', 'Weight Loss', '📉', 'body_composition', 'Körpergewicht reduzieren'), ('muscle_gain', 'Muskelaufbau', 'Muscle Gain', '💪', 'body_composition', 'Muskelmasse aufbauen'), ('body_recomposition', 'Body Recomposition', 'Body Recomposition', '⚖️', 'body_composition', 'Gleichzeitig Fett abbauen und Muskeln aufbauen'), -- Training - Kraft ('strength', 'Maximalkraft', 'Strength', '🏋️', 'training', 'Maximale Kraftfähigkeit'), ('strength_endurance', 'Kraftausdauer', 'Strength Endurance', '💪🏃', 'training', 'Kraft über längere Zeit aufrechterhalten'), ('power', 'Schnellkraft', 'Power', '⚡', 'training', 'Kraft in kurzer Zeit entfalten'), -- Training - Beweglichkeit ('flexibility', 'Beweglichkeit', 'Flexibility', '🤸', 'training', 'Gelenkigkeit und Bewegungsumfang'), ('mobility', 'Mobilität', 'Mobility', '🦴', 'training', 'Aktive Beweglichkeit und Kontrolle'), -- Ausdauer ('aerobic_endurance', 'Aerobe Ausdauer', 'Aerobic Endurance', '🫁', 'endurance', 'VO2Max, lange moderate Belastung'), ('anaerobic_endurance', 'Anaerobe Ausdauer', 'Anaerobic Endurance', '⚡', 'endurance', 'Laktattoleranz, kurze intensive Belastung'), ('cardiovascular_health', 'Herz-Kreislauf', 'Cardiovascular Health', '❤️', 'endurance', 'Herzgesundheit und Ausdauer'), -- Koordination ('balance', 'Gleichgewicht', 'Balance', '⚖️', 'coordination', 'Statisches und dynamisches Gleichgewicht'), ('reaction', 'Reaktionsfähigkeit', 'Reaction', '⚡', 'coordination', 'Schnelligkeit der Reaktion auf Reize'), ('rhythm', 'Rhythmusgefühl', 'Rhythm', '🎵', 'coordination', 'Zeitliche Abstimmung von Bewegungen'), ('coordination', 'Koordination', 'Coordination', '🎯', 'coordination', 'Zusammenspiel verschiedener Bewegungen'), -- Mental ('stress_resistance', 'Stressresistenz', 'Stress Resistance', '🧘', 'mental', 'Umgang mit mentalem und physischem Stress'), ('concentration', 'Konzentration', 'Concentration', '🎯', 'mental', 'Fokussierung und Aufmerksamkeit'), ('willpower', 'Willenskraft', 'Willpower', '💎', 'mental', 'Durchhaltevermögen und Selbstdisziplin'), ('mental_health', 'Mentale Gesundheit', 'Mental Health', '🧠', 'mental', 'Psychisches Wohlbefinden'), -- Recovery ('sleep_quality', 'Schlafqualität', 'Sleep Quality', '😴', 'recovery', 'Erholsamer Schlaf'), ('regeneration', 'Regeneration', 'Regeneration', '♻️', 'recovery', 'Körperliche Erholung'), ('rest', 'Ruhe', 'Rest', '🛌', 'recovery', 'Aktive und passive Erholung'), -- Health ('metabolic_health', 'Stoffwechselgesundheit', 'Metabolic Health', '🔥', 'health', 'Blutzucker, Insulin, Stoffwechsel'), ('blood_pressure', 'Blutdruck', 'Blood Pressure', '❤️‍🩹', 'health', 'Gesunder Blutdruck'), ('hrv', 'Herzratenvariabilität', 'HRV', '💓', 'health', 'Autonomes Nervensystem'), ('general_health', 'Allgemeine Gesundheit', 'General Health', '🏥', 'health', 'Vitale Gesundheit und Wohlbefinden') ON CONFLICT (key) DO NOTHING; -- ============================================================================ -- Part 4: Auto-Mapping - Bestehende Goals zu Focus Areas -- ============================================================================ -- Helper function to get focus_area_id by key CREATE OR REPLACE FUNCTION get_focus_area_id(area_key VARCHAR) RETURNS UUID AS $$ BEGIN RETURN (SELECT id FROM focus_area_definitions WHERE key = area_key LIMIT 1); END; $$ LANGUAGE plpgsql; -- Weight goals → weight_loss (100%) INSERT INTO goal_focus_contributions (goal_id, focus_area_id, contribution_weight) SELECT g.id, get_focus_area_id('weight_loss'), 100.00 FROM goals g WHERE g.goal_type = 'weight' ON CONFLICT (goal_id, focus_area_id) DO NOTHING; -- Body Fat goals → weight_loss (60%) + body_recomposition (40%) INSERT INTO goal_focus_contributions (goal_id, focus_area_id, contribution_weight) SELECT g.id, fa.id, CASE fa.key WHEN 'weight_loss' THEN 60.00 WHEN 'body_recomposition' THEN 40.00 END FROM goals g CROSS JOIN focus_area_definitions fa WHERE g.goal_type = 'body_fat' AND fa.key IN ('weight_loss', 'body_recomposition') ON CONFLICT (goal_id, focus_area_id) DO NOTHING; -- Lean Mass goals → muscle_gain (70%) + body_recomposition (30%) INSERT INTO goal_focus_contributions (goal_id, focus_area_id, contribution_weight) SELECT g.id, fa.id, CASE fa.key WHEN 'muscle_gain' THEN 70.00 WHEN 'body_recomposition' THEN 30.00 END FROM goals g CROSS JOIN focus_area_definitions fa WHERE g.goal_type = 'lean_mass' AND fa.key IN ('muscle_gain', 'body_recomposition') ON CONFLICT (goal_id, focus_area_id) DO NOTHING; -- Strength goals → strength (70%) + muscle_gain (30%) INSERT INTO goal_focus_contributions (goal_id, focus_area_id, contribution_weight) SELECT g.id, fa.id, CASE fa.key WHEN 'strength' THEN 70.00 WHEN 'muscle_gain' THEN 30.00 END FROM goals g CROSS JOIN focus_area_definitions fa WHERE g.goal_type = 'strength' AND fa.key IN ('strength', 'muscle_gain') ON CONFLICT (goal_id, focus_area_id) DO NOTHING; -- Flexibility goals → flexibility (100%) INSERT INTO goal_focus_contributions (goal_id, focus_area_id, contribution_weight) SELECT g.id, get_focus_area_id('flexibility'), 100.00 FROM goals g WHERE g.goal_type = 'flexibility' ON CONFLICT (goal_id, focus_area_id) DO NOTHING; -- VO2Max goals → aerobic_endurance (80%) + cardiovascular_health (20%) INSERT INTO goal_focus_contributions (goal_id, focus_area_id, contribution_weight) SELECT g.id, fa.id, CASE fa.key WHEN 'aerobic_endurance' THEN 80.00 WHEN 'cardiovascular_health' THEN 20.00 END FROM goals g CROSS JOIN focus_area_definitions fa WHERE g.goal_type = 'vo2max' AND fa.key IN ('aerobic_endurance', 'cardiovascular_health') ON CONFLICT (goal_id, focus_area_id) DO NOTHING; -- Resting Heart Rate goals → cardiovascular_health (100%) INSERT INTO goal_focus_contributions (goal_id, focus_area_id, contribution_weight) SELECT g.id, get_focus_area_id('cardiovascular_health'), 100.00 FROM goals g WHERE g.goal_type = 'rhr' ON CONFLICT (goal_id, focus_area_id) DO NOTHING; -- Blood Pressure goals → blood_pressure (80%) + cardiovascular_health (20%) INSERT INTO goal_focus_contributions (goal_id, focus_area_id, contribution_weight) SELECT g.id, fa.id, CASE fa.key WHEN 'blood_pressure' THEN 80.00 WHEN 'cardiovascular_health' THEN 20.00 END FROM goals g CROSS JOIN focus_area_definitions fa WHERE g.goal_type = 'bp' AND fa.key IN ('blood_pressure', 'cardiovascular_health') ON CONFLICT (goal_id, focus_area_id) DO NOTHING; -- HRV goals → hrv (70%) + stress_resistance (30%) INSERT INTO goal_focus_contributions (goal_id, focus_area_id, contribution_weight) SELECT g.id, fa.id, CASE fa.key WHEN 'hrv' THEN 70.00 WHEN 'stress_resistance' THEN 30.00 END FROM goals g CROSS JOIN focus_area_definitions fa WHERE g.goal_type = 'hrv' AND fa.key IN ('hrv', 'stress_resistance') ON CONFLICT (goal_id, focus_area_id) DO NOTHING; -- Sleep Quality goals → sleep_quality (100%) INSERT INTO goal_focus_contributions (goal_id, focus_area_id, contribution_weight) SELECT g.id, get_focus_area_id('sleep_quality'), 100.00 FROM goals g WHERE g.goal_type = 'sleep_quality' ON CONFLICT (goal_id, focus_area_id) DO NOTHING; -- Training Frequency goals → general catch-all (strength + endurance) INSERT INTO goal_focus_contributions (goal_id, focus_area_id, contribution_weight) SELECT g.id, fa.id, CASE fa.key WHEN 'strength' THEN 40.00 WHEN 'aerobic_endurance' THEN 40.00 WHEN 'general_health' THEN 20.00 END FROM goals g CROSS JOIN focus_area_definitions fa WHERE g.goal_type = 'training_frequency' AND fa.key IN ('strength', 'aerobic_endurance', 'general_health') ON CONFLICT (goal_id, focus_area_id) DO NOTHING; -- Cleanup helper function DROP FUNCTION IF EXISTS get_focus_area_id(VARCHAR); -- ============================================================================ -- Summary -- ============================================================================ COMMENT ON TABLE focus_area_definitions IS 'v2.0: Dynamic focus areas - replaces hardcoded 6-dimension system. 26 base areas across 7 categories. User-extensible via admin UI.'; COMMENT ON TABLE goal_focus_contributions IS 'Many-to-Many mapping: Goals contribute to multiple focus areas with weights. Auto-mapped from goal_type, editable by user.'; COMMENT ON TABLE user_focus_preferences IS 'Legacy flat structure (weight_loss_pct, muscle_gain_pct, etc.) remains for backward compatibility. Future: Use focus_area_definitions + dynamic preferences.';