파일 목록으로

Local Data Directory (gitignored)

This directory holds raw Health Data Bridge exports needed by charts.mjs to regenerate the visualizations embedded in the sibling reports (report.html, caffeine-multidim.html, cycling-performance.html, cola-drill-down.html).

Raw *.json files are gitignored — they contain personal HealthKit data (timestamps, biometric values, food labels) and we keep them out of the repo deliberately. Only this README and .gitignore are tracked.

Required files

After populating this directory locally, the chart generator expects:

FileHealth Data Bridge metricModeApprox size
caffeine_1y.jsonquantity_sample:dietaryCaffeineraw~110 KB
sleep_1y.jsoncategory_sample:sleepAnalysisraw, paginated~1.1 MB
hp_hrv.jsonquantity_sample:heartRateVariabilitySDNNbucketed/day~40 KB
hp_rhr.jsonquantity_sample:restingHeartRatebucketed/day~40 KB
hp_steps.jsonquantity_sample:stepCountbucketed/day~36 KB
hp_kcal.jsonquantity_sample:activeEnergyBurnedbucketed/day~38 KB
hp_workouts.jsonworkoutraw~66 KB
hp_glucose.jsonquantity_sample:bloodGlucoseraw, paginatedvaries

Reproduction (full pull)

Prereqs: Health Data Bridge iOS app paired and reachable. Verify with cd web/packages/healthbridge-cli && bun run doctor (expect Overall: healthy).

cd web/packages/healthbridge-cli

WIN_START=2025-12-19T00:00:00+09:00
WIN_END=2026-04-11T23:59:59+09:00
DATA=../../research/taeho-health/sleep-caffeine/data

# Caffeine (raw)
bun run query --object-type quantity_sample --identifier dietaryCaffeine \
  --mode raw --timezone Asia/Seoul \
  --start-at "$WIN_START" --end-at "$WIN_END" \
  --limit 1000 --json > $DATA/caffeine_1y.json

# Sleep (raw, paginated — 4 pages typically cover 110 days)
for c in 0 1000 2000 3000; do
  bun run query --object-type category_sample --identifier sleepAnalysis \
    --mode raw --timezone Asia/Seoul \
    --start-at "$WIN_START" --end-at "$WIN_END" \
    --limit 1000 --cursor "$c" --json > $DATA/sleep_p$c.json
done
node -e '
  const fs=require("fs");
  const ps=[0,1000,2000,3000].map(c=>JSON.parse(fs.readFileSync(`'$DATA'/sleep_p${c}.json`)));
  const merged={...ps[0],records:ps.flatMap(p=>p.records),nextCursor:undefined};
  fs.writeFileSync("'$DATA'/sleep_1y.json",JSON.stringify(merged));
'

# HRV / RHR / Steps / Calories (daily bucketed)
for METRIC in heartRateVariabilitySDNN:hrv restingHeartRate:rhr stepCount:steps activeEnergyBurned:kcal; do
  ID="${METRIC%:*}"
  OUT="${METRIC#*:}"
  bun run query --object-type quantity_sample --identifier $ID \
    --mode bucketed --bucket day --timezone Asia/Seoul \
    --start-at "$WIN_START" --end-at "$WIN_END" \
    --limit 1000 --json > $DATA/hp_$OUT.json
done

# Workouts (raw)
bun run query --object-type workout \
  --mode raw --timezone Asia/Seoul \
  --start-at "$WIN_START" --end-at "$WIN_END" \
  --limit 1000 --json > $DATA/hp_workouts.json

# Glucose (raw, paginated — typically 3-4 pages)
for c in 0 1000 2000 3000; do
  bun run query --object-type quantity_sample --identifier bloodGlucose \
    --mode raw --timezone Asia/Seoul \
    --start-at "$WIN_START" --end-at "$WIN_END" \
    --limit 1000 --cursor "$c" --json > $DATA/glucose_p$c.json
done
node -e '
  const fs=require("fs");
  const ps=[0,1000,2000,3000].map(c=>JSON.parse(fs.readFileSync(`'$DATA'/glucose_p${c}.json`)));
  const merged={...ps[0],records:ps.flatMap(p=>p.records),nextCursor:undefined};
  fs.writeFileSync("'$DATA'/hp_glucose.json",JSON.stringify(merged));
'

Regeneration

cd <repo root>
node research/taeho-health/sleep-caffeine/charts.mjs

This reads everything in data/, computes per-day aggregates, and writes:

  • research/taeho-health/sleep-caffeine/charts/*.svg — individual SVG files (also tracked in git)
  • Updated HTML reports (report.html, caffeine-multidim.html, cycling-performance.html, cola-drill-down.html) with embedded chart cards

If data/ is empty, the script logs which files are missing and skips the charts that depend on them — partial regeneration still works.

0 / 16