fix(api): 在日報月報 preview 顯示資料源沉澱
This commit is contained in:
@@ -23,6 +23,7 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from src.services import weekly_report_service as weekly_report_module
|
||||
from src.services.report_generation_service import (
|
||||
DAILY_REPORT_HOUR_TAIPEI,
|
||||
POSTMORTEM_MIN_DURATION_MINUTES,
|
||||
@@ -31,7 +32,6 @@ 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))
|
||||
@@ -143,15 +143,15 @@ class TestFormatDailyReport:
|
||||
|
||||
def _make_kpi(self, **kwargs) -> DailyKpi:
|
||||
now = datetime.now(_TZ_TAIPEI)
|
||||
defaults = dict(
|
||||
total_alerts=20,
|
||||
auto_resolved=15,
|
||||
human_approved=3,
|
||||
auto_repair_success=12,
|
||||
auto_repair_failed=3,
|
||||
km_new_entries=5,
|
||||
playbook_count=18,
|
||||
)
|
||||
defaults = {
|
||||
"total_alerts": 20,
|
||||
"auto_resolved": 15,
|
||||
"human_approved": 3,
|
||||
"auto_repair_success": 12,
|
||||
"auto_repair_failed": 3,
|
||||
"km_new_entries": 5,
|
||||
"playbook_count": 18,
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
return DailyKpi(
|
||||
period_start=now - timedelta(hours=24),
|
||||
@@ -239,6 +239,85 @@ class TestFormatDailyReport:
|
||||
report = svc.format_daily_report(kpi)
|
||||
assert "<b>" in report
|
||||
|
||||
def test_contains_report_source_health_assets(self):
|
||||
"""日報應顯示資料源健康與自動化資產沉澱"""
|
||||
kpi = self._make_kpi()
|
||||
source_health = {
|
||||
"rollups": {
|
||||
"source_ok_count": 2,
|
||||
"source_count": 5,
|
||||
"confidence_percent": 40,
|
||||
},
|
||||
"source_health": [
|
||||
{"work_item_id": "report-source-gap:incident_summary"},
|
||||
{"work_item_id": "report-source-gap:ai_performance"},
|
||||
],
|
||||
"automation_assets": [
|
||||
{"label": "KM", "state": "draft_ready", "done_count": 3, "blocked_count": 2},
|
||||
{"label": "PlayBook", "state": "draft_required", "done_count": 0, "blocked_count": 2},
|
||||
{"label": "腳本", "state": "readback_only", "done_count": 1, "blocked_count": 0},
|
||||
{"label": "排程", "state": "no_send_preview", "done_count": 3, "blocked_count": 0},
|
||||
{"label": "Verifier", "state": "source_health_ready", "done_count": 1, "blocked_count": 2},
|
||||
],
|
||||
"all_zero_assessment": {
|
||||
"all_zero_observed": True,
|
||||
"verdict": "source_gap_or_no_signal_requires_review",
|
||||
},
|
||||
}
|
||||
svc = ReportGenerationService()
|
||||
report = svc.format_daily_report(kpi, source_health)
|
||||
|
||||
assert "報表資料源 / 沉澱" in report
|
||||
assert "來源: <code>2/5</code>" in report
|
||||
assert "report-source-gap:incident_summary" in report
|
||||
assert "KM: draft_ready 3/5" in report
|
||||
assert "PlayBook: draft_required 0/2" in report
|
||||
assert "腳本: readback_only 1/1" in report
|
||||
assert "排程: no_send_preview 3/3" in report
|
||||
assert "Verifier: source_health_ready 1/3" in report
|
||||
assert "全 0 判讀: source_gap_or_no_signal_requires_review" in report
|
||||
assert "不自動改排程" in report
|
||||
|
||||
def test_monthly_preview_contains_no_send_source_health(self):
|
||||
"""月報 preview 應顯示 no-send 邊界與資產沉澱"""
|
||||
source_health = {
|
||||
"rollups": {
|
||||
"source_ok_count": 2,
|
||||
"source_count": 5,
|
||||
"confidence_percent": 40,
|
||||
"no_send_preview_count": 3,
|
||||
},
|
||||
"source_health": [
|
||||
{"work_item_id": "report-source-gap:resolution_stats"},
|
||||
],
|
||||
"no_send_previews": [
|
||||
{
|
||||
"cadence_id": "monthly",
|
||||
"owner_agent": "Hermes",
|
||||
"delivery_state": "no_send_preview",
|
||||
"gap_source_ids": ["resolution_stats", "ai_performance"],
|
||||
},
|
||||
],
|
||||
"automation_assets": [
|
||||
{"label": "KM", "state": "draft_ready", "done_count": 3, "blocked_count": 1},
|
||||
{"label": "Verifier", "state": "source_health_ready", "done_count": 1, "blocked_count": 1},
|
||||
],
|
||||
}
|
||||
svc = ReportGenerationService()
|
||||
report = svc.format_monthly_report_preview(
|
||||
source_health,
|
||||
generated_at=datetime(2026, 6, 18, 10, 0, tzinfo=_TZ_TAIPEI),
|
||||
)
|
||||
|
||||
assert "月報 no-send preview" in report
|
||||
assert "Owner: Hermes" in report
|
||||
assert "實發: 0" in report
|
||||
assert "來源: <code>2/5</code>" in report
|
||||
assert "resolution_stats" in report
|
||||
assert "KM: draft_ready 3/4" in report
|
||||
assert "Verifier: source_health_ready 1/2" in report
|
||||
assert "不代表已授權發送或自動修復" in report
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# format_postmortem
|
||||
@@ -250,18 +329,18 @@ class TestFormatPostmortem:
|
||||
|
||||
def _make_postmortem(self, **kwargs) -> PostmortemData:
|
||||
now = datetime.now(_TZ_TAIPEI)
|
||||
defaults = dict(
|
||||
incident_id="INC-20260414-001",
|
||||
title="KubePodOOMKilled on awoooi-api",
|
||||
duration_minutes=25.5,
|
||||
root_cause="記憶體洩漏導致 OOMKilled",
|
||||
resolution_action="kubectl rollout restart deployment/awoooi-api",
|
||||
ai_provider="OpenClaw (deepseek-r1:14b)",
|
||||
auto_repaired=True,
|
||||
retry_count=0,
|
||||
created_at=now - timedelta(minutes=25, seconds=30),
|
||||
resolved_at=now,
|
||||
)
|
||||
defaults = {
|
||||
"incident_id": "INC-20260414-001",
|
||||
"title": "KubePodOOMKilled on awoooi-api",
|
||||
"duration_minutes": 25.5,
|
||||
"root_cause": "記憶體洩漏導致 OOMKilled",
|
||||
"resolution_action": "kubectl rollout restart deployment/awoooi-api",
|
||||
"ai_provider": "OpenClaw (deepseek-r1:14b)",
|
||||
"auto_repaired": True,
|
||||
"retry_count": 0,
|
||||
"created_at": now - timedelta(minutes=25, seconds=30),
|
||||
"resolved_at": now,
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
return PostmortemData(**defaults)
|
||||
|
||||
|
||||
@@ -3,6 +3,29 @@ from fastapi.testclient import TestClient
|
||||
from src.main import app
|
||||
|
||||
|
||||
def test_daily_report_preview_exposes_source_health_no_send_preview():
|
||||
client = TestClient(app)
|
||||
response = client.get("/api/v1/stats/daily/preview")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "report_date" in data
|
||||
assert "alert_total" in data
|
||||
assert "km_new_entries" in data
|
||||
assert "playbook_count" in data
|
||||
assert "source_ok_count" in data
|
||||
assert "source_total_count" in data
|
||||
assert "source_confidence_percent" in data
|
||||
assert "source_gap_ids" in data
|
||||
assert "formatted_preview" in data
|
||||
|
||||
preview = data["formatted_preview"]
|
||||
assert "AWOOOI 日度巡檢報告" in preview
|
||||
assert "報表資料源 / 沉澱" in preview
|
||||
assert f"來源: <code>{data['source_ok_count']}/{data['source_total_count']}</code>" in preview
|
||||
assert "不自動改排程" in preview
|
||||
|
||||
|
||||
def test_weekly_report_preview_exposes_source_health_no_send_preview():
|
||||
client = TestClient(app)
|
||||
response = client.get("/api/v1/stats/weekly/preview")
|
||||
@@ -25,3 +48,25 @@ def test_weekly_report_preview_exposes_source_health_no_send_preview():
|
||||
assert "報表資料源 / 沉澱" in preview
|
||||
assert f"來源: <code>{data['source_ok_count']}/{data['source_total_count']}</code>" in preview
|
||||
assert "不自動改排程" in preview
|
||||
|
||||
|
||||
def test_monthly_report_preview_exposes_source_health_no_send_preview():
|
||||
client = TestClient(app)
|
||||
response = client.get("/api/v1/stats/monthly/preview")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "report_month" in data
|
||||
assert "source_ok_count" in data
|
||||
assert "source_total_count" in data
|
||||
assert "source_confidence_percent" in data
|
||||
assert "source_gap_ids" in data
|
||||
assert "no_send_preview_count" in data
|
||||
assert "formatted_preview" in data
|
||||
|
||||
preview = data["formatted_preview"]
|
||||
assert "月報 no-send preview" in preview
|
||||
assert "報表資料源 / 沉澱" in preview
|
||||
assert f"來源: <code>{data['source_ok_count']}/{data['source_total_count']}</code>" in preview
|
||||
assert "實發: 0" in preview
|
||||
assert "不代表已授權發送或自動修復" in preview
|
||||
|
||||
Reference in New Issue
Block a user