Pārlūkot izejas kodu

修改保存算分数据清洗问题

袁威 2 nedēļas atpakaļ
vecāks
revīzija
e96d803cb7
1 mainītis faili ar 95 papildinājumiem un 15 dzēšanām
  1. 95 15
      app/api/formate_xy.py

+ 95 - 15
app/api/formate_xy.py

@@ -32,6 +32,81 @@ class QueryMode(str, Enum):
     prev = "prev"
 
 
+def _is_center_box_shapes_empty(center_result: dict) -> bool:
+    """
+    判断 center_result 中 inner/outer box 的 shapes 是否都为空。
+    前端某些场景会传空 shapes,直接下发给算分服务可能触发其内部越界。
+    """
+    if not isinstance(center_result, dict):
+        return True
+    box_result = center_result.get("box_result", {})
+    if not isinstance(box_result, dict):
+        return True
+
+    inner_shapes = box_result.get("inner_box", {}).get("shapes", [])
+    outer_shapes = box_result.get("outer_box", {}).get("shapes", [])
+    return not inner_shapes and not outer_shapes
+
+
+def _prepare_recalculate_payload(edited_json: dict, source_json: dict) -> dict:
+    """
+    以数据库里的原始 JSON 为底稿,合并前端编辑结果,得到更稳定的重算入参。
+    当前仅明确覆盖 defects;center_result 只有在前端传了非空 shapes 时才覆盖。
+    """
+    base = copy.deepcopy(source_json) if isinstance(source_json, dict) else {}
+    incoming = edited_json if isinstance(edited_json, dict) else {}
+
+    if "id" in incoming:
+        base["id"] = incoming["id"]
+    if "imageWidth" in incoming:
+        base["imageWidth"] = incoming["imageWidth"]
+    if "imageHeight" in incoming:
+        base["imageHeight"] = incoming["imageHeight"]
+
+    base.setdefault("result", {})
+    incoming_result = incoming.get("result", {})
+    if not isinstance(incoming_result, dict):
+        incoming_result = {}
+
+    # defects 使用前端编辑结果覆盖
+    incoming_defects = (
+        incoming_result.get("defect_result", {}).get("defects", [])
+        if isinstance(incoming_result.get("defect_result", {}), dict)
+        else []
+    )
+    base["result"].setdefault("defect_result", {})
+    base["result"]["defect_result"]["defects"] = incoming_defects if isinstance(incoming_defects, list) else []
+
+    # center_result 仅在前端有有效 shapes 时覆盖;否则沿用底稿
+    incoming_center = incoming_result.get("center_result")
+    if isinstance(incoming_center, dict) and not _is_center_box_shapes_empty(incoming_center):
+        base["result"]["center_result"] = incoming_center
+    elif "center_result" not in base["result"]:
+        base["result"]["center_result"] = incoming_center if isinstance(incoming_center, dict) else {}
+
+    return base
+
+
+def _sanitize_defects_for_recalculate(defects: list):
+    """
+    清理前端展示/编辑辅助字段,减少算分服务解析失败概率。
+    """
+    if not isinstance(defects, list):
+        return
+    for d in defects:
+        if not isinstance(d, dict):
+            continue
+        if d.get("label") == "slight_scratch":
+            d["label"] = "scratch"
+        d.pop("defectImgUrl", None)
+        d.pop("defectImgUrls", None)
+        d.pop("gray_id", None)
+        d.pop("fusion_id", None)
+        d.pop("edit_type", None)
+        d.pop("severity_level", None)
+        d.pop("new_score", None)
+
+
 def _process_defects_for_json(card_id: int, img_id: int, img_path: str, json_data: dict, side: str, all_images: list = None):
     if not json_data or "result" not in json_data:
         return
@@ -255,24 +330,18 @@ async def update_image_modified_json(
     cursor = None
 
     # *** 1. 格式还原 ***
-    # 将前端的 xy dict 格式转回 [[x,y]],并丢弃 points 里的 id
+    # 将前端的 xy dict 格式转回 [[x,y]]
     internal_json_payload = convert_xy_to_internal_format(new_json_data)
-    
-    # 丢弃前端展示用的辅助字段,防止传给算分服务导致报错
-    _defects = internal_json_payload.get("result", {}).get("defect_result", {}).get("defects", [])
-    for d in _defects:
-        if d.get("label") == "slight_scratch":
-            d["label"] = "scratch"
-        d.pop("defectImgUrl", None)
-        d.pop("defectImgUrls", None)
-        d.pop("gray_id", None)
-        d.pop("fusion_id", None)
 
     try:
         cursor = db_conn.cursor(dictionary=True)
 
         # 2. 获取图片信息
-        cursor.execute(f"SELECT image_type, card_id FROM {settings.DB_IMAGE_TABLE_NAME} WHERE id = %s", (id,))
+        cursor.execute(
+            f"SELECT image_type, card_id, detection_json, modified_json "
+            f"FROM {settings.DB_IMAGE_TABLE_NAME} WHERE id = %s",
+            (id,)
+        )
         row = cursor.fetchone()
         if not row:
             raise HTTPException(status_code=404, detail=f"ID为 {id} 的图片未找到。")
@@ -293,15 +362,26 @@ async def update_image_modified_json(
         if not score_type:
             raise HTTPException(status_code=400, detail=f"未知的 image_type: {image_type}")
 
+        # 3. 准备重算 payload:以库内原始 JSON 为底稿,仅覆盖编辑后的 defects
+        source_json_str = row["modified_json"] if row["modified_json"] else row["detection_json"]
+        if isinstance(source_json_str, str):
+            source_json_data = json.loads(source_json_str)
+        else:
+            source_json_data = source_json_str if isinstance(source_json_str, dict) else {}
+
+        payload_for_recalculate = _prepare_recalculate_payload(internal_json_payload, source_json_data)
+        _defects = payload_for_recalculate.get("result", {}).get("defect_result", {}).get("defects", [])
+        _sanitize_defects_for_recalculate(_defects)
+
         logger.info(f"开始计算分数 (ID: {id}, Type: {score_type})")
 
-        # 3. 调用远程计算接口 (使用还原后的 JSON)
+        # 4. 调用远程计算接口
         try:
             response = await run_in_threadpool(
                 lambda: requests.post(
                     settings.SCORE_RECALCULATE_ENDPOINT,
                     params={"score_type": score_type},
-                    json=internal_json_payload,  # 传递还原后的数据
+                    json=payload_for_recalculate,
                     timeout=20
                 )
             )
@@ -317,7 +397,7 @@ async def update_image_modified_json(
         # 获取计算服务返回的结果(这个结果通常已经是标准的 internal 格式,带有分数和面积)
         final_json_data = response.json()
 
-        # 4. 保存结果到数据库
+        # 5. 保存结果到数据库
         recalculated_json_str = json.dumps(final_json_data, ensure_ascii=False)
         update_query = (f"UPDATE {settings.DB_IMAGE_TABLE_NAME} "
                         f"SET modified_json = %s, is_edited = TRUE "