{"id":"5f878349-6541-4a11-97e1-94e6bbe2b93e","entityType":"agent","slug":"clawhub-bartsoj-health-metrics","name":"Health Metrics","canonicalUrl":"https://www.xpersona.co/agent/clawhub-bartsoj-health-metrics","canonicalPath":"/agent/clawhub-bartsoj-health-metrics","generatedAt":"2026-10-09T23:09:47.888Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T09:31:07.083Z","emptyReason":null},"description":"Ingest Apple Health Auto Export JSON (HealthMetrics + Workouts) into a local DuckDB database and render offline HTML dashboards plus a Markdown daily summary...","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 3.1K downloads reported by the source. Last updated 10/9/2026.","installCommand":"clawhub skill install s17fh7c035hba497j017fat8rd88496r:health-metrics","sourceUrl":"https://clawhub.ai/bartsoj/health-metrics","homepage":"https://clawhub.ai/bartsoj/skills/health-metrics","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/bartsoj/health-metrics","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/bartsoj/skills/health-metrics","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":70,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Health Metrics technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-09T09:31:07.083Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T09:31:07.083Z","emptyReason":null},"stars":null,"forks":null,"downloads":3145,"packageName":null,"latestVersion":"0.1.1","tractionLabel":"3.1K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T09:31:07.082Z","emptyReason":null},"lastUpdatedAt":"2026-10-09T09:31:07.083Z","lastCrawledAt":"2026-10-09T09:31:07.082Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-10T09:31:07.082Z","lastVerifiedAt":null,"highlights":[{"version":"0.1.1","createdAt":"2026-07-22T10:05:47.586Z","changelog":"health-metrics 1.1.0 introduces a new runner script and improves reliability for iCloud users. - Added scripts/run.sh: a unified command to handle ingestion and report generation. - scripts/run.sh automatically materializes iCloud placeholder files to prevent ingest failures on macOS. - Included documentation for using the new runner and handling iCloud \"Optimize Storage\" issues. - README and SKILL.md updated to explain new workflows and configuration defaults. - No breaking changes to the underlying ingestion or reporting pipelines.","fileCount":28,"zipByteSize":58613},{"version":"0.1.0","createdAt":"2026-07-22T07:31:18.422Z","changelog":"Initial release: Ingest Apple Health export JSON into DuckDB and generate offline health dashboards + summaries. - Ingests Apple Health Auto Export JSON (metrics + workouts) into a local DuckDB database. - Generates offline HTML dashboards and Markdown daily summaries with activity rings, training load, sleep, vitals, and per-workout maps. - No dependencies beyond Python3 standard library and DuckDB CLI (no pip packages required). - Scripts are idempotent, supporting targeted re-runs and custom report output locations. - Configuration via environment variables, with sensible defaults for all paths. - Personal health data is never stored inside the skill folder—lives in user-controlled locations only.","fileCount":26,"zipByteSize":53942}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17fh7c035hba497j017fat8rd88496r:health-metrics","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s17fh7c035hba497j017fat8rd88496r:health-metrics` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/bartsoj/health-metrics before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-bartsoj-health-metrics/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-bartsoj-health-metrics/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-bartsoj-health-metrics/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-bartsoj-health-metrics/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-bartsoj-health-metrics/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-bartsoj-health-metrics/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-09T23:09:47.887Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-bartsoj-health-metrics/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-bartsoj-health-metrics/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-bartsoj-health-metrics/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-bartsoj-health-metrics/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T09:31:07.083Z","emptyReason":null},"readme":"Skill: Health Metrics\n\nOwner: bartsoj\n\nSummary: Ingest Apple Health Auto Export JSON (HealthMetrics + Workouts) into a local DuckDB database and render offline HTML dashboards plus a Markdown daily summary...\n\nTags: latest:0.1.1\n\nVersion history:\n\nv0.1.1 | 2026-07-22T10:05:47.586Z | auto\n\nhealth-metrics 1.1.0 introduces a new runner script and improves reliability for iCloud users.\n\n- Added scripts/run.sh: a unified command to handle ingestion and report generation.\n- scripts/run.sh automatically materializes iCloud placeholder files to prevent ingest failures on macOS.\n- Included documentation for using the new runner and handling iCloud \"Optimize Storage\" issues.\n- README and SKILL.md updated to explain new workflows and configuration defaults.\n- No breaking changes to the underlying ingestion or reporting pipelines.\n\nv0.1.0 | 2026-07-22T07:31:18.422Z | auto\n\nInitial release: Ingest Apple Health export JSON into DuckDB and generate offline health dashboards + summaries.\n\n- Ingests Apple Health Auto Export JSON (metrics + workouts) into a local DuckDB database.\n- Generates offline HTML dashboards and Markdown daily summaries with activity rings, training load, sleep, vitals, and per-workout maps.\n- No dependencies beyond Python3 standard library and DuckDB CLI (no pip packages required).\n- Scripts are idempotent, supporting targeted re-runs and custom report output locations.\n- Configuration via environment variables, with sensible defaults for all paths.\n- Personal health data is never stored inside the skill folder—lives in user-controlled locations only.\n\nArchive index:\n\nArchive v0.1.1: 28 files, 58613 bytes\n\nFiles: .clawhubignore (200b), .gitignore (228b), CLAUDE.md (1133b), LICENSE (908b), README.md (2442b), references (0b), references/architecture.md (7100b), references/schema.md (1540b), scripts (0b), scripts/ingest_health_metrics.py (5595b), scripts/ingest_workouts.py (9253b), scripts/ingest.py (1119b), scripts/lib (0b), scripts/lib/geo.py (2514b), scripts/lib/ichart.py (7130b), scripts/lib/metrics.py (2574b), scripts/lib/query.py (1043b), scripts/lib/svg.py (11201b), scripts/report_daily_summary.py (26655b), scripts/report_health_metrics.py (18934b), scripts/report_rings.py (8676b), scripts/report_training_load.py (10308b), scripts/report_workouts.py (13889b), scripts/report.py (2145b), scripts/run.sh (4411b), skill-card.md (2364b), SKILL.md (6538b), _meta.json (133b)\n\nFile v0.1.1:SKILL.md\n\n---\nname: health-metrics\ndescription: >-\n  Ingest Apple Health Auto Export JSON (HealthMetrics + Workouts) into a local DuckDB\n  database and render offline HTML dashboards plus a Markdown daily summary (activity\n  rings, training load, sleep, vitals, per-workout maps). Use when the user wants to\n  process Apple Health exports, refresh their health dashboards, or get a training-load /\n  rings / sleep / vitals report from their exported data.\nversion: 1.1.0\nmetadata:\n  openclaw:\n    emoji: \"🏃\"\n    requires:\n      bins: [\"python3\", \"duckdb\"]\n    env: [\"HEALTH_METRICS_DIR\", \"HEALTH_WORKOUTS_DIR\", \"HEALTH_DB_PATH\"]\n    os: [\"macos\", \"linux\"]\n---\n\n# Health Metrics\n\nA self-contained pipeline that ingests **Apple Health Auto Export** JSON feeds into a local\nDuckDB database and renders static, offline-first HTML dashboards plus an AI-oriented Markdown\ndigest. No web server, no scheduler, no pip packages — just Python stdlib and the `duckdb` CLI.\nYou run it on demand whenever new export files land.\n\n## Prerequisites\n\n- `python3` and the `duckdb` CLI binary on `PATH` (no Python `duckdb` package needed).\n- Apple Health Auto Export daily JSON files available on disk (see **Configuration**).\n- Internet is needed only to *view* workout route maps (Leaflet + satellite tiles from a CDN).\n  Everything else renders fully offline.\n\n## Normal workflow\n\nRegenerate everything after new exports arrive:\n\n```bash\npython3 {baseDir}/scripts/ingest.py && python3 {baseDir}/scripts/report.py -o <OUT_DIR>\n```\n\n- `ingest.py` reads the source JSON and upserts into the DuckDB file (idempotent — safe to\n  re-run; unchanged files are skipped).\n- `report.py -o <OUT_DIR>` writes all dashboards into a directory **you choose**. Pick a working\n  or output directory the user controls; do not write inside the skill folder.\n\n## One-command runner (`scripts/run.sh`) — recommended\n\n`scripts/run.sh` wraps the pipeline and handles two things the raw scripts do not:\n\n1. **Materializes iCloud placeholder files** before ingest (see the gotcha below).\n2. **Always ingests before rendering**, so reports never show stale data.\n\n```bash\nbash {baseDir}/scripts/run.sh daily-md [YYYY-MM-DD] [OUT_DIR]   # ingest -> flat <OUT_DIR>/YYYY-MM-DD.md (default date = today)\nbash {baseDir}/scripts/run.sh html [OUT_DIR]                    # ingest -> full HTML dashboard set in OUT_DIR\nbash {baseDir}/scripts/run.sh ingest                           # materialize + ingest only\n```\n\nOutput dirs: `daily-md` defaults to `$HEALTH_MD_DIR` (else `reports/summary`); `html` defaults\nto `$HEALTH_HTML_DIR` (else a timestamped `reports/html-*`). `html` prints `HTML_OUT_DIR=<dir>`\non the last lines. Exits non-zero on failure so schedulers surface it.\n\n**Use `daily-md` from a scheduler** to keep a per-day Markdown log current (e.g. a nightly cron\nthat writes into a knowledge/notes folder), and **`html` on demand** for shareable dashboards.\n\n### ⚠️ iCloud \"dataless placeholder\" gotcha (macOS)\n\nApple Health Auto Export writes into an **iCloud Drive** folder. With **\"Optimize Mac Storage\"**\nenabled, macOS evicts file *contents* to placeholders — the files still appear in `ls` (with a\nsize!) but their bytes are not on disk until something opens them. DuckDB and Python then fail\nto read them with:\n\n```\nIO Error: Could not read from file \"…HealthMetrics-YYYY-MM-DD.json\": Resource deadlock avoided\n```\n\n(`EDEADLK`). `run.sh` handles this by running `brctl download` on each source file and waiting\nuntil the bytes are present before ingesting (no-op on Linux / non-iCloud dirs).\n\n**Permanent fix (do once):** in Finder, right-click each source folder\n(`iCloud Drive HealthMetrics` and `iCloud Drive Workouts`) → **\"Keep Downloaded\"**. This pins the\nfolders so iCloud never evicts them; there is no CLI equivalent. After pinning, the `brctl`\nstep in `run.sh` becomes a harmless safety net.\n\n## Configuration (all optional — sensible defaults)\n\n| Env var | Purpose | Default |\n|---|---|---|\n| `HEALTH_METRICS_DIR` | Folder of `HealthMetrics-YYYY-MM-DD.json` files | Apple Health Auto Export iCloud `…/iCloud Drive HealthMetrics` under `$HOME` |\n| `HEALTH_WORKOUTS_DIR` | Folder of `Workouts-YYYY-MM-DD.json` files | Apple Health Auto Export iCloud `…/iCloud Drive Workouts` under `$HOME` |\n| `HEALTH_DB_PATH` | Where the DuckDB file lives | `~/.local/state/health-metrics/health.duckdb` |\n\n`--db PATH` on `ingest.py` / `report.py` overrides `HEALTH_DB_PATH` for that run.\n\n> ⚠️ **The DuckDB database is personal, sensitive health data.** It is created on first ingest,\n> lives **outside** this skill, and must **never** be committed, published, or shared. It is not\n> part of the skill bundle.\n\n## Choosing where reports go\n\n`report.py -o <DIR>` mirrors this layout beneath `<DIR>`:\n\n- HTML dashboards → `<DIR>/` (`daily-*.html`, `weekly-*.html`, `monthly-*.html`, `rings.html`,\n  `training-load.html`)\n- Per-workout pages + index → `<DIR>/workouts/`\n- Markdown daily summaries → `<DIR>/summary/`\n\n## Individual stages (targeted re-runs)\n\nEach script is independently runnable and honors `HEALTH_DB_PATH`. A single script's `--out-dir`\nis the *literal, flat* directory it writes into (no subfolder appended).\n\n| Script | What it produces | Key flags |\n|---|---|---|\n| `scripts/ingest_health_metrics.py` | `samples_qty` / `samples_hr` / `sleep_sessions` | — |\n| `scripts/ingest_workouts.py` | `workouts` + `workout_route/hr/hr_recovery` | — |\n| `scripts/report_health_metrics.py` | activity / sleep / vitals summaries | `--period daily\\|weekly\\|monthly\\|all` `--date YYYY-MM-DD` `-o DIR` |\n| `scripts/report_workouts.py` | one page per workout + index | `--force` (re-render all) `-o DIR` |\n| `scripts/report_rings.py` | Apple-style activity rings | `-o DIR` |\n| `scripts/report_training_load.py` | TRIMP training load trend | `-o DIR` |\n| `scripts/report_daily_summary.py` | Markdown digest for AI readers | `--date YYYY-MM-DD` `-o DIR` |\n\n## Ad-hoc analysis\n\nQuery the database directly — the tables in `references/schema.md` are the full analysis surface:\n\n```bash\nduckdb \"$HEALTH_DB_PATH\" -json -c \"SELECT * FROM workouts ORDER BY date DESC LIMIT 5\"\n```\n\nOr from Python via `scripts/lib/query.py`'s `query(sql)` helper (shells out to the `duckdb` CLI,\nreturns parsed JSON rows).\n\n## Internals\n\nSee `{baseDir}/references/architecture.md` (data flow, ingestion pattern, report conventions,\n`lib/` helpers) and `{baseDir}/references/schema.md` (DuckDB table reference) before modifying\nthe scripts or adding a new report/metric.\n\nFile v0.1.1:README.md\n\n# health-metrics\n\nAn **Agent Skill** for **Apple Health Auto Export** reporting and logging. It lets an agent\n(OpenClaw, Claude Code, or any tool that reads the portable `SKILL.md` format) ingest Apple\nHealth Auto Export JSON feeds into a local DuckDB database and render offline, self-contained\nHTML dashboards plus a Markdown daily digest — activity rings, training load, sleep, vitals,\nand per-workout maps.\n\nNo web server, no scheduler, no Python packages beyond the stdlib + the `duckdb` CLI. Runs on\ndemand whenever new export files land.\n\n## Quick start\n\n```bash\npython3 scripts/ingest.py && python3 scripts/report.py -o <OUT_DIR>\n```\n\n- `ingest.py` parses the Apple Health Auto Export daily JSON and upserts into DuckDB (idempotent).\n- `report.py -o <OUT_DIR>` renders all dashboards into a directory you choose.\n\nOr use the one-command runner, which ingests first and (on macOS/iCloud) force-downloads any\nevicted placeholder files before reading them:\n\n```bash\nbash scripts/run.sh daily-md [YYYY-MM-DD] [OUT_DIR]   # Markdown daily summary (default: today)\nbash scripts/run.sh html [OUT_DIR]                    # full HTML dashboard set\nbash scripts/run.sh ingest                            # materialize + ingest only\n```\n\n> **macOS / iCloud note:** if the source folders live in iCloud Drive with \"Optimize Mac\n> Storage\" on, reads can fail with `Resource deadlock avoided` until files are downloaded.\n> `run.sh` handles this automatically; the permanent fix is Finder → right-click each source\n> folder → **Keep Downloaded**. See [`SKILL.md`](SKILL.md) for details.\n\nSource folders and the database location are configurable via environment variables\n(`HEALTH_METRICS_DIR`, `HEALTH_WORKOUTS_DIR`, `HEALTH_DB_PATH`), with sensible defaults.\n\n## Learn more\n\n- **[`SKILL.md`](SKILL.md)** — the skill entry point: prerequisites, workflow, configuration, and\n  every runnable stage.\n- **[`references/architecture.md`](references/architecture.md)** — internals (data flow, ingestion\n  pattern, report conventions, `scripts/lib/` helpers).\n- **[`references/schema.md`](references/schema.md)** — the DuckDB table reference.\n\n## Data & privacy\n\nThe DuckDB database is **personal, sensitive health data**. It is created on first ingest, lives\n**outside** this repository (default `~/.local/state/health-metrics/health.duckdb`), and is never\ncommitted, published, or bundled.\n\n## License\n\n[MIT No Attribution (MIT-0)](LICENSE).\n\nFile v0.1.1:_meta.json\n\n{\n  \"ownerId\": \"kn75shmdha8pdb93g5rpghnw6s80knmx\",\n  \"slug\": \"health-metrics\",\n  \"version\": \"0.1.1\",\n  \"publishedAt\": 1784714747586\n}\n\nFile v0.1.1:references/architecture.md\n\n# Architecture\n\nInternals of the `health-metrics` skill — read this before modifying the scripts or adding a new\nreport/metric. Operational usage lives in `../SKILL.md`; the table reference in `./schema.md`.\n\n## Data flow\n\n```\nApple Health Auto Export folders (source JSON, outside this skill)\n  -> scripts/ingest_*.py   (parse + normalize, idempotent upsert into the DuckDB file)\n  -> scripts/report_*.py   (query DuckDB, render self-contained HTML/Markdown into an out-dir)\n```\n\nSource JSON lives outside the skill, in the Apple Health Auto Export folders. The folders are\nconfigurable via `HEALTH_METRICS_DIR` / `HEALTH_WORKOUTS_DIR` (see `../SKILL.md`); the defaults\npoint at the standard iCloud locations:\n\n- `…/iCloud~com~ifunography~HealthExport/Documents/iCloud Drive HealthMetrics/HealthMetrics-YYYY-MM-DD.json`\n- `…/iCloud Drive Workouts/Workouts-YYYY-MM-DD.json`\n\nEach ingester only reads **daily** files (`HealthMetrics-YYYY-MM-DD.json` /\n`Workouts-YYYY-MM-DD.json`). Weekly/monthly/yearly rollup files in those same folders are ignored.\n\nThe DuckDB file location comes from `HEALTH_DB_PATH` (default\n`~/.local/state/health-metrics/health.duckdb`), resolved centrally by `scripts/lib/query.py`'s\n`db_path()`. It is per-person sensitive state — created on first ingest, never versioned or\nbundled.\n\n## Ingestion pattern (`scripts/ingest_health_metrics.py`, `scripts/ingest_workouts.py`)\n\nBoth follow the same idempotent shape, invoking the `duckdb` CLI via `subprocess` (never the\n`duckdb` Python package):\n\n1. `CREATE TABLE IF NOT EXISTS` schema block, run every time.\n2. Glob candidate files matching the strict daily-file regex.\n3. Skip a file if its `mtime` matches what's recorded in `ingested_files` / `ingested_workout_files`.\n4. Otherwise DELETE-then-INSERT the affected rows in one transaction, then upsert the tracking row.\n   - `ingest_health_metrics.py` dedupes by `date` (one file = one day = full delete/replace of that\n     day's rows across all three tables).\n   - `ingest_workouts.py` dedupes by workout `id`, not by file/day — a workout can appear in\n     multiple period files with the same `id`, and re-ingesting replaces that workout's rows\n     wherever it lands.\n5. `scripts/lib/metrics.py` is a whitelist: only metrics listed there are kept from HealthMetrics\n   exports. Anything else (nutrition, weight, mindful minutes, swimming, …) is silently dropped at\n   ingest time — check that file before assuming a metric name is queryable.\n6. `ingest_workouts.py` additionally defends against Apple omitting fields per-workout-type (e.g.\n   `flightsClimbed` absent for a swim) rather than nulling them — `available_fields()` probes the\n   file's inferred schema via `DESCRIBE` before building the INSERT, since an all-null/all-absent\n   JSON field can't be `.qty`-accessed or UNNESTed. Read `WORKOUT_FIELD_EXPRS` in that file before\n   adding a new workout column.\n\n## Report scripts\n\nAll build a full HTML/Markdown string in Python and `write_text()` it into the out-dir.\n\n- `report_health_metrics.py` — daily/weekly/rolling-28-day summary dashboards (activity, sleep,\n  vitals, \"other tracked\" metrics, auto-generated text insights vs. a 28-day baseline). Static SVG\n  via `lib/svg.py`.\n- `report_workouts.py` — one detail page per workout (`workouts/<date>-<slug>.html`) plus\n  `index.html`. Route map uses Leaflet + Esri World Imagery satellite tiles from public CDNs at\n  view time — the **only** part that needs internet to render; everything else is fully offline.\n- `report_rings.py` — Apple-style Activity Rings (Move/Exercise/Stand against fixed goals), hero\n  ring + 7-day mini rings + 28-day interactive trend charts via `lib/ichart.py`.\n- `report_training_load.py` — TRIMP-based training load (Banister formula) as a 7-day rolling sum\n  (\"weekly load\") vs. a tau=28-day EWMA scaled to weekly-equivalent units (\"monthly trend\"). Both\n  series kept in the same units so they're visually comparable on one chart (see `ewma_series()`).\n- `report_daily_summary.py` — the **only Markdown report**, written for AI/LLM readers. One\n  self-contained file per day at `summary/YYYY-MM-DD.md` (YAML frontmatter + prose), deep-diving the\n  target day plus 7d/28d/90d trend context, in three insight tiers (progress, readiness,\n  trajectory). It **imports and reuses** the engines from `report_training_load.py` (TRIMP/EWMA),\n  `report_rings.py` (ring goals + `daily_activity`), and `report_health_metrics.py` (`DISPLAY_NAME`)\n  rather than re-deriving them, so the Markdown numbers can't drift from the HTML dashboards.\n\n### CSS building convention\n\nAll report `HTML_HEAD` strings build CSS via plain single-brace strings +\n`.replace(\"__PLACEHOLDER__\", …)` substitution, **not** `str.format()` — a past bug came from CSS\nbraces needing `{{ }}` escaping for a `.format()` that was never actually called, silently breaking\nthe `<style>` block. Keep new report scripts consistent with this pattern. (The only legitimate\n`__…__` token that survives into rendered output is `window.__ICHART_DATA`, the ichart runtime.)\n\n## `scripts/lib/` (shared, imported by ingest and report scripts)\n\n- `query.py` — `db_path()` (resolves `HEALTH_DB_PATH`) and the `query(sql)` helper.\n- `metrics.py` — metric whitelist/category maps (`QTY_METRICS`, `HR_METRICS`, `ALL_QTY`,\n  `CUMULATIVE_METRICS`, `CATEGORY_OF`). Whether a metric is summed vs. averaged over a period is\n  decided by `CUMULATIVE_METRICS` — check it before aggregating a new metric.\n- `geo.py` — `haversine_m`, `douglas_peucker` (route simplification), `km_splits` (per-km pace).\n  Pure stdlib.\n- `svg.py` — static precomputed SVG chart primitives (rings, gauges, calendar heatmap, stacked\n  bars, sparklines, `line_chart`). Used where no interactivity is needed.\n- `ichart.py` — the interactive chart component (client-side hover/touch tooltips). Use this (not\n  `svg.line_chart`) for any new interactive trend chart.\n\nEvery script does `sys.path.insert(0, str(Path(__file__).parent))` then imports siblings and\n`lib.*`, so the whole `scripts/` tree relocates as a unit without import edits.\n\n## Adding a stage\n\nBoth `scripts/ingest.py` and `scripts/report.py` are thin orchestrators — import any new stage\nmodule and call its `main()` / `render()` in sequence there. Follow the\n`report_rings.py`/`report_training_load.py` pattern (interactive `lib/ichart.py` charts,\n`.replace()`-based HTML head) for new reports unless a chart genuinely doesn't need interactivity.\n\n## Historical note: backfill\n\nHistory older than when daily exports began was originally bootstrapped once with two one-off\nscripts (`split_healthmetrics_history.py`, `split_workout_history.py`) that split multi-day\nweekly/monthly/yearly rollup files into synthetic per-day files the daily-only ingesters could pick\nup. Those scripts were non-idempotent-by-design, have already been run, and are **not shipped** with\nthis skill. HealthMetrics *yearly* files were intentionally never split (their metrics are bucketed\nper-week, not per-day, so splitting would misattribute a week's total to a single day).\n\nFile v0.1.1:references/schema.md\n\n# DuckDB schema\n\nThe full analysis surface. Query these tables directly via the `duckdb` CLI or\n`scripts/lib/query.py`'s `query(sql)` — there is no ORM, just SQL strings. If a metric you expect\nisn't present, check `scripts/lib/metrics.py` first (it whitelists what ingest keeps).\n\n## Analysis tables\n\n- **`samples_qty(date, ts, metric, unit, qty, source)`** — simple quantity samples (steps, active\n  energy, …).\n- **`samples_hr(date, ts, metric, unit, min, avg, max, source)`** — min/avg/max-per-interval\n  metrics (heart_rate).\n- **`sleep_sessions(date, ts, in_bed_start, in_bed_end, sleep_start, sleep_end, core, deep, rem,\n  awake, asleep, in_bed, total_sleep, source)`**.\n- **`workouts(id PK, date, name, start, end, duration_s, is_indoor, location, temperature_c,\n  humidity_pct, intensity, distance_km, avg_hr, min_hr, max_hr, avg_speed, max_speed,\n  elevation_up_m, active_energy_kj, total_energy_kj, step_cadence, flights_climbed)`** — one row per\n  workout.\n- **`workout_route(workout_id, seq, ts, lat, lon, altitude, speed, course)`** — GPS points, only for\n  outdoor workouts.\n- **`workout_hr(workout_id, ts, min, avg, max)`** — per-minute HR during a workout.\n- **`workout_hr_recovery(workout_id, seq, ts, min, avg, max)`** — post-workout HR recovery curve.\n\n## Bookkeeping tables (not analysis data)\n\n- **`ingested_files(filename PK, mtime, ingested_at)`** — mtime-based dedup tracking for\n  HealthMetrics files.\n- **`ingested_workout_files(filename PK, mtime, ingested_at)`** — same, for Workouts files.\n\nFile v0.1.1:CLAUDE.md\n\n# CLAUDE.md\n\nThis repository **is** an Agent Skill named `health-metrics` (portable SKILL.md format, usable by\nOpenClaw, Claude Code, and other agents). The skill ingests Apple Health Auto Export JSON into a\nlocal DuckDB database and renders offline HTML dashboards + a Markdown daily summary.\n\nStart here:\n\n- **`SKILL.md`** — what the skill does, prerequisites, the normal workflow, configuration\n  (`HEALTH_METRICS_DIR` / `HEALTH_WORKOUTS_DIR` / `HEALTH_DB_PATH`), and every runnable stage.\n- **`references/architecture.md`** — internals: data flow, ingestion pattern, report conventions,\n  the `scripts/lib/` helpers, and the CSS `.replace()` convention.\n- **`references/schema.md`** — the DuckDB table reference (the full analysis surface).\n\nExecutable code lives in `scripts/` (with shared helpers in `scripts/lib/`). The DuckDB file is\nper-person sensitive data — it lives outside this tree (default\n`~/.local/state/health-metrics/health.duckdb`) and is never committed or bundled (see\n`.gitignore` / `.clawhubignore`).\n\nNormal workflow:\n\n```bash\npython3 scripts/ingest.py && python3 scripts/report.py -o <OUT_DIR>\n```\n\nFile v0.1.1:skill-card.md\n\n## Description:\n\nIngests Apple Health Auto Export JSON into a local DuckDB database and renders offline HTML dashboards plus Markdown daily summaries for activity rings, training load, sleep, vitals, and workout maps.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[bartsoj](https://clawhub.ai/user/bartsoj)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users and developers use this skill to process their Apple Health Auto Export files, refresh local dashboards, and generate daily Markdown summaries for personal health and training review.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill stores and renders sensitive health and GPS data.\n\nMitigation: Keep the DuckDB database and generated reports in private directories, and do not commit, publish, or share outputs.\n\nRisk: Modified or untrusted Apple Health exports may trigger SQL or HTML injection in generated outputs.\n\nMitigation: Run only on trusted exports and avoid opening generated workout reports from modified or untrusted data until values are escaped.\n\nRisk: Workout route maps load external map assets when viewed.\n\nMitigation: Open route maps only in trusted environments or use a version with bundled or integrity-protected map assets.\n\n## Reference(s):\n\n- [ClawHub Skill Page](https://clawhub.ai/bartsoj/skills/health-metrics)\n- [Source Repository](https://github.com/BartSoj/health-metrics)\n- [Architecture Reference](references/architecture.md)\n- [DuckDB Schema Reference](references/schema.md)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration instructions, Files, Markdown, HTML]\n\n**Output Format:** [Markdown guidance with shell commands; generated outputs are HTML dashboards and Markdown daily summaries.]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Requires python3 and the duckdb CLI; uses HEALTH_METRICS_DIR, HEALTH_WORKOUTS_DIR, and HEALTH_DB_PATH for local data locations.]\n\n## Skill Version(s):\n\n0.1.1 (source: ClawHub release metadata; artifact SKILL.md frontmatter reports 1.1.0)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v0.1.1:LICENSE\n\nMIT No Attribution\n\nCopyright 2026 Bartosz Sojka\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this\nsoftware and associated documentation files (the \"Software\"), to deal in the Software\nwithout restriction, including without limitation the rights to use, copy, modify,\nmerge, publish, distribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\nINCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\nCONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\nOR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nArchive v0.1.0: 26 files, 53942 bytes\n\nFiles: .clawhubignore (200b), .gitignore (228b), CLAUDE.md (1133b), LICENSE (908b), README.md (1680b), references (0b), references/architecture.md (7100b), references/schema.md (1540b), scripts (0b), scripts/ingest_health_metrics.py (5595b), scripts/ingest_workouts.py (9253b), scripts/ingest.py (1119b), scripts/lib (0b), scripts/lib/geo.py (2514b), scripts/lib/ichart.py (7130b), scripts/lib/metrics.py (2574b), scripts/lib/query.py (1043b), scripts/lib/svg.py (11201b), scripts/report_daily_summary.py (26655b), scripts/report_health_metrics.py (18934b), scripts/report_rings.py (8676b), scripts/report_training_load.py (10308b), scripts/report_workouts.py (13889b), scripts/report.py (2145b), SKILL.md (4479b), _meta.json (133b)\n\nFile v0.1.0:SKILL.md\n\n---\nname: health-metrics\ndescription: >-\n  Ingest Apple Health Auto Export JSON (HealthMetrics + Workouts) into a local DuckDB\n  database and render offline HTML dashboards plus a Markdown daily summary (activity\n  rings, training load, sleep, vitals, per-workout maps). Use when the user wants to\n  process Apple Health exports, refresh their health dashboards, or get a training-load /\n  rings / sleep / vitals report from their exported data.\nversion: 1.0.0\nmetadata:\n  openclaw:\n    emoji: \"🏃\"\n    requires:\n      bins: [\"python3\", \"duckdb\"]\n    env: [\"HEALTH_METRICS_DIR\", \"HEALTH_WORKOUTS_DIR\", \"HEALTH_DB_PATH\"]\n    os: [\"macos\", \"linux\"]\n---\n\n# Health Metrics\n\nA self-contained pipeline that ingests **Apple Health Auto Export** JSON feeds into a local\nDuckDB database and renders static, offline-first HTML dashboards plus an AI-oriented Markdown\ndigest. No web server, no scheduler, no pip packages — just Python stdlib and the `duckdb` CLI.\nYou run it on demand whenever new export files land.\n\n## Prerequisites\n\n- `python3` and the `duckdb` CLI binary on `PATH` (no Python `duckdb` package needed).\n- Apple Health Auto Export daily JSON files available on disk (see **Configuration**).\n- Internet is needed only to *view* workout route maps (Leaflet + satellite tiles from a CDN).\n  Everything else renders fully offline.\n\n## Normal workflow\n\nRegenerate everything after new exports arrive:\n\n```bash\npython3 {baseDir}/scripts/ingest.py && python3 {baseDir}/scripts/report.py -o <OUT_DIR>\n```\n\n- `ingest.py` reads the source JSON and upserts into the DuckDB file (idempotent — safe to\n  re-run; unchanged files are skipped).\n- `report.py -o <OUT_DIR>` writes all dashboards into a directory **you choose**. Pick a working\n  or output directory the user controls; do not write inside the skill folder.\n\n## Configuration (all optional — sensible defaults)\n\n| Env var | Purpose | Default |\n|---|---|---|\n| `HEALTH_METRICS_DIR` | Folder of `HealthMetrics-YYYY-MM-DD.json` files | Apple Health Auto Export iCloud `…/iCloud Drive HealthMetrics` under `$HOME` |\n| `HEALTH_WORKOUTS_DIR` | Folder of `Workouts-YYYY-MM-DD.json` files | Apple Health Auto Export iCloud `…/iCloud Drive Workouts` under `$HOME` |\n| `HEALTH_DB_PATH` | Where the DuckDB file lives | `~/.local/state/health-metrics/health.duckdb` |\n\n`--db PATH` on `ingest.py` / `report.py` overrides `HEALTH_DB_PATH` for that run.\n\n> ⚠️ **The DuckDB database is personal, sensitive health data.** It is created on first ingest,\n> lives **outside** this skill, and must **never** be committed, published, or shared. It is not\n> part of the skill bundle.\n\n## Choosing where reports go\n\n`report.py -o <DIR>` mirrors this layout beneath `<DIR>`:\n\n- HTML dashboards → `<DIR>/` (`daily-*.html`, `weekly-*.html`, `monthly-*.html`, `rings.html`,\n  `training-load.html`)\n- Per-workout pages + index → `<DIR>/workouts/`\n- Markdown daily summaries → `<DIR>/summary/`\n\n## Individual stages (targeted re-runs)\n\nEach script is independently runnable and honors `HEALTH_DB_PATH`. A single script's `--out-dir`\nis the *literal, flat* directory it writes into (no subfolder appended).\n\n| Script | What it produces | Key flags |\n|---|---|---|\n| `scripts/ingest_health_metrics.py` | `samples_qty` / `samples_hr` / `sleep_sessions` | — |\n| `scripts/ingest_workouts.py` | `workouts` + `workout_route/hr/hr_recovery` | — |\n| `scripts/report_health_metrics.py` | activity / sleep / vitals summaries | `--period daily\\|weekly\\|monthly\\|all` `--date YYYY-MM-DD` `-o DIR` |\n| `scripts/report_workouts.py` | one page per workout + index | `--force` (re-render all) `-o DIR` |\n| `scripts/report_rings.py` | Apple-style activity rings | `-o DIR` |\n| `scripts/report_training_load.py` | TRIMP training load trend | `-o DIR` |\n| `scripts/report_daily_summary.py` | Markdown digest for AI readers | `--date YYYY-MM-DD` `-o DIR` |\n\n## Ad-hoc analysis\n\nQuery the database directly — the tables in `references/schema.md` are the full analysis surface:\n\n```bash\nduckdb \"$HEALTH_DB_PATH\" -json -c \"SELECT * FROM workouts ORDER BY date DESC LIMIT 5\"\n```\n\nOr from Python via `scripts/lib/query.py`'s `query(sql)` helper (shells out to the `duckdb` CLI,\nreturns parsed JSON rows).\n\n## Internals\n\nSee `{baseDir}/references/architecture.md` (data flow, ingestion pattern, report conventions,\n`lib/` helpers) and `{baseDir}/references/schema.md` (DuckDB table reference) before modifying\nthe scripts or adding a new report/metric.\n\nFile v0.1.0:README.md\n\n# health-metrics\n\nAn **Agent Skill** for **Apple Health Auto Export** reporting and logging. It lets an agent\n(OpenClaw, Claude Code, or any tool that reads the portable `SKILL.md` format) ingest Apple\nHealth Auto Export JSON feeds into a local DuckDB database and render offline, self-contained\nHTML dashboards plus a Markdown daily digest — activity rings, training load, sleep, vitals,\nand per-workout maps.\n\nNo web server, no scheduler, no Python packages beyond the stdlib + the `duckdb` CLI. Runs on\ndemand whenever new export files land.\n\n## Quick start\n\n```bash\npython3 scripts/ingest.py && python3 scripts/report.py -o <OUT_DIR>\n```\n\n- `ingest.py` parses the Apple Health Auto Export daily JSON and upserts into DuckDB (idempotent).\n- `report.py -o <OUT_DIR>` renders all dashboards into a directory you choose.\n\nSource folders and the database location are configurable via environment variables\n(`HEALTH_METRICS_DIR`, `HEALTH_WORKOUTS_DIR`, `HEALTH_DB_PATH`), with sensible defaults.\n\n## Learn more\n\n- **[`SKILL.md`](SKILL.md)** — the skill entry point: prerequisites, workflow, configuration, and\n  every runnable stage.\n- **[`references/architecture.md`](references/architecture.md)** — internals (data flow, ingestion\n  pattern, report conventions, `scripts/lib/` helpers).\n- **[`references/schema.md`](references/schema.md)** — the DuckDB table reference.\n\n## Data & privacy\n\nThe DuckDB database is **personal, sensitive health data**. It is created on first ingest, lives\n**outside** this repository (default `~/.local/state/health-metrics/health.duckdb`), and is never\ncommitted, published, or bundled.\n\n## License\n\n[MIT No Attribution (MIT-0)](LICENSE).\n\nFile v0.1.0:_meta.json\n\n{\n  \"ownerId\": \"kn75shmdha8pdb93g5rpghnw6s80knmx\",\n  \"slug\": \"health-metrics\",\n  \"version\": \"0.1.0\",\n  \"publishedAt\": 1784705478422\n}\n\nFile v0.1.0:references/architecture.md\n\n# Architecture\n\nInternals of the `health-metrics` skill — read this before modifying the scripts or adding a new\nreport/metric. Operational usage lives in `../SKILL.md`; the table reference in `./schema.md`.\n\n## Data flow\n\n```\nApple Health Auto Export folders (source JSON, outside this skill)\n  -> scripts/ingest_*.py   (parse + normalize, idempotent upsert into the DuckDB file)\n  -> scripts/report_*.py   (query DuckDB, render self-contained HTML/Markdown into an out-dir)\n```\n\nSource JSON lives outside the skill, in the Apple Health Auto Export folders. The folders are\nconfigurable via `HEALTH_METRICS_DIR` / `HEALTH_WORKOUTS_DIR` (see `../SKILL.md`); the defaults\npoint at the standard iCloud locations:\n\n- `…/iCloud~com~ifunography~HealthExport/Documents/iCloud Drive HealthMetrics/HealthMetrics-YYYY-MM-DD.json`\n- `…/iCloud Drive Workouts/Workouts-YYYY-MM-DD.json`\n\nEach ingester only reads **daily** files (`HealthMetrics-YYYY-MM-DD.json` /\n`Workouts-YYYY-MM-DD.json`). Weekly/monthly/yearly rollup files in those same folders are ignored.\n\nThe DuckDB file location comes from `HEALTH_DB_PATH` (default\n`~/.local/state/health-metrics/health.duckdb`), resolved centrally by `scripts/lib/query.py`'s\n`db_path()`. It is per-person sensitive state — created on first ingest, never versioned or\nbundled.\n\n## Ingestion pattern (`scripts/ingest_health_metrics.py`, `scripts/ingest_workouts.py`)\n\nBoth follow the same idempotent shape, invoking the `duckdb` CLI via `subprocess` (never the\n`duckdb` Python package):\n\n1. `CREATE TABLE IF NOT EXISTS` schema block, run every time.\n2. Glob candidate files matching the strict daily-file regex.\n3. Skip a file if its `mtime` matches what's recorded in `ingested_files` / `ingested_workout_files`.\n4. Otherwise DELETE-then-INSERT the affected rows in one transaction, then upsert the tracking row.\n   - `ingest_health_metrics.py` dedupes by `date` (one file = one day = full delete/replace of that\n     day's rows across all three tables).\n   - `ingest_workouts.py` dedupes by workout `id`, not by file/day — a workout can appear in\n     multiple period files with the same `id`, and re-ingesting replaces that workout's rows\n     wherever it lands.\n5. `scripts/lib/metrics.py` is a whitelist: only metrics listed there are kept from HealthMetrics\n   exports. Anything else (nutrition, weight, mindful minutes, swimming, …) is silently dropped at\n   ingest time — check that file before assuming a metric name is queryable.\n6. `ingest_workouts.py` additionally defends against Apple omitting fields per-workout-type (e.g.\n   `flightsClimbed` absent for a swim) rather than nulling them — `available_fields()` probes the\n   file's inferred schema via `DESCRIBE` before building the INSERT, since an all-null/all-absent\n   JSON field can't be `.qty`-accessed or UNNESTed. Read `WORKOUT_FIELD_EXPRS` in that file before\n   adding a new workout column.\n\n## Report scripts\n\nAll build a full HTML/Markdown string in Python and `write_text()` it into the out-dir.\n\n- `report_health_metrics.py` — daily/weekly/rolling-28-day summary dashboards (activity, sleep,\n  vitals, \"other tracked\" metrics, auto-generated text insights vs. a 28-day baseline). Static SVG\n  via `lib/svg.py`.\n- `report_workouts.py` — one detail page per workout (`workouts/<date>-<slug>.html`) plus\n  `index.html`. Route map uses Leaflet + Esri World Imagery satellite tiles from public CDNs at\n  view time — the **only** part that needs internet to render; everything else is fully offline.\n- `report_rings.py` — Apple-style Activity Rings (Move/Exercise/Stand against fixed goals), hero\n  ring + 7-day mini rings + 28-day interactive trend charts via `lib/ichart.py`.\n- `report_training_load.py` — TRIMP-based training load (Banister formula) as a 7-day rolling sum\n  (\"weekly load\") vs. a tau=28-day EWMA scaled to weekly-equivalent units (\"monthly trend\"). Both\n  series kept in the same units so they're visually comparable on one chart (see `ewma_series()`).\n- `report_daily_summary.py` — the **only Markdown report**, written for AI/LLM readers. One\n  self-contained file per day at `summary/YYYY-MM-DD.md` (YAML frontmatter + prose), deep-diving the\n  target day plus 7d/28d/90d trend context, in three insight tiers (progress, readiness,\n  trajectory). It **imports and reuses** the engines from `report_training_load.py` (TRIMP/EWMA),\n  `report_rings.py` (ring goals + `daily_activity`), and `report_health_metrics.py` (`DISPLAY_NAME`)\n  rather than re-deriving them, so the Markdown numbers can't drift from the HTML dashboards.\n\n### CSS building convention\n\nAll report `HTML_HEAD` strings build CSS via plain single-brace strings +\n`.replace(\"__PLACEHOLDER__\", …)` substitution, **not** `str.format()` — a past bug came from CSS\nbraces needing `{{ }}` escaping for a `.format()` that was never actually called, silently breaking\nthe `<style>` block. Keep new report scripts consistent with this pattern. (The only legitimate\n`__…__` token that survives into rendered output is `window.__ICHART_DATA`, the ichart runtime.)\n\n## `scripts/lib/` (shared, imported by ingest and report scripts)\n\n- `query.py` — `db_path()` (resolves `HEALTH_DB_PATH`) and the `query(sql)` helper.\n- `metrics.py` — metric whitelist/category maps (`QTY_METRICS`, `HR_METRICS`, `ALL_QTY`,\n  `CUMULATIVE_METRICS`, `CATEGORY_OF`). Whether a metric is summed vs. averaged over a period is\n  decided by `CUMULATIVE_METRICS` — check it before aggregating a new metric.\n- `geo.py` — `haversine_m`, `douglas_peucker` (route simplification), `km_splits` (per-km pace).\n  Pure stdlib.\n- `svg.py` — static precomputed SVG chart primitives (rings, gauges, calendar heatmap, stacked\n  bars, sparklines, `line_chart`). Used where no interactivity is needed.\n- `ichart.py` — the interactive chart component (client-side hover/touch tooltips). Use this (not\n  `svg.line_chart`) for any new interactive trend chart.\n\nEvery script does `sys.path.insert(0, str(Path(__file__).parent))` then imports siblings and\n`lib.*`, so the whole `scripts/` tree relocates as a unit without import edits.\n\n## Adding a stage\n\nBoth `scripts/ingest.py` and `scripts/report.py` are thin orchestrators — import any new stage\nmodule and call its `main()` / `render()` in sequence there. Follow the\n`report_rings.py`/`report_training_load.py` pattern (interactive `lib/ichart.py` charts,\n`.replace()`-based HTML head) for new reports unless a chart genuinely doesn't need interactivity.\n\n## Historical note: backfill\n\nHistory older than when daily exports began was originally bootstrapped once with two one-off\nscripts (`split_healthmetrics_history.py`, `split_workout_history.py`) that split multi-day\nweekly/monthly/yearly rollup files into synthetic per-day files the daily-only ingesters could pick\nup. Those scripts were non-idempotent-by-design, have already been run, and are **not shipped** with\nthis skill. HealthMetrics *yearly* files were intentionally never split (their metrics are bucketed\nper-week, not per-day, so splitting would misattribute a week's total to a single day).\n\nFile v0.1.0:references/schema.md\n\n# DuckDB schema\n\nThe full analysis surface. Query these tables directly via the `duckdb` CLI or\n`scripts/lib/query.py`'s `query(sql)` — there is no ORM, just SQL strings. If a metric you expect\nisn't present, check `scripts/lib/metrics.py` first (it whitelists what ingest keeps).\n\n## Analysis tables\n\n- **`samples_qty(date, ts, metric, unit, qty, source)`** — simple quantity samples (steps, active\n  energy, …).\n- **`samples_hr(date, ts, metric, unit, min, avg, max, source)`** — min/avg/max-per-interval\n  metrics (heart_rate).\n- **`sleep_sessions(date, ts, in_bed_start, in_bed_end, sleep_start, sleep_end, core, deep, rem,\n  awake, asleep, in_bed, total_sleep, source)`**.\n- **`workouts(id PK, date, name, start, end, duration_s, is_indoor, location, temperature_c,\n  humidity_pct, intensity, distance_km, avg_hr, min_hr, max_hr, avg_speed, max_speed,\n  elevation_up_m, active_energy_kj, total_energy_kj, step_cadence, flights_climbed)`** — one row per\n  workout.\n- **`workout_route(workout_id, seq, ts, lat, lon, altitude, speed, course)`** — GPS points, only for\n  outdoor workouts.\n- **`workout_hr(workout_id, ts, min, avg, max)`** — per-minute HR during a workout.\n- **`workout_hr_recovery(workout_id, seq, ts, min, avg, max)`** — post-workout HR recovery curve.\n\n## Bookkeeping tables (not analysis data)\n\n- **`ingested_files(filename PK, mtime, ingested_at)`** — mtime-based dedup tracking for\n  HealthMetrics files.\n- **`ingested_workout_files(filename PK, mtime, ingested_at)`** — same, for Workouts files.\n\nFile v0.1.0:CLAUDE.md\n\n# CLAUDE.md\n\nThis repository **is** an Agent Skill named `health-metrics` (portable SKILL.md format, usable by\nOpenClaw, Claude Code, and other agents). The skill ingests Apple Health Auto Export JSON into a\nlocal DuckDB database and renders offline HTML dashboards + a Markdown daily summary.\n\nStart here:\n\n- **`SKILL.md`** — what the skill does, prerequisites, the normal workflow, configuration\n  (`HEALTH_METRICS_DIR` / `HEALTH_WORKOUTS_DIR` / `HEALTH_DB_PATH`), and every runnable stage.\n- **`references/architecture.md`** — internals: data flow, ingestion pattern, report conventions,\n  the `scripts/lib/` helpers, and the CSS `.replace()` convention.\n- **`references/schema.md`** — the DuckDB table reference (the full analysis surface).\n\nExecutable code lives in `scripts/` (with shared helpers in `scripts/lib/`). The DuckDB file is\nper-person sensitive data — it lives outside this tree (default\n`~/.local/state/health-metrics/health.duckdb`) and is never committed or bundled (see\n`.gitignore` / `.clawhubignore`).\n\nNormal workflow:\n\n```bash\npython3 scripts/ingest.py && python3 scripts/report.py -o <OUT_DIR>\n```\n\nFile v0.1.0:LICENSE\n\nMIT No Attribution\n\nCopyright 2026 Bartosz Sojka\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this\nsoftware and associated documentation files (the \"Software\"), to deal in the Software\nwithout restriction, including without limitation the rights to use, copy, modify,\nmerge, publish, distribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\nINCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\nCONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\nOR THE USE OR OTHER DEALINGS IN THE SOFTWARE.","readmeExcerpt":"Skill: Health Metrics Owner: bartsoj Summary: Ingest Apple Health Auto Export JSON (HealthMetrics + Workouts) into a local DuckDB database and render offline HTML dashboards plus a Markdown daily summary... Tags: latest:0.1.1 Version history: v0.1.1 | 2026-07-22T10:05:47.586Z | auto health-metrics 1.1.0 introduces a new runner script and improves reliability for iCloud users. - Added scripts/run.sh: a unified command","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"python3 {baseDir}/scripts/ingest.py && python3 {baseDir}/scripts/report.py -o <OUT_DIR>"},{"language":"bash","snippet":"bash {baseDir}/scripts/run.sh daily-md [YYYY-MM-DD] [OUT_DIR]   # ingest -> flat <OUT_DIR>/YYYY-MM-DD.md (default date = today)\nbash {baseDir}/scripts/run.sh html [OUT_DIR]                    # ingest -> full HTML dashboard set in OUT_DIR\nbash {baseDir}/scripts/run.sh ingest                           # materialize + ingest only"},{"language":"text","snippet":"IO Error: Could not read from file \"…HealthMetrics-YYYY-MM-DD.json\": Resource deadlock avoided"},{"language":"bash","snippet":"duckdb \"$HEALTH_DB_PATH\" -json -c \"SELECT * FROM workouts ORDER BY date DESC LIMIT 5\""},{"language":"bash","snippet":"python3 scripts/ingest.py && python3 scripts/report.py -o <OUT_DIR>"},{"language":"bash","snippet":"bash scripts/run.sh daily-md [YYYY-MM-DD] [OUT_DIR]   # Markdown daily summary (default: today)\nbash scripts/run.sh html [OUT_DIR]                    # full HTML dashboard set\nbash scripts/run.sh ingest                            # materialize + ingest only"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: health-metrics\ndescription: >-\n  Ingest Apple Health Auto Export JSON (HealthMetrics + Workouts) into a local DuckDB\n  database and render offline HTML dashboards plus a Markdown daily summary (activity\n  rings, training load, sleep, vitals, per-workout maps). Use when the user wants to\n  process Apple Health exports, refresh their health dashboards, or get a training-load /\n  rings / sleep / vitals report from their exported data.\nversion: 1.1.0\nmetadata:\n  openclaw:\n    emoji: \"🏃\"\n    requires:\n      bins: [\"python3\", \"duckdb\"]\n    env: [\"HEALTH_METRICS_DIR\", \"HEALTH_WORKOUTS_DIR\", \"HEALTH_DB_PATH\"]\n    os: [\"macos\", \"linux\"]\n---\n\n# Health Metrics\n\nA self-contained pipeline that ingests **Apple Health Auto Export** JSON feeds into a local\nDuckDB database and renders static, offline-first HTML dashboards plus an AI-oriented Markdown\ndigest. No web server, no scheduler, no pip packages — just Python stdlib and the `duckdb` CLI.\nYou run it on demand whenever new export files land.\n\n## Prerequisites\n\n- `python3` and the `duckdb` CLI binary on `PATH` (no Python `duckdb` package needed).\n- Apple Health Auto Export daily JSON files available on disk (see **Configuration**).\n- Internet is needed only to *view* workout route maps (Leaflet + satellite tiles from a CDN).\n  Everything else renders fully offline.\n\n## Normal workflow\n\nRegenerate everything after new exports arrive:\n\n```bash\npython3 {baseDir}/scripts/ingest.py && python3 {baseDir}/scripts/report.py -o <OUT_DIR>\n```\n\n- `ingest.py` reads the source JSON and upserts into the DuckDB file (idempotent — safe to\n  re-run; unchanged files are skipped).\n- `report.py -o <OUT_DIR>` writes all dashboards into a directory **you choose**. Pick a working\n  or output directory the user controls; do not write inside the skill folder.\n\n## One-command runner (`scripts/run.sh`) — recommended\n\n`scripts/run.sh` wraps the pipeline and handles two things the raw scripts do not:\n\n1. **Materializes iCloud placeholder files** before ingest (see the gotcha below).\n2. **Always ingests before rendering**, so reports never show stale data.\n\n```bash\nbash {baseDir}/scripts/run.sh daily-md [YYYY-MM-DD] [OUT_DIR]   # ingest -> flat <OUT_DIR>/YYYY-MM-DD.md (default date = today)\nbash {baseDir}/scripts/run.sh html [OUT_DIR]                    # ingest -> full HTML dashboard set in OUT_DIR\nbash {baseDir}/scripts/run.sh ingest                           # materialize + ingest only\n```\n\nOutput dirs: `daily-md` defaults to `$HEALTH_MD_DIR` (else `reports/summary`); `html` defaults\nto `$HEALTH_HTML_DIR` (else a timestamped `reports/html-*`). `html` prints `HTML_OUT_DIR=<dir>`\non the last lines. Exits non-zero on failure so schedulers surface it.\n\n**Use `daily-md` from a scheduler** to keep a per-day Markdown log current (e.g. a nightly cron\nthat writes into a knowledge/notes folder), and **`html` on demand** for shareable dashboards.\n\n### ⚠️ iCloud \"dataless placeholder\" gotcha (macOS)\n\nApple Health Auto Export writes into "},{"path":"README.md","content":"# health-metrics\n\nAn **Agent Skill** for **Apple Health Auto Export** reporting and logging. It lets an agent\n(OpenClaw, Claude Code, or any tool that reads the portable `SKILL.md` format) ingest Apple\nHealth Auto Export JSON feeds into a local DuckDB database and render offline, self-contained\nHTML dashboards plus a Markdown daily digest — activity rings, training load, sleep, vitals,\nand per-workout maps.\n\nNo web server, no scheduler, no Python packages beyond the stdlib + the `duckdb` CLI. Runs on\ndemand whenever new export files land.\n\n## Quick start\n\n```bash\npython3 scripts/ingest.py && python3 scripts/report.py -o <OUT_DIR>\n```\n\n- `ingest.py` parses the Apple Health Auto Export daily JSON and upserts into DuckDB (idempotent).\n- `report.py -o <OUT_DIR>` renders all dashboards into a directory you choose.\n\nOr use the one-command runner, which ingests first and (on macOS/iCloud) force-downloads any\nevicted placeholder files before reading them:\n\n```bash\nbash scripts/run.sh daily-md [YYYY-MM-DD] [OUT_DIR]   # Markdown daily summary (default: today)\nbash scripts/run.sh html [OUT_DIR]                    # full HTML dashboard set\nbash scripts/run.sh ingest                            # materialize + ingest only\n```\n\n> **macOS / iCloud note:** if the source folders live in iCloud Drive with \"Optimize Mac\n> Storage\" on, reads can fail with `Resource deadlock avoided` until files are downloaded.\n> `run.sh` handles this automatically; the permanent fix is Finder → right-click each source\n> folder → **Keep Downloaded**. See [`SKILL.md`](SKILL.md) for details.\n\nSource folders and the database location are configurable via environment variables\n(`HEALTH_METRICS_DIR`, `HEALTH_WORKOUTS_DIR`, `HEALTH_DB_PATH`), with sensible defaults.\n\n## Learn more\n\n- **[`SKILL.md`](SKILL.md)** — the skill entry point: prerequisites, workflow, configuration, and\n  every runnable stage.\n- **[`references/architecture.md`](references/architecture.md)** — internals (data flow, ingestion\n  pattern, report conventions, `scripts/lib/` helpers).\n- **[`references/schema.md`](references/schema.md)** — the DuckDB table reference.\n\n## Data & privacy\n\nThe DuckDB database is **personal, sensitive health data**. It is created on first ingest, lives\n**outside** this repository (default `~/.local/state/health-metrics/health.duckdb`), and is never\ncommitted, published, or bundled.\n\n## License\n\n[MIT No Attribution (MIT-0)](LICENSE)."},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn75shmdha8pdb93g5rpghnw6s80knmx\",\n  \"slug\": \"health-metrics\",\n  \"version\": \"0.1.1\",\n  \"publishedAt\": 1784714747586\n}"},{"path":"references/architecture.md","content":"# Architecture\n\nInternals of the `health-metrics` skill — read this before modifying the scripts or adding a new\nreport/metric. Operational usage lives in `../SKILL.md`; the table reference in `./schema.md`.\n\n## Data flow\n\n```\nApple Health Auto Export folders (source JSON, outside this skill)\n  -> scripts/ingest_*.py   (parse + normalize, idempotent upsert into the DuckDB file)\n  -> scripts/report_*.py   (query DuckDB, render self-contained HTML/Markdown into an out-dir)\n```\n\nSource JSON lives outside the skill, in the Apple Health Auto Export folders. The folders are\nconfigurable via `HEALTH_METRICS_DIR` / `HEALTH_WORKOUTS_DIR` (see `../SKILL.md`); the defaults\npoint at the standard iCloud locations:\n\n- `…/iCloud~com~ifunography~HealthExport/Documents/iCloud Drive HealthMetrics/HealthMetrics-YYYY-MM-DD.json`\n- `…/iCloud Drive Workouts/Workouts-YYYY-MM-DD.json`\n\nEach ingester only reads **daily** files (`HealthMetrics-YYYY-MM-DD.json` /\n`Workouts-YYYY-MM-DD.json`). Weekly/monthly/yearly rollup files in those same folders are ignored.\n\nThe DuckDB file location comes from `HEALTH_DB_PATH` (default\n`~/.local/state/health-metrics/health.duckdb`), resolved centrally by `scripts/lib/query.py`'s\n`db_path()`. It is per-person sensitive state — created on first ingest, never versioned or\nbundled.\n\n## Ingestion pattern (`scripts/ingest_health_metrics.py`, `scripts/ingest_workouts.py`)\n\nBoth follow the same idempotent shape, invoking the `duckdb` CLI via `subprocess` (never the\n`duckdb` Python package):\n\n1. `CREATE TABLE IF NOT EXISTS` schema block, run every time.\n2. Glob candidate files matching the strict daily-file regex.\n3. Skip a file if its `mtime` matches what's recorded in `ingested_files` / `ingested_workout_files`.\n4. Otherwise DELETE-then-INSERT the affected rows in one transaction, then upsert the tracking row.\n   - `ingest_health_metrics.py` dedupes by `date` (one file = one day = full delete/replace of that\n     day's rows across all three tables).\n   - `ingest_workouts.py` dedupes by workout `id`, not by file/day — a workout can appear in\n     multiple period files with the same `id`, and re-ingesting replaces that workout's rows\n     wherever it lands.\n5. `scripts/lib/metrics.py` is a whitelist: only metrics listed there are kept from HealthMetrics\n   exports. Anything else (nutrition, weight, mindful minutes, swimming, …) is silently dropped at\n   ingest time — check that file before assuming a metric name is queryable.\n6. `ingest_workouts.py` additionally defends against Apple omitting fields per-workout-type (e.g.\n   `flightsClimbed` absent for a swim) rather than nulling them — `available_fields()` probes the\n   file's inferred schema via `DESCRIBE` before building the INSERT, since an all-null/all-absent\n   JSON field can't be `.qty`-accessed or UNNESTed. Read `WORKOUT_FIELD_EXPRS` in that file before\n   adding a new workout column.\n\n## Report scripts\n\nAll build a full HTML/Markdown string in Python and `write_text()` it into the out-d"},{"path":"references/schema.md","content":"# DuckDB schema\n\nThe full analysis surface. Query these tables directly via the `duckdb` CLI or\n`scripts/lib/query.py`'s `query(sql)` — there is no ORM, just SQL strings. If a metric you expect\nisn't present, check `scripts/lib/metrics.py` first (it whitelists what ingest keeps).\n\n## Analysis tables\n\n- **`samples_qty(date, ts, metric, unit, qty, source)`** — simple quantity samples (steps, active\n  energy, …).\n- **`samples_hr(date, ts, metric, unit, min, avg, max, source)`** — min/avg/max-per-interval\n  metrics (heart_rate).\n- **`sleep_sessions(date, ts, in_bed_start, in_bed_end, sleep_start, sleep_end, core, deep, rem,\n  awake, asleep, in_bed, total_sleep, source)`**.\n- **`workouts(id PK, date, name, start, end, duration_s, is_indoor, location, temperature_c,\n  humidity_pct, intensity, distance_km, avg_hr, min_hr, max_hr, avg_speed, max_speed,\n  elevation_up_m, active_energy_kj, total_energy_kj, step_cadence, flights_climbed)`** — one row per\n  workout.\n- **`workout_route(workout_id, seq, ts, lat, lon, altitude, speed, course)`** — GPS points, only for\n  outdoor workouts.\n- **`workout_hr(workout_id, ts, min, avg, max)`** — per-minute HR during a workout.\n- **`workout_hr_recovery(workout_id, seq, ts, min, avg, max)`** — post-workout HR recovery curve.\n\n## Bookkeeping tables (not analysis data)\n\n- **`ingested_files(filename PK, mtime, ingested_at)`** — mtime-based dedup tracking for\n  HealthMetrics files.\n- **`ingested_workout_files(filename PK, mtime, ingested_at)`** — same, for Workouts files."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":1816,"uniquenessScore":43,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-09T09:31:07.083Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-09T09:31:07.083Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-09T23:09:47.888Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}