149 lines
5.4 KiB
SQL
149 lines
5.4 KiB
SQL
-- 002_src_tables.sql: таблицы слоя src (порядок — FK-зависимости).
|
|
-- Идемпотентно (IF NOT EXISTS); состав — 1-docs\2-db\src\tables.md.
|
|
|
|
CREATE TABLE IF NOT EXISTS src.athlete (
|
|
id serial PRIMARY KEY,
|
|
name text NOT NULL,
|
|
api_key_hash text NOT NULL UNIQUE,
|
|
is_blocked boolean NOT NULL DEFAULT false,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS src.athlete_profile (
|
|
id serial PRIMARY KEY,
|
|
athlete_id int NOT NULL UNIQUE REFERENCES src.athlete(id),
|
|
sex text CHECK (sex IN ('m', 'f')),
|
|
birth_year int,
|
|
weight_kg numeric(5,1),
|
|
hr_max int,
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS src.exercise (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
athlete_id int NOT NULL REFERENCES src.athlete(id),
|
|
name text NOT NULL,
|
|
group_id int NOT NULL REFERENCES ref.exercise_group(id),
|
|
is_base boolean NOT NULL DEFAULT true,
|
|
is_active boolean NOT NULL DEFAULT true,
|
|
UNIQUE (athlete_id, name)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS src.exercise_replacement (
|
|
exercise_id uuid NOT NULL REFERENCES src.exercise(id),
|
|
replacement_id uuid NOT NULL REFERENCES src.exercise(id),
|
|
CHECK (exercise_id <> replacement_id),
|
|
PRIMARY KEY (exercise_id, replacement_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS src.program (
|
|
id serial PRIMARY KEY,
|
|
athlete_id int NOT NULL UNIQUE REFERENCES src.athlete(id),
|
|
title text NOT NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS src.program_version (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
program_id int NOT NULL REFERENCES src.program(id),
|
|
version int NOT NULL,
|
|
based_on_version int,
|
|
status text NOT NULL DEFAULT 'draft'
|
|
CHECK (status IN ('draft', 'active', 'archived')),
|
|
author text NOT NULL CHECK (author IN ('user', 'admin', 'rules', 'llm')),
|
|
body jsonb NOT NULL,
|
|
rationale text,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
UNIQUE (program_id, version)
|
|
);
|
|
|
|
-- 🛡 активная версия — ровно одна на программу
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_program_version_active
|
|
ON src.program_version (program_id) WHERE status = 'active';
|
|
|
|
CREATE TABLE IF NOT EXISTS src.workout (
|
|
id uuid PRIMARY KEY,
|
|
athlete_id int NOT NULL REFERENCES src.athlete(id),
|
|
program_version_id uuid REFERENCES src.program_version(id),
|
|
started_at timestamptz NOT NULL,
|
|
ended_at timestamptz,
|
|
status text NOT NULL DEFAULT 'active'
|
|
CHECK (status IN ('active', 'finished', 'cancelled')),
|
|
session_kind text NOT NULL DEFAULT 'normal'
|
|
CHECK (session_kind IN ('normal', 'return', 'deload', 'test')),
|
|
avg_hr int,
|
|
facts_json jsonb,
|
|
fit_file_id text,
|
|
hr_recovery_s int,
|
|
UNIQUE (athlete_id, fit_file_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS src.workout_set (
|
|
id uuid PRIMARY KEY,
|
|
workout_id uuid NOT NULL REFERENCES src.workout(id),
|
|
athlete_id int NOT NULL REFERENCES src.athlete(id),
|
|
exercise_id uuid REFERENCES src.exercise(id),
|
|
seq int NOT NULL,
|
|
weight_kg numeric(6,2) NOT NULL,
|
|
reps int NOT NULL,
|
|
done_at timestamptz NOT NULL,
|
|
source text NOT NULL CHECK (source IN ('voice', 'watch', 'edit')),
|
|
in_plan boolean NOT NULL,
|
|
rpe int CHECK (rpe BETWEEN 1 AND 10),
|
|
knee_pain int CHECK (knee_pain BETWEEN 0 AND 10)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS src.command_log (
|
|
id uuid PRIMARY KEY,
|
|
workout_id uuid NOT NULL REFERENCES src.workout(id),
|
|
t_offset_s int NOT NULL,
|
|
recognized_text text NOT NULL,
|
|
semantics jsonb NOT NULL,
|
|
accepted boolean NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS src.daily_log (
|
|
id uuid PRIMARY KEY,
|
|
athlete_id int NOT NULL REFERENCES src.athlete(id),
|
|
date date NOT NULL,
|
|
bp_sys int,
|
|
bp_dias int,
|
|
weight_kg numeric(5,1),
|
|
hr_resting int,
|
|
waist_cm numeric(5,1),
|
|
sleep_h numeric(3,1),
|
|
wellbeing int CHECK (wellbeing BETWEEN 1 AND 5),
|
|
UNIQUE (athlete_id, date)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS src.workout_metrics (
|
|
id serial PRIMARY KEY,
|
|
workout_id uuid NOT NULL UNIQUE REFERENCES src.workout(id),
|
|
athlete_id int NOT NULL REFERENCES src.athlete(id),
|
|
adherence numeric(4,1),
|
|
tonnage numeric(9,1),
|
|
tonnage_by_group jsonb,
|
|
e1pm_by_exercise jsonb,
|
|
hr_metrics jsonb,
|
|
red_flags jsonb,
|
|
computed_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS src.breaks (
|
|
id uuid PRIMARY KEY,
|
|
athlete_id int NOT NULL REFERENCES src.athlete(id),
|
|
kind text CHECK (kind IN
|
|
('illness', 'vacation', 'workload', 'injury', 'other')),
|
|
source text NOT NULL DEFAULT 'auto' CHECK (source IN ('auto', 'manual')),
|
|
started_on date NOT NULL,
|
|
planned_return date,
|
|
ended_on date,
|
|
note text
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS src.migration (
|
|
id serial PRIMARY KEY,
|
|
filename text NOT NULL UNIQUE,
|
|
applied_at timestamptz NOT NULL DEFAULT now()
|
|
);
|