Files
ts-anomaly-td/ts_anomaly_td/detection.py
T
张宗平 031aa35604 fix(schema): escape TDengine 3.4 reserved words 'value'/'label' and use column alias for ANOMALY_WINDOW
- schema: rename label -> is_anomaly, escape `value` as backtick
- detection/forecast: wrap subquery with SELECT ts, `value` AS v
  so ANOMALY_WINDOW/FORECAST receive non-keyword column name
- resolves [0x2600] syntax error and unblocks E2E pipeline on community tsdb 3.4.1
2026-06-11 16:08:34 +08:00

78 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""异常检测模块: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