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

7.2 KiB
Raw Blame History

Требования к модулю: 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)

{"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 — зелёные; сборка решения без ошибок.