63 lines
3.2 KiB
Markdown
63 lines
3.2 KiB
Markdown
# Спецификация 01: FitParser — канонический факт workout-facts/1
|
||
|
||
Источники: tech-req 2.3, Ф-8.2, Ф-13.4. SDK — `Garmin.FIT.Sdk`
|
||
(NuGet 21.218.0), namespace `Dynastream.Fit`, событийный
|
||
`Decode.MesgEvent`.
|
||
|
||
## 1. Сигнатура
|
||
|
||
```csharp
|
||
public sealed record FitFacts(
|
||
string FactsJson, // workout-facts/1, разд. 2
|
||
DateTime StartedAt, // session start (UTC)
|
||
int? AvgHr, // среднее records.hr (округление AwayFromZero)
|
||
int? HrRecoveryS, // разд. 3
|
||
string FitFileId, // file_idmesg.time_created (UTC ISO) + серийник
|
||
string Device); // device_infomesg (продукт/версия) или ""
|
||
```
|
||
|
||
`Parse(string path)` — файл читается один раз (потоково,
|
||
tech-req 5); `Decode.CheckIntegrity` перед разбором; кривой файл →
|
||
`FitException` (обрабатывает ImportService — spec 04).
|
||
|
||
## 2. Схема JSON
|
||
|
||
```json
|
||
{"schema": "workout-facts/1",
|
||
"session": {"type": "strength_training", "start": "2026-10-18T18:00:00Z",
|
||
"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": "..."}
|
||
```
|
||
|
||
| Сообщение | Что берём |
|
||
|---|---|
|
||
| `FileIdMesg` | `TimeCreated` (UTC) + `SerialNumber` → `fit_file_id` (`"{serial}-{yyyyMMddTHHmmssZ}"`); оба пусты → имя файла |
|
||
| `SessionMesg` | `Sport`/`SubSport` (строкой), `StartTime`, `TotalTimerTime` → `duration_s` (сек, округление) |
|
||
| `LapMesg` | index по порядку, `StartTime`, `TotalTimerTime` |
|
||
| `RecordMesg` | `Timestamp` → `t_offset_s` (от session start), `HeartRate` (есть и > 0) |
|
||
| `SetMesg` | index по порядку, `NumReps`, `Weight` (кг; 0xFFFF/0 — нет) |
|
||
| `DeviceInfoMesg` | последний (свежий) `Product`/`GarminProduct` → `device` (строкой) |
|
||
|
||
- Session нет → `FitException` (файл не активность).
|
||
- `sets`/`laps`/`records` пустые → пустые массивы (не null).
|
||
- Тип сессии — как есть строкой (`strength_training`, `cardio`, …);
|
||
фильтра по типу импорт не делает (решает сопоставление).
|
||
|
||
## 3. hr_recovery_s (Ф-13.4)
|
||
|
||
Конец последнего Lap (`start + duration`) → первый Record строго
|
||
после него с `hr < 90` (порог `Safety.RestingHrSitting` — конфиг,
|
||
Р3-А): `hr_recovery_s = t_offset(record) − конец последнего lap в
|
||
offset`. Нет записей после последнего Lap или не опустился → `null`.
|
||
|
||
## 4. Тест-фикстуры
|
||
|
||
Генерируются в тестах энкодером SDK (`Encode`, `Mesg`-объекты в
|
||
прямом порядке FileId → DeviceInfo → Record* → Lap* → Set* → Session):
|
||
полная активность (3 рекорда до/1 после Lap с hr < 90, Set с
|
||
reps/weight), активность без Set, битый файл (усечённый байтовый
|
||
массив).
|