fix(report): 默认关闭第九章策略大模型并改为读者说明

默认 report_config.llm_strategy_opportunities 为 false;第九章无大模型正文时输出固定说明并指向按细类「策略制定」。strategy 节选仅在 strategy_opportunities_llm.json 含 markdown 时返回,避免固定说明进入策略稿对齐。同步调整单测与 strategy_hints 占位文案。

Made-with: Cursor
This commit is contained in:
hub-gif 2026-04-21 16:59:07 +08:00
parent e03c19928c
commit 805e9b4de4
5 changed files with 49 additions and 46 deletions

View File

@ -3,7 +3,7 @@
关键词 调用 ``jd_keyword_pipeline`` 全链路采集 生成 **标准化竞品分析报告**Markdown
报告结构对齐常见竞品分析框架研究范围与方法执行摘要**整体市场观察列表可见度参考**
市场与竞争结构**按细分类目分组的竞品对比矩阵**价格分析含规则化价差/活动信号与可选 **细类价盘·促销** 大模型归纳**按细分类目的消费者反馈与用户画像****策略与机会提示**以大模型归纳为主可选与附录并明确数据边界
市场与竞争结构**按细分类目分组的竞品对比矩阵**价格分析含规则化价差/活动信号与可选 **细类价盘·促销** 大模型归纳**按细分类目的消费者反馈与用户画像****策略与机会提示**固定读者说明指向产品中按细类策略制定不再默认生成本章大模型长文与附录并明确数据边界
若运行配置中提供了外部市场规模摘录``EXTERNAL_MARKET_TABLE_ROWS``则追加对应表格小节否则不输出占位行
依赖全量抓取时与 ``crawler_copy/jd_pc_search/jd_keyword_pipeline.py`` 相同Nodeh5stPlaywright``common/jd_cookie.txt``
@ -120,6 +120,22 @@ OVERRIDE_MAX_SKUS: int | None = None
OVERRIDE_PAGE_START: int | None = None
OVERRIDE_PAGE_TO: int | None = None
# 报告正文面向读者:不写配置键名、文件名、环境变量名或原始异常栈。
_CH8_PROBE_ENABLE_HINT = (
"请在任务配置中开启「第八章评论文本探针」并重新生成报告,以输出词频、共现与主题等开放词表分析。"
)
def _strategy_opportunities_reader_fixed_lines() -> list[str]:
"""
第九章不再默认输出全任务大模型长文时的固定读者说明与产品策略制定按细类生成分工
"""
return [
"本任务**监测与归纳**见前文各章(执行摘要、整体观察、集中度、竞品矩阵、价格与促销、消费者反馈等)。"
"**可执行的市场策略稿**请在产品中通过「**策略制定**」**按矩阵细类**逐类生成:与同任务数据及第五章划分对齐,避免仅凭全关键词检索池做一句泛化结论。",
"",
"本报告**默认不再**附加全任务大模型「策略与机会」长文;若个别历史批次仍含该正文,以当时任务配置与落稿为准。",
"",
]
def build_competitor_markdown(
*,
@ -264,7 +280,7 @@ def build_competitor_markdown(
(
"- **用户画像(第八章)****不再**使用星级子集内**预设口语短语**条形图、正负面扇形图及同口径摘要;**第八章第二节**为上述**文本补充分析**(探针)。"
if _ch8_probe_sec
else "- **用户画像(第八章)**:若未启用探针,第二节仅说明方法并列出按细类评价条数;**请开启** `chapter8_text_mining_probe` 以生成开放词表统计与插图。"
else f"- **用户画像(第八章)**:若未启用探针,第二节仅说明方法并列出按细类评价条数;{_CH8_PROBE_ENABLE_HINT}"
),
"- **细类划分(第五至第八章)****仅**依据合并表中的**商品详情页类目路径**;该信息缺失或无法读出细类名称的 SKU **不参与**竞品矩阵与按细类评价统计(相关评价条亦**不进入**按细类图表)。",
"- **检索结果规模**:来自京东 PC 搜索返回的「结果条数」类指标,表示平台侧申报的匹配数量级,**不等于**动销、库存或独立 SKU 数。",
@ -364,7 +380,7 @@ def build_competitor_markdown(
)
else:
exec_bullets.append(
"评价已按**第五章同一细类**归组;**第八章第二节**定量主题分析依赖探针,可在报告调参中开启 `chapter8_text_mining_probe`。"
f"评价已按**第五章同一细类**归组;**第八章第二节**定量主题分析依赖探针,{_CH8_PROBE_ENABLE_HINT}"
)
if api_rc is not None:
exec_bullets.append(
@ -608,7 +624,7 @@ def build_competitor_markdown(
)
if not (run_dir / "report_assets" / mx_chart).is_file():
lines.append(
f"*尚未生成 ``report_assets/{mx_chart}``:请确认已执行报告出图流程,或重新生成报告。)*"
"*本细类矩阵价量散点图尚未生成:请确认已执行报告出图流程,或重新生成报告。)*"
)
lines.append("")
lines.append("")
@ -723,7 +739,7 @@ def build_competitor_markdown(
(
"- **评论文本补充分析(第八章第二节)**:本任务已用中文分词与统计工具做了开放词表分析(词频、关键词突出度、词对共现、主题归纳等,可选词云),**不再**在报告中使用「星级子集内预设口语短语」条形图、正负面扇形图及同口径摘要;**不再**使用「预设关注词 + 预设场景词组」子串计数与并列条图。"
if _ch8_probe_sec
else "- **评论文本补充分析(第八章第二节)**:本任务**未**嵌入探针正文;**不再**输出预设关注词/场景子串统计图。请在 `report_config` 中开启 `chapter8_text_mining_probe` 并重跑以生成开放词表统计与插图。"
else f"- **评论文本补充分析(第八章第二节)**:本任务**未**嵌入探针正文;**不再**输出预设关注词/场景子串统计图。{_CH8_PROBE_ENABLE_HINT}"
),
"",
]
@ -744,8 +760,8 @@ def build_competitor_markdown(
[
"### 8.2 评论文本补充分析(未启用探针时的占位)",
"",
"> **说明**:本版本**不再**生成「预设关注词 + 预设场景」子串统计与 ``chart_focus_and_scenarios_bar__*.png``"
"开放词表、词频、共现与主题等请开启 ``chapter8_text_mining_probe`` 后重跑。",
"> **说明**:本版本**不再**生成「预设关注词 + 预设场景」子串统计及旧版并列条形图"
f"开放词表、词频、共现与主题等分析需{_CH8_PROBE_ENABLE_HINT}",
"",
]
)
@ -805,12 +821,7 @@ def build_competitor_markdown(
]
)
else:
lines.extend(
[
"未生成本节大模型正文:请在任务 `report_config` 中开启 `llm_strategy_opportunities` 并重跑产物,或检查 run 目录下 `strategy_opportunities_llm.json` 是否报错。",
"",
]
)
lines.extend(_strategy_opportunities_reader_fixed_lines())
lines.extend(
[
@ -1083,7 +1094,7 @@ def build_competitor_brief(
"notes": [
"与在线分析报告各章**计数规则**一致;**不再**输出预设关注词/场景子串统计,评论侧主题以第八章文本挖掘探针(若启用)为准。",
"价格来自页面展示字段抽取,含促销与规格差异;促销与标价对齐等为启发式摘录,仅供对照。",
"「集中度」中:默认按**列表行**计数(同一 SKU 多页曝光会重复计);`shops_from_list.unique_sku_basis` 为按**去重 SKU** 的对照口径。二者均**不是**销量、库存或全渠道市场份额。",
"「集中度」中:默认按**列表行**计数(同一 SKU 多页曝光会重复计);另有按**去重 SKU** 计数的对照口径。二者均**不是**销量、库存或全渠道市场份额。",
],
}
return _sanitize_json_numbers(out)

View File

@ -172,7 +172,8 @@ def get_default_report_config() -> dict[str, Any]:
"llm_comment_group_summaries": True,
"llm_price_group_summaries": True,
"llm_promo_group_summaries": True,
"llm_strategy_opportunities": True,
# 全任务第九章大模型长文已弃用:可执行策略由「策略制定」按矩阵细类生成(见 jd_report 第九章固定说明)。
"llm_strategy_opportunities": False,
"llm_group_summaries_chunk_by_matrix": True,
"chapter8_text_mining_probe": True,
"chapter8_text_mining_probe_live_llm": True,

View File

@ -57,6 +57,9 @@ def load_report_strategy_excerpt(
if md:
s = md if len(md) <= cap else md[: cap - 80].rstrip() + "\n\n…(已截断)\n"
return s, "json_markdown"
# 已存在落盘记录但无大模型正文时,不再回退截取 competitor_analysis.md
# 第九章现为固定读者说明,避免误入策略稿 ``report_strategy_excerpt``。
return "", "none"
md_path = root / "competitor_analysis.md"
if md_path.is_file():

View File

@ -135,7 +135,7 @@ def filter_strategy_hints_for_ch8_probe(hints: Any) -> list[str]:
continue
out.append(s)
return out if out else [
"(与「关注词/预设场景条形图」相关的自动提示已省略;用户洞察请以报告 §8 文本挖掘与第九章节选为准。)"
"(与「关注词/预设场景条形图」相关的自动提示已省略;用户洞察请以报告 §8 文本挖掘及第五至第八章细类归纳为准;可执行策略以「策略制定」按细类生成为准。)"
]
@ -143,7 +143,8 @@ def report_uses_chapter8_text_mining_probe(report_config: dict[str, Any] | None)
"""
与任务 ``report_config`` ``chapter8_text_mining_probe`` 一致未显式设置时默认 ``True``
``jd.runner.get_default_report_config`` 一致
``True`` 策略稿用户与评论侧一节不再逐条列举关注词/场景子串命中以免与当前报告正文口径冲突
用于 §1.2 文案分支及对 ``strategy_hints`` 的过滤开启探针时与子串命中枚举相关的自动线索会被压掉
关闭时 §1.2 仍说明简报不附带预设关注词/场景子串统计评论侧以报告第八章探针若启用与原文为准
"""
if not isinstance(report_config, dict):
return True
@ -254,29 +255,9 @@ def build_strategy_draft_markdown(
]
)
else:
ckw = brief.get("comment_focus_keywords") or []
usc = brief.get("usage_scenarios") or []
lines.append("*下列为关注词/场景**统计摘录**(仅底稿审计用);**成稿删除逐条枚举**,只保留对策略有用的一两句结论,避免与同任务竞品分析报告重复。*")
lines.append("")
if ckw:
for item in ckw[:8]:
if isinstance(item, dict):
w = _esc(item.get("word"))
c = _num(item.get("count"))
lines.append(
f"- 「{w}」:子串统计命中约 **{c}** 次(口径同报告关注词)。"
)
if usc:
for item in usc[:6]:
if isinstance(item, dict):
sc = _esc(item.get("scenario"))
cn = _num(item.get("count"))
sh = _pct(item.get("share_of_text_units"))
lines.append(
f"- 场景「{sc}」:约 **{cn}** 条,约占 **{sh}** 文本单元(预设场景分组)。"
)
if not ckw and not usc:
lines.append("*摘要中无关注词/场景组,请结合评论侧分析补全本节。*")
lines.append(
"*简报中**不再**附带预设关注词/场景子串统计;评论侧请依据同任务《竞品分析报告》**第八章第二节**(文本挖掘探针,若已启用)及抽样原文撰写本节。*"
)
lines.append("")
mix = brief.get("category_mix_top") or []
@ -575,11 +556,7 @@ def build_strategy_draft_markdown(
rk = bool(d.get("ack_risk_keywords"))
rp = bool(d.get("ack_risk_price"))
rc = bool(d.get("ack_risk_concentration"))
rk_kw = (
"评论侧归纳是否以偏概全?(需原评论抽样)"
if use_ch8_probe
else "关注词/场景统计是否以偏概全?(需原评论抽样)"
)
rk_kw = "评论侧归纳是否以偏概全?(需原评论抽样)"
lines.extend(
[
"## 九、风险、假设与待验证",

View File

@ -48,11 +48,22 @@ def test_load_prefers_json_markdown(tmp_path: Path) -> None:
assert "JSON 内第九章" in text
def test_load_falls_back_to_competitor_md(tmp_path: Path) -> None:
def test_load_no_fallback_when_json_exists_without_markdown(tmp_path: Path) -> None:
"""存在 strategy_opportunities_llm.json 但无 markdown 时不再截取 competitor_analysis第九章可为固定说明"""
(tmp_path / "strategy_opportunities_llm.json").write_text(
json.dumps({"schema_version": 1, "ok": True}, ensure_ascii=False),
encoding="utf-8",
)
(tmp_path / "competitor_analysis.md").write_text(
f"{CHAPTER_NINE_HEADING}(假设清单,待验证)\n\n从 MD 截取。\n\n## 附录 A\n",
encoding="utf-8",
)
text, src = load_report_strategy_excerpt(tmp_path)
assert src == "none"
assert text == ""
def test_load_falls_back_to_competitor_md_when_no_json(tmp_path: Path) -> None:
(tmp_path / "competitor_analysis.md").write_text(
f"{CHAPTER_NINE_HEADING}(假设清单,待验证)\n\n从 MD 截取。\n\n## 附录 A\n",
encoding="utf-8",