build_datasets.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. #!/usr/bin/env python3
  2. """Build vibration model samples from the reconstructed vibration algorithm output."""
  3. from __future__ import annotations
  4. import argparse
  5. import shutil
  6. import sys
  7. from pathlib import Path
  8. sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
  9. from models.common import paths, queries, windowing
  10. from models.common.constants import VIBRATION_BASELINE_DAYS
  11. from models.vibration import features_vibration
  12. def vibration_features(series, end_index, window):
  13. features = {
  14. "running_coverage_mean": windowing.mean([row["运行覆盖率"] for row in window]),
  15. "running_coverage_min": min(row["运行覆盖率"] for row in window),
  16. }
  17. for side in ("coupling", "chain"):
  18. averages = [row[f"{side}_avg"] for row in window]
  19. peaks = [row[f"{side}_max"] for row in window]
  20. avg_center, avg_scale = windowing.previous_baseline(series, end_index, f"{side}_avg", VIBRATION_BASELINE_DAYS * 24)
  21. peak_center, peak_scale = windowing.previous_baseline(series, end_index, f"{side}_max", VIBRATION_BASELINE_DAYS * 24)
  22. current_avg, current_peak = averages[-1], peaks[-1]
  23. features.update({
  24. f"{side}_avg_mean": windowing.mean(averages), f"{side}_avg_max": max(averages), f"{side}_avg_range": windowing.value_range(averages),
  25. f"{side}_avg_slope": windowing.slope(averages), f"{side}_peak_mean": windowing.mean(peaks), f"{side}_peak_max": max(peaks),
  26. f"{side}_peak_range": windowing.value_range(peaks), f"{side}_peak_slope": windowing.slope(peaks),
  27. f"{side}_avg_baseline_deviation": ((current_avg - avg_center) / avg_scale) if avg_center is not None and avg_scale else None,
  28. f"{side}_peak_baseline_deviation": ((current_peak - peak_center) / peak_scale) if peak_center is not None and peak_scale else None,
  29. f"{side}_avg_rel_last": (current_avg / avg_center - 1) if avg_center else None,
  30. f"{side}_peak_rel_last": (current_peak / peak_center - 1) if peak_center else None,
  31. f"{side}_avg_rel_mean": (windowing.mean(averages) / avg_center - 1) if avg_center else None,
  32. f"{side}_peak_rel_max": (max(peaks) / peak_center - 1) if peak_center else None,
  33. f"{side}_high_ratio_mean": windowing.mean([windowing.ratio(row[f"{side}_high_count"], row["运行样本数"]) for row in window]),
  34. f"{side}_high_high_ratio_mean": windowing.mean([windowing.ratio(row[f"{side}_high_high_count"], row["运行样本数"]) for row in window]),
  35. })
  36. features["both_sides_rising"] = int(features["coupling_avg_slope"] > 0 and features["chain_avg_slope"] > 0)
  37. return features
  38. def main() -> None:
  39. parser = argparse.ArgumentParser(description="构建振动模型样本(窗口可配置)")
  40. parser.add_argument("--start", default="2025-01-01 00:00:00")
  41. parser.add_argument("--end", default="2026-09-01 00:00:00")
  42. parser.add_argument("--chunk-days", type=int, default=14)
  43. parser.add_argument("--window-hours", type=int, default=3)
  44. parser.add_argument("--output-dir", default=str(paths.VIBRATION_DATAS))
  45. parser.add_argument("--hourly-csv", default=str(paths.HOURLY_FULL))
  46. parser.add_argument("--units", nargs="+", default=None)
  47. args = parser.parse_args()
  48. output = Path(args.output_dir)
  49. if output.exists():
  50. shutil.rmtree(output)
  51. output.mkdir(parents=True)
  52. hourly_path = Path(args.hourly_csv)
  53. if hourly_path.is_file():
  54. print(f"复用小时中间表:{hourly_path}")
  55. rows = queries.load_hourly_csv(hourly_path)
  56. else:
  57. from models.common import db
  58. batch_to_unit = db.load_pks_batches()
  59. selected = sorted(batch_to_unit)
  60. configs = db.load_alarm_config({batch: batch_to_unit[batch] for batch in selected})
  61. start, end = queries.parse_dt(args.start), queries.parse_dt(args.end)
  62. rows = queries.fetch_all_hourly(start, end, args.chunk_days, batch_to_unit, configs, selected)
  63. scoped = [row for row in rows if not args.units or row["机组"] in set(args.units)]
  64. windowing.write_hourly(output / "hourly.csv", scoped)
  65. stages = windowing.stage_ranges(paths.VIBRATION_RESULTS / "振动异常段.csv", "振动阶段")
  66. if args.units:
  67. stages = [stage for stage in stages if stage["unit"] in set(args.units)]
  68. samples = windowing.build_samples(scoped, "vibration", stages, args.window_hours, vibration_features)
  69. counts = windowing.write_samples(output, samples)
  70. print(f"vibration samples: {dict(counts)}")
  71. print(f"小时中间数据:{len(scoped):,} 行")
  72. print(f"样本输出目录:{output}")
  73. if __name__ == "__main__":
  74. main()