fix(strategy): 探针模式下剔除词频类 hints 并强制与报告促销/第九章对齐

- 规则底稿:过滤 strategy_hints 中与关注词/预设场景条形图同源的句子;§7 增加促销与活动对齐说明(price_promotion_signals、§6/§9)。
- LLM:payload 增加 chapter8_text_mining_probe;structured_brief 同步过滤 hints;系统提示与 omission_note 明确禁止用词频为据、须承接第九章与促销信号。

Made-with: Cursor
This commit is contained in:
hub-gif 2026-04-17 17:42:20 +08:00
parent 1a569ce727
commit 99bd2ef40d
3 changed files with 88 additions and 4 deletions

View File

@ -8,6 +8,7 @@ from typing import Any
from ..reporting.brief_compact import compact_brief_for_llm
from ..reporting.strategy_draft import (
build_strategy_draft_markdown,
filter_strategy_hints_for_ch8_probe,
report_uses_chapter8_text_mining_probe,
)
from .llm_client import call_llm, estimate_chat_input_tokens, llm_context_window_size
@ -30,6 +31,11 @@ STRATEGY_SYSTEM = """你是市场策略顾问,根据**结构化监测摘要**
**矩阵** `structured_brief` 含矩阵相关字段**呼应**细分类目与竞品矩阵结论不得无故删光
**第八章文本挖掘探针 JSON `chapter8_text_mining_probe` 为真时硬性**
- **禁止**关注词子串命中次数预设场景分组条数/占比当作评论侧或需求侧的主要论据`structured_brief` 已省略这些字段与报告正文 §8 主口径一致
- 用户洞察场景与传播结论须与宿主报告 **§8 文本挖掘** `report_strategy_excerpt`第九章一致
- **促销与活动** `report_strategy_excerpt` `structured_brief.price_promotion_signals` 中出现满减到手价差促销形态等成稿须在**价格/营销/渠道**相关段落**承接**与报告 **第六章第九章** 建议一致**不得无故省略****禁止**编造具体满减门槛或补贴比例仅复述输入中已有线索
**输出** Markdown 正文不要 ``` 围栏须收束各小节与全文勿中途截断"""
STRATEGY_USER_PREFIX = """请基于以下 JSON 输出最终策略稿Markdown\n\n"""
@ -80,6 +86,10 @@ def generate_strategy_draft_markdown_llm(
"usage_scenarios_by_matrix_group",
):
compact.pop(k, None)
if isinstance(compact.get("strategy_hints"), list):
compact["strategy_hints"] = filter_strategy_hints_for_ch8_probe(
compact["strategy_hints"]
)
ex = (
_truncate_strategy_narrative(excerpt_raw, excerpt_max)
if excerpt_raw
@ -98,11 +108,15 @@ def generate_strategy_draft_markdown_llm(
"structured_brief": compact,
"rules_draft_markdown": rd,
"report_strategy_excerpt": ex,
"chapter8_text_mining_probe": bool(
report_uses_chapter8_text_mining_probe(report_config)
),
}
if report_uses_chapter8_text_mining_probe(report_config):
payload["structured_brief_omission_note"] = (
"已启用第八章文本挖掘structured_brief 已省略关注词/场景子串计数字段,避免与报告正文口径冲突。"
"请依报告 §8 与摘要其他字段,将底稿「用户与评论侧」一节写成具体执行要点(需求焦点、场景、传播),勿逐条编造子串命中列表。"
"已启用第八章文本挖掘探针为主structured_brief 已省略「关注词/场景子串计数」及「与条形图同源的 strategy_hints 句子」,"
"避免与报告 §8 主口径冲突。**不得**再以词频或预设场景占比作为论据。"
"用户与评论侧须依报告 §8 文本挖掘归纳;**促销、满减、券价差**须与报告第六章、`price_promotion_signals` 及下方 `report_strategy_excerpt`(第九章)对齐,不得省略报告已写明的活动建议。"
)
raw = json.dumps(payload, ensure_ascii=False)
if len(raw) > 500_000:

View File

@ -117,6 +117,28 @@ def _risk_line(checked: bool, text: str) -> str:
return f"- {mark} {text}"
def filter_strategy_hints_for_ch8_probe(hints: Any) -> list[str]:
"""
当报告以 **第八章文本挖掘** 为主呈现评论侧时规则引擎的 ``strategy_hints`` 中仍可能含
关注词出现较多预设场景占比类句子 §8 主口径冲突此处剔除避免进入策略底稿与 LLM
"""
if not isinstance(hints, list):
return []
out: list[str] = []
for h in hints:
s = _esc(h) if h is not None else ""
if not s.strip():
continue
if "评价文本中「" in s and "等主题出现较多" in s:
continue
if "用途/场景中「" in s and "有效评价自述" in s:
continue
out.append(s)
return out if out else [
"(与「关注词/预设场景条形图」相关的自动提示已省略;用户洞察请以报告 §8 文本挖掘与第九章节选为准。)"
]
def report_uses_chapter8_text_mining_probe(report_config: dict[str, Any] | None) -> bool:
"""
与任务 ``report_config`` ``chapter8_text_mining_probe`` 一致未显式设置时默认 ``True``
@ -353,12 +375,21 @@ def build_strategy_draft_markdown(
lines.append("*摘要中无关注词/场景组,请结合评论侧分析补全本节。*")
lines.append("")
hints = brief.get("strategy_hints") or []
raw_hints = brief.get("strategy_hints") or []
hints = (
filter_strategy_hints_for_ch8_probe(raw_hints)
if use_ch8_probe
else (list(raw_hints) if isinstance(raw_hints, list) else [])
)
lines.extend(
[
"## 七、机会与策略支柱",
"",
"### 摘要提示(`strategy_hints`",
(
"### 摘要提示(`strategy_hints`,已按探针口径过滤)"
if use_ch8_probe
else "### 摘要提示(`strategy_hints`"
),
"",
]
)
@ -385,6 +416,22 @@ def build_strategy_draft_markdown(
"",
]
)
if use_ch8_probe:
pst_sig = brief.get("price_promotion_signals") or {}
has_promo = isinstance(pst_sig, dict) and bool(pst_sig)
lines.extend(
[
"### 促销与活动(须与报告对齐)",
"",
"*评论侧需求与场景以报告 **§8 文本挖掘** 为准,**不以**关注词/场景子串统计为论据。*",
(
"*促销、满减、券后价差等:须与报告 **第六章** 及摘要 `price_promotion_signals`、**第九章**节选一致;成稿须承接报告已写明的活动与价差线索。*"
if has_promo
else "*促销与价差:若报告 **第六章/第九章** 或摘要 `price_promotion_signals` 有归纳,成稿须承接;无则勿编造具体满减门槛。*"
),
"",
]
)
rk = bool(d.get("ack_risk_keywords"))
rp = bool(d.get("ack_risk_price"))

View File

@ -133,3 +133,26 @@ class StrategyDraftTests(SimpleTestCase):
self.assertIn("按去重 SKU", md)
self.assertIn("120", md)
self.assertIn("35.0%", md)
def test_chapter8_probe_filters_strategy_hints_focus_scenario_lines(self) -> None:
brief = {
"schema_version": 1,
"keyword": "K",
"strategy_hints": [
"评价文本中「口感、甜」等主题出现较多,可作为假设输入(非严格主题模型)。",
"用途/场景中「控糖/血糖相关」在约 72% 的有效评价自述中出现,可作为优先假设(词组规则)。",
"样本内品牌较分散,存在定位空间(需验证)。",
],
"price_promotion_signals": {"rows_with_both_list_and_coupon": 10},
}
md = build_strategy_draft_markdown(
job_id=1,
keyword="K",
brief=brief,
report_config={"chapter8_text_mining_probe": True},
)
self.assertNotIn("评价文本中「口感", md)
self.assertNotIn("用途/场景中「控糖", md)
self.assertIn("样本内品牌较分散", md)
self.assertIn("促销与活动(须与报告对齐)", md)
self.assertIn("price_promotion_signals", md)