diff --git a/backend/crawler_copy/jd_pc_search/jd_competitor_report.py b/backend/crawler_copy/jd_pc_search/jd_competitor_report.py index dbe34f0..00f8b7d 100644 --- a/backend/crawler_copy/jd_pc_search/jd_competitor_report.py +++ b/backend/crawler_copy/jd_pc_search/jd_competitor_report.py @@ -2169,7 +2169,7 @@ def build_competitor_markdown( ) ) lines.append( - "*更细行数分布见结构化摘要 JSON 的 ``list_brand_mix_top``。*" + "*更细的品牌行数分布见本任务「结构化摘要」数据包。*" ) elif list_export: lines.append( @@ -2211,7 +2211,7 @@ def build_competitor_markdown( ) ) lines.append( - "*更细店铺行数分布见结构化摘要 ``list_shop_mix_top``。*" + "*更细的店铺行数分布见本任务「结构化摘要」数据包。*" ) else: lines.append("*无店铺字段。*") @@ -2772,8 +2772,8 @@ def build_competitor_brief( list_brand_block: dict[str, Any] | None if len(brands_s) >= min_brand_rows: list_brand_block = { - "cr1": cr1_list_brand, - "cr3": cr3_list_brand, + "first_share": cr1_list_brand, + "top_three_combined_share": cr3_list_brand, "top_label": top_list_brand, } else: @@ -2800,15 +2800,15 @@ def build_competitor_brief( "list_visibility_proxy": proxy, "concentration": { "shops_from_list": { - "cr1": cr1_shop, - "cr3": cr3_shop, + "first_share": cr1_shop, + "top_three_combined_share": cr3_shop, "top_label": top_shop_s, "top_share_pct": top_shop_share, }, "list_brand_field": list_brand_block, "detail_brand_among_merged": { - "cr1": cr1_deep, - "cr3": cr3_deep, + "first_share": cr1_deep, + "top_three_combined_share": cr3_deep, "top_label": top_brand_deep, "top_share_pct": top_brand_deep_share, }, @@ -2857,9 +2857,10 @@ def build_competitor_brief( "consumer_feedback_by_matrix_group": feedback_by_group, "comment_sentiment_lexicon": comment_sentiment_lexicon, "notes": [ - "与在线分析报告各章统计口径一致;关注词与场景以任务 report_config 为底,生成报告时还可经 keyword_suggest_llm 合并延伸(仍为子串命中统计,非深度主题模型)。", - "价格来自页面展示字段抽取,含促销与规格差异;price_promotion_signals 为标价/券后对齐与卖点话术的启发式摘录。", - "comment_sentiment_lexicon 为关键词粗判,非深度学习情感模型。", + "与在线分析报告各章统计口径一致;关注词与场景以任务中的分析规则为准(子串命中统计,非深度主题模型)。", + "价格来自页面展示字段抽取,含促销与规格差异;促销与标价对齐等为启发式摘录,仅供对照。", + "评价语气为关键词粗判,非深度学习情感模型。", + "「集中度」中:最大一家占比、前三名合计占比为小数(如 0.12 表示约 12%),对应列表或深入样本中的相关行。", ], } return _sanitize_json_numbers(out) diff --git a/backend/pipeline/brief_concentration.py b/backend/pipeline/brief_concentration.py new file mode 100644 index 0000000..ab9258a --- /dev/null +++ b/backend/pipeline/brief_concentration.py @@ -0,0 +1,24 @@ +"""竞品简报「集中度」块:对外字段名面向非技术用户,并兼容旧键名。""" +from __future__ import annotations + +from typing import Any + + +def concentration_first_share(block: dict[str, Any] | None) -> Any: + """最大一家占全部相关行的比例(0~1)。新键 ``first_share``,旧键 ``cr1``。""" + if not block: + return None + v = block.get("first_share") + if v is not None: + return v + return block.get("cr1") + + +def concentration_top_three_share(block: dict[str, Any] | None) -> Any: + """前三名合计占全部相关行的比例(0~1)。新键 ``top_three_combined_share``,旧键 ``cr3``。""" + if not block: + return None + v = block.get("top_three_combined_share") + if v is not None: + return v + return block.get("cr3") diff --git a/backend/pipeline/brief_pack.py b/backend/pipeline/brief_pack.py index daf4081..26d08f1 100644 --- a/backend/pipeline/brief_pack.py +++ b/backend/pipeline/brief_pack.py @@ -7,6 +7,11 @@ import zipfile from pathlib import Path from typing import Any +from pipeline.brief_concentration import ( + concentration_first_share, + concentration_top_three_share, +) + def _pct(x: Any) -> str: if x is None: @@ -32,7 +37,7 @@ def markdown_summary_from_brief(brief: dict[str, Any]) -> str: lines: list[str] = [ "# 竞品要点摘录(机器整理)", "", - "> 与同批 **完整报告**、**结构化 JSON** 同源;规则汇总,定稿前请人工核对。", + "> 与同批 **完整报告**、**数据汇总**同源;规则汇总,定稿前请人工核对。", "", ] kw = brief.get("keyword") or "—" @@ -65,33 +70,33 @@ def markdown_summary_from_brief(brief: dict[str, Any]) -> str: if raw.get("result_count_consensus") is not None: lines.extend( [ - "## 列表侧检索规模(接口申报)", + "## 列表侧检索规模(平台展示)", "", - f"- **resultCount 共识值**:{_num(raw.get('result_count_consensus'))}", + f"- **检索结果条数(多份响应取一致值)**:{_num(raw.get('result_count_consensus'))}", "", ] ) conc = brief.get("concentration") or {} shops = conc.get("shops_from_list") or {} - if shops.get("cr1") is not None or shops.get("top_label"): + if concentration_first_share(shops) is not None or shops.get("top_label"): lines.extend( [ "## 店铺集中度(列表)", "", - f"- **第一大店铺份额**:{_pct(shops.get('cr1'))}(第一店铺:{shops.get('top_label') or '—'})", - f"- **前三店铺合计份额**:{_pct(shops.get('cr3'))}", + f"- **第一大店铺份额**:{_pct(concentration_first_share(shops))}(第一店铺:{shops.get('top_label') or '—'})", + f"- **前三店铺合计份额**:{_pct(concentration_top_three_share(shops))}", "", ] ) dbrand = conc.get("detail_brand_among_merged") or {} - if dbrand.get("cr1") is not None or dbrand.get("top_label"): + if concentration_first_share(dbrand) is not None or dbrand.get("top_label"): lines.extend( [ "## 品牌(深入样本)", "", - f"- **第一大品牌份额(深入样本)**:{_pct(dbrand.get('cr1'))}(头部:{dbrand.get('top_label') or '—'})", - f"- **前三品牌合计份额**:{_pct(dbrand.get('cr3'))}", + f"- **第一大品牌份额(深入样本)**:{_pct(concentration_first_share(dbrand))}(头部:{dbrand.get('top_label') or '—'})", + f"- **前三品牌合计份额**:{_pct(concentration_top_three_share(dbrand))}", "", ] ) @@ -103,7 +108,7 @@ def markdown_summary_from_brief(brief: dict[str, Any]) -> str: [ "## 价格(展示价统计)", "", - f"- **样本量 n**:{_num(pst.get('n'))};**统计口径**:{src}", + f"- **样本量(条)**:{_num(pst.get('n'))};**统计口径**:{src}", f"- **区间**:{_num(pst.get('min'))} ~ {_num(pst.get('max'))};**中位数**:{_num(pst.get('median'))}", "", ] diff --git a/backend/pipeline/llm_generate.py b/backend/pipeline/llm_generate.py index de96702..765e280 100644 --- a/backend/pipeline/llm_generate.py +++ b/backend/pipeline/llm_generate.py @@ -49,7 +49,7 @@ REPORT_SYSTEM = """你是业务与产品读者顾问。输入 JSON 含 `keyword` - **不要**使用「## 一」「## 八」等会打乱宿主文档的顶级章节号;请使用 ``####`` 或必要时 ``###`` 作为本段内小节标题; - **不要**输出完整报告目录或复述「研究范围与方法」长章; - **不要**撰写 Markdown 表格版「竞品对比矩阵」或罗列 SKU 明细——**正文已含矩阵**,此处只做分组级语义归纳; -- **不要使用** CR1、CR3 等英文缩写;集中度请用「第一大品牌份额」「前三品牌合计份额」。 +- **不要使用** CR1、CR3 等集中度缩写作主表述;集中度请用「第一大店铺/品牌份额」「前三家合计份额」;输入中的英文字段名勿照抄进正文,请写成中文业务用语。 **请输出**(仅输出将置于 §8.5 下的正文,不要自造「### 8.5」标题行): - **Markdown**,约 **800~1500 字**; @@ -265,7 +265,7 @@ STRATEGY_SYSTEM = """你是市场策略顾问,根据**结构化监测摘要** **规则**: - 输入 JSON 含 `rules_draft_markdown`(规则引擎生成的底稿,与同任务数据一致)、`structured_brief`(摘要子集)、`strategy_decisions`、`business_notes` 等; -- **不得编造**输入中不存在的销量、占比、价格数字;若底稿与摘要中有数字,须保持一致;表述集中度时用「第一大品牌份额」等中文,**不要用** CR1、CR3 缩写; +- **不得编造**输入中不存在的销量、占比、价格数字;若底稿与摘要中有数字,须保持一致;表述集中度时用「第一大……份额」「前三家合计」等中文,**不要用** CR1、CR3 等缩写; - 若 `structured_brief` 含 `matrix_overview_for_llm` 或矩阵相关字段,策略中应**呼应**细分类目分组与竞品矩阵结论,不得无故删光矩阵相关建议; - 可调整段落衔接、标题层级、列表与表格呈现,使更易读;可补充「建议」「待业务确认」类表述,但不虚构竞品名称或数据; - **仅输出** Markdown 正文(不要 ``` 围栏包裹全文)。""" diff --git a/backend/pipeline/strategy_draft.py b/backend/pipeline/strategy_draft.py index 09e65c5..cdfdc09 100644 --- a/backend/pipeline/strategy_draft.py +++ b/backend/pipeline/strategy_draft.py @@ -9,6 +9,11 @@ from __future__ import annotations import math from typing import Any +from pipeline.brief_concentration import ( + concentration_first_share, + concentration_top_three_share, +) + def _esc(s: Any) -> str: t = "" if s is None else str(s).strip() @@ -154,8 +159,18 @@ def build_strategy_draft_markdown( shops = conc.get("shops_from_list") or {} dbrand = conc.get("detail_brand_among_merged") or {} lines.extend(["## 三、竞争格局 → 策略含义", ""]) - n_shop = _cr_narrative("列表侧店铺集中度", shops.get("cr1"), shops.get("cr3"), shops.get("top_label")) - n_brand = _cr_narrative("深入样本内品牌集中度", dbrand.get("cr1"), dbrand.get("cr3"), dbrand.get("top_label")) + n_shop = _cr_narrative( + "列表侧店铺集中度", + concentration_first_share(shops), + concentration_top_three_share(shops), + shops.get("top_label"), + ) + n_brand = _cr_narrative( + "深入样本内品牌集中度", + concentration_first_share(dbrand), + concentration_top_three_share(dbrand), + dbrand.get("top_label"), + ) if n_shop: lines.append(n_shop) if n_brand: diff --git a/docs/examples/competitor_brief_schema_v1.json b/docs/examples/competitor_brief_schema_v1.json index 695bfd9..174b0f2 100644 --- a/docs/examples/competitor_brief_schema_v1.json +++ b/docs/examples/competitor_brief_schema_v1.json @@ -26,15 +26,15 @@ }, "concentration": { "shops_from_list": { - "cr1": 0.12, - "cr3": 0.35, + "first_share": 0.12, + "top_three_combined_share": 0.35, "top_label": "某旗舰店", "top_share_pct": "12.0%" }, "list_brand_field": null, "detail_brand_among_merged": { - "cr1": 0.2, - "cr3": 0.6, + "first_share": 0.2, + "top_three_combined_share": 0.6, "top_label": "品牌A", "top_share_pct": "20.0%" } diff --git a/frontend/src/views/jd/JdAnalysisView.vue b/frontend/src/views/jd/JdAnalysisView.vue index 58f1548..800fa00 100644 --- a/frontend/src/views/jd/JdAnalysisView.vue +++ b/frontend/src/views/jd/JdAnalysisView.vue @@ -208,9 +208,9 @@ watch(

分析报告查看

选择已成功的任务,在线阅读报告或下载。 - 流水线生成报告时会自动基于全部评价正文分块调用大模型扩展关注词,并单次调用归纳使用场景(在预设场景组之外追加 label+触发子串),再写入统计图(PNG,见「二点五」章与简报包 report_assets)。 - 一键下载简报包含报告稿、统计图、结构化 JSON、要点摘录。 - 需要改规则或重算,请至 + 若开启大模型,系统会在后台根据评价正文补充关注词使用场景标签,并生成报告中的统计图(与报告插图章节对应)。 + 一键下载简报包内含:报告正文、插图文件夹、机器整理的数据摘要、以及便于扫读的要点摘录。 + 需要改分析规则或重新出稿,请至 报告生成

@@ -290,7 +290,7 @@ watch(
-

结构化竞品摘要

+

竞品数据摘要(机器整理)

-

与上方报告统计口径一致的数据汇总,可复制或下载给其它工具使用。

+

与上方报告统计口径一致,供复制或存档;一般业务阅读以报告正文与要点摘录为主,本块主要用于存档或交给同事继续分析。

{{ briefJson }}
diff --git a/frontend/src/views/jd/JdDatasetBrowseView.vue b/frontend/src/views/jd/JdDatasetBrowseView.vue index 3b2be8e..6f1de77 100644 --- a/frontend/src/views/jd/JdDatasetBrowseView.vue +++ b/frontend/src/views/jd/JdDatasetBrowseView.vue @@ -73,9 +73,8 @@ watch(selectedId, () => {

- 选择任务后浏览已入库的搜索、商详、评价与整合宽表(合并表按列拆分入库,与 - keyword_pipeline_merged.csv lean 列一致);各 Tab 下「导出当前表」可导出 JSON / CSV / - Excel。竞品报告请在「报告查看」阅读或「报告生成」重新生成。 + 选择任务后可查看本批已入库的搜索列表商品详情评价整合同步表(一行对应一个商品在报告里用到的主要字段)。 + 各标签下可**导出**为表格或数据文件。完整文字报告请在「报告查看」阅读,或在「报告生成」中重新出稿。

{{ loadError }}