Просмотр исходного кода

功图:按级通道QC、盖/轴互换说明、突发跳变检测与观察修正

18922397810 3 недель назад
Родитель
Сommit
73c4f31aa3

+ 13 - 4
backend/indicator_features/analyze.py

@@ -55,16 +55,25 @@ def series_medians(rows: list[dict], feature: str) -> dict:
 
 
 def flag_channels(rows: list[dict]) -> dict:
-    """Flag weak/dead channels relative to the stage-wide expected discharge level."""
+    """Flag weak/dead channels relative to the *same stage* expected discharge level."""
     medians = series_medians(rows, "p_discharge")
-    reference = np.median([v for v in medians.values() if v > DEAD_DISCHARGE]) if medians else 0.0
+    stage_of = {}
+    for row in rows:
+        stage_of[(row["cylinder"], row["side"])] = row["stage"]
+    by_stage = defaultdict(list)
+    for key, value in medians.items():
+        by_stage[stage_of.get(key, "?")].append(value)
+    reference = {stage: float(np.median([v for v in values if v > DEAD_DISCHARGE]))
+                 for stage, values in by_stage.items()
+                 if any(v > DEAD_DISCHARGE for v in values)}
     flags = {}
     for key, value in medians.items():
+        ref = reference.get(stage_of.get(key, "?"), 0.0)
         if value < DEAD_DISCHARGE:
             flags[key] = "dead"
-        elif reference > 0 and value < 0.6 * reference:
+        elif ref > 0 and value < 0.6 * ref:
             flags[key] = "weak"
-        elif reference > 0 and value > 1.5 * reference:
+        elif ref > 0 and value > 1.5 * ref:
             flags[key] = "high"
     return flags
 

+ 50 - 0
backend/indicator_features/trend.py

@@ -58,6 +58,42 @@ def mark_segments(day: str, segments):
     return [s["kind"] + "/" + str(s["level"]) for s in segments if s["start"] <= moment <= s["end"]]
 
 
+def peer_sudden_changes(rows, feature, segments):
+    """Day-to-day jumps of the peer-relative deviation (cancels operating level)."""
+    by_key = defaultdict(dict)
+    for row in rows:
+        if row[feature] is not None:
+            by_key[(row["stage"], row["side"], row["day"])][row["cylinder"]] = row[feature]
+    series = defaultdict(dict)
+    for (_stage, side, day), members in by_key.items():
+        values = [v for v in members.values() if v is not None]
+        if len(values) < 2:
+            continue
+        med = float(np.median(values))
+        if abs(med) < 1e-9:
+            continue
+        for cylinder, value in members.items():
+            if value is not None:
+                series[(cylinder, side)][day] = (value - med) / med
+    print(f"\n== 同级缸间偏差 突发变化({feature})==")
+    for (cylinder, side), daily in sorted(series.items()):
+        days = sorted(daily)
+        if len(days) < 3:
+            continue
+        values = np.array([daily[d] for d in days], dtype=float)
+        scale = float(np.median(np.abs(values - np.median(values)))) * 1.4826 or float(np.std(values))
+        if scale <= 1e-9:
+            continue
+        for i in range(1, len(days)):
+            gap = (datetime.strptime(days[i], "%Y-%m-%d") - datetime.strptime(days[i - 1], "%Y-%m-%d")).days
+            if gap > 3:
+                continue
+            delta = values[i] - values[i - 1]
+            if abs(delta) / scale >= 3.0:
+                flags = mark_segments(days[i], segments)
+                print(f"   {cylinder}缸{side}: 偏差 {values[i-1]*100:+.1f}% → {values[i]*100:+.1f}% 于 {days[i]} ({delta/scale:+.1f}×)  {' '.join(flags)}")
+
+
 def main():
     parser = argparse.ArgumentParser(description="功图特征长期偏离检测(对齐 evolution 异常段)")
     parser.add_argument("csv_path")
@@ -98,6 +134,20 @@ def main():
                 if abs(z) >= 2.0 or flags:
                     tag = "  ".join(flags)
                     print(f"      {day}  {daily[day]:>8.3f}  z={z:+.2f}  {tag}")
+            # Abrupt day-to-day change (sudden step), independent of any trend.
+            present = [d for d in days if d in daily]
+            jumps = []
+            for i in range(1, len(present)):
+                gap = (datetime.strptime(present[i], "%Y-%m-%d") - datetime.strptime(present[i - 1], "%Y-%m-%d")).days
+                if gap <= 3:
+                    jumps.append((present[i], daily[present[i]] - daily[present[i - 1]]))
+            if jumps:
+                day_jump, delta = max(jumps, key=lambda item: abs(item[1]))
+                if abs(delta) / scale >= 3.0:
+                    flags = mark_segments(day_jump, segments)
+                    tag = "  ".join(flags)
+                    print(f"      [突发] {feature}: Δ={delta:+.3f} ({delta / scale:+.1f}×基线尺度) 于 {day_jump}  {tag}")
+    peer_sudden_changes(rows, "p_discharge", segments)
 
 
 if __name__ == "__main__":

+ 37 - 39
backend/indicator_features/观察记录.md

@@ -1,64 +1,62 @@
 # 功图找气阀/活塞故障 —— 观察记录
 
 > 只读分析。样本 `wave_file` + `wave_sample_one`(首周期,约 1545 点)。故障锚点:`7,8,9号压缩机故障汇总.xlsx`;异常段对齐 `cache/pks_scanner/evolution/`。
-> 目录 `backend/indicator_features/` 为**新增独立**目录,只读数据库、只 `import app.config`,不影响任何现有算法/程序。
+> `backend/indicator_features/` 为**新增独立**目录,只读数据库、只 `import app.config`,不影响现有算法/程序。
 
-## 1. 数据方法(确认)
+## 1. 数据方法
 
 - 25.6 kHz、8 脉冲/转、≈996 RPM → 单周期≈60 ms;15 分钟一个文件。
 - `wave_sample_one` = 每文件**一个首周期**;`cycle_start/end` 废弃。
-- 级配:**一级=2/4/6、二级=3/5、三级=1**(Pd 约 1.4~2.2 / 3.2~4.4 / 5.0~5.1 MPa)。
+- 级配:**一级=2/4/6、二级=3/5、三级=1**。
+- **同一缸的“压力盖侧 / 压力轴侧”可互换替代**(同一缸两个腔的测点)。
 
-## 2. 可用性(往前找,直到有周期的日子)
+## 2. 可用性(往前找,直到有周期;盖/轴可互补)
 
-`availability.py` 扫出“真正存在完整功图周期”的日子;`rpm` 不作为运行判据(不可靠)。
+`availability.py` 不依赖 rpm,直接找有完整周期的日子:
 
-| 测点 | 有效段(有周期) |
+| 测点 | 有效段 |
 |---|---|
-| 8 号机三缸盖侧 | 2026-03-16~20、04-19~05-06、05-31~06-18 |
-| 7 号机二缸盖侧 | 2025-06-13、07-25~27、11-18~20、2026-02、03-13~04-04、05-07~21、06-01~06 |
-| 9 号机一缸盖侧 | 2026-01-30~02-09、03-04~13、03-22~27、04-02~18、05-22~31、06-15~22 |
+| 8 号机 全缸(盖/轴) | 2025-03-13~05-03;2026-03-16~20、04-19~05-06、05-31~06-18 |
+| 7 号机 二缸等 | 2025-06-13、07-25~27、11-18~20、2026-02、03-13~04-04、05-07~21、06-01~06 |
+| 9 号机 二缸/三缸 | 2025-03、06、09、10 零散 |
+| 9 号机 全缸 | 2026-01-30~02-09、03-04~13、03-22~27、04-02~18、05-22~31、06-15~22 |
 
-**据此修正后可用的事件**(往前找最后一段):
+**活塞事件的故障前功图情况**(用全缸、盖/轴互补核过):
 
-| 事件 | 故障前最后有功图段 |
+| 事件 | 情况 |
 |---|---|
-| 7 号机 2025.8.8 气阀 | 2025-07-25~27 ✅ |
-| 8 号机 2026.3.29 / 4.1 气阀 | 2026-03-16~20 ✅ |
-| 7 号机 2026.5.21 气阀 | 2026-05-07~21 ✅(连续) |
-| 8 号机 2025.12.6 活塞 | 无功图(2025-06 后未采集)❌ |
-| 9 号机 2026.1.18 活塞 | 1 缸 2026-01-30 才有(故障后)❌ |
-| 7 号机 2025.1.10 活塞 | 无功图 ❌ |
+| 7 号机 2025.1.10 活塞 | 全缸 2024-10~2025-04 **无有效周期** ❌ |
+| 8 号机 2025.12.6 活塞 | 全缸最后一段为 **2025-05-03**,其后长期停机到 2026-03 ❌ |
+| 9 号机 2026.1.18 活塞(三级=1缸) | 全缸有效数据**从 2026-01-30 起**(事件后)❌ |
 
-## 3. 特征(任务 1/2 完成)
+## 3. 特征与检测(已完成)
 
-- 稳健平台:Otsu 双峰求 `P_s / P_d / pressure_ratio`。
-- 相位对齐:整转内取 PV 环面积最大的曲柄偏移 → `indicated_work`、`ideal_work_isentropic`、**`efficiency`(等熵效率,实测 0.68~0.76,合理)**、`n_comp`。
-- `analyze.py`:同级缸间、同缸双腔(含相位剖面 L2)、本缸自身 z,含通道 QC(dead/weak/high 自动排除)。
-- `trend.py`:按天自身基线 z,并与 evolution 压力/振动异常段对齐(任务 3)。
+- 稳健平台:Otsu 双峰求 `P_s/P_d/pressure_ratio`。
+- 相位对齐:整转内取 PV 环面积最大的偏移 → 指示功、**等熵效率 efficiency(0.68~0.89,合理)**、`n_comp`。
+- 通道 QC:**按“级”**判断 dead/weak/high(修正了跨级误判,如三级一缸压力天然高)。
+- `analyze.py`:同级缸间、同缸双腔(相位剖面 L2)、本缸自身 z。
+- `trend.py`:按天自身基线 z + **突发跳变** + **同级偏差的突发跳变**,并对齐 evolution 段。
 
 ## 4. 关键观察
 
-### 4.1 8 号机 2026 春季(含 3.29/4.1 气阀)
-- 3-16~20 一级排气压力比 4/5 月低约 10 个 MAD(z≈−10),03-18 落在 `压力/轻微` 段内。
-- 但**同级缸间对照**显示:二/四盖侧仅 ∓4%、各缸与盖/轴两腔**同步移动** → 这是**工况/负荷级变化,不是单缸故障**。
-- 说明:必须用同级/双腔对照消工况;单看自身 z 会误报。
+### 4.1 8 号机 2025 春季(活塞事件前最后运行段)
+- 全通道正常;**二缸盖侧** `p_discharge` 比同级低约 8%、指示功低约 9%,但**二缸轴侧正常** → 倾向单侧偏置,不是活塞双腔特征。
+- 同级偏差的“突发跳变”集中在 **2025-03-17/18、05-01~03**,是**工况切换日**(多缸同步移动)。
 
-### 4.2 7 号机 2026-05 气阀前两周
-- 通道:六缸盖侧 weak、四缸盖侧 dead。
-- 同级轴侧偏差稳定(Pd ±0.5~6.6%、压比 ±2~6%);无趋势(该事件为管道突发)。
+### 4.2 8 号机 2026 春季(3.29/4.1 气阀)
+- 3-16~20 一级 Pd 比 4/5 月低约 10 MAD,03-18 落在 `压力/轻微` 段内;
+- 但**同级缸间 + 盖/轴双腔同步移动** → 工况级变化,非单缸故障。
 
-### 4.3 问题
-- `n_comp` 在通道间不稳定(部分为 0/负),暂不宜作为主特征。
-- 坏/弱通道常见,必须 QC。
+### 4.3 7 号机 2026-05 气阀前两周
+- 同级轴侧偏差稳定、无趋势(该事件为管道突发)。
 
 ## 5. 结论
 
-- 抽取链路、稳健平台、等熵效率、三种归一化、长期偏离检测均已就绪。
-- 现有数据里:可用事件都是**气阀**且多为突发;**尚未发现单缸级、故障前可持续观测的功图趋势**。
-- 8 号机 3 月的大偏离是工况级,说明对照归一化是必需的。
+- 工具链完备(平台/效率/三种归一化/突发跳变/evolution 对齐/通道 QC)。
+- 但**活塞事件在现有功图数据里没有故障前可用段**;气阀事件多为突发,故障前无单缸趋势。
+- 观察到的较大变化都能被“同级/双腔同步”解释为**工况**,说明归一化是必需的,也说明目前**功图暂未提供可区分的故障特征**。
 
-## 6. 下一步
-1. 等 2026-06 后新事件(4/6 号机与 7/8/9 密集段);
-2. 稳定 `n_comp`(限制拟合区间/最小样本);
-3. 用“同级 + 双腔”偏差做长期基线与偏离报警,与 evolution 段多证据互验。
+## 6. 建议
+1. 用**未来/新采集**的密集段(4/6 号机与 7/8/9 持续运行 + 有维修记录)做验证;
+2. 稳定 `n_comp`(限制拟合区间、最小样本);
+3. 将“同级偏差 + 双腔差”的长期基线与 evolution 段联合,做多证据互验与偏离报警。