7.2 KiB
Требования к модулю: 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, NuGetGarmin.FIT.Sdk— tech-req разд. 5, «официальный FIT SDK for C#»):FitParser: потоковый разбор .fit (событияDecode):Session(типstrength_training, старт, длительность),Lap,Record(пульс),Set(повторения/вес) → канонический JSONworkout-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.csServer.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. Инварианты (🛡 — нарушать запрещено)
- Изоляция атлета — все записи по
athleteId, выведенному из подпапки; чужие данные не читаются и не пишутся. - Сопоставление не разрушает — .fit дописывает
facts_json/avg_hr/hr_recovery_s/fit_file_idв строку; поля телефона (статус, session_kind, подходы) не трогаются. - Идемпотентность — повторный импорт того же .fit (по
fit_file_id) — пропуск; повторный импорт пакета (поexternal_id) — новая версия, существующие не переписываются (🛡 неизменяемость версий). - Сбой одного файла не останавливает пакет (tech-req 5) — файл
остаётся в
Import.Dir, итоговый код возврата 2. - API пишет факт-данные только из sync и импорта — импорт не
создаёт/не меняет ничего, кроме
workout(+sets нет)/program/program_version/exercise(создание отсутствующих упражнений пакета).
4. Критерии приёмки модуля
- .fit с Session/Lap/Record/Set разбирается:
facts_jsonсоответствует схемеworkout-facts/1,avg_hr/hr_recovery_sпосчитаны; сопоставление с телефонной тренировкой в окне ± 15 минут — факты в её строке; вне окна — строка «только .fit». - Повторный импорт того же .fit — пропуск (0 записей); .fit с чужим
fit_file_id— импорт. - Пакет программы: первая загрузка создаёт программу+версию,
упражнения отсутствующие создаются; повторный импорт — новая версия
(max+1),
external_idсохраняется. - TCX/GPX — упрощённый факт, архивируются.
- Сбой одного файла не мешает остальным; код возврата 2; сбойный файл
остаётся в
Import.Dir. - Тесты интеграционные против docker
watchbody-pg— зелёные; сборка решения без ошибок.