"""领域模型(dataclass) ↔ Output DTO(Pydantic) 转换。 规范 §6.1/§7.3(对齐 ``external_systems/use_cases/mappers.py``): - persistence 返回领域模型 dataclass,service 返回 Output DTO - scheduler 域无敏感字段,mapper 仅做字段映射与 DateTime → ISO 字符串格式化 - use_cases 层不调用 ORM 的 ``to_dict`` 方法 DateTime 字段(``run_at`` / ``last_run_at`` / ``next_run_at`` / ``started_at`` / ``finished_at`` / ``created_at`` / ``updated_at`` / ``deleted_at``)在 dataclass 中以 naive UTC ``datetime`` 表达,mapper 通过 ``format_utc_datetime`` 统一格式化 为 ISO 8601 字符串写入 Output DTO。 """ from __future__ import annotations from typing import Any from yuxi.scheduler.core.models import ( DailyStat, HandlerSummary, ScheduledTask, ScheduledTaskRunLog, ) from yuxi.scheduler.use_cases.dto.scheduler import ( CountByStatusOutput, DailyStatOutput, GetHealthOutput, HandlerSummaryOutput, ListDailyStatsOutput, ListHandlerSummaryOutput, ListUpcomingOutput, RunLogOutput, TaskOutput, ) from yuxi.utils.datetime_utils import format_utc_datetime __all__ = [ "daily_stat_to_dict", "to_count_by_status_output", "to_daily_stat_output", "to_get_health_output", "to_handler_summary_output", "to_list_daily_stats_output", "to_run_log_output", "to_task_output", ] def to_task_output(task: ScheduledTask) -> TaskOutput: """``ScheduledTask`` dataclass → ``TaskOutput`` DTO。 DateTime 字段通过 ``format_utc_datetime`` 格式化为 ISO 8601 字符串。 """ return TaskOutput( task_id=task.task_id, handler_name=task.handler_name, owner_scope=task.owner_scope, owner_id=task.owner_id, schedule_kind=task.schedule_kind, cron_expression=task.cron_expression, run_at=format_utc_datetime(task.run_at), tz=task.tz, payload=task.payload or {}, enabled=task.enabled, delete_after_run=task.delete_after_run, block_strategy=task.block_strategy, stagger_seconds=task.stagger_seconds, consecutive_errors=task.consecutive_errors, status=task.status, last_run_at=format_utc_datetime(task.last_run_at), next_run_at=format_utc_datetime(task.next_run_at), last_error=task.last_error, created_by=task.created_by, updated_by=task.updated_by, id=task.id, created_at=format_utc_datetime(task.created_at), updated_at=format_utc_datetime(task.updated_at), is_deleted=task.is_deleted, deleted_at=format_utc_datetime(task.deleted_at), ) def to_run_log_output(log: ScheduledTaskRunLog) -> RunLogOutput: """``ScheduledTaskRunLog`` dataclass → ``RunLogOutput`` DTO。""" return RunLogOutput( task_id=log.task_id, run_id=log.run_id, triggered_by=log.triggered_by, status=log.status, error_message=log.error_message, output=log.output, started_at=format_utc_datetime(log.started_at), finished_at=format_utc_datetime(log.finished_at), created_by=log.created_by, updated_by=log.updated_by, id=log.id, created_at=format_utc_datetime(log.created_at), updated_at=format_utc_datetime(log.updated_at), is_deleted=log.is_deleted, deleted_at=format_utc_datetime(log.deleted_at), ) def to_handler_summary_output(summary: HandlerSummary) -> HandlerSummaryOutput: """``HandlerSummary`` dataclass → ``HandlerSummaryOutput`` DTO。 ``description`` 字段仓储层不聚合(DB 无此列),由调用方按需补充; ``active_task_count`` 同理,默认 0,由调用方按需补充。 """ return HandlerSummaryOutput( handler_name=summary.name, task_count=summary.task_count, last_active_at=format_utc_datetime(summary.last_active_at), ) def to_count_by_status_output(dist: dict[str, int]) -> CountByStatusOutput: """状态分布字典 → ``CountByStatusOutput`` DTO。 仓储层 ``count_by_status`` 返回 ``{status: count}``,未出现的状态不在字典中, 这里统一补 0。 """ return CountByStatusOutput( active=dist.get("active", 0), paused=dist.get("paused", 0), dead_letter=dist.get("dead_letter", 0), ) def to_get_health_output( *, last_tick_at: Any, active_task_count: int, dead_letter_count: int, running_count: int, healthy: bool, ) -> GetHealthOutput: """构造 ``GetHealthOutput`` DTO。 ``healthy`` 由调用方按"最近 tick 是否在 5 分钟内"判断后传入。 """ return GetHealthOutput( status="healthy" if healthy else "unhealthy", last_tick_at=format_utc_datetime(last_tick_at), active_task_count=active_task_count, dead_letter_count=dead_letter_count, running_count=running_count, ) def daily_stat_to_dict(stat: DailyStat) -> dict[str, Any]: """``DailyStat`` dataclass → dict(供指标导出使用)。 指标服务(``SchedulerMetricService``)从 ``DailyStat`` 提取字段构造 Prometheus 文本格式,此处提供统一的 dict 转换避免重复字段读取。 """ return { "stat_date": stat.stat_date, "handler_name": stat.handler_name, "success_count": stat.success_count, "failure_count": stat.failure_count, "timeout_count": stat.timeout_count, "dead_letter_count": stat.dead_letter_count, } def to_daily_stat_output(stat: DailyStat) -> DailyStatOutput: """``DailyStat`` dataclass → ``DailyStatOutput`` DTO。 计算派生字段 ``total_count`` / ``success_rate``,``stat_date`` 格式化为 ISO 日期字符串(``YYYY-MM-DD``)。 """ total_count = stat.success_count + stat.failure_count + stat.timeout_count success_rate = stat.success_count / total_count if total_count > 0 else 0.0 return DailyStatOutput( stat_date=stat.stat_date.isoformat(), handler_name=stat.handler_name, success_count=stat.success_count, failure_count=stat.failure_count, timeout_count=stat.timeout_count, dead_letter_count=stat.dead_letter_count, total_count=total_count, success_rate=success_rate, ) def to_list_daily_stats_output(stats: list[DailyStat]) -> ListDailyStatsOutput: """``DailyStat`` 列表 → ``ListDailyStatsOutput`` DTO。""" return ListDailyStatsOutput(items=[to_daily_stat_output(s) for s in stats]) def to_list_upcoming_output(tasks: list[ScheduledTask]) -> ListUpcomingOutput: """``ScheduledTask`` 列表 → ``ListUpcomingOutput`` DTO。""" return ListUpcomingOutput(items=[to_task_output(t) for t in tasks]) def to_list_handler_summary_output( summaries: list[HandlerSummary], ) -> ListHandlerSummaryOutput: """``HandlerSummary`` 列表 → ``ListHandlerSummaryOutput`` DTO。""" return ListHandlerSummaryOutput(items=[to_handler_summary_output(s) for s in summaries])