1
0

scheme.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. import json
  2. from datetime import datetime
  3. from pathlib import Path
  4. from typing import Optional, Dict, Any, List
  5. from pydantic import BaseModel, field_validator, Field, field_serializer
  6. from enum import Enum
  7. class ImageType(str, Enum):
  8. # 历史同轴光图类型,仅保留兼容旧数据
  9. front_coaxial = "front_coaxial"
  10. back_coaxial = "back_coaxial"
  11. # 环光图
  12. front_ring = "front_ring"
  13. back_ring = "back_ring"
  14. # 灰度图类型
  15. front_gray = "front_gray"
  16. back_gray = "back_gray"
  17. # 融合图类型
  18. front_fusion = "front_fusion"
  19. back_fusion = "back_fusion"
  20. # 调光(stripe)图,分别对应一面的 4 张
  21. front_stripe1 = "front_stripe1"
  22. front_stripe2 = "front_stripe2"
  23. front_stripe3 = "front_stripe3"
  24. front_stripe4 = "front_stripe4"
  25. back_stripe1 = "back_stripe1"
  26. back_stripe2 = "back_stripe2"
  27. back_stripe3 = "back_stripe3"
  28. back_stripe4 = "back_stripe4"
  29. class ScoreType(str, Enum):
  30. front_corner_edge = "front_corner_edge"
  31. front_face = "front_face"
  32. front_face_ring_light = "front_face_ring_light"
  33. back_corner_edge = "back_corner_edge"
  34. back_face = "back_face"
  35. back_face_ring_light = "back_face_ring_light"
  36. class CardType(str, Enum):
  37. pokemon = "pokemon"
  38. basketball = "basketball"
  39. football = "football"
  40. class ResultImagePathType(str, Enum):
  41. detection = "detection"
  42. modified = "modified"
  43. class SortBy(str, Enum):
  44. card_name = "card_name"
  45. created_at = "created_at"
  46. updated_at = "updated_at"
  47. detection_score = "detection_score"
  48. modified_score = "modified_score"
  49. class SortOrder(str, Enum):
  50. asc = "ASC"
  51. desc = "DESC"
  52. class CardNoList(BaseModel):
  53. cardNoList: List[str]
  54. # 图片类型和推理服务 score_type 映射表
  55. # stitch_score_inference 按 front / back 整面调用,故新版主流程不再依赖这张表。
  56. # 保留映射主要是为了让旧的编辑/重算逻辑(按单张图)继续可用。
  57. IMAGE_TYPE_TO_SCORE_TYPE = {
  58. ImageType.front_coaxial.value: ScoreType.front_face.value,
  59. ImageType.back_coaxial.value: ScoreType.back_face.value,
  60. ImageType.front_ring.value: ScoreType.front_corner_edge.value,
  61. ImageType.back_ring.value: ScoreType.back_corner_edge.value,
  62. ImageType.front_gray.value: None,
  63. ImageType.back_gray.value: None,
  64. ImageType.front_fusion.value: None,
  65. ImageType.back_fusion.value: None,
  66. ImageType.front_stripe1.value: None,
  67. ImageType.front_stripe2.value: None,
  68. ImageType.front_stripe3.value: None,
  69. ImageType.front_stripe4.value: None,
  70. ImageType.back_stripe1.value: None,
  71. ImageType.back_stripe2.value: None,
  72. ImageType.back_stripe3.value: None,
  73. ImageType.back_stripe4.value: None,
  74. }
  75. # --- Pydantic 数据模型 ---
  76. class CardImageResponse(BaseModel):
  77. """用于API响应的图片数据模型 (主键为 id)"""
  78. id: int
  79. card_id: int
  80. image_type: str
  81. image_name: Optional[str] = None
  82. image_path: str
  83. # Gray images need these fields to be Optional/None
  84. detection_image_path: Optional[str] = None
  85. modified_image_path: Optional[str] = None
  86. detection_json: Dict[str, Any]
  87. modified_json: Optional[Dict[str, Any]] = None
  88. is_edited: bool = False
  89. created_at: datetime
  90. updated_at: datetime
  91. class Config:
  92. from_attributes = True
  93. @field_validator('detection_json', 'modified_json', mode='before')
  94. @classmethod
  95. def parse_json_string(cls, v):
  96. if v is None:
  97. return None
  98. if isinstance(v, str):
  99. try:
  100. return json.loads(v)
  101. except json.JSONDecodeError:
  102. raise ValueError("Invalid JSON string in database")
  103. return v
  104. # 拦截序列化,格式化掉中间的 "T"
  105. @field_serializer('created_at', 'updated_at', check_fields=False)
  106. def serialize_datetime(self, dt: datetime, _info):
  107. if dt is not None:
  108. return dt.strftime('%Y-%m-%d %H:%M:%S')
  109. return dt
  110. class CardDetailResponse(BaseModel):
  111. """用于响应单个卡牌详细信息的模型 (主键为 id)"""
  112. id: int
  113. id_prev: Optional[int] = None
  114. id_next: Optional[int] = None
  115. card_name: Optional[str] = None
  116. cardNo: Optional[str] = None
  117. created_at: datetime
  118. updated_at: datetime
  119. card_type: str
  120. is_edited: bool
  121. review_state: int = 1
  122. detection_score: Optional[float] = None
  123. modified_score: Optional[float] = None
  124. detection_score_detail: Optional[Dict[str, Any]] = None
  125. modified_score_detail: Optional[Dict[str, Any]] = None
  126. images: List[CardImageResponse] = []
  127. class Config:
  128. from_attributes = True
  129. # 拦截序列化,格式化掉中间的 "T"
  130. @field_serializer('created_at', 'updated_at', check_fields=False)
  131. def serialize_datetime(self, dt: datetime, _info):
  132. if dt is not None:
  133. return dt.strftime('%Y-%m-%d %H:%M:%S')
  134. return dt
  135. class ImageJsonPairResponse(BaseModel):
  136. id: int
  137. detection_json: Dict[str, Any]
  138. modified_json: Optional[Dict[str, Any]] = None
  139. class Config:
  140. from_attributes = True
  141. @field_validator('detection_json', 'modified_json', mode='before')
  142. @classmethod
  143. def parse_json_string(cls, v):
  144. if v is None:
  145. return None
  146. if isinstance(v, str):
  147. try:
  148. return json.loads(v)
  149. except json.JSONDecodeError:
  150. raise ValueError("Invalid JSON string in database")
  151. return v
  152. class CardImageInListResponse(BaseModel):
  153. id: int
  154. image_type: str
  155. image_path: str
  156. detection_image_path: Optional[str] = None
  157. modified_image_path: Optional[str] = None
  158. class Config:
  159. from_attributes = True
  160. class CardListDetailResponse(BaseModel):
  161. id: int
  162. card_name: Optional[str] = None
  163. cardNo: Optional[str] = None
  164. card_type: str
  165. detection_score: Optional[float] = None
  166. modified_score: Optional[float] = None
  167. is_edited: bool
  168. review_state: int = 1
  169. created_at: datetime
  170. updated_at: datetime
  171. image_path_list: Dict[str, Optional[str]] = {}
  172. detection_image_path_list: Dict[str, Optional[str]] = {}
  173. modified_image_path_list: Dict[str, Optional[str]] = {}
  174. is_bound: bool = False
  175. class Config:
  176. from_attributes = True
  177. # 拦截序列化,格式化掉中间的 "T"
  178. @field_serializer('created_at', 'updated_at', check_fields=False)
  179. def serialize_datetime(self, dt: datetime, _info):
  180. if dt is not None:
  181. return dt.strftime('%Y-%m-%d %H:%M:%S')
  182. return dt
  183. class CardListWithTotal(BaseModel):
  184. total: int
  185. list: List[CardListDetailResponse]
  186. class CardListResponseWrapper(BaseModel):
  187. data: CardListWithTotal
  188. class ReviewUpdate(BaseModel):
  189. review_state: int = Field(..., ge=1, le=4, description="审核状态 (1待复检, 2已复检, 3审核未通过, 4审核通过)")
  190. class CardTable(BaseModel):
  191. id: int
  192. card_name: Optional[str] = None
  193. cardNo: Optional[str] = None
  194. card_type: str = "pokemon"
  195. detection_score: Optional[float] = None
  196. modified_score: Optional[float] = None
  197. is_edited: bool = False
  198. created_at: datetime
  199. updated_at: datetime
  200. review_state: int = 1
  201. class Config:
  202. from_attributes = True
  203. @field_serializer("created_at", "updated_at", check_fields=False)
  204. def serialize_datetime(self, dt: datetime, _info):
  205. if dt is not None:
  206. return dt.strftime("%Y-%m-%d %H:%M:%S")
  207. return dt
  208. class CardImageTable(BaseModel):
  209. id: int
  210. card_id: int
  211. image_type: str
  212. image_name: Optional[str] = None
  213. image_path: str
  214. detection_image_path: Optional[str] = None
  215. modified_image_path: Optional[str] = None
  216. detection_json: Dict[str, Any]
  217. modified_json: Optional[Dict[str, Any]] = None
  218. is_edited: bool = False
  219. created_at: datetime
  220. updated_at: datetime
  221. class Config:
  222. from_attributes = True
  223. @field_validator("detection_json", "modified_json", mode="before")
  224. @classmethod
  225. def parse_json_string(cls, v):
  226. if v is None:
  227. return None
  228. if isinstance(v, str):
  229. try:
  230. return json.loads(v)
  231. except json.JSONDecodeError:
  232. raise ValueError("Invalid JSON string in database")
  233. return v
  234. @field_serializer("created_at", "updated_at", check_fields=False)
  235. def serialize_datetime(self, dt: datetime, _info):
  236. if dt is not None:
  237. return dt.strftime("%Y-%m-%d %H:%M:%S")
  238. return dt
  239. class CardGrayImageTable(BaseModel):
  240. id: int
  241. card_id: int
  242. image_type: str
  243. image_path: str
  244. created_at: datetime
  245. updated_at: datetime
  246. class Config:
  247. from_attributes = True
  248. @field_serializer("created_at", "updated_at", check_fields=False)
  249. def serialize_datetime(self, dt: datetime, _info):
  250. if dt is not None:
  251. return dt.strftime("%Y-%m-%d %H:%M:%S")
  252. return dt