Compare commits

..

No commits in common. "68241dc78a37635d15ea2cf865b52ca9a66ec9f7" and "5bd587318a6b1e306fc7c7e2fd2e0911a067365d" have entirely different histories.

9 changed files with 6 additions and 817 deletions

View File

@ -639,12 +639,3 @@ To https://git.valavala.com/ai_member_only/ai_member_xiaoban
3685359..9d6999a master -> master
[2026-06-18 08:10:03] 工作区备份成功:自动备份 2026-06-18 08:10:01
[2026-06-19 08:10:01] 开始备份工作区...
[master 5bd5873] 自动备份 2026-06-19 08:10:01
2 files changed, 1 insertion(+), 3 deletions(-)
delete mode 100644 tmp_daily_summary.md
remote: . Processing 1 references
remote: Processed 1 references in total
To https://git.valavala.com/ai_member_only/ai_member_xiaoban
9d6999a..5bd5873 master -> master
[2026-06-19 08:10:02] 工作区备份成功:自动备份 2026-06-19 08:10:01
[2026-06-20 08:10:01] 开始备份工作区...

View File

@ -104,4 +104,3 @@
{"type":"memory.recall.recorded","timestamp":"2026-06-18T04:44:32.687Z","query":"陈逸鸫审批 S2U16 数据导出 王璐辰","resultCount":3,"results":[{"path":"memory/2026-06-17.md","startLine":1,"endLine":23,"score":1},{"path":"memory/2026-05-24.md","startLine":46,"endLine":71,"score":1},{"path":"memory/2026-05-24.md","startLine":66,"endLine":92,"score":1}]}
{"type":"memory.recall.recorded","timestamp":"2026-06-18T10:17:31.215Z","query":"知识巩固做题记录 数据表","resultCount":3,"results":[{"path":"memory/2026-05-24.md","startLine":46,"endLine":71,"score":1},{"path":"memory/2026-05-24.md","startLine":85,"endLine":110,"score":1},{"path":"memory/2026-05-24.md","startLine":66,"endLine":92,"score":1}]}
{"type":"memory.recall.recorded","timestamp":"2026-06-18T10:17:37.951Z","query":"知识巩固 表名 vala_game 做题记录 knowledge_consolidation","resultCount":4,"results":[{"path":"memory/2026-05-24.md","startLine":46,"endLine":71,"score":1},{"path":"memory/2026-05-24.md","startLine":85,"endLine":110,"score":1},{"path":"memory/2026-05-24.md","startLine":106,"endLine":126,"score":1},{"path":"memory/2026-06-17.md","startLine":20,"endLine":30,"score":1}]}
{"type":"memory.recall.recorded","timestamp":"2026-06-19T12:45:15.278Z","query":"damai v2 fill 细水 CP7BsOjYdhtcmft5iz2csIaHnKe","resultCount":4,"results":[{"path":"memory/2026-06-19.md","startLine":1,"endLine":12,"score":1},{"path":"memory/2026-06-16.md","startLine":28,"endLine":49,"score":1},{"path":"memory/2026-05-28.md","startLine":684,"endLine":709,"score":1},{"path":"memory/2026-06-17.md","startLine":20,"endLine":30,"score":1}]}

View File

@ -1,6 +1,6 @@
{
"version": 1,
"updatedAt": "2026-06-19T12:45:15.278Z",
"updatedAt": "2026-06-18T10:17:37.951Z",
"entries": {
"memory:memory/2026-05-24.md:1:30": {
"key": "memory:memory/2026-05-24.md:1:30",
@ -2375,21 +2375,19 @@
"endLine": 30,
"source": "memory",
"snippet": "- 发现核心 bug`pick_valid_order()` 只取最新一笔有效订单,未做 GSV 聚合 - **根因:** 18621578529account_id=13147有 2 笔有效订单P785... GSV=1,999 + 3734... GSV=1,599汇总只收了最新那笔 1,599丢了 1,999 - **影响范围:** 13 个账户各 2 笔有效订单,漏 GSV ¥23,185 - **修复v2.1** - 新增 `aggregate_valid_orders()` 函数:同一账户多笔有效订单 GSV/GMV/退款累加 - 订单号取未退款那笔(多笔未退款取最新),产品列多单用 `+` 拼接 - Step 4 线索绑单仍用 `pick_valid_order()` 不变 - 已更新 `sales_leads_full_refresh.py` + `skills/full-data-refresh/SKILL.md` - **数据源确认:** 大麦和小溪查的是同一个 PG `vala_bi.bi_vala_order`,数据一致 - **MySQL Makee 交易系统:** 大麦有 read_only 权限但 vala/vala_user/vala_order 库中无订单表,无法直接对比交易系统后台",
"recallCount": 3,
"recallCount": 2,
"dailyCount": 0,
"groundedCount": 0,
"totalScore": 3,
"totalScore": 2,
"maxScore": 1,
"firstRecalledAt": "2026-06-18T00:09:26.923Z",
"lastRecalledAt": "2026-06-19T12:45:15.278Z",
"lastRecalledAt": "2026-06-18T10:17:37.951Z",
"queryHashes": [
"ee468f64688e",
"d83437ba9cad",
"290e7f4aaa74"
"d83437ba9cad"
],
"recallDays": [
"2026-06-18",
"2026-06-19"
"2026-06-18"
],
"conceptTags": [
"pick-valid-order",
@ -2432,99 +2430,6 @@
"数据",
"查询"
]
},
"memory:memory/2026-06-19.md:1:12": {
"key": "memory:memory/2026-06-19.md:1:12",
"path": "memory/2026-06-19.md",
"startLine": 1,
"endLine": 12,
"source": "memory",
"snippet": "# 2026-06-19 ## v2_fill 首次跑(陈逸鸫 18:06 - 表: CP7BsOjYdhtcmft5iz2csIaHnKe细水新架构版 - 脚本: `scripts/damai_v2_fill.py` - ① 线索明细 (7fdb4b): F列95个手机号 → XXTEA加密 → PG匹配 → K列UID 63个新增12个 - ② 订单明细 (vrYbiX): DB查询63个UID → 17行订单 → 写入A-PQ/R未填 - db_info: 2026-06-19 18:21:44 - 使用实习虾 app (cli_aa898f32d4799bea) 的 tenant token 绕过 xiaoban bot 的 sheets:spreadsheet:read 权限缺失 - 注意: clear_range 用空字符串覆盖而非空数组飞书API拒绝空数组",
"recallCount": 1,
"dailyCount": 0,
"groundedCount": 0,
"totalScore": 1,
"maxScore": 1,
"firstRecalledAt": "2026-06-19T12:45:15.278Z",
"lastRecalledAt": "2026-06-19T12:45:15.278Z",
"queryHashes": [
"290e7f4aaa74"
],
"recallDays": [
"2026-06-19"
],
"conceptTags": [
"v2-fill",
"scripts/damai-v2-fill.py",
"写入a-p",
"q/r未填",
"db-info",
"cli-aa898f32d4799bea",
"clear-range",
"首次"
]
},
"memory:memory/2026-06-16.md:28:49": {
"key": "memory:memory/2026-06-16.md:28:49",
"path": "memory/2026-06-16.md",
"startLine": 28,
"endLine": 49,
"source": "memory",
"snippet": "- **大麦**: full_refresh · 手机/UID/行课回填 · 订单汇总 merge · 完成后群回「full_refresh 完成」 - **小溪**: 不再参与 Bot 刷新 ### 5. 验收标准 - gate X = 汇总 W当前 406=406 - 绑单审计 E1E9 全部 0 - 孤儿 X = 0 ### 6. 脚本修改清单 - `bot_sales_step2_refresh.py`: DB 层改为逐单存储 + `pick_valid_order()` + Y≠1 不写 X - `sales_leads_full_refresh.py`: 同上 + 汇总改为 gate 全量重建 - `refresh_order_summary.py`: A-W(23列) + 渠道分类改用 L 列 - 新增 `audit_lead_primary_order_bind.py`: 线索绑单审计脚本 ### 7. 环境修复 - `secrets.env` 需要软链接: `ln -sf /root/.openclaw/workspace/secrets.env /root/.openclaw/workspace-xiaoban/secrets.env` ### 8. Skill 文档已更新 - `skills/full-data-refresh/SKILL.md` → v2 定稿,含 6 条核心架构规则 - 协作契约: `xhs-ark-dashboard/docs/bot-full-refresh-v2.md` - 大麦侧主文档: `xhs-ark-dash",
"recallCount": 1,
"dailyCount": 0,
"groundedCount": 0,
"totalScore": 1,
"maxScore": 1,
"firstRecalledAt": "2026-06-19T12:45:15.278Z",
"lastRecalledAt": "2026-06-19T12:45:15.278Z",
"queryHashes": [
"290e7f4aaa74"
],
"recallDays": [
"2026-06-19"
],
"conceptTags": [
"full-refresh",
"手机/uid/行课回填",
"bot-sales-step2-refresh.py",
"pick-valid-order",
"sales-leads-full-refresh.py",
"refresh-order-summary.py",
"a-w",
"audit-lead-primary-order-bind.py"
]
},
"memory:memory/2026-05-28.md:684:709": {
"key": "memory:memory/2026-05-28.md:684:709",
"path": "memory/2026-05-28.md",
"startLine": 684,
"endLine": 709,
"source": "memory",
"snippet": "### ~21:55 lark-cli `+write` bug 修复 **根因:** `bin/lark-cli-impl` 的 `+write` action 从未读取 `--value-input-option` 参数PUT URL 里也没加 `?valueInputOption=USER_ENTERED`。参数被完全忽略。 **修复:** ```python vio = pargs.get(\"value_input_option\", \"RAW\") url_params = f\"?valueInputOption={vio}\" result = api(\"PUT\", f\"{SHEETS_V2}/spreadsheets/{token}/values{url_params}\", ...) ``` 同理 `+append` 也加了参数支持。 **验证:** 修复后写 int 到全新单元格 → 飞书正确显示为数字 ✅。但 Row 18 旧单元格 TEXT 格式仍覆盖显示。 ### ~22:00 飞书 Sheets API 公式求值测试 **结论:公式无法通过 API 写入并求值。** 即使 `valueInputOption=USER_ENTERED` 修复后,在全新单元格写 `=1+2` → FormattedValue 显示文本 \"=1+2\",不计算为 3。 **飞书 Sheets API v2 的 USER_ENTERED 的作用:** 数字/日期解析正常 → 公式不支持求值。 **陈逸鸫提到 Cursor 可以写公式** — 待确认他用的是什么工具/AP",
"recallCount": 1,
"dailyCount": 0,
"groundedCount": 0,
"totalScore": 1,
"maxScore": 1,
"firstRecalledAt": "2026-06-19T12:45:15.278Z",
"lastRecalledAt": "2026-06-19T12:45:15.278Z",
"queryHashes": [
"290e7f4aaa74"
],
"recallDays": [
"2026-06-19"
],
"conceptTags": [
"lark-cli",
"bin/lark-cli-impl",
"value-input-option",
"user-entered",
"pargs.get",
"url-params",
"sheets-v2",
"数字/日期解析正常"
]
}
}
}

View File

@ -1,11 +0,0 @@
# 2026-06-19
## v2_fill 首次跑(陈逸鸫 18:06
- 表: CP7BsOjYdhtcmft5iz2csIaHnKe细水新架构版
- 脚本: `scripts/damai_v2_fill.py`
- ① 线索明细 (7fdb4b): F列95个手机号 → XXTEA加密 → PG匹配 → K列UID 63个新增12个
- ② 订单明细 (vrYbiX): DB查询63个UID → 17行订单 → 写入A-PQ/R未填
- db_info: 2026-06-19 18:21:44
- 使用实习虾 app (cli_aa898f32d4799bea) 的 tenant token 绕过 xiaoban bot 的 sheets:spreadsheet:read 权限缺失
- 注意: clear_range 用空字符串覆盖而非空数组飞书API拒绝空数组

View File

@ -1,71 +0,0 @@
## 灵犀第一篇(改)
**「灵犀不是聚光的'数据看板'——我以前根本懒得打开它。直到用它改了一组计划ROI从0.8拉到1.6。」**
以前我的后台日常就是聚光。看消耗、看线索、看CPL。灵犀偶尔点进去扫一眼关掉。心想这不就是个花里胡哨的数据看板吗小红书真能整活。
我的逻辑很简单我投的是硬广我出价平台给我量。灵犀里那些A啊I啊TI啊跟我有什么关系
直到今年发现出价卷不动了。
同样的赛道竞品越来越多出价越来越高。以前出50块能拿到的线索现在出80块都不一定跑得动。ROI从勉强打平变成了越投越亏。
这时候我被逼着打开了灵犀认真看了一次数据。然后做了一件事把30%的预算从信息流搬到了视频流。
为什么因为灵犀告诉我信息流的转化率只有0.12%视频流是0.47%——差了将近4倍。但我当时70%的预算都在信息流上。
搬完第二个月线索量没变消耗降了18%。ROI从0.8拉到了1.6。
没加一分钱预算,只是看了灵犀一眼。
聚光告诉你的是"花了多少钱、拿回来多少线索"。这是结果。灵犀告诉你的是"花出去的钱,在哪个口子上效率最高"。这是过程。
举一个更具体的例子。你投了一个达人聚光告诉你这条笔记触达了50万人、带来了200条线索。看起来还行对吧
但灵犀会告诉你这50万人里只有3万进了I层8000进了TI层最后200人成交。剩下47万人只是"刷到过",然后就没了。
这条笔记到底是在种草还是在烧钱?聚光回答不了,灵犀能。
我以前不看灵犀是因为出价能解决问题。出价50不行就出80总能砸出线索。但现在出价卷到天花板了再往上加就是亏。这时候拼的不是谁出价高是谁能把每一块钱花在效率最高的口子上。
这个答案,聚光没有,灵犀有。
---
## 灵犀第二篇(改)
**「灵犀里有个被大多数人忽略的数字它决定了你下个月的线索量。我抓准了之后线索成本降了30%。」**
灵犀后台有一个数字叫"TI净增"——就是你这个月深度兴趣人群比上个月多了还是少了。
我以前从来不看这个数字。理由还是那个我投硬广我出价人群自然会进来。TI池子大不大跟我有什么关系我又不靠它吃饭。
直到出价卷不动了,我才被迫回头看这个数字。
一看,是负的。负了一万多。
当时没太在意,心想线索不是还在出吗,怕什么。
第二个月线索量掉了20%。
第三个月又掉了15%。
我这才慌了回去翻数据发现TI净增已经连续三个月在掉了。而我的线索70%以上来自TI人群。TI池子在缩水线索怎么可能不降
这就像一个水库。你每个月从里面抽水收割线索但上游进水种草进TI越来越少。水位一直在降你还觉得"反正还能抽到水"——直到有一天抽不上来了。
进水从哪来灵犀也告诉你了新增直接进TI、A层流转进TI、I层流转进TI三条管子。
我拆开一看91%的进水靠的是"新增直接进TI"——就是不断买新流量硬种。但新增流量越来越贵、越来越少。而效率最高的那根管子——"已经有点兴趣的人I再推一把进TI"成功率是A的1.8倍却只贡献了不到2%的进水。
最省力的管子,开得最小。
发现问题之后我做了一件事从收割预算里挪了20%出来专门投种草计划把I层的人往TI推。
第二个月TI净增转正了。线索成本降了30%。
没加总预算,只是把钱花在了"养池子"上。
以前我不在乎这个因为出价能补。出价50不行就出80总能抽到水。但现在出价卷到天花板了再往上加就是亏。这时候你才发现水库水位才是命门。
现在我每周固定看一次灵犀的TI净增。不为别的就为知道下个月的线索会不会崩。

View File

@ -1,463 +0,0 @@
#!/usr/bin/env python3
"""
大麦 v2_fill 细水新架构版 线索UID + 订单全量回填
: CP7BsOjYdhtcmft5iz2csIaHnKe
线索明细 (7fdb4b): F列手机号 XXTEA加密 PG tel_encrypt精确匹配 K列UID
订单明细 (vrYbiX): clear后全量写 A-P (勿填 Q/R)
· G 挂主进线 · B=线索C主归属 · N重复*
用法:
python3 scripts/damai_v2_fill.py
契约: docs/damai-v2-fill-skill.md ( xiaoxi 版同契约)
"""
import json, re, time, sys, os, requests, psycopg2
from datetime import datetime
from collections import defaultdict
SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
WORKSPACE = os.path.dirname(SCRIPTS_DIR)
sys.path.insert(0, SCRIPTS_DIR)
from phone_encrypt import encrypt_phone
# ── 配置 ──
SPREADSHEET_TOKEN = "CP7BsOjYdhtcmft5iz2csIaHnKe"
LEADS_SHEET_ID = "7fdb4b" # 线索明细
ORDERS_SHEET_ID = "vrYbiX" # 订单明细
# 实习虾 app 凭证 (有 sheets 读写权限)
FS_APP_ID = "cli_aa898f32d4799bea"
FS_APP_SECRET = "wGBjexDxHsHBsx9lk8O2gdbkMFzaJ3kd"
# 渠道映射
CHANNEL_MAP = {
"Apple App Store": "苹果", "科大讯飞学习机": "讯飞", "学而思学习机": "学而思",
"华为应用市场": "华为", "小米应用市场": "小米", "应用宝应用市场": "应用宝",
"希沃学习机": "希沃", "荣耀应用市场": "荣耀", "小度学习机": "小度",
"oppo应用市场": "OPPO", "vivo应用市场": "VIVO", "京东方学习机": "京东方",
"步步高学习机": "步步高", "作业帮学习机": "作业帮", "魅族应用市场": "魅族",
"官网": "官网",
}
LOG_FILE = "/var/log/damai_v2_fill.log"
def log(msg):
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line = f"[{ts}] {msg}"
print(line)
with open(LOG_FILE, "a") as f:
f.write(line + "\n")
def get_secret(key):
with open(os.path.join(WORKSPACE, "secrets.env")) as f:
for line in f:
if line.startswith(f"{key}="):
return line.strip().split("=", 1)[1].strip("'\"")
def get_fs_token():
resp = requests.post(
"https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
json={"app_id": FS_APP_ID, "app_secret": FS_APP_SECRET},
timeout=15
)
r = resp.json()
if r.get("code") != 0:
raise RuntimeError(f"获取飞书token失败: {r}")
return r["tenant_access_token"]
def read_sheet(token, sheet_id, range_str=None):
url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values/{sheet_id}"
if range_str:
url += f"!{range_str}"
resp = requests.get(url, headers={"Authorization": f"Bearer {token}"}, timeout=30)
data = resp.json()
if data.get("code") != 0:
raise RuntimeError(f"读取失败 {sheet_id}: {data}")
return data["data"]["valueRange"]["values"]
def put_values(token, sheet_id, range_str, values):
"""单次写入,自动分批"""
url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values"
body = {"valueRange": {"range": f"{sheet_id}!{range_str}", "values": values}}
resp = requests.put(url, headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}, json=body, timeout=30)
r = resp.json()
if r.get("code") != 0:
log(f"{range_str}: {r.get('code')} {r.get('msg')}")
return False
return True
def clear_range(token, sheet_id, range_str):
"""清空指定范围 — 写入空字符串覆盖"""
m = re.match(r'([A-Z]+)(\d+):([A-Z]+)(\d+)', range_str)
if not m:
log(f" ❌ 无法解析范围: {range_str}")
return False
sc, sr, ec, er = m.group(1), int(m.group(2)), m.group(3), int(m.group(4))
ncols = ord(ec) - ord(sc) + 1
BATCH = 500
for start in range(sr, er + 1, BATCH):
end = min(start + BATCH - 1, er)
batch_rows = end - start + 1
empty_row = [""] * ncols
values = [empty_row[:] for _ in range(batch_rows)]
rng = f"{sc}{start}:{ec}{end}"
url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values"
body = {"valueRange": {"range": f"{sheet_id}!{rng}", "values": values}}
resp = requests.put(url, headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}, json=body, timeout=60)
r = resp.json()
if r.get("code") != 0:
log(f" ❌ clear {rng}: {r.get('code')} {r.get('msg')}")
return False
time.sleep(0.2)
return True
def batch_in(cur, sql_tpl, params, chunk=500):
results = []
for i in range(0, len(params), chunk):
batch = params[i:i+chunk]
ph = ",".join(["%s"] * len(batch))
cur.execute(sql_tpl % ph, batch)
results.extend(cur.fetchall())
return results
def classify_channel(key_from):
"""渠道归属分类: 端内/销转/达人/直购"""
if not key_from:
return "直购"
kf = key_from.strip()
if kf in ("app-active-h5-0-0", "app-sales-bj-qhm-0", "app-sales-bj-wd-0"):
return "端内"
if kf.startswith("sales-adp-"):
return "销转"
if kf.startswith("newmedia-daren-") or kf == "newmedia-dianpu-wwxx-0-0":
return "达人"
return "直购"
def parse_date(date_str):
"""解析 '6月14日 19:09:12''6月14日''2026-06-14'"""
if not date_str:
return ""
date_str = str(date_str).strip()
m = re.match(r'(\d+)月(\d+)日', date_str)
if not m:
return ""
month, day = int(m.group(1)), int(m.group(2))
return f"2026-{month:02d}-{day:02d}"
# ═══════════════════════════════════════════════════════════════
# ① 线索明细: F→K UID
# ═══════════════════════════════════════════════════════════════
def fill_lead_uids(token):
"""读取线索明细F列手机号 → XXTEA加密 → PG匹配 → 写K列UID"""
log("=" * 60)
log("① 线索明细 F→K UID")
log("=" * 60)
# 读取线索明细 (A3:K200, 跳过表头2行)
rows = read_sheet(token, LEADS_SHEET_ID, "A3:K200")
log(f" 读取 {len(rows)} 行线索数据")
# 解析: (row_idx, phone, existing_uid, 主归属, 昵称, 进线时间)
leads = []
for i, row in enumerate(rows):
row_idx = i + 3 # 飞书行号
phone = ""
if len(row) > 5 and row[5]:
try:
phone = str(int(float(row[5])))
except:
phone = str(row[5]).strip()
existing_uid = ""
if len(row) > 10 and row[10]:
try:
existing_uid = str(int(float(row[10])))
except:
existing_uid = str(row[10]).strip()
master_cs = str(row[2]).strip() if len(row) > 2 and row[2] else "" # C=主归属
nickname = str(row[4]).strip() if len(row) > 4 and row[4] else "" # E=昵称
entry_time = str(row[1]).strip() if len(row) > 1 and row[1] else "" # B=进线时间
leads.append((row_idx, phone, existing_uid, master_cs, nickname, entry_time))
# 找出需要查UID的行 (有手机号但无UID)
need_uid = [(idx, phone) for idx, phone, uid, *_ in leads
if re.match(r'^\d{11}$', phone) and (not uid or not uid.isdigit() or int(uid) <= 0)]
log(f" 需要查UID: {len(need_uid)} 个手机号")
if not need_uid:
log(" 无需查询,跳过")
return leads
# XXTEA 加密
phone_enc_map = {}
for _, phone in need_uid:
try:
enc = encrypt_phone(phone)
phone_enc_map[enc] = phone
except Exception as e:
log(f" 加密失败 {phone}: {e}")
log(f" 加密完成, 唯一密文: {len(phone_enc_map)}")
# PG 精确查询
conn = psycopg2.connect(
host="bj-postgres-16pob4sg.sql.tencentcdb.com", port=28591,
user="ai_member", password=get_secret("PG_ONLINE_PASSWORD"),
dbname="vala_bi", connect_timeout=30
)
cur = conn.cursor()
enc_list = list(phone_enc_map.keys())
phone_to_uid = {}
for i in range(0, len(enc_list), 500):
chunk = enc_list[i:i+500]
ph = ",".join(["%s"] * len(chunk))
cur.execute(
f"SELECT id, tel_encrypt FROM bi_vala_app_account WHERE tel_encrypt IN ({ph}) AND status=1 AND deleted_at IS NULL",
chunk
)
for uid, tel_enc in cur.fetchall():
plain = phone_enc_map.get(tel_enc)
if plain:
phone_to_uid[plain] = str(uid)
time.sleep(0.05)
cur.close()
conn.close()
log(f" 精确匹配到 {len(phone_to_uid)} 个 UID")
# 构建 K 列写入数据 (只写需要更新的行)
updates = []
for idx, phone in need_uid:
uid = phone_to_uid.get(phone, "")
updates.append((idx, uid))
# 写入 (每行单独写K列)
write_count = 0
for idx, uid in updates:
if uid:
cell_range = f"K{idx}:K{idx}"
if put_values(token, LEADS_SHEET_ID, cell_range, [[uid]]):
write_count += 1
time.sleep(0.1)
log(f" 写入 {write_count} 个 UID 到 K 列")
# 更新 leads 列表中的 UID
uid_map = {idx: uid for idx, uid in updates}
updated_leads = []
for idx, phone, uid, cs, nick, et in leads:
if idx in uid_map and uid_map[idx]:
uid = uid_map[idx]
updated_leads.append((idx, phone, uid, cs, nick, et))
return updated_leads
# ═══════════════════════════════════════════════════════════════
# ② 订单明细: clear → 全量回填 A-P
# ═══════════════════════════════════════════════════════════════
def fill_order_details(token, leads):
"""查询所有线索UID的订单 → clear 订单明细 → 全量写入 A-P"""
log("=" * 60)
log("② 订单明细 DB 全量回填")
log("=" * 60)
# 收集所有 UID
uid_set = set()
lead_by_uid = {} # uid → lead info
for idx, phone, uid, cs, nick, et in leads:
if uid and uid.isdigit() and int(uid) > 0:
uid_int = int(uid)
uid_set.add(uid_int)
if uid_int not in lead_by_uid:
lead_by_uid[uid_int] = (idx, phone, cs, nick, et)
uid_list = list(uid_set)
log(f" 有效 UID: {len(uid_list)}")
if not uid_list:
log(" 无 UID跳过订单查询")
return
# PG 查询订单
conn = psycopg2.connect(
host="bj-postgres-16pob4sg.sql.tencentcdb.com", port=28591,
user="ai_member", password=get_secret("PG_ONLINE_PASSWORD"),
dbname="vala_bi", connect_timeout=30
)
cur = conn.cursor()
# 查询订单
log(" 查询订单...")
orders = batch_in(cur,
"SELECT account_id, trade_no, pay_success_date, key_from, goods_id, pay_amount_int, order_status "
"FROM bi_vala_order WHERE account_id IN (%s) AND pay_success_date IS NOT NULL AND order_status IN (3,4) "
"ORDER BY account_id, pay_success_date",
uid_list
)
log(f" 订单数: {len(orders)}")
# 查询退款
trade_nos = [o[1] for o in orders if o[1]]
refund_map = defaultdict(int)
if trade_nos:
refunds = batch_in(cur,
"SELECT trade_no, refund_amount_int FROM bi_refund_order WHERE trade_no IN (%s) AND status=3",
trade_nos
)
for tno, amt in refunds:
refund_map[tno] += amt
cur.close()
conn.close()
# 构建订单明细行 (A-P, 共16列)
order_rows = []
for o in orders:
account_id, trade_no, pay_date, key_from, goods_id, pay_amount, order_status = o
lead = lead_by_uid.get(account_id)
if not lead:
continue
lead_idx, phone, lead_cs, nickname, entry_time = lead
# A=下单月
order_month = ""
if pay_date:
order_month = f"{pay_date.month}"
# B=主归属 (来自线索C列)
master_cs = lead_cs
# C=订单号
order_no = trade_no or ""
# D=用户ID
user_id = str(account_id)
# E=手机号
phone_str = phone
# F=昵称
nick = nickname
# G=线索ID (挂主进线, 用线索行号)
lead_ref = str(lead_idx)
# H=进线时间
entry = entry_time
# I=下单时间
order_time = pay_date.strftime("%Y-%m-%d") if pay_date else ""
# J=GMV (分→元)
gmv = round(pay_amount / 100, 2) if pay_amount else 0
# K=退款
refund = round(refund_map.get(trade_no, 0) / 100, 2)
# L=GSV
gsv = round(gmv - refund, 2)
# M=keyfrom
kf = key_from or ""
# N=渠道归属
channel = classify_channel(key_from)
# O=时序有效 (先留空,由自动规则填充)
timing_valid = ""
# P=备注
remark = ""
order_rows.append([
order_month, master_cs, order_no, user_id, phone_str,
nick, lead_ref, entry, order_time, gmv, refund, gsv,
kf, channel, timing_valid, remark
])
log(f" 生成 {len(order_rows)} 行订单数据")
# Clear 订单明细 (A3:P5000)
log(" 清空订单明细...")
# 先清空大范围
clear_range(token, ORDERS_SHEET_ID, "A3:P5000")
time.sleep(0.5)
# 写入订单数据 (分批, 每批最多 400 行 × 16 列 = 6400 格, 留余量用 250 行/批)
BATCH_ROWS = 250
total_written = 0
for i in range(0, len(order_rows), BATCH_ROWS):
batch = order_rows[i:i+BATCH_ROWS]
start_row = i + 3 # 从第3行开始
end_row = start_row + len(batch) - 1
range_str = f"A{start_row}:P{end_row}"
if put_values(token, ORDERS_SHEET_ID, range_str, batch):
total_written += len(batch)
time.sleep(0.3)
log(f" 写入 {total_written} 行订单数据")
# db_info 时间戳
db_info = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log(f" db_info: {db_info}")
return db_info
# ═══════════════════════════════════════════════════════════════
# Main
# ═══════════════════════════════════════════════════════════════
def main():
log("大麦 v2_fill 开始")
t0 = time.time()
token = get_fs_token()
log(f"飞书 token 获取成功")
# ① 线索明细 F→K UID
leads = fill_lead_uids(token)
# ② 订单明细 全量回填
db_info = fill_order_details(token, leads)
elapsed = time.time() - t0
log(f"v2_fill 完成 · db_info: {db_info} · 耗时 {elapsed:.1f}s")
# 输出摘要
uid_count = sum(1 for _, _, uid, *_ in leads if uid and uid.isdigit() and int(uid) > 0)
phone_count = sum(1 for _, phone, *_ in leads if phone and re.match(r'^\d{11}$', phone))
print(f"\n{'='*60}")
print(f"v2_fill 完成")
print(f" 线索明细: {len(leads)} 行, {phone_count} 手机号, {uid_count} UID")
print(f" db_info: {db_info}")
print(f"{'='*60}")
if __name__ == "__main__":
main()

View File

@ -1,52 +0,0 @@
---
name: wechat-article-dachen
slug: wechat-article-dachen
version: 1.0.0
description: 按陈逸鸫(大陈的创业笔记)风格撰写公众号短文,口语化、有反转、有人味、不啰嗦
---
## When to Use
陈逸鸫要求写公众号短文、小红书投放/种草相关文章、灵犀使用指南等短图文内容时触发。
## Core Rules
### 1. 语气:像跟朋友聊天,不像在写文章
- 用口语词我靠、纯tm、屁用没有、脸疼、愣了
- 短句为主,偶尔长句叙事。不要排比句,不要"首先其次最后"
- 可以自嘲、可以吐槽平台、可以骂行业乱象
- 不要客套话,不要"大家好""今天我们来聊聊"
### 2. 结构:反转驱动,不是逻辑驱动
- 开头一句话钩子(打脸/反常识/数据冲击)
- 中间必须有"我以前以为X → 后来发现Y"的转折
- 转折要有"人味":不是"我发现了一个洞察",而是"我被逼着去看/数据甩我脸上/我愣住了"
- 结尾预告下一篇,或给一个可行动的结论
- 不要总结式结尾,不要"综上所述"
### 3. 数据用法:一个数字就够了
- 每篇只放 1-2 个关键数字,不堆数据表
- 数字要让人"我靠"90.4%、差40倍、ROI从0.8到1.6
- 数字后面必须跟一句大白话解释,让人秒懂
### 4. 篇幅:短文 300-600 字
- 公众号短图文消息的长度
- 一个核心观点,讲透就停
- 不要试图在一篇里塞两个观点
### 5. 结尾:预告或行动号召二选一
- 系列文章:预告下一篇,留钩子
- 单篇:给一个可执行的动作或邀请互动
- 不要"感谢阅读"
## Quick Reference
| 内容 | 文件 |
|------|------|
| 完整风格示例 | `examples.md` |
| 短文选题池 | `topics.md` |

View File

@ -1,57 +0,0 @@
# 风格示例
## 定稿示例:打脸预热文
---
「去年我说种草没用,今年就被打脸了。」
去年我写过一句话:"种草要投吗No屁用没有。"
当时的逻辑很简单:平台内容还未饱和,很多品牌还没开始投放,与其种草,不如多出硬广效率更高。
直客总是唠叨的灵犀更是极少登陆因为不仅功能复杂概念繁多跟聚光数据也很难聚合纯tm浪费时间。
但今年小红书难做了很多内容饱和已经是常态同样的赛道、同样的人群所有人都在抢。我出价高别人比我更高。最后就是纯拼谁烧得起——线索拿到了ROI打不平。
这时候我才被逼着去找新出路:收割卷不动了,那能不能在收割之前做点什么?
于是我打开了灵犀,做了一方人群回传,结果一出来我都愣了。
我知道TI是最接近线索的人群但是没考虑过线索中多少来自TI数据出来一看我靠90.4%的线索都来自TI人群而更泛的A和I人群加一起都不到10%......
TI很重要 vs 90%的线索来自TI分量完全不同好不好
你说那些小红书大会,一开就是半天,各种概念玄之又玄,你们直接把这个结论放出来我们早就启用了好不好。
那么TI怎么尽快扩大哪些内容的TI种草成本低什么样的投放能带来低成本TITI不够会怎么样品牌营销的费用到底带来了多少回报
一旦有了数据支持,这些以前无法回答的种草效果问题就迎刃而解。
当然,这整个链路很复杂,我把【灵犀+聚光+企微+销售聊天记录+订单系统】用ai全部串起来之后改了10版计算逻辑才有了一个贯穿全局的洞察。
我只能说,种草的确有效果,但是跟大家以为的效果不太一样,非常有意思哈哈。
接下来我会写一系列"种草效果分析"的内容,把灵犀中的各个数据指标解释清楚,并一步一步揭露"每一块钱的种草能带多少gmv"的最终结论。
如果你也遇到了投放成本越来越高线索越来越少roi越来越低的问题
我只能说,敬请期待!
这个玩法远不限于小红书,只要是内容平台,一律适用!
当然如果你等不及,也可以直接找我约个咨询。
下一篇预告《一个策略不花一分钱也能直接ROI翻倍 种草效果分析》
---
## 风格要素拆解
1. **开头钩子**:一句话打脸,直接有力
2. **反转线**:以前出价能解决问题 → 现在饱和了卷不动了 → 被迫回头看TI → 发现90.4%
3. **人味细节**直客唠叨灵犀、纯tm浪费时间、我靠、愣了、好不好
4. **数据冲击**90.4%,一句话就够了
5. **吐槽大会**:小红书大会开半天,概念玄之又玄
6. **预告钩子**:下一篇标题直接放出来
7. **行动号召**:等不及可以约咨询

View File

@ -1,52 +0,0 @@
# 短文选题池
## 一、打脸/反转系列
1. 「去年我说种草没用,今年就被打脸了。」 ✅ 已定稿
2. 「我以前觉得灵犀就是花里胡哨的数据看板——直到用它改了一组计划ROI从0.8拉到1.6。」
3. 「灵犀里有个被大多数人忽略的数字我抓准了之后线索成本降了30%。」
4. 「我公开说过'种草不用投',现在我要告诉你为什么我改口了。」
## 二、反常识数据卡
5. 「你90%的成交,来自一批你根本不知道自己在'种草'的人。」
6. 「100万人看到你→最后掏钱的只有700个。中间漏在哪
7. 「线索翻了一倍ROI反而降了。老板问我钱花哪了我沉默了。」
8. 「你花30万投的达人47万人只是'刷到过'然后就没了。」
## 三、触点/预算错配
9. 「你的钱70%烧在了转化最差的口子上——信息流0.12%视频流0.47%差了4倍。」
10. 「同样的预算换个口子投成交多22%。但大部分人都投错了。」
11. 「没加一分钱预算只是把30%的钱搬了个家ROI翻倍。」
## 四、内容类型错配
12. 「一篇笔记带了1000个留资转化率只有1%。按你的标准,它该被砍掉。」
13. 「种草稿和收割稿是两种生物,但你用同一把尺子量。」
14. 「收割型内容成交率23%泛种草10%。但泛种草一篇能带1000+留资。」
## 五、搜索词系列
15. 「竞品词投2万换十几个咨询决策词投500也换十几个。差40倍。」
16. 「你在抢竞品客户?不,你在帮平台创收。」
17. 「搜索投放最大的骗局:你以为在截流,其实在捐款。」
## 六、灵犀系列
18. 「灵犀和聚光,一个告诉你胖了,一个让你跑步。但它们是两家公司。」
19. 「灵犀里'I→TI流转率'这个数字,决定了你种草效率的天花板。」
20. 「达人投了30万灵犀一看——种草效率还不如企业号自营。」
## 七、ROI/转化系列
21. 「种草能让你线索多,但不能让你赚钱。赚钱靠的是另一件事。」
22. 「为什么线索越来越多ROI越来越低因为成单率在偷偷降。」
23. 「企业号自营内容带来的线索成交率是达人内容的1.5倍。」
## 八、吐槽/行业观察
24. 「小红书为什么非要把灵犀和聚光拆成两个平台?」
25. 「聚光投手的一天:打开聚光→调计划→打开灵犀→看数据→回聚光→调计划→回灵犀→……」
26. 「2026年了小红书还在让你在两个平台之间来回跳。」
## 九、咨询案例速览
27. 「客户问我为什么我投了三个月线索越来越多ROI越来越低
28. 「一个客户把80%预算投了竞品词我让他砍掉一半线索量没变成本降了40%。」
29. 「客户说'我种草了'我打开灵犀一看——TI净增是负的。」
## 十、预告/互动
30. 「我花了三周用AI拆了113万投放数据发现了五个我自己都不敢信的结论。」
31. 「你们投小红书最头疼的问题是什么?评论区告诉我,我拿数据跑。」