scan_all.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. #!/usr/bin/env python3
  2. """Full PKS scan (pressure + vibration) with device-code input.
  3. Modes
  4. -----
  5. * ``--store local`` (default): delete previous intermediate/result files, then
  6. rebuild them under ``models/datas`` / ``models/results``. Output is byte
  7. compatible with the reference CSVs.
  8. * ``--store db``: rebuild intermediate files locally, then replace the
  9. ``compressor_alarm`` rows (pressure + vibration) for the selected devices.
  10. Device input
  11. ------------
  12. ``--device 7# 8# 9#`` (default). Each device resolves to its PKS import
  13. batches; hourly aggregation keeps the original batch grouping so the result
  14. matches the existing ``润滑油压力小时阶段段.csv`` / ``振动异常段.csv``.
  15. Examples
  16. --------
  17. python models/scan_all.py --device 7# 8# 9#
  18. python models/scan_all.py --auto-range --store db
  19. python models/scan_all.py --start "2025-01-01 00:00:00" --end "2026-09-01 00:00:00"
  20. """
  21. from __future__ import annotations
  22. import argparse
  23. import sys
  24. from pathlib import Path
  25. sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
  26. from models.common import alarm_store, db, devices, io, model_predict, paths, queries
  27. from models.generalized import pressure_algorithm
  28. from models.vibration import vibration_algorithm
  29. SCAN_OUTPUT_FILES = {
  30. paths.COMMON_RESULTS: ["报警阈值配置.csv"],
  31. paths.PRESSURE_RESULTS: ["润滑油压力小时等级.csv", "润滑油压力小时阶段段.csv", "润滑油压力演化周期.csv"],
  32. paths.VIBRATION_RESULTS: ["振动异常段.csv"],
  33. }
  34. def clear_dir_files(directory: Path, names: list[str] | None = None) -> int:
  35. """Delete files in a directory; if ``names`` given, only those file names."""
  36. directory = Path(directory)
  37. if not directory.exists():
  38. return 0
  39. removed = 0
  40. targets = [directory / name for name in names] if names is not None else [entry for entry in directory.iterdir() if entry.is_file()]
  41. for entry in targets:
  42. if entry.is_file():
  43. entry.unlink()
  44. removed += 1
  45. return removed
  46. def clean_intermediates() -> dict:
  47. """Remove previous scan intermediate/result files only (keep model outputs)."""
  48. removed = {"hourly": clear_dir_files(paths.HOURLY_DIR)}
  49. for directory, names in SCAN_OUTPUT_FILES.items():
  50. removed[directory.name] = clear_dir_files(directory, names)
  51. return removed
  52. def main() -> None:
  53. parser = argparse.ArgumentParser(description="PKS 压力+振动全量扫描(按设备号)")
  54. parser.add_argument("--device", nargs="+", default=list(devices.DEVICE_CODES), help="压缩机编号,如 7# 8# 9#")
  55. parser.add_argument("--start", default=None)
  56. parser.add_argument("--end", default=None)
  57. parser.add_argument("--auto-range", action="store_true", help="按数据自动确定起止时间(默认在未指定 --start/--end 时启用)")
  58. parser.add_argument("--chunk-days", type=int, default=14)
  59. parser.add_argument("--hourly-csv", default=None, help="复用已落盘的小时中间表,不查询数据库")
  60. parser.add_argument("--store", choices=("local", "db"), default="local")
  61. parser.add_argument("--with-model", action="store_true", help="对故障段做模型滑动预测(默认关闭)")
  62. parser.add_argument("--dry-run", action="store_true", help="只统计不写库(store=db 时)")
  63. args = parser.parse_args()
  64. devices_selected = devices.normalize_devices(args.device)
  65. device_batches = devices.load_device_batches(devices_selected)
  66. unit_by_batch = devices.batch_to_unit(device_batches)
  67. batch_device = devices.batch_to_device(device_batches)
  68. configs = db.load_alarm_config(unit_by_batch)
  69. print(f"设备:{devices_selected}")
  70. print(f"批次映射:{ {d: bs for d, bs in device_batches.items()} }")
  71. rows = None
  72. if args.hourly_csv:
  73. print(f"复用小时中间表:{args.hourly_csv}")
  74. rows = queries.load_hourly_csv(Path(args.hourly_csv))
  75. removed = clean_intermediates()
  76. print(f"已清理中间/结果文件:{removed}")
  77. if args.start and args.end:
  78. start, end = queries.parse_dt(args.start), queries.parse_dt(args.end)
  79. else:
  80. start, end = devices.auto_range(device_batches)
  81. print(f"自动时间范围:{start} .. {end}")
  82. if rows is None:
  83. rows = queries.fetch_all_hourly(start, end, args.chunk_days, unit_by_batch, configs, sorted(unit_by_batch))
  84. queries.write_hourly_csv(paths.HOURLY_FULL, rows)
  85. print(f"小时中间表已缓存:{paths.HOURLY_FULL}")
  86. io.write_thresholds(paths.COMMON_RESULTS / "报警阈值配置.csv", configs)
  87. hourly_levels, hour_segments, cycles = pressure_algorithm.run(rows, configs)
  88. events = vibration_algorithm.run(rows, configs)
  89. print(f"小时特征:{len(rows):,} 行")
  90. print(f"润滑油压力小时等级:{len(hourly_levels):,} 行")
  91. print(f"润滑油压力小时阶段段:{len(hour_segments):,} 段")
  92. print(f"润滑油压力周期:{len(cycles):,} 个")
  93. print(f"振动异常段:{len(events):,} 段")
  94. extra_fields = ()
  95. if args.with_model:
  96. pressure_stats = model_predict.annotate("pressure", rows, hour_segments)
  97. vibration_stats = model_predict.annotate("vibration", rows, events)
  98. print(f"模型预测:压力 {pressure_stats},振动 {vibration_stats}")
  99. extra_fields = tuple(model_predict.MODEL_FIELDS) + (model_predict.NOTE_FIELD,)
  100. pressure_algorithm.write_results(paths.PRESSURE_RESULTS, hourly_levels, hour_segments, cycles, extra_fields=extra_fields)
  101. vibration_algorithm.write_results(paths.VIBRATION_RESULTS, events, extra_fields=extra_fields)
  102. print(f"结果已写入本地:{paths.RESULTS}")
  103. if args.store == "db":
  104. clean_segments = io.clean_rows(hour_segments)
  105. clean_events = io.clean_rows(events)
  106. alarms = alarm_store.build_alarms(clean_segments, clean_events, batch_device)
  107. result = alarm_store.write_full(alarms, devices_selected, start, end, dry_run=args.dry_run)
  108. print(f"数据库写入:{result}")
  109. if __name__ == "__main__":
  110. main()