All checks were successful
CD Pipeline / deploy (push) Successful in 2m49s
Operation Ollama-First v5.0 / Phase 24 — 戰役 ROI 自動量化 services/roi_report_service.py (200+ 行) - BASELINE 戰前估算(gemini 50M/$20、nim 5.8M、ollama 30M、total $25/月) - query_last_month_stats() — SQL 查上月 ai_calls + mcp_calls + rag_query_log - render_roi_report(stats) — HTML 訊息(含 5 區塊:成本攔截/provider 分布/RAG/MCP+Cache/KPI) - generate_and_send_roi_report() — 主入口,推 Telegram + 寫 ai_insights 長期記錄 - 達標標記:Gemini -23.5% ✅ / RAG 命中 ≥25% ✅ Telegram 訊息範例: 📊 ROI 月報 2026年04月 💰 成本攔截 Gemini: 35,000,000 tokens / $14.00 vs 戰前: 50,000,000 / $20.00 ✅ 攔截: 15,000,000 tokens / $6.00 (30.0%) 🤖 Provider 分布 🧠 RAG 自主學習(含 saved_call / 反饋分數) 🔧 MCP + Cache 📈 戰役 v5.0 KPI ✅ 達標 run_scheduler.py — 每日 09:00 跑(內部判斷月初第 1 日才送) - run_roi_monthly_report_if_new_month task wrapper - 失敗 swallow log,不影響其他排程 tests/test_roi_report_service.py (7 tests 全綠) - BASELINE 必要欄位 / 月份範圍計算 (1月→去年12月) - 達標訊息含 ✅ + 攔截數字 - 未達標訊息含 ⚠️ - 空 stats 容錯 / DB fail 回空 dict 不 raise Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
121 lines
4.1 KiB
Python
121 lines
4.1 KiB
Python
"""
|
|
tests/test_roi_report_service.py
|
|
─────────────────────────────────────────────────────────────────
|
|
Operation Ollama-First v5.0 / Phase 24 — ROI 月報生成器驗證
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
|
|
def test_baseline_constants():
|
|
"""戰前 baseline 應有 5 個必要欄位"""
|
|
from services.roi_report_service import BASELINE
|
|
|
|
required = {'gemini_monthly_tokens', 'gemini_monthly_cost_usd',
|
|
'nim_monthly_tokens', 'ollama_monthly_tokens',
|
|
'total_monthly_cost_usd'}
|
|
assert required.issubset(BASELINE.keys())
|
|
assert BASELINE['gemini_monthly_tokens'] == 50_000_000
|
|
assert BASELINE['gemini_monthly_cost_usd'] == 20.0
|
|
|
|
|
|
def test_last_month_range_january():
|
|
"""1 月跑時上月應為去年 12 月"""
|
|
from services.roi_report_service import _last_month_range
|
|
|
|
today = datetime(2026, 1, 5)
|
|
last_start, this_start = _last_month_range(today)
|
|
assert last_start == datetime(2025, 12, 1)
|
|
assert this_start == datetime(2026, 1, 1)
|
|
|
|
|
|
def test_last_month_range_normal_month():
|
|
from services.roi_report_service import _last_month_range
|
|
|
|
today = datetime(2026, 5, 15)
|
|
last_start, this_start = _last_month_range(today)
|
|
assert last_start == datetime(2026, 4, 1)
|
|
assert this_start == datetime(2026, 5, 1)
|
|
|
|
|
|
def test_render_roi_report_with_savings():
|
|
"""戰役後成功攔截 Gemini → 訊息含「攔截」+ 達標 ✅"""
|
|
from services.roi_report_service import render_roi_report
|
|
|
|
stats = {
|
|
'period_label': '2026年04月',
|
|
'period_start': datetime(2026, 4, 1),
|
|
'period_end': datetime(2026, 5, 1),
|
|
'ai_total_tokens': 80_000_000,
|
|
'ai_total_cost': 12.0,
|
|
'ai_total_calls': 5000,
|
|
'ollama_calls': 4000,
|
|
'gemini_calls': 800,
|
|
'claude_calls': 100,
|
|
'nim_calls': 100,
|
|
'rag_hit_calls': 200,
|
|
'cache_hit_calls': 150,
|
|
'gemini_tokens': 35_000_000, # 戰前 50M → 省 15M (30%)
|
|
'gemini_cost': 14.0, # 戰前 $20 → 省 $6
|
|
'claude_cost': 5.0,
|
|
'mcp_total': 180,
|
|
'mcp_cache_hits': 50,
|
|
'rag_total': 800,
|
|
'rag_saved': 250, # 31% RAG 攔截率
|
|
'rag_avg_feedback': 4.2,
|
|
}
|
|
msg = render_roi_report(stats)
|
|
|
|
assert '2026年04月' in msg
|
|
assert '攔截' in msg
|
|
assert '15,000,000' in msg or '15M' in msg or '15,000' in msg
|
|
assert '$6' in msg or '6.00' in msg
|
|
assert '✅' in msg # Gemini -23.5% 目標達標
|
|
|
|
|
|
def test_render_roi_report_below_target():
|
|
"""未達 -23.5% 目標 → 訊息含 ⚠️"""
|
|
from services.roi_report_service import render_roi_report
|
|
|
|
stats = {
|
|
'period_label': '2026年04月',
|
|
'period_start': datetime(2026, 4, 1),
|
|
'period_end': datetime(2026, 5, 1),
|
|
'ai_total_tokens': 80_000_000, 'ai_total_cost': 18.0, 'ai_total_calls': 5000,
|
|
'ollama_calls': 1000, 'gemini_calls': 3000, 'claude_calls': 500, 'nim_calls': 500,
|
|
'rag_hit_calls': 50, 'cache_hit_calls': 30,
|
|
'gemini_tokens': 45_000_000, # 戰前 50M → 只省 10% < 23%
|
|
'gemini_cost': 18.0, 'claude_cost': 5.0,
|
|
'mcp_total': 100, 'mcp_cache_hits': 10,
|
|
'rag_total': 200, 'rag_saved': 30, 'rag_avg_feedback': 3.5,
|
|
}
|
|
msg = render_roi_report(stats)
|
|
|
|
assert '⚠️' in msg # 未達標標記
|
|
|
|
|
|
def test_render_empty_stats():
|
|
"""空 stats → 預設失敗訊息"""
|
|
from services.roi_report_service import render_roi_report
|
|
|
|
assert '⚠️' in render_roi_report({})
|
|
|
|
|
|
def test_query_last_month_stats_db_fail_returns_empty(monkeypatch):
|
|
"""DB 失敗應回 {} 不 raise"""
|
|
from services.roi_report_service import query_last_month_stats
|
|
|
|
class _BrokenSession:
|
|
def execute(self, *a, **kw):
|
|
raise RuntimeError('DB connection lost')
|
|
def close(self):
|
|
pass
|
|
|
|
import services.roi_report_service as ros
|
|
monkeypatch.setattr('database.manager.get_session', lambda: _BrokenSession())
|
|
result = ros.query_last_month_stats()
|
|
assert result == {}
|