From 74f57a5f6e35366ba335a9aaa12638661783422d Mon Sep 17 00:00:00 2001 From: virus-home Date: Sat, 17 Oct 2026 19:10:19 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A8=D0=B0=D0=B3=205=20=D1=83=D1=82=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D0=B6=D0=B4=D1=91=D0=BD;=20=D1=88=D0=B0=D0=B3=206:?= =?UTF-8?q?=20=D1=80=D0=B5=D1=88=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=20=D0=91?= =?UTF-8?q?=D0=94,=20readme=20=D0=91=D0=94,=20tables.md=20=D1=81=D0=BB?= =?UTF-8?q?=D0=BE=D1=91=D0=B2=20src=20=D0=B8=20ref?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1-docs/0-mng/plan.md | 17 ++- 1-docs/2-db/readme.md | 34 ++++++ 1-docs/2-db/ref/tables.md | 25 +++++ 1-docs/2-db/src/tables.md | 172 +++++++++++++++++++++++++++++ 1-docs/3-tech/tech-requirements.md | 3 +- 5 files changed, 241 insertions(+), 10 deletions(-) create mode 100644 1-docs/2-db/readme.md create mode 100644 1-docs/2-db/ref/tables.md create mode 100644 1-docs/2-db/src/tables.md diff --git a/1-docs/0-mng/plan.md b/1-docs/0-mng/plan.md index 303a4d9..592143b 100644 --- a/1-docs/0-mng/plan.md +++ b/1-docs/0-mng/plan.md @@ -13,19 +13,18 @@ | 2. Создание рабочей области | `readme.md` системы, структура репозитория, `.gitignore` | ✅ | | 3. Разработка функциональных требований | `1-docs\1-reqs\requirements.md` (утверждён 2026-10-17) | ✅ | | 4. Разработка архитектуры | `1-docs\2-arch\architecture.md`, `hld.puml` (+ рендер) (утверждена 2026-10-17) | ✅ | -| 5. Разработка технических требований | `1-docs\3-tech\tech-requirements.md`, спецификации библиотек | 🔄 | -| 6. Проектирование баз данных | решение о составе данных (фиксируется здесь) | ⬜ | +| 5. Разработка технических требований | `1-docs\3-tech\tech-requirements.md`, спецификации библиотек (утверждены 2026-10-17) | ✅ | +| 6. Проектирование баз данных | `1-docs\2-db\` — readme, `src\tables.md`, `ref\tables.md`, `functions.md` | 🔄 | | 7. Итерационная разработка | `3-app\` (код), `1-docs\4-modules\` (документы модулей) | ⬜ | ## Решение шага 6 (состав данных) -Не принято (шаг 6 не начинался). Предварительно по vision и дизайну -LLM-планирования: серверная БД PostgreSQL обязательна — программы -(версионированный JSON), факты тренировок, метрики, журнал LLM -(таблицы `programs`, `program_changes`, `workout_facts`, -`workout_metrics`, `athlete_state`, `llm_calls` — набросок из дизайна -LLM-планирования, разд. 8); состояние телефона — локальная БД -(Room/SQLite); окончательное решение — на шаге 6. +Принято (2026-10-17): серверная БД — **PostgreSQL** (реляционные +данные, ссылочная целостность, изоляция атлетов); состояние телефона — +локальная Room/SQLite; файлы (`.fit`, логи) — вне БД. Схемы-слои: +`src` — данные тренировок, программ, атлетов, метрик; `ref` — +справочники (двигательные группы). Документация — `1-docs\2-db\`. +JSON-тела (версии программ, факты .fit) — `jsonb` внутри таблиц. ## Состав версий (vision 2.2–2.4, решения опросников) diff --git a/1-docs/2-db/readme.md b/1-docs/2-db/readme.md new file mode 100644 index 0000000..1fc1c19 --- /dev/null +++ b/1-docs/2-db/readme.md @@ -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 не проектируется. diff --git a/1-docs/2-db/ref/tables.md b/1-docs/2-db/ref/tables.md new file mode 100644 index 0000000..eda22c4 --- /dev/null +++ b/1-docs/2-db/ref/tables.md @@ -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`). diff --git a/1-docs/2-db/src/tables.md b/1-docs/2-db/src/tables.md new file mode 100644 index 0000000..7dde977 --- /dev/null +++ b/1-docs/2-db/src/tables.md @@ -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 | `{"": 82.5}` (Эпли, ОВ-7) | +| `hr_metrics` | jsonb | средний/пиковый пульс подхода, отдых между подходами | +| `red_flags` | jsonb | сработавшие красные флаги (АД, пульс, колено — документ метрик) | +| `computed_at` | timestamptz NOT NULL | — | diff --git a/1-docs/3-tech/tech-requirements.md b/1-docs/3-tech/tech-requirements.md index e147321..b458c5f 100644 --- a/1-docs/3-tech/tech-requirements.md +++ b/1-docs/3-tech/tech-requirements.md @@ -5,7 +5,8 @@ > [requirements.md](../1-reqs/requirements.md) (утверждён, раздел 7 — > открытые вопросы), [architecture.md](../2-arch/architecture.md) > (утверждена), дизайн LLM-планирования, документ форматов. -> Версия: черновик 1 (2026-10-17). +> Версия 1.1 — **УТВЕРЖДЕНО** (2026-10-17; интеграция документа метрик — +> Ф-13, Safety-пороги, daily_log — утверждена вместе с документом). ## 1. Решения открытых вопросов (закрытие раздела 7 requirements)