feat: Migration 031 - Focus Area System v2.0 (dynamic, extensible)
This commit is contained in:
parent
0a1da37197
commit
2f64656d4d
254
backend/migrations/031_focus_area_system_v2.sql
Normal file
254
backend/migrations/031_focus_area_system_v2.sql
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
-- 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.';
|
||||||
Loading…
Reference in New Issue
Block a user