Files
mama-box/tools/whcm/build.py

116 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""孕妈悄悄话置顶笔记的 100 条模拟祝福评论(11-07 用户:「评论里按正常评论放上 100 个」)。
lines.txt 一行一条,「||」后面是楼中楼回复(其他孕妈/宝妈)。人设从 cm.json 的 1000 人里挑:主评论用孕期人设、回复混宝妈;
尽量挑在别的帖子下出现少的人。确定性(固定种子),重跑结果不变;只追加/覆盖 c[TARGET],不动其他帖子。"""
import json, random
from collections import Counter
TARGET = 'wh-20260928'
P = 'guangchang/cm.json'
d = json.load(open(P, encoding='utf-8'))
used = Counter(c[0] for k, L in d['c'].items() if k != TARGET for c in L)
rnd = random.Random(20261107)
import re
def wk(p):
m = re.match(r'孕 ?(\d+)', p[2]); return int(m.group(1)) if m else 0
# 内容多是胎动、产检、待产(孕中晚期的事)→ 主评论只用孕 24 周以上的人设,免得「孕 6 周」说「头朝下了」
preg = sorted([i for i, p in enumerate(d['p']) if wk(p) >= 24], key=lambda i: (used[i], i))
anyp = sorted(range(len(d['p'])), key=lambda i: (used[i], i))
# 11-07 用户看评论:名字套路太像(4 个「在挑名字」、4 个「今天也开心」、3 个「水獭」、虎虎的老母亲/虎虎妈妈…)→ 同一帖里前两个字、后两个字都不撞
SIG = set()
def fresh(i):
n = d['p'][i][0]; ks = {'h:' + n[:2], 't:' + n[-2:]}
return not (ks & SIG)
def take(pool, k):
out = []
for i in pool:
if len(out) >= k: break
if fresh(i):
n = d['p'][i][0]; SIG.update({'h:' + n[:2], 't:' + n[-2:]}); out.append(i)
for i in pool: # 不够就只要求后两个字不撞
if len(out) >= k: break
n = d['p'][i][0]
if i not in out and 't:' + n[-2:] not in SIG: SIG.add('t:' + n[-2:]); out.append(i)
for i in pool: # 还不够就随它
if len(out) >= k: break
if i not in out: out.append(i)
return out
preg = take(preg, 160)
mains = preg[:200]; rnd.shuffle(mains)
anyp = [i for i in anyp if i not in set(preg) and not re.match(r'准爸爸', d['p'][i][2])] # 回复里不放「准爸爸」(还没娃,说不了打针)
reps = take(anyp, 31); rnd.shuffle(reps)
lines = [l.strip() for l in open('tools/whcm/lines.txt', encoding='utf-8') if l.strip()]
assert len(lines) == 100, len(lines)
out, ri = [], 0
# 内容要和孕周对得上:按关键词给每条定孕周范围,从打乱后的人设里挑第一个落在范围内的
RULES = [(r'糖耐', 24, 28), (r'预产期在月底', 37, 39), (r'过年', 25, 28),
(r'待产包|入盆|无痛|胎位|头朝下|小床|产假|月子餐|育儿嫂|报纸|满月|儿科医生|插座|标签|绘本|包被', 32, 39),
(r'踢的位置越来越低|孕妇照|羊水|吸奶器|数胎动|数够十下|产褥垫|剪指甲|第一批小衣服|婴儿车|疫苗时间表', 28, 39), (r'四维', 24, 30)]
def rng(t):
for pat, lo, hi in RULES:
if re.search(pat, t): return lo, hi
return 24, 39
left = list(mains)
def pick(t):
lo, hi = rng(t)
for i in left:
if lo <= wk(d['p'][i]) <= hi: left.remove(i); return i
raise SystemExit('没有合适人设:' + t)
for k, l in enumerate(lines):
parts = l.split('||')
likes = max(3, int(520 * (0.965 ** k)) + rnd.randint(-18, 18))
rr = []
for t in parts[1:]:
rr.append([reps[ri], t]); ri += 1
out.append([pick(parts[0]), parts[0], likes, rr])
# 11-07 用户:「评论里也加一些孕早期的孕妈,看到别人有胎动也有期待,也有自己的话对孩子说;都要真人感」
# early.txt = 孕 6~13 周人设的留言(回复用孕中晚期/宝妈人设,像过来人);均匀插进前 100 条中间,不全堆在后面
# early_rep.txt = 「原留言开头|早孕妈妈的回复」,挂在已有留言楼里(「看你们说胎动好羡慕」这类)
early_p = take(sorted([i for i, p in enumerate(d['p']) if 6 <= wk(p) <= 13 and i not in set(reps) and not re.search(r'待产|宝妈|老母亲|养娃|学抱娃|拼尿布|麻麻|大宝', p[0])], key=lambda i: (used[i], i)), 51)
er = random.Random(20261108); er.shuffle(early_p)
ei = 0
for t in [l.strip() for l in open('tools/whcm/early_rep.txt', encoding='utf-8') if l.strip()]:
head, txt = t.split('|', 1)
hit = [c for c in out if c[1].startswith(head)]
assert len(hit) == 1, head
hit[0][3].append([early_p[ei], txt]); ei += 1
ERULES = [(r'两道杠|还没告诉老公|验孕棒|小黑圈|胎心胎芽', 6, 8), (r'孕酮|查血|见了一点血', 6, 10), (r'约了NT', 10, 12),
(r'满三个月', 8, 12), (r'才两个月', 8, 9), (r'葡萄|裤子有点紧|肚子还平', 10, 13)]
eleft = early_p[ei:]
def epick(t):
lo, hi = next(((a, b) for pat, a, b in ERULES if re.search(pat, t)), (6, 13))
for i in eleft:
if lo <= wk(d['p'][i]) <= hi: eleft.remove(i); return i
raise SystemExit('没有合适早孕人设:' + t)
early = [l.strip() for l in open('tools/whcm/early.txt', encoding='utf-8') if l.strip()]
eo = []
for k, l in enumerate(early):
parts = l.split('||')
rr = []
for t in parts[1:]:
rr.append([reps[ri], t]); ri += 1
eo.append([epick(parts[0]), parts[0], 0, rr])
# 定赞数:从第 3 条起每隔约 2~3 条放一条早孕的,赞数取前后两条之间
merged = []
step = len(out) / len(eo)
j = 0
for k, c in enumerate(out):
merged.append(c)
while j < len(eo) and k >= 2 + j * step * .96:
e = eo[j]; nxt = out[k + 1][2] if k + 1 < len(out) else 3
e[2] = max(3, (c[2] + nxt) // 2 + rnd.randint(-4, 4)); merged.append(e); j += 1
for e in eo[j:]: e[2] = 3
# 追加在最后(模拟留言的 key 是「帖子#下标」,插在中间会让原来那 100 条下标错位,真实回复/本机点赞挂错楼);
# 赞数已按插入位置取好,「最热」排序时自然穿插在中间
out = out + eo
# 11-07 用户定(方案 A):「被赞 3 次变星星」要和赞数对得上 → 赞数最高的 26 条标成已变星(第 5 位 = 1),
# 赞数压到几十~个位(不再几百);其余 0~5 个赞(门槛 6),看着就是「还差一点」,有人帮着点一下就能变星(前端本机算)
order = sorted(range(len(out)), key=lambda k: -out[k][2])
NS = 26
for r, k in enumerate(order):
if r < NS:
out[k][2] = max(6, int(168 * (0.88 ** r)) + rnd.randint(-5, 5)); out[k].append(1)
else: # 11-07 用户:门槛改 6 个赞 → 没变星的 0~5 个
out[k][2] = rnd.choice([0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5])
d['c'][TARGET] = out
json.dump(d, open(P, 'w', encoding='utf-8'), ensure_ascii=False, separators=(',', ':'))
print('ok', len(out), 'replies', ri, 'early', len(eo), 'early-replies', ei)