1
0

scheme.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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. thumbnail_path: Optional[str] = None
  84. # Gray images need these fields to be Optional/None
  85. detection_image_path: Optional[str] = None
  86. modified_image_path: Optional[str] = None
  87. detection_json: Dict[str, Any]
  88. modified_json: Optional[Dict[str, Any]] = None
  89. is_edited: bool = False
  90. created_at: datetime
  91. updated_at: datetime
  92. class Config:
  93. from_attributes = True
  94. @field_validator('detection_json', 'modified_json', mode='before')
  95. @classmethod
  96. def parse_json_string(cls, v):
  97. if v is None:
  98. return None
  99. if isinstance(v, str):
  100. try:
  101. return json.loads(v)
  102. except json.JSONDecodeError:
  103. raise ValueError("Invalid JSON string in database")
  104. return v
  105. # 拦截序列化,格式化掉中间的 "T"
  106. @field_serializer('created_at', 'updated_at', check_fields=False)
  107. def serialize_datetime(self, dt: datetime, _info):
  108. if dt is not None:
  109. return dt.strftime('%Y-%m-%d %H:%M:%S')
  110. return dt
  111. class CardDetailResponse(BaseModel):
  112. """用于响应单个卡牌详细信息的模型 (主键为 id)"""
  113. id: int
  114. id_prev: Optional[int] = None
  115. id_next: Optional[int] = None
  116. card_name: Optional[str] = None
  117. cardNo: Optional[str] = None
  118. created_at: datetime
  119. updated_at: datetime
  120. card_type: str
  121. is_edited: bool
  122. review_state: int = 1
  123. detection_score: Optional[float] = None
  124. modified_score: Optional[float] = None
  125. detection_score_detail: Optional[Dict[str, Any]] = None
  126. modified_score_detail: Optional[Dict[str, Any]] = None
  127. images: List[CardImageResponse] = []
  128. class Config:
  129. from_attributes = True
  130. # 拦截序列化,格式化掉中间的 "T"
  131. @field_serializer('created_at', 'updated_at', check_fields=False)
  132. def serialize_datetime(self, dt: datetime, _info):
  133. if dt is not None:
  134. return dt.strftime('%Y-%m-%d %H:%M:%S')
  135. return dt
  136. class ImageJsonPairResponse(BaseModel):
  137. id: int
  138. detection_json: Dict[str, Any]
  139. modified_json: Optional[Dict[str, Any]] = None
  140. class Config:
  141. from_attributes = True
  142. @field_validator('detection_json', 'modified_json', mode='before')
  143. @classmethod
  144. def parse_json_string(cls, v):
  145. if v is None:
  146. return None
  147. if isinstance(v, str):
  148. try:
  149. return json.loads(v)
  150. except json.JSONDecodeError:
  151. raise ValueError("Invalid JSON string in database")
  152. return v
  153. class CardImageInListResponse(BaseModel):
  154. id: int
  155. image_type: str
  156. image_path: str
  157. detection_image_path: Optional[str] = None
  158. modified_image_path: Optional[str] = None
  159. class Config:
  160. from_attributes = True
  161. class CardListDetailResponse(BaseModel):
  162. id: int
  163. card_name: Optional[str] = None
  164. cardNo: Optional[str] = None
  165. card_type: str
  166. detection_score: Optional[float] = None
  167. modified_score: Optional[float] = None
  168. is_edited: bool
  169. review_state: int = 1
  170. created_at: datetime
  171. updated_at: datetime
  172. image_path_list: Dict[str, Optional[str]] = {}
  173. detection_image_path_list: Dict[str, Optional[str]] = {}
  174. modified_image_path_list: Dict[str, Optional[str]] = {}
  175. is_bound: bool = False
  176. class Config:
  177. from_attributes = True
  178. # 拦截序列化,格式化掉中间的 "T"
  179. @field_serializer('created_at', 'updated_at', check_fields=False)
  180. def serialize_datetime(self, dt: datetime, _info):
  181. if dt is not None:
  182. return dt.strftime('%Y-%m-%d %H:%M:%S')
  183. return dt
  184. class CardListWithTotal(BaseModel):
  185. total: int
  186. list: List[CardListDetailResponse]
  187. class CardListResponseWrapper(BaseModel):
  188. data: CardListWithTotal
  189. class ReviewUpdate(BaseModel):
  190. review_state: int = Field(..., ge=1, le=4, description="审核状态 (1待复检, 2已复检, 3审核未通过, 4审核通过)")
  191. class CardTable(BaseModel):
  192. id: int
  193. card_name: Optional[str] = None
  194. cardNo: Optional[str] = None
  195. card_type: str = "pokemon"
  196. detection_score: Optional[float] = None
  197. modified_score: Optional[float] = None
  198. is_edited: bool = False
  199. created_at: datetime
  200. updated_at: datetime
  201. review_state: int = 1
  202. class Config:
  203. from_attributes = True
  204. @field_serializer("created_at", "updated_at", check_fields=False)
  205. def serialize_datetime(self, dt: datetime, _info):
  206. if dt is not None:
  207. return dt.strftime("%Y-%m-%d %H:%M:%S")
  208. return dt
  209. class CardImageTable(BaseModel):
  210. id: int
  211. card_id: int
  212. image_type: str
  213. image_name: Optional[str] = None
  214. image_path: str
  215. detection_image_path: Optional[str] = None
  216. modified_image_path: Optional[str] = None
  217. detection_json: Dict[str, Any]
  218. modified_json: Optional[Dict[str, Any]] = None
  219. is_edited: bool = False
  220. created_at: datetime
  221. updated_at: datetime
  222. class Config:
  223. from_attributes = True
  224. @field_validator("detection_json", "modified_json", mode="before")
  225. @classmethod
  226. def parse_json_string(cls, v):
  227. if v is None:
  228. return None
  229. if isinstance(v, str):
  230. try:
  231. return json.loads(v)
  232. except json.JSONDecodeError:
  233. raise ValueError("Invalid JSON string in database")
  234. return v
  235. @field_serializer("created_at", "updated_at", check_fields=False)
  236. def serialize_datetime(self, dt: datetime, _info):
  237. if dt is not None:
  238. return dt.strftime("%Y-%m-%d %H:%M:%S")
  239. return dt
  240. class CardGrayImageTable(BaseModel):
  241. id: int
  242. card_id: int
  243. image_type: str
  244. image_path: str
  245. created_at: datetime
  246. updated_at: datetime
  247. class Config:
  248. from_attributes = True
  249. @field_serializer("created_at", "updated_at", check_fields=False)
  250. def serialize_datetime(self, dt: datetime, _info):
  251. if dt is not None:
  252. return dt.strftime("%Y-%m-%d %H:%M:%S")
  253. return dt