mitai-jinkendo/backend/migrations/027_focus_areas_system.sql
Lars 4a11d20c4d
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 14s
feat: Goal System v2.0 - Focus Areas with weighted priorities
BREAKING: Replaces single 'primary goal' with weighted multi-goal system

Migration 027:
- New table: focus_areas (6 dimensions with percentages)
- Constraint: Sum must equal 100%
- Auto-migration: goal_mode → focus_areas for existing users
- Unique constraint: One active focus_areas per profile

Backend:
- get_focus_weights() V2: Reads from focus_areas table
- Fallback: Uses goal_mode if focus_areas not set
- New endpoints: GET/PUT /api/goals/focus-areas
- Validation: Sum=100, range 0-100

API:
- getFocusAreas() - Get current weights
- updateFocusAreas(data) - Update weights (upsert)

Focus dimensions:
1. weight_loss_pct   (Fettabbau)
2. muscle_gain_pct   (Muskelaufbau)
3. strength_pct      (Kraftsteigerung)
4. endurance_pct     (Ausdauer)
5. flexibility_pct   (Beweglichkeit)
6. health_pct        (Allgemeine Gesundheit)

Benefits:
- Multiple goals with custom priorities
- More flexible than single primary goal
- KI can use weighted scores
- Ready for Phase 0b placeholder integration

UI: Coming in next commit (slider interface)
2026-03-27 08:38:03 +01:00

135 lines
4.4 KiB
SQL

-- Migration 027: Focus Areas System (Goal System v2.0)
-- Date: 2026-03-27
-- Purpose: Replace single primary goal with weighted multi-goal system
-- ============================================================================
-- Focus Areas Table
-- ============================================================================
CREATE TABLE IF NOT EXISTS focus_areas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
-- Six focus dimensions (percentages, sum = 100)
weight_loss_pct INTEGER DEFAULT 0 CHECK (weight_loss_pct >= 0 AND weight_loss_pct <= 100),
muscle_gain_pct INTEGER DEFAULT 0 CHECK (muscle_gain_pct >= 0 AND muscle_gain_pct <= 100),
strength_pct INTEGER DEFAULT 0 CHECK (strength_pct >= 0 AND strength_pct <= 100),
endurance_pct INTEGER DEFAULT 0 CHECK (endurance_pct >= 0 AND endurance_pct <= 100),
flexibility_pct INTEGER DEFAULT 0 CHECK (flexibility_pct >= 0 AND flexibility_pct <= 100),
health_pct INTEGER DEFAULT 0 CHECK (health_pct >= 0 AND health_pct <= 100),
-- Status
active BOOLEAN DEFAULT true,
-- Audit
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
-- Constraints
CONSTRAINT sum_equals_100 CHECK (
weight_loss_pct + muscle_gain_pct + strength_pct +
endurance_pct + flexibility_pct + health_pct = 100
)
);
-- Only one active focus_areas per profile
CREATE UNIQUE INDEX IF NOT EXISTS idx_focus_areas_profile_active
ON focus_areas(profile_id) WHERE active = true;
COMMENT ON TABLE focus_areas IS 'User-defined focus area weights (replaces simple goal_mode). Enables multi-goal prioritization with custom percentages.';
COMMENT ON COLUMN focus_areas.weight_loss_pct IS 'Focus on fat loss (0-100%)';
COMMENT ON COLUMN focus_areas.muscle_gain_pct IS 'Focus on muscle growth (0-100%)';
COMMENT ON COLUMN focus_areas.strength_pct IS 'Focus on strength gains (0-100%)';
COMMENT ON COLUMN focus_areas.endurance_pct IS 'Focus on aerobic capacity (0-100%)';
COMMENT ON COLUMN focus_areas.flexibility_pct IS 'Focus on mobility/flexibility (0-100%)';
COMMENT ON COLUMN focus_areas.health_pct IS 'Focus on general health (0-100%)';
-- ============================================================================
-- Migrate existing goal_mode to focus_areas
-- ============================================================================
-- For each profile with a goal_mode, create initial focus_areas
INSERT INTO focus_areas (
profile_id,
weight_loss_pct, muscle_gain_pct, strength_pct,
endurance_pct, flexibility_pct, health_pct
)
SELECT
id AS profile_id,
CASE goal_mode
WHEN 'weight_loss' THEN 60 ELSE 0
END +
CASE goal_mode
WHEN 'recomposition' THEN 30 ELSE 0
END AS weight_loss_pct,
CASE goal_mode
WHEN 'strength' THEN 40 ELSE 0
END +
CASE goal_mode
WHEN 'recomposition' THEN 30 ELSE 0
END AS muscle_gain_pct,
CASE goal_mode
WHEN 'strength' THEN 50 ELSE 0
END +
CASE goal_mode
WHEN 'recomposition' THEN 25 ELSE 0
END +
CASE goal_mode
WHEN 'weight_loss' THEN 10 ELSE 0
END AS strength_pct,
CASE goal_mode
WHEN 'endurance' THEN 70 ELSE 0
END +
CASE goal_mode
WHEN 'recomposition' THEN 10 ELSE 0
END +
CASE goal_mode
WHEN 'weight_loss' THEN 20 ELSE 0
END AS endurance_pct,
CASE goal_mode
WHEN 'endurance' THEN 10 ELSE 0
END +
CASE goal_mode
WHEN 'health' THEN 15 ELSE 0
END +
CASE goal_mode
WHEN 'recomposition' THEN 5 ELSE 0
END +
CASE goal_mode
WHEN 'weight_loss' THEN 5 ELSE 0
END AS flexibility_pct,
CASE goal_mode
WHEN 'health' THEN 50 ELSE 0
END +
CASE goal_mode
WHEN 'endurance' THEN 20 ELSE 0
END +
CASE goal_mode
WHEN 'strength' THEN 10 ELSE 0
END +
CASE goal_mode
WHEN 'weight_loss' THEN 5 ELSE 0
END AS health_pct
FROM profiles
WHERE goal_mode IS NOT NULL
ON CONFLICT DO NOTHING;
-- For profiles without goal_mode, use balanced health focus
INSERT INTO focus_areas (
profile_id,
weight_loss_pct, muscle_gain_pct, strength_pct,
endurance_pct, flexibility_pct, health_pct
)
SELECT
id AS profile_id,
0, 0, 10, 20, 15, 55
FROM profiles
WHERE goal_mode IS NULL
AND id NOT IN (SELECT profile_id FROM focus_areas WHERE active = true)
ON CONFLICT DO NOTHING;