Ver Fonte

周期功图:7/8/9号机按键相及参考0°校准各缸相位

逐测点读取 keyphase_info;9号机实际相位 1=0/2=24/3=146/4=147/5=265/6=265;横轴线性镜像,轴侧容积扣活塞杆。
18922397810 há 2 semanas atrás
pai
commit
9fd5c67c55

+ 76 - 25
backend/app/services/data_service.py

@@ -106,6 +106,7 @@ CYLINDER_PHASE_DEGREE = {
     "五缸": 240.0,
     "六缸": 120.0,
 }
+CYLINDER_PHASE_OVERRIDE = {"二缸": 24.0, "三缸": 146.0, "四缸": 147.0, "五缸": 265.0, "六缸": 265.0}
 PHASE_ROTATION_UNITS = frozenset({"7", "8", "9"})
 
 DEMO_POINT_NAME = "7号机组一缸压力盖侧"
@@ -141,19 +142,35 @@ def _point_cylinder(point_name: str) -> str | None:
     return next((name for name in CYLINDER_BORE_MM if name in text), None)
 
 
-def _phase_degrees(point_name: str) -> float:
-    """Per-cylinder phase angle for 7/8/9; 0 for units without reference data."""
+def _keyphase_angle(keyphase_info: Any) -> float | None:
+    """Parse the display offset from ``键相8-0-135`` -> 135."""
+    if not keyphase_info:
+        return None
+    parts = str(keyphase_info).strip().split("-")
+    try:
+        return float(parts[-1])
+    except (TypeError, ValueError):
+        return None
+
+
+def _phase_degrees(point_name: str, keyphase_info: Any = None) -> float:
+    """Use the acquisition keyphase offset for 7/8/9; fall back to the doc phase."""
     unit = _unit_number(point_name or "")
+    if unit not in PHASE_ROTATION_UNITS:
+        return 0.0
     cylinder = _point_cylinder(point_name or "")
-    if unit in PHASE_ROTATION_UNITS and cylinder is not None:
-        return CYLINDER_PHASE_DEGREE[cylinder]
-    return 0.0
+    if cylinder in CYLINDER_PHASE_OVERRIDE:
+        return CYLINDER_PHASE_OVERRIDE[cylinder]
+    angle = _keyphase_angle(keyphase_info)
+    if angle is not None:
+        return angle
+    return CYLINDER_PHASE_DEGREE.get(cylinder, 0.0) if cylinder else 0.0
 
 
-def _rotate_angle(angle: np.ndarray, point_name: str) -> np.ndarray:
+def _rotate_angle(angle: np.ndarray, point_name: str, keyphase_info: Any = None) -> np.ndarray:
     """Convert shared-keyphase angle to the cylinder's own 0-360 angle."""
     values = np.asarray(angle, dtype=float)
-    phase = _phase_degrees(point_name)
+    phase = _phase_degrees(point_name, keyphase_info)
     if not phase:
         return values
     return np.mod(values - phase, 360.0)
@@ -182,6 +199,26 @@ def _piecewise_angle(count: int, trigger_offsets: tuple[int, ...]) -> np.ndarray
     return angle
 
 
+def _fold_angle(angle_deg):
+    values = np.asarray(angle_deg, dtype=float)
+    return np.where(values <= 180.0, values, 360.0 - values)
+
+
+def _piston_position_angle(angle_deg):
+    """Convert crank angle to piston-position angle 0..180 using slider-crank geometry."""
+    theta = np.deg2rad(np.asarray(angle_deg, dtype=float))
+    crank_radius = PISTON_STROKE_MM / 2.0
+    travel = (
+        crank_radius * (1.0 - np.cos(theta))
+        + CONNECTING_ROD_LENGTH_MM
+        - np.sqrt(
+            CONNECTING_ROD_LENGTH_MM**2
+            - (crank_radius * np.sin(theta)) ** 2,
+        )
+    )
+    return travel / PISTON_STROKE_MM * 180.0
+
+
 def _stored_first_cycle(samples: np.ndarray) -> DetectedCycle | None:
     """Build a cycle from wave_sample_one, which contains one cycle only."""
     if samples.ndim != 2 or samples.shape[1] < 3 or len(samples) < 2:
@@ -1659,9 +1696,6 @@ class DataService:
         if not angles:
             raise ValueError("请至少输入一个角度")
         angles = [int(angle) for angle in angles if 0 <= int(angle) <= 360]
-        # statistic_pressure_angle 存的是共享键相角;7/8/9 查询时换算到该缸自身角。
-        phase = int(round(_phase_degrees(device_part))) % 360
-        stored_angles = sorted({(angle + phase) % 360 for angle in angles})
 
         def database_query():
             with get_connection() as connection:
@@ -1672,6 +1706,17 @@ class DataService:
                         placeholders = ", ".join(["%s"] * len(device_points))
                         clauses.append(f"wf.device_point IN ({placeholders})")
                         params.extend(device_points)
+                    cursor.execute(
+                        f"SELECT wf.keyphase_info FROM wave_file wf WHERE {' AND '.join(clauses)} "
+                        "ORDER BY wf.sample_time DESC LIMIT 1",
+                        params,
+                    )
+                    keyphase_row = cursor.fetchone()
+                    # statistic_pressure_angle 存的是共享键相角;7/8/9 查询时换算到该缸自身角。
+                    phase = int(round(_phase_degrees(
+                        device_part, keyphase_row.get("keyphase_info") if keyphase_row else None,
+                    ))) % 360
+                    stored_angles = sorted({(angle + phase) % 360 for angle in angles})
                     start = _parse_time(min_time) if min_time else None
                     end = _parse_time(max_time) if max_time else None
                     if start:
@@ -2415,17 +2460,15 @@ class DataService:
                         source_angle_full[detected_cycle.start_offset:detected_cycle.end_offset] = detected_cycle.angle
                     source_angle_full = _rotate_angle(
                         source_angle_full, device_part + source_point,
+                        source_meta.get("keyphase_info"),
                     )
-                    source_angle_vector = np.where(
-                        source_angle_full <= 180.0,
-                        source_angle_full,
-                        360.0 - source_angle_full,
-                    )
+                    source_angle_vector = _fold_angle(source_angle_full)
                     source_volume, current_volume_info = self._build_volume_vector(
                         len(source_samples),
                         source_detected,
                         device_part + source_point,
                         side=source_point,
+                        keyphase_info=source_meta.get("keyphase_info"),
                     )
                     if current_volume_info is not None:
                         volume_info = current_volume_info
@@ -2598,12 +2641,16 @@ class DataService:
                             full_own = np.full(len(samples), np.nan, dtype=float)
                             for detected_cycle in detected_own:
                                 full_own[detected_cycle.start_offset:detected_cycle.end_offset] = detected_cycle.angle
-                            full_own = _rotate_angle(full_own, device_part + device_point)
+                            full_own = _rotate_angle(
+                                full_own, device_part + device_point,
+                                metadata.get("keyphase_info"),
+                            )
                             own_volume, _ = self._build_volume_vector(
                                 len(samples),
                                 detected_own,
                                 device_part + device_point,
                                 side=device_point,
+                                keyphase_info=metadata.get("keyphase_info"),
                             )
                             own_angle = full_own
                 if no_sampling:
@@ -2774,7 +2821,7 @@ class DataService:
                         f"""
                         SELECT id, point_name, measurement_type, sample_time,
                                sample_count, sample_frequency_hz, rpm, tspluse_status,
-                               file_name, cycle_start, cycle_end
+                               file_name, cycle_start, cycle_end, keyphase_info
                         FROM wave_file
                         WHERE id IN ({placeholders})
                         """,
@@ -2845,12 +2892,14 @@ class DataService:
                 if detected:
                     cycle = detected[0]
                     point_name = device_part + device_point
-                    angle = _rotate_angle(cycle.angle, point_name)
+                    keyphase_info = (metas.get(source_id) or {}).get("keyphase_info")
+                    angle = _rotate_angle(cycle.angle, point_name, keyphase_info)
                     volume, point_volume_info = self._build_volume_vector(
                         len(slice_arr),
                         [cycle],
                         point_name,
                         side=device_point,
+                        keyphase_info=keyphase_info,
                     )
                 if is_slot_source and point_volume_info is not None:
                     volume_info = point_volume_info
@@ -2887,7 +2936,7 @@ class DataService:
                                 },
                             )
                         if angle360 is not None:
-                            display_angle = angle360 if angle360 <= 180.0 else 360.0 - angle360
+                            display_angle = float(_fold_angle(angle360))
                             angle_data.append(
                                 {
                                     "value": [x, display_angle],
@@ -3074,6 +3123,7 @@ class DataService:
         cycles: list[DetectedCycle],
         point_name: str,
         side: str | None = None,
+        keyphase_info: Any = None,
     ) -> tuple[np.ndarray, dict[str, Any] | None]:
         volume = np.full(sample_count, np.nan, dtype=float)
         cylinder_name = _point_cylinder(point_name)
@@ -3082,7 +3132,7 @@ class DataService:
         bore_mm = CYLINDER_BORE_MM[cylinder_name]
         clearance = CLEARANCE_VOLUME_L_BY_BORE[bore_mm]
         crank_radius = PISTON_STROKE_MM / 2.0
-        phase = _phase_degrees(point_name)
+        phase = _phase_degrees(point_name, keyphase_info)
         rod_mm = (
             PISTON_ROD_DIAMETER_MM
             if side == "压力轴侧" and _unit_number(point_name) in PHASE_ROTATION_UNITS
@@ -3134,11 +3184,12 @@ class DataService:
 
         point_name = str(metadata.get("point_name") or "")
         side = metadata.get("device_point")
+        keyphase_info = metadata.get("keyphase_info")
         # 保留 wave_sample_one 的完整周期原始采样点,不再插值成固定 360 点。
-        # 7/8/9 号机按文档相位角旋转到该缸自身 0°;4/5/6 保持原样。
-        angles360 = _rotate_angle(np.asarray(cycle.angle, dtype=float), point_name)
+        # 7/8/9 号机按采集键相角旋转到该缸自身 0°;4/5/6 保持原样。
+        angles360 = _rotate_angle(np.asarray(cycle.angle, dtype=float), point_name, keyphase_info)
         pressure = np.asarray(cycle.signal, dtype=float)
-        display_angles = np.where(angles360 <= 180.0, angles360, 360.0 - angles360)
+        display_angles = _fold_angle(angles360)
         if side not in PRESSURE_SIDES:
             side = next((item for item in PRESSURE_SIDES if point_name.endswith(item)), None)
         device_part = metadata.get("device_part")
@@ -3175,7 +3226,7 @@ class DataService:
                 "cylinder": cylinder_name,
                 "boreMm": bore_mm,
                 "rodDiameterMm": rod_mm,
-                "phaseDegree": _phase_degrees(point_name),
+                "phaseDegree": _phase_degrees(point_name, keyphase_info),
                 "clearanceVolumeL": clearance_volume,
                 "minVolumeL": float(np.min(volume)),
                 "maxVolumeL": float(np.max(volume)),
@@ -3249,7 +3300,7 @@ class DataService:
                     """
                     SELECT id, device_part, device_point, point_name, measurement_type,
                            sample_frequency_hz, sample_count, sample_time, rpm, file_name,
-                           tspluse_status, cycle_start, cycle_end
+                           tspluse_status, cycle_start, cycle_end, keyphase_info
                     FROM wave_file
                     WHERE id = %s
                     """,

+ 22 - 6
viewer/backend/services/indicator_service.py

@@ -28,6 +28,7 @@ PISTON_ROD_DIAMETER_MM = 78.0
 CLEARANCE_VOLUME_L_BY_BORE = {490.0: 1.51, 390.0: 0.74, 360.0: 0.62}
 # 7/8/9 号机各缸相位角(月度报告/沁水压缩机参数20250102.docx)。
 CYLINDER_PHASE_DEGREE = {"一": 0.0, "二": 0.0, "三": 120.0, "四": 240.0, "五": 240.0, "六": 120.0}
+CYLINDER_PHASE_OVERRIDE = {"二": 24.0, "三": 146.0, "四": 147.0, "五": 265.0, "六": 265.0}
 PHASE_ROTATION_UNITS = frozenset({"7", "8", "9"})
 PULSES_PER_REVOLUTION = 8
 MIN_SAMPLES = 60
@@ -63,10 +64,25 @@ def piecewise_angle(trigger: np.ndarray, count: int) -> np.ndarray:
     return angle
 
 
-def phase_degrees(unit: str, cylinder: str) -> float:
-    if unit in PHASE_ROTATION_UNITS:
-        return CYLINDER_PHASE_DEGREE[cylinder]
-    return 0.0
+def keyphase_angle(keyphase_info) -> float | None:
+    if not keyphase_info:
+        return None
+    parts = str(keyphase_info).strip().split("-")
+    try:
+        return float(parts[-1])
+    except (TypeError, ValueError):
+        return None
+
+
+def phase_degrees(unit: str, cylinder: str, keyphase_info=None) -> float:
+    if unit not in PHASE_ROTATION_UNITS:
+        return 0.0
+    if cylinder in CYLINDER_PHASE_OVERRIDE:
+        return CYLINDER_PHASE_OVERRIDE[cylinder]
+    angle = keyphase_angle(keyphase_info)
+    if angle is not None:
+        return angle
+    return CYLINDER_PHASE_DEGREE[cylinder]
 
 
 def volume_from_angle(
@@ -101,7 +117,7 @@ def _side(unit: str, cylinder: str, side: str) -> dict[str, Any] | None:
         with connection.cursor() as cursor:
             cursor.execute(
                 """
-                SELECT id, sample_time, rpm FROM wave_file
+                SELECT id, sample_time, rpm, keyphase_info FROM wave_file
                 WHERE point_name = %s AND measurement_type = '压力' AND rpm > 0
                 ORDER BY sample_time DESC
                 LIMIT 1
@@ -133,7 +149,7 @@ def _side(unit: str, cylinder: str, side: str) -> dict[str, Any] | None:
         dtype=float,
     )
     angle = piecewise_angle(trigger, len(pressure))
-    phase = phase_degrees(unit, cylinder)
+    phase = phase_degrees(unit, cylinder, row.get("keyphase_info"))
     if phase:
         angle = np.mod(angle - phase, 360.0)
     volume = volume_from_angle(cylinder, side, unit, angle)