diff --git a/logs/backup.log b/logs/backup.log index b8b90ac..37f90d7 100644 --- a/logs/backup.log +++ b/logs/backup.log @@ -639,3 +639,11 @@ 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 diff --git a/memory/.dreams/events.jsonl b/memory/.dreams/events.jsonl index 4cedfb7..2f28112 100644 --- a/memory/.dreams/events.jsonl +++ b/memory/.dreams/events.jsonl @@ -104,3 +104,4 @@ {"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}]} diff --git a/memory/.dreams/short-term-recall.json b/memory/.dreams/short-term-recall.json index 27efdf7..7bb512f 100644 --- a/memory/.dreams/short-term-recall.json +++ b/memory/.dreams/short-term-recall.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-06-18T10:17:37.951Z", + "updatedAt": "2026-06-19T12:45:15.278Z", "entries": { "memory:memory/2026-05-24.md:1:30": { "key": "memory:memory/2026-05-24.md:1:30", @@ -2375,19 +2375,21 @@ "endLine": 30, "source": "memory", "snippet": "- 发现核心 bug:`pick_valid_order()` 只取最新一笔有效订单,未做 GSV 聚合 - **根因:** 18621578529(account_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": 2, + "recallCount": 3, "dailyCount": 0, "groundedCount": 0, - "totalScore": 2, + "totalScore": 3, "maxScore": 1, "firstRecalledAt": "2026-06-18T00:09:26.923Z", - "lastRecalledAt": "2026-06-18T10:17:37.951Z", + "lastRecalledAt": "2026-06-19T12:45:15.278Z", "queryHashes": [ "ee468f64688e", - "d83437ba9cad" + "d83437ba9cad", + "290e7f4aaa74" ], "recallDays": [ - "2026-06-18" + "2026-06-18", + "2026-06-19" ], "conceptTags": [ "pick-valid-order", @@ -2430,6 +2432,99 @@ "数据", "查询" ] + }, + "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-P(Q/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) - 绑单审计 E1–E9 全部 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", + "数字/日期解析正常" + ] } } } diff --git a/memory/2026-06-19.md b/memory/2026-06-19.md new file mode 100644 index 0000000..1b02dad --- /dev/null +++ b/memory/2026-06-19.md @@ -0,0 +1,11 @@ +# 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-P(Q/R未填) +- db_info: 2026-06-19 18:21:44 +- 使用实习虾 app (cli_aa898f32d4799bea) 的 tenant token 绕过 xiaoban bot 的 sheets:spreadsheet:read 权限缺失 +- 注意: clear_range 用空字符串覆盖而非空数组(飞书API拒绝空数组) diff --git a/output/lingxi_short_1.md b/output/lingxi_short_1.md new file mode 100644 index 0000000..aa42a75 --- /dev/null +++ b/output/lingxi_short_1.md @@ -0,0 +1,71 @@ +## 灵犀第一篇(改) + +**「灵犀不是聚光的'数据看板'——我以前根本懒得打开它。直到用它改了一组计划,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净增。不为别的,就为知道下个月的线索会不会崩。 diff --git a/scripts/damai_v2_fill.py b/scripts/damai_v2_fill.py new file mode 100644 index 0000000..ecf96c3 --- /dev/null +++ b/scripts/damai_v2_fill.py @@ -0,0 +1,463 @@ +#!/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() diff --git a/skills/wechat-article-dachen/SKILL.md b/skills/wechat-article-dachen/SKILL.md new file mode 100644 index 0000000..3d35212 --- /dev/null +++ b/skills/wechat-article-dachen/SKILL.md @@ -0,0 +1,52 @@ +--- +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` | diff --git a/skills/wechat-article-dachen/examples.md b/skills/wechat-article-dachen/examples.md new file mode 100644 index 0000000..8edc47d --- /dev/null +++ b/skills/wechat-article-dachen/examples.md @@ -0,0 +1,57 @@ +# 风格示例 + +## 定稿示例:打脸预热文 + +--- + +「去年我说种草没用,今年就被打脸了。」 + +去年我写过一句话:"种草要投吗?No,屁用没有。" + +当时的逻辑很简单:平台内容还未饱和,很多品牌还没开始投放,与其种草,不如多出硬广效率更高。 + +直客总是唠叨的灵犀更是极少登陆,因为不仅功能复杂,概念繁多,跟聚光数据也很难聚合,纯tm浪费时间。 + +但今年小红书难做了很多,内容饱和已经是常态,同样的赛道、同样的人群,所有人都在抢。我出价高,别人比我更高。最后就是纯拼谁烧得起——线索拿到了,ROI打不平。 + +这时候我才被逼着去找新出路:收割卷不动了,那能不能在收割之前做点什么? + +于是我打开了灵犀,做了一方人群回传,结果一出来我都愣了。 + +我知道TI是最接近线索的人群,但是没考虑过线索中多少来自TI,数据出来一看,我靠,90.4%的线索都来自TI人群!而更泛的A和I人群加一起都不到10%...... + +TI很重要 vs 90%的线索来自TI,分量完全不同好不好! + +你说那些小红书大会,一开就是半天,各种概念玄之又玄,你们直接把这个结论放出来我们早就启用了好不好。 + +那么TI怎么尽快扩大?哪些内容的TI种草成本低?什么样的投放能带来低成本TI?TI不够会怎么样?品牌营销的费用到底带来了多少回报? + +一旦有了数据支持,这些以前无法回答的种草效果问题就迎刃而解。 + +当然,这整个链路很复杂,我把【灵犀+聚光+企微+销售聊天记录+订单系统】用ai全部串起来之后,改了10版计算逻辑,才有了一个贯穿全局的洞察。 + +我只能说,种草的确有效果,但是跟大家以为的效果不太一样,非常有意思哈哈。 + +接下来我会写一系列"种草效果分析"的内容,把灵犀中的各个数据指标解释清楚,并一步一步揭露"每一块钱的种草能带多少gmv?"的最终结论。 + +如果你也遇到了投放成本越来越高,线索越来越少,roi越来越低的问题, + +我只能说,敬请期待! + +这个玩法远不限于小红书,只要是内容平台,一律适用! + +当然如果你等不及,也可以直接找我约个咨询。 + +下一篇预告《一个策略,不花一分钱也能直接ROI翻倍| 种草效果分析》 + +--- + +## 风格要素拆解 + +1. **开头钩子**:一句话打脸,直接有力 +2. **反转线**:以前出价能解决问题 → 现在饱和了卷不动了 → 被迫回头看TI → 发现90.4% +3. **人味细节**:直客唠叨灵犀、纯tm浪费时间、我靠、愣了、好不好 +4. **数据冲击**:90.4%,一句话就够了 +5. **吐槽大会**:小红书大会开半天,概念玄之又玄 +6. **预告钩子**:下一篇标题直接放出来 +7. **行动号召**:等不及可以约咨询 diff --git a/skills/wechat-article-dachen/topics.md b/skills/wechat-article-dachen/topics.md new file mode 100644 index 0000000..2dc3e8e --- /dev/null +++ b/skills/wechat-article-dachen/topics.md @@ -0,0 +1,52 @@ +# 短文选题池 + +## 一、打脸/反转系列 +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. 「你们投小红书最头疼的问题是什么?评论区告诉我,我拿数据跑。」 diff --git a/tmp_daily_summary.md b/tmp_daily_summary.md new file mode 100644 index 0000000..66d1a5b --- /dev/null +++ b/tmp_daily_summary.md @@ -0,0 +1,3 @@ +=== 每日总结 20260620 === +## 昨日关键进展 +- ① 线索明细 (7fdb4b): F列95个手机号 → XXTEA加密 → PG匹配 → K列UID 63个(新增12个)