Spaces:
Sleeping
Sleeping
Commit
·
5a8e0d1
1
Parent(s):
588d698
chore: delete project specification documents, GitHub prompts, and templates.
Browse files- .github/copilot-instructions.md +0 -182
- .github/prompts/project.apply.prompt.md +0 -45
- .github/prompts/project.init.prompt.md +0 -44
- .github/prompts/project.plan.prompt.md +0 -50
- .github/prompts/project.report.prompt.md +0 -49
- .github/prompts/project.spec.prompt.md +0 -98
- .github/prompts/project.task.prompt.md +0 -51
- .github/spec-template.md +0 -48
- .github/task-template.md +0 -62
- spec/00-overview.md +0 -123
- spec/01-data-contract.md +0 -322
- spec/02-processing-rules.md +0 -289
- spec/03-error-handling.md +0 -416
- spec/04-extensions.md +0 -360
- spec/README.md +0 -192
- spec/plan.md +0 -167
- spec/task.md +0 -676
.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 |
-
- **狀態**:等待使用者確認
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|