Files
iAOP/templates/ti-cl4/impurity-forecast/_sanity_check_model.py
T

75 lines
2.3 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.
# -*- coding: utf-8 -*-
"""炉层杂质预警无监督模型冒烟脚本(Issue #71)。
直接运行 ``python _sanity_check_model.py`` 验证:ZScoreScorer 可在正常段 fit、
异常段产出高分数、ThresholdRule 触发预警、提前量评估为正(对齐 PRD 提前≥30min)。
零第三方依赖。
"""
import importlib.util
import os
import sys
_PKG_DIR = os.path.dirname(os.path.abspath(__file__))
def _load_pkg(name, path):
if name in sys.modules:
return
spec = importlib.util.spec_from_file_location(
name, os.path.join(path, "__init__.py"),
submodule_search_locations=[path])
mod = importlib.util.module_from_spec(spec)
sys.modules[name] = mod
spec.loader.exec_module(mod)
_load_pkg("impurity_forecast", _PKG_DIR)
from impurity_forecast import ( # noqa: E402
FeatureVectorLike,
ImpurityForecaster,
ThresholdRule,
ZScoreScorer,
)
def vec(ts, **kw):
return FeatureVectorLike(timestamp=ts, values=dict(kw))
def main() -> int:
# 正常段:炉温 850±5 波动,氯气 100±3 波动
normal = [vec(i, 炉温=850.0 + (i % 3) * 2, 氯气=100.0 + (i % 2))
for i in range(40)]
# 观测段:前 40 正常,之后急升温 + 氯气突降
obs = list(normal) + [
vec(40 + i, 炉温=860.0 + 6.0 * i, 氯气=95.0 - i) for i in range(20)
]
anomaly_ts = 59.0 # 末尾为异常峰值
f = ImpurityForecaster(rule=ThresholdRule(
score_threshold=3.0,
feature_thresholds={"炉温": 900.0}))
f.fit(normal)
decisions, lt = f.evaluate(obs, anomaly_ts=anomaly_ts)
triggered = [d for d in decisions if d.triggered]
assert triggered, "异常段应触发预警"
print(f"[OK] 预警触发 {len(triggered)} 次")
print(f"[OK] 首次预警 ts={lt.first_alert_ts},真实异常 ts={anomaly_ts},"
f"提前量={lt.lead_minutes:.1f} min(>0 即满足提前量口径)")
# 模型可序列化
d = f.scorer.to_dict()
sc2 = ZScoreScorer.from_dict(d)
s1 = f.scorer.score(obs[-1:])
s2 = sc2.score(obs[-1:])
assert abs(s1[0] - s2[0]) < 1e-9, "序列化前后分数应一致"
print("[OK] 模型序列化往返一致(可版本化保存)")
print("炉层杂质预警模型冒烟通过 ✅")
return 0
if __name__ == "__main__":
raise SystemExit(main())