Files
beige/backend/app/models/task.py
yangqianqian 7f419f4c8b 存量积累:生图素人感约束+评图分+幂等防重跑+审核回路
- 015-017迁移:image_candidate 文案复审/AI视觉分/重生标记
- constants C7素人感约束(反电商摆拍对齐真实笔记)+C3叠字口子
- celery visibility_timeout=2h 防长任务被误判重投重复烧钱(task75教训)
- image_scorer 评图分(只筛选+展示,真实信号才进飞轮权重)
- storyboard/image_gen/pipeline_io 生图存量
- task_actions/tasks/task_service/flywheel 审核回路+飞轮存量

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

160 lines
7.4 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)
edited: 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)
# R2 重生标记True=用户手动重生产出(新增不删旧,前端同strategy+role默认只展示最新)
is_regen: Mapped[bool] = mapped_column(default=False, nullable=False)
eval_score: Mapped[float | None] = mapped_column(Float) # 一期留 NULL
# 标题文字校验结果JSON(NULL=通过;有值=曾不过/重生,前端提示运营人工筛)
text_review: Mapped[str | None] = mapped_column(String(512))
# AI评图分0-100(展示+生成时筛选+排序用,绝不进飞轮权重;eval_score 另留 NULL)
ai_visual_score: Mapped[float | None] = mapped_column(Float)
ai_visual_note: Mapped[str | None] = mapped_column(String(256)) # AI评图一句话评语(前端展示)
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
)