Files
beige/backend/app/models/task.py
yangqianqian 6a2632da70 baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。
当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包),
M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 11:30:22 +08:00

152 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
app/models/task.py — generation_tasks / text_candidates / image_candidates / delivery_packages
Alembic 003 业务主体2/3
任务主键:自增 BIGINT + mongo_trace_id VARCHAR(24)
eval_score 留 NULL不接 banana 假评分(基石)
"""
from datetime import datetime
from typing import Optional
from sqlalchemy import (
BigInteger, Boolean, DateTime, Enum, Float, ForeignKey,
Index, Integer, String, Text, func,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.constants.enums import (
BannedWordStatus, CandidateSource, ImageRole,
PackageStatus, ReviewStatus, TaskStatus,
)
from app.core.database import Base
class GenerationTask(Base):
"""生产任务,审核字段平铺(不建独立审核表)。"""
__tablename__ = "generation_tasks"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
workspace_id: Mapped[int] = mapped_column(
BigInteger, ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False
)
product_id: Mapped[int] = mapped_column(
BigInteger, ForeignKey("products.id"), nullable=False
)
operator_id: Mapped[int] = mapped_column(
BigInteger, ForeignKey("users.id"), nullable=False
)
theme: Mapped[str | None] = mapped_column(String(256))
text_count: Mapped[int] = mapped_column(Integer, default=5, nullable=False)
image_count: Mapped[int] = mapped_column(Integer, default=3, nullable=False)
track: Mapped[str] = mapped_column(String(16), default="ai", nullable=False)
# 本次产品是否入镜True=必须用产品参考图(无图禁生成,不降级纯文生图)False=允许纯文生图
need_product_image: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
status: Mapped[str] = mapped_column(
Enum(TaskStatus, values_callable=lambda x: [e.value for e in x]),
default=TaskStatus.PENDING, nullable=False,
)
mongo_trace_id: Mapped[str | None] = mapped_column(String(24)) # MongoDB trace
# ── 审核字段(平铺)──────────────────────────────
review_status: Mapped[str | None] = mapped_column(
Enum(ReviewStatus, values_callable=lambda x: [e.value for e in x])
)
reviewer_id: Mapped[int | None] = mapped_column(BigInteger, ForeignKey("users.id"))
reviewed_at: Mapped[datetime | None] = mapped_column(DateTime)
reject_reason: Mapped[str | None] = mapped_column(Text)
approved_at: Mapped[datetime | None] = mapped_column(DateTime)
archived_at: Mapped[datetime | None] = mapped_column(DateTime)
# 011: 第2环S12+第11环裂变
benchmark_ids: Mapped[str | None] = mapped_column(Text, comment="关联标杆笔记ID列表JSON list第2环引用")
source_fission_id: Mapped[int | None] = mapped_column(Integer, comment="裂变来源fission_task IDNULL=普通任务")
created_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now(), nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
)
__table_args__ = (
Index("idx_generation_tasks_workspace_status", "workspace_id", "status"),
)
class TextCandidate(Base):
"""文案候选source=ai/import 区分双轨。eval_score 留 NULL。"""
__tablename__ = "text_candidates"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
workspace_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
task_id: Mapped[int] = mapped_column(
BigInteger, ForeignKey("generation_tasks.id", ondelete="CASCADE"), nullable=False
)
source: Mapped[str] = mapped_column(
Enum(CandidateSource, values_callable=lambda x: [e.value for e in x]),
default=CandidateSource.AI, nullable=False,
)
angle_label: Mapped[str | None] = mapped_column(String(64))
content: Mapped[str | None] = mapped_column(Text)
score_json: Mapped[str | None] = mapped_column(Text) # 五维分 JSON
banned_word_status: Mapped[str] = mapped_column(
Enum(BannedWordStatus, values_callable=lambda x: [e.value for e in x]),
default=BannedWordStatus.PASS, nullable=False,
)
eval_score: Mapped[float | None] = mapped_column(Float) # 一期留 NULL
is_selected: Mapped[bool] = mapped_column(default=False, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now(), nullable=False
)
__table_args__ = (
Index("idx_text_candidates_task_id", "task_id"),
)
class ImageCandidate(Base):
"""图片候选。eval_score 留 NULL。"""
__tablename__ = "image_candidates"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
workspace_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
task_id: Mapped[int] = mapped_column(
BigInteger, ForeignKey("generation_tasks.id", ondelete="CASCADE"), nullable=False
)
# role 用 varchar 不用 enumstoryboard 角色名仍在演进,约束放应用层 ImageRole
role: Mapped[str] = mapped_column(
String(32), default=ImageRole.HOOK.value, nullable=False,
)
url: Mapped[str | None] = mapped_column(String(512))
strategy: Mapped[str | None] = mapped_column(String(4)) # A/B/C二期
seq: Mapped[int] = mapped_column(Integer, default=1) # 分镜序号
is_selected: Mapped[bool] = mapped_column(default=False, nullable=False)
eval_score: Mapped[float | None] = mapped_column(Float) # 一期留 NULL
created_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now(), nullable=False
)
__table_args__ = (
Index("idx_image_candidates_task_id", "task_id"),
)
class DeliveryPackage(Base):
"""达人素材交付包。"""
__tablename__ = "delivery_packages"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
workspace_id: Mapped[int] = mapped_column(
BigInteger, ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False
)
task_id: Mapped[int] = mapped_column(
BigInteger, ForeignKey("generation_tasks.id"), nullable=False
)
status: Mapped[str] = mapped_column(
Enum(PackageStatus, values_callable=lambda x: [e.value for e in x]),
default=PackageStatus.PENDING, nullable=False,
)
package_path: Mapped[str | None] = mapped_column(String(512))
download_url: Mapped[str | None] = mapped_column(String(512))
expires_at: Mapped[datetime | None] = mapped_column(DateTime)
created_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now(), nullable=False
)