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 27bd9a5..e95702c 100644 --- a/backend/crawler_copy/jd_pc_search/jd_competitor_report.py +++ b/backend/crawler_copy/jd_pc_search/jd_competitor_report.py @@ -1472,6 +1472,21 @@ def _competitor_matrix_md_line( ) +def _competitor_matrix_compact_md_line( + row: dict[str, str], *, sku_header: str, title_h: str +) -> str: + """§5 简表:与矩阵条形图同源字段,便于对照;不含配料与长摘要。""" + sku = _md_cell(_cell(row, sku_header), 14) + title = _md_cell(_cell(row, title_h), 72) + brand = _md_cell(_cell(row, "detail_brand"), 16) + df = _cell(row, "detail_price_final").strip() + pj = _cell(row, "标价(jdPrice,jdPriceText,realPrice)").strip() + price_cell = _md_cell(df or pj or "—", 12) + cc = _md_cell(_cell(row, "评价量(commentFuzzy)"), 12) + shop = _md_cell(_cell(row, "店铺名(shopName)", "detail_shop_name"), 20) + return f"| {sku} | {title} | {brand} | {shop} | {price_cell} | {cc} |" + + def _strategy_hints( *, cr1: float | None, @@ -1555,6 +1570,12 @@ def _focus_keywords_group_bar_filename(group: str, index: int) -> str: return f"chart_focus_keywords_bar__{slug}.png" +def _matrix_prices_reviews_chart_filename(group: str, index: int) -> str: + """与 ``report_charts.generate_report_charts`` 中 ``chart_matrix_prices_reviews__*`` 一致。""" + slug = _scenario_group_asset_slug(group, index) + return f"chart_matrix_prices_reviews__{slug}.png" + + def _lines_4_reading_brand( *, cr1: float | None, @@ -2104,18 +2125,14 @@ def build_competitor_markdown( "分组**仅**使用合并表列 ``detail_category_path``(商详类目路径):**三级路径**取中间一段(如 … > **饼干** > 粗粮饼干)," "**四级及以上**取倒数第二段(如 … > **面条** > 挂面)。**该列为空**或路径段均为内部编码、**无法解析出可读细类**的 SKU **不进入**本矩阵,亦**不参与**第八章按细类的评价统计。", "", - "维度说明:**产品**(标题/规格)、**价格**(列表展示)、**渠道**(京东店铺)、**推广**(卖点/榜单文案)、" - "**类目路径**(``detail_category_path``)、**配料表**(见下)、**声量**(评价量与摘要)。", - "", - "**配料表**:优先使用配料正文列(开启配料视觉解析时为识别出的文字);" - "仅有详情长图链接时列内会提示;若商详参数含「配料/配料表:」则摘录该段。" - "均为页面信息摘录,**以包装实物与法规标签为准**。", + "**读图方式**:每个细类下优先给出**并列横向条形图**(左:**展示价**(元);右:**评价量**(搜索列表侧文案,作**声量 proxy**,**非**平台销量/动销))," + "纵轴为**产品标题**(与图文件 ``chart_matrix_prices_reviews__*.png`` 同源)。下方附**精简对照表**(SKU、店铺、价与评价量);配料、卖点与评价摘要等完整列见合并表 CSV。", "", ] ) - matrix_header = [ - "| SKU | 产品(标题) | 品牌 | 标价 | 详情价 | 渠道(店铺) | 推广(卖点) | 榜单/标签 | 类目路径(商详) | 配料表 | 评价量(搜索) | 消费者反馈摘要 |", - "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |", + matrix_header_compact = [ + "| SKU | 产品(标题) | 品牌 | 渠道(店铺) | 展示价(详情优先) | 评价量(搜索) |", + "| --- | --- | --- | --- | --- | --- |", ] grouped_matrix = _merged_rows_grouped_for_matrix(merged_rows) if not grouped_matrix: @@ -2127,14 +2144,30 @@ def build_competitor_markdown( else: lines.append("*无合并表 SKU。*") lines.append("") - for gname, grows in grouped_matrix: + for gi, (gname, grows) in enumerate(grouped_matrix): lines.append(f"### {gname}(**{len(grows)}** 款)") lines.append("") - lines.extend(matrix_header) - grows_sorted = sorted(grows, key=lambda r: _cell(r, sku_header) or "") + mx_chart = _matrix_prices_reviews_chart_filename(gname, gi) + lines.extend( + _embed_chart( + run_dir, + mx_chart, + f"「{_md_cell(gname, 20)}」· 展示价与评价量(搜索侧声量 proxy);纵轴为产品标题,与下图简表一致。", + ) + ) + if not (run_dir / "report_assets" / mx_chart).is_file(): + lines.append( + f"*(尚未生成 ``report_assets/{mx_chart}``:请确认已执行报告出图流程,或重新生成报告。)*" + ) + lines.append("") + lines.extend(matrix_header_compact) + grows_sorted = sorted( + grows, + key=lambda r: (_cell(r, title_h) or _cell(r, sku_header) or "").strip(), + ) for row in grows_sorted: lines.append( - _competitor_matrix_md_line( + _competitor_matrix_compact_md_line( row, sku_header=sku_header, title_h=title_h ) ) @@ -2145,9 +2178,9 @@ def build_competitor_markdown( lines.extend( [ "", - "#### 细类要点归纳(大模型,与上表矩阵互补)", + "#### 细类要点归纳(大模型,与上文条形图及简表互补)", "", - "> **说明**:与 §5 相同的细类划分下归纳卖点与配料共性;**具体 SKU、价格与表格列以正文为准**。", + "> **说明**:与 §5 相同的细类划分下归纳卖点与配料共性;**具体 SKU、价格与条形图以正文为准**。", "", _llm_mx, "", diff --git a/backend/pipeline/jd_runner.py b/backend/pipeline/jd_runner.py index 3e45089..1a4eb9d 100644 --- a/backend/pipeline/jd_runner.py +++ b/backend/pipeline/jd_runner.py @@ -219,6 +219,10 @@ def write_competitor_analysis_for_run_dir( eff_rc: dict[str, Any] = ( dict(report_config) if isinstance(report_config, dict) else {} ) + # 任务仅保存部分调参时,补齐默认项(含各 llm_* 开关);显式写入的键不被覆盖 + for _k, _v in get_default_report_config().items(): + if _k not in eff_rc: + eff_rc[_k] = _v all_tx = _flat_comment_texts(comment_rows) suggest_path = run_dir / "keyword_suggest_llm.json" suggest_record: dict[str, Any] = { diff --git a/backend/pipeline/report_charts.py b/backend/pipeline/report_charts.py index af3b9fd..c653584 100644 --- a/backend/pipeline/report_charts.py +++ b/backend/pipeline/report_charts.py @@ -426,7 +426,7 @@ def generate_report_charts(run_dir: Path, brief: dict[str, Any]) -> list[str]: sku = str(s.get("sku_id") or "").strip() # 与 §5 矩阵「产品」列一致:纵轴优先品名,无标题时再退化为 SKU if title: - label = title if len(title) <= 30 else title[:28] + "…" + label = title if len(title) <= 48 else title[:46] + "…" elif sku: label = sku if len(sku) <= 22 else sku[:20] + "…" else: