Files
signal-platform/tests/unit/test_sentiment_recommendation.py
T
dennisthiessen e5166ed668
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 34s
Deploy / deploy (push) Successful in 21s
sentiment: LLM buy/hold/avoid + full analysis, and search-budget scoping
Richer LLM output (same grounded call, ~no extra cost):
- All providers now also return a recommendation (buy/hold/avoid) and a thorough
  reasoning paragraph; Gemini now actually captures reasoning + grounding
  citations (it was dropping them). Stored on sentiment_scores (migration 008),
  exposed in the API; display-only — NOT fed into the composite/EV.
- Ticker Sentiment panel shows an "LLM view" badge and a "Full analysis & sources"
  expander with the complete reasoning + citations.

Search-budget scoping (Gemini grounding free tier = 5000/mo):
- collect_sentiment now targets only watchlist + open paper trades + top-N by
  composite, skips tickers refreshed within sentiment_fresh_hours (72h), and caps
  per run (sentiment_max_per_run). Once the relevant set is fresh, runs spend 0
  searches until it ages out — bounding monthly usage well under the free tier.
- Widened sentiment lookback to 7d (scoring + display) so sparser collection
  still feeds the dimension score.

Deploy: alembic upgrade (sentiment_scores.recommendation). Switch provider to
Gemini Flash in Admin for the cost win (grounded, cheapest).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:34:19 +02:00

47 lines
1.4 KiB
Python

"""Tests for the LLM recommendation field on sentiment."""
from __future__ import annotations
import pytest
from app.models.ticker import Ticker
from app.providers.openai_sentiment import parse_recommendation
from app.services import sentiment_service as svc
from tests.conftest import _test_session_factory # type: ignore
@pytest.fixture
async def session():
async with _test_session_factory() as s:
yield s
def test_parse_recommendation_valid_and_invalid():
assert parse_recommendation("buy") == "buy"
assert parse_recommendation("HOLD") == "hold"
assert parse_recommendation(" Avoid ") == "avoid"
assert parse_recommendation("strong buy") is None
assert parse_recommendation(None) is None
assert parse_recommendation("") is None
async def test_store_persists_recommendation(session):
session.add(Ticker(symbol="AAA"))
await session.commit()
rec = await svc.store_sentiment(
session,
symbol="AAA",
classification="bullish",
confidence=70,
source="gemini",
reasoning="Analysts upgraded; strong retail buzz.",
citations=[{"url": "http://x", "title": "X"}],
recommendation="buy",
)
assert rec.recommendation == "buy"
rows = await svc.get_sentiment_scores(session, "AAA", lookback_hours=168)
assert rows[0].recommendation == "buy"
assert rows[0].reasoning.startswith("Analysts")