# -*- coding: utf-8 -*- """ETL任务基类""" from datetime import datetime, timedelta from zoneinfo import ZoneInfo class BaseTask: """ETL任务基类""" def __init__(self, config, db_connection, api_client, logger): self.config = config self.db = db_connection self.api = api_client self.logger = logger self.tz = ZoneInfo(config.get("app.timezone", "Asia/Taipei")) def get_task_code(self) -> str: """获取任务代码""" raise NotImplementedError("子类需实现 get_task_code 方法") def execute(self) -> dict: """执行任务""" raise NotImplementedError("子类需实现 execute 方法") def _get_time_window(self, cursor_data: dict = None) -> tuple: """计算时间窗口""" now = datetime.now(self.tz) # 判断是否在闲时窗口 idle_start = self.config.get("run.idle_window.start", "04:00") idle_end = self.config.get("run.idle_window.end", "16:00") is_idle = self._is_in_idle_window(now, idle_start, idle_end) # 获取窗口大小 if is_idle: window_minutes = self.config.get("run.window_minutes.default_idle", 180) else: window_minutes = self.config.get("run.window_minutes.default_busy", 30) # 计算窗口 overlap_seconds = self.config.get("run.overlap_seconds", 120) if cursor_data and cursor_data.get("last_end"): window_start = cursor_data["last_end"] - timedelta(seconds=overlap_seconds) else: window_start = now - timedelta(minutes=window_minutes) window_end = now return window_start, window_end, window_minutes def _is_in_idle_window(self, dt: datetime, start_time: str, end_time: str) -> bool: """判断是否在闲时窗口""" current_time = dt.strftime("%H:%M") return start_time <= current_time <= end_time def _build_result(self, status: str, counts: dict) -> dict: """构建结果字典""" return { "status": status, "counts": counts }