extract_from_db.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. #!/usr/bin/env python3
  2. """Extract per-cycle indicator-diagram features from the wave database (read-only).
  3. Fetches a *small, sampled* set of running cycles (wave_sample_one holds one first
  4. cycle per wave_file) for the chosen cylinder points and time range, computes the
  5. phase-level features in indicator_features.py, and writes one CSV row per cycle.
  6. Sampling keeps the query volume low: at most --per-day files per point per day,
  7. picked evenly across that day's running files.
  8. """
  9. from __future__ import annotations
  10. import argparse
  11. import csv
  12. import sys
  13. from collections import defaultdict
  14. from datetime import datetime
  15. from pathlib import Path
  16. import numpy as np
  17. import pymysql
  18. BACKEND = Path(__file__).resolve().parents[1]
  19. sys.path.insert(0, str(BACKEND))
  20. from app.config import settings # noqa: E402
  21. from indicator_features.indicator_features import ( # noqa: E402
  22. CYLINDER_STAGE,
  23. cycle_features,
  24. )
  25. CYLINDERS = ("一", "二", "三", "四", "五", "六")
  26. SIDES = ("盖侧", "轴侧")
  27. def connect() -> pymysql.connections.Connection:
  28. return pymysql.connect(
  29. host=settings.db_host, port=settings.db_port, user=settings.db_user,
  30. password=settings.db_password, database=settings.db_name, charset="utf8mb4",
  31. cursorclass=pymysql.cursors.DictCursor, connect_timeout=8, read_timeout=300,
  32. )
  33. def point_name(unit: str, cylinder: str, side: str) -> str:
  34. return f"{unit}号机组{cylinder}缸压力{side}"
  35. def fetch_file_rows(connection, point: str, start: str, end: str) -> list[dict]:
  36. with connection.cursor() as cursor:
  37. cursor.execute(
  38. """
  39. SELECT id, sample_time, rpm
  40. FROM wave_file
  41. WHERE point_name = %s AND measurement_type = '压力'
  42. AND sample_time >= %s AND sample_time < %s
  43. ORDER BY sample_time
  44. """,
  45. (point, start, end),
  46. )
  47. return list(cursor.fetchall())
  48. def sample_per_day(rows: list[dict], per_day: int) -> list[dict]:
  49. if per_day <= 0:
  50. return rows
  51. by_day: dict = defaultdict(list)
  52. for row in rows:
  53. by_day[row["sample_time"].date()].append(row)
  54. picked: list[dict] = []
  55. for day in sorted(by_day):
  56. day_rows = by_day[day]
  57. if len(day_rows) <= per_day:
  58. picked.extend(day_rows)
  59. continue
  60. idx = np.linspace(0, len(day_rows) - 1, per_day).round().astype(int)
  61. picked.extend(day_rows[int(i)] for i in idx)
  62. return picked
  63. def fetch_cycles(connection, file_ids: list[int], batch: int) -> dict[int, np.ndarray]:
  64. cycles: dict[int, list[tuple[int, float]]] = defaultdict(list)
  65. with connection.cursor() as cursor:
  66. for start in range(0, len(file_ids), batch):
  67. chunk = file_ids[start:start + batch]
  68. placeholders = ", ".join(["%s"] * len(chunk))
  69. cursor.execute(
  70. f"""
  71. SELECT wave_file_id, sample_index, CAST(signal_value AS FLOAT) AS sig
  72. FROM wave_sample_one
  73. WHERE wave_file_id IN ({placeholders})
  74. ORDER BY wave_file_id, sample_index
  75. """,
  76. tuple(chunk),
  77. )
  78. for row in cursor.fetchall():
  79. if row["sig"] is None:
  80. continue
  81. cycles[int(row["wave_file_id"])].append((int(row["sample_index"]), float(row["sig"])))
  82. result = {}
  83. for file_id, rows in cycles.items():
  84. rows.sort()
  85. result[file_id] = np.asarray([value for _, value in rows], dtype=float)
  86. return result
  87. def main() -> None:
  88. parser = argparse.ArgumentParser(description="只读抽取功图单周期特征(抽样)")
  89. parser.add_argument("--unit", required=True, choices=("7", "8", "9"))
  90. parser.add_argument("--cylinders", nargs="+", default=list(CYLINDERS), choices=CYLINDERS)
  91. parser.add_argument("--sides", nargs="+", default=list(SIDES), choices=SIDES)
  92. parser.add_argument("--start", required=True, help="YYYY-MM-DD")
  93. parser.add_argument("--end", required=True, help="YYYY-MM-DD(不含)")
  94. parser.add_argument("--per-day", type=int, default=3, help="每测点每天最多取几个文件")
  95. parser.add_argument("--batch", type=int, default=200, help="IN 查询批大小")
  96. parser.add_argument("--output", required=True)
  97. args = parser.parse_args()
  98. connection = connect()
  99. rows_out: list[dict] = []
  100. try:
  101. for cylinder in args.cylinders:
  102. for side in args.sides:
  103. point = point_name(args.unit, cylinder, side)
  104. files = fetch_file_rows(connection, point, args.start, args.end)
  105. picked = sample_per_day(files, args.per_day)
  106. print(f"{point}: 运行文件 {len(files)},抽样 {len(picked)}", flush=True)
  107. if not picked:
  108. continue
  109. id_to_time = {int(r["id"]): r for r in picked}
  110. cycles = fetch_cycles(connection, list(id_to_time), args.batch)
  111. for file_id, signal in cycles.items():
  112. feats = cycle_features(signal, cylinder)
  113. if not feats:
  114. continue
  115. meta = id_to_time[file_id]
  116. rows_out.append({
  117. "unit": args.unit,
  118. "cylinder": cylinder,
  119. "stage": CYLINDER_STAGE[cylinder],
  120. "side": side,
  121. "point_name": point,
  122. "wave_file_id": file_id,
  123. "sample_time": str(meta["sample_time"]),
  124. "rpm": float(meta["rpm"] or 0.0),
  125. **feats,
  126. })
  127. finally:
  128. connection.close()
  129. if not rows_out:
  130. print("没有特征行,退出")
  131. return
  132. rows_out.sort(key=lambda r: (r["cylinder"], r["side"], r["sample_time"]))
  133. output = Path(args.output)
  134. output.parent.mkdir(parents=True, exist_ok=True)
  135. with output.open("w", encoding="utf-8-sig", newline="") as handle:
  136. writer = csv.DictWriter(handle, fieldnames=list(rows_out[0].keys()))
  137. writer.writeheader()
  138. writer.writerows(rows_out)
  139. print(f"特征行 {len(rows_out):,} -> {output}")
  140. print(f"时间:{datetime.now():%Y-%m-%d %H:%M:%S}")
  141. if __name__ == "__main__":
  142. main()