Metadata-Version: 2.4
Name: swim-fit-parse
Version: 3.2
Summary: Deterministic Garmin lap-swimming .FIT parser — session/lap/block metrics with length-level anomaly detection
Project-URL: Homepage, https://github.com/ssim-baa/swim-fit-parse
Project-URL: Source, https://github.com/ssim-baa/swim-fit-parse
Author: ssim-baa
License: MIT
Keywords: fit,fitparse,garmin,lap-swimming,swimming
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Requires-Python: >=3.9
Requires-Dist: fitparse>=1.2
Description-Content-Type: text/markdown

# swim-fit-parse

Garmin 랩수영 `.FIT` 파일에서 결정론적으로 지표를 추출하는 파서.

`.FIT`은 바이너리라 LLM 컨텍스트에 직접 넣을 수 없다. 이 스크립트가
1세션을 약 450토큰의 텍스트로 축약해 방출한다(record 1Hz 시계열은
hrr_60 산출에만 소비하고 출력하지 않는다).

## 설치

```
pip install fitparse
```

## 실행

```
python fit_parse.py <FIT 경로...>
```

여러 파일을 넘기면 시작 시각 인접성으로 하나의 세션으로 조립한다
(워치가 한 세션을 전/후로 나눠 저장한 경우).

## 출력

1. 세션 헤더 — 프로그램명·풀 길이·거리·시간·스트로크·HR·수온
2. 랩표 — 랩별 거리/시간/페이스/SWOLF/스트로크/영법/HR
3. `block_metrics` JSON — 블록 단위 집계
4. 검출 플래그 — F1~F6

첫 줄에 `schema_version` / `parser_tag` 스탬프가 찍힌다.

## 검출 플래그

거리는 추정치가 아니라 **length 카운트**다(각 active length = 정확히
`pool_length`). 따라서 실패 모드는 거리 오추정이 아니라 턴 미검출로 인한
length 병합·오생성이다.

1차 지표는 **스트로크**다. 제자리 정지 후 재출발하면 시간은 오염되지만
스트로크는 오염되지 않으므로, 시간 기반 판정이 실패하는 지점에서
스트로크 기반이 작동한다. 시간 비율은 보조 지표로 병기한다.

| 플래그 | 조건 | 의미 |
|---|---|---|
| F1 | strokes < 중앙값 × 0.65 | 분할 아티팩트 — 거리 과대 기록 |
| F2 | strokes ≥1.6× **그리고** duration ≥1.6× | 턴 미검출 — 거리 과소 기록 |
| F3 | duration ≥1.4×, strokes 0.75~1.25 | 시간 오염 — **거리는 정확, 병합 금지** |
| F4 | length 영법 ≠ 설계 영법 | 영법 오검출 |
| F5 | 실측 본수 ≠ `repeat_value` | 구조 불일치 |
| F6 | `wkt_step_index` 없음 | 루틴 외 추가 (정보) |

정규화 기준은 **동일 파일 내 동일 `wkt_step_index` 그룹의 중앙값**이다.
`wkt_step_index`는 파일 내에서만 유일하므로 다파일 세션에서 세션 단위로
풀링하면 무관한 블록이 한 그룹이 된다. 그룹 n<5는 중앙값이 모집단
통계로 성립하지 않아 "평가 불가"로 출력한다(침묵하지 않는다).

### F1 확정 테스트

| 테스트 | 조건 | 커버 |
|---|---|---|
| C1 설계 대조 | 초과 length 수 E = 랩 후보 수 | 조각이 **추가**된 경우 |
| C2 합산 복원 | 인접 후보 스트로크 합이 0.75~1.25 복원 | 하나가 **쪼개진** 경우 |
| C3 미확정 | 둘 다 미성립 | 제안 없음 — 문진 대상 |

C1·C2는 상보적이다. C1이 확정되면 C1을 우선 적용한다.

## 교정 적용

플래그는 검출과 제안까지만 한다. **자동 적용하지 않는다.**

확정된 F1에 대해 파서가 제안 인자를 출력한다:

```
python fit_parse.py <FIT> --drop-lengths 36,37
```

거리는 `잔여 active length 수 × pool_length`로 자동 산출된다 — 사람이
거리 숫자를 입력하지 않으므로 입력 오류 클래스가 소멸한다. 제거된
length의 시간·스트로크는 앞 생존 length로 흡수된다(버리지 않는다 —
실제로 헤엄친 물이므로).

`--merge <lap>+<lap>`은 폴백이다. length 교정으로 해소되지 않는
경우에만 쓴다.

## 설계 원칙

파서는 결정론적 추출만 한다. 판정하지 않고, 개인 상수(연령·체중·기준
기록·게이트 임계)를 반입하지 않는다.
