浏览代码

缸体振动:统一标签、报告与旧链路清理

18922397810 3 天之前
父节点
当前提交
a3545b4455
共有 100 个文件被更改,包括 1015 次插入 和 1249 次删除
  1. 98 0
      _tmp_cylinder_vibration/README.md
  2. 10 3
      _tmp_cylinder_vibration/adaptive.py
  3. 10 4
      _tmp_cylinder_vibration/context_html.py
  4. 0 253
      _tmp_cylinder_vibration/html_export.py
  5. 0 165
      _tmp_cylinder_vibration/html_waveforms.py
  6. 0 215
      _tmp_cylinder_vibration/intervals.py
  7. 0 1
      _tmp_cylinder_vibration/labels.py
  8. 18 10
      _tmp_cylinder_vibration/local_labels.py
  9. 295 0
      _tmp_cylinder_vibration/make_report.py
  10. 0 60
      _tmp_cylinder_vibration/out/adaptive_summary.json
  11. 0 254
      _tmp_cylinder_vibration/out/baseline.json
  12. 128 0
      _tmp_cylinder_vibration/out/cylinder_vibration_label_unified.csv
  13. 0 4
      _tmp_cylinder_vibration/out/event_validation_adaptive.csv
  14. 3 3
      _tmp_cylinder_vibration/out/event_validation_full.csv
  15. 0 4
      _tmp_cylinder_vibration/out/event_validation_intervals.csv
  16. 0 4
      _tmp_cylinder_vibration/out/event_validation_refined.csv
  17. 285 0
      _tmp_cylinder_vibration/out/fault_label_unified_preview.csv
  18. 31 31
      _tmp_cylinder_vibration/out/full_summary.json
  19. 0 36
      _tmp_cylinder_vibration/out/html/event_2026-06-12.html
  20. 0 36
      _tmp_cylinder_vibration/out/html/event_2026-06-21.html
  21. 0 36
      _tmp_cylinder_vibration/out/html/event_2026-07-11.html
  22. 0 17
      _tmp_cylinder_vibration/out/html/timeline.html
  23. 11 5
      _tmp_cylinder_vibration/out/html/waveforms.html
  24. 0 66
      _tmp_cylinder_vibration/out/interval_summary.json
  25. 126 42
      _tmp_cylinder_vibration/out/label_review.csv
  26. 二进制
      _tmp_cylinder_vibration/out/plots/event_2026-06-12_cyl2.png
  27. 二进制
      _tmp_cylinder_vibration/out/plots/event_2026-06-21_cyl5.png
  28. 二进制
      _tmp_cylinder_vibration/out/plots/event_2026-07-11_cyl2.png
  29. 二进制
      _tmp_cylinder_vibration/out/plots/features_kurt_ratio_2026-06-12.png
  30. 二进制
      _tmp_cylinder_vibration/out/plots/features_kurt_ratio_2026-06-21.png
  31. 二进制
      _tmp_cylinder_vibration/out/plots/features_kurt_ratio_2026-07-11.png
  32. 二进制
      _tmp_cylinder_vibration/out/plots/features_rms_ratio_2026-06-12.png
  33. 二进制
      _tmp_cylinder_vibration/out/plots/features_rms_ratio_2026-06-21.png
  34. 二进制
      _tmp_cylinder_vibration/out/plots/features_rms_ratio_2026-07-11.png
  35. 二进制
      _tmp_cylinder_vibration/out/plots/score_heatmap.png
  36. 二进制
      _tmp_cylinder_vibration/out/plots/waveforms_2026-06-12.png
  37. 二进制
      _tmp_cylinder_vibration/out/plots/waveforms_2026-06-21.png
  38. 二进制
      _tmp_cylinder_vibration/out/plots/waveforms_2026-07-11.png
  39. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0001.png
  40. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0002.png
  41. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0003.png
  42. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0004.png
  43. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0005.png
  44. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0006.png
  45. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0007.png
  46. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0008.png
  47. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0009.png
  48. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0010.png
  49. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0011.png
  50. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0012.png
  51. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0013.png
  52. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0014.png
  53. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0015.png
  54. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0016.png
  55. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0017.png
  56. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0018.png
  57. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0019.png
  58. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0020.png
  59. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0021.png
  60. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0022.png
  61. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0023.png
  62. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0024.png
  63. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0025.png
  64. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0026.png
  65. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0027.png
  66. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0028.png
  67. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0029.png
  68. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0030.png
  69. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0031.png
  70. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0032.png
  71. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0033.png
  72. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0034.png
  73. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0035.png
  74. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0036.png
  75. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0037.png
  76. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0038.png
  77. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0039.png
  78. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0040.png
  79. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0041.png
  80. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0042.png
  81. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0043.png
  82. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0044.png
  83. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0045.png
  84. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0046.png
  85. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0047.png
  86. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0048.png
  87. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0049.png
  88. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0050.png
  89. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0051.png
  90. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0052.png
  91. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0053.png
  92. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0054.png
  93. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0055.png
  94. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0056.png
  95. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0057.png
  96. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0058.png
  97. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0059.png
  98. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0060.png
  99. 二进制
      _tmp_cylinder_vibration/out/report_assets/shot_0061.png
  100. 0 0
      _tmp_cylinder_vibration/out/report_assets/shot_0062.png

+ 98 - 0
_tmp_cylinder_vibration/README.md

@@ -0,0 +1,98 @@
+# 5 号机缸体振动标签(临时实验目录)
+
+> 位置:项目根目录 `_tmp_cylinder_vibration/`。  
+> 参考 `_tmp_temperature/` 的组织方式;**未改动 `models/`**。  
+> 当前阶段只做到:算法、标签、统一字段、HTML、报告和说明文档,暂不合并进模型。
+
+## 目标
+
+把 5 号机缸体振动做成和温度/润滑油压力类似的“故障演化标签”:
+
+- 15 分钟首周期波形特征;
+- 动态局部基线(1~30 天);
+- 轻微 / 异常 / 严重异常三级;
+- 相邻小时合并成小时标签;
+- 同一时刻 6 缸并排查看,标签前后各补 6 小时;
+- 标签带可读的 `阶段触发依据`;
+- 输出统一字段表,后续可进入数据库/模型。
+
+## 数据口径
+
+- 机组:5 号机;
+- 点位:`5号机组一缸缸体振动` ~ `5号机组六缸缸体振动`;
+- 每个文件只用**首周期**;
+- 只使用运行态:`rpm > 0`;
+- 约 15 分钟一个文件;
+- 温度只作为事后对照,不参与振动标签生成。
+
+## 目录
+
+| 文件 | 作用 |
+|---|---|
+| `config.py` | 机组、缸号、事件、阈值和路径 |
+| `data.py` | 读取 `wave_file` / `wave_sample_one` 和温度参考 |
+| `features.py` | 首周期时域、频域、包络、跨缸残差特征 |
+| `local_labels.py` | 30 天动态局部基线、稳健阈值、15 分钟标签 |
+| `adaptive.py` | 自适应扩段、方向、等级、显著性 |
+| `hourly.py` | 15 分钟标签汇总为小时标签,相邻小时合并 |
+| `review.py` | 排查指标和 `review_flags` |
+| `unified_labels.py` | 映射为统一故障标签字段 |
+| `context_html.py` | 生成 `waveforms.html` 交互排查页 |
+| `make_report.py` | 生成统一表、截图和 Word 报告 |
+| `run_full.py` | 全时段特征和标签 |
+| `run_context.py` | 小时标签、排查指标、HTML |
+| `out/` | 标签、统一表、报告、截图 |
+
+## 快速运行
+
+```bash
+# 1. 全量特征 + 15 分钟标签 + 小时标签前处理
+python3 _tmp_cylinder_vibration/run_full.py
+
+# 2. 小时标签 + 排查指标 + HTML
+python3 _tmp_cylinder_vibration/run_context.py
+
+# 3. 统一字段表 + waveforms.html 截图 + Word 报告
+python3 -m _tmp_cylinder_vibration.make_report
+```
+
+打开:
+
+```bash
+open _tmp_cylinder_vibration/out/html/waveforms.html
+```
+
+## 主要输出
+
+| 文件 | 内容 |
+|---|---|
+| `out/vibration_features_full.csv` | 全时段首周期原始特征 |
+| `out/vibration_features_full_local.csv` | 动态局部基线、z、分数、等级前数据 |
+| `out/vibration_labels_full.csv` | 15 分钟点级标签 |
+| `out/vibration_intervals_full.csv` | 自适应 15 分钟段 |
+| `out/vibration_hourly_labels.csv` | 小时标签(最终展示口径) |
+| `out/label_review.csv` | 排查指标和标记 |
+| `out/cylinder_vibration_label_unified.csv` | 振动统一字段表 |
+| `out/fault_label_unified_preview.csv` | 压力/温度/振动三类统一预览 |
+| `out/html/waveforms.html` | 1×6 子图交互排查页 |
+| `总结文档/缸体振动演化过程故障标签手册.docx` | Word 报告 |
+| `out/report_assets/` | 报告截图(只截右侧图表) |
+
+## 当前结果(2026-11)
+
+- 小时标签:127 条;
+- 轻微 68、异常 29、严重异常 30;
+- 多特征确认 46;
+- 3 次事件全部命中;
+- 具体结果见 `out/分析报告.md` 和 `总结文档/缸体振动演化过程故障标签手册.docx`。
+
+## 限制
+
+- 振动是首周期快照,不是连续 15 分钟波形;
+- 启动/停机瞬态仍可能形成标签,用 `review_flags` 标记而不是删除;
+- 标签是规则扫描结果,不等同于物理故障结论;
+- 本目录暂不写数据库、不接 API、不合并 `models/`。
+
+## 后续
+
+见 `合并方案.md`。下一阶段再把稳定模块迁入 `models/` 并接入统一故障标签表。

+ 10 - 3
_tmp_cylinder_vibration/adaptive.py

@@ -124,12 +124,19 @@ def build_intervals(frame: pd.DataFrame, point_labels: pd.DataFrame, baseline: d
                 direction = "高幅"
             else:
                 direction = "未确认"
-            if confirmed and float(max_row["score"]) >= 6.0 and duration >= 2.0:
-                level = "严重"
+            score = float(max_row["score"])
+            candidate_threshold = float(part["threshold"].iloc[0])
+            anomaly_threshold = float(part["anomaly_threshold"].iloc[0])
+            severe_threshold = float(part["severe_threshold"].iloc[0])
+            if score >= severe_threshold:
+                level = "严重异常"
                 confidence = "高"
-            elif confirmed:
+            elif score >= anomaly_threshold:
                 level = "异常"
                 confidence = "中"
+            elif score >= candidate_threshold:
+                level = "轻微"
+                confidence = "低"
             else:
                 level = "观察"
                 confidence = "低"

+ 10 - 4
_tmp_cylinder_vibration/context_html.py

@@ -165,8 +165,10 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;ma
     <div class="filters" id="filters">
       <button data-filter="all" class="active">全部</button>
       <button data-filter="event">事件窗口</button>
+      <button data-filter="minor">轻微</button>
+      <button data-filter="anomaly">异常</button>
+      <button data-filter="severe">严重异常</button>
       <button data-filter="confirmed">确认</button>
-      <button data-filter="observation">观察</button>
     </div>
     <div id="list" class="list"></div>
   </div>
@@ -215,7 +217,9 @@ function applyFilter() {
     let show = true;
     if (activeFilter === 'event') show = card.dataset.event === '1';
     else if (activeFilter === 'confirmed') show = card.dataset.confirmed === '1';
-    else if (activeFilter === 'observation') show = s.level === '观察';
+    else if (activeFilter === 'minor') show = s.level === '轻微';
+    else if (activeFilter === 'anomaly') show = s.level === '异常';
+    else if (activeFilter === 'severe') show = s.level === '严重异常';
     card.style.display = show ? 'flex' : 'none';
     if (show && firstVisible === null) firstVisible = card;
   }
@@ -226,10 +230,12 @@ function updateFilterCounts() {
   const counts = {
     all: segments.length,
     event: segments.filter(s => s.event_hits && s.event_hits.length).length,
+    minor: segments.filter(s => s.level === '轻微').length,
+    anomaly: segments.filter(s => s.level === '异常').length,
+    severe: segments.filter(s => s.level === '严重异常').length,
     confirmed: segments.filter(s => s.confirmed).length,
-    observation: segments.filter(s => s.level === '观察').length,
   };
-  const labels = {all: '全部', event: '事件窗口', confirmed: '确认', observation: '观察'};
+  const labels = {all: '全部', event: '事件窗口', minor: '轻微', anomaly: '异常', severe: '严重异常', confirmed: '确认'};
   document.querySelectorAll('#filters button').forEach(button => {
     const key = button.dataset.filter;
     button.textContent = `${labels[key]}(${counts[key] || 0})`;

+ 0 - 253
_tmp_cylinder_vibration/html_export.py

@@ -1,253 +0,0 @@
-"""用 HTML + ECharts 输出交互图。"""
-from __future__ import annotations
-
-import json
-from datetime import timedelta
-from pathlib import Path
-
-import numpy as np
-import pandas as pd
-
-from . import config, data
-
-COLORS = {
-    "一缸": "#c0392b",
-    "二缸": "#e67e22",
-    "三缸": "#16a085",
-    "四缸": "#2980b9",
-    "五缸": "#8e44ad",
-    "六缸": "#7f8c8d",
-}
-
-
-def _nearest_time(times: list[str], target) -> str:
-    target = pd.Timestamp(target)
-    parsed = pd.to_datetime(pd.Series(times))
-    index = (parsed - target).abs().idxmin()
-    return times[int(index)]
-
-
-def _aligned(part: pd.DataFrame, times: list[str], cylinder: str, column: str) -> list[float | None]:
-    group = part[part["cylinder"] == cylinder].sort_values("sample_time")
-    if group.empty:
-        return [None] * len(times)
-    values = dict(zip(group["sample_time"].dt.strftime("%Y-%m-%d %H:%M:%S"), group[column]))
-    return [None if pd.isna(values.get(time)) else float(values[time]) for time in times]
-
-
-def _chart_common(times: list[str], fault_time: str, intervals: pd.DataFrame, cylinder: str) -> dict:
-    areas = []
-    part = intervals[intervals["cylinder"] == cylinder]
-    for row in part.itertuples(index=False):
-        areas.append([
-            {"xAxis": _nearest_time(times, row.start_time)},
-            {"xAxis": _nearest_time(times, row.end_time)},
-        ])
-    return {
-        "tooltip": {"trigger": "axis"},
-        "legend": {"top": 0, "type": "scroll"},
-        "grid": {"left": 45, "right": 20, "top": 35, "bottom": 55},
-        "xAxis": {
-            "type": "category",
-            "data": times,
-            "axisLabel": {"rotate": 30, "fontSize": 9},
-        },
-        "yAxis": {"type": "value", "scale": True},
-        "dataZoom": [
-            {"type": "inside", "start": 0, "end": 100},
-            {"type": "slider", "height": 18, "bottom": 10},
-        ],
-        "markLine": {
-            "symbol": "none",
-            "silent": True,
-            "label": {"formatter": "fault"},
-            "lineStyle": {"color": "#2c3e50", "width": 1.2},
-            "data": [{"xAxis": fault_time}],
-        },
-    }
-
-
-def _waveform_option(frame: pd.DataFrame, intervals: pd.DataFrame, event: dict) -> dict | None:
-    cylinder = event["cylinder"]
-    candidates = intervals[intervals["cylinder"] == cylinder]
-    if candidates.empty:
-        return None
-    seed = candidates.loc[candidates["max_score"].idxmax()]
-    seed_time = pd.Timestamp(seed["max_time"])
-    rows = {}
-    for name in config.CYLINDERS:
-        group = frame[frame["cylinder"] == name]
-        if group.empty:
-            continue
-        distance = (group["sample_time"] - seed_time).abs()
-        index = distance.idxmin()
-        if distance.loc[index] <= pd.Timedelta(hours=2):
-            rows[name] = group.loc[index]
-    if not rows:
-        return None
-    samples = data.fetch_samples([int(row["id"]) for row in rows.values()])
-    series = []
-    for name, row in rows.items():
-        signal = samples.get(int(row["id"]))
-        if signal is None or signal.size == 0:
-            continue
-        scale = float(np.sqrt(np.mean(signal ** 2))) or 1.0
-        fs = float(row["sample_frequency_hz"] or 0) or 25600.0
-        x = np.arange(signal.size) / fs * 1000.0
-        series.append(
-            {
-                "name": f"cyl{config.CYLINDER_TO_INDEX[name]}",
-                "type": "line",
-                "showSymbol": False,
-                "sampling": "lttb",
-                "lineStyle": {"width": 2.2 if name == cylinder else 1.0},
-                "itemStyle": {"color": "#c0392b" if name == cylinder else COLORS[name]},
-                "data": [[round(float(x[i]), 3), round(float(signal[i] / scale), 4)] for i in range(signal.size)],
-            }
-        )
-    return {
-        "tooltip": {"trigger": "axis"},
-        "legend": {"top": 0},
-        "grid": {"left": 50, "right": 20, "top": 35, "bottom": 45},
-        "xAxis": {"type": "value", "name": "ms", "scale": True},
-        "yAxis": {"type": "value", "name": "normalized amplitude", "scale": True},
-        "dataZoom": [{"type": "inside"}, {"type": "slider", "height": 18, "bottom": 10}],
-        "series": series,
-    }
-
-
-def _page(event: dict, frame: pd.DataFrame, intervals: pd.DataFrame, validation: pd.DataFrame) -> str:
-    center = pd.Timestamp(event["date"])
-    start = center - pd.Timedelta(days=10)
-    end = center + pd.Timedelta(days=3)
-    part = frame[(frame["sample_time"] >= start) & (frame["sample_time"] <= end)].copy()
-    part["time"] = part["sample_time"].dt.strftime("%Y-%m-%d %H:%M:%S")
-    times = sorted(part["time"].unique().tolist())
-    fault_time = _nearest_time(times, center)
-
-    base = _chart_common(times, fault_time, intervals, event["cylinder"])
-    score_series = []
-    rms_series = []
-    kurt_series = []
-    for name in config.CYLINDERS:
-        color = "#c0392b" if name == event["cylinder"] else COLORS[name]
-        width = 2.4 if name == event["cylinder"] else 1.0
-        score_series.append(
-            {
-                "name": f"cyl{config.CYLINDER_TO_INDEX[name]}",
-                "type": "line",
-                "showSymbol": False,
-                "sampling": "lttb",
-                "connectNulls": False,
-                "lineStyle": {"width": width},
-                "itemStyle": {"color": color},
-                "data": _aligned(part, times, name, "score"),
-            }
-        )
-        rms_series.append(
-            {
-                "name": f"cyl{config.CYLINDER_TO_INDEX[name]}",
-                "type": "line",
-                "showSymbol": False,
-                "sampling": "lttb",
-                "connectNulls": False,
-                "lineStyle": {"width": width},
-                "itemStyle": {"color": color},
-                "data": _aligned(part, times, name, "rms_ratio"),
-            }
-        )
-        kurt_series.append(
-            {
-                "name": f"cyl{config.CYLINDER_TO_INDEX[name]}",
-                "type": "line",
-                "showSymbol": False,
-                "sampling": "lttb",
-                "connectNulls": False,
-                "lineStyle": {"width": width},
-                "itemStyle": {"color": color},
-                "data": _aligned(part, times, name, "kurt_ratio"),
-            }
-        )
-
-    score_series[0]["markArea"] = {
-        "silent": True,
-        "itemStyle": {"color": "rgba(230,126,34,0.15)"},
-        "data": [
-            [
-                {"xAxis": _nearest_time(times, row.start_time)},
-                {"xAxis": _nearest_time(times, row.end_time)},
-            ]
-            for row in intervals[intervals["cylinder"] == event["cylinder"]].itertuples(index=False)
-        ],
-    }
-    score_series[0]["markLine"] = base.pop("markLine")
-
-    score_option = {**base, "yAxis": {"type": "value", "name": "score", "scale": True}, "series": score_series}
-    rms_option = {**base, "yAxis": {"type": "value", "name": "RMS / baseline", "scale": True}, "series": rms_series}
-    kurt_option = {**base, "yAxis": {"type": "value", "name": "kurtosis / baseline", "scale": True}, "series": kurt_series}
-    wave_option = _waveform_option(frame, intervals, event)
-
-    event_rows = validation[validation["event_id"] == event["event_id"]].to_dict("records")
-    table_rows = "".join(
-        f"<tr><td>{event['event_id']}</td><td>{row.get('cylinder','')}</td><td>{row.get('start_time','')}</td>"
-        f"<td>{row.get('end_time','')}</td><td>{row.get('direction','')}</td><td>{row.get('level','')}</td>"
-        f"<td>{row.get('confirmed','')}</td></tr>"
-        for row in event_rows
-    )
-
-    return f"""<!doctype html>
-<html lang="zh-CN"><head><meta charset="utf-8"><title>{event['event_id']} 缸体振动</title>
-<style>
-body{{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;margin:18px;color:#222;background:#fff}}
-h1{{font-size:22px;margin:0 0 8px}} h2{{font-size:16px;margin:22px 0 8px}}
-.chart{{width:100%;height:380px;border:1px solid #eee;border-radius:6px;margin-bottom:14px}}
-.note{{color:#666;font-size:13px;line-height:1.6}} table{{border-collapse:collapse;margin-top:8px;font-size:13px}}
-th,td{{border:1px solid #ddd;padding:6px 10px;text-align:left}} th{{background:#f7f7f7}}
-a{{color:#1a73e8;text-decoration:none}}
-</style></head><body>
-<h1>{event['event_id']} · 事件缸 {event['cylinder']} · {event['description']}</h1>
-<div class="note">灰色断档 = 停机/无运行数据;橙色区域 = 振动候选时间段;竖线 = 故障表日期。温度不参与这些标签。</div>
-<h2>异常分数(6 缸)</h2><div id="score" class="chart"></div>
-<h2>实际 RMS / 基线(6 缸)</h2><div id="rms" class="chart"></div>
-<h2>实际峭度 / 基线(6 缸)</h2><div id="kurt" class="chart"></div>
-<h2>首周期波形对比(种子时刻,归一化)</h2><div id="wave" class="chart"></div>
-<h2>事件验证</h2>
-<table><thead><tr><th>事件</th><th>缸</th><th>开始</th><th>结束</th><th>方向</th><th>等级</th><th>多特征确认</th></tr></thead>
-<tbody>{table_rows}</tbody></table>
-<p class="note"><a href="index.html">返回索引</a></p>
-<script src="echarts.min.js"></script>
-<script>
-const scoreOption = {json.dumps(score_option, ensure_ascii=False)};
-const rmsOption = {json.dumps(rms_option, ensure_ascii=False)};
-const kurtOption = {json.dumps(kurt_option, ensure_ascii=False)};
-const waveOption = {json.dumps(wave_option, ensure_ascii=False) if wave_option else 'null'};
-const charts = [
-  [echarts.init(document.getElementById('score')), scoreOption],
-  [echarts.init(document.getElementById('rms')), rmsOption],
-  [echarts.init(document.getElementById('kurt')), kurtOption],
-];
-if (waveOption) charts.push([echarts.init(document.getElementById('wave')), waveOption]);
-for (const [chart, option] of charts) chart.setOption(option);
-window.addEventListener('resize', () => charts.forEach(([chart]) => chart.resize()));
-</script>
-</body></html>"""
-
-
-def export_html(frame: pd.DataFrame, intervals: pd.DataFrame, validation: pd.DataFrame) -> None:
-    out_dir = config.OUTPUT_DIR / "html"
-    out_dir.mkdir(parents=True, exist_ok=True)
-    for event in config.EVENTS:
-        path = out_dir / f"event_{event['event_id']}.html"
-        path.write_text(_page(event, frame, intervals, validation), encoding="utf-8")
-        print(f"[html] {path}")
-    links = "".join(
-        f"<li><a href=\"event_{event['event_id']}.html\">{event['event_id']} · {event['cylinder']} · {event['description']}</a></li>"
-        for event in config.EVENTS
-    )
-    (out_dir / "index.html").write_text(
-        f"""<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><title>5号机缸体振动</title>
-<style>body{{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;margin:24px}}a{{color:#1a73e8}}</style>
-</head><body><h1>5号机缸体振动分析</h1><ul>{links}</ul></body></html>""",
-        encoding="utf-8",
-    )
-    print(f"[html] {out_dir / 'index.html'}")

+ 0 - 165
_tmp_cylinder_vibration/html_waveforms.py

@@ -1,165 +0,0 @@
-"""全时间段标签的 HTML:选中一个标签,看该时刻的首周期多缸归一化波形。"""
-from __future__ import annotations
-
-import json
-from pathlib import Path
-
-import numpy as np
-import pandas as pd
-
-from . import config, data
-
-COLORS = {
-    "一缸": "#c0392b",
-    "二缸": "#e67e22",
-    "三缸": "#16a085",
-    "四缸": "#2980b9",
-    "五缸": "#8e44ad",
-    "六缸": "#7f8c8d",
-}
-MAX_POINTS = 520
-
-
-def _nearest_row(frame: pd.DataFrame, cylinder: str, target: pd.Timestamp, tolerance=pd.Timedelta(hours=2)):
-    group = frame[frame["cylinder"] == cylinder]
-    if group.empty:
-        return None
-    distance = (group["sample_time"] - target).abs()
-    index = distance.idxmin()
-    if distance.loc[index] > tolerance:
-        return None
-    return group.loc[index]
-
-
-def _baseline_rms(meta: dict) -> float:
-    median = float(meta.get("rms_log", [0.0, 1.0])[0])
-    return float(10 ** median)
-
-
-def export_waveforms(frame: pd.DataFrame, intervals: pd.DataFrame, baseline: dict) -> None:
-    if frame.empty or intervals.empty:
-        return
-    out_dir = config.OUTPUT_DIR / "html"
-    out_dir.mkdir(parents=True, exist_ok=True)
-
-    records: list[dict] = []
-    id_to_row: dict[int, pd.Series] = {}
-    for row in intervals.itertuples(index=False):
-        seed_time = pd.Timestamp(row.max_time)
-        rows: dict[str, pd.Series] = {}
-        for name in config.CYLINDERS:
-            item = _nearest_row(frame, name, seed_time)
-            if item is not None:
-                rows[name] = item
-                id_to_row[int(item["id"])] = item
-        if not rows:
-            continue
-        records.append(
-            {
-                "id": len(records),
-                "cylinder": row.cylinder,
-                "start": str(row.start_time),
-                "end": str(row.end_time),
-                "duration": float(row.duration_hours),
-                "max_score": float(row.max_score),
-                "direction": str(row.direction),
-                "level": str(row.level),
-                "confirmed": bool(row.multi_feature_confirmed),
-                "seed_time": str(seed_time),
-                "rows": {name: int(item["id"]) for name, item in rows.items()},
-            }
-        )
-
-    samples = data.fetch_samples(list(id_to_row), chunk_size=200)
-    for record in records:
-        series = []
-        for name, file_id in record.pop("rows").items():
-            signal = samples.get(int(file_id))
-            if signal is None or signal.size == 0:
-                continue
-            item = id_to_row[int(file_id)]
-            scale = _baseline_rms(baseline.get(name, {}))
-            if not np.isfinite(scale) or scale <= 0:
-                scale = float(np.sqrt(np.mean(signal ** 2))) or 1.0
-            fs = float(item["sample_frequency_hz"] or 0) or 25600.0
-            step = max(1, int(np.ceil(signal.size / MAX_POINTS)))
-            chosen = np.arange(0, signal.size, step)
-            x = chosen / fs * 1000.0
-            y = signal[chosen] / scale
-            series.append(
-                {
-                    "name": f"cyl{config.CYLINDER_TO_INDEX[name]}",
-                    "cylinder": name,
-                    "color": "#c0392b" if name == record["cylinder"] else COLORS[name],
-                    "width": 2.2 if name == record["cylinder"] else 1.0,
-                    "data": [[round(float(x[i]), 3), round(float(y[i]), 4)] for i in range(len(chosen))],
-                }
-            )
-        record["series"] = series
-
-    html = """<!doctype html>
-<html lang="zh-CN"><head><meta charset="utf-8"><title>5号机全时段缸体振动标签</title>
-<style>
-body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;margin:18px;color:#222;background:#fff}
-h1{font-size:22px;margin:0 0 6px} .note{color:#666;font-size:13px;line-height:1.6;margin-bottom:12px}
-.panel{display:grid;grid-template-columns:300px 1fr;gap:16px}
-select{width:100%;padding:8px;font-size:13px}
-.info{border:1px solid #eee;border-radius:6px;padding:10px;margin-top:10px;font-size:13px;line-height:1.7}
-#chart{height:520px;border:1px solid #eee;border-radius:6px}
-table{border-collapse:collapse;margin-top:10px;font-size:12px;width:100%}
-th,td{border:1px solid #ddd;padding:5px 7px;text-align:left} th{background:#f7f7f7}
-</style></head><body>
-<h1>5号机全时段缸体振动标签 · 首周期多缸归一化波形</h1>
-<div class="note">下拉选择标签;横轴是首周期时间(ms),纵轴是“波形 / 该缸正常基线 RMS”。事件缸加粗红色。缺数据的缸不画。</div>
-<div class="panel">
-  <div>
-    <select id="pick"></select>
-    <div id="info" class="info"></div>
-    <table id="table"><thead><tr><th>开始</th><th>方向</th><th>等级</th><th>确认</th></tr></thead><tbody></tbody></table>
-  </div>
-  <div id="chart"></div>
-</div>
-<script src="echarts.min.js"></script>
-<script>
-const records = __RECORDS__;
-const pick = document.getElementById('pick');
-const chart = echarts.init(document.getElementById('chart'));
-for (const r of records) {
-  const opt = document.createElement('option');
-  opt.value = r.id;
-  opt.text = `${r.start} | ${r.cylinder} | ${r.direction} | ${r.level} | 分数 ${r.max_score}${r.confirmed ? ' | 确认' : ''}`;
-  pick.appendChild(opt);
-}
-function render(id) {
-  const r = records.find(x => x.id === Number(id));
-  if (!r) return;
-  document.getElementById('info').innerHTML = `<b>${r.start} ~ ${r.end}</b><br>缸:${r.cylinder}<br>方向:${r.direction}<br>等级:${r.level}<br>多特征确认:${r.confirmed ? '是' : '否'}<br>最大分数:${r.max_score}<br>种子时刻:${r.seed_time}`;
-  const tbody = document.querySelector('#table tbody');
-  tbody.innerHTML = `<tr><td>${r.start}</td><td>${r.direction}</td><td>${r.level}</td><td>${r.confirmed ? '是' : '否'}</td></tr>`;
-  chart.setOption({
-    tooltip: {trigger: 'axis'},
-    legend: {top: 0},
-    grid: {left: 55, right: 20, top: 35, bottom: 45},
-    xAxis: {type: 'value', name: 'ms', scale: true},
-    yAxis: {type: 'value', name: 'waveform / baseline RMS', scale: true},
-    dataZoom: [{type: 'inside'}, {type: 'slider', height: 18, bottom: 10}],
-    series: r.series.map(s => ({
-      name: s.name, type: 'line', showSymbol: false, sampling: 'lttb',
-      lineStyle: {width: s.width}, itemStyle: {color: s.color}, data: s.data
-    }))
-  }, true);
-}
-pick.addEventListener('change', () => render(pick.value));
-const first = records.filter(r => r.confirmed).sort((a, b) => b.max_score - a.max_score)[0] || records[0];
-if (first) { pick.value = first.id; render(first.id); }
-window.addEventListener('resize', () => chart.resize());
-</script></body></html>"""
-    html = html.replace("__RECORDS__", json.dumps(records, ensure_ascii=False))
-    (out_dir / "waveforms.html").write_text(html, encoding="utf-8")
-    (out_dir / "index.html").write_text(
-        """<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><title>5号机缸体振动</title>
-<style>body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;margin:24px}a{color:#1a73e8}</style>
-</head><body><h1>5号机缸体振动</h1><p><a href="waveforms.html">打开全时段标签波形</a></p></body></html>""",
-        encoding="utf-8",
-    )
-    print(f"[html] {out_dir / 'waveforms.html'} ({len(records)} labels)")

+ 0 - 215
_tmp_cylinder_vibration/intervals.py

@@ -1,215 +0,0 @@
-"""第二轮:把单点候选扩展成连续时间段,并做事件验证。"""
-from __future__ import annotations
-
-from datetime import timedelta
-
-import numpy as np
-import pandas as pd
-
-from . import config
-
-
-def _level(max_score: float, active_ratio: float, duration_hours: float) -> str:
-    if max_score >= 6.0 and active_ratio >= 0.5 and duration_hours >= 2.0:
-        return "严重"
-    if max_score >= 4.5 and active_ratio >= 0.3:
-        return "异常"
-    return "轻微"
-
-
-def _confidence(max_score: float, active_ratio: float, duration_hours: float) -> str:
-    if max_score >= 6.0 and active_ratio >= 0.5 and duration_hours >= 2.0:
-        return "高"
-    if max_score >= 4.5 and active_ratio >= 0.3:
-        return "中"
-    return "低"
-
-
-def build_intervals(
-    frame: pd.DataFrame,
-    point_labels: pd.DataFrame,
-    before_hours: float = 1.0,
-    after_hours: float = 1.0,
-    merge_gap_hours: float = 2.0,
-    min_duration_hours: float = 0.75,
-) -> pd.DataFrame:
-    """每个单点候选向左右扩展 before/after 小时,再按缸合并相邻段。"""
-    if point_labels.empty or frame.empty:
-        return pd.DataFrame()
-
-    frame = frame.copy()
-    frame["sample_time"] = pd.to_datetime(frame["sample_time"])
-    rows: list[dict] = []
-    for cylinder, group in frame.groupby("cylinder", sort=False):
-        group = group.sort_values("sample_time").reset_index(drop=True)
-        seeds = point_labels[point_labels["cylinder"] == cylinder]
-        if seeds.empty:
-            continue
-        candidates: list[tuple[pd.Timestamp, pd.Timestamp, pd.Series]] = []
-        for seed in seeds.itertuples(index=False):
-            seed_time = pd.Timestamp(getattr(seed, "max_time"))
-            start = seed_time - timedelta(hours=before_hours)
-            end = seed_time + timedelta(hours=after_hours)
-            part = group[(group["sample_time"] >= start) & (group["sample_time"] <= end)]
-            if part.empty:
-                part = group.iloc[[int((group["sample_time"] - seed_time).abs().argmin())]]
-            # 不足 1 小时时,扩大到前后 1.5 小时,保证形成可解释的时间段。
-            if (pd.Timestamp(part["sample_time"].iloc[-1]) - pd.Timestamp(part["sample_time"].iloc[0])).total_seconds() / 3600.0 < 1.0:
-                part = group[
-                    (group["sample_time"] >= seed_time - timedelta(hours=1.5))
-                    & (group["sample_time"] <= seed_time + timedelta(hours=1.5))
-                ]
-            if part.empty:
-                continue
-            candidates.append((pd.Timestamp(part["sample_time"].iloc[0]), pd.Timestamp(part["sample_time"].iloc[-1]), seed))
-
-        if not candidates:
-            continue
-        candidates.sort(key=lambda item: item[0])
-        merged: list[tuple[pd.Timestamp, pd.Timestamp, list[pd.Series]]] = []
-        for start, end, seed in candidates:
-            if merged and start <= merged[-1][1] + timedelta(hours=merge_gap_hours):
-                merged[-1] = (merged[-1][0], max(merged[-1][1], end), merged[-1][2] + [seed])
-            else:
-                merged.append((start, end, [seed]))
-
-        for start, end, seed_rows in merged:
-            part = group[(group["sample_time"] >= start) & (group["sample_time"] <= end)]
-            if part.empty:
-                continue
-            actual_start = pd.Timestamp(part["sample_time"].iloc[0])
-            actual_end = pd.Timestamp(part["sample_time"].iloc[-1])
-            duration = (actual_end - actual_start).total_seconds() / 3600.0
-            if duration < min_duration_hours:
-                continue
-            max_row = part.loc[part["score"].idxmax()]
-            max_score = float(max_row["score"])
-            mean_score = float(part["score"].mean())
-            active_points = int((part["score"] >= 3.0).sum())
-            active_ratio = active_points / max(len(part), 1)
-            seed_best = max(seed_rows, key=lambda item: float(item.max_score))
-            rows.append(
-                {
-                    "cylinder": cylinder,
-                    "stage": config.CYLINDERS[cylinder]["stage"],
-                    "start_time": actual_start,
-                    "end_time": actual_end,
-                    "duration_hours": round(duration, 2),
-                    "points": int(len(part)),
-                    "mean_score": round(mean_score, 3),
-                    "max_score": round(max_score, 3),
-                    "active_points": active_points,
-                    "active_ratio": round(active_ratio, 2),
-                    "max_time": pd.Timestamp(max_row["sample_time"]),
-                    "driver": str(max_row["driver"]),
-                    "seed_max_score": round(float(seed_best.max_score), 3),
-                    "seed_max_time": pd.Timestamp(seed_best.max_time),
-                    "level": _level(max_score, active_ratio, duration),
-                    "confidence": _confidence(max_score, active_ratio, duration),
-                },
-            )
-
-    if not rows:
-        return pd.DataFrame()
-    return pd.DataFrame(rows).sort_values(["start_time", "cylinder"]).reset_index(drop=True)
-
-
-def validate_events(intervals: pd.DataFrame, temperature: pd.DataFrame) -> pd.DataFrame:
-    rows: list[dict] = []
-    event_windows: list[tuple[pd.Timestamp, pd.Timestamp]] = []
-    for event in config.EVENTS:
-        event_time = pd.Timestamp(event["date"])
-        window_start = event_time - pd.Timedelta(days=config.VALIDATION_BEFORE_DAYS)
-        window_end = event_time + pd.Timedelta(days=config.VALIDATION_AFTER_DAYS)
-        event_windows.append((window_start, window_end))
-        cylinder = event["cylinder"]
-
-        candidate = intervals[
-            (intervals["cylinder"] == cylinder)
-            & (intervals["start_time"] <= window_end)
-            & (intervals["end_time"] >= window_start)
-        ] if not intervals.empty else pd.DataFrame()
-
-        if candidate.empty:
-            hit = False
-            interval = None
-        else:
-            interval = candidate.loc[candidate["max_score"].idxmax()]
-            hit = True
-
-        temp_row = pd.DataFrame()
-        if not temperature.empty:
-            temp_row = temperature[
-                (temperature["cylinder"] == cylinder)
-                & (temperature["start_time"] <= window_end)
-                & (temperature["end_time"] >= window_start)
-            ].sort_values("start_time")
-
-        if interval is None:
-            rows.append(
-                {
-                    "event_id": event["event_id"],
-                    "event_date": event["date"],
-                    "cylinder": cylinder,
-                    "description": event["description"],
-                    "vibration_hit": False,
-                    "interval_start": None,
-                    "interval_end": None,
-                    "duration_hours": None,
-                    "mean_score": None,
-                    "max_score": None,
-                    "driver": None,
-                    "level": None,
-                    "confidence": None,
-                    "lead_days": None,
-                    "temp_reference_start": None if temp_row.empty else temp_row["start_time"].iloc[0],
-                    "temp_reference_end": None if temp_row.empty else temp_row["end_time"].iloc[0],
-                    "temp_reference_stage": None if temp_row.empty else " / ".join(temp_row["温度阶段"].astype(str).unique()),
-                },
-            )
-            continue
-
-        if pd.Timestamp(interval["start_time"]) <= event_time:
-            lead_days = round((event_time - pd.Timestamp(interval["start_time"])).total_seconds() / 86400.0, 2)
-        else:
-            lead_days = -round((pd.Timestamp(interval["start_time"]) - event_time).total_seconds() / 86400.0, 2)
-
-        rows.append(
-            {
-                "event_id": event["event_id"],
-                "event_date": event["date"],
-                "cylinder": cylinder,
-                "description": event["description"],
-                "vibration_hit": True,
-                "interval_start": interval["start_time"],
-                "interval_end": interval["end_time"],
-                "duration_hours": interval["duration_hours"],
-                "mean_score": interval["mean_score"],
-                "max_score": interval["max_score"],
-                "driver": interval["driver"],
-                "level": interval["level"],
-                "confidence": interval["confidence"],
-                "lead_days": lead_days,
-                "temp_reference_start": None if temp_row.empty else temp_row["start_time"].iloc[0],
-                "temp_reference_end": None if temp_row.empty else temp_row["end_time"].iloc[0],
-                "temp_reference_stage": None if temp_row.empty else " / ".join(temp_row["温度阶段"].astype(str).unique()),
-            },
-        )
-
-    result = pd.DataFrame(rows)
-    if not intervals.empty:
-        related = intervals.apply(
-            lambda row: any(
-                pd.Timestamp(row["start_time"]) <= end and pd.Timestamp(row["end_time"]) >= start
-                for start, end in event_windows
-            ),
-            axis=1,
-        )
-        false_alarms = intervals[~related]
-        false_hours = float(false_alarms["duration_hours"].sum()) if not false_alarms.empty else 0.0
-    else:
-        false_alarms = pd.DataFrame()
-        false_hours = 0.0
-    result.attrs["false_alarm_intervals"] = int(len(false_alarms))
-    result.attrs["false_alarm_hours"] = round(false_hours, 2)
-    return result

+ 0 - 1
_tmp_cylinder_vibration/labels.py

@@ -236,7 +236,6 @@ def _extract_segments(frame: pd.DataFrame, interval_minutes: int) -> pd.DataFram
             strong_single = (
                 hit_points == 1
                 and max_score >= config.STRONG_SINGLE_SCORE
-                and float(max_row.get("contrast", 0.0)) >= config.STRONG_SINGLE_CONTRAST
                 and specificity is not None
                 and specificity >= config.STRONG_SINGLE_SPECIFICITY
             )

+ 18 - 10
_tmp_cylinder_vibration/local_labels.py

@@ -7,7 +7,7 @@ import pandas as pd
 from . import config, labels
 
 MIN_BASELINE_POINTS = 96
-WINDOW = "14D"
+WINDOW = "30D"
 GAP_HOURS = 2
 TRANSITION_HOURS = 1
 
@@ -76,18 +76,26 @@ def build_local_labels(frame: pd.DataFrame) -> tuple[pd.DataFrame, pd.DataFrame,
         local = score.rolling("24h", min_periods=4).median().shift(1)
         frame.loc[group.index, "contrast"] = (score - local).fillna(0.0).to_numpy()
 
-    normal_mask = ~labels.event_window_mask(frame["sample_time"])
     thresholds: dict[str, float] = {}
+    anomaly_thresholds: dict[str, float] = {}
+    severe_thresholds: dict[str, float] = {}
     for cylinder, group in frame.groupby("cylinder", sort=False):
-        normal = group.loc[labels._normal_mask_for(normal_mask, group.index), "score"].dropna()
-        quantile = float(normal.quantile(config.SCORE_QUANTILE)) if len(normal) else config.MIN_SCORE_Z
-        thresholds[cylinder] = max(config.MIN_SCORE_Z, quantile)
+        normal = group[(group["baseline_ok"]) & (~group["transition"])]["score"].dropna()
+        if normal.empty:
+            normal = group["score"].dropna()
+        median = float(normal.median()) if not normal.empty else 1.0
+        mad = float((normal - median).abs().median()) if not normal.empty else 0.0
+        mad = max(mad, 0.25)
+        candidate = max(3.5, median + 6.0 * mad)
+        anomaly = max(5.0, median + 10.0 * mad)
+        severe = max(7.0, median + 14.0 * mad)
+        thresholds[cylinder] = candidate
+        anomaly_thresholds[cylinder] = max(candidate, anomaly)
+        severe_thresholds[cylinder] = max(candidate, anomaly, severe)
     frame["threshold"] = frame["cylinder"].map(thresholds)
-    frame["hit"] = (
-        frame["baseline_ok"]
-        & (frame["score"] >= frame["threshold"])
-        & (frame["contrast"] >= config.MIN_CONTRAST)
-    )
+    frame["anomaly_threshold"] = frame["cylinder"].map(anomaly_thresholds)
+    frame["severe_threshold"] = frame["cylinder"].map(severe_thresholds)
+    frame["hit"] = frame["baseline_ok"] & (frame["score"] >= frame["threshold"])
 
     point_labels = labels._extract_segments(frame, 15)
     return frame, point_labels, thresholds

+ 295 - 0
_tmp_cylinder_vibration/make_report.py

@@ -0,0 +1,295 @@
+"""生成振动统一标签表、截图和 Word 报告。"""
+from __future__ import annotations
+
+import json
+import os
+import subprocess
+import tempfile
+from pathlib import Path
+
+import pandas as pd
+from docx import Document
+from docx.enum.section import WD_ORIENT
+from docx.enum.text import WD_ALIGN_PARAGRAPH
+from docx.oxml.ns import qn
+from docx.shared import Cm, Pt, RGBColor
+
+from . import config, unified_labels
+
+SHOT_SCRIPT = r'''
+const { chromium } = require('playwright');
+(async () => {
+  const html = process.argv[2];
+  const outDir = process.argv[3];
+  const count = Number(process.argv[4]);
+  const browser = await chromium.launch({ channel: 'chrome', headless: true });
+  const page = await browser.newPage({ viewport: { width: 1700, height: 1200 } });
+  await page.goto('file://' + html, { waitUntil: 'networkidle' });
+  await page.waitForTimeout(1500);
+  const cards = page.locator('.card');
+  for (let i = 0; i < count; i++) {
+    if (i > 0) await cards.nth(i).evaluate(el => el.click());
+    await page.waitForTimeout(180);
+    const name = 'shot_' + String(i + 1).padStart(4, '0') + '.png';
+    await page.locator('#chart').screenshot({ path: outDir + '/' + name });
+  }
+  await browser.close();
+})();
+'''
+
+
+def capture_screenshots(count: int) -> Path:
+    out_dir = config.OUTPUT_DIR / "report_assets"
+    out_dir.mkdir(parents=True, exist_ok=True)
+    script = Path(tempfile.gettempdir()) / "compressor_cylinder_capture.js"
+    script.write_text(SHOT_SCRIPT, encoding="utf-8")
+    env = dict(os.environ)
+    env["NODE_PATH"] = "/Users/zhouhao/Code/Python/Compressor/frontend/node_modules"
+    subprocess.run(
+        ["node", str(script), str(config.OUTPUT_DIR / "html" / "waveforms.html"), str(out_dir), str(count)],
+        check=True,
+        env=env,
+        timeout=900,
+    )
+    return out_dir
+
+
+def _set_black(style) -> None:
+    style.font.color.rgb = RGBColor(0, 0, 0)
+    rPr = style.element.get_or_add_rPr()
+    color = rPr.find(qn("w:color"))
+    if color is not None:
+        for attr in ("themeColor", "themeTint", "themeShade"):
+            color.attrib.pop(qn("w:" + attr), None)
+
+
+def add_table(doc: Document, rows: list[list], headers: list[str]) -> None:
+    table = doc.add_table(rows=1, cols=len(headers))
+    table.style = "Table Grid"
+    for index, header in enumerate(headers):
+        cell = table.rows[0].cells[index]
+        cell.text = str(header)
+        for paragraph in cell.paragraphs:
+            for run in paragraph.runs:
+                run.font.name = "宋体"
+                run._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
+                run.font.size = Pt(9.5)
+                run.bold = True
+                run.font.color.rgb = RGBColor(0, 0, 0)
+    for row in rows:
+        cells = table.add_row().cells
+        for index, value in enumerate(row):
+            cells[index].text = "" if value is None else str(value)
+            for paragraph in cells[index].paragraphs:
+                for run in paragraph.runs:
+                    run.font.name = "宋体"
+                    run._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
+                    run.font.size = Pt(9.5)
+                    run.font.color.rgb = RGBColor(0, 0, 0)
+
+
+def _style_attr_cell(cell, bold_label: bool = True) -> None:
+    for paragraph in cell.paragraphs:
+        for run in paragraph.runs:
+            run.font.name = "宋体"
+            run._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
+            run.font.size = Pt(9.5)
+            run.font.color.rgb = RGBColor(0, 0, 0)
+
+
+def add_attribute_table(doc: Document, attributes: list[tuple[str, object]], trigger: str) -> None:
+    """标签属性:一行 3 个属性;阶段触发依据单独占一整行。"""
+    table = doc.add_table(rows=0, cols=3)
+    table.style = "Table Grid"
+    for index in range(0, len(attributes), 3):
+        row = table.add_row()
+        for offset in range(3):
+            cell = row.cells[offset]
+            if index + offset < len(attributes):
+                field, value = attributes[index + offset]
+                paragraph = cell.paragraphs[0]
+                label = paragraph.add_run(f"{field}:")
+                label.bold = True
+                label.font.name = "宋体"
+                label._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
+                label.font.size = Pt(9.5)
+                label.font.color.rgb = RGBColor(0, 0, 0)
+                content = paragraph.add_run("" if value is None else str(value))
+                content.font.name = "宋体"
+                content._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
+                content.font.size = Pt(9.5)
+                content.font.color.rgb = RGBColor(0, 0, 0)
+    row = table.add_row()
+    merged = row.cells[0].merge(row.cells[1]).merge(row.cells[2])
+    paragraph = merged.paragraphs[0]
+    label = paragraph.add_run("阶段触发依据:")
+    label.bold = True
+    for run in paragraph.runs:
+        run.font.name = "宋体"
+        run._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
+        run.font.size = Pt(9.5)
+        run.font.color.rgb = RGBColor(0, 0, 0)
+    content = paragraph.add_run("" if trigger is None else str(trigger))
+    content.font.name = "宋体"
+    content._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
+    content.font.size = Pt(9.5)
+    content.font.color.rgb = RGBColor(0, 0, 0)
+    _style_attr_cell(merged)
+
+
+def build_docx(unified: pd.DataFrame, validation: pd.DataFrame, assets: Path) -> Path:
+    doc = Document()
+    for section in doc.sections:
+        section.orientation = WD_ORIENT.LANDSCAPE
+        section.page_width = Cm(29.7)
+        section.page_height = Cm(21.0)
+        section.left_margin = Cm(2.0)
+        section.right_margin = Cm(2.0)
+        section.top_margin = Cm(2.0)
+        section.bottom_margin = Cm(2.0)
+
+    normal = doc.styles["Normal"]
+    normal.font.name = "宋体"
+    normal._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
+    normal.font.size = Pt(12)
+    _set_black(normal)
+    normal.paragraph_format.line_spacing = 1.5
+    for name, size, before, after in (("Heading 1", 16, 18, 10), ("Heading 2", 14, 12, 6)):
+        style = doc.styles[name]
+        style.font.name = "黑体"
+        style._element.rPr.rFonts.set(qn("w:eastAsia"), "黑体")
+        style.font.size = Pt(size)
+        style.font.bold = True
+        _set_black(style)
+        style.paragraph_format.line_spacing = 1.0
+        style.paragraph_format.space_before = Pt(before)
+        style.paragraph_format.space_after = Pt(after)
+
+    for text in ("组态振动数据演化过程", "故障标签手册"):
+        title = doc.add_paragraph()
+        title.alignment = WD_ALIGN_PARAGRAPH.CENTER
+        run = title.add_run(text)
+        run.bold = True
+        run.font.name = "黑体"
+        run._element.rPr.rFonts.set(qn("w:eastAsia"), "黑体")
+        run.font.size = Pt(26)
+        run.font.color.rgb = RGBColor(0, 0, 0)
+    subtitle = doc.add_paragraph()
+    subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    run = subtitle.add_run(f"振动事件 · 小时标签(共 {len(unified)} 条)")
+    run.font.name = "宋体"
+    run._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
+    run.font.size = Pt(16)
+    run.font.color.rgb = RGBColor(0, 0, 0)
+    date_line = doc.add_paragraph("2026 年 11 月")
+    date_line.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    for run in date_line.runs:
+        run.font.color.rgb = RGBColor(0, 0, 0)
+
+    doc.add_heading("文档阅读说明", level=1)
+    doc.add_paragraph(
+        "本手册汇总 5 号机缸体振动的规则扫描标签,逐条给出标签属性、演化等级、局部基线、"
+        "阶段触发依据和标签前后曲线。标签是规则扫描结果,不等同于物理故障结论;温度只作参考,不参与振动标签生成。"
+    )
+    doc.add_paragraph(
+        "曲线截图来自 waveforms.html:只截取右侧 1×6 子图,不包含左侧标签列表。"
+        "图中每个 15 分钟槽位拼接该文件 0~60ms 首周期波形;橙色为异常分数,紫色虚线为实际峭度;"
+        "红色/黄色背景分别为当前缸/其他缸的标签段,淡蓝色为上下文内其他标签。"
+    )
+
+    doc.add_heading("一、标签数量总结", level=1)
+    counts = unified.groupby(["阶段"]).size().reset_index(name="数量")
+    add_table(doc, counts.values.tolist(), ["阶段", "数量"])
+    doc.add_paragraph(f"标签总数:{len(unified)};多特征确认:{int((unified['是否形成确认等级'] == '是').sum())}。")
+    doc.add_paragraph(
+        "严重异常、异常、轻微分别对应不同分数阈值;阈值只由局部基线和稳健尺度决定,"
+        "不针对具体维修事件单独调整。"
+    )
+
+    doc.add_heading("二、事件验证", level=1)
+    rows = []
+    for row in validation.itertuples(index=False):
+        rows.append([
+            row.event_id, row.cylinder, row.hit, row.confirmed, row.start_time,
+            row.end_time, row.duration_hours, row.direction, row.level, row.lead_days,
+        ])
+    add_table(
+        doc,
+        rows,
+        ["事件", "缸", "命中", "确认", "开始", "结束", "小时", "方向", "等级", "提前天"],
+    )
+    doc.add_paragraph(
+        "事件只用于验证和抽样观察,不参与标签生成;同一次事件可能对应多个标签,"
+        "也可能只有温度证据而没有振动标签。"
+    )
+
+    doc.add_heading("三、字段说明", level=1)
+    doc.add_paragraph(
+        "统一核心字段包括:标签ID、标签类型、机组、批次、周期编号、阶段、阶段部位、阶段点位、"
+        "开始小时、结束小时、持续自然小时数、有效运行小时数、基线值、正常波动带、阶段开始/结束/最大值、"
+        "相对基线变化、阶段最大连续异常有效小时数、高/高高报警小时数、方向、特征类型、置信度、"
+        "是否形成确认等级、阶段触发依据、事件关联、启动过渡比例、局部基线可用比例、数据来源、算法版本。"
+    )
+    doc.add_paragraph(
+        "振动专有字段:近期基线RMS、RMS正常波动带、阶段开始/结束/最大RMS、阶段最大峭度、"
+        "阶段最大峭度/基线、阶段最大残差峭度z、触发特征、多特征确认。"
+    )
+
+    doc.add_heading("四、标签详情", level=1)
+    ordered = unified.to_dict("records")
+    for index, row in enumerate(ordered, start=1):
+        doc.add_heading(
+            f"{row['阶段部位']}-{row['阶段']}-{row['方向']}-{str(row['开始小时'])[:16]}", level=2
+        )
+        attributes = [
+            ("标签ID", row.get("标签ID")),
+            ("阶段", row.get("阶段")),
+            ("阶段部位", row.get("阶段部位")),
+            ("开始小时", row.get("开始小时")),
+            ("结束小时", row.get("结束小时")),
+            ("持续自然小时数", row.get("持续自然小时数")),
+            ("方向", row.get("方向")),
+            ("近期基线RMS", row.get("近期基线RMS")),
+            ("正常波动带", row.get("RMS正常波动带")),
+            ("阶段最大RMS", row.get("阶段最大RMS")),
+            ("阶段最大峭度", row.get("阶段最大峭度")),
+            ("阶段最大峭度/基线", row.get("阶段最大峭度/基线")),
+            ("是否形成确认等级", row.get("是否形成确认等级")),
+        ]
+        add_attribute_table(doc, attributes, row.get("阶段触发依据"))
+        shot = assets / f"shot_{index:04d}.png"
+        if shot.exists():
+            doc.add_picture(str(shot), width=Cm(19.5))
+            caption = doc.add_paragraph("图:标签前后运行曲线(仅右侧 1×6 子图)")
+            caption.alignment = WD_ALIGN_PARAGRAPH.CENTER
+        if index % 8 == 0:
+            doc.add_page_break()
+
+    path = config.BASE_DIR.parent / "总结文档" / "缸体振动演化过程故障标签手册.docx"
+    path.parent.mkdir(parents=True, exist_ok=True)
+    doc.save(path)
+    return path
+
+
+def main() -> None:
+    hourly = pd.read_csv(config.OUTPUT_DIR / "vibration_hourly_labels.csv", parse_dates=["start_hour", "end_hour"])
+    review = pd.read_csv(config.OUTPUT_DIR / "label_review.csv", parse_dates=["start_hour", "end_hour"])
+    frame = pd.read_csv(config.OUTPUT_DIR / "vibration_features_full_local.csv", parse_dates=["sample_time"])
+    validation = pd.read_csv(config.OUTPUT_DIR / "event_validation_full.csv")
+
+    unified = unified_labels.build_vibration_unified(hourly, frame, review)
+    unified_path = config.OUTPUT_DIR / "cylinder_vibration_label_unified.csv"
+    unified.to_csv(unified_path, index=False, date_format="%Y-%m-%d %H:%M:%S")
+
+    combined = unified_labels.build_combined_preview(config.OUTPUT_DIR)
+    combined.to_csv(config.OUTPUT_DIR / "fault_label_unified_preview.csv", index=False, date_format="%Y-%m-%d %H:%M:%S")
+
+    assets = capture_screenshots(len(unified))
+    path = build_docx(unified, validation, assets)
+    print(f"written -> {unified_path}")
+    print(f"written -> {config.OUTPUT_DIR / 'fault_label_unified_preview.csv'}")
+    print(f"written -> {path}")
+
+
+if __name__ == "__main__":
+    main()

+ 0 - 60
_tmp_cylinder_vibration/out/adaptive_summary.json

@@ -1,60 +0,0 @@
-{
-  "false_alarm_intervals": 5,
-  "false_alarm_hours": 16.25,
-  "confirmed_false_alarm_intervals": 1,
-  "confirmed_false_alarm_hours": 2.75,
-  "intervals": 26,
-  "confirmed_intervals": 13,
-  "event_hits": 3,
-  "confirmed_event_hits": 2,
-  "validation": [
-    {
-      "event_id": "2026-06-12",
-      "event_date": "2026-06-12",
-      "cylinder": "二缸",
-      "description": "5#压缩机一级2#缸温度异常",
-      "hit": true,
-      "confirmed": true,
-      "start_time": "2026-06-10 23:15:02",
-      "end_time": "2026-06-11 01:00:04",
-      "duration_hours": 1.75,
-      "max_score": 6.08,
-      "direction": "高幅高冲击",
-      "level": "异常",
-      "confidence": "中",
-      "lead_days": 1.03
-    },
-    {
-      "event_id": "2026-06-21",
-      "event_date": "2026-06-21",
-      "cylinder": "五缸",
-      "description": "5#压缩机二级2#缸温度异常",
-      "hit": true,
-      "confirmed": false,
-      "start_time": "2026-06-18 09:45:03",
-      "end_time": "2026-06-18 10:45:04",
-      "duration_hours": 1.0,
-      "max_score": 5.643,
-      "direction": "低幅高冲击",
-      "level": "观察",
-      "confidence": "低",
-      "lead_days": 2.59
-    },
-    {
-      "event_id": "2026-07-11",
-      "event_date": "2026-07-11",
-      "cylinder": "二缸",
-      "description": "一级二缸排温高停机",
-      "hit": true,
-      "confirmed": true,
-      "start_time": "2026-07-07 19:45:01",
-      "end_time": "2026-07-07 22:30:03",
-      "duration_hours": 2.75,
-      "max_score": 6.11,
-      "direction": "高幅高冲击",
-      "level": "严重",
-      "confidence": "高",
-      "lead_days": 3.18
-    }
-  ]
-}

+ 0 - 254
_tmp_cylinder_vibration/out/baseline.json

@@ -1,254 +0,0 @@
-{
-  "一缸": {
-    "resid_rms_log": [
-      -0.13918935044385172,
-      0.03958135147265431
-    ],
-    "resid_kurt": [
-      -1.7938436513756546,
-      1.4298257378120682
-    ],
-    "resid_hf_ratio": [
-      -0.01303839451562716,
-      0.007261572758346589
-    ],
-    "resid_env_1x_log": [
-      0.0,
-      0.33072644415543895
-    ],
-    "resid_env_2x_log": [
-      0.0,
-      0.13679099655415605
-    ],
-    "rms_log": [
-      1.2936846347603388,
-      0.023719091459914392
-    ],
-    "kurt": [
-      4.801342375533159,
-      0.965964118993873
-    ],
-    "hf_ratio": [
-      0.9784075274347118,
-      0.006545857837448002
-    ],
-    "env_1x_log": [
-      -12.0,
-      5.711814956442226
-    ],
-    "env_2x_log": [
-      -12.0,
-      5.909465713251568
-    ]
-  },
-  "三缸": {
-    "resid_rms_log": [
-      -0.16368214758190558,
-      0.044218425846214666
-    ],
-    "resid_kurt": [
-      -1.277253454453224,
-      1.2509993948523956
-    ],
-    "resid_hf_ratio": [
-      0.012195856616890266,
-      0.003895508000727983
-    ],
-    "resid_env_1x_log": [
-      0.0,
-      0.14873829405618233
-    ],
-    "resid_env_2x_log": [
-      0.0,
-      0.09125657399891111
-    ],
-    "rms_log": [
-      1.2697668975528118,
-      0.04032199257423896
-    ],
-    "kurt": [
-      5.220147686571314,
-      0.6900432474399892
-    ],
-    "hf_ratio": [
-      0.995772734916549,
-      0.0016182911214438428
-    ],
-    "env_1x_log": [
-      -12.0,
-      5.975328064263494
-    ],
-    "env_2x_log": [
-      -12.0,
-      5.953612694783564
-    ]
-  },
-  "二缸": {
-    "resid_rms_log": [
-      0.298796774901948,
-      0.041779330338357484
-    ],
-    "resid_kurt": [
-      2.7602641218924724,
-      1.9409987206545902
-    ],
-    "resid_hf_ratio": [
-      0.002020593944268234,
-      0.0059197940828543145
-    ],
-    "resid_env_1x_log": [
-      0.0,
-      0.22298856934511946
-    ],
-    "resid_env_2x_log": [
-      0.0,
-      0.1807016192411493
-    ],
-    "rms_log": [
-      1.586254763552721,
-      0.035947160171187736
-    ],
-    "kurt": [
-      7.968180045349318,
-      1.791792214741794
-    ],
-    "hf_ratio": [
-      0.988804329250409,
-      0.00421793381162456
-    ],
-    "env_1x_log": [
-      -12.0,
-      6.019179217243303
-    ],
-    "env_2x_log": [
-      -12.0,
-      6.0852656515917465
-    ]
-  },
-  "五缸": {
-    "resid_rms_log": [
-      -0.36642523570193686,
-      0.03294688310241985
-    ],
-    "resid_kurt": [
-      0.29434436534312525,
-      1.1937405607131908
-    ],
-    "resid_hf_ratio": [
-      0.004477917056457947,
-      0.007959674154513796
-    ],
-    "resid_env_1x_log": [
-      0.0,
-      0.3211942389023753
-    ],
-    "resid_env_2x_log": [
-      0.0,
-      0.26204292550820946
-    ],
-    "rms_log": [
-      1.268783987140441,
-      0.020975024112513046
-    ],
-    "kurt": [
-      5.522774348113722,
-      0.9533539765679562
-    ],
-    "hf_ratio": [
-      0.9890533647472204,
-      0.003803988737586319
-    ],
-    "env_1x_log": [
-      -12.0,
-      5.848708886753168
-    ],
-    "env_2x_log": [
-      -12.0,
-      5.937460336367076
-    ]
-  },
-  "六缸": {
-    "resid_rms_log": [
-      0.27068537717847097,
-      0.05079729410264048
-    ],
-    "resid_kurt": [
-      0.012391954247327419,
-      1.3290798025884578
-    ],
-    "resid_hf_ratio": [
-      0.019021172095786132,
-      0.006194054389153637
-    ],
-    "resid_env_1x_log": [
-      0.0,
-      0.2532372916668744
-    ],
-    "resid_env_2x_log": [
-      0.0,
-      0.21552593946838594
-    ],
-    "rms_log": [
-      1.6932712856256076,
-      0.0352913690160724
-    ],
-    "kurt": [
-      5.313279181144191,
-      1.1591171459929885
-    ],
-    "hf_ratio": [
-      0.99805266238918,
-      0.0010413383029814645
-    ],
-    "env_1x_log": [
-      -12.0,
-      6.179680790713255
-    ],
-    "env_2x_log": [
-      -12.0,
-      6.22003856885301
-    ]
-  },
-  "四缸": {
-    "resid_rms_log": [
-      0.09430798271281327,
-      0.0463805833371065
-    ],
-    "resid_kurt": [
-      -0.6791621815303825,
-      1.0604392263691895
-    ],
-    "resid_hf_ratio": [
-      -0.023506132622568954,
-      0.013497382905942979
-    ],
-    "resid_env_1x_log": [
-      0.0,
-      0.14013607256246316
-    ],
-    "resid_env_2x_log": [
-      0.0,
-      0.10586491023131996
-    ],
-    "rms_log": [
-      1.5745320358652104,
-      0.03655254075095136
-    ],
-    "kurt": [
-      4.837669621921609,
-      0.6407267041812134
-    ],
-    "hf_ratio": [
-      0.969728178022689,
-      0.013263331680115977
-    ],
-    "env_1x_log": [
-      -12.0,
-      6.067726214320368
-    ],
-    "env_2x_log": [
-      -12.0,
-      6.122802192277199
-    ]
-  }
-}

文件差异内容过多而无法显示
+ 128 - 0
_tmp_cylinder_vibration/out/cylinder_vibration_label_unified.csv


+ 0 - 4
_tmp_cylinder_vibration/out/event_validation_adaptive.csv

@@ -1,4 +0,0 @@
-event_id,event_date,cylinder,description,hit,confirmed,start_time,end_time,duration_hours,max_score,direction,level,confidence,lead_days
-2026-06-12,2026-06-12,二缸,5#压缩机一级2#缸温度异常,True,True,2026-06-10 23:15:02,2026-06-11 01:00:04,1.75,6.08,高幅高冲击,异常,中,1.03
-2026-06-21,2026-06-21,五缸,5#压缩机二级2#缸温度异常,True,False,2026-06-18 09:45:03,2026-06-18 10:45:04,1.0,5.643,低幅高冲击,观察,低,2.59
-2026-07-11,2026-07-11,二缸,一级二缸排温高停机,True,True,2026-07-07 19:45:01,2026-07-07 22:30:03,2.75,6.11,高幅高冲击,严重,高,3.18

+ 3 - 3
_tmp_cylinder_vibration/out/event_validation_full.csv

@@ -1,4 +1,4 @@
 event_id,event_date,cylinder,description,hit,confirmed,start_time,end_time,duration_hours,max_score,direction,level,confidence,lead_days
-2026-06-12,2026-06-12,二缸,5#压缩机一级2#缸温度异常,True,True,2026-06-11 10:00:02,2026-06-11 14:30:04,4.5,20.0,高幅高冲击,严重,高,0.58
-2026-06-21,2026-06-21,五缸,5#压缩机二级2#缸温度异常,False,False,,,,,,,,
-2026-07-11,2026-07-11,二缸,一级二缸排温高停机,True,False,2026-07-07 20:30:00,2026-07-07 22:30:03,2.0,5.456,低幅高冲击,观察,低,3.15
+2026-06-12,2026-06-12,二缸,5#压缩机一级2#缸温度异常,True,True,2026-06-11 11:00:04,2026-06-11 15:45:00,4.75,20.0,高幅高冲击,严重异常,高,0.54
+2026-06-21,2026-06-21,五缸,5#压缩机二级2#缸温度异常,True,False,2026-06-18 09:45:03,2026-06-18 11:15:01,1.5,5.308,低幅高冲击,轻微,低,2.59
+2026-07-11,2026-07-11,二缸,一级二缸排温高停机,True,True,2026-07-05 01:15:02,2026-07-05 03:00:00,1.75,5.969,高幅高冲击,轻微,低,5.95

+ 0 - 4
_tmp_cylinder_vibration/out/event_validation_intervals.csv

@@ -1,4 +0,0 @@
-event_id,event_date,cylinder,description,vibration_hit,interval_start,interval_end,duration_hours,mean_score,max_score,driver,level,confidence,lead_days,temp_reference_start,temp_reference_end,temp_reference_stage
-2026-06-12,2026-06-12,二缸,5#压缩机一级2#缸温度异常,True,2026-06-10 23:15:02,2026-06-11 01:00:04,1.75,2.931,6.08,resid_kurt,异常,中,1.03,2026-06-11 12:00:00,2026-06-11 17:00:00,严重异常
-2026-06-21,2026-06-21,五缸,5#压缩机二级2#缸温度异常,True,2026-06-18 09:45:03,2026-06-18 11:15:01,1.5,3.381,5.643,resid_kurt,异常,中,2.59,2026-06-21 01:00:00,2026-06-21 15:00:00,异常
-2026-07-11,2026-07-11,二缸,一级二缸排温高停机,True,2026-07-07 20:30:00,2026-07-07 22:15:00,1.75,3.403,6.11,resid_kurt,异常,中,3.15,,,

+ 0 - 4
_tmp_cylinder_vibration/out/event_validation_refined.csv

@@ -1,4 +0,0 @@
-event_id,event_date,cylinder,description,vibration_hit,segment_start,segment_end,segment_max_score,segment_driver,segment_confidence,lead_days,peer_max_score,specificity,temp_reference_start,temp_reference_end,temp_reference_stage,temp_reference_confirmed
-2026-06-12,2026-06-12,二缸,5#压缩机一级2#缸温度异常,True,2026-06-11 00:00:38,2026-06-11 00:00:38,6.08,resid_kurt,中,1.0,4.337,1.4,2026-06-11 12:00:00,2026-06-11 17:00:00,严重异常,是
-2026-06-21,2026-06-21,五缸,5#压缩机二级2#缸温度异常,True,2026-06-18 09:45:03,2026-06-18 09:45:03,5.643,resid_kurt,中,2.59,3.682,1.53,2026-06-21 01:00:00,2026-06-21 15:00:00,异常,是
-2026-07-11,2026-07-11,二缸,一级二缸排温高停机,True,2026-07-07 21:15:04,2026-07-07 22:30:03,6.11,resid_kurt,中,3.11,4.399,1.39,,,,

文件差异内容过多而无法显示
+ 285 - 0
_tmp_cylinder_vibration/out/fault_label_unified_preview.csv


+ 31 - 31
_tmp_cylinder_vibration/out/full_summary.json

@@ -1,9 +1,9 @@
 {
   "files": 23901,
   "feature_rows": 23901,
-  "point_labels": 80,
-  "intervals": 73,
-  "confirmed_intervals": 22,
+  "point_labels": 144,
+  "intervals": 127,
+  "confirmed_intervals": 46,
   "event_validation": [
     {
       "event_id": "2026-06-12",
@@ -12,30 +12,30 @@
       "description": "5#压缩机一级2#缸温度异常",
       "hit": true,
       "confirmed": true,
-      "start_time": "2026-06-11 10:00:02",
-      "end_time": "2026-06-11 14:30:04",
-      "duration_hours": 4.5,
+      "start_time": "2026-06-11 11:00:04",
+      "end_time": "2026-06-11 15:45:00",
+      "duration_hours": 4.75,
       "max_score": 20.0,
       "direction": "高幅高冲击",
-      "level": "严重",
+      "level": "严重异常",
       "confidence": "高",
-      "lead_days": 0.58
+      "lead_days": 0.54
     },
     {
       "event_id": "2026-06-21",
       "event_date": "2026-06-21",
       "cylinder": "五缸",
       "description": "5#压缩机二级2#缸温度异常",
-      "hit": false,
+      "hit": true,
       "confirmed": false,
-      "start_time": "NaT",
-      "end_time": "NaT",
-      "duration_hours": NaN,
-      "max_score": NaN,
-      "direction": null,
-      "level": null,
-      "confidence": null,
-      "lead_days": NaN
+      "start_time": "2026-06-18 09:45:03",
+      "end_time": "2026-06-18 11:15:01",
+      "duration_hours": 1.5,
+      "max_score": 5.308,
+      "direction": "低幅高冲击",
+      "level": "轻微",
+      "confidence": "低",
+      "lead_days": 2.59
     },
     {
       "event_id": "2026-07-11",
@@ -43,23 +43,23 @@
       "cylinder": "二缸",
       "description": "一级二缸排温高停机",
       "hit": true,
-      "confirmed": false,
-      "start_time": "2026-07-07 20:30:00",
-      "end_time": "2026-07-07 22:30:03",
-      "duration_hours": 2.0,
-      "max_score": 5.456,
-      "direction": "低幅高冲击",
-      "level": "观察",
+      "confirmed": true,
+      "start_time": "2026-07-05 01:15:02",
+      "end_time": "2026-07-05 03:00:00",
+      "duration_hours": 1.75,
+      "max_score": 5.969,
+      "direction": "高幅高冲击",
+      "level": "轻微",
       "confidence": "低",
-      "lead_days": 3.15
+      "lead_days": 5.95
     }
   ],
   "thresholds": {
-    "一缸": 4.5,
-    "三缸": 4.5,
-    "二缸": 4.5,
-    "五缸": 5.975591392317734,
-    "六缸": 6.464915472317281,
-    "四缸": 6.930069206492413
+    "一缸": 4.613183023442144,
+    "三缸": 4.579688950805135,
+    "二缸": 4.271120708365807,
+    "五缸": 4.180099932341708,
+    "六缸": 4.003446683362904,
+    "四缸": 4.7610814514853415
   }
 }

文件差异内容过多而无法显示
+ 0 - 36
_tmp_cylinder_vibration/out/html/event_2026-06-12.html


文件差异内容过多而无法显示
+ 0 - 36
_tmp_cylinder_vibration/out/html/event_2026-06-21.html


文件差异内容过多而无法显示
+ 0 - 36
_tmp_cylinder_vibration/out/html/event_2026-07-11.html


文件差异内容过多而无法显示
+ 0 - 17
_tmp_cylinder_vibration/out/html/timeline.html


文件差异内容过多而无法显示
+ 11 - 5
_tmp_cylinder_vibration/out/html/waveforms.html


+ 0 - 66
_tmp_cylinder_vibration/out/interval_summary.json

@@ -1,66 +0,0 @@
-{
-  "point_labels": 29,
-  "intervals": 29,
-  "event_hits": 3,
-  "false_alarm_intervals": 5,
-  "false_alarm_hours": 8.0,
-  "event_validation": [
-    {
-      "event_id": "2026-06-12",
-      "event_date": "2026-06-12",
-      "cylinder": "二缸",
-      "description": "5#压缩机一级2#缸温度异常",
-      "vibration_hit": true,
-      "interval_start": "2026-06-10 23:15:02",
-      "interval_end": "2026-06-11 01:00:04",
-      "duration_hours": 1.75,
-      "mean_score": 2.931,
-      "max_score": 6.08,
-      "driver": "resid_kurt",
-      "level": "异常",
-      "confidence": "中",
-      "lead_days": 1.03,
-      "temp_reference_start": "2026-06-11 12:00:00",
-      "temp_reference_end": "2026-06-11 17:00:00",
-      "temp_reference_stage": "严重异常"
-    },
-    {
-      "event_id": "2026-06-21",
-      "event_date": "2026-06-21",
-      "cylinder": "五缸",
-      "description": "5#压缩机二级2#缸温度异常",
-      "vibration_hit": true,
-      "interval_start": "2026-06-18 09:45:03",
-      "interval_end": "2026-06-18 11:15:01",
-      "duration_hours": 1.5,
-      "mean_score": 3.381,
-      "max_score": 5.643,
-      "driver": "resid_kurt",
-      "level": "异常",
-      "confidence": "中",
-      "lead_days": 2.59,
-      "temp_reference_start": "2026-06-21 01:00:00",
-      "temp_reference_end": "2026-06-21 15:00:00",
-      "temp_reference_stage": "异常"
-    },
-    {
-      "event_id": "2026-07-11",
-      "event_date": "2026-07-11",
-      "cylinder": "二缸",
-      "description": "一级二缸排温高停机",
-      "vibration_hit": true,
-      "interval_start": "2026-07-07 20:30:00",
-      "interval_end": "2026-07-07 22:15:00",
-      "duration_hours": 1.75,
-      "mean_score": 3.403,
-      "max_score": 6.11,
-      "driver": "resid_kurt",
-      "level": "异常",
-      "confidence": "中",
-      "lead_days": 3.15,
-      "temp_reference_start": "NaT",
-      "temp_reference_end": "NaT",
-      "temp_reference_stage": null
-    }
-  ]
-}

+ 126 - 42
_tmp_cylinder_vibration/out/label_review.csv

@@ -1,44 +1,128 @@
 cylinder,start_hour,end_hour,hour_count,score_mean,score_max,rms_ratio_mean,rms_ratio_max,kurt_ratio_mean,kurt_ratio_max,kurt_max,active_rms,active_kurt,transition_ratio,baseline_ok_ratio,pre6h_score_slope,pre6h_kurt_slope,direction,level,confirmed,review_flags
-五缸,2026-04-16 08:00:00,2026-04-16 10:00:00,2,2.552,9.293,1.032,1.101,1.252,3.088,17.162,0.12,0.12,0.0,1.0,0.3009,,高幅高冲击,异常,True,持续证据弱
-三缸,2026-04-17 00:00:00,2026-04-17 02:00:00,2,1.734,6.652,0.99,1.064,1.149,2.685,13.766,0.0,0.12,0.0,1.0,-0.0285,0.0143,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-二缸,2026-04-17 09:00:00,2026-04-17 12:00:00,3,1.869,5.943,1.015,1.135,1.153,2.244,17.564,0.25,0.08,0.0,1.0,-0.1552,0.0022,高幅高冲击,异常,True,持续证据弱
-五缸,2026-04-17 10:00:00,2026-04-17 12:00:00,2,1.759,6.226,1.013,1.096,1.247,2.389,13.271,0.0,0.12,0.0,1.0,0.1333,-0.0186,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-一缸,2026-04-17 12:00:00,2026-04-17 14:00:00,2,2.642,5.706,0.99,1.114,1.331,2.237,12.083,0.12,0.12,0.0,1.0,0.0884,-0.0381,高幅高冲击,异常,True,持续证据弱
-五缸,2026-04-17 18:00:00,2026-04-17 23:00:00,5,2.339,14.617,1.0,1.055,1.285,4.137,23.214,0.0,0.15,0.0,1.0,0.0016,-0.004,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-三缸,2026-04-17 22:00:00,2026-04-18 00:00:00,2,1.921,5.587,1.043,1.133,1.267,2.316,12.023,0.12,0.12,0.0,1.0,-0.0186,-0.0128,高幅高冲击,异常,True,持续证据弱
-五缸,2026-04-23 16:00:00,2026-04-23 18:00:00,2,2.137,6.374,0.912,0.963,1.248,2.489,13.864,0.0,0.12,0.0,1.0,0.1878,0.0431,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-二缸,2026-04-23 19:00:00,2026-04-23 22:00:00,3,2.027,4.61,0.95,1.067,1.121,2.116,16.547,0.0,0.08,0.0,1.0,0.0724,-0.0247,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-二缸,2026-04-24 00:00:00,2026-04-24 05:00:00,5,1.657,5.156,0.973,1.16,1.112,2.057,16.051,0.05,0.15,0.0,1.0,-0.1524,0.0056,高幅高冲击,异常,True,持续证据弱
-五缸,2026-04-24 11:00:00,2026-04-24 14:00:00,3,1.526,6.027,0.996,1.055,1.04,2.222,12.392,0.0,0.08,0.0,1.0,0.038,0.0218,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
+五缸,2026-04-16 08:00:00,2026-04-16 10:00:00,2,2.552,9.293,1.032,1.101,1.252,3.088,17.162,0.12,0.12,0.0,1.0,0.3009,,高幅高冲击,严重异常,True,持续证据弱
+四缸,2026-04-16 11:00:00,2026-04-16 13:00:00,2,1.958,5.391,0.989,1.03,1.215,2.474,12.095,0.0,0.12,0.0,1.0,0.0134,-0.041,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱
+三缸,2026-04-17 00:00:00,2026-04-17 02:00:00,2,1.734,6.652,0.99,1.064,1.149,2.685,13.766,0.0,0.12,0.0,1.0,-0.0285,0.0143,低幅高冲击,异常,False,未确认 / 低幅高冲击 / 持续证据弱
+六缸,2026-04-17 07:00:00,2026-04-17 10:00:00,3,2.106,7.161,1.044,1.324,1.301,3.42,21.5,0.25,0.17,0.0,1.0,0.0793,0.0117,高幅高冲击,异常,True,持续证据弱
+二缸,2026-04-17 09:00:00,2026-04-17 12:00:00,3,1.869,5.943,1.015,1.135,1.153,2.244,17.564,0.25,0.08,0.0,1.0,-0.1552,0.0022,高幅高冲击,轻微,True,持续证据弱
+五缸,2026-04-17 10:00:00,2026-04-17 12:00:00,2,1.759,6.226,1.013,1.096,1.247,2.389,13.271,0.0,0.12,0.0,1.0,0.1333,-0.0186,低幅高冲击,异常,False,未确认 / 低幅高冲击 / 持续证据弱
+一缸,2026-04-17 12:00:00,2026-04-17 14:00:00,2,2.642,5.706,0.99,1.114,1.331,2.237,12.083,0.12,0.12,0.0,1.0,0.0884,-0.0381,高幅高冲击,轻微,True,持续证据弱
+五缸,2026-04-17 18:00:00,2026-04-17 23:00:00,5,2.339,14.617,1.0,1.055,1.285,4.137,23.214,0.0,0.15,0.0,1.0,0.0016,-0.004,低幅高冲击,严重异常,False,未确认 / 低幅高冲击 / 持续证据弱
+三缸,2026-04-17 22:00:00,2026-04-18 00:00:00,2,1.921,5.587,1.043,1.133,1.267,2.316,12.023,0.12,0.12,0.0,1.0,-0.0186,-0.0128,高幅高冲击,轻微,True,持续证据弱
+五缸,2026-04-18 04:00:00,2026-04-18 07:00:00,3,1.873,4.889,1.006,1.08,1.083,1.658,9.241,0.0,0.17,0.0,1.0,-0.2207,-0.0429,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱
+一缸,2026-04-23 08:00:00,2026-04-23 10:00:00,2,2.69,5.768,0.867,0.914,1.235,2.27,12.325,0.0,0.17,0.83,1.0,0.0,0.0,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱 / 启动/过渡
+五缸,2026-04-23 16:00:00,2026-04-23 18:00:00,2,2.137,6.374,0.912,0.963,1.248,2.489,13.864,0.0,0.12,0.0,1.0,0.1878,0.0431,低幅高冲击,异常,False,未确认 / 低幅高冲击 / 持续证据弱
+一缸,2026-04-23 18:00:00,2026-04-23 21:00:00,3,2.54,4.846,0.911,0.998,1.09,1.355,7.41,0.0,0.0,0.0,1.0,0.0162,0.014,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+二缸,2026-04-23 19:00:00,2026-04-23 22:00:00,3,2.027,4.61,0.95,1.067,1.121,2.116,16.547,0.0,0.08,0.0,1.0,0.0724,-0.0247,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱
+二缸,2026-04-24 00:00:00,2026-04-24 05:00:00,5,1.657,5.156,0.973,1.16,1.112,2.057,16.051,0.05,0.15,0.0,1.0,-0.1524,0.0056,高幅高冲击,轻微,True,持续证据弱
+五缸,2026-04-24 11:00:00,2026-04-24 14:00:00,3,1.526,6.027,0.996,1.055,1.04,2.222,12.392,0.0,0.08,0.0,1.0,0.038,0.0218,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱
 四缸,2026-04-24 20:00:00,2026-04-24 22:00:00,2,2.693,7.188,0.998,1.27,1.358,2.509,12.226,0.12,0.38,0.0,1.0,-0.0115,0.0165,高幅高冲击,异常,True,
-五缸,2026-04-25 05:00:00,2026-04-25 07:00:00,2,1.538,6.858,0.979,1.054,1.232,2.566,14.312,0.0,0.12,0.0,1.0,-0.0882,-0.0753,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-五缸,2026-05-03 22:00:00,2026-05-04 01:00:00,3,2.076,11.268,1.029,1.082,1.24,3.345,18.085,0.0,0.08,0.0,1.0,-0.1218,-0.0374,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-五缸,2026-05-05 03:00:00,2026-05-05 05:00:00,2,2.623,11.567,0.999,1.068,1.24,3.27,17.826,0.0,0.12,0.0,1.0,0.028,0.0151,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-五缸,2026-05-05 08:00:00,2026-05-05 10:00:00,2,2.606,13.223,1.013,1.087,1.326,3.677,20.063,0.0,0.12,0.0,1.0,-0.2648,-0.0341,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-六缸,2026-05-06 07:00:00,2026-05-06 10:00:00,3,2.037,8.88,0.987,1.237,1.289,3.1,15.271,0.08,0.25,0.0,1.0,0.0451,-0.0233,高幅高冲击,异常,True,持续证据弱
-五缸,2026-05-06 15:00:00,2026-05-06 18:00:00,3,2.255,10.634,1.026,1.116,1.27,2.902,15.855,0.17,0.08,0.0,1.0,0.1053,-0.0205,高幅高冲击,严重,True,持续证据弱
-三缸,2026-05-07 05:00:00,2026-05-07 08:00:00,3,1.973,5.85,1.04,1.125,1.098,2.238,11.619,0.17,0.08,0.0,1.0,0.1491,-0.0115,高幅高冲击,异常,True,持续证据弱
-五缸,2026-05-07 22:00:00,2026-05-08 00:00:00,2,2.837,14.835,1.008,1.094,1.395,3.901,21.215,0.0,0.12,0.0,1.0,-0.0862,-0.0063,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-五缸,2026-05-08 22:00:00,2026-05-09 03:00:00,5,1.965,13.82,1.011,1.106,1.185,3.628,19.541,0.05,0.1,0.0,1.0,-0.0375,0.0047,高幅高冲击,严重,True,持续证据弱
-五缸,2026-05-09 13:00:00,2026-05-09 16:00:00,3,2.284,8.471,0.982,1.088,1.207,2.623,14.098,0.0,0.09,0.0,1.0,0.0145,-0.0019,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-六缸,2026-06-03 07:00:00,2026-06-03 09:00:00,2,3.477,7.919,1.059,1.124,1.593,2.593,13.625,0.25,0.38,0.0,1.0,0.5151,,高幅高冲击,严重,True,
-六缸,2026-06-04 02:00:00,2026-06-04 05:00:00,3,1.701,6.892,1.048,1.275,1.188,2.517,14.128,0.17,0.17,0.0,1.0,0.0275,0.0254,高幅高冲击,异常,True,持续证据弱
-六缸,2026-06-10 10:00:00,2026-06-10 15:00:00,5,5.797,20.0,0.951,1.155,1.284,2.438,13.67,0.05,0.15,0.05,1.0,-8.4866,-2.582,高幅高冲击,严重,True,持续证据弱
-四缸,2026-06-10 10:00:00,2026-06-10 14:00:00,4,6.987,20.0,0.975,1.081,1.032,1.512,6.942,0.0,0.06,0.06,1.0,-6.1311,-0.0408,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-五缸,2026-06-11 08:00:00,2026-06-11 13:00:00,5,4.112,16.196,1.012,1.13,0.934,1.538,8.611,0.1,0.05,0.0,1.0,-0.0619,0.0334,高幅高冲击,严重,True,持续证据弱
-二缸,2026-06-11 10:00:00,2026-06-11 15:00:00,5,9.988,20.0,1.039,1.223,1.104,1.529,12.095,0.2,0.05,0.0,1.0,0.0111,-0.0315,高幅高冲击,严重,True,持续证据弱
-二缸,2026-06-18 18:00:00,2026-06-18 21:00:00,3,2.581,7.785,1.029,1.13,1.303,2.637,21.391,0.17,0.42,0.0,1.0,0.1198,0.0723,高幅高冲击,严重,True,
-六缸,2026-06-30 10:00:00,2026-06-30 12:00:00,2,2.307,8.41,1.037,1.177,1.442,3.2,16.802,0.25,0.25,0.0,1.0,-0.088,0.014,高幅高冲击,异常,True,持续证据弱
-二缸,2026-06-30 20:00:00,2026-06-30 22:00:00,2,2.462,6.338,1.002,1.063,1.076,1.575,12.835,0.0,0.11,0.0,1.0,0.0337,-0.0224,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-三缸,2026-07-07 02:00:00,2026-07-07 04:00:00,2,2.734,7.494,1.026,1.152,1.371,2.619,12.383,0.12,0.25,0.0,1.0,0.1557,0.0284,高幅高冲击,异常,True,持续证据弱
-二缸,2026-07-07 20:00:00,2026-07-07 23:00:00,3,2.27,5.456,0.998,1.095,1.265,2.32,18.913,0.0,0.17,0.0,1.0,-0.0726,-0.0214,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-三缸,2026-07-07 23:00:00,2026-07-08 01:00:00,2,1.86,4.567,1.026,1.069,1.164,1.885,8.921,0.0,0.12,0.0,1.0,0.207,0.0074,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-三缸,2026-07-08 06:00:00,2026-07-08 08:00:00,2,3.08,7.49,0.978,1.063,1.433,3.086,14.617,0.0,0.25,0.0,1.0,-0.117,-0.0151,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-三缸,2026-07-10 03:00:00,2026-07-10 06:00:00,3,2.161,8.756,0.99,1.073,1.221,3.081,14.68,0.0,0.08,0.0,1.0,0.0324,0.0137,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-六缸,2026-07-22 22:00:00,2026-07-23 05:00:00,7,5.148,9.164,1.051,1.182,1.097,1.895,10.369,0.14,0.11,0.0,1.0,-0.1291,-0.022,高幅高冲击,严重,True,持续证据弱
-三缸,2026-07-23 02:00:00,2026-07-23 04:00:00,2,3.27,5.011,0.968,1.069,1.041,1.566,7.838,0.0,0.12,0.0,1.0,-0.0636,-0.0002,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-五缸,2026-07-23 12:00:00,2026-07-23 14:00:00,2,3.066,6.306,1.011,1.081,1.423,2.305,12.103,0.0,0.25,0.0,1.0,-0.3933,-0.0368,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-六缸,2026-07-24 19:00:00,2026-07-24 21:00:00,2,2.037,7.345,1.052,1.177,1.321,2.833,15.985,0.25,0.12,0.0,1.0,0.1013,0.0066,高幅高冲击,异常,True,持续证据弱
-三缸,2026-07-27 21:00:00,2026-07-27 23:00:00,2,2.182,4.948,1.005,1.103,1.097,2.338,12.771,0.12,0.12,0.0,1.0,-0.12,-0.0321,高幅高冲击,异常,True,持续证据弱
-三缸,2026-07-28 01:00:00,2026-07-28 04:00:00,3,2.973,5.052,1.023,1.087,0.941,1.773,9.636,0.0,0.08,0.0,1.0,0.3001,0.0369,低幅高冲击,观察,False,未确认 / 低幅高冲击 / 持续证据弱
-六缸,2026-07-30 19:00:00,2026-07-30 21:00:00,2,2.378,8.616,1.027,1.159,1.315,2.923,16.828,0.25,0.25,0.0,1.0,0.0064,0.0258,高幅高冲击,异常,True,持续证据弱
+五缸,2026-04-25 05:00:00,2026-04-25 07:00:00,2,1.538,6.858,0.979,1.054,1.232,2.566,14.312,0.0,0.12,0.0,1.0,-0.0882,-0.0753,低幅高冲击,异常,False,未确认 / 低幅高冲击 / 持续证据弱
+五缸,2026-04-25 16:00:00,2026-04-25 18:00:00,2,3.018,8.983,1.006,1.105,1.406,2.818,15.698,0.14,0.29,0.71,1.0,0.0,0.0,低幅高冲击,严重异常,False,未确认 / 持续证据弱 / 启动/过渡
+五缸,2026-05-02 00:00:00,2026-05-02 02:00:00,2,1.633,4.53,0.997,1.038,1.117,1.832,10.109,0.0,0.12,0.0,1.0,-0.0877,-0.0191,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱
+五缸,2026-05-02 11:00:00,2026-05-02 14:00:00,3,1.496,4.742,0.987,1.027,1.072,1.999,10.998,0.0,0.08,0.0,1.0,0.0057,-0.0106,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱
+五缸,2026-05-03 22:00:00,2026-05-04 01:00:00,3,1.985,11.179,1.017,1.07,1.223,3.301,18.085,0.0,0.08,0.0,1.0,-0.1318,-0.0366,低幅高冲击,严重异常,False,未确认 / 低幅高冲击 / 持续证据弱
+五缸,2026-05-05 03:00:00,2026-05-05 05:00:00,2,2.469,11.359,0.989,1.057,1.231,3.246,17.826,0.0,0.12,0.0,1.0,-0.0079,0.0152,低幅高冲击,严重异常,False,未确认 / 低幅高冲击 / 持续证据弱
+五缸,2026-05-05 08:00:00,2026-05-05 10:00:00,2,2.493,12.912,1.004,1.077,1.316,3.649,20.063,0.0,0.12,0.0,1.0,-0.2371,-0.0339,低幅高冲击,严重异常,False,未确认 / 低幅高冲击 / 持续证据弱
+五缸,2026-05-05 14:00:00,2026-05-05 16:00:00,2,1.999,5.755,0.958,1.023,1.113,2.057,11.298,0.0,0.12,0.0,1.0,-0.2907,-0.0676,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱
+六缸,2026-05-06 07:00:00,2026-05-06 10:00:00,3,1.712,7.02,0.998,1.251,1.21,2.911,15.271,0.08,0.17,0.0,1.0,0.0393,-0.0216,高幅高冲击,异常,True,持续证据弱
+五缸,2026-05-06 15:00:00,2026-05-06 18:00:00,3,2.361,10.445,1.017,1.106,1.261,2.883,15.855,0.08,0.08,0.0,1.0,0.1147,-0.0203,高幅高冲击,严重异常,True,持续证据弱
+三缸,2026-05-07 05:00:00,2026-05-07 08:00:00,3,1.965,5.836,1.045,1.13,1.098,2.237,11.619,0.17,0.08,0.0,1.0,0.1478,-0.0116,高幅高冲击,轻微,True,持续证据弱
+五缸,2026-05-07 22:00:00,2026-05-08 00:00:00,2,2.728,14.555,1.001,1.087,1.384,3.873,21.215,0.0,0.12,0.0,1.0,-0.1002,-0.0065,低幅高冲击,严重异常,False,未确认 / 低幅高冲击 / 持续证据弱
+五缸,2026-05-08 22:00:00,2026-05-09 03:00:00,5,1.843,13.209,1.003,1.097,1.168,3.577,19.541,0.0,0.1,0.0,1.0,-0.0503,0.0043,低幅高冲击,严重异常,False,未确认 / 低幅高冲击 / 持续证据弱
+五缸,2026-05-09 13:00:00,2026-05-09 16:00:00,3,2.277,8.012,0.975,1.08,1.189,2.583,14.098,0.0,0.09,0.0,1.0,0.0452,-0.002,低幅高冲击,严重异常,False,未确认 / 低幅高冲击 / 持续证据弱
+六缸,2026-05-31 15:00:00,2026-05-31 18:00:00,3,2.075,4.724,1.0,1.188,1.431,2.072,9.885,0.18,0.27,0.45,1.0,0.0,0.0,高幅高冲击,轻微,True,持续证据弱 / 启动/过渡
+一缸,2026-05-31 17:00:00,2026-05-31 19:00:00,2,4.291,6.244,0.848,0.924,1.04,1.261,6.332,0.0,0.0,0.0,1.0,1.4269,-0.1495,未确认,异常,False,未确认 / 分数驱动不明 / 持续证据弱
+二缸,2026-05-31 18:00:00,2026-05-31 19:00:00,1,3.879,4.41,1.206,1.283,0.986,1.192,9.185,1.0,0.0,0.0,1.0,0.2651,-0.1092,高幅,轻微,False,未确认
+五缸,2026-06-02 09:00:00,2026-06-02 10:00:00,1,1.93,3.176,0.983,1.014,1.11,1.35,7.313,0.0,0.0,1.0,1.0,0.0,0.0,低幅高冲击,严重异常,False,未确认 / 持续证据弱 / 启动/过渡
+六缸,2026-06-02 09:00:00,2026-06-02 11:00:00,2,2.476,6.25,0.975,1.089,1.524,2.371,11.336,0.0,0.38,0.5,1.0,0.0,0.0,低幅高冲击,异常,False,未确认 / 低幅高冲击 / 启动/过渡
+六缸,2026-06-03 06:00:00,2026-06-03 09:00:00,3,3.359,7.8,1.011,1.089,1.519,2.798,13.625,0.0,0.33,0.0,1.0,-0.0265,0.0385,低幅高冲击,严重异常,False,未确认 / 低幅高冲击
+六缸,2026-06-04 02:00:00,2026-06-04 05:00:00,3,1.799,7.305,1.03,1.256,1.337,2.832,14.128,0.08,0.17,0.0,1.0,0.055,0.0318,高幅高冲击,异常,True,持续证据弱
+六缸,2026-06-04 21:00:00,2026-06-05 00:00:00,3,2.168,5.155,0.969,1.121,1.347,2.4,12.133,0.08,0.25,0.0,1.0,0.3472,0.0889,高幅高冲击,轻微,True,持续证据弱
+一缸,2026-06-10 10:00:00,2026-06-10 15:00:00,5,6.355,16.326,0.975,1.092,0.915,1.365,7.4,0.0,0.0,0.05,1.0,-1.6586,0.0718,未确认,严重异常,False,未确认 / 分数驱动不明 / 持续证据弱
+三缸,2026-06-10 10:00:00,2026-06-10 13:00:00,3,8.438,20.0,0.973,1.051,1.063,1.293,7.049,0.0,0.0,0.08,1.0,-5.1383,0.2509,未确认,严重异常,False,未确认 / 分数驱动不明 / 持续证据弱
+二缸,2026-06-10 10:00:00,2026-06-10 12:00:00,2,7.219,16.035,1.056,1.174,1.024,1.18,9.293,0.12,0.0,0.12,1.0,0.9717,-0.4634,高幅,严重异常,False,未确认 / 持续证据弱
+五缸,2026-06-10 10:00:00,2026-06-10 13:00:00,3,5.927,13.67,1.027,1.105,1.007,1.381,7.77,0.08,0.0,0.08,1.0,-5.0203,-0.3954,高幅,严重异常,False,未确认 / 持续证据弱
+六缸,2026-06-10 10:00:00,2026-06-10 15:00:00,5,5.93,20.0,0.951,1.155,1.288,2.445,13.67,0.05,0.15,0.05,1.0,-9.6276,-2.867,高幅高冲击,严重异常,True,持续证据弱
+四缸,2026-06-10 10:00:00,2026-06-10 14:00:00,4,7.136,20.0,0.978,1.085,1.033,1.512,6.942,0.0,0.06,0.06,1.0,-5.3342,-0.0027,低幅高冲击,严重异常,False,未确认 / 低幅高冲击 / 持续证据弱
+一缸,2026-06-10 19:00:00,2026-06-11 02:00:00,7,2.824,5.91,1.026,1.114,0.947,1.148,6.174,0.07,0.0,0.0,1.0,-0.3953,0.006,高幅,轻微,False,未确认 / 持续证据弱
+四缸,2026-06-10 22:00:00,2026-06-11 05:00:00,7,3.494,9.303,0.9,0.997,0.991,1.351,6.173,0.0,0.0,0.0,1.0,-0.3357,0.0048,未确认,严重异常,False,未确认 / 分数驱动不明 / 持续证据弱
+二缸,2026-06-10 23:00:00,2026-06-11 01:00:00,2,3.655,6.455,0.992,1.153,1.204,2.439,19.214,0.12,0.25,0.0,1.0,-0.0169,0.0186,高幅高冲击,异常,True,持续证据弱
+二缸,2026-06-11 05:00:00,2026-06-11 07:00:00,2,3.522,6.557,0.955,1.105,1.205,1.573,12.429,0.12,0.25,0.0,1.0,-0.1264,0.0018,高幅高冲击,异常,True,持续证据弱
+五缸,2026-06-11 09:00:00,2026-06-11 13:00:00,4,4.557,16.229,1.012,1.128,0.945,1.547,8.611,0.12,0.06,0.0,1.0,-0.015,0.0055,高幅高冲击,严重异常,True,持续证据弱
+六缸,2026-06-11 09:00:00,2026-06-11 11:00:00,2,3.595,9.822,0.965,1.046,1.006,1.229,6.832,0.0,0.0,0.0,1.0,-0.0626,0.0239,高幅,严重异常,False,未确认 / 分数驱动不明 / 持续证据弱
+一缸,2026-06-11 10:00:00,2026-06-11 15:00:00,5,8.772,20.0,1.044,1.116,0.848,1.008,5.243,0.05,0.0,0.0,1.0,0.2698,0.0102,高幅,严重异常,False,未确认 / 持续证据弱
+二缸,2026-06-11 11:00:00,2026-06-11 16:00:00,5,9.261,20.0,1.021,1.222,1.059,1.528,12.095,0.15,0.05,0.0,1.0,-0.0985,-0.0248,高幅高冲击,严重异常,True,持续证据弱
+四缸,2026-06-11 11:00:00,2026-06-11 15:00:00,4,10.847,20.0,0.94,1.02,0.982,1.209,5.516,0.0,0.0,0.0,1.0,-0.3232,-0.0142,未确认,严重异常,False,未确认 / 分数驱动不明 / 持续证据弱
+五缸,2026-06-18 10:00:00,2026-06-18 12:00:00,2,3.358,4.948,1.063,1.125,1.027,1.678,9.364,0.12,0.12,0.5,1.0,0.0,0.0,低幅高冲击,轻微,False,未确认 / 持续证据弱 / 启动/过渡
+六缸,2026-06-18 10:00:00,2026-06-18 13:00:00,3,2.717,5.445,0.995,1.178,1.469,2.405,13.339,0.17,0.42,0.33,1.0,0.0,0.0,高幅高冲击,轻微,True,启动/过渡
+三缸,2026-06-18 12:00:00,2026-06-18 19:00:00,7,16.188,20.0,0.999,1.119,0.909,1.153,6.226,0.04,0.0,0.0,1.0,-2.6874,-1.4026,高幅,严重异常,False,未确认 / 持续证据弱
+四缸,2026-06-18 13:00:00,2026-06-18 16:00:00,3,3.572,8.728,1.078,1.223,1.047,1.267,5.769,0.33,0.0,0.0,1.0,-0.5658,-0.052,高幅,异常,False,未确认
+四缸,2026-06-18 23:00:00,2026-06-19 03:00:00,4,2.16,4.989,1.049,1.161,1.092,1.393,6.375,0.38,0.0,0.0,1.0,0.0042,0.0294,高幅,轻微,False,未确认
+一缸,2026-06-19 10:00:00,2026-06-19 13:00:00,3,3.651,7.531,1.001,1.057,0.826,0.926,4.62,0.0,0.0,0.0,1.0,-0.1156,0.0032,未确认,异常,False,未确认 / 分数驱动不明 / 持续证据弱
+四缸,2026-06-19 13:00:00,2026-06-19 16:00:00,3,2.299,6.461,1.071,1.181,1.071,1.35,6.225,0.25,0.0,0.0,1.0,0.165,-0.0135,高幅,轻微,False,未确认 / 持续证据弱
+一缸,2026-06-19 17:00:00,2026-06-19 19:00:00,2,3.065,4.897,1.017,1.064,0.862,0.977,4.809,0.0,0.0,0.0,1.0,-0.3601,0.0115,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+三缸,2026-06-19 17:00:00,2026-06-19 19:00:00,2,3.775,6.618,1.057,1.126,0.956,1.145,6.045,0.5,0.0,0.0,1.0,-0.3593,0.0125,高幅,轻微,False,未确认
+三缸,2026-06-19 21:00:00,2026-06-20 02:00:00,5,3.777,8.17,1.017,1.071,0.911,1.133,5.953,0.0,0.0,0.0,1.0,-0.1131,-0.0174,未确认,异常,False,未确认 / 分数驱动不明 / 持续证据弱
+四缸,2026-06-19 23:00:00,2026-06-20 03:00:00,4,12.366,20.0,1.058,1.128,1.053,1.28,5.902,0.19,0.0,0.0,1.0,0.2059,0.0234,高幅,严重异常,False,未确认 / 持续证据弱
+一缸,2026-06-20 00:00:00,2026-06-20 02:00:00,2,4.005,8.343,1.04,1.074,0.999,1.28,6.254,0.0,0.0,0.0,1.0,-0.1895,0.019,未确认,异常,False,未确认 / 分数驱动不明 / 持续证据弱
+一缸,2026-06-20 12:00:00,2026-06-20 15:00:00,3,2.555,5.111,1.065,1.178,0.872,1.04,5.008,0.25,0.0,0.0,1.0,-0.0671,-0.0227,高幅,轻微,False,未确认 / 持续证据弱
+三缸,2026-06-20 19:00:00,2026-06-20 21:00:00,2,2.901,7.746,1.04,1.073,0.949,1.045,5.467,0.0,0.0,0.0,1.0,-0.2613,0.0116,未确认,异常,False,未确认 / 分数驱动不明 / 持续证据弱
+二缸,2026-06-20 23:00:00,2026-06-21 01:00:00,2,2.667,6.786,1.102,1.236,1.436,2.548,20.395,0.5,0.38,0.0,1.0,0.0829,0.0286,高幅高冲击,异常,True,
+一缸,2026-06-21 02:00:00,2026-06-21 04:00:00,2,2.244,4.788,1.044,1.086,0.944,1.243,5.881,0.0,0.0,0.0,1.0,0.2731,0.011,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+三缸,2026-06-21 03:00:00,2026-06-21 05:00:00,2,2.611,5.928,0.952,1.003,1.015,1.145,5.968,0.0,0.0,0.0,1.0,-0.0812,0.0384,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+三缸,2026-06-21 11:00:00,2026-06-21 13:00:00,2,2.559,6.278,0.945,1.005,1.106,1.434,7.48,0.0,0.0,0.0,1.0,0.1469,-0.0065,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+四缸,2026-06-28 17:00:00,2026-06-29 12:00:00,19,3.989,8.514,1.13,1.289,1.294,1.972,9.265,0.74,0.14,0.04,1.0,-3.8949,0.2245,高幅高冲击,异常,True,
+三缸,2026-06-28 18:00:00,2026-06-28 23:00:00,5,2.201,4.752,1.157,1.259,1.081,1.308,6.844,0.95,0.0,0.0,1.0,-0.6165,-0.075,高幅,轻微,False,未确认
+二缸,2026-06-29 01:00:00,2026-06-29 04:00:00,3,1.973,5.388,1.038,1.183,1.147,2.252,18.106,0.42,0.17,0.0,1.0,-0.049,-0.0543,高幅高冲击,轻微,True,
+三缸,2026-06-29 02:00:00,2026-06-29 06:00:00,4,3.158,5.391,1.107,1.159,1.055,1.361,7.129,0.62,0.0,0.0,1.0,-0.0989,-0.0206,高幅,轻微,False,未确认
+一缸,2026-06-29 03:00:00,2026-06-29 06:00:00,3,2.397,5.014,0.965,1.002,0.859,1.038,4.847,0.0,0.0,0.0,1.0,0.1993,-0.0006,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+三缸,2026-06-29 09:00:00,2026-06-29 18:00:00,9,2.763,7.583,1.125,1.237,1.019,1.273,6.672,0.69,0.0,0.0,1.0,-0.3849,-0.0225,高幅,异常,False,未确认
+三缸,2026-06-30 00:00:00,2026-06-30 07:00:00,7,2.263,4.732,1.086,1.182,0.981,1.257,6.586,0.36,0.0,0.0,1.0,-0.0314,0.0083,高幅,轻微,False,未确认
+四缸,2026-06-30 07:00:00,2026-06-30 10:00:00,3,2.355,6.348,1.127,1.233,1.399,2.592,12.465,0.67,0.25,0.0,1.0,-0.2201,-0.0499,高幅高冲击,轻微,True,
+三缸,2026-06-30 09:00:00,2026-06-30 14:00:00,5,1.926,4.733,1.138,1.263,0.943,1.129,5.912,0.8,0.0,0.0,1.0,-0.0732,0.003,高幅,轻微,False,未确认
+六缸,2026-06-30 10:00:00,2026-06-30 12:00:00,2,2.288,8.099,1.024,1.162,1.408,3.127,16.802,0.25,0.12,0.0,1.0,0.0009,0.0139,高幅高冲击,严重异常,True,持续证据弱
+四缸,2026-06-30 20:00:00,2026-07-01 00:00:00,4,3.82,7.142,1.159,1.267,1.092,1.284,6.207,0.82,0.0,0.0,1.0,-0.2181,0.0105,高幅,异常,False,未确认
+三缸,2026-07-01 02:00:00,2026-07-01 05:00:00,3,2.536,4.844,1.136,1.187,0.859,1.066,5.547,0.83,0.0,0.0,1.0,0.0646,-0.0365,高幅,轻微,False,未确认
+四缸,2026-07-01 07:00:00,2026-07-01 15:00:00,8,3.023,4.999,1.198,1.452,1.149,1.62,7.876,0.94,0.09,0.0,1.0,-0.3716,-0.0024,高幅高冲击,轻微,True,
+四缸,2026-07-01 20:00:00,2026-07-02 10:00:00,14,2.455,6.5,1.145,1.238,1.208,1.728,8.454,0.85,0.15,0.07,1.0,-0.4437,0.0204,高幅高冲击,轻微,True,
+五缸,2026-07-04 03:00:00,2026-07-04 05:00:00,2,2.249,5.452,1.017,1.049,0.967,1.169,5.977,0.0,0.0,0.0,1.0,0.0375,0.0183,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+六缸,2026-07-04 12:00:00,2026-07-04 14:00:00,2,2.69,4.817,0.839,0.87,0.889,0.951,5.011,0.0,0.0,0.0,1.0,0.064,-0.0014,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+二缸,2026-07-05 01:00:00,2026-07-05 03:00:00,2,2.168,5.969,0.983,1.132,1.103,2.297,18.475,0.12,0.12,0.0,1.0,0.1256,-0.023,高幅高冲击,轻微,True,持续证据弱
+六缸,2026-07-05 03:00:00,2026-07-05 05:00:00,2,1.942,5.263,1.006,1.144,1.219,2.502,13.063,0.12,0.12,0.0,1.0,-0.0412,0.0063,高幅高冲击,轻微,True,持续证据弱
+一缸,2026-07-06 03:00:00,2026-07-06 05:00:00,2,1.702,5.001,1.055,1.122,0.949,1.062,4.507,0.12,0.0,0.0,1.0,0.0897,-0.0102,高幅,轻微,False,未确认 / 持续证据弱
+五缸,2026-07-06 04:00:00,2026-07-06 06:00:00,2,1.864,4.906,1.021,1.079,1.109,2.164,10.773,0.0,0.12,0.0,1.0,0.1131,-0.0203,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱
+一缸,2026-07-06 09:00:00,2026-07-06 11:00:00,2,2.089,6.37,1.053,1.104,0.962,1.012,4.284,0.25,0.0,0.0,1.0,0.0423,-0.0095,高幅,轻微,False,未确认 / 持续证据弱
+五缸,2026-07-06 12:00:00,2026-07-06 15:00:00,3,1.615,5.562,0.996,1.065,1.156,2.123,10.582,0.0,0.17,0.0,1.0,0.0984,0.0304,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱
+四缸,2026-07-06 15:00:00,2026-07-06 17:00:00,2,1.846,4.767,1.038,1.147,1.201,1.992,10.413,0.12,0.12,0.0,1.0,-0.0145,-0.028,高幅高冲击,轻微,True,持续证据弱
+六缸,2026-07-06 17:00:00,2026-07-06 19:00:00,2,1.684,5.356,1.035,1.201,1.245,2.229,11.628,0.12,0.12,0.0,1.0,-0.0521,-0.0344,高幅高冲击,轻微,True,持续证据弱
+三缸,2026-07-07 02:00:00,2026-07-07 04:00:00,2,2.586,7.125,1.07,1.202,1.341,2.562,12.383,0.38,0.25,0.0,1.0,0.1363,0.0275,高幅高冲击,异常,True,
+五缸,2026-07-07 09:00:00,2026-07-07 11:00:00,2,2.498,6.161,0.995,1.043,0.97,1.283,6.383,0.0,0.0,0.0,1.0,-0.0167,-0.0042,未确认,异常,False,未确认 / 分数驱动不明 / 持续证据弱
+一缸,2026-07-07 20:00:00,2026-07-07 23:00:00,3,2.427,5.185,1.016,1.092,0.983,1.117,4.705,0.0,0.0,0.0,1.0,-0.1536,-0.004,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+二缸,2026-07-07 20:00:00,2026-07-07 23:00:00,3,2.22,5.576,1.001,1.098,1.267,2.324,18.913,0.0,0.17,0.0,1.0,-0.0484,-0.0212,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱
+六缸,2026-07-07 23:00:00,2026-07-08 01:00:00,2,1.968,5.071,1.111,1.258,1.293,2.16,11.336,0.38,0.25,0.0,1.0,-0.0998,-0.0309,高幅高冲击,轻微,True,
+三缸,2026-07-08 06:00:00,2026-07-08 08:00:00,2,2.983,7.597,1.011,1.099,1.407,3.03,14.617,0.0,0.25,0.0,1.0,-0.0836,-0.0147,低幅高冲击,异常,False,未确认 / 低幅高冲击 / 持续证据弱
+六缸,2026-07-08 21:00:00,2026-07-08 23:00:00,2,2.323,4.941,1.036,1.094,1.303,2.148,11.274,0.0,0.25,0.0,1.0,-0.0763,0.0539,高幅高冲击,轻微,True,低幅高冲击 / 持续证据弱
+三缸,2026-07-10 03:00:00,2026-07-10 06:00:00,3,2.124,8.907,1.017,1.103,1.201,3.032,14.68,0.08,0.08,0.0,1.0,0.0458,0.0135,高幅高冲击,严重异常,True,持续证据弱
+五缸,2026-07-22 10:00:00,2026-07-22 13:00:00,3,3.6,5.609,1.048,1.121,1.312,2.063,10.22,0.08,0.25,0.42,1.0,0.0,0.0,高幅高冲击,轻微,True,持续证据弱 / 启动/过渡
+五缸,2026-07-22 20:00:00,2026-07-22 23:00:00,3,2.959,5.671,1.043,1.127,1.375,2.023,10.057,0.08,0.33,0.0,1.0,0.2719,0.0766,高幅高冲击,轻微,True,
+一缸,2026-07-23 02:00:00,2026-07-23 04:00:00,2,2.388,5.793,0.93,0.964,0.943,1.022,4.214,0.0,0.0,0.0,1.0,-0.0581,0.0254,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+六缸,2026-07-23 02:00:00,2026-07-23 05:00:00,3,2.867,4.532,1.059,1.172,1.158,1.956,10.369,0.17,0.25,0.0,1.0,0.196,0.0182,高幅高冲击,轻微,True,持续证据弱
+六缸,2026-07-23 07:00:00,2026-07-23 10:00:00,3,2.33,5.457,1.052,1.192,1.187,2.414,12.754,0.33,0.08,0.0,1.0,-0.0355,-0.0237,高幅高冲击,轻微,True,
+五缸,2026-07-23 12:00:00,2026-07-23 14:00:00,2,3.57,7.02,1.043,1.116,1.493,2.42,12.103,0.12,0.25,0.0,1.0,-0.1994,-0.0334,高幅高冲击,异常,True,持续证据弱
+五缸,2026-07-24 18:00:00,2026-07-24 21:00:00,3,2.834,4.504,1.051,1.144,1.44,2.08,10.444,0.17,0.33,0.0,1.0,-0.0322,-0.0709,高幅高冲击,轻微,True,
+六缸,2026-07-24 19:00:00,2026-07-24 21:00:00,2,2.5,7.861,1.066,1.192,1.4,3.004,15.985,0.38,0.12,0.0,1.0,-0.0082,0.0141,高幅高冲击,严重异常,True,
+三缸,2026-07-24 23:00:00,2026-07-25 01:00:00,2,2.269,4.744,1.017,1.141,1.181,1.341,6.48,0.12,0.0,0.0,1.0,-0.0406,-0.0368,高幅,轻微,False,未确认 / 持续证据弱
+五缸,2026-07-25 03:00:00,2026-07-25 09:00:00,6,2.371,6.199,1.01,1.089,1.172,2.337,11.771,0.0,0.08,0.0,1.0,0.0134,-0.0023,低幅高冲击,异常,False,未确认 / 低幅高冲击 / 持续证据弱
+一缸,2026-07-25 05:00:00,2026-07-25 07:00:00,2,1.961,6.537,0.977,1.013,1.085,1.161,4.792,0.0,0.0,0.0,1.0,-0.1045,0.0148,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+六缸,2026-07-25 08:00:00,2026-07-25 13:00:00,5,2.115,5.212,1.078,1.241,1.196,2.331,12.423,0.35,0.1,0.0,1.0,0.1455,-0.0138,高幅高冲击,轻微,True,
+一缸,2026-07-25 20:00:00,2026-07-25 22:00:00,2,2.329,4.721,1.001,1.031,1.041,1.144,4.723,0.0,0.0,0.0,1.0,0.0691,-0.0114,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+五缸,2026-07-26 04:00:00,2026-07-26 06:00:00,2,2.343,5.322,1.003,1.068,1.182,2.036,10.34,0.0,0.12,0.0,1.0,-0.036,0.0069,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱
+三缸,2026-07-27 21:00:00,2026-07-27 23:00:00,2,1.959,5.463,0.991,1.087,1.205,2.568,12.771,0.0,0.12,0.0,1.0,-0.2927,-0.036,低幅高冲击,轻微,False,未确认 / 低幅高冲击 / 持续证据弱
+五缸,2026-07-27 21:00:00,2026-07-27 23:00:00,2,2.36,6.858,0.994,1.096,1.215,2.454,12.589,0.0,0.12,0.0,1.0,-0.0789,-0.0382,低幅高冲击,异常,False,未确认 / 低幅高冲击 / 持续证据弱
+六缸,2026-07-28 01:00:00,2026-07-28 03:00:00,2,1.553,4.711,1.039,1.127,1.176,2.015,10.941,0.12,0.12,0.0,1.0,-0.0761,0.0058,高幅高冲击,轻微,True,持续证据弱
+五缸,2026-07-28 17:00:00,2026-07-28 20:00:00,3,2.033,4.871,1.068,1.131,1.303,2.0,10.262,0.17,0.08,0.0,1.0,0.1394,0.0692,高幅高冲击,轻微,True,持续证据弱
+一缸,2026-07-29 00:00:00,2026-07-29 03:00:00,3,2.578,5.278,0.972,1.057,1.001,1.121,4.631,0.0,0.0,0.0,1.0,-0.1423,-0.0068,未确认,轻微,False,未确认 / 分数驱动不明 / 持续证据弱
+六缸,2026-07-29 10:00:00,2026-07-29 12:00:00,2,1.872,5.289,1.009,1.103,1.205,2.075,11.392,0.11,0.11,0.0,1.0,-0.165,-0.0254,高幅高冲击,轻微,True,持续证据弱
+五缸,2026-07-30 05:00:00,2026-07-30 07:00:00,2,2.168,6.601,0.999,1.076,1.32,2.707,13.894,0.0,0.12,0.0,1.0,0.0527,0.0028,低幅高冲击,异常,False,未确认 / 低幅高冲击 / 持续证据弱
+一缸,2026-07-30 09:00:00,2026-07-30 11:00:00,2,2.749,6.867,0.992,1.075,0.982,1.207,4.974,0.0,0.0,0.0,1.0,-0.0672,0.0025,未确认,异常,False,未确认 / 分数驱动不明 / 持续证据弱
+六缸,2026-07-30 19:00:00,2026-07-30 21:00:00,2,2.464,8.749,1.033,1.166,1.365,3.035,16.828,0.25,0.25,0.0,1.0,0.1026,0.0262,高幅高冲击,严重异常,True,持续证据弱

二进制
_tmp_cylinder_vibration/out/plots/event_2026-06-12_cyl2.png


二进制
_tmp_cylinder_vibration/out/plots/event_2026-06-21_cyl5.png


二进制
_tmp_cylinder_vibration/out/plots/event_2026-07-11_cyl2.png


二进制
_tmp_cylinder_vibration/out/plots/features_kurt_ratio_2026-06-12.png


二进制
_tmp_cylinder_vibration/out/plots/features_kurt_ratio_2026-06-21.png


二进制
_tmp_cylinder_vibration/out/plots/features_kurt_ratio_2026-07-11.png


二进制
_tmp_cylinder_vibration/out/plots/features_rms_ratio_2026-06-12.png


二进制
_tmp_cylinder_vibration/out/plots/features_rms_ratio_2026-06-21.png


二进制
_tmp_cylinder_vibration/out/plots/features_rms_ratio_2026-07-11.png


二进制
_tmp_cylinder_vibration/out/plots/score_heatmap.png


二进制
_tmp_cylinder_vibration/out/plots/waveforms_2026-06-12.png


二进制
_tmp_cylinder_vibration/out/plots/waveforms_2026-06-21.png


二进制
_tmp_cylinder_vibration/out/plots/waveforms_2026-07-11.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0001.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0002.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0003.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0004.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0005.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0006.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0007.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0008.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0009.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0010.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0011.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0012.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0013.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0014.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0015.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0016.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0017.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0018.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0019.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0020.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0021.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0022.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0023.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0024.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0025.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0026.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0027.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0028.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0029.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0030.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0031.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0032.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0033.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0034.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0035.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0036.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0037.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0038.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0039.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0040.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0041.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0042.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0043.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0044.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0045.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0046.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0047.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0048.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0049.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0050.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0051.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0052.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0053.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0054.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0055.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0056.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0057.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0058.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0059.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0060.png


二进制
_tmp_cylinder_vibration/out/report_assets/shot_0061.png


+ 0 - 0
_tmp_cylinder_vibration/out/report_assets/shot_0062.png


部分文件因为文件数量过多而无法显示