feat(scripts): Phase 26 補寫腳本 — 從 approval_records 反向建立 KM

225 筆歷史告警處理記錄全部補寫到 knowledge_entries (INCIDENT_CASE)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-06 11:47:47 +08:00
parent 658337ec18
commit b133631b2d

View File

@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""
Phase 26: 從 approval_records 歷史資料補寫 KM
2026-04-06 ogt
問題: 258 筆 Approval 記錄,只有 9 筆 KM entry
大量告警處理歷史沒有沉澱到 Knowledge Base
做法: 遍歷所有 APPROVED / EXECUTION_SUCCESS / EXECUTION_FAILED 的記錄,
建立 INCIDENT_CASE 類型的 KM entry (不重複)
"""
import asyncio
import os
import sys
import asyncpg
async def backfill():
db_url = os.environ.get("DATABASE_URL", "").replace("postgresql+asyncpg://", "postgresql://")
if not db_url:
print("ERROR: DATABASE_URL not set")
sys.exit(1)
conn = await asyncpg.connect(db_url)
# 取得所有有意義的 approval records (已批准/執行)
approvals = await conn.fetch("""
SELECT id, action, description, status, risk_level, created_at, incident_id
FROM approval_records
WHERE status IN ('APPROVED', 'EXECUTION_SUCCESS', 'EXECUTION_FAILED')
ORDER BY created_at ASC
""")
print(f"找到 {len(approvals)} 筆 Approval 記錄")
# 取得已存在的 KM entries避免重複
existing = await conn.fetch("""
SELECT related_approval_id FROM knowledge_entries
WHERE related_approval_id IS NOT NULL
""")
existing_ids = {r["related_approval_id"] for r in existing}
print(f"已有 {len(existing_ids)} 筆 KM entry 有 related_approval_id")
inserted = 0
skipped = 0
for ap in approvals:
ap_id = str(ap["id"])
if ap_id in existing_ids:
skipped += 1
continue
status = ap["status"]
status_icon = "" if status == "EXECUTION_SUCCESS" else ("" if status == "APPROVED" else "")
status_text = {"EXECUTION_SUCCESS": "成功", "APPROVED": "已批准(未執行)", "EXECUTION_FAILED": "失敗"}[status]
title = f"[告警處理記錄] {status_icon} {(ap['action'] or '')[:60]}"
content = (
f"# {status_icon} 告警處理記錄\n\n"
f"**操作**: {ap['action'] or ''}\n"
f"**說明**: {ap['description'] or ''}\n"
f"**執行結果**: {status_text}\n"
f"**風險等級**: {ap['risk_level'] or '未知'}\n"
f"**Approval ID**: {ap_id}\n"
f"**Incident ID**: {ap['incident_id'] or '未關聯'}\n"
f"**建立時間**: {ap['created_at']}\n"
)
import json, uuid
tags_json = json.dumps(["backfill", "approval", status.lower()])
entry_id = str(uuid.uuid4())
await conn.execute("""
INSERT INTO knowledge_entries
(id, title, content, entry_type, category, tags, source, status,
view_count, related_incident_id, related_approval_id,
created_by, created_at, updated_at)
VALUES
($1, $2, $3, 'INCIDENT_CASE', 'alert_handling',
$4::json, 'AI_EXTRACTED', 'APPROVED',
0, $5, $6, 'backfill_script', NOW(), NOW())
""",
entry_id,
title,
content,
tags_json,
ap["incident_id"],
ap_id,
)
inserted += 1
print(f"補寫完成: 新增 {inserted} 筆,略過 {skipped}")
# 確認最終狀態
total = await conn.fetchrow("SELECT count(*) as cnt FROM knowledge_entries")
by_type = await conn.fetch("SELECT entry_type, count(*) FROM knowledge_entries GROUP BY entry_type ORDER BY count DESC")
print(f"\nKM 最終狀態 (共 {total['cnt']} 筆):")
for r in by_type:
print(f" {r['entry_type']}: {r['count']}")
await conn.close()
if __name__ == "__main__":
asyncio.run(backfill())