Files
beige/backend/app/models/task.py
yangqianqian ff21116ff8 A8: 文案按A/B/C三套正交叙事生成,避免套路化重复
- constants: 新增 TEXT_NARRATIVE_BY_STRATEGY(A痛点/B场景/C成分),与图片侧同轴
- build_prompt: 加 strategy_narrative 参数并注入 prompt
- text_variants: 全链路透传(含优化轮)
- run_text_generation: 改循环三套,text_count均摊(divmod余前补),跨套去重,打_strategy标记
- TextCandidate: 加 strategy String(4) 字段 + 迁移021(已upgrade head)
- packaging: 打包按strategy精准配对文图(texts_by_strategy映射+三层兜底)
- SSE text_candidate 事件携带 strategy

独立agent交叉验证7改造点全过,边界(text_count<3/无别名/不截断)无must-fix

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

161 lines
7.5 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))
strategy: Mapped[str | None] = mapped_column(String(4)) # A/B/C 正交叙事套(A痛点/B场景/C成分),与图片侧同轴
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
)