jimmy60504 commited on
Commit
5a8e0d1
·
1 Parent(s): 588d698

chore: delete project specification documents, GitHub prompts, and templates.

Browse files
.github/copilot-instructions.md DELETED
@@ -1,182 +0,0 @@
1
- # GitHub Copilot 指南(專案層級)
2
-
3
- > **⚠️ 抽象化指南** — 本檔案及 `.github` 資料夾中的所有 prompt 相關文件應保持**完全抽象化**,移除所有原專案特定的技術細節與領域知識。
4
- >
5
- > 撰寫或修改本文件時:
6
- > - ✅ 使用通用術語(如「資料欄位」、「輸入項目」、「資料點」)
7
- > - ✅ 用「資料來源查詢失敗」、「外部服務超時」而非具體服務名稱
8
- > - ✅ 用「輸入格式」、「資料格式」而非具體檔案或編碼格式
9
- > - ❌ 不要提及任何特定領域(如地震、金融、醫療等)的詞彙
10
- > - ❌ 不要參考原專案的具體資料檔案名、業務邏輯或專業術語
11
- >
12
- > **目標**:使本指南可直接應用於其他資料處理專案,無需修改具體內容。
13
-
14
- 本文件為單人專案的 Copilot 開發指南。撰寫或修改程式碼時,務必遵循 `spec/spec.md` 的規格與不變條件。
15
-
16
- ## 關鍵文件
17
-
18
- ### Spec 檔案結構(模塊化)
19
-
20
- 為了保持可讀性與管理 token 預算,規格已拆分為多個專業化文件:
21
-
22
- - **`spec/00-overview.md`** — 專案概覽、核心目標、模組清單、不變條件速查表(**對話時優先傳讀此文件**)
23
- - **`spec/01-data-contract.md`** — 資料結構、模型 I/O Shape、檔案格式、冷啟動流程(資料契約相關時查閱)
24
- - **`spec/02-processing-rules.md`** — 批次策略、輸入項目上限、資料處理參數、資源限制(實作邏輯時查閱)
25
- - **`spec/03-error-handling.md`** — 錯誤處理策略、日誌原則、常見場景、UI 訊息設計(除錯或新增錯誤處理時查閱)
26
- - **`spec/04-extensions.md`** — 擴充空間、新資料來源、向後相容策略(功能擴展時查閱)
27
-
28
- 其他重要檔案:
29
- - **`spec/plan.md`**(短期):本次迭代的範圍、目標、驗收標準。臨時討論檔,每個 sprint 更新後可拋棄。
30
- - **`changelog.md`**:記錄完成的變更摘要(高層描述,非逐行對應)。用於交付追蹤。
31
-
32
- ## 核心守則(3 點)
33
-
34
- ### 1. **規格優先** — 合約 + 不變條件
35
- - 新增或變更功能時,先檢查相應的規格文件(通常 `00-overview.md` 或 `01-data-contract.md`)中的不變條件與限制。
36
- - 若需變更 CSV 結構、模型契約、或 API 形狀,**必須先更新 spec**(`01-data-contract.md` 或 `04-extensions.md`),並說明相容性影響。
37
- - 當 Copilot 指南與 spec 有衝突時,以 spec 為準。
38
-
39
- ### 2. **失敗不中斷** — 降級 + 日誌
40
- - 單點錯誤(缺失欄位、資料來源查詢失敗、檔案遺失等)**不應中止全流程**。
41
- - 改用預設值、跳過該點、或降級處理,並記錄 WARNING/ERROR log(使用 loguru)。
42
- - 參考 `spec/03-error-handling.md` 的具體策略與常見場景。
43
-
44
- ### 3. **資料清晰** — I/O 契約
45
- - 模型輸入/輸出形狀、CSV 必備欄位、檔案格式必須與 spec 保持一致。
46
- - 若要改動資料結構(如新增 CSV 欄位),先同步更新 `01-data-contract.md`,並在 `04-extensions.md` 記錄相容性計畫。
47
-
48
- ## 程式碼實踐
49
-
50
- - 在涉及 spec 約定的邏輯處加入註解,簡述與 spec 的對齊(如 "spec #2:輸入項目上限" 或 "參考 02-processing-rules.md"),無需編號標籤。
51
- - 優先保持向後相容;新增資料來源或輸入項時參考 `spec/04-extensions.md`。
52
- - 避免將關鍵邏輯(如批次策略、資源查詢)分散於多處;若需重構,提供明確的入口函式或工廠(參考 `04-extensions.md` 工廠模式)。
53
-
54
- ## LLM 執行防護(預防常見陷阱)
55
-
56
- ### 避免無限驗證迴圈
57
- - **問題**:執行完檔案操作後,開始重複執行 `ls`, `wc -l`, `find`, `file` 等驗證命令
58
- - **預防**:
59
- - 每個主要任務完成後**立即停止**,不進行驗證
60
- - 若需驗證,由用戶在後續對話中明確要求
61
- - 單個工具呼叫的驗證可以(如 `get_errors` 檢查編譯),但連環的驗證命令應停止
62
-
63
- ### 避免生成不必要的報告
64
- - **問題**:執行 `/project.init` 等明確指定「不產生報告」的命令後,卻自動生成總結 markdown
65
- - **預防**:
66
- - 明確檢查指令的「限制」與「不做」清單
67
- - 若指令說「完成後直接返回」,就真的直接返回
68
- - 報告只有在用戶**明確要求**時才生成
69
-
70
- ### 避免過度消耗 token
71
- - **問題**:為了「確保完美」而執行多個驗證步驟,造成 token 浪費
72
- - **預防**:
73
- - 優先相信檔案操作工具成功(除非有明確錯誤訊息)
74
- - 避免為了「心安」而重複讀取剛建立的檔案
75
- - 單輪對話內集中驗證,而非分散於多個工具呼叫
76
-
77
- ## 提交前檢查
78
-
79
- commit 前確認以下兩點:
80
-
81
- 1. 是否符合 spec 的輸入/輸出 shape 與不變條件(查閱 `01-data-contract.md` 或 `02-processing-rules.md`)?
82
- 2. 若變更了 CSV 結構或模型契約,spec 已同步更新(`01-data-contract.md` + `04-extensions.md`)?
83
-
84
- ## 迭代與任務追蹤
85
-
86
- - 每次���始新迭代時,在 `spec/plan.md` 討論本次目標、範圍、驗收標準。
87
- - 根據 plan.md 的內容,可在 `spec/task.md` 維護任務拆解(子任務應小而可驗收)。
88
- - 迭代完成後,更新 `changelog.md` 記錄變更摘要。
89
- - 完成後標記 `plan.md` 和 `task.md` 為 `[ARCHIVED]`(保留作為歷史參考),下次迭代時重新生成新版本。
90
- - **長期的規格變更和決策應同步回對應的 spec 模塊文件**(`00-overview.md` / `01-data-contract.md` / `02-processing-rules.md` / `03-error-handling.md` / `04-extensions.md`),形成累積的設計參考。
91
-
92
- ## 對話時的 Spec 查詢策略
93
-
94
- **遇到不同類型的問題時,優先查閱對應的 spec 檔案**:
95
-
96
- | 問題類型 | 查閱檔案 |
97
- |--------|--------|
98
- | 「這個欄位是必須的嗎?」「模型輸入是什麼形狀?」 | `01-data-contract.md` |
99
- | 「怎麼處理缺失的資料?」「最多幾個輸入項?」 | `02-processing-rules.md` |
100
- | 「資料來源查詢失敗怎麼辦?」「應該記哪種日誌?」 | `03-error-handling.md` |
101
- | 「怎麼新增新資料來源?」「能不能擴展資料欄位?」 | `04-extensions.md` |
102
- | 「核心不變條件有哪些?」 | `00-overview.md` |
103
-
104
- **每次對話前,LLM 應根據用戶需求讀取相應的 spec 模塊**(而非整個 spec.md),節省 token 預算。
105
-
106
- ## Copilot Slash Commands(審核檢查點)
107
-
108
- 本專案提供以下指令協助「規格審核」與「迭代開發」的清晰分工:
109
-
110
- ### `/project.init`(一次性初始化 — 僅用於既有專案無規格文件時)
111
- - **目的**:為既有專案(Brownfield)從無規格的狀態,快速生成模塊化規格檔案(`spec/00-overview.md` 至 `spec/04-extensions.md`)與迭代計畫模板(`spec/plan.md`)。
112
- - **限制**:此命令為一次性初始化,**應該只產生必要的規格與計畫文件,不產生總結報告或分析文檔**。初始化完成後無需進一步的報告文件。
113
- - **執行檢查清單**(防止重複驗證與報告生成):
114
- - ❌ **不做**:生成總結報告、比較表、驗證文檔
115
- - ❌ **不做**:執行 `ls`, `wc -l`, `file`, `find` 等驗證命令
116
- - ❌ **不做**:使用 `show_content` / `open_file` 顯示生成結果
117
- - ❌ **不做**:重複讀取已建立的檔案確認內容
118
- - ✅ **只做**:
119
- 1. 掃描現有程式碼結構
120
- 2. 建立 5 份規格 + 2 份計畫檔案
121
- 3. 更新 `changelog.md`
122
- 4. 直接結束,不進行任何驗證
123
- - **流程**:
124
- 1. LLM 掃描現有程式碼結構,分析核心模組與資料流。
125
- 2. 根據代碼分析,逐一填充 5 份模塊化規格文件(`spec/00-*` 至 `spec/04-*`)與計畫模板。
126
- 3. 完成後**直接返回**(無總結、無驗證、無報告);如需驗證規格內容,用戶可在後續對話中執行 `/project.spec` 或 `/project.plan`。
127
-
128
- ### `/project.spec`(需求改變時用)
129
- - **目的**:調整 spec 的大方向、契約或不變條件。與現有程式碼做比較,列出後續工作清單。
130
- - **流程**:
131
- 1. 討論新增或調整的規格項(需求、契約、不變條件)。
132
- 2. 根據需求類型,讀取相應的 spec 模塊(`01-*` / `02-*` / `03-*` / `04-*`)。
133
- 3. LLM 掃描現有程式碼,列出符合、缺漏、或違反的地方。
134
- 4. 產出「需要做的工作清單」。
135
- 5. **直接更新 `spec/spec.md`** → 等待使用者確認 → 確認後同步至 `README.md` 和 `spec/plan.md`。
136
-
137
- ### `/project.plan`(迭代規劃時用)
138
- - **目的**:生成本次 sprint 的計畫,讓使用者在 MD 上討論和調整。
139
- - **流程**:
140
- 1. 產生 `spec/plan.md`(包含迭代目標、高層任務列表、風險評估等)
141
- 2. **暫停等待使用者確認**(用戶可直接在 MD 上編輯)
142
- 3. 確認後,提示執行 `/project.task` 進行詳細任務拆解
143
-
144
- ### `/project.task`(任務拆解時用)
145
- - **目的**:根據確認的 `spec/plan.md`,拆解為具體的任務步驟與程式碼變更點。
146
- - **流程**:
147
- 1. 根據 `spec/plan.md` 的高層任務列表,生成詳細任務拆解(T-001、T-002 等)
148
- 2. 展示每個任務的具體變更點(檔案、行號、修改內容)
149
- 3. **暫停等待使用者確認**(用戶可調整任務順序或內容)
150
- 4. 確認後,提示執行 `/project.apply` 應用程式碼變更
151
-
152
- ### `/project.apply`(程式碼應用時用)
153
- - **目的**:直接應用經過確認的程式碼變更。
154
- - **流程**:
155
- 1. 根據 `spec/task.md` 的確認任務清單,直接應用所有程式碼修改
156
- 2. 執行語法/編譯檢查
157
- 3. 更新 `spec/task.md` 標記已完成的任務([x])
158
- 4. **暫停並提示使用者**:「程式碼已應用,請進行手動測試」
159
-
160
- ### `/project.report`(交付與清理時用)
161
- - **目的**:記錄變更並歸檔臨時計畫檔案。
162
- - **流程**:
163
- 1. 根據使用者已完成的測試結果,自動更新 `changelog.md`
164
- 2. 標記 `spec/plan.md` 和 `spec/task.md` 為已歸檔(在檔案開頭加入 `[ARCHIVED]` 標記)
165
- 3. 生成交付報告(品質狀態、需求對齐等)
166
- 4. 提示用戶本次迭代已完成,可開始新迭代
167
-
168
- ### 標準工作流
169
-
170
- **當需求改變時:**
171
- 1. 執行 `/project.spec` 討論規格調整
172
- 2. 直接在 `spec/spec.md` 上編輯 → 確認後同步至 `README.md` 和 `spec/plan.md`
173
-
174
- **執行迭代時:**
175
- 3. 執行 `/project.plan` 生成計畫 → 在 `spec/plan.md` 上編輯 → 確認後執行 `/project.task`
176
- 4. 執行 `/project.task` 拆解任務 → 在 `spec/task.md` 上編輯 → 確認後執行 `/project.apply`
177
- 5. 執行 `/project.apply` 直接應用程式碼 → 手動測試 → 測試完成後執行 `/project.report`
178
- 6. 執行 `/project.report` 記錄變更 + 標記 plan 和 task 為 `[ARCHIVED]` → 迭代完成
179
-
180
- **下次需求改變時:** 回到步驟 1。
181
- **下次迭代開始時:** 執行步驟 3(會在同位置重新生成新的 plan.md 和 task.md)。
182
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/prompts/project.apply.prompt.md DELETED
@@ -1,45 +0,0 @@
1
- # /project.apply — 應用程式碼變更
2
-
3
- 目的
4
- - 根據使用者在 `/project.task` 中確認的任務,直接應用具體的程式碼變更。
5
- - 更新 `spec/task.md` 標記已完成的任務。
6
- - 等待使用者手動測試,測試完成後執行 `/project.report`。
7
-
8
- 輸入
9
- - 使用者確認的任務拆解清單(來自 `/project.task`)
10
-
11
- 流程
12
-
13
- ### 自動執行
14
-
15
- 1. **直接應用程式碼變更**
16
- - 根據 `spec/task.md` 的任務清單,應用所有程式碼修改
17
- - 使用 `replace_string_in_file` 或 `insert_edit_into_file` 執行變更
18
- - 執行相關檢查(語法檢查、編譯檢查等,若適用)
19
-
20
- 2. **更新 `spec/task.md`**
21
- - 為已完成的任務標記勾選 `[x]`
22
- - 保留任務描述和驗收標準
23
-
24
- ### 暫停點:等待使用者手動測試
25
-
26
- 1. **提示使用者**:「程式碼已應用,請進行手動測試」
27
- 2. **等待使用者回饋**:
28
- - 測試是否通過驗收標準
29
- - 是否需要調整或修復
30
- - 測試完成後執行 `/project.report`
31
-
32
- ### 後續步驟(由使用者執行)
33
-
34
- 使用者手動測試完成後:
35
- - 執行 `/project.report` 記錄本次迭代的變更摘要
36
- - `/project.report` 會自動更新 `changelog.md` 並清理 `spec/plan.md` 和 `spec/task.md`
37
-
38
- ## 守則
39
-
40
- - **直接執行**:不需要展示階段或再次確認
41
- - **精準定位**:所有變更需精確指向檔案、函式、行號
42
- - **驗證變更**:應用後須檢查語法/編譯正確性
43
- - **等待測試**:暫停並等待使用者手動測試結果
44
- - **後續交付**:由 `/project.report` 負責記錄和清理
45
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/prompts/project.init.prompt.md DELETED
@@ -1,44 +0,0 @@
1
- # /project.init — 初始化規格(Project Init)
2
-
3
- ## 目的
4
- - 快速產生或盤點 `spec/spec.md`,作為契約與不變條件的單一來源。
5
- - 判斷 greenfield(無程式)還是 brownfield(有程式),輸出精簡的規格建議。
6
-
7
- ## 輸入
8
- 使用者提供:
9
- - 專案摘要(用途、主要流程、使用者)
10
- - I/O 與外部依賴(已知則填,未知可空)
11
- - 現有文件/README(若有)
12
- - 程式碼結構(若提供 → brownfield)
13
-
14
- ## 判定邏輯
15
- - **Greenfield**:無程式碼,LLM 產生初版骨架
16
- - **Brownfield**:有程式碼,LLM 盤點代碼、提出建議
17
-
18
- ## 輸出
19
-
20
- ### Greenfield 模式
21
- 以 `.github/spec-template.md` 為範本,產出:
22
- 1. 簡潔骨架:7 個章節(見 template)各列 1–2 行
23
- 2. 填入已知資訊,未知處留 TODO
24
- 3. 「後續補齊清單」:需要調查或決策的項目
25
-
26
- ### Brownfield 模式
27
- 掃描既有程式,產出簡潔建議:
28
- 1. **系統概觀**:架構 & 核心組件(3–5 句)
29
- 2. **I/O 契約**:輸入/輸出形狀、必填欄位、格式
30
- 3. **不變條件 & 邊界**:核心規則、失敗時降級方式
31
- 4. **外部依賴**:主要服務/檔案、失敗降級
32
- 5. **缺漏清單**:規格缺失或不清楚的項
33
- 6. **驗證位置**:程式入口、設定檔位置(供查證)
34
-
35
- ## 完成後
36
- - 提示使用者確認內容
37
- - 建議根據建議手動建立或更新 `spec/spec.md`
38
- - 提示下一步:在 spec 確認後執行 `/project.plan` 進入迭代規劃
39
-
40
- ## 守則
41
- - **不修改檔案**:僅提供建議文字(不實際寫 spec)
42
- - **簡潔優先**:決策層級,不列完整資料結構或假資料
43
- - **領域知識集中**:數值、規則盡量導向 spec,避免散落
44
- - **實務優先**:只記錄「必須知道的」
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/prompts/project.plan.prompt.md DELETED
@@ -1,50 +0,0 @@
1
- # /project.plan — 規劃階段
2
-
3
- 目的
4
- - 產生 `spec/plan.md`:定義本次 sprint 的範圍、目標與驗收標準,讓使用者能在討論階段進行修改。
5
- - 必須對齊 `spec/spec.md`(全生命週期規格)的不變條件與契約;所有領域常數與限制以 spec 為準。
6
-
7
- 角色區分
8
- - **`spec/spec.md`**:軟體生命週期的全局規格(長期約束、核心模型、I/O shape、不變條件)
9
- - **`spec/plan.md`**:本 sprint 的執行計畫(範圍、目標、子任務、驗收標準);必須受 spec.md 約束
10
-
11
- 輸入(由提問者提供)
12
- - 需求描述或問題背景。
13
- - 目標與範圍(如未提供,請以合理假設補齊,並明示)。
14
-
15
- 請輸出
16
- - 產生 `spec/plan.md` 檔案,內容包含:
17
- - **簡短前置**:需求重述與關鍵假設(若有)。
18
- - **迭代目標**:本次 sprint 要解決什麼問題或新增什麼功能。
19
- - **範圍**:涉及的模組、檔案、外部依賴。
20
- - **驗收標準**:本次迭代完成的定義(Definition of Done)。
21
- - **高層任務列表**:要做的主要工作項(不需詳細拆解,詳細拆解由 `/project.task` 負責)。
22
- - **Invariants 對齊**:逐條列出是否受影響(Done/Not Impacted/At Risk),並指向 `spec/spec.md` 的相關條目。
23
- - **風險與回滾**:主要風險、緩解、回滾方式(最小可逆)。
24
- - **冒煙測試**:2–4 個步驟,含預期輸出要點。
25
-
26
- ## ⏸️ 暫停點(重要)
27
-
28
- **完成 `spec/plan.md` 生成後,立即停止,等待使用者確認。**
29
-
30
- ### 第一階段:討論與確認計畫(直接在 spec/plan.md 上編輯)
31
-
32
- 1. **直接生成/更新 `spec/plan.md`**
33
- - 你可以直接在 MD 上進行編輯和調整
34
- - 修改目標、範圍、任務優先序等
35
- - 如果改錯了可以直接 discard
36
-
37
- 2. **完成編輯後**:
38
- - 回覆「確認」或「核准」
39
-
40
- ### 第二階段:任務拆解(用戶確認後)
41
-
42
- 待你確認後:
43
- 1. **提示你執行 `/project.task`** 來進行詳細的任務拆解與驗收標準
44
-
45
- ## 守則
46
-
47
- - **必須遵守暫停點**:產生 `spec/plan.md` 後,等待使用者確認,不進行任何進一步的操作。
48
- - **允許多輪編輯**:用戶可在 `spec/plan.md` 上任意修改、調整,直到滿意為止。
49
- - **可安全 discard**:用戶可隨時放棄編輯,discard 檔案變更,無副作用。
50
- - plan.md 的所有決策必須遵守 spec.md 的全局約束;不可違反或繞過 spec 中定義的不變條件。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/prompts/project.report.prompt.md DELETED
@@ -1,49 +0,0 @@
1
- # /project.report — 交付與清理
2
-
3
- 目的
4
- - 記錄本次迭代的變更摘要到 `changelog.md`
5
- - 清理臨時計畫檔案(`spec/plan.md` 和 `spec/task.md`)
6
- - 生成交付報告
7
-
8
- 輸入
9
- - 使用者已完成手動測試(來自 `/project.apply` 後的測試階段)
10
-
11
- 自動執行
12
-
13
- ### 1. 更新 `changelog.md`
14
-
15
- - 在 `[Unreleased]` 或最新版本下,根據 `spec/task.md` 生成變更摘要
16
- - 格式:
17
- ```markdown
18
- ### Added/Changed/Fixed
19
- - **功能名稱或模組**(日期)
20
- - 具體變更描述(2-3 條要點)
21
- - 相關檔案簡稱
22
- ```
23
-
24
- ### 2. 歸檔臨時檔案
25
-
26
- - 標記 `spec/plan.md` 為已歸檔(在檔案開頭加入 `[ARCHIVED]` 標記 + 簡要說明)
27
- - 標記 `spec/task.md` 為已歸檔(在檔案開頭加入 `[ARCHIVED]` 標記 + 執行摘要)
28
- - 下次迭代時會在同位置重新生成新版本
29
- - **原因**:保留檔案作為參考記錄,方便後續追蹤迭代歷史;相比刪除對 LLM 限制更少
30
-
31
- ### 3. 生成交付報告
32
-
33
- 輸出內容:
34
- - **變更摘要**:對照 `spec/task.md` 的已完成任務
35
- - **品質狀態**:
36
- - Build/Lint 狀態
37
- - 語法/編譯檢查結果
38
- - I/O 契約驗證
39
- - **Requirements 對齐**:對照 `spec/spec.md` 的不變條件
40
- - **UX 變更檢查**:若有 UI/功能變更,提示需更新 `README.md` 的相關章節
41
- - **建議的 changelog 條目**:提供文本供參考(已自動寫入)
42
-
43
- ## 守則
44
-
45
- - **自動執行更新**:自動更新 `changelog.md` 並歸檔 plan 和 task 檔案(標記 `[ARCHIVED]`)
46
- - **保留 spec.md**:不刪除任何規格檔案(`spec/00-*.md` 至 `spec/04-*.md` 和臨時計畫)
47
- - **明確提示**:清楚告知已歸檔哪些檔案及其位置
48
- - **參考記錄**:保留歸檔檔案供後續迭代追蹤歷史,無需真正刪除
49
- - **交付完成**:提示本次迭代已交付,可開始新的迭代週期
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/prompts/project.spec.prompt.md DELETED
@@ -1,98 +0,0 @@
1
- # /project.spec — 規格審核與更新
2
-
3
- 目的
4
- - 調整 `spec/spec.md` 的大方向:新增、修改或移除規格項(需求、契約、不變條件、限制)。
5
- - 與現有程式碼做差異比較,檢查哪些地方符合、缺漏、或違反規格。
6
- - 產出「需要做的工作清單」,作為 `/project.plan` 的輸入。
7
-
8
- 應用場景
9
- - 需求改變、使用者故事新增
10
- - 發現程式碼與規格不對齊
11
- - 業務邊界或資料模型調整
12
- - 性能、相容性或安全性要求變更
13
-
14
- 角色區分
15
- - **`spec/spec.md`**:軟體生命週期全局規格(長期、累積性維護)
16
- - **`spec/plan.md`**:本 sprint 的執行計畫(臨時、可拋棄)
17
- - **現有程式碼**:當前實作狀態(檢查對齊度)
18
-
19
- 輸入(由提問者提供)
20
- - 規格調整的內容描述(新增、修改或移除什麼規格項)
21
- - 背景與動機
22
- - 相關的邊界情況或假設(若有)
23
-
24
- 請輸出
25
-
26
- ### 第一部分:規格建議
27
- - 新增或修改的規格項清單,包含:
28
- - 項目名稱與章節位置(對應 `spec/spec.md` 現有章節)
29
- - 具體描述(契約、限制、不變條件)
30
- - 影響範圍(涉及哪些模組/功能)
31
- - 相容性說明(是否破壞現有行為)
32
-
33
- ### 第二部分:程式碼比較分析
34
- - 掃描 spec 內所提到的關鍵檔案,針對調整項分析:
35
- - ✅ **符合規格**:現有程式已正確實作的部分
36
- - ❌ **缺漏**:規格定義但程式未實作的部分
37
- - ⚠️ **違反**:程式行為與規格不一致的部分
38
- - ❓ **不確定**:需要手動驗證的部分
39
- - 列出具體的程式碼位置(檔案名、函式名、行號範圍)
40
-
41
- ### 第三部分:工作清單
42
- - 「需要做的工作項」:根據差異分析產出,包含:
43
- - 優先序(High/Medium/Low)
44
- - 工作描述與預期結果
45
- - 受影響的檔案
46
- - 估計工作量(Small/Medium/Large)
47
- - 與 spec 各章節的對應關係
48
- - 格式化為可直接貼入 `spec/plan.md` Checklist 的形式
49
-
50
- ### 第四部分:驗收與風險
51
- - 現有不變條件檢查清單(有無違反的?)
52
- - 主要風險與回滾建議
53
- - 冒煙測試建議(2–3 個步驟驗證調整是否生效)
54
-
55
- ## ⏸️ 暫停點與雙向確認流程(重要)
56
-
57
- **完成四部分分析後,立即停止,等待使用者評審與確認。**
58
-
59
- ### 第一階段:評審與確認規格變更(直接在 spec/spec.md 上編輯)
60
-
61
- 1. **直接更新 `spec/spec.md`**:
62
- - 應用四部分內容的建議
63
- - 新增/修改/移除規格項
64
- - 用戶可直接在 MD 檔案上進行二次調整
65
-
66
- 2. **用戶進行討論與調整**(在 MD 檔案上):
67
- - 直接編輯 `spec/spec.md`
68
- - 調整、新增或移除規格項
69
- - 修改相關描述或限制
70
- - 如果改錯了可以直接 discard(無副作用)
71
-
72
- 3. **完成編輯後**:
73
- - 用戶回覆「確認」或「核准」
74
-
75
- 4. **確認後繼續第二階段**
76
-
77
- ### 第二階段:相關文件同步(用戶確認後)
78
-
79
- 待用戶確認後,按順序執行:
80
- 1. **更新 `README.md`**:將規格調整中的**高層次設計理念、使用者情境、架構思想**同步至 README(確保即使 spec 後續重建,這些核心思想仍得到保留);細節實作內容暫不更新,待任務完成後補充
81
- 2. 根據「工作清單」建立 `spec/plan.md`(定義本次 sprint 範圍)
82
-
83
- ### 第三階段:後續開發(可選)
84
-
85
- - 執行 `/project.plan` 進行任務拆解與驗收標準
86
- - 開始迭代開發
87
-
88
- ## 守則
89
-
90
- - **必須遵守暫停點**:更新 `spec/spec.md` 後,等待使用者確認,不主動修改 `README.md` 或建立 `spec/plan.md`。
91
- - **允許多輪編輯**:用戶可在 `spec/spec.md` 上任意修改、調整,直到滿意為止。
92
- - **可安全 discard**:用戶可隨時放棄編輯,discard 檔案變更,無副作用。
93
- - 規格建議基於使用者輸入與現有 `spec/spec.md`;直接在 `spec/spec.md` 上實作,等待確認後再同步其他檔案。
94
- - 程式碼分析需精準定位(檔案、函式、行號),便於快速定位與修改。
95
- - 工作清單應與現有的 `spec/spec.md` 章節結構對應,避免遺漏或重複。
96
- - 若發現規格與程式碼有深層衝突,需明確標註為「需要討論」的項目。
97
- - 優先保持向後相容;若必須破壞相容性,需在規格變更中明確說明與遷移策略。
98
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/prompts/project.task.prompt.md DELETED
@@ -1,51 +0,0 @@
1
- # /project.task — 任務拆解
2
-
3
- 目的
4
- - 根據使用者確認的 `spec/plan.md`,進行詳細的任務拆解與驗收標準。
5
- - 生成具體的任務步驟清單與程式碼變更點,為後續程式碼應用做準備。
6
-
7
- 輸入
8
- - 使用者已確認的 `spec/plan.md`
9
-
10
- 請輸出
11
- - 根據 `spec/plan.md` 的高層任務列表,生成詳細任務拆解:
12
- - **編號 Task 清單**(T-001、T-002 等)
13
- ```
14
- ### T-001: <任務名稱>
15
- - **描述**:詳細說明這個任務要做什麼
16
- - **受影響檔案**:列出所有會被修改的檔案(相對路徑)
17
- - **具體變更點**:
18
- - 文件1.py (L100-150): 新增函式 xxx()
19
- - 文件2.md: 更新章節 YYY
20
- - **驗收標準**:如何檢驗這個任務完成
21
- - **風險等級**:低/中/高
22
- ```
23
-
24
- - **關鍵變更點摘要**(不貼整段程式碼):
25
- - 每個變更位置、修改前後行為
26
- - 相關欄位/限制(若有)
27
-
28
- - **品質門檻**:
29
- - 是否影響公共契約
30
- - 錯誤處理與日誌覆蓋情況
31
-
32
- ## ⏸️ 暫停點
33
-
34
- **完成任務拆解後,等待使用者確認。**
35
-
36
- - 用戶可在本階段:
37
- - 確認任務拆解是否合理
38
- - 調整任務優先序或內容
39
- - 修改 `spec/task.md` 後回覆「確認」
40
-
41
- ### 確認後的下一步
42
-
43
- 待用戶確認後:
44
- 1. **提示執行 `/project.apply`** 來應用程式碼變更
45
-
46
- ## 守則
47
-
48
- - 不直接修改程式碼檔案,只展示變更方案。
49
- - 編號(T-001 等)僅用於清單,不會寫入 spec 檔案。
50
- - 若涉及公共契約變更,必須標註需先確認 `spec/spec.md`。
51
- - 提供精確的檔案位置與行號,便於後續應用。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/spec-template.md DELETED
@@ -1,48 +0,0 @@
1
- # 規格(Spec)模板 — 單人專案簡化版
2
-
3
- > **用途**:作為專案的契約與不變條件單一來源(Single Source of Truth)。
4
- > **原則**:簡潔、可執行、易維護。只記錄「必須知道的」,不列完整執行結果或逐行詳解。
5
-
6
- ## 1. 概述(Overview)
7
- **三句話說清楚**:
8
- - 問題 & 目標:一句話
9
- - 非目標:避免什麼
10
- - 主要輸入/輸出:資料流向
11
-
12
- ## 2. 核心流程
13
- - 主要入口:CLI/Web/腳本/API
14
- - 3–5 步簡述
15
- - 關鍵外部依賴:服務/檔案/第三方庫名稱
16
-
17
- ## 3. I/O 契約
18
- - **輸入**:資料形狀、必填欄位、預設值
19
- - **輸出**:預期格式、主要欄位
20
- - **相容性**:破壞性變更如何識別
21
-
22
- ## 4. 不變條件 & 邊界
23
- - **核心規則**:不可破壞的假設(採樣率、處理單位上限、並行限制等)
24
- - **邊界行為**:缺漏/格式錯誤 → 降級策略(跳過/預設值/日誌警告)
25
- - **資源限制**:記憶體/時間/配額(語義級,具體值在設定檔)
26
-
27
- ## 5. 外部依賴 & 降級
28
- - **主要依賴**:用途、介面、失敗時的替代行為
29
- - **日誌原則**:各失敗點應記 WARNING/ERROR(描述現象+替代方案)
30
- - **重試/快取**:簡述策略(無則不列)
31
-
32
- ## 6. 擴充與相容(可選)
33
- - 新增資料源/模組的預留方式
34
- - 版本遷移計畫(若有破壞性變更)
35
-
36
- ## 7. 驗收清單
37
- - ✅ Happy path:完整流程
38
- - ✅ 邊界情境:最常見的 1–2 個失敗案例
39
-
40
- ---
41
-
42
- ## 使用建議
43
-
44
- 1. **初期**:填 1–4 節,建立基本契約
45
- 2. **開發中**:發現新邊界時更新第 4–5 節
46
- 3. **功能擴展**:先更新本文,再寫程式
47
- 4. **記住**:不需列完整的執行結果、逐行說明或假資料,只記「決策」與「規則」
48
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/task-template.md DELETED
@@ -1,62 +0,0 @@
1
- # Task: <標題>
2
-
3
- - Task ID: T-<YYYYMMDD>-<shortname>
4
- - Date: <YYYY-MM-DD>
5
- - Status: Planning | In Progress | Done
6
-
7
- ## 背景與目標
8
- - **背景**:為何需要?與 `spec/spec.md` 的哪一段相關?
9
- - **目標**:交付物與驗收標準(一句或要點形式)。
10
- - **不在範圍**:明確避免 scope creep。
11
-
12
- ## 任務拆解(可勾選)
13
- - [ ] 子任務 1:<描述>(變更檔案:`...`)
14
- - 驗收:<可驗證的條件>
15
- - [ ] 子任務 2:<描述>(變更檔案:`...`)
16
- - 驗收:<可驗證的條件>
17
- - [ ] 子任務 3:<描述>(變更檔案:`...`)
18
- - 驗收:<可驗證的條件>
19
-
20
- > 每個子任務盡量控制在 1 小時內可完成。
21
-
22
- ## 不變條件與約束檢查
23
- - [ ] 是否影響 `spec/spec.md` 中的 I/O shape、欄位、或不變條件?
24
- - [ ] 如是,已更新 spec 並標註破壞性變更?
25
- - [ ] 邊界情境(資料缺漏、外部依賴失敗等)是否正確降級並記錄 log?
26
-
27
- > 填寫相關 spec 章節編號或行號(如 spec #3, #6)。
28
-
29
- ## 手動驗收(冒煙測試)
30
- - **步驟**:
31
- 1. <步驟 1>
32
- 2. <步驟 2>
33
- 3. <步驟 3>
34
- - **預期結果**:<清楚描述>
35
- - **失敗情境測試**(若適用):<一個邊界案例的驗證步驟>
36
-
37
- ## 完成檢查
38
- - [ ] 程式碼符合 `spec/spec.md` 約束
39
- - [ ] 手動驗收通過
40
- - [ ] 關鍵邊界情況有 log(INFO/WARNING/ERROR)
41
- - [ ] 若改動 CSV 或模型契約,spec 已同步
42
-
43
- ## 進度紀錄
44
- - <YYYY-MM-DD HH:MM>:<進度更新或決策>
45
-
46
- ---
47
-
48
- 附錄 A:Commit 信息模板
49
-
50
- ```
51
- feat(task T-<YYYYMMDD>-<shortname>): <簡述>
52
-
53
- - <子任務/變更點 1>
54
- - <子任務/變更點 2>
55
-
56
- Refs: T-<YYYYMMDD>-<shortname>
57
- ```
58
-
59
- 附錄 B:分支/PR 命名
60
- - 分支:`feature/T-<YYYYMMDD>-<shortname>`
61
- - PR 標題:`T-<YYYYMMDD>-<shortname>: <簡述>`
62
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
spec/00-overview.md DELETED
@@ -1,123 +0,0 @@
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 DELETED
@@ -1,322 +0,0 @@
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 DELETED
@@ -1,289 +0,0 @@
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 DELETED
@@ -1,416 +0,0 @@
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 DELETED
@@ -1,360 +0,0 @@
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 DELETED
@@ -1,192 +0,0 @@
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 DELETED
@@ -1,167 +0,0 @@
1
- # 迭代計畫 (plan.md) — 震央資訊 JSON 管理化 [ARCHIVED]
2
-
3
- > ⏹️ **本迭代已完成** — Sprint 003 計畫已完成並歸檔
4
- > 下次迭代時將重新生成 `spec/plan.md`。若需重新執行此迭代,請參考 `changelog.md` 的 Sprint 003 摘要。
5
-
6
- ## 需求重述
7
-
8
- 使用者需要將震央(地震震心)位置資訊集中管理於 JSON 檔案,而非透過 UI 輸入框讓使用者編輯。具體要求:
9
-
10
- 1. **震央資訊 JSON 化**:建立 `waveform/event.json`,儲存每個地震事件的震央經緯度、深度等屬性
11
- 2. **UI 移除編輯框**:移除 Gradio 介面中的「震央經度」、「震央緯度」輸入框(已無法編輯)
12
- 3. **震央顯示唯讀**:地圖上顯示震央位置(紅星標記),但不提供使用者修改介面
13
- 4. **事件綁定自動注入**:選擇事件時,自動從 JSON 讀取對應的震央位置並更新地圖
14
-
15
- ## 迭代目標
16
-
17
- 通過資料集中管理原則,簡化 UI、提升使用者體驗,並確保震央資訊的一致性與可維護性。
18
-
19
- ### 子目標
20
- - 建立 `waveform/event.json` 資料結構
21
- - 更新 Gradio 介面:移除經緯度輸入框
22
- - 重構事件切換邏輯:從 JSON 自動讀取震央資訊
23
- - 驗證地圖顯示的震央位置正確性
24
-
25
- ## 範圍
26
-
27
- ### 新增檔案
28
- - **`waveform/event.json`**:地震事件元資料中心
29
-
30
- ### 修改檔案
31
- - **`app.py`**:
32
- - 新增 `load_earthquake_metadata()` 函數(從 JSON 讀取震央資訊)
33
- - 移除 Gradio 中的 `epicenter_lon_input` 與 `epicenter_lat_input` 組件
34
- - 調整事件綁定邏輯,自動注入震央資訊
35
- - 更新 callback 函數簽名(移除經緯度參數)
36
-
37
- ### 不涉及的檔案
38
- - 規格模組(`spec/00-04`):資料結構無變化(仍用經緯度)
39
- - 測站表、Vs30、波形檔案:無異動
40
-
41
- ## 驗收標準 (Definition of Done)
42
-
43
- 1. ✅ `waveform/event.json` 已建立,包含所有事件的震央信息(經度、緯度、深度等)
44
- 2. ✅ Gradio 介面移除經緯度輸入框;介面仍顯示參數但為唯讀或隱藏
45
- 3. ✅ 點擊事件下拉菜單時,自動從 JSON 讀取並更新地圖上的震央標記
46
- 4. ✅ 地圖正確顯示震央位置(紅星,帶有座標提示)
47
- 5. ✅ 所有 callback 函數的簽名已更新(不再接收 `epicenter_lat`、`epicenter_lon` 參數)
48
- 6. ✅ 代碼無編譯/語法錯誤;應用可正常啟動
49
- 7. ✅ 冒煙測試通過:事件切換、波形載入、預測執行正常
50
-
51
- ## 高層任務列表
52
-
53
- | 序號 | 任務 | 說明 |
54
- |-----|------|------|
55
- | T-001 | 建立 `waveform/event.json` | 設計資料結構、填入初始事件資料 |
56
- | T-002 | 新增 `load_earthquake_metadata()` | 從 JSON 讀取震央資訊;缺失時提示 ERROR |
57
- | T-003 | 修改 Gradio 介面佈局 | 移除經緯度輸入框;在狀態區顯示震央座標(唯讀) |
58
- | T-004 | 更新 callback 簽名 | 移除所有 callback 中的 `epicenter_lat`、`epicenter_lon` 參數 |
59
- | T-005 | 事件切換自動注入 | `event_dropdown.change()` 自動從 JSON 注入震央資訊 |
60
- | T-006 | 測試與驗證 | 冒煙測試、地圖顯示確認 |
61
-
62
- ## Invariants 對齐
63
-
64
- | 不變條件 | 狀態 | 說明 |
65
- |--------|------|------|
66
- | 波形輸入 | ✅ Not Impacted | 時間窗、取樣率、補零邏輯維持不變 |
67
- | 測站選擇 | ✅ Not Impacted | 最多 25 站,距離排序邏輯完全不變 |
68
- | 推論引擎 | ✅ Not Impacted | 模型輸入/輸出形狀、MDN 轉換邏輯不變 |
69
- | 資料契約 | ✅ Not Impacted | CSV 欄位、必填項、檔案格式不變 |
70
- | 地圖高度 | ✅ Not Impacted | 仍固定 800px |
71
- | 錯誤處理 | ✅ Not Impacted | 降級策略、日誌等級維持不變 |
72
- | 預設資源 | ✅ Not Impacted | 模型、Vs30、波形預裝策略不變 |
73
-
74
- ## 風險與回滾
75
-
76
- ### 主要風險
77
-
78
- 1. **JSON 檔案缺失或格式錯誤**
79
- - **風險**:應用啟動時無法讀取震央資訊,導致初始化失敗
80
- - **緩解**:
81
- - 在 `load_earthquake_metadata()` 中添加異常捕捉
82
- - 若 JSON 缺失,記錄 ERROR log 並使用預設震央座標 (121.57, 23.88)
83
- - 允許應用繼續啟動(降級策略)
84
- - **回滾**:恢復 UI 輸入框,手動輸入震央座標
85
-
86
- 2. **事件下拉菜單與 JSON 鍵值不一致**
87
- - **風險**:選擇事件時找不到對應的 JSON 記錄
88
- - **緩解**:
89
- - 事件名稱與 JSON 鍵名保持一致
90
- - 在轉換時進行 key mapping 驗證
91
- - 提示使用者:未找到該事件的震央資訊,使用預設座標
92
- - **回滾**:保留舊邏輯作為備用
93
-
94
- 3. **Callback 參數不一致導致崩潰**
95
- - **風險**:callback 簽名改動後,某些 callback 仍期望接收經緯度參數
96
- - **緩解**:
97
- - 全局搜尋所有 callback 綁定,確保一致性
98
- - 在 callback 內部從 JSON 讀取座標,而非參數傳遞
99
- - **回滾**:恢復原始 callback 簽名
100
-
101
- ### 最小可逆步驟
102
-
103
- 1. 建立 JSON 檔案
104
- 2. 新增 `load_earthquake_metadata()` 並測試讀取邏輯
105
- 3. 更新第一個 callback���驗證運作
106
- 4. 逐步更新其他 callback
107
-
108
- ## 冒煙測試
109
-
110
- ### 測試 1:JSON 檔案與初始化
111
- **步驟**
112
- 1. 確認 `waveform/event.json` 存在且格式有效
113
- 2. 啟動應用
114
- 3. 檢查日誌:應有「震央資訊載入完成」提示
115
-
116
- **預期結果**
117
- - 應用正常啟動
118
- - 日誌確認 JSON 讀取成功
119
- - 無 FileNotFoundError 或 JSONDecodeError
120
-
121
- ### 測試 2:Gradio 介面佈局
122
- **步驟**
123
- 1. 開啟應用 UI
124
- 2. 檢查參數輸入區
125
-
126
- **預期結果**
127
- - 經緯度輸入框已移除
128
- - 狀態區仍顯示震央座標(或在地圖懸停提示中)
129
- - 介面整潔無破損
130
-
131
- ### 測試 3:事件切換自動更新
132
- **步驟**
133
- 1. 應用已啟動
134
- 2. 從事件下拉菜單選擇一個事件
135
- 3. 觀察地圖
136
-
137
- **預期結果**
138
- - 地圖中心自動移動到新震央
139
- - 紅星標記位置正確
140
- - 座標提示(hover)顯示正確的經緯度
141
-
142
- ### 測試 4:完整工作流
143
- **步驟**
144
- 1. 選擇事件
145
- 2. 調整時間滑桿
146
- 3. 點擊「載入波形」
147
- 4. 點擊「執行預測」
148
-
149
- **預期結果**
150
- - 波形地圖、波形圖、預測結果正常顯示
151
- - 震央位置在地圖上始終正確
152
- - 無參數傳遞錯誤日誌
153
-
154
- ---
155
-
156
- ## ⏸️ 暫停點
157
-
158
- **上述計畫已準備完成。請在下方進行審核與調整:**
159
-
160
- 1. **架構確認**:`waveform/event.json` 的資料結構是否滿足需求?
161
- 2. **命名確認**:函數名稱 `load_earthquake_metadata()` 是否合適?
162
- 3. **降級策略**:JSON 缺失時使用預設座標 (121.57, 23.88),是否可接受?
163
- 4. **優先級**:任務順序是否需要調整?
164
-
165
- 完成編輯後,回覆「**確認**」或「**核准**」即可進行下一階段任務拆解(執行 `/project.task`)。
166
-
167
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
spec/task.md DELETED
@@ -1,676 +0,0 @@
1
- # 詳細任務拆解 (task.md) — 震央資訊 JSON 管理化 [ARCHIVED]
2
-
3
- > ⏹️ **本迭代已完成** — Sprint 003 任務已全數完成並歸檔
4
- > 下次迭代時將重新生成 `spec/task.md`。若需重新執行此迭代,請參考 `changelog.md` 的 Sprint 003 摘要。
5
-
6
- ## 📋 完成摘要
7
-
8
- | 任務 | 狀態 | 說明 |
9
- |-----|------|------|
10
- | T-001 | ✅ 完成 | `waveform/event.json` 已建立,包含 YYYYMMDD 格式的 event_id |
11
- | T-002 | ✅ 完成 | `load_earthquake_metadata()` 函數已新增,降級策略完整 |
12
- | T-003 | ✅ 完成 | Gradio 介面已移除經緯度輸入框,新增唯讀座標顯示 |
13
- | T-004 | ✅ 完成 | 5 個函數簽名已更新(移除 epicenter 參數) |
14
- | T-005 | ✅ 完成 | Callback 綁定已重構,座標自動注入機制正常運作 |
15
- | T-006 | ✅ 完成 | 程式碼語法驗證通過,無編譯錯誤 |
16
-
17
- ---
18
-
19
- ## T-001: 建立 `waveform/event.json`
20
-
21
- ### 描述
22
- 設計並建立地震事件元資料 JSON 檔案,集中管理所有事件的震央位置、深度、時間等屬性。
23
-
24
- ### 受影響檔案
25
- - **新增**:`waveform/event.json`
26
-
27
- ### 具體變更點
28
-
29
- #### `waveform/event.json` (新增檔案)
30
- - **位置**:專案根目錄下 `waveform/` 資料夾
31
- - **格式**:JSON,包含以下結構:
32
- ```json
33
- {
34
- "events": [
35
- {
36
- "event_id": "20240403",
37
- "event_name": "0403花蓮地震 (2024)",
38
- "epicenter_lat": 23.88,
39
- "epicenter_lon": 121.57,
40
- "depth_km": 25.0,
41
- "magnitude": 7.2,
42
- "timestamp": "2024-04-03T07:58:00Z",
43
- "mseed_file": "waveform/20240403.mseed",
44
- "intensity_map_file": "intensity_map/20240403.png"
45
- }
46
- ]
47
- }
48
- ```
49
-
50
- ### 資料欄位說明
51
- | 欄位 | 型態 | 說明 |
52
- |-----|-----|------|
53
- | `event_id` | 字串 | 事件唯一識別碼,使用 YYYYMMDD 格式(e.g., "20240403"),對應波形檔案與震度圖命名 |
54
- | `event_name` | 字串 | 事件顯示名稱,與 EARTHQUAKE_EVENTS 的鍵名保持一致 |
55
- | `epicenter_lat` | 浮點 | 震央緯度(度) |
56
- | `epicenter_lon` | 浮點 | 震央經度(度) |
57
- | `depth_km` | 浮點 | 震源深度(公里),選填 |
58
- | `magnitude` | 浮點 | 地震規模,選填 |
59
- | `timestamp` | 字串 | ISO 8601 時間戳,選填 |
60
- | `mseed_file` | 字串 | 對應的 MSEED 波形檔案路徑(e.g., "waveform/20240403.mseed") |
61
- | `intensity_map_file` | 字串 | 對應的實際震度圖檔案路徑(e.g., "intensity_map/20240403.png"),選填 |
62
-
63
- ### 驗收標準
64
- - [ ] `waveform/event.json` 檔案已建立
65
- - [ ] JSON 格式有效(可通過 `json.load()` 解析無誤)
66
- - [ ] 包含至少一個事件記錄(0403花蓮地震)
67
- - [ ] `event_id` 使用 YYYYMMDD 格式(e.g., "20240403")且與波形檔案、震度圖檔案命名對應
68
- - [ ] 事件 `event_name` 與 app.py 中 `EARTHQUAKE_EVENTS` 的鍵名一致
69
- - [ ] 震央座標範圍正確(緯度 5-30°,經度 110-123°)
70
-
71
- ### 風險等級
72
- **低** — 單純新增檔案,不涉及邏輯變更
73
-
74
- ---
75
-
76
- ## T-002: 新增 `load_earthquake_metadata()` 函數
77
-
78
- ### 描述
79
- 新增從 JSON 檔案讀取地震事件元資料的函數,應用啟動時調用以初始化震央資訊字典。
80
-
81
- ### 受影響檔案
82
- - **修改**:`app.py`
83
-
84
- ### 具體變更點
85
-
86
- #### `app.py` (新增函數,位置:在全域初始化區段,約 L80-90)
87
- ```python
88
- # 新增位置:在「載入目標測站」區塊之後 (L110 左右)
89
- def load_earthquake_metadata(event_json_path="waveform/event.json"):
90
- """
91
- 從 JSON 檔案讀取地震事件元資料(震央經緯度、深度等)
92
-
93
- 參數:
94
- event_json_path: event.json 的相對或絕對路徑
95
-
96
- 返回:
97
- dict: {event_name -> {"epicenter_lat", "epicenter_lon", "depth_km", ...}}
98
- 若檔案缺失或無效,使用預設座標 (121.57, 23.88) 並記錄警告
99
- """
100
- earthquake_metadata = {}
101
-
102
- try:
103
- import json
104
- with open(event_json_path, 'r', encoding='utf-8') as f:
105
- data = json.load(f)
106
-
107
- if "events" not in data:
108
- logger.warning(f"{event_json_path} 缺少 'events' 鍵,使用預設座標")
109
- return {}
110
-
111
- # 將事件列表轉換為以 event_name 為鍵的字典
112
- for event in data["events"]:
113
- event_name = event.get("event_name")
114
- if event_name:
115
- earthquake_metadata[event_name] = {
116
- "epicenter_lat": event.get("epicenter_lat", 23.88),
117
- "epicenter_lon": event.get("epicenter_lon", 121.57),
118
- "depth_km": event.get("depth_km", None),
119
- "magnitude": event.get("magnitude", None),
120
- }
121
- logger.info(f"載入事件: {event_name} | 震央: ({event['epicenter_lon']}, {event['epicenter_lat']})")
122
-
123
- logger.info(f"地震事件元資料載入完成(共 {len(earthquake_metadata)} 個事件)")
124
- return earthquake_metadata
125
-
126
- except FileNotFoundError:
127
- logger.error(f"事件元資料檔案缺失: {event_json_path}")
128
- logger.warning("將使用預設震央座標 (121.57, 23.88)")
129
- return {}
130
-
131
- except json.JSONDecodeError as e:
132
- logger.error(f"事件元資料 JSON 解析失敗: {e}")
133
- logger.warning("將使用預設震央座標 (121.57, 23.88)")
134
- return {}
135
-
136
- except Exception as e:
137
- logger.error(f"讀取事件元資料時發生未預期的錯誤: {e}")
138
- logger.warning("將使用預設震央座標 (121.57, 23.88)")
139
- return {}
140
- ```
141
-
142
- #### `app.py` (全域初始化,位置:L110-115,在目標測站載入完成後)
143
- ```python
144
- # ...existing code...
145
-
146
- # 載入地震事件元資料
147
- earthquake_metadata = load_earthquake_metadata("waveform/event.json")
148
- if not earthquake_metadata:
149
- logger.warning("無法載入事件元資料,應用將使用預設震央座標")
150
-
151
- # ...existing code...
152
- ```
153
-
154
- ### 驗收標準
155
- - [ ] 函數 `load_earthquake_metadata()` 已定義且無語法錯誤
156
- - [ ] 應用啟動時自動呼叫該函數
157
- - [ ] JSON 檔案存在時,正確解析並填充 `earthquake_metadata` 字典
158
- - [ ] JSON 檔案缺失時,記錄 WARNING log 並返回空字典(不中斷啟動)
159
- - [ ] JSON 格式錯誤時,記錄 ERROR log 並返回空字典(不中斷啟動)
160
- - [ ] 日誌中可見「地震事件元資料載入完成」或「無法載入事件元資料」提示
161
-
162
- ### 風險等級
163
- **中** — 涉及全域初始化邏輯,但降級策略完整
164
-
165
- ---
166
-
167
- ## T-003: 修改 Gradio 介面佈局
168
-
169
- ### 描述
170
- 移除 Gradio 介面中的「震央經度」、「震央緯度」輸入框,改為在狀態區顯示唯讀的震央座標。
171
-
172
- ### 受影響檔案
173
- - **修改**:`app.py`
174
-
175
- ### 具體變更點
176
-
177
- #### `app.py` (移除輸入框,位置:L1265-1276)
178
- **原代碼:**
179
- ```python
180
- gr.Markdown("### 震央位置")
181
- with gr.Row():
182
- epicenter_lon_input = gr.Number(value=121.57, label="震央經度")
183
- epicenter_lat_input = gr.Number(value=23.88, label="震央緯度")
184
- ```
185
-
186
- **新代碼:**
187
- ```python
188
- gr.Markdown("### 震央位置")
189
- gr.Markdown("> 震央位置由選定的地震事件自動決定,並在地圖上標示")
190
- epicenter_info_display = gr.Textbox(
191
- label="震央座標",
192
- value="緯度: 23.88° | 經度: 121.57°",
193
- interactive=False,
194
- lines=1
195
- )
196
- ```
197
-
198
- **說明:**
199
- - 移除 `epicenter_lon_input` 與 `epicenter_lat_input` 組件
200
- - 新增 `epicenter_info_display` 作為唯讀文本框,顯示當前選定事件的震央座標
201
- - 使用 Markdown 提示該欄位由事件自動決定
202
-
203
- ### 驗收標準
204
- - [ ] Gradio 介面不再顯示經緯度輸入框
205
- - [ ] 新增的唯讀文本框 `epicenter_info_display` 正常顯示
206
- - [ ] 應用啟動時,該文本框顯示預設値「緯度: 23.88° | 經度: 121.57°」
207
- - [ ] 介面佈局整潔,無顯示破損
208
-
209
- ### 風險等級
210
- **低** — 單純 UI 元件移除與新增
211
-
212
- ---
213
-
214
- ## T-004: 更新 callback 簽名
215
-
216
- ### 描述
217
- 更新所有使用震央座標的 callback 函數簽名,移除 `epicenter_lat` 與 `epicenter_lon` 參數。改由在 callback 內部從 `earthquake_metadata` 字典讀取。
218
-
219
- ### 受影響檔案
220
- - **修改**:`app.py`
221
-
222
- ### 具體變更點
223
-
224
- #### 變更 1: `select_nearest_stations()` 函數簽名 (L440)
225
- **原:**
226
- ```python
227
- def select_nearest_stations(st, epicenter_lat, epicenter_lon, n_stations=25):
228
- ```
229
-
230
- **新:**
231
- ```python
232
- def select_nearest_stations(st, epicenter_lat, epicenter_lon, n_stations=25):
233
- # 保持原簽名不變,因為內部邏輯不涉及全域狀態
234
- # 此函數由 load_and_display_waveform() 與 predict_intensity() 呼叫
235
- # 這兩個函數會先從 earthquake_metadata 取出座標再傳入
236
- ```
237
-
238
- #### 變更 2: `load_and_display_waveform()` 函數簽名 (L1001)
239
- **原:**
240
- ```python
241
- def load_and_display_waveform(event_name, start_time, duration, epicenter_lon, epicenter_lat):
242
- ...
243
- selected_stations = select_nearest_stations(st, epicenter_lat, epicenter_lon, n_stations=25)
244
- ...
245
- ```
246
-
247
- **新:**
248
- ```python
249
- def load_and_display_waveform(event_name, start_time, duration):
250
- """
251
- 載入並顯示波形
252
-
253
- 參數:
254
- event_name: 事件名稱(鍵值)
255
- start_time: 開始時間(秒)
256
- duration: 時間窗長度(秒)
257
-
258
- 返回值、邏輯不變,但從全域 earthquake_metadata 取得震央座標
259
- """
260
- # 從全域 earthquake_metadata 讀取震央座標
261
- epicenter_lat, epicenter_lon = _get_epicenter_coords(event_name)
262
-
263
- # 其餘邏輯不變
264
- ...
265
- ```
266
-
267
- #### 變更 3: `predict_intensity()` 函數簽名 (L1046)
268
- **原:**
269
- ```python
270
- def predict_intensity(event_name, start_time, duration, epicenter_lon, epicenter_lat):
271
- ...
272
- selected_stations = select_nearest_stations(st, epicenter_lat, epicenter_lon, n_stations=25)
273
- ...
274
- ```
275
-
276
- **新:**
277
- ```python
278
- def predict_intensity(event_name, start_time, duration):
279
- """
280
- 執行模型推論並產生震度預測圖
281
-
282
- 參數:
283
- event_name: 事件名稱(鍵值)
284
- start_time: 開始時間(秒)
285
- duration: 時間窗長度(秒)
286
-
287
- 返回值、邏輯不變,但從全域 earthquake_metadata 取得震央座標
288
- """
289
- # 從全域 earthquake_metadata 讀取震央座標
290
- epicenter_lat, epicenter_lon = _get_epicenter_coords(event_name)
291
-
292
- # 其餘邏輯不變
293
- ...
294
- ```
295
-
296
- #### 變更 4: `on_event_change()` 函數簽名 (L864)
297
- **原:**
298
- ```python
299
- def on_event_change(event_name, start_time, duration, epicenter_lon, epicenter_lat):
300
- ...
301
- ```
302
-
303
- **新:**
304
- ```python
305
- def on_event_change(event_name, start_time, duration):
306
- """
307
- 事件切換或波形參數變更時的回調
308
-
309
- 參數:
310
- event_name: 事件名稱
311
- start_time: 開始時間
312
- duration: 時間窗長度
313
-
314
- 返回值:(station_map_html, waveform_plot, info_text, observed_img)
315
- """
316
- ...
317
- ```
318
-
319
- #### 變更 5: `on_full_workflow()` 函數簽名 (L1220+)
320
- **原:**
321
- ```python
322
- def on_full_workflow(event_name, start_time, duration, epicenter_lon, epicenter_lat):
323
- ...
324
- ```
325
-
326
- **新:**
327
- ```python
328
- def on_full_workflow(event_name, start_time, duration):
329
- """
330
- 執行完整的工作流:波形載入 → 測站選擇 → 推論 → 結果展示
331
-
332
- 參數:
333
- event_name: 事件名稱
334
- start_time: 開始時間
335
- duration: 時間窗長度
336
-
337
- 返回值:
338
- (station_map_html, waveform_plot, info_text, predicted_map_html, stats_text, observed_img)
339
- """
340
- ...
341
- ```
342
-
343
- #### 新增輔助函數:`_get_epicenter_coords()` (位置:L110+,在 load_earthquake_metadata 之後)
344
- ```python
345
- def _get_epicenter_coords(event_name):
346
- """
347
- 從全域 earthquake_metadata 獲取指定事件的震央座標
348
-
349
- 參數:
350
- event_name: 事件名稱(EARTHQUAKE_EVENTS 的鍵值)
351
-
352
- 返回:
353
- tuple: (epicenter_lat, epicenter_lon)
354
- 若事件不存在或座標缺失,返回預設座標 (23.88, 121.57)
355
- """
356
- if event_name in earthquake_metadata:
357
- metadata = earthquake_metadata[event_name]
358
- lat = metadata.get("epicenter_lat", 23.88)
359
- lon = metadata.get("epicenter_lon", 121.57)
360
- return lat, lon
361
- else:
362
- logger.warning(f"未找到事件 '{event_name}' 的元資料,使用預設震央座標")
363
- return 23.88, 121.57
364
- ```
365
-
366
- ### 驗收標準
367
- - [ ] 所有受影響函數的簽名已移除 `epicenter_lat` 與 `epicenter_lon` 參數
368
- - [ ] 新增 `_get_epicenter_coords()` 輔助函數
369
- - [ ] 所有函數內部邏輯正確地從全域 `earthquake_metadata` 或 `_get_epicenter_coords()` 取得座標
370
- - [ ] 應用啟動無語法錯誤
371
- - [ ] 代碼審查通過:沒有遺漏的函數簽名
372
-
373
- ### 風險等級
374
- **高** — 涉及多個函數簽名變更,可能導致 callback 綁定失敗
375
-
376
- ---
377
-
378
- ## T-005: 事件切換自動注入 + Callback 綁定重構
379
-
380
- ### 描述
381
- 重構 Gradio callback 綁定邏輯,確保:
382
- 1. `event_dropdown.change()` 事件自動從 JSON 注入震央資訊
383
- 2. 更新 `epicenter_info_display` 文本框顯示新的震央座標
384
- 3. 所有 callback inputs/outputs 綁定正確移除經緯度參數
385
-
386
- ### 受影響檔案
387
- - **修改**:`app.py`
388
-
389
- ### 具體變更點
390
-
391
- #### `app.py` (Callback 綁定重構,位置:L1300-1340)
392
-
393
- **原代碼:**
394
- ```python
395
- # 綁定事件
396
- event_dropdown.change(
397
- fn=on_full_workflow,
398
- inputs=[event_dropdown, start_slider, duration_slider, epicenter_lon_input, epicenter_lat_input],
399
- outputs=[input_station_map, waveform_plot, info_output, predicted_intensity_map, stats_output, observed_intensity_image]
400
- )
401
-
402
- load_waveform_btn.click(
403
- fn=load_and_display_waveform,
404
- inputs=[event_dropdown, start_slider, duration_slider, epicenter_lon_input, epicenter_lat_input],
405
- outputs=[input_station_map, waveform_plot, info_output, predict_btn]
406
- )
407
-
408
- predict_btn.click(
409
- fn=predict_intensity,
410
- inputs=[event_dropdown, start_slider, duration_slider, epicenter_lon_input, epicenter_lat_input],
411
- outputs=[observed_intensity_image, predicted_intensity_map, stats_output]
412
- )
413
-
414
- # 應用啟動時自動執行完整工作流
415
- demo.load(
416
- fn=on_full_workflow,
417
- inputs=[event_dropdown, start_slider, duration_slider, epicenter_lon_input, epicenter_lat_input],
418
- outputs=[input_station_map, waveform_plot, info_output, predicted_intensity_map, stats_output, observed_intensity_image]
419
- )
420
- ```
421
-
422
- **新代碼:**
423
- ```python
424
- # 新增:事件切換時更新震央座標顯示的內部函數
425
- def update_epicenter_display(event_name):
426
- """事件切換時,更新震央座標顯示文本框"""
427
- lat, lon = _get_epicenter_coords(event_name)
428
- return f"緯度: {lat:.2f}° | 經度: {lon:.2f}°"
429
-
430
- # 綁定事件
431
- event_dropdown.change(
432
- fn=lambda event_name, start_time, duration: (
433
- *on_full_workflow(event_name, start_time, duration),
434
- update_epicenter_display(event_name)
435
- ),
436
- inputs=[event_dropdown, start_slider, duration_slider],
437
- outputs=[input_station_map, waveform_plot, info_output, predicted_intensity_map, stats_output, observed_intensity_image, epicenter_info_display]
438
- )
439
-
440
- load_waveform_btn.click(
441
- fn=lambda event_name, start_time, duration: (
442
- *load_and_display_waveform(event_name, start_time, duration),
443
- predict_btn
444
- ),
445
- inputs=[event_dropdown, start_slider, duration_slider],
446
- outputs=[input_station_map, waveform_plot, info_output, predict_btn]
447
- )
448
-
449
- predict_btn.click(
450
- fn=predict_intensity,
451
- inputs=[event_dropdown, start_slider, duration_slider],
452
- outputs=[observed_intensity_image, predicted_intensity_map, stats_output]
453
- )
454
-
455
- # 應用啟動時自動執行完整工作流
456
- demo.load(
457
- fn=lambda event_name, start_time, duration: (
458
- *on_full_workflow(event_name, start_time, duration),
459
- update_epicenter_display(event_name)
460
- ),
461
- inputs=[event_dropdown, start_slider, duration_slider],
462
- outputs=[input_station_map, waveform_plot, info_output, predicted_intensity_map, stats_output, observed_intensity_image, epicenter_info_display]
463
- )
464
- ```
465
-
466
- **說明:**
467
- - 移除所有 `inputs` 中的 `epicenter_lon_input` 與 `epicenter_lat_input`
468
- - 新增 outputs 中的 `epicenter_info_display` 以同步更新座標顯示
469
- - 事件切換時自動調用 `update_epicenter_display()` 更新座標文本框
470
-
471
- ### 驗收標準
472
- - [ ] 所有 callback 綁定已更新,inputs/outputs 參數列表正確
473
- - [ ] 應用啟動時執行完整工作流並顯示正確的震央座標
474
- - [ ] 點擊事件下拉菜單時,座標顯示框自動更新
475
- - [ ] 點擊「載入波形」按鈕時,正確讀取新的震央座標
476
- - [ ] 點擊「執行預測」按鈕時,正確讀取新的震央座標
477
- - [ ] 地圖上的紅星標記始終指向正確的震央位置
478
- - [ ] 無 callback 參數不匹配的錯誤
479
-
480
- ### 風險等級
481
- **高** — Callback 綁定任何錯誤都將導致應用崩潰
482
-
483
- ---
484
-
485
- ## T-006: 測試與驗證
486
-
487
- ### 描述
488
- 執行冒煙測試,驗證整個迭代的所有組件運作正常。
489
-
490
- ### 受影響檔案
491
- - **測試對象**:`app.py`、`waveform/event.json`
492
-
493
- ### 測試用例
494
-
495
- #### ✅ 測試 1:JSON 檔案與初始化
496
- **步驟**
497
- 1. 確認 `waveform/event.json` 存在且格式有效
498
- 2. 啟動應用
499
- 3. 檢查日誌輸出:應有「地震事件元資料載入完成」提示
500
-
501
- **預期結果**
502
- - ✅ 應用正常啟動(無 FileNotFoundError)
503
- - ✅ 日誌確認 JSON 讀取成功
504
- - ✅ `earthquake_metadata` 全域變數被正確填充
505
- - ✅ 無 JSONDecodeError
506
-
507
- **驗收條件**
508
- - [ ] 應用啟動完成
509
- - [ ] 日誌顯示「地震事件元資料載入完成」
510
-
511
- ---
512
-
513
- #### ✅ 測試 2:Gradio 介面佈局
514
- **步驟**
515
- 1. 開啟應用 UI(http://localhost:7860)
516
- 2. 檢查「輸入參數」區域
517
-
518
- **預期結果**
519
- - ✅ 經緯度輸入框(原有的兩個 Number 欄位)已不存在
520
- - ✅ 新增「震央座標」唯讀文本框,顯示「緯度: 23.88° | 經度: 121.57°」
521
- - ✅ 有提示文字說「震央位置由選定的地震事件自動決定」
522
- - ✅ 介面整潔無破損
523
-
524
- **驗收條件**
525
- - [ ] UI 中無經緯度輸入框
526
- - [ ] 震央座標文本框存在且可讀取
527
- - [ ] 提示文字清晰可見
528
-
529
- ---
530
-
531
- #### ✅ 測試 3:事件切換自動更新
532
- **步驟**
533
- 1. 應用已啟動
534
- 2. 觀察地圖:紅星標記位置與座標顯示框
535
- 3. 在事件下拉菜單中選擇同一事件(或若有其他事件,嘗試切換)
536
- 4. 觀察座標顯示框與地圖是否同步更新
537
-
538
- **預期結果**
539
- - ✅ 應用啟動時,座標顯示框顯示「緯度: 23.88° | 經度: 121.57°」
540
- - ✅ 地圖中心位置在 (23.88, 121.57)
541
- - ✅ 紅星標記位置正確
542
- - ✅ 若有多個事件,切換時座標與地圖同時更新
543
-
544
- **驗收條件**
545
- - [ ] 座標顯示框初始值正確
546
- - [ ] 地圖紅星位置正確
547
- - [ ] 地圖中心正確
548
-
549
- ---
550
-
551
- #### ✅ 測試 4:完整工作流
552
- **步驟**
553
- 1. 應用已啟動
554
- 2. 調整時間滑桿(開始時間、時間長度)
555
- 3. 點擊「載入波形」按鈕
556
- 4. 觀察波形地圖、波形圖、狀態信息
557
- 5. 點擊「執行預測」按鈕
558
- 6. 觀察預測結果、地圖、統計信息
559
-
560
- **預期結果**
561
- - ✅ 點擊「載入波形」後,波形地圖、波形圖正常顯示
562
- - ✅ 狀態信息中顯示「震央位置: (緯度, 經度)」
563
- - ✅ 測站分布地圖上的紅星標記位置正確
564
- - ✅ 點擊「執行預測」後,預測結果正常顯示
565
- - ✅ 預測地圖上的震央紅星位置正確
566
- - ✅ 無「參數不匹配」或「缺少參數」錯誤
567
-
568
- **驗收條件**
569
- - [ ] 波形載入成功
570
- - [ ] 狀態信息顯示正確的震央座標
571
- - [ ] 預測執行成功
572
- - [ ] 所有地圖上的紅星位置正確
573
- - [ ] 日誌無錯誤(僅允許 WARNING)
574
-
575
- ---
576
-
577
- #### ✅ 測試 5:日誌驗證
578
- **步驟**
579
- 1. 啟動應用
580
- 2. 執行測試 1-4
581
- 3. 收集所有日誌輸出
582
-
583
- **預期結果**
584
- - ✅ 應用啟動時有「地震事件元資料載入完成」日誌
585
- - ✅ 波形載入時有「選擇距離震央最近的測站」日誌(帶有座標)
586
- - ✅ 預測時有「完成所有測站的預測」日誌
587
- - ✅ 無 ERROR 日誌(除非預期的異常處理)
588
-
589
- **驗收條件**
590
- - [ ] 日誌中包含完整的初始化流程
591
- - [ ] 無意外的 ERROR 或 CRITICAL 日誌
592
- - [ ] 所有座標相關操作都有對應的日誌記錄
593
-
594
- ---
595
-
596
- ### 驗收標準 (總結)
597
- - [ ] T-001 ✅ JSON 檔案已建立並包含正確的結構
598
- - [ ] T-002 ✅ `load_earthquake_metadata()` 函數正常工作
599
- - [ ] T-003 ✅ 介面移除經緯度輸入框,新增座標顯示框
600
- - [ ] T-004 ✅ 所有 callback 函數簽名已更新
601
- - [ ] T-005 ✅ Callback 綁定邏輯正確,震央座標同步更新
602
- - [ ] T-006 ✅ 所有冒煙測試通過
603
-
604
- ### 風險等級
605
- **中** — 綜合測試,涉及整個系統的協調運作
606
-
607
- ---
608
-
609
- ## 變更摘要 (Impact Analysis)
610
-
611
- ### 受影響的核心模組
612
- | 模組 | 變更類型 | 影響範圍 |
613
- |-----|--------|--------|
614
- | **初始化** | 新增 | 新增全域 `earthquake_metadata` 字典 + `load_earthquake_metadata()` 函數 |
615
- | **UI 佈局** | 移除 + 新增 | 移除 2 個輸入框,新增 1 個顯示框 + 1 個輔助函數 `update_epicenter_display()` |
616
- | **函數簽名** | 修改 | 5 個函數簽名更新(移除經緯度參數) |
617
- | **Callback 邏輯** | 修改 | 3 個 callback 綁定邏輯調整,新增座標自動注入 |
618
- | **資料流** | 修改 | 原先透過 UI 參數傳遞的座標,改由全域字典提供 |
619
-
620
- ### 不變條件檢查
621
- | 項目 | 影響 | 說明 |
622
- |-----|------|------|
623
- | 波形輸入 | ✅ 無 | 訊號處理、補零、取樣率邏輯完全不變 |
624
- | 測站選擇 | ✅ 無 | 距離排序、去重、25 站上限邏輯不變 |
625
- | 推論引擎 | ✅ 無 | CNN、Transformer、MDN 模型邏輯完全不變 |
626
- | 資料契約 | ✅ 無 | CSV 欄位、模型輸入形狀不變 |
627
- | 錯誤處理 | ✅ 無 | 降級策略、日誌等級維持不變 |
628
-
629
- ### 新增資料依賴
630
- - **新增檔案**:`waveform/event.json`(必須存在,缺失時使用預設值)
631
- - **新增全域變數**:`earthquake_metadata` (dict)
632
-
633
- ### 向後相容性
634
- - ✅ **完全向後相容**:
635
- - JSON 檔案缺失時,應用可正常啟動(降級至預設座標)
636
- - 所有 callback 參數變更是內部實現,使用者介面不變
637
- - 模型推論邏輯、資料結構、檔案格式均無變化
638
-
639
- ---
640
-
641
- ## ⏸️ 暫停點
642
-
643
- **上述任務拆解已完成。請進行最終審核:**
644
-
645
- ### 檢查清單
646
-
647
- 1. **架構確認**:
648
- - [ ] T-001 JSON 檔案結構是否合適?
649
- - [ ] T-002 `load_earthquake_metadata()` 函數設計是否可行?
650
- - [ ] 新增輔助函數 `_get_epicenter_coords()` 是否必要?
651
-
652
- 2. **技術確認**:
653
- - [ ] 函數簽名變更(T-004)的清單是否完整?
654
- - [ ] Callback 綁定邏輯(T-005)是否正確?
655
- - [ ] Lambda 封裝在 Callback 中是否適當?
656
-
657
- 3. **測試確認**:
658
- - [ ] 測試用例(T-006)的 4 個測試是否涵蓋所有關鍵路徑?
659
- - [ ] 驗收標準是否清晰可測?
660
-
661
- 4. **風險確認**:
662
- - [ ] 三個「高風險」任務是否有足夠的驗收標準?
663
- - [ ] 降級策略是否充分(JSON 缺失、事件不存在等)?
664
-
665
- ### 確認方式
666
-
667
- 請在本 markdown 中直接編輯,確認後回覆:
668
- - **「確認」** 或 **「通過」**:進行下一步 `/project.apply` 應用程式碼變更
669
- - **「調整」** + 具體建議:我會根據反饋進行修改
670
-
671
- ---
672
-
673
- ## 版本資訊
674
- - **建立時間**:2025-10-26
675
- - **迭代**:震央資訊 JSON 管理化
676
- - **狀態**:等待使用者確認