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)
130 lines
5.5 KiB
Python
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}
|