VEU.WathBody/1-docs/3-tech/tech-requirements.md

11 KiB
Raw Blame History

Технические требования: VEU.WathBody («Феникс 6X») v1

Шаг 5 процесса разработки (std1\_process\process.md). Источники: vision.md v1.2, requirements.md (утверждён, раздел 7 — открытые вопросы), architecture.md (утверждена), дизайн LLM-планирования, документ форматов. Версия: черновик 1 (2026-10-17).

1. Решения открытых вопросов (закрытие раздела 7 requirements)

# ОВ Решение Обоснование
1. Максимальная ЧСС Формула Танаки: HRmax = 208 − 0,7 × возраст; ручной ввод Hr.MaxBpm перекрывает расчёт Точнее для 45+ (профиль пользователя); ручной ввод — окончательная истина
2. Офлайн-STT Vosk (малая русская модель ~50 МБ) + узкая грамматика команд (Ф-1.2: числа и слова «подход/вес/отдых/секунд»); TTS-подтверждения — системный Android TTS Работа без сети, малый словарь → устойчивость к шуму; sherpa-onnx — запасная кандидатура, если точность < КУ-1 на контрольной тренировке
3. Канал телефон ↔ часы Connect IQ Communications SDK (companion-канал через приложение Garmin Connect Mobile на телефоне). Передаётся: текущий пульс (≈1 Гц), зона, таймер отдыха (секунды до конца), заряд часов. Ограничение: на телефоне должно быть установлено и залогинено Garmin Connect Mobile; канал может не доставлять — обе стороны деградируют молча (Ф-2.4, Ф-3.2) Единственный штатный канал сторонних приложений Garmin; проверка объёма передачи — прототипом в итерации 1 шага 7 (критерий: пульс и таймер доходят при экране часов активным)
4. Расписание fetch Внешний планировщик ОС: systemd timer (Linux-сервер) / Планировщик задач (Windows) вызывает --mode fetch; внутри сервера планировщика нет Проще, надёжнее, не дублирует средства ОС
5. Формула 1ПМ Эпли: 1ПМ = w × (1 + reps/30) Стандарт индустрии; при reps ≤ 10 различие с Бжицки < 2 %
6. UI телефона Jetpack Compose + Material 3 (тёмная тема по умолчанию) Актуальный стек Kotlin; крупные зоны касания — Min touch target 48 dp (Ф-3.6)
7. Модели часов v1 Таргет fenix6 (Fenix 6/6S/6X, включая Pro/Sapphire — одно разрешение 260×260); добавление моделей — новыми таргетами по фактическому парку (шаг 7) Якорное устройство ТЗ; 6S/6X отличаются размером корпуса, не экраном
8. Сопоставление телефон ↔ .fit Окно ± 15 минут по времени начала (Ф-8.4); при нескольких кандидатах — ближайший по времени ✅ закрыт в requirements

2. Форматы обмена

2.1. Конверт синхронизации (телефон → сервер)

POST /api/v1/sync, JSON-массив записей (пакет ≤ 50), каждая:

{
  "client_guid": "uuid-v4",
  "entity": "workout | exercise | program_version | profile | workout_set | set_note",
  "op": "upsert",
  "data": { ...схема сущности... }
}

Ответ: { "accepted": ["guid1", ...] } (все или ничего — пакет атомарен; 409 при конфликте версии программы — перезапись на телефоне после перечитывания). Повтор пакета безопасен: сервер отвечает тем же accepted по уже принятым GUID (идемпотентность, Ф-7.4).

2.2. Сущности data (ключевые поля)

  • exercise: exercise_id (uuid), name, group (push|pull|legs|core|…), is_base (bool), is_active (bool);
  • workout: workout_id (uuid), started_at/ended_at (UTC ISO 8601), status (active|finished|cancelled), program_version_id (nullable), avg_hr;
  • workout_set: workout_id, exercise_id, seq, weight_kg, reps, done_at, source (voice|watch|edit), in_plan (bool);
  • profile: sex, birth_year, weight_kg, hr_max (nullable);
  • program_version: JSON-схема программы из дизайна LLM-планирования (разд. 2) + athlete_id (выводится из ключа, в теле не передаётся).

2.3. Канонический JSON тренировки (сервер, из .fit)

Из FitParser → workout_facts.facts_json: session (тип, старт, длительность), laps[] (индекс, старт, длительность), records[] (t_offset_s, hr), sets[] (упражнение/повторы/вес — если писались), fit_file_id, device. Схема версионная: "schema": "workout-facts/1".

2.4. CSV-выгрузка (AdminWeb)

Разделитель — ;, кодировка UTF-8 c BOM (Excel), заголовок первой строкой: athlete;date;duration_min;avg_hr;sets;exercises;adherence_pct. Строка — тренировка; выборка = текущий фильтр таблицы.

3. Спецификация потребления Kit.Helpers

Библиотека — 3-app\1-lib\Kit.Helpers (std1, net10.0, Npgsql 10); потребление без изменений исходников (полная спецификация — kit-helpers-spec.md):

  • Db.Postgres.SqlServerHelper — доступ к PostgreSQL (единственный путь к БД из кода сервера; строка подключения — Db.ConnectionString);
  • Log.Logger — файловые логи сервера (Log.Path, уровень Info);
  • JsonHelper — сериализация фактов/программ;
  • остальное (Cache, Http, Session) в v1 не потребляется.

4. Схема конфигурации и правила валидации

4.1. Телефон (Preferences)

Параметр Тип Валидация
Server.BaseUrl string абсолютный https:// URL — иначе экран настроек не даёт сохранить
Server.ApiKey string непустой
Sync.WifiOnly bool —
Hr.MaxBpm int? 120–240 или пусто (расчёт по Танаке)
Rest.DefaultSec int 30–600

4.2. Сервер (appsettings.json)

Параметр Тип Валидация (startup, --mode *)
Api.Key string непустой; уникальность среди Api.Athletes[].apiKey
Api.Athletes list {name} непусто, {apiKey} непустой; дубликаты ключей — фатально
Admin.Login / Admin.PasswordHash string непустые; хэш — PBKDF2 (100k итераций), префикс pbkdf2$
Db.ConnectionString string тест подключения при старте — фатально при неудаче
Garmin.Login/Password string обязательны для --mode fetch (иначе код возврата 1)
Garmin.DownloadDir / Import.Dir / Import.ArchiveDir path создание при старте; недоступность — фатально
Progression.BaseStepKg double 0,5–10
Progression.BackoffPercent int 1–20
Deload.MaxWeeks int 3–12
Log.Path / Log.Level — уровень: Debug/Info/Error

Валидация конфигурации выполняется при старте любого режима; ошибки — в лог и stderr, код возврата 1 (раздел 2.3 requirements).

5. Технические решения по компонентам

  • FitFetcher: реализация логина Garmin Connect (SSO-последовательность) на C# — по контракту семьи библиотек garminconnect (подписи запросов, OAuth1-заголовок); при блокировке — решение ОВ шага 7 (fallback: папка импорта уже работает независимо). Пометка прототипа итерации 1.
  • FitParser: официальный Garmin FIT SDK for C#; парсинг — потоковый, файл читается один раз; сбой одного файла не останавливает пакет (код возврата 2).
  • SyncQueue: таблица Room sync_pending(guid, entity, payload, created_at); отправка — WorkManager (сетевое ограничение учитывает Sync.WifiOnly); ретрай — экспоненциально, 1 мин → 64 мин.
  • VoiceInput: Vosk-API (Kaldi) как service в процессе приложения; грамматика — JSON-словарь [«подход», «вес», «отдых», «на», «секунд», 0–999]; числа — постобработка («сто двадцать» → 120).
  • AdminWeb: Razor Pages, сессия по cookie после логина; пароль — PBKDF2 (см. 4.2); графики — Chart.js (локальный ассет, без CDN).
  • HTTPS: обратный прокси (nginx/Caddy) терминирует TLS; Kestrel — только localhost — вопрос развёртывания, вне кода.

6. Чек-лист шага 7 (проверки прототипами)

  1. Companion-канал: пульс/таймер доходят на телефон (ОВ-3) — итерация 1.
  2. Логин Garmin Connect из C# (FitFetcher) — итерация 2.
  3. Vosk: точность команд в шумном зале ≥ КУ-1 — итерация 2.
  4. Сопоставление телефон ↔ .fit на реальной тренировке — итерация 3.