| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- #!/usr/bin/env python3
- """Generate ``_tmp_temperature/out/分析报告.md`` from the unified outputs."""
- from __future__ import annotations
- import argparse
- from pathlib import Path
- import pandas as pd
- def md_table(frame: pd.DataFrame, columns: list[str]) -> str:
- frame = frame.fillna("")
- lines = ["| " + " | ".join(columns) + " |", "|" + "|".join(["---"] * len(columns)) + "|"]
- for _, row in frame.iterrows():
- lines.append("| " + " | ".join(str(row[column]) for column in columns) + " |")
- return "\n".join(lines)
- def main() -> None:
- parser = argparse.ArgumentParser()
- parser.add_argument("--dir", default=str(Path(__file__).resolve().parent / "out"))
- args = parser.parse_args()
- directory = Path(args.dir)
- segments = pd.read_csv(directory / "温度异常段.csv")
- backtest = pd.read_csv(directory / "回测_温度事件.csv")
- channels = pd.read_csv(directory / "温度通道异常段.csv")
- cycles = pd.read_csv(directory / "温度演化周期.csv")
- hf_path = directory / "温度高频波动段.csv"
- hf = pd.read_csv(hf_path) if hf_path.exists() else pd.DataFrame()
- candidate_path = directory / "温度高频候选.csv"
- candidates = pd.read_csv(candidate_path) if candidate_path.exists() else pd.DataFrame()
- events = segments[segments["特征类型"] != "高频波动"]
- observation_path = directory / "温度观察段.csv"
- observed = pd.read_csv(observation_path) if observation_path.exists() else pd.DataFrame()
- linked = int((observed["是否升级为事件"] == "是").sum()) if "是否升级为事件" in observed else 0
- significant_channels = int((channels["是否显著"] == "是").sum()) if "是否显著" in channels else 0
- counts = events.pivot_table(index="机组", columns="特征类型", values="批次", aggfunc="count", fill_value=0)
- counts["合计"] = counts.sum(axis=1)
- counts = counts.reset_index()
- severe = events[events["温度阶段"].isin(["异常", "严重异常"])].copy()
- severe = severe.sort_values(["机组", "开始小时"])
- severe_columns = ["机组", "级", "温度阶段", "特征类型", "方向", "置信度", "开始小时", "结束小时",
- "有效运行小时数", "最大级差变化", "最大同级偏离", "最大同级偏低", "最大同级相对3h", "阶段触发依据"]
- channel_counts = channels.groupby(["机组", "异常类型"]).size().unstack(fill_value=0).reset_index()
- channel_examples = channels[
- (channels["机组"].isin(["4号机", "6号机"]))
- & (channels["开始小时"].str.startswith(("2026-02-17", "2026-01-15")))
- ].copy()
- channel_columns = ["机组", "点位描述", "异常类型", "开始小时", "结束小时", "持续自然小时数",
- "最小值", "最大值", "同级基准中位"]
- hf_counts = hf.groupby(["机组", "级", "温度阶段"]).size().reset_index(name="段数") if not hf.empty else pd.DataFrame(columns=["机组", "级", "温度阶段", "段数"])
- hf_example = hf[(hf["机组"] == "9号机") & (hf["开始小时"].str.startswith("2026-04-04"))].head(1) if not hf.empty else pd.DataFrame()
- hf_example_columns = ["机组", "级", "温度阶段", "开始小时", "结束小时", "有效运行小时数", "高频波动倍数", "高频标准差", "高频基线标准差"]
- backtest_columns = ["机组", "故障日期", "描述", "最大同级偏离℃", "最大级差变化℃", "命中",
- "首个事件开始", "提前小时", "同日命中", "通道异常命中", "通道异常类型"]
- text = f"""# 缸体温度统一演化检测 —— 第一版结果
- > 代码在 `_tmp_temperature/`,`models/` 未改动。
- > 统一入口:`python3 _tmp_temperature/rescan.py --out _tmp_temperature/out`
- > 回测:`python3 _tmp_temperature/backtest.py --dir _tmp_temperature/out`
- ## 1. 结果概览
- - 总故障段表 `温度异常段.csv`:**{len(segments)}** 行(温度事件 {len(events)} + 高频波动 {len(hf)})。
- - 温度事件:**{len(events)}** 段;另有 1 小时观察段 **{len(observed)}** 条(单独在 `温度观察段.csv`,其中 **{linked}** 条在 24h 内升级为事件)。
- - 三级高频波动:**{len(hf)}** 段(高频候选 **{len(candidates)}** 段,未确认的留在 `温度高频候选.csv`,不再重复计数)。
- - 涵盖 4#~9#,统一阈值(一级/二级用同级相对;三级只用高频波动单独输出)。
- - 事件类型:{', '.join(f'{key}={value}' for key, value in events['特征类型'].value_counts().items())}。
- {md_table(counts, list(counts.columns))}
- ## 2. 统一规则
- 1. **数据有效性**:7/8/9 用转速;4# 用累计运行小时(+机身振动/进气压力兜底);5#/6# 用 2/4 信号(一级排气压力、一级进气压力、机身振动、调节阀开度);整级低于 45℃ 视为停机;单通道突然到环境温度并恢复视为“停机/通道异常”,不参与温度故障。
- 2. **同级温差(常开特征)**:级差、级差较平时变化、每缸相对同级中位、每缸相对自身历史偏移;无事件小时也输出到 `同级温差特征.csv`。
- 3. **短时单缸变化**:1h/3h/6h 同级相对变化;1h 记“观察”(单独输出、逐小时一行),2h 记“事件”;要求同方向连续 2 小时,并过滤“先降后升/先升后降”的过渡。
- 4. **确认/滞回**:持续型 3h 确认;短时型 2h;观察 1h;相邻段间隔 ≤6h 合并。
- 5. **阈值(统一)**:同级偏高/偏低 4/5、6/8、10/12℃;级差变化 4/8/15℃;短时 1h 3/4/6/9℃、3h 4/5/7/11℃、6h 6/7/9/13℃;三级高频波动:先去慢趋势,再要求去趋势残差标准差 ≥0.5℃、反复升降 ≥5 次/小时、持续 ≥3h(候选/确认分开);6h 单缸相对抬升 3/4.5/6℃、整级 6h 抬升在 ±3℃ 内、持续 ≥2h(已有持续偏高事件保持原类型;整级共同抬升只作特征,不作为事件)。
- ## 3. 维修事件回测(4#/5#/6#)
- {md_table(backtest, backtest_columns)}
- - 12 起中 11 起有数据的事件全部命中;5# 2026-07-11 超出批次 58 数据范围(截至 2026-06-30)。
- - 6# 2026-02-18 通过 **2/17 22:00 的通道骤降/停机异常**命中(2h 前),不再算未命中。
- - “提前小时”负数表示在故障日当天 0 点之后的同一日内命中;`同日命中=是` 更直观。
- ## 4. 异常/严重异常事件清单(供复核)
- {md_table(severe, severe_columns)}
- ## 5. 温度通道异常
- {md_table(channel_counts, list(channel_counts.columns))}
- - 通道异常总段数 **{len(channels)}**,其中“显著” **{significant_channels}** 段(持续≥2h / 坏道 / 冻值);复核时可先看显著项。
- 典型例子:
- {md_table(channel_examples, channel_columns)}
- ## 6. 7#/8#/9# 三级高频波动
- - 高频波动是 `温度异常段.csv` 中的 `特征类型=高频波动` 行,`温度高频波动段.csv` 为确认后的过滤副本;`温度高频候选.csv` 保留全部首轮候选及是否确认。温度事件仍为上面的 {len(events)} 条。
- - 判定:先去慢趋势,再要求去趋势残差标准差 ≥0.5℃、反复升降 ≥5 次/小时、持续 ≥3h;首轮候选 60 段,确认 {len(hf)} 段。
- - 目前只对单缸的三级输出;一级/二级暂不输出。
- {md_table(hf_counts, list(hf_counts.columns))}
- 典型例子:
- {md_table(hf_example, hf_example_columns)}
- ## 7. 演化周期
- `温度演化周期.csv` 按 **每次开机周期 × 级** 一行,包含:
- - 阶段演化顺序、方向、特征类型、置信度;
- - 观察/轻微/异常/严重异常各阶段起止与运行小时数;
- - 峰值时刻/峰值缸、最大级差变化、最大同级偏高/偏低;
- - 首次短时触发、首次持续触发、关联通道异常。
- ## 8. 运行态加强后的变化
- - 4# 改用**累计运行小时** `YSJ_31`(+1/h 运行、0 停机);5#/6# 改用 **2/4 信号**(一级排气压力、一级进气压力、机身振动、调节阀开度)。
- - 与旧版“一级进气 >0.15”相比,剔除了大量“油泵在转但压缩机已停/卸载”的小时:4# 约 117h、5# 约 101h、6# 约 669h。
- - 温度小时均值已排除 0 值;通道掉点用原始最小值和零值样本数标记,恢复段不再被当成上升;短时变化要求当前值高于近期 6h 中位。
- - 单缸 6h 相对抬升并入事件(类型 `单缸抬升`):阈值 3/4.5/6℃,且整级 6h 抬升在 ±3℃ 内;已有 `持续偏高` 事件保持原类型,不重新分类。整级共同抬升只保留为特征,不生成事件。
- - 因此有 **9 个旧事件落在启停过渡段**,新版不再作为确认事件:4# 2026-02-15、2026-03-24、2026-04-04、2026-04-25、2026-04-26、2026-05-30;5# 2025-05-09;7# 2025-02-17;8# 2025-01-18。它们多数是开机/停机温度爬坡,不是稳定运行故障;这些过渡段只保留在 `温度过渡段.csv`,不在页面和确认事件中展示。
- ## 9. 限制与下一步
- 1. 4/5/6 无转速,运行态已用累计运行小时/2-of-4 信号加强;极低负荷小时仍可能有少量误判。
- 2. 通道异常表较大(单点级,包含大量 1–2h 采样跌落);建议先按“持续≥2h 或 坏道/冻值”筛选复核。
- 3. 现有 level 阈值已由你复核为真事件,暂不提高;短时/偏低事件建议再做一轮现场复核后微调。
- 4. 规则稳定后再合并进 `models/temperature/`,并接入 `compressor_alarm`。
- """
- (directory / "分析报告.md").write_text(text, encoding="utf-8")
- print(f"written -> {directory / '分析报告.md'}")
- if __name__ == "__main__":
- main()
|