mazu.today 資料模型參考文件
最後更新:2026-04-04 | 適用版本:commit 4275cc6 之後
本文件記錄 mazu.today(Formosa ESG 2026 白沙屯媽祖進香 GPS 追蹤系統)的完整資料模型, 涵蓋 Cloudflare D1、KV、R2 三層儲存的結構與用途。
基礎架構綁定(wrangler.toml)
| 綁定名稱 | 類型 | 用途 |
|---|---|---|
AUTH_DB |
D1 (paulkuo-auth) | 核心資料庫,所有 Formosa + Auth 資料表 |
TICKER_KV |
KV | GPS 緩衝、Rate Limit、分散式鎖、狀態旗標 |
FORMOSA_OG |
R2 | OG 分享卡片圖片(動態產生) |
TQEF_AUDIO |
R2 | TQEF 專案音檔(非 Formosa) |
Cron 排程:
- 每 5 分鐘:GPS flush(KV → D1 批次寫入)
- 每小時:其他維護任務(stock/fitbit 等)
D1 資料表:Formosa ESG
formosa_users — 香客資料
| 欄位 | 類型 | 說明 |
|---|---|---|
line_user_id |
TEXT PRIMARY KEY | LINE User ID,主鍵 |
display_name |
TEXT | LINE 顯示名稱 |
picture_url |
TEXT | LINE 頭像 URL |
role |
TEXT DEFAULT 'pilgrim' | 角色:pilgrim / volunteer / manager / owner |
language |
TEXT DEFAULT 'zh-TW' | 偏好語言(zh-TW / en / ja) |
phone |
TEXT | 手機號碼(成就卡門檻之一) |
participant_status |
TEXT DEFAULT 'active' | 狀態:active / completed / withdrawn |
created_at |
TEXT | 建立時間 |
updated_at |
TEXT | 最後更新 |
備註:role 對應三層權限系統(owner / manager / volunteer),由 admin API 設定。
formosa_surveys — 問卷回答
| 欄位 | 類型 | 說明 |
|---|---|---|
id |
INTEGER PRIMARY KEY | 自增 ID |
line_user_id |
TEXT | 外鍵,關聯 formosa_users |
q1 |
TEXT | 問題 1 回答(JSON array) |
q2 |
TEXT | 問題 2 回答 |
q3 |
TEXT | 問題 3 回答 |
q4 |
TEXT | 問題 4 回答 |
q5 |
TEXT | 問題 5 回答 |
q6 |
TEXT | 問題 6 回答 |
transport_primary |
TEXT | 主要交通方式 |
transport_km |
REAL | 交通里程 |
transport_breakdown |
TEXT | 交通方式分佈(JSON) |
carbon_estimate |
REAL | 預估碳排量(kg CO₂) |
completed |
INTEGER DEFAULT 0 | 是否完成問卷 |
submitted_at |
TEXT | 提交時間 |
updated_at |
TEXT | 最後更新 |
| ...其餘欄位 | 問卷 Q7-Q10(改版後精簡為 10 題) |
備註:問卷從 13 題精簡為 10 題(Q13→Q10 改版),部分欄位以 JSON array 儲存多選題。
formosa_gps_points — GPS 打卡點
| 欄位 | 類型 | 說明 |
|---|---|---|
id |
INTEGER PRIMARY KEY | 自增 ID |
line_user_id |
TEXT | 外鍵 |
lat |
REAL | 緯度 |
lng |
REAL | 經度 |
altitude |
REAL | 海拔(可能為 null) |
accuracy |
REAL | GPS 精度(公尺) |
source |
TEXT | 來源:auto / manual / photo |
timestamp |
TEXT | 打卡時間 |
created_at |
TEXT | 寫入 D1 時間(≠ 打卡時間,因 KV buffer 延遲) |
備註:
source三種來源:auto(自動追蹤)、manual(手動打卡)、photo(拍照打卡)- 資料先寫 KV buffer,cron 每 5 分鐘 flush 到此表,所以
created_at會晚於timestamp - INSERT OR IGNORE 防重複(dedup by timestamp + userId)
formosa_daily_reports — 每日碳足跡摘要
| 欄位 | 類型 | 說明 |
|---|---|---|
id |
INTEGER PRIMARY KEY | 自增 ID |
line_user_id |
TEXT | 外鍵 |
date |
TEXT | 日期(YYYY-MM-DD) |
total_km |
REAL | 當日總里程 |
carbon_saved |
REAL | 當日減碳量(kg CO₂) |
checkin_count |
INTEGER | 當日打卡次數 |
created_at |
TEXT | 建立時間 |
formosa_feedback — 使用者回饋
| 欄位 | 類型 | 說明 |
|---|---|---|
id |
INTEGER PRIMARY KEY | 自增 ID |
line_user_id |
TEXT | 外鍵 |
category |
TEXT | 類別:bug / ux / environment |
content |
TEXT | 回饋內容 |
status |
TEXT DEFAULT 'open' | 狀態:open / in-progress / fixed / wontfix |
created_at |
TEXT | 建立時間 |
備註:Issue 關閉時 GitHub Actions 自動更新 status 為 fixed(需 <!-- feedback-id: XX --> 標記)。
KV 鍵值模式(TICKER_KV)
Formosa GPS 緩衝(核心)
| Key Pattern | TTL | 說明 |
|---|---|---|
gps:{timestamp}:{userId}:{uuid} |
259200s (3天) | GPS 打卡原始資料,每筆一個 key |
gps_count:{userId} |
30天 | 該用戶的 GPS 累計筆數快取 |
資料流:香客打卡 → Worker 寫 KV(不碰 D1)→ Cron 每 5 分鐘掃描 gps: prefix → batch INSERT OR IGNORE 到 D1 → 刪除已 flush 的 KV key。
分散式鎖與狀態
| Key Pattern | TTL | 說明 |
|---|---|---|
formosa:lock:gps_flush |
90s | GPS flush 分散式鎖,防止多 cron 同時執行 |
formosa_status |
無(持久) | 活動狀態:active / paused / ended |
formosa:lastFlush |
無(持久) | 最後一次成功 flush 的 ISO 時間戳 |
Rate Limiting
| Key Pattern | TTL | 說明 |
|---|---|---|
rate:{userId}:{minute} |
60s | 每分鐘打卡次數(上限 5 次) |
統計快取
| Key Pattern | TTL | 說明 |
|---|---|---|
formosa:stats |
60s | Dashboard 統計數據快取(避免頻繁查 D1) |
健康監控告警(RFC #100 A1 新增)
| Key Pattern | TTL | 說明 |
|---|---|---|
alert:last_sent |
無(持久) | 最後一次告警推播的 ISO 時間戳 |
alert:backoff_level |
無(持久) | 目前退避等級(0=正常, 1=10min, 2=30min, 3=1hr, 4=2hr cap) |
告警觸發條件:D1 連線失敗、KV→D1 flush 停滯超過 30 分鐘。
退避序列:立即 → 10min → 30min → 1hr → 2hr(cap)。
恢復時推送「✅ 系統已恢復」並重置 backoff_level 為 0。
需設定 secret:FORMOSA_ALERT_USER_ID(Paul 的 LINE user ID)。
Service Worker 快取(RFC #100 A2 新增)
| 資源類型 | 快取策略 | 說明 |
|---|---|---|
_astro/* hashed assets |
Cache-first | CSS/JS 帶 content hash,不可變 |
| Formosa HTML 頁面 | Network-first → cache fallback → offline.html | 優先取最新,離線時用快取 |
| API 請求 | Network-only | 不快取 API 資料 |
| 非 Formosa 頁面 | 不攔截 | SW scope 限定 /projects/formosa-esg-2026/ |
離線 fallback:public/offline.html(友善中文提示,告知打卡已儲存、連線後自動上傳)。
R2 物件儲存
FORMOSA_OG Bucket — 分享卡片
| 路徑 | 說明 |
|---|---|
/api/formosa/og/{userId}.png |
動態產生的 OG 分享圖片,含公仔 + 等級 + 里程 + QR Code |
產生時機:使用者點「分享」時動態產生並快取。
資料關聯圖
formosa_users (line_user_id PK)
├── formosa_surveys (line_user_id FK)
├── formosa_gps_points (line_user_id FK)
├── formosa_daily_reports (line_user_id FK)
└── formosa_feedback (line_user_id FK)
KV Buffer
gps:{ts}:{userId}:{uuid} ──(cron flush)──→ formosa_gps_points
R2
FORMOSA_OG/{userId}.png ←── 動態產生自 formosa_users + gps_points 統計
成就卡門檻(商業邏輯)
解鎖成就卡需同時滿足三個條件:
- 打卡次數 ≥ 3
- 問卷已完成(formosa_surveys.completed = 1)
- 已留手機號碼(formosa_users.phone IS NOT NULL)
等級系統
9 級香客等級,雙條件判定(累計里程 + 累計打卡次數,兩者都要達標):
等級計算統一(RFC #100 修復):Dashboard 和 Chat Bot 現在都使用 computeRank(km, checkins) 雙條件判定,全部 GPS 點計入,不再區分 source 類型。Chat Bot 也加入了 haversine 里程計算和噪音過濾(<10m, <30s, >300km/h),與 Dashboard 完全一致。LINE Bot 回覆新增 km 顯示。
| 等級 | 名稱 | 最低里程 | 最低打卡 | 來源 |
|---|---|---|---|---|
| 1 | 煉氣 | 0 km | 1 次 | formosa.js:846 TITLES[0] |
| 2 | 築基 | 15 km | 5 次 | formosa.js:846 TITLES[1] |
| 3 | 金丹 | 45 km | 10 次 | formosa.js:846 TITLES[2] |
| 4 | 元嬰 | 90 km | 15 次 | formosa.js:846 TITLES[3] |
| 5 | 化神 | 135 km | 20 次 | formosa.js:846 TITLES[4] |
| 6 | 煉虛 | 180 km | 25 次 | formosa.js:846 TITLES[5] |
| 7 | 合體 | 225 km | 30 次 | formosa.js:846 TITLES[6] |
| 8 | 大乘 | 270 km | 35 次 | formosa.js:846 TITLES[7] |
| 9 | 飛升 | 300 km | 40 次 | formosa.js:846 TITLES[8] |
⚠️ 勘誤:本表 2026-04-04 初版誤填了不存在於程式碼的門檻值,已於同日修正。
正確值來源為 worker/src/formosa.js 第 846 行的 TITLES 陣列。
注意:formosa.js 第 1473 行有第二份 TITLES 陣列(用於 Chat Bot),值相同但缺 sub 欄位。
碳排係數 GWP_FACTORS(formosa.js:1907-1911,Ecoinvent 3.10)
| 項目 | 係數 | 單位 | 來源行 |
|---|---|---|---|
| walk | 0 | kg CO₂e/km | 基準(零排放) |
| car | 0.30479 | kg CO₂e/km | formosa.js:1908 |
| scooter | 0.13734 | kg CO₂e/km | formosa.js:1908 |
| bike | 0.01220 | kg CO₂e/km | formosa.js:1908 |
| bus | 0.47515 | kg CO₂e/km | formosa.js:1909 |
| mrt | 0.07575 | kg CO₂e/km | formosa.js:1909 |
| train | 0.07575 | kg CO₂e/km | formosa.js:1909 |
| hsr | 0.07487 | kg CO₂e/km | formosa.js:1909 |
| water | 0.10974 | kg CO₂e/瓶 | formosa.js:1910 |
| recycle | -0.00265 | kg CO₂e/瓶 | formosa.js:1910(回收抵扣) |
| hotel | 8.85 | kg CO₂e/晚 | formosa.js:1910 |
速度推斷門檻(formosa.js:877-880):≤ 15 km/h → zero_emission, > 15 km/h → motorized (0.47515)
⚠️ 勘誤:本段 2026-04-04 初版寫「機車 0.05、汽車 0.21、遊覽車 0.04」,皆為錯誤舊值,已於同日修正。
補充:非 Formosa 的表(同一 D1)
同一個 AUTH_DB 裡還有:
users+sessions:OAuth 認證(Google/LINE/Facebook)tqef_*系列(14 張表):翻譯品質評估系統,與 Formosa 無關
這些表共用同一 D1 實例,但邏輯完全獨立。注意 D1 的 single-writer 限制會互相影響——這也是為什麼 Formosa 改用 KV buffer 的原因之一。