""" 群組與關鍵字資料模型 """ from datetime import datetime from sqlalchemy import String, Boolean, ForeignKey, Text, JSON, Enum as SQLEnum, UniqueConstraint, Index, DECIMAL from sqlalchemy.orm import Mapped, mapped_column, relationship from typing import Optional, List import enum from app.db.session import Base class GroupCategory(str, enum.Enum): """群組分類""" INDUSTRY = "industry" TOPIC = "topic" class Group(Base): """群組表""" __tablename__ = "groups" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(100), nullable=False, comment="群組名稱") description: Mapped[Optional[str]] = mapped_column(Text, comment="群組描述") category: Mapped[GroupCategory] = mapped_column(SQLEnum(GroupCategory), nullable=False, comment="分類") ai_background: Mapped[Optional[str]] = mapped_column(Text, comment="AI背景資訊設定") ai_prompt: Mapped[Optional[str]] = mapped_column(Text, comment="AI摘要方向提示") is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否啟用") created_by: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id"), comment="建立者ID") created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(default=datetime.utcnow, onupdate=datetime.utcnow) # 關聯 keywords: Mapped[List["Keyword"]] = relationship(back_populates="group", cascade="all, delete-orphan") article_matches: Mapped[List["ArticleGroupMatch"]] = relationship(back_populates="group", cascade="all, delete-orphan") reports: Mapped[List["Report"]] = relationship(back_populates="group") subscriptions: Mapped[List["Subscription"]] = relationship(back_populates="group", cascade="all, delete-orphan") class Keyword(Base): """關鍵字表""" __tablename__ = "keywords" __table_args__ = ( UniqueConstraint("group_id", "keyword", name="uk_group_keyword"), Index("idx_keywords_keyword", "keyword"), ) id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) group_id: Mapped[int] = mapped_column(ForeignKey("groups.id", ondelete="CASCADE"), nullable=False, comment="所屬群組ID") keyword: Mapped[str] = mapped_column(String(100), nullable=False, comment="關鍵字") is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) # 關聯 group: Mapped["Group"] = relationship(back_populates="keywords") class ArticleGroupMatch(Base): """新聞-群組匹配關聯表""" __tablename__ = "article_group_matches" __table_args__ = ( UniqueConstraint("article_id", "group_id", name="uk_article_group"), Index("idx_matches_group", "group_id"), ) id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) article_id: Mapped[int] = mapped_column(ForeignKey("news_articles.id", ondelete="CASCADE"), nullable=False) group_id: Mapped[int] = mapped_column(ForeignKey("groups.id", ondelete="CASCADE"), nullable=False) matched_keywords: Mapped[Optional[list]] = mapped_column(JSON, comment="匹配到的關鍵字列表") match_score: Mapped[Optional[float]] = mapped_column(DECIMAL(5, 2), comment="匹配分數") created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) # 關聯 article: Mapped["NewsArticle"] = relationship(back_populates="group_matches") group: Mapped["Group"] = relationship(back_populates="article_matches") # 避免循環引入 from app.models.news import NewsArticle from app.models.report import Report from app.models.interaction import Subscription