15 KiB
Слой src — основные данные
Таблицы слоя
src; правила — d-db-2, d-db-3 (std1\_rules\d\d-db.md). 🛡 Все функции слоя принимаютp_athlete_id; доступ — только в пределах атлета (изоляция, requirements Ф-8.5).
Перечень таблиц
| Таблица | Тип | Набор функций | Функции | Комментарий |
|---|---|---|---|---|
src.athlete |
сущность | athlete_insert, athlete_update, athlete_select, athlete_get | CUSTOM | без _delete: блокировка вместо удаления (is_blocked); запись/чтение — владелец и сервисы сервера, не атлет |
src.athlete_profile |
сущность | athlete_profile_upsert, athlete_profile_get | CUSTOM | 1:1 к athlete; upsert вместо insert/update (одна запись) |
src.exercise |
сущность | exercise_insert, exercise_update, exercise_delete, exercise_select, exercise_get | CRUD | _delete — логический (is_active=0, requirements Ф-4.2); функции фильтруют is_active по умолчанию |
src.exercise_replacement |
связь ММ | exercise_replacement_insert, exercise_replacement_delete | MM | типовой набор связи ММ — d-db-3; пара-замена для правила 6 (документ метрик); выборка — внутри exercise_select |
src.program |
сущность | program_insert, program_select, program_get | CUSTOM | контейнер версий; без update/delete — жизненный цикл ведут версии |
src.program_version |
сущность | program_version_insert, program_version_update, program_version_select, program_version_get | CRUD | body jsonb (схема дизайна LLM-планирования разд. 2); 🛡 неизменяемость: _update меняет только status |
src.workout |
сущность | workout_insert, workout_update, workout_select, workout_get | CRUD | PK UUID — идемпотентность (Р2-Б); facts_json — факты .fit |
src.workout_set |
сущность | workout_set_insert, workout_select (в составе), workout_set_get | CUSTOM | выборка — агрегатом в workout_select (подходы в карточке); _delete/_update нет: правка = новая пометка source=edit (история сохраняется) |
src.command_log |
сущность | command_log_insert, command_log_select | CUSTOM | журнал голосовых команд (Ф-1.5, 🛡 не редактируется); update/delete нет |
src.daily_log |
сущность | daily_log_upsert, daily_log_select | CUSTOM | день уникален для атлета (UNIQUE(athlete_id, date)) — upsert |
src.workout_metrics |
сущность | workout_metrics_upsert, workout_metrics_select | CUSTOM | 1:1 к workout, считает сервер (MetricsEngine) — телефон только читает; upsert (пересчёт) |
src.breaks |
сущность | break_insert, break_update, break_select | CUSTOM | перерывы (Ф-14); закрытие = update (ended_on); delete нет — журнал |
src.migration |
служебная | migration_insert, migration_select | CUSTOM | журнал применённых миграций (d-db-1); пишет --mode migrate |
Новая редакция (2026-10-17, ревью №2): FK-колонки приведены к uuid (Р2-Б доведён, З-16);
src.breaks—planned_return,source= auto|manual (З-17);src.migration— журнал миграций (З-25). УТВЕРЖДЕНО (2026-10-17). Правки ревью №3:src.program.external_id, примечаниеbody(З-31),is_active=false(З-34) — УТВЕРЖДЕНО (2026-10-17).
Описания
src.athlete — атлет
| Поле | Тип | Описание |
|---|---|---|
id |
serial PK | — |
name |
text NOT NULL | отображаемое имя (AdminWeb, Ф-9.1) |
api_key_hash |
text NOT NULL UNIQUE | SHA-256 ключа (ключ в открытом виде не хранится; сопоставление запроса — по хэшу) |
is_blocked |
boolean NOT NULL DEFAULT false | блокировка ключа → 401 (Ф-9.1) |
created_at |
timestamptz NOT NULL DEFAULT now() | — |
src.athlete_profile — профиль атлета (Ф-6, 1:1 к athlete)
| Поле | Тип | Описание |
|---|---|---|
id |
serial PK | — |
athlete_id |
int NOT NULL UNIQUE | → src.athlete.id |
sex |
text | m / f |
birth_year |
int | год рождения (Танака: 208 − 0,7×возраст) |
weight_kg |
numeric(5,1) | вес тела (последний введённый) |
hr_max |
int NULL | ручной ввод; NULL → расчёт по Танаке (ОВ-1) |
updated_at |
timestamptz NOT NULL | — |
src.exercise — упражнение библиотеки атлета (Ф-4)
| Поле | Тип | Описание |
|---|---|---|
id |
uuid PK DEFAULT gen_random_uuid() | 🛡 неизменяемый exercise_id (Ф-4.3); UUID генерирует телефон (Р2-Б) |
athlete_id |
int NOT NULL | → src.athlete.id; 🛡 библиотека принадлежит атлету (Ф-4.4) |
name |
text NOT NULL | название; UNIQUE(athlete_id, name) |
group_id |
int NOT NULL | → ref.exercise_group.id |
is_base |
boolean NOT NULL DEFAULT true | базовое/изоляция (шаг прогрессии, Ф-12.2) |
is_active |
boolean NOT NULL DEFAULT true | логическое удаление — is_active=false (Ф-4.2, З-34) |
src.exercise_replacement — пара-замена (ММ, правило 6)
| Поле | Тип | Описание |
|---|---|---|
exercise_id |
uuid | → src.exercise.id |
replacement_id |
uuid | → src.exercise.id; CHECK (exercise_id <> replacement_id); PK (exercise_id, replacement_id) |
Замена допустима только внутри группы — проверяет приложение (группы у обоих равны); БД фиксирует пары.
src.program — программа атлета (Ф-10)
| Поле | Тип | Описание |
|---|---|---|
id |
serial PK | — |
athlete_id |
int NOT NULL | → src.athlete.id; UNIQUE(athlete_id) — одна программа на атлета (версии — в program_version) |
title |
text NOT NULL | название («Программа v4») |
external_id |
text UNIQUE NULL | идентификатор пакета (program_id из program-import.json, З-31) |
created_at |
timestamptz NOT NULL DEFAULT now() | — |
src.program_version — неизменяемая версия программы (Ф-10, 🛡)
| Поле | Тип | Описание |
|---|---|---|
id |
uuid PK DEFAULT gen_random_uuid() | program_version_id для тренировок; UUID — телефон (Р2-Б) |
program_id |
int NOT NULL | → src.program.id |
version |
int NOT NULL | UNIQUE(program_id, version); 1, 2, 3… |
based_on_version |
int NULL | предыдущая версия (NULL — первая) |
status |
text NOT NULL DEFAULT 'draft' | draft | active | archived; 🛡 active — ровно одна на программу (частичный уникальный индекс WHERE status='active') |
author |
text NOT NULL | user | admin | rules | llm (v2) |
body |
jsonb NOT NULL | недели → тренировки → упражнения (exercise_id) → подходы-план; схема — дизайн LLM-планирования разд. 2; секция profile пакета |
| (program-import.json) в body не переносится (v1, З-31) | ||
rationale |
text | «почему» — журнал изменений, показывается пользователю |
created_at |
timestamptz NOT NULL DEFAULT now() | — |
src.workout — тренировка (телефон + факт .fit)
| Поле | Тип | Описание |
|---|---|---|
id |
uuid PK | UUID — телефон (строки «только .fit» — сервер); идемпотентность (Ф-7.4, Р2-Б) |
athlete_id |
int NOT NULL | → src.athlete.id |
program_version_id |
uuid NULL | → src.program_version.id; версия, активная на начало (Ф-10.3) |
started_at |
timestamptz NOT NULL | начало (UTC) |
ended_at |
timestamptz NULL | конец |
status |
text NOT NULL DEFAULT 'active' | active | finished | cancelled (Ф-3.5) |
session_kind |
text NOT NULL DEFAULT 'normal' | normal | return | deload | test — интерпретация снижения весов (Ф-14.5) |
avg_hr |
int NULL | средний пульс |
facts_json |
jsonb NULL | канонический факт из .fit (workout-facts/1, tech-requirements 2.3); заполняет FitParser |
fit_file_id |
text NULL | дедупликация fetch (Ф-8.1); уникальность — составная UNIQUE(athlete_id, fit_file_id) |
hr_recovery_s |
int NULL | возврат пульса к <90 (Ф-13.4, из .fit) |
UNIQUE(athlete_id, fit_file_id) — единственная уникальность
fit_file_id; сопоставление телефон ↔ .fit — окно ±15 минут по
started_at (Ф-8.4): факты пишутся в ту же строку (facts_json); если
тренировки с телефона не было — создаётся строка «только .fit» с UUID,
который генерирует сервер (Р2-Б).
src.workout_set — подход (телефон)
| Поле | Тип | Описание |
|---|---|---|
id |
uuid PK | UUID — телефон (Р2-Б) |
workout_id |
uuid NOT NULL | → src.workout.id |
athlete_id |
int NOT NULL | денормализация для изоляции выборок |
exercise_id |
uuid NULL | → src.exercise.id; NULL — упражнение не выбрано |
seq |
int NOT NULL | порядковый номер подхода в тренировке |
weight_kg |
numeric(6,2) NOT NULL | вес |
reps |
int NOT NULL | повторения |
done_at |
timestamptz NOT NULL | момент фиксации |
source |
text NOT NULL | voice | watch | edit |
in_plan |
boolean NOT NULL | «по плану» (Ф-11.2) |
rpe |
int NULL CHECK (rpe BETWEEN 1 AND 10) | субъективная нагрузка (Ф-13.3) |
knee_pain |
int NULL CHECK (0–10) | боль в колене (группа legs, Ф-13.3) |
src.command_log — журнал голосовых команд (Ф-1.5, 🛡)
| Поле | Тип | Описание |
|---|---|---|
id |
uuid PK | UUID — телефон (Р2-Б) |
workout_id |
uuid NOT NULL | → src.workout.id |
t_offset_s |
int NOT NULL | секунды от начала тренировки (сопоставление с .fit) |
recognized_text |
text NOT NULL | распознанный текст (диагностика STT) |
semantics |
jsonb NOT NULL | {cmd, weight, reps, rest_sec, rpe} |
accepted |
boolean NOT NULL | принята грамматикой/диапазоном |
src.daily_log — дневник самоконтроля (Ф-13.1)
| Поле | Тип | Описание |
|---|---|---|
id |
uuid PK | UUID — телефон (Р2-Б) |
athlete_id |
int NOT NULL | → src.athlete.id; UNIQUE(athlete_id, date) |
date |
date NOT NULL | день (локальная дата атлета) |
bp_sys |
int NULL | систолическое АД |
bp_dias |
int NULL | диастолическое АД |
weight_kg |
numeric(5,1) NULL | вес тела |
hr_resting |
int NULL | пульс покоя, утро (Р1-А — поле v1, данные правила 1) |
waist_cm |
numeric(5,1) NULL | талия (v2) |
sleep_h |
numeric(3,1) NULL | сон, часов (v2) |
wellbeing |
int NULL CHECK (1–5) | самочувствие 1–5 (Р1-А — поле v1, данные правила 1) |
src.breaks — перерывы (Ф-14)
| Поле | Тип | Описание |
|---|---|---|
id |
uuid PK | UUID — телефон/сервер (Р2-Б) |
athlete_id |
int NOT NULL | → src.athlete.id |
kind |
text NULL | ответ атлета: illness | vacation | workload | injury | other; NULL — ещё не отвечен (правила 12–14 ждут ответа) |
source |
text NOT NULL DEFAULT 'auto' | auto — сервер по пропускам (Ф-14.1); manual — запланированный простой, объявленный атлетом заранее с телефона (Ф-14.2). Из sync принимается только manual; auto проставляет сервер |
started_on |
date NOT NULL | первый пропущенный день |
planned_return |
date NULL | предполагаемая дата возврата (ответ атлета, Ф-14.2) |
ended_on |
date NULL | возврат (заполняется первой сессией после break) |
note |
text | причина-текст пользователя |
src.workout_metrics — метрики тренировки (MetricsEngine, 1:1)
| Поле | Тип | Описание |
|---|---|---|
id |
serial PK | — |
workout_id |
uuid NOT NULL UNIQUE | → src.workout.id |
athlete_id |
int NOT NULL | изоляция выборок |
adherence |
numeric(4,1) | % закрытых подходов плана (КУ-5) |
tonnage |
numeric(9,1) | Σ вес×повторы, кг |
tonnage_by_group |
jsonb | {"push": 4200, …} |
e1pm_by_exercise |
jsonb | {"<exercise_id>": 82.5} (Эпли, ОВ-7) |
hr_metrics |
jsonb | средний/пиковый пульс подхода, отдых между подходами |
red_flags |
jsonb | сработавшие красные флаги (АД, пульс, колено — документ метрик) |
computed_at |
timestamptz NOT NULL | — |
src.migration — журнал применённых миграций (служебная, d-db-1)
| Поле | Тип | Описание |
|---|---|---|
id |
serial PK | — |
filename |
text NOT NULL UNIQUE | имя применённого psql-файла из 2-db\migrations\ |
applied_at |
timestamptz NOT NULL DEFAULT now() | момент применения |