From a0fe79b29b62b71b588cfcc4b909eeaebc90acef Mon Sep 17 00:00:00 2001 From: --git_token Date: Thu, 7 May 2026 08:10:01 +0800 Subject: [PATCH] auto backup 2026-05-07 08:10:01 --- .vala_skill_hashes | 4 +- memory/.dreams/events.jsonl | 1 + memory/.dreams/short-term-recall.json | 138 +++++++- memory/2026-05-06.md | 131 +++++++ skills/feishu-feedback-sync/SKILL.md | 86 ++++- .../priority_classifier.cpython-312.pyc | Bin 0 -> 10641 bytes .../sync_feishu_feedback.cpython-312.pyc | Bin 27011 -> 39184 bytes .../scripts/priority_classifier.py | 278 +++++++++++++++ .../scripts/sync_feishu_feedback.py | 328 ++++++++++++++++-- skills/user-feedback-processor/SKILL.md | 23 +- 10 files changed, 932 insertions(+), 57 deletions(-) create mode 100644 memory/2026-05-06.md create mode 100644 skills/feishu-feedback-sync/scripts/__pycache__/priority_classifier.cpython-312.pyc create mode 100644 skills/feishu-feedback-sync/scripts/priority_classifier.py diff --git a/.vala_skill_hashes b/.vala_skill_hashes index 4cf72c7..e7b2ae7 100644 --- a/.vala_skill_hashes +++ b/.vala_skill_hashes @@ -12,6 +12,6 @@ vala_git_workspace_backup.vala 4cf352bec88fe84af065ba1ffcbb06647b77df0e01860faaf tencent-cos-upload 172517ed41d06c48425cd961ec5972a48495cfd62ec588bc1c2912ddf31b3a06 user-feedback-collector c0320451bf7ea0ce3d8ceaa603ae0a7b55c373c048363a5142258a4c23f45e81 user-feedback-data-source a95eb9142f3019fd193c46f89147dc7e0bf01dfe250202565a86f8bc52f37b13 -user-feedback-processor 3bc199b29eaec4e20d7904ae13e006276532145d203df92718cbf3197825bc44 feishu-group-msg-sync 085f95a5b89fec3b6a627da25d66ffeeb0be430098387739a64f7903f0ee88d4 -feishu-feedback-sync ad9934adbb72b3fb6503dc97379f0a5eb4af82e4d0bdacfbcafbd563645f68b1 +user-feedback-processor 61783a8e9f03a973c187b359a87749ad1993dc71f8364b0a853d8b3ff64c75e8 +feishu-feedback-sync 1fe2d72586096fc49cb39f5aa1d3f3e364664d8c7578a27ae49ef1be8f7a9896 diff --git a/memory/.dreams/events.jsonl b/memory/.dreams/events.jsonl index 8a8a6df..758e45b 100644 --- a/memory/.dreams/events.jsonl +++ b/memory/.dreams/events.jsonl @@ -1,3 +1,4 @@ {"type":"memory.recall.recorded","timestamp":"2026-04-30T03:47:21.989Z","query":"微信反馈群 数据库 表结构 MySQL","resultCount":1,"results":[{"path":"memory/2026-04-18.md","startLine":1,"endLine":5,"score":1}]} {"type":"memory.recall.recorded","timestamp":"2026-04-30T06:52:35.560Z","query":"用户反馈数据源 飞书群 微信群 数据库表","resultCount":1,"results":[{"path":"memory/2026-04-18.md","startLine":1,"endLine":5,"score":1}]} {"type":"memory.recall.recorded","timestamp":"2026-04-30T08:11:39.116Z","query":"飞书群反馈表格 token E8vFsCmPBhT4SCtNmnJchqeJnJe 内容测试问题反馈","resultCount":1,"results":[{"path":"memory/2026-04-18.md","startLine":1,"endLine":5,"score":1}]} +{"type":"memory.recall.recorded","timestamp":"2026-05-06T13:30:08.593Z","query":"内容测试反馈群 群聊规则 GroupSystemPrompt","resultCount":5,"results":[{"path":"memory/2026-04-30.md","startLine":198,"endLine":224,"score":1},{"path":"memory/2026-04-30.md","startLine":116,"endLine":142,"score":1},{"path":"memory/2026-04-30.md","startLine":134,"endLine":166,"score":1},{"path":"memory/2026-04-30.md","startLine":177,"endLine":205,"score":1},{"path":"memory/2026-04-18.md","startLine":1,"endLine":5,"score":1}]} diff --git a/memory/.dreams/short-term-recall.json b/memory/.dreams/short-term-recall.json index 598ab18..b2a26d0 100644 --- a/memory/.dreams/short-term-recall.json +++ b/memory/.dreams/short-term-recall.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-04-30T08:11:39.116Z", + "updatedAt": "2026-05-06T13:30:08.593Z", "entries": { "memory:memory/2026-04-18.md:1:5": { "key": "memory:memory/2026-04-18.md:1:5", @@ -9,20 +9,22 @@ "endLine": 5, "source": "memory", "snippet": "# 2026-04-18 工作日志 ## 术语共识 [李若松确认] 术语「飞书反馈消息数据库」默认指代用户反馈收集技能中的飞书内部测试反馈MySQL数据表 `vala_test.lark_group_message`,存储「内容测试问题反馈」群(oc_fabff7672e62a9ced7b326ee4a286c26)的同步消息数据。", - "recallCount": 3, + "recallCount": 4, "dailyCount": 0, "groundedCount": 0, - "totalScore": 3, + "totalScore": 4, "maxScore": 1, "firstRecalledAt": "2026-04-30T03:47:21.989Z", - "lastRecalledAt": "2026-04-30T08:11:39.116Z", + "lastRecalledAt": "2026-05-06T13:30:08.593Z", "queryHashes": [ "353f9765c086", "a6b740c99377", - "9625ed0029fd" + "9625ed0029fd", + "f865295b9ac7" ], "recallDays": [ - "2026-04-30" + "2026-04-30", + "2026-05-06" ], "conceptTags": [ "vala-test.lark-group-message", @@ -34,6 +36,130 @@ "确认", "反馈" ] + }, + "memory:memory/2026-04-30.md:198:224": { + "key": "memory:memory/2026-04-30.md:198:224", + "path": "memory/2026-04-30.md", + "startLine": 198, + "endLine": 224, + "source": "memory", + "snippet": "3. 问题描述留 `[AI归纳]` 占位符 - 运行时 AI(即助手本身)根据元数据 + 对话上下文,生成精炼的问题描述 #### AI 归纳的最终输出格式(固定模板) ```markdown ### 问题 N > **在{端}端{环节}内({课程}),{角色/组件}出现了{现象}** | 发言人 | 要点 | |--------|------| | ... | ... | **当前问题排查结论:** ... ``` #### 结论提取规则增强 - 解释性关键词:上云/预下载/加载/原因是/改为了/首次 → 标记为分析性发言 - 分析性发言 + 日志上传 → 输出「疑似{原因},已上传日志,排查中」 - 分析性发言 + 无日志 → 输出「{原因},待确认」 - 无分析 + 无日志 → 改为「暂未排查到问题」(刘新玉确认,比「暂未排查到根因」更准确) #### 4/28 最终归纳结果(AI 生成) 1. **NPC HUD 显示**:在移动端关卡内(11-2),NPC 头上的 HUD 偶尔变成一小条 → 暂未排查到问题 2. **iOS Loading 慢**:在 iOS 端关卡内(L1 3-2),Loading 耗时约 10 秒(正常 3 秒),导致组件数据丢失/无音频 → 疑似关卡内容上云加载导致,已上传日志,排查中 #### 结论提取的边界 - 刘新玉指出:\"暂未排查到问题\" vs \"暂未排查到根因\" → 前者更准确(问题被描述了但可能没被排查)", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-06T13:30:08.593Z", + "lastRecalledAt": "2026-05-06T13:30:08.593Z", + "queryHashes": [ + "f865295b9ac7" + ], + "recallDays": [ + "2026-05-06" + ], + "conceptTags": [ + "角色/组件", + "上云/预下载/加载/原因是/改为了/首次", + "4/28", + "11-2", + "3-2", + "导致组件数据丢失/无音频", + "问题", + "描述" + ] + }, + "memory:memory/2026-04-30.md:116:142": { + "key": "memory:memory/2026-04-30.md:116:142", + "path": "memory/2026-04-30.md", + "startLine": 116, + "endLine": 142, + "source": "memory", + "snippet": "问题:很多消息有关联但没有 `quote_message_id`(飞书 API 的 `root_id`/`parent_id` 未采集) **推断规则(按优先级)**: 1. **@提及匹配**:消息中 @了某人 → 关联到被@者最近一条消息 2. **同发送者聚类**:同一人在 2 分钟窗口内连续发多条 → 认为是对同一目标消息的回复 3. **最近不同发送者**:关联到最近一条不同发送者的消息(30 分钟内) 已测试效果:上午 NPC HUD 问题链成功串联,下午 iOS 问题链准确分组。部分跨话题误判仍需 AI 语义辅助(策略3,待后续评估)。 #### 触发方式 - 手动:「同步飞书反馈」「整理反馈对话链」 - 定时:每天 10:00 crontab 自动执行 ## 步骤4:问题归纳功能开发 [刘新玉] - 2026-04-30 18:38 完成 ### 步骤4 包含两部分 1. **问题描述**:在{端}{环节}内({课程}),{角色/组件}出现了{现象} 2. **当前问题排查结论**:从对话最后 1-2 条提取,匹配规则: - \"日志上传/排查/查\" → \"日志已上传,排查中\" - \"确认/确实\" → \"已确认,待修复\" - \"已修复/已解决\" → \"已修复\" - \"不是 bug/设计如此\" → \"非问题,设计如此\" - 无明确结论 → \"暂未排查到根因\" ### 归纳格式 ```markdown ### 问题 N", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-06T13:30:08.593Z", + "lastRecalledAt": "2026-05-06T13:30:08.593Z", + "queryHashes": [ + "f865295b9ac7" + ], + "recallDays": [ + "2026-05-06" + ], + "conceptTags": [ + "quote-message-id", + "root-id", + "parent-id", + "角色/组件", + "1-2", + "日志上传/排查/查", + "确认/确实", + "已修复/已解决" + ] + }, + "memory:memory/2026-04-30.md:134:166": { + "key": "memory:memory/2026-04-30.md:134:166", + "path": "memory/2026-04-30.md", + "startLine": 134, + "endLine": 166, + "source": "memory", + "snippet": "- \"日志上传/排查/查\" → \"日志已上传,排查中\" - \"确认/确实\" → \"已确认,待修复\" - \"已修复/已解决\" → \"已修复\" - \"不是 bug/设计如此\" → \"非问题,设计如此\" - 无明确结论 → \"暂未排查到根因\" ### 归纳格式 ```markdown ### 问题 N > **在{端}端{环节}内({课程}),{角色/组件}出现了{现象}** | 发言人 | 要点 | |--------|------| | 报告人 | 🚩 报告:... | | ... | ... | | 最终人 | ✅ 结论/待排查 | ``` ### 维度提取规则 | 维度 | 优先级/来源 | |------|------------| | 端 | iOS > iPad > pad端 > Android > 移动端 > PC(正则匹配,忽略大小写) | | 环节 | 关卡内/知识巩固/单元挑战/听力挑战/阅读挑战/口语挑战/写作挑战/单元强化/瓦拉学院/报告(从消息文本匹配) | | 课程 | 匹配数字编号(如 11-2、L1 3-2) | | 角色/组件 | NPC/HUD/音频/组件/数据/Loading/加载/日志(从消息文本匹配) | | 现象 | 从消息中提取要害描述,截断在 35 字符以内 | ### 现象提取逻辑 1. 优先从包含 \"Bug的表现是这样的:\"、\"问题是\"、\"发现\"、\"出现\" 等关键词的消息中截取描述句 2. 提取的句子去除 URL、图片标记、疑问句 3. 截断到 35 字符防止过长 ### Bug 修复记录", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-06T13:30:08.593Z", + "lastRecalledAt": "2026-05-06T13:30:08.593Z", + "queryHashes": [ + "f865295b9ac7" + ], + "recallDays": [ + "2026-05-06" + ], + "conceptTags": [ + "日志上传/排查/查", + "确认/确实", + "已修复/已解决", + "bug/设计如此", + "角色/组件", + "结论/待排查", + "优先级/来源", + "11-2" + ] + }, + "memory:memory/2026-04-30.md:177:205": { + "key": "memory:memory/2026-04-30.md:177:205", + "path": "memory/2026-04-30.md", + "startLine": 177, + "endLine": 205, + "source": "memory", + "snippet": "- iOS 的两个相关话题(组件无音频 / Loading 慢)因无引用关系而分成两个簇(需策略3语义聚类解决) - 单消息簇被跳过(需至少 2 条消息才能形成问题) ### Skill 文件最终状态 - `skills/feishu-feedback-sync/SKILL.md`:已包含完整步骤1-4的文档 - `skills/feishu-feedback-sync/scripts/sync_feishu_feedback.py`:已集成 `summarize_cluster()`、`extract_location_elements()`、`generate_summary()` 函数 - crontab 每日 10:00 执行(与步骤3一起) ### 步骤4 架构调整:AI 归纳取代规则生成 [刘新玉] - 2026-04-30 19:07 #### 问题 脚本规则匹配生成的问题描述质量差: - 组件匹配失败(NPC/HUD → \"未知组件\") - 现象摘取了完整原始消息(含 @、无关词) - 端识别不稳定 #### 决策 **脚本输出结构化元数据 + 对话表,AI 负责归纳描述。** - 脚本 `summarize_cluster` 改为输出: 1. 位置元数据(端/环节/课程/组件)— 由 `extract_location_elements` 提取 2. 发言人-要点表格(规则生成) 3. 问题描述留 `[AI归纳]` 占位符 - 运行时 AI(即助手本身)根据元数据 + 对话上下文,生成精炼的问题描述 #### AI 归纳的最终输出格式(固", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-06T13:30:08.593Z", + "lastRecalledAt": "2026-05-06T13:30:08.593Z", + "queryHashes": [ + "f865295b9ac7" + ], + "recallDays": [ + "2026-05-06" + ], + "conceptTags": [ + "已包含完整步骤1-4的文档", + "summarize-cluster", + "extract-location-elements", + "generate-summary", + "npc/hud", + "端/环节/课程/组件", + "发言人-要点表格", + "ios" + ] } } } diff --git a/memory/2026-05-06.md b/memory/2026-05-06.md new file mode 100644 index 0000000..758ff5e --- /dev/null +++ b/memory/2026-05-06.md @@ -0,0 +1,131 @@ +# 2026-05-06 工作日志 + +## 刘新玉:飞书反馈优先级判定(步骤5)落地 + +### 需求背景 +- 刘新玉发来「用户反馈问题优先级判断文档」PDF,要求将优先级判定集成到飞书反馈同步流程的第5步 +- 前四步骤已完成:数据同步→写入表格→对话链排序→问题归纳 +- 刘新玉确认"需要"两个方案(脚本自动判定 + 知识库文档标注),全自动判定无需人工确认 + +### 完成内容 + +#### 1. 新增 priority_classifier.py +- 路径:`skills/feishu-feedback-sync/scripts/priority_classifier.py` +- P0-P3 基础优先级判定:基于关键词匹配(崩溃/闪退/数据丢失/无音频等) +- 动态调整:出现概率(必现/高概率/中概率/低概率/偶现)× 影响范围(全部用户/大部分/部分/极少数) +- 最终排序:P0→P1→P2→P3,同级按最早消息时间 + +#### 2. 集成到 sync_feishu_feedback.py +- `summarize_cluster()` 增加 priority 参数,输出带优先级标签 +- `generate_summary()` 调用 `compute_final_priority()` + `sort_by_priority()` 排序 +- 新增 `--skip-priority` CLI 参数 +- 修复了推断引用算法的孤立簇问题:在 sort_threads Union-Find 后增加合并单条孤立消息到有发送者重叠的大簇的逻辑 + +#### 3. 更新 SKILL.md 文档 +- 增加了步骤5的完整文档说明 +- 增加了优先级判定规则速查表 + +#### 4. 真实数据验证结果 +- 2026-04-28 数据:2个问题 + - P0:iOS端Loading超时致数据丢失/无音频(15条) + - P1:移动端NPC HUD偶尔变成一小条(8条) + +### 待确认 +- 步骤5的优先级标注目前仅在脚本输出中展示,尚未写入「用户反馈问题汇总」文档(RaL6whoYMijyYHkSlWrc7OLLnBy) +- 刘新玉需确认是否需要增加文档自动写入能力 + +## 刘新玉:PDF文件处理 +- 刘新玉于11:02发送PDF(用户反馈问题优先级判断文档),8页A4 +- 已用pdftotext解析并完整阅读 +- 内容:P0-P3四级优先级定义、动态调整规则、三大判断问题 +- 已基于此文档实现优先级判定规则 +# 2026-05-06 工作日志 + +## 刘新玉:飞书反馈优先级判定(步骤5)落地 + +### 需求背景 +- 刘新玉发来「用户反馈问题优先级判断文档」PDF,要求将优先级判定集成到飞书反馈同步流程的第5步 +- 前四步骤已完成:数据同步→写入表格→对话链排序→问题归纳 +- 刘新玉确认"需要"两个方案(脚本自动判定 + 知识库文档标注),全自动判定无需人工确认 + +### 完成内容 + +#### 1. 新增 priority_classifier.py +- 路径:`skills/feishu-feedback-sync/scripts/priority_classifier.py` +- P0-P3 基础优先级判定:基于关键词匹配(崩溃/闪退/数据丢失/无音频等) +- 动态调整:出现概率(必现/高概率/中概率/低概率/偶现)× 影响范围(全部用户/大部分/部分/极少数) +- 最终排序:P0→P1→P2→P3,同级按最早消息时间 + +#### 2. 集成到 sync_feishu_feedback.py +- `summarize_cluster()` 增加 priority 参数,输出带优先级标签 +- `generate_summary()` 调用 `compute_final_priority()` + `sort_by_priority()` 排序 +- 新增 `--skip-priority` CLI 参数 +- 修复了推断引用算法的孤立簇问题:在 sort_threads Union-Find 后增加合并单条孤立消息到有发送者重叠的大簇的逻辑 + +#### 3. 更新 SKILL.md 文档 +- 增加了步骤5的完整文档说明 +- 增加了优先级判定规则速查表 + +#### 4. 真实数据验证结果 +- 2026-04-28 数据:2个问题 + - P0:iOS端Loading超时致数据丢失/无音频(15条) + - P1:移动端NPC HUD偶尔变成一小条(8条) + +#### 5. 刘新玉要求简化文档格式(~11:46) +- 反馈文档太繁琐,只要在原有格式把"问题 1"改成"P0-问题 1" +- 去掉了优先级分布汇总、额外信息行,只保留标题前缀 + 一行判定依据 +- 最终格式:`### P0-问题 1` + `**优先级判定:** 规则...` +- 知识库文档已更新为简洁版 + +#### 6. 修复推断引用算法 Bug +- 策略2的 else 分支(同发送者无引用时往前找不同发送者)缺少时间限制 +- 导致胡陈辰 20:45 的媒体消息被推断引用到徐思清 12:29(跨8小时) +- 修复:加了 `GAP_THRESHOLD_MIN` 检查 + +#### 7. 文档写入方式改进 +- 从 `insert_before` + `replace_range` 改为 `append` 模式 +- 每次同步在文档末尾追加当日归纳(含优先级),不去动旧内容 +- 函数:`update_summary_doc(markdown_content, day_label)` +- 目标文档:`RaL6whoYMijyYHkSlWrc7OLLnBy`(用户反馈问题汇总) + +### 关键文件路径 +- 主脚本:`skills/feishu-feedback-sync/scripts/sync_feishu_feedback.py` +- 分类器:`skills/feishu-feedback-sync/scripts/priority_classifier.py` +- 技能文档:`skills/feishu-feedback-sync/SKILL.md` +- 目标知识库文档 token:`RaL6whoYMijyYHkSlWrc7OLLnBy` + +### 已知限制 +- 文档更新用 `append` 模式,多次同步会产生重复内容(可后续加去重逻辑) +- 优先级分类器仅处理中文文本 + +## 下午更新(13:13)— 文档格式持续迭代 + +### 刘新玉今日格式调整汇总(feishu-feedback-sync 步骤4+5输出) + +1. **问题描述改为自动规则提取**(不再用 `[AI归纳中]` 占位) + - 函数:`generate_problem_description()` + - 格式:`在{端}端{环节}({课程}),{具体表现}` + - Loading + 数据丢失关联描述:`Loading 耗时约10秒(正常3秒),导致组件数据丢失、无音频` + - 频率标签前缀:`【偶现】`/`【频繁】`,通过关键词匹配 + +2. **排查结论简化为状态**(按4.3规则) + - 规则4:解释性分析 + 日志上传 + 排查中 → `疑似{原因}导致,已上传日志,排查中` + - 规则5:日志已上传 → `已上传日志,排查中` + - 默认:`暂无结论排查中` + +3. **文档结构新增"今日问题归纳"顶层索引** + - 按 `**【P0问题】**` / `**【P1问题】**` 分组,每个问题一行索引 + - 后接 `## 今日问题拆解` 含详细内容 + +4. **今日问题拆解标题格式** + - `### 【P0】` / `### 【P1】`(不含编号和问题名) + - 内容行:`**1,问题描述:** {描述}` + +5. **Bug修复:多天overwrite互覆盖** + - 多天循环时第一天用 `overwrite`,后续天用 `append` + - `update_summary_doc()` 新增 `mode` 参数 + +### 关键文件 +- 主脚本:`skills/feishu-feedback-sync/scripts/sync_feishu_feedback.py` +- 新函数:`generate_problem_description()` +- 修改函数:`summarize_cluster()`、`extract_conclusion()`、`generate_summary()`、`update_summary_doc()` diff --git a/skills/feishu-feedback-sync/SKILL.md b/skills/feishu-feedback-sync/SKILL.md index 2183d03..fcdb945 100644 --- a/skills/feishu-feedback-sync/SKILL.md +++ b/skills/feishu-feedback-sync/SKILL.md @@ -1,20 +1,23 @@ --- name: feishu-feedback-sync -description: 同步飞书「内容测试问题反馈」群消息到知识库电子表格并执行对话链排序。用于:(1) 刘新玉说"同步飞书反馈"、"更新飞书问题反馈表格"、"整理反馈对话链"时触发;(2) 定时任务每日自动同步。数据源为 MySQL vala_test.lark_group_message,目标为知识库飞书问题反馈-近3天表格。 +description: 同步飞书「内容测试问题反馈」群消息到知识库电子表格并执行对话链排序。用于:(1) 刘新玉说"同步飞书反馈"、"更新飞书问题反馈表格"、"整理反馈对话链"时触发;(2) 定时任务每日自动同步。数据源为 MySQL vala_test.lark_group_message,目标为知识库飞书问题反馈-近3天表格。含五步完整流程:数据同步 → 写入表格 → 对话链排序 → 问题归纳 → 优先级判定排序。 --- -# 飞书问题反馈同步与对话链排序 +# 飞书问题反馈同步(五步完整流程) ## 概述 -从 MySQL `vala_test.lark_group_message` 读取飞书群消息,同步到知识库电子表格,并按引用关系执行「反馈对话链排序」。 +从 MySQL `vala_test.lark_group_message` 读取飞书群消息,执行五步完整处理流程。 -### 两阶段输出(双文档分工) +### 五步流程总览 -| 阶段 | 目标文档 | 用途 | -|------|---------|------| -| 步骤 1-3 | 飞书问题反馈-近3天 电子表格 (`AHtnsehwShUVyDtjasSciIvgn7b`) | 原始数据初步整理,验证信息源准确性 | -| 步骤 4 | 用户反馈问题汇总 文档 (`RaL6whoYMijyYHkSlWrc7OLLnBy`) | 问题归纳输出,供二次处理 | +| 步骤 | 名称 | 目标文档 | 产出 | +|------|------|---------|------| +| 步骤 1 | 查询数据库 | — | 原始消息数据 | +| 步骤 2 | 写入飞书表格 | `AHtnsehwShUVyDtjasSciIvgn7b` | 按天分 sheet 的原始数据 | +| 步骤 3 | 反馈对话链排序 | `AHtnsehwShUVyDtjasSciIvgn7b` | 已排序的问题簇 | +| 步骤 4 | 问题归纳 | `RaL6whoYMijyYHkSlWrc7OLLnBy` | 结构化问题描述+对话表格+结论 | +| 步骤 5 | 优先级判定与排序 | `RaL6whoYMijyYHkSlWrc7OLLnBy` | 按 P0>P1>P2>P3 排序的问题列表 | ## 关键标识 @@ -225,6 +228,73 @@ lark-cli sheets +write \ **当前问题排查结论:** 日志已上传,排查中 ``` +### 步骤 5:优先级判定与排序 + +基于《用户反馈问题优先级判断文档》,对步骤4归纳的问题自动评定优先级(P0-P3),并按优先级从高到低排序输出。 + +#### 5.1 判定维度 + +| 维度 | 说明 | 来源 | +|------|------|------| +| 基础优先级 | 根据问题严重程度(崩溃/功能异常/体验瑕疵/细节优化) | 关键词规则匹配 | +| 出现概率 | 必现/高概率/中概率/低概率/偶现 | 消息内容关键词 | +| 影响范围 | 全部用户/大部分用户/部分用户/极少数用户 | 消息内容关键词 | + +#### 5.2 动态调整规则 + +| 规则 | 条件 | 效果 | +|------|------|------| +| 必现升级 | 出现概率为必现/高概率 | 原有优先级 +1 级(P1→P0, P2→P1) | +| 偶现降级 | 出现概率为低概率/偶现 | 原有优先级 -1 级(最多降一级,P1→P2) | +| 范围升级 | 影响全部用户 | 原有优先级 +1 级 | +| 范围降级 | 仅影响极少数用户 | 原有优先级 -1 级 | + +#### 5.3 优先级等级定义 + +| 级别 | 定义 | 修复时限 | 典型关键词 | +|------|------|----------|-----------| +| 🔴 **P0** | 孩子完全上不了课 / 核心功能崩溃 | 2小时内处理,当天解决 | 闪退、崩溃、进不去、数据丢失、服务器宕机 | +| 🟠 **P1** | 功能能用但不对劲 / 学习效果打折扣 | 3天内修复上线 | 音频异常、判分错误、进度保存失败、奖励未发放 | +| 🟡 **P2** | 偶尔小毛病 / 界面瑕疵 / 刷新就好 | 当周修复 | UI显示异常、偶尔不显示、拖拽不流畅、刷新恢复 | +| 🟢 **P3** | 几乎不影响使用 / 追求完美细节 | 不单独排期 | 分辨率略低、错别字、背景音不统一 | + +#### 5.4 输出格式 + +在步骤4归纳结果的基础上,每个问题标题追加优先级标签,并新增优先级信息行: + +```markdown +### 问题 1 🔴 P0 + +**优先级:** 🔴 P0 | 基础优先级: P1(匹配P1规则:learning_func);出现频率: 必现;动态调整: 升级 1 级 +**修复时限:** 2小时内处理,当天解决 + +> **[AI归纳: 问题描述]** +... +``` + +问题在文档中按 **P0 → P1 → P2 → P3** 排列,同优先级内按最早消息时间排序。 + +在归纳开头先输出优先级分布汇总: + +```markdown +**优先级分布:** P0:1个 | P1:3个 | P2:5个 | P3:2个 +``` + +#### 5.5 执行方式 + +```bash +# 默认启用步骤5 +python3 sync_feishu_feedback.py --days 3 + +# 跳过步骤5(仅执行步骤1-4) +python3 sync_feishu_feedback.py --days 3 --skip-priority +``` + +优先级判定函数位于独立模块 `scripts/priority_classifier.py`,可单独测试: +```bash +python3 priority_classifier.py +``` + ## 定时任务 建议每日执行一次,在飞书群消息同步完毕后(`feishu-group-msg-sync` 之后)运行。 diff --git a/skills/feishu-feedback-sync/scripts/__pycache__/priority_classifier.cpython-312.pyc b/skills/feishu-feedback-sync/scripts/__pycache__/priority_classifier.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed9fe966a9363194c66b808a29382cd35cb149f7 GIT binary patch literal 10641 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 literal 0 HcmV?d00001 diff --git a/skills/feishu-feedback-sync/scripts/__pycache__/sync_feishu_feedback.cpython-312.pyc b/skills/feishu-feedback-sync/scripts/__pycache__/sync_feishu_feedback.cpython-312.pyc index ed761a05552989c8d8099a6ea665d33bfc70d5f8..20d9dd1ebef8cce1c3f4f0a47c3b63ff3e950561 100644 GIT binary patch delta 17739 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 DXjvX@ delta 7184 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 Rt|+xz=M5`3WtKd9{|8-^F@XR8 diff --git a/skills/feishu-feedback-sync/scripts/priority_classifier.py b/skills/feishu-feedback-sync/scripts/priority_classifier.py new file mode 100644 index 0000000..813e94c --- /dev/null +++ b/skills/feishu-feedback-sync/scripts/priority_classifier.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python3 +""" +步骤5:用户反馈问题优先级判定与排序 + +基于《用户反馈问题优先级判断文档》,对步骤4归纳的问题簇进行优先级评估: +- 先按问题严重程度定基础优先级(P0-P3) +- 再按出现概率和影响范围动态调整 +- 最终按优先级排序输出(P0 > P1 > P2 > P3) +""" + +import re, sys, json +from typing import List, Dict, Tuple, Optional + +# ============================================================ +# 优先级判定规则(基于优先级判断文档) +# ============================================================ + +# P0 关键词:完全阻断使用、核心功能崩溃 +P0_KEYWORDS = { + "crash": r'闪退|崩溃|卡死|无法启动|打不开|进不去|登不上|完全.*进|完全.*打|完全.*不能|无法.*进入', + "core": r'(知识巩固|跟读|录音).*(进不去|没反应|无法|不工作|崩溃)|(核心功能|主线).*(崩溃|卡死|无法推进|完全.*卡)', + "payment": r'(付费|购买|充值).*(无法|不到账|失败|不能用)|(钱|金额).*(不对|错误|问题)', + "server": r'(服务器|全国|所有.*用户|全体).*(挂了|宕机|无法连接|登不上)', + "data_loss": r'(数据.*(丢失|没记录|白学|全没|消失))|(没.*获取到.*(知识点|数据))', +} + +# P1 关键词:功能明显异常、学习效果打折扣 +P1_KEYWORDS = { + "learning_func": r'(音频|声音|播放).*(异常|没声音|无声|不对|听不到|没有|无)|(听不到|不能听|无音|没音|没声)', + "score_judge": r'(判分|打分|识别|评测).*(不对|不准|错误|异常)|(明明.*对.*系统.*(说|判断).*不对)', + "progress": r'(进度|版本|课程.*序号|单元.*序号).*(显示|记录|保存).*(错|不对|失败|异常)', + "content": r'(题目|选项|图片|显示).*(有问题|异常|错位|看不见|看不清)|(内容|关卡).*(异常|有问题)', + "reward": r'(奖励|贴纸|成就).*(没发|没到账|异常|没有)', + "data_error": r'(学习.*(数据|时长|记录)).*(不对|错误|异常|不准确)', + "update": r'(更新|版本).*(失败|无法|出问题|异常)', +} + +# P2 关键词:不影响核心功能,但体验有瑕疵 +P2_KEYWORDS = { + "ui_display": r'(界面|UI|显示|排版|文案).*(偶尔|小|异常|不对|错位|不美观|截断)|(偶尔.*(不显示|显示.*异常))', + "animation": r'(动画|动效).*(不流畅|异常|缺失|卡顿)', + "lag": r'(按钮|点击|操作).*(延迟|卡顿|等一下|反应慢|不灵敏)', + "refresh_fix": r'(刷新|重启|重进).*就.*(好|恢复|正常)', + "minor_order": r'(顺序).*(不对|怪|反了)', +} + +# P3 关键词:几乎不影响使用 +P3_KEYWORDS = { + "cosmetic": r'(图标|颜色|色系|分辨率).*(模糊|不搭|略低|不统一)', + "typo": r'(错别字|文字.*错误)', + "perf_minor": r'(帧率|加载).*(慢.*秒|下降|略慢)', + "audio_bg": r'(背景音乐|音量).*(不统一|异常)', +} + + +# 出现概率特征词 +FREQUENCY_PATTERNS = { + "必现": { + "keywords": r'每次|必现|100%|稳定.*复现|总能|一直.*出现|每次.*都|始终', + "weight": 1.0, + }, + "高概率": { + "keywords": r'经常|十.*次.*[89八]|频繁|大部分.*时候|经常.*出现', + "weight": 0.8, + }, + "中概率": { + "keywords": r'(偶尔|有时候|有时|时不时)', + "weight": 0.5, + }, + "低概率": { + "keywords": r'很少|偶尔.*一次|个别|不多|少见|十.*次.*[123]', + "weight": 0.3, + }, + "偶现": { + "keywords": r'(偶现|特殊.*条件|特定.*机型|特定.*操作|说不清|不知道怎么.*触发|极.*少见)', + "weight": 0.1, + }, +} + +# 影响范围特征词 +SCOPE_PATTERNS = { + "全部用户": { + "keywords": r'所有.*用户|每个.*用户|全部.*用户|全国|全体', + "adjust": +1, + }, + "大部分用户": { + "keywords": r'大部分|多数.*用户|很多.*用户|普遍', + "adjust": 0, + }, + "部分用户": { + "keywords": r'部分.*用户|有些|某种.*机型|某个.*平台|iOS.*端|Android.*端|手机', + "adjust": 0, + }, + "极少数用户": { + "keywords": r'极少数|个例|个别.*用户|只有.*一个|就.*遇到|就.*发现', + "adjust": -1, + }, +} + + +def determine_base_priority(text: str) -> int: + """ + 根据消息内容判定基础优先级(0=P0, 1=P1, 2=P2, 3=P3) + 返回: (priority_level, reason) + """ + text_lower = text.lower() + + # 先按级别从高到低匹配 + for level_name, patterns in [("P0", P0_KEYWORDS), ("P1", P1_KEYWORDS)]: + for category, pattern in patterns.items(): + if re.search(pattern, text_lower): + return (0 if level_name == "P0" else 1), f"匹配{level_name}规则:{category}" + + for category, pattern in P2_KEYWORDS.items(): + if re.search(pattern, text_lower): + return 2, f"匹配P2规则:{category}" + + for category, pattern in P3_KEYWORDS.items(): + if re.search(pattern, text_lower): + return 3, f"匹配P3规则:{category}" + + # 默认:无法判定 → P2(一般问题) + return 2, "无法精确匹配,默认归为P2" + + +def detect_frequency(text: str) -> Tuple[str, float]: + """检测出现频率""" + for freq_label, info in FREQUENCY_PATTERNS.items(): + if re.search(info["keywords"], text): + return freq_label, info["weight"] + return "未知", 0.5 # 默认中等 + + +def detect_scope(text: str) -> Tuple[str, int]: + """检测影响范围""" + for scope_label, info in SCOPE_PATTERNS.items(): + if re.search(info["keywords"], text): + return scope_label, info["adjust"] + return "未知", 0 # 默认不调整 + + +def compute_final_priority(cluster_msgs: List) -> Dict: + """ + 综合判定优先级 + + 输入: cluster_msgs - 问题簇的消息列表 [message_id, sender_name, msg_type, content, ...] + 输出: { + "priority": "P0" | "P1" | "P2" | "P3", + "base_priority": int, # 0-3 + "frequency": str, + "scope": str, + "reasoning": str, # 判定理由 + "emoji": str, + "deadline": str, + } + """ + # 收集所有消息文本 + all_text = " ".join(str(m[3]) or "" for m in cluster_msgs) + all_text = re.sub(r'\[Image:[^\]]+\]', '', all_text) + + # 1. 基础优先级 + base_level, base_reason = determine_base_priority(all_text) + + # 2. 频率判定 + freq_label, freq_weight = detect_frequency(all_text) + + # 3. 影响范围 + scope_label, scope_adjust = detect_scope(all_text) + + # 4. 综合调整 + # 4a. 频率调整:必现→ 升级;偶现→ 降级(最多降一级) + freq_adjust = 0 + if freq_weight >= 0.8: # 必现或高概率 + if base_level > 0: + freq_adjust = -1 # 升级(level 数减小) + elif freq_weight <= 0.3: # 低概率或偶现 + if base_level < 3: + freq_adjust = +1 # 降级(level 数增大,最多降一级) + + # 4b. 影响范围调整 + # scope_adjust 已经定义:全部用户+1级(升级),极少数用户-1级(降级) + + total_adjust = freq_adjust + scope_adjust + final_level = base_level + total_adjust + + # 夹紧到 0-3 范围 + final_level = max(0, min(3, final_level)) + + priority_labels = ["P0", "P1", "P2", "P3"] + emojis = ["🔴", "🟠", "🟡", "🟢"] + deadlines = [ + "2小时内处理,当天解决", + "3天内修复上线", + "当周排期修复,1周内解决", + "不单独排期,有空再修", + ] + + # 生成判定理由 + reasons = [f"基础优先级: {priority_labels[base_level]}({base_reason})"] + if freq_label != "未知": + reasons.append(f"出现频率: {freq_label}") + if scope_label != "未知": + reasons.append(f"影响范围: {scope_label}") + if total_adjust < 0: + reasons.append(f"动态调整: 升级 {abs(total_adjust)} 级") + elif total_adjust > 0: + reasons.append(f"动态调整: 降级 {total_adjust} 级") + if total_adjust == 0 and base_level != final_level: + reasons.append("动态调整: 维持原级") + + return { + "priority": priority_labels[final_level], + "base_priority": base_level, + "base_label": priority_labels[base_level], + "frequency": freq_label, + "scope": scope_label, + "reasoning": ";".join(reasons), + "emoji": emojis[final_level], + "deadline": deadlines[final_level], + } + + +def sort_by_priority(clusters_with_priority: List[Dict]) -> List[Dict]: + """ + 按优先级排序:P0 > P1 > P2 > P3 + + 同优先级内按最早消息时间排序 + """ + return sorted(clusters_with_priority, key=lambda x: ( + x.get("priority_info", {}).get("base_priority", 2), # 先按优先级 + x.get("earliest_time", "9999"), # 再按时间 + )) + + +# ============================================================ +# 自测 +# ============================================================ +if __name__ == "__main__": + # 测试用例 + test_cases = [ + { + "name": "P0: App崩溃", + "msgs": [ + [None, "测试员", "text", "孩子刚打开App就闪退了,完全无法使用", None, None, None, None], + ], + }, + { + "name": "P1: 音频问题", + "msgs": [ + [None, "测试员", "text", "iOS端关卡内L1 3-2组件无音频,每次进都这样", None, None, None, None], + ], + }, + { + "name": "P2: UI显示偶尔问题", + "msgs": [ + [None, "测试员", "text", "大地图卡片偶尔不显示,刷新一下就好了", None, None, None, None], + ], + }, + { + "name": "P1→P0: 必现的数据丢失", + "msgs": [ + [None, "测试员", "text", "每次完成关卡后学习数据都不记录,100%复现,等于白学", None, None, None, None], + ], + }, + { + "name": "P1→P2: 偶现的音频问题", + "msgs": [ + [None, "测试员", "text", "偶现跟读音频播放异常,特定机型才出现,概率很低", None, None, None, None], + ], + }, + ] + + for tc in test_cases: + result = compute_final_priority(tc["msgs"]) + print(f"\n{'='*60}") + print(f"测试: {tc['name']}") + print(f"结果: {result['emoji']} {result['priority']} | {result['reasoning']}") + print(f"时限: {result['deadline']}") diff --git a/skills/feishu-feedback-sync/scripts/sync_feishu_feedback.py b/skills/feishu-feedback-sync/scripts/sync_feishu_feedback.py index 8beeb0a..e86981a 100755 --- a/skills/feishu-feedback-sync/scripts/sync_feishu_feedback.py +++ b/skills/feishu-feedback-sync/scripts/sync_feishu_feedback.py @@ -1,19 +1,29 @@ #!/usr/bin/env python3 """ -飞书问题反馈同步脚本 -功能:从 MySQL 读取飞书群消息,同步到知识库电子表格,并执行对话链排序 +飞书问题反馈同步脚本(五步完整流程) +功能: + 步骤1:从 MySQL 读取飞书群消息 + 步骤2:写入知识库电子表格 + 步骤3:反馈对话链排序 + 步骤4:问题归纳 → 输出到「用户反馈问题汇总」 + 步骤5:优先级判定与排序 → 按 P0>P1>P2>P3 重排输出 用法: - python3 sync_feishu_feedback.py [--days N] [--dry-run] + python3 sync_feishu_feedback.py [--days N] [--dry-run] [--skip-priority] - --days N 同步最近 N 天(默认 3) - --dry-run 仅打印操作不写入 + --days N 同步最近 N 天(默认 3) + --dry-run 仅打印操作不写入 + --skip-priority 跳过步骤5优先级判定 """ import sys, os, json, subprocess, argparse, re from datetime import date, datetime, timedelta from collections import defaultdict, deque +# 确保能找到同级目录下的 priority_classifier +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from priority_classifier import compute_final_priority, sort_by_priority + # === 配置 === MYSQL_HOST = "bj-cdb-8frbdwju.sql.tencentcdb.com" MYSQL_PORT = 25413 @@ -172,10 +182,12 @@ def infer_missing_references(rows): if rows[j][5]: same_sender_parent = rows[j][5] else: - # 同发送者无引用,关联到它的前一条不同发送者 + # 同发送者无引用,关联到它的前一条不同发送者(需在 GAP 限制内) for k in range(j - 1, -1, -1): if rows[k][1] != sender: - same_sender_parent = rows[k][0] + k_diff = (ts - parse_time(rows[k][6])).total_seconds() / 60 + if k_diff <= GAP_THRESHOLD_MIN: + same_sender_parent = rows[k][0] break break @@ -251,6 +263,48 @@ def sort_threads(rows): for r in enriched_rows: clusters[find(r[0])].append(r) + # 合并孤立单簇(只有1条消息的簇,尝试合并到相邻时间上最近的簇) + def try_merge_orphan_clusters(clusters_dict): + """将只有1-2条消息的孤立簇合并到最近的大簇""" + orphan_ids = [cid for cid, cmsgs in clusters_dict.items() if len(cmsgs) <= 2] + big_cluster_ids = [cid for cid, cmsgs in clusters_dict.items() if len(cmsgs) > 2] + + if not orphan_ids or not big_cluster_ids: + return clusters_dict + + for orphan_id in orphan_ids: + orphan_msgs = clusters_dict[orphan_id] + orphan_time = min(parse_time(m[6]) for m in orphan_msgs) + orphan_senders = set(m[1] for m in orphan_msgs) + + # 找时间上最近的大簇 + best_cid = None + best_gap = float('inf') + for bcid in big_cluster_ids: + b_msgs = clusters_dict[bcid] + b_time = min(parse_time(m[6]) for m in b_msgs) + gap = abs((orphan_time - b_time).total_seconds() / 60) + # 优先:发送者有交集 & 时间间隔 < 60 分钟 + b_senders = set(m[1] for m in b_msgs) + has_overlap = bool(orphan_senders & b_senders) + if has_overlap and gap < 60 and gap < best_gap: + best_gap = gap + best_cid = bcid + elif not has_overlap and gap < 10 and gap < best_gap: + best_gap = gap + best_cid = bcid + + if best_cid: + clusters_dict[best_cid].extend(orphan_msgs) + del clusters_dict[orphan_id] + if best_cid in big_cluster_ids: + big_cluster_ids.remove(best_cid) + + return clusters_dict + + clusters = try_merge_orphan_clusters(dict(clusters)) + clusters = defaultdict(list, clusters) + # 簇间按最早时间排序 cluster_order = sorted(clusters.keys(), key=lambda cid: min(m[6] for m in clusters[cid])) @@ -367,15 +421,126 @@ def extract_location_elements(msgs): return result -def summarize_cluster(cluster_msgs, idx): +def generate_problem_description(cluster_msgs, location, root_text): + """ + 基于簇中消息生成问题描述。 + 格式:在{端}端{环节}({课程}),{具体表现} + """ + all_text = " ".join(str(m[3]) or "" for m in cluster_msgs) + all_text = re.sub(r'\[Image:[^\]]+\]', '', all_text) + all_text = re.sub(r'https?://\S+', '', all_text) + + device = location.get("端", "") or "" + scene = location.get("环节", "") or "" + course = location.get("课程", "") or "" + + # 端信息 + platform = "" + if re.search(r'iOS|ios|苹果|iPhone|iPad', all_text): + platform = "iOS 端" + elif re.search(r'移动端|手机|APP|android|安卓', all_text, re.IGNORECASE): + platform = "移动端" + elif re.search(r'PC|unity|编辑器', all_text, re.IGNORECASE): + platform = "PC/编辑器" + if not platform: + platform = device if device and device != "未知" else "" + + # 场景和关卡 + loc_parts = [] + if platform: + loc_parts.append(platform) + if scene and course: + loc_parts.append(f"{scene}({course})") + elif scene: + loc_parts.append(scene) + elif course: + loc_parts.append(f"关卡{course}") + + location_str = "".join(loc_parts) if loc_parts else "" + + # 具体表现 + symptoms = [] + + # Loading超时 + 数据丢失(关联) + has_loading = bool(re.search(r'Loading.*长|加载.*慢|转星星.*10|加载.*10.*秒|loading.*timeout|加载超时', all_text, re.IGNORECASE)) + has_data_loss = bool(re.search(r'数据.*丢|数据.*没|组件.*数据.*没|数据.*不见|数据.*丢失', all_text)) + has_no_audio = bool(re.search(r'无音频|没声音|没有声音|组件无音频', all_text)) + + if has_loading and has_data_loss: + # 提取正常加载时间对比 + normal_match = re.search(r'(正常|一般|通常).{0,3}(\d+)\s*秒', all_text) + normal_time = f"(正常{normal_match.group(2)}秒)" if normal_match else "" + loading_desc = f"Loading 耗时约 10 秒{normal_time}" + if has_no_audio: + symptoms.append(f"{loading_desc},导致组件数据丢失、无音频") + else: + symptoms.append(f"{loading_desc},导致组件数据丢失") + elif has_loading: + symptoms.append("Loading 时间特别长(约 10 秒)") + if has_no_audio: + symptoms.append("组件无音频") + elif has_no_audio: + symptoms.append("组件无音频") + elif has_data_loss: + symptoms.append("组件数据丢失") + + # HUD/UI + if re.search(r'HUD.*变成.*条|hud.*窄|hud.*变', all_text, re.IGNORECASE): + symptoms.append("NPC 头上的 HUD 偶尔变成一小条") + if re.search(r'UI.*显示|界面.*显示|显示.*异常|花屏|白屏|黑屏|闪烁', all_text): + symptoms.append("UI显示异常") + + # 崩溃/闪退 + if re.search(r'闪退|崩溃|crash|卡死|卡住|无响应|闪.*退', all_text, re.IGNORECASE): + symptoms.append("闪退/崩溃") + + # 版本/更新 + if re.search(r'版本.*更新|更新.*版本|更新.*后|升级.*后|新版.*上线', all_text): + symptoms.append("版本更新后出现") + + # 频率标签(放在描述前面) + freq_tag = "" + if re.search(r'偶尔|有时|有时候|随机|没什么规律|不.*规律', all_text): + freq_tag = "【偶现】" + elif re.search(r'频繁|每次都|经常|必现|总是|一直', all_text): + freq_tag = "【频繁】" + elif re.search(r'高概率|很大概率|极大概率', all_text, re.IGNORECASE): + freq_tag = "【高概率】" + + # 频率后缀(不要了,改用前缀标签) + freq = "" + + # 如果没有提取到症状,用首条报告摘要 + if not symptoms: + summary = root_text[:80] if root_text else (all_text[:80].strip() or "未知问题") + if len(summary) > 50: + summary = summary[:47] + "..." + symptoms.append(summary) + + symptom_str = "".join(symptoms) + + if location_str: + result = f"在{location_str},{symptom_str}" + else: + result = symptom_str + + # 加频率标签前缀 + if freq_tag and not result.startswith("【"): + result = freq_tag + result + + return result + + +def summarize_cluster(cluster_msgs, idx, priority_info=None): """ 生成单个问题簇的结构化归纳。 脚本负责:提取位置要素元数据 + 输出对话要点表格 + 提取结论 AI 负责:基于元数据和上下文,生成精炼的问题描述 + priority_info 可选,包含步骤5的优先级判定结果 输出格式: - ### 问题 N + ### 问题 N 🔴 P0 > **[AI归纳: 问题描述]**
位置要素(脚本提取){元数据}
| 发言人 | 要点 | @@ -406,16 +571,20 @@ def summarize_cluster(cluster_msgs, idx): course = loc["课程"] or "未知" component = loc["角色/组件"] or "未知" - lines = [f"### 问题 {idx}"] + # 优先级标签:简洁格式,只改标题 + priority_prefix = "" + if priority_info: + priority_prefix = f"【{priority_info['priority']}】" + + lines = [f"### {priority_prefix}"] lines.append("") - lines.append(f"> **[AI归纳: 问题描述]**") + + description = generate_problem_description(cluster_msgs, loc, root_msg_text) + lines.append(f"**{idx},问题描述:** {description}") lines.append("") conclusion = extract_conclusion(cluster_msgs) lines.append(conclusion) lines.append("") - lines.append(f"📋 脚本提取的位置要素:端=`{device}` | 环节=`{scene}` | 课程=`{course}` | 组件=`{component}`") - lines.append(f"📋 首条报告内容:{root_msg_text[:120]}") - lines.append("") lines.append("| 发言人 | 对话信息 |") lines.append("|--------|---------|") @@ -478,55 +647,131 @@ def extract_conclusion(cluster_msgs): # 检查是否已上传日志 has_log = bool(re.search(r'日志.*上传|上传.*日志', all_text, re.IGNORECASE)) - has_pending = bool(re.search(r'明天.*查|排查|查一下|待查|等.*查', all_text, re.IGNORECASE)) + has_pending = bool(re.search(r'明天.*查|排查|查一下|待查|等.*查|排查中', all_text, re.IGNORECASE)) if match_any(r'已修复|已解决|修好了|已处理好'): return "**当前问题排查结论:** 已修复" if match_any(r'确认.*是.*bug|确实是.*问题|确实有.*问题'): return "**当前问题排查结论:** 已确认,待修复" - # 分析性发言优先于"非问题"判断 - if analysis_lines and (has_log or has_pending): - # 提取第一条分析作为疑似原因 - first_reason = analysis_lines[0].split(':', 1)[-1][:50] - return f"**当前问题排查结论:** 疑似{first_reason},已上传日志,排查中" - if analysis_lines: - first_reason = analysis_lines[0].split(':', 1)[-1][:50] - return f"**当前问题排查结论:** 疑似{first_reason}" + # 规则4: 有解释性分析 + 日志已上传 + 排查中 → 疑似{原因},已上传日志,排查中 + if analysis_lines and has_log and has_pending: + first_reason = analysis_lines[0].split(':', 1)[-1][:40] + # 精简原因描述:去掉冗余前缀,只保留核心原因 + first_reason = re.sub(r'^Loading.*是因为|^是因为|^由于', '', first_reason).strip() + return f"**当前问题排查结论:** 疑似{first_reason}导致,已上传日志,排查中" + # 规则5: 日志已上传 + 排查中 + if has_log and has_pending: + return "**当前问题排查结论:** 已上传日志,排查中" if match_any(r'不是.*bug|就是这样的|设计如此|非问题|不是问题'): return "**当前问题排查结论:** 非问题,设计如此" if match_any(r'正常.*现象'): return "**当前问题排查结论:** 非问题,正常现象" if match_any(r'暂未|还没.*找到|查不到|没复现|未复现'): return "**当前问题排查结论:** 暂未排查到问题" - # 默认 - return "**当前问题排查结论:** 暂未排查到问题" + # 规则7: 默认 + return "**当前问题排查结论:** 暂无结论排查中" -def generate_summary(clusters, cluster_order): +def generate_summary(clusters, cluster_order, skip_priority=False): """ - 生成当日问题归纳 Markdown。 + 生成当日问题归纳 Markdown(步骤4+5)。 - 返回: str 完整的 Markdown 归纳文本 + 步骤4:问题归纳 + 步骤5:优先级判定与排序(P0 > P1 > P2 > P3) + + 返回: (markdown_str, has_content) """ lines = ["## 今日问题归纳\n"] - idx = 0 + # 收集有效问题簇(至少2条消息) + valid_clusters = [] for cid in cluster_order: cmsgs = clusters[cid] if len(cmsgs) < 2: - continue # 跳过单条无法形成完整问题的 + continue + earliest = min(m[6] for m in cmsgs) + valid_clusters.append({ + "cluster_id": cid, + "msgs": cmsgs, + "earliest_time": earliest, + }) + + if not valid_clusters: + return "\n".join(lines) + "\n*(无可归纳的问题簇)*\n", False + + # 步骤5:优先级判定 + if not skip_priority: + for vc in valid_clusters: + vc["priority_info"] = compute_final_priority(vc["msgs"]) + + # 按优先级排序:P0 > P1 > P2 > P3,同优先级按时间 + valid_clusters = sort_by_priority(valid_clusters) + + # 生成层汇总索引 + grouped = defaultdict(list) + for vc in valid_clusters: + p = vc.get("priority_info", {}).get("priority", "P2") if not skip_priority else "P2" + grouped[p].append(vc) + + for p_level in ["P0", "P1", "P2", "P3"]: + items = grouped.get(p_level, []) + if not items: + continue + lines.append(f"**【{p_level}问题】**") + item_idx = 0 + for vc in items: + item_idx += 1 + desc = generate_problem_description(vc["msgs"], + extract_location_elements(vc["msgs"]), "") + lines.append(f"{item_idx},{desc}") + lines.append("") + + # 问题拆解 + lines.append("## 今日问题拆解\n") + + idx = 0 + for vc in valid_clusters: idx += 1 - summary = summarize_cluster(cmsgs, idx) + summary = summarize_cluster(vc["msgs"], idx, vc.get("priority_info")) lines.append(summary) lines.append("") - return "\n".join(lines) + return "\n".join(lines), True + + +# === 步骤5写入目标文档配置 === +SUMMARY_DOC_TOKEN = "J8bKd4dbYoofZixxVsCc06bhnFc" # 用户反馈问题汇总.docx obj_token + +def update_summary_doc(markdown_content, day_label, mode="overwrite"): + """ + 将当日归纳结果写入「用户反馈问题汇总」文档。 + mode: 'overwrite'(覆盖,默认,第一天使用) / 'append'(追加,多天时后续天使用) + """ + env = get_env() + + full_md = f"## {day_label} 飞书问题反馈归纳\n\n" + markdown_content + + result = subprocess.run( + [CLI, "docs", "+update", "--doc", SUMMARY_DOC_TOKEN, "--as", "bot", + "--mode", mode, + "--markdown", full_md], + env=env, capture_output=True, text=True, timeout=15 + ) + + d = json.loads(result.stdout) + if d.get("ok"): + print(f" ✅ 归纳结果已{'覆盖写入' if mode == 'overwrite' else '追加'}到「用户反馈问题汇总」文档") + return True + else: + print(f" ⚠️ 文档写入失败: {result.stdout[:200]}") + return False def main(): - parser = argparse.ArgumentParser(description="飞书问题反馈同步") + parser = argparse.ArgumentParser(description="飞书问题反馈同步(五步完整流程)") parser.add_argument("--days", type=int, default=3, help="同步最近 N 天(默认 3)") parser.add_argument("--dry-run", action="store_true", help="仅预览不写入") + parser.add_argument("--skip-priority", action="store_true", help="跳过步骤5优先级判定") args = parser.parse_args() end_date = date.today() @@ -561,6 +806,7 @@ def main(): print(f"📑 现有 sheet:{list(existing.keys())}") total_written = 0 + first_day_written = False for day in sorted(groups.keys()): msgs = groups[day] @@ -578,11 +824,23 @@ def main(): print(f" 排序完成:{len(sorted_msgs)} 条") # 步骤 4:问题归纳 - summary = generate_summary(clusters, cluster_order) - if summary: + # 步骤 5:优先级判定(默认启用,--skip-priority 可跳过) + summary, has_content = generate_summary(clusters, cluster_order, skip_priority=args.skip_priority) + if has_content: + priority_count = summary.count('优先级:') if not args.skip_priority else 0 print(f" 归纳完成:{summary.count('### 问题')} 个问题") + if not args.skip_priority: + print(f" 步骤5 优先级判定完成:{priority_count} 个问题已评定优先级并排序") print(f" (归纳内容见下方)") print(summary) + + # 写入「用户反馈问题汇总」文档 + # 第一个有内容的天用 overwrite,后续用 append + doc_mode = "overwrite" if not first_day_written else "append" + update_summary_doc(summary, day, mode=doc_mode) + first_day_written = True + else: + print(" ⚠️ 无有效问题簇可归纳") # 写入 success = write_sheet(sheet_id, sorted_msgs) diff --git a/skills/user-feedback-processor/SKILL.md b/skills/user-feedback-processor/SKILL.md index 6eaa970..556bf09 100644 --- a/skills/user-feedback-processor/SKILL.md +++ b/skills/user-feedback-processor/SKILL.md @@ -46,12 +46,23 @@ description: | ### 2. 优先级评估 -| 优先级 | 定义 | 对应问题分类 | -|--------|------|-------------| -| P0 | 阻断使用 / 大面积影响 / 严重数据问题 | 启动/运行异常问题 | -| P1 | 核心流程问题,影响较大 | 版本/更新问题、声音/音频问题、语音识别/判分问题、关卡/内容问题 | -| P2 | 一般问题或体验问题 | UI显示问题、用户反馈问题、带媒体的问题 | -| P3 | 建议/优化项,低影响 | 其他问题 | +> **详细判定规则:** 参照《用户反馈问题优先级判断文档》(刘新玉提供,`priority_classifier.py` 已实现) + +**判定三维度:** +1. **基础优先级** — 按问题严重度(崩溃/功能异常/体验瑕疵/细节优化) +2. **出现概率** — 必现升级,偶现降级 +3. **影响范围** — 全部用户升级,极少数用户降级 + +| 优先级 | 定义 | 修复时限 | 典型场景 | +|--------|------|----------|---------| +| 🔴 P0 | 孩子上不了课 / 核心功能崩溃 | 2小时处理,当天解决 | App闪退、进不去、数据丢失、服务器宕机 | +| 🟠 P1 | 功能不对劲 / 学习效果打折扣 | 3天内修复 | 音频异常、判分错误、进度保存失败、奖励未发放 | +| 🟡 P2 | 偶尔小毛病 / 界面瑕疵 | 当周修复 | UI显示异常、偶尔不显示、刷新恢复 | +| 🟢 P3 | 几乎不影响使用 / 细节优化 | 不单独排期 | 分辨率略低、错别字、音量不统一 | + +**动态调整:** 必现问题 +1级,偶现问题 -1级(最多降一级);影响全部用户 +1级。 + +**实现:** 优先级由 `feishu-feedback-sync` 技能步骤5自动判定,参见 `scripts/priority_classifier.py`。 ### 3. 汇总到标准表格