|
@@ -153,6 +153,146 @@ def _prepare_recalculate_payload(edited_json: dict, source_json: dict) -> dict:
|
|
|
return base
|
|
return base
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+_GRAY_IMAGE_TYPES = frozenset({
|
|
|
|
|
+ ImageType.front_gray.value,
|
|
|
|
|
+ ImageType.back_gray.value,
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+# 以融合图 JSON 中的缺陷为准,在同面下列类型原图上裁图(不含灰度图)
|
|
|
|
|
+_FRONT_DEFECT_URL_TARGET_TYPES = [
|
|
|
|
|
+ ImageType.front_fusion.value,
|
|
|
|
|
+ ImageType.front_ring.value,
|
|
|
|
|
+ ImageType.front_stripe1.value,
|
|
|
|
|
+ ImageType.front_stripe2.value,
|
|
|
|
|
+ ImageType.front_stripe3.value,
|
|
|
|
|
+ ImageType.front_stripe4.value,
|
|
|
|
|
+ ImageType.front_coaxial.value, # 兼容历史同轴光
|
|
|
|
|
+]
|
|
|
|
|
+_BACK_DEFECT_URL_TARGET_TYPES = [
|
|
|
|
|
+ ImageType.back_fusion.value,
|
|
|
|
|
+ ImageType.back_ring.value,
|
|
|
|
|
+ ImageType.back_stripe1.value,
|
|
|
|
|
+ ImageType.back_stripe2.value,
|
|
|
|
|
+ ImageType.back_stripe3.value,
|
|
|
|
|
+ ImageType.back_stripe4.value,
|
|
|
|
|
+ ImageType.back_coaxial.value,
|
|
|
|
|
+]
|
|
|
|
|
+_DEFECT_URL_TARGET_TYPES_BY_SIDE = {
|
|
|
|
|
+ "front": _FRONT_DEFECT_URL_TARGET_TYPES,
|
|
|
|
|
+ "back": _BACK_DEFECT_URL_TARGET_TYPES,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# ring / stripe 等可能落在 card_gray_images,裁图时需与主表合并
|
|
|
|
|
+_ALL_DEFECT_URL_TARGET_TYPES = frozenset(
|
|
|
|
|
+ _FRONT_DEFECT_URL_TARGET_TYPES + _BACK_DEFECT_URL_TARGET_TYPES
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _is_gray_image_type(image_type: str) -> bool:
|
|
|
|
|
+ return image_type in _GRAY_IMAGE_TYPES
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _side_key_from_image_type(image_type: str) -> str:
|
|
|
|
|
+ if image_type.startswith("front_"):
|
|
|
|
|
+ return "front"
|
|
|
|
|
+ if image_type.startswith("back_"):
|
|
|
|
|
+ return "back"
|
|
|
|
|
+ return ""
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _defect_rect_hash(min_rect) -> str:
|
|
|
|
|
+ if not min_rect or len(min_rect) != 3:
|
|
|
|
|
+ return ""
|
|
|
|
|
+ rect_str = str(min_rect)
|
|
|
|
|
+ return hashlib.md5(rect_str.encode("utf-8")).hexdigest()[:8]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _resolve_fusion_images_by_side(all_images: list) -> dict:
|
|
|
|
|
+ """每面仅以融合图 JSON 作为缺陷与裁图坐标来源。"""
|
|
|
|
|
+ type_to_img = {getattr(img, "image_type", ""): img for img in all_images}
|
|
|
|
|
+ return {
|
|
|
|
|
+ "front": type_to_img.get(ImageType.front_fusion.value),
|
|
|
|
|
+ "back": type_to_img.get(ImageType.back_fusion.value),
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class _DefectCropImageRef:
|
|
|
|
|
+ """裁图用的轻量图片引用(主表 Pydantic 或灰度辅助表行均可)。"""
|
|
|
|
|
+
|
|
|
|
|
+ __slots__ = ("id", "image_type", "image_path")
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, image_id: int, image_type: str, image_path: str):
|
|
|
|
|
+ self.id = image_id
|
|
|
|
|
+ self.image_type = image_type
|
|
|
|
|
+ self.image_path = image_path
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _build_defect_crop_pool_by_type(
|
|
|
|
|
+ card_id: int,
|
|
|
|
|
+ all_images: list,
|
|
|
|
|
+ db_conn: PooledMySQLConnection = None,
|
|
|
|
|
+) -> dict:
|
|
|
|
|
+ """
|
|
|
|
|
+ 合并主表 card_images 与辅助表 card_gray_images 中的裁图目标。
|
|
|
|
|
+ 同类型主表优先;ring/stripe 导入在灰度表时也能被 defectImgUrls 用到。
|
|
|
|
|
+ """
|
|
|
|
|
+ pool: dict = {}
|
|
|
|
|
+ for img in all_images or []:
|
|
|
|
|
+ image_type = getattr(img, "image_type", "")
|
|
|
|
|
+ if image_type not in _ALL_DEFECT_URL_TARGET_TYPES:
|
|
|
|
|
+ continue
|
|
|
|
|
+ pool[image_type] = img
|
|
|
|
|
+
|
|
|
|
|
+ if db_conn is not None:
|
|
|
|
|
+ cursor = None
|
|
|
|
|
+ try:
|
|
|
|
|
+ cursor = db_conn.cursor(dictionary=True)
|
|
|
|
|
+ cursor.execute(
|
|
|
|
|
+ f"SELECT id, image_type, image_path FROM {settings.DB_GRAY_IMAGE_TABLE_NAME} "
|
|
|
|
|
+ f"WHERE card_id = %s",
|
|
|
|
|
+ (card_id,),
|
|
|
|
|
+ )
|
|
|
|
|
+ for row in cursor.fetchall():
|
|
|
|
|
+ image_type = row.get("image_type") or ""
|
|
|
|
|
+ if image_type not in _ALL_DEFECT_URL_TARGET_TYPES:
|
|
|
|
|
+ continue
|
|
|
|
|
+ if image_type in pool:
|
|
|
|
|
+ continue
|
|
|
|
|
+ pool[image_type] = _DefectCropImageRef(
|
|
|
|
|
+ row["id"],
|
|
|
|
|
+ image_type,
|
|
|
|
|
+ settings.get_full_url(row.get("image_path")),
|
|
|
|
|
+ )
|
|
|
|
|
+ finally:
|
|
|
|
|
+ if cursor:
|
|
|
|
|
+ cursor.close()
|
|
|
|
|
+
|
|
|
|
|
+ return pool
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _defect_url_target_type_list(crop_pool_by_type: dict, side_key: str) -> list:
|
|
|
|
|
+ """同面裁图类型列表;若已有 stripe 则不再使用历史 coaxial。"""
|
|
|
|
|
+ target_types = list(_DEFECT_URL_TARGET_TYPES_BY_SIDE.get(side_key, []))
|
|
|
|
|
+ has_stripe = any(
|
|
|
|
|
+ t.startswith(f"{side_key}_stripe") and t in crop_pool_by_type
|
|
|
|
|
+ for t in target_types
|
|
|
|
|
+ )
|
|
|
|
|
+ if has_stripe:
|
|
|
|
|
+ coaxial = ImageType.front_coaxial.value if side_key == "front" else ImageType.back_coaxial.value
|
|
|
|
|
+ target_types = [t for t in target_types if t != coaxial]
|
|
|
|
|
+ return target_types
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _defect_url_target_images(crop_pool_by_type: dict, side_key: str) -> list:
|
|
|
|
|
+ """按固定类型顺序返回同面需生成 defectImgUrls 的图片(灰度图除外)。"""
|
|
|
|
|
+ targets = []
|
|
|
|
|
+ for image_type in _defect_url_target_type_list(crop_pool_by_type, side_key):
|
|
|
|
|
+ img = crop_pool_by_type.get(image_type)
|
|
|
|
|
+ if img is not None:
|
|
|
|
|
+ targets.append(img)
|
|
|
|
|
+ return targets
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def _sanitize_defects_for_recalculate(defects: list):
|
|
def _sanitize_defects_for_recalculate(defects: list):
|
|
|
"""
|
|
"""
|
|
|
清理前端展示/编辑辅助字段,减少算分服务解析失败概率。
|
|
清理前端展示/编辑辅助字段,减少算分服务解析失败概率。
|
|
@@ -173,47 +313,48 @@ def _sanitize_defects_for_recalculate(defects: list):
|
|
|
d.pop("new_score", None)
|
|
d.pop("new_score", None)
|
|
|
|
|
|
|
|
|
|
|
|
|
-def _process_defects_for_json(
|
|
|
|
|
|
|
+def _generate_defect_img_urls_for_json(
|
|
|
card_id: int,
|
|
card_id: int,
|
|
|
- img_id: int,
|
|
|
|
|
- img_path: str,
|
|
|
|
|
|
|
+ fusion_img_id: int,
|
|
|
json_data: dict,
|
|
json_data: dict,
|
|
|
- side: str,
|
|
|
|
|
- all_images: list = None,
|
|
|
|
|
- generate_related_images: bool = True
|
|
|
|
|
-):
|
|
|
|
|
|
|
+ side_key: str,
|
|
|
|
|
+ crop_pool_by_type: dict,
|
|
|
|
|
+ generate_related_images: bool = True,
|
|
|
|
|
+) -> dict:
|
|
|
"""
|
|
"""
|
|
|
- 为每条缺陷生成同面其他类型图的位置截图列表 defectImgUrls。
|
|
|
|
|
- 不再生成主缺陷截图 defectImgUrl,也不再聚合 defectDetailList。
|
|
|
|
|
|
|
+ 以融合图 JSON 中的缺陷 min_rect 为准,在同面各目标类型原图上裁图,
|
|
|
|
|
+ 生成 defectImgUrls 并返回 rect_hash -> urls 缓存,供同面其它图复用。
|
|
|
"""
|
|
"""
|
|
|
start_time = perf_counter()
|
|
start_time = perf_counter()
|
|
|
|
|
+ url_cache_by_rect = {}
|
|
|
if not json_data or "result" not in json_data:
|
|
if not json_data or "result" not in json_data:
|
|
|
logger.info(
|
|
logger.info(
|
|
|
- "耗时埋点 _process_defects_for_json: card_id=%s image_id=%s side=%s defects=0 elapsed_ms=%.2f",
|
|
|
|
|
- card_id, img_id, side, (perf_counter() - start_time) * 1000
|
|
|
|
|
|
|
+ "耗时埋点 _generate_defect_img_urls: card_id=%s fusion_image_id=%s side=%s defects=0 elapsed_ms=%.2f",
|
|
|
|
|
+ card_id, fusion_img_id, side_key, (perf_counter() - start_time) * 1000,
|
|
|
)
|
|
)
|
|
|
- return
|
|
|
|
|
|
|
+ return url_cache_by_rect
|
|
|
|
|
+
|
|
|
defect_result = json_data["result"].get("defect_result", {})
|
|
defect_result = json_data["result"].get("defect_result", {})
|
|
|
defects = defect_result.get("defects", [])
|
|
defects = defect_result.get("defects", [])
|
|
|
-
|
|
|
|
|
- side_prefix = "front_" if side.startswith("front_") else "back_"
|
|
|
|
|
|
|
+ crop_target_images = _defect_url_target_images(crop_pool_by_type, side_key)
|
|
|
|
|
+ target_types = _defect_url_target_type_list(crop_pool_by_type, side_key)
|
|
|
|
|
+ missing_types = [t for t in target_types if t not in crop_pool_by_type]
|
|
|
|
|
+ if missing_types:
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "defectImgUrls 裁图目标缺失: card_id=%s side=%s missing=%s",
|
|
|
|
|
+ card_id, side_key, ",".join(missing_types),
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
for idx, defect in enumerate(defects, start=1):
|
|
for idx, defect in enumerate(defects, start=1):
|
|
|
min_rect = defect.get("min_rect")
|
|
min_rect = defect.get("min_rect")
|
|
|
defect_img_url_list = []
|
|
defect_img_url_list = []
|
|
|
|
|
+ rect_hash = _defect_rect_hash(min_rect)
|
|
|
|
|
|
|
|
- if min_rect and len(min_rect) == 3 and generate_related_images and all_images:
|
|
|
|
|
- rect_str = str(min_rect)
|
|
|
|
|
- rect_hash = hashlib.md5(rect_str.encode('utf-8')).hexdigest()[:8]
|
|
|
|
|
-
|
|
|
|
|
- same_side_images = [
|
|
|
|
|
- img for img in all_images
|
|
|
|
|
- if getattr(img, 'image_type', '').startswith(side_prefix) and getattr(img, 'id', None) != img_id
|
|
|
|
|
- ]
|
|
|
|
|
- for s_img in same_side_images:
|
|
|
|
|
- s_img_type = getattr(s_img, 'image_type', '')
|
|
|
|
|
- s_img_path = getattr(s_img, 'image_path', '')
|
|
|
|
|
- s_img_id = getattr(s_img, 'id', 0)
|
|
|
|
|
|
|
+ if min_rect and len(min_rect) == 3 and generate_related_images and crop_target_images:
|
|
|
|
|
+ for s_img in crop_target_images:
|
|
|
|
|
+ s_img_type = getattr(s_img, "image_type", "")
|
|
|
|
|
+ s_img_path = getattr(s_img, "image_path", "")
|
|
|
|
|
+ s_img_id = getattr(s_img, "id", 0)
|
|
|
|
|
|
|
|
s_filename = f"xy_{card_id}_{s_img_id}_{idx}_{rect_hash}.jpg"
|
|
s_filename = f"xy_{card_id}_{s_img_id}_{idx}_{rect_hash}.jpg"
|
|
|
s_out_rel_path = f"/DefectImage/{s_filename}"
|
|
s_out_rel_path = f"/DefectImage/{s_filename}"
|
|
@@ -230,52 +371,135 @@ def _process_defects_for_json(
|
|
|
if s_url:
|
|
if s_url:
|
|
|
defect_img_url_list.append({
|
|
defect_img_url_list.append({
|
|
|
"image_type": s_img_type,
|
|
"image_type": s_img_type,
|
|
|
- "url": s_url
|
|
|
|
|
|
|
+ "url": s_url,
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
defect["defectImgUrls"] = defect_img_url_list
|
|
defect["defectImgUrls"] = defect_img_url_list
|
|
|
|
|
+ if rect_hash:
|
|
|
|
|
+ url_cache_by_rect[rect_hash] = copy.deepcopy(defect_img_url_list)
|
|
|
|
|
|
|
|
logger.info(
|
|
logger.info(
|
|
|
- "耗时埋点 _process_defects_for_json: card_id=%s image_id=%s side=%s defects=%s elapsed_ms=%.2f",
|
|
|
|
|
- card_id, img_id, side, len(defects), (perf_counter() - start_time) * 1000
|
|
|
|
|
|
|
+ "耗时埋点 _generate_defect_img_urls: card_id=%s fusion_image_id=%s side=%s "
|
|
|
|
|
+ "defects=%s target_types=%s elapsed_ms=%.2f",
|
|
|
|
|
+ card_id,
|
|
|
|
|
+ fusion_img_id,
|
|
|
|
|
+ side_key,
|
|
|
|
|
+ len(defects),
|
|
|
|
|
+ len(crop_target_images),
|
|
|
|
|
+ (perf_counter() - start_time) * 1000,
|
|
|
)
|
|
)
|
|
|
|
|
+ return url_cache_by_rect
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _apply_defect_img_urls_from_cache(json_data: dict, url_cache_by_rect: dict):
|
|
|
|
|
+ """同面非 canonical 图:按 min_rect 哈希复用已生成的 defectImgUrls,不再访问 MinIO。"""
|
|
|
|
|
+ if not json_data or "result" not in json_data or not url_cache_by_rect:
|
|
|
|
|
+ return
|
|
|
|
|
+ defects = json_data["result"].get("defect_result", {}).get("defects", [])
|
|
|
|
|
+ for defect in defects:
|
|
|
|
|
+ if not isinstance(defect, dict):
|
|
|
|
|
+ continue
|
|
|
|
|
+ rect_hash = _defect_rect_hash(defect.get("min_rect"))
|
|
|
|
|
+ defect["defectImgUrls"] = copy.deepcopy(url_cache_by_rect.get(rect_hash, []))
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _clear_defect_img_urls(json_data: dict):
|
|
|
|
|
+ """灰度图不生成关联缺陷图,清空展示用字段。"""
|
|
|
|
|
+ if not json_data or "result" not in json_data:
|
|
|
|
|
+ return
|
|
|
|
|
+ defects = json_data["result"].get("defect_result", {}).get("defects", [])
|
|
|
|
|
+ for defect in defects:
|
|
|
|
|
+ if isinstance(defect, dict):
|
|
|
|
|
+ defect["defectImgUrls"] = []
|
|
|
|
|
|
|
|
|
|
|
|
|
def _process_images_to_xy_format(
|
|
def _process_images_to_xy_format(
|
|
|
card_data: dict,
|
|
card_data: dict,
|
|
|
- generate_related_images: bool = True
|
|
|
|
|
|
|
+ generate_related_images: bool = True,
|
|
|
|
|
+ db_conn: PooledMySQLConnection = None,
|
|
|
):
|
|
):
|
|
|
"""
|
|
"""
|
|
|
内部辅助函数:遍历卡牌数据中的图片,将 JSON 格式转换为前端需要的 XY 格式。
|
|
内部辅助函数:遍历卡牌数据中的图片,将 JSON 格式转换为前端需要的 XY 格式。
|
|
|
|
|
+ 每面仅以融合图 JSON 生成一次 defectImgUrls(按固定 14 类中的同面非灰类型裁图),
|
|
|
|
|
+ 再拷贝到同面 ring / stripe 等;灰度图不生成 defectImgUrls。
|
|
|
直接修改传入的 card_data 字典。
|
|
直接修改传入的 card_data 字典。
|
|
|
"""
|
|
"""
|
|
|
start_time = perf_counter()
|
|
start_time = perf_counter()
|
|
|
card_id = card_data.get("id")
|
|
card_id = card_data.get("id")
|
|
|
all_images = card_data.get("images", [])
|
|
all_images = card_data.get("images", [])
|
|
|
|
|
+ fusion_by_side = _resolve_fusion_images_by_side(all_images) if all_images else {}
|
|
|
|
|
+ crop_pool_by_type = _build_defect_crop_pool_by_type(
|
|
|
|
|
+ card_id, all_images, db_conn=db_conn,
|
|
|
|
|
+ )
|
|
|
|
|
+ detection_url_cache_by_side = {}
|
|
|
|
|
+ modified_url_cache_by_side = {}
|
|
|
|
|
+
|
|
|
if all_images:
|
|
if all_images:
|
|
|
|
|
+ parsed_json_by_img_id = {}
|
|
|
for img in all_images:
|
|
for img in all_images:
|
|
|
d_internal = img.detection_json
|
|
d_internal = img.detection_json
|
|
|
if isinstance(d_internal, str):
|
|
if isinstance(d_internal, str):
|
|
|
- d_internal = json.loads(d_internal)
|
|
|
|
|
|
|
+ d_internal = json.loads(d_internal) if d_internal else None
|
|
|
|
|
+ m_internal = img.modified_json
|
|
|
|
|
+ if isinstance(m_internal, str):
|
|
|
|
|
+ m_internal = json.loads(m_internal) if m_internal else None
|
|
|
|
|
+ parsed_json_by_img_id[img.id] = {
|
|
|
|
|
+ "detection": d_internal,
|
|
|
|
|
+ "modified": m_internal,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if generate_related_images:
|
|
|
|
|
+ for side_key, fusion_img in fusion_by_side.items():
|
|
|
|
|
+ if not fusion_img:
|
|
|
|
|
+ continue
|
|
|
|
|
+ parsed = parsed_json_by_img_id.get(fusion_img.id, {})
|
|
|
|
|
+ d_internal = parsed.get("detection")
|
|
|
|
|
+ if d_internal:
|
|
|
|
|
+ detection_url_cache_by_side[side_key] = _generate_defect_img_urls_for_json(
|
|
|
|
|
+ card_id,
|
|
|
|
|
+ fusion_img.id,
|
|
|
|
|
+ d_internal,
|
|
|
|
|
+ side_key,
|
|
|
|
|
+ crop_pool_by_type,
|
|
|
|
|
+ generate_related_images=True,
|
|
|
|
|
+ )
|
|
|
|
|
+ m_internal = parsed.get("modified")
|
|
|
|
|
+ if m_internal:
|
|
|
|
|
+ modified_url_cache_by_side[side_key] = _generate_defect_img_urls_for_json(
|
|
|
|
|
+ card_id,
|
|
|
|
|
+ fusion_img.id,
|
|
|
|
|
+ m_internal,
|
|
|
|
|
+ side_key,
|
|
|
|
|
+ crop_pool_by_type,
|
|
|
|
|
+ generate_related_images=True,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ for img in all_images:
|
|
|
|
|
+ image_type = img.image_type
|
|
|
|
|
+ is_gray = _is_gray_image_type(image_type)
|
|
|
|
|
+ side_key = _side_key_from_image_type(image_type)
|
|
|
|
|
+ parsed = parsed_json_by_img_id.get(img.id, {})
|
|
|
|
|
+ d_internal = parsed.get("detection")
|
|
|
|
|
+ m_internal = parsed.get("modified")
|
|
|
|
|
|
|
|
if d_internal:
|
|
if d_internal:
|
|
|
- _process_defects_for_json(
|
|
|
|
|
- card_id, img.id, img.image_path, d_internal, img.image_type, all_images,
|
|
|
|
|
- generate_related_images=generate_related_images
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ if is_gray:
|
|
|
|
|
+ _clear_defect_img_urls(d_internal)
|
|
|
|
|
+ elif side_key and detection_url_cache_by_side.get(side_key):
|
|
|
|
|
+ _apply_defect_img_urls_from_cache(
|
|
|
|
|
+ d_internal, detection_url_cache_by_side[side_key],
|
|
|
|
|
+ )
|
|
|
img.detection_json = convert_internal_to_xy_format(d_internal)
|
|
img.detection_json = convert_internal_to_xy_format(d_internal)
|
|
|
else:
|
|
else:
|
|
|
img.detection_json = convert_internal_to_xy_format({})
|
|
img.detection_json = convert_internal_to_xy_format({})
|
|
|
|
|
|
|
|
- m_internal = img.modified_json
|
|
|
|
|
- if isinstance(m_internal, str):
|
|
|
|
|
- m_internal = json.loads(m_internal)
|
|
|
|
|
-
|
|
|
|
|
if m_internal:
|
|
if m_internal:
|
|
|
- _process_defects_for_json(
|
|
|
|
|
- card_id, img.id, img.image_path, m_internal, img.image_type, all_images,
|
|
|
|
|
- generate_related_images=generate_related_images
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ if is_gray:
|
|
|
|
|
+ _clear_defect_img_urls(m_internal)
|
|
|
|
|
+ elif side_key and modified_url_cache_by_side.get(side_key):
|
|
|
|
|
+ _apply_defect_img_urls_from_cache(
|
|
|
|
|
+ m_internal, modified_url_cache_by_side[side_key],
|
|
|
|
|
+ )
|
|
|
img.modified_json = convert_internal_to_xy_format(m_internal)
|
|
img.modified_json = convert_internal_to_xy_format(m_internal)
|
|
|
else:
|
|
else:
|
|
|
m_fallback = copy.deepcopy(d_internal) if d_internal else {}
|
|
m_fallback = copy.deepcopy(d_internal) if d_internal else {}
|
|
@@ -352,7 +576,8 @@ def get_card_details(
|
|
|
# 4. 遍历图片,转换格式 (使用抽取出的辅助函数)
|
|
# 4. 遍历图片,转换格式 (使用抽取出的辅助函数)
|
|
|
_process_images_to_xy_format(
|
|
_process_images_to_xy_format(
|
|
|
card_data,
|
|
card_data,
|
|
|
- generate_related_images=True
|
|
|
|
|
|
|
+ generate_related_images=True,
|
|
|
|
|
+ db_conn=db_conn,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# 5. 将 images 从 Pydantic 对象转为 dict,避免 model_validate 重复验证导致类型异常
|
|
# 5. 将 images 从 Pydantic 对象转为 dict,避免 model_validate 重复验证导致类型异常
|