godcrm/backend/database/migrations/012-wellness-ecosystem.sql
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

148 lines
6.9 KiB
SQL

-- =============================================================
-- MIGRATION: 012-wellness-ecosystem.sql
-- ADR-027: Wellness Ecosystem — Тамагочи для Человека
-- Phase 1: Foundation (Profile, Vitals, Gamification)
-- =============================================================
-- Профиль пользователя (физические параметры)
CREATE TABLE IF NOT EXISTS wellness_profiles (
id SERIAL PRIMARY KEY,
space_id INTEGER REFERENCES spaces(id) NOT NULL UNIQUE,
gender VARCHAR(10), -- male, female, other
birth_date DATE,
height_cm INTEGER,
target_weight_kg DECIMAL(5,2),
activity_level VARCHAR(20), -- sedentary, light, moderate, active, very_active
bmr INTEGER, -- Basal Metabolic Rate (calculated)
tdee INTEGER, -- Total Daily Energy Expenditure
timezone VARCHAR(50) DEFAULT 'UTC',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- =============================================================
-- VITALS MODULE
-- =============================================================
-- Показатели здоровья
CREATE TABLE IF NOT EXISTS wellness_vitals (
id SERIAL PRIMARY KEY,
space_id INTEGER REFERENCES spaces(id) NOT NULL,
measured_at TIMESTAMP NOT NULL,
vital_type VARCHAR(50) NOT NULL, -- weight, heart_rate, blood_pressure_sys,
-- blood_pressure_dia, temperature, spo2,
-- blood_glucose, body_fat_pct, body_battery
value DECIMAL(10,3) NOT NULL,
unit VARCHAR(20), -- kg, bpm, mmHg, C, %, mg/dL
source VARCHAR(50) DEFAULT 'manual', -- manual, garmin, apple_health, oura
notes TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- Индекс для быстрого поиска последних значений
CREATE INDEX IF NOT EXISTS idx_vitals_latest ON wellness_vitals(space_id, vital_type, measured_at DESC);
CREATE INDEX IF NOT EXISTS idx_vitals_space ON wellness_vitals(space_id);
-- =============================================================
-- GAMIFICATION MODULE
-- =============================================================
-- Очки
CREATE TABLE IF NOT EXISTS wellness_points (
id SERIAL PRIMARY KEY,
space_id INTEGER REFERENCES spaces(id) NOT NULL,
earned_at TIMESTAMP DEFAULT NOW(),
points INTEGER NOT NULL,
source_type VARCHAR(50) NOT NULL, -- vital, workout, habit, goal, achievement, streak
source_id INTEGER,
reason VARCHAR(255),
metadata JSONB
);
CREATE INDEX IF NOT EXISTS idx_points_space ON wellness_points(space_id, earned_at DESC);
-- Уровни пользователя
CREATE TABLE IF NOT EXISTS wellness_levels (
id SERIAL PRIMARY KEY,
space_id INTEGER REFERENCES spaces(id) NOT NULL UNIQUE,
current_level INTEGER DEFAULT 1,
total_xp INTEGER DEFAULT 0,
level_xp INTEGER DEFAULT 0, -- XP in current level
title VARCHAR(100) DEFAULT 'Beginner',
avatar_url TEXT,
updated_at TIMESTAMP DEFAULT NOW()
);
-- Справочник достижений (глобальный)
CREATE TABLE IF NOT EXISTS wellness_achievements (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
icon VARCHAR(100),
category VARCHAR(50), -- fitness, nutrition, consistency, health
tier VARCHAR(20) DEFAULT 'bronze', -- bronze, silver, gold, platinum
condition JSONB NOT NULL, -- {"type": "streak", "streak_type": "workout", "count": 7}
points_reward INTEGER DEFAULT 50,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW()
);
-- Достижения пользователей
CREATE TABLE IF NOT EXISTS wellness_user_achievements (
id SERIAL PRIMARY KEY,
space_id INTEGER REFERENCES spaces(id) NOT NULL,
achievement_id INTEGER REFERENCES wellness_achievements(id),
earned_at TIMESTAMP,
progress DECIMAL(5,2) DEFAULT 0, -- 0-100%
UNIQUE(space_id, achievement_id)
);
-- Streaks
CREATE TABLE IF NOT EXISTS wellness_streaks (
id SERIAL PRIMARY KEY,
space_id INTEGER REFERENCES spaces(id) NOT NULL,
streak_type VARCHAR(50) NOT NULL, -- workout, vitals_logged, nutrition_logged, water_goal
current_count INTEGER DEFAULT 0,
longest_count INTEGER DEFAULT 0,
last_activity_date DATE,
started_at DATE,
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(space_id, streak_type)
);
CREATE INDEX IF NOT EXISTS idx_streaks_space ON wellness_streaks(space_id);
-- =============================================================
-- SEED DATA: Default Achievements
-- =============================================================
INSERT INTO wellness_achievements (name, description, icon, category, tier, condition, points_reward) VALUES
-- Vitals Achievements
('First Check-in', 'Log your first vital sign', '❤️', 'health', 'bronze', '{"type": "count", "table": "wellness_vitals", "count": 1}', 10),
('Health Monitor', 'Log vitals for 7 days in a row', '📊', 'health', 'silver', '{"type": "streak", "streak_type": "vitals_logged", "count": 7}', 50),
('Vital Master', 'Log vitals for 30 days in a row', '🏆', 'health', 'gold', '{"type": "streak", "streak_type": "vitals_logged", "count": 30}', 200),
-- Fitness Achievements (cross-reference with ADR-025)
('First Workout', 'Complete your first workout', '💪', 'fitness', 'bronze', '{"type": "count", "table": "fitness_workouts", "count": 1}', 10),
('Week Warrior', 'Work out 7 days in a row', '🔥', 'fitness', 'silver', '{"type": "streak", "streak_type": "workout", "count": 7}', 50),
('Month Master', 'Work out 30 days in a row', '🏆', 'fitness', 'gold', '{"type": "streak", "streak_type": "workout", "count": 30}', 200),
('Century Club', 'Complete 100 workouts', '💯', 'fitness', 'platinum', '{"type": "count", "table": "fitness_workouts", "count": 100}', 500),
-- Level Achievements
('Level 5', 'Reach Level 5', '', 'consistency', 'bronze', '{"type": "level", "level": 5}', 25),
('Level 10', 'Reach Level 10', '🌟', 'consistency', 'silver', '{"type": "level", "level": 10}', 75),
('Level 25', 'Reach Level 25', '', 'consistency', 'gold', '{"type": "level", "level": 25}', 150),
('Level 50', 'Reach Level 50', '🎖️', 'consistency', 'platinum', '{"type": "level", "level": 50}', 500)
ON CONFLICT DO NOTHING;
-- =============================================================
-- COMMENT: Level Thresholds
-- =============================================================
-- XP requirements per level (exponential curve)
-- Level 1: 0 XP, Level 2: 100 XP, Level 3: ~283 XP, Level 4: ~400 XP...
-- Formula: required_xp = round(100 * (level ^ 1.5))
COMMENT ON TABLE wellness_levels IS 'Level XP formula: required_xp = round(100 * (level ^ 1.5))';
COMMENT ON TABLE wellness_vitals IS 'Supported vital_types: weight, heart_rate, blood_pressure_sys, blood_pressure_dia, temperature, spo2, blood_glucose, body_fat_pct, body_battery';