build_datasets.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. #!/usr/bin/env python3
  2. """Build pressure model samples from the reconstructed pressure 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
  10. from models.common import windowing
  11. from models.generalized import features_pressure, pressure_algorithm
  12. from models.common.constants import MIN_HOUR_COVERAGE
  13. def cycle_baseline(series, days=3):
  14. return windowing.cycle_baseline(series, days)
  15. def pressure_features(series, end_index, window):
  16. from models.common.constants import HOURLY_BASELINE_HOURS
  17. oil = [row["oil_avg"] for row in window]
  18. coverages = [row["运行覆盖率"] for row in window]
  19. baseline = windowing.median_value([row["oil_avg"] for row in window[:-1]])
  20. current = oil[-1]
  21. oil_mean, oil_min, oil_max = windowing.mean(oil), min(oil), max(oil)
  22. oil_range = windowing.value_range(oil)
  23. oil_slope = windowing.slope(oil)
  24. cycle_base = cycle_baseline(series)
  25. recent_center, _ = windowing.previous_baseline(series, end_index, "oil_avg", HOURLY_BASELINE_HOURS)
  26. return {
  27. "oil_mean": oil_mean, "oil_min": oil_min, "oil_max": oil_max, "oil_range": oil_range,
  28. "oil_slope": oil_slope,
  29. "oil_last_relative_to_window": (current / baseline - 1) if baseline else None,
  30. "oil_low_ratio_mean": windowing.mean([windowing.ratio(row["oil_low_count"], row["运行样本数"]) for row in window]),
  31. "oil_low_low_ratio_mean": windowing.mean([windowing.ratio(row["oil_low_low_count"], row["运行样本数"]) for row in window]),
  32. "running_coverage_mean": windowing.mean(coverages), "running_coverage_min": min(coverages),
  33. "oil_rel_cycle_mean": (oil_mean / cycle_base - 1) if cycle_base else None,
  34. "oil_rel_cycle_min": (oil_min / cycle_base - 1) if cycle_base else None,
  35. "oil_rel_cycle_last": (current / cycle_base - 1) if cycle_base else None,
  36. "oil_rel_recent_mean": (oil_mean / recent_center - 1) if recent_center else None,
  37. "oil_rel_recent_last": (current / recent_center - 1) if recent_center else None,
  38. "oil_slope_rel_cycle": (oil_slope / cycle_base) if cycle_base else None,
  39. "oil_decline_hours": windowing.decline_hours(series, end_index),
  40. "oil_drawdown_cycle": ((cycle_base - oil_min) / cycle_base) if cycle_base else None,
  41. "oil_range_rel_cycle": (oil_range / cycle_base) if cycle_base else None,
  42. }
  43. def main() -> None:
  44. parser = argparse.ArgumentParser(description="构建压力模型样本(窗口可配置)")
  45. parser.add_argument("--start", default="2025-01-01 00:00:00")
  46. parser.add_argument("--end", default="2026-09-01 00:00:00")
  47. parser.add_argument("--chunk-days", type=int, default=14)
  48. parser.add_argument("--window-hours", type=int, default=3)
  49. parser.add_argument("--output-dir", default=str(paths.PRESSURE_DATAS))
  50. parser.add_argument("--hourly-csv", default=str(paths.HOURLY_FULL))
  51. parser.add_argument("--units", nargs="+", default=None)
  52. args = parser.parse_args()
  53. output = Path(args.output_dir)
  54. if output.exists():
  55. shutil.rmtree(output)
  56. output.mkdir(parents=True)
  57. hourly_path = Path(args.hourly_csv)
  58. if hourly_path.is_file():
  59. print(f"复用小时中间表:{hourly_path}")
  60. rows = queries.load_hourly_csv(hourly_path)
  61. else:
  62. from models.common import db
  63. batch_to_unit = db.load_pks_batches()
  64. selected = sorted(batch_to_unit)
  65. configs = db.load_alarm_config({batch: batch_to_unit[batch] for batch in selected})
  66. start, end = queries.parse_dt(args.start), queries.parse_dt(args.end)
  67. rows = queries.fetch_all_hourly(start, end, args.chunk_days, batch_to_unit, configs, selected)
  68. scoped = [row for row in rows if not args.units or row["机组"] in set(args.units)]
  69. # 0 MPa 和部分掉点小时都不是正常样本,也不应进入模型窗口。
  70. scoped = [row for row in scoped if row.get("oil_avg") is not None and row["oil_avg"] > 0]
  71. invalid = pressure_algorithm._dropout_invalid_hours(scoped)
  72. scoped = [
  73. row for row in scoped
  74. if (row["批次"], row["_dt"]) not in invalid
  75. ]
  76. startup_exclusions = paths.PRESSURE_RESULTS / "压力开机恢复剔除小时.csv"
  77. if startup_exclusions.is_file():
  78. excluded = {
  79. (int(row["批次"]), queries.parse_dt(row["小时"]))
  80. for row in windowing.csv_rows(startup_exclusions)
  81. }
  82. scoped = [
  83. row for row in scoped
  84. if (row["批次"], row["_dt"]) not in excluded
  85. ]
  86. windowing.write_hourly(output / "hourly.csv", scoped)
  87. stages = windowing.stage_ranges(paths.PRESSURE_RESULTS / "润滑油压力小时阶段段.csv", "压力阶段")
  88. if args.units:
  89. stages = [stage for stage in stages if stage["unit"] in set(args.units)]
  90. samples = windowing.build_samples(scoped, "pressure", stages, args.window_hours, pressure_features)
  91. counts = windowing.write_samples(output, samples)
  92. print(f"pressure samples: {dict(counts)}")
  93. print(f"小时中间数据:{len(scoped):,} 行")
  94. print(f"样本输出目录:{output}")
  95. if __name__ == "__main__":
  96. main()