from __future__ import annotations from typing import Any from sqlalchemy import Float, ForeignKey, Index, Integer, JSON, String, Text, text from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column from app.models.base import Base, SoftDeleteMixin, TimestampMixin _JSON_TYPE = JSON().with_variant(JSONB, "postgresql") class ShotReplicateTaskSet(Base, TimestampMixin, SoftDeleteMixin): """拆镜复刻总任务集。""" __tablename__ = "shot_replicate_task_sets" __table_args__ = ( Index("idx_shot_replicate_task_sets_user_created", "user_id", "created_at"), Index("idx_shot_replicate_task_sets_status", "status"), Index("idx_shot_replicate_task_sets_analysis_status", "analysis_status"), Index("idx_shot_replicate_task_sets_split_status", "split_status"), Index( "uq_shot_replicate_task_sets_user_idempotency", "user_id", "idempotency_key", unique=True, postgresql_where=text("deleted_at IS NULL AND idempotency_key IS NOT NULL"), ), ) id: Mapped[str] = mapped_column(String(32), primary_key=True) user_id: Mapped[str] = mapped_column( String(32), ForeignKey("users.id", ondelete="CASCADE"), index=True, nullable=False ) title: Mapped[str | None] = mapped_column(String(160), nullable=True) video_url: Mapped[str] = mapped_column(String(512), nullable=False) video_path: Mapped[str] = mapped_column(String(512), nullable=False) video_duration_seconds: Mapped[float] = mapped_column(Float, nullable=False) status: Mapped[str] = mapped_column(String(32), default="pending_analysis", index=True, nullable=False) analysis_status: Mapped[str] = mapped_column(String(32), default="pending", index=True, nullable=False) split_status: Mapped[str] = mapped_column(String(32), default="none", index=True, nullable=False) original_video_content: Mapped[str | None] = mapped_column(Text, nullable=True) original_video_category: Mapped[str | None] = mapped_column(String(160), nullable=True) original_video_audience: Mapped[str | None] = mapped_column(Text, nullable=True) ai_suggestion_json: Mapped[list[Any] | dict[str, Any] | None] = mapped_column(_JSON_TYPE, nullable=True) analysis_raw_json: Mapped[dict[str, Any] | list[Any] | None] = mapped_column(_JSON_TYPE, nullable=True) analysis_result_json: Mapped[dict[str, Any] | list[Any] | None] = mapped_column(_JSON_TYPE, nullable=True) segment_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) completed_segment_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) failed_segment_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) analysis_error_message: Mapped[str | None] = mapped_column(Text, nullable=True) split_error_message: Mapped[str | None] = mapped_column(Text, nullable=True) idempotency_key: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)