"""数据模型:Product(商品)/ ProductAsset(采集素材)/ Suite(套图任务)/ SuiteImage(生成图)。""" from __future__ import annotations import uuid from datetime import datetime from sqlalchemy import DateTime, Float, ForeignKey, Integer, JSON, String, Text, Uuid, func from sqlalchemy.orm import Mapped, mapped_column from db import Base # 产品阶段 STAGE_COLLECTED = "collected" STAGE_GENERATED = "generated" # 素材/生成图状态 STATUS_PENDING = "pending" STATUS_DOWNLOADING = "downloading" STATUS_OK = "ok" STATUS_FAILED = "failed" # 套图任务状态 SUITE_PENDING = "pending" SUITE_RUNNING = "running" SUITE_DONE = "done" SUITE_PARTIAL = "partial" SUITE_FAILED = "failed" class Product(Base): __tablename__ = "products" id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4) stage: Mapped[str] = mapped_column(String(16), default=STAGE_COLLECTED, index=True) # 采集溯源 source_platform: Mapped[str | None] = mapped_column(String(16), nullable=True) # ozon | 1688 | taobao source_item_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) source_url: Mapped[str | None] = mapped_column(Text, nullable=True) name: Mapped[str] = mapped_column(Text, default="") description: Mapped[str] = mapped_column(Text, default="") price: Mapped[float | None] = mapped_column(Float, nullable=True) # 采集原文:{title, price, brand, params: [...], sellingPoints, desc, texts: [...]} raw: Mapped[dict | None] = mapped_column(JSON, nullable=True) asset_counts: Mapped[dict | None] = mapped_column(JSON, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), index=True ) class ProductAsset(Base): """采集素材(源站图片,转存到本地 media)。""" __tablename__ = "product_assets" id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4) product_id: Mapped[uuid.UUID] = mapped_column( Uuid(as_uuid=True), ForeignKey("products.id", ondelete="CASCADE"), index=True ) group_key: Mapped[str] = mapped_column(String(16), default="main") # main/sku/detail/video variant_name: Mapped[str | None] = mapped_column(String(128), nullable=True) sort_order: Mapped[int] = mapped_column(Integer, default=0) type: Mapped[str] = mapped_column(String(8), default="img") # img / video source_url: Mapped[str] = mapped_column(Text, default="") stored_url: Mapped[str | None] = mapped_column(Text, nullable=True) # 本地 media key 或公网 URL status: Mapped[str] = mapped_column(String(16), default=STATUS_PENDING, index=True) dedupe_key: Mapped[str | None] = mapped_column(String(512), nullable=True, index=True) error: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) class Suite(Base): """一次套图生成任务(无状态:直接携带采集数据,不依赖商品库)。""" __tablename__ = "suites" id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4) # 兼容旧的商品挂载路径;工具化流程为空 product_id: Mapped[uuid.UUID | None] = mapped_column( Uuid(as_uuid=True), ForeignKey("products.id", ondelete="CASCADE"), nullable=True, index=True ) status: Mapped[str] = mapped_column(String(16), default=SUITE_PENDING, index=True) style_set: Mapped[int] = mapped_column(Integer, default=1) # 风格模板 1-5 platform: Mapped[str] = mapped_column(String(8), default="cn") # 目标平台 ozon | wb | cn lang: Mapped[str] = mapped_column(String(4), default="zh") # ru / zh(由平台推导) ratio: Mapped[str] = mapped_column(String(8), default="1:1") # 图片比例(由平台推导) types: Mapped[list | None] = mapped_column(JSON, nullable=True) # 图类型 id 列表(旧) plan: Mapped[list | None] = mapped_column(JSON, nullable=True) # 出图方案(展开后的逐张任务) provider: Mapped[str] = mapped_column(String(16), default="doubao") # 工具化流程:请求自带的数据(生图上下文 + 参考图 URL 列表) context: Mapped[dict | None] = mapped_column(JSON, nullable=True) ref_images: Mapped[list | None] = mapped_column(JSON, nullable=True) error: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class SuiteImage(Base): """任务里单张生成图。""" __tablename__ = "suite_images" id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4) suite_id: Mapped[uuid.UUID] = mapped_column( Uuid(as_uuid=True), ForeignKey("suites.id", ondelete="CASCADE"), index=True ) type_id: Mapped[str] = mapped_column(String(32)) # white_bg / key_features / ... name: Mapped[str] = mapped_column(String(64), default="") # 中文名(文件名) stored_url: Mapped[str | None] = mapped_column(Text, nullable=True) status: Mapped[str] = mapped_column(String(16), default=STATUS_PENDING) error: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())