개발자 정보

데이터 명세

Room 스키마, CSV 계약, Watch→Phone wire 포맷, 타임스탬프, 원본 보존 정책을 다룹니다. 여기 적힌 계약은 공개 스펙이라 한쪽만 바꾸면 양쪽 코드가 함께 깨집니다.

이 페이지의 계약 — CSV 컬럼 순서·파일명, Data Layer 경로·키, 샘플 JSON 스키마 — 은 공개 스펙입니다. 하나 바꾸면 writer·reader 양쪽과 다운스트림 분석 코드가 같이 깨집니다.

Room 스키마

엔티티는 두 모듈에서 거의 같은 Room @Entity입니다. 폰 DB는 v3MIGRATION_1_2favorite 컬럼을, MIGRATION_2_3가 wide ticks 테이블을 더합니다(둘 다 비파괴, destructive fallback 없음).

다중 센서: wide ticks 테이블

이제는 여러 센서를 한꺼번에 모읍니다. 워치 틱 하나가 wide row(SensorTick → Room ticks) 한 행이 되고, 값이 없는 피처 컬럼은 null입니다. 폰도 같은 wide 스키마를 그대로 미러링합니다. 심박 전용 samples 테이블은 옛 세션 호환용으로 남겨 둡니다. 어떤 센서를 켤지와 수집 주기는 폰 설정에서 워치로 Data Layer를 타고 내려갑니다 (WatchConfigSync → ConfigListenerService).

fieldtype센서 그룹notes
idLongPK, 자동 생성
sessionIdStringindexed FK → sessions
tickIndexInt0부터, 세션 내 단조 증가
timestampWatchMsLong저장 순간 워치 wall clock
timestampPhoneReceivedMsLong?폰 수신 시각
timestampIsoStringoffset 포함 ISO-8601
hrInt?심박bpm, raw 저장(0 포함)
hrIbiMsInt?심박DataPoint 마지막 IBI(ms)
hrStatusInt?심박SDK raw 상태
skinTempDouble?피부온도Watch5+ 한정
stepsDouble?Health Services
distanceDouble?Health Servicesm
speedDouble?Health Servicesm/s
cadenceDouble?Health Servicessteps/min
accelX / Y / ZDouble?모션 (SensorManager)m/s²
accelMagDouble?모션|a| 크기
gyroX / Y / ZDouble?모션rad/s
magX / Y / ZDouble?모션µT
baroDouble?모션hPa
lat / lonDouble?GPSWGS-84 degrees
watchModelString?
phoneModelString?mobile 모듈만
createdAtMsLongrow 생성 시각

피처 컬럼은 모두 nullable입니다. null은 "이 틱에 해당 센서 데이터가 없음"(센서 없음·꺼짐·탈착)을 뜻합니다. 이 wide 형태가 곧 ML·이상탐지 피처 행렬이라 SELECT * FROM ticks ORDER BY tickIndex면 join 없이 그대로 나옵니다.

HeartRateSession

fieldtypenotes
sessionIdStringPK. 워치에서 만든 UUID
startedAtMsLong시작 시각(워치 wall clock)
endedAtMsLong?진행 중엔 null
startedAtIsoStringISO-8601 + offset
endedAtIsoString?
sampleCountIntstop 때 확정
watchModelString?Build.MODEL (워치)
phoneModelString?Build.MODEL (폰). 수신 때 채움
favoriteBoolean폰 전용. 별표, 기본 false

원본 보존 정책

분석에 그대로 넘기려고 raw 레코드를 손대지 않습니다. 수집 시점에:

  • 빠진 1초 구간을 채우지 않습니다. 빈칸은 빈칸.
  • hr == 0도 저장.
  • off-wrist·비정상 status도 저장.
  • 중복 제거·smoothing·clamping 없음.

거르는 건 분석의 몫

hr <= 0 필터는 차트·지도 같은 시각화에서만 씁니다. Room·CSV에선 절대 거르지 않습니다. 데이터 무결성 요구사항입니다.

타임스탬프

  • timestampWatchMs — 틱을 저장하는 순간의 워치 System.currentTimeMillis(). 수집 시각의 기준입니다.
  • timestampIso — offset 포함 ISO-8601. 예: 2026-06-02T13:45:01.123+09:00 (OffsetDateTime + ISO_OFFSET_DATE_TIME).
  • timestampPhoneReceivedMs — 폰 onDataChanged 시점의 System.currentTimeMillis().
  • SDK DataPoint 타임스탬프는 센서 래퍼 안에서 fallback으로만 씁니다. 저장값 timestampWatchMs는 늘 저장 순간의 워치 시계입니다.

Watch → Phone 전송

MessageClient가 아니라 DataClient를 씁니다. DataClient는 DataItem을 영속화하고 재연결 때 알아서 다시 동기화해서, 폰이 끊긴 동안 버퍼 역할을 대신해 줍니다. 워치 Room이 source of truth라 큐에 걸리거나 실패한 전송이 있어도 데이터는 잃지 않습니다.

경로

  • /session/{sessionId} — 세션 메타. _updatedAt을 단조 증가시켜 세션 종료 업데이트가 매번 이전과 다른 payload가 되도록 합니다(DataItem은 내용이 같으면 dedupe되기 때문입니다).
  • /ticks/{sessionId}/{batchSeq} — wide 센서 틱 배치. put마다 batchSeq가 달라 덮어쓰기를 막습니다. 서비스가 ~1초에 한 번 flush합니다.

DataMap 키

sessionId, startedAtMs, startedAtIso, endedAtMs, endedAtIso,
sampleCount, watchModel, ticks

틱 배치 JSON (ticks 문자열 = JSON 배열)

{ "i": 0, "tsW": 1717305901123, "tsI": "2026-06-02T13:45:01.123+09:00",
  "hr": 72, "hrIbi": 833, "hrStatus": 0,
  "steps": 1.0, "speed": 0.8, "accelX": 0.12, "baro": 1013.2,
  "lat": 37.123, "lon": 127.456 }

값이 없는 센서 컬럼은 JSON에서 아예 빠집니다(null-omitting). 수신 측은 optInt2·optDouble2로 방어적으로 읽습니다.

writer와 reader는 함께 바꿉니다

이 스키마는 writer(WearDataLayerClient)와 reader (MobileDataLayerListenerService)가 같이 움직여야 합니다. 한쪽만 고치면 깨집니다.

신뢰성

  • 워치는 배치를 큐에 넣기 전에 Room에 씁니다. 폰이 끊겨도 로컬 저장은 계속되고, 다시 붙으면 DataClient가 큐를 동기화합니다.
  • putDataItem(...).setUrgent()로 즉시 동기화를 요청합니다.
  • DataClient 너머의 영속 재전송 큐는 아직 TODO입니다.

CSV 내보내기

  • 세션마다 Export CSV → 파일 작성 → 안드로이드 공유 시트(ACTION_SEND, text/csv)로 나갑니다. FileProvider (${applicationId}.fileprovider, res/xml/file_paths.xml, external-cache exports/).
  • 파일명: sensor_session_YYYYMMDD_HHMMSS.csv (startedAtMs 로컬 시각).
  • 헤더와 추출기는 TickColumns.kt에 함께 정의된 단일 소스라 헤더·데이터 행이 절대 어긋나지 않습니다.
  • nullable 컬럼은 null이면 빈 문자열(ML 로더가 NaN으로 읽음). 모델명은 최소한으로 CSV 따옴표 처리합니다(콤마·따옴표·개행).
  • 틱은 tickIndex 오름차순으로 씁니다.

헤더 (28열 고정 순서 — 이상탐지 피처 행렬 순서)

session_id,tick_index,timestamp_iso,timestamp_watch_ms,timestamp_phone_received_ms,
hr,hr_ibi_ms,hr_status,skin_temp,
steps,distance,speed,cadence,
accel_x,accel_y,accel_z,accel_mag,gyro_x,gyro_y,gyro_z,mag_x,mag_y,mag_z,baro,
lat,lon,watch_model,phone_model

legacy 심박 CSV

v0.2.0 이전 세션은 심박 전용 samples 스키마 (session_id,sample_index,…,heart_rate_bpm,ibi_ms,status,…)로 남아 있습니다. 새 세션은 위 wide ticks 포맷만 씁니다. 새 컬럼은 뒤에 더하고 기존 순서는 건드리지 않습니다.