소스 검색

修复隐患识别无法查询历史记录的bug

KCY 1 개월 전
부모
커밋
887048cfc0
2개의 변경된 파일527개의 추가작업 그리고 76개의 파일을 삭제
  1. 311 53
      shudao-chat-py/routers/hazard.py
  2. 216 23
      shudao-chat-py/routers/scene.py

+ 311 - 53
shudao-chat-py/routers/hazard.py

@@ -2,15 +2,16 @@
 Hazard detection routes.
 """
 
-from typing import Any, Dict, List
+from typing import Any, Dict, List, Optional
 import io
 import json
 import time
+import traceback
 from urllib.parse import urlparse, parse_qs
 
 import httpx
 from fastapi import APIRouter, Depends, Request
-from pydantic import BaseModel
+from pydantic import BaseModel, Field
 from sqlalchemy.orm import Session
 from PIL import Image, ImageDraw, ImageFont
 
@@ -32,14 +33,26 @@ class HazardRequest(BaseModel):
     image: str = ""
     image_url: str = ""
     date: str = ""
+    title: str = ""
+    description: str = ""
+    labels: str = ""
+    current_step: Optional[int] = None
+    hazard_count: Optional[int] = None
+    third_scene: str = ""
+    second_scene: str = ""
+    recognition_image_url: str = ""
+    original_image_url: str = ""
+    tag_type: str = ""
+    hazard_details: Any = Field(default_factory=dict)
+    save_history: bool = True
 
     @property
     def source_image(self) -> str:
-        return self.image or self.image_url
+        return self.image or self.image_url or self.original_image_url or ""
 
     @property
     def source_scene(self) -> str:
-        return self.scene_name or self.scene_type
+        return self.scene_name or self.scene_type or self.tag_type
 
 
 class SaveStepRequest(BaseModel):
@@ -192,7 +205,7 @@ def _unique_ordered(items: List[str]) -> List[str]:
 
 
 def _build_scene_relations(
-    db: Session, normalized_labels: List[str], scene_key: str
+    db: Session, normalized_labels: List[str]
 ) -> tuple[list[str], dict[str, list[str]]]:
     third_scene_names: List[str] = []
     element_hazards: Dict[str, List[str]] = {}
@@ -243,31 +256,56 @@ async def hazard(
         logger.warning("[hazard] request.state.user is empty")
         return {"statusCode": 401, "msg": "获取用户信息失败"}
 
+    user_id = getattr(user, "id", None) or getattr(user, "user_id", None) or 1
+    user_code = _get_user_code(user)
+    request_id = getattr(request.state, "request_id", None) or f"hazard-{int(time.time() * 1000)}"
+
     try:
         logger.info(
-            "[hazard] incoming payload: scene_name=%r, image_present=%s, image_len=%s, date=%r",
+            "[hazard][%s] incoming payload: user_id=%r, user_code=%r, scene_name=%r, scene_type=%r, image_present=%s, image_url_present=%s, image_len=%s, image_url_len=%s, date=%r",
+            request_id,
+            user_id,
+            user_code,
             data.scene_name,
+            data.scene_type,
             bool(data.image),
+            bool(data.image_url),
             len(data.image or ""),
+            len(data.image_url or ""),
             data.date,
         )
         logger.info(
-            "[hazard] request headers: content_type=%r, content_length=%r, user_agent=%r, path=%r",
+            "[hazard][%s] request headers: content_type=%r, content_length=%r, user_agent=%r, path=%r, method=%r",
+            request_id,
             request.headers.get("content-type"),
             request.headers.get("content-length"),
             request.headers.get("user-agent"),
             request.url.path,
+            request.method,
         )
 
         source_image_url = _resolve_image_url(data.source_image)
+        logger.info(
+            "[hazard][%s] resolved image url: raw=%r, resolved=%r",
+            request_id,
+            data.source_image,
+            source_image_url,
+        )
 
         scene_key = _resolve_scene_key(data.source_scene)
         if not scene_key:
             scene_key = DEFAULT_SCENE_KEY
+        logger.info(
+            "[hazard][%s] resolved scene: source_scene=%r, scene_key=%r",
+            request_id,
+            data.source_scene,
+            scene_key,
+        )
 
         if not source_image_url:
             logger.warning(
-                "[hazard] validation failed: scene_name=%r, scene_type=%r, image_present=%s, image_url_present=%s, raw_image_len=%s, raw_image_url_len=%s",
+                "[hazard][%s] validation failed: scene_name=%r, scene_type=%r, image_present=%s, image_url_present=%s, raw_image_len=%s, raw_image_url_len=%s",
+                request_id,
                 data.scene_name,
                 data.scene_type,
                 bool(data.image),
@@ -277,35 +315,70 @@ async def hazard(
             )
             return {"statusCode": 400, "msg": "图片链接不能为空"}
 
-        if not _scene_exists(db, scene_key):
-            logger.warning("[hazard] scene not found: %r", scene_key)
-            return {"statusCode": 500, "msg": "场景名称不存在"}
-
+        scene_exists = _scene_exists(db, scene_key)
         logger.info(
-            "[hazard] normalized payload: scene_key=%r, image_len=%s, date=%r",
+            "[hazard][%s] scene lookup result: scene_key=%r, exists=%s",
+            request_id,
             scene_key,
-            len(source_image_url),
-            data.date,
+            scene_exists,
         )
+        if not scene_exists:
+            logger.warning("[hazard][%s] scene not found: %r", request_id, scene_key)
+            return {"statusCode": 500, "msg": "场景名称不存在"}
 
+        logger.info(
+            "[hazard][%s] downloading image: url=%r",
+            request_id,
+            source_image_url,
+        )
         try:
             async with httpx.AsyncClient(timeout=30.0) as client:
                 img_response = await client.get(source_image_url)
+                logger.info(
+                    "[hazard][%s] image download response: status_code=%s, content_type=%r, content_length=%s",
+                    request_id,
+                    img_response.status_code,
+                    img_response.headers.get("content-type"),
+                    img_response.headers.get("content-length"),
+                )
                 img_response.raise_for_status()
                 image_bytes = img_response.content
         except httpx.HTTPError as e:
             logger.error(
-                f"下载图片失败: {e}",
-                exc_info=True,
+                "[hazard][%s] 下载图片失败: url=%r, error=%s, traceback=%s",
+                request_id,
+                source_image_url,
+                e,
+                traceback.format_exc(),
             )
             return {"statusCode": 500, "msg": f"下载图片失败: {str(e)}"}
 
+        logger.info(
+            "[hazard][%s] image downloaded: bytes=%s",
+            request_id,
+            len(image_bytes),
+        )
+
         try:
+            logger.info("[hazard][%s] calling yolo_service.detect_hazards: scene_key=%r", request_id, scene_key)
             yolo_result = await yolo_service.detect_hazards(image_bytes, scene_key)
+            logger.info(
+                "[hazard][%s] yolo result summary: keys=%s, labels_count=%s, boxes_count=%s, scores_count=%s, model_type=%r",
+                request_id,
+                list(yolo_result.keys()) if isinstance(yolo_result, dict) else type(yolo_result).__name__,
+                len((yolo_result or {}).get("labels", []) or []) if isinstance(yolo_result, dict) else -1,
+                len((yolo_result or {}).get("boxes", []) or []) if isinstance(yolo_result, dict) else -1,
+                len((yolo_result or {}).get("scores", []) or []) if isinstance(yolo_result, dict) else -1,
+                (yolo_result or {}).get("model_type", "") if isinstance(yolo_result, dict) else "",
+            )
+            logger.debug("[hazard][%s] yolo result raw: %s", request_id, json.dumps(yolo_result, ensure_ascii=False, default=str))
         except Exception as e:
             logger.error(
-                f"YOLO检测失败: sceneName={scene_key}, error={e}",
-                exc_info=True,
+                "[hazard][%s] YOLO检测失败: scene_key=%r, error=%s, traceback=%s",
+                request_id,
+                scene_key,
+                e,
+                traceback.format_exc(),
             )
             return {"statusCode": 500, "msg": f"YOLO API返回错误: {str(e)}"}
 
@@ -316,7 +389,8 @@ async def hazard(
 
         if not labels:
             logger.info(
-                "[hazard] YOLO返回空结果: scene_key=%r, model_type=%r, image_len=%s",
+                "[hazard][%s] YOLO返回空结果: scene_key=%r, model_type=%r, image_bytes=%s",
+                request_id,
                 scene_key,
                 model_type,
                 len(image_bytes),
@@ -344,6 +418,13 @@ async def hazard(
         normalized_labels = [
             _normalize_label_for_scene(str(label), scene_key) for label in labels
         ]
+        logger.info(
+            "[hazard][%s] normalized labels: raw_labels=%s, normalized_labels=%s",
+            request_id,
+            labels,
+            normalized_labels,
+        )
+
         detection_results = []
         for i, label in enumerate(normalized_labels):
             if i < len(scores) and i < len(boxes):
@@ -355,17 +436,37 @@ async def hazard(
                         "percent": f"{scores[i] * 100:.2f}%",
                     }
                 )
+        logger.info(
+            "[hazard][%s] detection results built: total=%s, detail=%s",
+            request_id,
+            len(detection_results),
+            detection_results,
+        )
 
         third_scene_names, element_hazards = _build_scene_relations(
-            db, normalized_labels, scene_key
+            db, normalized_labels
+        )
+        logger.info(
+            "[hazard][%s] scene relations built: third_scene_names=%s, element_hazards=%s",
+            request_id,
+            third_scene_names,
+            element_hazards,
         )
 
-        user_id = getattr(user, "id", None) or getattr(user, "user_id", None) or 1
         scene_name = scene_key
         scene_display_name = SCENE_DISPLAY_NAMES.get(scene_key, scene_name)
         tag_type = scene_key
-        title = f"{scene_display_name}隐患提示"
+        title = scene_display_name
+        description = " ".join(third_scene_names)
+        current_step = 1
+        hazard_count = len(detection_results)
 
+        logger.info(
+            "[hazard][%s] drawing boxes and watermark: user_name=%r, user_account=%r",
+            request_id,
+            getattr(user, "name", "") or getattr(user, "username", "") or "",
+            getattr(user, "contactNumber", "") or getattr(user, "account", "") or "",
+        )
         result_image_bytes = await _draw_boxes_and_watermark(
             image_bytes,
             [
@@ -379,10 +480,21 @@ async def hazard(
             user_name=getattr(user, "name", "") or getattr(user, "username", "") or "",
             user_account=getattr(user, "contactNumber", "") or getattr(user, "account", "") or "",
         )
+        logger.info(
+            "[hazard][%s] annotated image generated: bytes=%s",
+            request_id,
+            len(result_image_bytes),
+        )
 
         utc_now = time.gmtime()
         timestamp = int(time.time())
         file_name = f"hazard_annotated/{time.strftime('%Y', utc_now)}/{time.strftime('%m%d', utc_now)}_{timestamp}.jpg"
+        logger.info(
+            "[hazard][%s] uploading annotated image to oss: file_name=%r, folder=%r",
+            request_id,
+            file_name,
+            "hazard_detection",
+        )
 
         try:
             upload_fn = getattr(oss_service, "upload_bytes", None)
@@ -398,54 +510,156 @@ async def hazard(
                     file_name,
                     folder="hazard_detection",
                 )
+            logger.info(
+                "[hazard][%s] oss upload success: result_url=%r",
+                request_id,
+                result_url,
+            )
         except Exception as e:
-            logger.error(f"上传标注图片到OSS失败: {e}", exc_info=True)
+            logger.error(
+                "[hazard][%s] 上传标注图片到OSS失败: error=%s, traceback=%s",
+                request_id,
+                e,
+                traceback.format_exc(),
+            )
             return {"statusCode": 500, "msg": f"上传标注图片到OSS失败: {str(e)}"}
 
         try:
-            record = RecognitionRecord(
-                original_image_url=source_image_url,
-                recognition_image_url=result_url,
-                user_id=user_id,
-                labels=_remove_duplicate_labels([item["label"] for item in detection_results]),
-                title=title,
-                tag_type=tag_type,
-                second_scene="",
-                third_scene=" ".join(third_scene_names),
+            labels_text = _remove_duplicate_labels([item["label"] for item in detection_results])
+            if data.labels.strip():
+                labels_text = _remove_duplicate_labels(
+                    [item.strip() for item in data.labels.split(",") if item.strip()]
+                ) or labels_text
+
+            record_title = data.title.strip() or title
+            record_description = data.description.strip() or description
+            record_third_scene = data.third_scene.strip() or " ".join(third_scene_names)
+            record_second_scene = data.second_scene.strip()
+            record_tag_type = data.tag_type.strip() or tag_type
+            record_current_step = data.current_step if data.current_step is not None else current_step
+            record_hazard_count = (
+                data.hazard_count if data.hazard_count is not None else hazard_count
             )
+
+            raw_hazard_details = data.hazard_details
+            if isinstance(raw_hazard_details, str):
+                try:
+                    record_hazard_details = json.loads(raw_hazard_details)
+                except Exception:
+                    record_hazard_details = raw_hazard_details
+            elif raw_hazard_details:
+                record_hazard_details = raw_hazard_details
+            else:
+                record_hazard_details = detection_results
+
+            recognition_image_url = (
+                data.recognition_image_url.strip() or result_url
+            )
+
+            now_ts = int(time.time())
+            record_payload = {
+                "original_image_url": source_image_url,
+                "recognition_image_url": recognition_image_url,
+                "user_id": user_id,
+                "scene_type": scene_name,
+                "hazard_count": record_hazard_count,
+                "current_step": record_current_step,
+                "hazard_details": json.dumps(record_hazard_details, ensure_ascii=False)
+                if not isinstance(record_hazard_details, str)
+                else record_hazard_details,
+                "title": record_title,
+                "description": record_description,
+                "labels": labels_text,
+                "tag_type": record_tag_type,
+                "second_scene": record_second_scene,
+                "third_scene": record_third_scene,
+                "created_at": now_ts,
+                "updated_at": now_ts,
+                "is_deleted": 0,
+            }
+
+            logger.info(
+                "[hazard][%s] ===== HISTORY SAVE START ===== user_id=%r, scene_key=%r, labels=%r, title=%r, save_history=%s",
+                request_id,
+                user_id,
+                scene_key,
+                labels_text,
+                record_title,
+                data.save_history,
+            )
+            logger.info(
+                "[hazard][%s] history record payload: %s",
+                request_id,
+                json.dumps(record_payload, ensure_ascii=False, default=str),
+            )
+
+            record = RecognitionRecord(**record_payload)
             db.add(record)
+            logger.info("[hazard][%s] record added to session, committing db transaction", request_id)
             db.commit()
+            logger.info("[hazard][%s] db commit success, refreshing record", request_id)
             db.refresh(record)
+            logger.info(
+                "[hazard][%s] record saved successfully: id=%r, current_step=%r, created_at=%r, updated_at=%r",
+                request_id,
+                getattr(record, "id", None),
+                getattr(record, "current_step", None),
+                getattr(record, "created_at", None),
+                getattr(record, "updated_at", None),
+            )
         except Exception as e:
-            logger.error(f"识别失败: {e}", exc_info=True)
+            logger.error(
+                "[hazard][%s] 识别失败(保存记录阶段): error=%s, traceback=%s",
+                request_id,
+                e,
+                traceback.format_exc(),
+            )
             db.rollback()
+            logger.info("[hazard][%s] db rollback executed after record save failure", request_id)
             return {"statusCode": 500, "msg": f"识别失败: {str(e)}"}
 
         display_labels = _dedupe_list([item["label"] for item in detection_results])
 
+        response_data = {
+            "scene_name": scene_name,
+            "scene_display_name": scene_display_name,
+            "tag_type": tag_type,
+            "title": record_title,
+            "description": record_description,
+            "current_step": record_current_step,
+            "hazard_count": record_hazard_count,
+            "total_detections": len(detection_results),
+            "detections": detection_results,
+            "model_type": model_type,
+            "original_image": source_image_url,
+            "annotated_image": recognition_image_url,
+            "recognition_image_url": recognition_image_url,
+            "labels": labels_text,
+            "display_labels": display_labels,
+            "third_scenes": third_scene_names,
+            "element_hazards": element_hazards,
+            "record_id": getattr(record, "id", None),
+        }
+        logger.info(
+            "[hazard][%s] response prepared successfully: %s",
+            request_id,
+            json.dumps(response_data, ensure_ascii=False, default=str),
+        )
         return {
             "statusCode": 200,
             "msg": "识别成功",
-            "data": {
-                "scene_name": scene_name,
-                "scene_display_name": scene_display_name,
-                "tag_type": tag_type,
-                "title": title,
-                "total_detections": len(detection_results),
-                "detections": detection_results,
-                "model_type": model_type,
-                "original_image": source_image_url,
-                "annotated_image": result_url,
-                "labels": _remove_duplicate_labels([item["label"] for item in detection_results]),
-                "display_labels": display_labels,
-                "third_scenes": third_scene_names,
-                "element_hazards": element_hazards,
-            },
+            "data": response_data,
         }
 
     except Exception as e:
-        logger.error(f"处理失败: {e}", exc_info=True)
+        logger.error(
+            "[hazard][%s] 处理失败: error=%s, traceback=%s",
+            request_id,
+            e,
+            traceback.format_exc(),
+        )
         db.rollback()
+        logger.info("[hazard][%s] db rollback executed in outer exception handler", request_id)
         return {"statusCode": 500, "msg": f"处理失败: {str(e)}"}
 
 
@@ -459,14 +673,34 @@ async def save_step(
 
     user = request.state.user
     if not user:
+        logger.warning("[save_step] unauthorized request: request.state.user is empty")
         return {"statusCode": 401, "msg": "未授权"}
 
+    user_id = getattr(user, "user_id", None) or getattr(user, "id", None)
+    request_id = getattr(request.state, "request_id", None) or f"save-step-{int(time.time() * 1000)}"
+
     try:
+        logger.info(
+            "[save_step][%s] incoming payload: user_id=%r, record_id=%r, current_step=%r, path=%r, method=%r",
+            request_id,
+            user_id,
+            data.record_id,
+            data.current_step,
+            request.url.path,
+            request.method,
+        )
+        logger.info(
+            "[save_step][%s] updating RecognitionRecord: record_id=%r, user_id=%r",
+            request_id,
+            data.record_id,
+            user_id,
+        )
+
         affected = (
             db.query(RecognitionRecord)
             .filter(
                 RecognitionRecord.id == data.record_id,
-                RecognitionRecord.user_id == getattr(user, "user_id", None) or getattr(user, "id", None),
+                RecognitionRecord.user_id == user_id,
             )
             .update(
                 {
@@ -475,12 +709,26 @@ async def save_step(
                 }
             )
         )
+        logger.info("[save_step][%s] update result: affected_rows=%s", request_id, affected)
 
         if affected == 0:
+            logger.warning(
+                "[save_step][%s] record not found or no permission: record_id=%r, user_id=%r",
+                request_id,
+                data.record_id,
+                user_id,
+            )
             return {"statusCode": 404, "msg": "记录不存在"}
 
+        logger.info("[save_step][%s] committing db transaction", request_id)
         db.commit()
 
+        logger.info(
+            "[save_step][%s] save success: record_id=%r, current_step=%r",
+            request_id,
+            data.record_id,
+            data.current_step,
+        )
         return {
             "statusCode": 200,
             "msg": "保存成功",
@@ -491,8 +739,14 @@ async def save_step(
         }
 
     except Exception as e:
-        logger.error(f"[save_step] 异常: {e}")
+        logger.error(
+            "[save_step][%s] 异常: error=%s, traceback=%s",
+            request_id,
+            e,
+            traceback.format_exc(),
+        )
         db.rollback()
+        logger.info("[save_step][%s] db rollback executed after save_step failure", request_id)
         return {"statusCode": 500, "msg": f"保存失败: {str(e)}"}
 
 
@@ -586,5 +840,9 @@ async def _draw_boxes_and_watermark(
         return output.getvalue()
 
     except Exception as e:
-        logger.error(f"[_draw_boxes_and_watermark] 图片处理失败: {e}")
+        logger.error(
+            "[_draw_boxes_and_watermark] 图片处理失败: %s, traceback=%s",
+            e,
+            traceback.format_exc(),
+        )
         return image_bytes

+ 216 - 23
shudao-chat-py/routers/scene.py

@@ -1,4 +1,5 @@
 import json
+import logging
 import time
 from typing import Any, Optional
 
@@ -17,6 +18,7 @@ from models.scene import (
 )
 
 router = APIRouter()
+logger = logging.getLogger(__name__)
 
 
 def _get_user_code(user: Any) -> str:
@@ -222,36 +224,187 @@ async def get_third_scene_example_image(
 async def get_history_recognition_record(
     request: Request, db: Session = Depends(get_db)
 ):
-    user = request.state.user
+    # 让日志更容易被全局日志系统捕获:同时输出 INFO / WARNING / DEBUG 关键点
+    logger.warning(
+        "=== get_history_recognition_record ENTER === path=%s method=%s client=%s",
+        request.url.path,
+        request.method,
+        request.client.host if request.client else None,
+    )
+
+    request_id = getattr(request.state, "request_id", None) or getattr(
+        request.state, "trace_id", None
+    )
+    logger.warning(
+        "get_history_recognition_record request context: request_id=%s state_keys=%s",
+        request_id,
+        list(getattr(request.state, "__dict__", {}).keys()),
+    )
+
+    user = getattr(request.state, "user", None)
     if not user:
+        logger.warning(
+            "get_history_recognition_record unauthorized: request_id=%s missing request.state.user headers=%s",
+            request_id,
+            {
+                "authorization": request.headers.get("authorization"),
+                "cookie": request.headers.get("cookie"),
+            },
+        )
         return {"statusCode": 401, "msg": "未授权"}
 
     user_code = _get_user_code(user)
-    records = (
-        db.query(RecognitionRecord)
-        .filter(RecognitionRecord.user_id == user_code, RecognitionRecord.is_deleted == 0)
-        .order_by(RecognitionRecord.updated_at.desc())
-        .all()
+    user_id = getattr(user, "user_id", None)
+    account = getattr(user, "account", None)
+    user_snapshot = {
+        "userCode": getattr(user, "userCode", None),
+        "user_code": getattr(user, "user_code", None),
+        "user_id": user_id,
+        "account": account,
+        "type": type(user).__name__,
+        "raw_repr": repr(user),
+    }
+    logger.warning(
+        "get_history_recognition_record user resolved: request_id=%s user_code=%s user_id=%s account=%s user_snapshot=%s",
+        request_id,
+        user_code,
+        user_id,
+        account,
+        user_snapshot,
     )
 
-    total = (
-        db.query(RecognitionRecord)
-        .filter(RecognitionRecord.user_id == user_code, RecognitionRecord.is_deleted == 0)
-        .count()
+    query_filter = [
+        RecognitionRecord.user_id == user_id,
+        RecognitionRecord.is_deleted == 0,
+    ]
+    logger.warning(
+        "get_history_recognition_record query prepared: request_id=%s filters=user_id==%s,is_deleted==0 order_by=updated_at.desc()",
+        request_id,
+        user_id,
+    )
+    logger.warning(
+        "get_history_recognition_record query detail: request_id=%s sql_filter_user_id_value=%r sql_filter_is_deleted=0",
+        request_id,
+        user_id,
+    )
+    logger.warning(
+        "get_history_recognition_record query mismatch check: request_id=%s record.user_id_column_against=user_id(%r) user_code_attr=%r",
+        request_id,
+        user_id,
+        user_code,
     )
 
-    return {
-        "statusCode": 200,
-        "msg": "success",
-        "data": [
-            {
-                **_build_record_view(record),
-                "created_at": record.created_at,
-            }
-            for record in records
-        ],
-        "total": total,
-    }
+    try:
+        total = db.query(RecognitionRecord).filter(*query_filter).count()
+        logger.warning(
+            "get_history_recognition_record count done: request_id=%s user_id=%s total=%s",
+            request_id,
+            user_id,
+            total,
+        )
+
+        records = (
+            db.query(RecognitionRecord)
+            .filter(*query_filter)
+            .order_by(RecognitionRecord.updated_at.desc())
+            .all()
+        )
+        logger.warning(
+            "get_history_recognition_record records loaded: request_id=%s user_id=%s loaded=%s record_ids=%s",
+            request_id,
+            user_id,
+            len(records),
+            [record.id for record in records[:20]],
+        )
+        logger.warning(
+            "get_history_recognition_record records debug sample: request_id=%s sample=%s",
+            request_id,
+            [
+                {
+                    "id": r.id,
+                    "user_id": r.user_id,
+                    "scene_type": r.scene_type,
+                    "created_at": r.created_at,
+                    "updated_at": r.updated_at,
+                    "is_deleted": r.is_deleted,
+                }
+                for r in records[:3]
+            ],
+        )
+
+        data = []
+        for index, record in enumerate(records):
+            try:
+                record_view = _build_record_view(record)
+                logger.warning(
+                    "get_history_recognition_record record built: request_id=%s index=%s record_id=%s scene_type=%s tag_type=%s labels=%s hazard_count=%s effect_evaluation=%s created_at=%s updated_at=%s",
+                    request_id,
+                    index,
+                    record.id,
+                    record.scene_type,
+                    record_view.get("tag_type"),
+                    record_view.get("labels"),
+                    len(record_view.get("hazard_details") or []),
+                    record.effect_evaluation,
+                    record.created_at,
+                    record.updated_at,
+                )
+                data.append(
+                    {
+                        **record_view,
+                        "created_at": record.created_at,
+                    }
+                )
+            except Exception as record_error:
+                logger.exception(
+                    "get_history_recognition_record record build failed: request_id=%s index=%s record_id=%s error=%s",
+                    request_id,
+                    index,
+                    getattr(record, "id", None),
+                    record_error,
+                )
+
+        logger.warning(
+            "get_history_recognition_record success: request_id=%s user_id=%s total=%s returned=%s",
+            request_id,
+            user_id,
+            total,
+            len(data),
+        )
+        logger.warning(
+            "get_history_recognition_record exit summary: request_id=%s user_code=%s user_id=%s account=%s total=%s returned=%s",
+            request_id,
+            user_code,
+            user_id,
+            account,
+            total,
+            len(data),
+        )
+        logger.warning("=== get_history_recognition_record EXIT OK === request_id=%s", request_id)
+
+        return {
+            "statusCode": 200,
+            "msg": "success",
+            "data": data,
+            "total": total,
+        }
+    except Exception as exc:
+        logger.exception(
+            "get_history_recognition_record failed: request_id=%s user_code=%s user_id=%s account=%s error=%s",
+            request_id,
+            user_code,
+            user_id,
+            account,
+            exc,
+        )
+        logger.error(
+            "=== get_history_recognition_record EXIT ERR === request_id=%s user_code=%s user_id=%s account=%s",
+            request_id,
+            user_code,
+            user_id,
+            account,
+        )
+        return {"statusCode": 500, "msg": "获取历史记录失败"}
 
 
 @router.get("/get_recognition_record_detail")
@@ -261,7 +414,14 @@ async def get_recognition_record_detail(
     db: Session = Depends(get_db),
 ):
     record_id = _resolve_record_id(recognition_id, recognition_record_id)
+    logger.info(
+        "get_recognition_record_detail start: recognition_id=%s recognition_record_id=%s resolved_record_id=%s",
+        recognition_id,
+        recognition_record_id,
+        record_id,
+    )
     if not record_id:
+        logger.warning("get_recognition_record_detail invalid request: missing record id")
         return {"statusCode": 422, "msg": "recognition_id 不能为空"}
 
     record = (
@@ -270,9 +430,16 @@ async def get_recognition_record_detail(
         .first()
     )
     if not record:
+        logger.warning("get_recognition_record_detail not found: record_id=%s", record_id)
         return {"statusCode": 404, "msg": "记录不存在"}
 
     record_view = _build_record_view(record)
+    logger.info(
+        "get_recognition_record_detail success: record_id=%s user_id=%s scene_type=%s",
+        record.id,
+        record.user_id,
+        record.scene_type,
+    )
     return {
         "statusCode": 200,
         "msg": "success",
@@ -313,13 +480,20 @@ async def delete_recognition_record(
 ):
     user = request.state.user
     if not user:
+        logger.warning("delete_recognition_record unauthorized: missing request.state.user")
         return {"statusCode": 401, "msg": "未授权"}
 
     record_id = _resolve_record_id(data.recognition_id, data.recognition_record_id)
+    logger.info(
+        "delete_recognition_record start: user_code=%s record_id=%s",
+        _get_user_code(user),
+        record_id,
+    )
     if not record_id:
+        logger.warning("delete_recognition_record invalid request: missing record id")
         return {"statusCode": 422, "msg": "recognition_id 不能为空"}
 
-    (
+    affected_rows = (
         db.query(RecognitionRecord)
         .filter(
             RecognitionRecord.id == record_id,
@@ -328,6 +502,12 @@ async def delete_recognition_record(
         .update({"is_deleted": 1, "deleted_at": int(time.time())})
     )
     db.commit()
+    logger.info(
+        "delete_recognition_record success: user_code=%s record_id=%s affected_rows=%s",
+        _get_user_code(user),
+        record_id,
+        affected_rows,
+    )
 
     return {"statusCode": 200, "msg": "删除成功"}
 
@@ -342,6 +522,14 @@ class EvaluationRequest(BaseModel):
 
 @router.post("/submit_evaluation")
 async def submit_evaluation(data: EvaluationRequest, db: Session = Depends(get_db)):
+    logger.info(
+        "submit_evaluation start: id=%s scene_match=%s tip_accuracy=%s effect_evaluation=%s has_user_remark=%s",
+        data.id,
+        data.scene_match,
+        data.tip_accuracy,
+        data.effect_evaluation,
+        data.user_remark is not None,
+    )
     record = (
         db.query(RecognitionRecord)
         .filter(RecognitionRecord.id == data.id, RecognitionRecord.is_deleted == 0)
@@ -349,6 +537,7 @@ async def submit_evaluation(data: EvaluationRequest, db: Session = Depends(get_d
     )
 
     if not record:
+        logger.warning("submit_evaluation record not found: id=%s", data.id)
         return {"statusCode": 404, "msg": "记录不存在"}
 
     if data.scene_match is not None:
@@ -362,6 +551,7 @@ async def submit_evaluation(data: EvaluationRequest, db: Session = Depends(get_d
 
     record.updated_at = int(time.time())
     db.commit()
+    logger.info("submit_evaluation success: id=%s", data.id)
 
     return {"statusCode": 200, "msg": "success"}
 
@@ -372,8 +562,11 @@ async def get_latest_recognition_record(
 ):
     user = request.state.user
     if not user:
+        logger.warning("get_latest_recognition_record unauthorized: missing request.state.user")
         return {"statusCode": 401, "msg": "未授权"}
 
+    user_code = _get_user_code(user)
+    logger.info("get_latest_recognition_record start: user_code=%s", user_code)
     record = (
         db.query(RecognitionRecord)
         .filter(