Files
wecom_it_smart_desk/backend/app/models/automation.py
T

314 lines
16 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智能服务台 — 阶段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"<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})>"