ForcePilot/backend/package/yuxi/channels/contract/dtos/dashboard.py
Kris 0d067d61d3 chore: 批量更新契约与实现,修复多处细节问题
本次提交包含多类代码优化与修复:
1. 重命名适配器协议类:WebhookTestable→WebhookTestAdapter、AttachmentUploadable→AttachmentUploadAdapter,并同步更新所有引用
2. 为飞书/企业微信插件添加凭证克隆能力开关配置
3. 修复飞书入站适配器URL解析错误,使用hostname替代host属性
4. 新增批量发送消息DTO与已读状态DTO
5. 新增插件目录列表接口与插件能力校验规则
6. 修复权限阶段配置,添加analytics权限映射
7. 优化健康检查、限流模块的环境变量配置支持
8. 修复配对过期扫描器参数名不匹配问题
9. 优化日志调用、文档注释与代码可读性
10. 移除废弃的AuditExportTask聚合根与相关导入
2026-07-02 18:27:06 +08:00

271 lines
8.6 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.

"""Dashboard 聚合视图域 DTO。
定义跨子域 KPI 聚合视图的不可变值对象,供 dashboard_router 端点返回。
所有 DTO 均为 ``dataclass(frozen=True)``,仅依赖标准库与契约层内部类型。
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from yuxi.channels.contract.dtos.channel import ChannelType
from yuxi.channels.contract.dtos.outbox import OutboxStats
from yuxi.channels.contract.errors import ValidationError
@dataclass(frozen=True)
class AccountStats:
"""账户统计快照DSB-02
字段:
total: 账户总数(仅含 is_deleted=0
by_channel: 按渠道类型分组的计数 dictkey 为 ChannelType.value
by_status: 按账户状态分组的计数 dictkey ∈ {active, disabled, degraded})。
"""
total: int
by_channel: dict[str, int]
by_status: dict[str, int]
@dataclass(frozen=True)
class MessageStats:
"""消息统计快照DSB-03
字段:
total: 消息总数(满足时间过滤条件)。
by_channel: 按渠道类型分组的计数 dictchannel_type 通过 conversation 关联填充)。
by_role: 按发送角色分组的计数 dictkey ∈ {user, assistant, system, tool})。
by_delivery_status: 按渠道侧投递状态分组的计数 dict
key ∈ {sent, delivered, read, recalled, edited})。
"""
total: int
by_channel: dict[str, int]
by_role: dict[str, int]
by_delivery_status: dict[str, int]
@dataclass(frozen=True)
class SessionStats:
"""会话统计快照DSB-04
字段:
total: 未软删除会话总数。
by_channel: 按渠道类型分组的计数 dict。
by_is_temporary: 按是否临时会话分组的计数 dictkey 为 "true" / "false")。
"""
total: int
by_channel: dict[str, int]
by_is_temporary: dict[str, int]
@dataclass(frozen=True)
class DashboardOverview:
"""全局总览快照DSB-01
聚合四类子域计数,供管理后台首页看板渲染。
字段:
accounts: 账户总览。
messages: 消息总览。
sessions: 会话总览。
outbox: 投递总览(复用 OutboxStats
"""
accounts: AccountStats
messages: MessageStats
sessions: SessionStats
outbox: OutboxStats
@dataclass(frozen=True)
class DashboardDeliveryQuery:
"""投递总览查询DSB-DELIVERY
描述投递总览查询条件,按渠道类型与时间范围过滤;所有字段可选,
缺省时返回全局聚合。
字段:
channel_type: 渠道类型过滤(可选)。
start_time: 起始时间(可选,须为 aware datetime
end_time: 截止时间(可选,须为 aware datetime
"""
channel_type: ChannelType | None = None
start_time: datetime | None = None
end_time: datetime | None = None
def __post_init__(self) -> None:
"""校验时间区间合法性与时区约束DSB-DELIVERY
- ``start_time`` / ``end_time`` 同时提供时,``start_time`` 必须
不晚于 ``end_time``,否则区间反转会导致 adapter 层 SQL 静默
返回空集INV-DASHBOARD-DELIVERY-RANGE
- 任一时间字段提供时必须为 aware datetime``tzinfo`` 非空),
与各表时间列的 UTC 存储语义对齐,避免 naive datetime 被按
本地时区解释导致偏移INV-DASHBOARD-DELIVERY-TZ
校验在构造时即抛出 ``ValidationError``,避免非法值传播到
adapter 层后才暴露。
"""
if self.start_time is not None and self.start_time.tzinfo is None:
raise ValidationError(
"start_time",
"start_time must be timezone-aware datetime",
)
if self.end_time is not None and self.end_time.tzinfo is None:
raise ValidationError(
"end_time",
"end_time must be timezone-aware datetime",
)
if (
self.start_time is not None
and self.end_time is not None
and self.start_time > self.end_time
):
raise ValidationError(
"time_range",
"start_time must not be later than end_time",
)
@dataclass(frozen=True)
class ChannelDeliveryStat:
"""渠道投递统计DSB-DELIVERY by_channel 条目)。
描述单渠道投递计数与成功率,用于投递总览的按渠道切片展示。
字段:
channel_type: 渠道类型字符串值。
sent: 该渠道投递总数。
success_rate: 成功率0-100
"""
channel_type: str
sent: int
success_rate: float
@dataclass(frozen=True)
class DashboardDeliveryResult:
"""投递总览结果DSB-DELIVERY
描述投递总览聚合结果,包含总计数、成功率、延迟分位数、队列深度、
死信数与按渠道切片。
字段:
total_sent: 投递总数。
success_count: 成功数。
failed_count: 失败数。
success_rate: 成功率0-100
avg_latency_ms: 平均延迟(毫秒)。
p95_latency_ms: P95 延迟(毫秒)。
p99_latency_ms: P99 延迟(毫秒)。
current_queue_depth: 当前队列深度。
dead_letter_count: 死信总数。
by_channel: 按渠道切片的统计元组。
"""
total_sent: int
success_count: int
failed_count: int
success_rate: float
avg_latency_ms: float
p95_latency_ms: float
p99_latency_ms: float
current_queue_depth: int
dead_letter_count: int
by_channel: tuple[ChannelDeliveryStat, ...]
@dataclass(frozen=True)
class RealtimeQuery:
"""实时监控查询DSB-REALTIME
描述实时监控查询条件,按渠道类型与滑动窗口过滤;``window_seconds``
范围 10-300默认 60。
字段:
channel_type: 渠道类型过滤(可选)。
window_seconds: 滑动窗口秒数(默认 60范围 10-300
"""
channel_type: ChannelType | None = None
window_seconds: int = 60
def __post_init__(self) -> None:
"""校验 ``window_seconds`` 取值范围DSB-REALTIME
``window_seconds`` 必须在 ``[10, 300]`` 区间内:过小会导致
滑动窗口采样不足,过大会使内存计数器 deque 膨胀。校验在构造时
即抛出 ``ValidationError``,与 Router 层 ``Query(ge=10, le=300)``
形成双层防御,覆盖非 HTTP 调用方(如内部 API 直接构造 DTO
兜底场景INV-DASHBOARD-REALTIME-WINDOW
"""
if self.window_seconds < 10 or self.window_seconds > 300:
raise ValidationError(
"window_seconds",
f"window_seconds must be in [10, 300], got {self.window_seconds}",
)
@dataclass(frozen=True)
class ChannelRealtimeStat:
"""渠道实时统计DSB-REALTIME by_channel 条目)。
描述单渠道实时消息速率与活跃会话数,用于实时监控的按渠道切片展示。
字段:
channel_type: 渠道类型字符串值。
mps: 每秒消息数messages per second
active_sessions: 活跃会话数。
"""
channel_type: str
mps: float
active_sessions: int
@dataclass(frozen=True)
class RealtimeMetricsResult:
"""实时监控结果DSB-REALTIME
描述实时监控纯内存态聚合结果,包含全局消息速率、活跃会话/会话数、
队列深度、worker 利用率与按渠道切片。高频查询场景使用,避免 DB 连接占用。
字段:
window_seconds: 滑动窗口秒数。
messages_per_second: 全局每秒消息数。
active_sessions: 活跃会话总数。
active_conversations: 活跃会话总数(与 active_sessions 同义,由
不同来源聚合sessions 来自 QueuePortconversations 来自会话仓库)。
queue_depth: 当前队列深度。
worker_utilization: worker 利用率0-100
by_channel: 按渠道切片的实时统计元组。
"""
window_seconds: int
messages_per_second: float
active_sessions: int
active_conversations: int
queue_depth: int
worker_utilization: float
by_channel: tuple[ChannelRealtimeStat, ...]
__all__ = [
"AccountStats",
"ChannelDeliveryStat",
"ChannelRealtimeStat",
"DashboardDeliveryQuery",
"DashboardDeliveryResult",
"DashboardOverview",
"MessageStats",
"RealtimeMetricsResult",
"RealtimeQuery",
"SessionStats",
]