|
@@ -1,19 +1,41 @@
|
|
|
-from fastapi import APIRouter, HTTPException, Depends, Query
|
|
|
|
|
-from mysql.connector.pooling import PooledMySQLConnection
|
|
|
|
|
-
|
|
|
|
|
from datetime import datetime
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
-from app.core.logger import get_logger
|
|
|
|
|
|
|
+from fastapi import APIRouter, Depends, HTTPException, Query
|
|
|
|
|
+from mysql.connector.pooling import PooledMySQLConnection
|
|
|
|
|
+
|
|
|
from app.core.config import settings
|
|
from app.core.config import settings
|
|
|
from app.core.database_loader import get_db_connection
|
|
from app.core.database_loader import get_db_connection
|
|
|
|
|
+from app.core.logger import get_logger
|
|
|
from app.crud import crud_card
|
|
from app.crud import crud_card
|
|
|
-from app.utils.rating_report_utils import (get_active_json, save_rating_report_history,
|
|
|
|
|
- format_datetime, parse_json_value, crop_defect_image)
|
|
|
|
|
|
|
+from app.utils.rating_report_utils import (
|
|
|
|
|
+ build_defect_compare_key,
|
|
|
|
|
+ crop_defect_image,
|
|
|
|
|
+ format_datetime,
|
|
|
|
|
+ get_active_json,
|
|
|
|
|
+ parse_json_value,
|
|
|
|
|
+ remove_common_defects,
|
|
|
|
|
+ save_rating_report_history,
|
|
|
|
|
+)
|
|
|
|
|
|
|
|
logger = get_logger(__name__)
|
|
logger = get_logger(__name__)
|
|
|
router = APIRouter()
|
|
router = APIRouter()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _build_history_detail_response(row: dict, report_data: dict) -> dict:
|
|
|
|
|
+ """
|
|
|
|
|
+ 统一历史详情返回结构,避免不同接口拼装不一致。
|
|
|
|
|
+ """
|
|
|
|
|
+ return {
|
|
|
|
|
+ "ratingId": row.get("rating_id"),
|
|
|
|
|
+ "cardId": row.get("card_id"),
|
|
|
|
|
+ "cardNo": row.get("cardNo"),
|
|
|
|
|
+ "reportName": row.get("report_name"),
|
|
|
|
|
+ "createdAt": format_datetime(row.get("created_at")),
|
|
|
|
|
+ "updatedAt": format_datetime(row.get("updated_at")),
|
|
|
|
|
+ "reportData": report_data,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@router.get("/generate", status_code=200, summary="生成评级报告数据")
|
|
@router.get("/generate", status_code=200, summary="生成评级报告数据")
|
|
|
def generate_rating_report(
|
|
def generate_rating_report(
|
|
|
cardNo: str,
|
|
cardNo: str,
|
|
@@ -22,35 +44,27 @@ def generate_rating_report(
|
|
|
if not cardNo or not cardNo.strip():
|
|
if not cardNo or not cardNo.strip():
|
|
|
raise HTTPException(status_code=400, detail="cardNo 不能为空")
|
|
raise HTTPException(status_code=400, detail="cardNo 不能为空")
|
|
|
|
|
|
|
|
- # 根据cardNo 查询id
|
|
|
|
|
try:
|
|
try:
|
|
|
with db_conn.cursor(buffered=True) as cursor:
|
|
with db_conn.cursor(buffered=True) as cursor:
|
|
|
query_sql = f"SELECT id FROM {settings.DB_CARD_TABLE_NAME} WHERE cardNo = %s LIMIT 1"
|
|
query_sql = f"SELECT id FROM {settings.DB_CARD_TABLE_NAME} WHERE cardNo = %s LIMIT 1"
|
|
|
cursor.execute(query_sql, (cardNo,))
|
|
cursor.execute(query_sql, (cardNo,))
|
|
|
row = cursor.fetchone()
|
|
row = cursor.fetchone()
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- logger.error(f"创建卡牌失败: {e}")
|
|
|
|
|
- raise HTTPException(status_code=500, detail="数据库查询失败。")
|
|
|
|
|
|
|
+ logger.error(f"根据 cardNo 查询卡牌失败: cardNo={cardNo}, error={e}")
|
|
|
|
|
+ raise HTTPException(status_code=500, detail="数据库查询失败")
|
|
|
|
|
|
|
|
if not row:
|
|
if not row:
|
|
|
- raise HTTPException(
|
|
|
|
|
- status_code=404,
|
|
|
|
|
- detail=f"未找到卡牌编号为 {cardNo} 的相关记录"
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ raise HTTPException(status_code=404, detail=f"未找到 cardNo={cardNo} 的卡牌记录")
|
|
|
|
|
|
|
|
card_id = row[0]
|
|
card_id = row[0]
|
|
|
rating_time_now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
rating_time_now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
|
|
|
+ logger.info(f"开始生成评级报告: cardNo={cardNo}, card_id={card_id}")
|
|
|
|
|
|
|
|
- # top_n_defects = 3
|
|
|
|
|
- """
|
|
|
|
|
- 根据 Card ID 生成评级报告 JSON
|
|
|
|
|
- """
|
|
|
|
|
- # 1. 获取卡片详情 (复用 Crud 逻辑,确保能拿到所有图片)
|
|
|
|
|
|
|
+ # 复用卡牌详情查询逻辑,直接拿到当前用于生成报告的图片和缺陷数据。
|
|
|
card_data = crud_card.get_card_with_details(db_conn, card_id)
|
|
card_data = crud_card.get_card_with_details(db_conn, card_id)
|
|
|
if not card_data:
|
|
if not card_data:
|
|
|
- raise HTTPException(status_code=404, detail="未找到该卡片信息")
|
|
|
|
|
|
|
+ raise HTTPException(status_code=404, detail="未找到该卡牌信息")
|
|
|
|
|
|
|
|
- # 初始化返回结构
|
|
|
|
|
response_data = {
|
|
response_data = {
|
|
|
"backImageUrl": "",
|
|
"backImageUrl": "",
|
|
|
"frontImageUrl": "",
|
|
"frontImageUrl": "",
|
|
@@ -70,13 +84,10 @@ def generate_rating_report(
|
|
|
"defectDetailList": []
|
|
"defectDetailList": []
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- # 临时列表用于收集所有缺陷,最后排序取 Top N
|
|
|
|
|
|
|
+ # 先统一收集缺陷,再按面积排序
|
|
|
all_defects_collected = []
|
|
all_defects_collected = []
|
|
|
-
|
|
|
|
|
- # 遍历图片寻找 Front Ring 和 Back Ring
|
|
|
|
|
images = card_data.get("images", [])
|
|
images = card_data.get("images", [])
|
|
|
|
|
|
|
|
- # 辅助字典:defect_type 到 统计字段 的映射
|
|
|
|
|
defect_map_keys = {
|
|
defect_map_keys = {
|
|
|
"front_ring": {
|
|
"front_ring": {
|
|
|
"corner": "cornerFrontNum",
|
|
"corner": "cornerFrontNum",
|
|
@@ -92,63 +103,44 @@ def generate_rating_report(
|
|
|
|
|
|
|
|
for img in images:
|
|
for img in images:
|
|
|
img_type = img.image_type
|
|
img_type = img.image_type
|
|
|
-
|
|
|
|
|
- # 只处理环光图
|
|
|
|
|
if img_type not in ["front_ring", "back_ring"]:
|
|
if img_type not in ["front_ring", "back_ring"]:
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
- # 设置主图 URL
|
|
|
|
|
if img_type == "front_ring":
|
|
if img_type == "front_ring":
|
|
|
response_data["frontImageUrl"] = img.image_path
|
|
response_data["frontImageUrl"] = img.image_path
|
|
|
elif img_type == "back_ring":
|
|
elif img_type == "back_ring":
|
|
|
response_data["backImageUrl"] = img.image_path
|
|
response_data["backImageUrl"] = img.image_path
|
|
|
|
|
|
|
|
- # 获取有效 JSON
|
|
|
|
|
json_data = get_active_json(img)
|
|
json_data = get_active_json(img)
|
|
|
if not json_data or "result" not in json_data:
|
|
if not json_data or "result" not in json_data:
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
result_node = json_data["result"]
|
|
result_node = json_data["result"]
|
|
|
|
|
|
|
|
- # 1. 处理居中 (Center)
|
|
|
|
|
center_inf = result_node.get("center_result", {}).get("box_result", {}).get("center_inference", {})
|
|
center_inf = result_node.get("center_result", {}).get("box_result", {}).get("center_inference", {})
|
|
|
if center_inf:
|
|
if center_inf:
|
|
|
- # 格式: L/R=47/53, T/B=51/49 (取整)
|
|
|
|
|
- # center_inference 包含 center_left, center_right, center_top, center_bottom
|
|
|
|
|
- c_str = (
|
|
|
|
|
|
|
+ center_text = (
|
|
|
f"L/R={int(round(center_inf.get('center_left', 0)))}/{int(round(center_inf.get('center_right', 0)))}, "
|
|
f"L/R={int(round(center_inf.get('center_left', 0)))}/{int(round(center_inf.get('center_right', 0)))}, "
|
|
|
f"T/B={int(round(center_inf.get('center_top', 0)))}/{int(round(center_inf.get('center_bottom', 0)))}"
|
|
f"T/B={int(round(center_inf.get('center_top', 0)))}/{int(round(center_inf.get('center_bottom', 0)))}"
|
|
|
)
|
|
)
|
|
|
if img_type == "front_ring":
|
|
if img_type == "front_ring":
|
|
|
- response_data["centerFront"] = c_str
|
|
|
|
|
- # 2. 处理尺寸 (仅从正面取,或者只要有就取) - mm 转 cm,除以 10,保留2位
|
|
|
|
|
- rw_mm = center_inf.get("real_width_mm", 0)
|
|
|
|
|
- rh_mm = center_inf.get("real_height_mm", 0)
|
|
|
|
|
- response_data["measureWidth"] = round(rw_mm / 10.0, 2)
|
|
|
|
|
- response_data["measureLength"] = round(rh_mm / 10.0, 2)
|
|
|
|
|
|
|
+ response_data["centerFront"] = center_text
|
|
|
|
|
+ response_data["measureWidth"] = round(center_inf.get("real_width_mm", 0) / 10.0, 2)
|
|
|
|
|
+ response_data["measureLength"] = round(center_inf.get("real_height_mm", 0) / 10.0, 2)
|
|
|
else:
|
|
else:
|
|
|
- response_data["centerBack"] = c_str
|
|
|
|
|
|
|
+ response_data["centerBack"] = center_text
|
|
|
|
|
|
|
|
- # 2. 处理缺陷 (Defects)
|
|
|
|
|
defects = result_node.get("defect_result", {}).get("defects", [])
|
|
defects = result_node.get("defect_result", {}).get("defects", [])
|
|
|
-
|
|
|
|
|
for defect in defects:
|
|
for defect in defects:
|
|
|
- # 过滤 edit_type == 'del'
|
|
|
|
|
if defect.get("edit_type") == "del":
|
|
if defect.get("edit_type") == "del":
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
- d_type = defect.get("defect_type", "") # corner, edge, face
|
|
|
|
|
- d_label = defect.get("label", "") # scratch, wear, etc.
|
|
|
|
|
-
|
|
|
|
|
- # 统计数量
|
|
|
|
|
- count_key = defect_map_keys.get(img_type, {}).get(d_type)
|
|
|
|
|
|
|
+ defect_type = defect.get("defect_type", "")
|
|
|
|
|
+ count_key = defect_map_keys.get(img_type, {}).get(defect_type)
|
|
|
if count_key:
|
|
if count_key:
|
|
|
response_data[count_key] += 1
|
|
response_data[count_key] += 1
|
|
|
|
|
|
|
|
- # 收集详细信息用于 Top N 列表
|
|
|
|
|
- # 需要保存:缺陷对象本身,图片路径,正反面标识
|
|
|
|
|
side_str = "FRONT" if img_type == "front_ring" else "BACK"
|
|
side_str = "FRONT" if img_type == "front_ring" else "BACK"
|
|
|
-
|
|
|
|
|
all_defects_collected.append({
|
|
all_defects_collected.append({
|
|
|
"defect_data": defect,
|
|
"defect_data": defect,
|
|
|
"image_path": img.image_path,
|
|
"image_path": img.image_path,
|
|
@@ -156,52 +148,38 @@ def generate_rating_report(
|
|
|
"area": defect.get("actual_area", 0)
|
|
"area": defect.get("actual_area", 0)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- # 3. 处理 defectDetailList (Top N 切图)
|
|
|
|
|
- # 按实际面积从大到小排序
|
|
|
|
|
- all_defects_collected.sort(key=lambda x: x["area"], reverse=True)
|
|
|
|
|
-
|
|
|
|
|
- # top_defects = all_defects_collected[:top_n_defects]
|
|
|
|
|
- top_defects = all_defects_collected
|
|
|
|
|
|
|
+ all_defects_collected.sort(key=lambda item: item["area"], reverse=True)
|
|
|
|
|
|
|
|
final_defect_list = []
|
|
final_defect_list = []
|
|
|
- for idx, item in enumerate(top_defects, start=1):
|
|
|
|
|
|
|
+ for idx, item in enumerate(all_defects_collected, start=1):
|
|
|
defect = item["defect_data"]
|
|
defect = item["defect_data"]
|
|
|
side = item["side"]
|
|
side = item["side"]
|
|
|
original_img_path = item["image_path"]
|
|
original_img_path = item["image_path"]
|
|
|
|
|
+ defect_id = idx
|
|
|
|
|
+ filename = f"{card_id}_{defect_id}_{rating_time_now}.jpg"
|
|
|
|
|
|
|
|
- # 构造 ID
|
|
|
|
|
- d_id = idx # 1, 2, 3
|
|
|
|
|
-
|
|
|
|
|
- # 构造文件名: {card_id}_{seq_id}.jpg
|
|
|
|
|
- filename = f"{card_id}_{d_id}_{rating_time_now}.jpg"
|
|
|
|
|
-
|
|
|
|
|
- # 执行切图
|
|
|
|
|
min_rect = defect.get("min_rect")
|
|
min_rect = defect.get("min_rect")
|
|
|
defect_img_url = ""
|
|
defect_img_url = ""
|
|
|
location_str = ""
|
|
location_str = ""
|
|
|
|
|
|
|
|
if min_rect and len(min_rect) == 3:
|
|
if min_rect and len(min_rect) == 3:
|
|
|
- # 切图并保存
|
|
|
|
|
defect_img_url = crop_defect_image(original_img_path, min_rect, filename)
|
|
defect_img_url = crop_defect_image(original_img_path, min_rect, filename)
|
|
|
|
|
+ center_x, center_y = min_rect[0]
|
|
|
|
|
+ location_str = f"{int(center_x)},{int(center_y)}"
|
|
|
|
|
+
|
|
|
|
|
+ raw_type = f"{defect.get('defect_type', '').upper()}".strip()
|
|
|
|
|
+ type_str_map = {
|
|
|
|
|
+ "CORNER": "CORNER",
|
|
|
|
|
+ "EDGE": "SIDE",
|
|
|
|
|
+ "FACE": "SURFACE"
|
|
|
|
|
+ }
|
|
|
|
|
+ type_str = type_str_map.get(raw_type)
|
|
|
|
|
|
|
|
- # 计算 Location (中心坐标)
|
|
|
|
|
- # min_rect[0] 是 [x, y]
|
|
|
|
|
- cx, cy = min_rect[0]
|
|
|
|
|
- location_str = f"{int(cx)},{int(cy)}"
|
|
|
|
|
-
|
|
|
|
|
- # 构造 Type 字符串: defect_type + label (大写)
|
|
|
|
|
- # 例如: defect_type="edge", label="wear" -> "EDGE WEAR"
|
|
|
|
|
- d_type_raw = defect.get("defect_type", "")
|
|
|
|
|
- # d_label_raw = defect.get("label", "")
|
|
|
|
|
-
|
|
|
|
|
- type_str = f"{d_type_raw.upper()}".strip()
|
|
|
|
|
- type_str_map = {"CORNER": "CORNER",
|
|
|
|
|
- "EDGE": "SIDE",
|
|
|
|
|
- "FACE": "SURFACE"}
|
|
|
|
|
- type_str = type_str_map.get(type_str)
|
|
|
|
|
-
|
|
|
|
|
|
|
+ # compareKey 使用面积、标签、正反面和 location。
|
|
|
|
|
+ compare_key = build_defect_compare_key(defect, side, location_str)
|
|
|
final_defect_list.append({
|
|
final_defect_list.append({
|
|
|
- "id": d_id,
|
|
|
|
|
|
|
+ "id": defect_id,
|
|
|
|
|
+ "compareKey": compare_key,
|
|
|
"side": side,
|
|
"side": side,
|
|
|
"location": location_str,
|
|
"location": location_str,
|
|
|
"type": type_str,
|
|
"type": type_str,
|
|
@@ -210,9 +188,22 @@ def generate_rating_report(
|
|
|
|
|
|
|
|
response_data["defectDetailList"] = final_defect_list
|
|
response_data["defectDetailList"] = final_defect_list
|
|
|
report_name = f"{cardNo}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
|
report_name = f"{cardNo}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
|
|
- rating_id = save_rating_report_history(db_conn, card_id, cardNo, report_name, response_data)
|
|
|
|
|
- response_data["ratingId"] = rating_id
|
|
|
|
|
- response_data["reportName"] = report_name
|
|
|
|
|
|
|
+ history_result = save_rating_report_history(db_conn, card_id, cardNo, report_name, response_data)
|
|
|
|
|
+
|
|
|
|
|
+ response_data["ratingId"] = history_result["rating_id"]
|
|
|
|
|
+ response_data["reportName"] = history_result["report_name"]
|
|
|
|
|
+ response_data["historySaved"] = history_result["created"]
|
|
|
|
|
+
|
|
|
|
|
+ if history_result["created"]:
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"评级报告生成并保存成功: cardNo={cardNo}, rating_id={history_result['rating_id']}, "
|
|
|
|
|
+ f"defect_count={len(final_defect_list)}"
|
|
|
|
|
+ )
|
|
|
|
|
+ else:
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"评级报告生成成功,但内容与上一条历史一致,未重复保存: cardNo={cardNo}, "
|
|
|
|
|
+ f"rating_id={history_result['rating_id']}, defect_count={len(final_defect_list)}"
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
return response_data
|
|
return response_data
|
|
|
|
|
|
|
@@ -252,8 +243,8 @@ def get_rating_report_history_list(
|
|
|
cursor.execute(query_sql, (cardNo, limit, skip))
|
|
cursor.execute(query_sql, (cardNo, limit, skip))
|
|
|
rows = cursor.fetchall()
|
|
rows = cursor.fetchall()
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- logger.error(f"查询评级报告历史列表失败: {e}")
|
|
|
|
|
- raise HTTPException(status_code=500, detail="查询评级报告历史列表失败。")
|
|
|
|
|
|
|
+ logger.error(f"查询评级报告历史列表失败: cardNo={cardNo}, error={e}")
|
|
|
|
|
+ raise HTTPException(status_code=500, detail="查询评级报告历史列表失败")
|
|
|
|
|
|
|
|
history_list = []
|
|
history_list = []
|
|
|
for row in rows:
|
|
for row in rows:
|
|
@@ -274,6 +265,58 @@ def get_rating_report_history_list(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+@router.get("/history/compare", status_code=200, summary="根据两个 rating_id 对比历史缺陷差异")
|
|
|
|
|
+def compare_rating_report_history(
|
|
|
|
|
+ rating_id1: int = Query(..., description="第一个历史记录 ID"),
|
|
|
|
|
+ rating_id2: int = Query(..., description="第二个历史记录 ID"),
|
|
|
|
|
+ db_conn: PooledMySQLConnection = Depends(get_db_connection)
|
|
|
|
|
+):
|
|
|
|
|
+ try:
|
|
|
|
|
+ with db_conn.cursor(dictionary=True) as cursor:
|
|
|
|
|
+ query_sql = (
|
|
|
|
|
+ f"SELECT rating_id, card_id, cardNo, report_name, report_json, created_at, updated_at "
|
|
|
|
|
+ f"FROM {settings.RATING_REPORT_HISTORY_TABLE_NAME} "
|
|
|
|
|
+ "WHERE rating_id IN (%s, %s)"
|
|
|
|
|
+ )
|
|
|
|
|
+ cursor.execute(query_sql, (rating_id1, rating_id2))
|
|
|
|
|
+ rows = cursor.fetchall()
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.error(
|
|
|
|
|
+ f"查询评级报告历史对比数据失败: rating_id1={rating_id1}, rating_id2={rating_id2}, error={e}"
|
|
|
|
|
+ )
|
|
|
|
|
+ raise HTTPException(status_code=500, detail="查询评级报告历史对比数据失败")
|
|
|
|
|
+
|
|
|
|
|
+ row_map = {row.get("rating_id"): row for row in rows}
|
|
|
|
|
+ missing_ids = [rating_id for rating_id in [rating_id1, rating_id2] if rating_id not in row_map]
|
|
|
|
|
+ if missing_ids:
|
|
|
|
|
+ missing_id_text = ",".join(str(item) for item in missing_ids)
|
|
|
|
|
+ raise HTTPException(status_code=404, detail=f"未找到 rating_id={missing_id_text} 的历史记录")
|
|
|
|
|
+
|
|
|
|
|
+ left_row = row_map[rating_id1]
|
|
|
|
|
+ right_row = row_map[rating_id2]
|
|
|
|
|
+ left_report, right_report, common_count = remove_common_defects(
|
|
|
|
|
+ left_row.get("report_json"),
|
|
|
|
|
+ right_row.get("report_json")
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ left_only_count = len(left_report.get("defectDetailList", []))
|
|
|
|
|
+ right_only_count = len(right_report.get("defectDetailList", []))
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"评级报告历史对比完成: rating_id1={rating_id1}, rating_id2={rating_id2}, "
|
|
|
|
|
+ f"same_count={common_count}, left_only_count={left_only_count}, right_only_count={right_only_count}"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ "comparisonSummary": {
|
|
|
|
|
+ "sameDefectCount": common_count,
|
|
|
|
|
+ "leftOnlyCount": left_only_count,
|
|
|
|
|
+ "rightOnlyCount": right_only_count
|
|
|
|
|
+ },
|
|
|
|
|
+ "left": _build_history_detail_response(left_row, left_report),
|
|
|
|
|
+ "right": _build_history_detail_response(right_row, right_report)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@router.get("/history/{rating_id}", status_code=200, summary="根据 rating_id 查询单个评级报告历史")
|
|
@router.get("/history/{rating_id}", status_code=200, summary="根据 rating_id 查询单个评级报告历史")
|
|
|
def get_rating_report_history_detail(
|
|
def get_rating_report_history_detail(
|
|
|
rating_id: int,
|
|
rating_id: int,
|
|
@@ -289,18 +332,10 @@ def get_rating_report_history_detail(
|
|
|
cursor.execute(query_sql, (rating_id,))
|
|
cursor.execute(query_sql, (rating_id,))
|
|
|
row = cursor.fetchone()
|
|
row = cursor.fetchone()
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- logger.error(f"查询评级报告历史详情失败: {e}")
|
|
|
|
|
- raise HTTPException(status_code=500, detail="查询评级报告历史详情失败。")
|
|
|
|
|
|
|
+ logger.error(f"查询评级报告历史详情失败: rating_id={rating_id}, error={e}")
|
|
|
|
|
+ raise HTTPException(status_code=500, detail="查询评级报告历史详情失败")
|
|
|
|
|
|
|
|
if not row:
|
|
if not row:
|
|
|
raise HTTPException(status_code=404, detail=f"未找到 rating_id={rating_id} 的历史记录")
|
|
raise HTTPException(status_code=404, detail=f"未找到 rating_id={rating_id} 的历史记录")
|
|
|
|
|
|
|
|
- return {
|
|
|
|
|
- "ratingId": row.get("rating_id"),
|
|
|
|
|
- "cardId": row.get("card_id"),
|
|
|
|
|
- "cardNo": row.get("cardNo"),
|
|
|
|
|
- "reportName": row.get("report_name"),
|
|
|
|
|
- "createdAt": format_datetime(row.get("created_at")),
|
|
|
|
|
- "updatedAt": format_datetime(row.get("updated_at")),
|
|
|
|
|
- "reportData": parse_json_value(row.get("report_json"))
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return _build_history_detail_response(row, parse_json_value(row.get("report_json")))
|