SteveChuang · Personal Hub
這個專案把 Garmin 手錶錄下來的原始 FIT 檔,變成一整套訓練分析網站。
全程零後端、零推論成本、沒有 build step —— 排程跑完 git commit,那就是線上版。
以下三張圖照著資料的流向排:先看它怎麼進來,再看機器算了什麼,最後看畫面怎麼長出來。
手錶錄完自己上傳到 Garmin Connect,之後不需要任何手動操作。
真正的主線是 intervals.icu —— 它免費、有開放 API、而且會把原始 FIT 檔完整交出來,
所以逐秒的功率、心率、踏頻、齒比才拿得到。
Strava 那條線刻意畫成虛線:它現在只剩年度統計和歷史成績,
留著是因為總覽的年度數字還住在 data/strava.json。室內騎乘尤其不能信它 ——
Rouvy 會自己上傳一份虛擬距離進去,那份不是訓練資料。
選 intervals.icu 不是因為它比較好看,是因為它肯把原始檔還給你。
資料的所有權決定了這整條管線能做到多深。
兩班排程,一天各跑兩到三次。主線 fit-sync.yml 依序跑六支腳本,
順序不能換 —— 後面每一支都吃前面的產出。
橘色那支是這個專案真正的自建部分:不採用 Strava 給的路段成績, 而是從自己的 FIT 逐秒重算閘門通過時間。同理,坡度也不讀 Strava 的高程 —— 路段 altitude stream 的均坡是對的,但在山壁窄路上局部坡度會高估兩到三倍。
最後一支 build-tasks.py 比較特別:它不維護狀態,而是從現況推導代辦。
該做的事做完了,重跑一次它自己就消失。
五個視覺世界共用同一份 data/*.json,但各自是一個獨立單檔,
CSS 和 JS 全部 inline,彼此不共用 bundle。這是刻意的:每個世界有自己的設計語言,
硬要抽共用元件會把它們拉成同一種長相。
右邊那 72 頁單日報告是另一種東西 —— 它們由 CI 直接烤成靜態 HTML, 打開後不 fetch 任何資料。所以報告模版改了不會自動傳播到舊頁面, 要重生得逐日指定。
沒有 build step 是一個功能,不是一個還沒做的待辦。
它換到的是:任何一頁都能單獨存檔、單獨打開、單獨活過十年。