上线版: 产品表单统一+form嵌套修复+用户管理+部署+三套叙事

- 产品编辑入口统一走 ProductFormFull(卖点/风格/人群/品牌词全字段);
  修复开任务页 <form> 套 <form> 致"编辑产品"报错、改不了、跳回首个产品
- dashboard 入口卡片对齐实际路由: 系统管理(/config) 与 工作配置(/settings) 分开;
  settings ?tab=products 直达改用挂载后读 URL, 消除 hydration mismatch
- 新增用户管理(users API/admin service/改密页) + alembic 022/023/024
- 上线部署: Dockerfile / docker-compose.prod+https / nginx https / .env.example
- A8 三套正交叙事(痛点/场景/成分背书) + beige 调色去AI化 + 飞轮 text_import 高权重信号

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-30 18:08:13 +08:00
parent a77212781c
commit df1856d793
150 changed files with 8616 additions and 1765 deletions

View File

@@ -15,35 +15,38 @@ _PERSONA = """你是一个日常生活分享博主,不是品牌推广号。
比例70% 写生活场景和使用感受30% 提产品,绝不颠倒。
收尾铁律:每条结尾方式必须不同,禁止使用"东西放这了/买不买跟我没关系"这类被用滥的固定句式。"""
# ── Q1 随机变量池 ABC反同质化核心每次随机抽组合N条不撞──────────────
# 方法层:框架固定,内容可扩展,绝不写死
_POOL_A_IDENTITY: list[str] = [
"上班族早八妆前随手抹",
"宿舍懒人护肤三分钟搞定",
"敏感肌妈妈哄完娃才有五分钟",
"学生党第一次用高价护肤品",
"素颜出门前最后一步",
# ── Q1 叙事切入配方(反同质化核心·北哥四象限人群法,全品类通用不写死)────────
# 方法层:只给"切入维度类型",具体人群/场景由模型按本产品卖点+目标人群填充
# 北哥四象限:场景/习惯/适配/时机——主人群维度按条轮转保证N条开篇不撞
_AUDIENCE_DIMENSIONS: list[str] = [
"使用场景人群(按该产品最高频的使用场景圈定一类人)",
"使用习惯人群(懒人省心/进阶讲究等按习惯分的一类人)",
"适配特质人群(该产品特别适配的某类特质或需求人群)",
"使用时机人群(按使用时间/阶段/契机圈定的一类人)",
"性价比人群(看重划算、对价格敏感的一类人)",
]
_POOL_B_EMOTION: list[str] = [
"看到镜子里发现气色暗了一周",
"闺蜜问你最近皮肤怎么这么好",
"出门被催快点根本来不及叠瓶",
# 情绪触发类型(通用,模型按产品填具体内容)
_EMOTION_TRIGGERS: list[str] = [
"被身边人提醒或夸到才注意到的瞬间",
"自己用的时候突然意识到的变化",
"对比之前踩过的雷或走过的弯路",
]
_POOL_C_FLAW: list[str] = [
"第一次用量太少了没啥感觉",
"包装简单到以为是山寨",
"价格摆在那以为会很油很厚",
# 真实小波折类型(增可信,通用不写死品类)
_REAL_FLAWS: list[str] = [
"一开始用法没掌握或用量不对",
"第一眼对包装或价格有点误解",
"起效没想象中快、用了阵子才感觉到",
]
def _pick_combo() -> dict[str, str]:
"""随机抽一组 ABC 变量每次生成调用一次N条各不相同"""
def _pick_combo(index: int = 0) -> dict[str, str]:
"""抽一组通用叙事切入配方维度类型非写死内容主人群维度按index轮转N条不撞"""
return {
"identity": random.choice(_POOL_A_IDENTITY),
"emotion": random.choice(_POOL_B_EMOTION),
"flaw": random.choice(_POOL_C_FLAW),
"identity": _AUDIENCE_DIMENSIONS[index % len(_AUDIENCE_DIMENSIONS)],
"emotion": random.choice(_EMOTION_TRIGGERS),
"flaw": random.choice(_REAL_FLAWS),
}
@@ -61,30 +64,34 @@ _EMOJI_RULES = """
- 正文段落可点缀少量 emoji 烘托情绪(如 🥹 😭 🤍 💛每段最多1-2个不堆砌
- 结尾话题标签前后带表情,如 "#好物分享 🛒"
- emoji 服务情绪和分点,不要每句都加;整条正文 emoji 总量控制在 6-12 个
- 常用小红书 emoji 池:✅✨🌿💧🪞🧴📦🔍💛🤍🥹😭🛒(按语义选,不乱用)
- 常用小红书 emoji 池:✅✨🌿💧📦🔍💛🤍🥹😭🛒💯(按语义选,不乱用)
""".strip()
# ── Q2 5步框架 + Q3 四段结构 ─────────────────────────────────────────────
_STRUCTURE_RULES = """
【5步框架必须严格遵循
① Hook暴击低价/痛点:第一句戳中场景或价格锚点,吊足读者好奇
② 痛点共鸣2-3句描写使用前的真实困境用上面抽到的起因情绪A·B
② 痛点共鸣2-3句描写使用前的真实困境结合本条的情绪触发,并按北哥要害②召集多类人
③ 救星登场:自然带出产品,口吻是"碰巧发现/朋友安利/囤货时顺手",不是"推荐给你"
④ 卖点罗列每条加✅小标题3条以内卖点翻译成使用感受不是功效列表
⑤ 收尾(每条必须从下方策略池随机选一种,同批次不得重复同一种,禁止"东西放这了/买不买跟我没关系"此类固定句式):
【收尾策略池·每条选不同策略】
A·留白式感受只说自己现在的状态不提买不买"反正现在素颜出门我不慌"
B·反问读者把感受抛回给读者"你们护肤有没有那种一用就回不去的东西?"
C·场景延续把故事延伸到未来某个细节"下次同事再问我皮肤的事我就知道啥了"
D·克制回购暗示轻描淡写说自己行为"第一用完了,已经在备第二"
A·留白式感受只说自己现在的状态不提买不买"反正现在出门我都带着它,心里踏实多"
B·反问读者把感受抛回给读者"你们有没有那种一用就回不去的东西?"
C·场景延续把故事延伸到未来某个细节"下次再有人问起,我就知道该推荐啥了"
D·克制回购暗示轻描淡写说自己行为"第一用完了,已经在备第二"
E·纯记录收笔像日记最后一句不引导不评价"大概就这样,记录一下"
F·引导搜索仅在有品牌词时使用自然提一句"感兴趣可以搜搜『{品牌词}",不催单
【正文四段结构(必须)】
段1·痛点引入:描写使用前的困境/触发场景身份场景A·起因情绪B
段2·实测记录真实使用过程带上小缺点C真实感来源
段3·种草核心产品带来的变化用感受描述而非功效声称
段4·引导收尾从收尾策略池随机选一种佛系口吻不强推末尾带1-2个相关话题标签
【正文叙事骨架(开篇方式由本条叙事主线决定,禁止三套同一开头)】
段1·开篇分叉(按本条叙事主线三选一,这是三套拉开差异的关键):
- 痛点先行→开篇即戳使用前的困境/情绪(结合主切入人群维度,按要害②召集多类人
- 场景先行→开篇先白描一个真实高频使用场景,痛点在场景里自然带出,不喊口号
- 成分背书先行→开篇用成分原理/测评对比视角切入("为什么有用/亲测对比"),痛点后置
段2·实测记录真实使用过程带上本条的真实小波折真实感来源用第三方视角背书北哥要害③
段3·种草核心产品带来的变化用感受描述而非功效声称核心成分逐条对应痛点北哥要害④
段4·引导收尾从收尾策略池随机选一种佛系口吻不强推末尾带精准购买意向标签北哥要害⑤
※ 卖点罗列的措辞/顺序/emoji 三套之间禁止雷同;身边人种草/被夸被问这类桥段三套不得共用同一套。
【字数】正文350-400字不含标题tags3-5处段落空行增强可读性
""".strip()
@@ -92,23 +99,46 @@ _STRUCTURE_RULES = """
# ── Q8 标题公式5类结构每批次覆盖不同类型不重复──────────────────────
_TITLE_FORMULA = """
【标题公式5类每条用不同类型禁止同批重复
肤质型:「{肤质}+用了{产品}+{感受}」例→"油皮用了素颜霜整个夏天不脱妆"
价格型:「{价格锚点}+{产品}+{效感受}」例→"三位数买到大牌平替,用完第一罐回购第二罐"
功效型:「{使用场景}+{产品}+{可感知变化}」例→"早起素颜出门靠这罐,同事问我最近皮肤怎么"
夸张型:「疑问/感叹+{夸张感受}+{产品}」例→"这什么神奇产品,涂上去感觉素颜也能出门"
标题党型:「{反常识/意外信息}+真相是{翻转}」例→"人说皮肤变好了,没说的是我了它一个月"
特征型:「{适配特征}+用了{产品}+{感受}」例→"久坐党用了这把椅子,腰一整天不酸了"
价格型:「{价格锚点}+{产品}+{效感受}」例→"三位数买到大牌平替,用完第一份就回购"
场景型:「{使用场景}+{产品}+{可感知变化}」例→"通勤路上靠这杯,到工位整个人都醒"
夸张型:「疑问/感叹+{夸张感受}+{产品}」例→"这什么神仙东西,用一次就回不去"
标题党型:「{反常识/意外信息}+真相是{翻转}」例→"夸最近状态好,没说的是我了它一个月"
硬性约束标题≤20字禁止出现"绝绝子/YYDS/杀疯了";不直接写功效词(美白/祛斑等)。
硬性约束·标题必须有具体锚点:禁止"有点反差/真的绝/太惊艳/有点东西"这类空泛无信息量的标题,
必须落到一个具体的人群/场景/数字/反差细节上(读者扫一眼就知道讲什么、关我什么事)。
""".strip()
_ANTI_AI = f"""
【反AI味必须遵守】
- 禁止固定开篇套话:不许"姐妹们/宝子们/今天给大家分享"开头
- 禁止以下AI味词出现在正文{_NEGATIVE_WORDS}
- 禁止人群重叠:{'{count}'}条文案中身份场景不能重复靠变量池ABC保证)
- 禁止场景重复:同批次文案不能都是"早上上班"或都是"学生宿舍"
- 禁止人群重叠:{'{count}'}条文案主切入人群维度不能重复靠Q1切入配方按条轮转保证)
- 禁止场景重复:同批次文案开篇场景不能雷同不能N条都挤在同一个使用场景
- 避免生硬推销词:按头安利/绝绝子/闭眼冲 不能出现
""".strip()
# ── 系统 prompt合并Q2/Q3/Q4/Q7/Q8数据层走build_prompt动态注入──────────
# ── 北哥出单强化层·五大要害(实测出单命门,与富贵框架叠加,冲突以本层为准)──
_BEIGE_FIVE = """
【北哥出单强化层·五大要害(最高优先级,与上面框架叠加,冲突时以本层为准)】
要害①·标题广覆盖+悬念钩子:标题要罩住尽量多的人,不要一上来就锁死单一窄人群
(只喊一类窄人群会把其他潜在人群挡在外面);用悬念/反差/疑问做钩子留个扣子让人想点进来;
精准筛人交给标签层,不靠标题筛人。
要害②·开篇召集痛点:正文要把这个产品能解决的痛点尽量多列几个、召集多类人
"不管你是…还是…"的覆盖感),让更多人对号入座,不要只写一类人的一个痛点。
注意:召集痛点不必都堆在第一句——开篇方式由本条的叙事主线决定(见下方叙事主线),
痛点先行的可开篇即戳痛点,场景先行/成分背书先行的则在叙事展开中自然召集多类人,
绝不能因为这条要害就把三套都写成同一个"赶时间/痛点"开头。
要害③·第三方闺蜜真实背书:种草借第三方视角(闺蜜/同事/家人说、被人问、别人推荐),
"别人替你证言"的真实感,不是自己反复夸自己回购多少次;自夸味=广告味=掉信任。
要害④·成分逐条对应痛点每个核心成分都明确对上它解决的那个痛点成分1→痛点1成分2→痛点2
逐条映射讲清"为什么有用",翻译成用户能感知的结果,不堆术语、不把成分名堆在一起不解释。
要害⑤·精准购买意向标签埋词tags 不要凑流量泛词(#好物分享#真实测评 这类谁都能用的大词价值低),
要埋"有明确购买意向的人才会搜"的精准词——目标人群+品类+核心卖点/场景组合成精准长尾标签,
至少2-3个精准埋词。
这5条是北哥实测出单的命门务必逐条命中。
""".strip()
# ── 系统 prompt合并Q2/Q3/Q4/Q7/Q8 + 北哥五段数据层走build_prompt动态注入──
COPY_SYSTEM = f"""{_PERSONA}
合规红线(全品类通用):
@@ -118,6 +148,8 @@ COPY_SYSTEM = f"""{_PERSONA}
{_ANTI_AI.format(count="N")}
{_BEIGE_FIVE}
{_TITLE_FORMULA}
{_EMOJI_RULES}
@@ -178,30 +210,42 @@ def build_prompt(product: dict, count: int, extra_rules: str = "",
custom = (product.get("custom_prompt") or "").strip()
brand_kw = (product.get("brand_keyword") or "").strip()
# Q1每条抽一组随机变量,传给模型作角色约束
combos = [_pick_combo() for _ in range(count)]
# Q1每条按index轮转主人群维度+随机情绪/波折,传给模型作切入约束
combos = [_pick_combo(i) for i in range(count)]
combos_text = "\n".join(
f"{i+1}条:身份场景=「{c['identity']}」·起因情绪=「{c['emotion']}」·小缺点=「{c['flaw']}"
f"{i+1}条:主切入人群维度=「{c['identity']}」·情绪触发=「{c['emotion']}」·真实小波折=「{c['flaw']}"
for i, c in enumerate(combos)
)
angle_hint = f"文案角度要覆盖:{''.join(angles)}(每条用不同角度)。" if angles else ""
brand_rule = f"每条正文和标题中植入品牌词「{brand_kw}」一次(自然融入,不生硬)。" if brand_kw else ""
brand_rule = (
f"每条正文中自然植入品牌词「{brand_kw}」至少一次(融入语境,不生硬)。"
f"标题以悬念/钩子优先(北哥要害①),品牌词不必硬塞进标题;"
f"若要进标题须自然不抢戏,禁止把品牌词堆在标题开头当详情页商品名(如'{brand_kw}{name}'式)。"
) if brand_kw else ""
benchmark_block = _build_benchmark_block(product.get("benchmark_refs") or [])
# strategy_narrative 是三套拉开差异的核心,加强标记顶到最高权重
narrative_block = (
f"\n========== 本套叙事主线(最高优先级·决定开篇与全文走向,禁止偏离)==========\n"
f"{strategy_narrative}\n"
f"==============================================================\n"
if strategy_narrative else ""
)
lines = [
f"产品:{name}",
f"核心卖点(必须翻译成用户能感知的生活化利益,禁止直接列功效词;翻译范例:'烟酰胺''熬夜后第二天脸不那么黄了''高保湿''涂上去一整天都没搓泥拔干'{selling}",
f"核心卖点(必须翻译成用户能感知的生活化利益,禁止直接列功效/参数词;翻译范例:技术参数'XX配方/XX材质''用起来的某个具体好处''大容量''出门一整天都不用补'{selling}",
f"风格调性:{style}",
strategy_narrative,
narrative_block,
angle_hint,
brand_rule,
custom,
benchmark_block,
f"\n【Q1随机变量池·每条身份/起因/小缺点各不相同,严格按下方分配使用",
f"\n【Q1切入配方·每条主切入人群维度不同,模型按本产品把维度填成具体人群",
combos_text,
extra_rules,
f"\n请严格按5步框架+四段结构生成 {count}每条350-400字返回纯JSON数组。",
f"\n请严格按5步框架+叙事骨架生成 {count} 条,开篇方式必须服从上方叙事主线,每条350-400字返回纯JSON数组。",
]
return "\n".join(l for l in lines if l)
@@ -228,15 +272,15 @@ def build_local_drafts(product: dict, count: int) -> list[dict]:
_fallback_angles = ["生活场景型", "成分分析型", "使用感受型", "性价比型", "痛点切入型"]
angles_pool = product.get("text_angles") or _fallback_angles
for i in range(count):
c = _pick_combo()
c = _pick_combo(i)
# 循环取不同角度(角度相同的两条会被 dedupe_copies 过滤掉,所以必须不重复)
angle = angles_pool[i % len(angles_pool)]
yield {
"title": f"发现一个{name}{angle}用法",
"content": (
f"{c['identity']}{c['emotion']}\n\n"
f"最近开始用{name}{c['emotion']}\n\n"
f"用了一段时间,{points[i % len(points)]}这点最让我意外。\n\n"
f"说个小缺点{c['flaw']},后来才摸到感觉。\n\n"
f"说个小波折{c['flaw']},后来才摸到感觉。\n\n"
f"反正现在用顺手了。✅"
),
"tags": [f"#{name}", "#真实测评", "#好物分享"],