ForcePilot/backend/package/yuxi/channels/contract/dtos/dashboard.py

331 lines
11 KiB
Python
Raw Normal View History

"""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
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 DashboardOverviewQuery:
"""全局总览查询DSB-01
描述全局总览查询条件按渠道类型过滤``start_time`` / ``end_time``
仅作用于 ``messages`` 子域消息数为时间窗口内事件``accounts`` /
``sessions`` / ``outbox`` 为当前状态快照不受时间范围影响
字段
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-01
``DashboardDeliveryQuery`` 保持一致避免区间反转与 naive
datetime 导致的数据偏移INV-DASHBOARD-OVERVIEW-RANGE
"""
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 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 | None
@dataclass(frozen=True)
class DashboardDeliveryResult:
"""投递总览结果DSB-DELIVERY
描述投递总览聚合结果包含总计数成功率延迟分位数队列深度
死信数与按渠道切片
字段
total_sent: 投递总数
success_count: 成功数
failed_count: 失败数
success_rate: 成功率0-100无投递样本时为 ``None``避免
前端将 ``0.0%`` 误解为低投递率
avg_latency_ms: 平均延迟毫秒无投递样本时为 ``None``
p95_latency_ms: P95 延迟毫秒无投递样本时为 ``None``
p99_latency_ms: P99 延迟毫秒无投递样本时为 ``None``
current_queue_depth: 当前队列深度
dead_letter_count: 死信总数
by_channel: 按渠道切片的统计元组
"""
total_sent: int
success_count: int
failed_count: int
success_rate: float | None
avg_latency_ms: float | None
p95_latency_ms: float | None
p99_latency_ms: float | None
current_queue_depth: int
dead_letter_count: int
by_channel: tuple[ChannelDeliveryStat, ...]
@dataclass(frozen=True)
class DashboardTodoResult:
"""工作台待办计数聚合结果DSB-TODOS
将分散在配对审批内容审核死信积压三个子域的待办计数聚合为
一次请求返回确保看板角标与跳转页数据口径一致
字段
pending_pairings: 待审批配对数量
pending_reviews: 待人工复核的内容审核数量
dead_letter_count: 当前死信积压数量按渠道过滤时仅统计该渠道
"""
pending_pairings: int
pending_reviews: int
dead_letter_count: int
@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 事务审计走 INDEPENDENT 独立事务 best-effort 写入
字段
window_seconds: 滑动窗口秒数
messages_per_second: 全局每秒消息数
active_sessions: 活跃会话总数
active_conversations: 活跃会话总数 active_sessions 同义
不同来源聚合sessions 来自 QueuePortconversations 来自会话仓库
queue_depth: 当前队列深度
worker_utilization: worker 利用率0.0-1.0 ``WorkerStatus``
同源ARQ 适配器当前不采集真实利用率返回 ``None`` 表示未采集
by_channel: 按渠道切片的实时统计元组全局查询时遍历已注册渠道
填充单渠道过滤时仅含该渠道一条
"""
window_seconds: int
messages_per_second: float
active_sessions: int
active_conversations: int
queue_depth: int
worker_utilization: float | None
by_channel: tuple[ChannelRealtimeStat, ...]
__all__ = [
"AccountStats",
"ChannelDeliveryStat",
"ChannelRealtimeStat",
"DashboardDeliveryQuery",
"DashboardDeliveryResult",
"DashboardOverview",
"DashboardTodoResult",
"MessageStats",
"RealtimeMetricsResult",
"RealtimeQuery",
"SessionStats",
]