hub-gif 6280e436d8 feat(pipeline): JD 流水线 CSV 纯中文表头与模块目录重构
- 统一 csv_schema 与搜索/合并/评价/商详导出为纯中文列名,入库与视图按中文表头解析

- 竞品分析报告兼容新旧表头;新增 csv_header_rewrite 与 rewrite_pipeline_csv_headers 管理命令

- 调整 pipeline 至 jd、llm、reporting、demos 子包并更新任务与测试引用

- 新增购买者优惠摘要抽取、合并表 regen/ingest 命令、0016 迁移及相关测试

Made-with: Cursor
2026-04-15 16:02:29 +08:00

104 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""压缩 competitor-brief 供大模型输入,控制 token 体积(无 Django 依赖)。"""
from __future__ import annotations
import copy
import json
from typing import Any
def matrix_overview_for_llm(brief: dict[str, Any]) -> list[dict[str, Any]]:
"""
从**完整** brief 提取矩阵分组摘要,供大模型在 matrix 正文被裁剪后仍能写「按细分类目分组」章节。
"""
mg = brief.get("matrix_by_group")
if not isinstance(mg, list) or not mg:
return []
out: list[dict[str, Any]] = []
for g in mg:
if not isinstance(g, dict):
continue
name = g.get("group") or ""
skus = g.get("skus") if isinstance(g.get("skus"), list) else []
brands: list[str] = []
for s in skus[:100]:
if isinstance(s, dict):
bb = (s.get("brand") or "").strip()
if bb and bb not in brands:
brands.append(bb)
out.append(
{
"group": name,
"sku_count": int(g.get("sku_count") or len(skus)),
"distinct_brands_sample": brands[:15],
}
)
return out
def _trim_matrix(b: dict[str, Any], *, per_group: int, max_groups: int) -> None:
mg = b.get("matrix_by_group")
if not isinstance(mg, list):
return
trimmed: list[dict[str, Any]] = []
for g in mg[:max_groups]:
if not isinstance(g, dict):
continue
g2 = dict(g)
skus = g2.get("skus")
if isinstance(skus, list):
if per_group <= 0:
g2["skus"] = []
else:
g2["skus"] = skus[:per_group]
trimmed.append(g2)
b["matrix_by_group"] = trimmed
def _trim_feedback(b: dict[str, Any], max_groups: int) -> None:
cf = b.get("consumer_feedback_by_matrix_group")
if isinstance(cf, list):
b["consumer_feedback_by_matrix_group"] = cf[:max_groups]
def _json_len(b: dict[str, Any]) -> int:
return len(json.dumps(b, ensure_ascii=False))
def compact_brief_for_llm(
brief: dict[str, Any],
*,
max_chars: int = 350_000,
) -> dict[str, Any]:
"""
深拷贝后裁剪矩阵 SKU 列表、反馈组数;仍超长则逐步收紧直至省略大块。
始终附带 ``matrix_overview_for_llm``(来自裁剪前完整 brief避免大模型漏写竞品矩阵章节。
"""
matrix_ov = matrix_overview_for_llm(brief)
b = copy.deepcopy(brief)
if isinstance(b.get("run_dir"), str):
b["run_dir"] = "(已省略)"
def _finalize() -> dict[str, Any]:
b["matrix_overview_for_llm"] = matrix_ov
return b
caps = [(120, 24), (80, 20), (40, 16), (30, 12), (18, 10), (10, 8), (5, 6), (0, 6)]
for per_g, max_gr in caps:
_trim_matrix(b, per_group=per_g, max_groups=max_gr)
_trim_feedback(b, max_gr)
if _json_len(b) <= max_chars:
return _finalize()
b.pop("matrix_by_group", None)
b["matrix_by_group_omitted"] = True
_trim_feedback(b, 6)
if _json_len(b) <= max_chars:
return _finalize()
b.pop("consumer_feedback_by_matrix_group", None)
b["consumer_feedback_by_matrix_group_omitted"] = True
lv = b.get("list_visibility_proxy")
if isinstance(lv, dict) and _json_len(b) > max_chars:
b["list_visibility_proxy"] = {"_omitted": True, "keys": list(lv.keys())[:20]}
return _finalize()