| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- #!/usr/bin/env python3
- """Extract per-cycle indicator-diagram features from the wave database (read-only).
- Fetches a *small, sampled* set of running cycles (wave_sample_one holds one first
- cycle per wave_file) for the chosen cylinder points and time range, computes the
- phase-level features in indicator_features.py, and writes one CSV row per cycle.
- Sampling keeps the query volume low: at most --per-day files per point per day,
- picked evenly across that day's running files.
- """
- from __future__ import annotations
- import argparse
- import csv
- import sys
- from collections import defaultdict
- from datetime import datetime
- from pathlib import Path
- import numpy as np
- import pymysql
- BACKEND = Path(__file__).resolve().parents[1]
- sys.path.insert(0, str(BACKEND))
- from app.config import settings # noqa: E402
- from indicator_features.indicator_features import ( # noqa: E402
- CYLINDER_STAGE,
- cycle_features,
- )
- CYLINDERS = ("一", "二", "三", "四", "五", "六")
- SIDES = ("盖侧", "轴侧")
- def connect() -> pymysql.connections.Connection:
- return pymysql.connect(
- host=settings.db_host, port=settings.db_port, user=settings.db_user,
- password=settings.db_password, database=settings.db_name, charset="utf8mb4",
- cursorclass=pymysql.cursors.DictCursor, connect_timeout=8, read_timeout=300,
- )
- def point_name(unit: str, cylinder: str, side: str) -> str:
- return f"{unit}号机组{cylinder}缸压力{side}"
- def fetch_file_rows(connection, point: str, start: str, end: str) -> list[dict]:
- with connection.cursor() as cursor:
- cursor.execute(
- """
- SELECT id, sample_time, rpm
- FROM wave_file
- WHERE point_name = %s AND measurement_type = '压力'
- AND sample_time >= %s AND sample_time < %s
- ORDER BY sample_time
- """,
- (point, start, end),
- )
- return list(cursor.fetchall())
- def sample_per_day(rows: list[dict], per_day: int) -> list[dict]:
- if per_day <= 0:
- return rows
- by_day: dict = defaultdict(list)
- for row in rows:
- by_day[row["sample_time"].date()].append(row)
- picked: list[dict] = []
- for day in sorted(by_day):
- day_rows = by_day[day]
- if len(day_rows) <= per_day:
- picked.extend(day_rows)
- continue
- idx = np.linspace(0, len(day_rows) - 1, per_day).round().astype(int)
- picked.extend(day_rows[int(i)] for i in idx)
- return picked
- def fetch_cycles(connection, file_ids: list[int], batch: int) -> dict[int, np.ndarray]:
- cycles: dict[int, list[tuple[int, float]]] = defaultdict(list)
- with connection.cursor() as cursor:
- for start in range(0, len(file_ids), batch):
- chunk = file_ids[start:start + batch]
- placeholders = ", ".join(["%s"] * len(chunk))
- cursor.execute(
- f"""
- SELECT wave_file_id, sample_index, CAST(signal_value AS FLOAT) AS sig
- FROM wave_sample_one
- WHERE wave_file_id IN ({placeholders})
- ORDER BY wave_file_id, sample_index
- """,
- tuple(chunk),
- )
- for row in cursor.fetchall():
- if row["sig"] is None:
- continue
- cycles[int(row["wave_file_id"])].append((int(row["sample_index"]), float(row["sig"])))
- result = {}
- for file_id, rows in cycles.items():
- rows.sort()
- result[file_id] = np.asarray([value for _, value in rows], dtype=float)
- return result
- def main() -> None:
- parser = argparse.ArgumentParser(description="只读抽取功图单周期特征(抽样)")
- parser.add_argument("--unit", required=True, choices=("7", "8", "9"))
- parser.add_argument("--cylinders", nargs="+", default=list(CYLINDERS), choices=CYLINDERS)
- parser.add_argument("--sides", nargs="+", default=list(SIDES), choices=SIDES)
- parser.add_argument("--start", required=True, help="YYYY-MM-DD")
- parser.add_argument("--end", required=True, help="YYYY-MM-DD(不含)")
- parser.add_argument("--per-day", type=int, default=3, help="每测点每天最多取几个文件")
- parser.add_argument("--batch", type=int, default=200, help="IN 查询批大小")
- parser.add_argument("--output", required=True)
- args = parser.parse_args()
- connection = connect()
- rows_out: list[dict] = []
- try:
- for cylinder in args.cylinders:
- for side in args.sides:
- point = point_name(args.unit, cylinder, side)
- files = fetch_file_rows(connection, point, args.start, args.end)
- picked = sample_per_day(files, args.per_day)
- print(f"{point}: 运行文件 {len(files)},抽样 {len(picked)}", flush=True)
- if not picked:
- continue
- id_to_time = {int(r["id"]): r for r in picked}
- cycles = fetch_cycles(connection, list(id_to_time), args.batch)
- for file_id, signal in cycles.items():
- feats = cycle_features(signal, cylinder)
- if not feats:
- continue
- meta = id_to_time[file_id]
- rows_out.append({
- "unit": args.unit,
- "cylinder": cylinder,
- "stage": CYLINDER_STAGE[cylinder],
- "side": side,
- "point_name": point,
- "wave_file_id": file_id,
- "sample_time": str(meta["sample_time"]),
- "rpm": float(meta["rpm"] or 0.0),
- **feats,
- })
- finally:
- connection.close()
- if not rows_out:
- print("没有特征行,退出")
- return
- rows_out.sort(key=lambda r: (r["cylinder"], r["side"], r["sample_time"]))
- output = Path(args.output)
- output.parent.mkdir(parents=True, exist_ok=True)
- with output.open("w", encoding="utf-8-sig", newline="") as handle:
- writer = csv.DictWriter(handle, fieldnames=list(rows_out[0].keys()))
- writer.writeheader()
- writer.writerows(rows_out)
- print(f"特征行 {len(rows_out):,} -> {output}")
- print(f"时间:{datetime.now():%Y-%m-%d %H:%M:%S}")
- if __name__ == "__main__":
- main()
|