# AI_CHANGELOG # - 2026-03-29 | Prompt: DWS_TASK_ENGINE ETL 任务 | 新建文件。 # 编排任务引擎全流程:完成检查 → 过期检查 → 任务生成。 # 通过 HTTP 调用后端 POST /api/internal/run-job 按 job_name 执行。 # -*- coding: utf-8 -*- """ DWS 任务引擎编排任务(DWS_TASK_ENGINE) 在 DWS 指数计算完成后执行,按顺序调用后端任务引擎的各个步骤: 1. recall_completion_check — 检测召回是否完成,生成回访任务 2. task_expiry_check — 标记超时未处理的任务 3. task_generator — 根据 WBI/NCI/RS 指数生成/替换任务 通过 HTTP 调用后端 POST /api/internal/run-job(Internal-Token 认证), 每步失败仅记录日志,不中断后续步骤。 """ from __future__ import annotations import logging import os from pathlib import Path from typing import Any import requests from dotenv import load_dotenv from ..base_task import BaseTask, TaskContext # 加载根 .env(BACKEND_API_URL / INTERNAL_API_TOKEN 不在 AppConfig 映射中) # task_engine.py → dws/ → tasks/ → feiqiu/ → connectors/ → etl/ → apps/ → root _REPO_ROOT = Path(__file__).resolve().parents[6] load_dotenv(_REPO_ROOT / ".env", override=False) logger = logging.getLogger(__name__) _TIMEOUT = (5, 30) # 连接 5s,读取 30s(任务执行可能较慢) # 按顺序执行的后端任务列表 _JOB_SEQUENCE = [ "recall_completion_check", "task_expiry_check", "task_generator", ] def _run_backend_job(backend_url: str, token: str, job_name: str) -> dict: """调用后端 POST /api/internal/run-job 执行指定任务。 Returns: {"success": bool, "message": str} 或 {"success": False, "message": error} """ url = f"{backend_url}/api/internal/run-job" headers = { "Authorization": f"Internal-Token {token}", "Content-Type": "application/json", } body = {"job_name": job_name} try: resp = requests.post(url, json=body, headers=headers, timeout=_TIMEOUT) if resp.status_code == 200: data = resp.json() # 后端 ResponseWrapperMiddleware 包装:{"code": 0, "data": {...}} inner = data.get("data", data) return { "success": inner.get("success", False), "message": inner.get("message", ""), } else: return { "success": False, "message": f"HTTP {resp.status_code}: {resp.text[:200]}", } except requests.RequestException as exc: return {"success": False, "message": str(exc)} class DwsTaskEngineTask(BaseTask): """DWS 任务引擎编排任务。 不读写 DWS 表,仅通过 HTTP 调用后端执行任务引擎步骤。 继承 BaseTask 而非 BaseDwsTask,因为不需要 DWS 层的数据操作方法。 """ def get_task_code(self) -> str: return "DWS_TASK_ENGINE" def extract(self, context: TaskContext) -> dict[str, Any]: """无需提取数据,返回空上下文。""" return {} def load(self, extracted: dict[str, Any], context: TaskContext) -> dict[str, Any]: """按顺序调用后端任务引擎的各个步骤。""" backend_url = os.environ.get("BACKEND_API_URL", "").rstrip("/") token = os.environ.get("INTERNAL_API_TOKEN", "") if not backend_url: self.logger.error("DWS_TASK_ENGINE 跳过:BACKEND_API_URL 未配置") return {"skipped": True, "reason": "BACKEND_API_URL 未配置"} if not token: self.logger.error("DWS_TASK_ENGINE 跳过:INTERNAL_API_TOKEN 未配置") return {"skipped": True, "reason": "INTERNAL_API_TOKEN 未配置"} results: dict[str, Any] = {} for job_name in _JOB_SEQUENCE: self.logger.info("DWS_TASK_ENGINE: 执行 %s ...", job_name) result = _run_backend_job(backend_url, token, job_name) success = result.get("success", False) message = result.get("message", "") results[job_name] = {"success": success, "message": message} if success: self.logger.info( "DWS_TASK_ENGINE: %s 成功 — %s", job_name, message ) else: self.logger.warning( "DWS_TASK_ENGINE: %s 失败 — %s", job_name, message ) return results