系統架構

系統如何構建

CPBL 官方 API 為資料源的 Python ETL 資料管線、Cloudflare D1 at edge、Hono-on-Workers 提供 15 個 API、Next.js 16 SSG 前端、以及自建 MCP server + Workers AI bge-m3 embedding 搜尋。 全 CF 邊緣堆疊、全自動 CI/CD。

(舊版 v1 有整合 Rebas Open Data,v2 monorepo 評估重疊 85% 後 scope 收斂至 CPBL 官方為單一來源)

系統架構

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

CPBL Public API
POST /getlive
ETL Pipeline
Python / scripts/
Cloudflare D1
SQLite at edge
Hono on Workers
15 endpoints
Next.js 16 SSG
static export
CF Pages
Global edge CDN
GitHub Actions 每日 06:00 (台灣) 自動執行完整管線

ETL 資料管線詳解

CPBL 官方 API 單一來源提取、轉換、冪等載入 SQLite。

資料擷取

CPBL 官方 APIPOST /box/getlive + /box/getboxsum。Box score、逐球 play-by-play、LOB。
歷史整合(v1 archived)舊 repo 有 Rebas Open Data loader(重疊 85%、v2 scope 收斂)
Game 鍵(game_sno, year) + (date, home, away) 自然鍵備援

資料品質與對應

Schema drift testpytest 每天驗上游格式,不符擋 deploy
冪等INSERT OR REPLACE + game_sno + player_name 複合鍵
磁碟快取原始 JSON 快取在 data/cpbl_raw/

速率限制(CPBL API)

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

管線執行順序

  1. 1seed_cpbl.py爬 CPBL 官方 API、解析 + 快取 raw JSON
  2. 2test_schema_consistency.pySchema drift 守門員,不通過擋 deploy
  3. 3calc_park_factors.py / calc_wrc_plus.py / compute_radar.py從 SQLite 計算進階統計
  4. 4export_static.py產靜態 JSON → apps/cpbl/public/api/
  5. 5sync_to_d1.pySQLite → D1 冪等 INSERT OR REPLACE

資料庫結構

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

列數關鍵欄位說明
games377game_id, game_date, home_team, away_team, season, kind_code所有表的主連接鍵。日期、主隊、客隊的複合唯一約束。
plate_appearances28,502pa_id, game_id, batter_id, pitcher_id, inning, outs, bases, result核心事件表。包含 RE24 的槓桿背景(局數、出局數、壘包狀態)。
pitch_events111,983event_id, pa_id, pitch_seq, pitch_type, result, balls, strikes最大的表。用於計數拆分和疲勞曲線分析。
batter_box8,400game_id, player_id, ab, h, bb, hbp, hr, rbi, lob每位打者每場比賽的成績單。LOB 欄位來自 CPBL API 補充。
pitcher_box3,217game_id, player_id, ip, h, bb, k, er, pitch_count, lob, left_behind_lobLOB% 分子/分母來自此處。left_behind_lob 來自 CPBL API。
analysis_cachecache_key, value_json, computed_at, ttl_seconds鍵值存儲。昂貴的彙總在 ETL 後快取,新資料載入時無效化。
player_mappingcanonical_name, cpbl_name_zh, team_code, season跨來源球員名稱對齊表(v1 用於 Rebas ID 串接,v2 保留為未來多源整合 scaffold)。
run_expectancy_matrix24outs, bases_state, expected_runs, season, sample_size24 個壘包出局狀態。用於 RE24 + 槓桿指數。

Entity-Relationship Diagram

1:N1:N1:N1:NgamesPK game_idgame_date, home_team, away_teamyear, venue, kind_codebatter_boxFK game_id, player_idab, h, bb, so, hr, rbilob, left_behind_lobplate_appearancesFK game_idbatter_id, pitcher_id, inningouts_before, runners_beforeresult, runs_scoredpitcher_boxFK game_id, player_idip, pitch_count, h, r, erbb, so, hrpitch_eventsFK game_id, pa_seqpitch_seq, pitch_resultballs_before, strikes_beforeplayersPK player_idname_zh, name_en, teamposition, bats, throwsplayer_mappingcpbl_name → canonical_name多源對齊 scaffoldrun_expectancy_matrixPK (year, base_state, outs)expected_runs, sample_size

核心資料流:games plate_appearances pitch_events(逐球事件鏈)。

開發者體驗基建

除了前台 dashboard,另有一套內部工具鏈:自建 MCP server 讓 Claude 直接查資料庫、 語義搜尋系統讓歷史決策可被快速找回、Worker 本身就是 AI 推論的入口。

murs-data MCP Server

本地 stdio MCP server(TypeScript)。4 個 parametric tool 一次覆蓋 5 個資料源:
  • d1_query(db, sql) — read-only SELECT across cpbl / fantasy / polymarket / mursfoto D1
  • d1_schema(db, table?) — list tables / columns
  • yaoye_sheets_read(key, range) — Google Sheets API v4
  • memory_search(query) — 語義搜尋 91 個 memory 檔

安全:`assertReadOnly()` 拒絕 INSERT/UPDATE/DELETE/DROP;拒絕 multi-statement;結果 cap 500 rows。

Workers AI bge-m3 embedding

Hono API 的 /api/ai/embed endpoint 用 env.AI.run('@cf/baai/bge-m3') 直接在 Worker 內跑推論。
  • 1024-dim 多語 embedding(bge-m3 中文支援優)
  • 免 API key(Worker binding)
  • Free tier 10,000 neurons/day,91 檔 embed 約 100 neurons
  • Bearer auth 保護額度

替代了初版用 Google Gemini embedding 的依賴,全 stack 留在 Cloudflare 邊緣。

語義搜尋記憶系統

`~/.claude/projects/*/memory/` 91 個踩坑記錄、決策文件、session archive, 全部透過 bge-m3 向量化存本地 SQLite JSON index。
  • 問「CF 部署踩坑」→ 找到 feedback_cloudflare_pitfalls.md(sim 0.626)
  • cosine similarity 線性掃描,91 檔 × 1024 維 < 10ms
  • Weekly launchd audit 自動 rebuild index

效果:從「我記得有寫過某某」進化到「語義相關性」查找,不用翻目錄。

CI/CD 流程

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

推送觸發觸發:推送、拉取請求
推送 / PR
檢查 (ruff)
測試 (pytest)
📊
涵蓋報告
🚀
部署到 CF
每日 Cron排程:0 22 * * * (UTC)
🕐
UTC 22:00
💾
植入資料
📄
匯出 JSON
🔄
Git 推送
自動部署

技術決策

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

ADR-001

SQLite 優於 PostgreSQL

儲存
+ 單一寫入 ETL 表示沒有並發寫入衝突
+ 零配置、零營運開銷
+ 整個資料庫是單一可移植檔案
+ 讀取密集工作負載適合 WAL 模式
! 無並發寫入、1 TB 實際大小限制
ADR-002

靜態 JSON 匯出

代管
+ Cloudflare Pages 免費層 — 每月 $0
+ 無需伺服器執行時;消除冷啟動
+ 透過 300+ 個 PoPs 的即時全球分佈
+ 資料每日透過 cron 更新
! 無實時查詢;僅預計算彙總
ADR-003

ECharts 優於 D3 / Plotly

視覺化
+ Canvas 渲染比 SVG 有更好的行動效能
+ ~750 KB vs Plotly ~3.5 MB
+ 內建互動零程式碼
+ 現成深色主題支援
! 對於完全自訂視覺不如 D3 靈活
ADR-004

Python 優於 Node.js

後端
+ pandas / numpy 資料處理
+ 更豐富的統計函式庫
+ FastAPI + SQLAlchemy 2.0 非同步效能
+ 未來 ML 功能已在生態系中
! Python GIL 限制 CPU 綁定並發