mirror of
https://github.com/primedigitaltech/market-assistant.git
synced 2026-07-21 23:41:39 +08:00
feat(pipeline): 策略骨架缩短并强化策略生成决策边界
- 规则稿:章节重排为短骨架,去重复假设套话;探针模式三条占位,传统模式改为统计摘录句式。 - LLM:STRATEGY_SYSTEM 明确表单已定项不可反写,空项由数据补全,成稿禁止二次决策话术。 - API/前端:新增目标客群、主要对标、资源备注字段并传入 strategy_decisions。 - OpenAPI 与单测同步。 Made-with: Cursor
This commit is contained in:
parent
932c3d6aeb
commit
16d64565a3
@ -12,17 +12,24 @@ from ..reporting.strategy_draft import (
|
||||
)
|
||||
from .llm_client import call_llm, estimate_chat_input_tokens, llm_context_window_size
|
||||
|
||||
STRATEGY_SYSTEM = """你是市场策略顾问,根据**结构化监测摘要**与业务侧填写的**决策字段**,把「规则底稿」润色为可读的策略 Markdown。
|
||||
STRATEGY_SYSTEM = """你是市场策略顾问,根据**结构化监测摘要**与业务侧填写的**决策字段**,把「规则底稿」写成**短、可执行**的策略 Markdown 成稿。
|
||||
|
||||
**规则**:
|
||||
- 输入 JSON 含 `rules_draft_markdown`(规则引擎生成的底稿,与同任务数据一致)、`structured_brief`(摘要子集)、`strategy_decisions`、`business_notes` 等;可选 `report_strategy_excerpt`(与同任务宿主报告**第九章**「策略与机会」正文同源,来自已落盘产物)。
|
||||
- **与报告第九章对齐(硬性,当 `report_strategy_excerpt` 非空时)**:润色后的全文在**战略方向与主要判断**上须与该节选**一致**,不得写出与之**明显矛盾**的结论(如定价档位、差异化主线、主要风险判断);若 `business_notes` 或 `strategy_decisions` 与节选冲突,须在正文适当位置简短说明「与报告第九章策略归纳不一致之处见业务备注」类表述,且**不得**把节选与 `structured_brief` 中均未出现的数字当作既定事实。
|
||||
- **当 `report_strategy_excerpt` 为空或仅为占位说明**:表示报告侧未生成或未重跑第九章大模型正文,仅依据 `rules_draft_markdown` 与 `structured_brief` 润色,**不得编造**与可能存在的报告第九章结论。
|
||||
- **不得编造**输入中不存在的销量、占比、价格数字;若底稿与摘要中有数字,须保持一致;表述集中度时用「第一大……份额」「前三家合计」等中文,**不要用** CR1、CR3 等缩写;
|
||||
- 若 `structured_brief` 含 `matrix_overview_for_llm` 或矩阵相关字段,策略中应**呼应**细分类目分组与竞品矩阵结论,不得无故删光矩阵相关建议;
|
||||
- 可调整段落衔接、标题层级、列表与表格呈现,使更易读;可补充「建议」「待业务确认」类表述,但不虚构竞品名称或数据;
|
||||
- **仅输出** Markdown 正文(不要 ``` 围栏包裹全文);
|
||||
- **输出完整性**:须收束各小节与全文,勿在句子或列表中途戛然而止。"""
|
||||
**输入**:`rules_draft_markdown`(规则骨架,与同任务数据一致)、`structured_brief`(摘要子集)、`strategy_decisions`、`business_notes`;可选 `report_strategy_excerpt`(与同任务宿主报告**第九章**「策略与机会」正文同源)。
|
||||
|
||||
**决策边界(硬性)**:
|
||||
- **业务已在 `strategy_decisions` 中填写的项**(角色、时间、成功标准、战场一句话、定位勾选、竞争倾向、四柱、目标客群/对标/资源备注等)视为**已定决策**:成稿须**落实为具体执行句**,**不得**改写成相反结论或再要求用户「请选择」。
|
||||
- **表单中为空或占位(如 *待填*、*骨架占位*)的项**:由你结合 `structured_brief`、`report_strategy_excerpt`(若非空)与数据摘录**补全为可执行表述**;补全须与数据方向一致,**不得**编造输入中不存在的销量、GMV、未给出的价格或占比。
|
||||
- **成稿阶段禁止**:再写一轮「请业务决策」「待确认后再定」「假设:待验证」等**二次决策话术**;不确定性用一句带过即可(如「需下周用原评论抽样核对」),勿重复堆砌。
|
||||
|
||||
**与报告第九章对齐(当 `report_strategy_excerpt` 非空时)**:战略方向与主要判断须与该节选**一致**,不得明显矛盾;若 `business_notes` 或表单与节选冲突,正文中简短点明「与报告第九章归纳差异见业务备注」,且**不得**把节选与 `structured_brief` 均未出现的数字当作事实。
|
||||
|
||||
**当 `report_strategy_excerpt` 为空**:仅依据底稿与摘要润色,**不得编造**报告第九章结论。
|
||||
|
||||
**数据**:不得编造销量、占比、价格数字;底稿与摘要中的数字须保持一致;集中度用「第一大……份额」「前三家合计」等中文,**不要用** CR1、CR3。
|
||||
|
||||
**矩阵**:若 `structured_brief` 含矩阵相关字段,须**呼应**细分类目与竞品矩阵结论,不得无故删光。
|
||||
|
||||
**输出**:仅 Markdown 正文(不要 ``` 围栏);须收束各小节与全文,勿中途截断。"""
|
||||
|
||||
STRATEGY_USER_PREFIX = """请基于以下 JSON 输出最终策略稿(Markdown)。\n\n"""
|
||||
|
||||
@ -93,8 +100,8 @@ def generate_strategy_draft_markdown_llm(
|
||||
}
|
||||
if report_uses_chapter8_text_mining_probe(report_config):
|
||||
payload["structured_brief_omission_note"] = (
|
||||
"已启用第八章文本挖掘:本 JSON 中的 structured_brief 已省略关注词/场景子串计数字段,"
|
||||
"避免与当前报告正文口径冲突;请依规则底稿第五节说明与报告 §8 自拟假设,勿逐条编造子串命中列表。"
|
||||
"已启用第八章文本挖掘:structured_brief 已省略关注词/场景子串计数字段,避免与报告正文口径冲突。"
|
||||
"请依报告 §8 与摘要其他字段,将底稿第五节写成具体执行要点(需求焦点、场景、传播),勿逐条编造子串命中列表。"
|
||||
)
|
||||
raw = json.dumps(payload, ensure_ascii=False)
|
||||
if len(raw) > 500_000:
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
"""
|
||||
市场策略 Markdown 草稿:侧重**策略制定框架**(目标、战场、定位、支柱、行动),
|
||||
基于同任务结构化摘要与可选业务备注规则生成;附录为关键数据速览。
|
||||
市场策略 Markdown 草稿:**规则骨架**(占位 + 少量数据摘录),供业务与大模型成稿对齐。
|
||||
|
||||
后续可接 LLM 润色;当前无模型调用,便于验收与追溯。
|
||||
- 决策在「策略生成」表单完成;未填项由大模型结合摘要与报告节选补全。
|
||||
- 骨架刻意短、可执行;避免与成稿重复的「假设 / 待验证」套话。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@ -121,7 +121,7 @@ def build_strategy_draft_markdown(
|
||||
strategy_decisions: dict[str, Any] | None = None,
|
||||
report_config: dict[str, Any] | None = None,
|
||||
) -> str:
|
||||
"""生成可下载的 Markdown:策略框架为主,附录为数据速览。"""
|
||||
"""生成可下载的 Markdown:策略骨架为主,附录为数据速览。"""
|
||||
use_ch8_probe = report_uses_chapter8_text_mining_probe(report_config)
|
||||
d = strategy_decisions or {}
|
||||
pos = _esc(d.get("positioning_choice") or "").strip()
|
||||
@ -129,7 +129,8 @@ def build_strategy_draft_markdown(
|
||||
lines: list[str] = [
|
||||
f"# 市场策略制定草稿 · 「{kw}」",
|
||||
"",
|
||||
"> 本稿用于**辅助制定市场策略**;由规则根据本批次结构化摘要与业务备注生成,**非大模型自由发挥**,定稿前请业务修订。",
|
||||
"> **骨架说明**:本页为**规则骨架**(占位与少量摘录)。勾选大模型生成时,在骨架与数据上写**短、可执行**成稿。"
|
||||
"**决策在策略生成表单完成**;未填项由模型结合本任务摘要与报告节选补全,**成稿不再写「请再选 / 请决策」式套话**。",
|
||||
"",
|
||||
]
|
||||
if generated_at_iso:
|
||||
@ -140,7 +141,7 @@ def build_strategy_draft_markdown(
|
||||
[
|
||||
"---",
|
||||
"",
|
||||
"## 一、战略背景与目标(请业务补全)",
|
||||
"## 一、目标与边界",
|
||||
"",
|
||||
_goal_bullet("本品角色", str(d.get("product_role") or ""), "新品 / 追赶 / 防守 / 拓品类 …"),
|
||||
_goal_bullet("时间范围", str(d.get("time_horizon") or ""), "如:本季度 / 未来 12 周"),
|
||||
@ -149,7 +150,22 @@ def build_strategy_draft_markdown(
|
||||
str(d.get("success_criteria") or ""),
|
||||
"如:搜索位次、转化率、声量、复购 …",
|
||||
),
|
||||
_goal_bullet("非目标(明确不做什么)", str(d.get("non_goals") or ""), "可选"),
|
||||
_goal_bullet("非目标", str(d.get("non_goals") or ""), "明确不做什么(可选)"),
|
||||
_goal_bullet(
|
||||
"目标客群",
|
||||
str(d.get("audience_segment") or ""),
|
||||
"一句话:为谁、什么场景(可选)",
|
||||
),
|
||||
_goal_bullet(
|
||||
"主要对标",
|
||||
str(d.get("competitor_reference") or ""),
|
||||
"品牌或价位带参照(可选)",
|
||||
),
|
||||
_goal_bullet(
|
||||
"资源与预算备注",
|
||||
str(d.get("resource_notes") or ""),
|
||||
"人力、投放、产能等量级(可选)",
|
||||
),
|
||||
"",
|
||||
]
|
||||
)
|
||||
@ -159,7 +175,7 @@ def build_strategy_draft_markdown(
|
||||
comm_n = scope.get("comment_flat_rows")
|
||||
lines.extend(
|
||||
[
|
||||
"## 二、战场界定(监测语境)",
|
||||
"## 二、战场与样本",
|
||||
"",
|
||||
f"- **监测关键词 / 货架语境**:{kw}",
|
||||
f"- **批次**:{_esc(brief.get('batch_label')) or '—'}",
|
||||
@ -167,22 +183,21 @@ def build_strategy_draft_markdown(
|
||||
)
|
||||
if merged_n is not None or comm_n is not None:
|
||||
lines.append(
|
||||
f"- **深入样本规模**:深入 SKU ≈ {_num(merged_n)};评价扁平条数 ≈ {_num(comm_n)}。"
|
||||
"*策略含义:样本越大,以下「假设」越需抽样复核原评论。*"
|
||||
f"- **深入样本**:深入 SKU ≈ {_num(merged_n)};评价扁平条数 ≈ {_num(comm_n)}。"
|
||||
)
|
||||
bf = _esc(d.get("battlefield_one_line") or "").strip()
|
||||
if bf:
|
||||
lines.append(f"- **一句话战场**:{bf}")
|
||||
else:
|
||||
lines.append(
|
||||
"- **一句话战场**:*(请用业务语言写:我们在哪个需求场景、与谁抢同一批用户?)*"
|
||||
"- **一句话战场**:*(在哪个需求场景、与谁抢同一批用户?)*"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
conc = brief.get("concentration") or {}
|
||||
shops = conc.get("shops_from_list") or {}
|
||||
dbrand = conc.get("detail_brand_among_merged") or {}
|
||||
lines.extend(["## 三、竞争格局 → 策略含义", ""])
|
||||
lines.extend(["## 三、竞争结构(摘录)", ""])
|
||||
n_shop = _cr_narrative(
|
||||
"列表侧店铺集中度",
|
||||
concentration_first_share(shops),
|
||||
@ -200,23 +215,20 @@ def build_strategy_draft_markdown(
|
||||
if n_brand:
|
||||
lines.append(n_brand)
|
||||
if not n_shop and not n_brand:
|
||||
lines.append("*本摘要未含集中度指标,请结合本批次竞争结构数据补全后再写判断。*")
|
||||
lines.append("*本摘要未含集中度指标,请结合本批次竞争结构数据补全。*")
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"**可下判断的提问(自测)**",
|
||||
"",
|
||||
"- 若头部已占稳心智,本品是**侧翼**还是**正面替代**?",
|
||||
"- 店铺/品牌分散时,是否适合用**细分场景**或**内容教育**切入?",
|
||||
"- **自测**:头部已占稳心智时,侧翼还是正面替代?分散时是否用细分场景或内容教育切入?",
|
||||
"",
|
||||
]
|
||||
)
|
||||
stance = _esc(d.get("competitive_stance") or "").strip()
|
||||
stance_line = {
|
||||
"flank": "- **本品倾向**:倾向**侧翼切入**,避免与头部正面硬碰。",
|
||||
"head_on": "- **本品倾向**:倾向**正面替代**,对标头部主战场。",
|
||||
"both": "- **本品倾向**:计划**分层推进**(部分场景侧翼、部分场景正面)。",
|
||||
"undecided": "- **本品倾向**:**尚未拍板**,需在会议中对齐后再定主战场叙事。",
|
||||
"flank": "- **本品倾向**:侧翼切入,避免与头部正面硬碰。",
|
||||
"head_on": "- **本品倾向**:正面替代,对标头部主战场。",
|
||||
"both": "- **本品倾向**:分层推进(部分场景侧翼、部分场景正面)。",
|
||||
"undecided": "- **本品倾向**:*(表单未选;成稿时据数据写清倾向)*",
|
||||
}.get(stance)
|
||||
if stance_line:
|
||||
lines.append(stance_line)
|
||||
@ -224,16 +236,15 @@ def build_strategy_draft_markdown(
|
||||
|
||||
mix = brief.get("category_mix_top") or []
|
||||
if mix:
|
||||
lines.append("### 类目结构提示(Top)")
|
||||
lines.append("### 类目结构(Top)")
|
||||
lines.append("")
|
||||
lines.append("*以下仅作「货架长什么样」的速记。*")
|
||||
for item in mix[:6]:
|
||||
if isinstance(item, dict):
|
||||
lines.append(f"- {_esc(item.get('label'))}:{_num(item.get('count'))}")
|
||||
lines.append("")
|
||||
|
||||
pst = brief.get("price_stats") or {}
|
||||
lines.extend(["## 四、价格带与定位选项(启发式)", ""])
|
||||
lines.extend(["## 四、价格带与定位(业务已选)", ""])
|
||||
if pst.get("n"):
|
||||
src = _esc(brief.get("price_stats_source")) or "—"
|
||||
lines.extend(
|
||||
@ -241,46 +252,46 @@ def build_strategy_draft_markdown(
|
||||
f"- **价格来源**:{src},有效价样本 n = {_num(pst.get('n'))}。",
|
||||
f"- **展示价区间**:{_num(pst.get('min'))} ~ {_num(pst.get('max'))};**中位数** {_num(pst.get('median'))}。",
|
||||
"",
|
||||
"**定位选项(请勾一条或改写,并写明理由)**",
|
||||
"**主定位(与表单一致)**",
|
||||
"",
|
||||
f"- {_pos_mark(pos, 'top')} **贴顶**:对标中高位或头部价位带,强调品质/成分/背书。",
|
||||
f"- {_pos_mark(pos, 'top')} **贴顶**:中高位或头部价位带,强调品质/成分/背书。",
|
||||
f"- {_pos_mark(pos, 'mid')} **卡腰**:围绕中位数一带,强调性价比与场景匹配。",
|
||||
f"- {_pos_mark(pos, 'entry')} **下探**:贴近区间下限,强调入门与拉新(注意毛利与品牌调性)。",
|
||||
f"- {_pos_mark(pos, 'different')} **另起带**:刻意避开主价格带,用规格/组合/服务差异化。",
|
||||
f"- {_pos_mark(pos, 'entry')} **下探**:贴近区间下限,强调入门与拉新(注意毛利与调性)。",
|
||||
f"- {_pos_mark(pos, 'different')} **另起带**:避开主价格带,用规格/组合/服务差异化。",
|
||||
"",
|
||||
]
|
||||
)
|
||||
else:
|
||||
lines.append("*摘要中无价带统计,请结合本批次价格相关数据补全后再填上表。*")
|
||||
lines.append("*摘要中无价带统计,请结合本批次价格数据补全。*")
|
||||
lines.append("")
|
||||
lines.extend(
|
||||
[
|
||||
"**定位选项(请勾一条或改写,并写明理由)**",
|
||||
"**主定位(与表单一致)**",
|
||||
"",
|
||||
f"- {_pos_mark(pos, 'top')} **贴顶**:对标中高位或头部价位带,强调品质/成分/背书。",
|
||||
f"- {_pos_mark(pos, 'top')} **贴顶**:中高位或头部价位带,强调品质/成分/背书。",
|
||||
f"- {_pos_mark(pos, 'mid')} **卡腰**:围绕中位数一带,强调性价比与场景匹配。",
|
||||
f"- {_pos_mark(pos, 'entry')} **下探**:贴近区间下限,强调入门与拉新(注意毛利与品牌调性)。",
|
||||
f"- {_pos_mark(pos, 'different')} **另起带**:刻意避开主价格带,用规格/组合/服务差异化。",
|
||||
f"- {_pos_mark(pos, 'entry')} **下探**:贴近区间下限,强调入门与拉新(注意毛利与调性)。",
|
||||
f"- {_pos_mark(pos, 'different')} **另起带**:避开主价格带,用规格/组合/服务差异化。",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
ckw = brief.get("comment_focus_keywords") or []
|
||||
usc = brief.get("usage_scenarios") or []
|
||||
lines.extend(["## 五、用户需求与场景 — 可写成策略的假设", ""])
|
||||
lines.extend(["## 五、用户与评论侧", ""])
|
||||
if use_ch8_probe:
|
||||
lines.extend(
|
||||
[
|
||||
"*当前任务报告以**第八章评论侧文本挖掘**为主线(替代原关注词/场景条形图为主呈现)时,**请勿**再逐条照搬「子串命中次数」式假设列表——结构化摘要里仍可能含 `comment_focus_keywords` / `usage_scenarios` 等字段,**与当前报告正文展示口径可能不一致**。*",
|
||||
"*当前任务报告以**第八章评论侧文本挖掘**为主呈现时,此处**不**逐条罗列子串命中次数(与报告 §8 口径一致)。*",
|
||||
"",
|
||||
"*请结合报告中 **§8** 评论与情感归纳、文本挖掘小节,以及评价原文抽样,自拟若干条「待验证假设」(需可复核、可证伪)。*",
|
||||
"- **需求焦点**:*(骨架占位;成稿写 1~2 句可执行结论)*",
|
||||
"- **场景侧重**:*(骨架占位)*",
|
||||
"- **传播切入点**:*(骨架占位)*",
|
||||
"",
|
||||
]
|
||||
)
|
||||
else:
|
||||
lines.append(
|
||||
"*下列由关注词/场景**计数**转化而来,是「待验证假设」而非结论;请结合评价原文抽样修订。*"
|
||||
)
|
||||
lines.append("*下列为关注词/场景**统计摘录**(口径同本批次摘要);成稿时写成具体动作,勿重复统计句。*")
|
||||
lines.append("")
|
||||
if ckw:
|
||||
for item in ckw[:8]:
|
||||
@ -288,8 +299,7 @@ def build_strategy_draft_markdown(
|
||||
w = _esc(item.get("word"))
|
||||
c = _num(item.get("count"))
|
||||
lines.append(
|
||||
f"- **假设**:用户决策中「{w}」被频繁提及(约 {c} 次统计命中)—— "
|
||||
f"*可追问:本品故事是否正面回应?传播关键词是否覆盖?*"
|
||||
f"- 「{w}」:子串统计命中约 **{c}** 次(口径同报告关注词)。"
|
||||
)
|
||||
if usc:
|
||||
for item in usc[:6]:
|
||||
@ -298,19 +308,18 @@ def build_strategy_draft_markdown(
|
||||
cn = _num(item.get("count"))
|
||||
sh = _pct(item.get("share_of_text_units"))
|
||||
lines.append(
|
||||
f"- **场景命题**:「{sc}」在预设场景中约 {cn} 条、约占 {sh} 文本单元—— "
|
||||
f"*可追问:主图/详情/客服话术是否对齐该场景?*"
|
||||
f"- 场景「{sc}」:约 **{cn}** 条,约占 **{sh}** 文本单元(预设场景分组)。"
|
||||
)
|
||||
if not ckw and not usc:
|
||||
lines.append("*摘要中无关注词/场景组结果,请补全评论侧分析后再写本节。*")
|
||||
lines.append("*摘要中无关注词/场景组,请结合评论侧分析补全本节。*")
|
||||
lines.append("")
|
||||
|
||||
hints = brief.get("strategy_hints") or []
|
||||
lines.extend(
|
||||
[
|
||||
"## 六、机会方向与策略支柱(草案)",
|
||||
"## 六、机会与策略支柱",
|
||||
"",
|
||||
"### 规则引擎提示(来自摘要 `strategy_hints`)",
|
||||
"### 摘要提示(`strategy_hints`)",
|
||||
"",
|
||||
]
|
||||
)
|
||||
@ -318,7 +327,7 @@ def build_strategy_draft_markdown(
|
||||
for h in hints:
|
||||
lines.append(f"- {_esc(h)}")
|
||||
else:
|
||||
lines.append("*(当前无自动线索,请结合本批次结论手写 3~5 条机会)*")
|
||||
lines.append("*(当前无自动线索)*")
|
||||
pp = str(d.get("pillar_product") or "")
|
||||
pr = str(d.get("pillar_price") or "")
|
||||
pch = str(d.get("pillar_channel") or "")
|
||||
@ -326,10 +335,10 @@ def build_strategy_draft_markdown(
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"### 策略支柱 — 请业务逐项填空",
|
||||
"### 策略支柱(表单已填优先)",
|
||||
"",
|
||||
"| 支柱 | 本品打算怎么做 | 与头部差异 | 证据 / 出处 |",
|
||||
"|------|----------------|------------|-------------|",
|
||||
"| 支柱 | 本品动作 | 与头部差异 | 证据 / 出处 |",
|
||||
"|------|----------|------------|-------------|",
|
||||
f"| 产品 | {_pillar_cell(pp)} | *待填* | *§* |",
|
||||
f"| 价格 | {_pillar_cell(pr)} | *待填* | *§* |",
|
||||
f"| 渠道/触点 | {_pillar_cell(pch)} | *待填* | *§* |",
|
||||
@ -342,17 +351,17 @@ def build_strategy_draft_markdown(
|
||||
rp = bool(d.get("ack_risk_price"))
|
||||
rc = bool(d.get("ack_risk_concentration"))
|
||||
rk_kw = (
|
||||
"评论侧量化/关键词归纳是否**以偏概全**?(需原评论抽样)"
|
||||
"评论侧归纳是否以偏概全?(需原评论抽样)"
|
||||
if use_ch8_probe
|
||||
else "关注词/场景是否**以偏概全**?(需原评论抽样)"
|
||||
else "关注词/场景统计是否以偏概全?(需原评论抽样)"
|
||||
)
|
||||
lines.extend(
|
||||
[
|
||||
"## 七、风险与待证伪",
|
||||
"## 七、风险核对项",
|
||||
"",
|
||||
_risk_line(rk, rk_kw),
|
||||
_risk_line(rp, "价格带是否含大促/异常挂价?(需核对清洗规则)"),
|
||||
_risk_line(rc, "列表集中度与深入样本品牌是否**矛盾**?(需解释渠道差异)"),
|
||||
_risk_line(rc, "列表集中度与深入样本品牌是否不一致?(需解释渠道差异)"),
|
||||
"",
|
||||
]
|
||||
)
|
||||
@ -360,21 +369,19 @@ def build_strategy_draft_markdown(
|
||||
notes = _esc(business_notes)
|
||||
lines.extend(
|
||||
[
|
||||
"## 八、业务约束与内部判断",
|
||||
"## 八、业务约束与备注",
|
||||
"",
|
||||
(notes if notes else "*(未填写。建议补充:渠道红线、价位策略、竞品对标名单、预算量级等。)*"),
|
||||
(notes if notes else "*(未填写业务备注。)*"),
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"## 九、建议下一步(策略向)",
|
||||
"## 九、下一步(可执行)",
|
||||
"",
|
||||
"- [ ] 开会对齐:**§一** 目标与 **§八** 约束,确认 1~2 条主策略命题。",
|
||||
"- [ ] 为 **§六** 策略支柱表格每一行各找 **1 条数据证据**(注明出处)。",
|
||||
"- [ ] 产出 **12 周节奏表**(里程碑 + 负责人),与本品排期挂钩。",
|
||||
"- [ ] 定义 **3 个可观测指标**(周或双周复盘)。",
|
||||
"- [ ] 对齐 **§一** 目标与 **§八** 约束,锁定 1~2 条主命题。",
|
||||
"- [ ] 为 **§六** 支柱各补 **1 条数据证据** + **12 周内可交付动作**(负责人 + 时间)。",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
|
||||
@ -438,6 +438,15 @@ class StrategyDraftRequestSerializer(serializers.Serializer):
|
||||
pillar_comm = serializers.CharField(
|
||||
required=False, allow_blank=True, default="", max_length=800, trim_whitespace=False
|
||||
)
|
||||
audience_segment = serializers.CharField(
|
||||
required=False, allow_blank=True, default="", max_length=500, trim_whitespace=False
|
||||
)
|
||||
competitor_reference = serializers.CharField(
|
||||
required=False, allow_blank=True, default="", max_length=800, trim_whitespace=False
|
||||
)
|
||||
resource_notes = serializers.CharField(
|
||||
required=False, allow_blank=True, default="", max_length=1000, trim_whitespace=False
|
||||
)
|
||||
ack_risk_keywords = serializers.BooleanField(required=False, default=False)
|
||||
ack_risk_price = serializers.BooleanField(required=False, default=False)
|
||||
ack_risk_concentration = serializers.BooleanField(required=False, default=False)
|
||||
|
||||
@ -26,7 +26,7 @@ class StrategyDraftTests(SimpleTestCase):
|
||||
self.assertIn("任务 ID**:99", md)
|
||||
self.assertIn("假设A", md)
|
||||
self.assertIn("重点:华东", md)
|
||||
self.assertIn("战略背景与目标", md)
|
||||
self.assertIn("目标与边界", md)
|
||||
self.assertIn("市场策略制定草稿", md)
|
||||
|
||||
def test_strategy_decisions_merge(self) -> None:
|
||||
@ -52,9 +52,9 @@ class StrategyDraftTests(SimpleTestCase):
|
||||
self.assertIn("- [ ] **贴顶**", md)
|
||||
self.assertIn("侧翼切入", md)
|
||||
self.assertIn("| 产品 | 做低糖配方 |", md)
|
||||
self.assertIn("- [x] 关注词/场景是否**以偏概全**", md)
|
||||
self.assertIn("- [x] 关注词/场景统计是否以偏概全", md)
|
||||
self.assertIn("- [ ] 价格带是否含大促", md)
|
||||
self.assertIn("- [x] 列表集中度与深入样本品牌是否**矛盾**", md)
|
||||
self.assertIn("- [x] 列表集中度与深入样本品牌是否不一致", md)
|
||||
|
||||
def test_chapter8_probe_omits_focus_scenario_count_bullets(self) -> None:
|
||||
brief = {
|
||||
@ -76,8 +76,8 @@ class StrategyDraftTests(SimpleTestCase):
|
||||
report_config={"chapter8_text_mining_probe": True},
|
||||
)
|
||||
self.assertIn("文本挖掘", md)
|
||||
self.assertNotIn("假设**:用户决策中「口感」", md)
|
||||
self.assertNotIn("场景命题**:「控糖", md)
|
||||
self.assertNotIn("子串统计命中约 **501**", md)
|
||||
self.assertNotIn("场景「控糖", md)
|
||||
|
||||
def test_legacy_report_shows_focus_scenario_bullets(self) -> None:
|
||||
brief = {
|
||||
@ -98,5 +98,5 @@ class StrategyDraftTests(SimpleTestCase):
|
||||
brief=brief,
|
||||
report_config={"chapter8_text_mining_probe": False},
|
||||
)
|
||||
self.assertIn("假设**:用户决策中「口感」", md)
|
||||
self.assertIn("场景命题**:「控糖/血糖相关」", md)
|
||||
self.assertIn("「口感」:子串统计命中约 **501** 次", md)
|
||||
self.assertIn("场景「控糖/血糖相关」:约 **305** 条", md)
|
||||
|
||||
@ -133,6 +133,9 @@ class JobStrategyDraftView(APIView):
|
||||
"pillar_price": vd.get("pillar_price") or "",
|
||||
"pillar_channel": vd.get("pillar_channel") or "",
|
||||
"pillar_comm": vd.get("pillar_comm") or "",
|
||||
"audience_segment": vd.get("audience_segment") or "",
|
||||
"competitor_reference": vd.get("competitor_reference") or "",
|
||||
"resource_notes": vd.get("resource_notes") or "",
|
||||
"ack_risk_keywords": bool(vd.get("ack_risk_keywords")),
|
||||
"ack_risk_price": bool(vd.get("ack_risk_price")),
|
||||
"ack_risk_concentration": bool(vd.get("ack_risk_concentration")),
|
||||
|
||||
@ -549,6 +549,18 @@ components:
|
||||
pillar_comm:
|
||||
type: string
|
||||
maxLength: 800
|
||||
audience_segment:
|
||||
type: string
|
||||
maxLength: 500
|
||||
description: 目标客群一句话(可选)
|
||||
competitor_reference:
|
||||
type: string
|
||||
maxLength: 800
|
||||
description: 主要对标品牌或价位带(可选)
|
||||
resource_notes:
|
||||
type: string
|
||||
maxLength: 1000
|
||||
description: 资源与预算量级备注(可选)
|
||||
ack_risk_keywords:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
@ -48,6 +48,9 @@ const decisions = reactive({
|
||||
pillar_price: '',
|
||||
pillar_channel: '',
|
||||
pillar_comm: '',
|
||||
audience_segment: '',
|
||||
competitor_reference: '',
|
||||
resource_notes: '',
|
||||
ack_risk_keywords: false,
|
||||
ack_risk_price: false,
|
||||
ack_risk_concentration: false,
|
||||
@ -89,6 +92,9 @@ function buildPayload() {
|
||||
pillar_price: decisions.pillar_price,
|
||||
pillar_channel: decisions.pillar_channel,
|
||||
pillar_comm: decisions.pillar_comm,
|
||||
audience_segment: decisions.audience_segment,
|
||||
competitor_reference: decisions.competitor_reference,
|
||||
resource_notes: decisions.resource_notes,
|
||||
ack_risk_keywords: decisions.ack_risk_keywords,
|
||||
ack_risk_price: decisions.ack_risk_price,
|
||||
ack_risk_concentration: decisions.ack_risk_concentration,
|
||||
@ -176,7 +182,7 @@ watch(
|
||||
<p class="hint-top">
|
||||
选择<strong>已成功</strong>任务,在下方填空与勾选。<strong>默认</strong>使用大模型在规则底稿与同任务数据摘要基础上成稿(需服务端已配置网关)。若需更快、不调用智能服务,可勾选「本次仅生成规则稿」。策略配置与「分析报告生成」页的<strong>报告配置</strong>相互独立。策略稿与宿主报告第九章的归纳<strong>默认对齐</strong>。提交后跳转到
|
||||
<RouterLink to="/jd/strategy-view">策略稿预览</RouterLink>
|
||||
。未填项在文稿中仍保留占位提示。
|
||||
。<strong>决策在本页完成</strong>:已填项会写入底稿并由大模型落实为执行句;未填项由大模型结合数据补全。成稿不再重复「请再选」式表述。
|
||||
</p>
|
||||
|
||||
<div class="toolbar">
|
||||
@ -226,6 +232,30 @@ watch(
|
||||
<span>非目标</span>
|
||||
<textarea v-model="decisions.non_goals" rows="2" placeholder="明确不做什么(可选)" />
|
||||
</label>
|
||||
<label class="fld fld-block">
|
||||
<span>目标客群</span>
|
||||
<input
|
||||
v-model="decisions.audience_segment"
|
||||
type="text"
|
||||
placeholder="一句话:为谁、什么场景(可选)"
|
||||
/>
|
||||
</label>
|
||||
<label class="fld fld-block">
|
||||
<span>主要对标</span>
|
||||
<input
|
||||
v-model="decisions.competitor_reference"
|
||||
type="text"
|
||||
placeholder="品牌或价位带参照(可选)"
|
||||
/>
|
||||
</label>
|
||||
<label class="fld fld-block">
|
||||
<span>资源与预算备注</span>
|
||||
<textarea
|
||||
v-model="decisions.resource_notes"
|
||||
rows="2"
|
||||
placeholder="人力、投放、产能等量级(可选)"
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="fieldset">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user