こんにちは、はるそめです。
突然ですが皆さん、パワーデータの分析は何のツールでやってますか?
ライドのパワーデータ分析はGarmin Connectやintervals.icuでチェックして、セグメントの分析はStravaを使って、人によって細かい分析はGolden Cheetahで・・・って感じで、見たいデータごとにツールを使い分けてる人、結構多いんじゃないだろうか。
自分もそんな感じで、複数ツールを使ってパワーデータをチェックしていたんだけど、ツールを切り替えるのが面倒な上、各ツール若干、痒いところに手が届かない感があったり、有料プランに切り替えないと見たいデータが見れなかったりと、ちょいちょい不満があった。(特に、有料プランじゃないとStravaセグメントの過去データが見れない点とか)
そんな中、「生成AIを使えば、理想のパワーデータ分析ツールが作れてしまうんじゃない!?」と思い立ち、intervals.icuとClaude Codeを使って、自分専用のパワーデータ分析ツールを作ってみた。
作ってみると、めちゃめちゃ良いもの(※当社比)が出来上がったので、ブログ記事にまとめようかなと思う。
分析ツールへの課金を辞めたいサイクリスト、何かしら生成AIを活用したいと思っているサイクリストの参考になれば幸いです。
課題:全てが詰まった理想のパワーデータ分析ツールって、ないよね

さて、ブログを書くにあたって、まずは課題感の共有から始めようかなと。冒頭にも書いたけど、ざっくり下記3点が課題だった。あと、エージェンティック・コーディングの練習もしたかったから、その練習台って側面もあったりしつつ。
- 見たいデータに合わせていちいちツールを切り替えるのが面倒
- 自分の要件にバッチリハマるレポートが出せない
- 見たいデータを見るためには、有料プランへの課金が必要
話が逸れまくるんだけど、これからの時代、AIを使いこなさないと、ホワイトワーカーとしての未来が危ういなって危機感をめっちゃ感じてる。”SaaSの死”なんて言われてテック企業の株価が大暴落したりしてたけど、今回のパワーデータ分析ツールだって、みんなが自分で自分に最適なものを生成AIで作り始めると、StravaやTrainingPeaksの有料会員がどんどん離脱しちゃうよね。(StravaはSNS機能、TrainingPeaksはコーチングサービス等で差別化図っていくんだろうけど・・・。)
話を戻す。特に課題②と③が自分にとって、課題に感じてる。
自分はPCGのオンラインコーチングを受けてることもあって、TrainigPeaksの有料アカウントを日々利用してるんだけど、例えば、CP(単に時間あたりのパワー)の自己ベストを自由に設定した分数・自由に設定した期間で比較しようと思うと、自分でデータを集めてまとめないといけなかったりする。
また、Stravaで特定セグメントの過去のタイムとパワー一覧を見ようと思うと、有料会員登録が必要。仮に課金したとしても、APは出せてもNPは出せないし、パワーメーターのシリアル番号からバイクを自動識別してバイク別のランキングを出すなんてこともできない。(※後述するけど、自作ツールではできてしまう!)
解決策:intervals.icuと生成AIで自作パワーデータ分析ツールを作る
当たり前だけど、自分が求める機能が全て詰まった無料のツールなんてもの、世の中にあるわけがない。有料でも多分、そうそう存在しない。じゃあどうするのか?生成AIで作っちゃえば良いじゃん!と。
ってことで、まずはClaudeパイセンと実現方式を相談。うまく要件を言語化できなくても、複数回の壁打ちである程度、やりたいことをまとめてくれるからすごいよね。
相談の結果、Garmin ConnectやTrainingPeaksはAPIが公開されているものの個人では使えず、Stravaは個人でも使えるけどセグメントデータの取得には制約が多そう。対してintervals.icuはAPIが個人向けに公開されているので、intervals.icuからライドデータを自動取得し、分析データとして使いましょう。となった。(※作成後、interval.icuは直近1年分のデータしか取得できないってことで、初回連携はGarmin Connectから一括ダウンロードしたデータを投入して食わせつつ、今後発生するデータをintervals.icuから取得する形に変更した。)
あとは、「こんなデータ作りたいんだけど!」、「こんな分析したいんだけど!」、「ブログ記事にも使えるようにしたいんだけど!」と、わがままを言いまくりつつ、実装してもらった。
もちろん、1発で完成はしなくて、実装されたものを見てフィードバックして改修させて・・・ってのを複数回繰り返して、完成。

かかった時間は半日ぐらい。たった半日でツールが作れてしまう。恐ろしい世の中になったもんだ・・・。(※念の為補足。自分用に作る分には楽勝だけど、じゃあこれを製品として公開して運用するのはまた別の話。)
最近、AI開発に関する本を読み漁ってるんだけど、これから学ぶ人に「おすすめの本ある?」って聞かれたら間違いなくこれをおすすめする。興味ある人は読んでみて。
結果:出来上がった自分だけのパワーデータ分析ツール
そんなこんなで完成したツールに「Cycling Training Analysis」って名前をつけた。以下、どんな機能を盛り込んだのか、紹介する。ぜひ、みなさんが作る自分だけの分析ツールに盛り込む機能の参考にしてもらえればと。
PMC機能

まずはPMC(Performance Management Chart)。パワトレをやっている人間にとって、一番気にしてるグラフじゃないだろうか?
上段で今日のCTL・ATL・TSBを表示して一目で数値がわかるようにしつつ、グラフは基準日を設定した上で3ヶ月〜全期間で期間を変更できるようにした。
ライド分析機能
続いてライド分析機能。一覧画面で、週別のトレーニング時間や距離、TSSがぱっと見でわかる。

各ライドの個別分析画面で、グラフやゾーン分析、dFRCのようなマニアックなグラフもチェック可能。




基本的には、intervals.icuでも閲覧できるものなんだけど、不要なものを削ぎ落とし、自分好みにカスタマイズしたって感じ。
パワー分析機能
個人的に、この機能がめちゃめちゃ良いと自画自賛してる。よくあるパワーカーブなんだけど、年別で表示しつつ、任意の年と比較することができる。「2024年と比べてどれぐらい進化した?」的な分析が、すぐできる。

FTPについても、推移がわかるようなグラフを作った。20分ベストからの推定や、mFTP/eFTPのようなinterval.icuで算出される推定値、あと、FTPテスト結果も入力できるようにした。

サマリー機能
週別・月別・年別で、走行距離や時間、TSS、ゾーン別滞在時間を表示できるようにした。また、期間も1年/3年/5年/全期間と選ぶこともできる。

何気に、今月どれぐらい走った?ってのをパッと確認できて、重宝してる。
セグメント分析
そして個人的目玉機能のセグメント分析。Stravaだと有料会員にならないと過去の走行データを一覧表示できないけど、自作ツールなら無料。Stravaのような他サイクリストとの比較はできないけど、自分のデータを見る分にはStrava以上の機能を搭載したつもり。

例えば普段の朝練でよく走る、くろんど池の登りだと下記のようなデータが見れる。


Stravaだと、アベレージパワー(AP)しか見れないと思うんだけど、このツールではNPも表示できるようにしてる。また、計測したパワーメーターのシリアルナンバーから、どのバイクで登ったか?も自動判別されるようにした。
これ、めっちゃ良くない??(自画自賛)
まとめ:有料ツールはもういらない疑惑
以上、intervals.icuとClaude Codeで自作したパワーデータ分析ツールの紹介でした。
ツールに課金しなくても、PMCやパワーカーブ、ライド分析からセグメント分析まで、1つのツールで見れるようになった。しかも、今後追加したい機能が出てきたら、Claudeに頼めばサクッと追加してくれる。
課金しても手に入らなかった「自分用」のツールが、課金なしで手に入った。 これが自作の一番のメリットだと思う。
もちろん、生成AIのサブスク代はかかる。でもそれはブログも仕事もレース分析も全部まかなってくれる相棒代なので、自分の中では完全にペイしてると思ってる。
おまけ:プロンプトの共有
「とりあえず似たようなツールをサクッと実装してみたい!」って方のために、Claudeにプロンプトを作らせて見たので貼っておきます。テストしていないので、うまく動かなかったら僕ではなく、AIに怒ってください(笑)
あなたは Python と Web フロントエンドに詳しいエンジニアです。
以下の仕様で、サイクリングのトレーニング分析ツールを作ってください。
自分の PC 上だけで動くローカル Web アプリで、外部に公開する想定はありません。
まずステップ 1 を完成させて動作確認し、その後にステップ 2 以降を 1 つずつ足してください。
各ステップの終わりに、何を作ったか・どう確認すればよいかを短く説明してください。
---
## 前提と技術スタック
- バックエンド: Python 3.11 以上、FastAPI + uvicorn。HTTP クライアントは httpx
- フロントエンド: フレームワークなしの素の HTML / CSS / JavaScript。グラフは Apache ECharts(CDN ではなくローカルに置く)
- 起動: `./run.sh` 1 つで仮想環境の作成・依存のインストール・サーバー起動・ブラウザを開くところまでやる。既定は http://127.0.0.1:8787
- 設定は `.env` に置き、`.env.example` を用意する。API キーや個人データはコードに書かない
- `data/`、`.cache/`、`.env`、`*.zip` は `.gitignore` に入れる(個人データのため)
- 外部 API のレスポンスは `.cache/` に JSON でキャッシュし、TTL(既定 15 分)と「キャッシュを無視して再取得」の両方を持つ
- ライト / ダークのテーマ切り替え(OS 設定に追従、手動切替も可)
- 全ページ共通のサイドバー(ページ一覧・テーマ切替・再取得ボタン)を 1 つの JS で描く
- 計算ロジックは純粋関数にまとめて pytest でテストを書く
## データ源(3 段階フォールバック)
1. **Garmin の全データエクスポート ZIP** — `data/` に置かれた最新の `*.zip` を自動で使う。
ZIP 内の `summarizedActivities.json` から全アクティビティを読む。
日別 TSS は `trainingStressScore` → `activityTrainingLoad` → 所要時間からの推定、の優先順位で作り、
推定に頼った割合をグラフのキャプションに出す
2. **intervals.icu の API** — `.env` の `INTERVALS_API_KEY` があれば使う。Basic 認証(ユーザー名 `API_KEY`、パスワードにキー)。
athlete id は `0` で「キーの持ち主」に解決する
3. **デモデータ** — どちらも無ければ見た目確認用の合成データで動く
ZIP と API キーの両方があるときは**自動でマージ**する。日別 TSS を統合し、重なる日は intervals.icu を優先し、
CTL / ATL は全期間ひと続きで再計算して継ぎ目を出さない(ZIP は過去の履歴、API は日々の最新、という役割分担)。
---
## ステップ 1: PMC(Performance Management Chart)ページ `/`
- CTL(時定数 42 日)と ATL(時定数 7 日)を指数移動平均で日次計算し、TSB = 前日の CTL − 前日の ATL
- CTL を主役(面+線)、ATL を重ねて描き、TSB は数値表示
- 期間切替: 3ヶ月 / 6ヶ月 / 1年 / 2年 / 3年 / 5年 / 10年 / 全期間
- 基準日: 計算の最終日を指定でき、そこから期間分さかのぼった窓を描く。「今日」ボタンで解除
- 画像出力: 「コピー」(クリップボードへ PNG)と「保存」(PNG ダウンロード)。
グラフだけを高解像度(2480×1520)で書き出す。クリップボードが使えない環境では自動で保存に切り替える
- ファイル名は `PMC_<YYYYMMDD>.png`。日付は**画面に出ているデータの最終日**(今日ではない)
- 直近 60 日の数値表を下に置く
- API: `GET /api/pmc?range=…&end=…`、`GET /api/config`
## ステップ 2: Activities(週カレンダー)`/activities.html` と Activity(詳細)`/activity.html?id=…`
**Activities**
- 月曜始まりの週カレンダー。1 行 1 週、新しい週が上。左列に週サマリー(走行時間・距離・TSS 合計、日曜時点の CTL / ATL / TSB)
- 各日セルにライドのカード(時間・距離・平均心拍・NP・Load・IF・名前)。カードのヘッダー色の濃さで Load を 4 段階表現
- 「さらに前の週」ボタンで 8 週ずつさかのぼる
**Activity**
- intervals.icu の 1 アクティビティのストリームからパワー(棒)と心拍(線)の時系列を描く。平滑化 なし / 10秒 / 30秒
- 2 軸のため、グリッド線は左軸(パワー)だけに引き、心拍軸はライドの実レンジに合わせる
- パワーゾーン・心拍ゾーンの滞在時間。ゾーン定義は intervals.icu のユーザー設定をそのまま使う。
描画は最大 4000 点に間引くが、**滞在時間の集計は間引き前のフル解像度で行う**
- ゾーン配色は TrainingPeaks の色順(青→水色→緑→黄→橙→赤)を踏襲しつつ、隣接ゾーンが判別できるよう ΔE を確保する。
色だけに依存しないよう、ゾーン名を凡例・ツールチップ・表にも出す。Z6 と Z7 は統合する
- ゾーンブロックだけを角丸・背景透過・3 倍解像度の PNG として書き出せる(HTML→画像ライブラリは使わず、同じデータを canvas に描き直す)
- **ラップ分析**: intervals.icu の `/activity/{id}/file` から元の FIT ファイルを取得し、`fitdecode` で `lap` メッセージを読む
(サイトが自動検出した区間ではなく、ライダーが実際に刻んだラップを使うため)
- 列: タイム・平均パワー・IF(平均)・NP・IF(NP)・区切り方
- NP はサイコンの記録値を使わず、パワー生データから標準式(30 秒移動平均→4 乗→平均→4 乗根)で再計算する。
サンプルは 1Hz にリサンプリングせず記録されたまま使う(停止中の隙間を 0 で埋めない)
- レスト判定は「平均パワー基準の IF が 0.5 以下」(NP 基準は惰性走行で高めに出るため不採用)。レスト行は淡色
- 5 秒未満のラップは除外
- 行をクリックで複数選択し、選んだラップの平均パワー・IF・NP・IF を単純平均で表示。「ワークのみ選択」ボタン
- 残 FRC(W'bal)の推移を別カードで描く
## ステップ 3: Power(パワーカーブ & FTP 推移)`/power.html`
- 期間を選んでパワーカーブ(1秒〜数時間のベスト平均パワー)を描く。年ごとの比較(今年 vs 昨年)もできる
- 5秒 / 1分 / 5分 / 20分 / 60分のベスト値と、20分・60分のベスト NP をタイル表示し、画像にも含める
- カーブの点をクリックするとそのライドの Activity ページへ
- FTP 推移を 3 系列で描く: Garmin の `max20MinPower × 0.95`、intervals.icu の mFTP、eFTP
- FTP テストの記録(日付・方式・結果)を手で追加・削除でき、`data/ftp_tests.json` に保存して FTP 推移に重ねる
- Pmax・FRC の推移、有酸素持久力(EF とデカップリング)のカード
## ステップ 4: Summary(週 / 月 / 年の集計)`/summary.html`
- 4 パネル: 走行距離・トレーニング時間・TSS 合計・パワーゾーン別滞在時間(積み上げ)
- 集計単位: 週 / 月 / 年
- TSS は PMC ページと同じ per-activity の算出ルールを使い、両ページの数字を一致させる
- ファイル名 `Summary_<YYYYMMDD>_Week.png` など。日付はバケットの終端ではなくデータの最終日
## ステップ 5: Weekly(週次ログ用の画像)`/weekly/<YYYY>week<NN>`
- 週は月曜始まり・日曜終わり。URL で週を指定(`2026week35` / `2026-W35` / `2026w35` などを正規化)。`/weekly` は最新の完了週
- ◀ ▶ ボタンとキーボードの ← → で前後の週へ。未来の週には進めない
- ワンクリックで 3 枚の PNG を出す:
1. その週の日曜日を基準日にした PMC(期間 3ヶ月 / 6ヶ月 / 1年。選択は記憶)
2. 月〜日のトレーニング時間・TSS、日曜時点の CTL と前週からの増減
3. 月〜日のパワーゾーン別トレーニング時間
- `GET /api/weekly/<週id>?series=false` で同じ内容を JSON で返す(AI ツールに読ませる用)
## ステップ 6: Segments(お気に入り区間の推移)`/segments.html`, `/segment.html?id=…`
- セグメントを「開始点・終了点・距離」の 3 つで定義し、`data/segments.json` に保存
- 過去のラップが「開始点から 60m 以内・終了点から 60m 以内・距離が ±5%(最低 ±100m)以内」をすべて満たせば同じ区間とみなす(ラップ単位の照合)
- 索引: Garmin ZIP 内の全 FIT ファイルと intervals.icu の屋外ライド(直近 400 日)を一度だけ読み、
ラップの開始・終了座標・距離・パワー・心拍を `.cache/segment-index.json` に索引化。
バックグラウンドで進行し、進み具合を画面に表示、中断しても続きから再開。「再スキャン」で新しいライドだけ追加
- 一覧: Leaflet(ローカルに置く)のマップ・距離・平均勾配・獲得標高
- 詳細: 走行回数・ベストタイム・最新タイム(ベスト比)のタイル、タイム / 速度 / 平均パワー / NP / 平均心拍のいずれかを日付順にグラフ(タイムは上ほど速い)、全ラップの表
## ステップ 7: Settings `/settings.html`
- パワーメーターのデバイス ID とバイク名の対応を登録し、`data/bikes.json` に保存。Segment の表でバイク名を表示する
## ステップ 8: 運用
- OS のログイン時に自動起動し、落ちたら再起動する仕組み(macOS なら launchd の plist と install / uninstall スクリプト)
- `data/`・`.env` を毎日別の場所へバックアップするスクリプト(rsync、日付付き履歴を 90 日分、ZIP はハッシュ照合で検証)
---
## 進め方について
- 各ステップで、まずデモデータで動くことを確認してから実データに対応してください
- 計算の定義(CTL / ATL / TSB、NP、IF、レスト判定、セグメント一致条件)は README に表で書き残してください
- 判断に迷う点は、勝手に省略せず、選択肢と推奨を示して確認してください
最近、AI開発に関する本を読み漁ってるんだけど、これから学ぶ人に「おすすめの本ある?」って聞かれたら間違いなくこれをおすすめする。興味ある人は読んでみて。



コメント