ForcePilot/backend/package/yuxi/scheduler/use_cases/mappers.py

202 lines
6.9 KiB
Python
Raw Normal View History

"""领域模型(dataclass) ↔ Output DTO(Pydantic) 转换。
规范 §6.1/§7.3对齐 ``external_systems/use_cases/mappers.py``
- persistence 返回领域模型 dataclassservice 返回 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])