# 用户学情数据导出 — 数据表与业务逻辑 > 整理日期:2026-06-03 | 基于 `export-user-data` 技能 v1.5 --- ## 一、数据源总览 该技能跨 **4 个数据源**(ES + PostgreSQL + 2 个 MySQL 库),共涉及 **17+ 张表/索引**: | 序号 | 数据源 | 库/索引 | 表名 | 用途 | |------|--------|---------|------|------| | 1 | ES Online | `user-audio` | — | 用户全部音频数据 | | 2 | ES Online | `llm_asr_log` | — | ASR 识别结果回填(按 makee_id 关联) | | 3 | PG Online | `vala` | `user_component_play_record_0` ~ `user_component_play_record_7` | 互动组件学习记录(8 张分表) | | 4 | PG Online | `vala` | `user_unit_review_question_result` | 课程巩固记录 | | 5 | PG Online | `vala` | `user_unit_challenge_question_result` | 单元挑战记录 | | 6 | PG Online | `vala` | `user_unit_summary_km_result` | 单元总结记录 | | 7 | MySQL Test | `vala_test` | `vala_game_info` | story_id → unit_id 映射表 | | 8 | MySQL Test | `vala_test` | `vala_game_chapter` | chapter_id → lesson_id 映射表 | | 9 | MySQL Test | `vala_test` | `middle_interaction_component` | 中互动组件配置(mid_* 类型) | | 10 | MySQL Test | `vala_test` | `core_interaction_component` | 核心互动组件配置(core_* 类型) | | 11 | MySQL Online | `vala_user` | `vala_app_character` | 账户 ID → 角色 ID 映射 | --- ## 二、各数据源详细说明 ### 2.1 ES `user-audio` 索引 — 全部音频数据 **查询条件:** `userId == 目标角色ID`,最多返回 10000 条,按时间倒序排列。 **输出字段:** | 字段 | 说明 | |------|------| | `userId` | 角色 ID | | `userMsg` | 用户消息(ASR 识别结果或原始 JSON) | | `userName` | 用户名 | | `soeData` | 语音评测数据 | | `audioUrl` | 音频文件地址 | | `asrStatus` | ASR 识别状态 | | `componentId` | 关联的组件 ID | | `componentType` | 组件类型 | | `dataVersion` | 数据版本号 | | `timeStr` | 时间字符串 | | `source` | 数据来源(仅当命中 makee_id 回填时有值) | **特殊逻辑(v1.2 — makee_id 回填):** 当 `userMsg` 字段内容包含 `makee_id` 时: 1. 从 `userMsg` 中提取 `makee_id` 的值(支持 JSON 解析 + 正则兜底) 2. 用 `makee_id` 去 ES `llm_asr_log` 索引查询对应记录 3. 将查询到的 `result_text` 回填到 `userMsg` 字段 4. 将 `source` 字段输出到 `source` 列 如果 `userMsg` 不包含 `makee_id`,保持原始逻辑不变。 --- ### 2.2 PG `user_component_play_record_0~7` — 互动组件学习记录 **表结构:** 8 张分表,需 `UNION ALL` 合并查询。 **查询条件:** `user_id == 目标角色ID`,按 `updated_at` 倒序。 **输出字段:** | 字段 | 来源 | 说明 | |------|------|------| | `user_id` | PG 原始字段 | 角色 ID | | `component_unique_code` | PG 原始字段 | 组件唯一标识 | | `session_id` | PG 原始字段 | 会话 ID | | `c_type` | PG 原始字段 | 组件类型编码(如 `mid_vocab_item`) | | `c_id` | PG 原始字段 | 组件 ID | | `互动组件名称` | MySQL 映射 | 根据 c_type 查组件配置表映射 | | `组件标题` | MySQL 映射 | 组件配置中的 `title` | | `组件配置摘要` | MySQL 映射 | mid 取 `question`,core 取 `taskInfo` | | `知识点` | MySQL 映射 | 组件配置中的 `kp_relation_info` | | `play_result` | PG 原始字段 | 答题结果(Perfect/Good/Failed/Pass/Oops) | | `user_behavior_info` | PG 原始字段 | 用户行为信息 JSON | | `updated_at` | PG 原始字段 | 更新时间 | #### 组件名称映射规则(v1.5) **中互动组件(c_type 以 `mid_` 开头)→ 查 `middle_interaction_component` 表:** | c_type | 互动组件名称 | |--------|-------------| | `mid_vocab_item` | 物品互动 | | `mid_vocab_image` | 图片互动 | | `mid_vocab_fillBlank` | 填词互动 | | `mid_vocab_instruction` | 指令互动 | | `mid_sentence_dialogue` + `mode=express` | 对话互动-表达 | | `mid_sentence_dialogue` + `mode=read` | 对话互动-朗读 | | `mid_sentence_voice` | 语音互动 | | `mid_sentence_material` | 材料互动 | | `mid_sentence_makeSentence` | 造句互动 | | `mid_grammar_cloze` | 挖空互动 | | `mid_grammar_sentence` | 组句互动 | | `mid_pron_pron` | 发音互动 | **核心互动组件(c_type 以 `core_` 开头)→ 查 `core_interaction_component` 表:** | c_type | 互动组件名称 | |--------|-------------| | `core_speaking_reply` | 口语快答 | | `core_speaking_inquiry` | 口语妙问 | | `core_speaking_explore` | 口语探讨 | | `core_speaking_monologue` | 口语独白 | | `core_reading_order` | 合作阅读 | | `core_listening_order` | 合作听力 | | `core_writing_imgMakeSentence` | 看图组句 | | `core_writing_imgWrite` | 看图撰写 | | `core_writing_questionMakeSentence` | 问题组句 | | `core_writing_questionWrite` | 问题撰写 | **组件配置摘要取值规则:** - `mid_*`:取 `component_config` 中的 `question` 字段 - `core_*`:取 `component_config` 中的 `taskInfo` 字段 **知识点取值:** 直接取 `kp_relation_info` 字段(JSON 数组格式,每个元素含 `kpId`、`kpType`、`kpTitle`、`kpSkill`、`kpSkillName`) --- ### 2.3 PG `user_unit_review_question_result` — 课程巩固记录 **查询条件:** `user_id == 目标角色ID`,按 `updated_at` 倒序。 **输出字段:** | 字段 | 来源 | 说明 | |------|------|------| | `user_id` | PG 原始字段 | 角色 ID | | `unit_id` | story_id 映射 | 通过 `vala_game_info` 表将 `story_id` 映射为 `unit_id` | | `lesson_id` | chapter_id 映射 | 通过 `vala_game_chapter` 表将 `chapter_id` 映射为 `lesson_id` | | `question_list` | PG 原始字段 | 题目列表 JSON | | `正确率` | 计算字段 | `isRight==true 的数量 / 总题数 × 100%` | | `updated_at` | PG 原始字段 | 更新时间 | **正确率计算逻辑:** 解析 `question_list` JSON 数组,统计其中 `isRight == true` 的题目数,除以总题数,结果以百分比展示。 --- ### 2.4 PG `user_unit_challenge_question_result` — 单元挑战记录 **查询条件:** `user_id == 目标角色ID`,按 `updated_at` 倒序。 **输出字段:** | 字段 | 来源 | 说明 | |------|------|------| | `user_id` | PG 原始字段 | 角色 ID | | `unit_id` | story_id 映射 | 通过 `vala_game_info` 表映射 | | `category` | PG 原始字段 | 挑战类别 | | `score_text` | PG 原始字段 | 得分文本 | | `question_list` | PG 原始字段 | 题目列表 JSON | | `updated_at` | PG 原始字段 | 更新时间 | --- ### 2.5 PG `user_unit_summary_km_result` — 单元总结记录 **查询条件:** `user_id == 目标角色ID`,按 `updated_at` 倒序。 **输出字段:** | 字段 | 来源 | 说明 | |------|------|------| | `id` | PG 原始字段 | 记录 ID | | `user_id` | PG 原始字段 | 角色 ID | | `unit_id` | story_id 映射 | 通过 `vala_game_info` 表映射 | | `updated_at` | PG 原始字段 | 更新时间 | | `km_id` | PG 原始字段 | 知识点 ID | | `km_type` | PG 原始字段 | 知识点类型 | | `play_time_seconds` | 计算字段 | 播放时长(原始毫秒值 ÷ 1000) | --- ### 2.6 MySQL 映射表 #### `vala_game_info` — story_id → unit_id 映射 **查询逻辑:** `SELECT id, story_id FROM vala_game_info ORDER BY season_package_id ASC, index ASC` **映射规则:** 查询结果按排序后的索引位置(从 1 开始)作为 `unit_id`。 #### `vala_game_chapter` — chapter_id → lesson_id 映射 **查询逻辑:** `SELECT id, chapter_id, index FROM vala_game_chapter` **映射规则:** `chapter.index` 字段值即为 `lesson_id`。 #### `vala_app_character` — 账户 ID → 角色 ID 映射 **查询逻辑:** `SELECT id FROM vala_app_character WHERE account_id = <账户ID>` **使用场景:** 当使用 `--account-ids` 模式时,先查此表获取该账户下所有角色 ID,再逐个导出。 --- ## 三、Excel 输出结构(8 个 Sheet) | Sheet 序号 | Sheet 名称 | 数据来源 | |-----------|-----------|---------| | 1 | 全部音频数据 | ES `user-audio` 索引 | | 2 | 互动组件学习记录 | PG 8 张分表 + MySQL 组件配置 | | 3 | 课程巩固记录 | PG `user_unit_review_question_result` | | 4 | 单元挑战记录 | PG `user_unit_challenge_question_result` | | 5 | 单元总结记录 | PG `user_unit_summary_km_result` | | 6 | 统计-互动组件通过情况 | 基于 Sheet 2 聚合,按组件名称统计 Perfect/Good/Failed/Pass/Oops 数量及比例 | | 7 | 统计-知识点通过情况 | 基于 Sheet 2 聚合,按知识点(kpId + kpType + kpTitle)统计通过情况 | | 8 | 统计-单元总结时长 | 基于 Sheet 5 聚合,按 unit_id 累加 play_time_seconds,并转换为分钟(取整) | --- ## 四、执行方式 ### 脚本位置 - **Wrapper 脚本:** `scripts/export_user_data.sh` - **核心 Python 脚本:** `git_repos/llm_offline_production/config_user_data_extract_and_analyze/export_user_id_data.py` ### 命令参数 ```bash # 单角色导出 ./scripts/export_user_data.sh --user-id 33123 # 多角色批量导出 ./scripts/export_user_data.sh --user-ids 33123,33124,33125 # 按账户批量导出(自动查该账户下所有角色) ./scripts/export_user_data.sh --account-ids 2148,2149 # 指定输出目录 ./scripts/export_user_data.sh --user-id 33123 --output-dir /tmp/my_output/ ``` 三种模式(`--user-id` / `--user-ids` / `--account-ids`)互斥,只能传一种。 ### 输出文件命名 - 角色模式:`角色id_{ID}_导出时间_{YYYYMMDD}.xlsx` - 账户模式:`账户id_{账户ID}_角色id_{角色ID}_导出时间_{YYYYMMDD}.xlsx` ### Python 依赖 `python3`, `pymysql`, `psycopg2`, `pandas`, `openpyxl`, `requests` --- ## 五、所需环境变量 | 环境变量 | 对应数据源 | 说明 | |---------|-----------|------| | `ES_HOST` | ES Online | Elasticsearch 地址 | | `ES_PORT` | ES Online | ES 端口(9200) | | `ES_SCHEME` | ES Online | 协议(https) | | `ES_USER` | ES Online | ES 用户名 | | `ES_PASSWORD` | ES Online | ES 密码 | | `PG_DB_HOST` | PG Online | PostgreSQL 地址 | | `PG_DB_PORT` | PG Online | PG 端口(28591) | | `PG_DB_USER` | PG Online | PG 用户名 | | `PG_DB_PASSWORD` | PG Online | PG 密码 | | `PG_DB_DATABASE` | PG Online | PG 数据库名(vala) | | `MYSQL_HOST` | MySQL Test | MySQL 测试库地址 | | `MYSQL_PORT` | MySQL Test | MySQL 测试库端口(25413) | | `MYSQL_USERNAME` | MySQL Test | MySQL 测试库用户名 | | `MYSQL_PASSWORD` | MySQL Test | MySQL 测试库密码 | | `MYSQL_HOST_online` | MySQL Online | MySQL 线上库地址 | | `MYSQL_PORT_online` | MySQL Online | MySQL 线上库端口(27751) | | `MYSQL_USERNAME_online` | MySQL Online | MySQL 线上库用户名 | | `MYSQL_PASSWORD_online` | MySQL Online | MySQL 线上库密码 | > 凭证具体值见 `secrets.md`,wrapper 脚本 `export_user_data.sh` 已内置默认值。 --- ## 六、注意事项 1. **所有数据库连接均为只读账号**,不会产生写操作 2. ES 查询使用 HTTPS + 自签证书,脚本已处理 `verify=False` 3. PG 返回的 `updated_at` 带时区信息,脚本会自动去除时区避免 Excel 写入报错 4. `play_result` 支持两种格式:纯字符串(`"Perfect"`)或 JSON(`{"result": "Perfect"}`) 5. 导出大批量角色时建议分批执行,避免单次耗时过长 6. 脚本默认输出到 `output/` 目录