fix(alerts): correct telegram execution truth
Some checks failed
CD Pipeline / tests (push) Failing after 52s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Code Review / ai-code-review (push) Successful in 11s

This commit is contained in:
Your Name
2026-05-31 13:58:21 +08:00
parent 943a6feacf
commit e2ab879636
15 changed files with 624 additions and 49 deletions

View File

@@ -232,11 +232,32 @@ class ReportGenerationService:
async with get_db_context() as db:
row = await db.execute(
text("""
WITH scoped AS (
SELECT
*,
(
COALESCE(extra_metadata->>'execution_kind', '') = 'no_action'
OR COALESCE(extra_metadata->>'repair_executed', '') = 'false'
OR btrim(coalesce(action, '')) = ''
OR UPPER(action) LIKE 'OBSERVE%'
OR UPPER(action) LIKE 'INVESTIGATE%'
OR UPPER(action) LIKE 'NO_ACTION%'
OR UPPER(action) LIKE '% NO_ACTION%'
OR UPPER(action) LIKE '%| NO_ACTION%'
) AS is_observe_only
FROM approval_records
WHERE created_at >= :since
)
SELECT
COUNT(*) FILTER (WHERE UPPER(status::text) = 'EXECUTION_SUCCESS') AS success,
COUNT(*) FILTER (WHERE UPPER(status::text) = 'EXECUTION_FAILED') AS failed
FROM approval_records
WHERE created_at >= :since
COUNT(*) FILTER (
WHERE UPPER(status::text) = 'EXECUTION_SUCCESS'
AND NOT is_observe_only
) AS success,
COUNT(*) FILTER (
WHERE UPPER(status::text) = 'EXECUTION_FAILED'
AND NOT is_observe_only
) AS failed
FROM scoped
"""),
{"since": since},
)
@@ -460,6 +481,7 @@ class ReportGenerationService:
# 失敗時發送告警到 SRE 群組,避免靜默吞掉錯誤
import asyncio as _asyncio
report_text = self.format_postmortem(data)
await self._persist_postmortem_km(data, report_text)
from src.services.telegram_gateway import get_telegram_gateway
gateway = get_telegram_gateway()
@@ -510,6 +532,72 @@ class ReportGenerationService:
error=str(_fe),
)
async def _persist_postmortem_km(
self,
data: PostmortemData,
report_text: str,
) -> None:
"""Persist generated postmortem as an idempotent KM entry before Telegram send."""
try:
from src.db.base import get_db_context
from src.models.knowledge import (
EntrySource,
EntryStatus,
EntryType,
KnowledgeEntryCreate,
)
from src.repositories.alert_operation_log_repository import (
get_alert_operation_log_repository,
)
from src.repositories.knowledge_repository import KnowledgeDBRepository
async with get_db_context() as db:
repo = KnowledgeDBRepository(db)
entry = await repo.create(
KnowledgeEntryCreate(
title=f"Postmortem {data.incident_id}: {data.title}"[:255],
content=report_text,
entry_type=EntryType.POSTMORTEM,
category="postmortem",
tags=[
"postmortem",
"incident",
"telegram",
"auto_repaired" if data.auto_repaired else "human_intervention",
],
source=EntrySource.AI_EXTRACTED,
status=EntryStatus.REVIEW,
related_incident_id=data.incident_id,
path_type="postmortem",
created_by="report_generation_service",
)
)
await get_alert_operation_log_repository().append(
"KM_CONVERTED",
incident_id=data.incident_id,
actor="report_generation_service",
action_detail="postmortem_persisted",
success=True,
context={
"knowledge_entry_id": entry.id,
"entry_type": EntryType.POSTMORTEM.value,
"path_type": "postmortem",
"duration_minutes": round(data.duration_minutes, 2),
},
)
logger.info(
"postmortem_km_persisted",
incident_id=data.incident_id,
knowledge_entry_id=entry.id,
)
except Exception as e:
logger.warning(
"postmortem_km_persist_failed",
incident_id=data.incident_id,
error=str(e),
)
# =============================================================================
# 日度報告排程迴圈