mirror of
https://github.com/primedigitaltech/market-assistant.git
synced 2026-07-21 23:41:39 +08:00
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:
parent
1a569ce727
commit
99bd2ef40d
@ -8,6 +8,7 @@ from typing import Any
|
|||||||
from ..reporting.brief_compact import compact_brief_for_llm
|
from ..reporting.brief_compact import compact_brief_for_llm
|
||||||
from ..reporting.strategy_draft import (
|
from ..reporting.strategy_draft import (
|
||||||
build_strategy_draft_markdown,
|
build_strategy_draft_markdown,
|
||||||
|
filter_strategy_hints_for_ch8_probe,
|
||||||
report_uses_chapter8_text_mining_probe,
|
report_uses_chapter8_text_mining_probe,
|
||||||
)
|
)
|
||||||
from .llm_client import call_llm, estimate_chat_input_tokens, llm_context_window_size
|
from .llm_client import call_llm, estimate_chat_input_tokens, llm_context_window_size
|
||||||
@ -30,6 +31,11 @@ STRATEGY_SYSTEM = """你是市场策略顾问,根据**结构化监测摘要**
|
|||||||
|
|
||||||
**矩阵**:若 `structured_brief` 含矩阵相关字段,须**呼应**细分类目与竞品矩阵结论,不得无故删光。
|
**矩阵**:若 `structured_brief` 含矩阵相关字段,须**呼应**细分类目与竞品矩阵结论,不得无故删光。
|
||||||
|
|
||||||
|
**第八章文本挖掘探针(当 JSON 中 `chapter8_text_mining_probe` 为真时,硬性)**:
|
||||||
|
- **禁止**将「关注词子串命中次数」「预设场景分组条数/占比」当作评论侧或需求侧的主要论据(`structured_brief` 已省略这些字段;与报告正文 §8 主口径一致)。
|
||||||
|
- 用户洞察、场景与传播结论须与宿主报告 **§8 文本挖掘** 及 `report_strategy_excerpt`(第九章)一致。
|
||||||
|
- **促销与活动**:若 `report_strategy_excerpt` 或 `structured_brief.price_promotion_signals` 中出现满减、券、到手价差、促销形态等,成稿须在**价格/营销/渠道**相关段落**承接**,与报告 **第六章、第九章** 建议一致,**不得无故省略**;**禁止**编造具体满减门槛或补贴比例(仅复述输入中已有线索)。
|
||||||
|
|
||||||
**输出**:仅 Markdown 正文(不要 ``` 围栏);须收束各小节与全文,勿中途截断。"""
|
**输出**:仅 Markdown 正文(不要 ``` 围栏);须收束各小节与全文,勿中途截断。"""
|
||||||
|
|
||||||
STRATEGY_USER_PREFIX = """请基于以下 JSON 输出最终策略稿(Markdown)。\n\n"""
|
STRATEGY_USER_PREFIX = """请基于以下 JSON 输出最终策略稿(Markdown)。\n\n"""
|
||||||
@ -80,6 +86,10 @@ def generate_strategy_draft_markdown_llm(
|
|||||||
"usage_scenarios_by_matrix_group",
|
"usage_scenarios_by_matrix_group",
|
||||||
):
|
):
|
||||||
compact.pop(k, None)
|
compact.pop(k, None)
|
||||||
|
if isinstance(compact.get("strategy_hints"), list):
|
||||||
|
compact["strategy_hints"] = filter_strategy_hints_for_ch8_probe(
|
||||||
|
compact["strategy_hints"]
|
||||||
|
)
|
||||||
ex = (
|
ex = (
|
||||||
_truncate_strategy_narrative(excerpt_raw, excerpt_max)
|
_truncate_strategy_narrative(excerpt_raw, excerpt_max)
|
||||||
if excerpt_raw
|
if excerpt_raw
|
||||||
@ -98,11 +108,15 @@ def generate_strategy_draft_markdown_llm(
|
|||||||
"structured_brief": compact,
|
"structured_brief": compact,
|
||||||
"rules_draft_markdown": rd,
|
"rules_draft_markdown": rd,
|
||||||
"report_strategy_excerpt": ex,
|
"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):
|
if report_uses_chapter8_text_mining_probe(report_config):
|
||||||
payload["structured_brief_omission_note"] = (
|
payload["structured_brief_omission_note"] = (
|
||||||
"已启用第八章文本挖掘:structured_brief 已省略关注词/场景子串计数字段,避免与报告正文口径冲突。"
|
"已启用第八章文本挖掘(探针为主):structured_brief 已省略「关注词/场景子串计数」及「与条形图同源的 strategy_hints 句子」,"
|
||||||
"请依报告 §8 与摘要其他字段,将底稿「用户与评论侧」一节写成具体执行要点(需求焦点、场景、传播),勿逐条编造子串命中列表。"
|
"避免与报告 §8 主口径冲突。**不得**再以词频或预设场景占比作为论据。"
|
||||||
|
"用户与评论侧须依报告 §8 文本挖掘归纳;**促销、满减、券价差**须与报告第六章、`price_promotion_signals` 及下方 `report_strategy_excerpt`(第九章)对齐,不得省略报告已写明的活动建议。"
|
||||||
)
|
)
|
||||||
raw = json.dumps(payload, ensure_ascii=False)
|
raw = json.dumps(payload, ensure_ascii=False)
|
||||||
if len(raw) > 500_000:
|
if len(raw) > 500_000:
|
||||||
|
|||||||
@ -117,6 +117,28 @@ def _risk_line(checked: bool, text: str) -> str:
|
|||||||
return f"- {mark} {text}"
|
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:
|
def report_uses_chapter8_text_mining_probe(report_config: dict[str, Any] | None) -> bool:
|
||||||
"""
|
"""
|
||||||
与任务 ``report_config`` 中 ``chapter8_text_mining_probe`` 一致;未显式设置时默认 ``True``
|
与任务 ``report_config`` 中 ``chapter8_text_mining_probe`` 一致;未显式设置时默认 ``True``
|
||||||
@ -353,12 +375,21 @@ def build_strategy_draft_markdown(
|
|||||||
lines.append("*摘要中无关注词/场景组,请结合评论侧分析补全本节。*")
|
lines.append("*摘要中无关注词/场景组,请结合评论侧分析补全本节。*")
|
||||||
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(
|
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"))
|
rk = bool(d.get("ack_risk_keywords"))
|
||||||
rp = bool(d.get("ack_risk_price"))
|
rp = bool(d.get("ack_risk_price"))
|
||||||
|
|||||||
@ -133,3 +133,26 @@ class StrategyDraftTests(SimpleTestCase):
|
|||||||
self.assertIn("按去重 SKU", md)
|
self.assertIn("按去重 SKU", md)
|
||||||
self.assertIn("120", md)
|
self.assertIn("120", md)
|
||||||
self.assertIn("35.0%", 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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user