Files
wecom_it_smart_desk/backend/app/schemas/automation.py
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

471 lines
14 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 自动化闭环 Schema
# =============================================================================
# 说明:定义自动化会话相关接口的 Pydantic 请求/响应模型,以及 ORM → dict
# 序列化辅助函数。所有响应沿用项目 {code, data, message} 约定,
# 此处只描述 data 结构。
# =============================================================================
from __future__ import annotations
from datetime import datetime
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
# --------------------------------------------------------------------------
# 请求模型
# --------------------------------------------------------------------------
class CreateSessionRequest(BaseModel):
"""创建自动化会话请求。"""
conversation_id: Optional[str] = Field(None, description="关联工单ID(弱关联)")
employee_id: str = Field(..., description="发起员工企微 UserID")
description: str = Field(..., description="员工诉求/原始消息")
mode: str = Field("real_exec", description="执行模式:plan_only / real_exec")
class ApprovalDecisionRequest(BaseModel):
"""坐席审批决策请求。"""
decision: str = Field(..., description="approve / reject")
note: Optional[str] = Field(None, description="审批意见")
class ConfirmRequest(BaseModel):
"""员工 H5 二次确认请求。"""
confirmed: bool = Field(..., description="是否确认执行")
note: Optional[str] = Field(None, description="备注")
class TakeoverRequest(BaseModel):
"""转人工接管请求。"""
agent_id: str = Field(..., description="接管坐席ID")
note: Optional[str] = Field(None, description="接管说明")
class ResolveFeedbackRequest(BaseModel):
"""处置结果反馈(员工是否满意)。"""
satisfied: bool = Field(True, description="是否满意")
note: Optional[str] = Field(None, description="反馈备注")
class ScenarioConfigUpdate(BaseModel):
"""场景配置更新请求(管理端)。"""
name: Optional[str] = None
description: Optional[str] = None
enabled: Optional[bool] = None
trigger_conditions: Optional[dict] = None
actions: Optional[list] = None
approval_strategy: Optional[dict] = None
# --------------------------------------------------------------------------
# 响应模型
# --------------------------------------------------------------------------
class IntentResult(BaseModel):
"""意图识别结果。"""
scenario_key: Optional[str] = None
confidence: float = 0.0
raw: str = ""
error: str = ""
class ActionResponse(BaseModel):
"""处置动作响应。"""
id: str
session_id: str
action_index: int
action_type: str
adapter: str
risk_level: str
title: str
description: str
status: str
payload: Optional[dict] = None
result: Optional[dict] = None
error: Optional[str] = None
approved_by: Optional[str] = None
approved_at: Optional[str] = None
class ApprovalTicketResponse(BaseModel):
"""审批单响应。"""
id: str
action_id: str
session_id: str
approver_id: Optional[str] = None
channel: str
status: str
reason: Optional[str] = None
decision_note: Optional[str] = None
decided_at: Optional[str] = None
class SessionResponse(BaseModel):
"""自动化会话响应。"""
id: str
conversation_id: Optional[str] = None
employee_id: str
agent_id: Optional[str] = None
scenario_key: Optional[str] = None
status: str
mode: str
confidence: float
title: str
intent: Optional[dict] = None
current_action_id: Optional[str] = None
auto_close_at: Optional[str] = None
resolved_at: Optional[str] = None
closed_by: Optional[str] = None
meta: Optional[dict] = None
# 复杂场景重构:暂停时间戳
paused_at: Optional[str] = None
actions: List[ActionResponse] = Field(default_factory=list)
approval: Optional[ApprovalTicketResponse] = None
created_at: Optional[str] = None
updated_at: Optional[str] = None
class ScenarioConfigResponse(BaseModel):
"""场景配置响应。"""
id: str
scenario_key: str
name: str
description: str
enabled: bool
trigger_conditions: Optional[dict] = None
actions: Optional[list] = None
approval_strategy: Optional[dict] = None
current_version_id: Optional[str] = None
class RuleVersionResponse(BaseModel):
"""规则版本响应。"""
id: str
scenario_key: str
version: int
content: Optional[dict] = None
status: str
canary_percent: int
created_by: Optional[str] = None
remark: str
created_at: Optional[str] = None
class AutoMetricsResponse(BaseModel):
"""自动化看板指标响应。"""
total_sessions: int = 0
resolved_sessions: int = 0
handoff_sessions: int = 0
error_sessions: int = 0
auto_executed_actions: int = 0
approval_required_actions: int = 0
by_scenario: Dict[str, int] = Field(default_factory=dict)
# --------------------------------------------------------------------------
# 序列化辅助
# --------------------------------------------------------------------------
def _iso(dt: Optional[datetime]) -> Optional[str]:
"""将 datetime 转为 ISO 字符串(None 透传)。"""
return dt.isoformat() if dt else None
def serialize_action(action: Any) -> ActionResponse:
"""将 AutoAction ORM 对象序列化为响应模型。"""
return ActionResponse(
id=action.id,
session_id=action.session_id,
action_index=action.action_index,
action_type=action.action_type,
adapter=action.adapter,
risk_level=action.risk_level,
title=action.title,
description=action.description,
status=action.status,
payload=action.payload,
result=action.result,
error=action.error,
approved_by=action.approved_by,
approved_at=_iso(action.approved_at),
)
def serialize_approval(ticket: Any) -> ApprovalTicketResponse:
"""将 ApprovalTicket ORM 对象序列化为响应模型。"""
return ApprovalTicketResponse(
id=ticket.id,
action_id=ticket.action_id,
session_id=ticket.session_id,
approver_id=ticket.approver_id,
channel=ticket.channel,
status=ticket.status,
reason=ticket.reason,
decision_note=ticket.decision_note,
decided_at=_iso(ticket.decided_at),
)
def serialize_session(
session: Any,
actions: Optional[List[Any]] = None,
ticket: Optional[Any] = None,
) -> SessionResponse:
"""将 AutoSession ORM 对象序列化为响应模型。"""
return SessionResponse(
id=session.id,
conversation_id=session.conversation_id,
employee_id=session.employee_id,
agent_id=session.agent_id,
scenario_key=session.scenario_key,
status=session.status,
mode=session.mode,
confidence=session.confidence,
title=session.title,
intent=session.intent,
current_action_id=session.current_action_id,
auto_close_at=_iso(session.auto_close_at),
resolved_at=_iso(session.resolved_at),
closed_by=session.closed_by,
meta=session.meta,
paused_at=_iso(session.paused_at),
actions=[serialize_action(a) for a in (actions or [])],
approval=serialize_approval(ticket) if ticket else None,
created_at=_iso(session.created_at),
updated_at=_iso(session.updated_at),
)
# ===========================================================================
# 复杂场景重构第一阶段 — 新增 Schema
# ===========================================================================
# --------------------------------------------------------------------------
# 全局意图识别结果(扩展 IntentResult
# --------------------------------------------------------------------------
class GlobalIntentResult(BaseModel):
"""全局意图识别结果。"""
global_intent: Optional[str] = None # pause / resume_task / correct / supplement / null
scenario_key: Optional[str] = None # 未命中全局意图时走场景识别
confidence: float = 0.0
corrected_field: Optional[str] = None # CORRECT: 更正的字段名
old_value: Optional[str] = None # CORRECT: 旧值
new_value: Optional[str] = None # CORRECT: 新值
supplement_field: Optional[str] = None # SUPPLEMENT: 补充的字段名
supplement_value: Optional[str] = None # SUPPLEMENT: 补充的值
raw: str = ""
error: str = ""
# --------------------------------------------------------------------------
# 请求模型(复杂场景重构)
# --------------------------------------------------------------------------
class PauseRequest(BaseModel):
"""暂停会话请求(通常由意图识别触发,也可直接调用)。"""
reason: Optional[str] = None
class ResumeRequest(BaseModel):
"""恢复会话请求。"""
session_id: Optional[str] = None # 多任务时指定恢复哪个;None 则自动选择唯一的
class CorrectRequest(BaseModel):
"""信息更正请求。"""
field: str = Field(..., description="更正的字段名")
new_value: str = Field(..., description="新值")
old_value: Optional[str] = None
class SupplementRequest(BaseModel):
"""信息补充请求。"""
field: str = Field(..., description="补充的字段名")
value: str = Field(..., description="补充的值")
class AgentResumeRequest(BaseModel):
"""坐席代恢复请求。"""
note: Optional[str] = None
class AgentCloseRequest(BaseModel):
"""坐席关闭会话请求。"""
note: Optional[str] = None
# --------------------------------------------------------------------------
# 响应模型(复杂场景重构)
# --------------------------------------------------------------------------
class InformationItemResponse(BaseModel):
"""信息项响应。"""
id: str
session_id: str
name: str
value: str
modifiers: List[str] = Field(default_factory=list)
is_filled: bool
is_locked: bool
version: int
update_history: List[dict] = Field(default_factory=list)
updated_at: Optional[str] = None
class ResumePointResponse(BaseModel):
"""恢复点响应。"""
session_id: str
title: str
scenario_key: Optional[str] = None
current_step: str
paused_at: Optional[str] = None
pending_items: List[str] = Field(default_factory=list)
info_items: List[InformationItemResponse] = Field(default_factory=list)
class PausedSessionItem(BaseModel):
"""暂停会话列表项。"""
session_id: str
title: str
scenario_key: Optional[str] = None
paused_at: Optional[str] = None
paused_duration: str = "" # 如 "2h 15min"
# --------------------------------------------------------------------------
# 序列化辅助(复杂场景重构)
# --------------------------------------------------------------------------
def serialize_information_item(item: Any) -> InformationItemResponse:
"""将 InformationItem ORM 对象序列化为响应模型。"""
return InformationItemResponse(
id=item.id,
session_id=item.session_id,
name=item.name,
value=item.value,
modifiers=item.modifiers or [],
is_filled=item.is_filled,
is_locked=item.is_locked,
version=item.version,
update_history=item.update_history or [],
updated_at=_iso(item.updated_at),
)
# ===========================================================================
# 复杂场景重构第二阶段 — P2/P3 新增 Schema
# ===========================================================================
# --------------------------------------------------------------------------
# P3 请求模型
# --------------------------------------------------------------------------
class BatchCorrectRequest(BaseModel):
"""批量更正请求。"""
corrections: List[dict] = Field(
..., description="更正列表 [{field, new_value, old_value?}, ...]"
)
reason: Optional[str] = Field(None, description="更正备注")
class UndoCorrectionRequest(BaseModel):
"""撤销更正请求。"""
pass # 无参数,撤销最近一次更正
class VersionDiffRequest(BaseModel):
"""版本对比请求。"""
v1: int = Field(..., description="版本号1")
v2: int = Field(..., description="版本号2")
# --------------------------------------------------------------------------
# P3 响应模型
# --------------------------------------------------------------------------
class BatchCorrectResponse(BaseModel):
"""批量更正响应。"""
corrected_items: List[dict] = Field(default_factory=list)
snapshot_id: int
dependency_warnings: List[dict] = Field(default_factory=list)
class UndoCorrectionResponse(BaseModel):
"""撤销更正响应。"""
undone_items: List[str] = Field(default_factory=list)
restored_values: Dict[str, str] = Field(default_factory=dict)
snapshot_id: int
remaining_undo_count: int = 5
class CorrectionHistoryResponse(BaseModel):
"""更正历史响应。"""
history: List[dict] = Field(default_factory=list)
class VersionChainResponse(BaseModel):
"""版本链响应。"""
item_key: str
chain: List[dict] = Field(default_factory=list)
class VersionDiffResponse(BaseModel):
"""版本对比响应。"""
item_key: str
v1: int
v1_value: str
v2: int
v2_value: str
changed: bool
# --------------------------------------------------------------------------
# P2 响应模型
# --------------------------------------------------------------------------
class CompressionLogItem(BaseModel):
"""压缩日志项。"""
id: int
session_id: str
tokens_before: int
tokens_after: int
compression_ratio: float
task_node: Optional[str] = None
duration_ms: int
compression_level: int
summary: Optional[str] = None
created_at: Optional[str] = None
class CompressionLogListResponse(BaseModel):
"""压缩日志列表响应。"""
logs: List[CompressionLogItem] = Field(default_factory=list)
total: int = 0