diff --git a/apps/api/src/services/telegram_gateway.py b/apps/api/src/services/telegram_gateway.py
index 8a3a855b..267e9693 100644
--- a/apps/api/src/services/telegram_gateway.py
+++ b/apps/api/src/services/telegram_gateway.py
@@ -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"🧭 報表資料信任度\n"
+ f"├ 判定: {report_trust}\n"
+ f"├ 來源: {html.escape(source_status)}\n"
+ f"└ 全 0: {'actionable_anomaly' if actionable_all_zero else 'no'}\n"
+ f"━━━━━━━━━━━━━━━━━━━\n"
f"{alert_health} 告警統計\n"
f"├ 總數: {self.alert_total}\n"
f"├ Critical: {self.alert_critical}\n"
@@ -3222,7 +3255,7 @@ class WeeklyReportMessage:
f"└ 自動化率: {auto_rate}%"
)
- return message[:1200]
+ return message[:1800]
@dataclass
diff --git a/apps/api/src/services/weekly_report_service.py b/apps/api/src/services/weekly_report_service.py
index 568c5a45..a484423d 100644
--- a/apps/api/src/services/weekly_report_service.py
+++ b/apps/api/src/services/weekly_report_service.py
@@ -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(
diff --git a/apps/api/tests/test_telegram_message_templates.py b/apps/api/tests/test_telegram_message_templates.py
index d7e5888e..bb9ee64a 100644
--- a/apps/api/tests/test_telegram_message_templates.py
+++ b/apps/api/tests/test_telegram_message_templates.py
@@ -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 "判定: 低可信" in body
+ assert "Stats=fail" in body
+ assert "Git=fail" in body
+ assert "Cost=missing" in body
+ assert "全 0: actionable_anomaly" 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 "判定: 可參考" in body
+ assert "全 0: no" in body
+ assert "Commits: 5" in body
+ assert "Tokens: 1,200" in body
+
+
def test_telegram_html_chunks_preserve_complete_lines() -> None:
"""歷史/詳情長訊息不得用 text[:500] 切壞 HTML tag。"""
lines = [