"""聚合视图域 analytics 子域契约 DTO。 定义聚合视图域 analytics 子域 6 个跨子域深度分析端点(ANL-01/02/04/05/06/07) 返回的不可变值对象。所有 DTO 均为 ``dataclass(frozen=True)``,仅依赖标准库, 用于按时间 / 类型 / 状态切片的细分聚合展示,供管理后台运营审计与运维排障使用。 与 dashboard 子域的区分:dashboard 提供总览计数,analytics 提供按维度切片的 细分聚合;与单子域 stats(如 OBX-02 outbox stats)的区分:单子域统计归各子域 router,本域仅承载跨子域深度分析视图。 """ from __future__ import annotations from dataclasses import dataclass from datetime import datetime from typing import Any from yuxi.channels.contract.dtos.channel import ChannelType from yuxi.channels.contract.dtos.common import CategoryStat, TrendDataPoint @dataclass(frozen=True) class TimeSeriesPoint: """时间序列数据点。 描述按时间粒度切片后的单个时间桶计数,供消息与会话分析的趋势展示使用。 字段: timestamp: 时间桶起始时间(ISO 8601 字符串)。 value: 计数值。 """ timestamp: str value: int def to_dict(self) -> dict[str, Any]: return {"timestamp": self.timestamp, "value": self.value} @dataclass(frozen=True) class MessageAnalytics: """消息深度分析结果(ANL-01)。 描述时间范围内消息量按时间 / 渠道 / 角色 / 投递状态切片的细分聚合,由 ``getMessageAnalytics`` 返回,供运营审计按多维切片定位消息量趋势。 字段: total: 时间范围内消息总数。 timeseries: 按时间粒度切片的消息量趋势。 by_channel: 按渠道类型分组的计数 dict(key 为 channel_type 字符串值)。 by_role: 按发送角色分组的计数 dict(key 为 user / assistant / system / tool)。 by_delivery_status: 按投递状态分组的计数 dict。 """ total: int timeseries: tuple[TimeSeriesPoint, ...] by_channel: dict[str, int] by_role: dict[str, int] by_delivery_status: dict[str, int] def to_dict(self) -> dict[str, Any]: """序列化为 dict,递归处理嵌套 ``TimeSeriesPoint`` 为 dict 列表。""" return { "total": self.total, "timeseries": [point.to_dict() for point in self.timeseries], "by_channel": self.by_channel, "by_role": self.by_role, "by_delivery_status": self.by_delivery_status, } @dataclass(frozen=True) class MessageDistribution: """消息类型分布结果(ANL-02)。 描述时间范围内消息按类型分组的计数与占比,由 ``getMessageDistribution`` 返回, 供内容运营按类型(text / card / attachment / mixed)定位消息构成。 字段: total: 时间范围内消息总数。 by_type: 按消息类型分组的计数 dict。 by_type_percent: 按消息类型分组的占比 dict(0-100)。 """ total: int by_type: dict[str, int] by_type_percent: dict[str, float] def to_dict(self) -> dict[str, Any]: """序列化为 dict,供控制面结果返回与日志输出使用。""" return { "total": self.total, "by_type": self.by_type, "by_type_percent": self.by_type_percent, } @dataclass(frozen=True) class SessionAnalytics: """会话分析结果(ANL-04)。 描述时间范围内会话量按时间 / 渠道 / 临时性 / 消息数区间切片的细分聚合,由 ``getSessionAnalytics`` 返回,供运营审计按多维切片定位会话量趋势。 字段: total: 时间范围内活跃会话总数。 timeseries: 按时间粒度切片的会话量趋势。 by_channel: 按渠道类型分组的计数 dict(key 为 channel_type 字符串值)。 by_is_temporary: 按是否临时会话分组的计数 dict(key 为 "true" / "false")。 by_message_count_bucket: 按消息数区间分组的计数 dict (key 为 "0-10" / "11-50" / "51-100" / "100+")。 """ total: int timeseries: tuple[TimeSeriesPoint, ...] by_channel: dict[str, int] by_is_temporary: dict[str, int] by_message_count_bucket: dict[str, int] def to_dict(self) -> dict[str, Any]: """序列化为 dict,递归处理嵌套 ``TimeSeriesPoint`` 为 dict 列表。""" return { "total": self.total, "timeseries": [point.to_dict() for point in self.timeseries], "by_channel": self.by_channel, "by_is_temporary": self.by_is_temporary, "by_message_count_bucket": self.by_message_count_bucket, } @dataclass(frozen=True) class DeliveryAnalytics: """投递链路分析结果(ANL-05)。 与现有 ``OutboxStats``(按状态分组计数)的关系:``DeliveryAnalytics`` 是 ``OutboxStats`` 的扩展,新增 success_rate / failure_rate / retry_distribution / avg_latency_ms 四个聚合维度,由 ``getDeliveryAnalytics`` 返回,供运维排障定位投递成功率与重试分布。 字段: total: 时间范围内投递条目总数。 success_rate: 投递成功率(0-100)。 failure_rate: 投递失败率(0-100)。 retry_distribution: 按重试次数分组的计数 dict (key 为 "0" / "1" / "2" / "3+" / "max_reached")。 avg_latency_ms: 平均投递延迟(毫秒),无 latency_ms 数据时为 None。 """ total: int success_rate: float failure_rate: float retry_distribution: dict[str, int] avg_latency_ms: float | None def to_dict(self) -> dict[str, Any]: """序列化为 dict,``avg_latency_ms`` 为 None 时返回 None。""" return { "total": self.total, "success_rate": self.success_rate, "failure_rate": self.failure_rate, "retry_distribution": self.retry_distribution, "avg_latency_ms": self.avg_latency_ms, } @dataclass(frozen=True) class DeliveryLatencyDistribution: """投递延迟分布结果(ANL-06)。 描述投递延迟的分位数与直方图分布,由 ``getDeliveryLatencyDistribution`` 返回,供运维排障定位长尾延迟。所有分位数字段无 latency_ms 数据时为 None。 字段: p50_ms: P50 延迟(毫秒),无数据时为 None。 p90_ms: P90 延迟(毫秒),无数据时为 None。 p99_ms: P99 延迟(毫秒),无数据时为 None。 max_ms: 最大延迟(毫秒),无数据时为 None。 histogram: 按延迟区间分组的计数 dict (key 为 "0-100ms" / "100-500ms" / "500ms-1s" / "1-5s" / "5s+")。 """ p50_ms: float | None p90_ms: float | None p99_ms: float | None max_ms: float | None histogram: dict[str, int] def to_dict(self) -> dict[str, Any]: """序列化为 dict,分位数字段为 None 时返回 None。""" return { "p50_ms": self.p50_ms, "p90_ms": self.p90_ms, "p99_ms": self.p99_ms, "max_ms": self.max_ms, "histogram": self.histogram, } @dataclass(frozen=True) class DeliveryFunnel: """投递漏斗分析结果(ANL-07)。 描述投递管道各节点的条目数与相对 enter 的转化率,由 ``getDeliveryFunnel`` 返回,供运维排障定位投递链路损耗节点。 字段: enter: 进入投递管道的条目数。 sent: 投递成功数。 suppressed: 被抑制数。 failed: 失败数。 dead: 死信数。 conversion_rate: 各节点相对 enter 的转化率 dict (key 为 "sent" / "suppressed" / "failed" / "dead",value 为 0-100)。 """ enter: int sent: int suppressed: int failed: int dead: int conversion_rate: dict[str, float] def to_dict(self) -> dict[str, Any]: """序列化为 dict,供控制面结果返回与日志输出使用。""" return { "enter": self.enter, "sent": self.sent, "suppressed": self.suppressed, "failed": self.failed, "dead": self.dead, "conversion_rate": self.conversion_rate, } @dataclass(frozen=True) class AccountAnalyticsQuery: """账户活跃度分析查询(ANL-ACCOUNTS)。 描述账户活跃度分析查询条件,按时间范围(必填)与渠道类型过滤, 支持按粒度切片。 字段: start_time: 起始时间(必填)。 end_time: 截止时间(必填)。 granularity: 时间粒度(默认 day,可选 hour/day/week)。 channel_type: 渠道类型过滤(可选)。 """ start_time: datetime end_time: datetime granularity: str = "day" channel_type: ChannelType | None = None @dataclass(frozen=True) class AccountActivityStat: """账户活跃统计(ANL-ACCOUNTS by_account 条目)。 描述单账户在时间范围内的活跃度指标,由 MessageRepositoryPort 与 ChannelSessionRepositoryPort 聚合计算。 字段: account_id: 账户 ID。 channel_type: 渠道类型字符串值。 message_count: 消息总数。 session_count: 会话总数。 active_days: 活跃天数。 avg_daily_messages: 日均消息数。 """ account_id: str channel_type: str message_count: int session_count: int active_days: int avg_daily_messages: float @dataclass(frozen=True) class AccountAnalyticsResult: """账户活跃度分析结果(ANL-ACCOUNTS)。 描述账户活跃度分析聚合结果,包含按账户切片的统计与活跃账户数趋势。 ``trend`` 复用通用 ``TrendDataPoint``,``value`` 承载该时间桶的活跃账户数, 由序列化层映射为 ``active_accounts`` 字段名。 字段: by_account: 按账户切片的统计元组。 trend: 活跃账户数趋势元组(value=active_accounts)。 """ by_account: tuple[AccountActivityStat, ...] trend: tuple[TrendDataPoint, ...] @dataclass(frozen=True) class PeerAnalyticsQuery: """对端活跃度分析查询(ANL-PEERS)。 描述对端活跃度分析查询条件,按时间范围(必填)与渠道类型/账户过滤, 返回按消息数降序的 Top N 对端。 字段: start_time: 起始时间(必填)。 end_time: 截止时间(必填)。 channel_type: 渠道类型过滤(可选)。 account_id: 账户 ID 过滤(可选)。 limit: Top N 上限(默认 100,1-500)。 """ start_time: datetime end_time: datetime channel_type: ChannelType | None = None account_id: str | None = None limit: int = 100 @dataclass(frozen=True) class PeerActivityStat: """对端活跃统计(ANL-PEERS top_active 条目)。 描述单对端在时间范围内的活跃度指标,由 MessageRepositoryPort 聚合计算。 字段: peer_id: 对端 ID。 channel_type: 渠道类型字符串值。 message_count: 消息总数。 session_count: 会话总数。 first_seen: 首次出现时间(可选,无数据时为 None)。 last_seen: 最后出现时间(可选,无数据时为 None)。 """ peer_id: str channel_type: str message_count: int session_count: int first_seen: datetime | None last_seen: datetime | None @dataclass(frozen=True) class PeerAnalyticsResult: """对端活跃度分析结果(ANL-PEERS)。 描述对端活跃度分析聚合结果,按 ``message_count`` 降序返回 Top N 对端。 字段: top_active: 按消息数降序的对端统计元组。 """ top_active: tuple[PeerActivityStat, ...] @dataclass(frozen=True) class ContentReviewAnalyticsQuery: """内容审核分析查询(ANL-CR)。 描述内容审核分析查询条件,按时间范围(必填)与渠道类型过滤, 支持按粒度切片。 字段: start_time: 起始时间(必填)。 end_time: 截止时间(必填)。 granularity: 时间粒度(默认 day,可选 hour/day/week)。 channel_type: 渠道类型过滤(可选)。 """ start_time: datetime end_time: datetime granularity: str = "day" channel_type: ChannelType | None = None @dataclass(frozen=True) class ContentReviewAnalyticsResult: """内容审核分析结果(ANL-CR)。 描述内容审核分析聚合结果,包含总审核/拦截计数、拦截率、按分类切片与 审核趋势。``by_category`` 复用通用 ``CategoryStat``(``rate`` 由序列化层 按 ``count / total_reviewed`` 计算补充);``trend`` 为多值时间序列 (``reviewed`` / ``blocked`` 双计数),无法用单一 ``value`` 字段承载, 故使用 ``dict[str, Any]`` 元组,由适配器层按 ``{timestamp, reviewed, blocked}`` 结构组装。 字段: total_reviewed: 审核总数。 total_blocked: 拦截总数。 block_rate: 拦截率(0-100)。 by_category: 按分类切片的统计元组(rate 由序列化层补充)。 trend: 审核趋势元组(每项含 timestamp/reviewed/blocked)。 """ total_reviewed: int total_blocked: int block_rate: float by_category: tuple[CategoryStat, ...] trend: tuple[dict[str, Any], ...] __all__ = [ "AccountActivityStat", "AccountAnalyticsQuery", "AccountAnalyticsResult", "ContentReviewAnalyticsQuery", "ContentReviewAnalyticsResult", "DeliveryAnalytics", "DeliveryFunnel", "DeliveryLatencyDistribution", "MessageAnalytics", "MessageDistribution", "PeerActivityStat", "PeerAnalyticsQuery", "PeerAnalyticsResult", "SessionAnalytics", "TimeSeriesPoint", ]