feat(GAP-D5 Task 4.2): Postmortem 自動組裝 hook
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled

incident_service.resolve_incident() 結尾 fire-and-forget 呼叫
report_generation_service.trigger_postmortem(),補完孤兒服務的觸發路徑。

觸發條件(由 trigger_postmortem 內部判斷):
- duration > POSTMORTEM_MIN_DURATION_MINUTES (10min)
- 含 AI root_cause / resolution_action / provider / auto_repaired

背景:
- report_generation_service.py 539 行服務於先前 session 建立
- main.py:322 已啟動 run_daily_report_loop(Task 4.1 )
- trigger_postmortem 在 src/ 下無呼叫方 → 本 commit 補上

MASTER 藍圖 Phase 4 至此完整收官:
 Task 4.1 日度巡檢報告(08:00 台北排程,生產環境已跑)
 Task 4.2 Postmortem 自動組裝(本 commit 接上 resolve hook)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-14 18:25:15 +08:00
parent b8b124c917
commit 8de807c40d

View File

@@ -1002,6 +1002,45 @@ class IncidentService:
except Exception as _disp_e:
logger.warning("disposition_manual_resolve_failed", error=str(_disp_e))
# MASTER Task 4.2 (2026-04-14 Claude Sonnet 4.6): Postmortem 自動組裝
# Incident duration > POSTMORTEM_MIN_DURATION_MINUTES(10min) 時自動生成
# 孤兒 report_generation_service.trigger_postmortem 本次接上 resolve 路徑
try:
import asyncio
from src.services.report_generation_service import get_report_generation_service
alertname = (
incident.signals[0].labels.get("alertname", "UnknownAlert")
if incident.signals else "UnknownAlert"
)
title = f"{alertname}{', '.join(incident.affected_services or ['N/A'])}"
root_cause = None
resolution_action = None
ai_provider = None
auto_repaired = False
if incident.decision_chain:
root_cause = incident.decision_chain.hypothesis
ai_provider = incident.decision_chain.model_used
if incident.outcome:
resolution_action = (incident.outcome.learning_notes or None)
auto_repaired = bool(incident.outcome.execution_success)
asyncio.create_task(
get_report_generation_service().trigger_postmortem(
incident_id=incident.incident_id,
title=title,
created_at=incident.signals[0].fired_at if incident.signals else incident.resolved_at,
resolved_at=incident.resolved_at,
root_cause=root_cause,
resolution_action=resolution_action,
ai_provider=ai_provider,
auto_repaired=auto_repaired,
)
)
except Exception as _pm_e:
logger.exception("postmortem_trigger_failed",
incident_id=incident_id, error=str(_pm_e))
return incident
async def find_by_proposal_id(self, proposal_id: str) -> Incident | None: