VEU.WathBody/1-docs/2-arch/architecture.md

155 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Архитектура: Феникс 6X v1
> Шаг 4 процесса разработки (`std1\_process\process.md`).
> Источники: [vision.md](../1-reqs/vision.md) (v1.5, изменения 1.1–1.5),
> [requirements.md](../1-reqs/requirements.md) v1.4 (утверждён; ревью
> №2 и №3),
> дизайн LLM-планирования (разд. 8), документ форматов, документы
> «Метрики…» и «Перерывы…».
> Схема компонентов: [hld.puml](hld.puml) (+ рендер [hld.svg](hld.svg),
> [hld.png](hld.png)).
> Версия 1.0 — **УТВЕРЖДЕНО** (2026-10-17).
> Версия 1.1 — **УТВЕРЖДЕНО** (2026-10-17): актуализация под Ф-13
> (ревью З-2) и Ф-14 — перерывы (изменение vision 1.4); волна 2 ревью
> (Р1–Р3).
> Версия 1.2 — **УТВЕРЖДЕНО** (2026-10-17): ревью №2, З-29 — перечень `LocalDb`
> дополнен (`daily_log`, `breaks`); источник — requirements v1.4.
## 1. Компоненты и размещение
### 1.1. Часы атлета — `FenixDataField` (Monkey C, Connect IQ)
Поле данных «Феникс» в штатном профиле «Силовая». Только отображение
(пульс, средний за подход, время, цвет зоны, вибро) — запись `.fit`
ведёт платформа Garmin (инвариант из requirements, Ф-2.7). Один исходный
код, сборки под целевые модели Garmin — конфигурациями (изменение
vision 1.1); перечень моделей — открытый вопрос 8 требований.
Связь с телефоном — штатная трансляция пульса «Broadcast Heart
Rate», приём на телефоне по BLE (`WatchChannel`, компонент
телефона; errata ОВ-3 от 2026-10-19: companion-канал Connect IQ
на fenix 6X недоступен для полей данных — CIQ ≤ 4.1.7): live-пульс
на телефоне, канал односторонний (часы → телефон), таймер отдыха
на часах автономный; отсутствие связи ничего не ломает — часы
автономны.
### 1.2. Телефон атлета — **Phone.WatchBody** (Android, Kotlin)
- `TrainingUi` — экраны (Jetpack Compose — кандидат, фиксируется
шагом 5): активная тренировка, история, программы, профиль,
«Сегодня» (дневник) и «Допуск»; тёмная тема, крупные зоны касания.
- `VoiceInput` — офлайн-STT (движок — открытый вопрос 2) + грамматика
команд (Ф-1.2) + TTS-подтверждения; журнал команд для сопоставления
с .fit.
- `WorkoutCore` — логика тренировки и программ: подходы, таймеры,
план/факт, редактор программ; 🛡 предстартовый допуск (пороги
Safety, экран допуска) и дневник самоконтроля (`daily_log`);
автомат перерывов и формула возврата (Ф-14); единственный писатель
`LocalDb`.
- `LocalDb` — Room/SQLite: текущая тренировка, история, программы,
библиотека, профиль, дневник самоконтроля (`daily_log`), перерывы
(`breaks`), очередь.
- `SyncQueue` — исходящие записи с `client_guid`, пакеты ≤ 50,
идемпотентная отправка `POST /api/v1/sync`; тихие баннеры статуса.
- `WatchChannel` — приём live-пульса с часов: BLE Heart Rate
Service за интерфейсом `WatchTransport` (платформо-независимый —
любые часы со стандартной трансляцией); статус связи, заряд,
средний пульс тренировки.
🛡 Офлайн-first: сеть стоит только на пути `SyncQueue`; все остальные
компоненты работают без сети (requirements, раздел 5).
### 1.3. Личный сервер — **Server.WatchBody** (C# / .NET 10, PostgreSQL)
Проекты в `3-app\`:
| Компонент | Проект (`3-app\2-cli\`) | Ответственность |
|---|---|---|
| `RestApi` | `Server.WatchBody.Web` | `POST /api/v1/sync`, `GET workouts`, `GET programs/active`; ключ атлета → `athlete_id`; HTTPS; + admin REST API (`/api/admin/*`) для консоли |
| `AdminWeb` | UI — отдельное Flutter Web-приложение (`3-app\3-web\admin-web`); бэкенд — admin REST API в `Server.WatchBody.Web` | Консоль владельца (логин/пароль): атлеты, тренировки + ход, программы, планы, метрики, CSV (решение владельца 2026-10-17: UI как отдельное Flutter-приложение вместо Razor Pages) |
| `FitFetcher` | `Server.WatchBody.Fetch` | Клиент Garmin Connect (неофициальный, опросник 4.3); `--mode fetch`; дедупликация по `fit_file_id` |
| `FitParser` | `Server.WatchBody.Import` | Garmin FIT SDK: Session/Lap/Record/Set → `workout_facts`; `--mode import` (fallback: .fit/.tcx/.gpx из папки) |
| `MetricsEngine` | `Server.WatchBody.Core` | adherence, тоннаж по группам, 1ПМ, пульс-метрики, красные флаги (АД/пульс/боль — документ метрик) → `workout_metrics` |
| `ProgressionRules` | `Server.WatchBody.Core` | Правила прогрессии, делоада и координации 1–11 (документ метрик) + 12–17 перерывы (документ перерывов): детекция `break` по пропускам, формула возврата → draft-версии (`author=rules`) |
| `ProgramStore` | `Server.WatchBody.Core` | Версионированный JSON программ; неизменяемость версий; `active` — ровно один на атлета |
| БД | схема `src` \| `ref` | PostgreSQL: атлеты, тренировки/факты + `session_kind`, дневник (`daily_log`), перерывы (`breaks`), метрики, программы, журнал LLM (v2) |
Общая библиотека — `Kit.Helpers` (`3-app\1-lib`, std1). Взаимодействие
с LLM (v2, `LlmPlanner`) — по правилам std1 `c-llm`; в v1 компонент
не реализуется (на схеме — серым).
### 1.4. Внешние системы (жёлтым на схеме)
- **Garmin Connect** — облако: источник `.fit` (забор `FitFetcher`
по расписанию — внешний планировщик, открытый вопрос 4).
- **Garmin (устройства)** — запись `.fit` на часах, трансляция
пульса (BLE/ANT+).
## 2. Поток цикла обработки
0. **Допуск и перерывы.** Перед стартом — экран допуска (АД/пульс;
красные флаги блокируют или переводят в лёгкое кардио; обход
логируется); автомат перерывов ставит `break` по пропускам и
запрашивает причину у атлета; при возврате — формульная версия
(Ф-14).
1. **Тренировка.** Атлет запускает «Силовую» на часах (`FenixDataField`
показывает пульс/зоны/подходы) и активирует голосовой ввод на телефоне
(`VoiceInput` → команды → `WorkoutCore` → `LocalDb`). Часы и телефон
независимы: любой из них работает без другого (🛡).
2. **Фиксация плана.** `WorkoutCore` показывает позицию активной
программы (`RestApi → ProgramStore`, при наличии сети; локальная
копия — офлайн), помечает подходы «по плану/отклонение».
3. **Синхронизация.** `SyncQueue` досылает новые записи на `RestApi`
при появлении сети; сервер пишет их по `client_guid` (идемпотентно)
от имени атлета ключа.
4. **Трек.** Часы синхронизируются с Garmin Connect; `FitFetcher`
по расписанию забирает новые `.fit`, `FitParser` раскладывает
в `workout_facts`; сопоставление с тренировкой телефона — по времени
начала ± 15 минут (Ф-8.4).
5. **Метрики и правила.** `MetricsEngine` считает adherence/тоннаж/1ПМ;
`ProgressionRules` формируют draft-версию следующей программы
(микроправки ±2,5 кг / −5 %, делоад).
6. **Подтверждение.** Draft показывается атлету на телефоне (дифф) или
владельцу в `AdminWeb`; после подтверждения версия становится
`active` (🛡 автоматической смены нет).
7. **Наблюдение.** Владелец видит в `AdminWeb` ход тренировок,
планы/факт, динамику; CSV — выгрузка выборки.
## 3. Границы и контракты
| Граница | Контракт | Открытые вопросы |
|---|---|---|
| Телефон ↔ сервер | REST/HTTPS, `X-Api-Key` (атлет), `client_guid`-идемпотентность, пакеты ≤ 50; формат записей — JSON (документ форматов) | — (спецификация — шаг 5) |
| Телефон ↔ часы | штатная трансляция пульса «Broadcast Heart Rate» → BLE-приём (`WatchTransport`): live-пульс, заряд; односторонний канал; деградация без связи | ОВ-3 (закрыт errata 2026-10-19) |
| Сервер ↔ Garmin Connect | неофициальный клиент, учётка владельца; деградация — папка импорта `--mode import` | — |
| Сервер ↔ LLM (v2) | structured output, валидатор, офлайн-fallback на правила (дизайн LLM-планирования) | до v2 |
## 4. Точки расширения
- **Модели часов** — новая конфигурация сборки `FenixDataField`
(разметка под экран), код не меняется (изменение vision 1.1).
- **LLM-планирование (v2)** — `LlmPlanner` потребляет сводку
`MetricsEngine` и предлагает версии через `ProgramStore`;
`ProgressionRules` остаются базовым режимом (офлайн-fallback).
- **Трековые виды спорта (v3+)** — расширение `FitParser` (телеметрия)
и `WorkoutCore` (ввод темпа/дистанции); пульс/синхронизация/архив
не меняются (опросник 4.7).
- **Общий справочник упражнений (v2)** — надстройка над изолированными
библиотеками атлетов (ОВ-11 требований).
## 5. Архитектурные инварианты (🛡)
1. **Независимость частей** — часы, телефон, сервер работают при
отсутствии остальных; ни один путь записи не проходит через сеть.
2. **Офлайн-first** — источник истины на телефоне — `LocalDb`; сервер
— архив и планирование.
3. **Идемпотентность** — `client_guid` на телефоне ↔ сервере,
`fit_file_id` на заборе, архивация файлов импорта: повторы не создают
дублей.
4. **Изоляция атлетов** — ключ → `athlete_id`; все выборки API и метрики
— в пределах атлета; веб — только владелец.
5. **Неизменяемость истории** — версии программ и факт-записи не
переписываются; корректировка = новая версия.
6. **LLM не источник истины** — в v1 решений LLM нет; в v2 — только
предложения через валидатор и подтверждение (БР-5).