test01.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. def validate_rule_ranges(data: dict, path: str = "") -> (bool, str):
  2. """
  3. 递归检查配置中看起来像评分规则的列表。
  4. 规则:
  5. 1. 列表中的元素必须包含 min, max, deduction。
  6. 2. 第一个元素的 min 必须为 0。
  7. 3. 当前元素的 min 必须等于上一个元素的 max。
  8. """
  9. if isinstance(data, dict):
  10. for key, value in data.items():
  11. is_valid, error = validate_rule_ranges(value, path=f"{path}.{key}" if path else key)
  12. if not is_valid:
  13. return False, error
  14. elif isinstance(data, list):
  15. # 检查这是否是一个规则列表(通过检查第一个元素是否包含特定key)
  16. if len(data) > 0 and isinstance(data[0], dict) and "min" in data[0] and "max" in data[0] and "deduction" in \
  17. data[0]:
  18. current_path = path
  19. # 1. 检查首项 min 是否为 0
  20. first_min = data[0].get("min")
  21. if first_min != 0:
  22. return False, f"范围错误 [{current_path}]: 第一个区间的 min 必须为 0,当前为 {first_min}"
  23. # 2. 检查连续性
  24. prev_max = None
  25. for idx, rule in enumerate(data):
  26. current_min = rule.get("min")
  27. current_max = rule.get("max")
  28. # 处理 inf 字符串
  29. if current_max == "inf":
  30. current_max_val = float("inf")
  31. else:
  32. try:
  33. current_max_val = float(current_max)
  34. except (ValueError, TypeError):
  35. return False, f"数值错误 [{current_path}]: max 值 '{current_max}' 无效"
  36. try:
  37. current_min_val = float(current_min)
  38. except (ValueError, TypeError):
  39. return False, f"数值错误 [{current_path}]: min 值 '{current_min}' 无效"
  40. # 检查连续性 (除了第一个元素)
  41. if idx > 0:
  42. # 使用一个微小的容差处理浮点数比较,或者直接相等
  43. if prev_max == "inf":
  44. return False, f"逻辑错误 [{current_path}]: 'inf' 只能出现在最后一个区间的 max"
  45. prev_max_val = float(prev_max) if prev_max != "inf" else float("inf")
  46. if current_min_val != prev_max_val:
  47. return False, f"不连续错误 [{current_path}]: 第 {idx + 1} 项的 min ({current_min}) 不等于上一项的 max ({prev_max})"
  48. prev_max = current_max
  49. return True, "验证通过"
  50. if __name__ == '__main__':
  51. new_config = {
  52. "base_score": 10,
  53. "corner": {
  54. "rules": {
  55. "wear_area": [
  56. {
  57. "min": 0,
  58. "max": 0.05,
  59. "deduction": -0.1
  60. },
  61. {
  62. "min": 0.05,
  63. "max": 0.1,
  64. "deduction": -0.5
  65. },
  66. {
  67. "min": 0.1,
  68. "max": 0.25,
  69. "deduction": -1.5
  70. },
  71. {
  72. "min": 0.25,
  73. "max": 0.5,
  74. "deduction": -3
  75. },
  76. {
  77. "min": 0.5,
  78. "max": "inf",
  79. "deduction": -5
  80. }
  81. ],
  82. "loss_area": [
  83. {
  84. "min": 0,
  85. "max": 0.05,
  86. "deduction": -0.1
  87. },
  88. {
  89. "min": 0.05,
  90. "max": 0.1,
  91. "deduction": -0.5
  92. },
  93. {
  94. "min": 0.1,
  95. "max": 0.25,
  96. "deduction": -1.5
  97. },
  98. {
  99. "min": 0.25,
  100. "max": 0.5,
  101. "deduction": -3
  102. },
  103. {
  104. "min": 0.5,
  105. "max": "inf",
  106. "deduction": -5
  107. }
  108. ]
  109. },
  110. "front_weights": {
  111. "wear_area": 0.3,
  112. "loss_area": 0.7
  113. },
  114. "back_weights": {
  115. "wear_area": 0.3,
  116. "loss_area": 0.7
  117. },
  118. "final_weights": {
  119. "front": 0.7,
  120. "back": 0.3
  121. }
  122. },
  123. "edge": {
  124. "rules": {
  125. "wear_area": [
  126. {
  127. "min": 0,
  128. "max": 0.05,
  129. "deduction": -0.1
  130. },
  131. {
  132. "min": 0.05,
  133. "max": 0.1,
  134. "deduction": -0.5
  135. },
  136. {
  137. "min": 0.1,
  138. "max": 0.25,
  139. "deduction": -1.5
  140. },
  141. {
  142. "min": 0.25,
  143. "max": 0.5,
  144. "deduction": -3
  145. },
  146. {
  147. "min": 0.5,
  148. "max": "inf",
  149. "deduction": -5
  150. }
  151. ],
  152. "loss_area": [
  153. {
  154. "min": 0,
  155. "max": 0.05,
  156. "deduction": -0.1
  157. },
  158. {
  159. "min": 0.05,
  160. "max": 0.1,
  161. "deduction": -0.5
  162. },
  163. {
  164. "min": 0.1,
  165. "max": 0.25,
  166. "deduction": -1.5
  167. },
  168. {
  169. "min": 0.25,
  170. "max": 0.5,
  171. "deduction": -3
  172. },
  173. {
  174. "min": 0.5,
  175. "max": "inf",
  176. "deduction": -5
  177. }
  178. ]
  179. },
  180. "front_weights": {
  181. "wear_area": 0.4,
  182. "loss_area": 0.6
  183. },
  184. "back_weights": {
  185. "wear_area": 0.4,
  186. "loss_area": 0.6
  187. },
  188. "final_weights": {
  189. "front": 0.7,
  190. "back": 0.3
  191. }
  192. },
  193. "face": {
  194. "rules": {
  195. "wear_area": [
  196. {
  197. "min": 0,
  198. "max": 0.05,
  199. "deduction": -0.1
  200. },
  201. {
  202. "min": 0.05,
  203. "max": 0.1,
  204. "deduction": -0.5
  205. },
  206. {
  207. "min": 0.1,
  208. "max": 0.25,
  209. "deduction": -1.5
  210. },
  211. {
  212. "min": 0.25,
  213. "max": 0.5,
  214. "deduction": -3
  215. },
  216. {
  217. "min": 0.5,
  218. "max": "inf",
  219. "deduction": -5
  220. }
  221. ],
  222. "pit_area": [
  223. {
  224. "min": 0,
  225. "max": 0.05,
  226. "deduction": -0.1
  227. },
  228. {
  229. "min": 0.05,
  230. "max": 0.1,
  231. "deduction": -0.5
  232. },
  233. {
  234. "min": 0.1,
  235. "max": 0.25,
  236. "deduction": -1.5
  237. },
  238. {
  239. "min": 0.25,
  240. "max": 0.5,
  241. "deduction": -3
  242. },
  243. {
  244. "min": 0.5,
  245. "max": "inf",
  246. "deduction": -5
  247. }
  248. ],
  249. "stain_area": [
  250. {
  251. "min": 0,
  252. "max": 0.05,
  253. "deduction": -0.1
  254. },
  255. {
  256. "min": 0.05,
  257. "max": 0.1,
  258. "deduction": -0.5
  259. },
  260. {
  261. "min": 0.1,
  262. "max": 0.25,
  263. "deduction": -1.5
  264. },
  265. {
  266. "min": 0.25,
  267. "max": 0.5,
  268. "deduction": -3
  269. },
  270. {
  271. "min": 0.5,
  272. "max": "inf",
  273. "deduction": -5
  274. }
  275. ],
  276. "scratch_length": [
  277. {
  278. "min": 0,
  279. "max": 1,
  280. "deduction": -0.1
  281. },
  282. {
  283. "min": 1,
  284. "max": 2,
  285. "deduction": -0.5
  286. },
  287. {
  288. "min": 2,
  289. "max": 5,
  290. "deduction": -1
  291. },
  292. {
  293. "min": 5,
  294. "max": 10,
  295. "deduction": -2
  296. },
  297. {
  298. "min": 10,
  299. "max": 20,
  300. "deduction": -3
  301. },
  302. {
  303. "min": 20,
  304. "max": 50,
  305. "deduction": -4
  306. },
  307. {
  308. "min": 50,
  309. "max": "inf",
  310. "deduction": -5
  311. }
  312. ]
  313. },
  314. "coefficients": {
  315. "wear_area": 0.25,
  316. "scratch_length": 0.25,
  317. "dent_area": 0.25,
  318. "stain_area": 0.25
  319. },
  320. "final_weights": {
  321. "front": 0.75,
  322. "back": 0.25
  323. }
  324. },
  325. "centering": {
  326. "front": {
  327. "rules": [
  328. {
  329. "min": 0,
  330. "max": 52,
  331. "deduction": 0
  332. },
  333. {
  334. "min": 52,
  335. "max": 55,
  336. "deduction": -0.5
  337. },
  338. {
  339. "min": 55,
  340. "max": 60,
  341. "deduction": -1
  342. },
  343. {
  344. "min": 60,
  345. "max": 62.5,
  346. "deduction": -1.5
  347. },
  348. {
  349. "min": 62.5,
  350. "max": 65,
  351. "deduction": -2
  352. },
  353. {
  354. "min": 65,
  355. "max": 67.5,
  356. "deduction": -2.5
  357. },
  358. {
  359. "min": 67.5,
  360. "max": 70,
  361. "deduction": -3
  362. },
  363. {
  364. "min": 70,
  365. "max": 72.5,
  366. "deduction": -3.5
  367. },
  368. {
  369. "min": 72.5,
  370. "max": 75,
  371. "deduction": -4
  372. },
  373. {
  374. "min": 75,
  375. "max": 77.5,
  376. "deduction": -4.5
  377. },
  378. {
  379. "min": 77.5,
  380. "max": 80,
  381. "deduction": -5
  382. },
  383. {
  384. "min": 80,
  385. "max": 82.5,
  386. "deduction": -5.5
  387. },
  388. {
  389. "min": 82.5,
  390. "max": 85,
  391. "deduction": -6
  392. },
  393. {
  394. "min": 85,
  395. "max": 87.5,
  396. "deduction": -6.5
  397. },
  398. {
  399. "min": 87.5,
  400. "max": 90,
  401. "deduction": -7
  402. },
  403. {
  404. "min": 90,
  405. "max": 92.5,
  406. "deduction": -7.5
  407. },
  408. {
  409. "min": 92.5,
  410. "max": 95,
  411. "deduction": -8
  412. },
  413. {
  414. "min": 95,
  415. "max": 97.5,
  416. "deduction": -8.5
  417. },
  418. {
  419. "min": 97.5,
  420. "max": "inf",
  421. "deduction": -9
  422. }
  423. ],
  424. "coefficients": {
  425. "horizontal": 1.2,
  426. "vertical": 0.9
  427. }
  428. },
  429. "back": {
  430. "rules": [
  431. {
  432. "min": 0,
  433. "max": 60,
  434. "deduction": -0.5
  435. },
  436. {
  437. "min": 60,
  438. "max": 70,
  439. "deduction": -1
  440. },
  441. {
  442. "min": 70,
  443. "max": 75,
  444. "deduction": -1.5
  445. },
  446. {
  447. "min": 75,
  448. "max": 85,
  449. "deduction": -2
  450. },
  451. {
  452. "min": 85,
  453. "max": 95,
  454. "deduction": -2.5
  455. },
  456. {
  457. "min": 95,
  458. "max": "inf",
  459. "deduction": -3
  460. }
  461. ],
  462. "coefficients": {
  463. "horizontal": 1.2,
  464. "vertical": 0.9
  465. }
  466. },
  467. "final_weights": {
  468. "front": 0.75,
  469. "back": 0.25
  470. }
  471. },
  472. "card": {
  473. "PSA": {
  474. "face": 0.35,
  475. "corner": 0.3,
  476. "edge": 0.1,
  477. "center": 0.25
  478. },
  479. "BGS": {
  480. "face": 0.3,
  481. "corner": 0.25,
  482. "edge": 0.1,
  483. "center": 0.25
  484. }
  485. }
  486. }
  487. is_range_valid, range_reason = validate_rule_ranges(new_config)
  488. print(is_range_valid, range_reason)