""" app/models/flywheel.py — preference_events / ai_call_logs Alembic 003(ai_call_logs)+ Alembic 004(preference_events) preference_profile 二期预留,一期不建。 """ from datetime import datetime from sqlalchemy import ( BigInteger, DateTime, Enum, ForeignKey, Index, Integer, String, Text, func, ) from sqlalchemy.orm import Mapped, mapped_column from app.constants.enums import DataOwnership, SignalType from app.core.database import Base class PreferenceEvent(Base): """ 飞轮信号日志。 workspace_id + product_id 都必须有(跨公司隔离 + 按产品分开学)。 angle_label 跟着产品的文案角度走,不写死(基石A)。 """ __tablename__ = "preference_events" id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) workspace_id: Mapped[int] = mapped_column(BigInteger, nullable=False) product_id: Mapped[int] = mapped_column( BigInteger, ForeignKey("products.id"), nullable=False ) task_id: Mapped[int] = mapped_column( BigInteger, ForeignKey("generation_tasks.id"), nullable=False ) user_id: Mapped[int] = mapped_column( BigInteger, ForeignKey("users.id"), nullable=False ) signal_type: Mapped[str] = mapped_column( Enum(SignalType, values_callable=lambda x: [e.value for e in x]), nullable=False, ) signal_weight: Mapped[int] = mapped_column(Integer, nullable=False) candidate_id: Mapped[int | None] = mapped_column(BigInteger) angle_label: Mapped[str | None] = mapped_column(String(64)) reason: Mapped[str | None] = mapped_column(Text) # 打回原因原文 signal_meta: Mapped[str | None] = mapped_column(Text) # JSON,扩展用 data_ownership: Mapped[str] = mapped_column( Enum(DataOwnership, values_callable=lambda x: [e.value for e in x]), default=DataOwnership.CLIENT_DATA, nullable=False, ) created_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), nullable=False ) __table_args__ = ( Index( "idx_preference_events_ws_product_created", "workspace_id", "product_id", "created_at", ), ) class AiCallLog(Base): """ AI 调用记录(usage + 排障基础)。 调用失败归因到个人 key(错误码50002)。 绝不记录明文 key,只记录 key_id。 """ __tablename__ = "ai_call_logs" id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) workspace_id: Mapped[int] = mapped_column(BigInteger, nullable=False) user_id: Mapped[int] = mapped_column( BigInteger, ForeignKey("users.id"), nullable=False ) key_id: Mapped[int | None] = mapped_column( BigInteger, ForeignKey("user_api_keys.id") ) task_id: Mapped[int | None] = mapped_column( BigInteger, ForeignKey("generation_tasks.id") ) provider: Mapped[str | None] = mapped_column(String(32)) model: Mapped[str | None] = mapped_column(String(64)) call_type: Mapped[str | None] = mapped_column(String(32)) # text/image/analyze prompt_tokens: Mapped[int | None] = mapped_column(Integer) completion_tokens: Mapped[int | None] = mapped_column(Integer) success: Mapped[bool] = mapped_column(default=True, nullable=False) error_code: Mapped[str | None] = mapped_column(String(32)) latency_ms: Mapped[int | None] = mapped_column(Integer) created_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), nullable=False ) __table_args__ = ( Index("idx_ai_call_logs_workspace_user", "workspace_id", "user_id"), Index("idx_ai_call_logs_task_id", "task_id"), )