Files
Simon bea288e414 feat: 2026-07-11 全量更新 - 代办集成+会议室预定+知识迭代修复+UI统一+Bug修复
== 已部署上线 (9项) ==
- 代办事项真实数据源集成 (企微审批API 8bug修复链)
- H5/坐席端 Logo样式统一+绿色背景
- 视频引导页修复 (localStorage key v2)
- 坐席端 v9 Vue版本修复 (ElMessage._context)
- 截图按钮 v10 修复 (getDisplayMedia user gesture)
- 扫码样式恢复+H5扫码登录跳转修复
- H5截图快捷键提示

== 代码完成待部署 (3项) ==
- 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查)
- 会议室预定-小鱼易联终端 (40文件, 40/40测试通过)
- IT资产升级审批推送 (asset_service.py)

== 需求文档 (2项) ==
- 坐席端AI辅助消息框-PRD (4项新功能确认)
- 坐席端布局优化建议 v2.0 (7天计划)

== 新增文档 ==
- 日报-2026-07-11.md
- 知识迭代Bug修复报告-20260711.md
- 会议室预定-部署指南.md
- CHANGELOG.md 更新

== 测试 ==
- test_todo_integration.py: 40/40
- test_meetingroom.py: 40/40
- test_bugfix_ki_suggestions.py: 21/21
2026-07-11 23:13:10 +08:00

434 lines
22 KiB
Python
Raw Permalink 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智能服务台 — 阶段5 自动化闭环 数据模型
# =============================================================================
# 说明:自动化引擎相关表,表名统一前缀 auto_。
# 模型清单:
# 1. AutoSession — 自动化处置会话(生命周期/状态机)
# 2. AutoAction — 单个处置动作(与场景动作计划对应)
# 3. ApprovalTicket — 审批单(高危动作 / 员工二次确认)
# 4. ScenarioConfig — 场景配置(开关 + 触发条件 + 动作 + 审批策略)
# 5. RuleVersion — 规则版本(P1 灰度发布)
# 6. ActionLog — 外部调用审计日志(出入参全记录)
# 7. MappingCache — 终端/员工映射缓存(联软>eHR,TTL)
#
# 风格:沿用项目既有模型写法(SQLAlchemy 2.0 Mapped / mapped_column + 注释)。
# 关联:AutoSession 与工单(conversation)为「弱关联」(仅存 conversation_id
# 不建外键约束,便于阶段5 独立演进)。
# =============================================================================
import uuid
from datetime import datetime
from typing import Optional
from sqlalchemy import JSON, Boolean, DateTime, Float, Integer, Numeric, SmallInteger, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
def _uuid() -> str:
"""生成 UUID 字符串主键(兼容 PostgreSQL 与 SQLite)。"""
return str(uuid.uuid4())
# =============================================================================
# 1. 自动化处置会话
# =============================================================================
class AutoSession(Base):
"""自动化处置会话 — 对应 auto_sessions 表。
一个员工的一次自动化处置诉求对应一个会话,贯穿「意图识别→动作编排→
执行/审批→处置成功→静默关单」全过程。
Attributes:
id: 会话唯一ID
conversation_id: 关联工单ID(弱关联,可空)
employee_id: 发起员工企微 UserID
agent_id: 接管坐席ID(转人工后填入)
scenario_key: 命中场景(password_reset/software_install/virus_dispose/terminal_locate
status: 会话状态机(created/running/paused/resolved/closed/handoff/error
mode: 执行模式(plan_only/real_exec
confidence: 意图识别置信度(0-1
intent: 意图识别原始结果(JSON)
current_action_id: 当前正在执行的动作ID
title: 会话标题(展示用)
auto_close_at: 静默关单触发时间(写入 Redis TTL 同步)
resolved_at: 处置成功时间
closed_by: 关单/接管操作人
meta: 扩展上下文(原始消息、映射结果等)
"""
__tablename__ = "auto_sessions"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
conversation_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True, index=True)
employee_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
agent_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, index=True)
scenario_key: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, index=True)
status: Mapped[str] = mapped_column(String(20), nullable=False, default="created", index=True)
mode: Mapped[str] = mapped_column(String(20), nullable=False, default="real_exec")
confidence: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
intent: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
current_action_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True)
title: Mapped[str] = mapped_column(String(256), nullable=False, default="")
auto_close_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
resolved_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
closed_by: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
meta: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
# 复杂场景重构:暂停时间戳,用于超时计算;恢复后置 NULL
paused_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=datetime.now, onupdate=datetime.now
)
def __repr__(self) -> str:
return f"<AutoSession(id={self.id}, scenario={self.scenario_key}, status={self.status})>"
# =============================================================================
# 2. 处置动作
# =============================================================================
class AutoAction(Base):
"""单个处置动作 — 对应 auto_actions 表。
每个动作对应场景动作计划中的一步,由 action_registry 的适配器真正执行。
Attributes:
id: 动作ID
session_id: 所属会话
action_index: 动作在计划中的顺序
action_type: 语义动作类型(password_reset_guide/software_install/virus_quarantine/terminal_locate...
adapter: 执行适配器(huorong/lianruan/ehr/atrust/...
risk_level: 风险等级(read/low/high
title/description: 展示信息
status: 动作状态机
payload: 动作入参
result: 动作执行结果
error: 错误信息
approved_by/approved_at: 审批人/时间
"""
__tablename__ = "auto_actions"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
session_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
action_index: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
action_type: Mapped[str] = mapped_column(String(64), nullable=False, default="")
adapter: Mapped[str] = mapped_column(String(32), nullable=False, default="")
risk_level: Mapped[str] = mapped_column(String(16), nullable=False, default="read")
title: Mapped[str] = mapped_column(String(256), nullable=False, default="")
description: Mapped[str] = mapped_column(Text, nullable=False, default="")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending", index=True)
payload: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
result: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
error: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
approved_by: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
approved_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=datetime.now, onupdate=datetime.now
)
def __repr__(self) -> str:
return f"<AutoAction(id={self.id}, type={self.action_type}, status={self.status})>"
# =============================================================================
# 3. 审批单
# =============================================================================
class ApprovalTicket(Base):
"""审批单 — 对应 auto_approval_tickets 表。
高危动作需坐席审批,或写操作需员工 H5 二次确认时生成审批单。
Attributes:
id: 审批单ID
action_id: 关联动作
session_id: 关联会话
approver_id: 审批人(坐席/员工)
channel: 审批渠道(agent/h5
status: 审批状态(pending/approved/rejected/expired
reason: 申请理由
decision_note: 审批意见
decided_at: 决定时间
"""
__tablename__ = "auto_approval_tickets"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
action_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
session_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
approver_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
channel: Mapped[str] = mapped_column(String(16), nullable=False, default="agent")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending", index=True)
reason: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
decision_note: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
decided_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
def __repr__(self) -> str:
return f"<ApprovalTicket(id={self.id}, action={self.action_id}, status={self.status})>"
# =============================================================================
# 4. 场景配置
# =============================================================================
class ScenarioConfig(Base):
"""场景配置 — 对应 auto_scenario_configs 表。
本期(Q5)管理后台结构化简易配置:场景开关 + 触发条件 + 动作 + 审批策略。
编排引擎列为 P2,本期动作计划为静态模板。
Attributes:
scenario_key: 场景键(唯一,password_reset/software_install/virus_dispose/terminal_locate
name/description: 展示信息
enabled: 是否启用
trigger_conditions: 触发条件({"intents":[],"keywords":[]}
actions: 动作计划(有序列表,每项 {action_type, adapter, risk_level, params}
approval_strategy: 审批策略({"read":"auto","low":"auto","high":"approval"}
current_version_id: 当前生效版本ID
"""
__tablename__ = "auto_scenario_configs"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
scenario_key: Mapped[str] = mapped_column(String(64), nullable=False, unique=True, index=True)
name: Mapped[str] = mapped_column(String(128), nullable=False, default="")
description: Mapped[str] = mapped_column(Text, nullable=False, default="")
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
trigger_conditions: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
actions: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
approval_strategy: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
current_version_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=datetime.now, onupdate=datetime.now
)
def __repr__(self) -> str:
return f"<ScenarioConfig(key={self.scenario_key}, enabled={self.enabled})>"
# =============================================================================
# 5. 规则版本(P1 灰度)
# =============================================================================
class RuleVersion(Base):
"""规则版本 — 对应 auto_rule_versions 表。
支持场景配置的版本化与灰度发布(P1):每次新建/修改生成快照,
canary_percent 控制灰度比例,status 控制 draft/published/archived。
Attributes:
scenario_key: 所属场景
version: 版本号(同场景内递增)
content: 配置快照(actions + approval_strategy + trigger_conditions
status: 版本状态(draft/published/archived
canary_percent: 灰度比例(0-100100 表示全量)
created_by: 创建人
remark: 版本说明
"""
__tablename__ = "auto_rule_versions"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
scenario_key: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
content: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
status: Mapped[str] = mapped_column(String(20), nullable=False, default="draft", index=True)
canary_percent: Mapped[int] = mapped_column(Integer, nullable=False, default=100)
created_by: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
remark: Mapped[str] = mapped_column(Text, nullable=False, default="")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
def __repr__(self) -> str:
return f"<RuleVersion(key={self.scenario_key}, v={self.version}, status={self.status})>"
# =============================================================================
# 6. 外部调用审计日志
# =============================================================================
class ActionLog(Base):
"""外部调用审计日志 — 对应 auto_action_logs 表。
所有外部系统调用的出入参全量落表,满足审计与排障需求。
Attributes:
session_id: 关联会话
action_id: 关联动作(可选)
employee_id: 关联员工(可选)
event: 事件名(如 huorong.isolate / dify.intent / lianruan.query
direction: 方向(in/out
system: 外部系统(huorong/lianruan/dify/ragflow/ehr/internal
request/response: 出入参(脱敏后)
status: 状态(success/error/http_xxx
latency_ms: 耗时
error: 错误
"""
__tablename__ = "auto_action_logs"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
session_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True, index=True)
action_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True, index=True)
employee_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
event: Mapped[str] = mapped_column(String(128), nullable=False, default="")
direction: Mapped[str] = mapped_column(String(8), nullable=False, default="out")
system: Mapped[str] = mapped_column(String(32), nullable=False, default="internal")
request: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
response: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
status: Mapped[str] = mapped_column(String(32), nullable=False, default="")
latency_ms: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
error: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
def __repr__(self) -> str:
return f"<ActionLog(event={self.event}, system={self.system}, status={self.status})>"
# =============================================================================
# 7. 终端/员工映射缓存
# =============================================================================
class MappingCache(Base):
"""映射缓存 — 对应 auto_mapping_cache 表。
联软(主)解析员工→终端结果缓存,TTL 过期后回退 EHR 兜底并刷新。
避免每次处置都打联软,降低外部依赖压力。
Attributes:
employee_id: 员工ID
source: 映射源(lianruan/ehr
mapped_data: 映射结果(终端列表/部门/资产等)
expires_at: 过期时间
"""
__tablename__ = "auto_mapping_cache"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
employee_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
source: Mapped[str] = mapped_column(String(32), nullable=False, default="lianruan")
mapped_data: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
expires_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
def __repr__(self) -> str:
return f"<MappingCache(employee={self.employee_id}, source={self.source})>"
# =============================================================================
# 8. 信息项(复杂场景重构第一阶段)
# =============================================================================
class InformationItem(Base):
"""信息项 — 对应 auto_information_items 表。
管理对话中收集的信息项及其变更历史,支持更正(CORRECT)与补充(SUPPLEMENT)。
修饰符(modifiers)取值:
固定 — 动作执行后锁定,不可更正
增量 — 补充时追加而非覆盖
明确 — 用户明确提供的值
隐含 — 从上下文推断的值
复述 — 更正后需向员工发送确认消息
必需 — 流程阻塞项,未填写时不可继续执行
Attributes:
id: UUID 主键
session_id: 关联会话ID(弱关联,不建外键)
name: 信息项名称(如"用户名""终端ID""部门"
value: 当前值
modifiers: 修饰符列表,如 ["固定","必需"]
is_filled: 是否已填写
is_locked: 是否已锁定(固定修饰符 + 关联动作已执行 → True,不可更正)
version: 版本号,每次更正/补充 +1
update_history: 变更历史数组
created_at: 创建时间
updated_at: 最后更新时间
"""
__tablename__ = "auto_information_items"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
session_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(128), nullable=False)
value: Mapped[str] = mapped_column(Text, nullable=False, default="")
modifiers: Mapped[list] = mapped_column(JSON, nullable=False, default=list)
is_filled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
is_locked: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
update_history: Mapped[list] = mapped_column(JSON, nullable=False, default=list)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=datetime.now, onupdate=datetime.now
)
# Phase 2: 复杂场景重构第二阶段新增
derived_from: Mapped[Optional[list]] = mapped_column(JSON, nullable=True) # 推导来源 item_key 数组
correction_reason: Mapped[Optional[str]] = mapped_column(String(200), nullable=True) # 更正备注
def __repr__(self) -> str:
return f"<InformationItem(session={self.session_id}, name={self.name}, v={self.version})>"
# =============================================================================
# 9. 上下文压缩记录(复杂场景重构第二阶段 P2)
# =============================================================================
class ContextCompression(Base):
"""上下文压缩记录 — 对应 auto_context_compressions 表。
每次上下文压缩操作的日志记录,包含压缩前后 token 数、压缩比、耗时等。
Attributes:
session_id: 关联会话ID
tokens_before: 压缩前 token 数
tokens_after: 压缩后 token 数
compression_ratio: 压缩比(after/before
task_node: 压缩时任务节点
duration_ms: 压缩耗时(毫秒)
compression_level: 压缩级别(1/2/3
summary: 压缩摘要内容
"""
__tablename__ = "auto_context_compressions"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
session_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
tokens_before: Mapped[int] = mapped_column(Integer, nullable=False)
tokens_after: Mapped[int] = mapped_column(Integer, nullable=False)
compression_ratio: Mapped[float] = mapped_column(Numeric(5, 2), nullable=False)
task_node: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
duration_ms: Mapped[int] = mapped_column(Integer, nullable=False)
compression_level: Mapped[int] = mapped_column(SmallInteger, nullable=False, default=1)
summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
def __repr__(self) -> str:
return f"<ContextCompression(session={self.session_id}, {self.tokens_before}{self.tokens_after})>"
# =============================================================================
# 10. 信息项快照(复杂场景重构第二阶段 P3)
# =============================================================================
class InformationSnapshot(Base):
"""信息项快照 — 对应 auto_information_snapshots 表。
每次更正发生前创建快照,记录该 session 全部信息项的完整状态,
用于支持更正撤销(undo)功能。
Attributes:
session_id: 关联会话ID
trigger_item_key: 触发更正的信息项 key
snapshot_data: 全部信息项快照 {item_key: {value, version}}
correction_ids: 本次更正涉及的信息项版本 ID 列表
is_undone: 是否已被撤销
"""
__tablename__ = "auto_information_snapshots"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
session_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
trigger_item_key: Mapped[str] = mapped_column(String(64), nullable=False)
snapshot_data: Mapped[dict] = mapped_column(JSON, nullable=False)
correction_ids: Mapped[list] = mapped_column(JSON, nullable=False, default=list)
is_undone: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=datetime.now)
def __repr__(self) -> str:
return f"<InformationSnapshot(session={self.session_id}, trigger={self.trigger_item_key}, undone={self.is_undone})>"