mirror of
https://github.com/primedigitaltech/market-assistant.git
synced 2026-07-22 08:01:34 +08:00
feat(brief): plain-language concentration keys and copy
- Add first_share/top_three_combined_share plus cr1/cr3 readers - Brief/schema/footnotes and LLM prompts avoid dev jargon where possible - Frontend JD analysis/dataset hints in business-friendly wording Made-with: Cursor
This commit is contained in:
parent
c3fdb2b970
commit
3d6193af06
@ -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)
|
||||
|
||||
24
backend/pipeline/brief_concentration.py
Normal file
24
backend/pipeline/brief_concentration.py
Normal file
@ -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")
|
||||
@ -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'))}",
|
||||
"",
|
||||
]
|
||||
|
||||
@ -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 正文(不要 ``` 围栏包裹全文)。"""
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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%"
|
||||
}
|
||||
|
||||
@ -208,9 +208,9 @@ watch(
|
||||
<h2>分析报告查看</h2>
|
||||
<p class="hint-top">
|
||||
选择<strong>已成功</strong>的任务,在线阅读报告或下载。
|
||||
流水线生成报告时会<strong>自动</strong>基于<strong>全部评价正文</strong>分块调用大模型扩展<strong>关注词</strong>,并单次调用归纳<strong>使用场景</strong>(在预设场景组之外追加 label+触发子串),再写入统计图(PNG,见「二点五」章与简报包 <code>report_assets</code>)。
|
||||
<strong>一键下载简报包</strong>含报告稿、统计图、结构化 JSON、要点摘录。
|
||||
需要改规则或重算,请至
|
||||
若开启大模型,系统会在后台根据评价正文补充<strong>关注词</strong>与<strong>使用场景</strong>标签,并生成报告中的统计图(与报告插图章节对应)。
|
||||
<strong>一键下载简报包</strong>内含:报告正文、插图文件夹、机器整理的<strong>数据摘要</strong>、以及便于扫读的<strong>要点摘录</strong>。
|
||||
需要改分析规则或重新出稿,请至
|
||||
<RouterLink to="/jd/analysis-build">报告生成</RouterLink>。
|
||||
</p>
|
||||
|
||||
@ -290,7 +290,7 @@ watch(
|
||||
|
||||
<section v-if="briefJson" class="ma-card preview-card">
|
||||
<div class="preview-head">
|
||||
<h2>结构化竞品摘要</h2>
|
||||
<h2>竞品数据摘要(机器整理)</h2>
|
||||
<div class="tabs">
|
||||
<button type="button" class="ma-btn ma-btn-secondary brief-tool" @click="copyBriefJson">
|
||||
{{ briefCopyOk ? '已复制' : '复制' }}
|
||||
@ -298,7 +298,7 @@ watch(
|
||||
<button type="button" class="ma-btn ma-btn-secondary brief-tool" @click="downloadBriefJson">下载文件</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint-top brief-hint">与上方报告统计口径一致的数据汇总,可复制或下载给其它工具使用。</p>
|
||||
<p class="hint-top brief-hint">与上方报告统计口径一致,供复制或存档;一般业务阅读以报告正文与要点摘录为主,本块主要用于存档或交给同事继续分析。</p>
|
||||
<pre class="raw-md brief-json">{{ briefJson }}</pre>
|
||||
</section>
|
||||
|
||||
|
||||
@ -73,9 +73,8 @@ watch(selectedId, () => {
|
||||
<button type="button" class="ma-btn ma-btn-secondary btn-refresh" @click="load">刷新任务列表</button>
|
||||
</div>
|
||||
<p class="lead">
|
||||
选择任务后浏览已入库的搜索、商详、评价与<strong>整合宽表</strong>(合并表按列拆分入库,与
|
||||
<code>keyword_pipeline_merged.csv</code> lean 列一致);各 Tab 下「导出当前表」可导出 JSON / CSV /
|
||||
Excel。竞品报告请在「报告查看」阅读或「报告生成」重新生成。
|
||||
选择任务后可查看本批已入库的<strong>搜索列表</strong>、<strong>商品详情</strong>、<strong>评价</strong>与<strong>整合同步表</strong>(一行对应一个商品在报告里用到的主要字段)。
|
||||
各标签下可**导出**为表格或数据文件。完整文字报告请在「报告查看」阅读,或在「报告生成」中重新出稿。
|
||||
</p>
|
||||
<p v-if="loadError" class="ma-err">{{ loadError }}</p>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user