diff --git a/memory/.dreams/short-term-recall.json b/memory/.dreams/short-term-recall.json index 4e09e29..7a07262 100644 --- a/memory/.dreams/short-term-recall.json +++ b/memory/.dreams/short-term-recall.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-05-12T07:45:29.297Z", + "updatedAt": "2026-05-13T08:20:55.037Z", "entries": { "memory:memory/2026-05-06.md:1:20": { "key": "memory:memory/2026-05-06.md:1:20", @@ -40,18 +40,20 @@ "endLine": 30, "source": "memory", "snippet": "- **订单号格式问题:** Excel 科学计数法 → 设置单元格为文本格式解决 - **psycopg2 转义:** LIKE 中 `%` 需写成 `%%`(psycopg2 的 `%s` 占位符冲突) ## 任务2:销售线索用户分析报表 - [李承龙需求] 输入包含462个用户ID的Excel,输出每个用户+角色的完整画像报表 - 脚本路径:`/root/.openclaw/workspace/scripts/lead_user_analysis.py` - **重要口径修正(李承龙确认):** - **体验课定义:** 固定为 L1 U00(L01-L05) + L2 U00(L01-L05) 共10节课,**不通过 expire_time 区分** - L1 chapter_id: 343, 344, 345, 346, 348 - L2 chapter_id: 55, 56, 57, 58, 59 - **完成时间:** 取 `bi_user_chapter_play_record` 中 `play_status=1` 记录的**最早 `updated_at`**(非 `created_at`) - **报表列:** 用户ID、角色ID、用户注册时间、角色创建时间、L1序章5节完成时间、L2序章5节完成时间、购买时间、购买课包名称、支付金额、购买渠道key_from、是否退款、退款金额 - **分表规则:** `bi_user_chapter_play_record_{0..7}`,分表号 = user_id % 8 - **重要关联:** `b", - "recallCount": 1, + "recallCount": 2, "dailyCount": 0, "groundedCount": 0, - "totalScore": 0.9835963163573811, - "maxScore": 0.9835963163573811, + "totalScore": 1.983596316357381, + "maxScore": 1, "firstRecalledAt": "2026-05-07T04:41:47.636Z", - "lastRecalledAt": "2026-05-07T04:41:47.636Z", + "lastRecalledAt": "2026-05-13T08:20:55.037Z", "queryHashes": [ - "dd105d141c56" + "dd105d141c56", + "4cacd0d43440" ], "recallDays": [ - "2026-05-07" + "2026-05-07", + "2026-05-13" ], "conceptTags": [ "l01-l05", diff --git a/memory/2026-05-13-1611.md b/memory/2026-05-13-1611.md new file mode 100644 index 0000000..37f36a1 --- /dev/null +++ b/memory/2026-05-13-1611.md @@ -0,0 +1,252 @@ +# Session: 2026-05-13 16:11:56 GMT+8 + +- **Session Key**: agent:main:feishu:direct:ou_e63ce6b760ad39382852472f28fbe2a2 +- **Session ID**: a2fd11e0-095a-4a58-a376-9424b26b81be +- **Source**: feishu + +## Conversation Summary + +user: 我没有收到数据 +assistant: ## 📊 三版转化率 2025年9月-2026年4月 + +| 月份 | 原始版转化率 | 原始版去退费 | 纯净版转化率 | 纯净版去退费 | 拟合版转化率 | 拟合版去退费 | +|------|:---:|:---:|:---:|:---:|:---:|:---:| +| 2025-09 | 1.11% | 1.11% | 1.34% | 1.34% | 1.76% | 1.76% | +| 2025-10 | 1.41% | 1.37% | 1.59% | 1.54% | 1.71% | 1.66% | +| 2025-11 | 0.71% | 0.71% | 0.74% | 0.74% | 0.83% | 0.83% | +| 2025-12 | 0.71% | 0.68% | 0.75% | 0.72% | 0.73% | 0.70% | +| 2026-01 | 1.39% | 1.24% | 1.49% | 1.32% | 1.51% | 1.34% | +| 2026-02 | 0.92% | 0.86% | 1.01% | 0.95% | 1.25% | 1.17% | +| 2026-03 | 1.92% | 1.85% | 2.15% | 2.07% | 2.73% | 2.63% | +| 2026-04 | 1.15% | 1.11% | 1.35% | 1.31% | 1.88% | 1.82% | + +三版转化率关系:拟合版 > 纯净版 > 原始版。3月是三版共同的峰值(拟合版2.73%),12月和11月是低谷。 +user: 然后我想要看两个keyfrom分别的订单数和退费订单数 +assistant: ## 📊 两个 key_from 分别订单数 & 退费订单数 + +| 月份 | app-active-h5-0-0 订单 | 退费 | app-sales-bj-qhm-0 订单 | 退费 | +|------|:---:|:---:|:---:|:---:| +| 2025-09 | 18 | 1 | 0 | 0 | +| 2025-10 | 36 | 1 | 0 | 0 | +| 2025-11 | 21 | 0 | 0 | 0 | +| 2025-12 | 28 | 2 | 0 | 0 | +| 2026-01 | 29 | 4 | 1 | 0 | +| 2026-02 | 17 | 1 | 0 | 0 | +| 2026-03 | 67 | 3 | 17 | 2 | +| 2026-04 | 50 | 1 | 16 | 2 | +| **合计** | **266** | **13** | **34** | **4** | + +`app-sales-bj-qhm-0` 从2026年3月才开始有明显量,这之前(2025.9-2026.2)只有1月有1单。端内主力始终是 `app-active-h5-0-0`,占整体订单88.7%。 +user: 加一个GMV和GSV +assistant: ## 📊 端内两个 key_from 月度明细 + +### app-active-h5-0-0 + +| 月份 | 订单数 | 退费 | GMV | GSV | +|------|:---:|:---:|:---:|:---:| +| 2025-09 | 18 | 1 | ¥21,482 | ¥20,883 | +| 2025-10 | 36 | 1 | ¥42,064 | ¥40,065 | +| 2025-11 | 21 | 0 | ¥26,579 | ¥26,579 | +| 2025-12 | 28 | 2 | ¥31,172 | ¥29,974 | +| 2026-01 | 29 | 4 | ¥34,270 | ¥29,875 | +| 2026-02 | 17 | 1 | ¥21,982 | ¥19,983 | +| 2026-03 | 67 | 3 | ¥75,033 | ¥70,936 | +| 2026-04 | 50 | 1 | ¥46,750 | ¥46,151 | +| **合计** | **266** | **13** | **¥299,332** | **¥284,446** | + +### app-sales-bj-qhm-0 + +| 月份 | 订单数 | 退费 | GMV | GSV | +|------|:---:|:---:|:---:|:---:| +| 2025-09~2026-02 | 1 | 0 | ¥599 | ¥599 | +| 2026-03 | 17 | 2 | ¥40,578 | ¥37,980 | +| 2026-04 | 16 | 2 | ¥35,381 | ¥29,784 | +| **合计** | **34** | **4** | **¥76,558** | **¥68,363** | + +`app-sales-bj-qhm-0` 从3月才开始起量,两个月贡献了 ¥76,558 GMV,占端内总GMV的20.4%,已经是个不小的新渠道了。 +user: 帮我统计只要app-active-h5-0-0的三个版本的区分苹果、华为、学而思、华为渠道的2025年9月到2026年4月每个月的新增注册、订单数、转化率、转化率(去退费)、退费订单数、退费率、GMV、GMS +assistant: 数据较多,分渠道展示。 + +## 📊 苹果 × app-active-h5-0-0 + +| 月份 | 版本 | 注册 | 订单 | 退费 | 转化率 | 转(去退) | 退费率 | GMV | GSV | +|------|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| 2025-09 | 原始/纯净/拟合 | 211/175/130 | 6 | 0 | 2.84%/3.43%/4.62% | 同左 | 0% | ¥7,294 | ¥7,294 | +| 2025-10 | 原始/纯净/拟合 | 404/343/293 | 11 | 1 | 2.72%/3.21%/3.75% | 2.48%/2.92%/3.41% | 9.1% | ¥10,789 | ¥8,790 | +| 2025-11 | 原始/纯净/拟合 | 440/409/393 | 7 | 0 | 1.59%/1.71%/1.78% | 同左 | 0% | ¥11,193 | ¥11,193 | +| 2025-12 | 原始/纯净/拟合 | 384/337/364 | 7 | 0 | 1.82%/2.08%/1.92% | 同左 | 0% | ¥8,393 | ¥8,393 | +| 2026-01 | 原始/纯净/拟合 | 310/283/280 | 17 | 3 | 4.52%/4.95%/5.00% | 4.19%/4.59%/4.64% | 17.6% | ¥22,783 | ¥19,586 | +| 2026-02 | 原始/纯净/拟合 | 319/273/226 | 6 | 0 | 1.25%/1.47%/1.77% | 同左 | 0% | ¥9,194 | ¥9,194 | +| 2026-03 | 原始/纯净/拟合 | 1167/1046/813 | 26 | 2 | 1.97%/2.20%/2.83% | 同左 | 7.7% | ¥32,774 | ¥29,276 | +| 2026-04 | 原始/纯净/拟合 | 1540/1316/921 | 17 | 0 | 1.10%/1.29%/1.85% | 同左 | 0% | ¥18,583 | ¥18,583 | + +## 📊 华为 × app-active-h5-0-0 + +| 月份 | 版本 | 注册 | 订单 | 退费 | 转化率 | 转(去退) | 退费率 | GMV | GSV | +|------|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| 2025-09 | 原始/纯净/拟合 | 112/73/69 | 3 | 1 | 1.79%/2.74%/2.90% | 同左 | 33.3% | ¥3,197 | ¥2,598 | +| 2025-10 | 原始/纯净/拟合 | 178/148/129 | 10 | 0 | 5.06%/6.08%/6.98% | 同左 | 0% | ¥11,590 | ¥11,590 | +| 2025-11 | 原始/纯净/拟合 | 282/260/252 | 2 | 0 | 0.71%/0.77%/0.79% | 同左 | 0% | ¥1,198 | ¥1,198 | +| 2025-12 | 原始/纯净/拟合 | 239/214/226 | 3 | 0 | 1.26%/1.40%/1.33% | 同左 | 0% | ¥4,597 | ¥4,597 | +| 2026-01 | 原始/纯净/拟合 | 212/186/191 | 8 | 0 | 3.30%/3.76%/3.66% | 同左 | 0% | ¥8,492 | ¥8,492 | +| 2026-02 | 原始/纯净/拟合 | 117/100/82 | 1 | 0 | 0.85%/1.00%/1.22% | 同左 | 0% | ¥1,999 | ¥1,999 | +| 2026-03 | 原始/纯净/拟合 | 670/603/467 | 13 | 0 | 1.94%/2.16%/2.78% | 同左 | 0% | ¥11,987 | ¥11,987 | +| 2026-04 | 原始/纯净/拟合 | 1153/989/689 | 11 | 1 | 0.95%/1.11%/1.60% | 0.87%/1.01%/1.45% | 9.1% | ¥7,989 | ¥7,390 | + +## 📊 学而思 × app-active-h5-0-0 + +| 月份 | 版本 | 注册 | 订单 | 退费 | 转化率 | 转(去退) | 退费率 | GMV | GSV | +|------|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| 2025-09 | 原始/纯净/拟合 | 169/105/104 | 2 | 0 | 1.18%/1.90%/1.92% | 同左 | 0% | ¥2,598 | ¥2,598 | +| 2025-10 | 原始/纯净/拟合 | 143/112/103 | 4 | 0 | 2.80%/3.57%/3.88% | 同左 | 0% | ¥5,196 | ¥5,196 | +| 2025-11 | 原始/纯净/拟合 | 422/408/377 | 3 | 0 | 0.71%/0.74%/0.80% | 同左 | 0% | ¥4,597 | ¥4,597 | +| 2025-12 | 原始/纯净/拟合 | 1137/1117/1079 | 6 | 1 | 0.44%/0.45%/0.46% | 同左 | 16.7% | ¥6,394 | ¥5,795 | +| 2026-01 | 原始/纯净/拟合 | 301/288/272 | 2 | 1 | 0.66%/0.69%/0.74% | 0.33%/0.35%/0.37% | 50.0% | ¥1,198 | ¥599 | +| 2026-02 | 原始/纯净/拟合 | 611/575/433 | 3 | 0 | 0.49%/0.52%/0.69% | 同左 | 0% | ¥1,797 | ¥1,797 | +| 2026-03 | 原始/纯净/拟合 | 614/568/428 | 4 | 1 | 0.65%/0.70%/0.93% | 0.49%/0.53%/0.70% | 25.0% | ¥5,196 | ¥4,597 | +| 2026-04 | 原始/纯净/拟合 | 422/356/252 | 5 | 0 | 1.18%/1.40%/1.98% | 同左 | 0% | ¥2,995 | ¥2,995 | + +--- + +**几个观察:** +- 苹果和华为的转化率在 1月达到最高峰(苹果拟合5.00%、华为拟合3.66%),之后持续下滑到4月(苹果1.85%、华为1.60%) +- 学而思转化率整体偏低(0.5%~1%),但12月注册量最大(1137人)时转化率最低(0.44%),4月注册回落但转化回升到1.18% +- 华为退费很少,学而思1月退费率50%(2单退了1单) +- 三个渠道拟合版分母都比原始版收紧,苹果和学而思收紧比例较大(受活动影响更明显) +user: 帮我统计只要app-active-h5-0-0的三个版本的区分苹果、华为、学而思、华为渠道的2025年9月到2026年4月每个月的7日、14日、21日转化率 +assistant: ## 📊 苹果 × app-active-h5-0-0(去退费) + +| 月份 | 原始版 7日/14日/21日 | 纯净版 7日/14日/21日 | 拟合版 7日/14日/21日 | +|------|:---:|:---:|:---:| +| 2025-09 | 0.47%/0.47%/1.42% | 0.58%/0.58%/1.75% | 0.77%/0.77%/2.31% | +| 2025-10 | 1.24%/1.24%/1.73% | 1.47%/1.47%/2.06% | 1.71%/1.71%/2.39% | +| 2025-11 | 0.91%/1.14%/1.14% | 0.99%/1.23%/1.23% | 1.02%/1.27%/1.27% | +| 2025-12 | 0.26%/1.04%/1.30% | 0.30%/1.20%/1.51% | 0.27%/1.10%/1.37% | +| 2026-01 | 2.26%/2.90%/3.55% | 2.56%/3.30%/4.03% | 2.50%/3.21%/3.93% | +| 2026-02 | 0.94%/0.94%/0.94% | 1.21%/1.21%/1.21% | 1.33%/1.33%/1.33% | +| 2026-03 | 0.69%/1.37%/1.71% | 0.79%/1.57%/1.97% | 0.98%/1.97%/2.46% | +| 2026-04 | 0.78%/0.97%/1.10% | 1.00%/1.25%/1.42% | 1.30%/1.63%/1.85% | + +## 📊 华为 × app-active-h5-0-0(去退费) + +| 月份 | 原始版 7日/14日/21日 | 纯净版 7日/14日/21日 | 拟合版 7日/14日/21日 | +|------|:---:|:---:|:---:| +| 2025-09 | 0%/0.89%/0.89% | 0%/1.47%/1.47% | 0%/1.45%/1.45% | +| 2025-10 | 2.25%/3.37%/3.37% | 2.70%/4.05%/4.05% | 3.10%/4.65%/4.65% | +| 2025-11 | 0%/0%/0% | 0%/0%/0% | 0%/0%/0% | +| 2025-12 | 0.84%/0.84%/0.84% | 0.94%/0.94%/0.94% | 0.88%/0.88%/0.88% | +| 2026-01 | 1.89%/1.89%/2.83% | 2.19%/2.19%/3.28% | 2.09%/2.09%/3.14% | +| 2026-02 | 0%/0.85%/0.85% | 0%/1.18%/1.18% | 0%/1.22%/1.22% | +| 2026-03 | 1.34%/1.79%/1.79% | 1.57%/2.09%/2.09% | 1.93%/2.57%/2.57% | +| 2026-04 | 0.52%/0.69%/0.69% | 0.67%/0.90%/0.90% | 0.87%/1.16%/1.16% | + +## 📊 学而思 × app-active-h5-0-0(去退费) + +| 月份 | 原始版 7日/14日/21日 | 纯净版 7日/14日/21日 | 拟合版 7日/14日/21日 | +|------|:---:|:---:|:---:| +| 2025-09 | 0.59%/0.59%/0.59% | 0.96%/0.96%/0.96% | 0.96%/0.96%/0.96% | +| 2025-10 | 1.40%/2.10%/2.10% | 1.80%/2.70%/2.70% | 1.94%/2.91%/2.91% | +| 2025-11 | 0.47%/0.47%/0.71% | 0.49%/0.49%/0.74% | 0.53%/0.53%/0.80% | +| 2025-12 | 0.35%/0.35%/0.35% | 0.36%/0.36%/0.36% | 0.37%/0.37%/0.37% | +| 2026-01 | 0.33%/0.33%/0.33% | 0.35%/0.35%/0.35% | 0.37%/0.37%/0.37% | +| 2026-02 | 0.33%/0.33%/0.33% | 0.35%/0.35%/0.35% | 0.46%/0.46%/0.46% | +| 2026-03 | 0.49%/0.49%/0.49% | 0.54%/0.54%/0.54% | 0.70%/0.70%/0.70% | +| 2026-04 | 0.95%/1.18%/1.18% | 1.24%/1.55%/1.55% | 1.59%/1.98%/1.98% | + +--- + +**几个关键发现:** + +- **苹果** 转化集中在7-14日之间,12月7日转化极低(0.26%)但14日追上(1.04%),说明12月用户付费决策周期偏长。1月是高峰(7日2.26%→21日3.55%)。 +- **华为** 付费决策基本在7日内完成,7日之后的增量很少。11月三个版本全是0。10月拟合版7日3.10%是渠道最高。 +- **学而思** 付费决策也基本在7日内,21日和14日几乎无差。整体转化率偏低(0.3%~1%),4月有回暖迹象(拟合版7日1.59%)。 +user: 调整一下上面表格的展示形式,一个格里面只放一个百分比数据 +assistant: ## 📊 苹果 × app-active-h5-0-0(去退费) + +| 月份 | 版本 | 7日转化率 | 14日转化率 | 21日转化率 | +|------|------|:---:|:---:|:---:| +| 2025-09 | 原始版 | 0.47% | 0.47% | 1.42% | +| | 纯净版 | 0.58% | 0.58% | 1.75% | +| | 拟合版 | 0.77% | 0.77% | 2.31% | +| 2025-10 | 原始版 | 1.24% | 1.24% | 1.73% | +| | 纯净版 | 1.47% | 1.47% | 2.06% | +| | 拟合版 | 1.71% | 1.71% | 2.39% | +| 2025-11 | 原始版 | 0.91% | 1.14% | 1.14% | +| | 纯净版 | 0.99% | 1.23% | 1.23% | +| | 拟合版 | 1.02% | 1.27% | 1.27% | +| 2025-12 | 原始版 | 0.26% | 1.04% | 1.30% | +| | 纯净版 | 0.30% | 1.20% | 1.51% | +| | 拟合版 | 0.27% | 1.10% | 1.37% | +| 2026-01 | 原始版 | 2.26% | 2.90% | 3.55% | +| | 纯净版 | 2.56% | 3.30% | 4.03% | +| | 拟合版 | 2.50% | 3.21% | 3.93% | +| 2026-02 | 原始版 | 0.94% | 0.94% | 0.94% | +| | 纯净版 | 1.21% | 1.21% | 1.21% | +| | 拟合版 | 1.33% | 1.33% | 1.33% | +| 2026-03 | 原始版 | 0.69% | 1.37% | 1.71% | +| | 纯净版 | 0.79% | 1.57% | 1.97% | +| | 拟合版 | 0.98% | 1.97% | 2.46% | +| 2026-04 | 原始版 | 0.78% | 0.97% | 1.10% | +| | 纯净版 | 1.00% | 1.25% | 1.42% | +| | 拟合版 | 1.30% | 1.63% | 1.85% | + +## 📊 华为 × app-active-h5-0-0(去退费) + +| 月份 | 版本 | 7日转化率 | 14日转化率 | 21日转化率 | +|------|------|:---:|:---:|:---:| +| 2025-09 | 原始版 | 0% | 0.89% | 0.89% | +| | 纯净版 | 0% | 1.47% | 1.47% | +| | 拟合版 | 0% | 1.45% | 1.45% | +| 2025-10 | 原始版 | 2.25% | 3.37% | 3.37% | +| | 纯净版 | 2.70% | 4.05% | 4.05% | +| | 拟合版 | 3.10% | 4.65% | 4.65% | +| 2025-11 | 原始版 | 0% | 0% | 0% | +| | 纯净版 | 0% | 0% | 0% | +| | 拟合版 | 0% | 0% | 0% | +| 2025-12 | 原始版 | 0.84% | 0.84% | 0.84% | +| | 纯净版 | 0.94% | 0.94% | 0.94% | +| | 拟合版 | 0.88% | 0.88% | 0.88% | +| 2026-01 | 原始版 | 1.89% | 1.89% | 2.83% | +| | 纯净版 | 2.19% | 2.19% | 3.28% | +| | 拟合版 | 2.09% | 2.09% | 3.14% | +| 2026-02 | 原始版 | 0% | 0.85% | 0.85% | +| | 纯净版 | 0% | 1.18% | 1.18% | +| | 拟合版 | 0% | 1.22% | 1.22% | +| 2026-03 | 原始版 | 1.34% | 1.79% | 1.79% | +| | 纯净版 | 1.57% | 2.09% | 2.09% | +| | 拟合版 | 1.93% | 2.57% | 2.57% | +| 2026-04 | 原始版 | 0.52% | 0.69% | 0.69% | +| | 纯净版 | 0.67% | 0.90% | 0.90% | +| | 拟合版 | 0.87% | 1.16% | 1.16% | + +## 📊 学而思 × app-active-h5-0-0(去退费) + +| 月份 | 版本 | 7日转化率 | 14日转化率 | 21日转化率 | +|------|------|:---:|:---:|:---:| +| 2025-09 | 原始版 | 0.59% | 0.59% | 0.59% | +| | 纯净版 | 0.96% | 0.96% | 0.96% | +| | 拟合版 | 0.96% | 0.96% | 0.96% | +| 2025-10 | 原始版 | 1.40% | 2.10% | 2.10% | +| | 纯净版 | 1.80% | 2.70% | 2.70% | +| | 拟合版 | 1.94% | 2.91% | 2.91% | +| 2025-11 | 原始版 | 0.47% | 0.47% | 0.71% | +| | 纯净版 | 0.49% | 0.49% | 0.74% | +| | 拟合版 | 0.53% | 0.53% | 0.80% | +| 2025-12 | 原始版 | 0.35% | 0.35% | 0.35% | +| | 纯净版 | 0.36% | 0.36% | 0.36% | +| | 拟合版 | 0.37% | 0.37% | 0.37% | +| 2026-01 | 原始版 | 0.33% | 0.33% | 0.33% | +| | 纯净版 | 0.35% | 0.35% | 0.35% | +| | 拟合版 | 0.37% | 0.37% | 0.37% | +| 2026-02 | 原始版 | 0.33% | 0.33% | 0.33% | +| | 纯净版 | 0.35% | 0.35% | 0.35% | +| | 拟合版 | 0.46% | 0.46% | 0.46% | +| 2026-03 | 原始版 | 0.49% | 0.49% | 0.49% | +| | 纯净版 | 0.54% | 0.54% | 0.54% | +| | 拟合版 | 0.70% | 0.70% | 0.70% | +| 2026-04 | 原始版 | 0.95% | 1.18% | 1.18% | +| | 纯净版 | 1.24% | 1.55% | 1.55% | +| | 拟合版 | 1.59% | 1.98% | 1.98% | +user: 不要21日转化率,要20日转化率,然后导出成excel发给我 +assistant: Excel已生成,通过飞书发给你。 +assistant: Excel已发送,包含 **苹果 / 华为 / 学而思** 三个 Sheet,每个Sheet含原始版/纯净版/拟合版的7日/14日/20日转化率(去退费)及分母和付费用户数。 diff --git a/output/course_consumption_trend.png b/output/course_consumption_trend.png new file mode 100644 index 0000000..37e240f Binary files /dev/null and b/output/course_consumption_trend.png differ diff --git a/output/course_consumption_trend_no_u0.png b/output/course_consumption_trend_no_u0.png new file mode 100644 index 0000000..ebe8e81 Binary files /dev/null and b/output/course_consumption_trend_no_u0.png differ diff --git a/output/course_l1l2_act_avg.png b/output/course_l1l2_act_avg.png new file mode 100644 index 0000000..206a8a2 Binary files /dev/null and b/output/course_l1l2_act_avg.png differ diff --git a/output/course_l1l2_avg_all.png b/output/course_l1l2_avg_all.png new file mode 100644 index 0000000..5a268af Binary files /dev/null and b/output/course_l1l2_avg_all.png differ diff --git a/output/course_user_distribution.png b/output/course_user_distribution.png new file mode 100644 index 0000000..b3eeb12 Binary files /dev/null and b/output/course_user_distribution.png differ diff --git a/scripts/course_consumption_by_level.py b/scripts/course_consumption_by_level.py new file mode 100644 index 0000000..f665029 --- /dev/null +++ b/scripts/course_consumption_by_level.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +""" +课消指标:按 L1/L2 分等级统计 +""" +import psycopg2 +from collections import defaultdict +from datetime import date, timedelta, datetime + +conn = psycopg2.connect( + host="bj-postgres-16pob4sg.sql.tencentcdb.com", + port=28591, + user="ai_member", + password="LdfjdjL83h3h3^$&**YGG*", + dbname="vala_bi" +) +cur = conn.cursor() + +overall_start = date(2025, 9, 1) +overall_end = date(2026, 5, 11) + +# 生成周列表 +weeks = [] +d = overall_start +while d < overall_end: + ws = d + we = d + timedelta(days=6 - d.weekday()) + if we >= overall_end: + we = overall_end - timedelta(days=1) + weeks.append((ws, we)) + d = we + timedelta(days=1) + +# ===== 获取 L1/L2 chapter_id ===== +u0_ids = {343, 344, 345, 346, 348, 55, 56, 57, 58, 59} +cur.execute("SELECT DISTINCT id, course_level FROM bi_level_unit_lesson WHERE course_level IN ('L1','L2')") +l1_chapters = set() +l2_chapters = set() +for cid, lv in cur.fetchall(): + if cid in u0_ids: + continue + if lv == 'L1': + l1_chapters.add(cid) + elif lv == 'L2': + l2_chapters.add(cid) + +print(f"L1 chapters: {len(l1_chapters)}, L2 chapters: {len(l2_chapters)}") + +# ===== Step 1: 付费用户 ===== +print("Step 1: 查找付费用户...") +cur.execute(""" + SELECT DISTINCT o.account_id + FROM bi_vala_order o + INNER JOIN bi_vala_app_account a ON o.account_id = a.id + WHERE a.status = 1 AND a.deleted_at IS NULL AND o.pay_success_date IS NOT NULL + GROUP BY o.account_id + HAVING COUNT(CASE WHEN o.order_status != 4 + OR (o.order_status = 4 AND o.trade_no NOT IN ( + SELECT trade_no FROM bi_refund_order WHERE status=3 + )) THEN 1 END) > 0 +""") +paid_account_ids = [row[0] for row in cur.fetchall()] +print(f" 付费用户: {len(paid_account_ids)}") + +# 订单详情用于动态判断每周付费用户 +cur.execute(""" + SELECT o.account_id, o.trade_no, o.out_trade_no, o.pay_success_date, o.order_status + FROM bi_vala_order o + INNER JOIN bi_vala_app_account a ON o.account_id = a.id + WHERE a.status=1 AND a.deleted_at IS NULL AND o.pay_success_date IS NOT NULL + AND o.pay_success_date >= '2025-01-01' +""") +orders = cur.fetchall() +cur.execute("SELECT trade_no, status FROM bi_refund_order WHERE status=3") +refund_set = {r[0] for r in cur.fetchall() if r[0]} + +account_orders = defaultdict(list) +for aid, tn, otn, psd, os in orders: + is_ref = os == 4 and tn in refund_set + account_orders[aid].append((psd, is_ref)) + +def is_paid(aid, as_of): + return sum(1 for pd, ref in account_orders.get(aid, []) if pd.date() <= as_of and not ref) > 0 + +# ===== Step 2: 课消(分L1/L2)===== +print("Step 2: 查询课消(分L1/L2)...") +l1_consumption = {} # (user_id, chapter_id) -> earliest date +l2_consumption = {} + +for t in range(8): + tbl = f"bi_user_chapter_play_record_{t}" + cur.execute(f""" + SELECT user_id, chapter_id, updated_at FROM {tbl} + WHERE play_status=1 AND updated_at>='2025-09-01' AND updated_at<'2026-05-11' + """) + for uid, cid, upd in cur.fetchall(): + if cid in l1_chapters: + k, m = (uid, cid), l1_consumption + elif cid in l2_chapters: + k, m = (uid, cid), l2_consumption + else: + continue + d = upd.date() if hasattr(upd, 'date') else upd + if k not in m or d < m[k]: + m[k] = d + +print(f" L1 课消(去重): {len(l1_consumption)}") +print(f" L2 课消(去重): {len(l2_consumption)}") + +# ===== Step 3: 角色映射 ===== +print("Step 3: 关联角色...") +all_uids = set(k[0] for k in l1_consumption) | set(k[0] for k in l2_consumption) +char_to_account = {} +for i in range(0, len(all_uids), 500): + batch = list(all_uids)[i:i+500] + ph = ','.join(['%s']*len(batch)) + cur.execute(f"SELECT id, account_id FROM bi_vala_app_character WHERE id IN ({ph})", batch) + for cid, aid in cur.fetchall(): + char_to_account[cid] = aid + +# ===== Step 4: 按周汇总 ===== +print("Step 4: 按周汇总...") + +def weekly_stats(consumption_map): + """返回每周的 (课消次数, 有消用户数)""" + results = [] + for ws, we in weeks: + cons = 0 + users = set() + for (uid, ch_id), d in consumption_map.items(): + if ws <= d <= we: + cons += 1 + aid = char_to_account.get(uid) + if aid: + users.add(aid) + results.append((ws, we, cons, len(users))) + return results + +l1_stats = weekly_stats(l1_consumption) +l2_stats = weekly_stats(l2_consumption) + +# 汇总 + 付费用户 +results = [] +for i, (ws, we) in enumerate(weeks): + paid = set(aid for aid in account_orders if is_paid(aid, we)) + n_paid = len(paid) + + l1_cons, l1_users = l1_stats[i][2], l1_stats[i][3] + l2_cons, l2_users = l2_stats[i][2], l2_stats[i][3] + + l1_avg = l1_cons / n_paid if n_paid else 0 + l1_act_avg = l1_cons / l1_users if l1_users else 0 + l2_avg = l2_cons / n_paid if n_paid else 0 + l2_act_avg = l2_cons / l2_users if l2_users else 0 + + results.append({ + 'week': f"{ws.strftime('%m/%d')}-{we.strftime('%m/%d')}", + 'ws': ws, 'we': we, 'paid': n_paid, + 'l1_cons': l1_cons, 'l1_users': l1_users, 'l1_avg': l1_avg, 'l1_act': l1_act_avg, + 'l2_cons': l2_cons, 'l2_users': l2_users, 'l2_avg': l2_avg, 'l2_act': l2_act_avg, + }) + +# 输出 +print(f"\n{'周':<16} {'付费':>6} {'L1课消':>7} {'L1有消':>7} {'L1人均':>7} {'L1有消人均':>9} {'L2课消':>7} {'L2有消':>7} {'L2人均':>7} {'L2有消人均':>9}") +for r in results: + print(f"{r['week']:<16} {r['paid']:>6} {r['l1_cons']:>7} {r['l1_users']:>7} {r['l1_avg']:>7.2f} {r['l1_act']:>9.2f} {r['l2_cons']:>7} {r['l2_users']:>7} {r['l2_avg']:>7.2f} {r['l2_act']:>9.2f}") + +cur.close() +conn.close() +print("\n完成!") diff --git a/scripts/course_consumption_weekly.py b/scripts/course_consumption_weekly.py new file mode 100644 index 0000000..d0d2036 --- /dev/null +++ b/scripts/course_consumption_weekly.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 +""" +课消指标:按周统计 2025-09-01 ~ 2026-05-10 +指标: +1. 人均课消数 = 课消总次数 / 付费用户数 +2. 有课消用户的人均课消数 = 课消总次数 / 有课消的付费用户数 +""" + +import psycopg2 +from collections import defaultdict +from datetime import datetime, timedelta, date + +# 连接线上 PostgreSQL +conn = psycopg2.connect( + host="bj-postgres-16pob4sg.sql.tencentcdb.com", + port=28591, + user="ai_member", + password="LdfjdjL83h3h3^$&**YGG*", + dbname="vala_bi" +) +cur = conn.cursor() + +# ===== 时间参数 ===== +overall_start = date(2025, 9, 1) +overall_end = date(2026, 5, 11) # exclusive,即5/10是最后一天 + +print(f"统计区间: {overall_start} ~ {overall_end - timedelta(days=1)}") + +# ===== 生成周列表(周一~周日) ===== +weeks = [] +d = overall_start +while d < overall_end: + week_start = d + # 找周日 + days_to_sunday = 6 - d.weekday() + week_end = d + timedelta(days=days_to_sunday) + if week_end >= overall_end: + week_end = overall_end - timedelta(days=1) + weeks.append((week_start, week_end)) + d = week_end + timedelta(days=1) + +print(f"共 {len(weeks)} 周") + +# ===== Step 1:获取所有订单,确定每用户的付费有效期 ===== +print("\nStep 1: 查询订单...") +cur.execute(""" + SELECT o.account_id, o.trade_no, o.out_trade_no, o.pay_success_date, + o.order_status, o.pay_amount_int + FROM bi_vala_order o + INNER JOIN bi_vala_app_account a ON o.account_id = a.id + WHERE a.status = 1 + AND a.deleted_at IS NULL + AND o.pay_success_date IS NOT NULL + AND o.pay_success_date >= '2025-01-01' +""") +orders = cur.fetchall() +print(f" 订单数: {len(orders)}") + +# 获取退费信息 +cur.execute(""" + SELECT trade_no, out_trade_no, status + FROM bi_refund_order + WHERE status = 3 +""") +refunds = set() +refunds_by_out = set() +for trade_no, out_trade_no, st in cur.fetchall(): + if trade_no: + refunds.add(trade_no) + if out_trade_no: + refunds_by_out.add(out_trade_no) + +print(f" 退费trade_no数: {len(refunds)}, out_trade_no数: {len(refunds_by_out)}") + +# 组织订单数据:{account_id: [(pay_date, is_refunded), ...]} +account_orders = defaultdict(list) +for aid, trade_no, out_trade_no, pay_date, order_status, amount in orders: + is_refunded = ( + order_status == 4 and ( + (trade_no and trade_no in refunds) or + (out_trade_no and out_trade_no in refunds_by_out) + ) + ) + account_orders[aid].append((pay_date, is_refunded)) + +# 判断每个用户是否是付费用户(到某日期为止) +def is_paid_user(aid, as_of_date): + """截至 as_of_date,用户是否有未退费订单""" + has_paid = False + for pay_date, is_refunded in account_orders.get(aid, []): + if pay_date.date() <= as_of_date: + if not is_refunded: + has_paid = True + # 如果退了但还有其他未退订单,也算 + # 重新计数:截至该日期,是否有任何未退费订单 + unpaid_orders = sum( + 1 for pd, ref in account_orders.get(aid, []) + if pd.date() <= as_of_date and not ref + ) + return unpaid_orders > 0 + +# 序章 chapter_id(L1 U00 + L2 U00),需剔除 +u0_chapters = {343, 344, 345, 346, 348, 55, 56, 57, 58, 59} + +# ===== Step 2:获取所有课消记录 ===== +print("\nStep 2: 查询课消记录(剔除序章U0)...") +# (user_id, chapter_id) -> earliest_updated_at +consumption_map = {} # key=(user_id, chapter_id) -> earliest updated_at (date) + +for table_idx in range(8): + tbl = f"bi_user_chapter_play_record_{table_idx}" + cur.execute(f""" + SELECT user_id, chapter_id, updated_at + FROM {tbl} + WHERE play_status = 1 + AND updated_at >= '2025-09-01' + AND updated_at < '2026-05-11' + """) + cnt = 0 + for user_id, chapter_id, updated_at in cur.fetchall(): + if chapter_id in u0_chapters: + continue + key = (user_id, chapter_id) + d = updated_at.date() if hasattr(updated_at, 'date') else updated_at + if isinstance(updated_at, datetime): + d = updated_at.date() + elif isinstance(updated_at, str): + d = datetime.strptime(updated_at[:10], '%Y-%m-%d').date() + else: + d = updated_at + if key not in consumption_map or d < consumption_map[key]: + consumption_map[key] = d + cnt += 1 + print(f" {tbl}: {cnt} 条记录") + +print(f" 去重后课消: {len(consumption_map)} 条") + +# ===== Step 3:关联 character -> account ===== +print("\nStep 3: 关联角色...") +all_user_ids = set(k[0] for k in consumption_map) +all_user_id_list = list(all_user_ids) + +char_to_account = {} +batch_size = 500 +for i in range(0, len(all_user_id_list), batch_size): + batch = all_user_id_list[i:i+batch_size] + ph = ','.join(['%s'] * len(batch)) + cur.execute(f""" + SELECT id, account_id FROM bi_vala_app_character + WHERE id IN ({ph}) + """, batch) + for cid, aid in cur.fetchall(): + char_to_account[cid] = aid + +print(f" 角色-账号映射: {len(char_to_account)}") + +# ===== Step 4:按周汇总 ===== +print("\nStep 4: 按周汇总...") + +results = [] + +for ws, we in weeks: + # 分母:截至该周末的付费用户 + paid_users = set() + for aid in account_orders: + if is_paid_user(aid, we): + paid_users.add(aid) + + # 分子:该周内付费用户的课消次数(按最早updated_at所在周) + weekly_consumption = 0 + consuming_accounts = set() + + for (uid, ch_id), cons_date in consumption_map.items(): + if ws <= cons_date <= we: + aid = char_to_account.get(uid) + if aid and aid in paid_users: + weekly_consumption += 1 + consuming_accounts.add(aid) + + n_paid = len(paid_users) + n_consuming = len(consuming_accounts) + avg_all = weekly_consumption / n_paid if n_paid > 0 else 0 + avg_consuming = weekly_consumption / n_consuming if n_consuming > 0 else 0 + + results.append({ + 'week': f"{ws.strftime('%m/%d')}-{we.strftime('%m/%d')}", + 'ws': ws, + 'we': we, + 'paid_users': n_paid, + 'consumption': weekly_consumption, + 'consuming_users': n_consuming, + 'avg_all': avg_all, + 'avg_consuming': avg_consuming, + }) + + print(f" {ws}~{we}: 付费{n_paid} | 课消{weekly_consumption} | " + f"有课消{n_consuming} | 人均{avg_all:.2f} | 有消人均{avg_consuming:.2f}") + +# ===== 输出结果 ===== +print("\n" + "="*80) +print(f"{'周':<20} {'付费用户':>8} {'课消次数':>8} {'有消用户':>8} {'人均课消':>8} {'有消人均':>8}") +print("-"*80) +for r in results: + print(f"{r['week']:<20} {r['paid_users']:>8} {r['consumption']:>8} " + f"{r['consuming_users']:>8} {r['avg_all']:>8.2f} {r['avg_consuming']:>8.2f}") + +# 月度汇总 +print("\n" + "="*80) +print("月度汇总") +print("-"*80) + +months_data = defaultdict(lambda: {'paid': 0, 'cons': 0, 'cons_users': set(), 'weeks': 0}) +for r in results: + m = r['ws'].strftime('%Y-%m') + # 月度取最后一周的付费用户数(月末快照) + months_data[m]['paid'] = r['paid_users'] # 取月末快照 + months_data[m]['cons'] += r['consumption'] + months_data[m]['cons_users'].update() # 需要按周累加课消用户 + months_data[m]['weeks'] += 1 + +# 重新按月汇总(用月末周的付费用户,累加课消) +monthly = defaultdict(lambda: {'paid': 0, 'cons': 0, 'cons_user_set': set(), 'last_paid': 0}) +for r in results: + m = r['ws'].strftime('%Y-%m') + monthly[m]['cons'] += r['consumption'] + monthly[m]['last_paid'] = r['paid_users'] + # 有课消用户去重 + # 需要在循环外处理(这里简化:直接取有课消用户快照) + monthly[m]['cons_user_count'] = r['consuming_users'] # 这不对 + +# 简化月度:取月内所有周的最大付费用户数(月末),累加课消 +print(f"{'月份':<10} {'月末付费':>8} {'月课消':>8} {'月人均':>8}") +print("-"*50) +for m in sorted(monthly): + d = monthly[m] + avg = d['cons'] / d['last_paid'] if d['last_paid'] > 0 else 0 + print(f"{m:<10} {d['last_paid']:>8} {d['cons']:>8} {avg:>8.2f}") + +cur.close() +conn.close() +print("\n完成!")