feat(pipeline): 策略表单增加营销与总体策略并顺延规则稿章节

- API:marketing_strategy、general_strategy;规则骨架新增「二、营销策略与总体方向」,原 §2–§9 顺延为 §3–§10。
- LLM 提示与探针说明用语同步;OpenAPI、单测与策略生成页字段集「五」已对齐。

Made-with: Cursor
This commit is contained in:
hub-gif 2026-04-17 17:08:25 +08:00
parent 16d64565a3
commit f09a46a79e
7 changed files with 76 additions and 13 deletions

View File

@ -17,7 +17,7 @@ STRATEGY_SYSTEM = """你是市场策略顾问,根据**结构化监测摘要**
**输入**`rules_draft_markdown`规则骨架与同任务数据一致`structured_brief`摘要子集`strategy_decisions``business_notes`可选 `report_strategy_excerpt`与同任务宿主报告**第九章**策略与机会正文同源
**决策边界硬性**
- **业务已在 `strategy_decisions` 中填写的项**角色时间成功标准战场一句话定位勾选竞争倾向四柱目标客群/对标/资源备注视为**已定决策**成稿须**落实为具体执行句****不得**改写成相反结论或再要求用户请选择
- **业务已在 `strategy_decisions` 中填写的项**角色时间成功标准战场一句话定位勾选竞争倾向四柱目标客群/对标/资源备注**营销策略****总体策略**视为**已定决策**成稿须**落实为具体执行句****不得**改写成相反结论或再要求用户请选择
- **表单中为空或占位 *待填**骨架占位*的项**由你结合 `structured_brief``report_strategy_excerpt`若非空与数据摘录**补全为可执行表述**补全须与数据方向一致**不得**编造输入中不存在的销量GMV未给出的价格或占比
- **成稿阶段禁止**再写一轮请业务决策待确认后再定假设待验证**二次决策话术**不确定性用一句带过即可需下周用原评论抽样核对勿重复堆砌
@ -101,7 +101,7 @@ def generate_strategy_draft_markdown_llm(
if report_uses_chapter8_text_mining_probe(report_config):
payload["structured_brief_omission_note"] = (
"已启用第八章文本挖掘structured_brief 已省略关注词/场景子串计数字段,避免与报告正文口径冲突。"
"请依报告 §8 与摘要其他字段,将底稿第五节写成具体执行要点(需求焦点、场景、传播),勿逐条编造子串命中列表。"
"请依报告 §8 与摘要其他字段,将底稿「用户与评论侧」一节写成具体执行要点(需求焦点、场景、传播),勿逐条编造子串命中列表。"
)
raw = json.dumps(payload, ensure_ascii=False)
if len(raw) > 500_000:

View File

@ -102,7 +102,7 @@ def report_uses_chapter8_text_mining_probe(report_config: dict[str, Any] | None)
"""
与任务 ``report_config`` ``chapter8_text_mining_probe`` 一致未显式设置时默认 ``True``
``jd.runner.get_default_report_config`` 一致
``True`` 策略稿第五节不再逐条列举关注词/场景子串命中以免与当前报告正文口径冲突
``True`` 策略稿用户与评论侧节不再逐条列举关注词/场景子串命中以免与当前报告正文口径冲突
"""
if not isinstance(report_config, dict):
return True
@ -170,12 +170,30 @@ def build_strategy_draft_markdown(
]
)
lines.extend(
[
"## 二、营销策略与总体方向",
"",
_goal_bullet(
"营销策略",
str(d.get("marketing_strategy") or ""),
"传播、活动、投放、内容主线等(可选)",
),
_goal_bullet(
"总体策略",
str(d.get("general_strategy") or ""),
"增长/品类/经营层面的总原则,可与 4P 支柱呼应(可选)",
),
"",
]
)
scope = brief.get("scope") or {}
merged_n = scope.get("merged_sku_count")
comm_n = scope.get("comment_flat_rows")
lines.extend(
[
"## 二、战场与样本",
"## 、战场与样本",
"",
f"- **监测关键词 / 货架语境**{kw}",
f"- **批次**{_esc(brief.get('batch_label')) or ''}",
@ -197,7 +215,7 @@ def build_strategy_draft_markdown(
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),
@ -244,7 +262,7 @@ def build_strategy_draft_markdown(
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(
@ -278,7 +296,7 @@ def build_strategy_draft_markdown(
ckw = brief.get("comment_focus_keywords") or []
usc = brief.get("usage_scenarios") or []
lines.extend(["## 、用户与评论侧", ""])
lines.extend(["## 、用户与评论侧", ""])
if use_ch8_probe:
lines.extend(
[
@ -317,7 +335,7 @@ def build_strategy_draft_markdown(
hints = brief.get("strategy_hints") or []
lines.extend(
[
"## 、机会与策略支柱",
"## 、机会与策略支柱",
"",
"### 摘要提示(`strategy_hints`",
"",
@ -357,7 +375,7 @@ def build_strategy_draft_markdown(
)
lines.extend(
[
"## 、风险核对项",
"## 、风险核对项",
"",
_risk_line(rk, rk_kw),
_risk_line(rp, "价格带是否含大促/异常挂价?(需核对清洗规则)"),
@ -369,7 +387,7 @@ def build_strategy_draft_markdown(
notes = _esc(business_notes)
lines.extend(
[
"## 、业务约束与备注",
"## 、业务约束与备注",
"",
(notes if notes else "*(未填写业务备注。)*"),
"",
@ -378,10 +396,10 @@ def build_strategy_draft_markdown(
lines.extend(
[
"## 、下一步(可执行)",
"## 、下一步(可执行)",
"",
"- [ ] 对齐 **§一** 目标与 **§** 约束,锁定 12 条主命题。",
"- [ ] 为 **§** 支柱各补 **1 条数据证据** + **12 周内可交付动作**(负责人 + 时间)。",
"- [ ] 对齐 **§一** 目标与 **§** 约束,锁定 12 条主命题。",
"- [ ] 为 **§** 支柱各补 **1 条数据证据** + **12 周内可交付动作**(负责人 + 时间)。",
"",
"---",
"",

View File

@ -447,6 +447,12 @@ class StrategyDraftRequestSerializer(serializers.Serializer):
resource_notes = serializers.CharField(
required=False, allow_blank=True, default="", max_length=1000, trim_whitespace=False
)
marketing_strategy = serializers.CharField(
required=False, allow_blank=True, default="", max_length=2000, trim_whitespace=False
)
general_strategy = serializers.CharField(
required=False, allow_blank=True, default="", max_length=2000, 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)

View File

@ -27,6 +27,7 @@ class StrategyDraftTests(SimpleTestCase):
self.assertIn("假设A", md)
self.assertIn("重点:华东", md)
self.assertIn("目标与边界", md)
self.assertIn("营销策略与总体方向", md)
self.assertIn("市场策略制定草稿", md)
def test_strategy_decisions_merge(self) -> None:
@ -35,6 +36,8 @@ class StrategyDraftTests(SimpleTestCase):
"product_role": "追赶型",
"positioning_choice": "mid",
"competitive_stance": "flank",
"marketing_strategy": "内容种草+搜索承接",
"general_strategy": "先腰后顶",
"pillar_product": "做低糖配方",
"ack_risk_keywords": True,
"ack_risk_price": False,
@ -48,6 +51,8 @@ class StrategyDraftTests(SimpleTestCase):
report_config={"chapter8_text_mining_probe": False},
)
self.assertIn("**本品角色**:追赶型", md)
self.assertIn("**营销策略**:内容种草+搜索承接", md)
self.assertIn("**总体策略**:先腰后顶", md)
self.assertIn("- [x] **卡腰**", md)
self.assertIn("- [ ] **贴顶**", md)
self.assertIn("侧翼切入", md)

View File

@ -136,6 +136,8 @@ class JobStrategyDraftView(APIView):
"audience_segment": vd.get("audience_segment") or "",
"competitor_reference": vd.get("competitor_reference") or "",
"resource_notes": vd.get("resource_notes") or "",
"marketing_strategy": vd.get("marketing_strategy") or "",
"general_strategy": vd.get("general_strategy") 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")),

View File

@ -561,6 +561,14 @@ components:
type: string
maxLength: 1000
description: 资源与预算量级备注(可选)
marketing_strategy:
type: string
maxLength: 2000
description: 营销策略(传播、活动、投放、内容等,可选)
general_strategy:
type: string
maxLength: 2000
description: 总体策略(增长/品类/经营总原则,可选)
ack_risk_keywords:
type: boolean
default: false

View File

@ -51,6 +51,8 @@ const decisions = reactive({
audience_segment: '',
competitor_reference: '',
resource_notes: '',
marketing_strategy: '',
general_strategy: '',
ack_risk_keywords: false,
ack_risk_price: false,
ack_risk_concentration: false,
@ -95,6 +97,8 @@ function buildPayload() {
audience_segment: decisions.audience_segment,
competitor_reference: decisions.competitor_reference,
resource_notes: decisions.resource_notes,
marketing_strategy: decisions.marketing_strategy,
general_strategy: decisions.general_strategy,
ack_risk_keywords: decisions.ack_risk_keywords,
ack_risk_price: decisions.ack_risk_price,
ack_risk_concentration: decisions.ack_risk_concentration,
@ -294,6 +298,26 @@ watch(
</label>
</fieldset>
<fieldset class="fieldset">
<legend>营销策略与总体策略</legend>
<label class="fld fld-block">
<span>营销策略</span>
<textarea
v-model="decisions.marketing_strategy"
rows="3"
placeholder="传播、活动、投放、内容主线等(可选)"
/>
</label>
<label class="fld fld-block">
<span>总体策略</span>
<textarea
v-model="decisions.general_strategy"
rows="3"
placeholder="增长 / 品类 / 经营总原则,可与下方 4P 支柱呼应(可选)"
/>
</label>
</fieldset>
<fieldset class="fieldset">
<legend>策略支柱 本品打算怎么做可先填一列</legend>
<label class="fld fld-block">