from datetime import datetime from typing import Optional, List from sqlalchemy import String, Text, Integer, Float, Boolean, DateTime, ForeignKey, ARRAY from sqlalchemy.orm import Mapped, mapped_column, relationship from ..database import Base class NewsSource(Base): __tablename__ = "news_sources" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(100)) url: Mapped[str] = mapped_column(String(500)) source_type: Mapped[str] = mapped_column(String(20), default="rss") # rss | scrape language: Mapped[str] = mapped_column(String(5), default="zh") # zh | en category: Mapped[Optional[str]] = mapped_column(String(50)) is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) raw_news: Mapped[List["RawNews"]] = relationship(back_populates="source") class RawNews(Base): __tablename__ = "raw_news" id: Mapped[int] = mapped_column(primary_key=True) source_id: Mapped[Optional[int]] = mapped_column(ForeignKey("news_sources.id")) title: Mapped[str] = mapped_column(String(500)) url: Mapped[str] = mapped_column(String(1000), unique=True) raw_content: Mapped[Optional[str]] = mapped_column(Text) published_at: Mapped[Optional[datetime]] = mapped_column(DateTime) crawled_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) status: Mapped[str] = mapped_column(String(20), default="pending") # pending|processed|skipped|error source: Mapped[Optional["NewsSource"]] = relationship(back_populates="raw_news") processed: Mapped[Optional["ProcessedNews"]] = relationship(back_populates="raw_news", uselist=False) class ProcessedNews(Base): __tablename__ = "processed_news" id: Mapped[int] = mapped_column(primary_key=True) raw_news_id: Mapped[int] = mapped_column(ForeignKey("raw_news.id")) title_zh: Mapped[str] = mapped_column(String(500)) summary: Mapped[str] = mapped_column(Text) opinion: Mapped[Optional[str]] = mapped_column(Text) keywords: Mapped[Optional[List[str]]] = mapped_column(ARRAY(String)) importance_score: Mapped[float] = mapped_column(Float, default=5.0) importance_reason: Mapped[Optional[str]] = mapped_column(Text) category: Mapped[str] = mapped_column(String(50), default="行业动态") is_featured: Mapped[bool] = mapped_column(Boolean, default=False) featured_rank: Mapped[Optional[int]] = mapped_column(Integer) source_name: Mapped[Optional[str]] = mapped_column(String(200)) source_url: Mapped[Optional[str]] = mapped_column(String(1000)) published_at: Mapped[Optional[datetime]] = mapped_column(DateTime) processed_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) raw_news: Mapped["RawNews"] = relationship(back_populates="processed") class LLMConfig(Base): __tablename__ = "llm_config" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(100)) provider: Mapped[str] = mapped_column(String(50)) # openai | anthropic | qwen | deepseek | custom api_key: Mapped[str] = mapped_column(String(500)) base_url: Mapped[str] = mapped_column(String(500)) model_name: Mapped[str] = mapped_column(String(200)) is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) class SystemLog(Base): __tablename__ = "system_logs" id: Mapped[int] = mapped_column(primary_key=True) event_type: Mapped[str] = mapped_column(String(50)) message: Mapped[str] = mapped_column(Text) level: Mapped[str] = mapped_column(String(20), default="INFO") created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)