mirror of
https://github.com/primedigitaltech/market-assistant.git
synced 2026-07-21 23:41:39 +08:00
fix(charts): 探针模式下跳过 §8.3 关注词场景条图并清理旧 PNG
Made-with: Cursor
This commit is contained in:
parent
31ac2f1ac7
commit
b8516662a8
@ -2733,7 +2733,7 @@ def build_competitor_markdown(
|
||||
"- **归因**:每条评价按其 SKU 对应到深入样本,再映射到该 SKU 所属细类;SKU 不在合并表中的评价单独归入说明性分组;**在合并表中但该 SKU 缺 ``detail_category_path`` 或路径无法解析为可读细类的,该评价不进入按细类统计**(与 §5 **同一条排除规则**)。",
|
||||
"- **正负面粗判(§8.2)**:若评价含有效「评分」列则**先按星级**粗分正负与中评,再在对应子集内统计口语短语;无评分时仍按关键词子串;若任务开启 **llm_comment_sentiment**,可附**大模型对抽样原文的主题归因**,与条形图互补。",
|
||||
(
|
||||
"- **文本挖掘探针(§8.3)**:本任务已启用 **jieba + sklearn** 的开放词表分析(词频 / TF-IDF / 共现 / LDA,可选词云),与 §8.2 规则词表条形图**口径不同**、**互补**;**不再**输出原「关注词次数 + 场景占比」左右并列条图。"
|
||||
"- **文本挖掘探索(§8.3)**:本任务已启用 **jieba + sklearn** 的开放词表分析(词频 / TF-IDF / 共现 / LDA,可选词云),与 §8.2 规则词表条形图**不同**、**互补**;**不再**输出原「关注词次数 + 场景占比」左右并列条图。"
|
||||
if _ch8_probe_sec
|
||||
else "- **关注词与使用场景(§8.3)**:对组内评价正文做关注词子串计数(左栏条形图);对每条有效文本独立扫描**本次任务生效的场景词组**(来自报告调参或系统默认),一条可属多场景,右栏为**占该细类有效文本比例 %**(多标签下可相加 **>** 100%)。二者在 **同一张图左右并列**,与 §5 矩阵细类一一对应。"
|
||||
),
|
||||
@ -2744,7 +2744,7 @@ def build_competitor_markdown(
|
||||
]
|
||||
if _sm_score:
|
||||
_sec82_block.append(
|
||||
"- **四象限口径**:本批存在有效「评分」时——**1~2 星**计为偏负向,**4~5 星**计为偏正向,**3 星**计为中评,**空文本**计为中性;"
|
||||
"- **正负面粗判规模**:本批存在有效「评分」时——**1~2 星**计为偏负向,**4~5 星**计为偏正向,**3 星**计为中评,**空文本**计为中性;"
|
||||
"无评分的条仍按关键词子串划分;「混合」仅在**无评分**且同条兼含正/负关键词时出现。"
|
||||
)
|
||||
_sec82_block.extend(
|
||||
@ -2778,7 +2778,7 @@ def build_competitor_markdown(
|
||||
_embed_chart(
|
||||
run_dir,
|
||||
"chart_sentiment_overview_pie.png",
|
||||
"评价语气占比(扇形图;与上表条数一致)",
|
||||
"评价正负面粗判规模(扇形图;与上表条数一致)",
|
||||
)
|
||||
)
|
||||
lines.extend(
|
||||
@ -2844,6 +2844,7 @@ def build_competitor_markdown(
|
||||
]
|
||||
)
|
||||
else:
|
||||
# 仅当未嵌入文本挖掘探针(_ch8_probe_sec 为空)时:原「关注词 + 场景」条图与逐细类段落
|
||||
lines.extend(
|
||||
[
|
||||
"### 8.3 关注词与使用场景(按细类)",
|
||||
|
||||
@ -356,7 +356,7 @@ def write_competitor_analysis_for_run_dir(
|
||||
)
|
||||
from ..reporting.charts import generate_report_charts
|
||||
|
||||
generate_report_charts(run_dir, brief_final)
|
||||
generate_report_charts(run_dir, brief_final, report_config=eff_rc)
|
||||
|
||||
llm_sentiment_md = ""
|
||||
sentiment_llm_record: dict[str, Any] = {
|
||||
|
||||
@ -256,8 +256,17 @@ def _cleanup_obsolete_report_assets(out_dir: Path) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def generate_report_charts(run_dir: Path, brief: dict[str, Any]) -> list[str]:
|
||||
"""生成扇形/条形 PNG。返回已写入的文件名列表(不含路径)。"""
|
||||
def generate_report_charts(
|
||||
run_dir: Path,
|
||||
brief: dict[str, Any],
|
||||
*,
|
||||
report_config: dict[str, Any] | None = None,
|
||||
) -> list[str]:
|
||||
"""生成扇形/条形 PNG。返回已写入的文件名列表(不含路径)。
|
||||
|
||||
若 ``report_config["chapter8_text_mining_probe"]`` 为真,**不**生成 ``chart_focus_and_scenarios_bar__*.png``
|
||||
(与竞品报告 §8.3 文本挖掘探针互斥,避免无效产出)。
|
||||
"""
|
||||
_setup_matplotlib_cjk()
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.ticker import FuncFormatter
|
||||
@ -543,76 +552,87 @@ def generate_report_charts(run_dir: Path, brief: dict[str, Any]) -> list[str]:
|
||||
core = "group"
|
||||
return f"i{index:02d}_{core}"
|
||||
|
||||
scen_by_slug: dict[str, tuple[list[str], list[float], int]] = {}
|
||||
by_grp = brief.get("usage_scenarios_by_matrix_group") or []
|
||||
if isinstance(by_grp, list):
|
||||
for item in by_grp:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
slug = (item.get("chart_slug") or "").strip()
|
||||
gname = str(item.get("group") or "").strip()[:24]
|
||||
idx = item.get("matrix_group_index")
|
||||
if not slug and gname != "" and isinstance(idx, int):
|
||||
slug = scenario_group_asset_slug(gname, idx)
|
||||
if not slug:
|
||||
continue
|
||||
scen_rows = item.get("scenarios") or []
|
||||
n_unit = int(item.get("effective_text_units") or 0)
|
||||
gpairs: list[tuple[str, float]] = []
|
||||
if isinstance(scen_rows, list):
|
||||
for r in scen_rows:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
lb = str(r.get("scenario") or "").strip()[:48]
|
||||
c = r.get("count")
|
||||
if lb and isinstance(c, (int, float)) and c > 0:
|
||||
gpairs.append((lb, float(c)))
|
||||
gpairs = _merge_labeled_counts_tail(gpairs, max_items=14)
|
||||
if gpairs and n_unit > 0:
|
||||
scen_by_slug[slug] = (
|
||||
[p[0] for p in gpairs],
|
||||
[p[1] for p in gpairs],
|
||||
n_unit,
|
||||
)
|
||||
_skip_focus_scenario_combo = bool(
|
||||
isinstance(report_config, dict)
|
||||
and report_config.get("chapter8_text_mining_probe")
|
||||
)
|
||||
if _skip_focus_scenario_combo:
|
||||
for fp in out_dir.glob("chart_focus_and_scenarios_bar__*.png"):
|
||||
try:
|
||||
fp.unlink()
|
||||
except OSError:
|
||||
pass
|
||||
if not _skip_focus_scenario_combo:
|
||||
scen_by_slug: dict[str, tuple[list[str], list[float], int]] = {}
|
||||
by_grp = brief.get("usage_scenarios_by_matrix_group") or []
|
||||
if isinstance(by_grp, list):
|
||||
for item in by_grp:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
slug = (item.get("chart_slug") or "").strip()
|
||||
gname = str(item.get("group") or "").strip()[:24]
|
||||
idx = item.get("matrix_group_index")
|
||||
if not slug and gname != "" and isinstance(idx, int):
|
||||
slug = scenario_group_asset_slug(gname, idx)
|
||||
if not slug:
|
||||
continue
|
||||
scen_rows = item.get("scenarios") or []
|
||||
n_unit = int(item.get("effective_text_units") or 0)
|
||||
gpairs: list[tuple[str, float]] = []
|
||||
if isinstance(scen_rows, list):
|
||||
for r in scen_rows:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
lb = str(r.get("scenario") or "").strip()[:48]
|
||||
c = r.get("count")
|
||||
if lb and isinstance(c, (int, float)) and c > 0:
|
||||
gpairs.append((lb, float(c)))
|
||||
gpairs = _merge_labeled_counts_tail(gpairs, max_items=14)
|
||||
if gpairs and n_unit > 0:
|
||||
scen_by_slug[slug] = (
|
||||
[p[0] for p in gpairs],
|
||||
[p[1] for p in gpairs],
|
||||
n_unit,
|
||||
)
|
||||
|
||||
fb = brief.get("consumer_feedback_by_matrix_group") or []
|
||||
if isinstance(fb, list):
|
||||
for item in fb:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
slug = (item.get("chart_slug") or "").strip()
|
||||
gname = str(item.get("group") or "").strip()[:24]
|
||||
idx = item.get("matrix_group_index")
|
||||
if not slug and gname != "" and isinstance(idx, int):
|
||||
slug = scenario_group_asset_slug(gname, idx)
|
||||
if not slug:
|
||||
continue
|
||||
hk = item.get("focus_keyword_hits") or []
|
||||
wl: list[str] = []
|
||||
vl: list[float] = []
|
||||
if isinstance(hk, list):
|
||||
for row in hk[:20]:
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
w = str(row.get("word") or "").strip()[:32]
|
||||
c = row.get("count")
|
||||
if w and isinstance(c, (int, float)) and c > 0:
|
||||
wl.append(w)
|
||||
vl.append(float(c))
|
||||
wl = wl[:18]
|
||||
vl = vl[:18]
|
||||
gl, gv, n_scen = scen_by_slug.get(slug, ([], [], 0))
|
||||
n_unit_fb = int(item.get("effective_comment_text_units") or 0)
|
||||
n_texts = n_scen if n_scen > 0 else n_unit_fb
|
||||
save_combo_focus_scenario_bar(
|
||||
gname=gname,
|
||||
slug=slug,
|
||||
wl=wl,
|
||||
vl=vl,
|
||||
gl=gl,
|
||||
gv=gv,
|
||||
n_texts=n_texts,
|
||||
)
|
||||
fb = brief.get("consumer_feedback_by_matrix_group") or []
|
||||
if isinstance(fb, list):
|
||||
for item in fb:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
slug = (item.get("chart_slug") or "").strip()
|
||||
gname = str(item.get("group") or "").strip()[:24]
|
||||
idx = item.get("matrix_group_index")
|
||||
if not slug and gname != "" and isinstance(idx, int):
|
||||
slug = scenario_group_asset_slug(gname, idx)
|
||||
if not slug:
|
||||
continue
|
||||
hk = item.get("focus_keyword_hits") or []
|
||||
wl: list[str] = []
|
||||
vl: list[float] = []
|
||||
if isinstance(hk, list):
|
||||
for row in hk[:20]:
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
w = str(row.get("word") or "").strip()[:32]
|
||||
c = row.get("count")
|
||||
if w and isinstance(c, (int, float)) and c > 0:
|
||||
wl.append(w)
|
||||
vl.append(float(c))
|
||||
wl = wl[:18]
|
||||
vl = vl[:18]
|
||||
gl, gv, n_scen = scen_by_slug.get(slug, ([], [], 0))
|
||||
n_unit_fb = int(item.get("effective_comment_text_units") or 0)
|
||||
n_texts = n_scen if n_scen > 0 else n_unit_fb
|
||||
save_combo_focus_scenario_bar(
|
||||
gname=gname,
|
||||
slug=slug,
|
||||
wl=wl,
|
||||
vl=vl,
|
||||
gl=gl,
|
||||
gv=gv,
|
||||
n_texts=n_texts,
|
||||
)
|
||||
|
||||
sent = brief.get("comment_sentiment_lexicon") or {}
|
||||
if isinstance(sent, dict):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user