from sqlalchemy import Float, ForeignKey, Index, Integer, String, Text, text from sqlalchemy.orm import Mapped, mapped_column from app.models.base import Base, TimestampMixin class ApiUsageLog(Base, TimestampMixin): """API 调用详细消耗记录表。 记录每次 API 请求的完整消费信息,包括: - 扣除金额和退回金额 - 模型详情(名称、分辨率、时长等) - 对应的生成任务 ID - 操作类型(扣除/退回) """ __tablename__ = "api_usage_logs" __table_args__ = ( Index("idx_api_usage_logs_api_key_created", "api_key_id", "created_at"), Index("idx_api_usage_logs_task_id", "api_generation_task_id"), Index("idx_api_usage_logs_action", "price_action"), ) id: Mapped[str] = mapped_column(String(32), primary_key=True) api_key_id: Mapped[str] = mapped_column( String(32), ForeignKey("api_keys.id", ondelete="CASCADE"), nullable=False, index=True ) api_generation_task_id: Mapped[str | None] = mapped_column( String(32), ForeignKey("api_generation_tasks.id", ondelete="SET NULL"), nullable=True ) # === 操作类型 === price_action: Mapped[str] = mapped_column(String(16), nullable=False, comment="deduct=扣除, refund=退回") # === 请求信息 === request_type: Mapped[str] = mapped_column(String(32), nullable=False, comment="video_create|image_generate") model_name: Mapped[str] = mapped_column(String(128), nullable=False) gen_type: Mapped[str] = mapped_column(String(16), nullable=False) resolution: Mapped[str | None] = mapped_column(String(16), nullable=True) duration: Mapped[int | None] = mapped_column(Integer, nullable=True) # === 金额信息 === credits_cost: Mapped[float] = mapped_column(Float, default=0.0, server_default="0.0", comment="实际扣除金额") refund_amount: Mapped[float] = mapped_column(Float, default=0.0, server_default="0.0", comment="退回金额") quota_before: Mapped[float | None] = mapped_column(Float, nullable=True, comment="操作前配额余额") quota_after: Mapped[float | None] = mapped_column(Float, nullable=True, comment="操作后配额余额") # === Token 用量 === tokens_used: Mapped[int] = mapped_column(Integer, default=0, server_default="0") request_duration_ms: Mapped[int] = mapped_column(Integer, default=0, server_default="0", comment="端到端耗时") # === 价格明细(JSON) === price_detail_json: Mapped[str | None] = mapped_column(Text, nullable=True, comment="价格计算明细JSON") # === 结果 === status: Mapped[str] = mapped_column(String(32), nullable=False, comment="success|failed") error_message: Mapped[str | None] = mapped_column(Text, nullable=True) error_code: Mapped[str | None] = mapped_column(String(64), nullable=True) # === 调试 === request_payload_json: Mapped[str | None] = mapped_column(Text, nullable=True, comment="原始请求快照")