Files
pod_trend_agent/graph/nodes/template_export_node.py
T
3218485270 b7f429db89 模板导出增强 + 模特性别分组 + 三合一提示词精简
1) 模板导出:识别「基码表-胸围」填 sku.bust(多个胸围列都填);申报价格模糊匹配多列统一按加价后价格填写;详情图文不再拼接 img_url_2;SPU 款式来源统一填「现货款」;商品产地国家简称映射(沙特→沙特阿拉伯)
2) 模特性别分组:model_features 按男女分组,按模板类目含男/女固定取对应性别模特(含 Pinterest 模式 pipeline)
3) 三合一提示词:去掉 DESIGN CONTENT 四要素描述(设计已由设计稿提供)
4) 生图尺寸:全部改为读 config 不再硬编码(设计图 compose.design_size / 合成图 compose.size / 种草图 seed_shot.size)
2026-08-26 18:05:11 +08:00

130 lines
5.5 KiB
Python

"""节点 9/9:商品上传模板导出(template_export)——在 seed_shot 之后。
把最终结果导入模板:
- SPU货号 / SKU货号 = 设计货号(oss_code,前缀+3位计数)
- 商品名称 = cn_title(多模态标题生成)
- 英文名称 = en_title
- 商品轮播图1:SKU 行按颜色路由(该颜色三合一链接),SPU 行随机一张
- 详情图文(SPU 行):全部三合一主图链接 + 种草图链接,| 分割
需在 oss_upload / seed_shot 之后运行(图床链接与货号已生成)。
"""
import time
from pathlib import Path
from typing import Any, Dict, List
from graph.paths import project_root, runtime_root
from graph.validate import with_fallback
def _template_out_path(prod_dir: Path, tpl_name: str) -> Path:
"""模板输出路径:默认 {tpl_name}_已填写.xlsx;已存在/被占用则自动换名加序号(同款号多产品不互相覆盖)。"""
base = prod_dir / f"{tpl_name}_已填写.xlsx"
try:
with open(base, "ab"):
pass
except OSError:
pass
else:
if not base.exists():
return base
for i in range(2, 100):
cand = prod_dir / f"{tpl_name}_已填写_{i}.xlsx"
if not cand.exists():
return cand
return prod_dir / f"{tpl_name}_已填写_{int(time.time())}.xlsx"
@with_fallback("template_export")
def template_export_node(state: Dict[str, Any]) -> Dict[str, Any]:
products: List[Dict[str, Any]] = state.get("product") or []
config = state["config"] or {}
pcfg = config.get("product") or {}
output_dir = Path(state["output_dir"])
errors = list(state.get("errors") or [])
stats = dict(state.get("stats") or {})
# 模板写入时机:所有集合/产品(含种草图、OSS)全部完成后才执行本节点
print(f"[template] 全部集合({len(products)} 个产品)处理完成,开始统一写入模板…")
tp = (pcfg.get("template_path") or "").strip()
if not tp:
print("[template] 未配置 product.template_path,跳过模板导出")
return {"stats": stats, "errors": errors}
if not Path(tp).exists():
cand = None
for root in (runtime_root(), project_root()):
c = root / tp
if c.exists():
cand = str(c)
break
if cand:
tp = cand
else:
print(f"[template] 模板文件不存在: {tp}")
return {"stats": stats, "errors": errors}
# db 路径
dbp = (pcfg.get("db_path") or "db/spu_sku.db")
db_path = Path(dbp)
if not db_path.is_absolute():
for root in (runtime_root(), project_root()):
if (root / db_path).exists():
db_path = root / db_path
break
from graph.template_export import export_products
tdir = (pcfg.get("template_dir") or "").strip() or str(Path(tp).parent)
prod_dir = output_dir / "product"
prod_dir.mkdir(parents=True, exist_ok=True)
# 批量合并导出:所有产品一次性写入同一模板,只打开/保存一次(避免逐产品频繁读写)
batch: List[Dict[str, Any]] = []
skipped = 0
for r in products:
# 失败跳过:合成图(composite/printed)与标题都失败的产品不写进模板
has_img = bool(r.get("composite_path") or r.get("printed_path"))
has_title = bool((r.get("en_title") or "").strip()) # 商品名称统一用 en_title
if not (has_img and has_title):
skipped += 1
print(f"[template] 跳过失败产品 {r.get('spu_code')}/{r.get('img_code','')}: "
f"合成图={'有' if has_img else '无'} 标题={'有' if has_title else '无'}(不写入模板)")
continue
sku_codes = [cc.get("sku_code") for cc in (r.get("color_composites") or [])]
if not sku_codes:
sku_codes = [r.get("sku_code") or ""]
batch.append({
"spu_code": r.get("spu_code", ""),
"sku_codes": sku_codes,
"images": [],
"spu_per_color": True, # 每颜色一个独立 SPU 块(单色多 SPU)
"oss_code": r.get("oss_code") or (r.get("color_composites") or [{}])[0].get("code", ""),
"cn_title": r.get("cn_title", ""),
"en_title": r.get("en_title", ""),
"ja_title": r.get("ja_title", ""),
"es_title": r.get("es_title", ""),
"composite_urls": r.get("color_composites") or [],
"seed_shot_urls": r.get("seed_shot_urls") or [],
})
exported: List[str] = []
if batch:
out = _template_out_path(prod_dir, "商品上传")
try:
out = export_products(
db_path, batch, tdir, tp, str(out),
markup_percent=float(pcfg.get("markup_percent") or 0),
)
for r in products:
if (r.get("composite_path") or r.get("printed_path")) and (r.get("en_title") or "").strip():
r["template_path"] = str(out)
exported.append(str(out))
print(f"[template] 商品上传模板已生成({len(batch)} 个产品一次合并): {out}")
except Exception as e: # noqa: BLE001
errors.append({"node": "template_export", "type": type(e).__name__,
"message": f"模板批量导出失败: {e}", "trace": ""})
print(f"[template] 模板批量导出失败: {e}")
stats["template_export"] = {"exported": len(exported)}
return {"product": products, "errors": errors, "stats": stats}