fix(api): 讓週報全零顯示資料缺口
All checks were successful
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / tests (push) Successful in 1m33s
CD Pipeline / build-and-deploy (push) Successful in 3m55s
CD Pipeline / post-deploy-checks (push) Successful in 1m49s

This commit is contained in:
Your Name
2026-06-18 18:13:29 +08:00
parent 67ee84818b
commit ac3258524f
4 changed files with 104 additions and 8 deletions

View File

@@ -3621,10 +3621,27 @@ class WeeklyReportMessage:
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'}"
f"統計={'正常' if self.stats_source_ok else '失效'} / "
f"K3s={'正常' if self.k3s_source_ok else '失效'} / "
f"Git={'正常' if self.git_source_ok else '失效'} / "
f"成本={'正常' if self.cost_source_ok else '缺資料'}"
)
source_gaps: list[str] = []
if not self.stats_source_ok:
source_gaps.append("告警 / AI 統計資料源失效:建立 report-source-gap:stats_api")
if not self.k3s_source_ok:
source_gaps.append("K3s 指標資料源失效:建立 report-source-gap:k3s_metrics")
if not self.git_source_ok:
source_gaps.append("開發活動資料源失效:建立 report-source-gap:gitea_activity")
if not self.cost_source_ok:
source_gaps.append("AI 成本資料源缺資料:建立 report-source-gap:ai_cost_ledger")
if actionable_all_zero:
source_gaps.insert(0, "全 0 不是健康:必須追查資料鏈路 freshness / confidence")
if not source_gaps:
source_gaps.append("資料源通過;持續比對趨勢、異常與修復沉澱")
gap_lines = "\n".join(
f"{html.escape(item)}" if index < len(source_gaps) - 1 else f"{html.escape(item)}"
for index, item in enumerate(source_gaps[:5])
)
message = (
@@ -3662,6 +3679,10 @@ class WeeklyReportMessage:
f"💰 <b>AI 成本</b>\n"
f"├ 費用: $<code>{self.ai_cost_week:.2f}</code>\n"
f"└ Tokens: <code>{self.ai_tokens_week:,}</code>\n"
f"━━━━━━━━━━━━━━━━━━━\n"
f"🧩 <b>資料缺口 / 下一步</b>\n"
f"{gap_lines}\n"
f"只讀判讀:不自動改排程、不直接發修復、不取代人工批准。\n"
)
# Sprint 4 F1: 處置分佈(有資料才加)
@@ -3678,7 +3699,7 @@ class WeeklyReportMessage:
f"└ 自動化率: <b>{auto_rate}%</b>"
)
return message[:1800]
return message[:2400]
@dataclass

View File

@@ -106,6 +106,13 @@ class WeeklyReportService:
timeout=10,
cwd="/app", # K8s 容器內的工作目錄
)
if result.returncode != 0:
logger.warning(
"git_stats_commits_failed",
returncode=result.returncode,
stderr=result.stderr[-300:],
)
return 0, 0, False
commits = len(result.stdout.strip().split("\n")) if result.stdout.strip() else 0
# 取得部署次數 (計算含 "deploy" 或 "CD" 的 commits)
@@ -116,6 +123,13 @@ class WeeklyReportService:
timeout=10,
cwd="/app",
)
if result_deploy.returncode != 0:
logger.warning(
"git_stats_deploys_failed",
returncode=result_deploy.returncode,
stderr=result_deploy.stderr[-300:],
)
return commits, 0, False
deploys = len(result_deploy.stdout.strip().split("\n")) if result_deploy.stdout.strip() else 0
return commits, deploys, True

View File

@@ -31,10 +31,66 @@ from src.services.report_generation_service import (
ReportGenerationService,
_seconds_until_next_report,
)
from src.services import weekly_report_service as weekly_report_module
from src.services.weekly_report_service import WeeklyReportService
_TZ_TAIPEI = timezone(timedelta(hours=8))
# =============================================================================
# WeeklyReportService Git 資料源可信度
# =============================================================================
class TestWeeklyReportGitStats:
"""週報不能把 Git 資料源失敗偽裝成 0 commits / 0 deploys。"""
def test_git_log_failure_marks_source_failed(self, monkeypatch):
class Result:
returncode = 128
stdout = ""
stderr = "fatal: not a git repository"
monkeypatch.setattr(
weekly_report_module.subprocess,
"run",
lambda *args, **kwargs: Result(),
)
svc = WeeklyReportService(stats_service=object(), k3s_monitor=object())
commits, deploys, source_ok = svc._get_git_stats(datetime.now(_TZ_TAIPEI))
assert commits == 0
assert deploys == 0
assert source_ok is False
def test_git_deploy_log_failure_marks_source_failed_after_commits(self, monkeypatch):
class CommitResult:
returncode = 0
stdout = "abc123 feat: one\nbcd234 fix: two\n"
stderr = ""
class DeployResult:
returncode = 128
stdout = ""
stderr = "fatal: bad revision"
calls = []
def fake_run(*args, **kwargs):
calls.append(args[0])
return CommitResult() if len(calls) == 1 else DeployResult()
monkeypatch.setattr(weekly_report_module.subprocess, "run", fake_run)
svc = WeeklyReportService(stats_service=object(), k3s_monitor=object())
commits, deploys, source_ok = svc._get_git_stats(datetime.now(_TZ_TAIPEI))
assert commits == 2
assert deploys == 0
assert source_ok is False
# =============================================================================
# DailyKpi 計算屬性
# =============================================================================

View File

@@ -265,10 +265,14 @@ def test_weekly_report_marks_all_zero_as_low_trust_anomaly() -> None:
assert "報表資料信任度" in body
assert "判定: <b>低可信</b>" in body
assert "Stats=fail" in body
assert "Git=fail" in body
assert "Cost=missing" in body
assert "統計=失效" in body
assert "Git=失效" in body
assert "成本=缺資料" in body
assert "全 0: <code>actionable_anomaly</code>" in body
assert "資料缺口 / 下一步" in body
assert "全 0 不是健康" in body
assert "report-source-gap:stats_api" in body
assert "report-source-gap:gitea_activity" in body
assert "告警統計" in body
@@ -291,6 +295,7 @@ def test_weekly_report_keeps_nonzero_source_status_visible() -> None:
assert "判定: <b>可參考</b>" in body
assert "全 0: <code>no</code>" in body
assert "資料源通過" in body
assert "Commits: <code>5</code>" in body
assert "Tokens: <code>1,200</code>" in body