Files
signal-platform/app/models/sentiment.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

28 lines
1.0 KiB
Python

from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class SentimentScore(Base):
__tablename__ = "sentiment_scores"
id: Mapped[int] = mapped_column(primary_key=True)
ticker_id: Mapped[int] = mapped_column(
ForeignKey("tickers.id", ondelete="CASCADE"), nullable=False
)
classification: Mapped[str] = mapped_column(String(20), nullable=False)
confidence: Mapped[int] = mapped_column(Integer, nullable=False)
source: Mapped[str] = mapped_column(String(100), nullable=False)
timestamp: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False
)
reasoning: Mapped[str] = mapped_column(Text, nullable=False, default="")
citations_json: Mapped[str] = mapped_column(Text, nullable=False, default="[]")
recommendation: Mapped[str | None] = mapped_column(String(10), nullable=True)
ticker = relationship("Ticker", back_populates="sentiment_scores")