from datetime import datetime, timezone, time from typing import Optional, Any import enum from sqlalchemy import ( String, Integer, BigInteger, Text, Boolean, DateTime, Time, Float, JSON, ForeignKey, Enum, UniqueConstraint, Index ) from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship # ========================================== # 0. 全局基类与枚举定义 # ========================================== class Base(DeclarativeBase): """SQLAlchemy 2.0 声明式基类""" pass class SourceType(str, enum.Enum): HOT_TREND = "HOT_TREND" RSS_FEED = "RSS_FEED" API = "API" class TargetType(str, enum.Enum): EVENT = "EVENT" TREND = "TREND" ARTICLE = "ARTICLE" class TaskStatus(str, enum.Enum): SUCCESS = "SUCCESS" ERROR = "ERROR" class GenderType(str, enum.Enum): MALE = "MALE" FEMALE = "FEMALE" OTHER = "OTHER" UNKNOWN = "UNKNOWN" def utcnow(): """获取带UTC时区的当前时间 (推荐实践)""" return datetime.now(timezone.utc) # ========================================== # 模块一:信息源管理 # ========================================== class InfoSource(Base): __tablename__ = "info_sources" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) source_name: Mapped[str] = mapped_column(String(100), comment="信息源名称") source_type: Mapped[SourceType] = mapped_column(Enum(SourceType)) home_url: Mapped[Optional[str]] = mapped_column(String(255)) is_enabled: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) # ========================================== # 模块二:AI 语义聚类中枢 (大事件池) # ========================================== class UnifiedEvent(Base): __tablename__ = "unified_events" id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) unified_title: Mapped[str] = mapped_column(String(255), comment="AI统一标题") ai_comprehensive_summary: Mapped[Optional[str]] = mapped_column(Text, comment="AI全局深度总结") # SQLite 没有原生 Vector 类型,存为用逗号分隔的字符串或JSON,Postgres可换成 PGVector center_embedding: Mapped[Optional[str]] = mapped_column(Text, comment="中心向量") hot_score: Mapped[int] = mapped_column(Integer, default=0, comment="聚合热度得分") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) # ========================================== # 模块三:内容存储库 (热搜 & 新闻子节点) # ========================================== class TrendingEvent(Base): __tablename__ = "trending_events" __table_args__ = ( UniqueConstraint("source_id", "external_id", name="idx_unique_external_trend"), ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) source_id: Mapped[int] = mapped_column(ForeignKey("info_sources.id")) unified_event_id: Mapped[Optional[int]] = mapped_column(ForeignKey("unified_events.id")) external_id: Mapped[str] = mapped_column(String(32), comment="32位MD5哈希指纹防重") title_embedding: Mapped[Optional[str]] = mapped_column(Text) icon_url: Mapped[Optional[str]] = mapped_column(String(500)) current_headline: Mapped[str] = mapped_column(String(255)) event_url: Mapped[Optional[str]] = mapped_column(String(500)) app_link: Mapped[Optional[str]] = mapped_column(String(500)) current_ranking: Mapped[Optional[int]] = mapped_column(Integer) brief_snippet: Mapped[Optional[str]] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) class NewsArticle(Base): __tablename__ = "news_articles" __table_args__ = ( UniqueConstraint("source_id", "external_id", name="idx_unique_external_article"), ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) source_id: Mapped[int] = mapped_column(ForeignKey("info_sources.id")) unified_event_id: Mapped[Optional[int]] = mapped_column(ForeignKey("unified_events.id")) external_id: Mapped[str] = mapped_column(String(32)) title_embedding: Mapped[Optional[str]] = mapped_column(Text) cover_image_url: Mapped[Optional[str]] = mapped_column(String(500)) article_title: Mapped[str] = mapped_column(String(255)) article_url: Mapped[Optional[str]] = mapped_column(String(500)) author_name: Mapped[Optional[str]] = mapped_column(String(100)) original_summary: Mapped[Optional[str]] = mapped_column(Text) publish_time: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) # ========================================== # 模块四:热度与轨迹追踪 # ========================================== class HeadlineRevision(Base): __tablename__ = "headline_revisions" id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) event_id: Mapped[int] = mapped_column(ForeignKey("trending_events.id")) previous_headline: Mapped[str] = mapped_column(String(255)) revised_headline: Mapped[str] = mapped_column(String(255)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) class RankingLog(Base): __tablename__ = "ranking_logs" __table_args__ = ( Index("idx_event_time", "event_id", "observed_at"), ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) event_id: Mapped[int] = mapped_column(ForeignKey("trending_events.id")) ranking_position: Mapped[int] = mapped_column(Integer) observed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) # ========================================== # 模块五:多态话题与多态评论 # ========================================== class ExtractedTopic(Base): __tablename__ = "extracted_topics" __table_args__ = ( Index("idx_topic_keyword", "topic_keyword"), Index("idx_polymorphic_topics", "target_type", "target_id"), ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) target_type: Mapped[TargetType] = mapped_column(Enum(TargetType)) target_id: Mapped[int] = mapped_column(BigInteger) topic_keyword: Mapped[str] = mapped_column(String(100)) relevance_score: Mapped[Optional[float]] = mapped_column(Float) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) class DiscussionComment(Base): __tablename__ = "discussion_comments" __table_args__ = ( Index("idx_polymorphic_comments", "target_type", "target_id"), ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) target_type: Mapped[TargetType] = mapped_column(Enum(TargetType)) target_id: Mapped[int] = mapped_column(BigInteger) commenter_name: Mapped[Optional[str]] = mapped_column(String(100)) comment_content: Mapped[str] = mapped_column(Text) likes_count: Mapped[int] = mapped_column(Integer, default=0) external_comment_id: Mapped[Optional[str]] = mapped_column(String(32)) comment_time: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) # ========================================== # 模块六:用户画像与多渠道高可用推送系统 # ========================================== class AppUser(Base): __tablename__ = "app_users" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) email: Mapped[str] = mapped_column(String(150), unique=True, index=True) password_hash: Mapped[Optional[str]] = mapped_column(String(255)) nickname: Mapped[Optional[str]] = mapped_column(String(100)) avatar_url: Mapped[Optional[str]] = mapped_column(String(500)) gender: Mapped[GenderType] = mapped_column(Enum(GenderType), default=GenderType.UNKNOWN) # 核心:万能扩展收纳箱 (SQLite 完美支持通过 SQLAlchemy 存储 JSON) metadata_: Mapped[Optional[Any]] = mapped_column("metadata", JSON, comment="自定义扩展偏好") timezone: Mapped[str] = mapped_column(String(50), default="Asia/Shanghai") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) class UserPushEndpoint(Base): __tablename__ = "user_push_endpoints" __table_args__ = ( UniqueConstraint("user_id", "channel_type", name="idx_unique_user_channel"), ) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(ForeignKey("app_users.id")) channel_type: Mapped[str] = mapped_column(String(50), comment="如 EMAIL, WECHAT") channel_account: Mapped[str] = mapped_column(String(255)) is_active: Mapped[bool] = mapped_column(Boolean, default=True) priority_level: Mapped[int] = mapped_column(Integer, default=1, comment="1最高,降级重试") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) class UserTopicPreference(Base): __tablename__ = "user_topic_preferences" __table_args__ = ( UniqueConstraint("user_id", "interested_keyword", name="idx_unique_preference"), ) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(ForeignKey("app_users.id")) interested_keyword: Mapped[str] = mapped_column(String(100)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) class UserDeliverySchedule(Base): __tablename__ = "user_delivery_schedules" __table_args__ = ( UniqueConstraint("user_id", "delivery_time", name="idx_unique_schedule"), ) id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(ForeignKey("app_users.id")) delivery_time: Mapped[time] = mapped_column(Time, comment="如 08:30:00") is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) class DeliveryHistory(Base): __tablename__ = "delivery_history" __table_args__ = ( UniqueConstraint("user_id", "target_type", "target_id", name="idx_prevent_duplicate_push"), ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(ForeignKey("app_users.id")) target_type: Mapped[TargetType] = mapped_column(Enum(TargetType)) target_id: Mapped[int] = mapped_column(BigInteger) status: Mapped[TaskStatus] = mapped_column(Enum(TaskStatus)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) # ========================================== # 模块七:系统任务监控 # ========================================== class DataSyncTask(Base): __tablename__ = "data_sync_tasks" id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) source_id: Mapped[int] = mapped_column(ForeignKey("info_sources.id")) items_fetched: Mapped[int] = mapped_column(Integer, default=0) task_status: Mapped[TaskStatus] = mapped_column(Enum(TaskStatus)) error_trace: Mapped[Optional[str]] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)