baseline: Clover 独立仓库首次基线提交

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-16 11:30:22 +08:00
commit 6a2632da70
253 changed files with 27467 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
"""
app/models/product.py — products / benchmark_notes / banned_words
Alembic 003 业务主体1/3
products.category 是纯数据字段禁止任何品类枚举基石A
"""
from datetime import datetime
from typing import Optional
from sqlalchemy import (
BigInteger, DateTime, Enum, ForeignKey,
Index, Integer, String, Text, func,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.constants.enums import BannedWordLevel, ProductSource
from app.core.database import Base
class Product(Base):
"""产品档案(卖点/违禁词/风格/调性/文案角度/可调prompt/source"""
__tablename__ = "products"
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
)
name: Mapped[str] = mapped_column(String(128), nullable=False)
# category 是纯数据字段不在代码里做枚举基石A
category: Mapped[str | None] = mapped_column(String(64))
source: Mapped[str] = mapped_column(
Enum(ProductSource, values_callable=lambda x: [e.value for e in x]),
default=ProductSource.CUSTOM, nullable=False,
)
selling_points: Mapped[str | None] = mapped_column(Text) # JSON数组
style_tone: Mapped[str | None] = mapped_column(String(128))
text_angles: Mapped[str | None] = mapped_column(Text) # JSON数组用户设定
custom_prompt: Mapped[str | None] = mapped_column(Text) # 等北哥方案注入
image_path: Mapped[str | None] = mapped_column(String(512)) # 产品参考图路径(前端上传后写入)
# 008: 品牌词(客户输入,植入文案每条+生图特写图第2/6张第5环
brand_keyword: Mapped[str | None] = mapped_column(String(64), comment="品牌词,客户录入,随产品固定")
# 012: 目标人群(客户输入,透传进文案/生图 promptstoryboard.py:52 原恒空现可填)
target_audience: Mapped[str | None] = mapped_column(String(128), comment="目标人群,客户输入,透传进文案/生图prompt")
is_active: Mapped[bool] = mapped_column(default=True, nullable=False)
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
)
benchmark_notes: Mapped[list["BenchmarkNote"]] = relationship(
back_populates="product", lazy="noload"
)
__table_args__ = (
Index("idx_products_workspace_id", "workspace_id"),
)
class BenchmarkNote(Base):
"""标杆笔记(截图+手填亮点为主通道)"""
__tablename__ = "benchmark_notes"
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", ondelete="CASCADE"), nullable=False
)
screenshot_url: Mapped[str | None] = mapped_column(String(512))
highlights: Mapped[str | None] = mapped_column(Text) # 手填亮点
link_url: Mapped[str | None] = mapped_column(String(512))
# 009: 第2环标杆分析字段
features_json: Mapped[str | None] = mapped_column(Text, comment="爆款8特征分析结果JSONAI解析后写入")
analyze_status: Mapped[str] = mapped_column(String(20), default="pending", nullable=False, comment="AI分析状态: pending/analyzing/done/failed")
created_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now(), nullable=False
)
product: Mapped["Product"] = relationship(back_populates="benchmark_notes")
__table_args__ = (
Index("idx_benchmark_notes_product_id", "product_id"),
)
class BannedWord(Base):
"""违禁词库三级auto_fix/soft_warn/hard_block"""
__tablename__ = "banned_words"
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
)
word: Mapped[str] = mapped_column(String(64), nullable=False)
level: Mapped[str] = mapped_column(
Enum(BannedWordLevel, values_callable=lambda x: [e.value for e in x]),
nullable=False,
)
replacement: Mapped[str | None] = mapped_column(String(128))
updatable: Mapped[bool] = mapped_column(default=True, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now(), nullable=False
)
__table_args__ = (
Index("idx_banned_words_workspace_id", "workspace_id"),
)