baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。 当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包), M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
132
backend/app/services/ai_engine/package_exporter.py
Normal file
132
backend/app/services/ai_engine/package_exporter.py
Normal 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)
|
||||
Reference in New Issue
Block a user