# Требования к модулю: 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` — зелёные; сборка решения без ошибок.