swim-fit-parse
Garmin 랩수영 .FIT 파일에서 결정론적으로 지표를 추출하는 파서.
.FIT은 바이너리라 LLM 컨텍스트에 직접 넣을 수 없다. 이 스크립트가
1세션을 약 450토큰의 텍스트로 축약해 방출한다(record 1Hz 시계열은
hrr_60 산출에만 소비하고 출력하지 않는다).
설치
pip install swim-fit-parse
실행
python -m swim_fit_parse <FIT 경로...>
swim-fit-parse <FIT 경로...> 명령도 동일하게 동작한다.
여러 파일을 넘기면 시작 시각 인접성으로 하나의 세션으로 조립한다 (워치가 한 세션을 전/후로 나눠 저장한 경우).
출력
- 세션 헤더 — 프로그램명·풀 길이·거리·시간·스트로크·HR·수온
- 랩표 — 랩별 거리/시간/페이스/SWOLF/스트로크/영법/HR
block_metrics— 블록 단위 집계- 검출 플래그 — F1~F6
첫 줄에 schema_version / parser_tag 스탬프가 찍힌다.
block_metrics 포맷
bm1| 접두가 붙은 뒤 JSON 배열이 온다.
bm1|[{...},{...}]
접두는 장식이 아니라 필수다. Notion MCP 커넥터가 텍스트 속성 값을 JSON으로 선파싱하는데 최상위가 배열이면 타입을 거부한다. 마커가 붙으면 파싱이 깨져 원문 그대로 저장된다.
소비 측은 value.split("|", 1)[1]을 json.loads에 넘긴다. bm1은
페이로드 스키마 버전이며 구조 변경 시 bm2로 올린다.
미측정 값은 null이다. Garmin은 swim_stroke='drill' length에서
스트로크를 세지 않으므로, total_cycles == 0인 블록은
cycles_per_length · swolf · dps_m · spi를 전부 null로 방출한다.
0은 "0회 측정"이고 null은 "미측정"이다.
검출 플래그
거리는 추정치가 아니라 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 교정으로 해소되지 않는
경우에만 쓴다.
블록 지표 규격
fade= 후반부 평균 / 전반부 평균 − 1. 양수면 후반에 느려진 것이다. 양 끝점(첫 rep vs 마지막 rep) 비교는 중간을 전부 버리고 이상치 하나에 좌우되므로 쓰지 않는다. n이 홀수면 중앙 rep은 양쪽에서 제외하고, n<4면 반쪽 평균이 성립하지 않으므로null이다.hrr_60= 블록 종료 시점 HR − 60초 후 HR. 후속 휴식이 60초 이상이고 그 안에 다음 활성 랩이 시작되지 않는 블록만 산출하며, 미충족은null이다 (0은 금지 — 오염된 값을 회복 저하로 오독하게 된다). 세션 마지막 활성 랩은 뒤따르는 idle이 세션 종료지 회복 구간이 아니므로 제외한다. 게이트가 평가한 실제 휴식 길이는hrr_rest_s로 함께 방출되어 감사 가능하다 — rep 사이 휴식인rest_median_s와는 다른 값이다.
설계 원칙
파서는 결정론적 추출만 한다. 판정하지 않고, 개인 상수(연령·체중·기준 기록·게이트 임계)를 반입하지 않는다.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file swim_fit_parse-3.3.tar.gz.
File metadata
- Download URL: swim_fit_parse-3.3.tar.gz
- Upload date:
- Size: 18.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bdf2f1ac775f6c289c809012b6b4c31277da0a77d70686c9b95ad6b32e141478
|
|
| MD5 |
b74c9994c6b75d6500c1681e752a1137
|
|
| BLAKE2b-256 |
d82a77da0e00974d1e4d25893805ba2ea7a7b1482256ec07f015c4dd23a42f8d
|
File details
Details for the file swim_fit_parse-3.3-py3-none-any.whl.
File metadata
- Download URL: swim_fit_parse-3.3-py3-none-any.whl
- Upload date:
- Size: 20.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1403dc6efa82e9d1763608184877baf85199a1383b9c99dd76fb9f65f356a481
|
|
| MD5 |
8193aa02db15ec3b63ee567c1832e3a4
|
|
| BLAKE2b-256 |
85e9608bfc5e9e5017774dae8561b650695b53fc581cd579fa33b80a5a301747
|