"""Tests for the optional LLM thesis rewriting layer. Tests prompt construction and the rewrite function's fallback behavior. """ from __future__ import annotations import pytest from services.recommendation.thesis_llm import ( THESIS_SYSTEM_PROMPT, build_thesis_rewrite_prompt, rewrite_thesis_with_llm, ) from services.shared.config import OllamaConfig from services.shared.schemas import ( TrendDirection, TrendSummary, TrendWindow, ) def _make_summary( ticker: str = "AAPL", direction: TrendDirection = TrendDirection.BULLISH, strength: float = 0.5, confidence: float = 0.65, contradiction: float = 0.1, catalysts: list[str] | None = None, risks: list[str] | None = None, ) -> TrendSummary: return TrendSummary( entity_type="company", entity_id=ticker, window=TrendWindow.SEVEN_DAY, trend_direction=direction, trend_strength=strength, confidence=confidence, top_supporting_evidence=["doc1", "doc2"], top_opposing_evidence=[], dominant_catalysts=catalysts or ["earnings"], material_risks=risks or ["regulatory scrutiny"], contradiction_score=contradiction, ) DETERMINISTIC_THESIS = ( "AAPL shows a bullish trend over the 7d window with strength 0.50 " "and confidence 0.65. Dominant catalysts: earnings. " "Key risks: regulatory scrutiny. " "Based on 2 supporting and 0 opposing evidence documents. " "Recommendation: BUY (paper eligible)." ) # --------------------------------------------------------------------------- # Prompt construction # --------------------------------------------------------------------------- def test_prompt_contains_deterministic_thesis(): summary = _make_summary() prompts = build_thesis_rewrite_prompt(DETERMINISTIC_THESIS, summary) assert DETERMINISTIC_THESIS in prompts["user"] def test_prompt_system_is_thesis_system_prompt(): summary = _make_summary() prompts = build_thesis_rewrite_prompt(DETERMINISTIC_THESIS, summary) assert prompts["system"] == THESIS_SYSTEM_PROMPT def test_prompt_includes_ticker_context(): summary = _make_summary(ticker="MSFT") prompts = build_thesis_rewrite_prompt(DETERMINISTIC_THESIS, summary) assert "MSFT" in prompts["user"] def test_prompt_includes_catalysts(): summary = _make_summary(catalysts=["product", "m_and_a"]) prompts = build_thesis_rewrite_prompt(DETERMINISTIC_THESIS, summary) assert "product" in prompts["user"] def test_prompt_includes_risks(): summary = _make_summary(risks=["supply chain disruption"]) prompts = build_thesis_rewrite_prompt(DETERMINISTIC_THESIS, summary) assert "supply chain disruption" in prompts["user"] def test_prompt_includes_trend_metrics(): summary = _make_summary(strength=0.72, confidence=0.88, contradiction=0.15) prompts = build_thesis_rewrite_prompt(DETERMINISTIC_THESIS, summary) assert "0.72" in prompts["user"] assert "0.88" in prompts["user"] assert "0.15" in prompts["user"] # --------------------------------------------------------------------------- # Fallback behavior — LLM failure returns deterministic thesis # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_rewrite_falls_back_on_connection_error(): """When Ollama is unreachable, the deterministic thesis is returned.""" summary = _make_summary() config = OllamaConfig(base_url="http://localhost:99999", timeout=2) result = await rewrite_thesis_with_llm( deterministic_thesis=DETERMINISTIC_THESIS, summary=summary, config=config, ) assert result == DETERMINISTIC_THESIS