#!/usr/bin/env python3 """弹窗策略数据分析 V2:仅端内购课用户 + 注册→购课天数分布""" import os import psycopg2 import psycopg2.extras def get_conn(): return psycopg2.connect( host="bj-postgres-16pob4sg.sql.tencentcdb.com", port=28591, user="ai_member", password=os.environ["PG_ONLINE_PASSWORD"], dbname="vala_bi" ) def run_sql(sql): conn = get_conn() cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) cur.execute(sql) rows = cur.fetchall() cur.close() conn.close() return rows # ── 端内过滤条件 ── ENDPOINT_INNER = "o.key_from IN ('app-active-h5-0-0', 'app-sales-bj-qhm-0')" GOODS_FILTER = "o.goods_id IN (57, 60, 63, 31, 32, 33, 54, 61)" ORDER_STATUS_FILTER = "o.order_status IN (3, 4)" # ====================================================================== print("=" * 70) print("分析零:注册→购课天数分布(端内付费用户)") print("=" * 70) sql0 = f""" SELECT o.pay_success_date::date - a.created_at::date AS days_to_purchase, COUNT(DISTINCT o.account_id) AS user_count FROM bi_vala_order o JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 WHERE o.pay_success_date IS NOT NULL AND o.order_status IN (3, 4) AND o.goods_id IN (57, 60, 63, 31, 32, 33, 54, 61) AND o.key_from IN ('app-active-h5-0-0', 'app-sales-bj-qhm-0') GROUP BY days_to_purchase ORDER BY days_to_purchase; """ rows0 = run_sql(sql0) total_users0 = sum(r['user_count'] for r in rows0) cum = 0 print(f"\n{'天数':>6} {'人数':>8} {'占比':>8} {'累计占比':>10}") print("-" * 38) for r in rows0: cum += r['user_count'] pct = 100.0 * r['user_count'] / total_users0 cum_pct = 100.0 * cum / total_users0 print(f"{r['days_to_purchase']:>6} {r['user_count']:>8} {pct:>7.1f}% {cum_pct:>9.1f}%") print(f"\n端内付费用户总数: {total_users0}") # ── 分段统计 ── sql0b = f""" SELECT CASE WHEN days_to_purchase = 0 THEN '当天购买' WHEN days_to_purchase BETWEEN 1 AND 3 THEN '1-3天' WHEN days_to_purchase BETWEEN 4 AND 7 THEN '4-7天' WHEN days_to_purchase BETWEEN 8 AND 14 THEN '8-14天' WHEN days_to_purchase BETWEEN 15 AND 30 THEN '15-30天' ELSE '30天以上' END AS day_bucket, COUNT(DISTINCT account_id) AS user_count, ROUND(100.0 * COUNT(DISTINCT account_id) / SUM(COUNT(DISTINCT account_id)) OVER(), 1) AS pct FROM ( SELECT o.pay_success_date::date - a.created_at::date AS days_to_purchase, o.account_id FROM bi_vala_order o JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 WHERE o.pay_success_date IS NOT NULL AND o.order_status IN (3, 4) AND o.goods_id IN (57, 60, 63, 31, 32, 33, 54, 61) AND o.key_from IN ('app-active-h5-0-0', 'app-sales-bj-qhm-0') ) gap GROUP BY day_bucket ORDER BY MIN(gap.days_to_purchase); """ rows0b = run_sql(sql0b) print(f"\n{'时间段':<12} {'人数':>8} {'占比':>8}") print("-" * 32) cum2 = 0 for r in rows0b: cum2 += r['user_count'] print(f"{r['day_bucket']:<12} {r['user_count']:>8} {r['pct']:>7}%") # ====================================================================== print("\n" + "=" * 70) print("分析一:购买节点分布(仅端内付费用户)") print("=" * 70) sql1 = f""" WITH u00_lessons AS ( SELECT id AS chapter_id, course_level, CASE course_lesson WHEN 'L01' THEN 1 WHEN 'L02' THEN 2 WHEN 'L03' THEN 3 WHEN 'L04' THEN 4 WHEN 'L05' THEN 5 END AS lesson_order FROM bi_level_unit_lesson WHERE course_unit = 'U00' ), paid_orders AS ( SELECT DISTINCT o.account_id, o.pay_success_date::date AS pay_date, o.goods_id FROM bi_vala_order o JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 WHERE o.pay_success_date IS NOT NULL AND {ORDER_STATUS_FILTER} AND {GOODS_FILTER} AND {ENDPOINT_INNER} ), paid_characters AS ( SELECT c.id AS character_id, po.account_id, po.pay_date, po.goods_id FROM paid_orders po JOIN bi_vala_app_character c ON po.account_id = c.account_id ), u00_done AS ( SELECT pc.account_id, pc.pay_date, pc.goods_id, l.lesson_order, l.course_level FROM paid_characters pc JOIN ( SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_0 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_1 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_2 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_3 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_4 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_5 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_6 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_7 WHERE play_status = 1 ) p ON pc.character_id = p.user_id JOIN u00_lessons l ON p.chapter_id = l.chapter_id WHERE p.created_at::date <= pc.pay_date GROUP BY pc.account_id, pc.pay_date, pc.goods_id, l.lesson_order, l.course_level ), best_progress AS ( SELECT DISTINCT ON (account_id, pay_date, goods_id) account_id, pay_date, goods_id, lesson_order FROM u00_done ORDER BY account_id, pay_date, goods_id, lesson_order DESC ) SELECT CASE WHEN lesson_order IS NULL THEN '学习前购买' WHEN lesson_order = 1 THEN 'L01之后' WHEN lesson_order = 2 THEN 'L02之后' WHEN lesson_order = 3 THEN 'L03之后' WHEN lesson_order = 4 THEN 'L04之后' WHEN lesson_order = 5 THEN 'L05之后' END AS purchase_node, CASE WHEN goods_id IN (57, 60, 63) THEN 'L1' WHEN goods_id IN (31, 32, 33, 54) THEN 'L2' WHEN goods_id = 61 THEN 'L1+L2' END AS product_level, COUNT(*) AS order_count, COUNT(DISTINCT account_id) AS user_count FROM best_progress GROUP BY purchase_node, product_level UNION ALL SELECT '学习前购买' AS purchase_node, CASE WHEN po.goods_id IN (57, 60, 63) THEN 'L1' WHEN po.goods_id IN (31, 32, 33, 54) THEN 'L2' WHEN po.goods_id = 61 THEN 'L1+L2' END AS product_level, COUNT(*) AS order_count, COUNT(DISTINCT po.account_id) AS user_count FROM ( SELECT DISTINCT o.account_id, o.pay_success_date::date AS pay_date, o.goods_id FROM bi_vala_order o JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 WHERE o.pay_success_date IS NOT NULL AND {ORDER_STATUS_FILTER} AND {GOODS_FILTER} AND {ENDPOINT_INNER} ) po LEFT JOIN best_progress bp ON po.account_id = bp.account_id AND po.pay_date = bp.pay_date AND po.goods_id = bp.goods_id WHERE bp.account_id IS NULL GROUP BY CASE WHEN po.goods_id IN (57, 60, 63) THEN 'L1' WHEN po.goods_id IN (31, 32, 33, 54) THEN 'L2' WHEN po.goods_id = 61 THEN 'L1+L2' END ORDER BY product_level, purchase_node; """ rows1 = run_sql(sql1) print(f"\n{'购买节点':<14} {'产品':<8} {'订单数':>8} {'用户数':>8}") print("-" * 44) for r in rows1: print(f"{r['purchase_node']:<14} {r['product_level']:<8} {r['order_count']:>8} {r['user_count']:>8}") # ── 汇总 ── sql1b = f""" WITH u00_lessons AS ( SELECT id AS chapter_id, CASE course_lesson WHEN 'L01' THEN 1 WHEN 'L02' THEN 2 WHEN 'L03' THEN 3 WHEN 'L04' THEN 4 WHEN 'L05' THEN 5 END AS lesson_order FROM bi_level_unit_lesson WHERE course_unit = 'U00' ), paid_orders AS ( SELECT DISTINCT o.account_id, o.pay_success_date::date AS pay_date FROM bi_vala_order o JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 WHERE o.pay_success_date IS NOT NULL AND {ORDER_STATUS_FILTER} AND {GOODS_FILTER} AND {ENDPOINT_INNER} ), paid_characters AS ( SELECT c.id AS character_id, po.account_id, po.pay_date FROM paid_orders po JOIN bi_vala_app_character c ON po.account_id = c.account_id ), u00_done AS ( SELECT pc.account_id, pc.pay_date, l.lesson_order FROM paid_characters pc JOIN ( SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_0 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_1 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_2 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_3 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_4 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_5 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_6 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_7 WHERE play_status = 1 ) p ON pc.character_id = p.user_id JOIN u00_lessons l ON p.chapter_id = l.chapter_id WHERE p.created_at::date <= pc.pay_date GROUP BY pc.account_id, pc.pay_date, l.lesson_order ), best_progress AS ( SELECT DISTINCT ON (account_id, pay_date) account_id, pay_date, lesson_order FROM u00_done ORDER BY account_id, pay_date, lesson_order DESC ) SELECT CASE WHEN lesson_order IS NULL THEN '学习前购买' WHEN lesson_order = 1 THEN 'L01之后' WHEN lesson_order = 2 THEN 'L02之后' WHEN lesson_order = 3 THEN 'L03之后' WHEN lesson_order = 4 THEN 'L04之后' WHEN lesson_order = 5 THEN 'L05之后' END AS purchase_node, COUNT(*) AS order_count, COUNT(DISTINCT account_id) AS user_count FROM best_progress GROUP BY purchase_node UNION ALL SELECT '学习前购买', COUNT(*), COUNT(DISTINCT po.account_id) FROM ( SELECT DISTINCT o.account_id, o.pay_success_date::date AS pay_date FROM bi_vala_order o JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 WHERE o.pay_success_date IS NOT NULL AND {ORDER_STATUS_FILTER} AND {GOODS_FILTER} AND {ENDPOINT_INNER} ) po LEFT JOIN best_progress bp ON po.account_id = bp.account_id AND po.pay_date = bp.pay_date WHERE bp.account_id IS NULL ORDER BY purchase_node; """ rows1b = run_sql(sql1b) print(f"\n{'购买节点':<14} {'订单数':>8} {'用户数':>8} {'占比':>8}") print("-" * 44) total_orders = sum(r['order_count'] for r in rows1b) for r in rows1b: pct = 100.0 * r['user_count'] / sum(x['user_count'] for x in rows1b) print(f"{r['purchase_node']:<14} {r['order_count']:>8} {r['user_count']:>8} {pct:>7.1f}%") print(f"\n端内付费总订单: {total_orders}") # ====================================================================== print("\n" + "=" * 70) print("分析二:峰值后用户 —— 学完U00但未购(端内购买为判断标准)") print("=" * 70) sql2 = f""" WITH u00_lessons AS ( SELECT id AS chapter_id, CASE course_lesson WHEN 'L01' THEN 1 WHEN 'L02' THEN 2 WHEN 'L03' THEN 3 WHEN 'L04' THEN 4 WHEN 'L05' THEN 5 END AS lesson_order FROM bi_level_unit_lesson WHERE course_unit = 'U00' ), user_u00_progress AS ( SELECT DISTINCT a.id AS account_id, l.lesson_order FROM bi_vala_app_account a JOIN bi_vala_app_character c ON a.id = c.account_id JOIN ( SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_0 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_1 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_2 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_3 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_4 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_5 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_6 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_7 WHERE play_status = 1 ) p ON c.id = p.user_id JOIN u00_lessons l ON p.chapter_id = l.chapter_id WHERE a.status = 1 AND a.deleted_at IS NULL ), user_max_progress AS ( SELECT account_id, MAX(lesson_order) AS max_lesson FROM user_u00_progress GROUP BY account_id ), u00_completers AS ( SELECT account_id FROM user_max_progress WHERE max_lesson = 5 ), -- 端内付费用户 paid_inner AS ( SELECT DISTINCT o.account_id FROM bi_vala_order o JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 WHERE o.pay_success_date IS NOT NULL AND o.order_status IN (3, 4) AND o.goods_id IN (57, 60, 63, 31, 32, 33, 54, 61) AND o.key_from IN ('app-active-h5-0-0', 'app-sales-bj-qhm-0') ) SELECT '学完U00全部5课' AS user_group, COUNT(*) AS total_users, COUNT(*) FILTER (WHERE pu.account_id IS NOT NULL) AS paid_users, COUNT(*) FILTER (WHERE pu.account_id IS NULL) AS unpaid_users, ROUND(100.0 * COUNT(*) FILTER (WHERE pu.account_id IS NOT NULL) / COUNT(*), 1) AS paid_rate FROM u00_completers uc LEFT JOIN paid_inner pu ON uc.account_id = pu.account_id; """ rows2 = run_sql(sql2) for r in rows2: print(f"\n总用户数: {r['total_users']}, 已端内付费: {r['paid_users']}, 未端内付费: {r['unpaid_users']}, 端内付费率: {r['paid_rate']}%") # ====================================================================== print("\n" + "=" * 70) print("分析三:各断点用户规模 + 端内付费率") print("=" * 70) sql3 = f""" WITH u00_lessons AS ( SELECT id AS chapter_id, course_level, CASE course_lesson WHEN 'L01' THEN 1 WHEN 'L02' THEN 2 WHEN 'L03' THEN 3 WHEN 'L04' THEN 4 WHEN 'L05' THEN 5 END AS lesson_order FROM bi_level_unit_lesson WHERE course_unit = 'U00' ), user_u00_progress AS ( SELECT a.id AS account_id, l.course_level, l.lesson_order FROM bi_vala_app_account a JOIN bi_vala_app_character c ON a.id = c.account_id JOIN ( SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_0 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_1 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_2 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_3 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_4 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_5 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_6 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_7 WHERE play_status = 1 ) p ON c.id = p.user_id JOIN u00_lessons l ON p.chapter_id = l.chapter_id WHERE a.status = 1 AND a.deleted_at IS NULL GROUP BY a.id, l.course_level, l.lesson_order ), user_max_per_level AS ( SELECT account_id, course_level, MAX(lesson_order) AS max_lesson FROM user_u00_progress GROUP BY account_id, course_level ), paid_inner AS ( SELECT DISTINCT o.account_id FROM bi_vala_order o JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 WHERE o.pay_success_date IS NOT NULL AND o.order_status IN (3, 4) AND o.goods_id IN (57, 60, 63, 31, 32, 33, 54, 61) AND o.key_from IN ('app-active-h5-0-0', 'app-sales-bj-qhm-0') ) SELECT course_level, max_lesson, COUNT(*) AS user_count, COUNT(*) FILTER (WHERE pu.account_id IS NOT NULL) AS paid_count, ROUND(100.0 * COUNT(*) FILTER (WHERE pu.account_id IS NOT NULL) / COUNT(*), 1) AS paid_rate FROM user_max_per_level um LEFT JOIN paid_inner pu ON um.account_id = pu.account_id GROUP BY course_level, max_lesson ORDER BY course_level, max_lesson; """ rows3 = run_sql(sql3) print(f"\n{'Level':<6} {'最大完成':<10} {'用户数':>8} {'端内付费':>10} {'端内付费率':>10}") print("-" * 48) for r in rows3: pct = 100.0 * r['paid_count'] / r['user_count'] print(f"{r['course_level']:<6} L{r['max_lesson']:02d} {r['user_count']:>8} {r['paid_count']:>10} {pct:>9.1f}%") # ====================================================================== print("\n" + "=" * 70) print("分析四:到达各lesson的端内付费转化率") print("=" * 70) sql4 = f""" WITH u00_lessons AS ( SELECT id AS chapter_id, course_level, CASE course_lesson WHEN 'L01' THEN 1 WHEN 'L02' THEN 2 WHEN 'L03' THEN 3 WHEN 'L04' THEN 4 WHEN 'L05' THEN 5 END AS lesson_order FROM bi_level_unit_lesson WHERE course_unit = 'U00' ), user_u00_progress AS ( SELECT a.id AS account_id, l.course_level, l.lesson_order FROM bi_vala_app_account a JOIN bi_vala_app_character c ON a.id = c.account_id JOIN ( SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_0 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_1 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_2 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_3 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_4 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_5 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_6 WHERE play_status = 1 UNION ALL SELECT user_id, chapter_id, created_at FROM bi_user_chapter_play_record_7 WHERE play_status = 1 ) p ON c.id = p.user_id JOIN u00_lessons l ON p.chapter_id = l.chapter_id WHERE a.status = 1 AND a.deleted_at IS NULL GROUP BY a.id, l.course_level, l.lesson_order ), paid_inner AS ( SELECT DISTINCT o.account_id FROM bi_vala_order o JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 WHERE o.pay_success_date IS NOT NULL AND o.order_status IN (3, 4) AND o.goods_id IN (57, 60, 63, 31, 32, 33, 54, 61) AND o.key_from IN ('app-active-h5-0-0', 'app-sales-bj-qhm-0') ) SELECT up.course_level, up.lesson_order AS reached_lesson, COUNT(DISTINCT up.account_id) AS reached_users, COUNT(DISTINCT pu.account_id) AS paid_users, ROUND(100.0 * COUNT(DISTINCT pu.account_id) / COUNT(DISTINCT up.account_id), 1) AS conversion_rate FROM user_u00_progress up LEFT JOIN paid_inner pu ON up.account_id = pu.account_id GROUP BY up.course_level, up.lesson_order ORDER BY up.course_level, up.lesson_order; """ rows4 = run_sql(sql4) print(f"\n{'Level':<6} {'到达':<10} {'到达人数':>8} {'端内付费':>10} {'端内转化率':>10}") print("-" * 48) for r in rows4: print(f"{r['course_level']:<6} L{r['reached_lesson']:02d} {r['reached_users']:>8} {r['paid_users']:>10} {r['conversion_rate']:>9}%") print("\n✅ 全部分析完成")