Files
wecom_it_smart_desk/backend/app/tasks/quiz_generation_task.py
T
Simon 449c6d4875 feat: 2026-07-12~13 全量更新 - AI对话链路改造+H5 v4/v5+坐席端v5+上下文感知诊断+知识库迭代3
## H5 员工端 v4 (2026-07-13 00:48 已部署)
- 人工按钮三态文案统一为"人工坐席"
- 按钮位置移至发送键和语音按钮上方(垂直堆叠)
- 点按钮直接调 store.shakeAgent(),删除 CallAgentModal 弹窗动画
- 截图快捷键提示改为"截图->粘贴:Alt+Shift+A-Ctrl+V ---> Ctrl+V"
- 移动端隐藏截图提示(CSS 媒体查询)
- AI转人工提示改为"已为您呼叫人工坐席,请稍等!"
- 坐席接入提示改为"坐席正在查看您的信息,请等待处理回复!"
- 删除"摇铃呼叫坐席"入口和文案
- 删除孤儿组件 MessageList.vue + shake 动画 CSS

## H5 员工端 v5 (2026-07-13 02:08 已部署)
- RightPanel v2.1:删除"软件安装"和"资源权限"标签页
- 移除标签栏,智能推荐(DynamicRecommend)直接展示
- 删除 SoftwareDownloads/ApprovalLinks 引用和相关 CSS

## AI 对话链路全栈改造 Phase 1-6 (已部署)
- Phase 1: Dify JSON输出 + 后端blocking解析 + 双WS推送 + 错误降级
- Phase 2: 关键词收窄(~25强意图词) + 两级分类Prompt + 删除前端checkApprovalIntent
- Phase 3: WS扩展(ai_thinking+dynamic_recommend) + ai_structured气泡 + RightPanel v2 + 选项回传
- Phase 4: VisionService接入 + 图片消息融合(5秒窗口) + 降级策略
- Phase 5: 坐席端ai_thinking指示器 + ai_structured/byod_card渲染 + handleNewMessage修复
- Phase 6: diagnosis_stage(6值) + response_time_ms计时 + 慢响应告警(>10s)

## 坐席端 v5 (2026-07-13 01:38 已部署)
- ai_structured/byod_card 只读渲染
- AI思考指示器 UI
- handleNewMessage 透传 msg_type/extra_data 修复
- 布局优化v2.0: QuickReplyBar L1+L2悬浮 + ReplyBox左右分区 + 右栏260/560px切换
- 键盘快捷键v2.3: 纯数字路由 + ESC分层撤销 + Shift+Space用event.code

## 上下文感知智能诊断闭环 (2026-07-12 已部署)
- 三层诊断(API→Script→AI) + 三段排队(VIP→info_locked→not locked)
- 答题插队 + 五场景关闭
- 迁移052(6表+6列) + queue_service + quiz_service + closing_service
- H5前端: QueueWaiting + RightPanel双Tab + InputBar三态 + ResolveConfirmCard
- 坐席前端: pending_close结单流程 + 信息锁定(Dify步骤完成+有效回答率≥70%)

## 知识库迭代3 (2026-07-12 已部署)
- 分诊交互(H5+坐席+Dify独立应用)
- 拓扑预览(ECharts只读)
- 代答排除(4种匹配器: keyword/regex/intent/category)
- 迁移051 + 44文件43测试通过

## 后端变更
- 6个Python文件改造(h5_ai_task.py/h5.py/ai_service.py/closing_service.py等)
- funny_phrase_service.py: shake/connected/keyword 默认文案更新
- session_service.py: 企微消息文案同步
- 新增: queue.py/quiz.py/triage.py/exclusion_rules.py 等API端点
- 新增: diagnostic.py/quiz.py/triage_session.py 等模型
- 新增: closing_service/queue_service/quiz_service/triage_service 等服务

## 文档更新
- CHANGELOG.md: 新增 [未发布] 区全部变更记录
- 项目管理主文档 v2.5: 新增v0.7.3版本 + 已完成看板 + 最近搞定
- 版本记录: 新增v0.7.3条目
- AI对话链路实施计划: Phase 1-6 全部标记已实施
- 新增架构图/时序图/类图(mermaid)

## 部署路径修正
- 服务器项目根路径: /opt/wecom-it-desk/
- 所有前端dist均为ro bind mount,只能在宿主机源路径操作
- 服务器nginx /h5/ 是静态文件服务(非proxy_pass)
- elFinder上传二进制不可靠(MD5不匹配),改用base64分块上传
2026-07-13 02:17:03 +08:00

175 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================================
# 企微IT智能服务台 — 每日测验题目自动生成定时任务
# =============================================================================
# 说明:每天凌晨 3:00 自动执行:
# 1. 按星期轮转生成 1 个类别的 5 道知识题(7 天一轮)
# 2. 分析近 7 天已解决工单 → 生成 3 道诊断题
# 3. 停用被 >80% 活跃员工答过的陈旧题目
#
# 调度:CronTrigger(hour=3, minute=0)
# 降级:Dify 不可用时记录错误,不影响其他步骤
# =============================================================================
import datetime
import logging
logger = logging.getLogger(__name__)
# 7 个题目类别(按星期轮转:周一=network, 周二=vpn, ...
QUIZ_CATEGORIES = ["network", "vpn", "email", "system", "printer", "security", "office"]
# 每日知识题生成数量
KNOWLEDGE_QUESTIONS_PER_DAY = 5
# 诊断题生成数量
DIAGNOSTIC_QUESTIONS_PER_BATCH = 3
# 默认诊断题问题类别(当近期无工单数据时使用)
DEFAULT_DIAGNOSTIC_CATEGORIES = ["network_connect", "vpn_auth_fail", "printer_offline"]
async def run_daily_quiz_generation():
"""每日测验题目生成任务。
执行流程:
1. 按星期轮转生成 1 个类别的 5 道知识题
2. 分析近 7 天已解决工单 → 取 top 1 问题类别生成 3 道诊断题
3. 停用被 >80% 活跃员工答过的陈旧题
4. 记录汇总日志
调度:每日 03:00CronTrigger hour=3, minute=0
"""
from app.database import _get_session_factory
from app.services.quiz_generation_service import get_quiz_generation_service
logger.info("===== 开始每日测验题目生成 =====")
factory = _get_session_factory()
service = get_quiz_generation_service()
total_generated = 0
total_errors = 0
async with factory() as db:
try:
# ============================================================
# 1. 知识题生成(按星期轮转 1 个类别)
# ============================================================
today = datetime.date.today()
weekday = today.weekday() # 0=Monday, 6=Sunday
category = QUIZ_CATEGORIES[weekday]
logger.info(f"今日轮转类别: {category} (weekday={weekday})")
try:
result = await service.generate_knowledge_questions_batch(
db=db,
category=category,
count=KNOWLEDGE_QUESTIONS_PER_DAY,
is_active=False, # 定时生成的题目需管理员审批
)
total_generated += result["success_count"]
total_errors += result["failed_count"]
logger.info(
f"知识题生成 [{category}]: "
f"成功 {result['success_count']}, 失败 {result['failed_count']}"
)
if result["errors"]:
logger.warning(f"知识题错误详情: {result['errors'][:3]}")
except Exception as e:
logger.error(f"知识题生成 [{category}] 异常: {e}")
total_errors += KNOWLEDGE_QUESTIONS_PER_DAY
# ============================================================
# 2. 诊断题生成(基于近期工单模式)
# ============================================================
try:
# 获取近期工单摘要
ticket_summaries = await service._get_recent_ticket_summaries(
db, days=7, limit=20
)
# 提取 top 1 问题类别
problem_category = _extract_top_problem_category(ticket_summaries)
logger.info(f"诊断题问题类别: {problem_category}")
result = await service.generate_diagnostic_questions_batch(
db=db,
problem_category=problem_category,
count=DIAGNOSTIC_QUESTIONS_PER_BATCH,
ticket_summaries=[t["summary"] for t in ticket_summaries if t["summary"]],
is_active=False,
)
total_generated += result["success_count"]
total_errors += result["failed_count"]
logger.info(
f"诊断题生成 [{problem_category}]: "
f"成功 {result['success_count']}, 失败 {result['failed_count']}"
)
except Exception as e:
logger.error(f"诊断题生成异常: {e}")
total_errors += DIAGNOSTIC_QUESTIONS_PER_BATCH
# ============================================================
# 3. 停用陈旧题目
# ============================================================
try:
stale_result = await service.deactivate_stale_questions(
db=db, threshold=0.8
)
if stale_result["deactivated_count"] > 0:
logger.info(
f"陈旧题目停用: {stale_result['deactivated_count']}"
f"(活跃员工 {stale_result['total_active_employees']} 人)"
)
except Exception as e:
logger.error(f"陈旧题目停用异常: {e}")
# 统一提交
await db.commit()
except Exception as e:
await db.rollback()
logger.error(f"每日题目生成任务异常: {e}", exc_info=True)
logger.info(
f"===== 每日题目生成完成: 新增 {total_generated} 道, "
f"失败 {total_errors} 道 ====="
)
def _extract_top_problem_category(
ticket_summaries: list,
) -> str:
"""从工单摘要中提取最高频的问题类别。
基于 category_hint 字段统计频率。
降级:如果无数据,返回默认类别。
Args:
ticket_summaries: _get_recent_ticket_summaries() 返回的列表
Returns:
str: 问题类别标识(如 "vpn_disconnect"
"""
if not ticket_summaries:
# 无工单数据时返回默认
return DEFAULT_DIAGNOSTIC_CATEGORIES[0]
# 统计 category_hint 频率
hint_counts: dict[str, int] = {}
for ticket in ticket_summaries:
hint = ticket.get("category_hint", "")
if hint:
hint_counts[hint] = hint_counts.get(hint, 0) + 1
if not hint_counts:
return DEFAULT_DIAGNOSTIC_CATEGORIES[0]
# 取最高频的类别
top_category = max(hint_counts, key=hint_counts.get)
# 拼接为 problem_category 格式(如 "vpn_disconnect"
return f"{top_category}_issue"