Skip to main content

lifetract

Query personal life-tracking data: Samsung Health (sleep, steps, heart rate, stress, exercise, weight) + aTimeLogger (18 time categories) + Home Assistant REST (live sensors via ha.junghanacs.com). All records use Denote IDs (YYYYMMDDTHHMMSS) for cross-referencing with denotecli. DB mode (lifetract.db) for instant queries, individual Samsung commands have CSV fallback, and today's DB gaps can use Home Assistant.

설치로 이동

소스 정보

저장소
junghan0611/agent-config
최근 소스 활동
2026년 7월 14일 08:15
감지된 SKILL.md 언어
다국어 혼합
스타
5
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
lifetract
description
Query personal life-tracking data: Samsung Health (sleep, steps, heart rate, stress, exercise, weight) + aTimeLogger (18 time categories) + Home Assistant REST (live sensors via ha.junghanacs.com). All records use Denote IDs (YYYYMMDDTHHMMSS) for cross-referencing with denotecli. DB mode (lifetract.db) for instant queries, individual Samsung commands have CSV fallback, and today's DB gaps can use Home Assistant.
# lifetract — Life Tracking CLI Query and analyze personal health and time-tracking data. All records carry Denote IDs (`YYYYMMDDTHHMMSS`) — same axis as denotecli. Binary is bundled in the skill directory. Invoke via `{baseDir}/lifetract`. All output is JSON. ## Why This Exists (not sqlite3/pandas) Do NOT open lifetract.db or CSV files directly with Python/sqlite3/pandas. 1. **Denote ID mapping** — Raw CSVs use Samsung's epoch timestamps. The CLI converts them to `YYYYMMDDTHHMMSS` Denote IDs for cross-referencing with denotecli/gitcli. 2. **Multi-source join** — Sleep, heart rate, steps, stress, exercise, time tracking from different tables/sources, unified per-day. Manual SQL gets this wrong. 3. **JSON for agents** — Structured output ready for reasoning. No parsing needed. ## When to Use - "오늘 몸 상태" → `lifetract today` - "어제 뭐 했지?" → `lifetract read 2026-03-09` - "최근 수면 패턴" → `lifetract sleep --days 30 --summary` - "이번 주 시간 사용" → `lifetract time --days 7` - "운동 기록" → `lifetract exercise --days 30` - "30일 추이" → `lifetract timeline --days 30` ## Quick Start ```bash lifetract status # 데이터 소스 + DB 상태 lifetract import --exec # CSV+aTimeLogger → lifetract.db (1.5초) lifetract today # 오늘 통합 요약 lifetract read 2025-10-04 # 특정 날짜 종합 (건강+시간추적) lifetract timeline --days 30 # 30일 횡단 뷰 ``` ## Architecture ``` lifetract.db 있음 → 모든 명령: DB 쿼리 (~90ms) → JSON lifetract.db 없음 → sleep/steps/heart/stress/exercise: CSV 파싱 (~300ms) → JSON → time/timeline/today/read: exit 1 ``` - `lifetract import --exec` 실행 후 모든 조회가 DB 모드 - DB 없으면 개별 Samsung 명령(`sleep`/`steps`/`heart`/`stress`/`exercise`)만 CSV 직접 파싱 - `time`과 시간축을 합치는 `timeline`/`today`/`read`는 DB가 없으면 **exit 1** — aTimeLogger를 못 읽은 구멍을 0으로 내보내지 않는다 ## Commands ### status — 데이터 소스 확인 ```bash lifetract status ``` ```json { "samsung_health": {"path": "...", "available": true, "csv_count": 78}, "atimelogger": {"path": "...", "available": true, "size_mb": 5.0}, "database": { "path": "...", "available": true, "size_mb": 37.3, "mode": "db", "last_time_block": "2026-07-13", "last_sleep": "2026-07-13", "last_steps": "2026-07-13", "stale_days": 1, "freshness_checked": true, "warnings": [] } } ``` **`last_*` / `stale_days` / `warnings` 가 이 명령의 요점이다.** Samsung export 는 사람이 폰에서 손으로 내보내야 흐른다 — 안 넣어주면 조용히 낡는다. 숫자를 저널에 사실로 박기 전에 여기부터 봐라 (§시간 계약 4항). **`warnings: []` 는 `freshness_checked: true` 일 때만 "성하다"는 뜻이다.** DB 가 없거나 검사 자체가 실패하면 `freshness_checked: false` 이고, 그때의 빈 목록은 "이상 없음"이 아니라 **"보지 않았음"** 이다. 검사하지 않은 것이 합격으로 보이면 그건 검사가 아니다. ### import — DB 생성 ```bash lifetract import # dry-run: 매니페스트 확인 lifetract import --exec # 실행: CSV+aTimeLogger → lifetract.db ``` 202,479 rows, ~38MB, ~2s. Active tables: sleep, sleep_stage, heart_rate, steps_daily, stress, exercise, weight, atl_category, atl_interval. HRV는 은퇴했다: Samsung export 1,058행에 `rmssd`가 없고 `binning_data`만 있어, 예전 importer가 전부 `0.0`으로 넣던 빈 껍데기였다. **import 는 자기가 뭘 잃었는지 말한다.** `total_rows` 말고 **`status` 를 먼저 봐라.** ```json { "status": "warning", "warnings": ["stress: 27,598 rows (2026-07-14 12:25) → 0 — stream lost [empty]"], "total_rows": 175941, "prev_total_rows": 203539, "tables": [ {"name": "stress", "rows": 0, "status": "empty", "prev_rows": 27598, "delta": -27598} ] } ``` | 낱말 | 뜻 | |---|---| | `ok` | 읽었고, 지난번보다 줄지 않았다 | | `empty` | 읽히긴 했는데 **0 행**. 지난번에 행이 있었다면 **잃은 것** | | `shrunk` | 지난 import 보다 **적다** — Samsung export 는 누적 덤프라 줄면 이상하다 | 직전 행수는 DB 안 `import_log` 원장에 산다 (import 가 DB 를 지워도 이월된다). **첫 import 는 비교 대상이 없으니 경고하지 않는다** — `note` 가 그렇게 말한다. 원장을 직접 읽는다면 **`GROUP BY import_id`** 를 써라. `imported_at` 은 한 import 를 묶지 못한다 (옛 행들은 초 경계를 넘어 2~3 개로 쪼개져 있다). **`rows` 는 DB 에 실제로 앉은 행수다** — 우리가 건넨 행수가 아니라. 예전엔 `INSERT` 결과를 안 보고 세서, DB 가 거부한 행도 "imported" 로 원장에 박혔다. **손실 가드가 그 가짜 숫자를 기준으로 삼고 있었다.** **`rejected` 는 승격을 막지 않지만 반드시 보고된다.** 재본 적 없는 시각(1970-01-01 epoch, 2000-01-01)을 실은 행과, Samsung 이 같은 날을 다시 동기화해 최신 개정판으로 대체된 행이다. 조용히 버리는 도구는 조용히 잃는 도구와 같은 침묵이라 매 run 이 이유별 개수를 말한다. 거절은 영구적인 손실 예산이 아니다. 정상 baseline 뒤에는 accepted rows 가 하나라도 줄면 현재 rejected 가 몇 개든 `shrunk` 다. 새 거절 정책은 전수 대조한 명시적 baseline 전환으로만 들어오며, 산술 허용량을 상시 열어두지 않는다. **경고가 하나라도 있으면 승격되지 않는다 — 첫 import 도 예외가 아니다. 우회 플래그는 없다.** 예전엔 "빈 것만" 막았는데, Samsung 여러 스트림 성공 + aTimeLogger 실패 같은 **부분 성공**은 그대로 운영 DB 가 됐다. 그 순간 "못 봤다"를 말할 에러 경로가 사라진다 — DB 가 있으니 이후로는 그냥 `[]` 다. **불완전한 DB 는 운영 자리에 오지 않는다.** 스트림 하나가 빠진 DB 는 그 스트림에 대해 영원히 `[]` 를 답하고, 소비자(관측소)는 그 구멍을 0 으로 기록한다. **`rows` 옆의 `invalid` 를 봐라.** 파일에 행은 있는데 필수 필드가 없거나 시각·필수 수치가 파싱 안 되는 행 수다. Samsung 이 헤더 하나만 개명하거나 숫자 자리에 garbage를 넣어도 예전엔 `empty` 또는 측정값 0으로 보였다 (첫 import 는 baseline 이 없어 경고조차 없었다). **`invalid > 0` 이면 승격이 막힌다.** 빈 선택 필드는 0으로 둘 수 있지만, 값이 있는데 읽히지 않는 것은 0이 아니다. **`atl_category` 는 독립 스트림이다.** `time` 조회는 카테고리를 INNER JOIN 하므로, 카테고리가 사라지면 블록 수가 그대로여도 **시간축이 전멸한다.** 예전 원장은 interval 만 세서 이 손실을 못 봤다 (`status: ok`, `warnings: []`, time 30일 → 0). 세지 않는 스트림은 잃어도 모른다. 고아 블록(없는 카테고리를 가리키는 블록) 검사도 함께 돈다. *왜 있나: 2026-07-14, 글롭 하나가 7MB stress 대신 1KB histogram 을 집어 27,598 행이 통째로 0 이 됐는데 import 는 `"ok"` 라고 했다. 테스트는 초록불이었다. 잡은 건 총 행수가 203,539 → 175,941 로 떨어진 걸 **사람이 눈으로 본 것**뿐이었다. 이제 도구가 말한다.* ### read — Denote ID로 조회 ```bash lifetract read 20250115T000000 # Day ID → 그날 종합 lifetract read 2025-01-15 # 같은 결과 (날짜 단축형) lifetract read 20250115T233000 # Event ID → 개별 수면/운동 ``` Day 조회 시 건강 메트릭 + aTimeLogger 시간 카테고리 + 수면 세션 + 운동 모두 포함. ### today — 오늘 요약 ```bash lifetract today ``` ```json // 데이터 있는 날 (read 2025-10-04 형태) {"date": "2025-10-04", "steps": 41382, "sleep_hours": 1.5, "avg_hr": 93.1, "stress_avg": 20.9, "time_categories": [...], "source": "db"} // 데이터 없는 날 — DB 가 빈 자리는 자동으로 HA 가 채움 (phase 7 read-only fallback) {"date": "2026-05-26", "steps": 7099, "sleep_hours": 4.8, "avg_hr": 137, "stress_avg": 0, "source": "db+ha", "ha_sources": ["steps","heart_rate","sleep"]} ``` `time_categories` 가 비면 JSON 에서 키 자체가 빠진다 (omitempty). 데이터 있는 날 vs 없는 날 둘 다 정상 출력. **자동 HA fallback (오늘 자리에 한정)**: DB 가 오늘 자리를 비웠으면 (Samsung CSV 가 아직 안 들어왔으면) `today` 와 `read <오늘>` 이 자동으로 HA 라이브 값으로 채운다. `source` 가 `"db+ha"` 로 바뀌고, `ha_sources` 가 어떤 필드가 HA 에서 왔는지 알려준다. *과거 날짜는 enrichment 안 됨* — HA recorder 는 backfill 자리가 아니다. 끄려면 `LIFETRACT_NO_HA=1`. Sleep 은 *옛 row 가 오늘로 잡히는 stale* 자리도 감지해서 HA 로 덮어쓴다 (최근 36h 의 sleep_duration history 를 합산 — main sleep + nap 둘 다 잡음). ### timeline — 날짜별 횡단 뷰 ```bash lifetract timeline --days 7 lifetract timeline --days 30 ``` denotecli 저널과 같은 날짜 키(`YYYYMMDDT000000`)로 정렬. 건강+시간+운동 통합. ### sleep / steps / heart / stress / exercise ```bash lifetract sleep --days 7 lifetract sleep --days 30 --summary lifetract steps --days 7 lifetract heart --days 7 lifetract stress --days 7 lifetract exercise --days 30 ``` **걸음 수 계약:** `steps_daily` 는 측정일마다 정확히 한 행이다. 날짜는 Samsung의 `day_time` 만 읽으며 `create_time` 으로 대신하지 않는다. `day_time` 은 epoch-ms와 벽시계 문자열 양쪽을 지원하고, 못 읽거나 미래면 invalid로 승격을 막는다. 같은 날의 재동기화 행은 최신 `update_time` 한 건만 남기며, 동시각에 값이 충돌하면 임의로 고르지 않고 invalid다. DB도 날짜 UNIQUE로 같은 불변식을 강제한다. ### time — aTimeLogger 시간 추적 ```bash lifetract time --days 7 lifetract time --days 30 --category 본짓 ``` 카테고리: 본짓, 수면, 가족, 식사, 독서, 운동, 걷기, 수행, 셀프토크, 낮잠, 준비, 집안일, 이동, 쇼핑, 딴짓, 유튜브, 짧은휴식, 여가활동 (18종) ### export — 공개용 내보내기 계획 ```bash lifetract export ``` ### ha — Home Assistant REST (live sensors) ```bash lifetract ha ping # 연결 확인 lifetract ha state heart_rate # 도메인 이름으로 한 sensor 가져오기 lifetract ha state sleep_duration # (또는 literal entity_id 도 OK) lifetract ha states # 등록된 24개 known sensor 일괄 조회 lifetract ha entities # HA 가 노출하는 모든 entity (raw, known 플래그 표시) lifetract ha history sleep_duration --days 7 # 7일치 state 변화 (HA recorder) ``` ```json // ha state heart_rate { "entity_id": "sensor.sm_s942n_s26_glgman_heart_rate", "kind": "heart_rate", "state": "111.0", "value": 111, "unit": "bpm", "last_changed": "2026-05-17T22:34:11Z", "attributes": {...} } ``` **토큰**: `pass show 2fa/totp/ha/junghanacs` (primary) → env `HA_TOKEN` (fallback) → `~/.lifetract/ha.env`. 토큰값 자체는 절대 commit/push 금지. **도메인 kind**: `sleep_duration`, `steps_daily`, `distance_daily`, `floors_daily`, `heart_rate`, `resting_heart_rate`, `heart_rate_variability`, `weight`, `body_fat`, `height`, `calories_burned`, `active_calories_burned`, `basal_metabolic_rate`, `hydration`, `detected_activity`, `geocoded_location`, `battery`, `sleep_confidence`, `respiratory_rate`, `oxygen_saturation`, `body_temperature`, `blood_glucose`, `systolic_blood_pressure`, `diastolic_blood_pressure` (24종). 새 sensor 추가 = `lifetract/ha_entities.go` 의 `KnownEntities` 에 한 줄. **`ha history` 동작**: HA recorder 는 *state 변화 시점에만* row 저장. recorder 30일 보관은 "있는 데이터 보존" 이지 "없는 데이터 채워줌" 이 아님. HA 인프라가 띄워진 시점 이전 데이터는 영원히 안 잡힘. 과거는 Samsung CSV export 가 유일한 길. HA history = *내일부터의 적립* 자리. ```json // ha history sleep_duration --days 7 { "entity_id": "sensor.sm_s942n_s26_glgman_sleep_duration", "kind": "sleep_duration", "unit": "min", "days": 7, "from": "2026-05-11T...+09:00", "to": "2026-05-18T...+09:00", "count": 2, "points": [ {"last_changed": "...", "value": 427, "unit": "min", "attributes": {"endTime": "..."}}, ... ] } ``` **현재 상태 (phase 7 read-only)**: `cmdToday` / `cmdRead <오늘>` 이 DB miss 또는 stale sleep 자리에서 자동으로 HA `GetState` + `GetHistory` 를 호출해 응답에 채워준다 (`source: "db+ha"`, `ha_sources: [...]`). DB upsert 는 의도적으로 하지 않는다. Samsung export 가 영구 SSOT이고 HA recorder 는 30일 보조면이라, 덜 아는 값을 본 DB에 흡수하지 않는다. ## Flags | Flag | Default | 설명 | |------|---------|------| | `--days N` | 7 | 창 길이 (**무시되지 않는다** — 아래 조합표) | | `--from YYYY-MM-DD` | — | 창 시작 (포함) | | `--to YYYY-MM-DD` | — | 창 끝 (**배타적**) | | `--data-dir DIR` | `~/repos/gh/self-tracking-data` | 데이터 루트 | | `--shealth-dir DIR` | 최신 자동감지 | Samsung Health 디렉토리 | | `--summary` | false | 요약 모드 | | `--category CAT` | 전체 | 시간 카테고리 필터 | | `--exec` | false | import 실행 모드 | ### 창 조합 — 모든 조합이 뜻을 갖는다 | 조합 | 창 | |------|-----| | `--days N` | `[내일-N, 내일)` — **오늘 포함 정확히 N일** (`1`=오늘) | | `--days N --to T` | `[T-N, T)` — **T 에 끝나는 N일** | | `--days N --from F` | `[F, F+N)` — **F 에 시작하는 N일** | | `--from F --to T` | `[F, T)` | | `--from F` | `[F, 내일)` |
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기