import json from datetime import datetime from pathlib import Path from typing import Optional, Dict, Any, List from pydantic import BaseModel, field_validator, Field, field_serializer from enum import Enum class ImageType(str, Enum): # 历史同轴光图类型,仅保留兼容旧数据 front_coaxial = "front_coaxial" back_coaxial = "back_coaxial" # 环光图 front_ring = "front_ring" back_ring = "back_ring" # 灰度图类型 front_gray = "front_gray" back_gray = "back_gray" # 融合图类型 front_fusion = "front_fusion" back_fusion = "back_fusion" # 调光(stripe)图,分别对应一面的 4 张 front_stripe1 = "front_stripe1" front_stripe2 = "front_stripe2" front_stripe3 = "front_stripe3" front_stripe4 = "front_stripe4" back_stripe1 = "back_stripe1" back_stripe2 = "back_stripe2" back_stripe3 = "back_stripe3" back_stripe4 = "back_stripe4" class ScoreType(str, Enum): front_corner_edge = "front_corner_edge" front_face = "front_face" front_face_ring_light = "front_face_ring_light" back_corner_edge = "back_corner_edge" back_face = "back_face" back_face_ring_light = "back_face_ring_light" class CardType(str, Enum): pokemon = "pokemon" basketball = "basketball" football = "football" class ResultImagePathType(str, Enum): detection = "detection" modified = "modified" class SortBy(str, Enum): card_name = "card_name" created_at = "created_at" updated_at = "updated_at" detection_score = "detection_score" modified_score = "modified_score" class SortOrder(str, Enum): asc = "ASC" desc = "DESC" class CardNoList(BaseModel): cardNoList: List[str] # 图片类型和推理服务 score_type 映射表 # stitch_score_inference 按 front / back 整面调用,故新版主流程不再依赖这张表。 # 保留映射主要是为了让旧的编辑/重算逻辑(按单张图)继续可用。 IMAGE_TYPE_TO_SCORE_TYPE = { ImageType.front_coaxial.value: ScoreType.front_face.value, ImageType.back_coaxial.value: ScoreType.back_face.value, ImageType.front_ring.value: ScoreType.front_corner_edge.value, ImageType.back_ring.value: ScoreType.back_corner_edge.value, ImageType.front_gray.value: None, ImageType.back_gray.value: None, ImageType.front_fusion.value: None, ImageType.back_fusion.value: None, ImageType.front_stripe1.value: None, ImageType.front_stripe2.value: None, ImageType.front_stripe3.value: None, ImageType.front_stripe4.value: None, ImageType.back_stripe1.value: None, ImageType.back_stripe2.value: None, ImageType.back_stripe3.value: None, ImageType.back_stripe4.value: None, } # --- Pydantic 数据模型 --- class CardImageResponse(BaseModel): """用于API响应的图片数据模型 (主键为 id)""" id: int card_id: int image_type: str image_name: Optional[str] = None image_path: str thumbnail_path: Optional[str] = None # Gray images need these fields to be Optional/None detection_image_path: Optional[str] = None modified_image_path: Optional[str] = None detection_json: Dict[str, Any] modified_json: Optional[Dict[str, Any]] = None is_edited: bool = False created_at: datetime updated_at: datetime class Config: from_attributes = True @field_validator('detection_json', 'modified_json', mode='before') @classmethod def parse_json_string(cls, v): if v is None: return None if isinstance(v, str): try: return json.loads(v) except json.JSONDecodeError: raise ValueError("Invalid JSON string in database") return v # 拦截序列化,格式化掉中间的 "T" @field_serializer('created_at', 'updated_at', check_fields=False) def serialize_datetime(self, dt: datetime, _info): if dt is not None: return dt.strftime('%Y-%m-%d %H:%M:%S') return dt class CardDetailResponse(BaseModel): """用于响应单个卡牌详细信息的模型 (主键为 id)""" id: int id_prev: Optional[int] = None id_next: Optional[int] = None card_name: Optional[str] = None cardNo: Optional[str] = None created_at: datetime updated_at: datetime card_type: str is_edited: bool review_state: int = 1 detection_score: Optional[float] = None modified_score: Optional[float] = None detection_score_detail: Optional[Dict[str, Any]] = None modified_score_detail: Optional[Dict[str, Any]] = None images: List[CardImageResponse] = [] class Config: from_attributes = True # 拦截序列化,格式化掉中间的 "T" @field_serializer('created_at', 'updated_at', check_fields=False) def serialize_datetime(self, dt: datetime, _info): if dt is not None: return dt.strftime('%Y-%m-%d %H:%M:%S') return dt class ImageJsonPairResponse(BaseModel): id: int detection_json: Dict[str, Any] modified_json: Optional[Dict[str, Any]] = None class Config: from_attributes = True @field_validator('detection_json', 'modified_json', mode='before') @classmethod def parse_json_string(cls, v): if v is None: return None if isinstance(v, str): try: return json.loads(v) except json.JSONDecodeError: raise ValueError("Invalid JSON string in database") return v class CardImageInListResponse(BaseModel): id: int image_type: str image_path: str detection_image_path: Optional[str] = None modified_image_path: Optional[str] = None class Config: from_attributes = True class CardListDetailResponse(BaseModel): id: int card_name: Optional[str] = None cardNo: Optional[str] = None card_type: str detection_score: Optional[float] = None modified_score: Optional[float] = None is_edited: bool review_state: int = 1 created_at: datetime updated_at: datetime image_path_list: Dict[str, Optional[str]] = {} detection_image_path_list: Dict[str, Optional[str]] = {} modified_image_path_list: Dict[str, Optional[str]] = {} is_bound: bool = False class Config: from_attributes = True # 拦截序列化,格式化掉中间的 "T" @field_serializer('created_at', 'updated_at', check_fields=False) def serialize_datetime(self, dt: datetime, _info): if dt is not None: return dt.strftime('%Y-%m-%d %H:%M:%S') return dt class CardListWithTotal(BaseModel): total: int list: List[CardListDetailResponse] class CardListResponseWrapper(BaseModel): data: CardListWithTotal class ReviewUpdate(BaseModel): review_state: int = Field(..., ge=1, le=4, description="审核状态 (1待复检, 2已复检, 3审核未通过, 4审核通过)") class CardTable(BaseModel): id: int card_name: Optional[str] = None cardNo: Optional[str] = None card_type: str = "pokemon" detection_score: Optional[float] = None modified_score: Optional[float] = None is_edited: bool = False created_at: datetime updated_at: datetime review_state: int = 1 class Config: from_attributes = True @field_serializer("created_at", "updated_at", check_fields=False) def serialize_datetime(self, dt: datetime, _info): if dt is not None: return dt.strftime("%Y-%m-%d %H:%M:%S") return dt class CardImageTable(BaseModel): id: int card_id: int image_type: str image_name: Optional[str] = None image_path: str detection_image_path: Optional[str] = None modified_image_path: Optional[str] = None detection_json: Dict[str, Any] modified_json: Optional[Dict[str, Any]] = None is_edited: bool = False created_at: datetime updated_at: datetime class Config: from_attributes = True @field_validator("detection_json", "modified_json", mode="before") @classmethod def parse_json_string(cls, v): if v is None: return None if isinstance(v, str): try: return json.loads(v) except json.JSONDecodeError: raise ValueError("Invalid JSON string in database") return v @field_serializer("created_at", "updated_at", check_fields=False) def serialize_datetime(self, dt: datetime, _info): if dt is not None: return dt.strftime("%Y-%m-%d %H:%M:%S") return dt class CardGrayImageTable(BaseModel): id: int card_id: int image_type: str image_path: str created_at: datetime updated_at: datetime class Config: from_attributes = True @field_serializer("created_at", "updated_at", check_fields=False) def serialize_datetime(self, dt: datetime, _info): if dt is not None: return dt.strftime("%Y-%m-%d %H:%M:%S") return dt