🤖 每日自动备份 - 2026-04-15 08:00:01

This commit is contained in:
小溪 2026-04-15 08:00:01 +08:00
parent 04f99edcd9
commit 210e9125f0
4 changed files with 408 additions and 61 deletions

View File

@ -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

View File

@ -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()

View File

@ -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 "报表发送完成!"

View File

@ -3,12 +3,15 @@
# 执行时间每天9:00 # 执行时间每天9:00
# 输出Excel报表发送到指定群 # 输出Excel报表发送到指定群
# 配置环境变量
export PATH=/root/.nvm/versions/node/v24.14.0/bin:$PATH
# 配置参数 # 配置参数
DB_HOST="bj-postgres-16pob4sg.sql.tencentcdb.com" DB_HOST="bj-postgres-16pob4sg.sql.tencentcdb.com"
DB_PORT="28591" DB_PORT="28591"
DB_NAME="vala_bi" DB_NAME="vala_bi"
DB_USER="ai_member" DB_USER="ai_member"
DB_PASS="LdfjdjL83h3h3^$&**YGG*" DB_PASS='LdfjdjL83h3h3^$&**YGG*'
STAT_DATE=$(date +%Y-%m-%d -d "yesterday") STAT_DATE=$(date +%Y-%m-%d -d "yesterday")
TODAY=$(date +%Y%m%d) TODAY=$(date +%Y%m%d)
SQL_L1_OOPS="/root/.openclaw/workspace/skills/vala-component-practice-stat/scripts/stat_l1.sql" SQL_L1_OOPS="/root/.openclaw/workspace/skills/vala-component-practice-stat/scripts/stat_l1.sql"
@ -70,7 +73,7 @@ if [ $? -ne 0 ]; then
fi fi
# 3. 发送到指定群 # 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 if [ $? -ne 0 ]; then
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 消息发送失败" >> $LOG_FILE echo "[$(date +'%Y-%m-%d %H:%M:%S')] 消息发送失败" >> $LOG_FILE