event_trend_summary.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. #!/usr/bin/env python3
  2. """Six-window event trend summary for sampled cycle profiles and pressure levels."""
  3. from __future__ import annotations
  4. import argparse
  5. import csv
  6. import sys
  7. from datetime import datetime, timedelta
  8. from pathlib import Path
  9. import numpy as np
  10. BACKEND = Path(__file__).resolve().parents[1]
  11. sys.path.insert(0, str(BACKEND))
  12. from event_bins import EVENTS # noqa: E402
  13. BAD = {("7", "四", "盖侧"), ("7", "六", "盖侧"), ("8", "六", "盖侧")}
  14. def load(path):
  15. with open(path, encoding="utf-8-sig", newline="") as handle:
  16. rows = list(csv.DictReader(handle))
  17. for row in rows:
  18. row["time"] = datetime.fromisoformat(row["sample_time"])
  19. for key in ("p_suction", "p_discharge", "p_span"):
  20. row[key] = float(row[key]) if row.get(key) else np.nan
  21. for key in row:
  22. if key.startswith("prof_"):
  23. row[key] = float(row[key]) if row[key] else np.nan
  24. return rows
  25. def med(rows, key):
  26. values = [r[key] for r in rows if np.isfinite(r[key])]
  27. return float(np.median(values)) if values else np.nan
  28. def main():
  29. parser = argparse.ArgumentParser(description="事件前后六段趋势")
  30. parser.add_argument("--csv", nargs="+", required=True)
  31. parser.add_argument("--unit", nargs="+", required=True)
  32. parser.add_argument("--out", required=True)
  33. parser.add_argument("--event", nargs=3, action="append", metavar=("UNIT", "DATE", "KIND"),
  34. help="只分析指定事件;可重复传入")
  35. args = parser.parse_args()
  36. data = {u: load(p) for u, p in zip(args.unit, args.csv)}
  37. rows_out = []
  38. events = [tuple(item) for item in args.event] if args.event else EVENTS
  39. for unit, date_text, kind in events:
  40. source = [r for r in data.get(unit, []) if (unit, r["cylinder"], r["side"]) not in BAD and r["p_span"] >= 0.3]
  41. t0 = datetime.fromisoformat(date_text)
  42. profile_names = sorted(k for k in source[0] if k.startswith("prof_")) if source else []
  43. for cylinder, side in sorted({(r["cylinder"], r["side"]) for r in source}):
  44. point = [r for r in source if r["cylinder"] == cylinder and r["side"] == side]
  45. base = None
  46. for index, (lo, hi) in enumerate([(-30, -20), (-20, -10), (-10, 0), (0, 10), (10, 20), (20, 30)]):
  47. a, b = t0 + timedelta(days=lo), t0 + timedelta(days=hi)
  48. selected = [r for r in point if a <= r["time"] < b]
  49. profile = np.asarray([[r[k] for k in profile_names] for r in selected], dtype=float)
  50. profile = np.median(profile, axis=0) if len(profile) and np.all(np.isfinite(profile), axis=1).any() else None
  51. if profile is not None and base is None:
  52. base = profile
  53. distance = float(np.linalg.norm(profile - base)) if profile is not None and base is not None else np.nan
  54. rows_out.append({
  55. "机组": unit, "事件": date_text, "故障类型": kind,
  56. "测点": f"{cylinder}缸{side}", "时间段": f"{lo}~{hi}天",
  57. "周期数": len(selected),
  58. "吸气压力": med(selected, "p_suction"),
  59. "排气压力": med(selected, "p_discharge"),
  60. "压力幅度": med(selected, "p_span"),
  61. "相对首段形状距离": "" if not np.isfinite(distance) else round(distance, 5),
  62. })
  63. out = Path(args.out)
  64. out.parent.mkdir(parents=True, exist_ok=True)
  65. fields = list(rows_out[0])
  66. with out.open("w", encoding="utf-8-sig", newline="") as handle:
  67. writer = csv.DictWriter(handle, fieldnames=fields)
  68. writer.writeheader()
  69. writer.writerows(rows_out)
  70. print(f"事件趋势 -> {out}({len(rows_out)} 行)")
  71. if __name__ == "__main__":
  72. main()