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分块上传
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
"""add meetingroom repair and guide tables with seed data
|
||||
|
||||
Revision ID: 051_add_meetingroom_repair_guide
|
||||
Revises: 050_add_meetingroom_tables
|
||||
Create Date: 2026-07-16
|
||||
|
||||
新增表:
|
||||
- meetingroom_guide: 会议室操作指南(终端大屏展示+二维码)
|
||||
- meetingroom_repair: 会议室报修记录(关联IT工单会话)
|
||||
同时插入操作指南种子数据
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# Alembic 修订标识符
|
||||
revision: str = "051_add_meetingroom_repair_guide"
|
||||
down_revision: Union[str, None] = "050_add_meetingroom_tables"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""升级:创建 meetingroom_guide 和 meetingroom_repair 表,插入种子数据。"""
|
||||
|
||||
# 1. 操作指南表
|
||||
op.create_table(
|
||||
"meetingroom_guide",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("category", sa.String(50), nullable=False),
|
||||
sa.Column("title", sa.String(100), nullable=False),
|
||||
sa.Column("brief", sa.Text(), nullable=False, server_default=""),
|
||||
sa.Column("detail_url", sa.String(500), nullable=False, server_default=""),
|
||||
sa.Column("icon", sa.String(50), nullable=False, server_default="📋"),
|
||||
sa.Column("sort_order", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_meetingroom_guide_category", "meetingroom_guide", ["category"])
|
||||
|
||||
# 2. 报修记录表
|
||||
op.create_table(
|
||||
"meetingroom_repair",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("terminal_sn", sa.String(64), nullable=False),
|
||||
sa.Column("meetingroom_id", sa.Integer(), nullable=False),
|
||||
sa.Column("meetingroom_name", sa.String(100), nullable=False, server_default=""),
|
||||
sa.Column("device_type", sa.String(50), nullable=False),
|
||||
sa.Column("fault_description", sa.Text(), nullable=False),
|
||||
sa.Column("reporter_name", sa.String(100), nullable=False, server_default="匿名"),
|
||||
sa.Column("reporter_userid", sa.String(64), nullable=False, server_default=""),
|
||||
sa.Column("conversation_id", sa.String(36), nullable=False),
|
||||
sa.Column("status", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_meetingroom_repair_terminal_sn", "meetingroom_repair", ["terminal_sn"])
|
||||
op.create_index("ix_meetingroom_repair_meetingroom_id", "meetingroom_repair", ["meetingroom_id"])
|
||||
op.create_index("ix_meetingroom_repair_conversation_id", "meetingroom_repair", ["conversation_id"])
|
||||
|
||||
# 3. 插入操作指南种子数据
|
||||
guide_table = sa.table(
|
||||
"meetingroom_guide",
|
||||
sa.column("category", sa.String),
|
||||
sa.column("title", sa.String),
|
||||
sa.column("brief", sa.Text),
|
||||
sa.column("detail_url", sa.String),
|
||||
sa.column("icon", sa.String),
|
||||
sa.column("sort_order", sa.Integer),
|
||||
sa.column("is_active", sa.Boolean),
|
||||
)
|
||||
|
||||
op.bulk_insert(guide_table, [
|
||||
{
|
||||
"category": "projector",
|
||||
"title": "投影仪使用指南",
|
||||
"brief": "1. 按遥控器电源键开机\n2. 选择信号源(HDMI1/2)\n3. 调整焦距和梯形校正\n4. 使用完毕按电源键关机\n5. 等待散热完成后断电",
|
||||
"detail_url": "",
|
||||
"icon": "📽️",
|
||||
"sort_order": 1,
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"category": "video_conf",
|
||||
"title": "视频会议使用指南",
|
||||
"brief": "1. 在终端主页选择"视频会议"\n2. 输入会议号或选择预约会议\n3. 点击"加入会议"\n4. 共享屏幕:点击"共享"按钮\n5. 会议结束:点击"挂断"",
|
||||
"detail_url": "",
|
||||
"icon": "📹",
|
||||
"sort_order": 2,
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"category": "aircon",
|
||||
"title": "空调控制指南",
|
||||
"brief": "1. 遥控器对准空调内机\n2. 按电源键开关机\n3. 模式键切换:制冷/制热/除湿\n4. 温度调节:↑/↓ 键\n5. 风速调节:低/中/高/自动",
|
||||
"detail_url": "",
|
||||
"icon": "❄️",
|
||||
"sort_order": 3,
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"category": "phone",
|
||||
"title": "会议室电话使用指南",
|
||||
"brief": "1. 摘机或按免提键\n2. 拨打号码后按#键确认\n3. 转接:按转接键 → 输入分机号\n4. 音量调节:侧面音量键\n5. 挂机结束通话",
|
||||
"detail_url": "",
|
||||
"icon": "📞",
|
||||
"sort_order": 4,
|
||||
"is_active": True,
|
||||
},
|
||||
{
|
||||
"category": "other",
|
||||
"title": "其他设备指南",
|
||||
"brief": "如需查看其他设备(电子白板、音响、灯光等)的使用说明,请扫描右侧二维码查看完整设备手册。",
|
||||
"detail_url": "",
|
||||
"icon": "📋",
|
||||
"sort_order": 5,
|
||||
"is_active": True,
|
||||
},
|
||||
])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""回滚:删除 meetingroom_repair 和 meetingroom_guide 表。"""
|
||||
op.drop_index("ix_meetingroom_repair_conversation_id", table_name="meetingroom_repair")
|
||||
op.drop_index("ix_meetingroom_repair_meetingroom_id", table_name="meetingroom_repair")
|
||||
op.drop_index("ix_meetingroom_repair_terminal_sn", table_name="meetingroom_repair")
|
||||
op.drop_table("meetingroom_repair")
|
||||
|
||||
op.drop_index("ix_meetingroom_guide_category", table_name="meetingroom_guide")
|
||||
op.drop_table("meetingroom_guide")
|
||||
@@ -0,0 +1,107 @@
|
||||
"""triage_sessions + exclusion_rules + exclusion_logs
|
||||
|
||||
Revision ID: 051_triage_exclusion
|
||||
Revises: 050_add_meetingroom_tables
|
||||
Create Date: 2026-07-12
|
||||
|
||||
新增表:
|
||||
- triage_sessions: AI 分诊会话记录
|
||||
- exclusion_rules: 代答排除规则
|
||||
- exclusion_logs: 排除命中日志
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "051_triage_exclusion"
|
||||
down_revision: Union[str, None] = "050_add_meetingroom_tables"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# 1. triage_sessions 表 — AI 分诊会话
|
||||
op.create_table(
|
||||
"triage_sessions",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("conversation_id", sa.String(36), nullable=False),
|
||||
sa.Column("user_id", sa.String(100), nullable=False),
|
||||
sa.Column("user_name", sa.String(100)),
|
||||
sa.Column("user_dept", sa.String(100)),
|
||||
sa.Column("user_level", sa.String(20)),
|
||||
sa.Column("device_info", sa.String(200)),
|
||||
sa.Column("request_title", sa.String(200), nullable=False),
|
||||
sa.Column("request_content", sa.Text, nullable=False),
|
||||
sa.Column("source", sa.String(50), server_default="wecom_h5"),
|
||||
sa.Column("problem_type", sa.String(50)),
|
||||
sa.Column("problem_category", sa.String(100)),
|
||||
sa.Column("confidence", sa.Float),
|
||||
sa.Column("urgency", sa.String(20), server_default="medium"),
|
||||
sa.Column("suggested_route", sa.String(50)),
|
||||
sa.Column("matched_knowledge", sa.String(500)),
|
||||
sa.Column("match_score", sa.Float),
|
||||
sa.Column("context_tags", sa.JSON, server_default="[]"),
|
||||
sa.Column("triage_steps", sa.JSON, server_default="[]"),
|
||||
sa.Column("collected_context", sa.JSON, server_default="[]"),
|
||||
sa.Column("status", sa.String(30), server_default="pending"),
|
||||
sa.Column("route_action", sa.String(50)),
|
||||
sa.Column("route_note", sa.Text),
|
||||
sa.Column("operator_id", sa.String(100)),
|
||||
sa.Column("operated_at", sa.DateTime),
|
||||
sa.Column("created_at", sa.DateTime, server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("idx_triage_status", "triage_sessions", ["status"])
|
||||
op.create_index("idx_triage_urgency", "triage_sessions", ["urgency"])
|
||||
op.create_index("idx_triage_conversation", "triage_sessions", ["conversation_id"])
|
||||
op.create_index("idx_triage_created", "triage_sessions", ["created_at"])
|
||||
op.create_index("idx_triage_user", "triage_sessions", ["user_id"])
|
||||
|
||||
# 2. exclusion_rules 表 — 代答排除规则
|
||||
op.create_table(
|
||||
"exclusion_rules",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("rule_name", sa.String(200), nullable=False, unique=True),
|
||||
sa.Column("rule_description", sa.Text),
|
||||
sa.Column("priority", sa.String(5), nullable=False, server_default="P2"),
|
||||
sa.Column("match_type", sa.String(20), nullable=False),
|
||||
sa.Column("match_condition", sa.Text, nullable=False),
|
||||
sa.Column("match_scope", sa.JSON, nullable=False, server_default='["ai_auto_reply"]'),
|
||||
sa.Column("action_type", sa.String(50), nullable=False, server_default="transfer_human"),
|
||||
sa.Column("transfer_message", sa.Text),
|
||||
sa.Column("status", sa.String(10), nullable=False, server_default="enabled"),
|
||||
sa.Column("hit_count", sa.Integer, server_default="0"),
|
||||
sa.Column("created_by", sa.String(100), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime, server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("idx_exclusion_rules_status", "exclusion_rules", ["status"])
|
||||
op.create_index("idx_exclusion_rules_priority", "exclusion_rules", ["priority"])
|
||||
op.create_index("idx_exclusion_rules_match_type", "exclusion_rules", ["match_type"])
|
||||
|
||||
# 3. exclusion_logs 表 — 排除命中日志
|
||||
op.create_table(
|
||||
"exclusion_logs",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("rule_id", sa.String(36), nullable=False),
|
||||
sa.Column("rule_name", sa.String(200)),
|
||||
sa.Column("conversation_id", sa.String(36)),
|
||||
sa.Column("user_id", sa.String(100)),
|
||||
sa.Column("message_content", sa.Text),
|
||||
sa.Column("match_type", sa.String(20)),
|
||||
sa.Column("matched_detail", sa.Text),
|
||||
sa.Column("action_type", sa.String(50)),
|
||||
sa.Column("action_result", sa.String(50), server_default="success"),
|
||||
sa.Column("created_at", sa.DateTime, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("idx_exclusion_logs_rule", "exclusion_logs", ["rule_id"])
|
||||
op.create_index("idx_exclusion_logs_created", "exclusion_logs", ["created_at"])
|
||||
op.create_index("idx_exclusion_logs_conversation", "exclusion_logs", ["conversation_id"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("exclusion_logs")
|
||||
op.drop_table("exclusion_rules")
|
||||
op.drop_table("triage_sessions")
|
||||
@@ -0,0 +1,231 @@
|
||||
"""diagnostic + queue + quiz + closing mechanism
|
||||
|
||||
Revision ID: 052_diag_queue_quiz
|
||||
Revises: 051_triage_exclusion
|
||||
Create Date: 2026-07-12
|
||||
|
||||
新增表(6张):
|
||||
- diagnostic_templates: 原子化诊断检查项模板库
|
||||
- diagnostic_dispatches: 诊断下发记录(dispatched→executed→analyzed→resolved)
|
||||
- diagnostic_reports: 诊断报告存储 + AI分析结论
|
||||
- quiz_questions: IT知识题库 + 诊断题
|
||||
- quiz_answers: 答题记录
|
||||
- employee_points: 员工积分账户(跨会话累积,5级等级)
|
||||
|
||||
Conversation表新增字段(6个):
|
||||
- queue_priority: 答题插队优先级(min(答题数//3, 2),上限2)
|
||||
- info_locked: 信息是否锁定(Dify步骤完成+有效率≥70%)
|
||||
- resolved_by: 关闭方(employee/agent/ai/system_timeout)
|
||||
- resolved_method: 关闭方式(ai_self/agent_confirm/employee_initiative/auto_timeout)
|
||||
- resolve_summary: 结单摘要(问题类型+根因+解决方式)
|
||||
- reference_conversation_id: 重开时关联的原会话ID
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "052_diag_queue_quiz"
|
||||
down_revision: Union[str, None] = "051_triage_exclusion"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# =====================================================================
|
||||
# 1. Conversation表新增字段
|
||||
# =====================================================================
|
||||
op.add_column("conversations", sa.Column(
|
||||
"queue_priority", sa.Integer, nullable=False, server_default="0",
|
||||
comment="答题插队优先级(上限2)"
|
||||
))
|
||||
op.add_column("conversations", sa.Column(
|
||||
"info_locked", sa.Boolean, nullable=False, server_default=sa.text("false"),
|
||||
comment="信息是否锁定"
|
||||
))
|
||||
op.add_column("conversations", sa.Column(
|
||||
"resolved_by", sa.String(20), nullable=True,
|
||||
comment="关闭方: employee/agent/ai/system_timeout"
|
||||
))
|
||||
op.add_column("conversations", sa.Column(
|
||||
"resolved_method", sa.String(30), nullable=True,
|
||||
comment="关闭方式: ai_self/agent_confirm/employee_initiative/auto_timeout"
|
||||
))
|
||||
op.add_column("conversations", sa.Column(
|
||||
"resolve_summary", sa.Text, nullable=True,
|
||||
comment="结单摘要"
|
||||
))
|
||||
op.add_column("conversations", sa.Column(
|
||||
"reference_conversation_id", sa.String(36), nullable=True,
|
||||
comment="重开时关联的原会话ID"
|
||||
))
|
||||
|
||||
# 为排队查询添加索引
|
||||
op.create_index(
|
||||
"idx_conversations_queue_priority",
|
||||
"conversations",
|
||||
["queue_priority"]
|
||||
)
|
||||
op.create_index(
|
||||
"idx_conversations_info_locked",
|
||||
"conversations",
|
||||
["info_locked"]
|
||||
)
|
||||
|
||||
# =====================================================================
|
||||
# 2. diagnostic_templates — 诊断模板库
|
||||
# =====================================================================
|
||||
op.create_table(
|
||||
"diagnostic_templates",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("category", sa.String(50), nullable=False, comment="问题类别"),
|
||||
sa.Column("name", sa.String(200), nullable=False, comment="检查项名称"),
|
||||
sa.Column("check_type", sa.String(20), nullable=False, server_default="api",
|
||||
comment="检查类型: api/script"),
|
||||
sa.Column("api_source", sa.String(50), nullable=True, comment="API来源: huorong/lianruan"),
|
||||
sa.Column("api_method", sa.String(100), nullable=True, comment="调用的API方法名"),
|
||||
sa.Column("script_template", sa.Text, nullable=True, comment="脚本模板"),
|
||||
sa.Column("fix_template", sa.Text, nullable=True, comment="修复脚本模板"),
|
||||
sa.Column("fix_risk_level", sa.String(20), nullable=False, server_default="medium",
|
||||
comment="修复风险等级: low/medium/high"),
|
||||
sa.Column("target_condition", sa.String(200), nullable=True, comment="触发条件"),
|
||||
sa.Column("description", sa.Text, nullable=True, comment="检查项描述"),
|
||||
sa.Column("is_active", sa.Boolean, nullable=False, server_default=sa.text("true"),
|
||||
comment="是否启用"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("idx_diag_tpl_category", "diagnostic_templates", ["category"])
|
||||
op.create_index("idx_diag_tpl_type", "diagnostic_templates", ["check_type"])
|
||||
op.create_index("idx_diag_tpl_active", "diagnostic_templates", ["is_active"])
|
||||
|
||||
# =====================================================================
|
||||
# 3. diagnostic_dispatches — 诊断下发记录
|
||||
# =====================================================================
|
||||
op.create_table(
|
||||
"diagnostic_dispatches",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("conversation_id", sa.String(36), nullable=False, comment="关联会话ID"),
|
||||
sa.Column("employee_id", sa.String(64), nullable=False, comment="员工ID"),
|
||||
sa.Column("template_ids", sa.JSON, nullable=False, server_default="[]",
|
||||
comment="诊断模板ID列表"),
|
||||
sa.Column("script_content", sa.Text, nullable=True, comment="生成的脚本内容"),
|
||||
sa.Column("script_hash", sa.String(64), nullable=True, comment="脚本SHA256哈希"),
|
||||
sa.Column("upload_token", sa.String(128), nullable=True, comment="一次性上传token"),
|
||||
sa.Column("status", sa.String(20), nullable=False, server_default="dispatched",
|
||||
comment="状态: dispatched/executed/analyzed/resolved"),
|
||||
sa.Column("report_id", sa.String(36), nullable=True, comment="关联诊断报告ID"),
|
||||
sa.Column("fix_dispatched", sa.Boolean, nullable=False, server_default=sa.text("false"),
|
||||
comment="是否已下发修复包"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True, comment="完成时间"),
|
||||
)
|
||||
op.create_index("idx_diag_dispatch_conv", "diagnostic_dispatches", ["conversation_id"])
|
||||
op.create_index("idx_diag_dispatch_employee", "diagnostic_dispatches", ["employee_id"])
|
||||
op.create_index("idx_diag_dispatch_status", "diagnostic_dispatches", ["status"])
|
||||
|
||||
# =====================================================================
|
||||
# 4. diagnostic_reports — 诊断报告存储
|
||||
# =====================================================================
|
||||
op.create_table(
|
||||
"diagnostic_reports",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("dispatch_id", sa.String(36), nullable=True, comment="关联下发记录ID"),
|
||||
sa.Column("conversation_id", sa.String(36), nullable=False, comment="关联会话ID"),
|
||||
sa.Column("employee_id", sa.String(64), nullable=False, comment="员工ID"),
|
||||
sa.Column("template_ids", sa.JSON, nullable=False, server_default="[]",
|
||||
comment="涉及诊断模板ID列表"),
|
||||
sa.Column("report_data", sa.JSON, nullable=False, server_default="{}",
|
||||
comment="检查结果JSON"),
|
||||
sa.Column("ai_analysis", sa.JSON, nullable=True, comment="AI分析结论JSON"),
|
||||
sa.Column("status", sa.String(20), nullable=False, server_default="pending",
|
||||
comment="报告状态: pending/analyzed/resolved"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("idx_diag_report_conv", "diagnostic_reports", ["conversation_id"])
|
||||
op.create_index("idx_diag_report_employee", "diagnostic_reports", ["employee_id"])
|
||||
op.create_index("idx_diag_report_status", "diagnostic_reports", ["status"])
|
||||
|
||||
# =====================================================================
|
||||
# 5. quiz_questions — IT知识题库
|
||||
# =====================================================================
|
||||
op.create_table(
|
||||
"quiz_questions",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("type", sa.String(20), nullable=False, server_default="knowledge",
|
||||
comment="题目类型: knowledge/diagnostic"),
|
||||
sa.Column("category", sa.String(50), nullable=False, comment="题目类别"),
|
||||
sa.Column("problem_category", sa.String(100), nullable=True,
|
||||
comment="诊断题对应的问题类别"),
|
||||
sa.Column("difficulty", sa.String(20), nullable=False, server_default="medium",
|
||||
comment="难度: easy/medium/hard"),
|
||||
sa.Column("question", sa.Text, nullable=False, comment="题目文本"),
|
||||
sa.Column("options", sa.JSON, nullable=False, comment="选项数组"),
|
||||
sa.Column("correct_index", sa.Integer, nullable=False, comment="正确答案索引"),
|
||||
sa.Column("explanation", sa.Text, nullable=True, comment="答案解析"),
|
||||
sa.Column("is_active", sa.Boolean, nullable=False, server_default=sa.text("true"),
|
||||
comment="是否启用"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("idx_quiz_q_type", "quiz_questions", ["type"])
|
||||
op.create_index("idx_quiz_q_category", "quiz_questions", ["category"])
|
||||
op.create_index("idx_quiz_q_active", "quiz_questions", ["is_active"])
|
||||
|
||||
# =====================================================================
|
||||
# 6. quiz_answers — 答题记录
|
||||
# =====================================================================
|
||||
op.create_table(
|
||||
"quiz_answers",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("employee_id", sa.String(64), nullable=False, comment="员工ID"),
|
||||
sa.Column("conversation_id", sa.String(36), nullable=True, comment="关联会话ID"),
|
||||
sa.Column("question_id", sa.String(36), nullable=False, comment="题目ID"),
|
||||
sa.Column("selected_index", sa.Integer, nullable=False, comment="选择的答案索引"),
|
||||
sa.Column("is_correct", sa.Boolean, nullable=False, comment="是否答对"),
|
||||
sa.Column("points_earned", sa.Integer, nullable=False, server_default="0",
|
||||
comment="获得积分"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("idx_quiz_a_employee", "quiz_answers", ["employee_id"])
|
||||
op.create_index("idx_quiz_a_conversation", "quiz_answers", ["conversation_id"])
|
||||
op.create_index("idx_quiz_a_created", "quiz_answers", ["created_at"])
|
||||
|
||||
# =====================================================================
|
||||
# 7. employee_points — 员工积分账户
|
||||
# =====================================================================
|
||||
op.create_table(
|
||||
"employee_points",
|
||||
sa.Column("employee_id", sa.String(64), primary_key=True),
|
||||
sa.Column("total_points", sa.Integer, nullable=False, server_default="0",
|
||||
comment="累计积分"),
|
||||
sa.Column("answered_count", sa.Integer, nullable=False, server_default="0",
|
||||
comment="答题总数"),
|
||||
sa.Column("correct_count", sa.Integer, nullable=False, server_default="0",
|
||||
comment="答对总数"),
|
||||
sa.Column("level", sa.String(20), nullable=False, server_default="IT小白",
|
||||
comment="当前等级"),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# 删除新增表(逆序)
|
||||
op.drop_table("employee_points")
|
||||
op.drop_table("quiz_answers")
|
||||
op.drop_table("quiz_questions")
|
||||
op.drop_table("diagnostic_reports")
|
||||
op.drop_table("diagnostic_dispatches")
|
||||
op.drop_table("diagnostic_templates")
|
||||
|
||||
# 删除Conversation表新增索引
|
||||
op.drop_index("idx_conversations_info_locked", "conversations")
|
||||
op.drop_index("idx_conversations_queue_priority", "conversations")
|
||||
|
||||
# 删除Conversation表新增字段
|
||||
op.drop_column("conversations", "reference_conversation_id")
|
||||
op.drop_column("conversations", "resolve_summary")
|
||||
op.drop_column("conversations", "resolved_method")
|
||||
op.drop_column("conversations", "resolved_by")
|
||||
op.drop_column("conversations", "info_locked")
|
||||
op.drop_column("conversations", "queue_priority")
|
||||
Reference in New Issue
Block a user