diff --git a/.shared_skill_hashes b/.shared_skill_hashes index c5ff446..f6b1bac 100644 --- a/.shared_skill_hashes +++ b/.shared_skill_hashes @@ -43,3 +43,4 @@ lark-workflow-meeting-summary 7479f4b43979627fde453c48b5b50970a81030d4c74d62185f feishu-group-monitor 8c3a459ae1383d51bc43e75e9ec39c910eb14406bda4d8c7875270058f1d3d15 lark-send-message-as-bot 5502e41e032a1b86adc91ebe42285735b12d17b9a6be8f5acb922a8a4fa6dad6 gpt-image-2-generator e1842d74b29c63ba218764e8156a50a47aa0f3d7be540af54d6d2db9d59c7382 +vala-data-export 10b848c9c91084e4a8aab3c3c917225a07ceeaa5510537b40d0f7b5d2a40412f diff --git a/output/知识巩固_音频_23600_20260625_114933.xlsx b/output/知识巩固_音频_23600_20260625_114933.xlsx new file mode 100644 index 0000000..3cab6a5 Binary files /dev/null and b/output/知识巩固_音频_23600_20260625_114933.xlsx differ diff --git a/output/知识巩固_音频_23600_20260625_115353.xlsx b/output/知识巩固_音频_23600_20260625_115353.xlsx new file mode 100644 index 0000000..4e05066 Binary files /dev/null and b/output/知识巩固_音频_23600_20260625_115353.xlsx differ diff --git a/output/知识巩固_音频_23600_20260625_115549.xlsx b/output/知识巩固_音频_23600_20260625_115549.xlsx new file mode 100644 index 0000000..deb5dd7 Binary files /dev/null and b/output/知识巩固_音频_23600_20260625_115549.xlsx differ diff --git a/output/知识巩固_音频_23600_20260625_122855.xlsx b/output/知识巩固_音频_23600_20260625_122855.xlsx new file mode 100644 index 0000000..f693f55 Binary files /dev/null and b/output/知识巩固_音频_23600_20260625_122855.xlsx differ diff --git a/scripts/export_review_audio.py b/scripts/export_review_audio.py index cf1ecf0..76639f5 100644 --- a/scripts/export_review_audio.py +++ b/scripts/export_review_audio.py @@ -56,7 +56,8 @@ with pg_conn.cursor(cursor_factory=RealDictCursor) as cur: """, (user_ids,)) review_rows = cur.fetchall() -# Parse question_list JSON for readable summary +# Parse question_list JSON — explode each question into its own row +expanded_rows = [] # one row per question for row in review_rows: ql = row["question_list"] if isinstance(ql, str): @@ -64,17 +65,34 @@ for row in review_rows: ql = json.loads(ql) except: pass - questions = [] + base = { + "角色ID": row["user_id"], + "Level": row["level"], + "Story ID": row["story_id"], + "Chapter ID": row["chapter_id"], + "Unique ID": row["unique_id"], + "更新时间": str(row["updated_at"]), + } if isinstance(ql, list): for item in ql: if isinstance(item, dict): q = item.get("question", {}) qtype = q.get("type", "") qtitle = q.get("title", "") - user_answer = item.get("userAnswer", "") - score = item.get("score", "") - questions.append(f"[{qtype}] {qtitle} | 回答: {user_answer} | 得分: {score}") - row["question_summary"] = "\n".join(questions) + is_right = item.get("isRight") + status = "正确" if is_right is True else "错误" + audio_url = item.get("userAudio", "") + user_answer = item.get("userAnswer", "") or item.get("userAnswers", "") + if isinstance(user_answer, list): + user_answer = ", ".join(str(a) for a in user_answer) + expanded_rows.append({ + **base, + "题目类型": qtype, + "题目内容": qtitle, + "题目对错": status, + "音频URL": audio_url, + "用户答案": user_answer, + }) row["question_count"] = len(ql) if isinstance(ql, list) else 0 pg_conn.close() @@ -149,26 +167,25 @@ from openpyxl.styles import Font, Alignment, PatternFill wb = Workbook() -# Sheet 1: 课程巩固记录 +# Sheet 1: 课程巩固记录(每题一行) ws1 = wb.active ws1.title = "课程巩固记录" + +# Build DataFrame from expanded_rows review_data = [] -for row in review_rows: +for row in expanded_rows: review_data.append({ - "角色ID": row["user_id"], - "Level": row["level"], - "Story ID": row["story_id"], - "Chapter ID": row["chapter_id"], - "Unique ID": row["unique_id"], - "得分": row["score"], - "评级": row["score_text"], - "SP值": row["sp_value"], - "经验值": row["exp"], - "题目数": row["question_count"], - "耗时(秒)": row["play_time"], - "题目详情": row["question_summary"], - "更新时间": str(row["updated_at"]), - "创建时间": str(row["created_at"]), + "角色ID": row["角色ID"], + "Level": row["Level"], + "Story ID": row["Story ID"], + "Chapter ID": row["Chapter ID"], + "Unique ID": row["Unique ID"], + "题目类型": row["题目类型"], + "题目内容": row["题目内容"], + "题目对错": row["题目对错"], + "音频URL": row["音频URL"], + "用户答案": row["用户答案"], + "更新时间": row["更新时间"], }) df1 = pd.DataFrame(review_data) @@ -186,8 +203,16 @@ for cell in ws1[1]: # Column widths ws1.column_dimensions["A"].width = 10 -ws1.column_dimensions["L"].width = 12 -ws1.column_dimensions["M"].width = 60 +ws1.column_dimensions["B"].width = 8 +ws1.column_dimensions["C"].width = 12 +ws1.column_dimensions["D"].width = 12 +ws1.column_dimensions["E"].width = 36 +ws1.column_dimensions["F"].width = 24 +ws1.column_dimensions["G"].width = 30 +ws1.column_dimensions["H"].width = 10 +ws1.column_dimensions["I"].width = 55 +ws1.column_dimensions["J"].width = 30 +ws1.column_dimensions["K"].width = 22 # Sheet 2: 音频数据 ws2 = wb.create_sheet("音频数据") @@ -244,13 +269,15 @@ ws3["A3"] = "导出时间" ws3["B3"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") ws3["A4"] = "角色ID" ws3["B4"] = ", ".join(str(u) for u in user_ids) -ws3["A5"] = "课程巩固记录数" -ws3["B5"] = len(review_rows) -ws3["A6"] = "音频记录数" -ws3["B6"] = len(audio_rows) +ws3["A5"] = "课程巩固记录数(题目拆分行)" +ws3["B5"] = len(expanded_rows) +ws3["A6"] = "原始巩固回合数" +ws3["B6"] = len(review_rows) +ws3["A7"] = "音频记录数" +ws3["B7"] = len(audio_rows) # Per-user breakdown -row_offset = 8 +row_offset = 10 ws3[f"A{row_offset}"] = "按角色统计" ws3[f"A{row_offset}"].font = Font(bold=True) row_offset += 1 @@ -284,6 +311,83 @@ ws3.column_dimensions["D"].width = 28 wb.save(output_path) print(f"\n✅ 导出完成: {output_path}") -print(f" Sheet 1 — 课程巩固记录: {len(review_rows)} 行") +print(f" Sheet 1 — 课程巩固记录(每题一行): {len(expanded_rows)} 行") print(f" Sheet 2 — 音频数据: {len(audio_rows)} 行") print(f" Sheet 3 — 汇总") + +# ── 4. 自动通过飞书发送文件 ────────────────────────── +# 默认发送给李若松 (user_id: 4aagb443) +# 可通过环境变量 FEISHU_SEND_TO 覆盖目标 user_id +SEND_TO = os.environ.get("FEISHU_SEND_TO", "4aagb443") +SEND_ENABLED = os.environ.get("FEISHU_SEND_ENABLED", "1") + +if SEND_ENABLED != "1": + print("\n⏭️ 飞书自动发送已禁用 (FEISHU_SEND_ENABLED != 1)") + sys.exit(0) + +print(f"\n[4/4] 通过飞书发送文件到 user_id={SEND_TO}...") + +FEISHU_APP_ID = g("FEISHU_APP_ID") +FEISHU_APP_SECRET = g("FEISHU_APP_SECRET") + +if not FEISHU_APP_ID or not FEISHU_APP_SECRET: + print(" ⚠️ 未找到 FEISHU_APP_ID/FEISHU_APP_SECRET,跳过发送") + sys.exit(0) + +def feishu_api(method, path, **kwargs): + """调用飞书 Open API,返回解析后的 JSON""" + cmd = ["curl", "-sk", "--connect-timeout", "10", "--max-time", "60"] + if method == "POST": + cmd += ["-X", "POST"] + cmd += ["-H", f"Authorization: Bearer {kwargs.get('token', '')}"] + if "json_data" in kwargs: + cmd += ["-H", "Content-Type: application/json", "-d", json.dumps(kwargs["json_data"])] + if "form_fields" in kwargs: + for key, val in kwargs["form_fields"].items(): + cmd += ["-F", f"{key}={val}"] + cmd.append(f"https://open.feishu.cn{path}") + r = subprocess.run(cmd, capture_output=True, text=True, timeout=65) + try: + return json.loads(r.stdout) + except: + print(f" ❌ API 返回解析失败: {r.stdout[:200]}") + return {"code": -1, "msg": r.stdout[:200]} + +# Step 1: 获取 tenant_access_token +print(" → 获取 tenant_access_token...") +token_resp = feishu_api("POST", "/open-apis/auth/v3/tenant_access_token/internal", + json_data={"app_id": FEISHU_APP_ID, "app_secret": FEISHU_APP_SECRET}) +token = token_resp.get("tenant_access_token") +if not token: + print(f" ❌ 获取 token 失败: {token_resp}") + sys.exit(1) +print(f" → Token 获取成功") + +# Step 2: 上传文件 +file_name = os.path.basename(output_path) +file_ext = os.path.splitext(output_path)[1].lower() +file_type_map = {".xlsx": "xls", ".xls": "xls", ".pdf": "pdf", ".docx": "doc", ".pptx": "ppt"} +file_type = file_type_map.get(file_ext, "stream") + +print(f" → 上传文件: {file_name} (type={file_type})...") +upload_resp = feishu_api("POST", "/open-apis/im/v1/files", token=token, + form_fields={"file_type": file_type, "file_name": file_name, "file": f"@{output_path}"}) +file_key = upload_resp.get("data", {}).get("file_key") +if not file_key: + print(f" ❌ 文件上传失败: {upload_resp}") + sys.exit(1) +print(f" → 上传成功: {file_key}") + +# Step 3: 发送文件消息 +print(f" → 发送文件到 user_id={SEND_TO}...") +send_resp = feishu_api("POST", "/open-apis/im/v1/messages?receive_id_type=user_id", token=token, + json_data={ + "receive_id": SEND_TO, + "msg_type": "file", + "content": json.dumps({"file_key": file_key}) + }) +msg_id = send_resp.get("data", {}).get("message_id") +if msg_id: + print(f" ✅ 文件已发送,message_id={msg_id}") +else: + print(f" ⚠️ 发送可能失败: {send_resp}")