from __future__ import annotations from datetime import datetime from typing import Any from sqlalchemy import DateTime, 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_task_sets_analysis_lease", "analysis_status", "analysis_lease_until"), 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_attempt_no: Mapped[int] = mapped_column(Integer, default=1, nullable=False) analysis_claim_token: Mapped[str | None] = mapped_column(String(64), nullable=True) analysis_started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) analysis_lease_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) 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)