fault_map_service.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. """故障 → 3D 部件对照表(data/fault_part_map.csv 的读写)。
  2. 列:model, alarm_type, device_point, device_part, part, camera_x/y/z, target_x/y/z
  3. - device_point / device_part 支持多值,用 ``|`` 分隔(未来一个故障多个点)。
  4. - part 为前端部件标签(createUniqueName 约定:如 ``三维立体_1687``);一个部件一行。
  5. - camera_/target_ 为该故障的“场景位置”(参考系坐标,空=未记录)。
  6. """
  7. import csv
  8. import os
  9. from typing import Any
  10. from config import DATA_DIR, FAULT_MAP_CSV
  11. FIELDS = [
  12. "model", "alarm_type", "device_point", "device_part", "part",
  13. "camera_x", "camera_y", "camera_z", "target_x", "target_y", "target_z",
  14. ]
  15. # 初始种子(一个部件一行,场景位置留空)。
  16. SEED: list[list[str]] = [
  17. ["压缩机2326-3000-one.glb", "润滑油压力故障", "YSJ_5", "润滑油压力", "三维立体_1687"],
  18. ["压缩机2326-3000-one.glb", "振动故障", "YSJ_11", "链轮端振动", "三维立体_3815"],
  19. ["压缩机2326-3000-one.glb", "振动故障", "YSJ_11", "链轮端振动", "三维立体_3814"],
  20. ["压缩机2326-3000-one.glb", "振动故障", "YSJ_10", "联轴器端振动", "三维立体_101"],
  21. ["压缩机2326-3000-one.glb", "振动故障", "YSJ_10", "联轴器端振动", "三维立体_97"],
  22. ["压缩机2326-3000-one.glb", "振动故障", "YSJ_10", "联轴器端振动", "三维立体_98"],
  23. ["压缩机2326-3000-one.glb", "振动故障", "YSJ_10", "联轴器端振动", "三维立体_130"],
  24. ["压缩机2326-3000-one.glb", "振动故障", "YSJ_10", "联轴器端振动", "三维立体_135"],
  25. ["压缩机2326-3000-one.glb", "振动故障", "YSJ_10", "联轴器端振动", "三维立体_134"],
  26. ["压缩机2326-3000-one.glb", "振动故障", "YSJ_10", "联轴器端振动", "三维立体_138"],
  27. ["压缩机2326-3000-one.glb", "振动故障", "YSJ_10", "联轴器端振动", "三维立体_3813"],
  28. ]
  29. def _ensure_file() -> None:
  30. if FAULT_MAP_CSV.exists():
  31. return
  32. DATA_DIR.mkdir(parents=True, exist_ok=True)
  33. with FAULT_MAP_CSV.open("w", encoding="utf-8", newline="") as handle:
  34. writer = csv.writer(handle)
  35. writer.writerow(FIELDS)
  36. for row in SEED:
  37. writer.writerow(row + [""] * 6)
  38. def _read() -> list[dict[str, str]]:
  39. _ensure_file()
  40. with FAULT_MAP_CSV.open("r", encoding="utf-8", newline="") as handle:
  41. return [
  42. {key: (row.get(key) or "").strip() for key in FIELDS}
  43. for row in csv.DictReader(handle)
  44. ]
  45. def _write(rows: list[dict[str, str]]) -> None:
  46. DATA_DIR.mkdir(parents=True, exist_ok=True)
  47. tmp = FAULT_MAP_CSV.parent / (FAULT_MAP_CSV.name + ".tmp")
  48. with tmp.open("w", encoding="utf-8", newline="") as handle:
  49. writer = csv.writer(handle)
  50. writer.writerow(FIELDS)
  51. for row in rows:
  52. writer.writerow([row.get(key, "") for key in FIELDS])
  53. os.replace(tmp, FAULT_MAP_CSV)
  54. def _split(value: str) -> list[str]:
  55. return [item.strip() for item in str(value or "").split("|") if item.strip()]
  56. def _vector(row: dict[str, str], x: str, y: str, z: str) -> list[float] | None:
  57. try:
  58. return [float(row[x]), float(row[y]), float(row[z])]
  59. except (ValueError, KeyError):
  60. return None
  61. def _to_api(row: dict[str, str]) -> dict[str, Any]:
  62. return {
  63. "model": row["model"],
  64. "alarm_type": row["alarm_type"],
  65. "device_point": row["device_point"],
  66. "device_part": row["device_part"],
  67. "part": row["part"],
  68. "camera": _vector(row, "camera_x", "camera_y", "camera_z"),
  69. "target": _vector(row, "target_x", "target_y", "target_z"),
  70. }
  71. def list_rows() -> list[dict[str, Any]]:
  72. return [_to_api(row) for row in _read() if row.get("part")]
  73. def remap_parts(model: str, mapping: dict[str, str]) -> int:
  74. """按 旧label→新label 映射更新指定模型的 part 列,返回改动行数。"""
  75. if not mapping:
  76. return 0
  77. rows = _read()
  78. changed = 0
  79. for row in rows:
  80. if row["model"] != model:
  81. continue
  82. part = row.get("part", "")
  83. if part in mapping:
  84. row["part"] = mapping[part]
  85. changed += 1
  86. if changed:
  87. _write(rows)
  88. return changed
  89. def set_scene_position(
  90. model: str,
  91. alarm_type: str,
  92. device_point: str,
  93. camera: list[float],
  94. target: list[float],
  95. ) -> int:
  96. rows = _read()
  97. wanted = set(_split(device_point))
  98. changed = 0
  99. for row in rows:
  100. if row["model"] != model or row["alarm_type"] != alarm_type:
  101. continue
  102. row_points = set(_split(row["device_point"]))
  103. if wanted and not (wanted & row_points):
  104. continue
  105. row["camera_x"], row["camera_y"], row["camera_z"] = (
  106. str(camera[0]), str(camera[1]), str(camera[2]),
  107. )
  108. row["target_x"], row["target_y"], row["target_z"] = (
  109. str(target[0]), str(target[1]), str(target[2]),
  110. )
  111. changed += 1
  112. if changed:
  113. _write(rows)
  114. return changed