From ed6c864197985cd06710335e220f0a5b3f7a8a4f Mon Sep 17 00:00:00 2001 From: xiaoban Date: Sun, 24 May 2026 10:24:01 +0800 Subject: [PATCH] =?UTF-8?q?studytime-analysis:=20=E6=8A=A5=E5=91=8A?= =?UTF-8?q?=E5=BC=80=E5=A4=B4=E5=A2=9E=E5=8A=A0=E8=A7=92=E8=89=B2=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E4=BF=A1=E6=81=AF=EF=BC=88=E8=A7=92=E8=89=B2ID?= =?UTF-8?q?=E3=80=81=E8=B4=A6=E5=8F=B7ID=E3=80=81=E6=89=8B=E6=9C=BA?= =?UTF-8?q?=E5=8F=B7=E5=90=8E4=E4=BD=8D=E3=80=81=E5=90=8D=E5=AD=97?= =?UTF-8?q?=E3=80=81=E6=80=A7=E5=88=AB=E3=80=81=E5=B9=B4=E9=BE=84=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vala_skill_hashes | 2 +- skills/studytime-analysis/SKILL.md | 7 +- .../scripts/studytime_analysis.py | 123 +++++++++++++++++- 3 files changed, 126 insertions(+), 6 deletions(-) diff --git a/.vala_skill_hashes b/.vala_skill_hashes index f4ff1c6..8061291 100644 --- a/.vala_skill_hashes +++ b/.vala_skill_hashes @@ -7,4 +7,4 @@ lark_wiki_operate_as_bot 2a37701f568849f03eb46dd938baeda171380fe252b698ac8bda69c vala_git_workspace_backup 4cf352bec88fe84af065ba1ffcbb06647b77df0e01860faaf0bca9fd64b968ec cron-schedule b1879fa59d60e3d99cea1138674f7abac84a4aecd32743b801d41bfd6ed7181d study-analysis 33217dc132073ecd47b921800834f6df89494da9e7708fa90f15b3de7742e37f -studytime-analysis ab15a60381dd281a229004977d3137a9ac59bca048743069dc6e2875e1075682 +studytime-analysis 042178f9f7e97eeef366fd619403f4d0c8e0dce290abf17af662dddfa67c4e83 diff --git a/skills/studytime-analysis/SKILL.md b/skills/studytime-analysis/SKILL.md index f02d2bc..c44709d 100644 --- a/skills/studytime-analysis/SKILL.md +++ b/skills/studytime-analysis/SKILL.md @@ -38,8 +38,8 @@ python3 skills/studytime-analysis/scripts/studytime_analysis.py ## 数据库说明 -- **数据源**: PostgreSQL Online (`vala` 库) -- **核心表**: `user_chapter_play_record_0~7`(完课记录,8张分表) +- **完课数据源**: PostgreSQL Online (`vala` 库) — `user_chapter_play_record_0~7`(8张分表) +- **角色信息源**: MySQL Online (`vala_user` 库) — `vala_app_character` + `vala_app_account` - **筛选**: `play_status = 1`(已完成),查询全量数据(不按月过滤) - **寒暑假处理**: 1-2月(寒假)和 7-8月(暑假)的记录仅在一周时间分布中排除,跨周趋势和完课明细完整保留 - **注意**: 表名无 `bi_` 前缀;课程通过 `level` + `chapter_id` 字段展示 @@ -47,7 +47,8 @@ python3 skills/studytime-analysis/scripts/studytime_analysis.py ## 输出说明 脚本输出为 Markdown 格式的分析报告,可直接发送给用户。包含: -- 基本信息(角色ID、完课总数、寒暑假/非寒暑假数量) +- 角色基本信息(角色ID、账号ID、手机号后4位、角色名字、性别、年龄) +- 数据概况(完课总数、寒暑假/非寒暑假数量) - 📌 一周时间分布数据范围说明 - 一周时间分布表 + 规律总结(仅非寒暑假) - 跨周学习趋势表 + 趋势分析(全部数据) diff --git a/skills/studytime-analysis/scripts/studytime_analysis.py b/skills/studytime-analysis/scripts/studytime_analysis.py index afb28bc..294abdc 100644 --- a/skills/studytime-analysis/scripts/studytime_analysis.py +++ b/skills/studytime-analysis/scripts/studytime_analysis.py @@ -12,6 +12,7 @@ import os import sys import psycopg2 import psycopg2.extras +import pymysql from datetime import datetime, timedelta from collections import defaultdict, OrderedDict @@ -24,6 +25,16 @@ PG_CONFIG = { "dbname": os.environ.get("PG_DB_DATABASE", "vala"), } +# MySQL Online (vala_user 库 — 角色基本信息) +MYSQL_CONFIG = { + "host": os.environ.get("MYSQL_HOST_online", "bj-cdb-dh2fkqa0.sql.tencentcdb.com"), + "port": int(os.environ.get("MYSQL_PORT_online", "27751")), + "user": os.environ.get("MYSQL_USERNAME_online", "read_only"), + "password": os.environ.get("MYSQL_PASSWORD_online", ""), + "db": "vala_user", + "charset": "utf8mb4", +} + EXCLUDED_MONTHS = (1, 2, 7, 8) # 寒假1-2月, 暑假7-8月 WEEKDAY_NAMES = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] @@ -51,6 +62,92 @@ def get_connection(): return conn +def get_mysql_connection(): + """连接 MySQL Online (vala_user 库)""" + conn = pymysql.connect( + host=MYSQL_CONFIG["host"], + port=MYSQL_CONFIG["port"], + user=MYSQL_CONFIG["user"], + password=MYSQL_CONFIG["password"], + db=MYSQL_CONFIG["db"], + charset=MYSQL_CONFIG["charset"], + ) + return conn + + +def fetch_role_info(role_id): + """ + 从 MySQL vala_user 库查询角色基本信息 + 返回 dict: role_id, account_id, nickname, gender, age, phone_tail + """ + sql = """ + SELECT + c.id AS role_id, + c.account_id, + c.nickname, + c.gender, + c.birthday, + a.tel + FROM vala_app_character c + LEFT JOIN vala_app_account a ON c.account_id = a.id + WHERE c.id = %s + """ + conn = get_mysql_connection() + try: + with conn.cursor() as cur: + cur.execute(sql, (role_id,)) + row = cur.fetchone() + finally: + conn.close() + + if not row: + return None + + role_id_val, account_id, nickname, gender, birthday, tel = row + + # 性别映射 + gender_str = "" + if gender == 0: + gender_str = "女" + elif gender == 1: + gender_str = "男" + elif gender is not None: + gender_str = str(gender) + + # 年龄(从 birthday 推算) + age = "" + if birthday: + try: + # birthday 可能是 "2015-5-28" 或 "2015-05-28" 格式 + parts = str(birthday).split("-") + if len(parts) >= 1 and parts[0].isdigit(): + birth_year = int(parts[0]) + current_year = datetime.now().year + age = current_year - birth_year + except (ValueError, IndexError): + pass + + # 手机号后4位 + phone_tail = "" + if tel: + tel_str = str(tel) + # tel 可能是 "186****1625" 格式,取最后4位 + digits = ''.join(c for c in tel_str if c.isdigit()) + if len(digits) >= 4: + phone_tail = digits[-4:] + elif digits: + phone_tail = digits + + return { + "role_id": role_id_val, + "account_id": account_id, + "nickname": nickname or "", + "gender": gender_str, + "age": age, + "phone_tail": phone_tail, + } + + def fetch_completion_records(role_id): """查询指定角色全部完课记录(包含寒暑假)""" params = {} @@ -224,7 +321,7 @@ def analyze_weekly_trend(records): # ── 输出格式化 ──────────────────────────────────────── -def format_report(role_id, all_records, non_holiday_records, holiday_count, day_counts, weekday_periods, weeks_data, analysis): +def format_report(role_id, role_info, all_records, non_holiday_records, holiday_count, day_counts, weekday_periods, weeks_data, analysis): """生成 Markdown 格式分析报告 Args: @@ -239,6 +336,25 @@ def format_report(role_id, all_records, non_holiday_records, holiday_count, day_ lines.append(f"# 📊 学习时间分析报告 — 角色 {role_id}") lines.append(f"") + + # ── 角色基本信息 ── + if role_info: + lines.append(f"## 基本信息") + lines.append(f"") + lines.append(f"| 项目 | 详情 |") + lines.append(f"|------|------|") + lines.append(f"| 角色ID | {role_info['role_id']} |") + lines.append(f"| 账号ID | {role_info['account_id']} |") + if role_info['nickname']: + lines.append(f"| 角色名字 | {role_info['nickname']} |") + lines.append(f"| 性别 | {role_info['gender']} |") + if role_info['age']: + lines.append(f"| 年龄 | {role_info['age']} 岁 |") + if role_info['phone_tail']: + lines.append(f"| 账号手机号后4位 | {role_info['phone_tail']} |") + lines.append(f"") + + # ── 数据概况 ── lines.append(f"**分析时间**: {now_str}") lines.append(f"**完课记录总数**: {len(all_records)} 条") if holiday_count > 0: @@ -447,12 +563,15 @@ def main(): non_holiday_records, holiday_records = split_records(all_records) holiday_count = len(holiday_records) + # 角色基本信息(MySQL) + role_info = fetch_role_info(role_id) + # 一周分布分析:仅用非寒暑假数据 day_counts, weekday_periods = analyze_weekly_distribution(non_holiday_records) # 跨周趋势分析:用全部数据 weeks_data, analysis = analyze_weekly_trend(all_records) - report = format_report(role_id, all_records, non_holiday_records, holiday_count, day_counts, weekday_periods, weeks_data, analysis) + report = format_report(role_id, role_info, all_records, non_holiday_records, holiday_count, day_counts, weekday_periods, weeks_data, analysis) print(report)