- Strategic Layer: Goal modes (weight_loss, strength, endurance, recomposition, health) - Tactical Layer: Concrete goal targets with progress tracking - Training phases (manual + auto-detection framework) - Fitness tests (standardized performance tracking) Backend: - Migration 022: goal_mode in profiles, goals, training_phases, fitness_tests tables - New router: routers/goals.py with full CRUD for goals, phases, tests - API endpoints: /api/goals/* (mode, list, create, update, delete) Frontend: - GoalsPage: Goal mode selector + goal management UI - Dashboard: Goals preview card with link - API integration: goal mode, CRUD operations, progress calculation Basis for 120+ placeholders and goal-aware analyses (Phase 0b) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
148 lines
6.9 KiB
SQL
148 lines
6.9 KiB
SQL
-- Migration 022: Goal System (Strategic + Tactical)
|
|
-- Date: 2026-03-26
|
|
-- Purpose: Two-level goal architecture for AI-driven coaching
|
|
|
|
-- ============================================================================
|
|
-- STRATEGIC LAYER: Goal Modes
|
|
-- ============================================================================
|
|
|
|
-- Add goal_mode to profiles (strategic training direction)
|
|
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS goal_mode VARCHAR(50) DEFAULT 'health';
|
|
|
|
COMMENT ON COLUMN profiles.goal_mode IS
|
|
'Strategic goal mode: weight_loss, strength, endurance, recomposition, health.
|
|
Determines score weights and interpretation context for all analyses.';
|
|
|
|
-- ============================================================================
|
|
-- TACTICAL LAYER: Concrete Goal Targets
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS goals (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
|
|
|
|
-- Goal Classification
|
|
goal_type VARCHAR(50) NOT NULL, -- weight, body_fat, lean_mass, vo2max, strength, flexibility, bp, rhr
|
|
is_primary BOOLEAN DEFAULT false,
|
|
status VARCHAR(20) DEFAULT 'active', -- draft, active, reached, abandoned, expired
|
|
|
|
-- Target Values
|
|
target_value DECIMAL(10,2),
|
|
current_value DECIMAL(10,2),
|
|
start_value DECIMAL(10,2),
|
|
unit VARCHAR(20), -- kg, %, ml/kg/min, bpm, mmHg, cm, reps
|
|
|
|
-- Timeline
|
|
start_date DATE DEFAULT CURRENT_DATE,
|
|
target_date DATE,
|
|
reached_date DATE,
|
|
|
|
-- Metadata
|
|
name VARCHAR(100), -- e.g., "Sommerfigur 2026"
|
|
description TEXT,
|
|
|
|
-- Progress Tracking
|
|
progress_pct DECIMAL(5,2), -- Auto-calculated: (current - start) / (target - start) * 100
|
|
projection_date DATE, -- Prognose wann Ziel erreicht wird
|
|
on_track BOOLEAN, -- true wenn Prognose <= target_date
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_goals_profile ON goals(profile_id);
|
|
CREATE INDEX IF NOT EXISTS idx_goals_status ON goals(profile_id, status);
|
|
CREATE INDEX IF NOT EXISTS idx_goals_primary ON goals(profile_id, is_primary) WHERE is_primary = true;
|
|
|
|
COMMENT ON TABLE goals IS 'Concrete user goals (tactical targets)';
|
|
COMMENT ON COLUMN goals.goal_type IS 'Type of goal: weight, body_fat, lean_mass, vo2max, strength, flexibility, bp, rhr';
|
|
COMMENT ON COLUMN goals.is_primary IS 'Primary goal gets highest priority in scoring and charts';
|
|
COMMENT ON COLUMN goals.status IS 'draft = not yet started, active = in progress, reached = successfully completed, abandoned = given up, expired = deadline passed';
|
|
COMMENT ON COLUMN goals.progress_pct IS 'Percentage progress: (current_value - start_value) / (target_value - start_value) * 100';
|
|
COMMENT ON COLUMN goals.projection_date IS 'Projected date when goal will be reached based on current trend';
|
|
COMMENT ON COLUMN goals.on_track IS 'true if projection_date <= target_date (goal reachable on time)';
|
|
|
|
-- ============================================================================
|
|
-- TRAINING PHASES (Auto-Detection)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS training_phases (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
|
|
|
|
-- Phase Classification
|
|
phase_type VARCHAR(50) NOT NULL, -- calorie_deficit, calorie_surplus, deload, maintenance, periodization
|
|
detected_automatically BOOLEAN DEFAULT false,
|
|
confidence_score DECIMAL(3,2), -- 0.00 - 1.00 (Wie sicher ist die Erkennung?)
|
|
status VARCHAR(20) DEFAULT 'suggested', -- suggested, accepted, active, completed, rejected
|
|
|
|
-- Timeframe
|
|
start_date DATE NOT NULL,
|
|
end_date DATE,
|
|
duration_days INT,
|
|
|
|
-- Detection Criteria (JSONB für Flexibilität)
|
|
detection_params JSONB, -- { "avg_calories": 1800, "weight_trend": -0.3, ... }
|
|
|
|
-- User Notes
|
|
notes TEXT,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_training_phases_profile ON training_phases(profile_id);
|
|
CREATE INDEX IF NOT EXISTS idx_training_phases_status ON training_phases(profile_id, status);
|
|
CREATE INDEX IF NOT EXISTS idx_training_phases_dates ON training_phases(profile_id, start_date, end_date);
|
|
|
|
COMMENT ON TABLE training_phases IS 'Training phases detected from data patterns or manually defined';
|
|
COMMENT ON COLUMN training_phases.phase_type IS 'calorie_deficit, calorie_surplus, deload, maintenance, periodization';
|
|
COMMENT ON COLUMN training_phases.detected_automatically IS 'true if AI detected this phase from data patterns';
|
|
COMMENT ON COLUMN training_phases.confidence_score IS 'AI confidence in detection (0.0 - 1.0)';
|
|
COMMENT ON COLUMN training_phases.status IS 'suggested = AI proposed, accepted = user confirmed, active = currently running, completed = finished, rejected = user dismissed';
|
|
COMMENT ON COLUMN training_phases.detection_params IS 'JSON with detection criteria: avg_calories, weight_trend, activity_volume, etc.';
|
|
|
|
-- ============================================================================
|
|
-- FITNESS TESTS (Standardized Performance Tests)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS fitness_tests (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
|
|
|
|
-- Test Type
|
|
test_type VARCHAR(50) NOT NULL, -- cooper_12min, step_test, pushups_max, plank_max, flexibility_sit_reach, vo2max_est, strength_1rm_squat, strength_1rm_bench
|
|
result_value DECIMAL(10,2) NOT NULL,
|
|
result_unit VARCHAR(20) NOT NULL, -- meters, bpm, reps, seconds, cm, ml/kg/min, kg
|
|
|
|
-- Test Metadata
|
|
test_date DATE NOT NULL,
|
|
test_conditions TEXT, -- Optional: Notizen zu Bedingungen
|
|
norm_category VARCHAR(30), -- sehr gut, gut, durchschnitt, unterdurchschnitt, schlecht
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_fitness_tests_profile ON fitness_tests(profile_id);
|
|
CREATE INDEX IF NOT EXISTS idx_fitness_tests_type ON fitness_tests(profile_id, test_type);
|
|
CREATE INDEX IF NOT EXISTS idx_fitness_tests_date ON fitness_tests(profile_id, test_date);
|
|
|
|
COMMENT ON TABLE fitness_tests IS 'Standardized fitness tests (Cooper, step test, strength tests, etc.)';
|
|
COMMENT ON COLUMN fitness_tests.test_type IS 'cooper_12min, step_test, pushups_max, plank_max, flexibility_sit_reach, vo2max_est, strength_1rm_squat, strength_1rm_bench';
|
|
COMMENT ON COLUMN fitness_tests.norm_category IS 'Performance category based on age/gender norms';
|
|
|
|
-- ============================================================================
|
|
-- VERSION UPDATE
|
|
-- ============================================================================
|
|
|
|
-- Track migration
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM schema_migrations WHERE version = '022') THEN
|
|
INSERT INTO schema_migrations (version, applied_at) VALUES ('022', NOW());
|
|
END IF;
|
|
END $$;
|