|
|
"""
|
|
|
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"),
|
|
|
)
|