one_page.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. #!/usr/bin/env python3
  2. """一页纸候选:每个事件只留最强的 2 条(机组/测点/角度段/指标/前后值/一句话)。"""
  3. from __future__ import annotations
  4. import argparse
  5. import csv
  6. import sys
  7. from collections import defaultdict
  8. from datetime import datetime
  9. from pathlib import Path
  10. import numpy as np
  11. BACKEND = Path(__file__).resolve().parents[1]
  12. sys.path.insert(0, str(BACKEND))
  13. from event_bins import EVENTS, collect_after, collect_before, cycle_count, load # noqa: E402
  14. from run_segments import unit_segments # noqa: E402
  15. METRICS = {"极差": "range", "粗糙度": "roughness", "水平": "mean",
  16. "波动次数": "zero_cross", "极值数": "n_extrema"}
  17. # 已知坏道/传感器通道(观察记录里确认过)
  18. BAD = {("7", "四", "盖侧"), ("7", "六", "盖侧"), ("8", "六", "盖侧")}
  19. UP = {"极差": "波动变大", "粗糙度": "变毛糙", "水平": "抬高",
  20. "波动次数": "起伏次数变多", "极值数": "转折变多"}
  21. DOWN = {"极差": "波动变小", "粗糙度": "变平滑", "水平": "降低",
  22. "波动次数": "起伏次数变少", "极值数": "转折变少"}
  23. def robust_z(pre, post):
  24. if len(pre) < 3 or len(post) < 3:
  25. return None
  26. med = float(np.median(pre))
  27. mad = float(np.median(np.abs(np.asarray(pre) - med))) * 1.4826
  28. if mad <= 1e-9:
  29. mad = float(np.std(pre))
  30. if mad <= 1e-9:
  31. return None
  32. return (float(np.median(post)) - med) / mad
  33. def main() -> None:
  34. parser = argparse.ArgumentParser(description="一页纸候选")
  35. parser.add_argument("--csv", nargs="+", required=True)
  36. parser.add_argument("--unit", nargs="+", required=True)
  37. parser.add_argument("--top", type=int, default=2, help="每个事件保留几条")
  38. parser.add_argument("--z", type=float, default=4.0, help="强度阈值")
  39. parser.add_argument("--out", required=True)
  40. args = parser.parse_args()
  41. picked = []
  42. for unit, path in zip(args.unit, args.csv):
  43. rows = load(path)
  44. segments = unit_segments(unit, 2)
  45. for eu, date, kind in EVENTS:
  46. if eu != unit:
  47. continue
  48. t0 = datetime.fromisoformat(date)
  49. cands = []
  50. for cyl, side in sorted({(r["cylinder"], r["side"]) for r in rows}):
  51. if (unit, cyl, side) in BAD:
  52. continue
  53. point = [r for r in rows if r["cylinder"] == cyl and r["side"] == side]
  54. pre, _ = collect_before(point, t0, segments, 6)
  55. post, _ = collect_after(point, t0, segments, 30, 45)
  56. if cycle_count(pre) < 3 or cycle_count(post) < 3:
  57. continue
  58. span = np.median([r["p_span"] for r in pre if r["p_span"]]) if pre else 0
  59. if span < 0.3: # 坏道/卡死
  60. continue
  61. for b in range(36):
  62. pr = [r for r in pre if r["bin"] == b]
  63. po = [r for r in post if r["bin"] == b]
  64. for cn, en in METRICS.items():
  65. a = [r[en] for r in pr if r[en] is not None]
  66. c = [r[en] for r in po if r[en] is not None]
  67. z = robust_z(a, c)
  68. if z is None or abs(z) < args.z:
  69. continue
  70. am, cm = float(np.median(a)), float(np.median(c))
  71. cands.append({
  72. "机组": unit, "事件": date, "故障类型": kind,
  73. "测点": f"{cyl}缸{side}", "角度段": f"{b*10}-{b*10+10}°",
  74. "指标": cn, "停机前": round(am, 3), "开机后": round(cm, 3),
  75. "强度": round(abs(z), 1),
  76. "结论": (UP if z > 0 else DOWN)[cn],
  77. })
  78. cands.sort(key=lambda r: -r["强度"])
  79. seen = set()
  80. for row in cands:
  81. key = (row["测点"], row["角度段"])
  82. if key in seen:
  83. continue
  84. seen.add(key)
  85. picked.append(row)
  86. if len(seen) >= args.top:
  87. break
  88. out = Path(args.out)
  89. out.parent.mkdir(parents=True, exist_ok=True)
  90. fields = ["机组", "事件", "故障类型", "测点", "角度段", "指标", "停机前", "开机后", "强度", "结论"]
  91. with out.open("w", encoding="utf-8-sig", newline="") as fh:
  92. w = csv.DictWriter(fh, fieldnames=fields)
  93. w.writeheader()
  94. w.writerows(picked)
  95. print(f"一页纸候选 -> {out}({len(picked)} 行)")
  96. print(f"{'机组':<4}{'事件':<12}{'测点':<9}{'角度段':<10}{'指标':<6}{'前':>8}{'后':>8}{'强度':>6} 结论")
  97. for r in picked:
  98. print(f"{r['机组']+'号':<4}{r['事件']:<12}{r['测点']:<9}{r['角度段']:<10}{r['指标']:<6}"
  99. f"{r['停机前']:>8}{r['开机后']:>8}{r['强度']:>6} {r['结论']}")
  100. if __name__ == "__main__":
  101. main()