From 6dc1505584feaa4f4e02b19590a1038ad9f540df Mon Sep 17 00:00:00 2001 From: OG T Date: Wed, 1 Apr 2026 21:06:39 +0800 Subject: [PATCH] feat(incident): add trigger_reanalysis() with Redis 10min dedup (ADR-050) --- apps/api/src/services/incident_service.py | 83 +++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/apps/api/src/services/incident_service.py b/apps/api/src/services/incident_service.py index 3878690b9..3887e17c6 100644 --- a/apps/api/src/services/incident_service.py +++ b/apps/api/src/services/incident_service.py @@ -746,6 +746,89 @@ class IncidentService: ) return None + async def trigger_reanalysis(self, incident_id: str) -> dict: + """ + 觸發 Incident 重診 (ADR-050 P2: reanalyze button) + + 去重保護:同一 incident 10 分鐘內只觸發一次。 + 觸發後將 incident status 標記為 analyzing,等待 AI 自動接手。 + + Args: + incident_id: Incident ID + + Returns: + dict: { + "triggered": bool, + "message": str, + "already_analyzing": bool, + } + + 2026-04-01 Claude Code (ADR-050 P2): reanalyze button handler + """ + REANALYZE_TTL_SECONDS = 600 # 10 分鐘去重 TTL (ADR-050) + dedup_key = f"reanalyze_dedup:{incident_id}" + + try: + redis_client = get_redis() + + # 去重檢查 (SETNX: 只有第一次設定會成功) + is_new = await redis_client.set(dedup_key, "1", ex=REANALYZE_TTL_SECONDS, nx=True) + if not is_new: + logger.info( + "reanalyze_deduplicated", + incident_id=incident_id, + reason="Already triggered within 10 minutes", + ) + return { + "triggered": False, + "message": "重診已在進行中,請 10 分鐘後再試", + "already_analyzing": True, + } + + # 從 Working Memory 取得 Incident + incident = await self.get_from_working_memory(incident_id) + if not incident: + incident = await self.get_from_episodic_memory(incident_id) + + if not incident: + # 刪除剛設定的去重 key(讓下次能重試) + await redis_client.delete(dedup_key) + logger.warning("reanalyze_incident_not_found", incident_id=incident_id) + return { + "triggered": False, + "message": f"找不到事件 {incident_id}", + "already_analyzing": False, + } + + # 標記 status 為 analyzing(讓 AI 引擎接手) + # 使用延遲 import 避免循環依賴(同 create_incident_from_signal 模式) + from src.models.incident import IncidentStatus + # 使用 INVESTIGATING 若 ANALYZING 不存在 + analyzing_status = getattr(IncidentStatus, "ANALYZING", None) or getattr(IncidentStatus, "INVESTIGATING", None) + if analyzing_status: + incident.status = analyzing_status + await self.save_to_working_memory(incident) + + logger.info( + "reanalyze_triggered", + incident_id=incident_id, + severity=incident.severity.value, + ) + + return { + "triggered": True, + "message": "重診已排程,AI 正在分析中", + "already_analyzing": False, + } + + except Exception as e: + logger.exception("reanalyze_failed", incident_id=incident_id, error=str(e)) + return { + "triggered": False, + "message": f"重診觸發失敗: {str(e)[:80]}", + "already_analyzing": False, + } + # ============================================================================= # Singleton