"""异常检测模块:ANOMALY_WINDOW × 6 算法多路跑 + 容错。""" import logging from typing import Any logger = logging.getLogger(__name__) ALL_ALGOS = ["ksigma", "iqr", "grubbs", "shesd", "lof", "sample_ad_model"] def run_anomaly_window(conn, stable: str, algo: str) -> list[tuple[int, int]]: """执行单算法 ANOMALY_WINDOW。 Args: conn: TDConnection 实例 stable: supertable 名称(不含 ds_ 前缀) algo: 算法名 Returns: [(start_ts_ms, end_ts_ms), ...] 异常窗口列表 Raises: ValueError: 算法不支持 Exception: SQL 执行失败(透传) """ if algo not in ALL_ALGOS: raise ValueError(f"不支持的算法: {algo},可选: {ALL_ALGOS}") sql = ( f"SELECT _WSTART, _WEND " f"FROM (SELECT ts, `value` AS v FROM ds_{stable}) " f"ANOMALY_WINDOW(v, 'algo={algo}')" ) rows = conn.execute(sql) windows = [] for row in rows: if row[0] is not None and row[1] is not None: windows.append((int(row[0]), int(row[1]))) return windows def detect_all_algos(conn, stable: str, algos: list[str] | None = None) -> dict[str, Any]: """依次执行所有指定算法,独立容错。 Args: conn: TDConnection 实例 stable: supertable 名称 algos: 算法列表,默认 ALL_ALGOS Returns: {algo_name: {"windows": [...], "error": None | str}, ...} """ algos = algos or ALL_ALGOS results: dict[str, Any] = {} for algo in algos: logger.info("running ANOMALY_WINDOW algo=%s stable=%s", algo, stable) try: if algo == "sample_ad_model": # 检查模型文件 import os model_path = os.path.join("data", "models", "sample_ad_model") if not os.path.exists(model_path): results[algo] = {"windows": [], "error": "model file not found"} logger.warning("sample_ad_model skip: model file not found at %s", model_path) continue windows = run_anomaly_window(conn, stable, algo) results[algo] = {"windows": windows, "error": None} logger.info("algo=%s: %d windows found", algo, len(windows)) except Exception as e: results[algo] = {"windows": [], "error": str(e)} logger.warning("algo=%s failed (non-fatal): %s", algo, e) return results