diff --git a/memory/2026-04-14-request-timed-out-before-a-res.md b/memory/2026-04-14-request-timed-out-before-a-res.md new file mode 100644 index 0000000..47585b5 --- /dev/null +++ b/memory/2026-04-14-request-timed-out-before-a-res.md @@ -0,0 +1,161 @@ +# Session: 2026-04-14 10:24:24 UTC + +- **Session Key**: agent:main:feishu:direct:ou_e63ce6b760ad39382852472f28fbe2a2 +- **Session ID**: 15ebe62e-e42c-4c33-92a2-5b3b0d0bd2bb +- **Source**: feishu + +## Conversation Summary + +user: System: [2026-04-14 18:06:19 GMT+8] Feishu[default] DM | 李承龙 (ou_e63ce6b760ad39382852472f28fbe2a2) [msg:om_x100b52d5576d18b8b25a794cfc7ec8a] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b52d5576d18b8b25a794cfc7ec8a", + "sender_id": "ou_e63ce6b760ad39382852472f28fbe2a2", + "sender": "李承龙", + "timestamp": "Tue 2026-04-14 18:06 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李承龙 (ou_e63ce6b760ad39382852472f28fbe2a2)", + "id": "ou_e63ce6b760ad39382852472f28fbe2a2", + "name": "李承龙" +} +``` + +类似情况的,4月份购课的用户有多少 +assistant: ### 4月份购课用户多订单统计结果: +- 4月份总购课用户数:1150人 +- 有多笔订单的用户数:76人,占比 **6.61%** + - 2笔订单用户:63人 + - 3笔订单用户:11人 + - 4笔及以上订单用户:2人 + +如果你需要查看这部分多订单用户的详细明细或者按订单场景分类(比如同时购买L1+L2、多次购买不同课程等),可以随时告诉我~ +user: System: [2026-04-14 18:07:57 GMT+8] Feishu[xiaoxi] DM | 李承龙 (ou_e63ce6b760ad39382852472f28fbe2a2) [msg:om_x100b52d5510430a8b37c13a5c65cba8] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b52d5510430a8b37c13a5c65cba8", + "sender_id": "ou_e63ce6b760ad39382852472f28fbe2a2", + "sender": "李承龙", + "timestamp": "Tue 2026-04-14 18:07 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李承龙 (ou_e63ce6b760ad39382852472f28fbe2a2)", + "id": "ou_e63ce6b760ad39382852472f28fbe2a2", + "name": "李承龙" +} +``` + +这76人中有多少用户有未退费订单 +assistant: ### 统计结果: +76个有多笔订单的4月购课用户中,**有60个用户存在未退费的有效订单**,占比78.95%;剩下16个用户的所有订单均已全部退费。 + +需要我导出这60个有未退费订单用户的明细数据吗? +user: System: [2026-04-14 18:09:15 GMT+8] Feishu[default] DM | 李承龙 (ou_e63ce6b760ad39382852472f28fbe2a2) [msg:om_x100b52d56a6b148cb4bc2c2b2a4a701] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b52d56a6b148cb4bc2c2b2a4a701", + "sender_id": "ou_e63ce6b760ad39382852472f28fbe2a2", + "sender": "李承龙", + "timestamp": "Tue 2026-04-14 18:09 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李承龙 (ou_e63ce6b760ad39382852472f28fbe2a2)", + "id": "ou_e63ce6b760ad39382852472f28fbe2a2", + "name": "李承龙" +} +``` + +这些用户各有多少笔订单 +assistant: 多订单用户订单数量统计.xlsx +assistant: NO_REPLY +user: System: [2026-04-14 18:12:57 GMT+8] Feishu[default] DM | 李承龙 (ou_e63ce6b760ad39382852472f28fbe2a2) [msg:om_x100b52d57c5f4cb0b2b49c095c96fc4] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b52d57c5f4cb0b2b49c095c96fc4", + "sender_id": "ou_e63ce6b760ad39382852472f28fbe2a2", + "sender": "李承龙", + "timestamp": "Tue 2026-04-14 18:12 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李承龙 (ou_e63ce6b760ad39382852472f28fbe2a2)", + "id": "ou_e63ce6b760ad39382852472f28fbe2a2", + "name": "李承龙" +} +``` + +先确定有多少人有多笔订单(剔除测试账号),然后区分这些人里面有多少人存在未退费订单,有多少人已经全部退费,然后展示这些用户各有多少订单 +assistant: 4月多订单用户统计明细_剔除测试账号.xlsx +assistant: NO_REPLY +user: System: [2026-04-14 18:15:59 GMT+8] Feishu[default] DM | 李承龙 (ou_e63ce6b760ad39382852472f28fbe2a2) [msg:om_x100b52d573249880b2432ddf7ebc011] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b52d573249880b2432ddf7ebc011", + "sender_id": "ou_e63ce6b760ad39382852472f28fbe2a2", + "sender": "李承龙", + "timestamp": "Tue 2026-04-14 18:15 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李承龙 (ou_e63ce6b760ad39382852472f28fbe2a2)", + "id": "ou_e63ce6b760ad39382852472f28fbe2a2", + "name": "李承龙" +} +``` + +四月份退费订单有多少个,涉及多少用户,剔除测试账号 +assistant: 剔除测试账号后,4月份退费订单统计结果: +- 退费订单总数:**427笔** +- 涉及有效用户数:**400人** +user: System: [2026-04-14 18:21:16 GMT+8] Feishu[default] DM | 李承龙 (ou_e63ce6b760ad39382852472f28fbe2a2) [msg:om_x100b52d51f0d44acb21f6d7c9430d81] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b52d51f0d44acb21f6d7c9430d81", + "sender_id": "ou_e63ce6b760ad39382852472f28fbe2a2", + "sender": "李承龙", + "timestamp": "Tue 2026-04-14 18:21 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李承龙 (ou_e63ce6b760ad39382852472f28fbe2a2)", + "id": "ou_e63ce6b760ad39382852472f28fbe2a2", + "name": "李承龙" +} +``` + +这些人里有多少人有多笔订单,然后区分这些人里面有多少人存在未退费订单,有多少人已经全部退费,然后展示这些用户各有多少订单 +assistant: 4月退费用户多订单统计明细.xlsx +assistant: NO_REPLY diff --git a/scripts/april_refund_analysis.py b/scripts/april_refund_analysis.py new file mode 100644 index 0000000..2205cd3 --- /dev/null +++ b/scripts/april_refund_analysis.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +""" +四月份退费用户学习进度分析 - 生成Excel表格 +表头:账号ID、购课渠道、购课时间、退费时间、用户类型、 + L1U0L1~L2U0L5 各课时的(是否进入、进入时间、是否完成、完成时间) +""" + +import psycopg2 +import pandas as pd +from datetime import datetime +import os + +# 数据库连接 +PG_ONLINE = { + 'host': 'bj-postgres-16pob4sg.sql.tencentcdb.com', + 'port': 28591, + 'user': 'ai_member', + 'password': os.environ.get('PG_ONLINE_PASSWORD', 'LdfjdjL83h3h3^$&**YGG*'), + 'dbname': 'vala_bi' +} + +# 课时映射: L1U0L1~L1U0L5 (A1), L2U0L1~L2U0L5 (A2) +CHAPTER_MAP = { + 343: 'L1U0L1', 344: 'L1U0L2', 345: 'L1U0L3', 346: 'L1U0L4', 348: 'L1U0L5', + 55: 'L2U0L1', 56: 'L2U0L2', 57: 'L2U0L3', 58: 'L2U0L4', 59: 'L2U0L5', +} +CHAPTER_IDS = list(CHAPTER_MAP.keys()) +LESSON_ORDER = ['L1U0L1','L1U0L2','L1U0L3','L1U0L4','L1U0L5', + 'L2U0L1','L2U0L2','L2U0L3','L2U0L4','L2U0L5'] + +def get_connection(): + return psycopg2.connect(**PG_ONLINE) + +def fetch_refund_users(conn): + """获取四月退费用户及订单信息""" + sql = """ + WITH april_refund_accounts 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.order_status = 4 + AND o.updated_at >= '2026-04-01' AND o.updated_at < '2026-05-01' + AND o.created_at >= '2026-04-01' AND o.created_at < '2026-05-01' + ), + -- 每个用户所有四月订单 + user_all_orders AS ( + SELECT o.account_id, + COUNT(DISTINCT o.id) AS total_orders, + COUNT(DISTINCT CASE WHEN o.order_status = 4 THEN o.id END) AS refunded_orders, + COUNT(DISTINCT CASE WHEN o.order_status = 3 THEN o.id END) AS active_orders + FROM bi_vala_order o + JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 + WHERE o.account_id IN (SELECT account_id FROM april_refund_accounts) + AND o.order_status IN (3, 4) + AND o.created_at >= '2026-04-01' AND o.created_at < '2026-05-01' + GROUP BY o.account_id + ), + -- 取最晚购课时间的退费订单 + latest_order AS ( + SELECT DISTINCT ON (o.account_id) + o.account_id, + o.key_from, + o.sale_channel, + o.created_at AS purchase_time, + o.updated_at AS refund_time + FROM bi_vala_order o + JOIN bi_vala_app_account a ON o.account_id = a.id AND a.status = 1 + WHERE o.account_id IN (SELECT account_id FROM april_refund_accounts) + AND o.order_status = 4 + AND o.created_at >= '2026-04-01' AND o.created_at < '2026-05-01' + ORDER BY o.account_id, o.created_at DESC + ) + SELECT lo.account_id, lo.key_from, lo.sale_channel, lo.purchase_time, lo.refund_time, + uo.total_orders, uo.refunded_orders, uo.active_orders + FROM latest_order lo + JOIN user_all_orders uo ON lo.account_id = uo.account_id + ORDER BY lo.account_id; + """ + with conn.cursor() as cur: + cur.execute(sql) + columns = [desc[0] for desc in cur.description] + rows = cur.fetchall() + return pd.DataFrame(rows, columns=columns) + +def classify_user(row): + if row['total_orders'] == 1: + return '单订单退费' + elif row['active_orders'] > 0: + return '多订单未全退' + else: + return '多订单全退' + +def get_channel_name(key_from, sale_channel): + """直接返回key_from原始值""" + return key_from or '' + +def fetch_chapter_records(conn, account_ids): + """获取用户的课时学习记录""" + # 获取角色ID映射 + acct_str = ','.join(str(a) for a in account_ids) + sql_char = f""" + SELECT id AS character_id, account_id FROM bi_vala_app_character + WHERE account_id IN ({acct_str}) + """ + with conn.cursor() as cur: + cur.execute(sql_char) + char_rows = cur.fetchall() + + if not char_rows: + return {} + + char_to_account = {} + char_ids_by_table = {} + for cid, aid in char_rows: + char_to_account[cid] = aid + table_idx = cid % 8 + char_ids_by_table.setdefault(table_idx, []).append(cid) + + # 从各分表获取课时记录 + chapter_ids_str = ','.join(str(c) for c in CHAPTER_IDS) + all_records = [] + + for table_idx, cids in char_ids_by_table.items(): + cids_str = ','.join(str(c) for c in cids) + sql_rec = f""" + SELECT user_id, chapter_id, play_status, created_at + FROM bi_user_chapter_play_record_{table_idx} + WHERE user_id IN ({cids_str}) + AND chapter_id IN ({chapter_ids_str}) + ORDER BY user_id, chapter_id, created_at + """ + with conn.cursor() as cur: + cur.execute(sql_rec) + records = cur.fetchall() + all_records.extend(records) + + # 整理数据: account_id -> chapter_id -> {entered, enter_time, completed, complete_time} + result = {} + for user_id, chapter_id, play_status, created_at in all_records: + account_id = char_to_account.get(user_id) + if not account_id: + continue + + if account_id not in result: + result[account_id] = {} + if chapter_id not in result[account_id]: + result[account_id][chapter_id] = { + 'entered': True, + 'enter_time': created_at, + 'completed': False, + 'complete_time': None + } + else: + # 更新最早进入时间 + if created_at < result[account_id][chapter_id]['enter_time']: + result[account_id][chapter_id]['enter_time'] = created_at + + # 完成状态 + if play_status == 1: + if not result[account_id][chapter_id]['completed']: + result[account_id][chapter_id]['completed'] = True + result[account_id][chapter_id]['complete_time'] = created_at + else: + # 取最早完成时间 + if created_at < result[account_id][chapter_id]['complete_time']: + result[account_id][chapter_id]['complete_time'] = created_at + + return result + +def format_time(t): + if t is None: + return '' + if isinstance(t, datetime): + return t.strftime('%Y-%m-%d %H:%M:%S') + return str(t) + +def main(): + conn = get_connection() + + print("1. 获取退费用户订单信息...") + df_users = fetch_refund_users(conn) + print(f" 共{len(df_users)}个退费用户") + + # 分类用户 + df_users['用户类型'] = df_users.apply(classify_user, axis=1) + df_users['购课渠道'] = df_users.apply(lambda r: get_channel_name(r['key_from'], r['sale_channel']), axis=1) + + print("2. 获取课时学习记录...") + account_ids = df_users['account_id'].tolist() + chapter_records = fetch_chapter_records(conn, account_ids) + print(f" 共{len(chapter_records)}个用户有学习记录") + + print("3. 组装表格...") + rows = [] + for _, user in df_users.iterrows(): + aid = user['account_id'] + row = { + '账号ID': aid, + '购课渠道': user['购课渠道'], + '购课时间': format_time(user['purchase_time']), + '退费时间': format_time(user['refund_time']), + '用户类型': user['用户类型'], + } + + user_chapters = chapter_records.get(aid, {}) + for lesson in LESSON_ORDER: + # 找到对应的chapter_id + ch_id = [k for k, v in CHAPTER_MAP.items() if v == lesson][0] + ch_data = user_chapters.get(ch_id, {}) + + row[f'{lesson}-是否进入'] = '是' if ch_data.get('entered', False) else '否' + row[f'{lesson}-进入时间'] = format_time(ch_data.get('enter_time')) + row[f'{lesson}-是否完成'] = '是' if ch_data.get('completed', False) else '否' + row[f'{lesson}-完成时间'] = format_time(ch_data.get('complete_time')) + + rows.append(row) + + df_result = pd.DataFrame(rows) + + # 导出Excel + output_path = '/tmp/openclaw/四月退费用户学习进度分析.xlsx' + os.makedirs('/tmp/openclaw', exist_ok=True) + + with pd.ExcelWriter(output_path, engine='openpyxl') as writer: + df_result.to_excel(writer, index=False, sheet_name='退费用户学习进度') + + # 调整列宽 + ws = writer.sheets['退费用户学习进度'] + for col in ws.columns: + max_length = max(len(str(cell.value or '')) for cell in col) + col_letter = col[0].column_letter + ws.column_dimensions[col_letter].width = min(max_length + 2, 25) + + print(f"4. 导出完成: {output_path}") + print(f" 总行数: {len(df_result)}") + print(f" 用户类型分布:") + print(df_result['用户类型'].value_counts().to_string()) + + conn.close() + +if __name__ == '__main__': + main() diff --git a/scripts/daily_oops_exit_rate_report.sh b/scripts/daily_oops_exit_rate_report.sh deleted file mode 100755 index 2939d0a..0000000 --- a/scripts/daily_oops_exit_rate_report.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash -# 每日组件Oops率和退出率报表生成与发送脚本 -# 执行时间:每天9:00,统计前一天的数据 - -set -e - -# 1. 计算统计日期(昨天) -STAT_DATE=$(date -d "yesterday" +"%Y-%m-%d") -REPORT_DATE=$(date +"%Y年%m月%d日") -REPORT_PATH="/tmp/组件Oops率与退出率报表_${STAT_DATE}.xlsx" - -echo "开始生成${REPORT_DATE}的组件Oops率与退出率报表..." - -# 2. 进入技能目录 -cd /root/.openclaw/workspace/skills/vala-component-oops-stat - -# 3. 统计L1 Oops率 -echo "正在统计L1组件Oops率..." -psql -h bj-postgres-16pob4sg.sql.tencentcdb.com -p 28591 -U ai_member -d vala_bi -f scripts/stat_l1.sql -v stat_date="'${STAT_DATE}'" -o /tmp/l1_oops.csv - -# 4. 统计L2 Oops率 -echo "正在统计L2组件Oops率..." -psql -h bj-postgres-16pob4sg.sql.tencentcdb.com -p 28591 -U ai_member -d vala_bi -f scripts/stat_l2.sql -v stat_date="'${STAT_DATE}'" -o /tmp/l2_oops.csv - -# 5. 统计L1退出率 -echo "正在统计L1退出率..." -# 退出率统计SQL -psql -h bj-postgres-16pob4sg.sql.tencentcdb.com -p 28591 -U ai_member -d vala_bi << EOF > /tmp/l1_exit.csv --- L1退出率统计逻辑待补充,后续根据实际口径修改 -SELECT '待补充' as component_name, 0 as exit_rate, 0 as user_count; -EOF - -# 6. 统计L2退出率 -echo "正在统计L2退出率..." -# 退出率统计SQL -psql -h bj-postgres-16pob4sg.sql.tencentcdb.com -p 28591 -U ai_member -d vala_bi << EOF > /tmp/l2_exit.csv --- L2退出率统计逻辑待补充,后续根据实际口径修改 -SELECT '待补充' as component_name, 0 as exit_rate, 0 as user_count; -EOF - -# 7. 生成Excel报表 -echo "正在生成Excel报表..." -python3 scripts/generate_excel.py --l1_oops /tmp/l1_oops.csv --l2_oops /tmp/l2_oops.csv --l1_exit /tmp/l1_exit.csv --l2_exit /tmp/l2_exit.csv --output "${REPORT_PATH}" - -# 8. 发送报表到飞书群 -echo "正在发送报表到飞书群..." -cd /root/.openclaw/workspace -python3 -c " -import sys -sys.path.append('/root/.openclaw/skills/lark-send-message-as-bot') -from send_message import send_file_to_group -send_file_to_group( - chat_id='oc_248d0efbc02d97d81d592d0417b6b296', - file_path='${REPORT_PATH}', - caption='📊 ${REPORT_DATE} 组件Oops率与退出率报表\n\n报表包含以下sheet:\n1. L1组件Oops率\n2. L2组件Oops率\n3. L1组件退出率\n4. L2组件退出率' -) -" - -echo "报表发送完成!" diff --git a/scripts/run_component_practice_stat_daily.sh b/scripts/run_component_practice_stat_daily.sh index cf33dc9..49e6d1b 100755 --- a/scripts/run_component_practice_stat_daily.sh +++ b/scripts/run_component_practice_stat_daily.sh @@ -3,12 +3,15 @@ # 执行时间:每天9:00 # 输出:Excel报表,发送到指定群 +# 配置环境变量 +export PATH=/root/.nvm/versions/node/v24.14.0/bin:$PATH + # 配置参数 DB_HOST="bj-postgres-16pob4sg.sql.tencentcdb.com" DB_PORT="28591" DB_NAME="vala_bi" DB_USER="ai_member" -DB_PASS="LdfjdjL83h3h3^$&**YGG*" +DB_PASS='LdfjdjL83h3h3^$&**YGG*' STAT_DATE=$(date +%Y-%m-%d -d "yesterday") TODAY=$(date +%Y%m%d) SQL_L1_OOPS="/root/.openclaw/workspace/skills/vala-component-practice-stat/scripts/stat_l1.sql" @@ -70,7 +73,7 @@ if [ $? -ne 0 ]; then fi # 3. 发送到指定群 -openclaw message send --channel feishu --target $TARGET_CHAT --message "【每日自动统计】$(date +%Y年%m月%d日)组件练习质量统计结果(统计日期:${STAT_DATE},包含Oops率和退出率双指标),共4个sheet,请查收。" --media $OUTPUT_EXCEL 2>> $LOG_FILE +/root/.nvm/versions/node/v24.14.0/bin/openclaw message send --channel feishu --target $TARGET_CHAT --message "【每日自动统计】$(date +%Y年%m月%d日)组件练习质量统计结果(统计日期:${STAT_DATE},包含Oops率和退出率双指标),共4个sheet,请查收。" --media $OUTPUT_EXCEL 2>> $LOG_FILE if [ $? -ne 0 ]; then echo "[$(date +'%Y-%m-%d %H:%M:%S')] 消息发送失败" >> $LOG_FILE