"""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: 按渠道类型分组的计数 dict(key 为 ChannelType)。 by_status: 按账户状态分组的计数 dict(key ∈ {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: 按渠道类型分组的计数 dict(channel_type 通过 conversation 关联填充)。 by_role: 按发送角色分组的计数 dict(key ∈ {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: 按是否临时会话分组的计数 dict(key 为 "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 来自 QueuePort,conversations 来自会话仓库)。 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", ]