# ============================================================================= # 企微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, 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) 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""