Spaces:
Running
Running
Commit
·
1628958
1
Parent(s):
bf4f8fc
docs: add project overview and data contract specifications for earthquake model demo
Browse files- .github/ISSUE_TEMPLATE/spec.yml +0 -60
- .github/pull_request_template.md +0 -18
- README.md +75 -15
- changelog.md +120 -0
- spec/00-overview.md +123 -0
- spec/01-data-contract.md +322 -0
- spec/02-processing-rules.md +289 -0
- spec/03-error-handling.md +416 -0
- spec/04-extensions.md +360 -0
- spec/README.md +192 -0
- spec/plan.md +93 -0
.github/ISSUE_TEMPLATE/spec.yml
DELETED
|
@@ -1,60 +0,0 @@
|
|
| 1 |
-
name: 規格/使用者故事(Spec / User Story)
|
| 2 |
-
description: 以結構化方式提出/更新規格,讓未來程式碼生成能遵循
|
| 3 |
-
labels: [spec]
|
| 4 |
-
body:
|
| 5 |
-
- type: textarea
|
| 6 |
-
id: user-story
|
| 7 |
-
attributes:
|
| 8 |
-
label: 使用者故事(User Story)
|
| 9 |
-
description: 作為 ___,我想要 ___,以便 ___。
|
| 10 |
-
placeholder: >-
|
| 11 |
-
作為使用者,我想選擇資料與參數並查看輸出結果的呈現與對照,以便快速比對系統表現。
|
| 12 |
-
validations:
|
| 13 |
-
required: true
|
| 14 |
-
|
| 15 |
-
- type: textarea
|
| 16 |
-
id: motivation
|
| 17 |
-
attributes:
|
| 18 |
-
label: 動機/背景
|
| 19 |
-
placeholder: 為何需要這項規格?是否對齊目前 docs/spec.md 的目標與不變條件?
|
| 20 |
-
|
| 21 |
-
- type: textarea
|
| 22 |
-
id: behavior
|
| 23 |
-
attributes:
|
| 24 |
-
label: 預期行為(Behavior)
|
| 25 |
-
description: 描述介面/流程/輸出行為,盡量具體
|
| 26 |
-
placeholder: >-
|
| 27 |
-
- UI:新增或調整元件的可見性、狀態與提示文案。
|
| 28 |
-
- 核心:不變。
|
| 29 |
-
|
| 30 |
-
- type: textarea
|
| 31 |
-
id: contract
|
| 32 |
-
attributes:
|
| 33 |
-
label: 契約(資料形狀/輸入輸出/限制)
|
| 34 |
-
description: 例如資料結構、欄位、預設值、邊界值
|
| 35 |
-
placeholder: >-
|
| 36 |
-
- 參見 docs/spec.md 中的 I/O shape、欄位約束與限制。
|
| 37 |
-
|
| 38 |
-
- type: textarea
|
| 39 |
-
id: edge-cases
|
| 40 |
-
attributes:
|
| 41 |
-
label: 邊界情境與降級策略(Edge cases & fallback)
|
| 42 |
-
placeholder: 例如:輸入不足、來源缺失、逾時、格式錯誤…(以 docs/spec.md 為準)
|
| 43 |
-
|
| 44 |
-
- type: textarea
|
| 45 |
-
id: ui
|
| 46 |
-
attributes:
|
| 47 |
-
label: UI 變更
|
| 48 |
-
placeholder: 新增/調整元件、固定尺寸、文案、提示訊息…(具體規格以 docs/spec.md 為準)
|
| 49 |
-
|
| 50 |
-
- type: checkboxes
|
| 51 |
-
id: checklist
|
| 52 |
-
attributes:
|
| 53 |
-
label: 檢查清單
|
| 54 |
-
options:
|
| 55 |
-
- label: 我已參考並對齊 docs/spec.md 的目標/不變條件/契約。
|
| 56 |
-
required: true
|
| 57 |
-
- label: 我已考量邊界情境與降級策略,不會阻斷整體流程。
|
| 58 |
-
required: true
|
| 59 |
-
- label: 變更涉及公共行為時,我會同步更新 docs/spec.md。
|
| 60 |
-
required: true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.github/pull_request_template.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
| 1 |
-
## 內容簡述
|
| 2 |
-
- 此 PR 做了什麼?(簡述變更)
|
| 3 |
-
|
| 4 |
-
## 規格對齊(spec compliance)
|
| 5 |
-
- 相關規格:
|
| 6 |
-
- [ ] 介面/流程契約符合 `docs/spec.md`
|
| 7 |
-
- [ ] 核心不變條件(依 `docs/spec.md` 條目)
|
| 8 |
-
- [ ] 欄位/資料格式要求(依 `docs/spec.md`)
|
| 9 |
-
|
| 10 |
-
## 邊界/降級驗證
|
| 11 |
-
- [ ] 針對 `docs/spec.md` 定義的邊界情境已驗證(輸入不足、來源缺失、逾時、格式錯誤等)
|
| 12 |
-
- [ ] 降級策略符合 `docs/spec.md`,並提供清楚 log
|
| 13 |
-
|
| 14 |
-
## 測試與驗證
|
| 15 |
-
- 測試方式或冒煙步驟(貼出輸出截圖/關鍵 log):
|
| 16 |
-
|
| 17 |
-
## 其他
|
| 18 |
-
- 是否需要在 `docs/spec.md` 更新條目?若是,請同步修改。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -32,7 +32,49 @@ TTSAM(Taiwan Transformer-based Shake Alert Model)是一個以 Transformer
|
|
| 32 |
- 易於擴充:
|
| 33 |
- 不需改模型與核心流程即可新增事件與目標測站(更新資料檔即可)。
|
| 34 |
|
| 35 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
需求
|
| 37 |
- Python 3.10–3.11(建議)
|
| 38 |
- 主要套件見 `requirements.txt`
|
|
@@ -58,15 +100,25 @@ TTSAM(Taiwan Transformer-based Shake Alert Model)是一個以 Transformer
|
|
| 58 |
- 新增輸入測站:
|
| 59 |
- 於 `station/site_info.csv` 增補列,欄位:`Station, Latitude, Longitude, Elevation`;去除重複站名列。
|
| 60 |
|
| 61 |
-
##
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
-
|
| 65 |
-
-
|
| 66 |
-
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
-
完整細節請參考 `docs/spec.md`。
|
| 70 |
|
| 71 |
## 專案結構
|
| 72 |
- `app.py`:Gradio GUI 與推論主流程
|
|
@@ -75,11 +127,15 @@ TTSAM(Taiwan Transformer-based Shake Alert Model)是一個以 Transformer
|
|
| 75 |
- `station/eew_target.csv`:目標測站表
|
| 76 |
- `waveform/`:事件波形(.mseed)
|
| 77 |
- `intensity_map/`:實際震度圖(可選)
|
| 78 |
-
- `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
- `.github/copilot-instructions.md`:生成程式碼指南
|
| 80 |
-
- `
|
| 81 |
-
|
| 82 |
-
- `docs/task-template.md`:任務模板(重建 `docs/task.md` 時使用)
|
| 83 |
|
| 84 |
## 疑難排解(Troubleshooting)
|
| 85 |
- Vs30 下載失敗或查無資料
|
|
@@ -96,6 +152,10 @@ TTSAM(Taiwan Transformer-based Shake Alert Model)是一個以 Transformer
|
|
| 96 |
- License:GPL-3.0
|
| 97 |
|
| 98 |
## 進一步閱讀
|
| 99 |
-
- `
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
- `.github/copilot-instructions.md`(開發與貢獻指南)
|
| 101 |
-
- `
|
|
|
|
| 32 |
- 易於擴充:
|
| 33 |
- 不需改模型與核心流程即可新增事件與目標測站(更新資料檔即可)。
|
| 34 |
|
| 35 |
+
## 設計思路
|
| 36 |
+
|
| 37 |
+
### 🎯 Project Scope: 互動式教育展示 Demo
|
| 38 |
+
|
| 39 |
+
本專案為地震模型的 **展覽演示系統**,而非生產級的早期預警系統。因此設計理念強調:
|
| 40 |
+
|
| 41 |
+
- **互動性優先**:使用者操作立即反饋視覺化結果(波形圖、地圖、統計)
|
| 42 |
+
- **教育性為中心**:清晰的介面與步驟引導,讓非地震專業人士理解「波形 → 模型 → 預測」的流程
|
| 43 |
+
- **功能簡潔化**:無需追求完整覆蓋或極限性能;易於操作與理解最重要
|
| 44 |
+
- **預裝化設計**:所有關鍵資源(模型權重、Vs30 資料庫、波形、測站表)預裝於 HF Space,無需運行時下載或外部依賴
|
| 45 |
+
|
| 46 |
+
### 📦 預裝架構
|
| 47 |
+
|
| 48 |
+
| 資源 | 位置 | 用途 | 預裝狀態 |
|
| 49 |
+
|-----|-----|------|--------|
|
| 50 |
+
| 模型權重 | `ttsam_trained_model_11.pt` | 推論核心 | ✅ 預裝 |
|
| 51 |
+
| Vs30 資料庫 | `Vs30ofTaiwan.nc` | 場址參數 | ✅ 預裝 |
|
| 52 |
+
| 波形資料 | `waveform/*.mseed` | 輸入資料 | ✅ 預裝(≥2 事件) |
|
| 53 |
+
| 測站表 | `station/site_info.csv`, `station/eew_target.csv` | 元資料 | ✅ 預裝 |
|
| 54 |
+
| 實際震度圖 | `intensity_map/YYYYMMDD.png` | 對照參考 | ✅ 預裝(可選) |
|
| 55 |
+
|
| 56 |
+
### 🛡️ 容錯設計
|
| 57 |
+
|
| 58 |
+
系統採用「**預裝優先,降級不中斷**」的容錯策略:
|
| 59 |
+
|
| 60 |
+
- **預裝資源失敗**(模型損毀、測站表缺失)→ 應用無法啟動(提前發現問題)
|
| 61 |
+
- **非關鍵資源失敗**(Vs30 初始化失敗、實際圖缺失)→ 使用預設值或占位,應用正常運作
|
| 62 |
+
- **單點資料缺失**(缺分量、測站不足)→ 使用替代值或降級處理,UI 明示警告,推論繼續
|
| 63 |
+
- **結果異常**(PGA 為 NaN)→ 記錄日誌,仍顯示於地圖
|
| 64 |
+
|
| 65 |
+
詳見 `spec/03-error-handling.md`。
|
| 66 |
+
|
| 67 |
+
### 🧪 展覽前檢查清單
|
| 68 |
+
|
| 69 |
+
在部署至 HF Space 前:
|
| 70 |
+
|
| 71 |
+
- [ ] 驗證所有預裝檔案完整(模型、Vs30、波形、測站表)
|
| 72 |
+
- [ ] 本地測試啟動流程(無外部網路依賴)
|
| 73 |
+
- [ ] 測試各事件的波形載入與預測
|
| 74 |
+
- [ ] 確認實際震度圖路徑與檔名正確
|
| 75 |
+
- [ ] 檢查日誌輸出(無錯誤訊息)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
需求
|
| 79 |
- Python 3.10–3.11(建議)
|
| 80 |
- 主要套件見 `requirements.txt`
|
|
|
|
| 100 |
- 新增輸入測站:
|
| 101 |
- 於 `station/site_info.csv` 增補列,欄位:`Station, Latitude, Longitude, Elevation`;去除重複站名列。
|
| 102 |
|
| 103 |
+
## 核心不變條件(摘要)
|
| 104 |
+
|
| 105 |
+
**波形處理**:
|
| 106 |
+
- 取樣率:100 Hz;輸入長度:30 秒(3000 點,不足補 0)
|
| 107 |
+
- 分量順序:Z, N, E;N/E 缺失時以 Z 代替
|
| 108 |
+
|
| 109 |
+
**測站處理**:
|
| 110 |
+
- 輸入測站:最多 25;不足允許但 UI 會顯示警告
|
| 111 |
+
- 目標測站批次:每批最多 25 點
|
| 112 |
+
- 缺分量統計:計數並在摘要中顯示
|
| 113 |
+
|
| 114 |
+
**資源管理**:
|
| 115 |
+
- 地圖高度:Folium 地圖固定 800px
|
| 116 |
+
- 實際震度圖缺失:以空白占位(不中止)
|
| 117 |
+
- Vs30 查詢失敗:使用預設值 600 m/s
|
| 118 |
+
|
| 119 |
+
**詳細規格**:見 `spec/00-overview.md` 與 `spec/01-data-contract.md`;**容錯與降級決策**見 `spec/03-error-handling.md`。
|
| 120 |
+
|
| 121 |
|
|
|
|
| 122 |
|
| 123 |
## 專案結構
|
| 124 |
- `app.py`:Gradio GUI 與推論主流程
|
|
|
|
| 127 |
- `station/eew_target.csv`:目標測站表
|
| 128 |
- `waveform/`:事件波形(.mseed)
|
| 129 |
- `intensity_map/`:實際震度圖(可選)
|
| 130 |
+
- `spec/`:模塊化規格檔案
|
| 131 |
+
- `00-overview.md`:核心目標、架構、設計原則
|
| 132 |
+
- `01-data-contract.md`:資料結構、必填欄位
|
| 133 |
+
- `02-processing-rules.md`:批次策略、處理規則
|
| 134 |
+
- `03-error-handling.md`:故障場景、容錯設計
|
| 135 |
+
- `04-extensions.md`:擴充空間、向後相容
|
| 136 |
- `.github/copilot-instructions.md`:生成程式碼指南
|
| 137 |
+
- `changelog.md`:變更摘要
|
| 138 |
+
|
|
|
|
| 139 |
|
| 140 |
## 疑難排解(Troubleshooting)
|
| 141 |
- Vs30 下載失敗或查無資料
|
|
|
|
| 152 |
- License:GPL-3.0
|
| 153 |
|
| 154 |
## 進一步閱讀
|
| 155 |
+
- `spec/00-overview.md`(核心目標、架構、不變條件)
|
| 156 |
+
- `spec/01-data-contract.md`(資料結構、必填欄位、冷啟動流程)
|
| 157 |
+
- `spec/02-processing-rules.md`(批次策略、輸入限制、資源限制)
|
| 158 |
+
- `spec/03-error-handling.md`(故障場景、降級策略、UI 訊息設計)
|
| 159 |
+
- `spec/04-extensions.md`(擴充空間、向後相容性)
|
| 160 |
- `.github/copilot-instructions.md`(開發與貢獻指南)
|
| 161 |
+
- `changelog.md`(歷次變更摘要)
|
changelog.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 變更日誌 (Changelog)
|
| 2 |
+
|
| 3 |
+
> 記錄專案的重大變更、新功能與修復。採用 [Keep a Changelog](https://keepachangelog.com/) 格式。
|
| 4 |
+
|
| 5 |
+
## [Unreleased]
|
| 6 |
+
|
| 7 |
+
### Changed
|
| 8 |
+
- **規格理念調整**:從「穩定可靠系統」改為「互動式教育展示 Demo」
|
| 9 |
+
- 強調預裝化設計:所有資源(模型、Vs30、波形、測站表)預裝於 HF Space,無需運行時下載
|
| 10 |
+
- 容錯策略調整:預裝資源失敗中止啟動(提早發現問題),非關鍵資源失敗降級處理
|
| 11 |
+
- 目標轉向展覽演示與教育體驗,而非生產級可靠性
|
| 12 |
+
|
| 13 |
+
- **規格檔案更新**
|
| 14 |
+
- `00-overview.md`:新增「設計理念」章節,明確 Demo 定位與預裝策略
|
| 15 |
+
- `01-data-contract.md`:新增預裝設計原則說明
|
| 16 |
+
- `02-processing-rules.md`:新增設計原則概述
|
| 17 |
+
- `03-error-handling.md`:完全重寫,移除網路容錯,強調預裝優先與降級策略
|
| 18 |
+
- 快速參考表:補充「部署環境」與預裝相關備註
|
| 19 |
+
|
| 20 |
+
- **README.md 擴展**
|
| 21 |
+
- 新增「設計思路」章節(500 字),解釋 Demo 定位、預裝架構、容錯策略
|
| 22 |
+
- 新增「預裝架構表」,列舉所有預裝資源
|
| 23 |
+
- 新增「展覽前檢查清單」,指導部署前驗證流程
|
| 24 |
+
- 更新「快速參考」與「進一步閱讀」指向新的模塊化規格
|
| 25 |
+
- 更新「專案結構」說明 spec 資料夾的模塊化檔案
|
| 26 |
+
|
| 27 |
+
### Added
|
| 28 |
+
- 模塊化規格文件(`spec/00-*.md` 至 `spec/04-*.md`)
|
| 29 |
+
- Copilot 開發指南(`.github/copilot-instructions.md`)
|
| 30 |
+
- 完整的錯誤處理策略文檔(`spec/03-error-handling.md`)
|
| 31 |
+
|
| 32 |
+
### Fixed
|
| 33 |
+
- (待填)
|
| 34 |
+
|
| 35 |
+
### Deprecated
|
| 36 |
+
- (待填)
|
| 37 |
+
|
| 38 |
+
### Removed
|
| 39 |
+
- (待填)
|
| 40 |
+
|
| 41 |
+
### Security
|
| 42 |
+
- (待填)
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
|
| 46 |
+
## [1.0.0] — 初版 (Initial Release)
|
| 47 |
+
|
| 48 |
+
### Added
|
| 49 |
+
- ✅ 完整的 Gradio GUI 介面
|
| 50 |
+
- 事件選擇、時間窗選擇、震央座標輸入
|
| 51 |
+
- 測站分布地圖、波形圖視覺化
|
| 52 |
+
- 互動式震度預測地圖(Folium)
|
| 53 |
+
- 實際觀測與預測對照
|
| 54 |
+
|
| 55 |
+
- ✅ 核心推論管道
|
| 56 |
+
- 支援 CNN + Position Embedding + Transformer + MLP + MDN 架構
|
| 57 |
+
- 自動距離排序,選擇最近 25 站
|
| 58 |
+
- 批次推論(每批 25 目標點)
|
| 59 |
+
- 條件處理與降級策略
|
| 60 |
+
|
| 61 |
+
- ✅ 穩健的資料處理
|
| 62 |
+
- 固定 100 Hz 取樣率、30 秒時間窗(3000 samples)
|
| 63 |
+
- 分量驗證與缺失降級(N/E 缺失以 Z 替代)
|
| 64 |
+
- 訊號處理(去趨勢、10 Hz 低通濾波)
|
| 65 |
+
- 補零對齊(不足 30 秒尾段補 0)
|
| 66 |
+
|
| 67 |
+
- ✅ 外部資源整合
|
| 68 |
+
- Hugging Face 模型載入(`SeisBlue/TTSAM`)
|
| 69 |
+
- Vs30 資料查詢(`SeisBlue/TaiwanVs30`);失敗降級至預設 600 m/s
|
| 70 |
+
- 本地 MSEED 波形讀取
|
| 71 |
+
- 本地測站表讀取(CSV)
|
| 72 |
+
|
| 73 |
+
- ✅ 日誌與監控
|
| 74 |
+
- loguru 日誌系統(INFO/WARNING/ERROR)
|
| 75 |
+
- 關鍵節點記錄(啟動、選擇、推論、完成)
|
| 76 |
+
- 降級決策透明化
|
| 77 |
+
|
| 78 |
+
- ✅ 測試資料
|
| 79 |
+
- 範例事件:2024年4月3日花蓮地震 (`20240403.mseed`)
|
| 80 |
+
- 範例目標點與測站表
|
| 81 |
+
|
| 82 |
+
### Known Limitations
|
| 83 |
+
- 模型輸入固定為 25 站、30 秒
|
| 84 |
+
- Vs30 查詢基於 2D 網格最近鄰法(不考慮深度)
|
| 85 |
+
- 不支援即時流模式(僅批次)
|
| 86 |
+
- 地圖高度固定 800px
|
| 87 |
+
|
| 88 |
+
### Notes for Future
|
| 89 |
+
- 見 `spec/04-extensions.md` 的擴充建議
|
| 90 |
+
- 見 `spec/plan.md` 的迭代計畫範本
|
| 91 |
+
|
| 92 |
+
---
|
| 93 |
+
|
| 94 |
+
## 版本說明
|
| 95 |
+
|
| 96 |
+
| 版本 | 發行日期 | 重點 |
|
| 97 |
+
|-----|--------|------|
|
| 98 |
+
| v1.0.0 | 2024年 | 初版發佈 |
|
| 99 |
+
| (未來) | (待定) | 功能擴展 |
|
| 100 |
+
|
| 101 |
+
---
|
| 102 |
+
|
| 103 |
+
## 貢獻指南
|
| 104 |
+
|
| 105 |
+
提交變更前,請:
|
| 106 |
+
1. 查閱 `.github/copilot-instructions.md` 開發指南
|
| 107 |
+
2. 確認相應 spec 檔案已同步(若涉及資料契約、處理規則、故障場景)
|
| 108 |
+
3. 更新本 changelog(`## [Unreleased]` 段落)
|
| 109 |
+
4. 執行測試確保無新增 ERROR 日誌
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
## 許可證
|
| 114 |
+
|
| 115 |
+
GPL-3.0
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
**最後更新**:2024 年 10 月 26 日
|
| 120 |
+
|
spec/00-overview.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 專案概覽 (00-Overview)
|
| 2 |
+
|
| 3 |
+
## 核心目標
|
| 4 |
+
**互動式教育性地震模型展示系統**。在 Hugging Face Space 上展示基於 Transformer 的地震震度預測模型,使觀眾通過直觀的操作流程理解「波形 → 模型推論 → 震度預測」的完整過程。
|
| 5 |
+
系統採用 GUI(Gradio)呈現互動式工作流:事件選擇 → 時間窗口選擇 → 測站選擇 → 波形預覽 → 批次推論 → 地圖可視化,並提供預測結果與實際觀測的對照。
|
| 6 |
+
|
| 7 |
+
### 🎯 設計理念
|
| 8 |
+
- **優先互動性**:所有操作應立即視覺化反饋(波形圖、地圖、統計訊息)
|
| 9 |
+
- **重視教育性**:透過清晰的介面與注解,讓非專業人士理解模型邏輯
|
| 10 |
+
- **功能簡潔化**:無需追求完整覆蓋或極限性能;易於操作與理解為首
|
| 11 |
+
- **預裝化設計**:所有資料(模型權重、Vs30、波形、測站表)均預裝於空間,無需外部下載
|
| 12 |
+
|
| 13 |
+
## 系統架構
|
| 14 |
+
|
| 15 |
+
```
|
| 16 |
+
使用者介面 (Gradio GUI)
|
| 17 |
+
↓
|
| 18 |
+
資料載入層 (讀 MSEED 檔案、載入測站表、初始化模型與 Vs30)
|
| 19 |
+
↓
|
| 20 |
+
測站選擇 (距離排序,最近 25 站;不足時降級)
|
| 21 |
+
↓
|
| 22 |
+
波形預處理 (去趨、低通濾波 @ 10 Hz、補零至 3000 samples)
|
| 23 |
+
↓
|
| 24 |
+
批次推論 (CNN → Position Embedding + Transformer → MLP → MDN)
|
| 25 |
+
↓
|
| 26 |
+
後處理與地圖可視化 (PGA → 震度等級 → Folium 地圖)
|
| 27 |
+
↓
|
| 28 |
+
對照結果 (預測 vs 實際觀測)
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
## 核心模組清單
|
| 32 |
+
|
| 33 |
+
| 模組 | 職責 | 關鍵檔案 |
|
| 34 |
+
|-----|------|---------|
|
| 35 |
+
| **模型層** | CNN + Position Embedding + Transformer + MLP + MDN | `app.py` (class definitions + `get_full_model()`) |
|
| 36 |
+
| **資料層** | 讀 MSEED、測站表、Vs30 資料庫 | `app.py` (initialization + `load_waveform()`) |
|
| 37 |
+
| **測站選擇** | 距離排序、去重複、狀態警告 | `app.py` (`select_nearest_stations()`) |
|
| 38 |
+
| **波形提取** | 分量驗證、補零、訊號處理 | `app.py` (`extract_waveforms_from_stream()` + `signal_processing()`) |
|
| 39 |
+
| **推論引擎** | 批次處理、Tensor 操作 | `app.py` (`predict_intensity()`) |
|
| 40 |
+
| **地圖可視化** | 互動式 Folium 地圖、實際圖對照 | `app.py` (`create_intensity_map()` + `load_observed_intensity_image()`) |
|
| 41 |
+
| **UI 工作流** | Gradio 事件綁定 | `app.py` (Gradio Blocks definition) |
|
| 42 |
+
|
| 43 |
+
## 核心不變條件 ⚠️ (必讀)
|
| 44 |
+
|
| 45 |
+
### 一、波形輸入
|
| 46 |
+
- **取樣率固定 100 Hz**
|
| 47 |
+
- **時間窗長度固定 30 秒**(3000 samples)
|
| 48 |
+
- **分量順序固定:Z, N, E**(模型期望此順序)
|
| 49 |
+
- **補零策略**:時間窗不足 30 秒時,尾段補 0 至 3000 samples
|
| 50 |
+
- **缺分量降級**:N 或 E 缺失時以 Z 替代,記錄統計數量
|
| 51 |
+
|
| 52 |
+
### 二、測站限制
|
| 53 |
+
- **輸入測站**:最多 1000+ 個(從 `site_info.csv` 讀取);每個測站可能有多個分量(Z/N/E)
|
| 54 |
+
- **被選測站**:模型固定接收 25 站;實際可用數 ≥ 1 時允許推論(< 25 時 Padding 至 25)
|
| 55 |
+
- **去重複**:僅保留站名,去除重複分量
|
| 56 |
+
|
| 57 |
+
### 三、目標測站
|
| 58 |
+
- **目標點總數**:從 `eew_target.csv` 讀取;無上限(但分批推論,每批 25 點)
|
| 59 |
+
- **批次大小**:每批最多 25 點;最後一批可少於 25 點
|
| 60 |
+
|
| 61 |
+
### 四、外部資源(預裝優先)
|
| 62 |
+
- **模型**:`ttsam_trained_model_11.pt` **預裝於 Space 本地**;無需下載
|
| 63 |
+
- **Vs30 資料**:`Vs30ofTaiwan.nc` **預裝於 Space 本地**;無需從 Hugging Face 下載;初始化失敗時用預設 600 m/s
|
| 64 |
+
- **MSEED 檔案**:`waveform/YYYYMMDD.mseed` **預裝於 Space**;若遺失則應用啟動時提示
|
| 65 |
+
- **測站表**:`station/site_info.csv`, `station/eew_target.csv` **預裝於 Space**;缺少必要欄位則提示
|
| 66 |
+
- **實際震度圖**:`intensity_map/YYYYMMDD.png` **預裝於 Space**(可選);不存在時以空白占位
|
| 67 |
+
|
| 68 |
+
### 五、訊號處理
|
| 69 |
+
- **去趨勢**:detrend(type="constant")
|
| 70 |
+
- **低通濾波**:10 Hz 4-order Butterworth
|
| 71 |
+
- **正規化**:進入 CNN 前進行軸向正規化(模型內部)
|
| 72 |
+
|
| 73 |
+
### 六、推論 → 震度轉換
|
| 74 |
+
- **模型輸出**:MDN 的加權高斯分布 (weight, sigma, mu)
|
| 75 |
+
- **PGA 計算**:μ_PGA = Σ(weight × μ)
|
| 76 |
+
- **PGA → 震度**:分段對數尺度查表(0–7 級,共 10 級別)
|
| 77 |
+
- **地圖顯示**:Folium CircleMarker + 顏色漸層(白 → 紫)
|
| 78 |
+
|
| 79 |
+
## 設計原則
|
| 80 |
+
|
| 81 |
+
### 1. 預裝優先(Preset-First Design)
|
| 82 |
+
- 所有關鍵資料(模型、Vs30、波形、測站表)均預裝於 HF Space,無需在應用啟動時下載
|
| 83 |
+
- 應用啟動應立即進入互動介面(無長時間初始化等待)
|
| 84 |
+
- 若預裝資料缺失(如某個事件的 MSEED),應於使用者操作時給予清晰提示,無需中止啟動
|
| 85 |
+
|
| 86 |
+
### 2. 降級不中斷(Graceful Degradation for Demo)
|
| 87 |
+
- 單點資料缺失(缺分量、實際震度圖缺失)→ 使用預設值或占位,記錄 INFO/WARNING log
|
| 88 |
+
- 非關鍵外部資源失敗(如 Vs30 查詢失敗)→ 使用預設值 600 m/s,記錄 WARNING log
|
| 89 |
+
- 測站數不足(< 25 站)→ 允許推論並在 UI 明示警告
|
| 90 |
+
- **關鍵資源失敗中止**(僅限:模型權重無法載入、測站表必填欄位缺失)
|
| 91 |
+
|
| 92 |
+
### 3. 教育性設計
|
| 93 |
+
- 介面應包含清晰的步驟說明與狀態反饋
|
| 94 |
+
- 波形、測站分布、地圖等視覺元件應帶有簡潔注解,解釋「為什麼這樣做」
|
| 95 |
+
- 統計資訊應顯示推論過程的關鍵數據(如選中測站數、缺分量數)
|
| 96 |
+
|
| 97 |
+
### 2. 規格優先
|
| 98 |
+
- 所有 I/O 形狀、必填欄位、檔案格式 → 參考本 spec(特別是 `01-data-contract.md`)
|
| 99 |
+
- 若要改動,先更新 spec,再改程式
|
| 100 |
+
|
| 101 |
+
### 3. 日誌清晰
|
| 102 |
+
- 使用 `loguru` 記錄:
|
| 103 |
+
- **INFO**:主流程進度(載入、選擇、推論)
|
| 104 |
+
- **WARNING**:降級決策(Vs30 失敗、缺分量、不足 25 站)
|
| 105 |
+
- **ERROR**:可恢復的單點失敗(測站查詢失敗、檔案讀取失敗)
|
| 106 |
+
- **不記錄 DEBUG**(除非調試)
|
| 107 |
+
- 所有 try-except 後立即 log.warning() / log.error()
|
| 108 |
+
|
| 109 |
+
## 快速參考表
|
| 110 |
+
|
| 111 |
+
| 項目 | 規格值 | 來源 | 備註 |
|
| 112 |
+
|-----|------|-----|------|
|
| 113 |
+
| 取樣率 | 100 Hz | 模型輸入 | 固定 |
|
| 114 |
+
| 時間窗 | 30 秒 = 3000 samples | 模型輸入 | 不足時尾段補 0 |
|
| 115 |
+
| 輸入測站上限 | 25 站 | 模型輸入 | Padding 至 25 |
|
| 116 |
+
| 目標批次大小 | 25 點/批 | 記憶體最適化 | 可調 |
|
| 117 |
+
| Vs30 預設值 | 600 m/s | 降級策略 | 預裝失敗時 |
|
| 118 |
+
| PGA 級別數 | 10 級 (0–7) | `calculate_intensity()` | 對數尺度 |
|
| 119 |
+
| 低通濾波 | 10 Hz, 4 order Butterworth | 訊號處理 | 固定 |
|
| 120 |
+
| 地圖高度 | 800 px | UI 設計 | 固定 |
|
| 121 |
+
| 實際震度圖路徑 | `intensity_map/YYYYMMDD.png` | 預裝檔案 | 可選 |
|
| 122 |
+
| 部署環境 | Hugging Face Space | 目標 | 預裝優先,無外部下載 |
|
| 123 |
+
|
spec/01-data-contract.md
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 資料契約 (01-Data-Contract)
|
| 2 |
+
|
| 3 |
+
## 概述
|
| 4 |
+
本檔案定義系統所有輸入/輸出資料結構、必填欄位、檔案格式與冷啟動流程。
|
| 5 |
+
|
| 6 |
+
**關鍵設計原則**:
|
| 7 |
+
- 所有外部資源(模型、Vs30、波形、測站表)**預裝於 HF Space**,無需運行時下載
|
| 8 |
+
- 資料層失敗時應立即提示(於 UI 或日誌),而非沉默降級
|
| 9 |
+
- 必填欄位缺失應中止啟動;非關鍵資料缺失應使用預設值
|
| 10 |
+
|
| 11 |
+
### 1.1 MSEED 波形檔案 (`waveform/YYYYMMDD.mseed`)
|
| 12 |
+
|
| 13 |
+
**用途**:儲存地震波形時間序列數據
|
| 14 |
+
|
| 15 |
+
**結構**(ObsPy Stream 格式):
|
| 16 |
+
```
|
| 17 |
+
每個 Trace 包含:
|
| 18 |
+
stats.station: 測站代碼 (e.g., "ALS")
|
| 19 |
+
stats.channel: 分量代碼 (e.g., "EHZ", "EHN", "EHE", "HHZ", "HHN", "HHE")
|
| 20 |
+
stats.sampling_rate: 100 Hz (預期值)
|
| 21 |
+
data: ndarray, shape (N,), dtype float64
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
**必需分量**:
|
| 25 |
+
- Z(豎向):必須存在;其他分量缺失時用 Z 替代
|
| 26 |
+
- N(南北):可選;缺失時以 Z 替代
|
| 27 |
+
- E(東西):可選;缺失時以 Z 替代
|
| 28 |
+
|
| 29 |
+
**取樣率驗證**:
|
| 30 |
+
- 預期 100 Hz
|
| 31 |
+
- 若不同,模型輸入會失配(不自動重取樣)
|
| 32 |
+
|
| 33 |
+
### 1.2 輸入測站表 (`station/site_info.csv`)
|
| 34 |
+
|
| 35 |
+
**用途**:1000+ 個地震測站的元資料;模型從中選擇距震央最近的 25 站
|
| 36 |
+
|
| 37 |
+
**必填欄位**:
|
| 38 |
+
```
|
| 39 |
+
Station,Latitude,Longitude,Elevation
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
**欄位型態與說明**:
|
| 43 |
+
| 欄位 | 型態 | 範圍 | 說明 |
|
| 44 |
+
|-----|-----|------|------|
|
| 45 |
+
| Station | 字串 | N/A | 測站代碼 (e.g., "ALS") |
|
| 46 |
+
| Latitude | 浮點 | (5, 30) | 緯度 (度) |
|
| 47 |
+
| Longitude | 浮點 | (110, 123) | 經度 (度) |
|
| 48 |
+
| Elevation | 浮點 | 0+ | 海拔高度 (公尺) |
|
| 49 |
+
|
| 50 |
+
**去重複規則**:
|
| 51 |
+
- 每個 Station 可能在 MSEED 中出現多次(不同分量/時間段)
|
| 52 |
+
- 讀取時保留唯一站名(使用 `drop_duplicates(subset=['Station'])`)
|
| 53 |
+
|
| 54 |
+
**驗證邏輯**:
|
| 55 |
+
```
|
| 56 |
+
if any(field not in site_info.columns for field in required_fields):
|
| 57 |
+
log.error(f"site_info.csv 缺少必要欄位: {missing_fields}")
|
| 58 |
+
raise ValueError(...)
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
### 1.3 目標測站表 (`station/eew_target.csv`)
|
| 62 |
+
|
| 63 |
+
**用途**:推論目標點集合;每行代表一個預測位置
|
| 64 |
+
|
| 65 |
+
**必填欄位**:
|
| 66 |
+
```
|
| 67 |
+
network,county,station,station_zh,longitude,latitude,elevation
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
**欄位型態與說明**:
|
| 71 |
+
| 欄位 | 型態 | 範圍 | 說明 |
|
| 72 |
+
|-----|-----|------|------|
|
| 73 |
+
| network | 字串 | N/A | 測網代碼 (e.g., "CWB_SMT", "TSMIP") |
|
| 74 |
+
| county | 字串 | N/A | 行政區名稱 |
|
| 75 |
+
| station | 字串 | N/A | 測站代碼 (e.g., "TAP") |
|
| 76 |
+
| station_zh | 字串 | N/A | 測站中文名稱 |
|
| 77 |
+
| longitude | 浮點 | (110, 123) | 經度 (度) |
|
| 78 |
+
| latitude | 浮點 | (5, 30) | 緯度 (度) |
|
| 79 |
+
| elevation | 浮點 | 0+ | 海拔高度 (公尺) |
|
| 80 |
+
|
| 81 |
+
**驗證邏輯**:
|
| 82 |
+
```
|
| 83 |
+
if any(field not in target_df.columns for field in required_fields):
|
| 84 |
+
log.error(f"eew_target.csv 缺少必要欄位: {missing_fields}")
|
| 85 |
+
raise ValueError(...)
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
### 1.4 Vs30 資料庫 (Hugging Face `SeisBlue/TaiwanVs30`)
|
| 89 |
+
|
| 90 |
+
**用途**:空間網格 Vs30 參數(地表剪切波速度 30 公尺深度平均值)
|
| 91 |
+
|
| 92 |
+
**檔案格式**:NetCDF4 (`Vs30ofTaiwan.nc`)
|
| 93 |
+
|
| 94 |
+
**資料結構**(xarray Dataset):
|
| 95 |
+
```
|
| 96 |
+
lat: 緯度網格 (e.g., 5–30°)
|
| 97 |
+
lon: 經度網格 (e.g., 110–123°)
|
| 98 |
+
vs30: 對應的 Vs30 值 (m/s)
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
**查詢方式**:
|
| 102 |
+
- 使用 scipy.spatial.cKDTree 進行最近鄰查詢
|
| 103 |
+
- 輸入:(lat, lon) → 輸出:Vs30 (m/s)
|
| 104 |
+
|
| 105 |
+
**預設值降級**:
|
| 106 |
+
- 下載失敗 → 使用 600 m/s
|
| 107 |
+
- 查詢失敗(超時、點出界)→ 使用 600 m/s
|
| 108 |
+
- **記錄 WARNING log**
|
| 109 |
+
|
| 110 |
+
### 1.5 模型權重 (Hugging Face `SeisBlue/TTSAM`)
|
| 111 |
+
|
| 112 |
+
**用途**:預訓練的 PyTorch 模型參數
|
| 113 |
+
|
| 114 |
+
**檔案**:`ttsam_trained_model_11.pt`
|
| 115 |
+
|
| 116 |
+
**載入方式**:
|
| 117 |
+
```python
|
| 118 |
+
model_dict = torch.load(model_path, weights_only=True, map_location=device)
|
| 119 |
+
model.load_state_dict(model_dict)
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
**失敗策略**:**中止流程**(無預設值可用)
|
| 123 |
+
|
| 124 |
+
### 1.6 實際震度圖 (本地可選) (`intensity_map/YYYYMMDD.png`)
|
| 125 |
+
|
| 126 |
+
**用途**:與預測結果對照的實際觀測震度圖
|
| 127 |
+
|
| 128 |
+
**檔案命名**:
|
| 129 |
+
- 格式:`intensity_map/YYYYMMDD.png`
|
| 130 |
+
- YYYYMMDD 與 MSEED 檔名對應 (e.g., `20240403.mseed` ↔ `20240403.png`)
|
| 131 |
+
|
| 132 |
+
**缺失策略**:
|
| 133 |
+
- 不存在時顯示空白占位(UI 提示 "實際觀測震度圖缺失")
|
| 134 |
+
- **不中止流程**
|
| 135 |
+
|
| 136 |
+
**支援格式**:`.png`, `.jpg`, `.jpeg`, `.gif`
|
| 137 |
+
|
| 138 |
+
## 二、內部資料結構(關鍵中間表示)
|
| 139 |
+
|
| 140 |
+
### 2.1 選定測站列表 (Python dict)
|
| 141 |
+
|
| 142 |
+
```python
|
| 143 |
+
selected_stations = [
|
| 144 |
+
{
|
| 145 |
+
"station": "ALS",
|
| 146 |
+
"distance": 0.05, # 度
|
| 147 |
+
"latitude": 23.5083,
|
| 148 |
+
"longitude": 120.8134,
|
| 149 |
+
"elevation": 2413.0
|
| 150 |
+
},
|
| 151 |
+
... # 最多 25 項
|
| 152 |
+
]
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
### 2.2 波形矩陣 (NumPy ndarray)
|
| 156 |
+
|
| 157 |
+
**單個測站波形**(供測試):
|
| 158 |
+
```python
|
| 159 |
+
waveform_3c = np.zeros((3000, 3), dtype=np.float64)
|
| 160 |
+
# [:, 0] = Z 分量
|
| 161 |
+
# [:, 1] = N 分量
|
| 162 |
+
# [:, 2] = E 分量
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
**批次波形(模型輸入)**:
|
| 166 |
+
```python
|
| 167 |
+
waveform_padded = np.zeros((25, 3000, 3), dtype=np.float64)
|
| 168 |
+
# shape: (n_stations, n_samples, n_components)
|
| 169 |
+
# 不足 25 站時,多餘行全 0
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
### 2.3 測站元資訊矩陣 (NumPy ndarray)
|
| 173 |
+
|
| 174 |
+
```python
|
| 175 |
+
station_info_padded = np.zeros((25, 4), dtype=np.float64)
|
| 176 |
+
# [:, 0] = 緯度
|
| 177 |
+
# [:, 1] = 經度
|
| 178 |
+
# [:, 2] = 海拔 (公尺)
|
| 179 |
+
# [:, 3] = Vs30 (m/s)
|
| 180 |
+
# 不足 25 站時,多餘行全 0
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
### 2.4 目標測站元資訊矩陣 (NumPy ndarray)
|
| 184 |
+
|
| 185 |
+
```python
|
| 186 |
+
target_padded = np.zeros((25, 4), dtype=np.float64)
|
| 187 |
+
# [:, 0] = 緯度
|
| 188 |
+
# [:, 1] = 經度
|
| 189 |
+
# [:, 2] = 海拔 (公尺)
|
| 190 |
+
# [:, 3] = Vs30 (m/s)
|
| 191 |
+
# 不足 25 點時,多餘行全 0
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
## 三、模型輸入/輸出形狀 (I/O Shape)
|
| 195 |
+
|
| 196 |
+
### 3.1 模型輸入 (Batch)
|
| 197 |
+
|
| 198 |
+
```python
|
| 199 |
+
tensor_data = {
|
| 200 |
+
"waveform": torch.tensor(waveform_padded).unsqueeze(0).double(),
|
| 201 |
+
# shape: (batch_size=1, n_stations=25, n_samples=3000, n_components=3)
|
| 202 |
+
# dtype: torch.float64
|
| 203 |
+
|
| 204 |
+
"station": torch.tensor(station_info_padded).unsqueeze(0).double(),
|
| 205 |
+
# shape: (batch_size=1, n_stations=25, 4)
|
| 206 |
+
# 欄位:[緯度, 經度, 海拔, Vs30]
|
| 207 |
+
# dtype: torch.float64
|
| 208 |
+
|
| 209 |
+
"target": torch.tensor(target_padded).unsqueeze(0).double(),
|
| 210 |
+
# shape: (batch_size=1, n_targets=25, 4)
|
| 211 |
+
# 欄位:[緯度, 經度, 海拔, Vs30]
|
| 212 |
+
# dtype: torch.float64
|
| 213 |
+
}
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
### 3.2 模型輸出
|
| 217 |
+
|
| 218 |
+
```python
|
| 219 |
+
weight, sigma, mu = model(tensor_data)
|
| 220 |
+
# weight: shape (batch_size=1, n_targets=25, n_gaussians=5)
|
| 221 |
+
# sigma: shape (batch_size=1, n_targets=25, n_gaussians=5)
|
| 222 |
+
# mu: shape (batch_size=1, n_targets=25, n_gaussians=5)
|
| 223 |
+
# dtype: torch.float32 (在 GPU/CPU 上計算後)
|
| 224 |
+
```
|
| 225 |
+
|
| 226 |
+
**PGA 計算**:
|
| 227 |
+
```python
|
| 228 |
+
pga = torch.sum(weight * mu, dim=2) # (batch_size, n_targets)
|
| 229 |
+
# 結果:加權高斯分布的平均值(每個目標點 1 個 PGA 值)
|
| 230 |
+
```
|
| 231 |
+
|
| 232 |
+
## 四、輸出資料結構
|
| 233 |
+
|
| 234 |
+
### 4.1 PGA 列表 (Python list)
|
| 235 |
+
|
| 236 |
+
```python
|
| 237 |
+
pga_list = [0.1234, 0.5678, ...] # 長度 = 目標點數量
|
| 238 |
+
```
|
| 239 |
+
|
| 240 |
+
### 4.2 目標點名稱列表 (Python list)
|
| 241 |
+
|
| 242 |
+
```python
|
| 243 |
+
target_names = ["TAP", "A024", ...] # 長度 = 目標點數量
|
| 244 |
+
```
|
| 245 |
+
|
| 246 |
+
### 4.3 互動式地圖 HTML (Folium)
|
| 247 |
+
|
| 248 |
+
```python
|
| 249 |
+
intensity_map = folium.Map(...)
|
| 250 |
+
map_html = intensity_map._repr_html_()
|
| 251 |
+
# 輸出:HTML 字串,可內嵌 Gradio HTML component
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
### 4.4 統計訊息 (字串)
|
| 255 |
+
|
| 256 |
+
```python
|
| 257 |
+
stats = """✅ 預測完成!
|
| 258 |
+
開始時間: 0.0 秒
|
| 259 |
+
時間長度: 30.0 秒 (0.0 - 30.0)
|
| 260 |
+
震央位置: (121.5700, 23.8800)
|
| 261 |
+
使用測站數: 25 / 25
|
| 262 |
+
預測目標點數: 50
|
| 263 |
+
預測最大震度: 6-
|
| 264 |
+
"""
|
| 265 |
+
```
|
| 266 |
+
|
| 267 |
+
## 五、冷啟動流程 (Bootstrap)
|
| 268 |
+
|
| 269 |
+
### 啟動順序
|
| 270 |
+
1. **檢查必要環境變數** (GPU/CPU 設定)
|
| 271 |
+
2. **初始化 Vs30 資料**
|
| 272 |
+
- 嘗試從 Hugging Face 下載
|
| 273 |
+
- 若失敗,記錄 WARNING,使用預設 600 m/s
|
| 274 |
+
3. **初始化模型**
|
| 275 |
+
- 從 Hugging Face 下載 `ttsam_trained_model_11.pt`
|
| 276 |
+
- 建構 FullModel 網路架構
|
| 277 |
+
- 載入權重、移至 GPU/CPU
|
| 278 |
+
4. **載入輸入測站表** (`station/site_info.csv`)
|
| 279 |
+
- 驗證必填欄位
|
| 280 |
+
- 去重複 (Station)
|
| 281 |
+
- 若失敗,記錄 ERROR、中止啟動
|
| 282 |
+
5. **載入目標測站表** (`station/eew_target.csv`)
|
| 283 |
+
- 驗證必填欄位
|
| 284 |
+
- 轉換為 dict 列表
|
| 285 |
+
- 若失敗,記錄 ERROR、中止啟動
|
| 286 |
+
6. **列舉 EARTHQUAKE_EVENTS**
|
| 287 |
+
- 從 `EARTHQUAKE_EVENTS` 字典掃描本地 `.mseed` 檔案
|
| 288 |
+
|
| 289 |
+
### 啟動失敗條件
|
| 290 |
+
|
| 291 |
+
| 資源 | 失敗行為 | 恢復策略 |
|
| 292 |
+
|-----|--------|--------|
|
| 293 |
+
| 模型權重 | ERROR | 中止啟動 |
|
| 294 |
+
| 輸入測站表 | ERROR | 中止啟動 |
|
| 295 |
+
| 目標測站表 | ERROR | 中止啟動 |
|
| 296 |
+
| Vs30 資料 | WARNING | 使用預設 600 m/s |
|
| 297 |
+
| MSEED 檔案 | WARNING | 該事件不可用 |
|
| 298 |
+
| 實際震度圖 | (無) | 推論時顯示空白占位 |
|
| 299 |
+
|
| 300 |
+
## 六、欄位對應關係 (Cross-Reference)
|
| 301 |
+
|
| 302 |
+
```
|
| 303 |
+
site_info.csv (輸入測站表)
|
| 304 |
+
↓ (去重複、按距離排序、選擇最近 25 站)
|
| 305 |
+
selected_stations = [
|
| 306 |
+
{"station": Station, "latitude": Latitude, "longitude": Longitude,
|
| 307 |
+
"elevation": Elevation, "distance": calc_distance(...)}
|
| 308 |
+
]
|
| 309 |
+
↓ (提取波形、查詢 Vs30)
|
| 310 |
+
station_info_padded = [[Latitude, Longitude, Elevation, Vs30], ...]
|
| 311 |
+
↓ (傳入模型)
|
| 312 |
+
model["station"] # shape (1, 25, 4)
|
| 313 |
+
|
| 314 |
+
eew_target.csv (目標測站表)
|
| 315 |
+
↓ (批次分割)
|
| 316 |
+
batch_targets = [{"station": station, "latitude": latitude, ...}]
|
| 317 |
+
↓ (查詢 Vs30)
|
| 318 |
+
target_padded = [[latitude, longitude, elevation, Vs30], ...]
|
| 319 |
+
↓ (傳入模型)
|
| 320 |
+
model["target"] # shape (1, 25, 4)
|
| 321 |
+
```
|
| 322 |
+
|
spec/02-processing-rules.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 處理規則與資源限制 (02-Processing-Rules)
|
| 2 |
+
|
| 3 |
+
## 概述
|
| 4 |
+
本檔案定義批次策略、輸入項限制、資料處理參數與資源上限。
|
| 5 |
+
|
| 6 |
+
**設計原則**:
|
| 7 |
+
- 所有輸入資料(波形、測站表)均預裝於 HF Space
|
| 8 |
+
- 批次策略專為互動式 Demo 設計,無需極限優化
|
| 9 |
+
- 容錯優先於性能:單點失敗時降級而非中止整個流程
|
| 10 |
+
|
| 11 |
+
### 1.1 選測站批次 (固定)
|
| 12 |
+
|
| 13 |
+
**流程**:
|
| 14 |
+
1. 載入 MSEED 全波形(所有測站)
|
| 15 |
+
2. 遍歷波形中的所有 Trace,建立 (station_code, distance) 映射
|
| 16 |
+
3. 按距離排序,選擇前 **25 站**
|
| 17 |
+
4. 不足 25 站:允許繼續推論,記錄 WARNING
|
| 18 |
+
5. Padding 至 25 站(多餘行用 0 填充)
|
| 19 |
+
|
| 20 |
+
**限制**:
|
| 21 |
+
- **固定選擇數**:25 站
|
| 22 |
+
- **去重複**:每個 station_code 只計算一次(多分量時取第一個)
|
| 23 |
+
|
| 24 |
+
### 1.2 目標批次 (可配置)
|
| 25 |
+
|
| 26 |
+
**流程**:
|
| 27 |
+
1. 讀 `eew_target.csv`,得到 N 個目標點
|
| 28 |
+
2. 分割成 `batch_size` 為單位的批次
|
| 29 |
+
3. 每批 ≤ 25 個目標點
|
| 30 |
+
4. 最後一批可少於 25 個
|
| 31 |
+
|
| 32 |
+
**計算邏輯**:
|
| 33 |
+
```python
|
| 34 |
+
batch_size = 25
|
| 35 |
+
num_batches = (total_targets + batch_size - 1) // batch_size
|
| 36 |
+
for batch_idx in range(num_batches):
|
| 37 |
+
start_idx = batch_idx * batch_size
|
| 38 |
+
end_idx = min((batch_idx + 1) * batch_size, total_targets)
|
| 39 |
+
batch_targets = target_dict[start_idx:end_idx]
|
| 40 |
+
# Padding 至 25 個
|
| 41 |
+
target_padded = np.zeros((batch_size, 4))
|
| 42 |
+
for i in range(len(batch_targets)):
|
| 43 |
+
target_padded[i] = [lat, lon, elev, vs30]
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
**限制**:
|
| 47 |
+
- **批次大小**:25 點 / 批(根據記憶體限制,可調整)
|
| 48 |
+
- **目標點總數**:無上限理論上限,但每次推論前須分批
|
| 49 |
+
|
| 50 |
+
## 二、輸入項限制
|
| 51 |
+
|
| 52 |
+
### 2.1 時間參數
|
| 53 |
+
|
| 54 |
+
| 參數 | 最小值 | 最大值 | 預設值 | 單位 | 說明 |
|
| 55 |
+
|-----|-------|-------|-------|------|------|
|
| 56 |
+
| start_time | 0 | 300 | 0 | 秒 | 波形開始時間 |
|
| 57 |
+
| duration | 0 | 30 | 30 | 秒 | 時間窗長度 |
|
| 58 |
+
|
| 59 |
+
**驗證邏輯**:
|
| 60 |
+
```python
|
| 61 |
+
if start_time < 0 or duration <= 0:
|
| 62 |
+
raise ValueError("start_time >= 0 and duration > 0")
|
| 63 |
+
if start_time + duration > total_duration:
|
| 64 |
+
log.warning(f"時間窗超出波形長度,截斷至 {total_duration} 秒")
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
**補零策略**(spec #2):
|
| 68 |
+
- 若 duration < 30 秒,尾段以 0 補齊至 30 秒(3000 samples @ 100 Hz)
|
| 69 |
+
- 若 duration > 30 秒,截取前 30 秒(只用前 3000 samples)
|
| 70 |
+
|
| 71 |
+
**記錄方式**:
|
| 72 |
+
```python
|
| 73 |
+
if needs_padding:
|
| 74 |
+
logger.info(f"時間長度 {duration} 秒 < 30 秒,將以 0 遮罩補齊至 30 秒")
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
### 2.2 空間參數
|
| 78 |
+
|
| 79 |
+
| 參數 | 最小值 | 最大值 | 單位 | 說明 |
|
| 80 |
+
|-----|-------|-------|------|------|
|
| 81 |
+
| epicenter_lat | 5 | 30 | 度 | 震央緯度 |
|
| 82 |
+
| epicenter_lon | 110 | 123 | 度 | 震央經度 |
|
| 83 |
+
|
| 84 |
+
**驗證邏輯**:
|
| 85 |
+
```python
|
| 86 |
+
if not (5 <= epicenter_lat <= 30 and 110 <= epicenter_lon <= 123):
|
| 87 |
+
log.warning(f"震央位置 ({epicenter_lat}, {epicenter_lon}) 超出預期範圍")
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
### 2.3 Vs30 預設值
|
| 91 |
+
|
| 92 |
+
| 參數 | 預設值 | 單位 | 說明 |
|
| 93 |
+
|-----|-------|------|------|
|
| 94 |
+
| user_vs30 | 600 | m/s | 當 Vs30 資料查詢失敗時使用 |
|
| 95 |
+
|
| 96 |
+
## 三、資料處理參數
|
| 97 |
+
|
| 98 |
+
### 3.1 訊號處理 (spec #2)
|
| 99 |
+
|
| 100 |
+
```python
|
| 101 |
+
def signal_processing(waveform):
|
| 102 |
+
# 步驟 1:去趨勢
|
| 103 |
+
data = detrend(waveform, type="constant")
|
| 104 |
+
|
| 105 |
+
# 步驟 2:低通濾波
|
| 106 |
+
data = lowpass(data, freq=10, df=100, corners=4)
|
| 107 |
+
# freq=10 Hz,fs=100 Hz,Nyquist=50 Hz → 10/50 = 0.2 (正規化頻率)
|
| 108 |
+
# 4-order Butterworth IIR 濾波器
|
| 109 |
+
|
| 110 |
+
return data
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
**參數說明**:
|
| 114 |
+
| 參數 | 值 | 說明 |
|
| 115 |
+
|-----|-----|------|
|
| 116 |
+
| detrend type | "constant" | 移除平均值 |
|
| 117 |
+
| 濾波頻率 | 10 Hz | 低通截頻 |
|
| 118 |
+
| 採樣率 | 100 Hz | 固定值 |
|
| 119 |
+
| 濾波器階數 | 4 | Butterworth 4th order |
|
| 120 |
+
|
| 121 |
+
### 3.2 波形補零與對齊 (spec #2)
|
| 122 |
+
|
| 123 |
+
```python
|
| 124 |
+
target_length = 3000 # 30 秒 @ 100 Hz
|
| 125 |
+
waveform_3c = np.zeros((target_length, 3))
|
| 126 |
+
|
| 127 |
+
# 填入實際資料(處理長度不足或過長的情況)
|
| 128 |
+
z_len = min(len(z_data), target_length)
|
| 129 |
+
n_len = min(len(n_data), target_length)
|
| 130 |
+
e_len = min(len(e_data), target_length)
|
| 131 |
+
|
| 132 |
+
waveform_3c[:z_len, 0] = z_data[:z_len]
|
| 133 |
+
waveform_3c[:n_len, 1] = n_data[:n_len]
|
| 134 |
+
waveform_3c[:e_len, 2] = e_data[:e_len]
|
| 135 |
+
# 多餘位置保持 0
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
### 3.3 分量對應 (spec #2)
|
| 139 |
+
|
| 140 |
+
| 通道代碼 | 對應分量 | 分量序號 |
|
| 141 |
+
|--------|--------|--------|
|
| 142 |
+
| Z / 1 | 豎向 | 0 |
|
| 143 |
+
| N | 南北 | 1 |
|
| 144 |
+
| E / 2 | 東西 | 2 |
|
| 145 |
+
|
| 146 |
+
**缺分量替代規則**:
|
| 147 |
+
```python
|
| 148 |
+
# Z 分量(必須存在)
|
| 149 |
+
if len(z_trace) == 0:
|
| 150 |
+
logger.warning(f"測站 {station_code} 缺少 Z 分量,跳過此測站")
|
| 151 |
+
continue
|
| 152 |
+
|
| 153 |
+
# N 分量(缺失時以 Z 代替)
|
| 154 |
+
if len(n_trace) > 0:
|
| 155 |
+
n_data = n_trace[0].data
|
| 156 |
+
else:
|
| 157 |
+
n_data = z_data.copy()
|
| 158 |
+
missing_components_count += 1
|
| 159 |
+
logger.debug(f"測站 {station_code} 缺少 N 分量,以 Z 分量代替")
|
| 160 |
+
|
| 161 |
+
# E 分量(缺失時以 Z 代替)
|
| 162 |
+
if len(e_trace) > 0:
|
| 163 |
+
e_data = e_trace[0].data
|
| 164 |
+
else:
|
| 165 |
+
e_data = z_data.copy()
|
| 166 |
+
missing_components_count += 1
|
| 167 |
+
logger.debug(f"測站 {station_code} 缺少 E 分量,以 Z 分量代替")
|
| 168 |
+
```
|
| 169 |
+
|
| 170 |
+
## 四、測站選擇規則
|
| 171 |
+
|
| 172 |
+
### 4.1 距離計算
|
| 173 |
+
|
| 174 |
+
```python
|
| 175 |
+
def calculate_distance(lat1, lon1, lat2, lon2):
|
| 176 |
+
"""簡化的平面距離 (度單位)"""
|
| 177 |
+
return np.sqrt((lat1 - lat2)**2 + (lon1 - lon2)**2)
|
| 178 |
+
```
|
| 179 |
+
|
| 180 |
+
**注意**:此為平面距離近似,不考慮地球曲率。用於快速排序,不是測地距離。
|
| 181 |
+
|
| 182 |
+
### 4.2 選擇邏輯
|
| 183 |
+
|
| 184 |
+
```python
|
| 185 |
+
# 1. 遍歷 MSEED 中所有 Trace,建立站點映射(去重複)
|
| 186 |
+
station_distances = {}
|
| 187 |
+
for tr in st:
|
| 188 |
+
station_code = tr.stats.station
|
| 189 |
+
if station_code in station_distances:
|
| 190 |
+
continue # 已處理,跳過其他分量
|
| 191 |
+
|
| 192 |
+
# 從 site_info 查詢位置
|
| 193 |
+
station_data = site_info[site_info["Station"] == station_code]
|
| 194 |
+
if len(station_data) == 0:
|
| 195 |
+
continue
|
| 196 |
+
|
| 197 |
+
lat, lon, elev = station_data.iloc[0][["Latitude", "Longitude", "Elevation"]]
|
| 198 |
+
distance = calculate_distance(epicenter_lat, epicenter_lon, lat, lon)
|
| 199 |
+
station_distances[station_code] = {..., "distance": distance}
|
| 200 |
+
|
| 201 |
+
# 2. 排序、選擇前 25 個
|
| 202 |
+
station_list = list(station_distances.values())
|
| 203 |
+
station_list.sort(key=lambda x: x["distance"])
|
| 204 |
+
selected_stations = station_list[:25]
|
| 205 |
+
|
| 206 |
+
# 3. 記錄統計
|
| 207 |
+
if len(selected_stations) < 25:
|
| 208 |
+
logger.warning(f"僅找到 {len(selected_stations)} 個可用測站(目標 25 個)")
|
| 209 |
+
```
|
| 210 |
+
|
| 211 |
+
## 五、Vs30 查詢規則 (spec #2)
|
| 212 |
+
|
| 213 |
+
### 5.1 查詢流程
|
| 214 |
+
|
| 215 |
+
```python
|
| 216 |
+
def get_vs30(lat, lon, user_vs30=600):
|
| 217 |
+
if tree is None or vs30_table is None:
|
| 218 |
+
# 資料庫未初始化
|
| 219 |
+
logger.info(f"使用使用者輸入的 Vs30 值 ({user_vs30} m/s) for ({lat}, {lon})")
|
| 220 |
+
return float(user_vs30)
|
| 221 |
+
|
| 222 |
+
try:
|
| 223 |
+
distance, i = tree.query([float(lat), float(lon)])
|
| 224 |
+
vs30 = vs30_table.iloc[i]["Vs30"]
|
| 225 |
+
logger.info(f"從資料庫查詢到 Vs30 值 ({vs30} m/s) for ({lat}, {lon})")
|
| 226 |
+
return float(vs30)
|
| 227 |
+
except Exception as e:
|
| 228 |
+
logger.warning(f"Vs30 查詢失敗 ({lat}, {lon}): {e},使用預設值 {user_vs30} m/s")
|
| 229 |
+
return float(user_vs30)
|
| 230 |
+
```
|
| 231 |
+
|
| 232 |
+
### 5.2 降級策略
|
| 233 |
+
|
| 234 |
+
| 情況 | 行為 | 日誌等級 |
|
| 235 |
+
|-----|------|--------|
|
| 236 |
+
| Vs30 資料庫未初始化 | 使用預設 600 m/s | INFO |
|
| 237 |
+
| 查詢點超出資料邊界 | 使用預設 600 m/s | WARNING |
|
| 238 |
+
| 查詢超時 | 使用預設 600 m/s | WARNING |
|
| 239 |
+
| 格式錯誤(NaN/Inf) | 使用預設 600 m/s | WARNING |
|
| 240 |
+
|
| 241 |
+
## 六、推論資源上限
|
| 242 |
+
|
| 243 |
+
| 資源 | 上限 | 說明 |
|
| 244 |
+
|-----|------|------|
|
| 245 |
+
| 批次大小 | 25 目標點 | VRAM 限制 (GPU) 或記憶體 (CPU) |
|
| 246 |
+
| 選測站數 | 25 站 | 模型固定輸入 |
|
| 247 |
+
| 時間窗 | 30 秒 = 3000 samples | 模型固定輸入 |
|
| 248 |
+
| 目標點總數 | 無限 (分批推論) | 時間複雜度 O(N / 25) |
|
| 249 |
+
|
| 250 |
+
## 七、日誌記錄規則
|
| 251 |
+
|
| 252 |
+
### 7.1 日誌等級配置
|
| 253 |
+
|
| 254 |
+
```python
|
| 255 |
+
from loguru import logger
|
| 256 |
+
|
| 257 |
+
# INFO:主流程進度
|
| 258 |
+
logger.info("載入波形資料...")
|
| 259 |
+
logger.info("選擇測站完成,共 N 個")
|
| 260 |
+
logger.info("預測完成")
|
| 261 |
+
|
| 262 |
+
# WARNING:降級決策(不中止)
|
| 263 |
+
logger.warning("Vs30 資料查詢失敗,使用預設值")
|
| 264 |
+
logger.warning("缺少 N 或 E 分量,以 Z 代替")
|
| 265 |
+
logger.warning(f"僅找到 {N} 個測站(目標 25 個)")
|
| 266 |
+
|
| 267 |
+
# ERROR:單點失敗(可恢復)
|
| 268 |
+
logger.error("測站資訊查詢失敗,跳過該站")
|
| 269 |
+
logger.error(f"無法提取波形資料: {exception}")
|
| 270 |
+
|
| 271 |
+
# 不記錄 DEBUG(除非特殊調試)
|
| 272 |
+
```
|
| 273 |
+
|
| 274 |
+
### 7.2 關鍵節點記錄
|
| 275 |
+
|
| 276 |
+
| 節點 | 記錄訊息 | 等級 | 條件 |
|
| 277 |
+
|-----|--------|------|------|
|
| 278 |
+
| 啟動 Vs30 庫 | "Vs30 資料載入成功" | INFO | 成功 |
|
| 279 |
+
| 啟動 Vs30 庫 | "Vs30 資料載入失敗: ..." | WARNING | 失敗 |
|
| 280 |
+
| 選擇測站 | "從 X 個測站中選擇了 Y 個" | INFO | 成功 |
|
| 281 |
+
| 選擇測站 | "僅找到 Y 個測站(目標 25 個)" | WARNING | Y < 25 |
|
| 282 |
+
| 提取波形 | "成功提取 N 個測站波形" | INFO | 成功 |
|
| 283 |
+
| 提取波形 | "其中 M 個測站缺少分量(已代替)" | INFO | M > 0 |
|
| 284 |
+
| 查詢 Vs30 | "從資料庫查詢到 Vs30 值" | INFO | 成功 |
|
| 285 |
+
| 查詢 Vs30 | "Vs30 查詢失敗,使用預設值" | WARNING | 失敗 |
|
| 286 |
+
| 推論 | "開始分批預測 N 個目標" | INFO | 開始 |
|
| 287 |
+
| 推論 | "預測第 X/Y 批" | INFO | 每批 |
|
| 288 |
+
| 推論 | "完成所有 N 個測站的預測" | INFO | 完成 |
|
| 289 |
+
|
spec/03-error-handling.md
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 錯誤處理策略 (03-Error-Handling)
|
| 2 |
+
|
| 3 |
+
## 概述
|
| 4 |
+
本檔案定義系統故障場景、降級策略、日誌原則與使用者訊息設計。
|
| 5 |
+
|
| 6 |
+
**核心原則**:
|
| 7 |
+
- **預裝優先**:所有關鍵資源預裝於 Space,無需運行時下載
|
| 8 |
+
- **降級不中斷**:非關鍵資料缺失時使用預設值或占位,記錄日誌;僅關鍵資源失敗時中止啟動
|
| 9 |
+
- **清晰提示**:使用者操作時若遇資料缺失,應給予清晰提示而非無聲失敗
|
| 10 |
+
|
| 11 |
+
## 一、故障場景表
|
| 12 |
+
|
| 13 |
+
### 場景 A:應用啟動階段
|
| 14 |
+
|
| 15 |
+
#### A1. Vs30 資料初始化失敗
|
| 16 |
+
|
| 17 |
+
**觸發條件**(預裝檔案損毀或格式錯誤):
|
| 18 |
+
- `Vs30ofTaiwan.nc` 損毀
|
| 19 |
+
- xarray 格式錯誤
|
| 20 |
+
|
| 21 |
+
**行為**:
|
| 22 |
+
- **不中止啟動**
|
| 23 |
+
- 設定全局 `tree = None`, `vs30_table = None`
|
| 24 |
+
- 所有後續 Vs30 查詢使用預設值 600 m/s
|
| 25 |
+
|
| 26 |
+
**日誌**:
|
| 27 |
+
```
|
| 28 |
+
logger.warning(f"Vs30 資料載入失敗: {e}")
|
| 29 |
+
logger.warning("將使用預設 Vs30 值 (600 m/s)")
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
**使用者影響**:
|
| 33 |
+
- 應用正常啟動,無明顯提示
|
| 34 |
+
- 查詢日誌時會看到 "使用預設值" 訊息
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
#### A2. 模型權重載入失敗
|
| 39 |
+
|
| 40 |
+
**觸發條件**(預裝檔案損毀或權重結構不符):
|
| 41 |
+
- `ttsam_trained_model_11.pt` 損毀
|
| 42 |
+
- 權重與模型架構不匹配
|
| 43 |
+
|
| 44 |
+
**行為**:
|
| 45 |
+
- **中止啟動**(無模型無法演示)
|
| 46 |
+
- 丟擲例外,Gradio 應用無法啟動
|
| 47 |
+
|
| 48 |
+
**日誌**:
|
| 49 |
+
```
|
| 50 |
+
logger.error(f"模型載入失敗: {e}")
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
**使用者影響**:
|
| 54 |
+
- 應用無法啟動,UI 顯示 500 error
|
| 55 |
+
- 須檢查模型檔案並重新部署
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
#### A3. 測站表 (CSV) 載入失敗
|
| 60 |
+
|
| 61 |
+
**觸發條件**:
|
| 62 |
+
- CSV 檔案損毀或格式錯誤
|
| 63 |
+
- 必填欄位缺失
|
| 64 |
+
|
| 65 |
+
**行為**:
|
| 66 |
+
- **中止啟動**(無測站無法進行推論)
|
| 67 |
+
- 丟擲例外
|
| 68 |
+
|
| 69 |
+
**日誌**:
|
| 70 |
+
```
|
| 71 |
+
logger.error(f"{site_info_file} 載入失敗: {e}")
|
| 72 |
+
# 或
|
| 73 |
+
logger.error(f"{site_info_file} 缺少必要欄位: {missing_fields}")
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
**使用者影響**:
|
| 77 |
+
- 應用無法啟動
|
| 78 |
+
|
| 79 |
+
---
|
| 80 |
+
|
| 81 |
+
### 場景 B:波形資料操作階段
|
| 82 |
+
|
| 83 |
+
#### B1. MSEED 檔案遺失或損毀
|
| 84 |
+
|
| 85 |
+
**觸發條件**(預裝檔案遺失或損毀):
|
| 86 |
+
- `waveform/YYYYMMDD.mseed` 不存在
|
| 87 |
+
- 檔案格式錯誤、無法解析
|
| 88 |
+
|
| 89 |
+
**行為**:
|
| 90 |
+
- **不中止啟動**;啟動後在使用者操作時提示
|
| 91 |
+
- 使用者點「載入波形」時,`load_waveform()` 丟擲例外
|
| 92 |
+
- `load_and_display_waveform()` 捕獲例外,返回清晰的錯誤訊息
|
| 93 |
+
|
| 94 |
+
**日誌**:
|
| 95 |
+
```
|
| 96 |
+
logger.error(f"波形載入失敗: {e}")
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
**使用者介面**:
|
| 100 |
+
```
|
| 101 |
+
info_output 顯示:
|
| 102 |
+
"❌ 錯誤:找不到波形檔案 waveform/20240403.mseed"
|
| 103 |
+
(不阻止使用者嘗試其他事件)
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
---
|
| 107 |
+
|
| 108 |
+
#### B2. 波形時間長度超出範圍
|
| 109 |
+
|
| 110 |
+
**觸發條件**:
|
| 111 |
+
- 選擇的時間窗 (start_time + duration) 超出波形實際長度
|
| 112 |
+
|
| 113 |
+
**行為**:
|
| 114 |
+
- 記錄 WARNING
|
| 115 |
+
- 截取可用的部分,或以 0 補齊至 3000 samples
|
| 116 |
+
|
| 117 |
+
**日誌**:
|
| 118 |
+
```
|
| 119 |
+
logger.warning(f"時間窗 ({start_time:.1f}–{end_time:.1f}s) 超出波形長度,將以 0 補齊")
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
**使用者影響**:
|
| 123 |
+
- 推論繼續,UI 會在摘要中提示補齊情況
|
| 124 |
+
|
| 125 |
+
---
|
| 126 |
+
|
| 127 |
+
### 場景 C:測站選擇與波形提取
|
| 128 |
+
|
| 129 |
+
#### C1. 找不到滿足條件的測站
|
| 130 |
+
|
| 131 |
+
**觸發條件**:
|
| 132 |
+
- site_info 中所有測站在 MSEED 中都無對應
|
| 133 |
+
- MSEED 檔案為空或僅有部分測站
|
| 134 |
+
|
| 135 |
+
**行為**:
|
| 136 |
+
- 若找到 ≥ 1 個測站:繼續推論,padding 至 25 站,UI 明示警告
|
| 137 |
+
- 若 0 個測站:返回錯誤提示,不中止應用(使用者可選其他事件)
|
| 138 |
+
|
| 139 |
+
**日誌**:
|
| 140 |
+
```
|
| 141 |
+
if len(selected_stations) == 0:
|
| 142 |
+
logger.error("未找到任何可用測站")
|
| 143 |
+
elif len(selected_stations) < 25:
|
| 144 |
+
logger.warning(f"僅找到 {len(selected_stations)}/25 個測站,將補零至 25")
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
**使用者介面**:
|
| 148 |
+
```
|
| 149 |
+
info_output 顯示:
|
| 150 |
+
- 若 0 站:「❌ 錯誤:找不到有效的測站資料」
|
| 151 |
+
- 若 < 25 站:「⚠️ 僅選中 8 個測站 (目標 25 個)」
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
---
|
| 155 |
+
|
| 156 |
+
#### C2. 可用測站 < 25 個
|
| 157 |
+
|
| 158 |
+
**觸發條件**:
|
| 159 |
+
- MSEED 中可對應的測站少於 25 個
|
| 160 |
+
|
| 161 |
+
**行為**:
|
| 162 |
+
- **不中止**,允許繼續推論
|
| 163 |
+
- Padding 至 25 個(多餘行用 0 填充)
|
| 164 |
+
- 記錄 WARNING,UI 明示
|
| 165 |
+
|
| 166 |
+
**日誌**:
|
| 167 |
+
```
|
| 168 |
+
logger.warning(f"僅找到 {actual_count} 個可用測站(目標 25 個),將以 0 補齊")
|
| 169 |
+
```
|
| 170 |
+
|
| 171 |
+
**使用者介面**:
|
| 172 |
+
```
|
| 173 |
+
info_output 顯示:「⚠️ 僅選中 15 個測站 (目標 25 個)」
|
| 174 |
+
stats_output 顯示:「使用測站數: 15 / 25」
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
---
|
| 178 |
+
|
| 179 |
+
#### C3. 缺少波形分量
|
| 180 |
+
|
| 181 |
+
**觸發條件**:
|
| 182 |
+
- N 分量缺失(無 "N" 或 "1" 通道)
|
| 183 |
+
- E 分量缺失(無 "E" 或 "2" 通道)
|
| 184 |
+
- Z 分量缺失(該測站完全不可用)
|
| 185 |
+
|
| 186 |
+
**行為**:
|
| 187 |
+
- **N/E 缺失**:以 Z 分量代替,統計計數,繼續使用
|
| 188 |
+
- **Z 缺失**:跳過該測站(不可恢復)
|
| 189 |
+
|
| 190 |
+
**日誌**:
|
| 191 |
+
```
|
| 192 |
+
# N/E 缺失時(可恢復)
|
| 193 |
+
logger.debug(f"測站 {station_code} 缺少 N 分量,以 Z 分量代替")
|
| 194 |
+
|
| 195 |
+
# 最後統計
|
| 196 |
+
logger.info(f"缺少 N/E 分量的測站: {missing_components_count} 個(已以 Z 代替)")
|
| 197 |
+
|
| 198 |
+
# Z 缺失時(不可恢復)
|
| 199 |
+
logger.warning(f"測站 {station_code} 缺少 Z 分量,跳過")
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
**使用者介面**:
|
| 203 |
+
```
|
| 204 |
+
stats_output 顯示(若有缺分量):
|
| 205 |
+
「⚠️ 缺少 N/E 分量測站數: 3 (已以 Z 分量代替)」
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
---
|
| 209 |
+
|
| 210 |
+
### 場景 D:Vs30 查詢與場址參數
|
| 211 |
+
|
| 212 |
+
#### D1. Vs30 查詢失敗
|
| 213 |
+
|
| 214 |
+
**觸發條件**:
|
| 215 |
+
- Vs30 資料庫未初始化(A1 發生)
|
| 216 |
+
- (lat, lon) 超出資料邊界
|
| 217 |
+
- 查詢超時
|
| 218 |
+
|
| 219 |
+
**行為**:
|
| 220 |
+
- **不中止**,使用預設值 600 m/s
|
| 221 |
+
- 記錄 WARNING / INFO
|
| 222 |
+
|
| 223 |
+
**日誌**:
|
| 224 |
+
```
|
| 225 |
+
logger.info(f"Vs30 資料庫未初始化,使用預設值 (600 m/s)")
|
| 226 |
+
# 或
|
| 227 |
+
logger.warning(f"Vs30 查詢失敗 ({lat}, {lon}): {e},使用預設值 600 m/s")
|
| 228 |
+
```
|
| 229 |
+
|
| 230 |
+
**使用者影響**:
|
| 231 |
+
- 無直接提示;推論繼續進行
|
| 232 |
+
|
| 233 |
+
---
|
| 234 |
+
|
| 235 |
+
#### D2. 空間座標超出範圍
|
| 236 |
+
|
| 237 |
+
**觸發條件**:
|
| 238 |
+
- epicenter_lat < 5 或 > 30
|
| 239 |
+
- epicenter_lon < 110 或 > 123
|
| 240 |
+
|
| 241 |
+
**行為**:
|
| 242 |
+
- **記錄 WARNING**,允許繼續(可能只是邊界點)
|
| 243 |
+
- Vs30 查詢時可能回傳預設值
|
| 244 |
+
|
| 245 |
+
**日誌**:
|
| 246 |
+
```
|
| 247 |
+
logger.warning(f"震央位置 ({epicenter_lat}, {epicenter_lon}) 超出預期範圍")
|
| 248 |
+
```
|
| 249 |
+
|
| 250 |
+
**使用者影響**:
|
| 251 |
+
- 推論繼續,但結果準確性可能降低
|
| 252 |
+
|
| 253 |
+
---
|
| 254 |
+
|
| 255 |
+
### 場景 E:推論與後處理
|
| 256 |
+
|
| 257 |
+
#### E1. 推論過程失敗(OOM、計算錯誤)
|
| 258 |
+
|
| 259 |
+
**觸發條件**:
|
| 260 |
+
- GPU 記憶體不足
|
| 261 |
+
- Tensor 形狀不匹配
|
| 262 |
+
- 數值錯誤(NaN、溢位)
|
| 263 |
+
|
| 264 |
+
**行為**:
|
| 265 |
+
- **中止該批次的推論**
|
| 266 |
+
- `predict_intensity()` 捕獲例外,返回錯誤訊息
|
| 267 |
+
|
| 268 |
+
**日誌**:
|
| 269 |
+
```
|
| 270 |
+
logger.error(f"預測過程發生錯誤: {e}")
|
| 271 |
+
import traceback
|
| 272 |
+
traceback.print_exc()
|
| 273 |
+
```
|
| 274 |
+
|
| 275 |
+
**使用者介面**:
|
| 276 |
+
```
|
| 277 |
+
predicted_intensity_map: None
|
| 278 |
+
stats_output 顯示:
|
| 279 |
+
"錯誤: [RuntimeError] CUDA out of memory"
|
| 280 |
+
```
|
| 281 |
+
|
| 282 |
+
---
|
| 283 |
+
|
| 284 |
+
#### E2. 實際震度圖缺失
|
| 285 |
+
|
| 286 |
+
**觸發條件**:
|
| 287 |
+
- `intensity_map/YYYYMMDD.png` 不存在
|
| 288 |
+
|
| 289 |
+
**行為**:
|
| 290 |
+
- **不中止**,顯示空白占位
|
| 291 |
+
- 記錄 WARNING
|
| 292 |
+
|
| 293 |
+
**日誌**:
|
| 294 |
+
```
|
| 295 |
+
logger.warning(f"找不到實際震度圖: {event_date}(將顯示空白占位)")
|
| 296 |
+
```
|
| 297 |
+
|
| 298 |
+
**使用者介面**:
|
| 299 |
+
```
|
| 300 |
+
observed_intensity_image 顯示空白(高度 800px)
|
| 301 |
+
無文字提示(由 UI component 負責)
|
| 302 |
+
```
|
| 303 |
+
|
| 304 |
+
---
|
| 305 |
+
|
| 306 |
+
#### E3. 預測值異常 (NaN、Inf、超出合理範圍)
|
| 307 |
+
|
| 308 |
+
**觸發條件**:
|
| 309 |
+
- PGA 值為 NaN 或 Inf
|
| 310 |
+
- PGA 值異常大(> 10 m/s²)或異常小(< 0)
|
| 311 |
+
|
| 312 |
+
**行為**:
|
| 313 |
+
- **記錄 WARNING** 或 **ERROR**,繼續使用該值
|
| 314 |
+
- 地圖顯示時可能出現異常顏色或缺失標記
|
| 315 |
+
|
| 316 |
+
**日誌**:
|
| 317 |
+
```
|
| 318 |
+
logger.warning(f"預測 PGA 值異常: {pga}(目標點 {target_name})")
|
| 319 |
+
```
|
| 320 |
+
|
| 321 |
+
**使用者影響**:
|
| 322 |
+
- 異常點在地圖上可能顯示為白色(無效值)或其他視覺異常
|
| 323 |
+
|
| 324 |
+
---
|
| 325 |
+
|
| 326 |
+
## 二、UI 訊息設計
|
| 327 |
+
|
| 328 |
+
### 2.1 成功訊息
|
| 329 |
+
|
| 330 |
+
**`info_output` (波形載入後)**:
|
| 331 |
+
```
|
| 332 |
+
✅ 已載入波形資料
|
| 333 |
+
開始時間: 0.0 秒
|
| 334 |
+
時間長度: 30.0 秒 (0.0 - 30.0)
|
| 335 |
+
震央位置: (121.5700, 23.8800)
|
| 336 |
+
選擇了 25 個最近的測站
|
| 337 |
+
請確認波形範圍後,點擊「執行預測」按鈕
|
| 338 |
+
```
|
| 339 |
+
|
| 340 |
+
**`stats_output` (推論完成後)**:
|
| 341 |
+
```
|
| 342 |
+
✅ 預測完成!
|
| 343 |
+
開始時間: 0.0 秒
|
| 344 |
+
時間長度: 30.0 秒 (0.0 - 30.0)
|
| 345 |
+
震央位置: (121.5700, 23.8800)
|
| 346 |
+
使用測站數: 25 / 25
|
| 347 |
+
預測目標點數: 50
|
| 348 |
+
預測最大震度: 6-
|
| 349 |
+
```
|
| 350 |
+
|
| 351 |
+
---
|
| 352 |
+
|
| 353 |
+
### 2.2 警告訊息
|
| 354 |
+
|
| 355 |
+
**少於 25 站**:
|
| 356 |
+
```
|
| 357 |
+
✅ 已載入波形資料
|
| 358 |
+
...
|
| 359 |
+
選擇了 15 個最近的測站 ⚠️(目標 25 個,實際 15 個)
|
| 360 |
+
```
|
| 361 |
+
|
| 362 |
+
**缺分量**:
|
| 363 |
+
```
|
| 364 |
+
✅ 預測完成!
|
| 365 |
+
...
|
| 366 |
+
⚠️ 缺少 N/E 分量測站數: 3 (已以 Z 分量代替)
|
| 367 |
+
```
|
| 368 |
+
|
| 369 |
+
---
|
| 370 |
+
|
| 371 |
+
### 2.3 錯誤訊息
|
| 372 |
+
|
| 373 |
+
**MSEED 不存在**:
|
| 374 |
+
```
|
| 375 |
+
❌ 錯誤:找不到波形檔案 waveform/20240403.mseed
|
| 376 |
+
```
|
| 377 |
+
|
| 378 |
+
**無可用測站**:
|
| 379 |
+
```
|
| 380 |
+
❌ 錯誤:找不到有效的測站資料
|
| 381 |
+
```
|
| 382 |
+
|
| 383 |
+
**推論失敗**:
|
| 384 |
+
```
|
| 385 |
+
❌ 錯誤: [RuntimeError] CUDA out of memory
|
| 386 |
+
```
|
| 387 |
+
|
| 388 |
+
---
|
| 389 |
+
|
| 390 |
+
## 三、日誌等級與關鍵字速查
|
| 391 |
+
|
| 392 |
+
| 等級 | 關鍵字 | 行為 | 例子 |
|
| 393 |
+
|-----|--------|------|------|
|
| 394 |
+
| **INFO** | 啟動、完成、進度 | 記錄主流程 | "Vs30 資料載入完成" |
|
| 395 |
+
| **WARNING** | 降級、不足、缺失 | 記錄決策改變 | "缺少 N 分量,以 Z 代替" |
|
| 396 |
+
| **ERROR** | 失敗、異常、中止 | 記錄單點或致命失敗 | "測站資訊查詢失敗" |
|
| 397 |
+
|
| 398 |
+
---
|
| 399 |
+
|
| 400 |
+
## 四、可恢復 vs 致命故障
|
| 401 |
+
|
| 402 |
+
### 可恢復故障(不中止)
|
| 403 |
+
- Vs30 查詢失敗 → 預設值
|
| 404 |
+
- 缺分量 → 以 Z 替代
|
| 405 |
+
- 測站 < 25 → Padding + WARNING
|
| 406 |
+
- 實際圖缺失 → 空白占位
|
| 407 |
+
- PGA 異常值 → 記錄 + 繼續顯示
|
| 408 |
+
|
| 409 |
+
### 致命故障(中止)
|
| 410 |
+
- 模型載入失敗 → 應用無法啟動
|
| 411 |
+
- 測站表缺失 → 應用無法啟動
|
| 412 |
+
- MSEED 檔案缺失 → 該事件無法推論
|
| 413 |
+
- OOM / Tensor 形狀錯誤 → 該批推論失敗
|
| 414 |
+
- 必填欄位缺失 → 應用無法啟動
|
| 415 |
+
|
| 416 |
+
|
spec/04-extensions.md
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 擴充空間與向後相容 (04-Extensions)
|
| 2 |
+
|
| 3 |
+
## 概述
|
| 4 |
+
本檔案定義新增功能(新資料來源、新欄位、新模型等)時的擴充點與向後相容性策略。
|
| 5 |
+
|
| 6 |
+
## 一、數據來源擴充
|
| 7 |
+
|
| 8 |
+
### 1.1 新增波形資料來源
|
| 9 |
+
|
| 10 |
+
**當前方案**:本地 MSEED 檔案(`waveform/YYYYMMDD.mseed`)
|
| 11 |
+
|
| 12 |
+
**擴充點**:
|
| 13 |
+
```python
|
| 14 |
+
class WaveformSource(ABC):
|
| 15 |
+
@abstractmethod
|
| 16 |
+
def load(self, event_id: str) -> obspy.Stream:
|
| 17 |
+
"""載入指定事件的完整波形"""
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
class LocalMSEEDSource(WaveformSource):
|
| 21 |
+
def __init__(self, directory="waveform"):
|
| 22 |
+
self.directory = directory
|
| 23 |
+
|
| 24 |
+
def load(self, event_id: str) -> obspy.Stream:
|
| 25 |
+
filepath = f"{self.directory}/{event_id}.mseed"
|
| 26 |
+
return obspy.read(filepath)
|
| 27 |
+
|
| 28 |
+
class RemoteSource(WaveformSource):
|
| 29 |
+
"""未來支援:FDSN WebService、IRIS DMC 等"""
|
| 30 |
+
def load(self, event_id: str) -> obspy.Stream:
|
| 31 |
+
# 實作遠端查詢邏輯
|
| 32 |
+
pass
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
**遷移步驟**:
|
| 36 |
+
1. 在 `app.py` 中定義 `WaveformSource` 抽象基類
|
| 37 |
+
2. 實現 `LocalMSEEDSource` 並替換原有 `load_waveform()` 呼叫
|
| 38 |
+
3. 新增 `RemoteSource` 實現(FDSN、DMC 等)
|
| 39 |
+
4. 在初始化時選擇數據來源
|
| 40 |
+
|
| 41 |
+
**向後相容性**:
|
| 42 |
+
- 現有 MSEED 檔案結構不變
|
| 43 |
+
- 新舊數據來源需透過工廠函式(Factory Pattern)選擇
|
| 44 |
+
- spec 更新:`04-extensions.md` → 新增 "Waveform Source" 小節
|
| 45 |
+
|
| 46 |
+
---
|
| 47 |
+
|
| 48 |
+
### 1.2 新增測站資料來源
|
| 49 |
+
|
| 50 |
+
**當前方案**:本地 CSV 檔案(`site_info.csv`, `eew_target.csv`)
|
| 51 |
+
|
| 52 |
+
**擴充點**:
|
| 53 |
+
```python
|
| 54 |
+
class StationSource(ABC):
|
| 55 |
+
@abstractmethod
|
| 56 |
+
def load_input_stations(self) -> pd.DataFrame:
|
| 57 |
+
"""載入輸入測站表"""
|
| 58 |
+
pass
|
| 59 |
+
|
| 60 |
+
@abstractmethod
|
| 61 |
+
def load_target_stations(self) -> pd.DataFrame:
|
| 62 |
+
"""載入目標測站表"""
|
| 63 |
+
pass
|
| 64 |
+
|
| 65 |
+
class LocalCSVStationSource(StationSource):
|
| 66 |
+
def __init__(self, input_csv="station/site_info.csv",
|
| 67 |
+
target_csv="station/eew_target.csv"):
|
| 68 |
+
self.input_csv = input_csv
|
| 69 |
+
self.target_csv = target_csv
|
| 70 |
+
|
| 71 |
+
def load_input_stations(self) -> pd.DataFrame:
|
| 72 |
+
return pd.read_csv(self.input_csv)
|
| 73 |
+
|
| 74 |
+
def load_target_stations(self) -> pd.DataFrame:
|
| 75 |
+
return pd.read_csv(self.target_csv)
|
| 76 |
+
|
| 77 |
+
class DatabaseStationSource(StationSource):
|
| 78 |
+
"""未來支援:從資料庫(PostgreSQL、MongoDB 等)讀取"""
|
| 79 |
+
pass
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
**遷移步驟**:
|
| 83 |
+
1. 定義 `StationSource` 抽象基類
|
| 84 |
+
2. 實現 `LocalCSVStationSource`
|
| 85 |
+
3. 在初始化時使用 `LocalCSVStationSource()`
|
| 86 |
+
4. 新增其他來源實現(資料庫等)
|
| 87 |
+
|
| 88 |
+
**必填欄位保持不變**:
|
| 89 |
+
- `site_info`: Station, Latitude, Longitude, Elevation
|
| 90 |
+
- `eew_target`: network, county, station, station_zh, longitude, latitude, elevation
|
| 91 |
+
|
| 92 |
+
---
|
| 93 |
+
|
| 94 |
+
### 1.3 新增外部參數來源(Vs30、site effects 等)
|
| 95 |
+
|
| 96 |
+
**當前方案**:Hugging Face (`SeisBlue/TaiwanVs30`) + 本地預設值 600
|
| 97 |
+
|
| 98 |
+
**擴充點**:
|
| 99 |
+
```python
|
| 100 |
+
class SiteParameterSource(ABC):
|
| 101 |
+
@abstractmethod
|
| 102 |
+
def query_vs30(self, lat: float, lon: float) -> float:
|
| 103 |
+
"""查詢 Vs30"""
|
| 104 |
+
pass
|
| 105 |
+
|
| 106 |
+
@abstractmethod
|
| 107 |
+
def query_site_class(self, lat: float, lon: float) -> str:
|
| 108 |
+
"""查詢場址分類(未來功能)"""
|
| 109 |
+
pass
|
| 110 |
+
|
| 111 |
+
class HFVs30Source(SiteParameterSource):
|
| 112 |
+
def __init__(self, default_vs30=600):
|
| 113 |
+
self.tree = None
|
| 114 |
+
self.vs30_table = None
|
| 115 |
+
self.default_vs30 = default_vs30
|
| 116 |
+
self.initialize()
|
| 117 |
+
|
| 118 |
+
def initialize(self):
|
| 119 |
+
# 當前邏輯
|
| 120 |
+
pass
|
| 121 |
+
|
| 122 |
+
def query_vs30(self, lat: float, lon: float) -> float:
|
| 123 |
+
# 當前邏輯
|
| 124 |
+
pass
|
| 125 |
+
|
| 126 |
+
def query_site_class(self, lat: float, lon: float) -> str:
|
| 127 |
+
"""未實作,回傳預設值"""
|
| 128 |
+
return "C"
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
**向後相容性**:
|
| 132 |
+
- Vs30 預設值保持 600 m/s
|
| 133 |
+
- 新增參數(site class、土壤類型等)需同步更新:
|
| 134 |
+
- `01-data-contract.md` → station_info_padded 欄位數調整
|
| 135 |
+
- 模型輸入形狀(若新增欄位,需重訓練或調整 input layer)
|
| 136 |
+
|
| 137 |
+
---
|
| 138 |
+
|
| 139 |
+
## 二、資料格式擴充
|
| 140 |
+
|
| 141 |
+
### 2.1 新增波形分量
|
| 142 |
+
|
| 143 |
+
**當前分量**:Z (豎向), N (南北), E (東西)
|
| 144 |
+
|
| 145 |
+
**未來擴充**:
|
| 146 |
+
- Radial (R)、Transverse (T) 分量
|
| 147 |
+
- 速度波形(V)、位移波形(D)等
|
| 148 |
+
|
| 149 |
+
**遷移步驟**:
|
| 150 |
+
1. 擴充 `extract_waveforms_from_stream()` 中的分量對應
|
| 151 |
+
2. 調整 waveform_3c 形狀從 (3000, 3) 至 (3000, N_components)
|
| 152 |
+
3. 更新模型 CNN 的輸入通道數(需重訓練)
|
| 153 |
+
|
| 154 |
+
**必做**:
|
| 155 |
+
- 更新 `01-data-contract.md` → 新增分量對應表
|
| 156 |
+
- 更新 `02-processing-rules.md` → 新增分量缺失規則
|
| 157 |
+
|
| 158 |
+
---
|
| 159 |
+
|
| 160 |
+
### 2.2 新增輸入特徵
|
| 161 |
+
|
| 162 |
+
**當前特徵**:
|
| 163 |
+
- 波形 (3000, 3)
|
| 164 |
+
- 測站位置 (lat, lon, elev, vs30)
|
| 165 |
+
- 目標位置 (lat, lon, elev, vs30)
|
| 166 |
+
|
| 167 |
+
**未來擴充**:
|
| 168 |
+
- 震源深度、震度規模(M)
|
| 169 |
+
- 頻率內容(PSD、傅立葉頻譜等)
|
| 170 |
+
- 歷史背景應力等
|
| 171 |
+
|
| 172 |
+
**遷移步驟**:
|
| 173 |
+
1. 擴充 tensor_data 字典新增鍵值對
|
| 174 |
+
2. 調整模型結構(Position Embedding、MLP 輸入維度等)
|
| 175 |
+
3. **需重訓練模型**
|
| 176 |
+
|
| 177 |
+
**必做**:
|
| 178 |
+
- 更新 `01-data-contract.md` → 模型輸入形狀
|
| 179 |
+
- 更新 `02-processing-rules.md` → 新特徵計算規則
|
| 180 |
+
|
| 181 |
+
---
|
| 182 |
+
|
| 183 |
+
## 三、模型與推論擴充
|
| 184 |
+
|
| 185 |
+
### 3.1 新增推論模型
|
| 186 |
+
|
| 187 |
+
**當前模型**:FullModel (CNN + Position Embedding + Transformer + MLP + MDN)
|
| 188 |
+
|
| 189 |
+
**擴充方式**(Strategy Pattern):
|
| 190 |
+
```python
|
| 191 |
+
class PredictionModel(ABC):
|
| 192 |
+
@abstractmethod
|
| 193 |
+
def predict(self, tensor_data: dict) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 194 |
+
"""
|
| 195 |
+
輸出 (weight, sigma, mu)
|
| 196 |
+
"""
|
| 197 |
+
pass
|
| 198 |
+
|
| 199 |
+
class TransformerMDNModel(PredictionModel):
|
| 200 |
+
def __init__(self, model_path):
|
| 201 |
+
self.model = get_full_model(model_path)
|
| 202 |
+
|
| 203 |
+
def predict(self, tensor_data: dict) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 204 |
+
with torch.no_grad():
|
| 205 |
+
return self.model(tensor_data)
|
| 206 |
+
|
| 207 |
+
class AlternativeModel(PredictionModel):
|
| 208 |
+
"""未來:LSTM、GRU、Attention-only 等架構"""
|
| 209 |
+
pass
|
| 210 |
+
|
| 211 |
+
# Factory
|
| 212 |
+
def get_model_factory(model_type: str = "transformer_mdn") -> PredictionModel:
|
| 213 |
+
if model_type == "transformer_mdn":
|
| 214 |
+
return TransformerMDNModel(model_path)
|
| 215 |
+
elif model_type == "alternative":
|
| 216 |
+
return AlternativeModel(...)
|
| 217 |
+
else:
|
| 218 |
+
raise ValueError(f"Unknown model type: {model_type}")
|
| 219 |
+
```
|
| 220 |
+
|
| 221 |
+
**向後相容性**:
|
| 222 |
+
- 所有模型必須遵守輸入/輸出形狀約定(見 `01-data-contract.md`)
|
| 223 |
+
- 新模型需提供相同的推論介面
|
| 224 |
+
|
| 225 |
+
---
|
| 226 |
+
|
| 227 |
+
### 3.2 新增輸出後處理模式
|
| 228 |
+
|
| 229 |
+
**當前方式**:MDN 加權平均 (Σ weight × μ)
|
| 230 |
+
|
| 231 |
+
**擴充方式**:
|
| 232 |
+
```python
|
| 233 |
+
class PGAEstimator(ABC):
|
| 234 |
+
@abstractmethod
|
| 235 |
+
def estimate(self, weight: np.ndarray, sigma: np.ndarray, mu: np.ndarray) -> np.ndarray:
|
| 236 |
+
"""
|
| 237 |
+
從 MDN 參數估計 PGA
|
| 238 |
+
"""
|
| 239 |
+
pass
|
| 240 |
+
|
| 241 |
+
class WeightedMeanEstimator(PGAEstimator):
|
| 242 |
+
def estimate(self, weight, sigma, mu):
|
| 243 |
+
return np.sum(weight * mu, axis=2) # 當前方式
|
| 244 |
+
|
| 245 |
+
class MedianEstimator(PGAEstimator):
|
| 246 |
+
def estimate(self, weight, sigma, mu):
|
| 247 |
+
# 未來:使用中位數或分位數
|
| 248 |
+
pass
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
---
|
| 252 |
+
|
| 253 |
+
## 四、批次策略擴充
|
| 254 |
+
|
| 255 |
+
### 4.1 動態批次大小調整
|
| 256 |
+
|
| 257 |
+
**當前方式**:固定 25 測站、25 目標點 / 批
|
| 258 |
+
|
| 259 |
+
**擴充方式**:
|
| 260 |
+
```python
|
| 261 |
+
class BatchConfig:
|
| 262 |
+
def __init__(self, n_stations=25, n_targets=25, device="cpu"):
|
| 263 |
+
self.n_stations = n_stations
|
| 264 |
+
self.n_targets = n_targets
|
| 265 |
+
self.device = device
|
| 266 |
+
|
| 267 |
+
@staticmethod
|
| 268 |
+
def auto_tune(available_memory_mb: float) -> "BatchConfig":
|
| 269 |
+
"""根據可用記憶體自動調整批次大小"""
|
| 270 |
+
if available_memory_mb < 2000:
|
| 271 |
+
return BatchConfig(n_stations=25, n_targets=10)
|
| 272 |
+
elif available_memory_mb < 8000:
|
| 273 |
+
return BatchConfig(n_stations=25, n_targets=25)
|
| 274 |
+
else:
|
| 275 |
+
return BatchConfig(n_stations=25, n_targets=50)
|
| 276 |
+
```
|
| 277 |
+
|
| 278 |
+
---
|
| 279 |
+
|
| 280 |
+
## 五、UI 擴充
|
| 281 |
+
|
| 282 |
+
### 5.1 新增推論模式(不影響 spec 契約)
|
| 283 |
+
|
| 284 |
+
**當前模式**:互動式 GUI(Gradio)
|
| 285 |
+
|
| 286 |
+
**未來擴充**:
|
| 287 |
+
- 批次命令列介面 (CLI)
|
| 288 |
+
- 後臺排隊系統 (Celery + Redis)
|
| 289 |
+
- 即時串流模式
|
| 290 |
+
|
| 291 |
+
**關鍵**:所有模式共享相同的核心邏輯,data flow 不變
|
| 292 |
+
|
| 293 |
+
---
|
| 294 |
+
|
| 295 |
+
### 5.2 新增可視化輸出格式
|
| 296 |
+
|
| 297 |
+
**當前格式**:Folium 地圖 (HTML) + 波形圖 (matplotlib)
|
| 298 |
+
|
| 299 |
+
**未來擴充**:
|
| 300 |
+
- 3D 地形圖 (Plotly 3D)
|
| 301 |
+
- 時間動畫(逐秒動態渲染)
|
| 302 |
+
- GeoJSON 匯出
|
| 303 |
+
|
| 304 |
+
---
|
| 305 |
+
|
| 306 |
+
## 六. 新功能檢查清單
|
| 307 |
+
|
| 308 |
+
每次新增功能時,必須檢查:
|
| 309 |
+
|
| 310 |
+
### 資料層面
|
| 311 |
+
- [ ] 新增欄位是否需更新 CSV 結構?若是,更新 `01-data-contract.md`
|
| 312 |
+
- [ ] 新增欄位是否破壞向後相容性?若是,計畫遷移策略
|
| 313 |
+
- [ ] 新資料來源是否需異常處理?若是,更新 `03-error-handling.md`
|
| 314 |
+
|
| 315 |
+
### 模型層面
|
| 316 |
+
- [ ] 新增輸入特徵是否改變 tensor shape?若是,檢查模型相容性
|
| 317 |
+
- [ ] 是否需重訓練模型?
|
| 318 |
+
- [ ] 新模型的輸出形狀是否與現有契約相同?
|
| 319 |
+
|
| 320 |
+
### 處理層面
|
| 321 |
+
- [ ] 新增參數是否涉及批次調整?若是,更新 `02-processing-rules.md`
|
| 322 |
+
- [ ] 新增邏輯是否需新的日誌點?若是,更新 `03-error-handling.md`
|
| 323 |
+
- [ ] 新增邏輯是否有失敗情景?若是,定義降級策略
|
| 324 |
+
|
| 325 |
+
### 規格更新
|
| 326 |
+
- [ ] 是否需更新 `00-overview.md` 不變條件?
|
| 327 |
+
- [ ] 是否需更新 `01-data-contract.md` I/O shape?
|
| 328 |
+
- [ ] 是否需更新 `02-processing-rules.md` 處理規則?
|
| 329 |
+
- [ ] 是否需更新 `03-error-handling.md` 故障場景?
|
| 330 |
+
- [ ] 本次變更是否記錄至 `changelog.md`?
|
| 331 |
+
|
| 332 |
+
---
|
| 333 |
+
|
| 334 |
+
## 七. 版本相容性表
|
| 335 |
+
|
| 336 |
+
| 版本 | 變更 | 相容性 |
|
| 337 |
+
|-----|------|--------|
|
| 338 |
+
| v1.0 | 初版(當前) | baseline |
|
| 339 |
+
| v1.1 (未來) | 新增 Radial/Transverse 分量 | 需遷移 waveform shape |
|
| 340 |
+
| v1.2 (未來) | 支援遠端 FDSN 波形 | 向後相容(工廠模式) |
|
| 341 |
+
| v2.0 (未來) | 新模型(重訓練) | 破壞相容,需更新 spec |
|
| 342 |
+
|
| 343 |
+
---
|
| 344 |
+
|
| 345 |
+
## 八. 決策樹:何時更新 Spec?
|
| 346 |
+
|
| 347 |
+
```
|
| 348 |
+
新增或改動功能
|
| 349 |
+
├─ 僅改動程式邏輯(無資料契約變更)?
|
| 350 |
+
│ └─ 不必更新 spec(但記錄至 changelog)
|
| 351 |
+
├─ 改動資料結構或 I/O shape?
|
| 352 |
+
│ └─ 更新 01-data-contract.md
|
| 353 |
+
├─ 改動處理規則或限制?
|
| 354 |
+
│ └─ 更新 02-processing-rules.md
|
| 355 |
+
├─ 新增故障場景?
|
| 356 |
+
│ └─ 更新 03-error-handling.md
|
| 357 |
+
└─ 改動核心不變條件?
|
| 358 |
+
└─ 更新 00-overview.md 快速參考表
|
| 359 |
+
```
|
| 360 |
+
|
spec/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 規格快速導航 (Quick Reference)
|
| 2 |
+
|
| 3 |
+
> 根據不同需求快速定位相應規格檔案。對應 GitHub Copilot 指南中的查詢策略。
|
| 4 |
+
|
| 5 |
+
## 問題類型 → 規格檔案對應表
|
| 6 |
+
|
| 7 |
+
### 資料與契約相關
|
| 8 |
+
|
| 9 |
+
| 問題 | 查閱檔案 | 關鍵章節 |
|
| 10 |
+
|-----|--------|--------|
|
| 11 |
+
| 「模型輸入形狀是什麼?」 | `01-data-contract.md` | 3.1 模型輸入 |
|
| 12 |
+
| 「site_info.csv 必填欄位有哪些?」 | `01-data-contract.md` | 1.2 輸入測站表 |
|
| 13 |
+
| 「缺少 N/E 分量時怎麼辦?」 | `01-data-contract.md` | 1.1 MSEED 波形檔案 |
|
| 14 |
+
| 「波形應該怎樣補零?」 | `02-processing-rules.md` | 3.2 波形補零與對齊 |
|
| 15 |
+
| 「目標測站表的必填欄位?」 | `01-data-contract.md` | 1.3 目標測站表 |
|
| 16 |
+
|
| 17 |
+
### 處理規則與限制相關
|
| 18 |
+
|
| 19 |
+
| 問題 | 查閱檔案 | 關鍵章節 |
|
| 20 |
+
|-----|--------|--------|
|
| 21 |
+
| 「最多幾個輸入測站?」 | `02-processing-rules.md` | 2.2 測站選擇規則 |
|
| 22 |
+
| 「時間窗長度的限制?」 | `02-processing-rules.md` | 2.1 時間參數 |
|
| 23 |
+
| 「Vs30 查詢失敗怎麼降級?」 | `02-processing-rules.md` | 5 Vs30 查詢規則 |
|
| 24 |
+
| 「目標批次大小多少?」 | `02-processing-rules.md` | 1.2 目標批次 |
|
| 25 |
+
| 「訊號處理流程?」 | `02-processing-rules.md` | 3.1 訊號處理 |
|
| 26 |
+
|
| 27 |
+
### 錯誤與故障相關
|
| 28 |
+
|
| 29 |
+
| 問題 | 查閱檔案 | 關鍵章節 |
|
| 30 |
+
|-----|--------|--------|
|
| 31 |
+
| 「Vs30 下載失敗會怎樣?」 | `03-error-handling.md` | A1 Vs30 資料下載失敗 |
|
| 32 |
+
| 「模型載入失敗怎麼處理?」 | `03-error-handling.md` | A2 模型權重載入失敗 |
|
| 33 |
+
| 「測站 < 25 個怎麼辦?」 | `03-error-handling.md` | C2 可用測站 < 25 個 |
|
| 34 |
+
| 「缺少 N 或 E 分量的 UI 訊息?」 | `03-error-handling.md` | 3.2 警告訊息 |
|
| 35 |
+
| 「波形時間超出範圍?」 | `03-error-handling.md` | B2 波形時間長度超出範圍 |
|
| 36 |
+
|
| 37 |
+
### 擴充與功能相關
|
| 38 |
+
|
| 39 |
+
| 問題 | 查閱檔案 | 關鍵章節 |
|
| 40 |
+
|-----|--------|--------|
|
| 41 |
+
| 「怎樣新增遠端波形來源?」 | `04-extensions.md` | 1.1 新增波形資料來源 |
|
| 42 |
+
| 「如何新增測站資料來源?」 | `04-extensions.md` | 1.2 新增測站資料來源 |
|
| 43 |
+
| 「新增功能時要檢查什麼?」 | `04-extensions.md` | 6 新功能檢查清單 |
|
| 44 |
+
| 「何時需要更新規格?」 | `04-extensions.md` | 8 決策樹 |
|
| 45 |
+
|
| 46 |
+
### 專案概覽相關
|
| 47 |
+
|
| 48 |
+
| 問題 | 查閱檔案 | 關鍵章節 |
|
| 49 |
+
|-----|--------|--------|
|
| 50 |
+
| 「系統架構是什麼?」 | `00-overview.md` | 系統架構 |
|
| 51 |
+
| 「核心不變條件有哪些?」 | `00-overview.md` | 核心不變條件 |
|
| 52 |
+
| 「各模組的職責?」 | `00-overview.md` | 核心模組清單 |
|
| 53 |
+
| 「快速查詢表(取樣率、補零等)?」 | `00-overview.md` | 快速參考表 |
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## 迭代開發工作流
|
| 58 |
+
|
| 59 |
+
### 當需求改變時(`/project.spec`)
|
| 60 |
+
|
| 61 |
+
1. **評估需求**:是什麼類型的改變?
|
| 62 |
+
- 資料結構變更 → 查 `01-data-contract.md`
|
| 63 |
+
- 處理流程改變 → 查 `02-processing-rules.md`
|
| 64 |
+
- 新故障場景 → 查 `03-error-handling.md`
|
| 65 |
+
- 新功能擴展 → 查 `04-extensions.md`
|
| 66 |
+
|
| 67 |
+
2. **掃描現有代碼**:該部分在 `app.py` 的哪裡?
|
| 68 |
+
|
| 69 |
+
3. **更新相應 spec**:修改上述定位到的規格檔案
|
| 70 |
+
|
| 71 |
+
4. **建立 `spec/plan.md`**:定義本次迭代的任務清單
|
| 72 |
+
|
| 73 |
+
---
|
| 74 |
+
|
| 75 |
+
### 執行迭代時(`/project.plan`)
|
| 76 |
+
|
| 77 |
+
1. **根據 `plan.md`** 拆解任務
|
| 78 |
+
|
| 79 |
+
2. **實作每個任務**:
|
| 80 |
+
- 檢查相應 spec 的約定(例如 I/O shape)
|
| 81 |
+
- 在代碼註解中引用 spec(例如 "spec #2:補零邏輯")
|
| 82 |
+
- 運行測試
|
| 83 |
+
|
| 84 |
+
3. **更新 `changelog.md`**:記錄本次變更摘要
|
| 85 |
+
|
| 86 |
+
4. **完成檢查**:
|
| 87 |
+
- 所有 spec 已同步?
|
| 88 |
+
- 所有日誌已記錄?
|
| 89 |
+
- 無新增 ERROR 日誌?
|
| 90 |
+
|
| 91 |
+
---
|
| 92 |
+
|
| 93 |
+
## 常見 Spec 修改場景
|
| 94 |
+
|
| 95 |
+
### 場景 1:新增 CSV 欄位
|
| 96 |
+
|
| 97 |
+
**步驟**:
|
| 98 |
+
1. 更新 `01-data-contract.md` 的相應表格(欄位型態、範圍)
|
| 99 |
+
2. 更新 `01-data-contract.md` 的驗證邏輯代碼片段
|
| 100 |
+
3. 若涉及模型,更新 `02-processing-rules.md` 的相應處理邏輯
|
| 101 |
+
4. 在 `04-extensions.md` 記錄向後相容性計畫
|
| 102 |
+
|
| 103 |
+
### 場景 2:新增故障場景
|
| 104 |
+
|
| 105 |
+
**步驟**:
|
| 106 |
+
1. 在 `03-error-handling.md` 的「故障場景表」新增行
|
| 107 |
+
2. 定義行為、日誌、使用者介面訊息
|
| 108 |
+
3. 在「決策樹」中新增分支
|
| 109 |
+
4. 在 `app.py` 實作捕獲邏輯
|
| 110 |
+
|
| 111 |
+
### 場景 3:新增外部資源
|
| 112 |
+
|
| 113 |
+
**步驟**:
|
| 114 |
+
1. 在 `01-data-contract.md` 的「冷啟動流程」新增步驟
|
| 115 |
+
2. 在 `02-processing-rules.md` 定義查詢/降級規則
|
| 116 |
+
3. 在 `03-error-handling.md` 定義故障場景
|
| 117 |
+
4. 在 `04-extensions.md` 記錄工廠模式
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
## 日誌級別速查
|
| 122 |
+
|
| 123 |
+
| 等級 | 何時使用 | 例子 | 關聯 Spec |
|
| 124 |
+
|-----|--------|------|-----------|
|
| 125 |
+
| **INFO** | 主流程進度 | "載入波形完成" | 02-processing-rules 7.1 |
|
| 126 |
+
| **WARNING** | 降級決策 | "使用預設 Vs30 值" | 03-error-handling A1 |
|
| 127 |
+
| **ERROR** | 可恢復單點失敗 | "測站查詢失敗,跳過" | 03-error-handling C3 |
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## 不變條件速查表
|
| 132 |
+
|
| 133 |
+
| 項目 | 值 | 來源 |
|
| 134 |
+
|-----|-----|------|
|
| 135 |
+
| 取樣率 | 100 Hz | 00-overview 或 02-processing-rules 2.1 |
|
| 136 |
+
| 時間窗 | 30 秒 (3000 samples) | 00-overview 或 02-processing-rules 2.1 |
|
| 137 |
+
| 輸入測站 | 最多 25 | 00-overview 或 02-processing-rules 1.1 |
|
| 138 |
+
| 補零策略 | 尾段補 0 至 3000 | 02-processing-rules 3.2 |
|
| 139 |
+
| Vs30 預設值 | 600 m/s | 02-processing-rules 5 |
|
| 140 |
+
| 低通濾波 | 10 Hz, 4-order Butterworth | 02-processing-rules 3.1 |
|
| 141 |
+
| 地圖高度 | 800 px | 00-overview 快速參考表 |
|
| 142 |
+
| 目標批次大小 | 25 點/批 | 02-processing-rules 1.2 |
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
## 常見編輯清單
|
| 147 |
+
|
| 148 |
+
### 新增功能時
|
| 149 |
+
|
| 150 |
+
- [ ] 更新 `00-overview.md` 的「模組清單」(若新增模組)
|
| 151 |
+
- [ ] 更新相應的 data contract 或 processing rules
|
| 152 |
+
- [ ] 在 `03-error-handling.md` 新增故障場景
|
| 153 |
+
- [ ] 建立 `spec/plan.md` 的任務清單
|
| 154 |
+
- [ ] 在 `changelog.md` 的 Unreleased 記錄
|
| 155 |
+
|
| 156 |
+
### 修復 Bug 時
|
| 157 |
+
|
| 158 |
+
- [ ] 如果涉及規格違反,先確認是 bug 還是規格有誤
|
| 159 |
+
- [ ] 在 `changelog.md` 的 Unreleased 記錄修復內容
|
| 160 |
+
- [ ] 若修改了日誌,更新 `03-error-handling.md` 的日誌等級表
|
| 161 |
+
|
| 162 |
+
### 重構代碼時
|
| 163 |
+
|
| 164 |
+
- [ ] 確認不改變 I/O 形狀、模組邊界、日誌原則
|
| 165 |
+
- [ ] 若改變,則按「新增功能」流程走
|
| 166 |
+
|
| 167 |
+
---
|
| 168 |
+
|
| 169 |
+
## 提交時檢查清單
|
| 170 |
+
|
| 171 |
+
```bash
|
| 172 |
+
# 1. 確認所有 spec 已更新
|
| 173 |
+
ls -la spec/
|
| 174 |
+
|
| 175 |
+
# 2. 檢查 changelog.md 已記錄
|
| 176 |
+
grep -A 5 "## \[Unreleased\]" changelog.md
|
| 177 |
+
|
| 178 |
+
# 3. 檢查代碼中的 spec 引用註解
|
| 179 |
+
grep -n "spec #\|spec/" app.py
|
| 180 |
+
|
| 181 |
+
# 4. 執行應用、查閱日誌,確認無新增 ERROR
|
| 182 |
+
# (執行後檢查日誌輸出)
|
| 183 |
+
|
| 184 |
+
# 5. 提交
|
| 185 |
+
git add spec/ changelog.md app.py
|
| 186 |
+
git commit -m "描述本次變更"
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
---
|
| 190 |
+
|
| 191 |
+
**有問題?** 查看 `.github/copilot-instructions.md` 的「對話時的 Spec 查詢策略」一節。
|
| 192 |
+
|
spec/plan.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 迭代計畫 (plan.md) — Sprint Template
|
| 2 |
+
|
| 3 |
+
> 本檔案於每個迭代開始時填充,定義本次迭代的範圍、目標與驗收標準。
|
| 4 |
+
> 完成後可拋棄;下次迭代時重新建立。
|
| 5 |
+
|
| 6 |
+
## 迭代資訊
|
| 7 |
+
|
| 8 |
+
| 項目 | 值 |
|
| 9 |
+
|-----|-----|
|
| 10 |
+
| Sprint 編號 | 待填 |
|
| 11 |
+
| 開始日期 | YYYY-MM-DD |
|
| 12 |
+
| 預計結束日期 | YYYY-MM-DD |
|
| 13 |
+
| 負責人 | 待填 |
|
| 14 |
+
|
| 15 |
+
## 迭代目標 (Goal)
|
| 16 |
+
|
| 17 |
+
(簡述本次迭代要解決什麼問題或新增什麼功能)
|
| 18 |
+
|
| 19 |
+
示例:
|
| 20 |
+
- 修復波形補零邏輯,確保不足 30 秒時正確補齊至 3000 samples
|
| 21 |
+
- 新增遠端 Vs30 查詢備用方案
|
| 22 |
+
- 改進 UI 地圖高度管理
|
| 23 |
+
|
| 24 |
+
## 範圍 (Scope)
|
| 25 |
+
|
| 26 |
+
(本次迭代涉及哪些模組、檔案、外部依賴)
|
| 27 |
+
|
| 28 |
+
| 模組 | 涉及檔案 | 變更類型 |
|
| 29 |
+
|-----|--------|---------|
|
| 30 |
+
| 待填 | `app.py` | bugfix / feature / refactor |
|
| 31 |
+
|
| 32 |
+
## 驗收標準 (Acceptance Criteria)
|
| 33 |
+
|
| 34 |
+
(本次迭代完成的定義 Definition of Done)
|
| 35 |
+
|
| 36 |
+
- [ ] 所有任務完成且代碼測試通過
|
| 37 |
+
- [ ] 相關規格文件 (spec/*.md) 已同步更新
|
| 38 |
+
- [ ] changelog.md 已記錄變更摘要
|
| 39 |
+
- [ ] 無新增 WARNING/ERROR 日誌(除非預期)
|
| 40 |
+
|
| 41 |
+
## 任務拆解 (Tasks)
|
| 42 |
+
|
| 43 |
+
(根據目標拆解成具體實作任務;參考 `spec/task.md` 範本)
|
| 44 |
+
|
| 45 |
+
### 任務 1: [功能名稱]
|
| 46 |
+
- **描述**:待填
|
| 47 |
+
- **涉及檔案**:`app.py`(L100-150)
|
| 48 |
+
- **估時**:X hours
|
| 49 |
+
- **驗收**:
|
| 50 |
+
- [ ] 代碼實作完成
|
| 51 |
+
- [ ] 相關 spec 更新
|
| 52 |
+
- [ ] 測試通過
|
| 53 |
+
|
| 54 |
+
### 任務 2: [功能名稱]
|
| 55 |
+
- **描述**:待填
|
| 56 |
+
- **涉及檔案**:待填
|
| 57 |
+
- **估時**:X hours
|
| 58 |
+
- **驗收**:
|
| 59 |
+
- [ ] 代碼實作完成
|
| 60 |
+
- [ ] 相關 spec 更新
|
| 61 |
+
- [ ] 測試通過
|
| 62 |
+
|
| 63 |
+
## 風險與緩解
|
| 64 |
+
|
| 65 |
+
| 風險 | 機率 | 影響 | 緩解方案 |
|
| 66 |
+
|-----|------|------|--------|
|
| 67 |
+
| 待填 | 低/中/高 | 待填 | 待填 |
|
| 68 |
+
|
| 69 |
+
## 進度追蹤
|
| 70 |
+
|
| 71 |
+
(每日更新或完成時更新)
|
| 72 |
+
|
| 73 |
+
| 日期 | 完成項 | 阻礙 | 備註 |
|
| 74 |
+
|-----|--------|------|------|
|
| 75 |
+
| 待填 | 待填 | 無 | 待填 |
|
| 76 |
+
|
| 77 |
+
## 完成檢查清單
|
| 78 |
+
|
| 79 |
+
迭代完成前確認以下項目:
|
| 80 |
+
|
| 81 |
+
- [ ] 所有任務標記為 DONE
|
| 82 |
+
- [ ] 代碼推送至主分支
|
| 83 |
+
- [ ] 所有文件(spec、changelog)已更新
|
| 84 |
+
- [ ] 日誌檢查:無重大 ERROR(除預期外)
|
| 85 |
+
- [ ] changelog.md 已記錄本次迭代摘要
|
| 86 |
+
- [ ] 本 plan.md 可拋棄或存檔
|
| 87 |
+
|
| 88 |
+
## 後續迭代建議
|
| 89 |
+
|
| 90 |
+
(迭代完成時,記錄對下次迭代的建議)
|
| 91 |
+
|
| 92 |
+
- 待填
|
| 93 |
+
|