jimmy60504 commited on
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 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
- - 取樣率:100 Hz;輸入長度:30 秒(3000 點,不足補 0)。
63
- - 輸入測站:最多 25;不足允許,需在 UI 顯示警告。
64
- - 缺分量:N/E 缺失以 Z 替代並統計。
65
- - 目標批次:每批最多 25,合併全部結果。
66
- - 地圖高度:Folium 地圖固定 800px;實際震度圖缺失以占位。
67
- - Vs30:優先使用 `SeisBlue/TaiwanVs30`;失敗則使用預設 600 m/s 並記錄 log。
 
 
 
 
 
 
 
 
 
 
 
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
- - `docs/spec.md`:規格與設計說明(契約)
 
 
 
 
 
79
  - `.github/copilot-instructions.md`:生成程式碼指南
80
- - `docs/task.md`:目前進行中的任務拆解(完成後可重置)
81
- - `change-log.md`:每次變更的高層摘要
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
- - `docs/spec.md`(契約與不變條件)
 
 
 
 
100
  - `.github/copilot-instructions.md`(開發與貢獻指南)
101
- - `change-log.md`(歷次變更摘要)
 
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
+