feat(phase27): 歷史按鈕雙層頻率統計 + DB frequency_snapshot 持久化
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 1m44s
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 1m44s
- telegram_gateway: _send_incident_history 改為 Phase 27 雙層策略 Layer 1: DB frequency_snapshot (建立時刻永久快照) Layer 2: Redis AnomalyCounter disposition 累積統計 (35d TTL) 修復舊版呼叫 record_anomaly() 導致誤計數的 bug - 新增 migration: phase27_incident_frequency_snapshot.sql (已在 prod 執行) - CLAUDE.md: 精簡至 123 行,減少 Token 消耗 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
239
CLAUDE.md
239
CLAUDE.md
@@ -4,210 +4,96 @@
|
||||
|
||||
---
|
||||
|
||||
## 🚨🚨🚨 強制提醒 (每小時自我檢查)
|
||||
|
||||
**你有確實執行以下動作嗎?沒有就立刻執行!**
|
||||
|
||||
```
|
||||
□ 讀過 MEMORY.md 索引?
|
||||
□ 讀過 docs/LOGBOOK.md 最新進度?
|
||||
□ 讀過 docs/HARD_RULES.md 絕對禁止規則?
|
||||
□ 涉及特定主題時,讀過對應 feedback_*.md?
|
||||
□ 修改檔案前,讀過該檔案的所有註解? 🔴 NEW
|
||||
```
|
||||
|
||||
**違反後果**: 重複犯錯、統帥需要反覆提醒、信任度下降
|
||||
|
||||
---
|
||||
|
||||
## 🔴 絕對禁止 (Hard Rules)
|
||||
|
||||
**做任何修改前,先讀對應的鐵律文件:**
|
||||
|
||||
→ [HARD_RULES.md](docs/HARD_RULES.md)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Session 啟動第一步
|
||||
|
||||
**在做任何事之前,先讀:**
|
||||
1. `MEMORY.md` - 記憶索引
|
||||
2. `docs/LOGBOOK.md` - 最新進度
|
||||
3. `docs/HARD_RULES.md` - 絕對禁止規則
|
||||
1. `MEMORY.md` — 記憶索引
|
||||
2. `docs/LOGBOOK.md` — 最新進度
|
||||
3. `docs/HARD_RULES.md` — 絕對禁止規則
|
||||
4. 涉及主題的 `feedback_*.md`
|
||||
|
||||
**不要讓統帥說「你讀過 Memory 了嗎?」**
|
||||
🔴🔴 **檢查 `project_current_status.md` 最後更新日期** — 超過 2 天 → 先執行 Memory 清理再開工
|
||||
|
||||
---
|
||||
|
||||
## 四大核心原則
|
||||
|
||||
1. **變更前 → 先讀註解** (理解設計意圖再動手) 🔴 NEW
|
||||
1. **變更前 → 先讀註解** (理解設計意圖再動手) 🔴
|
||||
2. **不可逆操作 → 人工確認** (刪除、logOut、DROP、force push)
|
||||
3. **有疑問 → 先問統帥** (不確定就停下來)
|
||||
4. **任務完成 → 更新 Memory** (不等被問)
|
||||
|
||||
---
|
||||
|
||||
## 🔴 紅區治理
|
||||
## 🔴 絕對禁止 → [HARD_RULES.md](docs/HARD_RULES.md)
|
||||
|
||||
→ **詳細文件:** [RED_ZONES.md](docs/RED_ZONES.md)
|
||||
|
||||
**簡述**: Tier 3 核心檔案 (decision_manager, trust_engine, config 等) 修改需首席架構師授權
|
||||
|
||||
## 專案架構
|
||||
|
||||
- `apps/api/` - FastAPI 後端
|
||||
- `apps/web/` - Next.js 前端
|
||||
- `k8s/` - Kubernetes 配置
|
||||
|
||||
## 🏗️ 基礎設施參考
|
||||
|
||||
→ [SERVICE-ENDPOINTS.md](docs/reference/SERVICE-ENDPOINTS.md) - 五主機架構與服務端點
|
||||
→ [K3S-OPTIMIZATION-RUNBOOK.md](docs/runbooks/K3S-OPTIMIZATION-RUNBOOK.md) - K3s 維運手冊
|
||||
|
||||
## 🔴 Gitea CI/CD (ADR-039)
|
||||
|
||||
**從 2026-03-29 起,所有 CI/CD 從 Gitea 執行!**
|
||||
|
||||
→ **詳細文件:** [reference_gitea_mirror.md](~/.claude/projects/-Users-ogt-awoooi/memory/reference_gitea_mirror.md)
|
||||
|
||||
| 項目 | 值 |
|
||||
|------|-----|
|
||||
| Gitea URL | http://192.168.0.110:3001 |
|
||||
| 推版方式 | `git push gitea main` |
|
||||
| Workflows | `.gitea/workflows/` |
|
||||
| GitHub | 只讀備份,已停用 Actions |
|
||||
|
||||
## 🎨 靈感實驗室
|
||||
|
||||
→ [INSPIRATION_LAB.md](docs/INSPIRATION_LAB.md) - 學習/模仿/發想/待定案內容
|
||||
|
||||
**用途**: 收集外部參考、突發奇想、待討論項目
|
||||
**分類**: 視覺/UI/UX/風格/功能/工具/服務/突發奇想
|
||||
**注意**: 內容皆為「待評估」,採用前需統帥批准
|
||||
|
||||
## 🛑 修改前
|
||||
|
||||
修改以下檔案前,**必須先讀** [HARD_RULES.md](docs/HARD_RULES.md):
|
||||
|
||||
- `.github/workflows/*` → GitHub Billing 章節
|
||||
- `*telegram*` → Telegram Token 章節
|
||||
- `apps/web/**` → i18n 章節
|
||||
- Incident/Approval 流程 → 確認 Telegram + DB 鏈路
|
||||
- **Alertmanager/NetworkPolicy** → ADR-025 告警鏈路 E2E 驗證 🔴🔴
|
||||
- **AI Provider 路由/Fallback** → Phase 24 AI Router 規範 🔴🔴
|
||||
## 🔴 紅區治理 → [RED_ZONES.md](docs/RED_ZONES.md)
|
||||
Tier 3 核心檔案 (decision_manager, trust_engine, config 等) 修改需首席架構師授權
|
||||
|
||||
---
|
||||
|
||||
## 任務前必讀
|
||||
## 專案架構
|
||||
|
||||
涉及以下主題時,**先讀取對應 Memory**:
|
||||
- `apps/api/` — FastAPI 後端
|
||||
- `apps/web/` — Next.js 前端
|
||||
- `k8s/` — Kubernetes 配置
|
||||
|
||||
| 主題 | Memory 路徑 |
|
||||
|------|-------------|
|
||||
| **🔴🔴🔴 費用變更** | `feedback_cost_change_approval.md` — 必須停下來等統帥批准 |
|
||||
| **變更前必讀** | `feedback_read_comments_first.md` 🔴 先讀註解 |
|
||||
| **變更註解** | `feedback_change_annotation_standard.md` 🔴🔴 人事物+版本+時區 |
|
||||
| **重大變更** | `feedback_product_survival_principles.md` |
|
||||
## 🔴 Gitea CI/CD (ADR-039) → [reference_gitea_mirror.md](~/.claude/projects/-Users-ogt-awoooi/memory/reference_gitea_mirror.md)
|
||||
|
||||
從 2026-03-29 起,所有 CI/CD 從 Gitea 執行。推版:`git push gitea main`。GitHub 只讀備份。
|
||||
|
||||
---
|
||||
|
||||
## 🛑 修改前必讀 → [HARD_RULES.md](docs/HARD_RULES.md)
|
||||
|
||||
| 檔案/功能 | 必讀章節 |
|
||||
|----------|---------|
|
||||
| `.github/workflows/*` | GitHub Billing |
|
||||
| `*telegram*` | Telegram Token |
|
||||
| `apps/web/**` | i18n |
|
||||
| Incident/Approval 流程 | Telegram + DB 鏈路 |
|
||||
| Alertmanager/NetworkPolicy 🔴🔴 | ADR-025 告警鏈路 E2E |
|
||||
| AI Provider 路由/Fallback 🔴🔴 | Phase 24 AI Router |
|
||||
|
||||
---
|
||||
|
||||
## 任務前必讀 Memory
|
||||
|
||||
| 主題 | Memory |
|
||||
|------|--------|
|
||||
| 🔴🔴 定期清理 | `feedback_memory_cleanup_schedule.md` |
|
||||
| 🔴🔴🔴 費用變更 | `feedback_cost_change_approval.md` |
|
||||
| 變更前必讀 🔴 | `feedback_read_comments_first.md` |
|
||||
| 變更註解 🔴🔴 | `feedback_change_annotation_standard.md` |
|
||||
| 重大變更 | `feedback_product_survival_principles.md` |
|
||||
| Telegram | `feedback_telegram_token_disaster.md` |
|
||||
| OpenClaw | `feedback_architecture_openclaw_core.md` |
|
||||
| 命名規範 | `feedback_openclaw_naming.md` |
|
||||
| i18n | `feedback_i18n_zero_hardcode.md` |
|
||||
| 防禦性工程 | `feedback_defensive_engineering.md` |
|
||||
| 模組化 | `feedback_modular_architecture.md` |
|
||||
| **🔴🔴 積木化強制** | `feedback_lewooogo_modular_enforcement.md` 🔴🔴 修改前 5 問 |
|
||||
| 積木化強制 🔴🔴 | `feedback_lewooogo_modular_enforcement.md` |
|
||||
| API 整合 | `feedback_api_response_verification.md` |
|
||||
| 構建部署 | `feedback_build_from_git_only.md` |
|
||||
| **測試** | `feedback_no_mock_testing.md` 🔴🔴 禁止 Mock |
|
||||
| **API 路徑** | `feedback_api_path_naming.md` 🔴 修改需同步前端 |
|
||||
| **部署驗證** | `feedback_deployment_verification.md` 🔴🔴 必須驗證 Pod 版本 |
|
||||
| **部署層級** | `feedback_deployment_layer_decision.md` 🔴🔴🔴 主機/容器/K3s 必須評估 |
|
||||
| **告警鏈路** | `feedback_alertchain_e2e_validation.md` 🔴🔴🔴 Alertmanager→API→Telegram |
|
||||
| **Telegram Secrets** | `feedback_telegram_secrets_injection.md` 🔴🔴🔴 CD 必須自動注入 K8s Secrets |
|
||||
| **🔴🔴🔴 前端內網禁令** | `feedback_docker_nextjs_api_url.md` + `feedback_sentry_local_network.md` |
|
||||
| **🔴🔴 AI Router 重構** | `project_phase24_ai_router.md` 🔴🔴 D1-D14 決策 + 絞殺者遷移 |
|
||||
| **AI Fallback 順序** | `feedback_ai_fallback_order.md` 🔴 修改需讀 Phase 24 指引 |
|
||||
| 測試 🔴🔴 | `feedback_no_mock_testing.md` |
|
||||
| API 路徑 🔴 | `feedback_api_path_naming.md` |
|
||||
| 部署驗證 🔴🔴 | `feedback_deployment_verification.md` |
|
||||
| 部署層級 🔴🔴🔴 | `feedback_deployment_layer_decision.md` |
|
||||
| 告警鏈路 🔴🔴🔴 | `feedback_alertchain_e2e_validation.md` |
|
||||
| Telegram Secrets 🔴🔴🔴 | `feedback_telegram_secrets_injection.md` |
|
||||
| 前端內網禁令 🔴🔴🔴 | `feedback_frontend_internal_ip_ban.md` |
|
||||
| AI Router 重構 🔴🔴 | `project_phase24_ai_router.md` |
|
||||
| AI Fallback 順序 🔴 | `feedback_ai_fallback_order.md` |
|
||||
| 前端 Icon 規範 🔴 | `feedback_no_emoji_use_icons.md` |
|
||||
| 設計稿預覽 🔴 | `feedback_ui_collaboration_protocol.md` |
|
||||
|
||||
---
|
||||
|
||||
## 🔴🔴 Phase 24 AI Provider Registry (2026-04-02)
|
||||
## 重要規則摘要(詳情在 Memory)
|
||||
|
||||
→ **詳細文件:** `project_phase24_ai_router.md`
|
||||
→ **ADR:** ADR-052 (已起草)
|
||||
→ **HARD_RULES:** OpenClaw 章節 Phase 24 規範
|
||||
|
||||
**核心規則:**
|
||||
- Router 只依賴 AIProvider Protocol,禁止 import 具體 Provider
|
||||
- DIAGNOSE/CODE_REVIEW 強制 local Provider (零信任)
|
||||
- 絞殺者開關 `USE_AI_ROUTER` 新舊並存
|
||||
- ConsensusEngine (P0/P1) 獨立路徑,不走 AIRouter
|
||||
|
||||
---
|
||||
|
||||
## 🔴🔴🔴 前端內網 IP 禁令 (2026-03-30)
|
||||
|
||||
→ **詳細文件:** `feedback_docker_nextjs_api_url.md` + `feedback_sentry_local_network.md`
|
||||
|
||||
**絕對禁止** 在 CD 建置時使用內網 IP:
|
||||
|
||||
```yaml
|
||||
# ❌ 觸發瀏覽器「存取區域網路」權限對話框
|
||||
--build-arg NEXT_PUBLIC_API_URL=http://192.168.0.125:32334
|
||||
--build-arg NEXT_PUBLIC_SENTRY_DSN=http://...@192.168.0.110:9000/2
|
||||
|
||||
# ✅ 必須使用公網域名
|
||||
--build-arg NEXT_PUBLIC_API_URL=https://awoooi.wooo.work
|
||||
```
|
||||
|
||||
**原因**: `NEXT_PUBLIC_*` 是 build-time 變數,會寫死到 JS Bundle
|
||||
|
||||
---
|
||||
|
||||
## 🔴 部署層級決策
|
||||
|
||||
→ **詳細文件:** [feedback_deployment_layer_decision.md](~/.claude/projects/-Users-ogt-awoooi/memory/feedback_deployment_layer_decision.md)
|
||||
|
||||
**簡述**: 部署新服務前必須評估 主機/容器/K3s 層級,禁止直接 `docker run` 或 `kubectl apply`
|
||||
|
||||
---
|
||||
|
||||
## 🔴🔴 leWOOOgo 積木化
|
||||
|
||||
→ **詳細文件:** [feedback_lewooogo_modular_enforcement.md](~/.claude/projects/-Users-ogt-awoooi/memory/feedback_lewooogo_modular_enforcement.md)
|
||||
|
||||
**簡述**: 修改 `apps/api/` 前必問 5 題,Router 層禁止直接存取 Redis/DB
|
||||
|
||||
---
|
||||
|
||||
## 🔴🔴🔴 Telegram 告警鏈路 (ADR-035)
|
||||
|
||||
→ **ADR**: [ADR-035-telegram-alert-chain-enforcement.md](docs/adr/ADR-035-telegram-alert-chain-enforcement.md)
|
||||
→ **Memory**: [feedback_telegram_secrets_injection.md](~/.claude/projects/-Users-ogt-awoooi/memory/feedback_telegram_secrets_injection.md)
|
||||
|
||||
### 強制規則
|
||||
|
||||
1. **CD 必須自動注入 K8s Secrets**
|
||||
- 每次部署都 `kubectl patch secret`
|
||||
- 禁止依賴 `03-secrets.yaml` 模板值
|
||||
|
||||
2. **Pre-flight 必須檢查 Telegram Secrets**
|
||||
- `OPENCLAW_TG_BOT_TOKEN` 必須存在
|
||||
- 缺少則 CI 失敗
|
||||
|
||||
3. **部署後必須 E2E 驗證**
|
||||
- 發送測試告警驗證鏈路
|
||||
- 失敗則繞過 API 直接告警
|
||||
|
||||
### 禁止事項
|
||||
|
||||
```yaml
|
||||
# ❌ 禁止: secrets.yaml 使用 CHANGE_ME
|
||||
OPENCLAW_TG_BOT_TOKEN: "CHANGE_ME"
|
||||
|
||||
# ❌ 禁止: CD 不處理 secrets
|
||||
# (沒有 kubectl patch secret 步驟)
|
||||
```
|
||||
- **前端內網 IP 禁令** 🔴🔴🔴 — `NEXT_PUBLIC_*` 禁用內網 IP,用公網域名(build-time 寫死進 JS Bundle)
|
||||
- **Telegram 告警鏈路** 🔴🔴🔴 — CD 必須自動注入 K8s Secrets;禁止 CHANGE_ME;部署後 E2E 驗證 → ADR-035
|
||||
- **leWOOOgo 積木化** 🔴🔴 — 修改 `apps/api/` 前必問 5 題,Router 層禁止直接存取 Redis/DB
|
||||
- **Phase 24 AI Router** ✅ — ADR-052 完成,Router 只依賴 Protocol,絞殺者開關 `USE_AI_ROUTER`
|
||||
|
||||
---
|
||||
|
||||
@@ -222,17 +108,16 @@ OPENCLAW_TG_BOT_TOKEN: "CHANGE_ME"
|
||||
| 測試 | `.agents/skills/05-awoooi-sre-qa.md` |
|
||||
| Git | `.agents/skills/06-awoooi-monorepo-master.md` |
|
||||
| Tool 整合 | `.agents/skills/07-tool-integration-expert.md` |
|
||||
| 模型路由 | `.agents/skills/08-model-router-expert.md` 🔴 Phase 24 AIRouter |
|
||||
| **絞殺者重構** | `.agents/skills/09-strangler-pattern-expert.md` 🔴 Phase 24 遷移 |
|
||||
| 模型路由 | `.agents/skills/08-model-router-expert.md` |
|
||||
| 絞殺者重構 | `.agents/skills/09-strangler-pattern-expert.md` |
|
||||
|
||||
## Memory 系統
|
||||
|
||||
- 長期記憶:`~/.claude/projects/-Users-ogt-awoooi/memory/`
|
||||
- 索引:`MEMORY.md`
|
||||
- 進度:`docs/LOGBOOK.md`
|
||||
- 參考:[SERVICE-ENDPOINTS.md](docs/reference/SERVICE-ENDPOINTS.md) / [K3S-OPTIMIZATION-RUNBOOK.md](docs/runbooks/K3S-OPTIMIZATION-RUNBOOK.md)
|
||||
|
||||
## Session 協議
|
||||
## Session 結束前
|
||||
|
||||
**啟動時**:讀 MEMORY.md → LOGBOOK.md → 確認當前任務
|
||||
|
||||
**結束前**:更新相關 Memory → 更新 LOGBOOK → 標記下一步
|
||||
更新相關 Memory → 更新 LOGBOOK → 標記下一步
|
||||
|
||||
24
apps/api/migrations/phase27_incident_frequency_snapshot.sql
Normal file
24
apps/api/migrations/phase27_incident_frequency_snapshot.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
-- Phase 27: Incident Frequency Snapshot 持久化
|
||||
-- 2026-04-10 ogt: frequency_stats 只存記憶體/Redis(35天TTL),重啟或超期即失
|
||||
-- 解決方案:在 incidents 表加 frequency_snapshot JSONB,建立 incident 時寫入快照
|
||||
-- 歷史按鈕優先讀 DB 快照,Redis AnomalyCounter 補充長期累積統計
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'incidents' AND column_name = 'frequency_snapshot'
|
||||
) THEN
|
||||
ALTER TABLE incidents ADD COLUMN frequency_snapshot JSONB DEFAULT NULL;
|
||||
COMMENT ON COLUMN incidents.frequency_snapshot IS
|
||||
'Snapshot of AnomalyFrequency at incident creation time. '
|
||||
'Fields: anomaly_key, count_1h, count_24h, count_7d, count_30d, '
|
||||
'escalation_level, auto_repair_count, last_repair_action, '
|
||||
'human_approved_count, manual_resolved_count, cold_start_trust_count, total_resolution_count. '
|
||||
'Added 2026-04-10 (Phase 27).';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_incidents_frequency_snapshot_key
|
||||
ON incidents ((frequency_snapshot->>'anomaly_key'))
|
||||
WHERE frequency_snapshot IS NOT NULL;
|
||||
@@ -2655,10 +2655,12 @@ class TelegramGateway:
|
||||
"""
|
||||
ADR-050 P2: 傳送事件頻率統計訊息
|
||||
|
||||
2026-04-10 ogt: 修復 — frequency_stats 不存 DB,改從 AnomalyCounter (Redis) 即時查詢
|
||||
Phase 27 雙層策略 (2026-04-10 ogt):
|
||||
- Layer 1: DB frequency_snapshot — 建立時刻快照,永久保存
|
||||
- Layer 2: Redis AnomalyCounter — 跨 incident 累積統計 (35d TTL)
|
||||
"""
|
||||
from src.repositories.incident_repository import get_incident_repository
|
||||
from src.services.anomaly_counter import get_anomaly_counter, AnomalyCounter
|
||||
from src.services.anomaly_counter import get_anomaly_counter
|
||||
|
||||
try:
|
||||
repo = get_incident_repository()
|
||||
@@ -2668,69 +2670,74 @@ class TelegramGateway:
|
||||
await self.send_notification(f"⚠️ 找不到事件 <code>{html.escape(incident_id)}</code>")
|
||||
return
|
||||
|
||||
# frequency_stats 不持久化,從 AnomalyCounter (Redis) 即時查詢
|
||||
anomaly_key = AnomalyCounter.derive_key_from_incident(incident)
|
||||
if not anomaly_key:
|
||||
await self.send_notification(
|
||||
f"📊 <b>事件歷史</b>\n\n🔖 <code>{html.escape(incident_id)}</code>\n\n⚠️ 無法推導告警鍵(signals 為空)"
|
||||
)
|
||||
return
|
||||
|
||||
counter = get_anomaly_counter()
|
||||
freq = await counter.record_anomaly({
|
||||
"alert_name": incident.signals[0].alert_name if incident.signals else "",
|
||||
"service": incident.affected_services[0] if incident.affected_services else "",
|
||||
"namespace": (incident.signals[0].labels or {}).get("namespace", "") if incident.signals else "",
|
||||
"error_type": (incident.signals[0].labels or {}).get("reason", (incident.signals[0].labels or {}).get("error_type", "")) if incident.signals else "",
|
||||
})
|
||||
|
||||
if freq is None:
|
||||
await self.send_notification(
|
||||
f"📊 <b>事件歷史</b>\n\n🔖 <code>{html.escape(incident_id)}</code>\n\n⚠️ 無頻率統計資料"
|
||||
)
|
||||
return
|
||||
|
||||
disposition = await counter.get_disposition_stats(anomaly_key)
|
||||
|
||||
lines = [
|
||||
f"📊 <b>事件歷史統計</b>",
|
||||
f"",
|
||||
"📊 <b>事件歷史統計</b>",
|
||||
"",
|
||||
f"🔖 <code>{html.escape(incident_id)}</code>",
|
||||
f"🔑 告警鍵: <code>{html.escape(anomaly_key)}</code>",
|
||||
f"",
|
||||
f"⏱ <b>發生頻率</b>",
|
||||
f" 1小時: {freq.count_1h} 次",
|
||||
f" 24小時: {freq.count_24h} 次",
|
||||
f" 7天: {freq.count_7d} 次",
|
||||
f" 30天: {freq.count_30d} 次",
|
||||
]
|
||||
|
||||
if freq.auto_repair_count > 0:
|
||||
# === Layer 1: DB 快照 (建立時刻,永久) ===
|
||||
fs = incident.frequency_stats
|
||||
if fs:
|
||||
lines += [
|
||||
f"",
|
||||
f"🔧 <b>自動修復</b>",
|
||||
f" 執行次數: {freq.auto_repair_count}",
|
||||
"",
|
||||
"📌 <b>建立時刻快照</b>",
|
||||
f" 1小時: {fs.count_1h} 次",
|
||||
f" 24小時: {fs.count_24h} 次",
|
||||
f" 7天: {fs.count_7d} 次",
|
||||
f" 30天: {fs.count_30d} 次",
|
||||
]
|
||||
if freq.last_repair_action:
|
||||
lines.append(f" 最後動作: {html.escape(freq.last_repair_action)}")
|
||||
if fs.auto_repair_count > 0:
|
||||
lines.append(f" 自動修復: {fs.auto_repair_count} 次")
|
||||
if fs.last_repair_action:
|
||||
lines.append(f" 最後動作: {html.escape(fs.last_repair_action)}")
|
||||
if fs.escalation_level:
|
||||
lines.append(f" 升級等級: {html.escape(fs.escalation_level)}")
|
||||
if fs.anomaly_key:
|
||||
lines.append(f"🔑 告警鍵: <code>{html.escape(fs.anomaly_key)}</code>")
|
||||
anomaly_key = fs.anomaly_key
|
||||
else:
|
||||
lines += ["", "⚠️ 無建立時快照(舊 incident 或 Redis 已超期)"]
|
||||
# 嘗試從 signals 推導 anomaly_key
|
||||
anomaly_key = None
|
||||
if incident.signals:
|
||||
sig = incident.signals[0]
|
||||
parts = [
|
||||
sig.alert_name or "",
|
||||
incident.affected_services[0] if incident.affected_services else "",
|
||||
(sig.labels or {}).get("namespace", ""),
|
||||
(sig.labels or {}).get("error_type", ""),
|
||||
]
|
||||
candidate = ":".join(p for p in parts if p)
|
||||
if candidate:
|
||||
anomaly_key = candidate
|
||||
|
||||
# 處置分佈
|
||||
auto_r = disposition.get("auto_repair_count", 0)
|
||||
cold_s = disposition.get("cold_start_trust_count", 0)
|
||||
human_a = disposition.get("human_approved_count", 0)
|
||||
manual_r = disposition.get("manual_resolved_count", 0)
|
||||
total_res = auto_r + cold_s + human_a + manual_r
|
||||
if total_res > 0:
|
||||
auto_rate = int((auto_r + cold_s) / total_res * 100)
|
||||
lines += [
|
||||
f"",
|
||||
f"📋 <b>處置分佈</b> (共 {total_res} 次)",
|
||||
f" 🤖 自動修復: {auto_r}",
|
||||
f" ❄️ 冷啟動信任: {cold_s}",
|
||||
f" 👤 人工審核: {human_a}",
|
||||
f" 🔧 手動處理: {manual_r}",
|
||||
f" 📈 自動化率: <b>{auto_rate}%</b>",
|
||||
]
|
||||
# === Layer 2: Redis 累積統計 (35d TTL) ===
|
||||
if anomaly_key:
|
||||
try:
|
||||
counter = get_anomaly_counter()
|
||||
disposition = await counter.get_disposition_stats(anomaly_key)
|
||||
auto_r = disposition.get("auto_repair_count", 0)
|
||||
cold_s = disposition.get("cold_start_trust_count", 0)
|
||||
human_a = disposition.get("human_approved_count", 0)
|
||||
manual_r = disposition.get("manual_resolved_count", 0)
|
||||
total_res = auto_r + cold_s + human_a + manual_r
|
||||
if total_res > 0:
|
||||
auto_rate = int((auto_r + cold_s) / total_res * 100)
|
||||
lines += [
|
||||
"",
|
||||
f"📋 <b>累積處置分佈</b> (共 {total_res} 次,35天內)",
|
||||
f" 🤖 自動修復: {auto_r}",
|
||||
f" ❄️ 冷啟動信任: {cold_s}",
|
||||
f" 👤 人工審核: {human_a}",
|
||||
f" 🔧 手動處理: {manual_r}",
|
||||
f" 📈 自動化率: <b>{auto_rate}%</b>",
|
||||
]
|
||||
else:
|
||||
lines += ["", "📋 <b>累積處置</b>: 尚無記錄 (Redis TTL 35天)"]
|
||||
except Exception as redis_err:
|
||||
logger.warning("incident_history_redis_error", error=str(redis_err))
|
||||
lines += ["", "⚠️ Redis 統計暫時無法取得"]
|
||||
|
||||
await self.send_notification("\n".join(lines))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user