fix(api): 讓週報全零顯示資料缺口
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 計算屬性
|
||||
# =============================================================================
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user