diff --git a/backend/pipeline/llm/generate_strategy.py b/backend/pipeline/llm/generate_strategy.py index d1e9ac7..0c3fe3c 100644 --- a/backend/pipeline/llm/generate_strategy.py +++ b/backend/pipeline/llm/generate_strategy.py @@ -122,6 +122,7 @@ STRATEGY_SYSTEM = f"""你是市场策略顾问,根据**结构化监测摘要** **对外成稿与禁止技术泄露(硬性)**: - 正文须为**可直接对业务或合作方阅读**的正式策略文档(对外前仍须按需脱敏)。**禁止**出现:反引号代码体、JSON 键名、英文字段名、内部数据结构名、源码或仓库路径、类文件名、「任务 ID」「工作台」「规则骨架」等系统痕迹;**禁止**照抄底稿中以 *成稿:*、*回答:*、*占位*、*骨架* 开头的**元说明句**,须改写为正式业务表述。 +- **禁止**在正文使用**写作指导式**套话(读者不应看到「作者须知」):例如「须与 §2/§2.1 一致」「与 §2.1 类目列可对上」「为后文……埋伏笔」「回扣 §2」「承接 §2」「勿重复 §×」等——须直接写**实质策略内容**,勿解释章节之间如何对齐。 - **一级标题**用文书式,例如 `# 「{{keyword}}」市场策略建议书(草案)`,其中 `keyword` 取自本消息 JSON 的同名字段;**勿**使用「草稿」「底稿」「归纳用」等对内用词。 - **附录**中的采集范围等信息用**中文短句**(如「列表页约采集第 3~10 页」),**禁止** `page_start=` 等键值对或英文键名。 @@ -148,8 +149,8 @@ STRATEGY_SYSTEM = f"""你是市场策略顾问,根据**结构化监测摘要** **分节要求(与底稿章节一一对应,勿省略)**: - **策略范围与前提**:回答「**这份策略是针对什么做的**」(监测任务、本品角色、战场、主推类目、**本阶段目标类型**、时间、成功标准)。与业务表单及备注对齐;`strategy_decisions_substantive` 为 false 时须写清假设前提与推荐目标类型(可含 A~E 选项),为 true 时未填项不得与已填决策矛盾。**禁止**与后文 §2.1、§六 自相矛盾。 -- **摘要**:除范围样本外,**阶段重点**须含 1~2 条**可执行动作**,且能回扣 §2 中的优先痛点(非空泛「加强运营」);须**承接**上文「策略范围与前提」已定的边界。 -- **一、顾客是谁**:**禁止**重复报告中的细类词频、分品类样本量展开、文本挖掘方法;用 **少量结论句**(谁搜、关心什么)即可,**1.3 本品聚焦**须写清「本期主攻人群/场景」与 **§2.1** 的对应关系。 +- **摘要**:除范围样本外,**阶段重点**须含 1~2 条**可执行动作**,指向优先痛点(非空泛「加强运营」);须与上文「策略范围与前提」边界一致(**勿**在正文写「回扣 §2」「承接上文」等指导语)。 +- **一、顾客是谁**:**禁止**重复报告中的细类词频、分品类样本量展开、文本挖掘方法;用 **少量结论句**(谁搜、关心什么、决策场景);**1.3 本品聚焦**须写清本期主攻人群/场景/细类(**勿**写「与 §2.1 可对上」类作者提示)。 - **二**:**仅 §2.1** 一张表:「类目/细类(本决策适用)| 用户痛点(简述)| 策略动作 | 具体怎么做 | 如何验证」。须覆盖监测已支撑的主要维度(**按类目分行**,口感/质地分线、分量/规格、信任与价格等,依数据取舍);**类目列 + 痛点简述列**遵守「§2 表」条款。**禁止**再写独立「痛点与证据表」「价值对表」「负向归因」子节(与 §二 重复的内容一律并入本表或删去)。 - **三**:**仅 §3.1**(品类与时机、检索/样本、购买理由);可结合 brief 写价带锚点一句。**禁止**写 §3.2「转化障碍与应对」;若与购买相关的障碍与应对已在 §2.1 表内,§3.1 **勿再复述**。 - **四**:品牌承诺与调性须能落到**可感知触点**(如商详第几屏、包装、客服首句),避免只有形容词。 diff --git a/backend/pipeline/reporting/strategy_draft.py b/backend/pipeline/reporting/strategy_draft.py index dc98eaf..396de14 100644 --- a/backend/pipeline/reporting/strategy_draft.py +++ b/backend/pipeline/reporting/strategy_draft.py @@ -214,11 +214,27 @@ def build_strategy_draft_markdown( "", ] if not for_llm_input - else [ - "*监测任务边界与业务角色:表单已填则写成短句;未填时由归纳稿基于监测摘要提出**假设性**表述。*", - "*阶段目标类型可列 A~E 备选(上市验证 / 份额追赶 / 价盘防守 / 新场景或新人群 / 其它),并写明推荐倾向。*", - "", - ] + else [] + ) + _table_main_cat = ( + "*(待填:如饼干线 / 面包线 / 多线并行;未定写「待业务定类」)*" + if not for_llm_input + else "*(待确认)*" + ) + _table_goal_type = ( + "*(成稿从规划文档「目标类型选项」择一或组合,并与下列成功标准一致)*" + if not for_llm_input + else "*(待确认)*" + ) + _summary_user_side = ( + "- **用户侧**:*(一两句结论即可:讨论焦点与负向主题;**按细类分句**归纳,**勿**混成「全站用户」一句;**勿**展开与报告重复的细类统计、词频。)*" + if not for_llm_input + else "- **用户侧**:—" + ) + _summary_stage = ( + "- **阶段重点**:*(须含 1~2 条**可执行动作**,回扣 §2 优先痛点;**尽量点明适用类目/主推线**;勿仅写「加强运营」。)*" + if not for_llm_input + else "- **阶段重点**:—" ) lines.extend( [ @@ -231,8 +247,8 @@ def build_strategy_draft_markdown( f"| **策略服务对象(本品角色)** | {_scope_cell(pr_role, '待填:新品 / 追赶 / 防守 / 拓品类 …')} |", f"| **一句话战场** | {_scope_cell(bf_line, '在哪个需求场景、与谁抢同一批用户?')} |", f"| **目标客群/场景** | {_scope_cell(aud, '可选')} |", - "| **主推类目/细类** | *(待填:如饼干线 / 面包线 / 多线并行;未定写「待业务定类」)* |", - "| **本阶段策略目标类型** | *(成稿从规划文档「目标类型选项」择一或组合,并与下列成功标准一致)* |", + f"| **主推类目/细类** | {_table_main_cat} |", + f"| **本阶段策略目标类型** | {_table_goal_type} |", f"| **时间范围** | {_scope_cell(th, '如本季度 / 未来 12 周')} |", f"| **成功标准(可量化)** | {_scope_cell(sc, '搜索位次、转化、复购等')} |", "", @@ -246,8 +262,8 @@ def build_strategy_draft_markdown( if merged_n is not None or comm_n is not None else "样本规模见附录。" ), - "- **用户侧**:*(一两句结论即可:讨论焦点与负向主题;**按细类分句**归纳,**勿**混成「全站用户」一句;**勿**展开与报告重复的细类统计、词频。)*", - "- **阶段重点**:*(须含 1~2 条**可执行动作**,回扣 §2 优先痛点;**尽量点明适用类目/主推线**;勿仅写「加强运营」。)*", + _summary_user_side, + _summary_stage, "", "## 一、顾客是谁", "", @@ -263,28 +279,40 @@ def build_strategy_draft_markdown( lines.append("- **一句话战场**:*(在哪个需求场景、与谁抢同一批用户?)*") lines.extend( [ - "- **典型路径**:*(成稿:搜索 → 列表比价 → 详情与配料 → 评价 → 下单/复购。)*", - "", - "*成稿须与 §2 一致:写清「谁在什么任务下检索、决策」及**主攻类目/细类**(与 §2.1 类目列可对上),为后文「针对痛点怎么做」埋伏笔。*", + ( + "- **典型路径**:搜索 → 列表比价 → 商详与配料 → 评价 → 下单/复购。" + if for_llm_input + else "- **典型路径**:*(成稿:搜索 → 列表比价 → 详情与配料 → 评价 → 下单/复购。)*" + ), "", + *( + [] + if for_llm_input + else [ + "*成稿须与 §2 一致:写清「谁在什么任务下检索、决策」及**主攻类目/细类**(与 §2.1 类目列可对上),为后文「针对痛点怎么做」埋伏笔。*", + "", + ] + ), "### 1.2 细类讨论焦点(评论文本分析)", "", ] ) if use_ch8_probe: - lines.extend( - [ - "*当前任务以**第八章评论侧文本挖掘**为主呈现时,此处**不**逐条罗列关注词子串命中次数。*", - "", - "- **饼干 / 糕点 / 面点等**:*(骨架占位;成稿**分细类**各一句归纳用户关心点,**勿**合并成模糊「全池」一句;**勿**复述 §8 词频与条数。)*", - "", - ] - ) + if not for_llm_input: + lines.extend( + [ + "*当前任务以**第八章评论侧文本挖掘**为主呈现时,此处**不**逐条罗列关注词子串命中次数。*", + "", + "- **饼干 / 糕点 / 面点等**:*(骨架占位;成稿**分细类**各一句归纳用户关心点,**勿**合并成模糊「全池」一句;**勿**复述 §8 词频与条数。)*", + "", + ] + ) else: - lines.append( - "*简报中**不再**附带预设关注词/场景子串统计;评论侧请依据同任务《竞品分析报告》**第八章第二节**(文本挖掘探针,若已启用)及抽样原文撰写本节。*" - ) - lines.append("") + if not for_llm_input: + lines.append( + "*简报中**不再**附带预设关注词/场景子串统计;评论侧请依据同任务《竞品分析报告》**第八章第二节**(文本挖掘探针,若已启用)及抽样原文撰写本节。*" + ) + lines.append("") mix = brief.get("category_mix_top") or [] if mix: @@ -299,8 +327,14 @@ def build_strategy_draft_markdown( [ "### 1.3 本品聚焦(占位)", "", - "*成稿写清本期**主攻人群/场景/类目或主推细类**与 §2.1「类目/细类」列的对应关系。*", - "", + *( + [] + if for_llm_input + else [ + "*成稿写清本期**主攻人群/场景/类目或主推细类**与 §2.1「类目/细类」列的对应关系。*", + "", + ] + ), _goal_bullet("本品角色", str(d.get("product_role") or ""), "新品 / 追赶 / 防守 / 拓品类 …"), _goal_bullet( "目标客群", @@ -320,16 +354,18 @@ def build_strategy_draft_markdown( [ "## 二、产品价值与用户痛点", "", - "*本节**仅**用下表写清**针对痛点要怎么做**(**类目** + 痛点简述 + 动作 + 落地 + 验证)。**不再**单设「痛点表 / 价值对表 / 负向归因」子节,避免与 §三、§八重复。*", - "", - ( - "*「用户痛点(简述)」须与监测摘要 / 策略线索 / 报告节选**可核对**;**禁止**编造「用户反馈『……』」式引语,除非原句已出现在上述输入中。*" + *( + [] if for_llm_input - else "*「用户痛点(简述)」须与 `structured_brief` / 策略线索 / 报告节选**可核对**;**禁止**编造「用户反馈『……』」式引语,除非原句已出现在上述输入中。*" + else [ + "*本节**仅**用下表写清**针对痛点要怎么做**(**类目** + 痛点简述 + 动作 + 落地 + 验证)。**不再**单设「痛点表 / 价值对表 / 负向归因」子节,避免与 §三、§八重复。*", + "", + "*「用户痛点(简述)」须与 `structured_brief` / 策略线索 / 报告节选**可核对**;**禁止**编造「用户反馈『……』」式引语,除非原句已出现在上述输入中。*", + "", + "*「类目/细类」列:写明本行决策**适用于哪一类**(如饼干/面包/全检索池);多细类须**分行**,**禁止**用一句「全站」覆盖彼此冲突的策略;类目未定可写「待业务定类」并附分类假设。*", + "", + ] ), - "", - "*「类目/细类」列:写明本行决策**适用于哪一类**(如饼干/面包/全检索池);多细类须**分行**,**禁止**用一句「全站」覆盖彼此冲突的策略;类目未定可写「待业务定类」并附分类假设。*", - "", "### 2.1 针对痛点要怎么做", "", "| 类目/细类(本决策适用) | 用户痛点(简述) | 策略动作 | 具体怎么做(触点/话术/规格/渠道) | 如何验证 |", @@ -374,7 +410,11 @@ def build_strategy_draft_markdown( elif merged_n is not None: lines.append(f"- **深入样本 SKU 数**:{_num(merged_n)}") else: - lines.append("- **品类与时机**:*(成稿结合摘要与监测范围。)*") + lines.append( + "- **品类与时机**:—" + if for_llm_input + else "- **品类与时机**:*(成稿结合摘要与监测范围。)*" + ) lines.append("") if pst.get("n"): src = _esc(brief.get("price_stats_source")) or "—" @@ -387,12 +427,16 @@ def build_strategy_draft_markdown( ] ) else: - lines.append("*摘要中无价带统计,成稿可结合本批次价格数据在 §3.1 补一句锚点;**勿**重复 §2 已写的应对动作。*") + if for_llm_input: + lines.append("- **价带摘录**:监测摘要中暂无统计表,可结合同任务报告补一句购买锚点。") + else: + lines.append("*摘要中无价带统计,成稿可结合本批次价格数据在 §3.1 补一句锚点;**勿**重复 §2 已写的应对动作。*") lines.append("") - lines.append( + if not for_llm_input: + lines.append( "*成稿在 §3.1 末尾用 1~2 句**承接** §2 优先痛点并写购买理由;**若**多细类并存,**分句**写清购买理由与主推;价带/规格类动作已在 §2 表内则此处**勿再展开一遍**。*" - ) - lines.append("") + ) + lines.append("") lines.extend( [ @@ -400,15 +444,28 @@ def build_strategy_draft_markdown( "", "### 4.1 品牌承诺与调性(占位)", "", - "*成稿:承诺与调性须能落到**触点**(商详/包装/客服首句等)上的**具体句子**;**若**多类目话术不同,按 §2.1 类目**分句**,勿仅形容词。*", - "", + *( + [] + if for_llm_input + else [ + "*成稿:承诺与调性须能落到**触点**(商详/包装/客服首句等)上的**具体句子**;**若**多类目话术不同,按 §2.1 类目**分句**,勿仅形容词。*", + "", + ] + ), "- **一句话**:*(占位)*", - "- **调性**:透明、可验证、合规控糖叙事(成稿可细化)。", + ( + "- **调性**:透明、可验证、合规控糖叙事。" + if for_llm_input + else "- **调性**:透明、可验证、合规控糖叙事(成稿可细化)。" + ), "", "### 4.2 信任与证据", "", - "- *(成稿:评价、配料、可核验表述边界。)*", - "", + *( + ["- *(评价、配料、可核验表述边界。)*", ""] + if for_llm_input + else ["- *(成稿:评价、配料、可核验表述边界。)*", ""] + ), "**主定位(与表单一致)**", "", f"- {_pos_mark(pos, 'top')} **贴顶**:中高位或头部价位带。", @@ -449,16 +506,33 @@ def build_strategy_draft_markdown( if n_brand: lines.append(n_brand) if not n_shop and not n_brand: - lines.append("*本摘要未含集中度指标,请结合本批次竞争结构数据补全。*") + lines.append( + "- **竞争结构**:监测摘要未含集中度摘录。" + if for_llm_input + else "*本摘要未含集中度指标,请结合本批次竞争结构数据补全。*" + ) lines.extend( [ "", - "- **环境自测**:头部强势时是侧翼还是正面替代?格局分散时是否用细分场景切入?", + *( + [] + if for_llm_input + else [ + "", + "- **环境自测**:头部强势时是侧翼还是正面替代?格局分散时是否用细分场景切入?", + ] + ), "", "### 5.2 差异化方向(占位)", "", - "*成稿:相对竞品**多做什么/少做什么**,写**可执行的一步**;**若**差异因细类而异,**分类目**写(非空泛「更好」)。*", - "", + *( + [] + if for_llm_input + else [ + "*成稿:相对竞品**多做什么/少做什么**,写**可执行的一步**;**若**差异因细类而异,**分类目**写(非空泛「更好」)。*", + "", + ] + ), "| 差异点 | 说明 | 风险 |", "|--------|------|------|", "| | *待填* | |", @@ -467,16 +541,21 @@ def build_strategy_draft_markdown( ) lines.append("### 5.3 竞争应对") lines.append("") - lines.append( - "*成稿:在表单倾向基础上,写清**跟价/不跟价时具体话术或机制**(一句即可)。*" - ) - lines.append("") + if not for_llm_input: + lines.append( + "*成稿:在表单倾向基础上,写清**跟价/不跟价时具体话术或机制**(一句即可)。*" + ) + lines.append("") stance = _esc(d.get("competitive_stance") or "").strip() stance_line = { "flank": "- **本品倾向**:侧翼切入,避免与头部正面硬碰。", "head_on": "- **本品倾向**:正面替代,对标头部主战场。", "both": "- **本品倾向**:分层推进(部分场景侧翼、部分场景正面)。", - "undecided": "- **本品倾向**:*(表单未选;成稿时据数据写清倾向)*", + "undecided": ( + "- **本品倾向**:*(待确认)*" + if for_llm_input + else "- **本品倾向**:*(表单未选;成稿时据数据写清倾向)*" + ), }.get(stance) if stance_line: lines.append(stance_line) @@ -498,8 +577,14 @@ def build_strategy_draft_markdown( "", "### 6.2 路径", "", - "*成稿:路径须与 **§2.1 针对痛点要怎么做** 可对齐;营销/总体策略为**动词句**,回扣痛点;**多类目并行**时**分线**写目标或写清主线/副线。*", - "", + *( + [] + if for_llm_input + else [ + "*成稿:路径须与 **§2.1 针对痛点要怎么做** 可对齐;营销/总体策略为**动词句**,回扣痛点;**多类目并行**时**分线**写目标或写清主线/副线。*", + "", + ] + ), _goal_bullet( "营销策略", str(d.get("marketing_strategy") or ""), @@ -527,9 +612,15 @@ def build_strategy_draft_markdown( [ "## 七、品牌四线:建设 · 打造 · 运营 · 体验", "", - "*(与表单「4P 策略支柱」对应:产品 / 定价 / 渠道 / 传播。)*", - "*成稿:**每条线**至少一句——服务哪类痛点、本阶段**具体做哪一步**;**尽量**与 §2.1「类目/细类」可对上,多类目则**分句**(勿四条同一泛化句)。*", - "", + *( + [] + if for_llm_input + else [ + "*(与表单「4P 策略支柱」对应:产品 / 定价 / 渠道 / 传播。)*", + "*成稿:**每条线**至少一句——服务哪类痛点、本阶段**具体做哪一步**;**尽量**与 §2.1「类目/细类」可对上,多类目则**分句**(勿四条同一泛化句)。*", + "", + ] + ), "### 7.1 品牌建设", "", f"- {_pillar_cell(pp)}", @@ -549,14 +640,10 @@ def build_strategy_draft_markdown( ] ) - if use_ch8_probe: + if use_ch8_probe and not for_llm_input: pst_sig = brief.get("price_promotion_signals") or {} has_promo = isinstance(pst_sig, dict) and bool(pst_sig) - promo_hint = ( - "*促销与活动线索:须与摘要中的价差与活动信号及报告第六章已有归纳一致;无则勿编造具体满减门槛。*" - if for_llm_input - else "*促销与活动线索:须与摘要 `price_promotion_signals` 及第六章/第九章已有归纳一致;无则勿编造具体满减门槛。*" - ) + promo_hint = "*促销与活动线索:须与摘要 `price_promotion_signals` 及第六章/第九章已有归纳一致;无则勿编造具体满减门槛。*" lines.extend( [ "", @@ -571,8 +658,14 @@ def build_strategy_draft_markdown( [ "## 八、战术支柱", "", - "*成稿:四支柱分别回扣 **痛点→动作→落地**(可与 §2.1 呼应,避免纯重复);**若**产品/定价/促销因类目策略不同,**分细类**写子条,勿一条盖全站。*", - "", + *( + [] + if for_llm_input + else [ + "*成稿:四支柱分别回扣 **痛点→动作→落地**(可与 §2.1 呼应,避免纯重复);**若**产品/定价/促销因类目策略不同,**分细类**写子条,勿一条盖全站。*", + "", + ] + ), "### 8.1 产品策略", "", f"- *(表单产品支柱:{_pillar_cell(pp)})*", @@ -583,13 +676,15 @@ def build_strategy_draft_markdown( "", "### 8.3 促销与活动策略", "", - "*须写促销**原则**(券/到手价/跟价节奏);**满减、满折、跨店**等:能引用的写清来源;监测未捕获具体门槛时写「待与运营/后台对齐」,**勿**整节留空,**勿**编造门槛数字。*", - ( - "*与监测摘要中的促销与价差信号、报告第六章一致;勿虚构活动。*" + *( + [] if for_llm_input - else "*与 `price_promotion_signals`、报告第六章一致;勿虚构活动。*" + else [ + "*须写促销**原则**(券/到手价/跟价节奏);**满减、满折、跨店**等:能引用的写清来源;监测未捕获具体门槛时写「待与运营/后台对齐」,**勿**整节留空,**勿**编造门槛数字。*", + "*与 `price_promotion_signals`、报告第六章一致;勿虚构活动。*", + "", + ] ), - "", "### 8.4 渠道与传播", "", f"- *(渠道/传播:{_pillar_cell(pch)} / {_pillar_cell(pcm)})*", @@ -609,14 +704,26 @@ def build_strategy_draft_markdown( _risk_line(rp, "价格带是否含大促/异常挂价?(需核对清洗规则)"), _risk_line(rc, "列表集中度与深入样本品牌是否不一致?(需解释渠道差异)"), "", - "*成稿:每条风险尽量带**应对动作或验证计划**(抽样、核对规则),勿只列标题。*", - "", - "*业务备注见下节。*", - "", + *( + [] + if for_llm_input + else [ + "*成稿:每条风险尽量带**应对动作或验证计划**(抽样、核对规则),勿只列标题。*", + "", + "*业务备注见下节。*", + "", + ] + ), "## 十、下一步与节奏", "", - "*成稿:下列为**可执行任务**(可补负责人/时间);与 §2.1 / §六 优先级一致;可含「按类目核对主图/商详与 §2.1 表」类项。*", - "", + *( + [] + if for_llm_input + else [ + "*成稿:下列为**可执行任务**(可补负责人/时间);与 §2.1 / §六 优先级一致;可含「按类目核对主图/商详与 §2.1 表」类项。*", + "", + ] + ), "- [ ] 锁定主推款与对标;过法务与合规。", "- [ ] 统一对外数据口径与话术。", "- [ ] 下轮监测更新后迭代策略。", diff --git a/backend/pipeline/tests/test_strategy_draft.py b/backend/pipeline/tests/test_strategy_draft.py index 5e4811e..35d3dc8 100644 --- a/backend/pipeline/tests/test_strategy_draft.py +++ b/backend/pipeline/tests/test_strategy_draft.py @@ -42,6 +42,9 @@ class StrategyDraftTests(SimpleTestCase): self.assertNotIn("strategy_hints", md) self.assertIn("监测摘要自动线索", md) self.assertIn("列表页约第 1~3 页", md) + self.assertNotIn("埋伏笔", md) + self.assertNotIn("成稿须与 §2", md) + self.assertNotIn("回扣 §2", md) def test_build_contains_sections_and_notes(self) -> None: brief = {