from __future__ import annotations from datetime import datetime from typing import Any from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column from app.models.base import Base, SoftDeleteMixin, TimestampMixin _STEP_JSON_TYPE = JSON().with_variant(JSONB, "postgresql") class ModuleGenerationStep(Base, TimestampMixin, SoftDeleteMixin): """通用模块生成步骤表。 爆款开头复刻固定步骤: 1 material_input 2 image_prompt_optimize 3 image_generate 4 video_prompt_optimize 5 video_generate input_json / output_json 使用 JSON/JSONB 存储。 建议结构: input_json = { "schema_version": "hot_opening_step_io_v1", "step_code": "...", "source": {...}, "payload": {...}, "context": {...} } output_json = { "schema_version": "hot_opening_step_io_v1", "step_code": "...", "status": "completed|failed|...", "payload": {...}, "result": {...}, "usage": {...}, "error": {...} } """ __tablename__ = "module_generation_steps" __table_args__ = ( Index("idx_module_generation_steps_project_current", "project_id", "is_current", "deleted_at"), Index("idx_module_generation_steps_project_code", "project_id", "step_code", "is_current"), Index("idx_module_generation_steps_chat_task", "chat_task_id"), ) id: Mapped[str] = mapped_column(String(32), primary_key=True) project_id: Mapped[str] = mapped_column( String(32), ForeignKey("module_generation_projects.id", ondelete="CASCADE"), index=True, nullable=False ) user_id: Mapped[str] = mapped_column( String(32), ForeignKey("users.id", ondelete="CASCADE"), index=True, nullable=False ) module: Mapped[str] = mapped_column(String(64), index=True, nullable=False) step_index: Mapped[int] = mapped_column(Integer, index=True, nullable=False) step_code: Mapped[str] = mapped_column(String(64), index=True, nullable=False) status: Mapped[str] = mapped_column(String(32), default="pending", index=True) version: Mapped[int] = mapped_column(Integer, default=1, nullable=False) is_current: Mapped[bool] = mapped_column(Boolean, default=True, index=True) parent_step_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) source_step_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) chat_task_id: Mapped[str | None] = mapped_column( String(32), ForeignKey("chat_generation_tasks.id", ondelete="SET NULL"), nullable=True, index=True ) input_json: Mapped[dict[str, Any] | list[Any] | None] = mapped_column(_STEP_JSON_TYPE, nullable=True) output_json: Mapped[dict[str, Any] | list[Any] | None] = mapped_column(_STEP_JSON_TYPE, nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)