From 54b4a9ce993daf899150d8445a80bd5ccf8ce6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=BA=AA?= Date: Tue, 23 Jun 2026 08:00:01 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20=E6=AF=8F=E6=97=A5=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=A4=87=E4=BB=BD=20-=202026-06-23=2008:00:01?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- memory/.dreams/short-term-recall.json | 113 ++++++++++--- scripts/wechat_refund_correlation.py | 235 ++++++++++++++++++++++++++ 2 files changed, 327 insertions(+), 21 deletions(-) create mode 100644 scripts/wechat_refund_correlation.py diff --git a/memory/.dreams/short-term-recall.json b/memory/.dreams/short-term-recall.json index 0b6521c..59db050 100644 --- a/memory/.dreams/short-term-recall.json +++ b/memory/.dreams/short-term-recall.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-06-19T02:14:18.761Z", + "updatedAt": "2026-06-22T07:01:32.169Z", "entries": { "memory:memory/2026-05-06.md:1:20": { "key": "memory:memory/2026-05-06.md:1:20", @@ -433,18 +433,20 @@ "endLine": 39, "source": "memory", "snippet": "- 口径 = MEMORY.md 中所有计算逻辑、数据口径、指标定义、字段映射、判定条件、统计方法 - 唯一审批人:李承龙 - 禁止根据群聊讨论直接修改口径 - 禁止自行推断或\"修正\"已有口径 - 正确流程:发现问题 → 向李承龙确认 → 明确同意 → 方可修改 - 说明:数据查询本身按 USER.md 权限规则执行,不需要审批;本规则仅约束 MEMORY.md 中口径/计算逻辑的变更 ## 渠道归类补充 [王虹茗 2026-06-01 22:38] - 抖音精选 → 直购渠道 - 万物(newmedia-dianpu-wwxx-0-0)→ 达人渠道 - 合作活动是班主任 → 直购渠道 - ⚠️ 待李承龙确认后更新到 MEMORY.md 渠道映射规则", - "recallCount": 1, + "recallCount": 2, "dailyCount": 0, "groundedCount": 0, - "totalScore": 1, + "totalScore": 2, "maxScore": 1, "firstRecalledAt": "2026-06-02T03:39:49.570Z", - "lastRecalledAt": "2026-06-02T03:39:49.570Z", + "lastRecalledAt": "2026-06-22T07:01:26.682Z", "queryHashes": [ - "de7e9eb98961" + "de7e9eb98961", + "0100fb44019e" ], "recallDays": [ - "2026-06-02" + "2026-06-02", + "2026-06-22" ], "conceptTags": [ "memory.md", @@ -805,23 +807,25 @@ "endLine": 38, "source": "memory", "snippet": "# 2026-06-15 工作日志 ## 陈逸鸫 - 订单口径变更:停直购 **来源:** 陈逸鸫(群聊「数据更新V2」) **变更内容:** 1. 以后不管直购单,只看小红书销转,直购 GMV 不再进销转对账 2. full_refresh 不再 merge 直购进订单汇总 3. 直购表(1sosYE)可删除(需人工在飞书操作,API 不支持删 sheet) 4. 仍支持按手机查 keyfrom/GSV/退款(不定销售归属) **脚本修改:** - `scripts/refresh_order_summary.py`:去掉 Step 1.5 读取直购表逻辑,不再合并直购行 - `scripts/full_refresh_sales.py`:不涉及直购,无需改动 - `scripts/refresh_direct_sheet.py`:保留不动(如后续仍需独立查直购数据) ## 陈逸鸫 - full_refresh 执行 - 时间:2026-06-15 08:24-08:26 - 销售三表:吴迪 697 / 小龙 1198 / 成都 2203 - 有效 user_id:2009 - 订单汇总:383 条(不含直购) - 直购表:75 条已刷新 - 订单汇总合并后:461 条(含直购,这是口径变更前的最后一次含直购的合并) ## 陈逸鸫 - 线索表字段调整(2026-06-15) **来源:** 陈逸鸫(群聊「小红书数据需求」) **线索表(销售三表)新列结构(A~Z):** | 列 | 字段名 | 脚本填写 | |----|--------|---------|", - "recallCount": 5, + "recallCount": 6, "dailyCount": 0, "groundedCount": 0, - "totalScore": 5, + "totalScore": 6, "maxScore": 1, "firstRecalledAt": "2026-06-16T00:11:00.366Z", - "lastRecalledAt": "2026-06-19T02:14:18.761Z", + "lastRecalledAt": "2026-06-22T02:21:46.229Z", "queryHashes": [ "15e68c5404aa", "d01c75870930", "247976469363", "13b782b49d74", - "503e4d1c4721" + "503e4d1c4721", + "7a6770cbdabe" ], "recallDays": [ "2026-06-16", - "2026-06-19" + "2026-06-19", + "2026-06-22" ], "conceptTags": [ "full-refresh", @@ -875,19 +879,21 @@ "endLine": 25, "source": "memory", "snippet": "# 2026-06-16 工作日志 ## 李承龙 - 强化审批规则(12:42) **背景:** 李承龙发现 `full_refresh_sales.py` 在今天上午被修改(08:55),且 `sales_leads_full_refresh.py` 在群聊中被陈逸鸫的上下文触发修改。 **李承龙要求:** 1. 所有 workspace 脚本、skill、定时任务、轮询、MEMORY.md 的创建和修改,仅李承龙、李若松、张昆鹏三人有权下达指令 2. 每次修改必须记录时间、内容、来源人 3. 不允许再在任何对话/群聊中看到在别人指令下修改或创建上述内容 **已执行:** 1. AGENTS.md 规则已强化:新增 MEMORY.md 纳入审批范围、新增\"禁止顺手修改\"条款、新增修改后必须登记的要求 2. MEMORY.md 规则已同步强化 3. SKILL_REGISTRY.md 已补登记: - `full_refresh_sales.py`(创建于 2026-06-02,李承龙) - `sales_leads_full_refresh.py`(创建于 2026-06-08,李承龙) - `refresh_order_summary.py`(创建于约 2026-06,李承龙) 4. 变更记录已追加到各条目 ## 李承龙 - 增加操作前强制拦截检查(14:06) **李承龙要求:** 在 AGENTS.md 和 MEMORY.md 的审批规则前增加操作前强制检查清单。 **已执行:**", - "recallCount": 2, + "recallCount": 3, "dailyCount": 0, "groundedCount": 0, - "totalScore": 2, + "totalScore": 3, "maxScore": 1, "firstRecalledAt": "2026-06-19T02:13:56.194Z", - "lastRecalledAt": "2026-06-19T02:14:18.761Z", + "lastRecalledAt": "2026-06-22T02:21:46.229Z", "queryHashes": [ "13b782b49d74", - "503e4d1c4721" + "503e4d1c4721", + "7a6770cbdabe" ], "recallDays": [ - "2026-06-19" + "2026-06-19", + "2026-06-22" ], "conceptTags": [ "full-refresh-sales.py", @@ -907,19 +913,21 @@ "endLine": 45, "source": "memory", "snippet": "| 2026-06-16 约11:00 | `sales_leads_full_refresh.py` | 修复日期解析 | 群聊「数据更新V2」陈逸鸫上下文 | 已登记,标记违规 | | 2026-06-15 | `refresh_order_summary.py` | 去掉直购表合并 | 群聊「数据更新V2」陈逸鸫 | 已登记 | ## 李承龙 - 销售转化渠道分类规则迁移(14:14) **操作:** 1. 将 MEMORY.md 中「销售转化渠道分类规则」(2026-06-15 王虹茗确认、李承龙确认)迁移到 `scripts/full_refresh_sales.py` 2. 新增 `classify_sales_channel(key_from)` 函数,将 key_from 归为四类:端内 / 销转 / 达人 / 直购 3. 从 MEMORY.md 中删除该规则段落 4. SKILL_REGISTRY.md 已追加变更记录", - "recallCount": 2, + "recallCount": 3, "dailyCount": 0, "groundedCount": 0, - "totalScore": 2, + "totalScore": 3, "maxScore": 1, "firstRecalledAt": "2026-06-19T02:13:56.194Z", - "lastRecalledAt": "2026-06-19T02:14:18.761Z", + "lastRecalledAt": "2026-06-22T02:21:46.229Z", "queryHashes": [ "13b782b49d74", - "503e4d1c4721" + "503e4d1c4721", + "7a6770cbdabe" ], "recallDays": [ - "2026-06-19" + "2026-06-19", + "2026-06-22" ], "conceptTags": [ "sales-leads-full-refresh.py", @@ -931,6 +939,69 @@ "skill-registry.md", "修复" ] + }, + "memory:memory/2026-05-11.md:1:30": { + "key": "memory:memory/2026-05-11.md:1:30", + "path": "memory/2026-05-11.md", + "startLine": 1, + "endLine": 30, + "source": "memory", + "snippet": "# 2026-05-11 工作日志 ## 转化率口径确认 [李承龙确认] ### 转化率(端内注册转付费转化率) - **定义:** 注册用户中在端内完成付费的比例 - **分母:** 按注册日期分组,`bi_vala_app_account.status=1` 非测试账号 - **分子(含退费):** 分母中在端内(`key_from IN ('app-active-h5-0-0', 'app-sales-bj-qhm-0')`)有支付成功订单的去重用户数 - **分子(剔除退费):** 同上,但仅剔除端内订单**全部被退费**的用户——只要用户还有任何一笔未退费的端内订单就保留(退费判定:`bi_refund_order.status=3` 且 `bi_vala_order.order_status=4`) - **时间基准:** 按注册日期分组,不限制订单发生时间 - **订单时间字段:** `pay_success_date` ### 7日转化率 - 分子限制:`pay_success_date ≤ 注册日期 + 7天`(含注册当日) ### 14日转化率 - 分子限制:`pay_success_date ≤ 注册日期 + 14天`(含注册当日) ### 注意事项 - 需关联 `bi_vala_app_account` 剔除测试账号,`deleted_at IS NULL` 剔除已删除账号 - 按注册日期分组时,注册日期取 `created_at` 的日期部分 - 7日/14日/20日窗口包含注册当日 - 端内订单筛选 `order_status IN", + "recallCount": 2, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 2, + "maxScore": 1, + "firstRecalledAt": "2026-06-22T07:01:26.682Z", + "lastRecalledAt": "2026-06-22T07:01:32.169Z", + "queryHashes": [ + "0100fb44019e", + "d4dd7fb095b3" + ], + "recallDays": [ + "2026-06-22" + ], + "conceptTags": [ + "bi-vala-app-account.status", + "key-from", + "app-active-h5-0-0", + "app-sales-bj-qhm-0", + "bi-refund-order.status", + "bi-vala-order.order-status", + "pay-success-date", + "bi-vala-app-account" + ] + }, + "memory:memory/2026-06-01.md:16:31": { + "key": "memory:memory/2026-06-01.md:16:31", + "path": "memory/2026-06-01.md", + "startLine": 16, + "endLine": 31, + "source": "memory", + "snippet": "- 同一用户可能有多条记录,只要存在任意一条即视为已加微 - 参考数据:老狼履约明细 31 人中,已加微 28 人(90.3%),未加微 3 人(24105, 25485, 25945) - MEMORY.md 已更新 ## 🚫 口径变更审批规则建立 [李承龙确认 2026-06-01 15:14] - **严重问题:** 之前在群聊中未经李承龙确认就修改了加微判断口径(从 stride_contact_bindings 改为 student_info),存在数据口径被非授权变更的风险 - **根因:** 缺少口径变更的审批机制,小溪在群聊中接受了其他人的计算逻辑并直接写入了长期记忆 - **解决方案:** 1. MEMORY.md 新增「口径变更审批规则」章节,明确李承龙为数据口径唯一审批人 2. AGENTS.md 新增「MEMORY.md 口径变更审批」章节,作为强制执行规则 3. 规则要点: - 口径 = MEMORY.md 中所有计算逻辑、数据口径、指标定义、字段映射、判定条件、统计方法 - 唯一审批人:李承龙 - 禁止根据群聊讨论直接修改口径 - 禁止自行推断或\"修正\"已有口径 - 正确流程:发现问题 → 向李承龙确认 → 明确同意 → 方可修改", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-06-22T07:01:26.682Z", + "lastRecalledAt": "2026-06-22T07:01:26.682Z", + "queryHashes": [ + "0100fb44019e" + ], + "recallDays": [ + "2026-06-22" + ], + "conceptTags": [ + "90.3", + "memory.md", + "stride-contact-bindings", + "student-info", + "agents.md", + "同一", + "用户", + "可能" + ] } } } diff --git a/scripts/wechat_refund_correlation.py b/scripts/wechat_refund_correlation.py new file mode 100644 index 0000000..aed76a9 --- /dev/null +++ b/scripts/wechat_refund_correlation.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +""" +分析「是否添加班主任微信」与「退款率」的相关性 +数据源: +- 加微判断:vala_class.public.student_info(vala_account_id 存在即已加微) +- 订单数据:vala_bi.bi_vala_order + bi_vala_app_account + bi_refund_order +""" +import psycopg2 +import os +from collections import defaultdict + +PG_HOST = "bj-postgres-16pob4sg.sql.tencentcdb.com" +PG_PORT = 28591 +PG_USER = "ai_member" +PG_PASS = os.environ.get("PG_ONLINE_PASSWORD", "") + +def get_conn(db): + return psycopg2.connect(host=PG_HOST, port=PG_PORT, user=PG_USER, password=PG_PASS, dbname=db) + +print("=" * 70) +print("📊 是否添加班主任微信 vs 退款率 相关性分析") +print("=" * 70) + +# ============================================================ +# Step 1: 从 vala_class 获取所有已加微的 account_id +# ============================================================ +print("\n[1/5] 获取已加班主任微信的用户列表...") +conn_class = get_conn("vala_class") +cur = conn_class.cursor() +cur.execute(""" + SELECT DISTINCT vala_account_id + FROM student_info + WHERE vala_account_id IS NOT NULL +""") +wechat_accounts = {row[0] for row in cur.fetchall()} +cur.close() +conn_class.close() +print(f" 已加微用户数: {len(wechat_accounts):,}") + +# ============================================================ +# Step 2: 获取所有购课用户(非测试账号) +# ============================================================ +print("\n[2/5] 获取购课用户及订单数据...") +conn_bi = get_conn("vala_bi") +cur = conn_bi.cursor() + +# 获取所有非测试账号的购课订单 +cur.execute(""" + SELECT + o.account_id, + o.id AS order_id, + o.trade_no, + o.out_trade_no, + o.order_status, + o.pay_amount_int, + o.pay_success_date, + o.key_from + FROM bi_vala_order o + INNER JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 AND a.deleted_at IS NULL + WHERE o.pay_success_date IS NOT NULL + AND o.order_status IN (3, 4) +""") +orders = cur.fetchall() +print(f" 购课订单数: {len(orders):,}") + +# 按 account_id 分组 +user_orders = defaultdict(list) +for row in orders: + user_orders[row[0]].append({ + 'order_id': row[1], + 'trade_no': row[2], + 'out_trade_no': row[3], + 'order_status': row[4], + 'pay_amount_int': row[5], + 'pay_success_date': row[6], + 'key_from': row[7] + }) + +buying_users = set(user_orders.keys()) +print(f" 购课用户数(去重): {len(buying_users):,}") + +# ============================================================ +# Step 3: 获取所有退费记录 +# ============================================================ +print("\n[3/5] 获取退费记录...") +cur.execute(""" + SELECT trade_no, out_trade_no, refund_amount, status + FROM bi_refund_order + WHERE status = 3 +""") +refund_rows = cur.fetchall() +print(f" 退费记录数: {len(refund_rows):,}") + +# 构建退费记录列表:每条记录 (trade_no, out_trade_no, amount_fen) +refund_records = [] +for row in refund_rows: + trade_no, out_trade_no, amount, status = row + amount_fen = int(float(amount) * 100) if amount else 0 + refund_records.append((trade_no, out_trade_no, amount_fen)) + +cur.close() +conn_bi.close() + +# ============================================================ +# Step 4: 判断每笔订单是否退费 +# ============================================================ +print("\n[4/5] 计算退费情况...") + +def get_refund_info(order): + """判断订单是否退费,返回 (is_refunded, total_refund_amount_fen)""" + # order_status=4 → 直接判定退费 + if order['order_status'] == 4: + # 计算退费金额:匹配所有退费记录 + total = 0 + tn = order['trade_no'] + otn = order['out_trade_no'] + seen = set() # 避免同一笔退费记录重复计算 + for i, (r_tn, r_otn, r_amt) in enumerate(refund_records): + if (r_tn and r_tn == tn) or (r_otn and r_otn == otn): + if i not in seen: + seen.add(i) + total += r_amt + return True, total + # order_status=3 → 需在退费表中存在 status=3 的记录 + if order['order_status'] == 3: + total = 0 + tn = order['trade_no'] + otn = order['out_trade_no'] + seen = set() + for i, (r_tn, r_otn, r_amt) in enumerate(refund_records): + if (r_tn and r_tn == tn) or (r_otn and r_otn == otn): + if i not in seen: + seen.add(i) + total += r_amt + if total > 0: + return True, total + return False, 0 + +# 按用户统计 +wechat_stats = { + 'users': set(), + 'total_orders': 0, + 'refunded_orders': 0, + 'total_gmv': 0, + 'total_refund_amount': 0, +} +no_wechat_stats = { + 'users': set(), + 'total_orders': 0, + 'refunded_orders': 0, + 'total_gmv': 0, + 'total_refund_amount': 0, +} + +for account_id, order_list in user_orders.items(): + has_wechat = account_id in wechat_accounts + target = wechat_stats if has_wechat else no_wechat_stats + target['users'].add(account_id) + + for order in order_list: + target['total_orders'] += 1 + target['total_gmv'] += order['pay_amount_int'] + + is_ref, refund_amt = get_refund_info(order) + if is_ref: + target['refunded_orders'] += 1 + target['total_refund_amount'] += refund_amt + +# ============================================================ +# Step 5: 输出结果 +# ============================================================ +print("\n[5/5] 输出分析结果...") +print("\n" + "=" * 70) +print("📊 分析结果") +print("=" * 70) + +def print_stats(label, stats): + users = len(stats['users']) + orders = stats['total_orders'] + refunded = stats['refunded_orders'] + gmv = stats['total_gmv'] / 100 + refund_amt = stats['total_refund_amount'] / 100 + gsv = gmv - refund_amt + + refund_rate = (refunded / orders * 100) if orders > 0 else 0 + refund_amount_rate = (refund_amt / gmv * 100) if gmv > 0 else 0 + avg_orders_per_user = orders / users if users > 0 else 0 + + print(f"\n{'─' * 50}") + print(f" {label}") + print(f"{'─' * 50}") + print(f" 购课用户数: {users:>8,} 人") + print(f" 订单总数: {orders:>8,} 单") + print(f" 人均订单数: {avg_orders_per_user:>8.1f} 单/人") + print(f" 退费订单数: {refunded:>8,} 单") + print(f" 退费率(按订单): {refund_rate:>8.1f}%") + print(f" GMV: {gmv:>10,.0f} 元") + print(f" 退费金额: {refund_amt:>10,.0f} 元") + print(f" GSV: {gsv:>10,.0f} 元") + print(f" 退费金额占比: {refund_amount_rate:>8.1f}%") + return { + 'users': users, 'orders': orders, 'refunded': refunded, + 'refund_rate': refund_rate, 'gmv': gmv, 'refund_amt': refund_amt, + 'gsv': gsv, 'refund_amount_rate': refund_amount_rate + } + +w = print_stats("✅ 已加班主任微信", wechat_stats) +n = print_stats("❌ 未加班主任微信", no_wechat_stats) + +# 对比 +print(f"\n{'=' * 70}") +print("📊 对比分析") +print(f"{'=' * 70}") +print(f" 退费率差异: 已加微 {w['refund_rate']:.1f}% vs 未加微 {n['refund_rate']:.1f}%") +diff = w['refund_rate'] - n['refund_rate'] +print(f" 退费率差值: {diff:+.1f} 个百分点") +if n['refund_rate'] > 0: + ratio = w['refund_rate'] / n['refund_rate'] + print(f" 退费率比值: 已加微是未加微的 {ratio:.2f} 倍") + +print(f"\n 退费金额占比差异: 已加微 {w['refund_amount_rate']:.1f}% vs 未加微 {n['refund_amount_rate']:.1f}%") + +# 用户维度退费率(有退费的用户 / 总用户) +wechat_refund_users = sum(1 for uid in wechat_stats['users'] + if any(get_refund_info(o)[0] for o in user_orders[uid])) +no_wechat_refund_users = sum(1 for uid in no_wechat_stats['users'] + if any(get_refund_info(o)[0] for o in user_orders[uid])) + +print(f"\n 用户维度退费率:") +print(f" 已加微: {wechat_refund_users}/{len(wechat_stats['users'])} = {wechat_refund_users/len(wechat_stats['users'])*100:.1f}%") +if len(no_wechat_stats['users']) > 0: + print(f" 未加微: {no_wechat_refund_users}/{len(no_wechat_stats['users'])} = {no_wechat_refund_users/len(no_wechat_stats['users'])*100:.1f}%") + +print(f"\n{'=' * 70}") +print("分析完成 ✅")