Răsfoiți Sursa

分数配置修改, 同时修改配套代码

AnlaAnla 2 săptămâni în urmă
părinte
comite
e971bbc13e

+ 73 - 34
README.MD

@@ -1,17 +1,50 @@
 # 启动
 ```cmd
-python run.py
+python run_defect_score_server.py
 ```
 
-### 模拟接口
-
-使用 /api/card_inference/mock_query
+## 中文对照
+```json
+{
+  "face": "面",
+  "wear": "磨损",
+  "scratch": "划痕",
+  "stain": "污渍",
+  "scuff": "磨痕",
+  "impact": "撞击痕迹",
+  "damaged": "损坏",
+  "wear_and_impact": "磨损和撞击痕迹",
+  "pit": "凹坑",
+  "corner": "角",
+  "wear_and_stain": "磨损和污渍",
+  "inner_box": "内框",
+  "outer_box": "外框"
+}
+```
 
+### 缺陷类型
+```text
+面缺陷: "1": "wear", "2": "scratch", "3": "stain",
+        "4": "scuff", "5": "impact", "6": "damaged",
+        "7": "wear_and_impact", "8": "pit"
+    
+边角缺陷: "1": "wear", "2": "wear_and_impact", "3": "damaged",
+         "4": "impact", "5": "wear_and_stain"
 
-磨损: 1: 'wear', 2: 'wear_and_impact', 5: 'wear_and_stain',
+```
 
-缺失: 3: 'impact', 4: 'damaged', 
+### 缺陷划分
+```text
+边角
+wear_area: 'wear', 'wear_and_impact', 'wear_and_stain'
+loss_area: 'impact', 'damaged'
 
+面
+wear_area: 'wear', 'wear_and_impact', 'damaged'
+scratch_length: 'scratch', 'scuff'
+pit_area: 'pit', 'impact'
+stain_area: 'stain'
+```
 
 ## 分数配置信息备注
 ```text
@@ -61,33 +94,39 @@ card: 卡片
         ...不同类型的权重最后分配
 ```
 
-### 分数规则详解
-```
-举例说明:
+### 分数规则要求
 
-    "wear_area": [
-        [
-        面积小于 0.05 平方毫米就扣0.1分
-          0.05,
-          -0.1
-        ],
-        面积在 0.05-0.1之间扣0.5分
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3.0
-        ],
-        [
-        面积大于 0.5平方毫米扣 5分
-          "inf",
-          -5.0
-        ]
-      ],
+规则为: min<=缺陷面积<max , 扣 deduction 的分数
+
+要求
+1. 第一个 min 必须为 0
+2. 除第一个min, 所有min必须等于上一个的max
+3. 最后一个的max必须为 "inf", 即无穷大
+```
+"wear_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
+        }
 ```

+ 497 - 219
Test/test01.py

@@ -1,222 +1,500 @@
-import numpy as np
-import cv2
+def validate_rule_ranges(data: dict, path: str = "") -> (bool, str):
+    """
+    递归检查配置中看起来像评分规则的列表。
+    规则:
+    1. 列表中的元素必须包含 min, max, deduction。
+    2. 第一个元素的 min 必须为 0。
+    3. 当前元素的 min 必须等于上一个元素的 max。
+    """
+    if isinstance(data, dict):
+        for key, value in data.items():
+            is_valid, error = validate_rule_ranges(value, path=f"{path}.{key}" if path else key)
+            if not is_valid:
+                return False, error
 
-points = [
-                [
-                  350,
-                  329
-                ],
-                [
-                  347,
-                  332
-                ],
-                [
-                  304,
-                  332
-                ],
-                [
-                  301,
-                  335
-                ],
-                [
-                  301,
-                  2045
-                ],
-                [
-                  297,
-                  2049
-                ],
-                [
-                  297,
-                  2262
-                ],
-                [
-                  301,
-                  2266
-                ],
-                [
-                  301,
-                  2371
-                ],
-                [
-                  297,
-                  2374
-                ],
-                [
-                  297,
-                  3136
-                ],
-                [
-                  301,
-                  3140
-                ],
-                [
-                  301,
-                  3607
-                ],
-                [
-                  304,
-                  3611
-                ],
-                [
-                  1611,
-                  3611
-                ],
-                [
-                  1614,
-                  3607
-                ],
-                [
-                  1689,
-                  3607
-                ],
-                [
-                  1692,
-                  3611
-                ],
-                [
-                  2563,
-                  3611
-                ],
-                [
-                  2566,
-                  3614
-                ],
-                [
-                  2591,
-                  3614
-                ],
-                [
-                  2594,
-                  3611
-                ],
-                [
-                  2597,
-                  3611
-                ],
-                [
-                  2600,
-                  3607
-                ],
-                [
-                  2600,
-                  3592
-                ],
-                [
-                  2597,
-                  3589
-                ],
-                [
-                  2597,
-                  3462
-                ],
-                [
-                  2600,
-                  3459
-                ],
-                [
-                  2600,
-                  1760
-                ],
-                [
-                  2603,
-                  1757
-                ],
-                [
-                  2603,
-                  1674
-                ],
-                [
-                  2600,
-                  1670
-                ],
-                [
-                  2600,
-                  1593
-                ],
-                [
-                  2603,
-                  1590
-                ],
-                [
-                  2603,
-                  335
-                ],
-                [
-                  2597,
-                  335
-                ],
-                [
-                  2594,
-                  332
-                ],
-                [
-                  2572,
-                  332
-                ],
-                [
-                  2569,
-                  335
-                ],
-                [
-                  2553,
-                  335
-                ],
-                [
-                  2550,
-                  332
-                ],
-                [
-                  2144,
-                  332
-                ],
-                [
-                  2141,
-                  335
-                ],
-                [
-                  1872,
-                  335
-                ],
-                [
-                  1869,
-                  332
-                ],
-                [
-                  1841,
-                  332
-                ],
-                [
-                  1838,
-                  335
-                ],
-                [
-                  1751,
-                  335
-                ],
-                [
-                  1748,
-                  332
-                ],
-                [
-                  449,
-                  332
-                ],
-                [
-                  446,
-                  329
-                ]
-              ]
-points = np.array(points, dtype=np.int32)
+    elif isinstance(data, list):
+        # 检查这是否是一个规则列表(通过检查第一个元素是否包含特定key)
+        if len(data) > 0 and isinstance(data[0], dict) and "min" in data[0] and "max" in data[0] and "deduction" in \
+                data[0]:
+            current_path = path
+
+            # 1. 检查首项 min 是否为 0
+            first_min = data[0].get("min")
+            if first_min != 0:
+                return False, f"范围错误 [{current_path}]: 第一个区间的 min 必须为 0,当前为 {first_min}"
+
+            # 2. 检查连续性
+            prev_max = None
+            for idx, rule in enumerate(data):
+                current_min = rule.get("min")
+                current_max = rule.get("max")
+
+                # 处理 inf 字符串
+                if current_max == "inf":
+                    current_max_val = float("inf")
+                else:
+                    try:
+                        current_max_val = float(current_max)
+                    except (ValueError, TypeError):
+                        return False, f"数值错误 [{current_path}]: max 值 '{current_max}' 无效"
+
+                try:
+                    current_min_val = float(current_min)
+                except (ValueError, TypeError):
+                    return False, f"数值错误 [{current_path}]: min 值 '{current_min}' 无效"
+
+                # 检查连续性 (除了第一个元素)
+                if idx > 0:
+                    # 使用一个微小的容差处理浮点数比较,或者直接相等
+                    if prev_max == "inf":
+                        return False, f"逻辑错误 [{current_path}]: 'inf' 只能出现在最后一个区间的 max"
 
-x, y, w, h = cv2.boundingRect(points)
-# 2. 构造 4 个顶点坐标
-result = np.array([
-    [x, y],             # 左上
-    [x + w, y],         # 右上
-    [x + w, y + h],     # 右下
-    [x, y + h]          # 左下
-], dtype=np.int32).tolist()
+                    prev_max_val = float(prev_max) if prev_max != "inf" else float("inf")
 
-print(f"Bounding Rect (x,y,w,h): {x}, {y}, {w}, {h}")
-print(f"Result (4 points):\n{result}")
+                    if current_min_val != prev_max_val:
+                        return False, f"不连续错误 [{current_path}]: 第 {idx + 1} 项的 min ({current_min}) 不等于上一项的 max ({prev_max})"
+
+                prev_max = current_max
+
+    return True, "验证通过"
+
+
+if __name__ == '__main__':
+    new_config = {
+        "base_score": 10,
+        "corner": {
+            "rules": {
+                "wear_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
+                    }
+                ],
+                "loss_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
+                    }
+                ]
+            },
+            "front_weights": {
+                "wear_area": 0.3,
+                "loss_area": 0.7
+            },
+            "back_weights": {
+                "wear_area": 0.3,
+                "loss_area": 0.7
+            },
+            "final_weights": {
+                "front": 0.7,
+                "back": 0.3
+            }
+        },
+        "edge": {
+            "rules": {
+                "wear_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
+                    }
+                ],
+                "loss_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
+                    }
+                ]
+            },
+            "front_weights": {
+                "wear_area": 0.4,
+                "loss_area": 0.6
+            },
+            "back_weights": {
+                "wear_area": 0.4,
+                "loss_area": 0.6
+            },
+            "final_weights": {
+                "front": 0.7,
+                "back": 0.3
+            }
+        },
+        "face": {
+            "rules": {
+                "wear_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
+                    }
+                ],
+                "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
+                    }
+                ]
+            },
+            "coefficients": {
+                "wear_area": 0.25,
+                "scratch_length": 0.25,
+                "dent_area": 0.25,
+                "stain_area": 0.25
+            },
+            "final_weights": {
+                "front": 0.75,
+                "back": 0.25
+            }
+        },
+        "centering": {
+            "front": {
+                "rules": [
+                    {
+                        "min": 0,
+                        "max": 52,
+                        "deduction": 0
+                    },
+                    {
+                        "min": 52,
+                        "max": 55,
+                        "deduction": -0.5
+                    },
+                    {
+                        "min": 55,
+                        "max": 60,
+                        "deduction": -1
+                    },
+                    {
+                        "min": 60,
+                        "max": 62.5,
+                        "deduction": -1.5
+                    },
+                    {
+                        "min": 62.5,
+                        "max": 65,
+                        "deduction": -2
+                    },
+                    {
+                        "min": 65,
+                        "max": 67.5,
+                        "deduction": -2.5
+                    },
+                    {
+                        "min": 67.5,
+                        "max": 70,
+                        "deduction": -3
+                    },
+                    {
+                        "min": 70,
+                        "max": 72.5,
+                        "deduction": -3.5
+                    },
+                    {
+                        "min": 72.5,
+                        "max": 75,
+                        "deduction": -4
+                    },
+                    {
+                        "min": 75,
+                        "max": 77.5,
+                        "deduction": -4.5
+                    },
+                    {
+                        "min": 77.5,
+                        "max": 80,
+                        "deduction": -5
+                    },
+                    {
+                        "min": 80,
+                        "max": 82.5,
+                        "deduction": -5.5
+                    },
+                    {
+                        "min": 82.5,
+                        "max": 85,
+                        "deduction": -6
+                    },
+                    {
+                        "min": 85,
+                        "max": 87.5,
+                        "deduction": -6.5
+                    },
+                    {
+                        "min": 87.5,
+                        "max": 90,
+                        "deduction": -7
+                    },
+                    {
+                        "min": 90,
+                        "max": 92.5,
+                        "deduction": -7.5
+                    },
+                    {
+                        "min": 92.5,
+                        "max": 95,
+                        "deduction": -8
+                    },
+                    {
+                        "min": 95,
+                        "max": 97.5,
+                        "deduction": -8.5
+                    },
+                    {
+                        "min": 97.5,
+                        "max": "inf",
+                        "deduction": -9
+                    }
+                ],
+                "coefficients": {
+                    "horizontal": 1.2,
+                    "vertical": 0.9
+                }
+            },
+            "back": {
+                "rules": [
+                    {
+                        "min": 0,
+                        "max": 60,
+                        "deduction": -0.5
+                    },
+                    {
+                        "min": 60,
+                        "max": 70,
+                        "deduction": -1
+                    },
+                    {
+                        "min": 70,
+                        "max": 75,
+                        "deduction": -1.5
+                    },
+                    {
+                        "min": 75,
+                        "max": 85,
+                        "deduction": -2
+                    },
+                    {
+                        "min": 85,
+                        "max": 95,
+                        "deduction": -2.5
+                    },
+                    {
+                        "min": 95,
+                        "max": "inf",
+                        "deduction": -3
+                    }
+                ],
+                "coefficients": {
+                    "horizontal": 1.2,
+                    "vertical": 0.9
+                }
+            },
+            "final_weights": {
+                "front": 0.75,
+                "back": 0.25
+            }
+        },
+        "card": {
+            "PSA": {
+                "face": 0.35,
+                "corner": 0.3,
+                "edge": 0.1,
+                "center": 0.25
+            },
+            "BGS": {
+                "face": 0.3,
+                "corner": 0.25,
+                "edge": 0.1,
+                "center": 0.25
+            }
+        }
+    }
+    is_range_valid, range_reason = validate_rule_ranges(new_config)
+    print(is_range_valid, range_reason)

+ 79 - 20
app/api/config_api.py

@@ -47,6 +47,67 @@ def compare_json_structure(template: dict, data: dict, path: str = "") -> (bool,
     return True, "结构匹配"
 
 
+def validate_rule_ranges(data: dict, path: str = "") -> (bool, str):
+    """
+    递归检查配置中看起来像评分规则的列表。
+    规则:
+    1. 列表中的元素必须包含 min, max, deduction。
+    2. 第一个元素的 min 必须为 0。
+    3. 当前元素的 min 必须等于上一个元素的 max。
+    """
+    if isinstance(data, dict):
+        for key, value in data.items():
+            is_valid, error = validate_rule_ranges(value, path=f"{path}.{key}" if path else key)
+            if not is_valid:
+                return False, error
+
+    elif isinstance(data, list):
+        # 检查这是否是一个规则列表(通过检查第一个元素是否包含特定key)
+        if len(data) > 0 and isinstance(data[0], dict) and "min" in data[0] and "max" in data[0] and "deduction" in \
+                data[0]:
+            current_path = path
+
+            # 1. 检查首项 min 是否为 0
+            first_min = data[0].get("min")
+            if first_min != 0:
+                return False, f"范围错误 [{current_path}]: 第一个区间的 min 必须为 0,当前为 {first_min}"
+
+            # 2. 检查连续性
+            prev_max = None
+            for idx, rule in enumerate(data):
+                current_min = rule.get("min")
+                current_max = rule.get("max")
+
+                # 处理 inf 字符串
+                if current_max == "inf":
+                    current_max_val = float("inf")
+                else:
+                    try:
+                        current_max_val = float(current_max)
+                    except (ValueError, TypeError):
+                        return False, f"数值错误 [{current_path}]: max 值 '{current_max}' 无效"
+
+                try:
+                    current_min_val = float(current_min)
+                except (ValueError, TypeError):
+                    return False, f"数值错误 [{current_path}]: min 值 '{current_min}' 无效"
+
+                # 检查连续性 (除了第一个元素)
+                if idx > 0:
+                    # 使用一个微小的容差处理浮点数比较,或者直接相等
+                    if prev_max == "inf":
+                        return False, f"逻辑错误 [{current_path}]: 'inf' 只能出现在最后一个区间的 max"
+
+                    prev_max_val = float(prev_max) if prev_max != "inf" else float("inf")
+
+                    if current_min_val != prev_max_val:
+                        return False, f"不连续错误 [{current_path}]: 第 {idx + 1} 项的 min ({current_min}) 不等于上一项的 max ({prev_max})"
+
+                prev_max = current_max
+
+    return True, "验证通过"
+
+
 @router.get("/scoring_config", summary="获取评分配置")
 async def get_scoring_config():
     """
@@ -80,41 +141,39 @@ async def get_scoring_config():
 @router.put("/scoring_config", summary="更新评分配置")
 async def update_scoring_config(new_config: dict = Body(...)):
     """
-    接收新的JSON配置,验证其结构与现有配置完全一致后,覆盖保存。
-    只允许修改值,不允许增、删、改任何字段名。
+    接收新的JSON配置,验证结构一致性以及分值范围的连续性(min=prev_max)
     """
     # 1. 检查并读取当前的配置文件作为模板
     if not settings.SCORE_CONFIG_PATH.exists():
-        logger.error(f"尝试更新一个不存在的配置文件: {settings.SCORE_CONFIG_PATH}")
-        raise HTTPException(
-            status_code=status.HTTP_404_NOT_FOUND,
-            detail="无法更新,因为原始配置文件未找到"
-        )
+        # 如果文件不存在,可能无法进行结构对比,视情况处理,这里假设必须存在
+        raise HTTPException(status_code=404, detail="原始配置文件未找到")
 
     try:
         with open(settings.SCORE_CONFIG_PATH, 'r', encoding='utf-8') as f:
             current_config = json.load(f)
     except Exception as e:
-        logger.error(f"更新前读取原始配置文件失败: {e}")
-        raise HTTPException(
-            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
-            detail=f"读取原始配置文件失败: {e}"
-        )
+        raise HTTPException(status_code=500, detail=f"读取原始配置文件失败: {e}")
 
-    # 2. 比较新旧配置的结构
-    is_valid, reason = compare_json_structure(current_config, new_config)
+    # 2. 比较新旧配置的结构 (Key的一致性)
+    is_structure_valid, struct_reason = compare_json_structure(current_config, new_config)
 
-    if not is_valid:
-        logger.warning(f"更新评分配置失败,结构校验未通过: {reason}")
+    if not is_structure_valid:
+        logger.warning(f"结构校验警告: {struct_reason}")
+        # raise HTTPException(status_code=400, detail=struct_reason)
+
+    # 3. **验证数值范围的连续性**
+    is_range_valid, range_reason = validate_rule_ranges(new_config)
+
+    if not is_range_valid:
+        logger.warning(f"数值范围校验失败: {range_reason}")
         raise HTTPException(
             status_code=status.HTTP_400_BAD_REQUEST,
-            detail=f"配置更新失败。{reason} 请确保只修改数值,不要添加、删除或重命名字段。"
+            detail=f"配置数值逻辑错误: {range_reason}"
         )
 
-    # 3. 结构验证通过,写入新文件
+    # 4. 写入新文件
     try:
         with open(settings.SCORE_CONFIG_PATH, 'w', encoding='utf-8') as f:
-            # 使用 indent=2 格式化输出,方便人工阅读
             json.dump(new_config, f, indent=2, ensure_ascii=False)
         logger.info("评分配置文件已成功更新。")
         return JSONResponse(
@@ -126,4 +185,4 @@ async def update_scoring_config(new_config: dict = Body(...)):
         raise HTTPException(
             status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
             detail=f"保存新配置文件时发生错误: {e}"
-        )
+        )

+ 335 - 268
app/core/scoring_config.json

@@ -3,48 +3,58 @@
   "corner": {
     "rules": {
       "wear_area": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3
-        ],
-        [
-          "inf",
-          -5
-        ]
+        {
+          "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
+        }
       ],
       "loss_area": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3
-        ],
-        [
-          "inf",
-          -5
-        ]
+        {
+          "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
+        }
       ]
     },
     "front_weights": {
@@ -63,48 +73,58 @@
   "edge": {
     "rules": {
       "wear_area": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3
-        ],
-        [
-          "inf",
-          -5
-        ]
+        {
+          "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
+        }
       ],
       "loss_area": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3
-        ],
-        [
-          "inf",
-          -5
-        ]
+        {
+          "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
+        }
       ]
     },
     "front_weights": {
@@ -123,100 +143,122 @@
   "face": {
     "rules": {
       "wear_area": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3
-        ],
-        [
-          "inf",
-          -5
-        ]
+        {
+          "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
+        }
       ],
       "pit_area": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3
-        ],
-        [
-          "inf",
-          -5
-        ]
+        {
+          "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": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3
-        ],
-        [
-          "inf",
-          -5
-        ]
+        {
+          "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": [
-        [
-          1,
-          -0.1
-        ],
-        [
-          2,
-          -0.5
-        ],
-        [
-          5,
-          -1
-        ],
-        [
-          10,
-          -2
-        ],
-        [
-          20,
-          -3
-        ],
-        [
-          50,
-          -4
-        ],
-        [
-          "inf",
-          -5
-        ]
+        {
+          "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
+        }
       ]
     },
     "coefficients": {
@@ -233,82 +275,101 @@
   "centering": {
     "front": {
       "rules": [
-        [
-          52,
-          0
-        ],
-        [
-          55,
-          -0.5
-        ],
-        [
-          60,
-          -1
-        ],
-        [
-          62.5,
-          -1.5
-        ],
-        [
-          65,
-          -2
-        ],
-        [
-          67.5,
-          -2.5
-        ],
-        [
-          70,
-          -3
-        ],
-        [
-          72.5,
-          -3.5
-        ],
-        [
-          75,
-          -4
-        ],
-        [
-          77.5,
-          -4.5
-        ],
-        [
-          80,
-          -5
-        ],
-        [
-          82.5,
-          -5.5
-        ],
-        [
-          85,
-          -6
-        ],
-        [
-          87.5,
-          -6.5
-        ],
-        [
-          90,
-          -7
-        ],
-        [
-          92.5,
-          -7.5
-        ],
-        [
-          95,
-          -8
-        ],
-        [
-          97.5,
-          -8.5
-        ],
-        [
-          "inf",
-          -9
-        ]
+        {
+          "min": 0,
+          "max": 52,
+          "deduction": 0
+        },
+        {
+          "min": 52,
+          "max": 55,
+          "deduction": -0.5
+        },
+        {
+          "min": 55,
+          "max": 60,
+          "deduction": -1
+        },
+        {
+          "min": 60,
+          "max": 62.5,
+          "deduction": -1.5
+        },
+        {
+          "min": 62.5,
+          "max": 65,
+          "deduction": -2
+        },
+        {
+          "min": 65,
+          "max": 67.5,
+          "deduction": -2.5
+        },
+        {
+          "min": 67.5,
+          "max": 70,
+          "deduction": -3
+        },
+        {
+          "min": 70,
+          "max": 72.5,
+          "deduction": -3.5
+        },
+        {
+          "min": 72.5,
+          "max": 75,
+          "deduction": -4
+        },
+        {
+          "min": 75,
+          "max": 77.5,
+          "deduction": -4.5
+        },
+        {
+          "min": 77.5,
+          "max": 80,
+          "deduction": -5
+        },
+        {
+          "min": 80,
+          "max": 82.5,
+          "deduction": -5.5
+        },
+        {
+          "min": 82.5,
+          "max": 85,
+          "deduction": -6
+        },
+        {
+          "min": 85,
+          "max": 87.5,
+          "deduction": -6.5
+        },
+        {
+          "min": 87.5,
+          "max": 90,
+          "deduction": -7
+        },
+        {
+          "min": 90,
+          "max": 92.5,
+          "deduction": -7.5
+        },
+        {
+          "min": 92.5,
+          "max": 95,
+          "deduction": -8
+        },
+        {
+          "min": 95,
+          "max": 97.5,
+          "deduction": -8.5
+        },
+        {
+          "min": 97.5,
+          "max": "inf",
+          "deduction": -9
+        }
       ],
       "coefficients": {
         "horizontal": 1.2,
@@ -317,30 +378,36 @@
     },
     "back": {
       "rules": [
-        [
-          60,
-          -0.5
-        ],
-        [
-          70,
-          -1
-        ],
-        [
-          75,
-          -1.5
-        ],
-        [
-          85,
-          -2
-        ],
-        [
-          95,
-          -2.5
-        ],
-        [
-          "inf",
-          -3
-        ]
+        {
+          "min": 0,
+          "max": 60,
+          "deduction": -0.5
+        },
+        {
+          "min": 60,
+          "max": 70,
+          "deduction": -1
+        },
+        {
+          "min": 70,
+          "max": 75,
+          "deduction": -1.5
+        },
+        {
+          "min": 75,
+          "max": 85,
+          "deduction": -2
+        },
+        {
+          "min": 85,
+          "max": 95,
+          "deduction": -2.5
+        },
+        {
+          "min": 95,
+          "max": "inf",
+          "deduction": -3
+        }
       ],
       "coefficients": {
         "horizontal": 1.2,

+ 336 - 269
app/core/基础备份_scoring_config.json

@@ -1,50 +1,60 @@
 {
-  "base_score": 10.0,
+  "base_score": 10,
   "corner": {
     "rules": {
       "wear_area": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3.0
-        ],
-        [
-          "inf",
-          -5.0
-        ]
+        {
+          "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
+        }
       ],
       "loss_area": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3.0
-        ],
-        [
-          "inf",
-          -5.0
-        ]
+        {
+          "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
+        }
       ]
     },
     "front_weights": {
@@ -63,48 +73,58 @@
   "edge": {
     "rules": {
       "wear_area": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3.0
-        ],
-        [
-          "inf",
-          -5.0
-        ]
+        {
+          "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
+        }
       ],
       "loss_area": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3.0
-        ],
-        [
-          "inf",
-          -5.0
-        ]
+        {
+          "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
+        }
       ]
     },
     "front_weights": {
@@ -123,100 +143,122 @@
   "face": {
     "rules": {
       "wear_area": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3.0
-        ],
-        [
-          "inf",
-          -5.0
-        ]
+        {
+          "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
+        }
       ],
       "pit_area": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3.0
-        ],
-        [
-          "inf",
-          -5.0
-        ]
+        {
+          "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": [
-        [
-          0.05,
-          -0.1
-        ],
-        [
-          0.1,
-          -0.5
-        ],
-        [
-          0.25,
-          -1.5
-        ],
-        [
-          0.5,
-          -3.0
-        ],
-        [
-          "inf",
-          -5.0
-        ]
+        {
+          "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": [
-        [
-          1.0,
-          -0.1
-        ],
-        [
-          2.0,
-          -0.5
-        ],
-        [
-          5.0,
-          -1.0
-        ],
-        [
-          10.0,
-          -2.0
-        ],
-        [
-          20.0,
-          -3.0
-        ],
-        [
-          50.0,
-          -4.0
-        ],
-        [
-          "inf",
-          -5.0
-        ]
+        {
+          "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
+        }
       ]
     },
     "coefficients": {
@@ -233,82 +275,101 @@
   "centering": {
     "front": {
       "rules": [
-        [
-          52,
-          0
-        ],
-        [
-          55,
-          -0.5
-        ],
-        [
-          60,
-          -1.0
-        ],
-        [
-          62.5,
-          -1.5
-        ],
-        [
-          65,
-          -2.0
-        ],
-        [
-          67.5,
-          -2.5
-        ],
-        [
-          70,
-          -3.0
-        ],
-        [
-          72.5,
-          -3.5
-        ],
-        [
-          75,
-          -4.0
-        ],
-        [
-          77.5,
-          -4.5
-        ],
-        [
-          80,
-          -5.0
-        ],
-        [
-          82.5,
-          -5.5
-        ],
-        [
-          85,
-          -6.0
-        ],
-        [
-          87.5,
-          -6.5
-        ],
-        [
-          90,
-          -7.0
-        ],
-        [
-          92.5,
-          -7.5
-        ],
-        [
-          95,
-          -8.0
-        ],
-        [
-          97.5,
-          -8.5
-        ],
-        [
-          "inf",
-          -9.0
-        ]
+        {
+          "min": 0,
+          "max": 52,
+          "deduction": 0
+        },
+        {
+          "min": 52,
+          "max": 55,
+          "deduction": -0.5
+        },
+        {
+          "min": 55,
+          "max": 60,
+          "deduction": -1
+        },
+        {
+          "min": 60,
+          "max": 62.5,
+          "deduction": -1.5
+        },
+        {
+          "min": 62.5,
+          "max": 65,
+          "deduction": -2
+        },
+        {
+          "min": 65,
+          "max": 67.5,
+          "deduction": -2.5
+        },
+        {
+          "min": 67.5,
+          "max": 70,
+          "deduction": -3
+        },
+        {
+          "min": 70,
+          "max": 72.5,
+          "deduction": -3.5
+        },
+        {
+          "min": 72.5,
+          "max": 75,
+          "deduction": -4
+        },
+        {
+          "min": 75,
+          "max": 77.5,
+          "deduction": -4.5
+        },
+        {
+          "min": 77.5,
+          "max": 80,
+          "deduction": -5
+        },
+        {
+          "min": 80,
+          "max": 82.5,
+          "deduction": -5.5
+        },
+        {
+          "min": 82.5,
+          "max": 85,
+          "deduction": -6
+        },
+        {
+          "min": 85,
+          "max": 87.5,
+          "deduction": -6.5
+        },
+        {
+          "min": 87.5,
+          "max": 90,
+          "deduction": -7
+        },
+        {
+          "min": 90,
+          "max": 92.5,
+          "deduction": -7.5
+        },
+        {
+          "min": 92.5,
+          "max": 95,
+          "deduction": -8
+        },
+        {
+          "min": 95,
+          "max": 97.5,
+          "deduction": -8.5
+        },
+        {
+          "min": 97.5,
+          "max": "inf",
+          "deduction": -9
+        }
       ],
       "coefficients": {
         "horizontal": 1.2,
@@ -317,30 +378,36 @@
     },
     "back": {
       "rules": [
-        [
-          60,
-          -0.5
-        ],
-        [
-          70,
-          -1.0
-        ],
-        [
-          75,
-          -1.5
-        ],
-        [
-          85,
-          -2.0
-        ],
-        [
-          95,
-          -2.5
-        ],
-        [
-          "inf",
-          -3.0
-        ]
+        {
+          "min": 0,
+          "max": 60,
+          "deduction": -0.5
+        },
+        {
+          "min": 60,
+          "max": 70,
+          "deduction": -1
+        },
+        {
+          "min": 70,
+          "max": 75,
+          "deduction": -1.5
+        },
+        {
+          "min": 75,
+          "max": 85,
+          "deduction": -2
+        },
+        {
+          "min": 85,
+          "max": 95,
+          "deduction": -2.5
+        },
+        {
+          "min": 95,
+          "max": "inf",
+          "deduction": -3
+        }
       ],
       "coefficients": {
         "horizontal": 1.2,

+ 32 - 7
app/utils/score_inference/CardScorer.py

@@ -20,15 +20,35 @@ class CardScorer:
         except json.JSONDecodeError:
             raise ValueError(f"配置文件格式错误: {config_path}")
 
-    def _get_score_from_tiers(self, value: float, rules: List[List[Any]]) -> float:
+    @staticmethod
+    def _get_score_from_tiers(value: float, rules: List[Dict[str, Any]]) -> float:
         """
-        一个通用的辅助函数,根据分层规则查找值对应的分数。
+        根据新的区间规则查找值对应的分数。
+        格式: [{"min": 0, "max": 0.5, "deduction": -1}, ...]
+        逻辑: min <= value < max (最后一项如果是inf,则包含)
         """
         for tier in rules:
-            threshold, score = tier
-            if threshold == "inf" or value < threshold:
-                return float(score)
-        return 0.0  # 如果没有匹配的规则,不扣分
+            # 获取范围,处理可能的字符串
+            min_val = tier.get("min", 0)
+            max_val_raw = tier.get("max", "inf")
+            deduction = tier.get("deduction", 0.0)
+
+            # 处理 inf
+            if max_val_raw == "inf":
+                max_val = float('inf')
+            else:
+                max_val = float(max_val_raw)
+
+            min_val = float(min_val)
+
+            # 判定范围:左闭右开 [min, max)
+            if min_val <= value < max_val:
+                return float(deduction)
+
+        # 如果数值非常大(超过了所有定义的max),或者没有匹配到
+        if rules:
+            return float(rules[-1].get("deduction", 0))
+        return 0.0
 
     def calculate_defect_score(self,
                                card_defect_type: str,
@@ -76,7 +96,12 @@ class CardScorer:
                     logger.error(f"数据缺陷类型不存在: {defect['label']}")
                     raise TypeError(f"数据缺陷类型不存在: {defect['label']}")
 
-            rules = aspect_config['rules'][defect_type]
+            # 获取规则列表
+            rules = aspect_config['rules'].get(defect_type)
+            if not rules:
+                logger.error(f"计算分数过程, 未找到配置规则: {defect_type}")
+                raise KeyError(f"计算分数过程, 未找到配置规则: {defect_type}")
+
             # 对于划痕取长度, 其他取面积
             if defect_type == "scratch_length":
                 area_mm = max(defect['width'], defect['height'])