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
This commit is contained in:
@@ -27,6 +27,12 @@ from app.models.audit_log import AuditLog
|
||||
from app.models.conversation_annotation import ConversationAnnotation # P2-10 会话标注
|
||||
from app.models.knowledge_suggestion import KnowledgeSuggestion # P2-13 知识库自动迭代
|
||||
from app.models.knowledge_base import KnowledgeBase
|
||||
from app.models.login_log import LoginLog # 登录日志(三端认证重构)
|
||||
from app.models.business_contact import BusinessContact # 业务联系人(路由推荐)
|
||||
from app.models.routing_event import RoutingEvent # 路由命中统计(P1)
|
||||
# 会议室预定模块模型
|
||||
from app.models.terminal_room_binding import TerminalRoomBinding
|
||||
from app.models.meetingroom_booking_snapshot import MeetingroomBookingSnapshot
|
||||
# 阶段5 自动化闭环模型
|
||||
from app.models.automation import (
|
||||
AutoSession,
|
||||
@@ -60,6 +66,11 @@ __all__ = [
|
||||
"ConversationAnnotation",
|
||||
"KnowledgeSuggestion",
|
||||
"KnowledgeBase",
|
||||
"LoginLog",
|
||||
"BusinessContact",
|
||||
"RoutingEvent",
|
||||
"TerminalRoomBinding",
|
||||
"MeetingroomBookingSnapshot",
|
||||
"AutoSession",
|
||||
"AutoAction",
|
||||
"ApprovalTicket",
|
||||
|
||||
@@ -20,7 +20,7 @@ import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import JSON, Boolean, DateTime, Float, Integer, String, Text
|
||||
from sqlalchemy import JSON, Boolean, DateTime, Float, Integer, Numeric, SmallInteger, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
@@ -75,6 +75,8 @@ class AutoSession(Base):
|
||||
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
|
||||
@@ -311,3 +313,121 @@ class MappingCache(Base):
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<MappingCache(employee={self.employee_id}, source={self.source})>"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 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"<InformationItem(session={self.session_id}, name={self.name}, v={self.version})>"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 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"<ContextCompression(session={self.session_id}, {self.tokens_before}→{self.tokens_after})>"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 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"<InformationSnapshot(session={self.session_id}, trigger={self.trigger_item_key}, undone={self.is_undone})>"
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 业务联系人模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 business_contacts 表,存储非IT业务部门联系人信息
|
||||
# 当员工提出的问题不属于IT服务台服务范围时,系统通过 Dify 意图识别
|
||||
# 判定业务类别,从本表查询对应联系人,推送名片卡片到聊天中。
|
||||
# 员工点击「联系TA」可跳转企微单聊(openEnterpriseChat)。
|
||||
#
|
||||
# 业务类别(business_category)取值:
|
||||
# 行政 / 人力资源 / 财务 / 法务 / 行政-物业
|
||||
# =============================================================================
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, Index, Integer, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class BusinessContact(Base):
|
||||
"""业务联系人模型 — 对应 business_contacts 表。
|
||||
|
||||
存储非IT业务部门联系人信息,用于路由推荐功能。
|
||||
当 Dify 判定消息为非IT业务路由时,按 business_category 查询本表,
|
||||
取第一条 is_active=True 的联系人,推送名片卡片。
|
||||
|
||||
Attributes:
|
||||
id: 主键,自增
|
||||
name: 联系人姓名
|
||||
gender: 性别(male/female)
|
||||
department: 部门名称(如「行政部」)
|
||||
position: 岗位(如「设备管理岗」)
|
||||
responsibility: 负责业务描述(如「打印机/复印机/扫描仪」)
|
||||
extension: 分机号
|
||||
service_area: 服务区域/办公地点
|
||||
wecom_userid: 企微用户ID(用于 openEnterpriseChat 调用)
|
||||
avatar_url: 头像URL(为空时用姓名首字渲染)
|
||||
business_category: 业务类别(行政/人力资源/财务/法务/行政-物业)
|
||||
is_active: 是否启用
|
||||
created_at: 创建时间
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
__tablename__ = "business_contacts"
|
||||
|
||||
# 主键:自增整数
|
||||
id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
primary_key=True,
|
||||
autoincrement=True,
|
||||
comment="主键",
|
||||
)
|
||||
|
||||
# 联系人姓名
|
||||
name: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
nullable=False,
|
||||
comment="联系人姓名",
|
||||
)
|
||||
|
||||
# 性别(male/female)
|
||||
gender: Mapped[str] = mapped_column(
|
||||
String(10),
|
||||
nullable=False,
|
||||
default="male",
|
||||
comment="性别(male/female)",
|
||||
)
|
||||
|
||||
# 部门名称
|
||||
department: Mapped[str] = mapped_column(
|
||||
String(100),
|
||||
nullable=False,
|
||||
comment="部门名称",
|
||||
)
|
||||
|
||||
# 岗位
|
||||
position: Mapped[str] = mapped_column(
|
||||
String(100),
|
||||
nullable=False,
|
||||
comment="岗位",
|
||||
)
|
||||
|
||||
# 负责业务描述
|
||||
responsibility: Mapped[str] = mapped_column(
|
||||
String(500),
|
||||
nullable=False,
|
||||
comment="负责业务描述",
|
||||
)
|
||||
|
||||
# 分机号
|
||||
extension: Mapped[str | None] = mapped_column(
|
||||
String(20),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="分机号",
|
||||
)
|
||||
|
||||
# 服务区域/办公地点
|
||||
service_area: Mapped[str | None] = mapped_column(
|
||||
String(200),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="服务区域/办公地点",
|
||||
)
|
||||
|
||||
# 企微用户ID(用于 wx.invoke('openEnterpriseChat', {userids: '...'}) 调用)
|
||||
wecom_userid: Mapped[str] = mapped_column(
|
||||
String(100),
|
||||
nullable=False,
|
||||
comment="企微用户ID",
|
||||
)
|
||||
|
||||
# 头像URL(为空时前端用姓名首字渐变色块渲染)
|
||||
avatar_url: Mapped[str | None] = mapped_column(
|
||||
String(500),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="头像URL(为空用姓名首字渲染)",
|
||||
)
|
||||
|
||||
# 业务类别(与 Dify 输出的 business_category 对应)
|
||||
# 取值:行政 / 人力资源 / 财务 / 法务 / 行政-物业
|
||||
business_category: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
nullable=False,
|
||||
comment="业务类别(行政/人力资源/财务/法务/行政-物业)",
|
||||
)
|
||||
|
||||
# 是否启用
|
||||
is_active: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=True,
|
||||
comment="是否启用",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 更新时间
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
onupdate=datetime.now,
|
||||
comment="更新时间",
|
||||
)
|
||||
|
||||
# 索引定义
|
||||
__table_args__ = (
|
||||
# 按业务类别 + 是否启用查询(路由推荐时使用)
|
||||
Index("idx_business_contacts_category", "business_category", "is_active"),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""将联系人信息转为字典(用于 extra_data 透传给前端)。
|
||||
|
||||
Returns:
|
||||
dict: 联系人完整信息
|
||||
"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"gender": self.gender,
|
||||
"department": self.department,
|
||||
"position": self.position,
|
||||
"responsibility": self.responsibility,
|
||||
"extension": self.extension or "",
|
||||
"service_area": self.service_area or "",
|
||||
"wecom_userid": self.wecom_userid,
|
||||
"avatar_url": self.avatar_url or "",
|
||||
"business_category": self.business_category,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""联系人对象的字符串表示,方便调试。"""
|
||||
return (
|
||||
f"<BusinessContact(id={self.id}, name={self.name}, "
|
||||
f"category={self.business_category})>"
|
||||
)
|
||||
@@ -162,6 +162,13 @@ class Employee(Base):
|
||||
comment="最后登录时间",
|
||||
)
|
||||
|
||||
# 最后登录IP
|
||||
last_login_ip: Mapped[str] = mapped_column(
|
||||
String(45),
|
||||
nullable=True,
|
||||
comment="最后登录IP地址",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 登录日志模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 login_logs 表,记录所有登录尝试(成功/失败),用于安全审计和故障排查。
|
||||
# 三端认证重构:统一认证后,所有登录方式(oauth/qrcode/bind)都记录到此表。
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, Index, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class LoginLog(Base):
|
||||
"""登录日志模型 — 对应 login_logs 表。
|
||||
|
||||
记录所有登录尝试(成功/失败),用于安全审计和故障排查。
|
||||
|
||||
Attributes:
|
||||
id: 主键(UUID)
|
||||
employee_id: 员工UserID(成功登录时有值)
|
||||
corp_id: 企业ID
|
||||
login_method: 登录方式(oauth/qrcode/bind)
|
||||
login_source: 登录来源(h5/agent/admin)
|
||||
ip_address: 客户端IP
|
||||
user_agent: 客户端User-Agent
|
||||
status: 登录状态(success/failed/cancelled)
|
||||
fail_reason: 失败原因
|
||||
created_at: 登录时间
|
||||
"""
|
||||
|
||||
# 表名
|
||||
__tablename__ = "login_logs"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字段定义
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# 主键:UUID
|
||||
id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()),
|
||||
comment="登录日志唯一标识",
|
||||
)
|
||||
|
||||
# 员工UserID(成功登录时有值)
|
||||
employee_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=True,
|
||||
comment="企微员工UserID(成功登录时有值)",
|
||||
)
|
||||
|
||||
# 企业微信企业ID
|
||||
corp_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
comment="企业微信企业ID",
|
||||
)
|
||||
|
||||
# 登录方式
|
||||
# oauth: 企微内OAuth静默授权
|
||||
# qrcode: 企微外扫码登录
|
||||
# bind: 互联企业账号绑定
|
||||
login_method: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
comment="登录方式: oauth/qrcode/bind",
|
||||
)
|
||||
|
||||
# 登录来源
|
||||
# h5: 员工H5端
|
||||
# agent: 坐席端
|
||||
# admin: 管理后台
|
||||
login_source: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
comment="登录来源: h5/agent/admin",
|
||||
)
|
||||
|
||||
# 客户端IP(IPv6支持)
|
||||
ip_address: Mapped[str] = mapped_column(
|
||||
String(45),
|
||||
nullable=True,
|
||||
comment="客户端IP地址",
|
||||
)
|
||||
|
||||
# 客户端User-Agent
|
||||
user_agent: Mapped[str] = mapped_column(
|
||||
String(512),
|
||||
nullable=True,
|
||||
comment="客户端User-Agent",
|
||||
)
|
||||
|
||||
# 登录状态
|
||||
# success: 登录成功
|
||||
# failed: 登录失败
|
||||
# cancelled: 用户取消/授权超时
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
comment="登录状态: success/failed/cancelled",
|
||||
)
|
||||
|
||||
# 失败原因
|
||||
fail_reason: Mapped[str] = mapped_column(
|
||||
String(256),
|
||||
nullable=True,
|
||||
comment="失败原因",
|
||||
)
|
||||
|
||||
# 登录时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="登录时间",
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 索引和约束定义
|
||||
# --------------------------------------------------------------------------
|
||||
__table_args__ = (
|
||||
# 按员工ID查询登录历史
|
||||
Index("idx_login_logs_employee_id", "employee_id"),
|
||||
# 按企业ID查询
|
||||
Index("idx_login_logs_corp_id", "corp_id"),
|
||||
# 按登录时间倒序查询(常用)
|
||||
Index("idx_login_logs_created_at", "created_at"),
|
||||
# 按状态查询
|
||||
Index("idx_login_logs_status", "status"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""登录日志对象的字符串表示。"""
|
||||
return (
|
||||
f"<LoginLog(id={self.id}, employee_id={self.employee_id}, "
|
||||
f"login_method={self.login_method}, status={self.status})>"
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 会议室预定快照模型(P2统计用)
|
||||
# =============================================================================
|
||||
# 说明:每日定时拉取预定记录存入本地数据库,作为统计快照(非实时数据源)
|
||||
# 实时预定状态仍以企微API为唯一数据源,此表仅用于P2统计分析
|
||||
# =============================================================================
|
||||
|
||||
from datetime import date, datetime
|
||||
|
||||
from sqlalchemy import Date, DateTime, Integer, String, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class MeetingroomBookingSnapshot(Base):
|
||||
"""会议室预定记录快照模型(P2统计用)。
|
||||
|
||||
每日定时拉取企微预定记录,存入本地作为统计快照。
|
||||
非实时数据源 — 实时预定状态仍以企微API为准。
|
||||
|
||||
Attributes:
|
||||
id: 自增主键
|
||||
meetingroom_id: 企微会议室ID
|
||||
booking_id: 企微预定ID
|
||||
subject: 会议主题
|
||||
booker: 预定人userid
|
||||
start_time: 会议开始时间
|
||||
end_time: 会议结束时间
|
||||
status: 预定状态(0=已预定, 1=已取消)
|
||||
snapshot_date: 快照日期
|
||||
created_at: 记录创建时间
|
||||
"""
|
||||
|
||||
__tablename__ = "meetingroom_booking_snapshot"
|
||||
|
||||
# 自增主键
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
# 企微会议室ID
|
||||
meetingroom_id: Mapped[int] = mapped_column(Integer, index=True, nullable=False)
|
||||
# 企微预定ID
|
||||
booking_id: Mapped[str] = mapped_column(String(64), index=True, nullable=False)
|
||||
# 会议主题
|
||||
subject: Mapped[str] = mapped_column(String(200), nullable=False, default="")
|
||||
# 预定人userid
|
||||
booker: Mapped[str] = mapped_column(String(64), nullable=False, default="")
|
||||
# 会议开始时间
|
||||
start_time: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
||||
# 会议结束时间
|
||||
end_time: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
||||
# 预定状态(0=已预定, 1=已取消)
|
||||
status: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
# 快照日期(用于按日期统计)
|
||||
snapshot_date: Mapped[date] = mapped_column(Date, index=True, nullable=False)
|
||||
# 记录创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""返回模型的可读字符串表示。"""
|
||||
return (
|
||||
f"<MeetingroomBookingSnapshot(id={self.id}, meetingroom_id={self.meetingroom_id}, "
|
||||
f"booking_id='{self.booking_id}', subject='{self.subject}', snapshot_date={self.snapshot_date})>"
|
||||
)
|
||||
@@ -0,0 +1,128 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 路由命中事件模型(P1)
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 routing_events 表,记录每次路由推荐事件
|
||||
# 用于后续优化 Prompt 准确率、分析高频非IT业务
|
||||
#
|
||||
# 记录字段:会话ID、员工消息内容、识别的业务类别、推荐联系人、是否点击联系
|
||||
# =============================================================================
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Index, Integer, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class RoutingEvent(Base):
|
||||
"""路由命中事件模型 — 对应 routing_events 表(P1)。
|
||||
|
||||
记录每次路由推荐事件,为后续优化 Prompt 准确率、分析高频非IT业务
|
||||
提供数据支撑。
|
||||
|
||||
Attributes:
|
||||
id: 主键,自增
|
||||
conversation_id: 会话ID(外键)
|
||||
employee_id: 员工ID
|
||||
message_content: 触发路由的员工消息(截断至500字)
|
||||
business_category: 识别的业务类别
|
||||
routing_confidence: 路由置信度
|
||||
contact_id: 推荐的联系人ID(外键)
|
||||
contact_name: 联系人姓名(冗余存储)
|
||||
is_clicked: 员工是否点击了「联系TA」
|
||||
created_at: 创建时间
|
||||
"""
|
||||
|
||||
__tablename__ = "routing_events"
|
||||
|
||||
# 主键:自增整数
|
||||
id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
primary_key=True,
|
||||
autoincrement=True,
|
||||
comment="主键",
|
||||
)
|
||||
|
||||
# 会话ID(外键,关联 conversations 表)
|
||||
conversation_id: Mapped[str] = mapped_column(
|
||||
String(36),
|
||||
ForeignKey("conversations.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
comment="会话ID",
|
||||
)
|
||||
|
||||
# 员工ID
|
||||
employee_id: Mapped[str] = mapped_column(
|
||||
String(64),
|
||||
nullable=False,
|
||||
comment="员工ID",
|
||||
)
|
||||
|
||||
# 触发路由的员工消息内容(截断至500字防止过长)
|
||||
message_content: Mapped[str] = mapped_column(
|
||||
String(500),
|
||||
nullable=False,
|
||||
comment="触发路由的员工消息(截断)",
|
||||
)
|
||||
|
||||
# 识别的业务类别(行政/人力资源/财务/法务/行政-物业)
|
||||
business_category: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
nullable=False,
|
||||
comment="识别的业务类别",
|
||||
)
|
||||
|
||||
# 路由置信度(0.0~1.0,≥0.7 触发名片推荐)
|
||||
routing_confidence: Mapped[float] = mapped_column(
|
||||
Float,
|
||||
nullable=False,
|
||||
comment="路由置信度",
|
||||
)
|
||||
|
||||
# 推荐的联系人ID(外键,关联 business_contacts 表)
|
||||
contact_id: Mapped[int | None] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("business_contacts.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
default=None,
|
||||
comment="推荐的联系人ID",
|
||||
)
|
||||
|
||||
# 联系人姓名(冗余存储,避免联系人删除后丢失记录)
|
||||
contact_name: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
nullable=False,
|
||||
comment="联系人姓名(冗余)",
|
||||
)
|
||||
|
||||
# 员工是否点击了「联系TA」按钮
|
||||
is_clicked: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=False,
|
||||
comment="是否点击了联系TA",
|
||||
)
|
||||
|
||||
# 创建时间
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=datetime.now,
|
||||
comment="创建时间",
|
||||
)
|
||||
|
||||
# 索引定义
|
||||
__table_args__ = (
|
||||
# 按业务类别查询(分析高频非IT业务)
|
||||
Index("idx_routing_events_category", "business_category"),
|
||||
# 按会话ID查询(查看某会话的路由历史)
|
||||
Index("idx_routing_events_conv", "conversation_id"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""路由事件对象的字符串表示,方便调试。"""
|
||||
return (
|
||||
f"<RoutingEvent(id={self.id}, conv={self.conversation_id}, "
|
||||
f"category={self.business_category}, confidence={self.routing_confidence})>"
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 终端-会议室绑定模型
|
||||
# =============================================================================
|
||||
# 说明:存储小鱼易联终端SN与企微会议室ID的映射关系
|
||||
# 这是企微API不提供的关系数据,需要在本地维护
|
||||
# =============================================================================
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, Integer, String, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class TerminalRoomBinding(Base):
|
||||
"""终端-会议室绑定模型。
|
||||
|
||||
存储小鱼易联终端SN与企微会议室ID的映射关系。
|
||||
一个终端只能绑定一个会议室(terminal_sn 唯一索引),
|
||||
一个会议室可被多个终端绑定(meetingroom_id 普通索引)。
|
||||
|
||||
Attributes:
|
||||
id: 自增主键
|
||||
terminal_sn: 小鱼易联终端序列号(唯一)
|
||||
terminal_name: 终端名称(如"18F东区大屏")
|
||||
meetingroom_id: 企微会议室ID
|
||||
meetingroom_name: 会议室名称(冗余字段,便于终端页面展示)
|
||||
location: 位置描述(如"18F东区")
|
||||
is_active: 是否启用
|
||||
created_at: 创建时间
|
||||
updated_at: 更新时间
|
||||
"""
|
||||
|
||||
__tablename__ = "terminal_room_binding"
|
||||
|
||||
# 自增主键
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
# 小鱼易联终端序列号(唯一索引,一个终端只能绑定一个会议室)
|
||||
terminal_sn: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)
|
||||
# 终端名称(便于管理后台展示)
|
||||
terminal_name: Mapped[str] = mapped_column(String(100), nullable=False, default="")
|
||||
# 企微会议室ID(普通索引,一个会议室可被多个终端绑定)
|
||||
meetingroom_id: Mapped[int] = mapped_column(Integer, index=True, nullable=False)
|
||||
# 会议室名称(冗余字段,便于终端页面直接展示,无需额外查询企微API)
|
||||
meetingroom_name: Mapped[str] = mapped_column(String(100), nullable=False, default="")
|
||||
# 位置描述
|
||||
location: Mapped[str] = mapped_column(String(200), nullable=False, default="")
|
||||
# 是否启用(默认True,管理员可禁用绑定)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
# 创建时间(服务器默认当前时间)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)
|
||||
# 更新时间(每次更新时自动刷新)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now(), nullable=False)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""返回模型的可读字符串表示。"""
|
||||
return (
|
||||
f"<TerminalRoomBinding(id={self.id}, terminal_sn='{self.terminal_sn}', "
|
||||
f"meetingroom_id={self.meetingroom_id}, meetingroom_name='{self.meetingroom_name}')>"
|
||||
)
|
||||
@@ -2,7 +2,7 @@
|
||||
# 企微IT智能服务台 — 待办事项模型
|
||||
# =============================================================================
|
||||
# 说明:对应数据库 todo_items 表,存储坐席的待办事项
|
||||
# 待办类型:ticket(工单)/approval(审批)/device(设备) 等
|
||||
# 待办类型:ticket(工单)/approval(审批)
|
||||
# =============================================================================
|
||||
|
||||
import uuid
|
||||
@@ -18,11 +18,11 @@ from app.database import Base
|
||||
class TodoItem(Base):
|
||||
"""待办事项模型 — 对应 todo_items 表。
|
||||
|
||||
存储坐席需要跟进的各类待办事项,包括工单、审批、设备处理等。
|
||||
存储坐席需要跟进的各类待办事项,包括工单、审批等。
|
||||
|
||||
Attributes:
|
||||
id: 待办唯一标识(UUID,数据库自动生成)
|
||||
type: 待办类型(ticket/approval/device)
|
||||
type: 待办类型(ticket/approval)
|
||||
title: 待办标题
|
||||
priority: 优先级(urgent/high/normal)
|
||||
description: 详细描述(JSON,存储结构化数据)
|
||||
@@ -53,7 +53,7 @@ class TodoItem(Base):
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="ticket",
|
||||
comment="待办类型: ticket/approval/device",
|
||||
comment="待办类型: ticket/approval",
|
||||
)
|
||||
|
||||
# 待办标题
|
||||
|
||||
Reference in New Issue
Block a user