瀏覽代碼

缺陷严重程度计分

AnlaAnla 2 周之前
父節點
當前提交
2b1d9eda3e
共有 5 個文件被更改,包括 630 次插入79 次删除
  1. 17 30
      Test/test01.py
  2. 72 3
      app/api/config_api.py
  3. 154 22
      app/core/scoring_config.json
  4. 335 21
      app/core/基础备份_scoring_config.json
  5. 52 3
      app/utils/score_inference/CardScorer.py

+ 17 - 30
Test/test01.py

@@ -1,32 +1,19 @@
-import cv2
-import numpy as np
+def _get_severity_coefficient(self, severity_type_key: str, level_name: str) -> float:
+    """
+    从配置中获取严重程度对应的系数
+    :param severity_type_key: 对应 severity_level 下的键,如 "wear", "stain"
+    :param level_name: 具体的等级名,如 "一般", "严重"
+    :return: 系数 value
+    """
+    severity_config = self.config.get("severity_level", {})
+    rules = severity_config.get(severity_type_key, [])
 
-data = np.array([
-    [
-        282,
-        316
-    ],
-    [
-        2593,
-        316
-    ],
-    [
-        2593,
-        3601
-    ],
-    [
-        282,
-        3601
-    ]
-])
+    # 默认系数为 1.0 (如果找不到配置)
+    default_val = 1.0
 
-ix, iy, iw, ih = cv2.boundingRect(data)
-
-
-def get_box_corners(x, y, w, h):
-    return [[x, y], [x + w, y], [x + w, y + h], [x, y + h]]
-
-
-print(ix, iy, iw, ih)
-inner_box_corners_int = get_box_corners(ix, iy, iw, ih)
-print(inner_box_corners_int)
+    # 寻找匹配的 name
+    for rule in rules:
+        if rule.get("name") == level_name:
+            return float(rule.get("value", 1.0))
+    # 如果传入了"一般"但配置里没"一般",则返回 1.0
+    return default_val

+ 72 - 3
app/api/config_api.py

@@ -1,6 +1,6 @@
 import os.path
 
-from fastapi import APIRouter, File, Body, HTTPException, status
+from fastapi import APIRouter, File, Body, HTTPException, status, Query
 from fastapi.responses import FileResponse, JSONResponse
 from ..core.config import settings
 import datetime
@@ -188,7 +188,6 @@ async def update_scoring_config(new_config: dict = Body(...)):
             json.dump(new_config, f, indent=2, ensure_ascii=False)
         logger.info("配置备份结束")
 
-
         return JSONResponse(
             status_code=status.HTTP_200_OK,
             content={"message": "配置已成功更新"}
@@ -198,4 +197,74 @@ async def update_scoring_config(new_config: dict = Body(...)):
         raise HTTPException(
             status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
             detail=f"保存新配置文件时发生错误: {e}"
-        )
+        )
+
+
+@router.get("/severity_names", summary="获取缺陷严重程度列表")
+async def get_severity_names(defect_label: str = Query(..., description="缺陷标签,如 wear, damaged, stain")):
+    """
+    根据缺陷标签返回对应的严重程度列表。
+    """
+    if not settings.SCORE_CONFIG_PATH.exists():
+        raise HTTPException(status_code=404, detail="评分配置文件未找到")
+
+    try:
+        with open(settings.SCORE_CONFIG_PATH, 'r', encoding='utf-8') as f:
+            config = json.load(f)
+
+        severity_config = config.get("severity_level", {})
+
+        # 1. 定义映射关系:从 缺陷label 映射到 severity_level 的 key
+        # 逻辑依据:参考 score_service 中将 label 转换为 rule key 的逻辑,再简化为 severity key
+        # rule key -> severity key:
+        # wear_area -> wear
+        # loss_area -> loss
+        # scratch_length -> scratch
+        # pit_area -> pit
+        # stain_area -> stain
+
+        severity_key = None
+
+        # 磨损类
+        if defect_label in ['wear', 'wear_and_impact', 'wear_and_stain']:
+            severity_key = "wear"
+
+        elif defect_label in ['damaged']:
+            severity_key = "loss"
+        elif defect_label in ['impact']:
+            # 这是一个歧义点,取决于它是边角还是面。
+            # 既然是获取列表,通常 pit 和 loss 的等级名可能是一样的(轻微/一般/严重)。
+            # 这里优先映射为 pit (常见于面),或者你可以根据实际 json 结构调整
+            severity_key = "pit"
+        elif defect_label in ['scratch', 'scuff']:
+            severity_key = "scratch"
+        elif defect_label in ['pit', 'protrudent']:
+            severity_key = "pit"
+        elif defect_label in ['stain']:
+            severity_key = "stain"
+
+        if not severity_key:
+            # 如果没匹配到,尝试直接用 label 查找 (容错)
+            if defect_label in severity_config:
+                severity_key = defect_label
+            else:
+                # 默认返回 wear 或抛出异常,这里选择返回空列表表示未找到
+                return JSONResponse(content={"data": {"type": defect_label, "names": []}})
+
+        # 2. 获取对应的配置列表
+        levels = severity_config.get(severity_key, [])
+
+        # 3. 提取 name 列表
+        # 假设结构是 [{"name": "轻微", "value": 0.5}, ...]
+        names = [item.get("name") for item in levels]
+
+        return JSONResponse(content={
+            "data": {
+                "defect_label": severity_key,
+                "names": names
+            }
+        })
+
+    except Exception as e:
+        logger.error(f"获取严重程度列表失败: {e}")
+        raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}")

+ 154 - 22
app/core/scoring_config.json

@@ -5,11 +5,11 @@
       "wear_area": [
         {
           "min": 0,
-          "max": 0.05,
+          "max": 0.01,
           "deduction": -0.1
         },
         {
-          "min": 0.05,
+          "min": 0.01,
           "max": 0.1,
           "deduction": -0.5
         },
@@ -327,28 +327,83 @@
       "wear_area": [
         {
           "min": 0,
+          "max": 0.02,
+          "deduction": "-0.01"
+        },
+        {
+          "min": 0.02,
           "max": 0.05,
-          "deduction": -0.1
+          "deduction": "-0.05"
         },
         {
           "min": 0.05,
           "max": 0.1,
-          "deduction": -0.5
+          "deduction": "-0.1"
         },
         {
           "min": 0.1,
-          "max": 0.25,
-          "deduction": -1.5
+          "max": 0.2,
+          "deduction": "-0.2"
         },
         {
-          "min": 0.25,
+          "min": 0.2,
           "max": 0.5,
-          "deduction": -3
+          "deduction": "-0.3"
         },
         {
           "min": 0.5,
+          "max": 1,
+          "deduction": "-0.5"
+        },
+        {
+          "min": 1,
+          "max": 1.5,
+          "deduction": "-1.0"
+        },
+        {
+          "min": 1.5,
+          "max": 2,
+          "deduction": "-1.5"
+        },
+        {
+          "min": 2,
+          "max": 3,
+          "deduction": "-2.0"
+        },
+        {
+          "min": 3,
+          "max": 5,
+          "deduction": "-2.5"
+        },
+        {
+          "min": 5,
+          "max": 7.5,
+          "deduction": "-3"
+        },
+        {
+          "min": 7.5,
+          "max": 10,
+          "deduction": "-3.5"
+        },
+        {
+          "min": 10,
+          "max": 12.5,
+          "deduction": "-4"
+        },
+        {
+          "min": 12.5,
+          "max": 15,
+          "deduction": "-4.5"
+        },
+        {
+          "min": 15,
+          "max": 20,
+          "deduction": "-5"
+        },
+        {
+          "min": 20,
           "max": "inf",
-          "deduction": -5
+          "deduction": "-6"
         }
       ],
       "pit_area": [
@@ -444,10 +499,10 @@
       ]
     },
     "coefficients": {
-      "wear_area": 0.25,
-      "scratch_length": 0.25,
-      "dent_area": 0.25,
-      "stain_area": 0.25
+      "wear_area": 1,
+      "scratch_length": 1,
+      "dent_area": 1,
+      "stain_area": 1
     },
     "light_weights": {
       "ring_weight": 0.8,
@@ -559,7 +614,7 @@
       ],
       "coefficients": {
         "horizontal": 1.2,
-        "vertical": 0.9
+        "vertical": 0.8
       }
     },
     "back": {
@@ -567,37 +622,42 @@
         {
           "min": 0,
           "max": 60,
-          "deduction": -0.5
+          "deduction": "0"
         },
         {
           "min": 60,
+          "max": 65,
+          "deduction": "-0.5"
+        },
+        {
+          "min": 65,
           "max": 70,
-          "deduction": -1
+          "deduction": "-1"
         },
         {
           "min": 70,
           "max": 75,
-          "deduction": -1.5
+          "deduction": "-1.5"
         },
         {
           "min": 75,
           "max": 85,
-          "deduction": -2
+          "deduction": "-2"
         },
         {
           "min": 85,
           "max": 95,
-          "deduction": -2.5
+          "deduction": "-2.5"
         },
         {
           "min": 95,
           "max": "inf",
-          "deduction": -3
+          "deduction": "-3"
         }
       ],
       "coefficients": {
         "horizontal": 1.2,
-        "vertical": 0.9
+        "vertical": 0.8
       }
     },
     "final_weights": {
@@ -605,6 +665,78 @@
       "back": 0.25
     }
   },
+  "severity_level": {
+    "wear": [
+      {
+        "name": "轻微",
+        "value": 0.5
+      },
+      {
+        "name": "一般",
+        "value": 1
+      },
+      {
+        "name": "严重",
+        "value": 2
+      }
+    ],
+    "loss": [
+      {
+        "name": "轻微",
+        "value": 0.5
+      },
+      {
+        "name": "一般",
+        "value": 1
+      },
+      {
+        "name": "严重",
+        "value": 2
+      }
+    ],
+    "pit": [
+      {
+        "name": "轻微",
+        "value": 0.5
+      },
+      {
+        "name": "一般",
+        "value": 1
+      },
+      {
+        "name": "严重",
+        "value": 2
+      }
+    ],
+    "stain": [
+      {
+        "name": "轻微",
+        "value": 0.5
+      },
+      {
+        "name": "一般",
+        "value": 1
+      },
+      {
+        "name": "严重",
+        "value": 2
+      }
+    ],
+    "scratch": [
+      {
+        "name": "轻微",
+        "value": 0.5
+      },
+      {
+        "name": "一般",
+        "value": 1
+      },
+      {
+        "name": "严重",
+        "value": 2
+      }
+    ]
+  },
   "card": {
     "PSA": {
       "face": 0.35,
@@ -615,7 +747,7 @@
     "BGS": {
       "face": 0.3,
       "corner": 0.25,
-      "edge": 0.1,
+      "edge": 0.2,
       "center": 0.25
     }
   }

+ 335 - 21
app/core/基础备份_scoring_config.json

@@ -3,6 +3,33 @@
   "corner": {
     "rules": {
       "wear_area": [
+        {
+          "min": 0,
+          "max": 0.01,
+          "deduction": -0.1
+        },
+        {
+          "min": 0.01,
+          "max": 0.1,
+          "deduction": -0.5
+        },
+        {
+          "min": 0.1,
+          "max": 0.25,
+          "deduction": -1.5
+        },
+        {
+          "min": 0.25,
+          "max": 0.5,
+          "deduction": -3
+        },
+        {
+          "min": 0.5,
+          "max": "inf",
+          "deduction": -5
+        }
+      ],
+      "loss_area": [
         {
           "min": 0,
           "max": 0.05,
@@ -29,7 +56,34 @@
           "deduction": -5
         }
       ],
-      "loss_area": [
+      "pit_area": [
+        {
+          "min": 0,
+          "max": 0.05,
+          "deduction": -0.1
+        },
+        {
+          "min": 0.05,
+          "max": 0.1,
+          "deduction": -0.5
+        },
+        {
+          "min": 0.1,
+          "max": 0.25,
+          "deduction": -1.5
+        },
+        {
+          "min": 0.25,
+          "max": 0.5,
+          "deduction": -3
+        },
+        {
+          "min": 0.5,
+          "max": "inf",
+          "deduction": -5
+        }
+      ],
+      "stain_area": [
         {
           "min": 0,
           "max": 0.05,
@@ -55,6 +109,43 @@
           "max": "inf",
           "deduction": -5
         }
+      ],
+      "scratch_length": [
+        {
+          "min": 0,
+          "max": 1,
+          "deduction": -0.1
+        },
+        {
+          "min": 1,
+          "max": 2,
+          "deduction": -0.5
+        },
+        {
+          "min": 2,
+          "max": 5,
+          "deduction": -1
+        },
+        {
+          "min": 5,
+          "max": 10,
+          "deduction": -2
+        },
+        {
+          "min": 10,
+          "max": 20,
+          "deduction": -3
+        },
+        {
+          "min": 20,
+          "max": 50,
+          "deduction": -4
+        },
+        {
+          "min": 50,
+          "max": "inf",
+          "deduction": -5
+        }
       ]
     },
     "front_weights": {
@@ -125,6 +216,97 @@
           "max": "inf",
           "deduction": -5
         }
+      ],
+      "pit_area": [
+        {
+          "min": 0,
+          "max": 0.05,
+          "deduction": -0.1
+        },
+        {
+          "min": 0.05,
+          "max": 0.1,
+          "deduction": -0.5
+        },
+        {
+          "min": 0.1,
+          "max": 0.25,
+          "deduction": -1.5
+        },
+        {
+          "min": 0.25,
+          "max": 0.5,
+          "deduction": -3
+        },
+        {
+          "min": 0.5,
+          "max": "inf",
+          "deduction": -5
+        }
+      ],
+      "stain_area": [
+        {
+          "min": 0,
+          "max": 0.05,
+          "deduction": -0.1
+        },
+        {
+          "min": 0.05,
+          "max": 0.1,
+          "deduction": -0.5
+        },
+        {
+          "min": 0.1,
+          "max": 0.25,
+          "deduction": -1.5
+        },
+        {
+          "min": 0.25,
+          "max": 0.5,
+          "deduction": -3
+        },
+        {
+          "min": 0.5,
+          "max": "inf",
+          "deduction": -5
+        }
+      ],
+      "scratch_length": [
+        {
+          "min": 0,
+          "max": 1,
+          "deduction": -0.1
+        },
+        {
+          "min": 1,
+          "max": 2,
+          "deduction": -0.5
+        },
+        {
+          "min": 2,
+          "max": 5,
+          "deduction": -1
+        },
+        {
+          "min": 5,
+          "max": 10,
+          "deduction": -2
+        },
+        {
+          "min": 10,
+          "max": 20,
+          "deduction": -3
+        },
+        {
+          "min": 20,
+          "max": 50,
+          "deduction": -4
+        },
+        {
+          "min": 50,
+          "max": "inf",
+          "deduction": -5
+        }
       ]
     },
     "front_weights": {
@@ -145,28 +327,83 @@
       "wear_area": [
         {
           "min": 0,
+          "max": 0.02,
+          "deduction": "-0.01"
+        },
+        {
+          "min": 0.02,
           "max": 0.05,
-          "deduction": -0.1
+          "deduction": "-0.05"
         },
         {
           "min": 0.05,
           "max": 0.1,
-          "deduction": -0.5
+          "deduction": "-0.1"
         },
         {
           "min": 0.1,
-          "max": 0.25,
-          "deduction": -1.5
+          "max": 0.2,
+          "deduction": "-0.2"
         },
         {
-          "min": 0.25,
+          "min": 0.2,
           "max": 0.5,
-          "deduction": -3
+          "deduction": "-0.3"
         },
         {
           "min": 0.5,
+          "max": 1,
+          "deduction": "-0.5"
+        },
+        {
+          "min": 1,
+          "max": 1.5,
+          "deduction": "-1.0"
+        },
+        {
+          "min": 1.5,
+          "max": 2,
+          "deduction": "-1.5"
+        },
+        {
+          "min": 2,
+          "max": 3,
+          "deduction": "-2.0"
+        },
+        {
+          "min": 3,
+          "max": 5,
+          "deduction": "-2.5"
+        },
+        {
+          "min": 5,
+          "max": 7.5,
+          "deduction": "-3"
+        },
+        {
+          "min": 7.5,
+          "max": 10,
+          "deduction": "-3.5"
+        },
+        {
+          "min": 10,
+          "max": 12.5,
+          "deduction": "-4"
+        },
+        {
+          "min": 12.5,
+          "max": 15,
+          "deduction": "-4.5"
+        },
+        {
+          "min": 15,
+          "max": 20,
+          "deduction": "-5"
+        },
+        {
+          "min": 20,
           "max": "inf",
-          "deduction": -5
+          "deduction": "-6"
         }
       ],
       "pit_area": [
@@ -262,10 +499,10 @@
       ]
     },
     "coefficients": {
-      "wear_area": 0.25,
-      "scratch_length": 0.25,
-      "dent_area": 0.25,
-      "stain_area": 0.25
+      "wear_area": 1,
+      "scratch_length": 1,
+      "dent_area": 1,
+      "stain_area": 1
     },
     "light_weights": {
       "ring_weight": 0.8,
@@ -377,7 +614,7 @@
       ],
       "coefficients": {
         "horizontal": 1.2,
-        "vertical": 0.9
+        "vertical": 0.8
       }
     },
     "back": {
@@ -385,37 +622,42 @@
         {
           "min": 0,
           "max": 60,
-          "deduction": -0.5
+          "deduction": "0"
         },
         {
           "min": 60,
+          "max": 65,
+          "deduction": "-0.5"
+        },
+        {
+          "min": 65,
           "max": 70,
-          "deduction": -1
+          "deduction": "-1"
         },
         {
           "min": 70,
           "max": 75,
-          "deduction": -1.5
+          "deduction": "-1.5"
         },
         {
           "min": 75,
           "max": 85,
-          "deduction": -2
+          "deduction": "-2"
         },
         {
           "min": 85,
           "max": 95,
-          "deduction": -2.5
+          "deduction": "-2.5"
         },
         {
           "min": 95,
           "max": "inf",
-          "deduction": -3
+          "deduction": "-3"
         }
       ],
       "coefficients": {
         "horizontal": 1.2,
-        "vertical": 0.9
+        "vertical": 0.8
       }
     },
     "final_weights": {
@@ -423,6 +665,78 @@
       "back": 0.25
     }
   },
+  "severity_level": {
+    "wear": [
+      {
+        "name": "轻微",
+        "value": 0.5
+      },
+      {
+        "name": "一般",
+        "value": 1
+      },
+      {
+        "name": "严重",
+        "value": 2
+      }
+    ],
+    "loss": [
+      {
+        "name": "轻微",
+        "value": 0.5
+      },
+      {
+        "name": "一般",
+        "value": 1
+      },
+      {
+        "name": "严重",
+        "value": 2
+      }
+    ],
+    "pit": [
+      {
+        "name": "轻微",
+        "value": 0.5
+      },
+      {
+        "name": "一般",
+        "value": 1
+      },
+      {
+        "name": "严重",
+        "value": 2
+      }
+    ],
+    "stain": [
+      {
+        "name": "轻微",
+        "value": 0.5
+      },
+      {
+        "name": "一般",
+        "value": 1
+      },
+      {
+        "name": "严重",
+        "value": 2
+      }
+    ],
+    "scratch": [
+      {
+        "name": "轻微",
+        "value": 0.5
+      },
+      {
+        "name": "一般",
+        "value": 1
+      },
+      {
+        "name": "严重",
+        "value": 2
+      }
+    ]
+  },
   "card": {
     "PSA": {
       "face": 0.35,
@@ -433,7 +747,7 @@
     "BGS": {
       "face": 0.3,
       "corner": 0.25,
-      "edge": 0.1,
+      "edge": 0.2,
       "center": 0.25
     }
   }

+ 52 - 3
app/utils/score_inference/CardScorer.py

@@ -50,6 +50,27 @@ class CardScorer:
             return float(rules[-1].get("deduction", 0))
         return 0.0
 
+    # 增加一个辅助方法用于查找严重程度系数
+    def _get_severity_coefficient(self, severity_type_key: str, level_name: str) -> float:
+        """
+        从配置中获取严重程度对应的系数
+        :param severity_type_key: 对应 severity_level 下的键,如 "wear", "stain"
+        :param level_name: 具体的等级名,如 "一般", "严重"
+        :return: 系数 value
+        """
+        severity_config = self.config.get("severity_level", {})
+        rules = severity_config.get(severity_type_key, [])
+
+        # 默认系数为 1.0 (如果找不到配置)
+        default_val = 1.0
+
+        # 寻找匹配的 name
+        for rule in rules:
+            if rule.get("name") == level_name:
+                return float(rule.get("value", 1.0))
+        # 如果传入了"一般"但配置里没"一般",则返回 1.0
+        return default_val
+
     def calculate_defect_score(self,
                                card_defect_type: str,
                                card_aspect: str,
@@ -121,9 +142,37 @@ class CardScorer:
             if defect_type not in weighted_scores.keys():
                 weighted_scores[defect_type] = 0
 
-            # 计算单个缺陷扣分
-            the_score = self._get_score_from_tiers(area_mm, rules)
-            print(f"[{card_defect_type}, {defect_type}]: {area_mm}, {the_score}")
+            # --- 计算分数并应用严重程度 ---
+
+            # 1. 基础分计算
+            base_score = self._get_score_from_tiers(area_mm, rules)
+
+            # 2. 获取严重程度系数
+            # 首先将 defect_type (如 wear_area) 映射到 severity key (如 wear)
+            if defect_type == "wear_area":
+                severity_key = "wear"
+            elif defect_type == "loss_area":
+                severity_key = "loss"  # 对应截图里的 loss
+            elif defect_type == "scratch_length":
+                severity_key = "scratch"
+            elif defect_type == "pit_area":
+                severity_key = "pit"
+            elif defect_type == "stain_area":
+                severity_key = "stain"
+            else:
+                severity_key = "wear"  # 默认 fallback
+
+            # 获取数据中的 level,默认为 "一般"
+            severity_level_name = defect.get("severity_level", "一般")
+            if not severity_level_name:  # 防止为 None 或空字符串
+                severity_level_name = "一般"
+
+            severity_coef = self._get_severity_coefficient(severity_key, severity_level_name)
+
+            # 3. 计算最终单项分数
+            the_score = base_score * severity_coef
+
+            print(f"[{card_defect_type}, {defect_type}]: 面积/长={area_mm}, 基础分={base_score}, 等级={severity_level_name}({severity_coef}), 最终分={the_score}")
             weighted_scores[defect_type] += the_score
 
             # 将分数写入json