
Garmin Export 데이터를 정규화하여 AI가 즉시 분석할 수 있는 데이터 만들기
요약
Garmin의 복잡한 Export 데이터를 AI가 즉시 분석할 수 있도록 정규화하는 프로세스와 메커니즘을 소개합니다. 데이터 구조 탐색과 전처리 과정을 줄여 분석 시작 시간을 단축하는 데 초점을 맞춥니다.
핵심 포인트
- Garmin 데이터의 파편화된 구조(Activities, FIT, Gear 등) 문제 해결
- 데이터와 '읽는 법(Schema/Context)'을 세트로 전달하여 AI 분석 효율화
- Activity와 FIT 데이터 간의 관계를 명시적으로 연결하여 데이터 무결성 확보
- 반복적인 데이터 탐색 및 전처리 비용을 최소화하는 정규화 워크플로우 구축
Garmin의 Export 데이터에는 Activity, FIT, Gear, Personal Records 등 러닝을 되돌아보기 위한 수많은 정보가 포함되어 있습니다.
하지만 파일 세트를 AI에게 전달하는 것만으로는 즉시 분석을 시작할 수 있다고 보장할 수 없습니다. 어떤 파일이 원본인지, 데이터 한 건이 무엇을 나타내는지, Activity와 FIT는 어떻게 대응되는지, 단위나 결손(missing value)을 어떻게 처리할지 등, 분석 전에 매번 동일한 탐색과 판단이 필요합니다.
그래서 만든 것이 Garmin Running Data Normalizer입니다.
핵심 아이디어는 간단합니다.
Garmin Export를 정규화하여, 데이터와 '그 읽는 법'을 세트로 AI에게 전달하는 것입니다.
데이터 구조의 탐색이나 관계 추측을 매번 반복하지 않고, 분석 그 자체부터 쉽게 시작할 수 있도록 합니다.
이것이 "AI가 일절 전처리(Preprocessing)를 하지 않아도 된다"거나 "어떤 데이터든 완전히 자동 분석할 수 있다"는 의미는 아닙니다. 매번 발생하는 탐색·추측·전처리를 줄여 분석 시작까지의 시간을 단축하기 위한 메커니즘입니다.
공개된 정규화 열(normalized column)로 답할 수 있는 기본 분석은 정규화 데이터와 스키마(Schema)·분석 컨텍스트(Context)로부터 시작할 수 있습니다. 정규화 열에 없는 지표까지 확인하고 싶은 경우에도, 확인된 Activity–FIT 링크를 기점으로 대응하는 FIT raw 데이터만 추가로 분석할 수 있습니다.
이 기사에서는 Garmin Export를 그대로 분석할 때 어떤 문제가 발생하는지, 그것을 정규화 데이터로서 어떻게 정리했는지, 실제 러닝 분석으로 어떻게 이어졌는지를 소개합니다.
Garmin Export에는 정보가 있지만, 그대로는 분석하기 어렵다
Garmin Account Data Export에는 JSON이나 FIT를 포함한 여러 데이터군이 있습니다. 각각 데이터 한 건이 나타내는 입도(Granularity)와 역할이 다릅니다.
| 데이터 | 주요 입도 |
|---|---|
| Activities | 1회의 Activity |
| ... |
분석하고 싶은 값이 하나의 거대한 표로 모여 있는 것이 아닙니다. 활동 개요는 Activities, 세부적인 랩(Lap) 값은 FIT, 슈즈는 Gear와 같은 식으로 나누어져 있습니다.
그대로 다룰 경우, 분석할 때마다 다음과 같은 준비가 발생합니다.
- 필요한 파일 찾기
- JSON이나 FIT의 구조 조사하기
- 데이터 한 건의 입도 확인하기
- Activity와 FIT 대응시키기
- 거리, 시간, 속도 등의 단위 맞추기
- 결손(Missing value)과 미분석 데이터 구분하기
- 분석용 표 만들기
한 번뿐이라면 수작업으로도 대응할 수 있습니다. 하지만 월별 집계, 슈즈별 집계, 레이스 비교를 할 때마다 동일한 준비를 반복하면 처리 조건이 조금씩 달라집니다. AI에게 재추측을 맡기면, 동일한 입력이라도 결합(Join)이나 결손 처리 방식이 흔들릴 가능성이 있습니다.
문제 1: Activity와 FIT를 매번 연결해야 함
Activities에는 활동 개요가, FIT에는 Session이나 Lap의 상세 정보가 있습니다. 하지만 두 데이터는 처음부터 하나의 분석용 레코드로 제공되지 않습니다.
후보를 찾으려면 일시, Activity ID, 거리, 시간, 경기 종목, 파일 중복 등을 확인해야 합니다. 시간이 비슷하다는 이유만으로 같은 Activity라고 단정 지으면, 다른 운동을 잘못 연결할 가능성이 있습니다.
Garmin Running Data Normalizer에서는 확인된 관계를 다음과 같은 독립된 결과물로 기록합니다.
normalized/activities.json
normalized/fit_sessions.json
normalized/activity_fit_links.json
Activity와 FIT Session의 결합은 normalized/activity_fit_links.json에 존재하는 garmin_activity_key와 fit_session_key의 쌍만을 정본(Source of truth)으로 삼습니다. 비슷한 일시나 거리를 발견하더라도, 링크 결과물에 없는 관계를 분석 측에서 새로 만들지 않습니다.
공개 사양에서는 링크 후보가 양방향으로 유일하며, 일시에 더해 거리, 시간, 경기 종목 등의 근거를 충족하는 경우에만 명시적 링크로 승격됩니다. 확인할 수 없는 것, 후보가 충돌하는 것, 구조상 대상 외인 것은 억지로 연결하지 않고 감사 정보(Audit information)로 남깁니다.
링크 행에는 fit_source_path와 fit_source_sha256이, normalized/fit_sessions.json에는 source_path와 source_sha256이 포함됩니다.
가 남습니다. 이는 상세 분석 시 원본 FIT 파일로 추적하기 위한 Provenance(출처 정보)이지만, Raw FIT 본체를 Run-All 출력물로 복제하는 것은 아닙니다.
한 사용자가 공개할 수 있는 검증 결과에 따르면, 3,468건의 Activities에 대해 3,464건의 Activity–FIT 관계를 명시할 수 있었습니다. 반면, 4건의 Activities와 1건의 대상 FIT Session은 미해결 상태로 남아 있습니다. 이는 성공률을 겨루는 숫자가 아니라, 근거가 도달한 범위를 나타내는 경계입니다.
상세한 조건과 검증 결과는 Dataset Relationship Catalog와 CS-002를 통해 공개하고 있습니다.
문제 2: FIT 내부 구조를 매번 해석해야 하는 문제
FIT는 단순한 표가 아닙니다. Message와 Field가 존재하며, 값에 따라 scale(배율)이나 offset(오프셋), 단위가 달라집니다. Session과 Lap은 입도(granularity)도 다릅니다. 결손을 나타내는 프로토콜상의 무효값(invalid value)도 존재합니다.
생(Raw) FIT를 매번 읽게 되면, 분석가나 구현 방식에 따라 다음과 같은 판단이 달라질 수 있습니다.
- 어떤 Message를 채택할 것인가
- Field를 어떤 단위로 변환할 것인가
- 무효값을 유효한 수치로 취급하고 있지는 않은가
- 여러 개의 Session과 Lap을 어떻게 할당할 것인가
- 불완전한 파일을 어디까지 분석 대상으로 삼을 것인가
이 도구에서는 FIT의 CRC를 확인하고, 공개 계약에서 다루는 Session·Lap 필드를 의미 있는 열(column)로 변환합니다. 예를 들어 FIT Lap에서는 total_distance, total_timer_time, avg_speed, avg_heart_rate, avg_cadence 등을 다룹니다. FIT 프로토콜의 무효값은 scale 변환이나 enum(열거형) 변환보다 앞서 null로 변환합니다.
중요한 것은 모르는 상태를 추측으로 바로잡지 않는 것입니다. 여러 Session의 Lap 할당을 일관되게 증명할 수 없는 경우에는 파일 전체를 정규화된 Session/Lap에서 제외하고, audit/fit_audit.json에 session_lap_allocation_conflict로 기록합니다.
'누구나 알 수 있는 절차'란 FIT를 단순화하여 설명을 생략하는 것이 아닙니다. 동일한 입력에 대해 동일한 확인과 변환을 재현할 수 있는 절차를 남기는 것입니다.
문제 3: Gear나 Lap 등이 여러 곳에 분산되어 있는 문제
모든 것을 하나의 거대한 표에 몰아넣으면 겉보기에는 편리해집니다. 하지만 Activity, Lap, Gear는 입도가 다릅니다. 거대한 표로 결합하면 Activity의 거리가 Lap 수만큼 중복되거나, Gear가 여러 개인 Activity에서 행(row)이 늘어나는 문제가 발생합니다.
따라서 데이터는 입도별로 분리된 상태를 유지하면서, 안정적인 키(key)와 확인된 링크를 통해 관계를 맺도록 합니다.
Activities
├─ Activity/Gear Links ─ Gear
├─ Personal Records
...
예를 들어 신발(shoes)별 거리를 집계할 때는 Activities와 Gear를 이름이나 날짜로 직접 연결하지 않습니다. normalized/activity_gear.json에 기록된 garmin_activity_key와 gear_key를 사용합니다.
정규화의 목적은 '무엇이든 하나로 섞는 것'이 아니라, 입도를 지키면서 관계를 파악할 수 있는 형태로 나누어 보유하는 것입니다.
문제 4: AI에게 매번 데이터 읽는 법을 설명해야 하는 문제
정규화된 JSON이나 CSV만 전달하는 것으로는 충분하지 않을 수 있습니다. AI는 다음과 같은 전제 조건을 알아야 합니다.
- 어떤 파일부터 읽기 시작해야 하는가
- 한 행(row)의 입도는 무엇인가
- 각 열(column)의 타입과 단위
- 공식적으로 허용된 결합(join)
- 공백이나
null의 의미 - 경고 및 부분적 성공의 영향
- 외부로 공유해서는 안 되는 정보
Run-All은 정규화된 데이터와 더불어, 읽는 법과 처리 상태를 함께 출력합니다.
사람이 가장 먼저 읽는 파일
START_HERE.md
DATASET_INVENTORY.md
ANALYSIS_HANDOFF.md
AI나 도구가 읽을 수 있는 기계 판독 가능(machine-readable) 파일
ANALYSIS_CONTEXT.json
SCHEMA_CATALOG.json
run_summary.json
run_manifest.json
ANALYSIS_CONTEXT.json에는 분석의 입구, 데이터군, 명시적인 관계, 경고, 금지 작업, 프라이버시 모드가 포함됩니다. SCHEMA_CATALOG.json
에는 필드(field), 타입(type), 단위(unit) 및 도메인(domain), 유래(origin), 프라이버시 민감도(privacy sensitivity)가 포함됩니다.
처리가 완료되었음을 나타내는 것은 마지막에 생성되는 run_summary.json입니다.
먼저 실행 상태와 경고를 확인한 후, START_HERE.md부터 읽어나갑니다.
"AI-ready"는 AI에게 필요한 문맥(context)을 공급할 수 있다는 의미입니다. AI의 답변이 자동으로 정확해진다는 보장은 아닙니다.
문제 5: 결측치나 불명확한 값을 추측하면 분석이 망가진다
공란에는 여러 가지 의미가 있습니다.
- 원본 데이터에 값이 없음
- 이번 Export에 포함되지 않음
- 파일은 있으나 해석할 수 없음
- Activity와 FIT를 매칭할 수 없음
- 해당 데이터군은 이번 입력의 대상에서 제외됨
이것들을 모두 0으로 바꾸면, "값이 없었던 것"과 "0이었던 것"을 구분할 수 없습니다. 매칭할 수 없었던 Activity를 유사한 기록에 연결하면, 분석 결과가 그럴싸해 보일지라도 근거를 잃게 됩니다.
이 때문에 정규화(normalization)에서는 다음을 준수합니다.
- 불명확한 값을 0이나 평균값으로 채우지 않는다
- 명시적인
null과 결락(omission)을 구분한다 - 경고, 제외 이유, 유래를 남긴다
PASS_WITH_WARNINGS와PARTIAL_SUCCESS를 완전한 성공과 혼동하지 않는다- 이후의 Export에서 보이지 않는 기록을 삭제된 것으로 즉시 단정하지 않는다
마지막 점은 여러 번의 Export를 지속적으로 사용할 때 중요합니다. v1.2.0에서 추가된 Snapshot Accumulation에서는, 이후의 Export에 없다고 해서 이를 삭제 명령으로 간주하지 않고, 관측 이력과 값의 상태를 유지합니다. 상세 내용은 별도의 주제이므로, 이 글에서는 "missing is not delete"라는 설계 사상만 다룹니다.
정규화 전과 정규화 후
정규화 전에는 AI 분석으로 넘어가기까지 많은 탐색이 필요했습니다.
Garmin Export
↓
필요한 파일을 찾음
...
정규화 후에는 데이터와 읽는 법을 하나의 핸드오프(handoff)로 다룰 수 있습니다.
Garmin Export
↓
Garmin Running Data Normalizer
...
더 자세한 지표가 필요한 경우에는 다른 경로를 사용합니다.
기본 분석:
정규화 데이터
+
...
물론, 분석 질문, 필요한 열(column), 집계 방법, 공개 범위는 매번 확인합니다. 줄일 수 있는 것은 Garmin 고유의 구조 탐색과, 이미 확인된 관계를 다시 추측하는 작업입니다.
정규화 후에 가능해진 분석 예시
여기서부터는 승인된 한 사용자의 실제 데이터 집계를 예로 듭니다. 공개하는 것은 집계값뿐이며, 원본 Garmin Export, 개별 Activity, ID, 정확한 일시, 파일명은 공개하지 않았습니다.
월간 주행 거리와 평균 페이스
analysis/activities.csv는 1 Activity당 1행입니다. activity_date_local, distance_m, duration_sec 등의 의미가 이미 정의되어 있으므로, 일시·거리·시간의 해석부터 다시 시작하지 않고 월별 집계로 바로 넘어갑니다.
2026년 1월부터 7월 중순까지를 집계하면 다음과 같습니다.
| 월 | 주행 거리 | 거리 가중 평균 페이스 |
|---|---|---|
| 1월 | 401.2 km | 4:49/km |
| ... |
7월은 월 중간의 값이며, 1개월분으로 외삽(extrapolation)하지 않았습니다. 이 예에서 AI에게 맡긴 것은 월별 집계와 표현 후보입니다. 대상 Activity를 선택하는 방법, 평균 페이스 공식, 결측치 처리, 7월을 경과 중인 것으로 표시하는 판단은 사람이 확인했습니다.
2026년에 자주 신은 슈즈
슈즈별 집계에서는 Activities와 Gear를 normalized/activity_gear.json의 명시적 링크로 연결합니다. 슈즈 이름의 유사성이나 Activity의 날짜를 통해 AI가 관계를 추측할 필요가 없습니다.
기간을 2026년 1월 1일부터 7월 12일까지로 한정하고, running의 Activity–Gear 링크를 Activity/Gear의 고유한 쌍(pair)으로 집계한 TOP 5입니다.
| 순위 | 슈즈 | 링크된 주행 거리 |
|---|---|---|
| 1 | On Cloudsurfer Next | 134.7 km |
| ... |
이것은 누적 전체 기간 랭킹이 아닙니다. 지정된 기간 내에 명시적 링크가 확인된 running Activity의 거리만을 집계한 것입니다.
さが桜2025와 下関海響2025
레이스 비교에서는 Activity와 FIT Session이 확인된 링크로 연결되어 있고, FIT Lap의 거리, 시간, 속도, 케이던스(Cadence) 등이 해석 가능한 열(Column)로 구성되어 있어 전반기/후반기 비교로 넘어가기 쉬워집니다.
승인된 개별 분석 결과는 다음과 같습니다.
| 항목 | さが桜2025 | 下関海響2025 |
|---|---|---|
| 넷 타임 (Net Time) | 3:17:19 | 3:39:30 |
| ... | ||
| 완주 시간은 넷 타임(Net Time)을, 분석은 Garmin 기록을 기준으로 하고 있습니다. |
정규화 열에 없는 지표는 대응하는 FIT raw에서 추가 분석한다
위 표의 GCT(접지 시간, Ground Contact Time)는 현재 v1.2.1의 공개 FIT Lap 정규화 열에는 포함되어 있지 않습니다. 이번 비교에서는 정규화된 Activity–FIT 명시적 링크를 통해 대상 FIT Session을 특정하고, 그 Provenance를 통해 대응하는 FIT raw를 확인하여 접지 시간을 추가 분석했습니다.
일반적인 One-shot Run-All에서 source_path는 입력 루트로부터의 상대 경로입니다. ZIP 내의 FIT라면 「ZIP의 상대 경로 + ZIP 내의 member path」를 나타내며, source_sha256으로 내용을 대조할 수 있습니다. 절대 경로가 아니며, Raw FIT 본체도 일반적인 Run-All 결과물에는 동봉되지 않습니다. 상세 분석에서는 수중에 보관한 Garmin Export와 정규화 결과물을 함께 사용합니다.
Snapshot 운영 방식에서는 FIT를 내용 단위의 불변(Immutable) blob으로 축적하여, 누적된 고유한 FIT 군으로부터 정규화 결과물을 재생성할 수 있습니다. 다만, 어떤 운영 방식에서도 링크가 해결되지 않은 Activity와 FIT를 일시나 거리만으로 추측하여 보충하지는 않습니다.
下関海響2025의 「25km 이후의 위화감」이라는 주관적 메모와 강풍·때때로 몰아치는 폭풍이라는 기상 정보는 Garmin 측정값과는 별개의 보조 정보로서 대조하고 있습니다. 수치 변화와의 인과관계는 단정하지 않았습니다.
정규화를 통해 줄어든 것은 Activity와 FIT의 대응이나 FIT 값의 기본 해석을 처음부터 다시 수행하는 작업입니다. 레이스 전개의 의미 부여, 주관적 메모, 기상과의 관계, 인과관계의 판단은 사람의 몫으로 남습니다.
AI에게 맡긴 것, 인간이 판단한 것
이 역할 분담은 명확히 해둘 필요가 있습니다.
| AI에게 맡긴 부분 | 인간이 확인·판단한 부분 |
|---|---|
| 정규화된 데이터의 집계 | Activity와 FIT의 대응 타당성 |
| ... | |
| AI가 자동으로 「정답을 낸」 것이 아닙니다. 정규화와 handoff를 통해, AI가 확인한 데이터 구조로부터 계산을 시작하기 쉽게 만들고, 그 결과를 사람이 근거와 경계에 비추어 리뷰하는 것입니다. |
현재 테스트 가능한 공개 버전은 v1.2.1
이 글을 작성하는 시점의 안정 버전은 v1.2.1입니다. Production PyPI에서 설치할 수 있습니다.
v1.2.1은 Windows에서 Asia/Tokyo를 해결하지 못하는 경우가 있었던 문제를 수정하고, 필요한 tzdata가 자동으로 도입되도록 한 패치입니다. GitHub Actions의 Windows 환경과 Production PyPI에서 도입한 Windows 실기 1대 환경에서 Synthetic Run-All을 확인했습니다. 다만, 모든 Windows 환경에서의 완전한 동작을 보장하는 것은 아닙니다.
도입 절차는 Product Quick Start를 참조하십시오.
요약
Garmin Export를 AI로 분석할 때, 정말로 반복하고 싶은 것은 구조 조사가 아니라, 러닝에 대한 질문을 바꿔가며 데이터를 되돌아보는 것입니다.
이를 위해 Garmin Running Data Normalizer는 다음을 하나의 handoff로서 남깁니다.
- 입도(Granularity)별로 나눈 정규화 데이터
- 확인된 Activity–FIT, Activity–Gear 관계
- 스키마(Schema)와 분석 컨텍스트(Context)
- 처리 결과, 경고, 감사(Audit) 정보
- 추측해서는 안 되는 결손 및 미해결 상태
정규화 데이터와 그 읽는 법을 AI에게 전달함으로써, 구조 탐색, Activity와 FIT의 대응 추측, FIT 필드의 기본 해석, 단위 변환, 표의 결합, 결손의 의미 추측을 매번 반복하지 않고 분석부터 시작하기 쉬워집니다.
단, 정규화가 올바른 분석 결과를 보장하는 것은 아닙니다. AI에게는 집계나 후보 추출을 맡기고, 사람이 데이터의 경계, 주관적 정보, 인과관계, 공개 범위, 최종 해석을 확인합니다. 이 분담까지 포함하여 재사용 가능한 분석의 준비라고 생각합니다.
다음 회차에서는 Garmin에서 Export 데이터를 가져와 v1.2.1로 정규화(Normalization)하고, 출력을 확인하여 AI 분석을 시작하기까지의 일련의 흐름을 소개합니다. 기본 분석 외에도, 정규화된 열(Column)에 없는 지표를 확인하고 싶을 때 정규화된 데이터에서 대응하는 FIT raw 데이터로 추적하는 방법도 다룰 예정입니다.
- GitHub: https://github.com/tsubotti63/garmin-running-data-normalizer
- PyPI: https://pypi.org/project/garmin-running-data-normalizer/1.2.1/
- 제1회 기사: https://zenn.dev/tsubotti63/articles/garmin-running-data-normalizer-summary
- AI Analysis Quick Start: https://github.com/tsubotti63/garmin-running-data-normalizer/blob/main/docs/ai_analysis_quick_start.md
- Run-All Output Contract: https://github.com/tsubotti63/garmin-running-data-normalizer/blob/main/docs/output_contract.md
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기