Apple WatchのデータをブログのグラフにするまでのTech Stack
前の記事で掲載したVO2Maxと安静時心拍のグラフ、どうやって作ったか聞かれたのでまとめておく。
全体の流れ
Apple Watch
↓ 自動記録
Apple Health アプリ
↓ XMLエクスポート → パース
DuckDB(ローカルDB)
↓ SQLクエリ(MCP経由)
Claude(AI)が数値を取得・分析
↓ MDXに埋め込み
Chart.js + Astro でグラフ表示
1. Apple Health → DuckDB
Apple WatchはVO2Max・安静時心拍・HRVなどを自動で計測し、iPhoneのHealthアプリに蓄積している。これをエクスポートしてローカルのDuckDBに取り込んでいる。
DuckDBはSQLが使える軽量な組み込みDBで、Pythonやコマンドラインから手軽に使える。Health データは hkquantitytypeidentifiervo2max_2026 のような名前のテーブルに格納されている。
2. MCP(Model Context Protocol)でSQLクエリ
MCP(Model Context Protocol)はClaudeなどのAIがローカルのツールやDBと連携するための仕組み。apple-health MCPサーバーを使うことで、ClaudeがDuckDBに直接SQLクエリを投げられる。
月別平均を出すのに使ったクエリはこんな感じ。
SELECT
strftime('%Y-%m', startDate) as month,
ROUND(AVG(value), 1) as avg_vo2max
FROM hkquantitytypeidentifiervo2max_2026
WHERE startDate >= '2025-01-01'
GROUP BY month
ORDER BY month ASC
このクエリをAIが実行→数値を取得→記事の原稿に埋め込む、という流れをほぼ自動でやってくれた。
3. Chart.js + Astro でグラフ表示
グラフの描画にはChart.jsを使っている。AstroのMDX記事の中でコンポーネントとして呼び出せるよう、LineChart.astro というコンポーネントを作った。
---
// src/components/LineChart.astro
interface Props {
id: string;
labels: string[];
datasets: { label: string; data: number[]; color: string; }[];
yLabel?: string;
yMin?: number;
yMax?: number;
}
---
<canvas id={id} data-labels={JSON.stringify(labels)} ... />
<script>
import { Chart, ... } from 'chart.js';
// canvasのdata属性からデータを読み取ってグラフ描画
</script>
MDX記事側はこれだけで呼び出せる。
import LineChart from '../../components/LineChart.astro';
<LineChart
id="vo2max-chart"
labels={["2025-01", "2025-02", ...]}
datasets={[{
label: "VO2Max(月平均)",
data: [34.4, 33.2, ...],
color: "#e76f51"
}]}
yMin={28}
yMax={38}
/>
データをコンポーネントのpropsとして直接渡せるのでシンプル。グラフを増やしたいときは id を変えて追加するだけ。
まとめ
| レイヤー | 使ったもの |
|---|---|
| データ収集 | Apple Watch + Health アプリ |
| データ保存 | DuckDB |
| データ取得 | Apple Health MCP + SQL |
| グラフ描画 | Chart.js |
| ブログ | Astro + MDX |
| ホスティング | Cloudflare Pages |
「Apple Watchのデータをブログに載せる」というだけなら以前は相当な手間がかかったと思う。MCPでAIがDBに直接クエリできる環境があると、データ取得から記事化まで会話の流れでできてしまう。
このブログ自体もAstro + Cloudflare Pagesで作ったので、次は構築の話も書くかもしれない。