288 lines
11 KiB
Markdown
288 lines
11 KiB
Markdown
# 用户学情数据导出 — 数据表与业务逻辑
|
||
|
||
> 整理日期: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/` 目录
|