Шаг 5 утверждён; шаг 6: решение о БД, readme БД, tables.md слоёв src и ref

This commit is contained in:
virus-home 2026-10-17 19:10:19 +05:00
parent 2b3b0125fa
commit 74f57a5f6e
5 changed files with 241 additions and 10 deletions

View File

@ -13,19 +13,18 @@
| 2. Создание рабочей области | `readme.md` системы, структура репозитория, `.gitignore` | ✅ | | 2. Создание рабочей области | `readme.md` системы, структура репозитория, `.gitignore` | ✅ |
| 3. Разработка функциональных требований | `1-docs\1-reqs\requirements.md` (утверждён 2026-10-17) | ✅ | | 3. Разработка функциональных требований | `1-docs\1-reqs\requirements.md` (утверждён 2026-10-17) | ✅ |
| 4. Разработка архитектуры | `1-docs\2-arch\architecture.md`, `hld.puml` (+ рендер) (утверждена 2026-10-17) | ✅ | | 4. Разработка архитектуры | `1-docs\2-arch\architecture.md`, `hld.puml` (+ рендер) (утверждена 2026-10-17) | ✅ |
| 5. Разработка технических требований | `1-docs\3-tech\tech-requirements.md`, спецификации библиотек | 🔄 | | 5. Разработка технических требований | `1-docs\3-tech\tech-requirements.md`, спецификации библиотек (утверждены 2026-10-17) | ✅ |
| 6. Проектирование баз данных | решение о составе данных (фиксируется здесь) | ⬜ | | 6. Проектирование баз данных | `1-docs\2-db\` — readme, `src\tables.md`, `ref\tables.md`, `functions.md` | 🔄 |
| 7. Итерационная разработка | `3-app\` (код), `1-docs\4-modules\` (документы модулей) | ⬜ | | 7. Итерационная разработка | `3-app\` (код), `1-docs\4-modules\` (документы модулей) | ⬜ |
## Решение шага 6 (состав данных) ## Решение шага 6 (состав данных)
Не принято (шаг 6 не начинался). Предварительно по vision и дизайну Принято (2026-10-17): серверная БД — **PostgreSQL** (реляционные
LLM-планирования: серверная БД PostgreSQL обязательна — программы данные, ссылочная целостность, изоляция атлетов); состояние телефона —
(версионированный JSON), факты тренировок, метрики, журнал LLM локальная Room/SQLite; файлы (`.fit`, логи) — вне БД. Схемы-слои:
(таблицы `programs`, `program_changes`, `workout_facts`, `src` — данные тренировок, программ, атлетов, метрик; `ref` —
`workout_metrics`, `athlete_state`, `llm_calls` — набросок из дизайна справочники (двигательные группы). Документация — `1-docs\2-db\`.
LLM-планирования, разд. 8); состояние телефона — локальная БД JSON-тела (версии программ, факты .fit) — `jsonb` внутри таблиц.
(Room/SQLite); окончательное решение — на шаге 6.
## Состав версий (vision 2.2–2.4, решения опросников) ## Состав версий (vision 2.2–2.4, решения опросников)

34
1-docs/2-db/readme.md Normal file
View File

@ -0,0 +1,34 @@
# БД WatchBody (PostgreSQL)
> Документация БД серверной части — шаг 6 процесса
> (`std1\_process\process.md`), правила d-db (`std1\_rules\d\d-db.md`),
> db-func (`std1\_rules\db\db-func.md`). Решение шага 6 —
> [0-mng\plan.md](../0-mng/plan.md).
## Общие сведения
- **СУБД:** PostgreSQL 17+; доступ — только через хранимые функции
(`Kit.Helpers\Db.Postgres.SqlServerHelper`, tech-requirements разд. 3).
- **Схемы-слои:** `src` — основные данные (атлеты, тренировки,
программы, метрики); `ref` — справочники (двигательные группы).
- **Изоляция атлетов (🛡):** каждая таблица данных несёт `athlete_id`;
все функции слоя `src` принимают `p_athlete_id` и выбирают/пишут
только в пределах атлета (requirements, Ф-8.5, инвариант раздела 5).
- **Идемпотентность (🛡):** клиентские сущности несут `client_guid`
(UNIQUE) — повторная вставка возвращает существующий id
(`ON CONFLICT DO NOTHING` + `SELECT`).
- **JSON:** версии программ и факты .fit — `jsonb` с проверкой схемы
на стороне приложения (не в БД).
## Состав
| Файл | Содержание |
|---|---|
| [ref\tables.md](ref/tables.md) | справочники слоя `ref` |
| [src\tables.md](src/tables.md) | таблицы слоя `src` |
| `{слой}\functions.md` | списки функций (после утверждения таблиц) |
## Таблица `llm_calls` (v2)
Журнал вызовов LLM-планировщика (дизайн LLM-планирования, разд. 8)
создаётся в v2 вместе с `LlmPlanner` — в v1 не проектируется.

25
1-docs/2-db/ref/tables.md Normal file
View File

@ -0,0 +1,25 @@
# Слой ref — справочники
> Таблицы слоя `ref`; правила — d-db-2, d-db-3 (`std1\_rules\d\d-db.md`).
## Перечень таблиц
| Таблица | Тип | Набор функций | Функции | Комментарий |
|---|---|---|---|---|
| `ref.exercise_group` | системный справочник | exercise_group_select | SELECT | редактирование не предусмотрено (правила замены опираются на стабильный список) |
## Описания
### `ref.exercise_group` — двигательные группы упражнений
| Поле | Тип | Описание |
|---|---|---|
| `id` | int PK | код группы |
| `code` | text UNIQUE | символьный код (`push`, `pull`, `legs`, `core`, `cardio`) |
| `title` | text | название («Жимовые», «Тяговые», «Ноги», «Кор», «Кардио») |
Значения: 1 `push`, 2 `pull`, 3 `legs`, 4 `core`, 5 `cardio`.
Группа определяет: баланс тоннажа (Метрики, разд. 2.5), допустимость
замены (правило 6 — замена в пределах группы), показ поля «боль
в колене» (только `legs`).

172
1-docs/2-db/src/tables.md Normal file
View File

@ -0,0 +1,172 @@
# Слой 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 | пара-замена для правила 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 | `client_guid` UNIQUE — идемпотентность; `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.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` | serial PK | 🛡 неизменяемый `exercise_id` (Ф-4.3) |
| `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 | логическое удаление (Ф-4.2) |
### `src.exercise_replacement` — пара-замена (ММ, правило 6)
| Поле | Тип | Описание |
|---|---|---|
| `exercise_id` | int | → `src.exercise.id` |
| `replacement_id` | int | → `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») |
| `created_at` | timestamptz NOT NULL DEFAULT now() | — |
### `src.program_version` — неизменяемая версия программы (Ф-10, 🛡)
| Поле | Тип | Описание |
|---|---|---|
| `id` | serial PK | `program_version_id` для тренировок |
| `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 |
| `rationale` | text | «почему» — журнал изменений, показывается пользователю |
| `created_at` | timestamptz NOT NULL DEFAULT now() | — |
### `src.workout` — тренировка (телефон + факт .fit)
| Поле | Тип | Описание |
|---|---|---|
| `id` | serial PK | — |
| `athlete_id` | int NOT NULL | → `src.athlete.id` |
| `client_guid` | uuid NOT NULL UNIQUE | идемпотентность (Ф-7.4) |
| `program_version_id` | int 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) |
| `avg_hr` | int NULL | средний пульс |
| `fit_workout_id` | int NULL | сопоставленная тренировка .fit — см. ниже |
| `facts_json` | jsonb NULL | канонический факт из .fit (`workout-facts/1`, tech-requirements 2.3); заполняет FitParser |
| `fit_file_id` | text NULL UNIQUE | дедупликация fetch (Ф-8.1) |
| `hr_recovery_s` | int NULL | возврат пульса к <90 (Ф-13.4, из .fit) |
`UNIQUE(athlete_id, fit_file_id)`; сопоставление телефон ↔ .fit — окно
±15 минут по `started_at` (Ф-8.4): факты пишутся в ту же строку
(`facts_json`), либо создается строка «только .fit» без `client_guid`
пары — тогда `client_guid` генерирует сервер.
### `src.workout_set` — подход (телефон)
| Поле | Тип | Описание |
|---|---|---|
| `id` | serial PK | — |
| `workout_id` | int NOT NULL | → `src.workout.id` |
| `athlete_id` | int NOT NULL | денормализация для изоляции выборок |
| `exercise_id` | int 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) |
| `client_guid` | uuid NOT NULL UNIQUE | идемпотентность |
### `src.command_log` — журнал голосовых команд (Ф-1.5, 🛡)
| Поле | Тип | Описание |
|---|---|---|
| `id` | serial PK | — |
| `workout_id` | int 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 | принята грамматикой/диапазоном |
| `client_guid` | uuid NOT NULL UNIQUE | идемпотентность |
### `src.daily_log` — дневник самоконтроля (Ф-13.1)
| Поле | Тип | Описание |
|---|---|---|
| `id` | serial PK | — |
| `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 | пульс покоя (v2) |
| `waist_cm` | numeric(5,1) NULL | талия (v2) |
| `sleep_h` | numeric(3,1) NULL | сон, часов (v2) |
| `wellbeing` | int NULL CHECK (1–5) | самочувствие (v2) |
| `client_guid` | uuid NOT NULL UNIQUE | идемпотентность |
### `src.workout_metrics` — метрики тренировки (MetricsEngine, 1:1)
| Поле | Тип | Описание |
|---|---|---|
| `id` | serial PK | — |
| `workout_id` | int 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 | — |

View File

@ -5,7 +5,8 @@
> [requirements.md](../1-reqs/requirements.md) (утверждён, раздел 7 — > [requirements.md](../1-reqs/requirements.md) (утверждён, раздел 7 —
> открытые вопросы), [architecture.md](../2-arch/architecture.md) > открытые вопросы), [architecture.md](../2-arch/architecture.md)
> (утверждена), дизайн LLM-планирования, документ форматов. > (утверждена), дизайн LLM-планирования, документ форматов.
> Версия: черновик 1 (2026-10-17). > Версия 1.1 — **УТВЕРЖДЕНО** (2026-10-17; интеграция документа метрик —
> Ф-13, Safety-пороги, daily_log — утверждена вместе с документом).
## 1. Решения открытых вопросов (закрытие раздела 7 requirements) ## 1. Решения открытых вопросов (закрытие раздела 7 requirements)