Шаг 4 утверждён; шаг 5: tech-requirements.md (закрытие ОВ, форматы обмена, конфигурация) + спецификация Kit.Helpers
This commit is contained in:
parent
112f55c43c
commit
2b3ce40673
|
|
@ -12,8 +12,8 @@
|
|||
| 1. Инициация разработки | `1-docs\1-reqs\vision.md` (утверждён 2026-10-17, v1.0), `1-docs\0-mng\plan.md` | ✅ |
|
||||
| 2. Создание рабочей области | `readme.md` системы, структура репозитория, `.gitignore` | ✅ |
|
||||
| 3. Разработка функциональных требований | `1-docs\1-reqs\requirements.md` (утверждён 2026-10-17) | ✅ |
|
||||
| 4. Разработка архитектуры | `1-docs\2-arch\architecture.md`, `hld.puml` (+ рендер) | 🔄 |
|
||||
| 5. Разработка технических требований | `1-docs\3-tech\tech-requirements.md`, спецификации библиотек | ⬜ |
|
||||
| 4. Разработка архитектуры | `1-docs\2-arch\architecture.md`, `hld.puml` (+ рендер) (утверждена 2026-10-17) | ✅ |
|
||||
| 5. Разработка технических требований | `1-docs\3-tech\tech-requirements.md`, спецификации библиотек | 🔄 |
|
||||
| 6. Проектирование баз данных | решение о составе данных (фиксируется здесь) | ⬜ |
|
||||
| 7. Итерационная разработка | `3-app\` (код), `1-docs\4-modules\` (документы модулей) | ⬜ |
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
> дизайн LLM-планирования (разд. 8), документ форматов.
|
||||
> Схема компонентов: [hld.puml](hld.puml) (+ рендер [hld.svg](hld.svg),
|
||||
> [hld.png](hld.png)).
|
||||
> Версия: черновик 1 (2026-10-17).
|
||||
> Версия 1.0 — **УТВЕРЖДЕНО** (2026-10-17).
|
||||
|
||||
## 1. Компоненты и размещение
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
# Спецификация потребления: Kit.Helpers
|
||||
|
||||
> Спецификация разделяемой библиотеки в объёме потребления системы
|
||||
> VEU.WathBody (шаг 5 процесса). Библиотека развивается вне проекта
|
||||
> (`3-app\1-lib\Kit.Helpers`, net10.0, Npgsql 10) — здесь фиксируется
|
||||
> только контракт использования; исходники не изменяются.
|
||||
|
||||
## Потребляемые компоненты (v1)
|
||||
|
||||
| Компонент | Использует | Назначение в VEU.WathBody |
|
||||
|---|---|---|
|
||||
| `Db.Postgres.SqlServerHelper` | `FitParser`, `MetricsEngine`, `ProgramStore`, `RestApi`, `AdminWeb` | Единственный путь к PostgreSQL: выполнение хранимых функций слоя `src`/`ref` (шаг 6) |
|
||||
| `SqlServerHelperFluent` | те же | Fluent-обёртка вызовов функций |
|
||||
| `Log.Logger` | все серверные компоненты | Файловые логи; `Log.Path`, уровень из конфигурации |
|
||||
| `JsonHelper` | `ProgramStore`, `FitParser` | Сериализация/десериализация версий программ и фактов (`facts_json`) |
|
||||
|
||||
## Контракты
|
||||
|
||||
- **БД:** соединение — из `Db.ConnectionString`; каждый вызов функции БД —
|
||||
короткое соединение (без долгоживущих транзакций); ошибки — исключения
|
||||
библиотеки, оборачиваются компонентом в лог + код ответа API.
|
||||
- **Логи:** уровень из конфигурации; в лог не пишутся ключи, пароли
|
||||
и хэши (инвариант «Тайны», requirements раздел 5); ротация — средствами
|
||||
Logger по дням.
|
||||
- **JSON:** `JsonHelper` — единственная точка сериализации программ
|
||||
(стабильный порядок полей — диффы версий сравнимы).
|
||||
|
||||
## Не потребляется в v1
|
||||
|
||||
`Cache` (нет кэшируемых справочников большого объёма), `Http.*`
|
||||
(Fetcher реализует собственный клиент Garmin — FluentHttpClient может
|
||||
быть подключён в итерации 2 после прототипа), `Session`, `HttpContext`.
|
||||
|
||||
## Версии и обновления
|
||||
|
||||
Фиксируется прямой ссылкой на проект `3-app\1-lib\Kit.Helpers` (monorepo);
|
||||
обновления библиотеки — только после прогона тестов сервера
|
||||
(`c-tst`); Breaking-изменения API библиотеки — повод пересмотреть
|
||||
настоящую спецификацию.
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
# Технические требования: VEU.WathBody («Феникс 6X») v1
|
||||
|
||||
> Шаг 5 процесса разработки (`std1\_process\process.md`).
|
||||
> Источники: [vision.md](../1-reqs/vision.md) v1.2,
|
||||
> [requirements.md](../1-reqs/requirements.md) (утверждён, раздел 7 —
|
||||
> открытые вопросы), [architecture.md](../2-arch/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), каждая:
|
||||
|
||||
```json
|
||||
{
|
||||
"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](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.
|
||||
Loading…
Reference in New Issue