| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- """Import algorithm stage labels into compressor_label.
- Phase 2: algorithm labels are written as segment records. Manual rows are
- never overwritten by a re-import.
- """
- from __future__ import annotations
- import argparse
- import csv
- import hashlib
- import json
- import re
- from datetime import datetime, timedelta
- from pathlib import Path
- from typing import Any
- from app.db import ensure_label_table, get_connection
- ROOT = Path(__file__).resolve().parents[1]
- VIBRATION_CSV = ROOT / "models" / "datas" / "reference" / "振动异常段.csv"
- OIL_CSV = ROOT / "models" / "datas" / "reference" / "润滑油压力小时阶段段.csv"
- TEMPERATURE_CSV = ROOT / "_tmp_temperature" / "out" / "温度异常段.csv"
- SEVERITY = {"轻微": 1, "异常": 2, "严重异常": 3}
- VIBRATION_SIDE_TO_METRIC = {"联轴器端": "YSJ_10", "链轮端": "YSJ_11"}
- INSERT_SQL = """
- INSERT INTO compressor_label (
- source_type, origin, target_type, target_id, target_key, channel_key, segment_key,
- unit, device_code, device_part, device_point, metric_key, metric_label,
- start_time, end_time, algorithm_start, algorithm_end,
- label_type, label_subtype, severity, confirm_status, usable_for_train,
- source_file, source_row, algorithm_version, features_json
- ) VALUES (
- %s, %s, %s, %s, %s, %s, %s,
- %s, %s, %s, %s, %s, %s,
- %s, %s, %s, %s,
- %s, %s, %s, %s, %s,
- %s, %s, %s, %s
- )
- ON DUPLICATE KEY UPDATE
- label_type = IF(compressor_label.origin = 'manual', compressor_label.label_type, VALUES(label_type)),
- label_subtype = IF(compressor_label.origin = 'manual', compressor_label.label_subtype, VALUES(label_subtype)),
- severity = IF(compressor_label.origin = 'manual', compressor_label.severity, VALUES(severity)),
- confirm_status = IF(compressor_label.origin = 'manual', compressor_label.confirm_status, VALUES(confirm_status)),
- usable_for_train = IF(compressor_label.origin = 'manual', compressor_label.usable_for_train, VALUES(usable_for_train)),
- features_json = IF(compressor_label.origin = 'manual', compressor_label.features_json, VALUES(features_json)),
- updated_at = CURRENT_TIMESTAMP
- """
- def read_csv(path: Path) -> list[dict[str, str]]:
- if not path.exists():
- print(f"跳过:文件不存在 {path}")
- return []
- with path.open("r", encoding="utf-8-sig", newline="") as handle:
- return list(csv.DictReader(handle))
- def parse_unit(raw: str) -> str | None:
- match = re.match(r"\s*(\d+)\s*号机", raw or "")
- return match.group(1) if match else None
- def parse_time(raw: str) -> datetime | None:
- text = (raw or "").strip()
- if not text:
- return None
- for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d"):
- try:
- return datetime.strptime(text, fmt)
- except ValueError:
- continue
- return None
- def time_text(value: datetime) -> str:
- return value.strftime("%Y-%m-%d %H:%M:%S")
- def metric_label(site_points: dict[str, str], unit: str, metric: str) -> str:
- return site_points.get(f"YSJ{unit}_{metric.split('_')[-1]}", metric)
- def confirm_status(row: dict[str, str]) -> str:
- return "已确认" if (row.get("是否形成确认等级") or "").strip() == "是" else "未确认"
- def make_segment(
- *,
- source_type: str,
- unit: str,
- metric: str,
- start: datetime,
- end: datetime,
- label_type: str,
- label_subtype: str | None,
- row: dict[str, str],
- source_file: str,
- site_points: dict[str, str],
- ) -> tuple[Any, ...]:
- device_code = f"{unit}#"
- target_key = f"pks:{unit}:{device_code}:{metric}"
- source_row = (row.get("周期编号") or "").strip()
- segment_key = hashlib.md5(
- f"{source_type}|{unit}|{metric}|{source_row}|{time_text(start)}|{time_text(end)}".encode("utf-8")
- ).hexdigest()
- label = metric_label(site_points, unit, metric)
- return (
- source_type,
- "algorithm",
- "pks",
- None,
- target_key,
- target_key,
- segment_key,
- unit,
- device_code,
- (row.get("机组") or "").strip(),
- label,
- metric,
- label,
- time_text(start),
- time_text(end),
- time_text(start),
- time_text(end),
- label_type,
- label_subtype,
- SEVERITY.get(label_type),
- confirm_status(row),
- 1,
- source_file,
- source_row,
- "csv-v1",
- json.dumps(row, ensure_ascii=False),
- )
- def build_rows(site_points: dict[str, str]) -> list[tuple[Any, ...]]:
- rows: list[tuple[Any, ...]] = []
- for row in read_csv(VIBRATION_CSV):
- unit = parse_unit(row.get("机组", ""))
- start = parse_time(row.get("开始小时", ""))
- end = parse_time(row.get("结束小时", ""))
- side = (row.get("最强异常侧") or "").strip()
- metric = VIBRATION_SIDE_TO_METRIC.get(side)
- if not unit or not start or not end or not metric:
- continue
- rows.append(
- make_segment(
- source_type="vibration",
- unit=unit,
- metric=metric,
- start=start,
- end=end + timedelta(hours=1),
- label_type=(row.get("振动阶段") or "").strip(),
- label_subtype=side or None,
- row=row,
- source_file=str(VIBRATION_CSV.relative_to(ROOT)),
- site_points=site_points,
- )
- )
- for row in read_csv(OIL_CSV):
- unit = parse_unit(row.get("机组", ""))
- start = parse_time(row.get("开始小时", ""))
- end = parse_time(row.get("结束小时", ""))
- if not unit or not start or not end:
- continue
- metric = "YSJ_5" if unit in {"7", "8", "9"} else "YSJ_1"
- rows.append(
- make_segment(
- source_type="oil_pressure",
- unit=unit,
- metric=metric,
- start=start,
- end=end + timedelta(hours=1),
- label_type=(row.get("压力阶段") or "").strip(),
- label_subtype="润滑油压力",
- row=row,
- source_file=str(OIL_CSV.relative_to(ROOT)),
- site_points=site_points,
- )
- )
- for row in read_csv(TEMPERATURE_CSV):
- unit = parse_unit(row.get("机组", ""))
- start = parse_time(row.get("开始小时", ""))
- end = parse_time(row.get("结束小时", ""))
- metric = (row.get("阶段点位") or "").strip()
- if not unit or not start or not end or not re.fullmatch(r"YSJ_\d+", metric):
- continue
- rows.append(
- make_segment(
- source_type="temperature",
- unit=unit,
- metric=metric,
- start=start,
- end=end + timedelta(hours=1),
- label_type=(row.get("温度阶段") or "").strip(),
- label_subtype=(row.get("阶段部位") or row.get("特征类型") or "").strip() or None,
- row=row,
- source_file=str(TEMPERATURE_CSV.relative_to(ROOT)),
- site_points=site_points,
- )
- )
- return [row for row in rows if row[17] in SEVERITY]
- def load_site_points() -> dict[str, str]:
- with get_connection() as connection:
- with connection.cursor() as cursor:
- cursor.execute("SELECT ItemName, ItemDescription FROM site_point")
- return {str(row["ItemName"]): str(row["ItemDescription"] or "") for row in cursor.fetchall()}
- def main() -> None:
- parser = argparse.ArgumentParser(description="导入算法阶段标签到 compressor_label")
- parser.add_argument("--dry-run", action="store_true", help="只统计,不写库")
- args = parser.parse_args()
- ensure_label_table()
- site_points = load_site_points()
- rows = build_rows(site_points)
- counts: dict[str, int] = {}
- for row in rows:
- counts[row[0]] = counts.get(row[0], 0) + 1
- print(f"待导入 {len(rows)} 条:{counts}")
- if args.dry_run:
- return
- with get_connection() as connection:
- with connection.cursor() as cursor:
- cursor.executemany(INSERT_SQL, rows)
- cursor.execute(
- "SELECT source_type, COUNT(*) AS n FROM compressor_label "
- "WHERE origin = 'algorithm' GROUP BY source_type ORDER BY source_type"
- )
- print("库内算法标签:", {row["source_type"]: int(row["n"]) for row in cursor.fetchall()})
- if __name__ == "__main__":
- main()
|