系統架構

系統如何構建

雙來源 ETL 資料管線、SQLite WAL 模式、17 個端點的 FastAPI, 以及部署到 Cloudflare Pages 的完全自動化 CI/CD 系統。

系統架構

從原始資料來源到即時儀表板的端對端資料流。

Rebas Open Data
JSON / GitHub
CPBL Public API
POST /box/getlive
ETL Pipeline
Python / scripts/
SQLite DB
8 tables, WAL
FastAPI
17 endpoints
Static JSON Export
dashboard/data/*.json
Cloudflare Pages
Global CDN
GitHub Actions
daily cron + CI/CD
Data Source
Processing
Storage
API Layer
Build Artifact
Hosting
Automation

ETL 資料管線詳解

說明如何從兩個來源提取、協調和載入 SQLite 的原始資料。

雙來源資料擷取

Rebas(主要來源) 完整打席 + 投球序列 JSON。結構化 6 個表的 Schema。2024–2025 賽季歷史資料。
CPBL(補充來源) 新增 Rebas 缺少的 Lobs / LeftBehindLobs 欄位。用於 LOB% 交叉驗證。
連接鍵 (game_date, home_team, away_team) — no shared ID across sources.

資料品質與對應

模糊匹配 CPBL 只提供中文名稱。player_mapping 表連接到 Rebas player_id。未解析名稱的編輯距離後備方案。
冪等 已存在 DB 的比賽被跳過。重新執行是安全的。使用 INSERT OR IGNORE 和比賽複合鍵。
磁碟快取 原始 JSON 回應快取在 data/cpbl_raw/ 下,以避免重試時的冗餘 API 呼叫。

速率限制(CPBL API)

2s
請求間隔
30/min
硬限制上限

cpbl_client.py 中實施為令牌桶樣式的延遲。尊重每個請求和每分鐘的限制,以避免公開端點的 IP 禁用。

管線執行順序

  1. 1 seed_rebas.py — 將 Rebas JSON 展平為標準化表
  2. 2 seed_cpbl.py — 提取 + 快取 CPBL API,合併 LOB 欄位
  3. 3 build_re_matrix.py — 從累積的 PA 資料計算 RE24
  4. 4 分析快取被無效化;FastAPI 提供新鮮計算的指標
  5. 5 靜態 JSON 匯出將 dashboard/data/*.json 寫入 Cloudflare Pages

資料庫結構

SQLite WAL 模式。8 個表、15+ 個索引、分析快取層。

列數 關鍵欄位 說明
games 370 game_id, game_date, home_team, away_team, season, kind_code 所有表的主連接鍵。日期、主隊、客隊的複合唯一約束。
plate_appearances 27,974 pa_id, game_id, batter_id, pitcher_id, inning, outs, bases, result 核心事件表。包含 RE24 的槓桿背景(局數、出局數、壘包狀態)。
pitch_events 109,897 event_id, pa_id, pitch_seq, pitch_type, result, balls, strikes 最大的表。用於計數拆分和疲勞曲線分析。
batter_box 8,400 game_id, player_id, ab, h, bb, hbp, hr, rbi, lob 每位打者每場比賽的成績單。LOB 欄位來自 CPBL API 補充。
pitcher_box 3,217 game_id, player_id, ip, h, bb, k, er, pitch_count, lob, left_behind_lob LOB% 分子/分母來自此處。left_behind_lob 來自 CPBL API。
analysis_cache cache_key, value_json, computed_at, ttl_seconds 鍵值存儲。昂貴的彙總在 ETL 後快取,新資料載入時無效化。
player_mapping rebas_player_id, cpbl_name_zh, team_code, season 將 CPBL 中文名稱與 Rebas 球員 ID 連接。手動維護邊界情況(交易、改名)。
run_expectancy_matrix 24 outs, bases_state, expected_runs, season, sample_size 24 個壘包出局狀態。透過 build_re_matrix.py 從 CPBL PA 資料建立。用於 RE24 + 槓桿指數。

關鍵關係

games → plate_appearances

一場比賽有多個打席。外鍵:game_id

plate_appearances → pitch_events

一個打席有多個投球。外鍵:pa_id

games → batter_box / pitcher_box

每位球員每場比賽的成績單列。外鍵:game_id

CI/CD 流程

兩個 GitHub Actions 工作流:推送觸發品質閘道 + 每日自動資料更新。

推送觸發 觸發:推送、拉取請求
推送 /
拉取請求
檢查
(ruff)
測試
(pytest)
📊
涵蓋
報告
🚀
部署
到 CF
ruff — 檢查匯入、風格、未使用的變數(取代 flake8 + isort)
pytest — 跨單元 + 整合層的 129 個測試
coverage — 如果低於 80% 則失敗,報告作為成品上傳
deploy — 只在所有檢查通過後在主分支推送上執行
每日 Cron 排程:0 22 * * * (UTC)
🕐
UTC
22:00
💾
植入
資料
🗎
匯出
JSON
🔄
Git
推送
自動
部署
seed — 執行 seed_rebas.py + seed_cpbl.py 獲取最新比賽
export — FastAPI 為所有 4 個模組產生靜態 JSON 快照
git push — 提交更新的 data/ 檔案並帶有時間戳記訊息
deploy — Cloudflare Pages 透過 webhook 在 git 推送時自動部署

技術決策

架構決策記錄 — 關鍵技術選擇背後的理由。

ADR-001

SQLite 優於 PostgreSQL

儲存
+ 單一寫入 ETL 表示沒有並發寫入衝突
+ 零配置、零營運開銷 — 在 Python 執行的任何地方執行
+ 整個資料庫是單一可移植檔案,簡化 CI/CD
+ 讀取密集的分析工作負載非常適合 WAL 模式
! 接受的折衷:無並發寫入、1 TB 實際大小限制
ADR-002

靜態 JSON 匯出

代管
+ Cloudflare Pages 免費層 — 每月 $0 用於全球 CDN
+ 無需伺服器執行時;消除冷啟動
+ 透過 300+ 個 PoPs 的即時全球分佈
+ 資料每日透過 cron 更新 — 對分析的新鮮度是可以接受的
! 接受的折衷:無實時查詢;僅預計算彙總
ADR-003

ECharts 優於 D3 / Plotly

視覺化
+ 基於 Canvas 的渲染 — 比 SVG 密集的 D3 具有更好的行動效能
+ ~750 KB vs Plotly 的 ~3.5 MB — 顯著的包大小縮減
+ 內建互動(縮放、提示、圖例切換)零程式碼
+ 現成的深色主題支援符合儀表板美觀
! 接受的折衷:對於完全自訂視覺不如 D3 靈活
ADR-004

Python 優於 Node.js

後端
+ pandas / numpy 用於資料處理;Node 生態系中無等價物
+ 更豐富的統計函式庫(scipy、statsmodels)用於分析模組
+ FastAPI + SQLAlchemy 2.0 提供與 Node 可比的非同步效能
+ 未來 ML 功能(投球分類、ERA 預測)已在生態系中
! 接受的折衷:Python GIL 限制 CPU 綁定並發 vs Node 事件迴圈