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,132 @@
"""
package_exporter.py — 达人素材交付包生成
架构方案§五 1A步骤5按笔记分文件夹 + 图(01/02/03) + 文案.txt + 发布清单 + 合规说明
路径规则uploads/packages/{workspace_id}/{task_id}/note_{n}/
"""
from __future__ import annotations
import json
import logging
import os
import zipfile
from datetime import datetime
from pathlib import Path
from typing import Any
logger = logging.getLogger(__name__)
# 文件夹结构
# uploads/packages/{workspace_id}/{task_id}/
# note_01/
# 01_hook.jpg # 按 seq 序号命名防传错序
# 02_proof.jpg
# 文案.txt # 标题 + 正文 + 标签
# note_02/
# ...
# 📋发布清单.txt
# ✅合规说明.txt
# package.zip # 最终打包文件
def build_delivery_package(
workspace_id: int,
task_id: int,
notes: list[dict], # 每条笔记,含 text_candidate + image_candidates
base_path: str = "uploads/packages",
) -> str:
"""
打包交付,返回 zip 文件的本地路径。
notes 格式:[{
"title": str, "content": str, "tags": list[str],
"images": [{"seq": int, "role": str, "data": bytes}],
"banned_word_status": str, # 合规说明用
}]
"""
package_dir = Path(base_path) / str(workspace_id) / str(task_id)
package_dir.mkdir(parents=True, exist_ok=True)
note_dirs: list[Path] = []
for idx, note in enumerate(notes, start=1):
note_dir = package_dir / f"note_{idx:02d}"
note_dir.mkdir(exist_ok=True)
note_dirs.append(note_dir)
# ── 图片文件(按 seq 序号命名)
for img in sorted(note.get("images", []), key=lambda x: x.get("seq", 0)):
seq = img.get("seq", idx)
role = img.get("role", "img")
fname = f"{seq:02d}_{role}.jpg"
img_data = img.get("data", b"")
if img_data:
(note_dir / fname).write_bytes(img_data)
# ── 文案.txt标题 + 正文 + 标签,达人可直接复制)
tags = note.get("tags") or []
body = note.get("content", "")
# 正文末尾如果 LLM 已写入 #话题 标签,不再重复追加(避免重复)
body_has_tags = bool(tags) and any(
t.strip("#") in body for t in tags if t
)
copy_lines = [
f"【标题】{note.get('title', '')}",
"",
body,
]
if tags and not body_has_tags:
copy_lines += ["", " ".join(tags)]
(note_dir / "文案.txt").write_text("\n".join(copy_lines), encoding="utf-8")
# ── 发布清单.txt
checklist_lines = [
"📋 发布清单",
f"生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}",
f"任务ID{task_id}",
"",
]
for idx, note in enumerate(notes, start=1):
title = note.get("title", f"笔记{idx}")
n_images = len(note.get("images", []))
checklist_lines.append(f"note_{idx:02d} 标题:{title[:30]} 图片数:{n_images}")
checklist_lines += [
"",
"发布注意事项:",
"- 每条笔记图片按 01/02/03 顺序上传,避免传错序",
"- 文案.txt 中标题/正文/标签已区分,复制对应部分",
"- 品牌词已植入,请勿删除",
"- 不要添加链接(种品牌词,引导天猫搜索成交)",
]
(package_dir / "📋发布清单.txt").write_text("\n".join(checklist_lines), encoding="utf-8")
# ── 合规说明.txt
compliance_lines = [
"✅ 合规说明",
f"生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}",
"",
"本批次内容已完成以下合规处理:",
"1. 违禁词扫描(美白/祛斑/速效/医用/药妆等)",
"2. 视觉违禁词处理(前后对比/变白等)",
"3. 图片去水印处理C2PA元数据已清除",
"",
"各笔记合规状态:",
]
for idx, note in enumerate(notes, start=1):
status = note.get("banned_word_status", "pass")
status_label = {"pass": "✅通过", "auto_fixed": "✅自动改写", "soft_warn": "⚠️软提示", "hard_block": "❌硬拦截"}.get(status, status)
compliance_lines.append(f"note_{idx:02d}{status_label}")
compliance_lines += [
"",
"C2PA元数据可去除私有像素水印只能削弱不保证100%清除。",
"如有合规疑问,请联系运营团队。",
]
(package_dir / "✅合规说明.txt").write_text("\n".join(compliance_lines), encoding="utf-8")
# ── 打 zip
zip_path = package_dir / "package.zip"
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
for note_dir in note_dirs:
for fpath in sorted(note_dir.iterdir()):
zf.write(fpath, arcname=f"{note_dir.name}/{fpath.name}")
zf.write(package_dir / "📋发布清单.txt", arcname="📋发布清单.txt")
zf.write(package_dir / "✅合规说明.txt", arcname="✅合规说明.txt")
logger.info("delivery package built: %s (notes=%d)", zip_path, len(notes))
return str(zip_path)