fix(api): 標示全零週報資料鏈異常
This commit is contained in:
@@ -3168,6 +3168,11 @@ class WeeklyReportMessage:
|
||||
disposition_manual: int = 0
|
||||
disposition_cold_start: int = 0
|
||||
disposition_total: int = 0
|
||||
stats_source_ok: bool = True
|
||||
k3s_source_ok: bool = True
|
||||
git_source_ok: bool = True
|
||||
cost_source_ok: bool = False
|
||||
all_zero_actionable_anomaly: bool = False
|
||||
|
||||
def format(self) -> str:
|
||||
"""格式化為 Telegram HTML"""
|
||||
@@ -3175,6 +3180,29 @@ class WeeklyReportMessage:
|
||||
alert_health = "✅" if self.resolved_rate >= 80 else "⚠️"
|
||||
ai_health = "✅" if self.ai_success_rate >= 70 else "⚠️"
|
||||
k3s_health = "✅" if self.k3s_uptime_pct >= 99 else "⚠️"
|
||||
source_ok_count = sum([
|
||||
self.stats_source_ok,
|
||||
self.k3s_source_ok,
|
||||
self.git_source_ok,
|
||||
self.cost_source_ok,
|
||||
])
|
||||
all_zero = (
|
||||
self.alert_total == 0
|
||||
and self.ai_proposal_count == 0
|
||||
and self.ai_executed_count == 0
|
||||
and self.commits_count == 0
|
||||
and self.deploy_count == 0
|
||||
and self.ai_tokens_week == 0
|
||||
and self.disposition_total == 0
|
||||
)
|
||||
actionable_all_zero = self.all_zero_actionable_anomaly or all_zero
|
||||
report_trust = "低可信" if actionable_all_zero or source_ok_count < 4 else "可參考"
|
||||
source_status = (
|
||||
f"Stats={'ok' if self.stats_source_ok else 'fail'} / "
|
||||
f"K3s={'ok' if self.k3s_source_ok else 'fail'} / "
|
||||
f"Git={'ok' if self.git_source_ok else 'fail'} / "
|
||||
f"Cost={'ok' if self.cost_source_ok else 'missing'}"
|
||||
)
|
||||
|
||||
message = (
|
||||
f"═══════════════════════════\n"
|
||||
@@ -3182,6 +3210,11 @@ class WeeklyReportMessage:
|
||||
f"═══════════════════════════\n"
|
||||
f"📅 {html.escape(self.week_range)} | {html.escape(self.report_date)}\n"
|
||||
f"━━━━━━━━━━━━━━━━━━━\n"
|
||||
f"🧭 <b>報表資料信任度</b>\n"
|
||||
f"├ 判定: <b>{report_trust}</b>\n"
|
||||
f"├ 來源: <code>{html.escape(source_status)}</code>\n"
|
||||
f"└ 全 0: <code>{'actionable_anomaly' if actionable_all_zero else 'no'}</code>\n"
|
||||
f"━━━━━━━━━━━━━━━━━━━\n"
|
||||
f"{alert_health} <b>告警統計</b>\n"
|
||||
f"├ 總數: <code>{self.alert_total}</code>\n"
|
||||
f"├ Critical: <code>{self.alert_critical}</code>\n"
|
||||
@@ -3222,7 +3255,7 @@ class WeeklyReportMessage:
|
||||
f"└ 自動化率: <b>{auto_rate}%</b>"
|
||||
)
|
||||
|
||||
return message[:1200]
|
||||
return message[:1800]
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -94,7 +94,7 @@ class WeeklyReportService:
|
||||
|
||||
return week_range, monday, sunday
|
||||
|
||||
def _get_git_stats(self, since: datetime) -> tuple[int, int]:
|
||||
def _get_git_stats(self, since: datetime) -> tuple[int, int, bool]:
|
||||
"""取得 Git 統計 (commits, deploys)"""
|
||||
try:
|
||||
# 取得本週 commits 數量
|
||||
@@ -118,10 +118,10 @@ class WeeklyReportService:
|
||||
)
|
||||
deploys = len(result_deploy.stdout.strip().split("\n")) if result_deploy.stdout.strip() else 0
|
||||
|
||||
return commits, deploys
|
||||
return commits, deploys, True
|
||||
except Exception as e:
|
||||
logger.warning("git_stats_failed", error=str(e))
|
||||
return 0, 0
|
||||
return 0, 0, False
|
||||
|
||||
async def generate_report(self) -> WeeklyReportMessage:
|
||||
"""
|
||||
@@ -134,25 +134,29 @@ class WeeklyReportService:
|
||||
report_date = now.strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
# 取得統計數據 (7 天)
|
||||
stats_source_ok = True
|
||||
try:
|
||||
incident_summary = await self._stats_service.get_incident_summary(days=7)
|
||||
resolution_stats = await self._stats_service.get_resolution_stats(days=7)
|
||||
ai_performance = await self._stats_service.get_ai_performance(days=7)
|
||||
except Exception as e:
|
||||
logger.warning("stats_fetch_failed", error=str(e))
|
||||
stats_source_ok = False
|
||||
incident_summary = {}
|
||||
resolution_stats = {}
|
||||
ai_performance = {}
|
||||
|
||||
# 取得 K3s 狀態
|
||||
k3s_source_ok = True
|
||||
try:
|
||||
k3s_status = await self._k3s_monitor.collect_cluster_status()
|
||||
except Exception as e:
|
||||
logger.warning("k3s_fetch_failed", error=str(e))
|
||||
k3s_source_ok = False
|
||||
k3s_status = None
|
||||
|
||||
# 取得 Git 統計
|
||||
commits, deploys = self._get_git_stats(monday)
|
||||
commits, deploys, git_source_ok = self._get_git_stats(monday)
|
||||
|
||||
# 計算指標
|
||||
total_incidents = incident_summary.get("total_incidents", 0)
|
||||
@@ -216,6 +220,18 @@ class WeeklyReportService:
|
||||
disposition_manual=disp_manual,
|
||||
disposition_cold_start=disp_cold,
|
||||
disposition_total=disp_total,
|
||||
stats_source_ok=stats_source_ok,
|
||||
k3s_source_ok=k3s_source_ok,
|
||||
git_source_ok=git_source_ok,
|
||||
cost_source_ok=False,
|
||||
all_zero_actionable_anomaly=(
|
||||
total_incidents == 0
|
||||
and ai_proposals == 0
|
||||
and ai_executed == 0
|
||||
and commits == 0
|
||||
and deploys == 0
|
||||
and disp_total == 0
|
||||
),
|
||||
)
|
||||
|
||||
logger.info(
|
||||
|
||||
@@ -18,6 +18,7 @@ from src.services.telegram_gateway import (
|
||||
SentryErrorMessage,
|
||||
TelegramGateway,
|
||||
TelegramMessage,
|
||||
WeeklyReportMessage,
|
||||
)
|
||||
|
||||
|
||||
@@ -53,6 +54,51 @@ def test_auto_repair_status_line_distinguishes_auto_resolved() -> None:
|
||||
assert "CPU 92%->30%" in result
|
||||
|
||||
|
||||
def test_weekly_report_marks_all_zero_as_low_trust_anomaly() -> None:
|
||||
report = WeeklyReportMessage(
|
||||
week_range="2026-W24",
|
||||
report_date="2026-06-12 10:00",
|
||||
stats_source_ok=False,
|
||||
k3s_source_ok=True,
|
||||
git_source_ok=False,
|
||||
cost_source_ok=False,
|
||||
all_zero_actionable_anomaly=True,
|
||||
)
|
||||
|
||||
body = report.format()
|
||||
|
||||
assert "報表資料信任度" in body
|
||||
assert "判定: <b>低可信</b>" in body
|
||||
assert "Stats=fail" in body
|
||||
assert "Git=fail" in body
|
||||
assert "Cost=missing" in body
|
||||
assert "全 0: <code>actionable_anomaly</code>" in body
|
||||
assert "告警統計" in body
|
||||
|
||||
|
||||
def test_weekly_report_keeps_nonzero_source_status_visible() -> None:
|
||||
report = WeeklyReportMessage(
|
||||
week_range="2026-W24",
|
||||
report_date="2026-06-12 10:00",
|
||||
alert_total=3,
|
||||
ai_proposal_count=2,
|
||||
commits_count=5,
|
||||
deploy_count=1,
|
||||
ai_tokens_week=1200,
|
||||
stats_source_ok=True,
|
||||
k3s_source_ok=True,
|
||||
git_source_ok=True,
|
||||
cost_source_ok=True,
|
||||
)
|
||||
|
||||
body = report.format()
|
||||
|
||||
assert "判定: <b>可參考</b>" in body
|
||||
assert "全 0: <code>no</code>" in body
|
||||
assert "Commits: <code>5</code>" in body
|
||||
assert "Tokens: <code>1,200</code>" in body
|
||||
|
||||
|
||||
def test_telegram_html_chunks_preserve_complete_lines() -> None:
|
||||
"""歷史/詳情長訊息不得用 text[:500] 切壞 HTML tag。"""
|
||||
lines = [
|
||||
|
||||
Reference in New Issue
Block a user