VEU.WathBody/1-docs/4-modules/m06-fit-import/module_reqs.md

103 lines
7.2 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.

# Требования к модулю: m06-fit-import
> Модуль — проект `Server.WatchBody.Import` (класс-библиотека) и режим
> `--mode import` единой точки входа CLI: разовый импорт .fit/.tcx/.gpx
> и пакета программы `program-import.json` из `Import.Dir`, разбор
> .fit в канонический факт `workout-facts/1` → `src.workout.facts_json`,
> сопоставление с тренировкой телефона (± 15 минут), архивирование.
> Источники: Ф-8.2–8.4, requirements 2.3, tech-req 2.3/5 (FitParser),
> `3-tech\program-import-format.md`, закрытые вопросы 10 (ОВ-1-А) и 12
> (О-4-Б). Самодостаточная копия требований (d-dev-6).
## 1. Назначение и границы
**В границах модуля:**
- проект `Server.WatchBody.Import` (ссылается на Core, NuGet
`Garmin.FIT.Sdk` — tech-req разд. 5, «официальный FIT SDK for C#»):
- `FitParser`: потоковый разбор .fit (события `Decode`):
`Session` (тип `strength_training`, старт, длительность), `Lap`,
`Record` (пульс), `Set` (повторения/вес) → канонический JSON
`workout-facts/1` (tech-req 2.3) + `avg_hr`, `hr_recovery_s`
(возврат к < 90 после последнего Lap, Ф-13.4), `fit_file_id`,
время старта;
- `TcxGpxParser`: .tcx/.gpx (XML) — упрощённый факт (тип, старт,
длительность, `records[]` пульса; подходов в этих форматах нет);
- `WorkoutImporter`: дедупликация по `fit_file_id`
(`UNIQUE(athlete_id, fit_file_id)` — дубль → пропуск без ошибки);
сопоставление тренировке телефона по `started_at` (окно ± 15 минут,
Ф-8.4) — факты пишутся в ту же строку; не сопоставилась — новая
строка «только .fit» (uuid генерирует сервер, Р2-Б); 🛡
сопоставление не удаляет и не переписывает телефонные данные;
- `ProgramPackageImporter`: пакет `program-import.json` →
`src.program` по `external_id` / `program_version` (повтор пакета —
новая версия max+1, формат — program-import-format.md; отсутствующие
упражнения библиотеки создаются, группа — по разделу фокуса, иначе
core + лог);
- `ImportService`: обход `Import.Dir\<имя атлета>\` (атрибуция файлов
атлету по подпапке — решение гейта 1.3), маршрутизация по
расширению, обработанные → `Import.ArchiveDir`, сбойные остаются
с записью в лог;
- режим `--mode import` в `Program.cs` `Server.WatchBody.Web` (единая
точка входа CLI — requirements 2.3): коды возврата 0 (успех/нет
новых) / 1 (авария конфигурации) / 2 (частичный сбой);
- конфиг `Import.Dir` / `Import.ArchiveDir` (требования разд. 3);
- тесты: интеграционные против docker `watchbody-pg`; .fit-фикстуры
генерируются энкодером SDK (`Encode`) в тестовом коде.
**Вне границ модуля:** забор из Garmin Connect (m07 — `FitFetcher`,
дедупликация та же), admin REST (m08), метрики по фактам (m04
пересчитает при следующем sync — wiring не входит), push-уведомления,
повторный разбор существующих `facts_json`.
## 2. Канонический факт `workout-facts/1` (tech-req 2.3)
```json
{"schema": "workout-facts/1",
"session": {"type": "strength_training", "start": "...ISO...",
"duration_s": 3600},
"laps": [{"index": 0, "start": "...", "duration_s": 120}],
"records": [{"t_offset_s": 0, "hr": 95}],
"sets": [{"index": 0, "reps": 12, "weight_kg": 100.0}],
"fit_file_id": "...", "device": "..."}
```
В `src.workout` дополнительно пишутся: `started_at` (из session),
`avg_hr` (среднее records), `hr_recovery_s` (секунды от конца
последнего Lap до первого Record с hr < 90; не достигнуто — null).
## 3. Инварианты (🛡 — нарушать запрещено)
1. **Изоляция атлета** — все записи по `athleteId`, выведенному из
подпапки; чужие данные не читаются и не пишутся.
2. **Сопоставление не разрушает** — .fit дописывает `facts_json`/
`avg_hr`/`hr_recovery_s`/`fit_file_id` в строку; поля телефона
(статус, session_kind, подходы) не трогаются.
3. **Идемпотентность** — повторный импорт того же .fit (по
`fit_file_id`) — пропуск; повторный импорт пакета (по
`external_id`) — новая версия, существующие не переписываются
(🛡 неизменяемость версий).
4. **Сбой одного файла не останавливает пакет** (tech-req 5) — файл
остаётся в `Import.Dir`, итоговый код возврата 2.
5. **API пишет факт-данные только из sync и импорта** — импорт не
создаёт/не меняет ничего, кроме `workout`(+sets нет)/`program`/
`program_version`/`exercise` (создание отсутствующих упражнений
пакета).
## 4. Критерии приёмки модуля
1. .fit с Session/Lap/Record/Set разбирается: `facts_json` соответствует
схеме `workout-facts/1`, `avg_hr`/`hr_recovery_s` посчитаны;
сопоставление с телефонной тренировкой в окне ± 15 минут — факты в
её строке; вне окна — строка «только .fit».
2. Повторный импорт того же .fit — пропуск (0 записей); .fit с чужим
`fit_file_id` — импорт.
3. Пакет программы: первая загрузка создаёт программу+версию,
упражнения отсутствующие создаются; повторный импорт — новая версия
(max+1), `external_id` сохраняется.
4. TCX/GPX — упрощённый факт, архивируются.
5. Сбой одного файла не мешает остальным; код возврата 2; сбойный файл
остаётся в `Import.Dir`.
6. Тесты интеграционные против docker `watchbody-pg` — зелёные;
сборка решения без ошибок.