from __future__ import annotations from datetime import datetime from typing import Literal, Optional from sqlalchemy import ( JSON, Boolean, DateTime, Enum, ForeignKey, Index, Numeric, func, ) from sqlalchemy.orm import Mapped, mapped_column from app.db.base import Base ContentStage = Literal["general", "expecting", "parenting", "unknown"] class ContentProfile(Base): """ 内容画像表(Content Profile / Cᵢ)。 字段语义必须严格对齐: - `设计说明文档/句子文案打分規則.md` """ __tablename__ = "content_profiles" __table_args__ = ( Index("idx_profiles_stage", "stage"), Index("idx_profiles_personalization_power", "personalization_power"), Index("idx_profiles_is_safe_pool", "is_safe_pool"), ) content_id: Mapped[int] = mapped_column( ForeignKey("contents.content_id", ondelete="CASCADE"), primary_key=True, comment="FK -> contents.content_id", ) stage: Mapped[ContentStage] = mapped_column( Enum("general", "expecting", "parenting", "unknown", name="content_stage"), nullable=False, server_default="general", comment="母职阶段定位(general/expecting/parenting/unknown)", ) emotion_score: Mapped[Optional[float]] = mapped_column( Numeric(3, 2), nullable=True, comment="情绪调性 0~1;NULL 表示 general", ) context_suitability_json: Mapped[dict] = mapped_column( JSON, nullable=False, comment="各 context 的适配度(JSON:0/0.5/1;必须包含 5 个 key)", ) need_suitability_json: Mapped[dict] = mapped_column( JSON, nullable=False, comment="各 need 的适配度(JSON:0/0.5/1;必须包含 5 个 key)", ) personalization_power: Mapped[int] = mapped_column( nullable=False, server_default="0", comment="个性化力度(约定只允许 0/5/10,分别映射 0/0.5/1)", ) review_confidence: Mapped[Optional[float]] = mapped_column( Numeric(3, 2), nullable=True, comment="标注置信度 0~1;NULL 表示由推荐侧按 0.7 兜底", ) is_safe_pool: Mapped[bool] = mapped_column( Boolean, nullable=False, server_default="0", comment="是否属于通用安全池(L3 兜底)", ) updated_at: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=func.now(), server_onupdate=func.now(), comment="画像更新时间", )