ForcePilot/backend/package/yuxi/channels/contract/dtos/dashboard.py
Kris 742299cb07 chore: 批量清理报告相关代码并完成多项功能迭代
本次提交包含多维度代码优化与功能增强:
1.  移除报告模块冗余导入与枚举,清理报表相关代码
2.  新增扫码登录支持方法与飞书适配器适配
3.  完善异常日志与健康检查信息
4.  扩展目录、配对管理、能力查询等接口
5.  优化出站管道与事务提交后钩子逻辑
6.  修复飞书消息解析与响应空值问题
7.  重构配置更新与服务账号创建逻辑
8.  统一传输错误分类契约与错误基类扩展
2026-07-06 20:49:35 +08:00

331 lines
11 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 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",
]