mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
* feat: add configurable report language * Fix review follow-ups for history and fallback localization * fix: prefer .env report language at startup * fix: address report language review feedback * fix: address latest report language review feedback
143 lines
4.5 KiB
Python
143 lines
4.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
===================================
|
|
Report Engine - Report renderer tests
|
|
===================================
|
|
|
|
Tests for Jinja2 report rendering and fallback behavior.
|
|
"""
|
|
|
|
import sys
|
|
import unittest
|
|
from unittest.mock import MagicMock
|
|
|
|
try:
|
|
import litellm # noqa: F401
|
|
except ModuleNotFoundError:
|
|
sys.modules["litellm"] = MagicMock()
|
|
|
|
from src.analyzer import AnalysisResult
|
|
from src.services.report_renderer import render
|
|
|
|
|
|
def _make_result(
|
|
code: str = "600519",
|
|
name: str = "贵州茅台",
|
|
sentiment_score: int = 72,
|
|
operation_advice: str = "持有",
|
|
analysis_summary: str = "稳健",
|
|
decision_type: str = "hold",
|
|
dashboard: dict = None,
|
|
report_language: str = "zh",
|
|
) -> AnalysisResult:
|
|
if dashboard is None:
|
|
dashboard = {
|
|
"core_conclusion": {"one_sentence": "持有观望"},
|
|
"intelligence": {"risk_alerts": []},
|
|
"battle_plan": {"sniper_points": {"stop_loss": "110"}},
|
|
}
|
|
return AnalysisResult(
|
|
code=code,
|
|
name=name,
|
|
trend_prediction="看多",
|
|
sentiment_score=sentiment_score,
|
|
operation_advice=operation_advice,
|
|
analysis_summary=analysis_summary,
|
|
decision_type=decision_type,
|
|
dashboard=dashboard,
|
|
report_language=report_language,
|
|
)
|
|
|
|
|
|
class TestReportRenderer(unittest.TestCase):
|
|
"""Report renderer tests."""
|
|
|
|
def test_render_markdown_summary_only(self) -> None:
|
|
"""Markdown platform renders with summary_only."""
|
|
r = _make_result()
|
|
out = render("markdown", [r], summary_only=True)
|
|
self.assertIsNotNone(out)
|
|
self.assertIn("决策仪表盘", out)
|
|
self.assertIn("贵州茅台", out)
|
|
self.assertIn("持有", out)
|
|
|
|
def test_render_markdown_full(self) -> None:
|
|
"""Markdown platform renders full report."""
|
|
r = _make_result()
|
|
out = render("markdown", [r], summary_only=False)
|
|
self.assertIsNotNone(out)
|
|
self.assertIn("核心结论", out)
|
|
self.assertIn("作战计划", out)
|
|
|
|
def test_render_wechat(self) -> None:
|
|
"""Wechat platform renders."""
|
|
r = _make_result()
|
|
out = render("wechat", [r])
|
|
self.assertIsNotNone(out)
|
|
self.assertIn("贵州茅台", out)
|
|
|
|
def test_render_brief(self) -> None:
|
|
"""Brief platform renders 3-5 sentence summary."""
|
|
r = _make_result()
|
|
out = render("brief", [r])
|
|
self.assertIsNotNone(out)
|
|
self.assertIn("决策简报", out)
|
|
self.assertIn("贵州茅台", out)
|
|
|
|
def test_render_markdown_in_english(self) -> None:
|
|
"""Markdown renderer switches headings and summary labels for English reports."""
|
|
r = _make_result(
|
|
name="Kweichow Moutai",
|
|
operation_advice="Buy",
|
|
analysis_summary="Momentum remains constructive.",
|
|
report_language="en",
|
|
)
|
|
out = render("markdown", [r], summary_only=True)
|
|
self.assertIsNotNone(out)
|
|
self.assertIn("Decision Dashboard", out)
|
|
self.assertIn("Summary", out)
|
|
self.assertIn("Buy", out)
|
|
|
|
def test_render_markdown_market_snapshot_uses_template_context(self) -> None:
|
|
"""Market snapshot macro should render localized labels with template context."""
|
|
r = _make_result(
|
|
code="AAPL",
|
|
name="Apple",
|
|
operation_advice="Buy",
|
|
report_language="en",
|
|
)
|
|
r.market_snapshot = {
|
|
"close": "180.10",
|
|
"prev_close": "178.25",
|
|
"open": "179.00",
|
|
"high": "181.20",
|
|
"low": "177.80",
|
|
"pct_chg": "+1.04%",
|
|
"change_amount": "1.85",
|
|
"amplitude": "1.91%",
|
|
"volume": "1200000",
|
|
"amount": "215000000",
|
|
"price": "180.35",
|
|
"volume_ratio": "1.2",
|
|
"turnover_rate": "0.8%",
|
|
"source": "polygon",
|
|
}
|
|
|
|
out = render("markdown", [r], summary_only=False)
|
|
|
|
self.assertIsNotNone(out)
|
|
self.assertIn("Market Snapshot", out)
|
|
self.assertIn("Volume Ratio", out)
|
|
|
|
def test_render_unknown_platform_returns_none(self) -> None:
|
|
"""Unknown platform returns None (caller fallback)."""
|
|
r = _make_result()
|
|
out = render("unknown_platform", [r])
|
|
self.assertIsNone(out)
|
|
|
|
def test_render_empty_results_returns_content(self) -> None:
|
|
"""Empty results still produces header."""
|
|
out = render("markdown", [], summary_only=True)
|
|
self.assertIsNotNone(out)
|
|
self.assertIn("0", out)
|