""" News headline raw table — multi-source append-only feed. Sources: alpaca_benzinga, stocktwits, finnhub, gdelt. Per-ticker row split (one row per ticker × source × source_id) for indexed queries, with `tickers_all` preserving full vendor symbol set. PIT safety: news facts are publish-time, so no `as_of_date` column. Queries that need point-in-time correctness should filter `ingested_at <= cutoff`. """ from datetime import datetime, timezone import uuid from sqlalchemy import Boolean, Column, Float, Index, String, Text, UniqueConstraint from sqlalchemy.dialects.postgresql import ARRAY, TIMESTAMP, UUID from app.core.database import Base class NewsHeadline(Base): __tablename__ = "news_headline" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) source = Column(String(30), nullable=False) source_id = Column(String(100), nullable=False) ticker = Column(String(10), nullable=False) tickers_all = Column(ARRAY(String(10)), nullable=True) published_at = Column(TIMESTAMP(timezone=True), nullable=False) headline = Column(Text, nullable=False) summary = Column(Text, nullable=True) url = Column(Text, nullable=True) language = Column(String(8), server_default="en") vendor_categories = Column(ARRAY(String(50)), nullable=True) categories = Column(ARRAY(String(50)), nullable=True) raw_sentiment = Column(Float, nullable=True) is_primary = Column(Boolean, nullable=False, server_default="false") ingested_at = Column( TIMESTAMP(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc), ) created_at = Column( TIMESTAMP(timezone=True), default=lambda: datetime.now(timezone.utc) ) updated_at = Column( TIMESTAMP(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), ) __table_args__ = ( UniqueConstraint( "source", "source_id", "ticker", name="uq_news_headline_source_ticker", ), Index("idx_news_ticker_published", "ticker", "published_at"), Index("idx_news_published", "published_at"), Index("idx_news_source_published", "source", "published_at"), Index("idx_news_ingested", "ingested_at"), )