314 lines
16 KiB
Python
314 lines
16 KiB
Python
|
|
# =============================================================================
|
|||
|
|
# 企微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-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"<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})>"
|