# ============================================================================= # 企微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"" # ============================================================================= # 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"" # ============================================================================= # 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"" # ============================================================================= # 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"" # ============================================================================= # 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-100,100 表示全量) 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"" # ============================================================================= # 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"" # ============================================================================= # 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"" # ============================================================================= # 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"" # ============================================================================= # 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"" # ============================================================================= # 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""